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:

TagMeaning
[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.1 are 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:

  1. Stores your intent as declarative config (UCI).
  2. Provides daemons (netifd, fw4, dnsmasq, odhcpd, hostapd) that translate intent into kernel state.
  3. 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].
  • option is a single value, list is 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:

ConfigurationRuntime state
Lives in/etc/config/* (flash/overlay)Kernel + RAM: netdevs, FDB, routes, nft ruleset, conntrack
Survives rebootYesNo
Written byYou / LuCI / scriptsnetifd, kernel, odhcp6c, udhcpc…
Inspect withuci show, catip, 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


ComponentRoleReadsWrites
UCIConfig store/etc/config/*—
procdInit + process supervisor; respawn, triggers reload when config changesinit scripts—
netifdNetwork interface daemon: manages devices and interfaces, runs proto handlers/etc/config/network (+ wireless)netlink (link/addr/route), ubus objects
proto handlersstatic, dhcp (udhcpc), dhcpv6 (odhcp6c), pppoe (pppd)…invoked by netifdreport state back to netifd
fw4Translates /etc/config/firewall into an nftables ruleset/etc/config/firewall, ubus networknft (table inet fw4)
dnsmasqDNS forwarder/cache + DHCPv4 server (default)/etc/config/dhcpUDP 53/67
odhcpdDHCPv6 server, Router Advertisement, NDP relay [version-dependent] can also do DHCPv4/etc/config/dhcpRA, DHCPv6
hostapd / wpa_supplicantControls AP/STA/mesh (auth, association, keys)files generated by the netifd wireless handlernl80211
kernelbridge, 8021q, routing, netfilter, mac80211netlink—
driverMAC/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:

  1. netifd is the center. It is both the config interpreter and the state machine. ubus call network.interface dump is the source of truth for what netifd thinks, while ip is the source of truth for what the kernel has.
  2. 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


FilePurposeMain components
/etc/config/networkL2/L3 topologydevice, interface, bridge-vlan, route, rule, switch* (legacy)
/etc/config/wirelessWi-Fiwifi-device (radio), wifi-iface (SSID/AP/STA/mesh), security
/etc/config/firewallSecurity/NATdefaults, zone, forwarding, rule, redirect
/etc/config/dhcpDHCP/DNS/IPv6 RAdnsmasq, 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?

  1. 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.
  2. One device, several interfaces and vice versa, e.g. wan (IPv4 DHCP) and wan6 (DHCPv6) on the same physical device.
  3. 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.
  4. 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:

OptionMeaning
name, typeNetdev name; bridge, 8021q, 8021ad, macvlan, veth…
ports (list)Bridge members
macaddr, mtu, txqueuelenNetdev properties
stp, forward_delay, priorityBridge STP
igmp_snooping, multicast, multicast_querierMulticast [version-dependent] defaults differ
vlan_filteringEnable VLAN-aware bridge (see section 6)
bridge_emptyAllow a bridge with no ports (for Wi-Fi-only bridges)
ipv6, acceptlocal, promiscFlags

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


protoRun byUse when
staticnetifd (built-in)LAN, static WAN IP, management IP
dhcpudhcpc (dhcp.sh)WAN from an ISP/upstream router; AP mode managed via DHCP
dhcpv6odhcp6cIPv6 from the ISP: IA_NA, IA_PD, SLAAC
pppoepppd + rp-pppoe.soDSL/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
relayrelayd [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 → port from 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 show displays 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 on br-lan is 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)
swconfigDSA
Physical ports have their own netdev?No (only eth0 + VLAN subifs eth0.N)Yes (lan1, lan2, wan…)
Where VLANs are configuredconfig switch / config switch_vlanconfig bridge-vlan on a VLAN-aware bridge
Port syntaxport numbers (0 1 2 3 6t), t = tagged (usually on the CPU port)port names + flags (lan1:u*, lan4:t)
Kernel mechanismout-of-tree/legacy switch APIbridge VLAN filtering + switchdev offload
Toolsswconfig dev switch0 showbridge vlan, ip -d link
Releasecommon 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/network contains config switch / config switch_vlan, it is swconfig. If it contains bridge-vlan and ports named lanX, 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:

FlagMeaningDetails
tTaggedFrames leaving this port carry the VID tag; frames entering must carry this VID tag to belong to the VLAN. Used for trunks.
uUntagged (egress untagged)The tag is stripped on egress. Used for access ports.
*PVIDUntagged ingress frames on the port are assigned to this VLAN.
u*Untagged + PVIDA 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_vlan sections, the trunk port can be in both as tagged; the interfaces are eth0.10, eth0.20 (VLAN subifs of the conduit).
  • DSA: one VLAN-aware bridge, two bridge-vlan sections; the interfaces are br-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 from gateway or DHCP/PPP, and config route entries.
  • Default routes have a metric; the lower metric wins.
  • There can be multiple routing tables; ip rule decides 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 of wan. The L3 device is pppoe-wan, which is why wan6 uses @wan, and the firewall uses the interface name wan, not eth1/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 (or lanX if you use that port as WAN). The VLAN tag is added by 8021q on the wan netdev; no bridge-vlan is needed because wan is 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'
OptionTechnical explanation
typeWireless handler: mac80211 for most upstream drivers; vendor SDKs may use other handlers [platform-dependent]
path / macaddrHow netifd identifies the phy; path is stable per device tree/PCI path
band2g/5g/6g/60g. Since about 21.02. Older releases use hwmode (11g/11a) [version-dependent]
channelChannel 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
htmodeHT20/HT40, VHT20/40/80/160, HE20/40/80/160, EHT… depending on phy capability and release
countryRegulatory domain. Determines valid channels/power; setting it correctly is mandatory. Some drivers have a self-managed regdom
txpowerdBm; limited by regdom/EEPROM/driver [driver-dependent]
cell_densityAdjusts basic rates; 0 = default, lower = better low-rate compatibility, higher = less airtime overhead [version-dependent]
disabledDisable 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'
modeMeaning
apAccess Point (hostapd)
staClient connecting to another AP (wpa_supplicant)
adhocIBSS (legacy, rarely used)
mesh802.11s mesh point [package-dependent] needs a wpad with mesh
monitorMonitor mode (sniffer), no association
encryptionNotes
noneOpen
psk2, psk2+ccmpWPA2-PSK
saeWPA3-Personal (SAE)
sae-mixedWPA2/WPA3 transition mode
oweOpportunistic Wireless Encryption
wpa2/wpa3Enterprise (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:

  1. Find that interface’s device (e.g. br-lan),
  2. Create the wlan netdev (e.g. phy0-ap0; the name is [driver-dependent]/[version-dependent]),
  3. 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 with bridge 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:

ArrowTypical errorSymptom
SSID → wifi-ifacedisabled, wrong device, radio not upSSID not visible
wifi-iface → networkWrong name, interface doesn’t existSSID visible but client can’t connect at L3 / lands in the wrong network
network → VLANMissing bridge-vlan, wrong u*/t flagsClient associates, gets no DHCP
VLAN → L3No IP on br-lan.N or no DHCP poolAssociates, no IP
L3 → firewall zoneInterface not in any zoneHas 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 lan4 trunk 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, because dnsmasq runs locally on the router and its traffic goes through the input chain.
  • forward REJECT + only forwarding guest → wan: guests can reach the Internet but have no path to lan.
  • The Guest-Block-LAN rule 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 with nft list ruleset.
  • NAT: the wan zone has masq '1', so everything forwarded out to wan (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’s input policy applies, not forward.

Isolation: three different layers


LayerMechanismWhat it blocks
L2 / VLANDedicated VLAN or bridgeSeparate broadcast domain; guest clients don’t see the LAN’s L2
L3 / SubnetDedicated subnet + router as gatewayMust go through the router to reach anything else
FirewallZone + forwarding + rulesDecides 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 forward and input policy. 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 wan in the bridge, plug the uplink into it; if you keep wan separate, 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)


