I've been trying to implement rate limiting for some HTTP POST requests on my website. It works great, except for one detail: the expiration of my entry in my stick-table is always reset to 30 seconds, which means that if the client mistakenly makes a request 29 seconds after being blocked, it will be blocked, again, for 30 seconds.

Here's my config, stripped down to the bare minimal for ease of reading:

frontend http-in
        mode    http
        bind *:80

        ### Request limiting
        # Declare stick table
        stick-table type string size 100k expire 30s store gpc0

        # Inspect layer 7
        tcp-request inspect-delay 15s

        # Declare ACLs
        acl source_is_abuser sc0_get_gpc0 gt 0

        tcp-request content track-sc0 req.cook(frontend) if !source_is_abuser
        ### End Request limiting

        use_backend rate-limit if source_is_abuser

        default_backend mybackend

backend mybackend
        mode   http

        stick-table type string size 100k expire 30s store http_req_rate(30s)
        tcp-request content track-sc1 req.cook(frontend) if METH_POST

        acl post_req_rate_abuse sc1_http_req_rate gt 30
        acl mark_as_abuser sc0_inc_gpc0 gt 0

        tcp-request content accept if post_req_rate_abuse mark_as_abuser

        server myLocalhost

backend rate-limit
        mode http
        errorfile 503 /usr/local/etc/haproxy/rate-limit.http

With this config, as soon as a client makes more than 1 request per second over 30 seconds, this client is marked as an abuser by mybackend. The following request are then, as expected, blocked by the http-in frontend.

However, every time the currently marked source_is_abuser client sends a request, the expiration counter of http-in 's stick-table is reset to 30 seconds. I would expect the expiration counter to keep going down, since the connection is supposedly only tracked when !source_is_abuser.

Any insight into what I am doing wrong?

  • I can't actually provide an answer, because in all honesty the whole gpc0, scX_YYY_gpc0 thing makes my head hurt, but it seems to me you're not just counting POSTs here (acl post_req_rate_abuse sc1_http_req_rate gt 30') and are accepting when you should be blocking (tcp-request content accept if post_req_rate_abuse mark_as_abuser`. Then again, maybe I'm not understanding how this whole thing works. Have you read these two articles? – GregL Aug 20 '15 at 18:54
  • @GregL: thanks for the 2 links. I did read them indeed. The POST tracking happens in mybackend. What the configuration does, however, is that once you've abused POST, any type of request (GET,HEAD,POST,etc) from the same client gets blocked. This wasn't on purpose, although I could see this functionality being useful. – liquidity Aug 21 '15 at 16:54
  • Oh, that case, just change use_backend rate-limit if source_is_abuser to use_backend rate-limit if source_is_abuser METH_POST, so that they only get sent to the rate-limit backend if they're an abuser AND they're POSTing. – GregL Aug 21 '15 at 16:58
  • @GregL That unfortunately doesn't solve my issue. The issue is that "the expiration of my entry in my stick-table is always reset to 30 seconds". – liquidity Aug 21 '15 at 19:50

To check the counter without updating its value, you can use table_http_req_rate. In your case you can put the below config in your frontend, so that you can deny the request before reaching the stick-table.

http-request deny if { key,table_http_req_rate(name_of_your_table) gt 30 }

More reference: Stick table doc

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.