{"id":5041,"date":"2025-07-05T23:27:06","date_gmt":"2025-07-06T03:27:06","guid":{"rendered":"https:\/\/dft.wiki\/?p=5041"},"modified":"2026-06-08T09:29:55","modified_gmt":"2026-06-08T13:29:55","slug":"dual-stack-and-nated-ipv6-networking-on-linux","status":"publish","type":"post","link":"https:\/\/dft.wiki\/?p=5041","title":{"rendered":"Dual-Stack and NATed IPv6 Networking on Linux"},"content":{"rendered":"<p><strong>IPv4<\/strong> commonly uses NAT to let many devices share one public IP address, adding a layer of protection by hiding internal devices.<\/p>\n<p>In <strong>IPv6<\/strong>, each device gets its own public (global) IP, which brings both new benefits and new security challenges.<\/p>\n<p>IPv4 and IPv6 can work together, but they don&#8217;t fully mix. This post covers how to handle different scenarios using:<\/p>\n<ul data-start=\"429\" data-end=\"551\">\n<li data-start=\"429\" data-end=\"454\"><strong>Dual-Stack<\/strong> Networking<\/li>\n<li data-start=\"455\" data-end=\"474\"><strong>NAT66<\/strong><\/li>\n<li data-start=\"455\" data-end=\"474\"><strong>DNS64<\/strong> + <strong>NAT64<\/strong><\/li>\n<li data-start=\"475\" data-end=\"507\"><strong>Filtering<\/strong> Traffic<\/li>\n<\/ul>\n<p>This is a continuation of the post about <strong>IPv6-Only Environment<\/strong> [<a href=\"https:\/\/dft.wiki\/?p=4987\">Link<\/a>].<\/p>\n<hr \/>\n<p><strong>DUAL-STACK<\/strong><\/p>\n<p>Dual-stack means running both <strong>IPv4 and IPv6 on the same network<\/strong> or device, allowing communication over either protocol.<\/p>\n<p>Some consider dual-stack the <strong>easier path to transition from IPv4-only<\/strong> to IPv6. Others argue it <strong>doubles complexity<\/strong> and that <strong>NAT64<\/strong> is the better approach.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-5056\" src=\"https:\/\/dft.wiki\/wp-content\/uploads\/sites\/15\/2025\/07\/ipv6-only_5.drawio.png\" alt=\"\" width=\"443\" height=\"182\" srcset=\"https:\/\/dft.wiki\/wp-content\/uploads\/sites\/15\/2025\/07\/ipv6-only_5.drawio.png 443w, https:\/\/dft.wiki\/wp-content\/uploads\/sites\/15\/2025\/07\/ipv6-only_5.drawio-300x123.png 300w\" sizes=\"auto, (max-width: 443px) 100vw, 443px\" \/><\/p>\n<hr \/>\n<p><strong>NAT66<\/strong><\/p>\n<p><strong>NAT66<\/strong> hides an internal IPv6 network behind a single public IPv6 address, similar to how IPv4 NAT works.<\/p>\n<p>The internal network can use either <strong>global<\/strong> or <strong>unique local<\/strong> IPv6 addresses, depending on the network design.<\/p>\n<p>One reason to <strong>use NAT66 is security<\/strong>: it blocks unsolicited incoming connections from the internet, even if the firewall is misconfigured or a device is compromised.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-5021\" src=\"https:\/\/dft.wiki\/wp-content\/uploads\/sites\/15\/2025\/06\/ipv6-only_3.drawio.png\" alt=\"\" width=\"578\" height=\"245\" srcset=\"https:\/\/dft.wiki\/wp-content\/uploads\/sites\/15\/2025\/06\/ipv6-only_3.drawio.png 578w, https:\/\/dft.wiki\/wp-content\/uploads\/sites\/15\/2025\/06\/ipv6-only_3.drawio-300x127.png 300w\" sizes=\"auto, (max-width: 578px) 100vw, 578px\" \/><\/p>\n<p><strong>Unique Local Addresses (ULA)<\/strong><\/p>\n<ul>\n<li><code>fc00::\/8<\/code> \u2014 Reserved (not yet defined).<\/li>\n<li><code>fd00::\/8<\/code> \u2014 Used for internal addressing (not publicly routable).<\/li>\n<\/ul>\n<p>Set a <strong>ULA<\/strong> prefix to advertise. The <code>\/64<\/code> prefix must be in the <code>fd00<\/code>\u2013<code>fdff<\/code> range to stay non-publicly routable. This example uses <code>fd12:3456:7890:abcd::\/64<\/code>.<\/p>\n<pre>apt install radvd -y\r\nnano \/etc\/radvd.conf<\/pre>\n<pre>interface <strong>eth0<\/strong> {\r\n  AdvSendAdvert on;\r\n  MaxRtrAdvInterval 600;\r\n  MinRtrAdvInterval 200;\r\n  prefix <strong>fd12:3456:7890:abcd::<\/strong>\/64 {\r\n    AdvOnLink on;\r\n    AdvAutonomous on;\r\n    AdvRouterAddr on;\r\n  };\r\n  RDNSS 2606:4700:4700::1001 {\r\n    AdvRDNSSLifetime 1200;\r\n  };\r\n};<\/pre>\n<p>Assign an IP within the <code>fd12:3456:7890:abcd::\/64<\/code> prefix to the router&#8217;s internal NIC \u2014 for example, <code>fd12:3456:7890:abcd::1<\/code>.<\/p>\n<p>Set up routing rules to masquerade outbound traffic and persist them across reboots.<\/p>\n<pre>ip6tables -A FORWARD <strong>-i eth0 -o wg0<\/strong> -j ACCEPT\r\nip6tables -A FORWARD <strong>-i wg0 -o eth0<\/strong> -m state --state <strong>ESTABLISHED,RELATED<\/strong> -j ACCEPT\r\nip6tables -t nat -A <strong>POSTROUTING -o wg0 -j MASQUERADE<\/strong>\r\napt install iptables-persistent -y<\/pre>\n<p>Outbound traffic from LAN to WAN (<code>eth0<\/code> to <code>wg0<\/code>) is translated at the gateway. Return traffic (<strong>ESTABLISHED and RELATED<\/strong>) is allowed back through.<\/p>\n<p>Monitor traffic translation between LAN and WAN:<\/p>\n<pre>sudo tcpdump ip6 -n -i <strong>eth0<\/strong>\r\nsudo tcpdump ip6 -n -i <strong>wg0<\/strong><\/pre>\n<p><strong>Global Unicast Addresses (GUAs)<\/strong><\/p>\n<ul>\n<li><code>2000::\/3<\/code> \u2014 From <code>2000::<\/code> to <code>3fff::<\/code> (publicly routable).<\/li>\n<\/ul>\n<p>The only change from the previous setup is applying a GUA <code>\/64<\/code> prefix in the Router Advertisement Daemon, then creating these routes instead:<\/p>\n<pre>ip6tables -A FORWARD <strong>-i eth0 -o wg0<\/strong> -j ACCEPT\r\nip6tables -A FORWARD<strong> -i wg0 -o eth0<\/strong> -j ACCEPT\r\napt install iptables-persistent -y<\/pre>\n<p><strong>Note:<\/strong> Traffic is forwarded in both directions (<strong>LAN\u2192WAN and WAN\u2192LAN<\/strong>), meaning all hosts with global IPv6 addresses are directly exposed to the internet (<strong>DMZ<\/strong>).<\/p>\n<hr \/>\n<p><strong>DNS64 + NAT64<\/strong><\/p>\n<p><strong>NAT64<\/strong> is a transition technology that allows <strong>IPv6-only clients<\/strong> to reach <strong>IPv4 servers<\/strong> by <strong>translating IPv6 addresses to IPv4 at the network edge<\/strong>.<\/p>\n<p>The <strong>NAT64<\/strong> gateway performs this translation using a special IPv6 prefix \u2014 usually <code>64:ff9b::\/96<\/code> \u2014 where the IPv4 address is embedded in the last 32 bits of the IPv6 address.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-5049\" src=\"https:\/\/dft.wiki\/wp-content\/uploads\/sites\/15\/2025\/07\/ipv6-only_4.drawio.png\" alt=\"\" width=\"442\" height=\"263\" srcset=\"https:\/\/dft.wiki\/wp-content\/uploads\/sites\/15\/2025\/07\/ipv6-only_4.drawio.png 442w, https:\/\/dft.wiki\/wp-content\/uploads\/sites\/15\/2025\/07\/ipv6-only_4.drawio-300x179.png 300w\" sizes=\"auto, (max-width: 442px) 100vw, 442px\" \/><\/p>\n<p><strong>How does it work?<\/strong><\/p>\n<p>If a domain <strong>has an AAAA record<\/strong>, DNS resolves it normally and IPv6 traffic is <strong>routed as usual<\/strong>. If there is <strong>only an A record<\/strong>, <strong>DNS64 synthesizes a AAAA record<\/strong> from it.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-5055\" src=\"https:\/\/dft.wiki\/wp-content\/uploads\/sites\/15\/2025\/07\/ipv6-only_6.drawio.png\" alt=\"\" width=\"413\" height=\"102\" srcset=\"https:\/\/dft.wiki\/wp-content\/uploads\/sites\/15\/2025\/07\/ipv6-only_6.drawio.png 413w, https:\/\/dft.wiki\/wp-content\/uploads\/sites\/15\/2025\/07\/ipv6-only_6.drawio-300x74.png 300w\" sizes=\"auto, (max-width: 413px) 100vw, 413px\" \/><\/p>\n<p><strong>NAT64<\/strong> then translates the IPv6 address back to IPv4 by extracting the last 32 bits, as shown above. Ideally, NAT64 sits at the edge of the <strong>IPv6-only LAN<\/strong> with at least one <strong>IPv4<\/strong> address on the WAN side or at the ISP gateway. The quick alternative is to use an <strong>external DNS64+NAT64<\/strong> service, at the cost of privacy and security (<span style=\"text-decoration: underline;\">understand the risks before using such services<\/span>).<\/p>\n<ul>\n<li><strong>The Easy (Lazy) Way<\/strong><\/li>\n<\/ul>\n<p>Set the RDNSS to a <strong>free<\/strong> DNS64+NAT64 <strong>service<\/strong>, such as <strong>NAT64.net<\/strong> [<a href=\"https:\/\/nat64.net\/\">Link<\/a>] or <strong>Level66<\/strong> [<a href=\"https:\/\/level66.services\/services\/nat64\/\">Link<\/a>].<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-5059\" src=\"https:\/\/dft.wiki\/wp-content\/uploads\/sites\/15\/2025\/07\/Screenshot_2025-07-05_23-40-43.png\" alt=\"\" width=\"485\" height=\"51\" srcset=\"https:\/\/dft.wiki\/wp-content\/uploads\/sites\/15\/2025\/07\/Screenshot_2025-07-05_23-40-43.png 485w, https:\/\/dft.wiki\/wp-content\/uploads\/sites\/15\/2025\/07\/Screenshot_2025-07-05_23-40-43-300x32.png 300w\" sizes=\"auto, (max-width: 485px) 100vw, 485px\" \/><\/p>\n<p>Multiple resolvers can also be set at once.<\/p>\n<p>Manually via <code>\/etc\/resolv.conf<\/code>:<\/p>\n<pre>nameserver 2a00:1098:2b::1\r\nnameserver 2001:67c:2960::64\r\nnameserver 2a00:1098:2c::1\r\nnameserver 2001:67c:2960::6464\r\nnameserver 2a01:4f8:c2c:123f::1<\/pre>\n<p>For persistence with <strong>systemd-resolved<\/strong>, edit <code>\/etc\/systemd\/resolved.conf<\/code>:<\/p>\n<pre>DNS=2a00:1098:2b::1 2001:67c:2960::64 2a00:1098:2c::1 2001:67c:2960::6464 2a01:4f8:c2c:123f::1<\/pre>\n<ul>\n<li><strong>The Proper Way<\/strong>\n<ul>\n<li>Self-host NAT64 using Tayga or Jool.<\/li>\n<li>Self-host DNS64 with Bind9 or Unbound, or use a well-known public DNS64.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p><strong>DNS64<\/strong> with <strong>Bind9<\/strong><\/p>\n<pre>sudo apt install bind9 -y\r\nsudo nano \/etc\/bind\/named.conf.options<\/pre>\n<pre>options {\r\n    dns64 64:ff9b::\/96 {\r\n        clients { any; };\r\n    };\r\n};<\/pre>\n<pre>sudo systemctl restart bind9<\/pre>\n<p><strong>DNS64<\/strong> with <strong>Unbound<\/strong><\/p>\n<pre>sudo apt install unbound -y\r\nsudo nano \/etc\/unbound\/unbound.conf.d\/forward-zones.conf<\/pre>\n<pre>forward-zone:\r\n   name: \".\"\r\n   forward-addr: 2606:4700:4700::1001\r\n   forward-addr: 2001:4860:4860::8888<\/pre>\n<pre>sudo nano \/etc\/unbound\/unbound.conf.d\/server.conf<\/pre>\n<pre>include: \"\/etc\/unbound\/unbound.conf.d\/*.conf\"\r\nserver:\r\n   module-config: \"dns64 validator iterator\"\r\n   interface: ::\r\n   access-control: ::0\/0 refuse\r\n   access-control: fd12:3456:7890:abcd::\/64 allow\r\n   dns64-prefix: 64:ff9b::\/96<\/pre>\n<pre>sudo systemctl restart unbound<\/pre>\n<p><strong>Well-known public DNS64 services<\/strong><\/p>\n<p>Cloudflare and Google offer DNS64, they synthesize IPv6 addresses using the standard <code>64:ff9b::\/96<\/code> prefix for local NAT64 translation, but do not provide NAT64 themselves.<\/p>\n<pre>nameserver 2606:4700:4700::64\r\nnameserver 2001:4860:4860::64\r\nnameserver 2606:4700:4700::6400\r\nnameserver 2001:4860:4860::6464<\/pre>\n<p><strong>NAT64<\/strong> with <strong>Tayga<\/strong> (User-Space NAT64) [<a href=\"https:\/\/github.com\/apalrd\/tayga\">Link<\/a>]<\/p>\n<pre>sudo sysctl -w net.ipv4.ip_forward=1\r\nsudo sysctl -w net.ipv6.conf.all.forwarding=1\r\nsudo apt install tayga -y\r\nsudo nano \/etc\/tayga.conf<\/pre>\n<pre>tun-device nat64\r\nipv4-addr 192.168.255.1\r\nipv6-addr fd12:3456:7890:abcd::1\r\nprefix 64:ff9b::\/96\r\ndynamic-pool 192.168.255.0\/24\r\ndata-dir \/var\/spool\/tayga<\/pre>\n<pre>sudo iptables -t nat -A POSTROUTING -o nat64 -j MASQUERADE\r\nsudo iptables -t nat -A POSTROUTING -s 192.168.255.0\/24 -j MASQUERADE\r\nsudo systemctl enable tayga --now<\/pre>\n<p><strong>NAT64<\/strong> with <strong>Jool<\/strong> (Kernel-Based NAT64)<\/p>\n<pre>sudo apt install jool-dkms jool-tools -y\r\nsudo modprobe jool\r\nsudo jool instance add \"nat64\" --netfilter --pool6 64:ff9b::\/96\r\nsudo jool -i nat64 pool4 add 192.168.254.0\/24\r\nsudo jool -i nat64 status\r\nsudo iptables -t nat -A POSTROUTING -s 192.168.254.0\/24 -j MASQUERADE<\/pre>\n<hr \/>\n<p><strong>FILTERING TRAFFIC<\/strong><\/p>\n<p>(pending)<\/p>\n<hr \/>\n<p><strong>BONUS<\/strong><\/p>\n<p><strong>Dual-Stack<\/strong>, to prioritize IPv4 over IPv6 on Linux, adjust the address selection policy via the <code>glibc<\/code> mechanism:<\/p>\n<pre>echo \"precedence ::ffff:0:0\/96 100\" &gt;&gt; \/etc\/gai.conf<\/pre>\n<hr \/>\n<p><strong>SEE ALSO<\/strong><\/p>\n<p><strong>IPv6-Only Environment with Free Global IPv6 Subnet<\/strong> [<a href=\"https:\/\/dft.wiki\/?p=4987\">Link<\/a>]<\/p>\n","protected":false},"excerpt":{"rendered":"<p>IPv4 commonly uses NAT to let many devices share one public IP address, adding a [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-5041","post","type-post","status-publish","format-standard","hentry","category-ccna"],"_links":{"self":[{"href":"https:\/\/dft.wiki\/index.php?rest_route=\/wp\/v2\/posts\/5041","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/dft.wiki\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/dft.wiki\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/dft.wiki\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/dft.wiki\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=5041"}],"version-history":[{"count":19,"href":"https:\/\/dft.wiki\/index.php?rest_route=\/wp\/v2\/posts\/5041\/revisions"}],"predecessor-version":[{"id":5598,"href":"https:\/\/dft.wiki\/index.php?rest_route=\/wp\/v2\/posts\/5041\/revisions\/5598"}],"wp:attachment":[{"href":"https:\/\/dft.wiki\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=5041"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/dft.wiki\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=5041"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/dft.wiki\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=5041"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}