1 UDM-SE IPv6 mit RA/SLAAC und VLANs via CLI "made easy"
Was wollen wir ?
IPv6 auf einer UDM-SE sauber über mehrere VLANs zum Laufen bringen.
Also nicht nur "WAN hat IPv6", sondern:
- WAN1 und WAN2 bekommen IPv6
- jedes VLAN bekommt sein eigenes /64
- Clients bekommen ihre IPv6 per RA/SLAAC
- DNS wird per RDNSS mitgegeben
- Source-Routing sorgt dafür, dass Prefix A über WAN1 und Prefix B über WAN2 rausgeht
- WAN->LAN bleibt bei IPv6 dicht
Klingt erstmal nach Hexenküche, ist aber am Ende halb so wild, wenn man es sauber auseinander nimmt. ![]()
Warum wollen wir das? (...eigentlich eine sinnfreie Frage...
)
Weil IPv6 langsam aber sicher dazugehört.
Und weil es schöner ist, wenn jedes Netz ein eigenes /64 hat, statt irgendwo halbgar IPv6 auf der UDM eingeschaltet zu haben und sich dann zu wundern, warum Clients nichts bekommen.
Wichtig dabei:
IPv6 ist nicht IPv4 mit längeren Zahlen.
Bei IPv6 hat ein Client oft direkt eine öffentliche Adresse. NAT versteckt da nicht mehr automatisch alles. Die Firewall ist also nicht "nice to have", sondern Pflicht.
1.1 Ausgangslage
Setup hier:
- UDM-SE
- WAN1 auf eth9
- WAN2 auf eth10
- mehrere VLANs / Bridges
- IPv6 Prefixe vom Provider / vorgeschaltetem Router
- RA über UniFi lief nicht so, wie ich es gebraucht habe
Darum wird RA/SLAAC hier per eigener dnsmasq-Instanz gemacht.
Hinweis: Die IPv6-Adressen in diesem Beitrag sind anonymisiert und verwenden das Dokumentationsnetz 2001:db8::/32. Bitte durch eure echten Prefixe ersetzen.
1.2 Beispiel Prefixe
In diesem Beispiel gibt es zwei Blöcke:
Daraus werden dann einzelne /64-Netze für die VLANs genommen.
| Netz | Bridge | IPv6 Prefix | Gateway |
|---|---|---|---|
| MGMT | br0 | 2001:db8:1111:7041::/64 | 2001:db8:1111:7041::1 |
| Protect | br2 | 2001:db8:1111:7042::/64 | 2001:db8:1111:7042::1 |
| IoT | br3 | 2001:db8:1111:7043::/64 | 2001:db8:1111:7043::1 |
| WiFi | br4 | 2001:db8:2222:7ba1::/64 | 2001:db8:2222:7ba1::1 |
| Test | br5 | 2001:db8:1111:7046::/64 | 2001:db8:1111:7046::1 |
| Shelly | br6 | 2001:db8:1111:7044::/64 | 2001:db8:1111:7044::1 |
| VPN-Netz | br7 | 2001:db8:1111:7047::/64 | 2001:db8:1111:7047::1 |
| HomeNet | br8 | 2001:db8:2222:7ba2::/64 | 2001:db8:2222:7ba2::1 |
| VOIP | br9 | 2001:db8:1111:7045::/64 | 2001:db8:1111:7045::1 |
| VMware | br10 | 2001:db8:2222:7ba5::/64 | 2001:db8:2222:7ba5::1 |
| Guest | br12 | 2001:db8:2222:7ba3::/64 | 2001:db8:2222:7ba3::1 |
| Kids | br13 | 2001:db8:2222:7ba4::/64 | 2001:db8:2222:7ba4::1 |
| WAN 03 | br50 | 2001:db8:2222:7ba7::/64 | 2001:db8:2222:7ba7::1 |
| WAN 02 | br99 | 2001:db8:2222:7ba6::/64 | 2001:db8:2222:7ba6::1 |
1.3 Vorher Backup machen
Bevor man in der UDM herumstochert:
Dann Backups ziehen:
TS="$(date +%F-%H%M%S)"
mongo --quiet --port 27117 ace --eval 'db.networkconf.find({}).forEach(printjson)' > /data/networkconf-backup-$TS.json
mongo --quiet --port 27117 ace --eval 'db.firewall_policy.find({}).forEach(printjson)' > /data/firewall-policy-backup-$TS.json
Ja, das ist langweilig. Nein, das sollte man nicht überspringen.
1.4 1) WAN-Interfaces und Gateways prüfen
Zuerst schauen, welche Interfaces die WANs sind:
Bei mir war es:
Bei IPv6 ist das Gateway oft eine Link-Local-Adresse:
Diese Adressen müsst ihr natürlich durch eure echten Gateways ersetzen.
1.5 2) Source-Routing für Dual-WAN
Damit Traffic aus dem WAN1-Prefix auch über WAN1 rausgeht und WAN2-Prefixe über WAN2, braucht es eigene Routing-Tabellen.
cat >/data/ipv6-dualwan-source-rules.sh <<'EOF'
#!/bin/sh
while ip -6 route del default table 201.eth9 2>/dev/null; do :; done
while ip -6 route del default table 202.eth10 2>/dev/null; do :; done
ip -6 route add default via fe80::1111:2222:3333:4444 dev eth9 table 201.eth9 metric 1
ip -6 route add default via fe80::aaaa:bbbb:cccc:dddd dev eth10 table 202.eth10 metric 1
while ip -6 rule del pref 101 2>/dev/null; do :; done
while ip -6 rule del pref 102 2>/dev/null; do :; done
ip -6 rule add pref 101 from 2001:db8:1111:7040::/61 table 201.eth9
ip -6 rule add pref 102 from 2001:db8:2222:7ba0::/61 table 202.eth10
EOF
chmod +x /data/ipv6-dualwan-source-rules.sh
/data/ipv6-dualwan-source-rules.sh
Display More
Kontrolle:
So ungefähr soll es aussehen:
101: from 2001:db8:1111:7040::/61 lookup 201.eth9
102: from 2001:db8:2222:7ba0::/61 lookup 202.eth10
1.6 3) IPv6-Adressen auf die Bridges setzen
Jetzt bekommen die VLAN-Bridges ihre Gateway-Adressen.
cat >/data/ipv6-lan-prefix-apply.sh <<'EOF'
#!/bin/sh
ip -6 addr replace 2001:db8:1111:7040::8/64 dev eth9 nodad
ip -6 addr replace 2001:db8:2222:7ba0::8/64 dev eth10 nodad
for IF in br0 br2 br3 br4 br5 br6 br7 br8 br9 br10 br12 br13 br50 br99; do
ip -6 addr show dev "$IF" 2>/dev/null | awk '$2 ~ /^2001:db8:/ && $2 ~ /::\/64$/ {print $2}' | while read A; do
ip -6 addr del "$A" dev "$IF" 2>/dev/null || true
done
done
ip -6 addr replace 2001:db8:1111:7041::1/64 dev br0 nodad
ip -6 addr replace 2001:db8:1111:7042::1/64 dev br2 nodad
ip -6 addr replace 2001:db8:1111:7043::1/64 dev br3 nodad
ip -6 addr replace 2001:db8:1111:7044::1/64 dev br6 nodad
ip -6 addr replace 2001:db8:1111:7045::1/64 dev br9 nodad
ip -6 addr replace 2001:db8:1111:7046::1/64 dev br5 nodad
ip -6 addr replace 2001:db8:1111:7047::1/64 dev br7 nodad
ip -6 addr replace 2001:db8:2222:7ba1::1/64 dev br4 nodad
ip -6 addr replace 2001:db8:2222:7ba2::1/64 dev br8 nodad
ip -6 addr replace 2001:db8:2222:7ba3::1/64 dev br12 nodad
ip -6 addr replace 2001:db8:2222:7ba4::1/64 dev br13 nodad
ip -6 addr replace 2001:db8:2222:7ba5::1/64 dev br10 nodad
ip -6 addr replace 2001:db8:2222:7ba6::1/64 dev br99 nodad
ip -6 addr replace 2001:db8:2222:7ba7::1/64 dev br50 nodad
/data/ipv6-dualwan-source-rules.sh
EOF
chmod +x /data/ipv6-lan-prefix-apply.sh
/data/ipv6-lan-prefix-apply.sh
Display More
Prüfen:
Wichtig ist, dass pro Bridge die ::1/64-Adresse sauber steht.
1.7 4) Persistenz per systemd
Da die UDM bei Provisioning gern Dinge überschreibt, kommt das in systemd.
cat >/etc/systemd/system/ipv6-lan-prefix-apply.service <<'EOF'
[Unit]
Description=Apply IPv6 LAN prefixes and dual WAN source routing
After=network-online.target unifi.service
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/data/ipv6-lan-prefix-apply.sh
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
EOF
cat >/etc/systemd/system/ipv6-lan-prefix-apply.timer <<'EOF'
[Unit]
Description=Periodically apply IPv6 LAN prefixes
[Timer]
OnBootSec=2min
OnUnitActiveSec=2min
Unit=ipv6-lan-prefix-apply.service
[Install]
WantedBy=timers.target
EOF
cat >/etc/systemd/system/ipv6-dualwan-source-rules.service <<'EOF'
[Unit]
Description=Apply IPv6 Dual-WAN source routing rules
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/data/ipv6-dualwan-source-rules.sh
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now ipv6-lan-prefix-apply.service ipv6-lan-prefix-apply.timer ipv6-dualwan-source-rules.service
Display More
Status:
systemctl is-active ipv6-lan-prefix-apply.service ipv6-lan-prefix-apply.timer ipv6-dualwan-source-rules.service
Sollte ergeben:
1.8 5) RA/SLAAC mit dnsmasq
Jetzt kommt der Teil, damit Clients automatisch IPv6 bekommen.
RA-only Config:
cat >/data/dnsmasq-ipv6-ra.conf <<'EOF'
port=0
bind-interfaces
enable-ra
quiet-ra
dhcp-option=option6:dns-server,[2606:4700:4700::1111],[2001:4860:4860::8888]
interface=br0
dhcp-range=::,constructor:br0,ra-only,64,4h
interface=br2
dhcp-range=::,constructor:br2,ra-only,64,4h
interface=br3
dhcp-range=::,constructor:br3,ra-only,64,4h
interface=br4
dhcp-range=::,constructor:br4,ra-only,64,4h
interface=br5
dhcp-range=::,constructor:br5,ra-only,64,4h
interface=br6
dhcp-range=::,constructor:br6,ra-only,64,4h
interface=br7
dhcp-range=::,constructor:br7,ra-only,64,4h
interface=br8
dhcp-range=::,constructor:br8,ra-only,64,4h
interface=br9
dhcp-range=::,constructor:br9,ra-only,64,4h
interface=br10
dhcp-range=::,constructor:br10,ra-only,64,4h
interface=br12
dhcp-range=::,constructor:br12,ra-only,64,4h
interface=br13
dhcp-range=::,constructor:br13,ra-only,64,4h
interface=br50
dhcp-range=::,constructor:br50,ra-only,64,4h
interface=br99
dhcp-range=::,constructor:br99,ra-only,64,4h
EOF
Display More
Service:
cat >/etc/systemd/system/dnsmasq-ipv6-ra.service <<'EOF'
[Unit]
Description=dnsmasq IPv6 RA only service
After=network-online.target ipv6-lan-prefix-apply.service
Wants=network-online.target
[Service]
Type=simple
ExecStart=/usr/sbin/dnsmasq --no-daemon --conf-file=/data/dnsmasq-ipv6-ra.conf
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now dnsmasq-ipv6-ra.service
Display More
Log prüfen:
Da sollte dann sowas kommen:
router advertisement on br0
router advertisement on br2
router advertisement on br3
router advertisement on br4
IPv6 router advertisement enabled
1.9 6) RA auf dem Interface prüfen
Zum Beispiel auf br0:
Beispiel:
router advertisement
prefix info option: 2001:db8:1111:7041::/64
Flags [onlink, auto]
rdnss option:
2606:4700:4700::1111
2001:4860:4860::8888
Wenn das auftaucht, verteilt die UDM bzw. dnsmasq das Prefix per RA.
1.10 7) Client testen
Auf einem Linux-Client:
Erwartung:
inet6 2001:db8:1111:7041:....../64 scope global
default via fe80::.... dev enp2s0 proto ra
3 packets transmitted, 3 received, 0% packet loss
Nicht wundern: UniFi zeigt SLAAC-Adressen nicht immer sauber in der Client-Ansicht. Besser direkt auf der UDM schauen:
1.11 Gateway-Adressen testen
for A in \
2001:db8:1111:7041::1 \
2001:db8:1111:7042::1 \
2001:db8:1111:7043::1 \
2001:db8:1111:7044::1 \
2001:db8:1111:7045::1 \
2001:db8:1111:7046::1 \
2001:db8:1111:7047::1 \
2001:db8:2222:7ba1::1 \
2001:db8:2222:7ba2::1 \
2001:db8:2222:7ba3::1 \
2001:db8:2222:7ba4::1 \
2001:db8:2222:7ba5::1 \
2001:db8:2222:7ba6::1 \
2001:db8:2222:7ba7::1
do
printf "%-35s " "$A"
ping -6 -c 1 -W 2 -I "$A" 2606:4700:4700::1111 >/dev/null && echo OK || echo FAIL
done
Display More
Wenn überall OK steht, passt das Routing.
1.12 9) Firewall prüfen
Jetzt der Teil, der bei IPv6 wirklich wichtig ist.
Prüfen:
Bei mir gab es eine unschöne Sache in den UniFi-Firewall-Policies.
Regeln hießen sinngemäß:
Intern waren sie aber:
ip_version: IPV6
protocol: all
action: ALLOW
source: External
destination: Internal / Hotspot / External / Vpn
Also eben nicht nur ICMPv6, sondern alles per IPv6. Schöner Name, unschöner Inhalt.
Prüfen:
mongo --quiet --port 27117 ace --eval '
db.firewall_policy.find(
{ name:/ICMP IPv6/i },
{ name:1, enabled:1, action:1, ip_version:1, protocol:1, icmp_v6_typename:1, source:1, destination:1 }
).forEach(printjson)
'
Wenn dort protocol: "all" steht, dann ist das keine reine ICMPv6-Regel.
1.13 10) Falsche Policies deaktivieren
Ich habe diese falschen Policies deaktiviert.
Bitte vorher prüfen, ob das bei euch wirklich dieselben Regeln sind.
mongo --quiet --port 27117 ace --eval '
db.firewall_policy.updateMany(
{ name:"Accept #ICMP IPv6 " },
{ $set:{ enabled:false } }
)
'
Kontrolle:
mongo --quiet --port 27117 ace --eval '
db.firewall_policy.find(
{ name:"Accept #ICMP IPv6 " },
{ name:1, enabled:1, ip_version:1, protocol:1, source:1, destination:1 }
).forEach(printjson)
'
Erwartung:
1.14 11) Runtime-Schutz für WAN->intern
Zusätzlich kann man vorne in die WAN-Zielketten einen Drop für neue und invalide Pakete setzen:
ip6tables -I UBIOS_WAN_LAN_USER 1 -m conntrack --ctstate NEW,INVALID -j DROP
ip6tables -I UBIOS_WAN_GUEST_USER 1 -m conntrack --ctstate NEW,INVALID -j DROP
ip6tables -I UBIOS_WAN_DMZ_USER 1 -m conntrack --ctstate NEW,INVALID -j DROP
ip6tables -I UBIOS_WAN_VPN_USER 1 -m conntrack --ctstate NEW,INVALID -j DROP
Damit wird neuer eingehender Traffic aus dem WAN zu internen Zonen blockiert. Ausgehender Traffic und Rückverkehr bleiben möglich.
Damit das nach Provisioning wieder gesetzt wird:
cat >/data/ipv6-wan-in-block.sh <<'EOF'
#!/bin/sh
CHAINS="UBIOS_WAN_LAN_USER UBIOS_WAN_GUEST_USER UBIOS_WAN_DMZ_USER UBIOS_WAN_VPN_USER"
for C in $CHAINS; do
ip6tables -L "$C" >/dev/null 2>&1 || continue
while ip6tables -D "$C" -m conntrack --ctstate NEW,INVALID -m comment --comment "BLOCK_WAN6_NEW_INVALID_TO_INTERNAL" -j DROP 2>/dev/null; do :; done
while ip6tables -D "$C" -m conntrack --ctstate NEW -j DROP 2>/dev/null; do :; done
ip6tables -I "$C" 1 -m conntrack --ctstate NEW,INVALID -m comment --comment "BLOCK_WAN6_NEW_INVALID_TO_INTERNAL" -j DROP
done
EOF
chmod +x /data/ipv6-wan-in-block.sh
Display More
Service und Timer:
cat >/etc/systemd/system/ipv6-wan-in-block.service <<'EOF'
[Unit]
Description=Block new inbound IPv6 WAN traffic to internal zones
After=network-online.target unifi.service
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/data/ipv6-wan-in-block.sh
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
EOF
cat >/etc/systemd/system/ipv6-wan-in-block.timer <<'EOF'
[Unit]
Description=Periodically reapply IPv6 WAN inbound block
[Timer]
OnBootSec=2min
OnUnitActiveSec=2min
Unit=ipv6-wan-in-block.service
[Install]
WantedBy=timers.target
EOF
systemctl daemon-reload
systemctl enable --now ipv6-wan-in-block.service ipv6-wan-in-block.timer
systemctl restart ipv6-wan-in-block.service
Display More
Prüfen:
for C in UBIOS_WAN_LAN_USER UBIOS_WAN_GUEST_USER UBIOS_WAN_DMZ_USER UBIOS_WAN_VPN_USER; do
echo "--- $C ---"
ip6tables -S "$C" | head -6
done
Erwartung:
-A UBIOS_WAN_LAN_USER -m conntrack --ctstate INVALID,NEW -m comment --comment BLOCK_WAN6_NEW_INVALID_TO_INTERNAL -j DROP
1.15 12) Finaler Check
echo "=== Services ==="
systemctl is-active \
ipv6-lan-prefix-apply.service \
ipv6-lan-prefix-apply.timer \
ipv6-dualwan-source-rules.service \
dnsmasq-ipv6-ra.service \
ipv6-wan-in-block.service \
ipv6-wan-in-block.timer
echo
echo "=== IPv6 Interfaces ==="
ip -6 -o addr show scope global | grep "2001:db8" | sort
echo
echo "=== IPv6 Rules ==="
ip -6 rule show | grep -E "101|102|2001:db8"
echo
echo "=== WAN Tables ==="
ip -6 route show table 201.eth9
ip -6 route show table 202.eth10
echo
echo "=== Firewall Runtime ==="
for C in UBIOS_WAN_LAN_USER UBIOS_WAN_GUEST_USER UBIOS_WAN_DMZ_USER UBIOS_WAN_VPN_USER; do
echo "--- $C ---"
ip6tables -S "$C" | head -6
done
Display More
Wenn alles passt, sieht es grob so aus:
Services: active
Bridge-Adressen: vorhanden
Source-Routing: vorhanden
RA/SLAAC: läuft
Client IPv6: funktioniert
WAN->intern NEW: DROP
1.16 13) Reboot-Test
Zum Schluss einmal rebooten und danach prüfen, ob alles wiederkommt:
Nach dem Neustart:
systemctl is-active ipv6-lan-prefix-apply.service ipv6-lan-prefix-apply.timer ipv6-dualwan-source-rules.service dnsmasq-ipv6-ra.service ipv6-wan-in-block.service ipv6-wan-in-block.timer
ip -6 -o addr show scope global | grep "2001:db8"
ip -6 rule show | grep -E "101|102"
for C in UBIOS_WAN_LAN_USER UBIOS_WAN_GUEST_USER UBIOS_WAN_DMZ_USER UBIOS_WAN_VPN_USER; do
echo "--- $C ---"
ip6tables -S "$C" | head -6
done
Wenn danach alles wieder da ist: passt.
1.17 Rollback
Falls man zurück will:
systemctl disable --now ipv6-lan-prefix-apply.timer ipv6-wan-in-block.timer
systemctl disable --now ipv6-lan-prefix-apply.service ipv6-wan-in-block.service dnsmasq-ipv6-ra.service
mv /data/ipv6-lan-prefix-apply.sh /data/ipv6-lan-prefix-apply.sh.disabled
mv /data/ipv6-wan-in-block.sh /data/ipv6-wan-in-block.sh.disabled
Danach UniFi neu provisionieren lassen und die Backups prüfen.
1.18 Anmerkung
Das ist nichts für "mal eben nebenbei". Direkt auf der UDM per CLI und Mongo zu arbeiten ist praktisch, aber man sollte schon wissen, was man da anfasst.
Bei mir läuft damit:
- IPv6 auf allen VLANs
- RA/SLAAC über dnsmasq
- DNS-Verteilung per RDNSS
- Dual-WAN Source-Routing
- Firewall-Schutz gegen WAN->LAN bei IPv6
FERTIG
![]()