Closed CodeZombieCH closed 2 years ago
Because it came up recently on twitter, here are a few additional notes:
I suppose the next step would be prototyping some nft rules that do what Dave outlined in the twitter thread.
More insight from Dave: https://twitter.com/dave_universetf/status/1298100264927289349
I guess we just wait another few days until he explains how things ought to really work in Linux :)
This comes up as 2nd hit when googling for nftables hairpinning, so I thought I share my experience. I'm not running router7 though.
For hairpinning NAT of a portforward from e.g. TCP port 16085 on the outer interface $out_if (using the address $out_ip) to local 192.168.122.2:22 on $lan_if (with the $lan_net IP range), you need to add
iif $lan_if ip daddr $out_ip tcp dport 16085 dnat to 192.168.122.2:22
(this rewrites the destination)iif $lan_if oif $lan_if ip daddr 192.168.122.2 tcp dport 22 accept
(this allows forwardng the NATted connection)ip saddr $lan_net ip daddr 192.168.122.2 tcp dport 22 counter masquerade
(this rewrites the source)And, if you want to make local connections from the router to the service work:
ip daddr $out_ip tcp dport 16085 dnat to 192.168.122.2:22
(this rewrites the "outgoing" connection)The rules might be a bit narrow, but I go down the careful route (pun intended) with this to avoid accidental NATting.
- a forward filter rule
iif $lan_if oif $lan_if ip daddr 192.168.122.2 tcp dport 22 accept
(this allows forwardng the NATted connection)
- (probably another output filter rule, but I generally allow outgoing connections, so I have this covered)
The forward filter rules can apparently be replaced by one universal ct status dnat counter accept
.
Same goes for the postrouting NAT rules - one iifname $lan_if oifname $lan_if ct status dnat counter masquerade
suffices.
Here's a full ruleset, configured as above but reduced to a handful of rules by using a map of port forwards (requires nftables >= 0.9.4).
https://gist.github.com/neingeist/c97b488f2511bb5ca07c8a07213eccbe
Thanks for sharing this! I’m pretty busy the next few days, but will take a closer look and try it out next week hopefully :)
There's no rush ;)
Yeah, on router7 indeed only 2 modifications are needed: the prerouting
dnat
rule needs to be expressed without using iifname "uplink0"
, and the postrouting
needs a masquerade
rule for lan0→lan0 traffic:
# nft add rule ip nat prerouting ip daddr 212.51.xx.0/24 tcp dport 8096 dnat to 10.0.0.252:8096
# nft add rule ip nat postrouting iifname "lan0" oifname "lan0" ct status dnat counter masquerade
I think we can express the rule without using hard-coded addresses (which means they would need to change whenever the public IP address changes) by using ip daddr != 127.0.0.0/8 ip daddr != 10.0.0.0/24 fib daddr type local
, which would match all locally-configured IP addresses (fib daddr type local
) except for the loopback and LAN IP addresses.
Unfortunately, the router7 kernel is built without CONFIG_NFT_FIB_IPV4
. I’ll enable that when I get a chance.
Yep, as expected, these rules work, too:
# nft add rule ip nat prerouting ip daddr != 127.0.0.0/8 ip daddr != 10.0.0.0/24 fib daddr type local tcp dport 8096 dnat to 10.0.0.252:8096
# nft add rule ip nat postrouting iifname "lan0" oifname "lan0" ct status dnat counter masquerade
Next up is changing the router7 netconfig to apply these rules.
I think we can express the rule without using hard-coded addresses (which means they would need to change whenever the public IP address changes) by using
ip daddr != 127.0.0.0/8 ip daddr != 10.0.0.0/24 fib daddr type local
, which would match all locally-configured IP addresses (fib daddr type local
) except for the loopback and LAN IP addresses.
Nice, I didn't know about that one!
I noticed that I'm unable to connect to a server inside my local network using the router7 public IP and the forwarded port.
Example Local machine: 192.168.0.100 Public IP: 12.34.56.78 Port forwarding rule: :22 -> 192.168.0.100:22
Result: connection refused
Connecting from outside of router7 works perfectly fine.
To ensure we can connect from the local network using the public IP, it would be nice if support for Hairpinning (see Hairpinning and Hairpin-NAT) could be added. This could be achieved by adding additional nftables rules.
(thanks for @stapelberg for helping me investigating this issue)