NetworkLayer

Trait NetworkLayer 

Source
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

  1. User creates socket: socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)
  2. User binds: bind(sockfd, {192.168.1.100:5000})
  3. ABI creates SocketConfig with “ip_local” and “tcp_local_port”
  4. Socket factory creates SocketObject, passing config to each layer
  5. TCP layer extracts port, IP layer extracts address
  6. 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§

Source

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 dependency
Source

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 send
  • context - 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])?;
Source

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 layer
  • context - 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)?;
Source

fn name(&self) -> &'static str

Get layer name for debugging

Source

fn as_any(&self) -> &dyn Any

Cast to Any for safe downcasting

Provided Methods§

Source

fn stats(&self) -> NetworkLayerStats

Get layer statistics

Source

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 pairs
  • next_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);

Implementors§