pub trait NetworkLayer:
Send
+ Sync
+ Any {
// Required methods
fn register_protocol(&self, proto_num: u16, handler: Arc<dyn NetworkLayer>);
fn send(
&self,
packet: &[u8],
context: &LayerContext,
next_layers: &[Arc<dyn NetworkLayer>],
) -> Result<(), SocketError>;
fn receive(
&self,
packet: &[u8],
context: Option<&LayerContext>,
) -> Result<(), SocketError>;
fn name(&self) -> &'static str;
fn as_any(&self) -> &dyn Any;
// Provided methods
fn stats(&self) -> NetworkLayerStats { ... }
fn configure(
&self,
config: &SocketConfig,
next_layers: &[Arc<dyn NetworkLayer>],
) -> Result<(), SocketError> { ... }
}Expand description
Network layer trait for composable protocol stacks
This trait enables building flexible protocol stacks where each layer is independent and can be composed at runtime. Layers communicate through protocol numbers (e.g., IP uses protocol 6 for TCP, 17 for UDP).
§Design Philosophy (VFS Pattern)
Following VFS architecture where filesystems are shared singletons:
- NetworkLayer = FileSystemOperations: Shared protocol implementation
- SocketObject = FileObject: Per-connection handle with references to layers
- NetworkManager = VfsManager: Global registry of protocol layer instances
Each NetworkLayer instance is shared across all sockets, similar to how a filesystem (ext2, tmpfs) is shared across all file handles. Per-socket state lives in SocketObject, not in NetworkLayer.
§Shared vs Per-Socket State
NetworkLayer (shared, stateless for protocol logic):
- Protocol logic and packet processing
- Routing tables, ARP cache (shared state)
- Registered protocol handlers
- Like: ext2 driver, tmpfs implementation
SocketObject (per-socket, stateful):
- Connection state (ports, addresses) - configured via SocketConfig
- Send/receive buffers
- References to NetworkLayer instances
- Like: FileObject with seek position, flags
§Socket Configuration Flow
- User creates socket:
socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) - User binds:
bind(sockfd, {192.168.1.100:5000}) - ABI creates SocketConfig with “ip_local” and “tcp_local_port”
- Socket factory creates SocketObject, passing config to each layer
- TCP layer extracts port, IP layer extracts address
- Socket handle stores references to shared layers + per-socket state
§Example: IP Layer (Shared Singleton)
struct IpLayer {
protocols: RwLock<BTreeMap<u16, Arc<dyn NetworkLayer>>>, // Shared
routing_table: RwLock<RoutingTable>, // Shared
arp_cache: RwLock<ArpCache>, // Shared
}
impl NetworkLayer for IpLayer {
fn send(&self, packet: &[u8], context: &LayerContext,
next_layers: &[Arc<dyn NetworkLayer>]) -> Result<(), SocketError> {
// Extract destination IP from protocol-agnostic context
let dest_ip = context.get("ip_dst")
.ok_or(SocketError::InvalidPacket)?;
// Add IP header with destination
let ip_packet = add_ip_header(packet, dest_ip);
// Route to lower layer (Ethernet, InfiniBand, etc.)
for layer in next_layers {
if let Ok(()) = layer.send(&ip_packet, context, &[]) {
return Ok(());
}
}
Err(SocketError::NoRoute)
}
fn receive(&self, packet: &[u8], context: Option<&LayerContext>) -> Result<(), SocketError> {
// Parse IP header
let (proto_num, payload) = parse_ip_header(packet)?;
// Route to registered protocol handler
if let Some(handler) = self.protocols.read().get(&proto_num) {
handler.receive(payload, context)
} else {
Err(SocketError::ProtocolNotSupported)
}
}
}§Per-Task NetworkManager (Future)
Like VfsManager can be per-task for filesystem namespace isolation, NetworkManager can be per-task for network namespace isolation:
// Container gets isolated NetworkManager
let container_net = Arc::new(NetworkManager::new());
// Share Ethernet layer (driver access), but separate IP/TCP
let shared_eth = global_net_manager.get_layer("ethernet")?;
container_net.register_layer("ethernet", shared_eth);
// Container gets its own IP and TCP layer instances
container_net.register_layer("ip", Arc::new(IpLayer::new()));
container_net.register_layer("tcp", Arc::new(TcpLayer::new()));
// Assign to task
task.network_manager = Some(container_net);Required Methods§
Sourcefn register_protocol(&self, proto_num: u16, handler: Arc<dyn NetworkLayer>)
fn register_protocol(&self, proto_num: u16, handler: Arc<dyn NetworkLayer>)
Register a protocol handler for this layer
Upper layer protocols register themselves with their protocol number. For example, TCP registers as protocol 6 with the IP layer.
§Important: Avoid Circular References
Registration is one-way only: lower layers register upper layers.
- ✅ Correct: Ethernet registers IP (for receive routing)
- ✅ Correct: IP registers TCP (for receive routing)
- ❌ Wrong: IP registers Ethernet (would create cycle)
For sending, upper layers pass lower layers as temporary references
via the send(next_layers) parameter, not as permanent registrations.
This prevents circular Arc references.
§Arguments
proto_num- Protocol number (e.g., 6 for TCP, 17 for UDP, 0x0800 for IPv4)handler- Protocol handler for this protocol number
§Example
// Setup protocol hierarchy (initialization time)
ethernet.register_protocol(0x0800, ip.clone()); // IPv4 = 0x0800
ip.register_protocol(6, tcp.clone()); // TCP = 6
ip.register_protocol(17, udp.clone()); // UDP = 17
// Sending (runtime) - pass lower layers temporarily
tcp.send(&segment, &ctx, &[ip.clone(), ethernet.clone()])?;
// No permanent reference stored, no circular dependencySourcefn send(
&self,
packet: &[u8],
context: &LayerContext,
next_layers: &[Arc<dyn NetworkLayer>],
) -> Result<(), SocketError>
fn send( &self, packet: &[u8], context: &LayerContext, next_layers: &[Arc<dyn NetworkLayer>], ) -> Result<(), SocketError>
Send a packet through this layer
The layer encapsulates the packet with its own header and passes it to one or more lower layers. The context contains routing information in a protocol-agnostic key-value format.
§Arguments
packet- Packet data to sendcontext- Protocol-agnostic routing context (key-value pairs)next_layers- Lower layer options for transmission
§Returns
Ok(()) if successfully sent through at least one lower layer, Err if all lower layers failed or routing information is insufficient
§Example
// TCP layer adds its info to context
let mut ctx = LayerContext::new();
ctx.set("tcp_src_port", &5000u16.to_be_bytes());
ctx.set("tcp_dst_port", &80u16.to_be_bytes());
tcp_layer.send(&tcp_segment, &ctx, &[ip_layer])?;
// IP layer adds its info and forwards
ctx.set("ip_src", &[192, 168, 1, 100]);
ctx.set("ip_dst", &[192, 168, 1, 1]);
ip_layer.send(&ip_packet, &ctx, &[ethernet_layer])?;Sourcefn receive(
&self,
packet: &[u8],
context: Option<&LayerContext>,
) -> Result<(), SocketError>
fn receive( &self, packet: &[u8], context: Option<&LayerContext>, ) -> Result<(), SocketError>
Receive and process a packet at this layer
The layer parses its header, extracts the protocol number, and routes the payload to the appropriate upper layer protocol handler.
§Arguments
packet- Packet data received from lower layercontext- Optional routing context from lower layer
§Returns
Ok(()) if successfully processed and delivered, Err if packet is malformed or no handler for the protocol
§Example
// IP layer receives packet, parses header, routes to TCP (proto=6)
ip_layer.receive(&packet)?;Provided Methods§
Sourcefn stats(&self) -> NetworkLayerStats
fn stats(&self) -> NetworkLayerStats
Get layer statistics
Sourcefn configure(
&self,
config: &SocketConfig,
next_layers: &[Arc<dyn NetworkLayer>],
) -> Result<(), SocketError>
fn configure( &self, config: &SocketConfig, next_layers: &[Arc<dyn NetworkLayer>], ) -> Result<(), SocketError>
Configure this layer with socket-specific parameters
Called at socket bind time to configure the layer for a specific socket. The configuration flows DOWN through the protocol stack, with each layer extracting relevant parameters and passing the config to lower layers.
§Purpose
Solves the “reception configuration problem”: How does IP layer know “deliver packets for 192.168.1.100 to this socket”? Answer: This method allows each layer to register itself for packet delivery based on the socket’s configuration.
§Arguments
config- Socket configuration with protocol-agnostic key-value pairsnext_layers- Lower layers to pass configuration to
§Returns
Ok(()) if configuration successful, Err if required parameters missing
§Example
// User binds socket to 192.168.1.100:5000
let mut config = SocketConfig::new();
config.set("tcp_local_port", &5000u16.to_be_bytes());
config.set("ip_local", &[192, 168, 1, 100]);
// TCP layer
tcp_layer.configure(&config, &[ip_layer, eth_layer])?;
// Inside TCP configure():
let port = config.get_u16("tcp_local_port").ok_or(...)?;
self.register_socket(port, socket_handle);
ip_layer.configure(&config, &[eth_layer])?; // Pass down
// IP layer registers for this address
let addr = config.get_ipv4("ip_local").ok_or(...)?;
self.register_address(addr, tcp_handler);