ModelL2/L3MechanismNotes
Routed clientL3 (NAT)STA as WAN; separate LANDouble NAT; stable; clients behind it don’t see the upstream’s L2
RepeaterUsually L3, or L2 depending on implementationSTA + AP on the same radio or different radiosSame radio halves airtime; the AP must run on the same channel as the STA
Relay (relayd)L3 pseudo-bridgeproto relay + relaydNot a real bridge; limited broadcast/multicast [package-dependent]
WDS (4addr)Real L2 bridge4-address mode between AP and STA (option wds '1')Needs support on both ends [driver-dependent]; not every commercial AP supports it
Mesh backhaulL2/L3 depending802.11s or a dedicated backhaul STASee 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


ConceptNatureComponents
AP modeStandalone AP, possibly with the same SSIDhostapd
Wireless backhaul (dedicated STA/AP)A node acts as STA to its parent; often uses a dedicated backhaul SSID/BSSwpa_supplicant + hostapd
802.11s (mesh)IEEE standard: mesh point, HWMP path selection at L2mode mesh, mesh_id, wpad with mesh; [package-dependent]
Mesh + L3 routing protocolbatman-adv / OLSR / babel running over the mesh linkbatman-adv, babeld…
EasyMesh / Wi-Fi Multi-APWi-Fi Alliance spec for controller–agent, steering, and backhaul management via IEEE 1905.1prplMesh, or vendor implementations (MediaTek, Qualcomm SDK…)
Ethernet backhaulCable between APsbridge/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


