Fundamentals 16 min read

How Linux Handles Network Packets: From NIC to Kernel and Back

This article explains the complete Linux network packet lifecycle—how a NIC receives a UDP packet, how the kernel processes it through interrupts, soft‑irqs, and the networking stack, and finally how the same mechanisms assemble and transmit packets back to the wire.

Liangxu Linux
Liangxu Linux
Liangxu Linux
How Linux Handles Network Packets: From NIC to Kernel and Back

Packet Reception Flow

This description follows a UDP packet from a physical NIC into the kernel and back out, focusing on the essential steps.

From NIC to Memory

The packet arrives at the NIC. If the destination MAC does not match the NIC and the NIC is not in promiscuous mode, the packet is dropped.

The NIC uses DMA to write the packet into a memory buffer that was allocated and initialized by the driver.

The NIC raises a hardware interrupt (IRQ) to notify the CPU that a new packet is available.

The CPU looks up the interrupt vector table, calls the registered interrupt handler, and forwards processing to the driver.

The driver disables further NIC interrupts, indicating that the packet is already in memory and that subsequent packets can be written without additional interrupts.

A soft‑interrupt (softirq) is scheduled so that the time‑consuming part of packet handling can be performed outside the hard‑interrupt context.

NIC to memory flow
NIC to memory flow

Kernel Packet Processing

The ksoftirqd process invokes net_rx_action. net_rx_action calls the NIC driver’s poll function to process packets one by one. poll reads the DMA‑filled memory area; the exact packet format is known only to the driver.

The driver converts the raw data into a kernel skb (socket buffer) and calls napi_gro_receive. napi_gro_receive performs Generic Receive Offload (GRO) merging and, if Receive Packet Steering (RPS) is enabled, calls enqueue_to_backlog. enqueue_to_backlog places the packet into input_pkt_queue. If the queue is full, the packet is dropped; the size is configurable via net.core.netdev_max_backlog.

The CPU later processes input_pkt_queue in soft‑irq context by calling __netif_receive_skb_core.

If RPS is not enabled, napi_gro_receive directly calls __netif_receive_skb_core.

If a raw socket of type AF_PACKET exists (e.g., for tcpdump), the packet is copied to that socket.

Finally, the packet is handed to the kernel TCP/IP stack for further processing.

When all packets have been processed (the poll loop finishes), the driver re‑enables the NIC’s hardware interrupt.

Kernel packet processing
Kernel packet processing

IP Network Layer

ip_rcv

is the entry point for the IP layer. It discards packets whose destination MAC does not belong to the NIC (unless promiscuous mode) and then invokes the NF_INET_PRE_ROUTING netfilter hook.

The NF_INET_PRE_ROUTING hook allows iptables rules to modify or drop the packet.

If the destination IP is not local and IP forwarding is disabled, the packet is dropped; otherwise ip_forward handles routing. ip_forward first runs the NF_INET_FORWARD hook, then calls dst_output_sk for actual transmission.

If the packet is destined for the local host, ip_local_deliver runs the NF_INET_LOCAL_IN hook and passes the packet to the transport layer.

IP layer processing
IP layer processing

Transport Layer (UDP)

udp_rcv

is the UDP entry point; it calls __udp4_lib_lookup_skb to find the matching socket (IP + port). If no socket matches, the packet is dropped.

The socket’s receive queue is checked; if full, the packet is dropped. Then sk_filter runs any BPF filter attached to the socket. __skb_queue_tail appends the packet to the socket’s receive queue. sk_data_ready notifies the socket that data is available.

After sk_data_ready, the packet waits for the application to read it.

sk_filter is the kernel interface for socket filtering; it executes a BPF program attached to the socket (implementation in net/core/filter.c via sk_filter_trim_cap ).

Packet Sending Flow

The sending path mirrors the receiving path, illustrated with a UDP example.

Application Layer

socket(...)

creates and initializes a socket structure. sendto(sock,...) invokes inet_sendmsg. inet_sendmsg checks for a bound source port; if missing, it calls inet_autobind to allocate one. inet_autobind uses get_port to obtain a free port.

Application layer send
Application layer send

Transport Layer (UDP)

udp_sendmsg

is the UDP send entry point; it calls ip_route_output_flow to obtain routing information, then ip_make_skb to build an skb. ip_route_output_flow determines the outgoing device and source IP. If the socket’s source IP cannot reach the destination, the packet is dropped. ip_make_skb constructs the IP header, calls __ip_append_dat to fragment if needed, and may return ENOBUFS when the send buffer is exhausted. udp_send_skb(skb, fl4) fills the UDP header, computes the checksum, and passes the skb to the IP layer.

UDP send processing
UDP send processing

IP Network Layer (Outgoing)

ip_send_skb

is the IP send entry point; it eventually calls __ip_local_out_sk to set length and checksum, then invokes the NF_INET_LOCAL_OUT netfilter hook. dst_output_sk invokes ip_output, which runs the NF_INET_POST_ROUTING hook (used for SNAT, etc.). ip_finish_output checks whether routing information changed; if so, it may call dst_output_sk again or other netfilter output functions. ip_finish_output2 looks up the next‑hop neighbor via __ipv4_neigh_lookup_noref (or creates a placeholder with __neigh_create). dst_neigh_output obtains the neighbor’s MAC address with neigh_resolve_output and finally calls dev_queue_xmit to transmit.

IP output processing
IP output processing

Kernel Handling of Outgoing Packets

dev_queue_xmit

is the entry point for sending; it obtains the device’s queueing discipline (qdisc). If none exists (e.g., loopback), it calls dev_hard_start_xmit directly; otherwise traffic‑control (TC) handles the packet.

TC may drop packets if the queue is full. dev_hard_start_xmit copies the skb to a “packet probe” (used by tcpdump), then calls the driver’s ndo_start_xmit to actually transmit.

If dev_hard_start_xmit returns an error, the caller re‑queues the skb and raises a NET_TX_SOFTIRQ that invokes net_tx_action for a retry. ndo_start_xmit is driver‑specific and finally hands the packet to the NIC hardware.

After transmission, the NIC generates an interrupt; the driver cleans up the skb.

Kernel transmit path
Kernel transmit path

Source: https://www.sobyte.net/post/2022-10/linux-net-snd-rcv/

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

KernelTCP/IPNetworkingUDPpacket processing
Liangxu Linux
Written by

Liangxu Linux

Liangxu, a self‑taught IT professional now working as a Linux development engineer at a Fortune 500 multinational, shares extensive Linux knowledge—fundamentals, applications, tools, plus Git, databases, Raspberry Pi, etc. (Reply “Linux” to receive essential resources.)

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.