In the environment specified below IPv4 purrs like a cat, but IPv6 vanishes after a short time – i.e. even the host can't reach its container via IPv6 over the Docker network. Did I miss something?
Edit #1
Replaced 64:ff9b:: w/ something global, but the problem persists. The host looses its IPv6 (but not IPv4) connectivity to the directly connected Docker container. First "No route to host", then timeout.
playbook.yml
---
- hosts: all
become: yes
become_method: sudo
tasks:
- import_tasks: tasks/firewall.yml
- import_tasks: tasks/router.yml
- import_tasks: tasks/docker.yml
- name: /usr/local/docker-services
file:
path: /usr/local/docker-services
owner: root
group: root
mode: '0700'
state: directory
- name: nginx-site.conf
copy:
dest: /usr/local/docker-services/nginx-site.conf
owner: root
group: root
mode: '0666'
src: files/nginx-site.conf
- name: docker-compose.yml
copy:
dest: /usr/local/docker-services/docker-compose.yml
owner: root
group: root
mode: '0666'
content: |
version: '2.4'
networks:
ext-nginx:
internal: true
enable_ipv6: true
driver_opts:
com.docker.network.bridge.name: docker1
ipam:
config:
- subnet: 192.168.234.0/30
gateway: 192.168.234.1
- subnet: 64:ff9b::192.168.234.0/126
gateway: 64:ff9b::192.168.234.1
services:
nginx:
container_name: nginx
image: nginx
restart: always
logging:
options:
labels: container
labels:
container: nginx
networks:
ext-nginx:
ipv4_address: 192.168.234.2
ipv6_address: 64:ff9b::192.168.234.2
priority: 1
volumes:
- type: bind
source: /usr/local/docker-services/nginx-site.conf
target: /etc/nginx/conf.d/default.conf
read_only: true
register: docker_compose_yml
- name: docker-compose.service
copy:
dest: /etc/systemd/system/docker-compose.service
owner: root
group: root
mode: '0644'
src: files/docker-compose.service
register: docker_compose_service
- name: systemctl daemon-reload
when: docker_compose_service.changed
systemd:
daemon_reload: yes
- name: systemctl stop docker-compose.service
when: >-
docker_compose_service.changed
or docker_compose_yml.changed
service:
name: docker-compose
state: stopped
- name: systemctl start docker-compose.service
service:
name: docker-compose
state: started
enabled: yes
tasks/firewall.yml
---
- name: Firewall rules applicator
apt:
name: iptables-persistent
- name: Firewall rules file
loop: [4, 6]
copy:
dest: '/etc/iptables/rules.v{{ item }}'
owner: root
group: root
mode: '0644'
src: 'files/firewall/rules.v{{ item }}'
register: firewall_file
- name: Apply firewall rules
when: 'firewall_file.results[0].changed or firewall_file.results[1].changed'
service:
name: netfilter-persistent
state: restarted
tasks/router.yml
---
- name: net.ipv4.ip_forward
sysctl:
name: net.ipv4.ip_forward
value: '1'
- name: net.ipv6.conf.all.forwarding
sysctl:
name: net.ipv6.conf.all.forwarding
value: '1'
tasks/docker.yml
---
- name: apt-transport-https
apt:
name: apt-transport-https
- name: Docker apt key
apt_key:
url: https://download.docker.com/linux/debian/gpg
- name: Docker apt repo
apt_repository:
filename: docker
repo: >
deb https://download.docker.com/linux/debian
{{ ansible_lsb.codename }} stable
- name: /etc/docker
file:
path: /etc/docker
owner: root
group: root
mode: '0755'
state: directory
- name: /etc/docker/daemon.json
copy:
dest: /etc/docker/daemon.json
owner: root
group: root
mode: '0644'
content: '{"iptables":false}'
- name: Docker
apt:
name: docker-ce
- name: Docker compose
apt:
name: docker-compose
files/firewall/rules.v4
*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [0:0]
-A INPUT -d 127.0.0.0/8 ! -i lo -j DROP
-A INPUT -i lo -j ACCEPT
-A INPUT -p tcp -m tcp --dport 22 -j ACCEPT
-A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
-A FORWARD -o docker1 -d 192.168.234.2/32 -p tcp -m tcp --dport 80 -j ACCEPT
-A FORWARD -i docker1 -o eth0 -j ACCEPT
-A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
COMMIT
*nat
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
-A PREROUTING -i eth0 -d 78.47.124.58 -p tcp -m tcp --dport 80 -j DNAT --to 192.168.234.2
-A POSTROUTING -o eth0 ! -s 78.47.124.58 -j MASQUERADE
COMMIT
files/firewall/rules.v6
*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [0:0]
-A INPUT -d ::1/128 ! -i lo -j DROP
-A INPUT -i lo -j ACCEPT
-A INPUT -p tcp -m tcp --dport 22 -j ACCEPT
-A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
-A FORWARD -o docker1 -d 64:ff9b::192.168.234.2 -p tcp -m tcp --dport 80 -j ACCEPT
-A FORWARD -i docker1 -o eth0 -j ACCEPT
-A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
COMMIT
*nat
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
-A PREROUTING -i eth0 -d 2a01:4f8:c0c:3bc1::/64 -p tcp -m tcp --dport 80 -j DNAT --to 64:ff9b::192.168.234.2
-A POSTROUTING -o eth0 ! -s 2a01:4f8:c0c:3bc1::/64 -j MASQUERADE
COMMIT
files/nginx-site.conf
server {
listen 80;
listen [::]:80;
server_name localhost;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
files/docker-compose.service
[Unit]
Requires=docker.service
[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/usr/local/docker-services
ExecStart=/usr/bin/docker-compose up -d --force-recreate
ExecStop=/usr/bin/docker-compose down
[Install]
WantedBy=multi-user.target
My guess is that your IPv6 firewall rules are blocking NDP (Neighbor Discovery Protocol) packets, thus preventing your host to resolve container's link layer address properly.
In IPv4 stack, resolution of link layer (Ethernet) addresses is handled by ARP (Address Resolution Protocol), which works using link layer addresses as packet destinations. When a host issues ARP requests or unsolicited ARP advertisements, those packets are forwarded directly to the broadcast link layer address (which is
FF:FF:FF:FF:FF:FF
in Ethernet protocol) and, because of that, are not subject toiptables
filtering.In IPv6 stack, resolution of link layer addresses is handled by NDP (Neighbor Discovery Protocol). Unlike ARP, NDP packets actually are ICMPv6 packets forwarded to IPv6 addresses and, as a result, subject to
ip6tables
filtering.I believe that, according to my experience and as seen in Arch Linux Wiki article, the CONNTRACK module is not designed to track ICMPv6 NDP packets and mark reply packets as ESTABLISHED or RELATED. My suggestion is to allow such traffic explicitly in
files/firewall/rules.v6
file:(new rules)
(complete file)