Skip to content

Jail/bridge fixes and a bridge-port procd data type for external bridge membership - #89

Open
dangowrt wants to merge 4 commits into
openwrt:masterfrom
dangowrt:netifd-procd-data
Open

Jail/bridge fixes and a bridge-port procd data type for external bridge membership#89
dangowrt wants to merge 4 commits into
openwrt:masterfrom
dangowrt:netifd-procd-data

Conversation

@dangowrt

@dangowrt dangowrt commented Sep 2, 2026

Copy link
Copy Markdown
Member

Three independent, self-contained bugfixes on the jail and bridge paths, plus one new opt-in procd data type. The three fixes are individually cherry-pickable and useful on their own even if the feature needs discussion.

  • bridge: do not free uninitialised vlan_aliases kvlist (Fixes: 4544f026bb09). A bridge created and freed over ubus without ever going through a config load never initialises its vlan_aliases kvlist, so bridge_free() dereferences it. Guard the free.
  • system-linux: refuse netns move of an unresolved device (Fixes: d93126d3b91e). system_link_netns_move() sent RTM_NEWLINK with ifi_index = 0 when the source device could not be resolved, so the kernel selected the target by name and could move the host's real interface (for example eth0) into a container. Bail out when the device has no ifindex.
  • interface: defer jailed device move until the device exists (Fixes: 1321c1bd8fe9). interface_start_jail() moved each jailed interface's device at netns registration time; if the device did not exist yet (a veth whose creation had not been triggered), the move was a no-op and the container came up with no network device. Keep a reference to the jail netns and complete the move once the device appears.
  • config: add bridge-port procd data type for external bridge membership. Bridge membership is declared only from the bridge side today (a bridge's own ports list and the ports lists of its bridge-vlan sections), so a separate procd publisher such as a container manager that wants to attach a port to a bridge it does not own has no declarative way to do so: a same-named network-device entry replaces the bridge's ports, and a bridge-vlan entry replaces the VLAN and forces vlan_filtering on. The new bridge-port type, keyed by the port device name and carrying { "device": "<bridge>", "vlans": [ "<vid>[:t|:*]", ... ] }, is merged into the loaded UCI network package in memory at the start of each config load, following the precedent of config_fixup_bridge_ports(). Nothing is committed and the package is reloaded on every config_init_all(), so the port lives exactly as long as the publisher's service data and is dropped by the bridge's vlist flush on the first reload after the data is withdrawn. The type is standalone: it introduces no build or runtime dependency on procd and is simply ignored when nothing publishes it.

The feature is consumed by procd's ujail/uxc-net, which publishes container veths as bridge-port entries so a container can join an existing user-owned bridge (including a VLAN-filtering bridge) without disturbing the owner's configuration, but netifd does not depend on that.

The vlan_aliases kvlist is initialised lazily in device_vlan_update(),
which only runs during configuration load. A bridge that is created and
freed without going through a config load (e.g. one created at runtime
over ubus) reaches bridge_free() with the kvlist still zero-filled, and
kvlist_free() then walks an uninitialised avl_tree whose list head
contains NULL pointers, crashing netifd. Guard the call on get_len, the
same marker device_vlan_update() uses to tell an initialised kvlist
apart.

Fixes: 4544f02 ("bridge-vlan: add support for defining aliases for vlan ids")
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
system_link_netns_move() built its RTM_NEWLINK message with whatever
system_if_resolve() returned for the source device. When the device does
not exist (e.g. a jailed interface whose veth has not been created yet),
system_if_resolve() returns 0, and the message goes out with
ifi_index = 0 while IFLA_IFNAME is set to target_ifname (the jail_device
name).

The kernel's __rtnl_newlink() then selects the target device by name when
ifi_index is 0 (net/core/rtnetlink.c: rtnl_dev_get -> __dev_get_by_name),
so the move operates on whatever host device happens to be named like the
jail_device. With a jail_device of "eth0" this moves the host's real eth0
into the container's network namespace, knocking the host off the
network.

Bail out when the source device cannot be resolved to a real ifindex so
the request is never sent with a zero index.

Fixes: d93126d ("interface: allow renaming interface when moving to jail netns")
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
interface_start_jail() moved each jailed interface's main device into the
container netns at the moment the jail's netns was registered. If the
device did not exist yet (e.g. a veth whose creation had not been
triggered), the move was a no-op and the container came up with no
network device; the in-jail netifd then failed DHCP with
"udhcpc: SIOCGIFINDEX: No such device" until the operator manually
brought the host interface up.

Keep a duplicated reference to the jail netns on the interface when the
move cannot be performed yet, and complete it from interface_main_dev_cb
once the device appears (DEV_EVENT_ADD). Any stale pending reference from
an earlier jail incarnation is dropped on the next interface_start_jail()
invocation, so a successful immediate move can never leave a dangling
netns reference behind that would later divert the device into a dead
namespace. interface_stop_jail and interface_free drop any still-pending
reference as well.

Fixes: 1321c1b ("add basic support for jail network namespaces")
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
Bridge membership is declared from the bridge side only: a bridge's
"ports" list in UCI or in a "network-device" procd data entry, and the
"ports" lists of its bridge-vlan sections. A separate procd publisher
such as a container manager that wants to attach a port to a bridge it
does not own therefore has no declarative way to do so. Publishing a
same-named "network-device" entry replaces the bridge's ports, and a
"bridge-vlan" entry replaces the VLAN and forces vlan_filtering on.

Add a "bridge-port" procd data type keyed by the port device name:

  "bridge-port": {
    "<port>": { "device": "<bridge>", "vlans": [ "<vid>[:t|:*]", ... ] }
  }

The entries are read at the start of each config load and merged into
the loaded UCI network package in memory, following the precedent of
config_fixup_bridge_ports(): the port is appended to the "ports" list of
the UCI device section whose name matches "device", and each "vlans"
element appends "<port>[:flags]" to the "ports" list of the bridge-vlan
section with the matching device and vlan id. A "ports" option written
as a whitespace separated string is split into a list first, since
uci_add_list() would otherwise keep the whole string as a single list
element. Legacy bridges declared with "option type bridge" on an
interface section are merged in the same way. Nothing is committed, and
config_init_package() reloads the package on every config_init_all(), so
the merge only lasts as long as the publisher's service data exists. The
port becomes an ordinary versioned member and is dropped by the bridge's
vlist flush on the first reload after the data is withdrawn.

Only UCI-defined bridges and bridge-vlan sections are merged; bridges
defined through "network-device" data carry their own "ports" from their
single owner. "vlans" elements naming a vlan id without a matching
bridge-vlan section are ignored, as are bridge-vlan sections whose vlan
id is not a number between 1 and 4095.

Signed-off-by: Daniel Golle <daniel@makrotopia.org>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant