Iptables – IPv6 Firewall Rules

firewalliptablesipv6linux-networking

I have a DigitalOcean droplet which is used for a few things, primarily as a web host and SMTP server. I have recently started dabbling in IPv6 and have successfully configured it on the VPS; ping6 works great to google.com, etc. The local LAN network is configured with IPv6 via slaac and can ping and connect to the DO droplet without issue.

Where I’m running into issues is with setting up ip6tables. I did a basic configuration which only allows SSH, Web, and SMTP, but after adding the rules, everything stops being able to connect to the VPS over IPv6. As soon as I flush the tables, it’s fine. It also seems that if I setup a connection (like web or ssh) before adding the rules, the connections work after the rules are added, it only for a little bit. I’m not sure what’s blocking the connections at this point, any guidance would be helpful.

ip6tables -nvL
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
  pkts bytes target     prot opt in     out     source               destination         
 1880  121K ACCEPT     all      *      *       ::/0                 ::/0                 ctstate RELATED,ESTABLISHED
    2   136 ACCEPT     all      lo     *       ::/0                 ::/0                
    1    84 ACCEPT     tcp      *      *       ::/0                 ::/0                 tcp dpt:22
    0     0 ACCEPT     tcp      *      *       ::/0                 ::/0                 tcp dpt:25
    0     0 ACCEPT     tcp      *      *       ::/0                 ::/0                 tcp dpt:80
    1    80 ACCEPT     tcp      *      *       ::/0                 ::/0                 tcp dpt:443
    0     0 ACCEPT     tcp      *      *       ::/0                 ::/0                 tcp dpt:587
  774 55932 DROP       all      *      *       ::/0                 ::/0                

Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         

Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         
 2537  168K ACCEPT     all      *      *       ::/0                 ::/0

UPDATE: Added a log statement to capture some logs, then went ahead and tried to SSH from a client that I know wouldn't work. It wouldn't connect and I gave up after waiting maybe 10-seconds.

Mar 29 08:57:55 cloud kernel: [170352.518576] IN=eth0 OUT= MAC=33:33:ff:e2:40:01:84:b5:9c:f9:18:30:86:dd SRC=fe80:0000:0000:0000:0000:0000:0000:0001 DST=ff02:0000:0000:0000:0000:0001:ffe2:4001 LEN=72 TC=192 HOPLIMIT=255 FLOWLBL=0 PROTO=ICMPv6 TYPE=135 CODE=0 
Mar 29 08:57:56 cloud kernel: [170353.518091] IN=eth0 OUT= MAC=33:33:ff:e2:40:01:84:b5:9c:f9:18:30:86:dd SRC=fe80:0000:0000:0000:0000:0000:0000:0001 DST=ff02:0000:0000:0000:0000:0001:ffe2:4001 LEN=72 TC=192 HOPLIMIT=255 FLOWLBL=0 PROTO=ICMPv6 TYPE=135 CODE=0 
Mar 29 08:57:57 cloud kernel: [170354.517936] IN=eth0 OUT= MAC=33:33:ff:e2:40:01:84:b5:9c:f9:18:30:86:dd SRC=fe80:0000:0000:0000:0000:0000:0000:0001 DST=ff02:0000:0000:0000:0000:0001:ffe2:4001 LEN=72 TC=192 HOPLIMIT=255 FLOWLBL=0 PROTO=ICMPv6 TYPE=135 CODE=0 
Mar 29 08:57:59 cloud kernel: [170356.517573] IN=eth0 OUT= MAC=33:33:ff:e2:40:01:84:b5:9c:f9:18:30:86:dd SRC=fe80:0000:0000:0000:0000:0000:0000:0001 DST=ff02:0000:0000:0000:0000:0001:ffe2:4001 LEN=72 TC=192 HOPLIMIT=255 FLOWLBL=0 PROTO=ICMPv6 TYPE=135 CODE=0 

Why is the SRC fe80::1? That's definitely not the source IP from where I'm connecting from (it's abreviated prefix is 2600:1700). Also, the DST is ff02::1:ffe2:4001 which does appear to be configured on the interface as well:

2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 04:01:3a:XX:XX:XX brd ff:ff:ff:ff:ff:ff
    inet 104.236.XXX.XXX/18 brd 104.236.XXX.XXX scope global eth0
       valid_lft forever preferred_lft forever
    inet6 2604:a880:800:XXXX::YYYY:4001/64 scope global 
       valid_lft forever preferred_lft forever
    inet6 fe80::601:3aff:fe85:dd01/64 scope link 
       valid_lft forever preferred_lft forever

ip -6 route
2604:a880:800:10::/64 dev eth0 proto kernel metric 256  pref medium
fe80::/64 dev tun0 proto kernel metric 256  pref medium
fe80::/64 dev tap0 proto kernel metric 256  pref medium
fe80::/64 dev eth0 proto kernel metric 256  pref medium
default via 2604:a880:800:XXXX::1 dev eth0 metric 1024  pref medium

As requested, ip6tables-save

ip6tables-save
# Generated by ip6tables-save v1.6.0 on Thu Mar 29 09:03:33 2018
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -p tcp -m tcp --dport 22 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 25 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 80 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 443 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 587 -j ACCEPT
-A INPUT -j LOG
-A INPUT -j DROP
-A OUTPUT -j ACCEPT
COMMIT
# Completed on Thu Mar 29 09:03:33 2018

Best Answer

IPv6 nodes all have link-local addresses, which are in the fe80::/10 network. Nodes will use the link-local addressing for various things that remain on the local link (it cannot be routed), so you must allow that addressing in your rules.

IPv6 doesn't have broadcast, so it makes heavy use of multicast. Nodes will have multiple unicast/anycast addresses on an interface, and a node must subscribe to the solicited-node multicast address for each of those addresses. The solicited-node multicast address is based on the last 24 bits of the unicast address, so if all your unicast/multicast addresses have the same last 24 bits, then you will only have one solicited-node multicast group for the interface (this is the ideal).

IPv6 Neighbor Discover uses the solicited-node multicast address. Since IPv6 doesn't have broadcast, ARP is not used to relate the layer-2 address to the layer-3 address. IPv6 uses ND for that, and that uses the solicited-node multicast address.

Related Topic