iptables not working with "x-forwarded-for" (behind Cloudflare)

I have a webserver (Ubuntu with Apache) running behind Cloudflare. I want to block a user using the iptables. Here I want to implement the string match extension of iptables and drop the connection if x-forwarded-for matched. I am adding the rule with following command:

iptables -I INPUT -m string --string "x-forwarded-for: 216.244.66.205" --algo bm --to 65535 -j DROP

After adding this rule the client is still able to hit the server.

I know about Cloudflare's specific header for client IP that is cf-connecting-ip. If I add a rule with the following command it works as expected. Here is the rule which works fine:

iptables -I INPUT -m string --string "cf-connecting-ip: 216.244.66.205" --algo bm --to 65535 -j DROP

Why I want to use XFF instead of cf-connecting-ip:

I have a separate load-balancer (haproxy) and some of the domains are running through this instead of Cloudflare. That is why I want to use XFF because cf-connecting-ip is specific to Cloudflare and XFF is supported by both.

The traffic from Cloudflare to the Web server is HTTP (port 80) so http data is visible to the iptables.

I can see Cloudflare is properly attaching both cf-connecting-ip and XFF headers. Here is the output of tcpdump among multiple requests:

tcpdump -A -s 65535 'tcp port 80' | grep 216.244.66.205
x-forwarded-for: 216.244.66.205
cf-connecting-ip: 216.244.66.205
x-forwarded-for: 216.244.66.205
cf-connecting-ip: 216.244.66.205
x-forwarded-for: 216.244.66.205
cf-connecting-ip: 216.244.66.205
x-forwarded-for: 216.244.66.205
cf-connecting-ip: 216.244.66.205

Somehow the iptables are able to detect the cf-connecting-ip but not x-forwarded-for.

What am i doing wrong here?


Solution 1:

The string match operates on the payload of single IP packet.

Therefore one possible reason is that the location of x-forwarded-for: header happens to be on a boundary of two TCP segments. Therefore first IP packet might contain x-forw and second IP packet arded-for:. Then the string match module doesn't match either packet.

To check if this is the reason, look into the different IP packet payloads belonging to the connection and see how the TCP segment is split into IP packets.

It is best to implement block features like this on HTTP level.