Esta solução requer pelo menos haproxy 1.6.
Primeiro, adicione o seguinte ao frontend:
http-request set-header X-DOS-Protect %[src];%[req.fhdr(host)]%[capture.req.uri]
Em seguida, adicione o seguinte ao back-end:
stick-table type integer size 1m expire 5m store http_req_rate(5m)
tcp-request inspect-delay 5s
tcp-request content track-sc0 req.fhdr(X-DOS-Protect),crc32(1) if HTTP
http-request tarpit if { sc0_http_req_rate gt 3000 }
Não consegui encontrar uma maneira de fazer o acompanhamento no frontend, pois não encontrei uma maneira de aplicar um conversor nessa string concatenada que compõe o cabeçalho X-DOS-Protect.
Estou aplicando a função hash para garantir que você não armazene uma cadeia enorme na tabela, já que ela poderia facilmente levar a uma negação de serviço. Se você acha que essa função hash não é adequada para você devido a muitas colisões possíveis, você também pode aumentá-la aplicando crc32 em cada um dos componentes concatenados (e, claro, removendo-a ao armazenar os dados e alternando para um maior armazenamento stick-table), assim:
http-request set-header X-DOS-Protect %[src,crc32(1)];%[req.fhdr(host),crc32(1)]%[capture.req.uri,crc32(1)]
stick-table type string len 30 size 1m expire 5m store http_req_rate(5m)
tcp-request inspect-delay 5s
tcp-request content track-sc0 req.fhdr(X-DOS-Protect) if HTTP
http-request tarpit if { sc0_http_req_rate gt 3000 }
Por favor, note que esta última solução usará mais de 7 vezes mais memória do que a primeira, para cada entrada na tabela. É claro que o risco de colisão também seria menor.