Skip to content

v2.4.1

Choose a tag to compare

@github-actions github-actions released this 03 Aug 21:20
· 120 commits to main since this release
v2.4.1

Fixed

  • WireGuard peer writes now reach the kernel. A POST, PUT or PATCH on network/wireguard_peers committed the section to uci, answered 200, and left the running tunnel untouched, so the peer did not exist as far as the kernel was concerned until something else restarted the interface. DELETE had the matching hole: it answered 204 and the peer stayed live, so revoking a peer through the API did not revoke its access. Operators who have deleted a peer through a release before this one and need that revocation to have taken effect should confirm with wg show <interface> peers.

    The cause is a platform-wide limitation rather than anything specific to uapi. netifd reads peer sections with config_foreach wireguard_<iface> inside the proto setup step, so a peer edit leaves the parent interface section unchanged, /etc/init.d/network reload finds nothing to converge, and does nothing at all. LuCI is affected too and documents the workaround in its own peer form ("Restart wireguard interface to apply changes"), because its Save and Apply resolves to that same network reload through a config.change event that carries only a package name and so cannot express which interface changed. uapi knows which resource was written, and therefore which interface is affected, so it applies the change itself.

    Each peer write is now pushed to the kernel with wg set after the commit: a set for a create or update, a remove for a delete, a remove for a peer being disabled, and a remove of the previous key before the set when a PUT rotates public_key. This is a new external command alongside the reload, apk and passwd calls uapi already makes. It is deliberate: WireGuard exposes no ubus service for peers, so netifd shells out to wg and so does LuCI's own backend. Asking netifd to re-apply instead was implemented and rejected on measurement, because the ubus call returns before the work happens and a failure takes the whole interface down: a single peer with an unresolvable endpoint_host dropped a working tunnel and its healthy peers while the API answered 200. With wg set a bad peer fails alone, synchronously, and the write rolls back with the reason reported. uci remains the only config writer and the applied state is derived from committed uci, never from the request. endpoint_host is shell-quoted rather than newly validated, so no previously accepted payload starts being rejected; a preshared key is passed as a 0600 file and never as an argument.

    route_allowed_ips is applied too, with the routes spelled the same way netifd spells them and placed in ip4table / ip6table when the interface sets one; a prefix is withdrawn only once no remaining peer and no config route section still wants it. Peers on an interface that is down, or that netifd does not know, are written to uci and applied at the next ifup, as before. Closes openwrt-iac/uapi#51.

  • network/interfaces no longer discards half of a body that sets ipaddr and ipaddrs to different addresses. Both are wire names for the same uci list ipaddr, and the list won whenever it was non-empty, so the scalar was dropped and the write answered 200 with the old address read back: a caller re-reading saw its own change vanish rather than fail.

    The answer differs by method, because the methods differ in what a caller can express. POST and PATCH report 422 validation_failed with a conflict on ipaddr: naming both there is a choice, and PATCH can say which one it meant. PUT cannot. A full-replace caller sends every field it knows, the read mirrors the first list entry into ipaddr, so the scalar sits in its state even when its own config named only the list, and one of the two is stale by construction on every apply. Rejecting that body made ipaddrs unwritable through any such client. On PUT a differing ipaddr is therefore dropped in favour of the list, which is the precedence the write path already applied, so no uci outcome changes.

    Carve-out (docs/versioning.md): the POST and PATCH rejection refuses a payload earlier releases accepted, and ships in a patch because the state it produced was one no caller could rely on. The request said two different things about one option and was told neither had been ignored.

    The same collision made PATCH naming only ipaddr a silent no-op, since the merge folded the just-read ipaddrs into the body and that won. Whichever of the two the caller actually names now wins, and the other is dropped rather than resurrected from the server's own read. Closes openwrt-iac/uapi#60 and openwrt-iac/uapi#65.

  • network/wireguard_peers accepts IPv6 and bare addresses in allowed_ips. The field required IPv4 CIDR notation, so every IPv6 peer was refused and a dual-stack tunnel could not be configured through the API at all, and a bare address was refused even though it is the form wg show prints back and netifd turns into a host route. All four shapes wg accepts (10.0.0.0/24, 10.0.0.5, fd00::/64, fd00::1, and the 0.0.0.0/0 and ::/0 catch-alls) are now accepted, checked against wg set on a real interface, and what wg rejects is still rejected. This only widens what is accepted, so no payload that worked before stops working.

  • network/rules accepts a packet mark as the only selector. It required one of in/out/src/dest, but mark is a selector in its own right and the one policy routing of reply traffic depends on: firewall4 marks in mangle prerouting and the rule sends the mark to a table, with neither source nor destination knowable in advance. ip rule add fwmark 0x43 lookup 43 is valid, netifd writes exactly that from a rule carrying only mark, lookup and priority, and the kernel prints it back as from all fwmark 0x43. The check prevented nothing, since the workaround was to add src: "0.0.0.0/0", which is what a mark-only rule already means. Closes openwrt-iac/uapi#52.