I am working on setting up a number of EC2 instances with IPv6-only networking.
A few of these servers require occasional access to IPv4-only resources controlled by third-parties.
Amazon has a blog post from February 2022 about using NAT Gateway for this. I don't want to pay $$$ for the "NAT Gateway" that will be rarely used, so I wanted to set up my own NAT gateway running on a t4g.nano
instance. Amazon has documentation about how to do this as well, which uses iptables
commands on an instance running Amazon Linux.
We have all of our servers running Ubuntu, and my boss does not want us to bring in a different package management system he has to learn, so we would like to use Ubuntu 22.04 to set up NAT. Ubuntu 22.04 ships with nftables
, and I would like to use that.
I found several guides for nftables NAT online, but none of them mention IPv6.
Putting bits and pieces together from those guides, I ended up with this:
Set net.ipv4.ip_forward = 1
and net.ipv6.conf.all.forwarding = 1
in sysctl.conf
(actually in /etc/sysctl.d/forwarding.conf
)
Create dual-stack NAT rules
nft add table inet nat
nft 'add chain inet nat postrouting { type nat hook postrouting priority 100 ; }'
nft add rule inet nat postrouting oifname ens5 masquerade
nft 'add chain inet nat prerouting { type nat hook prerouting priority -100 ; }'
# Debug output on
nft add rule inet nat prerouting meta nftrace set 1
nft add rule inet nat postrouting meta nftrace set 1
Verify rules exist (nft list table inet nat
)
table inet nat {
chain postrouting {
type nat hook postrouting priority srcnat; policy accept;
oifname "ens5" masquerade
meta nftrace set 1
}
chain prerouting {
type nat hook prerouting priority dstnat; policy accept;
meta nftrace set 1
}
}
Generate some traffic on an IPv6-only machine
curl -vv http://ip4only.me/api/
* Trying 64:ff9b::4275:27c9:80...
* Trying 66.117.39.201:80...
* Immediate connect fail for 66.117.39.201: Network is unreachable
Use tcpdump
and nft monitor
to verify that the traffic to the DNS64-encoded address is reaching this machine
# tcpdump -i ens5 -n port 80
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on ens5, link-type EN10MB (Ethernet), snapshot length 262144 bytes
05:10:52.348952 IP6 2600:1f18:xxxx:xxxx:b428:1047:7d73:9484.55882 > 64:ff9b::4275:27c9.80: Flags [S], seq 2015291953, win 62587, options [mss 8941,sackOK,TS val 3259538098 ecr 0,nop,wscale 7], length 0
05:10:52.349007 IP6 2600:1f18:xxxx:xxxx:36b0:5593:30bc:2d87.55882 > 64:ff9b::4275:27c9.80: Flags [S], seq 2015291953, win 62587, options [mss 8941,sackOK,TS val 3259538098 ecr 0,nop,wscale 7], length 0
05:10:53.357529 IP6 2600:1f18:xxxx:xxxx:b428:1047:7d73:9484.55882 > 64:ff9b::4275:27c9.80: Flags [S], seq 2015291953, win 62587, options [mss 8941,sackOK,TS val 3259539107 ecr 0,nop,wscale 7], length 0
05:10:53.357566 IP6 2600:1f18:xxxx:xxxx:36b0:5593:30bc:2d87.55882 > 64:ff9b::4275:27c9.80: Flags [S], seq 2015291953, win 62587, options [mss 8941,sackOK,TS val 3259539107 ecr 0,nop,wscale 7], length 0
05:10:55.373514 IP6 2600:1f18:xxxx:xxxx:b428:1047:7d73:9484.55882 > 64:ff9b::4275:27c9.80: Flags [S], seq 2015291953, win 62587, options [mss 8941,sackOK,TS val 3259541123 ecr 0,nop,wscale 7], length 0
05:10:55.373549 IP6 2600:1f18:xxxx:xxxx:36b0:5593:30bc:2d87.55882 > 64:ff9b::4275:27c9.80: Flags [S], seq 2015291953, win 62587, options [mss 8941,sackOK,TS val 3259541123 ecr 0,nop,wscale 7], length 0
# nft monitor trace
trace id 3b90a155 inet nat prerouting packet: iif "ens5" ether saddr 12:57:f4:30:c4:2e ether daddr 12:b3:8b:26:a9:f1 ip6 saddr 2600:1f18:xxxx:xxxx:b428:1047:7d73:9484 ip6 daddr 64:ff9b::4275:27c9 ip6 dscp cs0 ip6 ecn not-ect ip6 hoplimit 64 ip6 flowlabel 702875 ip6 nexthdr tcp ip6 length 40 tcp sport 42834 tcp dport 80 tcp flags == syn tcp window 62587
trace id 3b90a155 inet nat prerouting rule meta nftrace set 1 (verdict continue)
trace id 3b90a155 inet nat prerouting verdict continue
trace id 3b90a155 inet nat prerouting policy accept
trace id 3b90a155 inet filter forward packet: iif "ens5" oif "ens5" ether saddr 12:57:f4:30:c4:2e ether daddr 12:b3:8b:26:a9:f1 ip6 saddr 2600:1f18:xxxx:xxxx:b428:1047:7d73:9484 ip6 daddr 64:ff9b::4275:27c9 ip6 dscp cs0 ip6 ecn not-ect ip6 hoplimit 63 ip6 flowlabel 702875 ip6 nexthdr tcp ip6 length 40 tcp sport 42834 tcp dport 80 tcp flags == syn tcp window 62587
trace id 3b90a155 inet filter forward verdict continue
trace id 3b90a155 inet filter forward policy accept
trace id 3b90a155 inet nat postrouting packet: iif "ens5" oif "ens5" ether saddr 12:57:f4:30:c4:2e ether daddr 12:b3:8b:26:a9:f1 ip6 saddr 2600:1f18:xxxx:xxxx:b428:1047:7d73:9484 ip6 daddr 64:ff9b::4275:27c9 ip6 dscp cs0 ip6 ecn not-ect ip6 hoplimit 63 ip6 flowlabel 702875 ip6 nexthdr tcp ip6 length 40 tcp sport 42834 tcp dport 80 tcp flags == syn tcp window 62587
trace id 3b90a155 inet nat postrouting rule oifname "ens5" masquerade (verdict accept)
The traffic is clearly getting to my NAT machine, but it never seems to leave there out to the Internet.
How can I make this work?