Before you start#
Read tc, XDP, and network BPF, netfilter and nftables, and routing and neighbours. Linux v6.12 is the historical source baseline. The lab reads the current namespace and does not create, move or modify interfaces.
Learning goals#
Trace a veth handoff and bridge forwarding decision, distinguish a namespace name from its lifetime, and explain where IP routing, NAT and tunnel encapsulation can conditionally enter a container packet path.
Keep the layers separate#
Start with the object that answers each question: a namespace owns network-stack state; a veth pair supplies a virtual Ethernet link; a bridge forwards eligible Ethernet frames by MAC address and VLAN; IP routing chooses a next hop for an IP packet; netfilter policy can accept, drop or transform traffic at configured hooks; a tunnel adds an outer packet around an inner one. One packet can cross several of these layers, but none implies that the next layer is configured or succeeds.
The compare view below lists which key each layer uses, which is the distinction the walk later in this chapter depends on.
- Namespace
- Owns the network stack state: links, routes and sockets.
- Bridge FDB
- Keys on destination MAC and VLAN; selects a port or local delivery.
- IP FIB
- Keys on destination IP prefix; selects a next hop and output device.
- Netfilter and NAT
- Act only where hooks, rules and conntrack state are configured.
Source walkthrough#
- Container namespace
Socket route and neighbour select a veth path.
- veth peers
Transmit transfers skb to peer or drops on failure.
- Bridge
FDB, VLAN and port state choose forward, flood or local delivery.
- Optional IP forwarding
Host route, policy and NAT apply only when configured.
A network namespace has its own network stack state, including interfaces, routes, rules, sockets and many protocol settings. Kernel struct net represents that namespace. pernet_operations let subsystems initialize and tear down per-network state in an ordered lifecycle. A process's network namespace association, an open namespace fd or bind-mounted named namespace, sockets and other kernel references can keep a namespace in use according to their contracts. A named namespace is a userspace handle to an object, not the object itself: deleting its name does not necessarily destroy it while processes or references remain. See include/net/net_namespace.h and net/core/net_namespace.c, and ip-netns(8), which documents the named-handle convention and open-fd lifetime.
Sockets are associated with a network namespace at creation. Moving a task to a different network namespace does not automatically migrate an existing socket to the new one. Moving a netdev between namespaces is a separate operation with restrictions, namespace authority checks, naming and lifetime consequences; not every device can be moved. A namespace can be owned by a user namespace, and capabilities relative to that owner influence what an operation is allowed to do. Network namespaces isolate specific network resources but are not a complete security boundary against all privileged operations, shared kernel vulnerabilities or non-network resources.
cleanup_net runs from workqueue context after a namespace has entered the cleanup lifecycle. In net/core/net_namespace.c, v6.12, GPL-2.0-only, the removal phase includes:
llist_for_each_entry(net, net_kill_list, cleanup_list)
list_del_rcu(&net->list);
The cleanup code removes namespaces from the global list with RCU-aware deletion while holding the applicable namespace locks. Later it invokes per-network pre-exit and exit operations, handles devices and waits for appropriate synchronization before reclaiming state. Removing the named handle is not the same event as this cleanup, and list deletion is not permission to free an object still protected by references or RCU readers. Unregistering pernet operations also has ordering and synchronization requirements; see the full implementation.
veth handoff and teardown#
A veth pair has two virtual Ethernet endpoints connected as peers. Transmitting an skb on one endpoint can hand it to the receive side of its peer, possibly in a different network namespace. Each endpoint is a net_device with its own namespace, state, queues and counters. No physical cable or NIC is implied. An endpoint can be down, missing, detached from a bridge or unable to accept a packet; the peer relation alone does not provide addressing, routes or policy.
In veth_xmit, Linux v6.12, GPL-2.0-only, the driver reads its peer under RCU and checks packet header availability:
rcv = rcu_dereference(priv->peer);
if (unlikely(!rcv) || !pskb_may_pull(skb, ETH_HLEN)) {
kfree_skb(skb);
goto drop;
}
The RCU lookup protects the peer pointer during the read-side section; it does not create an eternal reference. If the peer is missing or the Ethernet header cannot be made available, this branch frees the skb and goes to drop accounting. Otherwise the function selects a receive queue when applicable and calls veth_forward_skb; NAPI/GRO use is conditional on peer queue state and skb eligibility. On failure, the driver's drop counter is incremented and its result reflects the drop path. Teardown and peer removal must coordinate readers and device lifetimes so an in-flight transmit does not dereference freed peer state.
For a hypothetical frame of 100 bytes, endpoint A transmits toward peer B. If B exists, is suitable for the forwarding path and processing succeeds, the frame is handed into B's receive processing; a bridge or IP stack attached there may then make another decision. If B is removed before the RCU peer lookup returns a usable pointer, the shown branch drops the skb instead. A counter on A and a counter on B can be updated at different stages, and GRO can change later skb counts. The example does not imply a particular interrupt or physical wire event.
The handoff after a successful peer lookup is in the helper that veth_xmit calls. In drivers/net/veth.c, Linux v6.12, GPL-2.0-only (surrounding code omitted):
static int veth_forward_skb(struct net_device *dev, struct sk_buff *skb,
struct veth_rq *rq, bool xdp)
{
return __dev_forward_skb(dev, skb) ?: xdp ?
veth_xdp_rx(rq, skb) :
__netif_rx(skb);
}
In this GNU ?: expression, a nonzero result from __dev_forward_skb is returned as the result; otherwise an XDP-enabled peer goes to veth_xdp_rx, and the remaining case calls __netif_rx. The excerpt does not show queue selection or the GRO decision, and it does not prove that the peer's stack accepts the frame.
Bridge forwarding versus IP routing#
A Linux bridge forwards Ethernet frames between bridge ports. Its forwarding database (FDB) maps destination MAC address and VLAN context to a port or local destination. Source MAC learning can update an FDB entry when a frame arrives, subject to port, VLAN and bridge policy. A known unicast destination can be forwarded to the matching eligible port; unknown unicast, broadcast or multicast can be flooded to eligible ports under their respective rules. VLAN filtering changes the forwarding domain, and spanning tree protocol (STP), port state and configuration can block or constrain forwarding. A bridge is not merely an IP router with different syntax.
In br_handle_frame_finish, Linux v6.12, GPL-2.0-or-later, the known-destination branch includes:
br_forward(dst->dst, skb, local_rcv, false);
The selected FDB destination provides the output bridge port and the skb is handed to bridge forwarding with the indicated local-delivery state. The following else branch selects flood behavior when no destination entry is available, subject to multicast handling and other conditions. The function also checks port state, VLAN admission, learning and local delivery. An FDB lookup and its RCU/lifetime rules do not establish that an IP route to a remote subnet exists. See br_fdb_update and br_forward.
The forwarding decision itself is in the same bridge receive function. In net/bridge/br_input.c, Linux v6.12, GPL-2.0-or-later (surrounding code omitted):
if (dst) {
unsigned long now = jiffies;
if (test_bit(BR_FDB_LOCAL, &dst->flags))
return br_pass_frame_up(skb, false);
if (now != dst->used)
dst->used = now;
br_forward(dst->dst, skb, local_rcv, false);
} else {
if (!mcast_hit)
br_flood(br, skb, pkt_type, local_rcv, false, vid);
else
br_multicast_flood(mdst, skb, brmctx, local_rcv, false);
}
The lookup key is the destination MAC and VLAN from br_fdb_find_rcu. A local-only entry passes the frame up, a known entry is forwarded to its port, and a miss floods, through the multicast path when a multicast entry matched. The excerpt does not show the port-state, VLAN admission or learning checks that happen earlier, and it does not describe what the IP stack does with a locally delivered frame.
Imagine a bridge with ports P1 and P2 in the same permitted VLAN. It learns source MAC A on P1 from a valid incoming frame. Later a frame from P2 to A can be sent toward P1 if the entry remains valid and the ports permit forwarding. Before the entry is learned, an unknown unicast frame can be flooded to eligible ports instead. A frame addressed to the bridge itself may be delivered locally. The FDB key and port state matter; the bridge does not look up a TCP port to choose an Ethernet output. IP routing and neighbor resolution can happen later if the host's IP stack receives and forwards an IP packet.
Bridge netfilter integration is optional and configuration-dependent. Bridge-family nftables hooks and optional br_netfilter behavior are not identical to saying every bridged frame traverses all IPv4/IPv6 routing hooks. Likewise, NAT only occurs where suitable rules and conntrack behavior are configured; it is not an automatic property of a bridge or veth pair. A flow offload or early XDP decision can alter observability. Describe the specific bridge, namespace, policy and hook placement before inferring which counters should change.
A conditional container packet walk#
Suppose namespace C contains a process with an IPv4 socket and veth endpoint C0; its peer H0 is in a host-side namespace and attached to a bridge. The process routes a packet toward its configured gateway, resolves the relevant neighbor, and sends an Ethernet frame through C0. veth_xmit hands the skb to H0's receive side if the peer and device conditions allow it. The bridge checks VLAN and port state and uses its FDB or flooding rules. If the destination MAC belongs to the bridge/host gateway, the host IP stack can receive the packet; with forwarding enabled and a matching route, it can forward to another interface. If configured netfilter hooks and NAT rules apply, conntrack and NAT can transform the flow at their relevant points.
Every arrow in that story has a condition. A missing route in C, unresolved neighbor, down veth, blocked bridge port, missing forwarding configuration, policy drop, failed NAT mapping or unavailable output device can stop delivery. If the destination is another container on the same bridge and VLAN, the bridge may forward directly without the host routing that packet. If the packet is destined to the host, it can be delivered locally rather than forwarded. This is a conceptual walk, not an assertion about your workstation or a captured trace.
The sequence below covers only the veth and bridge boundary; routing and NAT are conditional steps after the host receives the frame.
- Container C0 vethPeer H0 veth
veth_xmit: RCU peer lookup and Ethernet header pull
- Peer H0 vethBridge port receive
veth_forward_skb: frame enters the bridge port
- Bridge port receiveHost IP stack
local delivery only: br_pass_frame_up
Time runs downward. Dashed arrows show returns.
Read the text version
- Container C0 veth → Peer H0 veth: veth_xmit: RCU peer lookup and Ethernet header pull
- Peer H0 veth → Bridge port receive: veth_forward_skb: frame enters the bridge port
- Bridge port receive → Host IP stack: local delivery only: br_pass_frame_up
Tunnels and MTU#
A tunnel encapsulates an inner packet inside an outer packet carried by another network. VXLAN and Geneve commonly use UDP encapsulation and virtual-network identifiers; IPIP and GRE use different IP encapsulation formats. Endpoints, identifiers, routes and policy must agree. The tunnel device belongs to a namespace, and the outer route must reach the remote endpoint in the appropriate namespace and routing context. A bridge can attach a tunnel interface, but an FDB entry or route still has to lead traffic to the intended endpoint. See drivers/net/vxlan/vxlan_core.c, drivers/net/geneve.c, and net/ipv4/ip_gre.c.
Encapsulation adds outer IP and tunnel headers, reducing the space available for an inner packet under a fixed outer MTU. A 1500-byte outer path does not automatically carry a 1500-byte inner IP packet plus headers without fragmentation, segmentation or an error. Exact overhead depends on outer IP version and tunnel options. Path-MTU discovery, checksum rules, offloads and ICMP policy affect the outcome. A checksum visible in a local capture may be partial due to offload; it is not evidence that all tunnel packets are corrupt. A tunnel provides connectivity mechanics, not encryption or peer authentication unless another configured protocol supplies those properties.
Namespace teardown and observation#
A process can exit while another process or an open socket still references its network namespace. Removing a named handle does not force those references to disappear. Conversely, an interface can be moved or removed while a user retains a stale integer ifindex or name; a later device can reuse an identifier under the relevant namespace rules. When debugging, identify the namespace and time of observation. An ip link result in one namespace is not a global inventory of all containers.
During cleanup, pernet subsystems need to unregister state, quiesce asynchronous work and release devices in the order required by the kernel. RCU protects certain readers while teardown progresses, and references preserve objects where acquired; neither means a subsystem may ignore its own cleanup callbacks. A socket associated with a disappearing peer can encounter errors even if its local descriptor is still valid. An interface deletion can also affect routes, neighbors and bridge membership. A read-only observation cannot establish that all teardown races have been exercised.
Addressing and topology are separate layers#
A veth pair supplies a link-like connection, not an automatic IP configuration. A process in namespace C needs an appropriate local address, route and neighbor resolution to send to a gateway. The peer in namespace H needs to be up and connected to the intended bridge or routed interface. A bridge's VLAN membership and port state decide which Ethernet frames are eligible for forwarding; the IP FIB and neighbor table decide different questions once a packet reaches an IP endpoint. A single failed layer can produce a timeout even when the other layers look correct.
Suppose C sends to a second container D on the same subnet. C resolves D's IP address to D's MAC, sends a frame through its veth, and the bridge can forward based on its FDB or flood when appropriate. If C sends to an external subnet instead, C normally sends to its configured gateway's MAC; a host-side gateway can route the IP packet after local bridge delivery. NAT, firewall filtering and external connectivity occur only if separately configured. These examples do not require or imply that the bridge itself modifies IP addresses. If a bridge port is blocked by STP or excluded by VLAN policy, the expected FDB destination alone is not enough to make a frame forward.
Names, references, and disappearing peers#
A network namespace name used by ip netns typically corresponds to a handle under a namespace mount; deleting that handle removes one way to find the namespace. An open namespace fd, task or socket can preserve an association after the name disappears, subject to its reference rules. A socket does not acquire the network settings of a new namespace merely because the process later changes namespace membership. Check the socket's creation context as well as the process's current namespace when diagnosing a server inspected from another namespace.
A veth peer can disappear while packets or control operations are in progress. The veth_xmit RCU read-side section permits the peer lookup to be used under the driver's rules; if no peer is present or the required Ethernet header cannot be pulled, it frees the skb and accounts a drop. Removal also triggers broader netdevice cleanup and can remove bridge membership and routes. A saved interface name or ifindex is a label in a particular namespace and time, not a lifetime reference; refresh state after deletion or movement.
For a tunnel, an endpoint can remain configured while its outer route or neighbor fails. A VXLAN identifier, remote address and UDP destination port must match the intended peer; Geneve options and GRE keys have their own semantics. Check inner interface, encapsulation overhead, outer routing, outer neighbor and peer decapsulation as separate stages. Basic VXLAN or Geneve encapsulation does not supply encryption or peer authentication.
Practice#
Source-only route:
rg -n -A55 'static netdev_tx_t veth_xmit\(' "$KERNEL_SRC/drivers/net/veth.c"
rg -n -A45 'static void cleanup_net\(' "$KERNEL_SRC/net/core/net_namespace.c"
rg -n -A140 'int br_handle_frame_finish\(' "$KERNEL_SRC/net/bridge/br_input.c"
Confirm a v6.12 checkout. Mark RCU peer access, the missing-peer drop, the namespace-list deletion and the bridge's known-destination versus flood branches. Draw the conditional container-to-veth-to-bridge path and label where route and NAT become relevant only if configured.
Read-only current-namespace observation:
printf 'network namespace: '; readlink /proc/self/ns/net || echo 'unavailable'
if command -v ip >/dev/null 2>&1; then
ip -details link show
ip route show
ip -6 route show || echo 'IPv6 route view unavailable'
else echo 'ip unavailable; use source-only route'; fi
The namespace identifier and interfaces vary. ip -details link can show device kinds and peer information where available; absence of veth, bridge or a route is a valid result, as is permission or tool unavailability. A route dump does not send a packet. Do not create or move interfaces, add routes or change host firewall state. Deliver the namespace identifier or error, a link-kind observation if available, and the conditional packet walk. If experimenting with veth creation later, use a disposable guest and a separately specified cleanup plan; this chapter's observation makes no such changes.
Knowledge check#
- Why can a network namespace outlive the deletion of its name?
- What does the shown
veth_xmitbranch do when the peer is absent? - How does a bridge FDB decision differ from an IP FIB decision?
- When might a container packet use host routing and NAT rather than only bridge forwarding?
- Why can tunneling require a lower inner MTU?
Answers#
- Processes, sockets, namespace fds or other references can keep the underlying
struct netalive after a userspace name is removed. - It frees the skb, enters drop accounting and does not hand it to a missing peer.
- The bridge selects Ethernet forwarding using MAC/VLAN and port state; the IP FIB selects an IP route and next hop under routing policy.
- If the bridge delivers to a host gateway and forwarding, routes and relevant policy/NAT are configured, the packet can enter the host IP forwarding path. Same-bridge traffic can take a different path.
- Outer IP and tunnel headers consume part of the outer path MTU, leaving less room for the inner packet.
Checkpoint#
Save the conditional packet walk, peer-removal error path and namespace/link observation. Pass when you can explain namespace lifetime, FDB versus FIB, optional NAT and encapsulation overhead without assuming a physical NIC or automatic host routing. Re-read veth_xmit, cleanup_net and the bridge branch if needed. This is practice, not certification.