The IPv6 half of the L3 tables is written and read by the same code that drives the IPv4 half, and the setup enables it in hardware, but nothing has ever produced a route for it: the FIB notifier dropped every IPv6 entry event and the handler behind it was a stub. Take those events, hold the route across the work item that handles them, and program the prefix table from them the way the IPv4 handler does, minus the host route table: the prefix entry carries a host-route bit of its own, which the writer already sets for a prefix length of 128, so one table serves every prefix length. Offloading is refused for a nexthop object, a source-specific route, a multipath route, anything that is not plain unicast, a route with no gateway, and the two destinations no forwarding row should match: the default route, which would take everything, and the loopback address. Refused is not the same as left alone. A shorter prefix that is offloaded still matches such a destination and forwards it, whatever the route says: a blackhole /64 under an offloaded /48 would be forwarded to the /48's gateway. So a refused route gets a row of its own that traps to the CPU, which routes it as the kernel does. The default route gets none, since that row would match every destination; the catch-all row added later in this series traps what no other row matches. A multicast route gets nothing either. A delete takes that row out before anything else, since the route it belonged to was never judged offloadable. The cost is an entry per such route; on the bench, the connected prefixes, fe80::/64 and the ULA prefix netifd adds as unreachable, five entries in all. A multipath route keeps its row until its last member is gone: an add carries the route with every sibling it has, a delete names only the members that leave, so the row counts them. Source-specific routes to one prefix share its row, which counts the members of the last one to arrive, so deleting one can take the row from the others; the driver logs an error for every source-specific route it traps. An add arrives as a replace even when it is the only word the kernel sends about a programmed route going away, so the row for a destination is taken out before the route replacing it is judged, and a delete is matched on its gateway as well, because entries that share a destination arrive with their sibling count already cleared. A multipath route that shrinks back to a single one stays refused until something touches it again: the only word about it names the member that left. A route takes its row as soon as it is added, as a trap: until its gateway answers, a shorter prefix in hardware would otherwise forward what this route has yet to resolve. It holds the FIB entry it came from until the row goes, and is reported as trapping until the gateway answers and the row forwards, then as offloaded; one that finds no free row is reported as failed. One whose gateway answered on no port the switch knows keeps trapping, so it is reported as trapping, not offloaded, until an update gives it a port. So is one whose gateway's neighbour stops being valid, until it is valid again. That leaves the switch's own addresses. A packet whose destination MAC is the switch's own terminates on the router MAC table and is then looked up as a route, and that table carries no IP address, so the hardware has no notion of which addresses are its own: such a packet misses the lookup and is dropped. The IPv4 path mostly escapes this, because it writes a host route trapping each of its own addresses that sits on a device with a switch port below it, and because ARP is not IP and the setup traps non-IP traffic separately. Neighbour discovery is ICMPv6, so IPv6 has neither. What a neighbour sends to a multicast address is untouched, never carrying the switch's MAC, so a gateway is still learned from an unsolicited advertisement. Every unicast step after that is not: the solicitation addressed to the switch, and the reachability probe that follows, both carry its MAC and both die in the lookup, so the neighbour ends up FAILED all the same. So give every local IPv6 address a prefix route of its own that traps to the CPU, which is how the SDK asks for one - an entry with no next hop and the trap action. On a device with forwarding on, the kernel also joins the subnet-router anycast address of every prefix shorter than a /127 whose network part is not all zeroes, link-local ones included, and names its route RTN_ANYCAST rather than RTN_LOCAL; that address is the switch's as much as the others, so it is trapped with them. Measured on an RTL9303 with the offload built in, which no subtarget config does: the switch answers on its own address and on that anycast, five of five each, and the anycast row comes back with its hit bit set. With fd00:99::/48 offloaded and fd00:99:0:1::/64 a blackhole, the /64 gets a trap row and pings into it no longer reach the /48's gateway, 0 of 5, while the rest of the /48 still does, 5 of 5; with the blackhole deleted, its row goes and the same pings arrive, 5 of 5. A /64 through a gateway that never answers, under such a /48, has its trap row from the moment it is added: none of five pings reaches the /48's gateway and the sender is told the address is unreachable, where without that row the /48 forwarded all five. A multipath /48 under an offloaded /32 keeps its trap row when one member is deleted, so its traffic follows the member left, five pings of five, instead of the /32's gateway; and an address that becomes the switch's own where a route through a gateway held it gets its trap row, and the switch answers on it. An address the box holds on several devices at once reaches the driver once, for whichever of them the kernel made the leaf of that destination: a bridge, its VLAN interfaces and the conduit all carry the same link-local, and the one that wins need not be a switch port. So which device holds an address says nothing about whether it has to be trapped, and none of them is asked. While a copy remains the kernel replaces the entry rather than deleting it, so the row is written once and removed when the last copy goes. An address arrives before the replace that takes out a route already holding its destination, so only a row that traps is kept; any other row there is taken out first. Each row costs one of the 127 slots the table has for an IPv6 entry, taken from the end the IPv4 rows grow towards; an address that finds none is left without a row, which makes it unreachable from the switch ports, and dmesg is the only sign of it. Telling a row from another takes the next hop index it carries: the id of the route that wrote it once its gateway answered, and zero on a row that only traps. So the test that says whether a row is still the route being removed now compares the action as well, and the index with the one the route itself wrote rather than with its id - on the removal path both address families share. The per-event warning the notifier used to print for an IPv6 entry goes with the branch that printed it. Everything IPv6-specific sits behind IS_REACHABLE(CONFIG_IPV6): the FIB event branch, the FIB entry a route holds and its report. A kernel with CONFIG_IPV6=n builds and links, and never sees an IPv6 route. Driver removal gains a wait on the work queue and a pass over the route list that releases the FIB entries still held. otto_l3_remove() is on the unbind path of every family, so both run there: the pass finds nothing to release where no route ever held an entry, and the wait is for work that family queues on the same single-threaded queue. Assisted-by: Claude:claude-opus-5 Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Gennaro Cimmino <gcimmino@rayonra.net> Link: https://github.com/openwrt/openwrt/pull/25313 Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
OpenWrt Project is a Linux operating system targeting embedded devices. Instead of trying to create a single, static firmware, OpenWrt provides a fully writable filesystem with package management. This frees you from the application selection and configuration provided by the vendor and allows you to customize the device through the use of packages to suit any application. For developers, OpenWrt is the framework to build an application without having to build a complete firmware around it; for users this means the ability for full customization, to use the device in ways never envisioned.
Sunshine!
Download
Built firmware images are available for many architectures and come with a package selection to be used as WiFi home router. To quickly find a factory image usable to migrate from a vendor stock firmware to OpenWrt, try the Firmware Selector.
If your device is supported, please follow the Info link to see install instructions or consult the support resources listed below.
An advanced user may require additional or specific package. (Toolchain, SDK, ...) For everything else than simple firmware download, try the wiki download page:
Development
To build your own firmware you need a GNU/Linux, BSD or macOS system (case sensitive filesystem required). Cygwin is unsupported because of the lack of a case sensitive file system.
Requirements
You need the following tools to compile OpenWrt, the package names vary between distributions. A complete list with distribution specific packages is found in the Build System Setup documentation.
binutils bzip2 diff find flex gawk gcc-6+ getopt grep install libc-dev libz-dev
make4.1+ perl python3.8+ rsync subversion unzip which
Quickstart
-
Run
./scripts/feeds update -ato obtain all the latest package definitions defined in feeds.conf / feeds.conf.default -
Run
./scripts/feeds install -ato install symlinks for all obtained packages into package/feeds/ -
Run
make menuconfigto select your preferred configuration for the toolchain, target system & firmware packages. -
Run
maketo build your firmware. This will download all sources, build the cross-compile toolchain and then cross-compile the GNU/Linux kernel & all chosen applications for your target system.
Related Repositories
The main repository uses multiple sub-repositories to manage packages of
different categories. All packages are installed via the OpenWrt package
manager called opkg. If you're looking to develop the web interface or port
packages to OpenWrt, please find the fitting repository below.
-
LuCI Web Interface: Modern and modular interface to control the device via a web browser.
-
OpenWrt Packages: Community repository of ported packages.
-
OpenWrt Routing: Packages specifically focused on (mesh) routing.
-
OpenWrt Video: Packages specifically focused on display servers and clients (Xorg and Wayland).
Support Information
For a list of supported devices see the OpenWrt Hardware Database
Documentation
Support Community
- Forum: For usage, projects, discussions and hardware advise.
- Support Chat: Channel
#openwrton oftc.net.
Developer Community
- Bug Reports: Report bugs in OpenWrt
- Dev Mailing List: Send patches
- Dev Chat: Channel
#openwrt-develon oftc.net.
License
OpenWrt is licensed under GPL-2.0
