4

i need to redirect all UDP packets with destination port 15000 to port 15001 if the packet contains for example the string test. i have these two simple rules:

iptables -t nat -A PREROUTING -i eth0 -p udp --dport 15000 -m string --string 'test' --algo bm -j LOG --log-prefix='[netfilter] '
iptables -t nat -A PREROUTING -i eth0 -p udp --dport 15000 -m string --string 'test' --algo bm -j REDIRECT --to-ports 15001

The strange behaviors:

  • if the first packet contains test string, redirection is done for all packets of the connection;
  • if the first packet of the connection doesn't contains test, redirection is never done even if a subsequent packet contains test

However all packets matching rule are correctly logged.

i tried to add also the track information to the rule:

-m state --state NEW,ESTABLISHED

but the behaviour is the same. Some ideas?

This is the complete iptables ruleset:

filter table:

Chain INPUT (policy ACCEPT)
target     prot opt source               destination         

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination         

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination  

nat table:

Chain PREROUTING (policy ACCEPT)
 target     prot opt source               destination         
 LOG        udp  --  anywhere             anywhere             udp dpt:15000 STRING match  "test" ALGO name bm TO 65535 LOG level warning prefix "[netfilter] "
 REDIRECT   udp  --  anywhere             anywhere             udp dpt:15000 STRING match  "test" ALGO name bm TO 65535 redir ports 15001

 Chain INPUT (policy ACCEPT)
 target     prot opt source               destination         

 Chain OUTPUT (policy ACCEPT)
 target     prot opt source               destination         

 Chain POSTROUTING (policy ACCEPT)

mangle table:

Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination         

Chain INPUT (policy ACCEPT)
target     prot opt source               destination         

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination         

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination         

Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination  

raw table:

Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination         

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination
MirkoBanchi
  • 172
  • 2
  • 7

3 Answers3

5

This is caused by the fact that iptables applies connection tracking on PREROUTING chain. Whenever a new connection is made, iptables will consult the conntrack cache. If a match was found, no rule will be applied from nat table.

If you want to disable change this behavior, look at NOTRACK target in raw table.

Please, note that this applies even for UDP (which is a connection-less protocol). The first packet is considered as opening connection NEW and the other is a reply ESTABLISHED.

I found a related post on serverfault.

Khaled
  • 36,533
  • 8
  • 72
  • 99
  • I suspected this, but by adding `ESTABLISHED` state to the rule, should iptables bypass cache? If not, is there a way to force this? – MirkoBanchi Dec 06 '15 at 14:13
  • 1
    @MirkoBanchi: Look at the post I am referring in my answer – Khaled Dec 06 '15 at 14:15
  • target `NOTRACK` doesn't work for me, connection is still tracked – MirkoBanchi Dec 06 '15 at 14:32
  • Maybe, to be tested, to get the best of both worlds, instead of `-j NOTRACK` / `-j CT --notrack`, the newer `-j CT --zone` could be set (still in the raw table): so the packet flows can still be tracked, nated etc, but all having test will have their separate conntrack zone, thus hopefully put in their own flow – A.B Aug 03 '18 at 21:02
5

nat table rules always work only for first packet in connection. Subsequent packets of same connection never traverse nat rule list and only supported by conntrack code

As UDP is connectionless in nature, "connection" here is defined simply by addresses, ports and timeout. So, if second UDP packet with same source port and address and same destination port and address arrives within the timeout, Linux believes it belongs to established "connection" and doensn't evaluate nat rule table for it at all, reusing verdict issued for previous packet.

See here: http://www.netfilter.org/documentation/HOWTO/netfilter-hacking-HOWTO-3.html

Nikita Kipriyanov
  • 10,947
  • 2
  • 24
  • 45
  • 1
    Seems the only way to limit caching is by tuning `nf_conntrack_udp_timeout` parameter to 1 through `sysctl`. The bad thing is that this is a global parameter and affects all UDP cached "connections" – MirkoBanchi Dec 06 '15 at 14:40
  • 1
    In conntrack module there is ctexpire parameter, which afaik is designed exactly to do per-connection tuning. See iptables -m conntrack --help. There is also conntrack-tools userland package, which is used to synchronize firewalls in cluster setups, but I didn't investigated what else it could do. – Nikita Kipriyanov Dec 06 '15 at 15:05
2

iptables with conntrack zones

The conntrack zones feature allows to have two identical conntrack 5-uples (or part of) be considered distinct by their zone property. This is normally probably used with complex policy based routing handling identical IPs but flowing through different paths (routes) to prevent conntrack to merge unrelated flows from the distinct paths.

Here it can be used to solve this problem: consider UDP packets not having test and packet having test to be part of two different origin zones (by using CT --zone-orig): the normal zone and the redirected zone. Each origin zone will allow conntrack entries in the NEW state to not collide and be considered separate. Consider it as if there were two nat tables in the PREROUTING hook: instead of evaluation of the nat rules happening once for the first packet of the flow, it can happen once for the first normal packet and once for the first test packet.

While it's not really needed, to avoid even more duplication of rules, I'll set a mark and reuse it later to simplify.

iptables -t raw -A PREROUTING -i eth0 -p udp --dport 15000 -m string --string 'test' --algo bm -j MARK --set-mark 1
iptables -t raw -A PREROUTING -m mark --mark 1 -j CT --zone-orig 1   
iptables -t nat -A PREROUTING -m mark --mark 1 -j LOG --log-prefix='[netfilter] '
iptables -t nat -A PREROUTING -p udp -m mark --mark 1 -j REDIRECT --to-ports 15001

Testing (server is at IP 10.0.3.66). Chronologically, answers typed after the queries (answer1 from term1 and answer2 from term2):

serverterm1$ socat udp4-listen:15000,reuseaddr,fork -
query1normal
query3normal
answer1

serverterm2$ socat udp4-listen:15001,reuseaddr,fork -
query2test
query4test
answer2

client$ socat udp4:10.0.3.66:15000 -
query1normal
query2test
query3normal
query4test
answer1
answer2

serverterm3# conntrack -E -p udp --orig-port-dst 15000
    [NEW] udp      17 30 src=10.0.3.1 dst=10.0.3.66 sport=33150 dport=15000 [UNREPLIED] src=10.0.3.66 dst=10.0.3.1 sport=15000 dport=33150
    [NEW] udp      17 30 src=10.0.3.1 dst=10.0.3.66 sport=33150 dport=15000 zone-orig=1 [UNREPLIED] src=10.0.3.66 dst=10.0.3.1 sport=15001 dport=33150
 [UPDATE] udp      17 30 src=10.0.3.1 dst=10.0.3.66 sport=33150 dport=15000 src=10.0.3.66 dst=10.0.3.1 sport=15000 dport=33150
 [UPDATE] udp      17 30 src=10.0.3.1 dst=10.0.3.66 sport=33150 dport=15000 zone-orig=1 src=10.0.3.66 dst=10.0.3.1 sport=15001 dport=33150

Caveat: the string match is evaluated for every packet of the flow. Consider if possible using u32 instead for less overhead.

A.B
  • 11,090
  • 2
  • 24
  • 45