I observed that netfilter changes the source port when a connection is established in the conntrack module. I need to prevent this behavior.
Here is what I have done to reproduce my problem:
- I create a netfilter rule that will perform DNAT from port 2002 to 2003
sudo iptables -w -t nat -A OUTPUT -s 192.168.30.3 -d 192.168.30.1 -p udp --sport 2001 --dport 2002 -j DNAT --to-destination :2003
- I then create a conntrack entry to simulate a connection from 192.168.30.1:2001 (My computer) to 192.168.30.1:2003
sudo /sbin/conntrack -I -s 192.168.30.1 -d 192.168.30.3 -p udp --sport 2003 --dport 2001 --timeout 100000
- Eventually, I perform a connection to 192.168.30.1:2002 from my computer with source port 2001:
sudo nc -u -p 2001 192.168.30.1 2002
Due to the netfilter DNAT rule, I expected an output packet with destination port 2003 and source port 2001. However, on wireshark I observed the source port changed to a random number. I guess this is because my computer considers that there is an existing connection on port 2001 (due to the conntrack entry) and then prevents the source port to be 2001 (right?). But I don't want this behavior? How can I force the use of the port number 2001?
NAT changes source port to reduce the risk of a port conflict, what should happen if that sport 2002 is already busy on the NAT machine?
If you have specific requirements for specific ports then you could add specific
SNAT
rules for that, but again, what if multiple internal clients try to use the same source port?Here we should go back and acknowledge that NAT is a hack that was created to reduce the issue of lack of public IP-addresses. The real fix here is for everyone to have non NAT public IPs.
When talking about NAT these days we most often mean private IP-addresses behind one IP, In these cases it is actually NAPT a Similar question related to that, I was thinking about
MASQUERADE
target and notDNAT
For the DNAT to work (in the sense that, for the program on to be able to recognize the replies), "reverse NAT" that changes the source port of the replying traffics from
192.168.30.1
(to192.168.30.3:2001
) from2003
to2002
will need to be performed.However, when there are traffics coming from
192.168.30.1:2003
to192.168.30.3:2001
that from conntrack's point of view are not a consequence of the DNAT (because as per conntrack entry created, the host is not the one that initiated the connection), the reverse NAT will be inappropriate.Therefore, netfilter is "forced" to also perform SNAT for the traffics match with the DNAT rule, so that it can differentiate the replying traffics (that is also from
192.168.30.1:2003
) by the destination192.168.30.3:$random
.I assume netfilter will either perform reverse NAT for DNAT (which is an SNAT) before reverse NAT for SNAT (which is a DNAT), or manage to use the destination before the reverse NAT for SNAT (i.e.
192.168.30.3:$random
) as matching for the reverse NAT for DNAT, otherwise the forced SNAT will be pointless. (In the non-reversal case, however, neither of these is true AFAIK: DNAT will be performed in PREROUTING before SNAT in INPUT, and destination matching in the SNAT rule, if any, will use the value resulted in the DNAT)The thing is, the story above / the "problem" in your question hardly make any sense in reality. Take a two-host wireguard VPN as example: suppose you want to have
Endpoint=
set on both hosts (so that either of them can initiate the communication) and do not want the values to be "updated" unexpectedly because of the forced SNAT (assuming that could actually be triggered), what you should do is simply an "always-on" SNAT that "complements" DNAT / is equivalent to the reserve NAT:which is normally not necessary in the client-server model because of the automatic reverse NAT for the DNAT.
P.S. You are still not supposed to reach
192.168.30.1:2003
by192.168.30.1:2003
though, otherwise the forced source NAT will also occur if you reach it again by192.168.30.1:2002
before the conntrack entry of the former is dropped. The additional SNAT rule in INPUT should not cause you extra trouble either.You can set two flows that would normally collide in the conntrack lookup table (thus usually triggering a source port rewrite on the new flow to avoid the collision) to be in different conntrack zones. This additional zone property makes conntrack not match/collide with an existing flow in a different conntrack zone: no source port rewriting will happen.
For your specific example, here's a specific rule that will prevent the collision and thus prevent source port rewriting:
Normally depending on the use case, a more sensible selector is used. It's often used in the PREROUTING chain with an incoming interface as selector when routing, and often associated with a mark value so routing can be affected too.
The original use case that made this option appear is when conntrack on the same network stack (no additional network namespace) with a complex routing setup (eg: routing between 4 different private LANs using same IP addresses. eg between 192.168.1.0/24 eth0 <-> eth1 10.1.0.0/24, and again 192.168.1.0/24 eth2 <-> 10.1.0.0/24 eth3) can see two unrelated flows with same addresses/ports. As Netfilter and conntrack know nothing about routing (the conntrack lookup table includes only addresses) they must be taught to consider these flows separately by adding a zone property manually tied to the routing topology in the conntrack lookup table.
(Here's an LWN link when the feature was originally proposed.)