在 AX88U + Merlin 384.16 上实现 LAN 端口隔离(静态 VLAN)
最近把手头用了四年多的 AC68P 升级成了支持 802.11ax (WiFi 6) 的 AX88U。不用多说,到手第一天就刷了 Merlin 梅林固件。
不过无论是原厂固件还是梅林固件,它们都没有提供原生的 GUI 划分 VLAN 的功能(相比之下,在 OpenWrt 上配置 VLAN 就很方便)。但是由于博通搞的闭源驱动1,OpenWrt 在有生之年大概都不会支持 AX88U。所以要在 AX88U 上配置 VLAN,只能通过命令行的方式。
需求
AX88U 上一共有 8 个 LAN 口,我想把 LAN 2 隔离出来,用做访客网络。换句话说,LAN 1, 3 - 8 划入 VLAN 1
, LAN 2 单独划入 VLAN 2
。
理论上,真正的隔离应该是禁止两个 VLAN 互相访问。但是考虑到万一需要在我的电脑(VLAN 1
)上重新配置二级路由器(VLAN 2
),还是得允许 VLAN 1
到 VLAN 2
的单向通信。
此外,上述提到的隔离对客户端应当是完全透明的:VLAN 2
里的客户端不需要额外的配置,只需要插上网线即可上网。
有关 AX88U
没有 robocfg
?
很不幸的是,AX88U(博通 HND 平台)移除了 robocfg
命令。搜索了一番之后,发现似乎 vlanctl
2 可以用来配置 VLAN。但是玩了一阵子,目测 vlanctl
只能搞 Tagged VLAN,没法实现静态 VLAN。
不过以防有人有这方面的需求,推荐阅读这篇文章:上海电信 TL-EP110 + RT-AC86U 实现观看 4K IPTV 无卡顿 (2019-10)。这篇文章介绍了如何在 AC86U(也是 HND 平台的路由器)上配置 Tagged VLAN。
以太网接口到物理端口的映射
在网上我没搜到相关的信息,不过经过若干次网线插拔之后,总结出来这张表:
以太网接口 | 物理端口 |
---|---|
eth0 | WAN |
eth1 | LAN 4 |
eth2 | LAN 3 |
eth3 | LAN 2 |
eth4 | LAN 1 |
eth5 | LAN 5 - 8 的网桥 |
eth6 | 2.4 GHz 无线 |
eth7 | 5 GHz 无线 |
这里注意 eth5
应该是通过 BCM53134 组的硬件网桥(LAN 5 - 8)。使用 etchtl
和 ethswctl
或许能解散这个网桥,不过我没试验。所以,下文提到的解决方案只能用于隔离 LAN 1 - 4。
此外,AX88U 默认把 eth1
~ eth7
组成了网桥 br0
。
注意:我的 WAN 联机类型 是 动态 IP。如果你用的是 PPPoE 的话,可能需要看情况把下文提到的 eth0
换成 ppp0
。
一条命令(ebtables
)的解决方案
如果不需要给两个 VLAN 划分独立子网的话,只需要一条 ebtables
命令就可以了:
# 在 AX88U 上,eth3 是物理端口 LAN 2
ebtables -A FORWARD -i eth3 -o br0 -j DROP
这里的 eth3
是物理端口 LAN 2。这条命令使用了二层防火墙 ebtables
,用于阻隔 eth3
和 br0
之间的通信。由于 eth3
还在 br0
里,所以不需要额外配置 iptables
或者 DHCP。
基于网桥的解决方案
如果需要给两个 VLAN 划分独立的子网的话,就需要创建新的网桥了。下文以 VLAN 1
使用 192.168.50.0/24
、VLAN 2
使用 192.168.150.0/24
为例。
新建网桥 br1
首先,从 br0
里移除 eth3
,创建 br1
并把 eth3
加入 br1
:
# 从 br0 中删除需要隔离的端口
brctl delif br0 eth3
# 把需要隔离的端口加入 br1
brctl addbr br1
brctl stp br1 on # 启用 STP 协议防止环路,避免网络风暴
brctl addif br1 eth3
这里新建网桥 br1
而不是直接使用 eth3
是为了方便管理:如果以后想把其他端口也隔离进 VLAN 2
,只需要把对应的端口移出 br0
并加入 br1
即可。由于下文提到的所有规则都是针对 br1
的,所以新加入的端口不需要额外的设置。
执行 brctl show
验证 br1
已正确创建:
admin@ax88u:/# brctl show
bridge name bridge id STP enabled interfaces
br0 8000.a85e45fakeid yes eth1
eth2
eth4
eth5
eth6
eth7
br1 8000.a85e45fakeid yes eth3
然后,给 br1
设置 IPv4 地址 192.168.150.1/24
,并启用 br1
:
# 设置 br1 的 IPv4 地址并启用 br1
# 这里使用的 IPv4 子网是 192.168.150.0/24
# 系统会自动设置 IPv6 链路本地地址
ifconfig br1 192.168.150.1 netmask 255.255.255.0
ifconfig br1 allmulti up
由于 AX88U 默认给 br0
分配了 192.168.50.1/24
,所以不需要设置 br0
。
最后,执行 ifconfig br
验证 br1
的 IPv4 地址:
admin@ax88u:/# ifconfig br1
br1 Link encap:Ethernet HWaddr A8:5E:45:00:FA:KE
inet addr:192.168.150.1 Bcast:192.168.150.255 Mask:255.255.255.0
inet6 addr: fe80::aa5e:45ff:fe00:fake/64 Scope:Link
UP BROADCAST RUNNING ALLMULTI MULTICAST MTU:1500 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
系统会使用 EUI-64 算法自动为 br1
生成 IPv6 链路本地地址。
提示:完整的脚本可以在 脚本与配置文件:/jffs/scripts/services-start
中找到。
添加 iptables
规则
首先,允许来自 br1
的传入连接:
# 允许来自 br1 的传入连接
iptables -I INPUT -i br1 -m state --state NEW -j ACCEPT
但是禁止 br1
访问路由器 Web UI 和 SSH:
# 禁止 br1 访问路由器 Web UI 和 SSH
iptables -I INPUT -i br1 -p tcp --dport 80 -j DROP
iptables -I INPUT -i br1 -p tcp --dport 22 -j DROP
然后,不转发来自 br1
的数据包:
# 不转发来自 br1 的数据包
iptables -I FORWARD -i br1 -j DROP
但是允许 br1
内部的转发:
# 但是允许 br1 内部的转发
iptables -I FORWARD -i br1 -o br1 -j ACCEPT
也允许 br1
的数据包转发至 eth0
(WAN):
# 允许 br1 的数据包转发至 eth0 (WAN)
iptables -I FORWARD -i br1 -o eth0 -j ACCEPT
也允许 br0
到 br1
的单向通信:
# 允许 br0 到 br1 的单向通信
iptables -I FORWARD -i br0 -o br1 -j ACCEPT
iptables -I FORWARD -i br1 -o br0 -m state \
--state RELATED,ESTABLISHED -j ACCEPT
最后,配置 br1
所属子网 192.168.150.0/24
的 NAT:
iptables -t nat -A POSTROUTING -s 192.168.150.0/24 -d 192.168.150.0/24 \
-o br1 -j MASQUERADE
分别执行 iptables -S INPUT
,iptables -S FORWARD
和 iptables -t nat -S POSTROUTING
验证相应的规则已添加:
admin@ax88u:/# iptables -S INPUT
-P INPUT ACCEPT
-A INPUT -i br1 -p tcp -m tcp --dport 22 -j DROP
-A INPUT -i br1 -p tcp -m tcp --dport 80 -j DROP
-A INPUT -i br1 -m state --state NEW -j ACCEPT
# ... 省略多余输出 ...
-A INPUT -j DROP
admin@ax88u:/# iptables -S FORWARD
-P FORWARD DROP
-A FORWARD -i br1 -o br0 -m state --state RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -i br0 -o br1 -j ACCEPT
-A FORWARD -i br1 -o eth0 -j ACCEPT
-A FORWARD -i br1 -o br1 -j ACCEPT
-A FORWARD -i br1 -j DROP
# ... 省略多余输出 ...
admin@ax88u:/# iptables -t nat -S POSTROUTING
-P POSTROUTING ACCEPT
# ... 省略多余输出 ...
-A POSTROUTING -s 192.168.50.0/24 -d 192.168.50.0/24 -o br0 -j MASQUERADE
-A POSTROUTING -s 192.168.150.0/24 -d 192.168.150.0/24 -o br1 -j MASQUERADE
提示:
- 完整防火墙配置脚本:脚本与配置文件:/jffs/scripts/firewall-start
- 完整 NAT 配置脚本:脚本与配置文件:/jffs/scripts/nat-start
配置 DHCPv4 (dnsmasq
)
在 /jffs/configs/
中创建配置文件 dnsmasq.conf.add
:
touch /jffs/configs/dnsmasq.conf.add
比照 br0
,为 br1
设置 DHCPv4 的地址池和选项:
cat <<EOF >> /jffs/configs/dnsmasq.conf.add
interface=br1
# DHCPv4 地址池: 192.168.150.2 - 192.168.150.254, 子网掩码: 255.255.255.0
# DHCPv4 租约时间: 86400 秒 (1 天)
dhcp-range=br1,192.168.150.2,192.168.150.254,255.255.255.0,86400s
# DHCPv4 路由器 (option 3): 192.168.150.1
dhcp-option=br1,3,192.168.150.1
EOF
重启 dnsmasq
以应用设置:
service restart_dnsmasq
执行 tail /tmp/syslog.log -n 50
验证 dnsmasq.conf.add
已被正确加载:
admin@ax88u:/# tail /tmp/syslog.log -n 50
# ... 省略多余输出 ...
Apr 14 20:49:22 rc_service: service 15995:notify_rc restart_dnsmasq
Apr 14 20:49:22 dnsmasq[1149]: exiting on receipt of SIGTERM
Apr 14 20:49:22 custom_config: Appending content of /jffs/configs/dnsmasq.conf.add.
Apr 14 20:49:22 dnsmasq[15998]: started, version 2.81rc4-33-g7558f2b cachesize 1500
Apr 14 20:49:22 dnsmasq[15998]: asynchronous logging enabled, queue limit is 5 messages
Apr 14 20:49:22 dnsmasq-dhcp[15998]: DHCP, IP range 192.168.150.2 -- 192.168.150.254, lease time 1d
Apr 14 20:49:22 dnsmasq-dhcp[15998]: DHCP, IP range 192.168.50.2 -- 192.168.50.254, lease time 1d
# ... 省略多余输出 ...
配置 IPv6
本文使用的是无状态 DHCPv6 (DHCPv6 stateless),主机的 IPv6 地址是通过 SLAAC3 自动生成的。
由于 SLAAC 使用了 EUI-64 算法,这要求 IPv6 前缀长度必须小于 /64
。所以要使用下文提到的解决方案,ISP 分配的 IPv6 LAN 前缀长度必须小于等于 /63
。
通过前缀提示 (Prefix Hint) 获取更短的前缀
某些 ISP 允许通过发送前缀长度提示获取小于 /64
的前缀。例如,我这里的 ISP Charter Spectrum 就允许通过前缀提示获取 /56
的前缀。
我的 AX88U 上 IPv6 的设置是:
名称 | 值 |
---|---|
基本设置 | |
联机类型 | Native |
DHCP-PD | 启用 |
IPv6 内部网络设置 | |
自动配置设置 | Stateless |
IPv6 DNS 设置 | |
自动接上 DNS 服务器 | 关闭 |
IPv6 DNS 服务器 1 | 2606:4700:4700::1111 |
IPv6 DNS 服务器 2 | 2606:4700:4700::1001 |
IPv6 DNS 服务器 3 | 留空 |
自动配置设置 | |
是否启动路由广播 | 启用 |
AX88U 上的 DHCPv6 客户端 odhcp6c
默认不会发送前缀提示。要强制 odhcp6c
发送前缀提示4,首先执行:
ps | sed -e 's/^.*\odhcp6c \(.*\)$/\1/;t;d' | head -n 1
获取系统运行 odhcp6c
的命令:
-df -R -s /tmp/dhcp6c -N try -c <duid> -FP 0:<iaid> eth0
<duid>
和 <iaid>
与路由器的 MAC 地址有关。取决于 IPv6 设置,可能还有类似 -r23
、-r24
的参数,在下面的命令里也要加上这些参数。
对应替换 <duid>
和 <iaid>
,执行下面的两条命令:
# 结束现有的 odhcp6c
killall odhcp6c
# 重新运行 odhcp6c,前缀提示长度为 56
# 对应替换 -c 和 -FP 的参数 <duid> 和 <iaid>
odhcp6c -df -P56 -R -s /tmp/dhcp6c -N try -c <duid> -FP 0:<iaid> eth0
这里 -P56
中的 56
即是要发送的前缀提示长度。
执行 ifconfig br0
验证 br0
已被分配到 /56
的 IPv6 全局地址:
admin@ax88u:/# ifconfig br0
br0 Link encap:Ethernet HWaddr A8:5E:45:00:FA:KE
inet addr:192.168.50.1 Bcast:192.168.50.255 Mask:255.255.255.0
inet6 addr: 2600:6c51:fake:n00b::1/56 Scope:Global
inet6 addr: fe80::aa5e:45ff:fe00:fake/64 Scope:Link
UP BROADCAST RUNNING ALLMULTI MULTICAST MTU:1500 Metric:1
RX packets:4241 errors:0 dropped:0 overruns:0 frame:0
TX packets:3714 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:829133 (809.7 KiB) TX bytes:1787334 (1.7 MiB)
如果没看到类似的输出,那下文的解决方案并不适合你,但你可以在 br1
上设置有状态 DHCPv6 (DHCPv6 stateful)。
提示:完整的脚本可以在 脚本与配置文件:/jffs/scripts/wan-event
中找到。
添加 ip6tables
规则
ip6tables
的规则类似上文 iptables
的规则,但是不需要设置 NAT。
INPUT
链:
# 允许来自 br1 的传入连接
ip6tables -I INPUT -i br1 -j ACCEPT # br0 的默认规则
ip6tables -I INPUT -i br1 -m state --state NEW -j ACCEPT
# 禁止 br1 访问路由器 Web UI 和 SSH
ip6tables -I INPUT -i br1 -p tcp --dport 80 -j DROP
ip6tables -I INPUT -i br1 -p tcp --dport 22 -j DROP
FORWARD
链:
# 不转发来自 br1 的数据包
ip6tables -I FORWARD -i br1 -j DROP
# 但是允许 br1 内部的转发
ip6tables -I FORWARD -i br1 -o br1 -j ACCEPT
# 也允许 `br1` 的数据包转发至 `eth0` (WAN)
ip6tables -I FORWARD -i br1 -o eth0 -j ACCEPT
# 也允许 `br0` 到 `br1` 的单向通信
ip6tables -I FORWARD -i br0 -o br1 -j ACCEPT
ip6tables -I FORWARD -i br1 -o br0 -m state \
--state RELATED,ESTABLISHED -j ACCEPT
提示:完整的脚本可以在 脚本与配置文件:/jffs/scripts/firewall-start
中找到。
配置 DHCPv6
默认情况下,br0
会使用整个 /56
的子网。如果要为 br1
划分子网,则需要改变 br0
的前缀长度。
首先,执行 ip -6 addr show br0 scope global
获取 ISP 分配的 IPv6 LAN 前缀:
admin@ax88u:/# ip -6 addr show br0 scope global
22: br0: <BROADCAST,MULTICAST,ALLMULTI,UP,LOWER_UP> mtu 1500
inet6 2600:6c51:fake:n00b::1/56 scope global
valid_lft forever preferred_lft forever
你拿到的前缀肯定跟这里的是不一样的,所以执行下面的命令时别忘了对应替换。
然后,为 br0
重新设置 /64
的前缀长度:
# 删除 /56 前缀,把 2600:6c51:fake:n00b::1 换成你的
ip -6 addr del 2600:6c51:fake:n00b::1/56 dev br0
# 设置 /64 前缀,把 2600:6c51:fake:n00b::1 换成你的
ip -6 addr add 2600:6c51:fake:n00b::1/64 dev br0
当然这里可以为 br0
设置更短的前缀长度,但是我觉得 /64
已经够用了。
再次执行 ip -6 addr show br0 scope global
,检查 br0
的新前缀:
admin@ax88u:/# ip -6 addr show br0 scope global
22: br0: <BROADCAST,MULTICAST,ALLMULTI,UP,LOWER_UP> mtu 1500
inet6 2600:6c51:fake:n00b::1/64 scope global
valid_lft forever preferred_lft forever
由于 dnsmasq
并不支持 DHCPv6 前缀代理(DHCPv6 Prefix Delegation, DHCPv6-PD)5 6 所需的 option 25 和 26 7,下面分两种情况讨论。
不使用 DHCPv6-PD
如果不接二级路由器的话,倒是可以用 dnsmasq
当 DHCPv6 服务器。
首先,为 br1
设置 br0
的邻接 /64
前缀:
# 为 br1 设置 br0 的邻接 /64 前缀
# 比如 br0 的前缀为 aaaa:bbbb:cccc:dddd::1/64,
# 则 br1 的前缀为 aaaa:bbbb:cccc:ddde::1/64.
# 把 2600:6c51:fake:n00c::1 换成你的
ip -6 addr add 2600:6c51:fake:n00c::1/64 dev br1
然后,为 br1
设置 DHCPv6 地址池和选项:
cat <<EOF >> /jffs/configs/dnsmasq.conf.add
# DHCPv6 路由广播间隔:10 秒, 路由器生命周期:600 秒
ra-param=br1,10,600
# DHCPv6 地址池:通过 br1 的前缀构造
# DHCPv6 前缀长度:64, 模式:无状态 DHCPv6
# DHCPv6 租约时间:600 秒 (10 分钟)
dhcp-range=br1,::,constructor:br1,ra-stateless,64,600
# DHCPv6 DNS (option 23):从路由器继承
dhcp-option=br1,option6:23,[::]
EOF
重启 dnsmasq 以应用设置:
service restart_dnsmasq
执行 tail /tmp/syslog.log -n 50 验证 dnsmasq.conf.add 已被正确加载:
admin@ax88u:/# tail /tmp/syslog.log -n 50
# ... 省略多余输出 ...
Apr 14 20:49:22 rc_service: service 15995:notify_rc restart_dnsmasq
Apr 14 20:49:22 dnsmasq[1149]: exiting on receipt of SIGTERM
Apr 14 20:49:22 custom_config: Appending content of /jffs/configs/dnsmasq.conf.add.
Apr 14 20:49:22 dnsmasq[15998]: started, version 2.81rc4-33-g7558f2b cachesize 1500
Apr 14 20:49:22 dnsmasq[15998]: asynchronous logging enabled, queue limit is 5 messages
Apr 15 18:09:01 dnsmasq-dhcp[18800]: DHCP, IP range 192.168.150.2 -- 192.168.150.254, lease time 1d
Apr 15 18:09:01 dnsmasq-dhcp[18800]: DHCP, IP range 192.168.50.2 -- 192.168.50.254, lease time 1d
Apr 15 18:09:01 dnsmasq-dhcp[18800]: DHCPv6 stateless on br0
Apr 15 18:09:01 dnsmasq-dhcp[18800]: router advertisement on br0
Apr 15 18:09:01 dnsmasq-dhcp[18800]: DHCPv6 stateless on 2600:6c51:fake:n00b::, constructed for br0
Apr 15 18:09:01 dnsmasq-dhcp[18800]: router advertisement on 2600:6c51:fake:n00b::, constructed for br0
Apr 15 18:09:01 dnsmasq-dhcp[18800]: DHCPv6 stateless on br1
Apr 15 18:09:01 dnsmasq-dhcp[18800]: router advertisement on br1
Apr 15 18:09:01 dnsmasq-dhcp[18800]: DHCPv6 stateless on 2600:6c51:fake:n00b::, constructed for br1
Apr 15 18:09:01 dnsmasq-dhcp[18800]: router advertisement on 2600:6c51:fake:n00c::, constructed for br1
Apr 15 18:09:01 dnsmasq-dhcp[18800]: IPv6 router advertisement enabled
# ... 省略多余输出 ...
使用 DHCPv6-PD
如果需要在二级路由器上启用 DHCPv6-PD,则应该使用 6relayd
作为 DHCPv6-PD 服务器。
注意:如果使用 6relayd
,不要在 /jffs/configs/dnsmasq.conf.add
中添加 DHCPv6 相关的设置。
在 6relayd
8 的服务器模式下,它会通过 DHCPv6-PD 把第二个 /64
前缀分配给二级路由器。所以,只需要为 br1
分配一个 /63
的前缀。
这里使用的是 br0
的邻接 /63
前缀:
# 为 br1 设置 br0 的邻接 /63 前缀
# 把 2600:6c51:fake:n00c::1 换成你的
ip -6 addr add 2600:6c51:fake:n00c::1/63 dev br1
然后,执行 6relayd -v -d -S . br1
启动 DHCPv6-PD 服务器:
# 监听于 br1 上的 DHCPv6-PD 服务器
6relayd -v -d -S . br1
如果收到 DHCPv6 请求的话,系统日志 (cat /tmp/syslog.log | grep 6relayd
) 应该显示:
# admin@ax88u:/# cat /tmp/syslog.log | grep 6relayd
Apr 15 18:20:00 6relayd[1765]: Got DHCPv6 request
Apr 15 18:20:01 6relayd[1765]: Got DHCPv6 request
最后,在二级路由器上检查 IPv6 设置(系统记录 -> IPv6):
IPv6 Connection Type | Native with DHCP-PD |
---|---|
WAN IPv6 Address | 2600:6c51:fake:n00c::fake |
WAN IPv6 Gateway | fe80::aa5e:45ff:fe00:fake |
LAN IPv6 Address | 2600:6c51:fake:n00d::1/64 |
LAN IPv6 Link-Local Address | fe80::a62:66ff:fe01:fake/64 |
DHCP-PD | Enabled |
LAN IPv6 Prefix | 2600:6c51:fake:n00d::/64 |
DNS Address | 2600:6c51:fake:n00c::1 |
如果一切正常,LAN IPv6 Prefix 应该显示 2600:6c51:fake:n00d::/64
。
提示: 自动配置 6relayd
的脚本可以在 脚本与配置文件:/jffs/scripts/dhcpc-event 中找到。
脚本与配置文件
把下文中的脚本与配置文件上传到路由器对应的目录里,即可在路由器每次重启时自动配置 VLAN。
注意 1:路由器 系统管理 -> 系统设置 中的 Enable JFFS custom scripts and configs 需要设为 是。
注意 2:上传完毕之后,运行 chmod +x /jffs/scripts/*
为这些脚本赋予可执行权限。
/jffs/scripts/services-start
#!/bin/sh
# Make sure the script is indeed invoked
touch /tmp/000-services-start
# Physical port to interface map:
# eth0 WAN
# eth1 LAN 4
# eth2 LAN 3
# eth3 LAN 2
# eth4 LAN 1
# eth5 Bridge of LAN 5, LAN 6, LAN 7, LAN 8
# eth6 2.4 GHz Radio
# eth7 5 GHz Radio
# Delete those interfaces that we want to isolate from br0
logger -t "isolate_port" "services-start: deleting LAN 2 (eth3) from br0"
brctl delif br0 eth3
# Create a new bridge br1 for isolated interfaces
logger -t "isolate_port" "services-start: creating br1 with LAN 2 (eth3)"
brctl addbr br1
brctl stp br1 on # STP to prevent bridge loops
brctl addif br1 eth3
# Set up the IPv4 address for br1
# Here we set the subnet to be 192.168.150.0/24
# IPv6 link local address will be assigned automatically
logger -t "isolate_port" "services-start: setting up IPv4 address for br1"
ifconfig br1 192.168.150.1 netmask 255.255.255.0
ifconfig br1 allmulti up
logger -t "isolate_port" "services-start: all done"
date >> /tmp/000-services-start
/jffs/scripts/firewall-start
#!/bin/sh
# Make sure the script is indeed invoked
touch /tmp/000-firewall-start
logger -t "isolate_port" "firewall-start: applying INPUT rules for br1"
# Allow new incoming connections from br1
iptables -I INPUT -i br1 -m state --state NEW -j ACCEPT
ip6tables -I INPUT -i br1 -j ACCEPT # Same rule as br0 by default
ip6tables -I INPUT -i br1 -m state --state NEW -j ACCEPT
# Only forbid br1 access the web UI and SSH of the main router
iptables -I INPUT -i br1 -p tcp --dport 80 -j DROP
iptables -I INPUT -i br1 -p tcp --dport 22 -j DROP
ip6tables -I INPUT -i br1 -p tcp --dport 80 -j DROP
ip6tables -I INPUT -i br1 -p tcp --dport 22 -j DROP
logger -t "isolate_port" "firewall-start: applying FORWARD rules for br1"
# Forbid packets from br1 to be forwarded to other interfaces
iptables -I FORWARD -i br1 -j DROP
ip6tables -I FORWARD -i br1 -j DROP
# But allow packet forwarding inside br1
iptables -I FORWARD -i br1 -o br1 -j ACCEPT
ip6tables -I FORWARD -i br1 -o br1 -j ACCEPT
# Allow packet forwarding between br1 and eth0 (WAN)
iptables -I FORWARD -i br1 -o eth0 -j ACCEPT
ip6tables -I FORWARD -i br1 -o eth0 -j ACCEPT
# Allow one-way traffic from br0 to br1
iptables -I FORWARD -i br0 -o br1 -j ACCEPT
iptables -I FORWARD -i br1 -o br0 -m state \
--state RELATED,ESTABLISHED -j ACCEPT
ip6tables -I FORWARD -i br0 -o br1 -j ACCEPT
ip6tables -I FORWARD -i br1 -o br0 -m state \
--state RELATED,ESTABLISHED -j ACCEPT
logger -t "isolate_port" "firewall-start: all done"
date >> /tmp/000-firewall-start
/jffs/scripts/nat-start
#!/bin/sh
# Make sure the script is indeed invoked
touch /tmp/000-nat-start
logger -t "isolate_port" "nat-start: applying POSTROUTING rules for br1"
# NAT inside 192.168.150.0/24 on br1
iptables -t nat -A POSTROUTING -s 192.168.150.0/24 -d 192.168.150.0/24 \
-o br1 -j MASQUERADE
logger -t "isolate_port" "nat-start: all done"
date >> /tmp/000-nat-start
/jffs/scripts/wan-event
该脚本仅用于 IPv6。
#!/bin/sh
PD_PREFIX=56 # Change to which works for your ISP
RETRY=10
# Only run for connected event (the old wan-start)
if [ ! "$2" = "connected" ]; then
exit 0
fi
# Make sure the script is indeed invoked
touch /tmp/000-wan-event-connected
# ipv6_service = dhcp6 means Connection Type: Native,
# also we require DHCP PD to be enabled
if [ "$(nvram get ipv6_service)" = "dhcp6" ] && \
[ "$(nvram get ipv6_dhcp_pd)" = "1" ]; then
# Try to find odhcp6c
i=0;
while [ $i -lt $RETRY ]; do
# Get odhcp6c's pid. If there are multiple instances (unlikely),
# we use the smallest one.
PID=$(pidof odhcp6c | tr ' ' '\n' | head -n 1)
CMDLINE=/proc/$PID/cmdline
# Found odhcp6c?
if [ ! -z "$PID" ] && [ -f "$CMDLINE" ]; then
COMMAND="$(tr '\0' ' ' < /proc/$PID/cmdline)"
logger -t "isolate_port" "wan-event[connected]:" \
"found odhcp6c, PID: $PID, command: $COMMAND"
# The first 11 chars should be "odhcp6c -df"
PREFIX=$(echo $COMMAND | cut -c1-11)
if [ "$PREFIX" = "odhcp6c -df" ]; then
# There is a space between "odhcp6c -df" and remaining arguments.
# So we start from the 13rd char.
ARGS=$(echo $COMMAND | cut -c13-)
# Check if arguments start with -P$PD_PREFIX
ARG1=$(echo $ARGS | cut -c1-$(expr length "$PD_PREFIX" + 2))
if [ ! "$ARG1" = "-P$PD_PREFIX" ]; then
# Prefix length (-P$PD_PREFIX)
COMMAND="odhcp6c -df -P$PD_PREFIX $ARGS"
logger -t "isolate_port" "wan-event[connected]:" \
"re-run odhcp6c with prefix hint $PD_PREFIX: $COMMAND"
killall odhcp6c
eval $COMMAND
else
logger -t "isolate_port" "wan-event[connected]:" \
"odhcp6c already started with prefix hint $PD_PREFIX"
fi
else
logger -t "isolate_port" "wan-event[connected]:" \
"odhcp6c command prefix mismatch!" \
"found '$PREFIX', expects 'odhcp6c -df'"
fi
# We break from here once found the `odhcp6c` process
break
else
i=$(($i+1))
logger -t "isolate_port" "wan-event[connected]:" \
"odhcp6c not found ($i/$RETRY)"
# Hope we're lucky next time
sleep 1
fi
done
else
logger -t "isolate_port" "wan-event[connected]: DHCPv6 PD not enabled"
fi
logger -t "isolate_port" "wan-event[connected]: all done"
date >> /tmp/000-wan-event-connected
/jffs/scripts/dhcpc-event
该脚本仅用于 IPv6。
#!/bin/sh
link_local_ipv6() {
# Get IPv6 link local address from br0 (with the prefix length part)
echo $(ip -6 addr show br0 scope link |\
sed -e's/^.*inet6 \([^ ]*\).*$/\1/;t;d')
}
setup_br1() {
# ipv6_service = dhcp6 means Connection Type: Native,
# also we require DHCP PD to be enabled
if [ "$(nvram get ipv6_service)" = "dhcp6" ] && \
[ "$(nvram get ipv6_dhcp_pd)" = "1" ]; then
# Wait for udhcpc (udhcpc.c: bound6 -> add_ip6_lanaddr).
# We need to the updated ipv6 prefix from nvram.
sleep 2
# Allocated IPv6 prefix from ISP
PREFIX=$(nvram get ipv6_prefix)
if [ -z "$PREFIX" ]; then
logger -t "isolate_port" "dhcpc-event: empty IPv6 prefix"
else
PD_PREFIX=$(nvram get ipv6_prefix_length)
if [ "$PD_PREFIX" -ge 63 ]; then
# We need at least 3 /64 subnets, /63 only gives 2 /64 subnet
logger -t "isolate_port" "dhcpc-event: IPv6 prefix" \
"(/$PD_PREFIX) is too long, at least /62 requried"
else
if [ "$PD_PREFIX" -le 48 ]; then
# Shorter than /49?
# br1 will on a:b:c:2::/63 (br0 is on a:b:c:0::/64)
BR1_PREFIX="$(echo $PREFIX | cut -d':' -f1-3):2::"
else
# /49 to /62?
# br1 will on a:b:c:dddf::/63 (br0 is on a:b:c:dddd::/64)
BR0_NET_ID=$(echo $PREFIX | cut -d':' -f4)
BR1_NET_ID=$(printf "%x" $((0x$BR0_NET_ID + 0x2)))
BR1_PREFIX="$(echo $PREFIX | cut -d':' -f1-3):${BR1_NET_ID}::"
fi
# Clean up br0
ip -6 route flush dev br0
ip -6 addr flush dev br0 scope global
# Assign br0 with the first /64 subnet (instead the /56 one)
# ipv6_rtr_addr is the default router's IPv6 address
logger -t "isolate_port" "dhcpc-event: set prefix" \
"$(nvram get ipv6_rtr_addr)/64 to br0"
ip -6 addr add "$(nvram get ipv6_rtr_addr)/64" dev br0
# Clean up br1 (note we also remove link local address)
ip -6 route flush dev br1
ip -6 addr flush dev br1
# Re-add link local address
# So route to fe80::/64 will be added back
# Note this is VERY important for 6relayd
# Otherwise 6relayd will throw network unreachable error
# Because route to fe80::/64 doesn't exist
ip -6 addr add $(link_local_ipv6) dev br1
# Assign br1 with a /63 subnet, so the cascading router
# will be on the first /64 subnet and it will also get
# the second /64 prefix via DHCPv6-PD by 6relayd
logger -t "isolate_port" "dhcpc-event: set prefix" \
"${BR1_PREFIX}1/63 to br1"
ip -6 addr add "${BR1_PREFIX}1/63" dev br1
# Re-run 6relayd
logger -t "isolate_port" "dhcpc-event: re-run 6relayd on br1"
killall 6relayd
# Automatic DHCPv6 server to delegate prefix on br1 in daemon
6relayd -v -d -S . br1
fi
fi
fi
}
teardown_br1() {
# ipv6_service = dhcp6 means Connection Type: Native,
# also we require DHCP PD to be enabled
if [ "$(nvram get ipv6_service)" = "dhcp6" ] && \
[ "$(nvram get ipv6_dhcp_pd)" = "1" ]; then
logger -t "isolate_port" "dhcpc-event:" \
"flush IPv6 route and IPv6 address on br1"
# br0 will be handled by udhcpc,
# we only need to care about br1
# Note we also remove link local address
ip -6 route flush dev br1
ip -6 addr flush dev br1
# Re-add link local address
# So route to fe80::/64 will be added back
# Note this is VERY important for 6relayd
# Otherwise 6relayd will throw network unreachable error
# Because route to fe80::/64 doesn't exist
ip -6 addr add $(link_local_ipv6) dev br1
fi
}
# Make sure the script is indeed invoked
touch /tmp/000-dhcpc-event
# Adapted from odhcp6c-example-script.sh
(
flock 9
case "$1" in
bound)
teardown_br1
setup_br1
;;
informed|updated|rebound|ra-updated)
setup_br1
;;
stopped|unbound)
teardown_br1
;;
started)
teardown_br1
;;
esac
) 9>/tmp/odhcp6c.lock.br1
rm -f /tmp/odhcp6c.lock.br1
date >> /tmp/000-dhcpc-event
/jffs/configs/dnsmasq.conf.add
不需要 DHCPv6:
interface=br1
# DHCPv4 range: 192.168.150.2 - 192.168.150.254, netmask: 255.255.255.0
# DHCPv4 lease time: 86400s (1 day)
dhcp-range=br1,192.168.150.2,192.168.150.254,255.255.255.0,86400s
# DHCPv4 router (option 3): 192.168.150.1
dhcp-option=br1,3,192.168.150.1
需要 DHCPv6,并且需要为二级路由器启用 DHCPv6-PD:
interface=br1
# DHCPv4 range: 192.168.150.2 - 192.168.150.254, netmask: 255.255.255.0
# DHCPv4 lease time: 86400s (1 day)
dhcp-range=br1,192.168.150.2,192.168.150.254,255.255.255.0,86400s
# DHCPv4 router (option 3): 192.168.150.1
dhcp-option=br1,3,192.168.150.1
(你没看错,和不用 DHCPv6 是一样的)
需要 DHCPv6,但是不需要 DHCPv6-PD:
interface=br1
# DHCPv4 range: 192.168.150.2 - 192.168.150.254, netmask: 255.255.255.0
# DHCPv4 lease time: 86400s (1 day)
dhcp-range=br1,192.168.150.2,192.168.150.254,255.255.255.0,86400s
# DHCPv4 router (option 3): 192.168.150.1
dhcp-option=br1,3,192.168.150.1
# DHCPv6 RA interval: 10s, router lifetime: 600s
ra-param=br1,10,600
# DHCPv6 range: whole subnet, constructing from br1's prefix
# DHCPv6 prefix length: 64, mode: Stateless DHCPv6
# DHCPv6 lease time: 600s (10 minutes)
dhcp-range=br1,::,constructor:br1,ra-stateless,64,600
# DHCPv6 DNS (option 23): inherit from the router
dhcp-option=br1,option6:23,[::]
参考文献
-
slh, Reply: ASUS RT-AX88U Router. ↩
-
u128393, RT-AC86U VLAN 配置 - vlanctl 篇. ↩
-
Wikipedia, Stateless address autoconfiguration. ↩
-
buddyp, Reply: IPv6 with Prefix Delegation. ↩
-
Simon Kelley, dnsmasq: dhcp6-protocol.h. ↩
-
O. Troan, R. Droms, RFC 3633 - IPv6 Prefix Options for Dynamic Host Configuration Protocol (DHCP) version 6. ↩
-
Steven Barth, 6relayd - Embedded DHCPv6/RA Server & Relay. ↩
Comments Go back to the top