{"id":299,"date":"2017-09-29T15:50:25","date_gmt":"2017-09-29T13:50:25","guid":{"rendered":"http:\/\/blog.le-vert.net\/?p=299"},"modified":"2017-09-29T15:50:25","modified_gmt":"2017-09-29T13:50:25","slug":"mimic-ipv4-docker-behavior-for-ipv6-with-shorewall-and-nat","status":"publish","type":"post","link":"https:\/\/blog.le-vert.net\/?p=299","title":{"rendered":"Mimic IPv4 docker behavior for IPv6 with Shorewall and NAT"},"content":{"rendered":"<div class=\"twttr_buttons\"><div class=\"twttr_twitter\">\n\t\t\t\t\t<a href=\"http:\/\/twitter.com\/share?text=Mimic+IPv4+docker+behavior+for+IPv6+with+Shorewall+and+NAT\" class=\"twitter-share-button\" data-via=\"\" data-hashtags=\"\"  data-size=\"default\" data-url=\"https:\/\/blog.le-vert.net\/?p=299\"  data-related=\"\" target=\"_blank\">Tweet<\/a>\n\t\t\t\t<\/div><\/div><p>Hi,<\/p>\n<p>Docker IPv6 support is messed up. Instead of sharing a non-routed IPv6 prefix between host and containers, just like it does for IPv4, docker team decided to implement a full IPv6 support, with routing and everything.<br \/>\nThe point is: most of people just want basic IPv6 inside the container (in my case I&#8217;m doing monitoring checks) and would love to have it working out of the box. Honestly, who wants a dedicated routed prefix for its IPv6 containers ? Certainly not me.<\/p>\n<p>So here&#8217;s the trick: we&#8217;re going to use a chosen prefix between docker daemon and its host and we&#8217;ll use shorewall6 to NAT it when going outside. Basically it&#8217;ll mimic IPv4 behavior.<\/p>\n<p>So first, make sure IPv6 is working correctly on host (actually while writing this doc, it was not on my sample setup :D):<\/p>\n<pre>\r\nroot@server:~# ping6 -n google.lu\r\nPING google.lu(2a00:1450:4001:816::2003) 56 data bytes\r\n64 bytes from 2a00:1450:4001:816::2003: icmp_seq=1 ttl=57 time=10.3 ms\r\n64 bytes from 2a00:1450:4001:816::2003: icmp_seq=2 ttl=57 time=10.3 ms\r\n\r\n<\/pre>\n<p>Then, define a IPv6 prefix to be use inside docker by editing <strong>\/etc\/docker\/daemon.json<\/strong><\/p>\n<pre>\r\n{\r\n\t\"ipv6\": true,\r\n\t\"fixed-cidr-v6\": \"2a00:1450::\/64\"\r\n}\r\n<\/pre>\n<p>Create the file if not existing yet.<\/p>\n<p>Restart docker daemon:<\/p>\n<pre>systemctl restart docker<\/pre>\n<p>Now your container should have an IP range in the range (and a default gateway):<\/p>\n<pre>\r\nroot@server:~# docker <container_name> -ti centreon ip -6 a\r\n1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 state UNKNOWN qlen 1\r\n    inet6 ::1\/128 scope host \r\n       valid_lft forever preferred_lft forever\r\n228: eth0@if229: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP \r\n    inet6 2a00:1450::242:ac11:2\/64 scope global nodad \r\n       valid_lft forever preferred_lft forever\r\n    inet6 fe80::42:acff:fe11:2\/64 scope link \r\n       valid_lft forever preferred_lft forever\r\n<\/pre>\n<pre>\r\nroot@server:~# docker exec -ti <container_name> ip -6 r\r\n2a00:1450::\/64 dev eth0  proto kernel  metric 256 \r\nfe80::\/64 dev eth0  proto kernel  metric 256 \r\ndefault via 2a00:1450::1 dev eth0  metric 1024 \r\n<\/pre>\n<p>You host should be able to reach the container too:<\/p>\n<pre>\r\nroot@server:~# ping6 2a00:1450::242:ac11:2\r\nPING 2a00:1450::242:ac11:2(2a00:1450::242:ac11:2) 56 data bytes\r\n64 bytes from 2a00:1450::242:ac11:2: icmp_seq=1 ttl=64 time=0.076 ms\r\n64 bytes from 2a00:1450::242:ac11:2: icmp_seq=2 ttl=64 time=0.099 ms\r\n<\/pre>\n<p>Here we go&#8230; Now all we need to do is make a source NAT of 2a00:1450::\/64 when going out of eth0 on the host server.<\/p>\n<p>For this, we&#8217;ll use shorewall:<\/p>\n<pre>\r\napt -y install shorewall6\r\n<\/pre>\n<p>And we&#8217;ll create a default configuration based on provided examples:<\/p>\n<pre>\r\ncp \/usr\/share\/doc\/shorewall6\/examples\/one-interface\/{policy,interfaces,rules,zones} \/etc\/shorewall6\/\r\n<\/pre>\n<p>Default configuration can work but I&#8217;ll tweak it a bit.<\/p>\n<p>In <strong>\/etc\/shorewall6\/interfaces<\/strong> I renamed the interface to loc instead of net and disable routeur advertisment (IPv6 gateway statically defined in \/etc\/network\/interfaces) and I need to define the docker bridge interface:<\/p>\n<pre>\r\nloc     eth0            tcpflags,accept_ra=0,sourceroute=0\r\ndockr   docker0         tcpflags,accept_ra=0,sourceroute=0\r\n<\/pre>\n<p>In <strong>\/etc\/shorewall6\/zones<\/strong> there must be one ipv6 zone for each interface:<\/p>\n<pre>\r\nfw      firewall\r\nloc     ipv6\r\ndockr   ipv6\r\n<\/pre>\n<p>In <strong>\/etc\/shorewall6\/policy<\/strong> I give full access to host server to any network, docker has access to local network (but not to its host) and I block everything else<\/p>\n<pre>\r\nfw              all             ACCEPT\r\ndockr           loc             ACCEPT\r\nall             all             DROP            info\r\n<\/pre>\n<p>Then, I create a few overrides in <strong>\/etc\/shorewall6\/rules to authorize Ping and SSH from local network:<\/p>\n<pre>\r\n# From docker network\r\nPing(ACCEPT)    dockr           fw\r\nMySQL(ACCEPT)   dockr           fw\r\nSNMP(ACCEPT)    dockr           fw\r\nACCEPT          dockr           fw              tcp     5666\r\n\r\n# From local netowork\r\nPing(ACCEPT)    loc             fw\r\nSSH(ACCEPT)     loc             fw\r\nSNMP(ACCEPT)    loc             fw\r\nACCEPT          loc             fw              tcp     5666\r\nHTTP(ACCEPT)    loc             fw\r\n<\/pre>\n<p>As you can see, there&#8217;s a few more rules: at least one containers uses a MariaDB server on the host, so I permit that. Also I permit Nagios NRPE and SNMP from local network to host (fw) for monitoring purpose and I also accept the same from container to host because there&#8217;s a monitoring container that will actually monitor its own host.<\/p>\n<p>Now we need to NAT the private IPv6 subnet used by docker in <\/strong>\/etc\/shorewall6\/snat<\/strong><\/p>\n<pre>MASQUERADE\t\t2a00:1450::\/64\t\teth0<\/pre>\n<p>Finally we&#8217;ll enable IP_FORWARDING in *\/etc\/shorewall6\/shorewall.conf* by setting it to yes and restart shorewall:<\/p>\n<pre>\r\nsed -i 's!IP_FORWARDING=.*!IP_FORWARDING=Yes!' \/etc\/shorewall6\/shorewall6.conf\r\nsystemctl enable shorewall6\r\nsystemctl start shorewall6\r\n<\/pre>\n<p>And that&#8217;s it:<\/p>\n<pre>\r\nroot@server:~# docker exec -ti <container_name> ping6 -n google.lu\r\nPING google.lu(2a00:1450:4001:81b::2003) 56 data bytes\r\n64 bytes from 2a00:1450:4001:81b::2003: icmp_seq=1 ttl=56 time=10.6 ms\r\n64 bytes from 2a00:1450:4001:81b::2003: icmp_seq=2 ttl=56 time=10.5 ms\r\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Hi, Docker IPv6 support is messed up. Instead of sharing a non-routed IPv6 prefix between host and containers, just like it does for IPv4, docker team decided to implement a full IPv6 support, with routing and everything. The point is: &hellip; <a href=\"https:\/\/blog.le-vert.net\/?p=299\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"_links":{"self":[{"href":"https:\/\/blog.le-vert.net\/index.php?rest_route=\/wp\/v2\/posts\/299"}],"collection":[{"href":"https:\/\/blog.le-vert.net\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.le-vert.net\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.le-vert.net\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.le-vert.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=299"}],"version-history":[{"count":3,"href":"https:\/\/blog.le-vert.net\/index.php?rest_route=\/wp\/v2\/posts\/299\/revisions"}],"predecessor-version":[{"id":302,"href":"https:\/\/blog.le-vert.net\/index.php?rest_route=\/wp\/v2\/posts\/299\/revisions\/302"}],"wp:attachment":[{"href":"https:\/\/blog.le-vert.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=299"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.le-vert.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=299"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.le-vert.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=299"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}