BackhaulProsCons
EthernetMost stable, best throughput/latencyNeeds cabling
Dedicated Wi-Fi (separate radio)Doesn’t compete for airtime with clientsCosts a radio
Shared Wi-Fi (same radio)No cable neededAirtime is split; throughput drops per hop

IPv6


Components


MechanismRole
SLAACClients 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 DHCPv6Provides 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 /64 from the PD. The shorter the prefix the ISP delegates (e.g. /56), the more subnets you can have.
  • ra_flags: the managed-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 status and look at ipv6-prefix.

IPv6 firewall


IPv6 has no NAT (by default). Every client has a global address, so the firewall is the only barrier:

  • The default wan zone has input 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) with family 'ipv6'; don’t use redirect/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


  1. 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.
  2. 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].
  3. Load balancing / policy routing: split flows by weight, source, destination, port. Also mwan3 or your own ip rule scripts.

Interface UP does not mean Internet connectivity is healthy. ip link is 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 wan zone (or in dedicated zones with masq) 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'
OptionMeaning
inputPolicy for traffic from the zone to the router itself
outputPolicy for traffic from the router out to the zone
forwardPolicy for traffic between interfaces within the same zone
masqSNAT/masquerade when traffic leaves through this zone
mtu_fixMSS clamping
networkList 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:8080 need 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 rule with family '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


ServiceDefaultNotes
dnsmasqDNS forwarder/cache + DHCPv4Handles config dnsmasq and config dhcp (v4)
odhcpdDHCPv6 + RA + NDP relay[version-dependent] can take over DHCPv4 with dhcpv4 'server' and dnsmasq DHCP disabled
udhcpc / odhcp6cDHCP clientRun 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), or list server '…' in config 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 with list 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 → the u flag; nothing extra → tagged.
  • br-lan appearing 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 + Untagged for the VLAN, and that br-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 network written 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]. Use iw dev to learn the real names on your device.

Symptoms and where to investigate:

SymptomCheck
SSID doesn’t appearwifi status, iw dev, logread -e hostapd, wpad variant, country/DFS CAC, disabled
Associates then gets kicked / 4-way handshake failureencryption vs ieee80211w, wrong PSK, sae-mixed with old clients, hostapd logread
Link up, no IPbridge vlan, bridge link, the network option in wifi-iface, DHCP pool, firewall input
Has IP, no Internetip route, forwarding/masq, DNS
Low speed / poor roamingiw 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 GHzthe 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


  1. Copying a swconfig config onto a DSA device (or vice versa). The two models are completely different (section 6).
  2. Assuming port/interface names are universal. wan, lan1, eth0.2 depend on the target. Read /etc/board.json and ip link.
  3. uci commit and assuming it’s applied. You must reload the service (network, firewall, dnsmasq, wifi).
  4. Locking yourself out when editing network remotely. Always have a rollback/backup.
  5. VLAN ≠ firewall isolation. You need a zone + forward policy (section 11).
  6. u without * (or vice versa) → one-way traffic on an access port.
  7. Zone list network using device names instead of logical interface names.
  8. Missing DHCP/DNS allow rules for zones with input REJECT.
  9. Forgetting to disable dnsmasq/odhcpd/firewall on a Dumb AP → rogue DHCP, wrong RA, double NAT.
  10. L2 loops when mixing multiple Ethernet uplinks with mesh/Wi-Fi without STP.
  11. Subnet overlap (routed client, VPN, multiple VLANs) → ambiguous routing.
  12. PPPoE MTU/MSS: forgetting mtu_fix, some sites hang.
  13. Blocking ICMPv6 entirely → breaks ND and PMTUD.
  14. Believing “interface up = Internet OK” in multi-WAN.
  15. Too many SSIDs → airtime overhead; exceeding the driver’s BSS limit.
  16. Regulatory/DFS: not setting country; ACS picks a DFS channel and the AP is silent for a few minutes.
  17. wpad variant doesn’t match encryption (SAE/OWE/mesh/Enterprise).
  18. Confusing EasyMesh with 802.11r/k/v or 802.11s: three different things.
  19. Flow offloading makes SQM/mangle/mwan3 not behave as expected.
  20. Trusting ip route to infer the config. Routes generated by DHCP/PPP/PD don’t appear in UCI.

Cheat Sheet: “Change X, Remember to Touch Y”


You changeRemember to check / reload
A logical interface namewireless (network), dhcp (interface), firewall (list network), route/rule
Add a VLANbridge-vlan (flags), interface on br-lan.N, dhcp, firewall, downstream switch
Add an SSIDwifi-iface + network; BSS limit; firewall/dhcp for that interface
Change the WAN protowan6 (@wan), wan zone, MTU/MSS, DNS
Add a second WANmetric, zone, health check (mwan3), reply path
Change the LAN subnetDHCP 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 isolate when 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.