Linux SNAT with per-connection source address from IP pool

When doing NAT with pool of addresses to choose from (instead of masquerading) Linux insists on always using same IP from pool for particular source IP. Often this is preferred, but not always. To workaround we need to patch kernel a bit.

Code to tamper with is located in net/netfilter/nf_nat_core.c which also has explanation for this behavior. There's some discussion about this on https://patchwork.ozlabs.org/patch/699934/ which also includes patch we need to fix this.

Original nf_nat_core.c
283         /* Hashing source and destination IPs gives a fairly even
284          * spread in practice (if there are a small number of IPs
285          * involved, there usually aren't that many connections
286          * anyway).  The consistency means that servers see the same
287          * client coming from the same IP (some Internet Banking sites
288          * like this), even across reboots.
289          */
290         j = jhash2((u32 *)&tuple->src.u3, sizeof(tuple->src.u3) / sizeof(u32),
291                    range->flags & NF_NAT_RANGE_PERSISTENT ?
292                         0 : (__force u32)tuple->dst.u3.all[max] ^ zone->id);

All we need to do is replace line 290 with this and remove lines 291 and 292.
290         j = prandom_u32();

That's it. Now every new connection will get randomized IP from specified pool. My use case for this? Quite unique I guess. This was my first attempt to work around limitation of Linux IPSEC VTI interfaces not supporting more than one client connecting using same IP address. Situation which rather common with 4G LTE networks and CGNAT's masquerading thousands of users users behind single public IPv4 address.

Here's funky rules I came up with. First one redirect all incoming UDP traffic to Strongswan listening on port udp/4500. Second NATs connection to random IP address from 254.0.0.0/8 subnet. Yes, this is crazy. Yes, this actually works. Yes, it is useful. No, it's not supported by anyone and most certainly isn't how these components were intended to be used.

iptables -t nat -I PREROUTING -p udp -d 192.0.100.1 -j REDIRECT --to-port 4500
iptables -t nat -I INPUT -p udp -d 192.0.100.1 -j SNAT --to-source 254.0.0.0-254.255.255.255

On client side my settings are at least as odd. I added following rule for outbound traffic. This performs DNAT on outbound Strongswan packets. Paired with rightikeport=4500 on ipsec.conf and port=0 + port_nat_t=0 on charon.conf connections over Internet use random UDP source port and random UDP destination port. Yet it's still standard IKEv2 NAT-T traffic to both client and server processes.

iptables -t nat -I OUTPUT --dst 192.0.100.1 -p udp --dport 4500 -j DNAT --to-destination :1-65535 --random

Have fun. :)

Comments