Last active
July 7, 2025 17:39
-
-
Save fbraz3/3db625adf81e2ea335968d933e6d61b1 to your computer and use it in GitHub Desktop.
[OpenWRT] Shell Script to Create a Fully Isolated Guest Network with Bandwidth Control
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/sh | |
# | |
# FOR USE IN OPENWRT | |
# This script creates a guest network fully isolated from the main one. | |
# Tested on a Xiaomi AX3000T router; should work on any OpenWRT-powered router. | |
# | |
# - Ensure the Wi-Fi interfaces retain their default names (radio0 and radio1). | |
# - For enable download/upload limits, you MUST install the sqm-scripts package on your OpenWRT router. | |
# - For enable roaming (aka wifi mesh): | |
# - You must install the xxd package on your OpenWRT router. | |
# - Ensure the wireless interface supports 802.11r and 802.11k. | |
# - All router must have the same roaming domain | |
# | |
# Info about OpenWRT support on AX3000T: | |
# https://openwrt.org/inbox/toh/xiaomi/ax3000t | |
# | |
# Guest interface configuration | |
GUEST_INTERFACE='guest' | |
GUEST_NETWORK='192.168.6.1/24' | |
GUEST_DNS_SERVER='8.8.8.8,8.8.4.4' # DNS server for the guest network, split by commas if multiple (e.g. 8.8.8.8,8.8.4.4) | |
# The interface which serves as the internet gateway for the guest network; it could be your WAN or LAN interface depending on your setup. | |
DEST_INTERFACE='wan' # Change this to 'lan' if you want the guest network to access the local LAN. | |
DEST_RANGE='192.168.1.1/24' # The range of the destination interface, its needed for the firewall rules to isolate the guest network. | |
# Wi-Fi SSID and Password | |
RADIO_SSID='my-guest-wifi' # Choose your Wi-Fi network name here | |
RADIO_KEY='my-guest-password' # BE SURE TO CHANGE THE PASSWORD!!! | |
RADIO1_ENABLE=1 # Set to 1 to enable radio1; comment out to use only the radio0 interface. | |
# Reminder: In order to use bandwidth control, you must install the sqm-scripts package on your OpenWRT router. | |
SQM_ENABLE=1 # Set to 1 to enable SQM (Smart Queue Management) for bandwidth control; comment out to disable. | |
SQM_DOWNLOAD_LIMIT='10000' # Download limit in Kbps, e.g. 10000 for 10 Mbps | |
SQM_UPLOAD_LIMIT='10000' # Upload limit in Kbps, e.g. 10000 for 10 Mbps | |
# If you want to open ports on your WAN/LAN from the guest network, just add them below | |
ALLOW_PORTS='53,tcp 53,udp' # Allow DNS port, change or comment out if you don't want to allow requests from the guest network. | |
# Reminder: you must install the xxd package and all routers must have the same roaming domain. | |
ROAMING_ENABLE=1 # Set to 1 to enable roaming (aka wifi mesh); comment out to disable. | |
ROAMING_DOMAIN='a1c2' # MUST be a 4 character hexadecimal string, e.g. 'abcd'. | |
# | |
# Configuration below this line should not require modification. | |
# | |
# Define radio interfaces | |
RADIO0_IFACE='radio0' # radio0 is the default interface for the 2.4GHz band | |
RADIO1_IFACE='radio1' # radio1 is the default interface for the 5GHz band | |
# Configure network bridge | |
uci -q delete network.${GUEST_INTERFACE}_dev | |
uci set network.${GUEST_INTERFACE}_dev='device' | |
uci set network.${GUEST_INTERFACE}_dev.type='bridge' | |
uci set network.${GUEST_INTERFACE}_dev.name="br-${GUEST_INTERFACE}" | |
# Configure network interface | |
uci -q delete network.${GUEST_INTERFACE} | |
uci set network.${GUEST_INTERFACE}='interface' | |
uci set network.${GUEST_INTERFACE}.proto='static' | |
uci set network.${GUEST_INTERFACE}.device="br-${GUEST_INTERFACE}" | |
uci set network.${GUEST_INTERFACE}.ipaddr="${GUEST_NETWORK}" | |
uci commit network | |
service network restart | |
# Configure dhcp server | |
uci -q delete dhcp.${GUEST_INTERFACE} | |
uci set dhcp.${GUEST_INTERFACE}='dhcp' | |
uci set dhcp.${GUEST_INTERFACE}.interface="${GUEST_INTERFACE}" | |
uci set dhcp.${GUEST_INTERFACE}.start='100' | |
uci set dhcp.${GUEST_INTERFACE}.limit='150' | |
uci set dhcp.${GUEST_INTERFACE}.leasetime='1h' | |
uci add_list dhcp.${GUEST_INTERFACE}.dhcp_option="6,${GUEST_DNS_SERVER}" | |
uci commit dhcp | |
service dnsmasq restart | |
# Add firewall group | |
uci -q delete firewall.${GUEST_INTERFACE} | |
uci set firewall.${GUEST_INTERFACE}='zone' | |
uci set firewall.${GUEST_INTERFACE}.name="${GUEST_INTERFACE}" | |
uci set firewall.${GUEST_INTERFACE}.network="${GUEST_INTERFACE}" | |
uci set firewall.${GUEST_INTERFACE}.input='REJECT' | |
uci set firewall.${GUEST_INTERFACE}.output='ACCEPT' | |
uci set firewall.${GUEST_INTERFACE}.forward='REJECT' | |
if [ ! -z "${ALLOW_PORTS}" ]; then | |
for ITEM in $ALLOW_PORTS; do | |
PORT=$(echo $ITEM | cut -d',' -f1) | |
PROTO=$(echo $ITEM | cut -d',' -f2) | |
[ "$PROTO" != "tcp" ] && [ "$PROTO" != "udp" ] && PROTO="tcp" | |
RULE_NAME="${GUEST_INTERFACE}_open_${PORT}_${PROTO}" | |
uci -q delete firewall.${RULE_NAME} | |
uci set firewall.${RULE_NAME}='rule' | |
uci set firewall.${RULE_NAME}.name="${RULE_NAME}" | |
uci set firewall.${RULE_NAME}.src="${GUEST_INTERFACE}" | |
uci set firewall.${RULE_NAME}.dest="${DEST_INTERFACE}" | |
uci set firewall.${RULE_NAME}.dest_port="${PORT}" | |
uci set firewall.${RULE_NAME}.proto="${PROTO}" | |
uci set firewall.${RULE_NAME}.target='ACCEPT' | |
done | |
fi | |
# FW Rule: Allow DHCP | |
uci -q delete firewall.${GUEST_INTERFACE}_dhcp | |
uci set firewall.${GUEST_INTERFACE}_dhcp='rule' | |
uci set firewall.${GUEST_INTERFACE}_dhcp.name="${GUEST_INTERFACE}_dhcp" | |
uci set firewall.${GUEST_INTERFACE}_dhcp.src="${GUEST_INTERFACE}" | |
uci set firewall.${GUEST_INTERFACE}_dhcp.dest_port='67-68' | |
uci set firewall.${GUEST_INTERFACE}_dhcp.target='ACCEPT' | |
uci set firewall.${GUEST_INTERFACE}_dhcp.proto='tcp udp' | |
# FW Rule: Isolate network from local LAN | |
uci -q delete firewall.${GUEST_INTERFACE}_iso | |
uci set firewall.${GUEST_INTERFACE}_iso='rule' | |
uci set firewall.${GUEST_INTERFACE}_iso.name="${GUEST_INTERFACE}_iso" | |
uci set firewall.${GUEST_INTERFACE}_iso.src="${GUEST_INTERFACE}" | |
uci set firewall.${GUEST_INTERFACE}_iso.dest="${DEST_INTERFACE}" | |
uci set firewall.${GUEST_INTERFACE}_iso.dest_ip="${DEST_RANGE}" | |
uci set firewall.${GUEST_INTERFACE}_iso.target='REJECT' | |
# Add forwarding to lan | |
uci -q delete firewall.${GUEST_INTERFACE}_forward | |
uci set firewall.${GUEST_INTERFACE}_forward='forwarding' | |
uci set firewall.${GUEST_INTERFACE}_forward.src="${GUEST_INTERFACE}" | |
uci set firewall.${GUEST_INTERFACE}_forward.dest="${DEST_INTERFACE}" | |
uci commit firewall | |
service firewall restart | |
# Create radio0 wifi network | |
uci -q delete wireless.${GUEST_INTERFACE} | |
uci set wireless.${GUEST_INTERFACE}=wifi-iface | |
uci set wireless.${GUEST_INTERFACE}.device="${RADIO0_IFACE}" | |
uci set wireless.${GUEST_INTERFACE}.mode='ap' | |
uci set wireless.${GUEST_INTERFACE}.network="${GUEST_INTERFACE}" | |
uci set wireless.${GUEST_INTERFACE}.ssid="${RADIO_SSID}" | |
uci set wireless.${GUEST_INTERFACE}.encryption='sae-mixed' | |
uci set wireless.${GUEST_INTERFACE}.key="${RADIO_KEY}" | |
uci set wireless.${GUEST_INTERFACE}.isolate='1' | |
# Create radio1 wifi network | |
if [ ! -z "${RADIO1_ENABLE}" ]; then | |
uci -q delete wireless.${GUEST_INTERFACE}_1 | |
uci set wireless.${GUEST_INTERFACE}_1=wifi-iface | |
uci set wireless.${GUEST_INTERFACE}_1.device="${RADIO1_IFACE}" | |
uci set wireless.${GUEST_INTERFACE}_1.mode='ap' | |
uci set wireless.${GUEST_INTERFACE}_1.network="${GUEST_INTERFACE}" | |
uci set wireless.${GUEST_INTERFACE}_1.ssid="${RADIO_SSID}" | |
uci set wireless.${GUEST_INTERFACE}_1.encryption='sae-mixed' | |
uci set wireless.${GUEST_INTERFACE}_1.key="${RADIO_KEY}" | |
uci set wireless.${GUEST_INTERFACE}_1.isolate='1' | |
fi | |
if [ ! -z "${ROAMING_ENABLE}" ]; then | |
NAS_ID=$(echo "${GUEST_INTERFACE}" | awk '{print toupper($0)}') | |
NAS_ID="${NAS_ID}_$(head -c 1 /dev/urandom | xxd -p -l 12 | awk '{print toupper($0)}')" | |
R1_KEY_HOLDER=$(head -c 6 /dev/urandom | xxd -p -l 12) | |
uci set wireless.${GUEST_INTERFACE}.ieee80211r='1' | |
uci set wireless.${GUEST_INTERFACE}.nasid="${NAS_ID}" | |
uci set wireless.${GUEST_INTERFACE}.mobility_domain="${ROAMING_DOMAIN}" | |
uci set wireless.${GUEST_INTERFACE}.ft_over_ds='0' | |
uci set wireless.${GUEST_INTERFACE}.r1_key_holder="${R1_KEY_HOLDER}" | |
uci set wireless.${GUEST_INTERFACE}.pmk_r1_push='1' | |
uci set wireless.${GUEST_INTERFACE}.ieee80211k='1' | |
uci set wireless.${GUEST_INTERFACE}.time_advertisement='2' | |
uci set wireless.${GUEST_INTERFACE}.time_zone='<-03>3' | |
uci set wireless.${GUEST_INTERFACE}.bss_transition='1' | |
uci set wireless.${GUEST_INTERFACE}.ocv='0' | |
if [ ! -z "${RADIO1_ENABLE}" ]; then | |
NAS_ID="${NAS_ID}_1" | |
R1_KEY_HOLDER=$(head -c 6 /dev/urandom | xxd -p -l 12) | |
uci set wireless.${GUEST_INTERFACE}_1.ieee80211r='1' | |
uci set wireless.${GUEST_INTERFACE}_1.nasid="${NAS_ID}" | |
uci set wireless.${GUEST_INTERFACE}_1.mobility_domain="${ROAMING_DOMAIN}" | |
uci set wireless.${GUEST_INTERFACE}_1.ft_over_ds='0' | |
uci set wireless.${GUEST_INTERFACE}_1.r1_key_holder="${R1_KEY_HOLDER}" | |
uci set wireless.${GUEST_INTERFACE}_1.pmk_r1_push='1' | |
uci set wireless.${GUEST_INTERFACE}_1.ieee80211k='1' | |
uci set wireless.${GUEST_INTERFACE}_1.time_advertisement='2' | |
uci set wireless.${GUEST_INTERFACE}_1.time_zone='<-03>3' | |
uci set wireless.${GUEST_INTERFACE}_1.bss_transition='1' | |
uci set wireless.${GUEST_INTERFACE}_1.ocv='0' | |
fi | |
fi | |
uci commit wireless | |
wifi reload | |
# Configure SQM (Smart Queue Management) for bandwidth control | |
if [ ! -z "${SQM_ENABLE}" ]; then | |
uci del sqm.${GUEST_INTERFACE} | |
uci set sqm.${GUEST_INTERFACE}=queue | |
uci set sqm.${GUEST_INTERFACE}.enabled='1' | |
uci set sqm.${GUEST_INTERFACE}.interface="br-${GUEST_INTERFACE}" | |
uci set sqm.${GUEST_INTERFACE}.download="${SQM_UPLOAD_LIMIT}" # upload from the guest network to the internet | |
uci set sqm.${GUEST_INTERFACE}.upload="${SQM_DOWNLOAD_LIMIT}" # download from internet to the guest network | |
uci set sqm.${GUEST_INTERFACE}.debug_logging='0' | |
uci set sqm.${GUEST_INTERFACE}.verbosity='5' | |
uci set sqm.${GUEST_INTERFACE}.qdisc="cake" | |
uci set sqm.${GUEST_INTERFACE}.script='piece_of_cake.qos' | |
uci set sqm.${GUEST_INTERFACE}.linklayer='none' | |
uci commit sqm | |
service sqm restart | |
fi |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment