<feed xmlns='http://www.w3.org/2005/Atom'>
<title>procd/jail/uxc-net, branch master</title>
<subtitle>OpenWrt service / process manager</subtitle>
<id>https://git.openwrt.org/project/procd/atom?h=master</id>
<link rel='self' href='https://git.openwrt.org/project/procd/atom?h=master'/>
<link rel='alternate' type='text/html' href='https://git.openwrt.org/project/procd/'/>
<updated>2026-09-02T09:07:05Z</updated>
<entry>
<title>uxc-net: attach containers to multiple bridge VLANs</title>
<updated>2026-09-02T09:07:05Z</updated>
<author>
<name>Daniel Golle</name>
</author>
<published>2026-09-02T08:41:17Z</published>
<link rel='alternate' type='text/html' href='https://git.openwrt.org/project/procd/commit/?id=6d44d6bb77e27ecbaa881976e2bb4d3cf8ccd253'/>
<id>urn:sha1:6d44d6bb77e27ecbaa881976e2bb4d3cf8ccd253</id>
<content type='text'>
A bridged container joined exactly one VLAN of a VLAN-filtering bridge:
bridge_of() turned the br-X.N device of the attach network into a single
"N:*" element on the bridge-port entry, so the host end of the veth was
an untagged access port and the container could reach no other VLAN of
that bridge.

Add the annotation org.openwrt.network.vlans, a comma-separated list of
&lt;vid&gt;[:t] entries that has a meaning only on top of bridged:&lt;net&gt; where
&lt;net&gt; sits on a VLAN-filtering bridge. The attach network's own VLAN
stays the untagged PVID member exactly as before; a bare &lt;vid&gt; adds the
port as a further untagged member and &lt;vid&gt;:t adds it as a tagged trunk
member. Without the annotation nothing changes, neither for a plain
bridge nor for a single VLAN.

On the host the bridge-port entry keeps its one "vlans" array, which now
carries every requested element, for example ["20:*","30:t","40:t"].
netifd merges each element into the ports list of the UCI bridge-vlan
section with that VID, so a single entry puts the veth into several
VLANs with the right flag on each. VIDs must lie within 1 to 4094,
identical entries are folded, and an entry is rejected when it is listed
both tagged and untagged, when it names the attach network's own VLAN,
or when the bridge has no such VLAN according to network.device status.
The last check matters because netifd merges only into bridge-vlan
sections that exist and a port of a VLAN-filtering bridge without any
membership forwards nothing, so accepting the VID would leave a dead
interface in the container. Networks created on demand are plain
bridges owned by uxc-net and refuse the annotation.

Inside the container a tagged VLAN is reachable only through an 802.1Q
sub-interface, so for every &lt;vid&gt;:t the in-jail network file gains a
device section of type 8021q named eth0.&lt;vid&gt; on top of eth0 and an
interface 'vlan&lt;vid&gt;' on that device. The same netifd runs inside the
jail and creates the device when the interface claims it, given the
8021q module on the host kernel. Untagged members need nothing inside:
their frames are the untagged traffic on eth0. The VLAN interfaces
default to proto none and switch to static when
org.openwrt.network.vlan.&lt;vid&gt;.address supplies an address/prefix. A
container on a trunk typically uses its extra VLANs for plain L2 or for
a service with a fixed address; a DHCP client on every VLAN is unusual
and would compete for the default route that eth0 already provides.

Teardown is unchanged: withdrawing the instance data removes the
bridge-port entry with all of its memberships on the next reload, and
the in-jail file is per container.

Signed-off-by: Daniel Golle &lt;daniel@makrotopia.org&gt;
</content>
</entry>
<entry>
<title>uxc-net: publish host network as procd service data instead of driving netifd</title>
<updated>2026-09-01T23:28:42Z</updated>
<author>
<name>Daniel Golle</name>
</author>
<published>2026-09-01T23:11:29Z</published>
<link rel='alternate' type='text/html' href='https://git.openwrt.org/project/procd/commit/?id=8b66962cbf3ed41553e0c7f6bbffa83448bdeb2d'/>
<id>urn:sha1:8b66962cbf3ed41553e0c7f6bbffa83448bdeb2d</id>
<content type='text'>
Describe the host side of a container's network as procd service data
and let netifd materialise it on config load, rather than creating the
objects one by one through the runtime ubus API (network create_device,
network add_dynamic, network.interface.X add_device/remove_device/remove).
Only the read-only queries (interface status, device status, interface
dump, service get_data) remain.

Everything a single container needs is attached to its own instance in
one set_data call: the veth pair as a network-device entry, the jailed
end (and the routed /31 gateway) as network-interface entries, the port
membership in a user-owned UCI bridge as a bridge-port entry, plus the
firewall and dhcp sections that were already published this way. The
blob is assembled by a single function that always emits all five type
keys, because set_data replaces the whole instance blob and a partial
publish would silently withdraw the container's link. The lifecycle of
these objects is therefore bound to the instance record: withdrawing the
data on down, or deleting the instance, removes them on the next reload
without any explicit teardown call.

Bridges that uxc-net owns (on-demand br-&lt;net&gt; autonets and bhr-&lt;seg&gt;
backhaul segments) are shared between containers and live at service
level on the uxc-net data service, since netifd replaces a bridge's
ports list on every publish and exactly one publisher must hold the
full union. The ports list of each owned bridge is the reference count:
on every up and down the list is pruned to the veth names currently
published by some instance, an owned bridge whose ports run empty is
dropped together with its interface, zone and dhcp entries, and the
service is deleted once nothing is left. The read-modify-write of the
shared data, and the instance publish that follows it, are serialised
with an exclusive flock so that concurrent container starts cannot lose
an update or prune each other's pending port. Subnet selection for a new
autonet also takes the published interfaces into account, not only
netifd's runtime state.

netifd re-reads procd data only during config load, so after each publish
or withdrawal a synchronous network reload is issued before the firewall
and dhcp config.change events. On up a failed reload is fatal, because
the jailed interface and its veth must exist by the time ujail hands the
network namespace over with netns_updown.

The veth blob is byte-stable across republishes: MAC addresses come from
the persisted store and are never regenerated, so a reload never
restarts the pair and never destroys the container's eth0. The in-jail
UCI rendering, annotation parsing and IPv6 handling are unchanged. The
downstream-only persistent attribute is gone from all published
interfaces.

Signed-off-by: Daniel Golle &lt;daniel@makrotopia.org&gt;
</content>
</entry>
<entry>
<title>uxc-net: publish the host configuration instead of writing UCI</title>
<updated>2026-08-22T22:55:02Z</updated>
<author>
<name>Daniel Golle</name>
</author>
<published>2026-08-22T22:55:02Z</published>
<link rel='alternate' type='text/html' href='https://git.openwrt.org/project/procd/commit/?id=5f3bd0b66867afc9d7ff8bd9c11721f63a0be131'/>
<id>urn:sha1:5f3bd0b66867afc9d7ff8bd9c11721f63a0be131</id>
<content type='text'>
Every container brought a bridge, an interface, a firewall zone with its
forwardings and rules, and a DHCP section into /etc/config, committed at
ten sites across three packages. That is a flash write to the overlay on
every start and stop, and whatever a power cut interrupts stays behind,
describing a container that no longer exists. Stopping a container also
left the deletions staged in /tmp/.uci rather than applied, so the next
"Save &amp; Apply" decided what the host config would be.

Publish it as procd data instead. A container's zone, forwardings,
redirects, rules and DHCP section go into its own instance data, which
procd drops when the instance goes away, so the host configuration
cannot outlive what it describes. fw4 already folds data of type
"firewall" into its ruleset and odhcpd now does the same for "dhcp", and
both are told to reload with the config.change event they already
trigger on, because attaching data to an instance emits no event of its
own.

Networks created on demand for bridged containers are shared, so they
cannot hang off one container's instance. They live in the data of a
service of their own, and the published set is the state: the list is
read back to add or drop a member, which is why no state file is needed
and why nothing is lost when the volume holding it is not mounted.

The zone a container gets is closed, so the services the host offers
into it are opened explicitly, the way /etc/config/firewall opens them
for wan rather than fw4 inferring anything: DNS always, ICMPv6 whenever
the container has any IPv6 at all because neighbour discovery is ICMPv6
over IP and a static-IPv6 container behind a closed zone cannot resolve
its gateway, and DHCPv6 only when the container asks for it.

Creating the interface no longer waits for a reload to bring it up, so
the thirty second wait for the interface object to appear is gone.

Signed-off-by: Daniel Golle &lt;daniel@makrotopia.org&gt;
</content>
</entry>
<entry>
<title>uxc-net: remove the host configuration it creates</title>
<updated>2026-08-21T17:56:17Z</updated>
<author>
<name>Daniel Golle</name>
</author>
<published>2026-08-21T17:53:37Z</published>
<link rel='alternate' type='text/html' href='https://git.openwrt.org/project/procd/commit/?id=66781b2b3544ba552ed31b61897ec6471c4a52e4'/>
<id>urn:sha1:66781b2b3544ba552ed31b61897ec6471c4a52e4</id>
<content type='text'>
ensure_network() commits persistent sections to network, dhcp and
firewall when a bridged attachment names a network that does not exist,
and bridged_down() never removed them, so a single container run left a
network, a DHCP pool and a firewall zone in /etc/config forever.

Removing the sections on bring-down is the right resolution rather than
not committing them in the first place, because the DHCP pool can only
exist as persistent configuration: dnsmasq reads UCI alone and offers no
ubus path to hand it a pool, so an ephemerally created network would be
one without addressing. The per-container firewall sections already
follow this lifecycle for the same reason, stock fw4 ignoring zone data
delivered over ubus: create at start, idempotent, delete on teardown.

Record each auto-created network in a marker under the uxc state
directory and name the created sections deterministically, the device
section as &lt;net&gt;_dev and the firewall zone section as &lt;net&gt;. On
bring-down of a bridged container the network is removed again when the
marker exists and its bridge has no members left, deleting exactly the
four sections creation wrote, and the marker with them. A network the
operator defined is never touched, because it has no marker, and a
network still carrying other containers keeps its configuration until
the last member goes down. The marker lives on the uvol metadata volume,
so a firmware re-image that wipes /etc/config but keeps the container
registrations simply leads to the network being recreated on the next
start.

A marker can go stale if an auto-created network is never brought down
and the operator later defines the same network by hand; the next
last-member bring-down would then remove the hand-written sections. The
window is narrow and the alternative is refcounting operator intent,
which UCI cannot express.

With this, bring-down restores /etc/config for bridged containers as
well; while a container is up, its container-lifetime sections exist,
exactly as the routed firewall and router advertisement sections do.

Signed-off-by: Daniel Golle &lt;daniel@makrotopia.org&gt;
</content>
</entry>
<entry>
<title>uxc-net: make routed containers a delegated-prefix downstream</title>
<updated>2026-08-21T17:56:17Z</updated>
<author>
<name>Daniel Golle</name>
</author>
<published>2026-08-21T17:51:47Z</published>
<link rel='alternate' type='text/html' href='https://git.openwrt.org/project/procd/commit/?id=85dce23e684aef2abbfb6e48b02613ab39258471'/>
<id>urn:sha1:85dce23e684aef2abbfb6e48b02613ab39258471</id>
<content type='text'>
A routed container had no IPv6 path at all: the host gateway interface
carried only the /31 IPv4 address, and nothing on the host answered
router solicitations on the link, so a container asking for proto6 could
never configure itself.

Give the gateway interface created through network.add_dynamic an
ip6assign of 64 and ip6ifaceid '::1'. netifd hands the whole blob to
interface_alloc(), which parses every interface attribute, so netifd
carves a /64 for the link out of the upstream delegated prefix and the
host end takes the deterministic ::1 address within it; no netifd change
is needed.

Prefix assignment alone does not make the link work: router
advertisements must actually be emitted on the gateway interface for the
container to learn the prefix and its default route. That takes an
odhcpd section, which is new host-side state: bring-up commits a
per-container section named after the gateway interface to the
persistent dhcp configuration, with ra and dhcpv6 in server mode, and
reloads it through the config.change service event. Bring-down removes
exactly that section again, following the lifecycle the per-container
firewall sections already use. dnsmasq ignores the section because its
dhcpv4 option defaults to disabled.

fw_reload() becomes pkg_reload() so the firewall and dhcp reload paths
share one helper.

Signed-off-by: Daniel Golle &lt;daniel@makrotopia.org&gt;
</content>
</entry>
<entry>
<title>uxc-net: add IPv6 addressing and a deterministic interface identifier</title>
<updated>2026-08-21T17:56:17Z</updated>
<author>
<name>Daniel Golle</name>
</author>
<published>2026-08-21T17:50:01Z</published>
<link rel='alternate' type='text/html' href='https://git.openwrt.org/project/procd/commit/?id=ef6d6e62c946efc9497bf886d3c5f8d74e85ac85'/>
<id>urn:sha1:ef6d6e62c946efc9497bf886d3c5f8d74e85ac85</id>
<content type='text'>
The in-jail interface was IPv4 only; nothing in the contract could ask
for an IPv6 address.

Add org.openwrt.network.proto6, accepting 'dhcpv6', 'slaac', 'static'
and 'none'. Where IPv6 is requested a second in-jail interface section
is rendered on the moved device: 'dhcpv6' runs the full odhcp6c cycle,
'slaac' renders proto 'dhcpv6' with reqaddress 'none' and reqprefix 'no'
so odhcp6c only processes router advertisements, and 'static' takes
org.openwrt.network.address6 in address/prefix notation with an optional
org.openwrt.network.gateway6.

Add org.openwrt.network.ip6ifaceid, an address with a zero network part
such as '::1234', rendered as option ip6ifaceid on the dhcpv6 section;
/lib/netifd/proto/dhcpv6.sh hands it to odhcp6c through -i, giving the
container a deterministic interface identifier within whatever prefix
the link advertises.

The default is 'none': no IPv6 section is rendered, preserving the
existing behaviour for containers that do not ask for IPv6.

Signed-off-by: Daniel Golle &lt;daniel@makrotopia.org&gt;
</content>
</entry>
<entry>
<title>uxc-net: honour org.openwrt.network.proto for the in-jail interface</title>
<updated>2026-08-21T17:56:17Z</updated>
<author>
<name>Daniel Golle</name>
</author>
<published>2026-08-21T17:48:51Z</published>
<link rel='alternate' type='text/html' href='https://git.openwrt.org/project/procd/commit/?id=17a6650cd060e5c4cdfeb92249083f66976a4bdc'/>
<id>urn:sha1:17a6650cd060e5c4cdfeb92249083f66976a4bdc</id>
<content type='text'>
The in-jail addressing was hardcoded per attachment mode: bridged
containers always received a DHCP interface and routed containers always
received the static /31 configuration, while the documented
org.openwrt.network.proto annotation was read nowhere.

Implement the annotation. Replace the hardcoded configuration blobs with
a renderer parameterised on the requested protocol, composed through
append_injail(), and regenerate the in-jail file from scratch on every
bring-up so a stale file from an earlier run cannot leak into the
composition.

Accepted values are 'dhcp' and 'static'. The defaults preserve today's
behaviour exactly: bridged defaults to 'dhcp', routed to 'static'. An
unknown value is rejected with a diagnostic instead of silently falling
back. Requesting 'dhcp' for a routed container is refused as well,
because routed builds a /31 point-to-point link whose gateway end runs
no DHCP server, so the request could never be served.

A bridged container with proto 'static' takes its address from
org.openwrt.network.address in address/prefix notation, with the
optional org.openwrt.network.gateway and org.openwrt.network.dns
annotations rendered verbatim; the backhaul section now goes through the
same renderer.

Signed-off-by: Daniel Golle &lt;daniel@makrotopia.org&gt;
</content>
</entry>
<entry>
<title>jail: wire container network-namespace setup to uxc-net</title>
<updated>2026-08-21T04:32:01Z</updated>
<author>
<name>Daniel Golle</name>
</author>
<published>2026-06-17T14:41:32Z</published>
<link rel='alternate' type='text/html' href='https://git.openwrt.org/project/procd/commit/?id=b1407e790711e13b1c6036ac61489bdb20b316cb'/>
<id>urn:sha1:b1407e790711e13b1c6036ac61489bdb20b316cb</id>
<content type='text'>
Drive uxc-net synchronously on start and stop: "up" compiles the
container's declarative annotations into ephemeral host netifd config,
declaring the jail interface that the subsequent host-side device move picks
up, and "down" tears it down. The host device move (jail_network_attach)
now runs for every named netns container, so an annotation-less container
still receives its host-managed device without a private netifd.

A container-private ubusd and netifd are opt-in via annotations and default
off, so OCI orchestrators managing networking on the host side are not
fought by a parallel in-jail netifd. gen_jail_uci_network() drops the UCI
section-rewriting in favour of copying the config uxc-net rendered, falling
back to bare loopback. The netifd startup wait gains a timeout backstop, and
a stale ubus socket from an unclean teardown is removed so its IN_CREATE
still fires.

jail_network_stop becomes the netns-agnostic jail_network_teardown, called
from poststop and every error path so the per-jail ubusd and netifd never
leak as procd orphans.

Signed-off-by: Daniel Golle &lt;daniel@makrotopia.org&gt;
</content>
</entry>
<entry>
<title>uxc-net: declarative container networking helper</title>
<updated>2026-08-21T04:31:44Z</updated>
<author>
<name>Daniel Golle</name>
</author>
<published>2026-06-17T14:37:24Z</published>
<link rel='alternate' type='text/html' href='https://git.openwrt.org/project/procd/commit/?id=794866611371f70882da073b27188f38ce27e024'/>
<id>urn:sha1:794866611371f70882da073b27188f38ce27e024</id>
<content type='text'>
Add uxc-net, a ucode helper that brings a container's host-side network
up and down over ubus, leaving no residue in persistent /etc/config. A veth
is created in netifd and the container end handed to the jail netns via a
dynamic jail interface; the host end is wired per attachment mode read from
the OCI annotations (org.openwrt.network.{attach,egress,ingress,proto}).

bridged:&lt;network&gt; enslaves the host end into a network's bridge and the jail
inherits that network's zone wholesale, auto-creating an isolated network
once if it is absent. routed places the host end as the gateway of a
point-to-point /31 in the container's own fw4 zone, compiling egress and
ingress into explicit forwardings and DNAT redirects, defaulting to
deny-all. host and none do no wiring; rollback is symmetric.

Every netifd-facing identifier is an fnv1a slug of the qualified name to fit
IFNAMSIZ and avoid the dot being read as a VLAN tag. The in-jail netifd
config is rendered to /tmp/run/uxc-net so the container side is configured
from the same declarative source.

Signed-off-by: Daniel Golle &lt;daniel@makrotopia.org&gt;
</content>
</entry>
</feed>
