OpenWrt
Network Configuration in OpenWrt
From UCI to netifd, from bridge/VLAN/DSA to firewall and Wi-Fi: how a single option line in /etc/config/network becomes packets moving through the kernel.
Conventions and Important Caveats
This article targets engineers who already know basic Linux networking. The goal is to explain architecture, configuration model, runtime behavior, and troubleshooting methodology, not to be a click-through tutorial.
Statements that depend on the environment are tagged:
| Tag | Meaning |
|---|---|
[version-dependent] | Syntax/behavior differs between releases (19.07 / 21.02 / 22.03 / 23.05 / 24.10 / snapshot) |
[platform-dependent] | Depends on the target/SoC (MediaTek, Qualcomm, Broadcom, Realtek…) |
[driver-dependent] | Depends on the Wi-Fi/Ethernet driver (mt76, ath9k/ath10k/ath11k, vendor driver…) |
[DSA-dependent] | True only for DSA devices, not for swconfig |
[package-dependent] | Depends on additionally installed packages (mwan3, relayd, prplMesh, wpad variant…) |
Interface names are not universal.
eth0,eth1,wan,lan1…lan4,br-lan,eth0.1are only examples. They come from the device tree, the driver, and the target’s/etc/board.json. An MT7621 DSA router, an AR9344 swconfig router, and an IPQ807x using a vendor SSDK can have completely different port names and switch models. Always inspect the real device before copying a config:
cat /etc/board.json # network section generated by board_detect
ubus call system board # model, release, kernel
ip -br link # netdevs that actually exist
ls /sys/class/net # alternative
Examples in this article use DSA-style names (lan1..lan4, wan) because that is the modern model (since 21.02). Where a comparison is useful, there is a separate swconfig example.
Introduction
How OpenWrt differs from closed router firmware
Closed firmware is typically a web UI bolted onto a monolithic daemon and a few kernel modules. Config lives in an NVRAM blob or a binary file, and “WAN/LAN” are hard-coded concepts.
OpenWrt is different. It is a Linux distribution for embedded devices, and everything you configure reduces to Linux primitives: bridge, vlan, ip route, nftables, hostapd. The OpenWrt layer does three things:
- Stores your intent as declarative config (UCI).
- Provides daemons (
netifd,fw4,dnsmasq,odhcpd,hostapd) that translate intent into kernel state. - Provides reload/hotplug mechanisms that converge runtime state toward the config when events occur (link up/down, DHCP lease, new prefix).
The consequence: “WAN” and “LAN” are nothing special at the kernel level. They are just logical interface names that the firewall zones, DHCP, and the default route happen to attach to.
UCI and /etc/config/*
UCI (Unified Configuration Interface) is a text config store, one file per subsystem:
config <section-type> '<optional-name>'
option <key> '<value>'
list <key> '<value>'
- Named sections (
config interface 'lan') and anonymous sections (config device). Anonymous sections are referenced via@device[0]. optionis a single value,listis a list.- Declarative: you describe the desired state, not the steps.
uci show network # dump everything
uci get network.lan.ipaddr
uci set network.lan.ipaddr='192.168.10.1/24'
uci changes # staged (not yet committed)
uci commit network # write to /etc/config/network
/etc/init.d/network reload # ask netifd to apply
uci set only writes to a staging area (/tmp/.uci). uci commit writes the file. Commit is not apply. You must reload the relevant service, otherwise the runtime does not change.
Configuration vs runtime state
This is the most common source of confusion when debugging:
| Configuration | Runtime state | |
|---|---|---|
| Lives in | /etc/config/* (flash/overlay) | Kernel + RAM: netdevs, FDB, routes, nft ruleset, conntrack |
| Survives reboot | Yes | No |
| Written by | You / LuCI / scripts | netifd, kernel, odhcp6c, udhcpc… |
| Inspect with | uci show, cat | ip, bridge, nft, ubus |
Runtime can differ from config because: a reload hasn’t happened, the link isn’t up, DHCP hasn’t answered, a proto handler failed, or a script/hotplug intervened. Many routes/IPs you see in ip route are not in /etc/config/network because DHCP/PD/PPP generated them.
Mental model
Configuration /etc/config/{network,wireless,firewall,dhcp}
↓
UCI libuci, ubus "uci", LuCI (rpcd)
↓
OpenWrt services procd → netifd, fw4, dnsmasq, odhcpd, hostapd
↓
Linux networking netlink: link / addr / route / bridge vlan / nftables
↓
Runtime state netdev, FDB, FIB, conntrack, station table
When something breaks, ask in this order: Is the config right → does the service understand it correctly (ubus) → is the kernel right (ip/bridge/nft) → do packets actually flow correctly (tcpdump). Section 20 turns this methodology into a checklist.
OpenWrt Network Architecture
The layers
Applications / Services (ssh, uhttpd, ntpd, VPN daemons)
↓
DNS / DHCP / VPN / QoS (dnsmasq, odhcpd, wireguard, sqm)
↓
Firewall / nftables (fw4 → table inet fw4)
↓
L3 / IP interfaces (address, prefix, neighbor)
↓
Routing (FIB, ip rule, multiple tables)
↓
L2 / Bridge / VLAN (br-lan, 802.1Q, bridge vlan filtering)
↓
Ethernet / Wi-Fi (MAC: DSA/switch, mac80211/cfg80211)
↓
Hardware / Driver (SoC MAC, switch chip, radio)
Who does what
| Component | Role | Reads | Writes |
|---|---|---|---|
| UCI | Config store | /etc/config/* | — |
| procd | Init + process supervisor; respawn, triggers reload when config changes | init scripts | — |
| netifd | Network interface daemon: manages devices and interfaces, runs proto handlers | /etc/config/network (+ wireless) | netlink (link/addr/route), ubus objects |
| proto handlers | static, dhcp (udhcpc), dhcpv6 (odhcp6c), pppoe (pppd)… | invoked by netifd | report state back to netifd |
| fw4 | Translates /etc/config/firewall into an nftables ruleset | /etc/config/firewall, ubus network | nft (table inet fw4) |
| dnsmasq | DNS forwarder/cache + DHCPv4 server (default) | /etc/config/dhcp | UDP 53/67 |
| odhcpd | DHCPv6 server, Router Advertisement, NDP relay [version-dependent] can also do DHCPv4 | /etc/config/dhcp | RA, DHCPv6 |
| hostapd / wpa_supplicant | Controls AP/STA/mesh (auth, association, keys) | files generated by the netifd wireless handler | nl80211 |
| kernel | bridge, 8021q, routing, netfilter, mac80211 | netlink | — |
| driver | MAC/PHY, switch (DSA), radio | — | hardware |
Main flow:
/etc/config/network ──► netifd ──┬─► netlink: create br-lan, br-lan.10, assign IPs, routes
├─► proto handler: udhcpc / odhcp6c / pppd
├─► ubus: network.interface.*, network.device
└─► hotplug: /etc/hotplug.d/iface/ (ifup/ifdown)
│
├─► fw4: reload zones when interfaces change
└─► your scripts
/etc/config/wireless ─► netifd (wireless handler, e.g. mac80211.sh)
└─► generates hostapd/wpa_supplicant config ─► nl80211 ─► driver
└─► puts the wlan interface into the bridge according to `network`
Two things to remember:
- netifd is the center. It is both the config interpreter and the state machine.
ubus call network.interface dumpis the source of truth for what netifd thinks, whileipis the source of truth for what the kernel has. - fw4 and dnsmasq/odhcpd do not discover topology on their own. They depend on logical interface names and information supplied by netifd. Changing an interface without reloading firewall/dhcp is a classic source of bugs.
Apply semantics
/etc/init.d/network reload # netifd computes a diff, changes only what changed
/etc/init.d/network restart # full down/up, drops your SSH session if you're on the LAN
/etc/init.d/firewall reload # or: fw4 reload
/etc/init.d/dnsmasq restart
wifi reload # applies /etc/config/wireless
ifup wan / ifdown wan # per interface
ubus call network reload # equivalent to reload
[version-dependent] Some changes (adding/removing bridge-vlan, changing vlan_filtering, adding a port to a running bridge) may need restart instead of reload. For changes that can cut off your own management session, use a rollback mechanism: LuCI uses ubus call uci apply with a rollback timer. When working by hand, back up /etc/config first and arm a job that restores it automatically.
Important OpenWrt Configuration Files
| File | Purpose | Main components |
|---|---|---|
/etc/config/network | L2/L3 topology | device, interface, bridge-vlan, route, rule, switch* (legacy) |
/etc/config/wireless | Wi-Fi | wifi-device (radio), wifi-iface (SSID/AP/STA/mesh), security |
/etc/config/firewall | Security/NAT | defaults, zone, forwarding, rule, redirect |
/etc/config/dhcp | DHCP/DNS/IPv6 RA | dnsmasq, dhcp (per interface), host, odhcpd |
The files reference each other by name
SSID "Guest"
↓
wifi-iface (wireless: option network 'guest')
↓
network interface (network: config interface 'guest', option device 'br-lan.30')
↓
device (br-lan.30 = VLAN 30 on bridge br-lan)
↓
VLAN 30 / 192.168.30.0/24
↓
dhcp pool (dhcp: config dhcp 'guest', option interface 'guest')
↓
firewall zone (firewall: config zone / list network 'guest')
This chain runs on the logical interface name (guest), not the device name (br-lan.30):
wireless:option network 'guest'dhcp:option interface 'guest'firewall:list network 'guest'
Misspelling an interface name, or renaming it without fixing all three places, is a very common mistake. The SSID still comes up, but clients get no DHCP or no Internet.
Other files you will meet
/etc/config/system (hostname, timezone, LEDs), /etc/config/uhttpd, /etc/config/dropbear, /etc/config/rpcd, /etc/config/mwan3 [package-dependent], /etc/config/sqm [package-dependent].
The netifd Object Model
Before the syntax, you need to understand that netifd manages two different kinds of objects.
device ──► L2 object: a netdev (eth, bridge, 8021q, macvlan, veth…)
interface ──► L3/protocol object: address, gateway, DNS, routes, proto handler, zone
- A device has a netdev lifecycle: create → configure (MAC, MTU, bridge member) → up.
- An interface is a logical network: it attaches to a device, runs a proto (
static,dhcp…), and is the unit that firewall/DHCP/wireless reference.
Interface state (ubus call network.interface.lan status): up, pending, available, autostart. If up: false, pending: true, it is usually because the link is down or the device doesn’t exist yet. Check layer 1/2 before suspecting the IP config.
/etc/config/network
Device vs Interface
config device ──► Layer 2 (the "pipe": bridge, VLAN, port settings)
config interface ──► Layer 3 / protocol (what is "plugged into the pipe": IP, routes, DHCP client…)
config device
option name 'br-lan'
option type 'bridge'
list ports 'lan1'
list ports 'lan2'
config interface 'lan'
option device 'br-lan'
option proto 'static'
option ipaddr '192.168.1.1/24'
Why separate them?
- Different lifecycles. A bridge can exist and forward frames without an IP. An interface can flap (DHCP renew, PPP reconnect) without tearing down the bridge.
- One device, several interfaces and vice versa, e.g.
wan(IPv4 DHCP) andwan6(DHCPv6) on the same physical device. - Devices carry L2 properties (MTU, MAC,
igmp_snooping,stp,vlan_filtering), while interfaces carry L3 properties (ipaddr,gateway,metric,peerdns). Merging the two is the origin of the old, hard-to-read syntax. - Firewall/DHCP/wireless need an “L3 handle” to reference. That handle is the interface, not the port.
[version-dependent] Before 21.02, bridges were declared inside the interface (option type 'bridge', option ifname 'eth0.1 eth0.2') and ifname was the main option name. From 21.02, config device + option device is the official approach; ifname on interfaces is deprecated but still accepted in some releases. Don’t mix the two syntaxes in one file.
The @ device alias. option device '@wan' means “use the current L3 device of interface wan”. This matters for wan6 when WAN is PPPoE (the real L3 device is pppoe-wan, not wan).
config interface 'wan6'
option device '@wan'
option proto 'dhcpv6'
Commonly used config device options:
| Option | Meaning |
|---|---|
name, type | Netdev name; bridge, 8021q, 8021ad, macvlan, veth… |
ports (list) | Bridge members |
macaddr, mtu, txqueuelen | Netdev properties |
stp, forward_delay, priority | Bridge STP |
igmp_snooping, multicast, multicast_querier | Multicast [version-dependent] defaults differ |
vlan_filtering | Enable VLAN-aware bridge (see section 6) |
bridge_empty | Allow a bridge with no ports (for Wi-Fi-only bridges) |
ipv6, acceptlocal, promisc | Flags |
Commonly used config interface options: proto, device, ipaddr, gateway, dns, metric, peerdns, defaultroute, ip6assign, ip6hint, delegate, auto, disabled, ip4table, ip6table, force_link, mtu.
ipaddr accepts CIDR (192.168.1.1/24) in recent releases. Older releases use separate ipaddr + netmask [version-dependent].
Complete mapping: UCI → kernel
config device 'br-lan' (bridge, ports lan1 lan2)
→ netifd: ip link add br-lan type bridge
ip link set lan1 master br-lan ; ip link set lan2 master br-lan
config interface 'lan' (device br-lan, static 192.168.1.1/24)
→ netifd: ip addr add 192.168.1.1/24 dev br-lan
ip route add 192.168.1.0/24 dev br-lan (proto kernel, auto-generated)
→ ubus: network.interface.lan {up, ipv4-address, route, dns-server…}
→ hotplug: ifup lan → fw4 updates zones, dnsmasq/odhcpd rebind
Verify with ip -d link show br-lan, ip addr show br-lan, ubus call network.interface.lan status.
Protocol types
proto | Run by | Use when |
|---|---|---|
static | netifd (built-in) | LAN, static WAN IP, management IP |
dhcp | udhcpc (dhcp.sh) | WAN from an ISP/upstream router; AP mode managed via DHCP |
dhcpv6 | odhcp6c | IPv6 from the ISP: IA_NA, IA_PD, SLAAC |
pppoe | pppd + rp-pppoe.so | DSL/GPON/FTTH where the ISP requires PPPoE |
none | — | L2-only interfaces: VLAN trunk, or a “network handle” for a wifi-iface that needs no IP |
relay | relayd [package-dependent] | Pseudo-bridge for Wi-Fi clients (relayd) |
[package-dependent] Other protos such as wireguard, 3g/qmi/mbim/ncm (modems), gre, vxlan, 6in4… are supplied by packages.
static: LAN or WAN with a fixed IP.
config interface 'wan'
option device 'wan'
option proto 'static'
option ipaddr '203.0.113.2/24'
option gateway '203.0.113.1'
list dns '203.0.113.53'
gateway produces default via 203.0.113.1 dev wan. dns is written to the resolv file used by netifd/dnsmasq, but peerdns only applies to protos that produce DNS dynamically.
dhcp: the ordinary WAN.
config interface 'wan'
option device 'wan'
option proto 'dhcp'
option metric '10'
option peerdns '0'
list dns '1.1.1.1'
option hostname '*' # don't send a hostname, or set a specific one
option clientid '' # some ISPs require client-id/vendorid
From DHCP, netifd receives: IP, gateway (option 3), DNS (option 6), lease. The default route gets the interface’s metric. If the ISP requires vendorid, sendopts, or options 60/61, configure them here.
dhcpv6: see section 16.
pppoe: see section 8. The ppp interface creates the device pppoe-wan. Options such as mtu, keepalive 'lcp-echo-failure lcp-echo-interval', ipv6 'auto', service, ac are commonly used.
none: an “L3 handle” without L3.
config interface 'trunk'
option device 'lan4'
option proto 'none'
Use it when you need an interface name for firewall/wireless but L3 is handled elsewhere. For example a Wi-Fi AP bridged into a VLAN, or just to “bring up” a port.
relay [package-dependent]: an L3 pseudo-bridge for cases where a Wi-Fi STA can’t be bridged.
config interface 'repeater_bridge'
option proto 'relay'
option ipaddr '192.168.1.2'
list network 'lan'
list network 'wwan'
See section 14 for when to use it and its drawbacks.
Bridge
A Linux bridge is a software L2 switch (if a DSA switch chip offloads it, the actual forwarding is done by hardware). Concepts to know:
- Broadcast domain: all bridge ports (without VLAN filtering) share one broadcast domain. ARP, DHCP discover, and mDNS reach every port, including Wi-Fi.
- MAC learning & FDB: the bridge learns
source MAC → portfrom received frames and stores it in the FDB; frames with a known DA are unicast to the right port, while unknown DAs are flooded.bridge fdb showdisplays this table; entries have an ageing time (default 300 s). - Bridge ports: Ethernet netdevs, DSA user ports, wlan AP interfaces, wlan 4addr STAs, bonds, veths, VLAN sub-interfaces…
- The bridge itself is a netdev (
br-lan) and acts as the bridge’s “CPU port”. An IP onbr-lanis the router’s own address, so traffic from a port to that IP goes into the input chain.
config device
option name 'br-lan'
option type 'bridge'
list ports 'lan1'
list ports 'lan2'
LAN1 ----\
LAN2 -----+---- br-lan ---- (IP 192.168.1.1/24, router)
Wi-Fi ----/
Wi-Fi does not appear in list ports: netifd automatically adds the wlan interface to whichever bridge the logical interface referenced by option network lives on (see section 9). Don’t manually put wlan0 in ports.
A few properties worth knowing:
config device
option name 'br-lan'
option type 'bridge'
list ports 'lan1'
list ports 'lan2'
list ports 'lan3'
option stp '1' # only needed if loops are possible (multiple uplinks)
option igmp_snooping '1' # avoid flooding multicast (IPTV, mDNS) to Wi-Fi
option macaddr '02:00:00:00:00:01'
Pitfall: Two ports of the same bridge connected to the same downstream switch create an L2 loop and a broadcast storm. Enable STP or design a loop-free topology. Wi-Fi mesh and wired backhaul at the same time is a common example (section 15).
A complete sample config (DSA)
config interface 'loopback'
option device 'lo'
option proto 'static'
option ipaddr '127.0.0.1/8'
config globals 'globals'
option ula_prefix 'fd12:3456:789a::/48'
option packet_steering '1' # [platform-dependent] RPS/XPS
config device
option name 'br-lan'
option type 'bridge'
list ports 'lan1'
list ports 'lan2'
list ports 'lan3'
list ports 'lan4'
config interface 'lan'
option device 'br-lan'
option proto 'static'
option ipaddr '192.168.1.1/24'
option ip6assign '60'
config interface 'wan'
option device 'wan'
option proto 'dhcp'
config interface 'wan6'
option device '@wan'
option proto 'dhcpv6'
VLAN and DSA
802.1Q in 60 seconds
- 802.1Q inserts a 4-byte tag (TPID
0x8100, PCP, DEI, 12-bit VID) into the Ethernet frame. - Tagged: the frame carries the VID tag on the wire. Untagged: the frame has no tag; the switch assigns a VLAN to it according to the port’s PVID.
- Access port: untagged, belongs to one VLAN; end devices don’t know VLANs exist.
- Trunk port: carries several tagged VLANs (usually plus a native/untagged VLAN).
- A VLAN is a separate broadcast domain. Communication between VLANs requires L3 (a router).
Two worlds: swconfig (legacy) vs DSA (modern)
Legacy (swconfig) Modern (DSA)
SoC MAC (eth0) ──CPU port──► switch SoC MAC = "conduit" (eth0, rarely used directly)
│ │
switch0 (chip) ▼
├ ports 1..4 (no netdevs) lan1..lan4, wan = real netdevs
└ VLAN table inside the chip
│ bridge ──► bridge-vlan ──► DSA offload
swconfig ──► eth0.1, eth0.2 (the kernel bridge is where VLANs are configured)
swconfig | DSA | |
|---|---|---|
| Physical ports have their own netdev? | No (only eth0 + VLAN subifs eth0.N) | Yes (lan1, lan2, wan…) |
| Where VLANs are configured | config switch / config switch_vlan | config bridge-vlan on a VLAN-aware bridge |
| Port syntax | port numbers (0 1 2 3 6t), t = tagged (usually on the CPU port) | port names + flags (lan1:u*, lan4:t) |
| Kernel mechanism | out-of-tree/legacy switch API | bridge VLAN filtering + switchdev offload |
| Tools | swconfig dev switch0 show | bridge vlan, ip -d link |
| Release | common up to 19.07; some targets still use it later [platform-dependent] | 21.02 onward for many targets [platform-dependent] |
Do not use swconfig syntax to explain a DSA device. If your
/etc/config/networkcontainsconfig switch/config switch_vlan, it is swconfig. If it containsbridge-vlanand ports namedlanX, it is DSA. The two models are not interchangeable. Many old tutorials online are written for swconfig and will not work on DSA.
A swconfig example to show the difference (older device, illustrative port numbers, do not copy to DSA):
# --- LEGACY swconfig, NOT for DSA ---
config switch
option name 'switch0'
option reset '1'
option enable_vlan '1'
config switch_vlan
option device 'switch0'
option vlan '1'
option ports '1 2 3 4 0t' # 0 = CPU port (tagged), 1-4 = LAN
config switch_vlan
option device 'switch0'
option vlan '2'
option ports '5 0t' # 5 = WAN
config interface 'lan'
option device 'eth0.1' # VLAN 1 on the CPU port
option proto 'static'
option ipaddr '192.168.1.1/24'
[platform-dependent] Port count, CPU port position, and the t flag depend entirely on the chip (RTL8366, AR8327, MT7530 in swconfig mode, B53…). Read swconfig dev switch0 show and /etc/board.json.
[platform-dependent] On some modern SoCs (some Qualcomm IPQ60xx/IPQ807x lines using SSDK/NSS, or firmware built on a vendor SDK), the switch/bridge offload model is not upstream-standard DSA. VLAN configuration and hardware offload may differ. Check your target’s documentation.
DSA: the configuration model
In DSA every user port is a netdev. VLANs are configured on the bridge, and the kernel/driver pushes them down to the switch chip via switchdev.
- Conduit (formerly master): the netdev of the SoC MAC connected to the switch chip (e.g.
eth0). It must be UP but you do not put an IP on it.[platform-dependent]Automatic bring-up is handled by OpenWrt init/netifd. - User ports:
lan1..lanN,wan. - Bridge VLAN filtering: when enabled, the bridge is no longer a single broadcast domain; each frame is classified by VLAN (PVID/tag) and forwarded only to ports that are members of that VLAN.
config device
option name 'br-lan'
option type 'bridge'
option vlan_filtering '1'
list ports 'lan1'
list ports 'lan2'
list ports 'lan3'
list ports 'lan4'
config bridge-vlan
option device 'br-lan'
option vlan '10'
list ports 'lan1:u*'
list ports 'lan4:t'
config bridge-vlan
option device 'br-lan'
option vlan '20'
list ports 'lan4:t'
Flag meanings:
| Flag | Meaning | Details |
|---|---|---|
t | Tagged | Frames leaving this port carry the VID tag; frames entering must carry this VID tag to belong to the VLAN. Used for trunks. |
u | Untagged (egress untagged) | The tag is stripped on egress. Used for access ports. |
* | PVID | Untagged ingress frames on the port are assigned to this VLAN. |
u* | Untagged + PVID | A complete access port: egress untagged, ingress untagged → this VLAN |
| (no flag) | Default | [version-dependent] usually equivalent to untagged; state it explicitly to avoid ambiguity |
u without * means frames leave untagged, but untagged frames entering are not assigned to this VLAN. This is a very common one-way bug: the client receives the DHCP offer but its discover never arrives (or vice versa).
About VLAN 1: on a VLAN-filtering bridge, the bridge has a default VLAN (PVID 1) unless netifd/you configure otherwise. If you use VLAN 1 as the default LAN and add other VLANs, deliberately manage PVIDs so ports don’t end up in unintended VLANs. Check with bridge vlan show.
The local option of bridge-vlan: by default the bridge itself (CPU) is a member of the VLAN (local '1'), meaning the router can have an L3 interface on that VLAN. If the router only forwards this VLAN at L2 without needing an IP (a pass-through VLAN), you can set option local '0' [version-dependent].
Topology:
trunk (VLAN 10,20 tagged)
OpenWrt lan4 ---------------------------- Managed Switch
| / \
lan1 (VLAN10 untagged) VLAN10 VLAN20
access port access port
Attaching an L3 interface to a VLAN
Create the interface on br-lan.<vid> (the VLAN sub-interface name of a VLAN-aware bridge):
config interface 'main'
option device 'br-lan.10'
option proto 'static'
option ipaddr '192.168.10.1/24'
config interface 'iot'
option device 'br-lan.20'
option proto 'static'
option ipaddr '192.168.20.1/24'
The model: bridge-vlan = L2 membership; the interface on br-lan.N = the L3 gateway of that VLAN. If there is no bridge-vlan for VLAN N, br-lan.N has no members and traffic doesn’t flow.
[version-dependent] Sub-interface names like br-lan.10 and netifd’s automatic placement of wlan into a VLAN (sections 9, 10) are supported from the DSA era (21.02+). Confirm with bridge vlan show after applying.
VLANs on swconfig vs DSA: a quick comparison for one use case
“Access port VLAN 10, trunk VLAN 10+20.”
- swconfig: two
switch_vlansections, the trunk port can be in both as tagged; the interfaces areeth0.10,eth0.20(VLAN subifs of the conduit). - DSA: one VLAN-aware bridge, two
bridge-vlansections; the interfaces arebr-lan.10,br-lan.20.
Routing
Mental model
Every IP packet the router handles goes through a routing decision in the kernel (FIB). On OpenWrt:
- netifd installs routes when an interface comes up: connected routes (
proto kernel), default routes fromgatewayor DHCP/PPP, andconfig routeentries. - Default routes have a metric; the lower metric wins.
- There can be multiple routing tables;
ip ruledecides which table a packet uses (policy routing).
Default route and metric
config interface 'wan'
option device 'wan'
option proto 'dhcp'
option metric '10'
config interface 'wan2'
option device 'lan4'
option proto 'dhcp'
option metric '20'
Both default routes exist; the metric-10 route is used, and the metric-20 route is a backup only when the first route disappears from the FIB (e.g. link down). If the link stays up but the upstream is dead, the route remains and there is no failover. See section 17.
Static route
config route
option interface 'lan'
option target '192.168.20.0/24'
option gateway '192.168.10.254'
option metric '5'
Use it when another L3 router inside the LAN has additional subnets behind it. gateway must be inside the interface’s subnet (or use option onlink '1'). Other options: table, type (unicast/blackhole/unreachable/prohibit), source, mtu. For IPv6 use config route6.
Policy-based routing
ip rule selects a table by source, ingress interface, fwmark…
config route
option interface 'wan'
option target '0.0.0.0/0'
option gateway '203.0.113.1'
option table '100'
config rule
option in 'iot' # logical interface name (iif)
option lookup '100'
option priority '1000'
Or by source subnet:
config rule
option src '192.168.20.0/24'
option lookup 'vpn' # table name declared in /etc/iproute2/rt_tables
option priority '2000'
Typical use cases: push the IoT VLAN through a VPN/second WAN, or send guests out through a separate ISP. In addition, an interface has ip4table / ip6table so that routes generated by that interface are installed in its own table instead of main.
Note: A policy route whose target table has no default route makes traffic fall through to the next rule, or get dropped, depending on the rules. Verify step by step with
ip route get.
Debug
ip route # main table
ip route show table all # all tables (including local, 100, vpn…)
ip rule # policy rules + priority
ip route get 8.8.8.8 from 192.168.20.5 iif br-lan.20 # simulate the decision
ip -6 route
ubus call network.interface.wan status # routes managed by netifd
ip route get is the most powerful tool here: it tells you which route/table the kernel will pick for a specific flow, including policy rules.
WAN Configurations
WAN device name [platform-dependent]: it can be wan (DSA), eth1, eth0.2 (swconfig), or a lanX used as WAN on devices with a single switch port.
DHCP WAN
config interface 'wan'
option device 'wan'
option proto 'dhcp'
config interface 'wan6'
option device '@wan'
option proto 'dhcpv6'
Static WAN
config interface 'wan'
option device 'wan'
option proto 'static'
option ipaddr '203.0.113.2/24'
option gateway '203.0.113.1'
list dns '203.0.113.53'
PPPoE
config interface 'wan'
option device 'wan'
option proto 'pppoe'
option username 'USERNAME'
option password 'PASSWORD'
option ipv6 'auto'
option keepalive '5 5' # lcp-echo-failure lcp-echo-interval
option mtu '1492' # PPPoE overhead is 8 bytes; depends on ISP/link
Technical points:
- PPPoE creates a new netdev (
pppoe-wan) running on top ofwan. The L3 device ispppoe-wan, which is whywan6uses@wan, and the firewall uses the interface namewan, noteth1/wan. - The 8-byte overhead makes the effective MTU 1492 (on a 1500-MTU link). Without MSS clamping (
mtu_fix '1'on the wan zone) you get the symptom of some websites hanging due to a PMTU blackhole. - Credentials are plaintext in
/etc/config/network; be careful when sharing configs/backups.
PPPoE over VLAN
Many carriers (especially FTTH) require PPPoE to run inside a specific VLAN ID.
Ethernet (wan)
↓
VLAN (wan.<vid>, 802.1Q)
↓
PPPoE (pppoe-wan)
↓
WAN (interface 'wan')
Two ways to declare it, with device names being platform-dependent:
# Way 1: declare the VLAN device explicitly
config device
option name 'wan.100'
option type '8021q'
option ifname 'wan' # parent device [platform-dependent]
option vid '100'
config interface 'wan'
option device 'wan.100'
option proto 'pppoe'
option username 'USERNAME'
option password 'PASSWORD'
# Way 2: let netifd create the VLAN sub-interface from the "parent.vid" name
config interface 'wan'
option device 'wan.100'
option proto 'pppoe'
...
- DSA: the parent is the user port
wan(orlanXif you use that port as WAN). The VLAN tag is added by 8021q on thewannetdev; nobridge-vlanis needed becausewanis not in a bridge. - swconfig: the WAN VLAN is typically
eth0.2(a switch VLAN), and PPPoE-VLAN needs another VLAN layer on top, e.g.eth0.2+ vid 100 →eth0.2.100, or tagging configured at the switch level. The exact syntax depends on the chip. [platform-dependent]Some SoCs can offload PPPoE/VLAN in hardware NAT. When flow offloading is enabled, re-check performance and behavior.
/etc/config/wireless
wifi-device: the radio
config wifi-device 'radio0'
option type 'mac80211'
option path 'platform/soc/18000000.wifi'
option band '2g'
option channel 'auto'
option htmode 'HE40'
option country 'VN'
option txpower '20'
option cell_density '0'
| Option | Technical explanation |
|---|---|
type | Wireless handler: mac80211 for most upstream drivers; vendor SDKs may use other handlers [platform-dependent] |
path / macaddr | How netifd identifies the phy; path is stable per device tree/PCI path |
band | 2g/5g/6g/60g. Since about 21.02. Older releases use hwmode (11g/11a) [version-dependent] |
channel | Channel number, or auto = ACS (Automatic Channel Selection) [driver-dependent]. Not every driver/phy supports ACS well; ACS on 5 GHz can be delayed by DFS CAC |
htmode | HT20/HT40, VHT20/40/80/160, HE20/40/80/160, EHT… depending on phy capability and release |
country | Regulatory domain. Determines valid channels/power; setting it correctly is mandatory. Some drivers have a self-managed regdom |
txpower | dBm; limited by regdom/EEPROM/driver [driver-dependent] |
cell_density | Adjusts basic rates; 0 = default, lower = better low-rate compatibility, higher = less airtime overhead [version-dependent] |
disabled | Disable the radio |
Notes:
- HE40 on 2.4 GHz is technically valid but usually poor in dense AP environments because of OBSS coexistence (HT40 on 2.4 GHz may fall back to 20 MHz when neighboring APs are detected). 20 MHz is recommended for 2.4 GHz.
- DFS (5 GHz, channels 52-144) requires CAC (several minutes when choosing a channel) and radar detection; ACS may pick a DFS channel and the AP is “silent” during CAC.
- Multiple radios may exist:
radio0(2.4),radio1(5)… Names and ordering are[platform-dependent].
wifi-iface: the logical BSS/interface
config wifi-iface 'default_radio0'
option device 'radio0'
option mode 'ap'
option ssid 'Home'
option network 'lan'
option encryption 'sae-mixed'
option key 'PASSWORD'
option ieee80211w '1'
mode | Meaning |
|---|---|
ap | Access Point (hostapd) |
sta | Client connecting to another AP (wpa_supplicant) |
adhoc | IBSS (legacy, rarely used) |
mesh | 802.11s mesh point [package-dependent] needs a wpad with mesh |
monitor | Monitor mode (sniffer), no association |
encryption | Notes |
|---|---|
none | Open |
psk2, psk2+ccmp | WPA2-PSK |
sae | WPA3-Personal (SAE) |
sae-mixed | WPA2/WPA3 transition mode |
owe | Opportunistic Wireless Encryption |
wpa2/wpa3 | Enterprise (802.1X) requires auth_server… |
[package-dependent] SAE/OWE/mesh/Enterprise require the right wpad variant (wpad-basic-* or the fuller wpad-mbedtls/wpad-openssl). The old wpad-mini does not support WPA3. A mismatch between encryption and wpad variant leaves the SSID down and only shows an error in logread.
Other important AP options: isolate '1' (client isolation, blocks L2 between clients of the same BSS), hidden, wmm, ieee80211r + mobility_domain (802.11r FT), ieee80211k, bss_transition (802.11v), multicast_to_unicast, dtim_period, macfilter, max_inactivity, wds (4addr). [version-dependent] / [driver-dependent] Availability differs by release and driver.
network is the critical link: option network 'lan' names the logical interface this BSS belongs to. netifd will:
- Find that interface’s device (e.g.
br-lan), - Create the wlan netdev (e.g.
phy0-ap0; the name is[driver-dependent]/[version-dependent]), - Add the wlan netdev as a bridge port. If that interface’s device is
br-lan.20, i.e. a VLAN on a VLAN-aware bridge, netifd places the wlan in the bridge with the corresponding VLAN[version-dependent]; verify withbridge vlan show.
network can list multiple interfaces (list network 'a' 'b') but normally each SSID has one network.
From config to hostapd
wifi-device + wifi-iface
↓ (netifd wireless handler, e.g. mac80211.sh)
/var/run/hostapd-phy0.conf (and wpa_supplicant-*.conf)
↓
hostapd / wpa_supplicant ──nl80211──► mac80211/cfg80211 ──► driver ──► radio
See the config actually in use with cat /var/run/hostapd-phy0.conf. This is the quickest way to see which options really reach hostapd.
Multi-SSID
OpenWrt
|
+---------+---------+
| | |
Main IoT Guest
VLAN10 VLAN20 VLAN30
The mapping chain (must be understood clearly)
SSID
↓
wifi-iface (wireless: option network '<name>')
↓
network (network: config interface '<name>')
↓
L3 interface (IP, DHCP pool, routes)
↓
VLAN (device br-lan.<vid> with bridge-vlan <vid>)
↓
firewall zone (zone: list network '<name>')
Each arrow is a place where things can go wrong:
| Arrow | Typical error | Symptom |
|---|---|---|
| SSID → wifi-iface | disabled, wrong device, radio not up | SSID not visible |
| wifi-iface → network | Wrong name, interface doesn’t exist | SSID visible but client can’t connect at L3 / lands in the wrong network |
| network → VLAN | Missing bridge-vlan, wrong u*/t flags | Client associates, gets no DHCP |
| VLAN → L3 | No IP on br-lan.N or no DHCP pool | Associates, no IP |
| L3 → firewall zone | Interface not in any zone | Has an IP but no Internet / can’t reach the router |
A complete example (DSA, one radio per band)
Plan: VLAN 10 Main 192.168.10.0/24, VLAN 20 IoT 192.168.20.0/24, VLAN 30 Guest 192.168.30.0/24.
/etc/config/network
config device
option name 'br-lan'
option type 'bridge'
option vlan_filtering '1'
list ports 'lan1'
list ports 'lan2'
list ports 'lan3'
list ports 'lan4'
config bridge-vlan
option device 'br-lan'
option vlan '10'
list ports 'lan1:u*'
list ports 'lan2:u*'
list ports 'lan4:t'
config bridge-vlan
option device 'br-lan'
option vlan '20'
list ports 'lan3:u*'
list ports 'lan4:t'
config bridge-vlan
option device 'br-lan'
option vlan '30'
list ports 'lan4:t'
config interface 'main'
option device 'br-lan.10'
option proto 'static'
option ipaddr '192.168.10.1/24'
config interface 'iot'
option device 'br-lan.20'
option proto 'static'
option ipaddr '192.168.20.1/24'
config interface 'guest'
option device 'br-lan.30'
option proto 'static'
option ipaddr '192.168.30.1/24'
/etc/config/wireless (abridged; repeat for radio1)
config wifi-iface 'main_2g'
option device 'radio0'
option mode 'ap'
option ssid 'Main'
option network 'main'
option encryption 'sae-mixed'
option key 'MAIN_PASSWORD'
config wifi-iface 'iot_2g'
option device 'radio0'
option mode 'ap'
option ssid 'IoT'
option network 'iot'
option encryption 'psk2' # many IoT devices don't support WPA3
option key 'IOT_PASSWORD'
option isolate '1'
config wifi-iface 'guest_2g'
option device 'radio0'
option mode 'ap'
option ssid 'Guest'
option network 'guest'
option encryption 'sae-mixed'
option key 'GUEST_PASSWORD'
option isolate '1'
/etc/config/dhcp: one pool per interface (see section 19). /etc/config/firewall: one zone per interface (sections 11, 18).
Practical limits
- The number of BSSes per radio is limited
[driver-dependent](typically 8-16 depending on driver/chip). Each SSID is a separate beacon sent at the basic rate, so many SSIDs increase airtime overhead (especially on 2.4 GHz). Don’t create too many SSIDs. - An IoT SSID on 2.4 GHz and a main SSID on 5 GHz is usually a sensible split.
- VLANs carried over the
lan4trunk to a switch must be configured to match on the downstream switch (same VIDs, trunk port, native VLAN).
Guest Wi-Fi
Guest Wi-Fi
|
VLAN 30 (or a local bridge)
|
192.168.30.0/24
|
Firewall
/ \
ALLOW DROP
| |
Internet LAN
Two ways to build L2
A. Guest exists only on this AP (simple, no VLAN): a dedicated bridge with no Ethernet ports.
config device
option name 'br-guest'
option type 'bridge'
option bridge_empty '1' # bridge with no Ethernet ports, wifi will join it
config interface 'guest'
option device 'br-guest'
option proto 'static'
option ipaddr '192.168.30.1/24'
bridge_empty '1' lets the bridge exist even with no members yet; the Guest SSID’s wlan will be added to this bridge by netifd.
B. Guest must travel over a trunk to a switch/another AP: use a VLAN (sections 6, 10): br-lan.30 + bridge-vlan 30 with a t port toward the trunk.
DHCP + Firewall
# /etc/config/dhcp
config dhcp 'guest'
option interface 'guest'
option start '100'
option limit '150'
option leasetime '1h'
list dhcp_option '6,1.1.1.1,9.9.9.9' # public DNS, avoid the internal resolver if desired
# /etc/config/firewall
config zone
option name 'guest'
list network 'guest'
option input 'REJECT'
option output 'ACCEPT'
option forward 'REJECT'
config forwarding
option src 'guest'
option dest 'wan'
config rule
option name 'Guest-Allow-DHCP'
option src 'guest'
option proto 'udp'
option dest_port '67'
option target 'ACCEPT'
config rule
option name 'Guest-Allow-DNS'
option src 'guest'
option proto 'tcp udp'
option dest_port '53'
option target 'ACCEPT'
config rule
option name 'Guest-Block-LAN'
option src 'guest'
option dest 'lan'
option proto 'all'
option target 'REJECT'
Explanation:
input REJECT: guest clients cannot reach services on the router (LuCI, SSH, ping…) except what is whitelisted: DHCP (UDP 67) and DNS (53). Without these two rules, clients get no IP or can’t resolve names, becausednsmasqruns locally on the router and its traffic goes through the input chain.forward REJECT+ onlyforwarding guest → wan: guests can reach the Internet but have no path tolan.- The
Guest-Block-LANrule is defense in depth: if someone later adds a forwarding by mistake, traffic to LAN is still explicitly blocked (explicit rules take precedence over the zone policy)[version-dependent]rule order within chains; check withnft list ruleset. - NAT: the
wanzone hasmasq '1', so everything forwarded out towan(including from guest) is SNATed; no separate masquerade is needed for guest. - The router still has an IP in the guest subnet and its LAN IP. A packet from guest to
192.168.10.1(the router’s own LAN IP) is input traffic, so the guest zone’sinputpolicy applies, not forward.
Isolation: three different layers
| Layer | Mechanism | What it blocks |
|---|---|---|
| L2 / VLAN | Dedicated VLAN or bridge | Separate broadcast domain; guest clients don’t see the LAN’s L2 |
| L3 / Subnet | Dedicated subnet + router as gateway | Must go through the router to reach anything else |
| Firewall | Zone + forwarding + rules | Decides which traffic may cross the router, and which may reach the router itself |
| Client isolation (Wi-Fi) | option isolate '1' | Blocks client↔client within the same SSID/BSS (L2) |
VLAN separation alone is not equivalent to firewall isolation. A VLAN only separates broadcast domains. If the router (or an upstream router) has an L3 interface on both VLANs and the default forward policy is ACCEPT, the two VLANs can still talk to each other via L3. On the router, beyond the VLAN you also need a dedicated zone with appropriate
forwardandinputpolicy. When the guest VLAN travels over a trunk to another router, isolation also depends on that router’s firewall configuration. A device that acts only as a switch/AP provides no L3 isolation on its own.
Dumb AP
Main Router (NAT, DHCP, DNS, firewall)
|
Ethernet
|
OpenWrt (Dumb AP: bridge + Wi-Fi only)
/ \
LAN Wi-Fi
Concept
A Dumb AP turns OpenWrt into an L2 bridge + radio:
- No NAT (NAT is done at the main router; double NAT complicates port forwarding, multicast, and gaming).
- No local DHCP server: clients get IPs from the main router (same broadcast domain thanks to the bridge).
- All ports + wlan are in a single bridge.
- A management IP to reach the AP (static or via DHCP reservation).
- No routing between networks.
Configuration
/etc/config/network
config device
option name 'br-lan'
option type 'bridge'
list ports 'lan1'
list ports 'lan2'
list ports 'lan3'
list ports 'lan4'
list ports 'wan' # the WAN port also becomes a bridge port; commonly used as the uplink
config interface 'lan'
option device 'br-lan'
option proto 'static'
option ipaddr '192.168.1.2/24' # management IP, same subnet as the main router, outside its DHCP pool
option gateway '192.168.1.1'
list dns '192.168.1.1'
option delegate '0' # don't delegate an IPv6 prefix
Remove the wan and wan6 interfaces from the file (or set option disabled '1'). They are unnecessary and can cause conflicts.
/etc/config/dhcp
config dhcp 'lan'
option interface 'lan'
option ignore '1' # don't serve DHCP
Disable unused services:
/etc/init.d/dnsmasq disable && /etc/init.d/dnsmasq stop
/etc/init.d/odhcpd disable && /etc/init.d/odhcpd stop # avoid rogue RA/DHCPv6
/etc/init.d/firewall disable && /etc/init.d/firewall stop # no NAT/forwarding on the AP
/etc/config/wireless: option network 'lan', and encryption matching the main router. For seamless roaming across several APs, use the same SSID/key/mobility_domain on all APs, enable 802.11r/k/v, and plan channels.
Pitfalls specific to this setup
- Duplicate/in-pool IP: assigning a static management IP that collides with the main router’s DHCP pool is a common mistake. Use a DHCP reservation or an IP outside the pool.
- Disabling the firewall means no input filtering on the AP. Acceptable only if the AP sits behind a trusted router; if the AP is exposed to an untrusted network, keep a minimal firewall.
- Loops: plugging two AP ports into the same switch as the main router creates a loop. Enable
stp '1'or avoid looped topologies. - Which port for the uplink? If you put
wanin the bridge, plug the uplink into it; if you keepwanseparate, the AP will not automatically bridge it. - IPv6/RA: if odhcpd is still running with
ra server, the AP may emit wrong RAs and clients get the wrong gateway.
Standard NAT Router
Internet
|
WAN (device 'wan', DHCP/PPPoE/static)
|
OpenWrt (firewall + NAT)
|
NAT
|
LAN (br-lan, 192.168.1.0/24, DHCP server)
Config
/etc/config/network: use the sample in section 5.4.
/etc/config/firewall (typical defaults)
config defaults
option input 'REJECT'
option output 'ACCEPT'
option forward 'REJECT'
option synflood_protect '1'
config zone
option name 'lan'
list network 'lan'
option input 'ACCEPT'
option output 'ACCEPT'
option forward 'ACCEPT'
config zone
option name 'wan'
list network 'wan'
list network 'wan6'
option input 'REJECT'
option output 'ACCEPT'
option forward 'REJECT'
option masq '1'
option mtu_fix '1'
config forwarding
option src 'lan'
option dest 'wan'
/etc/config/dhcp
config dnsmasq
option domainneeded '1'
option localise_queries '1'
option rebind_protection '1'
option local '/lan/'
option domain 'lan'
option expandhosts '1'
option authoritative '1'
config dhcp 'lan'
option interface 'lan'
option start '100'
option limit '150'
option leasetime '12h'
option dhcpv4 'server'
option dhcpv6 'server'
option ra 'server'
config dhcp 'wan'
option interface 'wan'
option ignore '1'
Traffic flow
Client (192.168.1.50)
↓ dst 8.8.8.8 via gateway 192.168.1.1
LAN (br-lan)
↓ netfilter prerouting → routing decision: not local → FORWARD
Firewall forwarding (forward_lan: zone lan → wan permitted by `config forwarding`)
↓ postrouting: srcnat_wan (masquerade: 192.168.1.50 → wan's IP)
NAT (conntrack remembers the mapping to handle return packets)
↓
WAN → Internet
↑ return packet: conntrack match → reverse NAT → forwarded to the client
Key point: return traffic needs no separate rule, thanks to conntrack (ct state established,related accept). Only initiating (NEW) packets go through policy checks.
Verification
ip route # default via <gw> dev wan
nft list ruleset | less # table inet fw4
conntrack -L | head # [package-dependent] needs conntrack-tools
logread -e dnsmasq # DHCP request/offer
Wi-Fi Client / Routed Client
Upstream AP (192.168.0.0/24)
|
Wi-Fi (STA link)
|
OpenWrt (WAN side = wlan STA, LAN side = Ethernet + own AP)
|
Ethernet LAN (192.168.50.0/24)
Routed client (common, stable)
OpenWrt connects upstream as a STA and routes + NATs from its own LAN to the upstream. The STA interface is OpenWrt’s WAN:
# /etc/config/wireless
config wifi-iface 'upstream'
option device 'radio1'
option mode 'sta'
option network 'wwan'
option ssid 'Upstream-SSID'
option encryption 'sae'
option key 'UPSTREAM_PASSWORD'
# /etc/config/network
config interface 'wwan'
option proto 'dhcp'
config interface 'lan'
option device 'br-lan'
option proto 'static'
option ipaddr '192.168.50.1/24'
Firewall: add wwan to the wan zone (list network 'wwan') to get masquerading and the right policy. The LAN subnet must differ from the upstream subnet (overlap breaks routing). This is the simplest and most reliable way to “repeat” Wi-Fi because it avoids the limitations of 802.11 3-address mode.
Distinguishing the models (they are not the same thing)
| Model | L2/L3 | Mechanism | Notes |
|---|---|---|---|
| Routed client | L3 (NAT) | STA as WAN; separate LAN | Double NAT; stable; clients behind it don’t see the upstream’s L2 |
| Repeater | Usually L3, or L2 depending on implementation | STA + AP on the same radio or different radios | Same radio halves airtime; the AP must run on the same channel as the STA |
| Relay (relayd) | L3 pseudo-bridge | proto relay + relayd | Not a real bridge; limited broadcast/multicast [package-dependent] |
| WDS (4addr) | Real L2 bridge | 4-address mode between AP and STA (option wds '1') | Needs support on both ends [driver-dependent]; not every commercial AP supports it |
| Mesh backhaul | L2/L3 depending | 802.11s or a dedicated backhaul STA | See section 15 |
Why the distinction matters: an ordinary 802.11 STA (3-address mode) cannot bridge multiple MACs behind it. The upstream AP only knows the STA’s MAC. So “transparent” L2 bridging over a Wi-Fi STA is only possible with WDS/4addr, or by hiding the MACs behind it (relayd, NAT, proxy ARP…).
Mesh / EasyMesh / Wireless Backhaul
This section covers architecture only. [version-dependent] / [package-dependent] Not every OpenWrt release has the same UCI syntax or the same packages.
Controller (gateway / main router)
|
+------+------+
| |
AP1 AP2
| |
STA <--- Wi-Fi --> Backhaul
Concepts to distinguish
| Concept | Nature | Components |
|---|---|---|
| AP mode | Standalone AP, possibly with the same SSID | hostapd |
| Wireless backhaul (dedicated STA/AP) | A node acts as STA to its parent; often uses a dedicated backhaul SSID/BSS | wpa_supplicant + hostapd |
| 802.11s (mesh) | IEEE standard: mesh point, HWMP path selection at L2 | mode mesh, mesh_id, wpad with mesh; [package-dependent] |
| Mesh + L3 routing protocol | batman-adv / OLSR / babel running over the mesh link | batman-adv, babeld… |
| EasyMesh / Wi-Fi Multi-AP | Wi-Fi Alliance spec for controller–agent, steering, and backhaul management via IEEE 1905.1 | prplMesh, or vendor implementations (MediaTek, Qualcomm SDK…) |
| Ethernet backhaul | Cable between APs | bridge/VLAN/STP |
802.11s (configuration level)
config wifi-iface 'mesh0'
option device 'radio1'
option mode 'mesh'
option mesh_id 'mymesh'
option encryption 'sae'
option key 'MESH_PASSWORD'
option network 'lan'
option mesh_fwding '1' # 1 = HWMP forwarding at L2; 0 when using L3/batman-adv
Requires a wpad build with mesh support. 802.11s L2 forwarding combined with a bridge can create loops if an additional Ethernet path exists; plan STP or use a routing protocol instead of relying on HWMP.
EasyMesh / Multi-AP
- It is a controller + agent architecture: the controller manages policy, and agents on each AP configure radios, report, and support steering.
- The control plane (IEEE 1905.1, CMDU, topology discovery) is a layer above Wi-Fi/Ethernet. It is not provided by hostapd alone.
- hostapd/wpa_supplicant supports Multi-AP flags at the data plane (backhaul BSS, backhaul STA, fronthaul BSS), but the UCI options to enable them, and whether a controller/agent exists at all, depend on release/package/vendor
[package-dependent]. - Many EasyMesh implementations on OpenWrt today are vendor SDKs (MediaTek, Qualcomm) or prplMesh. The UCI syntax is not standardized. Read the documentation and packages for your target.
Don’t confuse:
- 802.11r/k/v +
usteer/dawn: improves roaming and steering between independent APs with the same SSID. This is not EasyMesh. - Marketing “Mesh Wi-Fi” is often a mix: AP mode + dedicated backhaul + a vendor controller.
Choosing a backhaul
| Backhaul | Pros | Cons |
|---|---|---|
| Ethernet | Most stable, best throughput/latency | Needs cabling |
| Dedicated Wi-Fi (separate radio) | Doesn’t compete for airtime with clients | Costs a radio |
| Shared Wi-Fi (same radio) | No cable needed | Airtime is split; throughput drops per hop |
IPv6
Components
| Mechanism | Role |
|---|---|
| SLAAC | Clients self-generate addresses from the prefix in the RA (/64) |
| Router Advertisement (RA) | Router advertises prefix, default router, M/O flags, DNS (RDNSS) |
| DHCPv6 (IA_NA) | Stateful address assignment (rarely used on residential WAN) |
| DHCPv6-PD (IA_PD) | The ISP delegates a prefix (e.g. /56, /60, /64) to the router |
| Stateless DHCPv6 | Provides information only (DNS); addresses come from SLAAC |
ISP
| DHCPv6-PD (e.g. 2001:db8:1234:5600::/56)
|
OpenWrt (odhcp6c on WAN, odhcpd on LAN)
| splits the prefix: ip6assign '60' → each LAN gets a child /64
|
LAN (RA + SLAAC / DHCPv6)
|
IPv6 clients
Config
# /etc/config/network
config interface 'wan6'
option device '@wan'
option proto 'dhcpv6'
option reqaddress 'try'
option reqprefix 'auto' # or the prefix length you want to request, e.g. '56'
config interface 'lan'
option device 'br-lan'
option proto 'static'
option ipaddr '192.168.1.1/24'
option ip6assign '64' # assign a /64 from the delegated prefix to the LAN
# option ip6hint '10' # pick a specific subnet ID within the prefix, useful with multiple VLANs
# /etc/config/dhcp
config dhcp 'lan'
option interface 'lan'
option ra 'server'
option dhcpv6 'server'
option ra_slaac '1'
list ra_flags 'other-config'
Explanation:
ip6assign '64': the prefix size assigned to the interface; with several VLANs, each interface gets a different/64from the PD. The shorter the prefix the ISP delegates (e.g./56), the more subnets you can have.ra_flags: themanaged-config(M) flag tells clients to use DHCPv6 for addresses;other-config(O) means only extra information. SLAAC still works through the A flag in the prefix option.- If the ISP gives only a single
/64(no PD), you need NDP proxy / RA relay (ra 'relay',ndp 'relay'on the master interface)[ISP-dependent]; you can’t route multiple subnets. - ISPs differ on IPv6 support and delivery (which PD size, over PPPoE or DHCPv6). Check with
ubus call network.interface.wan6 statusand look atipv6-prefix.
IPv6 firewall
IPv6 has no NAT (by default). Every client has a global address, so the firewall is the only barrier:
- The default
wanzone hasinput REJECT,forward REJECT: new inbound connections are blocked. - The default rules allow ICMPv6 (mandatory: Neighbor Discovery, Packet Too Big for PMTUD, RA), DHCPv6 replies, and MLD. Don’t block ICMPv6 wholesale: blocking PTB causes MTU blackholes.
- To expose a service from outside to an internal IPv6 host, use a
config rule(allow) withfamily 'ipv6'; don’t useredirect/DNAT.
Debug
ip -6 addr
ip -6 route
ip -6 neigh
ubus call network.interface.wan6 status # prefix delegation, DNS
logread -e odhcp6c -e odhcpd
tcpdump -ni wan icmp6 # RA, NS/NA, PTB
Common sign: wan6 has an ipv6-prefix but the LAN has no global address → check ip6assign, ra/dhcpv6 in /etc/config/dhcp, and the state of odhcpd.
Multi-WAN
ISP1
|
wan1 (metric 10)
\
OpenWrt
/
wan2 (metric 20)
|
ISP2
Three levels
- Crude failover with metrics: two default routes with different metrics. The route is removed when the link/interface goes down. It does not detect upstream failures.
- Failover with health checks: test real reachability (ping/HTTP to an external address) and demote the route on failure. Usually done with
mwan3[package-dependent]. - Load balancing / policy routing: split flows by weight, source, destination, port. Also
mwan3or your ownip rulescripts.
Interface UP does not mean Internet connectivity is healthy.
ip linkis UP, the DHCP lease is valid, the gateway answers ARP, yet the ISP may have lost its path to the Internet (broken BNG, dead DNS, upstream routing failure). Only an end-to-end health check detects that. Test several targets (not just one IP), both by IP and by DNS.
Metric failover
config interface 'wan1'
option device 'wan'
option proto 'dhcp'
option metric '10'
config interface 'wan2'
option device 'lan4' # [platform-dependent] a port repurposed as the second WAN
option proto 'dhcp'
option metric '20'
mwan3 (concepts)
[package-dependent] The mwan3 package has its own mechanism (iptables/ipset/ip rule, fwmark). Behavior and backend (iptables vs nftables) depend on the package version and OpenWrt release; check the documentation of the installed package.
config interface 'wan1'
option enabled '1'
list track_ip '1.1.1.1'
list track_ip '8.8.8.8'
option reliability '1'
option count '1'
option timeout '2'
option interval '5'
option down '3'
option up '3'
config member 'wan1_m1'
option interface 'wan1'
option metric '1'
option weight '1'
config policy 'failover'
list use_member 'wan1_m1'
list use_member 'wan2_m2'
config rule 'default_rule'
option dest_ip '0.0.0.0/0'
option use_policy 'failover'
Things to watch
- Firewall: both WANs should be in the
wanzone (or in dedicated zones withmasq) to get NAT. - Reply path: return packets must leave via the same WAN the request arrived on. Policy routing + conntrack/marks handle this.
- DNS: on failover, the upstream resolver (
peerdns) may be unreachable via the remaining WAN. - Sessions break on failover (the source IP after NAT changes). Don’t promise “no interruption”.
- IPv6 multi-WAN is more complex (different prefixes, source-based routing) and needs its own design.
Firewall and NAT
fw4 architecture (nftables)
[version-dependent] From 22.03 the default is fw4 + nftables. Earlier releases use fw3 + iptables. UCI syntax is almost the same, but the actual ruleset differs. Every nft ... in this article applies to fw4 only.
fw4 generates the table inet fw4 (IPv4 + IPv6 together):
┌──────────────── prerouting ────────────────┐
packet in ──► raw/mangle ──► dstnat (DNAT / port forward) ────────┤
│
routing decision │
┌───────────┴────────────┐
▼ ▼
input forward ───► postrouting
│ │ (srcnat: masq)
local process │ │
│ │ ▼
└────────► output ───────┴──────── packet out
Dispatch: each main chain jumps into per-zone chains based on the ingress/egress device name (iifname "br-lan" jump input_lan…). A zone’s network is a logical interface name; fw4 resolves it to real devices through netifd at load time. Therefore changing a device/VLAN without reloading the firewall can leave the ruleset pointing at the wrong place.
Zone
config zone
option name 'lan'
list network 'lan'
option input 'ACCEPT'
option output 'ACCEPT'
option forward 'ACCEPT'
| Option | Meaning |
|---|---|
input | Policy for traffic from the zone to the router itself |
output | Policy for traffic from the router out to the zone |
forward | Policy for traffic between interfaces within the same zone |
masq | SNAT/masquerade when traffic leaves through this zone |
mtu_fix | MSS clamping |
network | List of logical interfaces in the zone |
A zone’s forward is forwarding within the same zone, not between different zones. Between different zones you need config forwarding.
Forwarding
config forwarding
option src 'lan'
option dest 'wan'
This allows one direction only: from lan to wan. The reverse (wan→lan) is not allowed, except for replies belonging to existing connections (conntrack) and port forwards.
NAT
config zone
option name 'wan'
list network 'wan'
list network 'wan6'
option input 'REJECT'
option output 'ACCEPT'
option forward 'REJECT'
option masq '1'
option mtu_fix '1'
masq '1' creates rules in the srcnat_wan chain (postrouting hook): packets leaving a device in the wan zone get their source address rewritten to that interface’s address. You can restrict sources/destinations with masq_src / masq_dest. masq6 is for IPv6 (NPT/masquerade, rarely used).
Port forwarding
config redirect
option name 'Web-to-NAS'
option src 'wan'
option dest 'lan'
option proto 'tcp'
option src_dport '8080'
option dest_ip '192.168.1.100'
option dest_port '80'
option target 'DNAT'
Packet flow:
Internet client ──► WAN_IP:8080
1. prerouting → dstnat_wan : DNAT WAN_IP:8080 → 192.168.1.100:80 (conntrack stores the mapping)
2. routing decision: destination 192.168.1.100 is not local → FORWARD
3. forward chain : fw4 accepts DNATed packets (ct status dnat accept)
(no separate `forwarding wan→lan` needed)
4. LAN host receives and replies → conntrack reverse-NAT → leaves via WAN with src WAN_IP:8080
Notes:
- DNAT changes the destination before the routing decision, so routing and the forward chain see the translated address.
- Port forward opens nothing if the internal service isn’t listening, or if the LAN server’s default gateway is not this router. The reply will take the wrong path (asymmetric routing).
- NAT reflection / hairpin: LAN clients reaching
WAN_IP:8080need reflection (option reflection '1', usually on by default). Hairpin flows require SNAT. Split-horizon DNS is the cleaner solution. - IPv6 doesn’t use DNAT; use a
config rulewithfamily 'ipv6'.
Flow offloading
config defaults
option flow_offloading '1' # software flow offload (nft flowtable)
option flow_offloading_hw '1' # [platform-dependent] hardware offload
It raises throughput by bypassing part of netfilter for established flows. Features that depend on the traffic passing through netfilter (SQM/QoS, some mangle rules, mwan3) may be bypassed or behave differently. Hardware offload is [platform-dependent].
DHCP / DNS / IPv6 RA
Who serves what
| Service | Default | Notes |
|---|---|---|
| dnsmasq | DNS forwarder/cache + DHCPv4 | Handles config dnsmasq and config dhcp (v4) |
| odhcpd | DHCPv6 + RA + NDP relay | [version-dependent] can take over DHCPv4 with dhcpv4 'server' and dnsmasq DHCP disabled |
| udhcpc / odhcp6c | DHCP client | Run by netifd proto handlers on the WAN side |
Both servers read /etc/config/dhcp, and each config dhcp section is tied to one logical interface via option interface.
DHCPv4 pool
config dhcp 'lan'
option interface 'lan'
option start '100'
option limit '150'
option leasetime '12h'
list dhcp_option '3,192.168.1.1' # gateway
list dhcp_option '6,192.168.1.1' # DNS
The pool is 192.168.1.100–192.168.1.249 (start + limit - 1). The pool is computed within the interface’s subnet; if the interface has no static IP, dnsmasq doesn’t serve it.
Static lease:
config host
option name 'nas'
option mac 'AA:BB:CC:DD:EE:FF'
option ip '192.168.1.100'
DNS
- By default dnsmasq listens on every interface (unless restricted); the router is also the resolver for clients.
- Upstream DNS comes from the WAN’s
peerdns(a resolv file generated by netifd:/tmp/resolv.conf.d/resolv.conf.auto), orlist server '…'inconfig dnsmasq;option noresolv '1'ignores the resolv file. rebind_protection '1'blocks answers where a public domain resolves to a private address (DNS rebinding protection). If you host internal services under a public domain, whitelist it withlist rebind_domain.- Guest/IoT firewall zones must allow UDP/TCP 53 to the router if clients use the router as DNS (see section 11).
IPv6 (odhcpd)
config dhcp 'lan'
option interface 'lan'
option ra 'server' # send Router Advertisements
option dhcpv6 'server' # or 'disabled' / 'relay' / 'hybrid'
option ra_slaac '1'
list ra_flags 'other-config'
option dhcpv4 'server' # [version-dependent]
The ra/dhcpv6 modes: server, relay, hybrid, disabled. ndp 'relay' covers the no-PD case. ra_default, ra_preference, ra_maxinterval control default-router advertisement.
Verification
cat /tmp/dhcp.leases # dnsmasq DHCPv4 leases
logread -e dnsmasq -e odhcpd
tcpdump -ni br-lan port 67 or port 68 # DORA
tcpdump -ni br-lan icmp6 # RA/NS/NA
Network Debugging
Methodology
1. Define the scope: one client? one VLAN? one SSID? everything? IPv6 only?
2. Go bottom-up (L1 → L7), or bisect at the router:
client ── [L1/L2] ── router ── [L3/route] ── [firewall/NAT] ── WAN ── Internet
3. At each layer, compare 3 views:
Config (uci) vs netifd/ubus vs Kernel (ip/bridge/nft)
4. Use tcpdump at several points to see where the packet "dies":
wlan → br-lan.X → br-lan → wan
5. Change ONE thing at a time, and write it down.
Four questions for every incident: Is the link up? → Is it in the right VLAN/bridge? → Are IP/routes correct? → Does the firewall/NAT let it through?
Layer 1/2
ip -br link # link state, MAC
ip -d link show br-lan # -d: details, shows vlan_filtering, stp
ethtool lan1 # speed, duplex, link detected (needs ethtool)
bridge link # which port belongs to which bridge, state (forwarding/blocking)
bridge fdb show # MAC → port learning table
bridge fdb show br br-lan
bridge vlan # DSA: VLAN membership + PVID + flags per port
bridge -d vlan show
cat /sys/class/net/br-lan/bridge/vlan_filtering
swconfig dev switch0 show # [legacy swconfig only]
dmesg | grep -iE 'dsa|switch|link is'
How to read bridge vlan:
port vlan-id
lan1 10 PVID Egress Untagged
lan4 10
20
br-lan 10
20
PVID→ the*flag;Egress Untagged→ theuflag; nothing extra → tagged.br-lanappearing in the list means the router has an L3 interface on that VLAN (local).- If a client gets no DHCP: check that the port has the right
PVID+Untaggedfor the VLAN, and thatbr-lan.<vid>has members.
Layer 3
ip addr
ip -br addr
ip route
ip route show table all
ip rule
ip route get 8.8.8.8 from 192.168.20.5 iif br-lan.20
ip neigh # ARP/NDP: REACHABLE/STALE/FAILED
ip -6 addr; ip -6 route; ip -6 neigh
An ip neigh entry in FAILED/INCOMPLETE for the gateway means L2 isn’t working yet (wrong VLAN, loop, wrong port), even if the L3 config looks right.
Firewall / NAT
nft list ruleset # everything (fw4)
nft list table inet fw4
nft list chain inet fw4 input_guest
nft list chain inet fw4 forward_lan
nft list chain inet fw4 srcnat_wan
fw4 print # the ruleset fw4 WOULD generate from the current config (compare with runtime)
fw4 reload
conntrack -L # [package-dependent] connection table
conntrack -E # real-time events
Trace a packet through nftables (needs temporary config, use with care):
nft insert rule inet fw4 prerouting_raw meta nftrace set 1 # depends on the chain existing in your release
nft monitor trace
[version-dependent] fw4’s internal chain names may differ between releases. Use nft list ruleset to confirm chain names before acting.
Common mix-ups to check:
- Zone
list networkwritten with a device name instead of an interface name. - Missing
forwarding(e.g. guest→wan). - Missing allow rules for DHCP/DNS on zones with
input REJECT. - Forgetting
masq '1'on the wan zone.
OpenWrt runtime (ubus / netifd)
ubus list | grep -E 'network|hostapd'
ubus call network.interface dump # all interfaces: up, device, address, routes, dns
ubus call network.interface.lan status
ubus call network.device status # all devices: type, state, bridge members, VLAN
ubus call network.device status '{"name":"br-lan"}'
ubus call network.wireless status # radios & vifs: up, config, ifname, pending
ubus call network reload
ifstatus wan # shortcut
logread -f # follow netifd/hostapd/dnsmasq in real time
logread -e netifd
How to cross-check:
uci show network # CONFIG: what you want
ubus … network.* # NETIFD: what netifd understood and did
ip / bridge / nft # KERNEL: what actually exists
If uci is right but ubus lacks the object → netifd didn’t parse/apply it (syntax error, wrong option, missing package). If ubus is right but the kernel is wrong → the problem is at the kernel/driver layer (bridge-vlan can’t be offloaded, DSA limitation).
Wi-Fi
iw dev # wlan netdev list, phy, type (AP/managed/mesh), channel, txpower
iw dev wlan0 info
iw phy # phy capabilities: bands, HT/VHT/HE, modes, concurrent interfaces
iw phy phy0 info | less # interface combinations (max number of BSSes)
iw dev wlan0 station dump # clients: signal, tx/rx bitrate, inactive time
iw dev wlan0 scan # [needs an idle radio; affects the AP]
iw reg get # regulatory domain currently applied
iwinfo # (OpenWrt) summary: SSID, channel, signal, encryption
iwinfo wlan0 assoclist
iwinfo wlan0 freqlist
wifi status # equivalent to ubus network.wireless status, easier to read
hostapd_cli -i wlan0 status # (if present) BSS status
ubus call hostapd.wlan0 get_clients # [version-dependent] clients via ubus
cat /var/run/hostapd-phy0.conf # the hostapd config actually generated
logread -e hostapd -e wpa_supplicant
Interface names (
wlan0,phy0-ap0,phy0-sta0…) are[version-dependent]/[driver-dependent]. Useiw devto learn the real names on your device.
Symptoms and where to investigate:
| Symptom | Check |
|---|---|
| SSID doesn’t appear | wifi status, iw dev, logread -e hostapd, wpad variant, country/DFS CAC, disabled |
| Associates then gets kicked / 4-way handshake failure | encryption vs ieee80211w, wrong PSK, sae-mixed with old clients, hostapd logread |
| Link up, no IP | bridge vlan, bridge link, the network option in wifi-iface, DHCP pool, firewall input |
| Has IP, no Internet | ip route, forwarding/masq, DNS |
| Low speed / poor roaming | iw dev wlan0 station dump (bitrate, signal), channel/htmode, DFS, 2.4 GHz HT40, cell_density, 802.11r/k/v |
| Fails only on 2.4 or 5 GHz | the corresponding radio, band, channel, country, driver/firmware log (dmesg) |
Packet capture
tcpdump -ni br-lan.30 -e vlan # see VLAN tags
tcpdump -ni phy0-ap0 port 67 or port 68 # DHCP on the wlan
tcpdump -ni wan -c 20 icmp
tcpdump -ni br-lan -w /tmp/cap.pcap # save, then open in Wireshark
Bisect technique: ping from the client → capture on the wlan, br-lan.N, wan. The hop where the packet disappears tells you the fault lies between that hop and the next.
Engineering Pitfalls / Common Mistakes
- Copying a swconfig config onto a DSA device (or vice versa). The two models are completely different (section 6).
- Assuming port/interface names are universal.
wan,lan1,eth0.2depend on the target. Read/etc/board.jsonandip link. uci commitand assuming it’s applied. You must reload the service (network,firewall,dnsmasq,wifi).- Locking yourself out when editing network remotely. Always have a rollback/backup.
- VLAN ≠ firewall isolation. You need a zone + forward policy (section 11).
uwithout*(or vice versa) → one-way traffic on an access port.- Zone
list networkusing device names instead of logical interface names. - Missing DHCP/DNS allow rules for zones with
input REJECT. - Forgetting to disable dnsmasq/odhcpd/firewall on a Dumb AP → rogue DHCP, wrong RA, double NAT.
- L2 loops when mixing multiple Ethernet uplinks with mesh/Wi-Fi without STP.
- Subnet overlap (routed client, VPN, multiple VLANs) → ambiguous routing.
- PPPoE MTU/MSS: forgetting
mtu_fix, some sites hang. - Blocking ICMPv6 entirely → breaks ND and PMTUD.
- Believing “interface up = Internet OK” in multi-WAN.
- Too many SSIDs → airtime overhead; exceeding the driver’s BSS limit.
- Regulatory/DFS: not setting
country; ACS picks a DFS channel and the AP is silent for a few minutes. wpadvariant doesn’t matchencryption(SAE/OWE/mesh/Enterprise).- Confusing EasyMesh with 802.11r/k/v or 802.11s: three different things.
- Flow offloading makes SQM/mangle/mwan3 not behave as expected.
- Trusting
ip routeto infer the config. Routes generated by DHCP/PPP/PD don’t appear in UCI.
Cheat Sheet: “Change X, Remember to Touch Y”
| You change | Remember to check / reload |
|---|---|
| A logical interface name | wireless (network), dhcp (interface), firewall (list network), route/rule |
| Add a VLAN | bridge-vlan (flags), interface on br-lan.N, dhcp, firewall, downstream switch |
| Add an SSID | wifi-iface + network; BSS limit; firewall/dhcp for that interface |
| Change the WAN proto | wan6 (@wan), wan zone, MTU/MSS, DNS |
| Add a second WAN | metric, zone, health check (mwan3), reply path |
| Change the LAN subnet | DHCP pool, static leases, port forward dest_ip, routes, firewall subnets, VPN |
# A safe working loop
cp -a /etc/config /root/config.bak.$(date +%s)
uci set ...; uci commit network
/etc/init.d/network reload; wifi reload; /etc/init.d/firewall reload
ubus call network.interface dump | head
bridge vlan; ip -br addr; nft list ruleset | head
Summary
- OpenWrt is a set of daemons that turn declarative config into Linux kernel state. UCI is not the runtime.
device= L2;interface= L3/proto; the logical interface name is the “glue” between network, wireless, dhcp, and firewall.- DSA ≠ swconfig. DSA configures VLANs on the bridge (
bridge-vlan,t,u,*); swconfig configures them inside the chip. - VLANs separate L2, the firewall separates L3. For isolation you need both (plus
isolatewhen you need client↔client isolation). - Debugging: config (uci) → netifd (ubus) → kernel (ip/bridge/nft) → packets (tcpdump), bottom-up.
- Anything tied to port names, drivers, releases, or packages must be verified on the real device.