LAN port isolation (port-based VLAN) on ASUS RT-AX88U with Asuswrt-Merlin 384.16
Recently I upgraded my 4-years-old AC68P to the 802.11ax (or WiFi 6, but I prefer the former) supported AX88U. Like my old AC68P, I flashed Asuswrt-Merlin on AX88U since day one.
However, one issue with Asuswrt (either the stock firmware or Merlin) is no built-in GUI support on VLANs. I really miss the good old days with OpenWrt, as you can configure VLANs in GUI. But OpenWrt won’t support AX88U in a foreseeable future (due to Broadcom’s closed source driver1), CLI seems to be the only solution.
Requirement
Pretty straightforward. There are 8 LAN ports on AX88U and I want to isolate LAN 2 from the remaining 7 ports. LAN 1 and LAN 3 through 8 will be in VLAN 1
, while LAN 2 will be isolated into VLAN 2
(guest network).
The traffic between two VLANs should be forbidden. But ideally, I want to allow one way traffic from VLAN 1
to VLAN 2
. So I can still access the cascading router (in VLAN 2
) from my computer (in VLAN 1
), in case I need to reconfigure the secondary router.
In addition, I prefer everything to be configured automatically. So for clients in VLAN 2
, the only thing is to plug in the ethernet cable and get connected to the Internet.
About AX88U
No robocfg
?
The first problem is that robocfg
is no more provided on AX88U (Broadcom’s HND platform). An alternative to robocfg
on HND platform seems to be vlanctl
2. However, after several hours of searching, trying and error, I believe vlanctl
can only create tagged VLAN, which unfortunately can’t satisfy my need.
But in case someone else has special demand on tagged VLAN, I recommend to read (in Chinese): 上海电信 TL-EP110 + RT-AC86U 实现观看 4K IPTV 无卡顿 (2019-10). This post describes how to set tagged VLANs up properly on AC86U (also a HND platform router).
Interface to Physical Port Mapping
Somehow I didn’t find it in the Internet. After playing with an ethernet cable on different ports, I figured out:
Interface | Physical Port |
---|---|
eth0 | WAN |
eth1 | LAN 4 |
eth2 | LAN 3 |
eth3 | LAN 2 |
eth4 | LAN 1 |
eth5 | Bridge of LAN 5 - 8 |
eth6 | 2.4 GHz Radio |
eth7 | 5 GHz Radio |
Note that eth5
seems to be a hardware bridge (BCM53134) of LAN 5 - 8. You may be able to ungroup it with ethctl
or ethswctl
, but I didn’t spend much time on them. My solution can only isolate LAN ports 1 - 4.
By default, eth1
~ eth7
are grouped in bridge br0
.
Note: My WAN Connection Type is Automatic IP. If you are using other types like PPPoE, you may need to replace eth0
to ppp0
in the below accordingly.
One-liner with ebtables
If you don’t care about separating ports into different subnets, there actually exists an one-line solution:
# eth3 maps to LAN port 2 on AX88U
ebtables -A FORWARD -i eth3 -o br0 -j DROP
Here eth3
maps to LAN 2. The layer 2 firewall ebtables
will essentially block access between eth3
and remaining interfaces in br0
.
Since eth3
is still in the same subnet as br0
, there is no need to worry about iptables
or DHCP. Everything will just be fine.
Note 1: If it doesn’t work, try ebtables -A FORWARD -i eth3 --logical-out br0 -j DROP
. Thanks to @MarianMaciag!
Note 2: If it still doesn’t work, try iptables
instead: iptables -A FORWARD -i eth3 -o br0 -j DROP
. The downside with iptables
is that only layer 3+ access is restricted.
Note 3: Also remember this only blocks access in one direction: eth3
to br0
. You need -i br0 -o eth3
(or --logical-in br0 -o eth3
) to block br0
to eth3
.
Note 4: If it again doesn’t work, go separate bridge approach below.
Better Approach with Separate Bridge
However, my goal is to associate two VLANs with two subnets. Say VLAN 1
with 192.168.50.0/24
and VLAN 2
with 192.168.150.0/24
.
Create Bridge br1
First, remove eth3
from br0
and create a new bridge br1
with eth3
:
# Delete those interfaces that we want to isolate from br0
brctl delif br0 eth3
# Create a new bridge br1 for our isolated interfaces
brctl addbr br1
brctl stp br1 on # STP to prevent bridge loops
brctl addif br1 eth3
Here we use bridge br1
for easier management. If you want to add isolate other LAN ports, you can simply add the corresponding interface to br1
. All rules on br1
will automatically apply to that port.
Now run brctl show
to verify the settings for 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
By default, br0
is assigned with 192.168.50.1/24
on AX88U, so we don’t have to worry about it
Then, assign 192.168.150.1/24
to br1
and bring it up:
# Set up the IPv4 address for br1 and bring it up
# Here we set the subnet to be 192.168.150.0/24
# IPv6 link local address will be assigned automatically
ifconfig br1 192.168.150.1 netmask 255.255.255.0
ifconfig br1 allmulti up
Finally, run ifconfig br1
to see if br1
is up:
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)
The IPv6 link local address is automatically generated by EUI-64.
Tip: Complete script can be found in Scripts & Configs: /jffs/scripts/services-start
.
Add iptables
Rules
First, allow new incoming connections from br1
to the router:
# Allow new incoming connections from br1
iptables -I INPUT -i br1 -m state --state NEW -j ACCEPT
But forbid accessing web UI and SSH from br1
:
# 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
Then, drop all forwarding packets from br1
:
# Forbid packets from br1 to be forwarded to other interfaces
iptables -I FORWARD -i br1 -j DROP
But allow packet forwarding inside br1
:
# But allow packet forwarding inside br1
iptables -I FORWARD -i br1 -o br1 -j ACCEPT
… also allow packet forwarding between br1
and eth0
(WAN):
# Allow packet forwarding between br1 and eth0 (WAN)
iptables -I FORWARD -i br1 -o eth0 -j ACCEPT
… also allow one-way traffic from br0
to br1
:
# 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
Finally, set up 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
Now run iptables -S INPUT
, iptables -S FORWARD
and iptables -t nat -S POSTROUTING
to verify that rules have been added:
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
# ... output omitted ...
-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
# ... output omitted ...
admin@ax88u:/# iptables -t nat -S POSTROUTING
-P POSTROUTING ACCEPT
# ... output omitted ...
-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
Tip: Complete scripts can be found below.
- Script for firewall: Scripts & Configs: /jffs/scripts/firewall-start.
- Script for NAT: Scripts & Configs: /jffs/scripts/nat-start.
Configure dnsmasq
for DHCPv4
Create an additional configuration file dnsmasq.conf.add
in /jffs/configs/
:
touch /jffs/configs/dnsmasq.conf.add
Similar to br0
, set up DHCPv4 ranges and options on br1
:
cat <<EOF >> /jffs/configs/dnsmasq.conf.add
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
EOF
Restart dnsmasq
to apply the config:
service restart_dnsmasq
Now run tail /tmp/syslog.log -n 50
to see if dnsmasq.conf.add
is loaded:
admin@ax88u:/# tail /tmp/syslog.log -n 50
# ... output omitted ...
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
# ... output omitted ...
What about IPv6?
Even in the scenario of stateless DHCPv6, hosts’ IPv6 addresses are still generated via SLAAC3 according to RFC 3736:
A node that uses stateless DHCP must have obtained its IPv6 addresses through some other mechanism, typically stateless address autoconfiguration (or SLAAC).
Since SLAAC uses EUI-64 algorithm, the subnet prefix is required to be shorter than /64
. That means to do subnetting with stateless DHCPv6, the IPv6 LAN prefix allocated from your ISP must be at least of /63
.
Prefix Hint for Shorter Subnet Prefix
Some ISPs allow you send prefix length hint to get a prefix shorter than /64
(fortunately Charter Spectrum does assign /56
). If you’re not sure about this, you can try the following steps.
By the way, my AX88U’s IPv6 configuration is:
Name | Value |
---|---|
Basic Config | |
Connection type | Native |
DHCP-PD | Enable |
IPv6 LAN Setting | |
Auto Configuration Setting | Stateless |
IPv6 DNS Setting | |
Connect to DNS Server automatically | Disable |
IPv6 DNS Server 1 | 2606:4700:4700::1111 |
IPv6 DNS Server 2 | 2606:4700:4700::1001 |
IPv6 DNS Server 3 | <leave blank> |
Auto Configuration Setting | |
Enable Router Advertisement | Enable |
By default, the DHCPv6 client odhcp6c
on AX88U wouldn’t send prefix hint.
To force it do so4, first run
ps | sed -e 's/^.*\odhcp6c \(.*\)$/\1/;t;d' | head -n 1
to get the arguments of odhcp6c
:
-df -R -s /tmp/dhcp6c -N try -c <duid> -FP 0:<iaid> eth0
<duid>
and <iaid>
are linked to your router’s MAC address. Depends on your IPv6 configuration, there may be additional arguments like -r23
and -r24
, don’t forget to append them too.
So in the next two commands, replace them with the output you got:
# Kill existing odhcp6c.
killall odhcp6c
# Re-run odhcp6c with prefix hint 56.
# Replace `-c` and `-FP` arguments with the `ps` output you got.
odhcp6c -df -P56 -R -s /tmp/dhcp6c -N try -c <duid> -FP 0:<iaid> eth0
… and -P56
determines which prefix hint length to send.
Now you can run ifconfig br0
to verify if /56
subnet is assigned:
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)
If something similar is printed to your screen, Congratulations! You can proceed to the next section. Otherwise, you may have to stop here and try to set up stateful DHCPv6 server on br1
, which I didn’t try.
Tip: Complete script for re-running odhcp6c
automatically after reboot can be found in Scripts & Configs: /jffs/scripts/wan-event
.
Add ip6tables
Rules
Similiar to IPv4 solution, some necessary ip6tables
rules have to be added. But no need to deal with NAT (It’s IPv6!).
For INPUT
chain:
# Allow new incoming connections from br1
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
ip6tables -I INPUT -i br1 -p tcp --dport 80 -j DROP
ip6tables -I INPUT -i br1 -p tcp --dport 22 -j DROP
For FORWARD
chain:
# Forbid packets from br1 to be forwarded to other interfaces
ip6tables -I FORWARD -i br1 -j DROP
# But allow packet forwarding inside br1
ip6tables -I FORWARD -i br1 -o br1 -j ACCEPT
# Allow packet forwarding between br1 and eth0 (WAN)
ip6tables -I FORWARD -i br1 -o eth0 -j ACCEPT
# Allow one-way traffic from br0 to br1
ip6tables -I FORWARD -i br0 -o br1 -j ACCEPT
ip6tables -I FORWARD -i br1 -o br0 -m state \
--state RELATED,ESTABLISHED -j ACCEPT
Tip: Complete script can be found in Scripts & Configs: /jffs/scripts/firewall-start
.
Configure DHCPv6
By default, br0
will take the whole /56
subnet. We need to change it to /64
for subnetting on br1
.
First, check what prefix br0
has gotten from ISP with ip -6 addr show br0 scope global
:
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
You IPv6 address is different so remember to replace 2600:6c51:fake:n00b::1
with yours in following commands.
Then, reassign br0
with a /64
subnet:
# Remove /56 subnet. Replace 2600:6c51:fake:n00b::1 with yours.
ip -6 addr del 2600:6c51:fake:n00b::1/56 dev br0
# Assign /64 subnet. Replace 2600:6c51:fake:n00b::1 with yours.
ip -6 addr add 2600:6c51:fake:n00b::1/64 dev br0
Of course you can make a shorter subnet prefix for br0
, but I feel comfortable enough with /64
.
Next, run ip -6 addr show br0 scope global
again to check br0
’s new perfix:
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
The subnet for br1
is a little bit tricky, because dnsmasq
doesn’t support DHCPv6 Prefix Delegation (PD), quoting from dnsmasq
’s author5:
You’re right, though this (dnsmasq does not support replying to IA_PD (prefix delegation) requests) may change in the future.
The code of dnsmasq
shipped in Merlin6 also proves no support of DHCPv6 prefix delegation (option 25 and 267).
Without Prefix Delegation
If you have no cascading routers or don’t care about PD, dnsmasq
can be used as DHCPv6 server.
First, assign br1
with the sibling /64
subnet of br0
:
# Assign the sibling /64 subnet of br0 to br1.
# For example, if br0 is on aaaa:bbbb:cccc:dddd::1/64,
# then br1 should on aaaa:bbbb:cccc:ddde::1/64.
# Replace 2600:6c51:fake:n00c::1 with yours.
ip -6 addr add 2600:6c51:fake:n00c::1/64 dev br1
Then, set up DHCPv6 ranges and options on br1
:
cat <<EOF >> /jffs/configs/dnsmasq.conf.add
# 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,[::]
EOF
Restart dnsmasq
to apply the config:
service restart_dnsmasq
Now run tail /tmp/syslog.log -n 50
to see if dnsmasq.conf.add
is loaded:
admin@ax88u:/# tail /tmp/syslog.log -n 50
# ... output omitted ...
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
# ... output omitted ...
With Prefix Delegation
Otherwise, if you want cascading router to get IPv6 prefix automatically through PD, 6relayd
is required as a minimalistic DHCPv6 PD server.
Note: Don’t add DHCPv6 range and options to /jffs/configs/dnsmasq.conf.add
if you wish to use 6relayd
.
In the sever mode of 6realyd
8:
If there are non-local addresses assigned to the slave interface when a router solicitation is received, said prefixes are announced automatically for stateless autoconfiguration and also offered via stateful DHCPv6. If all prefixes are bigger than /64 all but the first /64 of these prefixes is offered via DHCPv6-PD to downstream routers.
… that is what we want. We’ll assign a /63
subnet for br1
, and 6relayd
will delegate a /64
subnet for the cascading router.
First, assign a 63
subnet for br1
(here we use the sibling /63
subnet of br0
):
# Assign the sibling /63 subnet of br0 to br1.
# Use a IPv6 subnet tool for the `/63` subnet range on `br0`.
# Replace 2600:6c51:fake:n00c::1 with yours.
ip -6 addr add 2600:6c51:fake:n00c::1/63 dev br1
Then, run 6relayd -v -d -S . br1
for DHCPv6 PD server:
# Automatic DHCPv6 server to delegate prefix on br1 in daemon
6relayd -v -d -S . br1
If there are incoming DHCPv6 requests, system log (cat /tmp/syslog.log | grep 6relayd
) should show:
# 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
… which means 6relayd
is working properly.
Now check the System Log -> IPv6 page in the cascading router (here my old AC68P is the cascading one):
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 |
If everything is good, LAN IPv6 Prefix should show 2600:6c51:fake:n00d::/64
.
Tip: Automatic script for setting up DHCPv6 PD can be found in Scripts & Configs: /jffs/scripts/dhcpc-event.
Scripts & Configs
In order to apply all settings after reboot, save the following scripts and configs into corresponding folders.
Note 1: Don’t forget to set Enable JFFS custom scripts and configs to Yes in Administration -> System.
Note 2: After uploading the following scripts, don’t forget to mark them as executable with 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
This script is only for 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
This script is only for 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
If you need DHCPv6-PD (with 6relayd
) for cascading router, or if you don’t care about IPv6:
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
Otherwise, DHCPv6 is done by dnsmasq on br1
:
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,[::]
References
-
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