Inside Linux: How UDP Packets Travel From NIC to Kernel and Back
This article explains the complete Linux network packet lifecycle for UDP, detailing how a packet moves from the physical NIC into kernel memory, is processed by drivers, soft‑irqs, the IP and transport layers, and finally traverses the send path back to the NIC, including key functions, queues, and configuration points.
Packet Reception Path
Linux receives a network packet on a physical NIC and processes it through the driver, soft‑interrupts, and the kernel networking stack. The description uses a UDP packet as an example.
From NIC to Memory
The packet arrives at the NIC. If the destination MAC does not match the NIC’s address 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 the driver allocated.
The NIC raises a hardware interrupt (IRQ) to notify the CPU.
The CPU looks up the interrupt vector table, invokes the registered interrupt handler, and calls the driver’s interrupt routine.
The driver disables further NIC interrupts, acknowledges that the packet is now in memory, and asks the NIC to write subsequent packets directly without generating more interrupts.
A soft‑interrupt (NAPI poll) is scheduled so that time‑consuming processing can be performed outside the hard‑interrupt context.
Kernel Packet Processing (soft‑interrupt)
The ksoftirqd thread invokes net_rx_action. net_rx_action calls the NIC driver’s poll function to retrieve packets from the receive ring. poll converts the raw DMA buffer into a kernel skb (socket buffer).
The driver calls napi_gro_receive to perform Generic Receive Offload (GRO) merging. If Receive Packet Steering (RPS) is enabled, enqueue_to_backlog is invoked. enqueue_to_backlog places the packet into input_pkt_queue. If the queue is full, the packet is dropped; the queue size is configurable via net.core.netdev_max_backlog.
The kernel processes packets from input_pkt_queue by calling __netif_receive_skb_core.
If a raw socket of type AF_PACKET (e.g., tcpdump) is bound, the packet is copied to that socket.
Finally, the packet is handed to the IP layer of the TCP/IP stack.
IP Layer Processing
ip_rcvvalidates the packet and invokes the NF_INET_PRE_ROUTING netfilter hook.
If the packet survives netfilter, routing decides whether to forward it via ip_forward or deliver it locally via ip_local_deliver. ip_forward runs the NF_INET_FORWARD hook and then calls dst_output_sk to continue transmission. ip_local_deliver runs the NF_INET_LOCAL_IN hook before passing the packet to the transport layer.
Transport Layer – UDP
udp_rcvis the entry point; it calls __udp4_lib_lookup_skb to find a matching socket. If none is found, the packet is dropped. sock_queue_rcv_skb checks the socket’s receive buffer, applies any attached BPF filter via sk_filter, and enqueues the packet with __skb_queue_tail. sk_data_ready notifies the socket that data is available for user‑space consumption.
sk_filter is the kernel interface for socket filtering; it runs a BPF program attached to the socket before the packet reaches user space. See sk_filter_trim_cap in net/core/filter.c for the implementation.
Packet Sending Path
The send path mirrors the receive path, again illustrated with UDP.
Application Layer
socket(...)creates and initializes a socket structure. sendto(sock, ...) invokes inet_sendmsg. inet_sendmsg ensures the socket has a source port (calling inet_autobind if necessary) and then hands the payload to the UDP layer. inet_autobind obtains a free port via get_port.
Transport Layer – UDP
udp_sendmsgobtains routing information via ip_route_output_flow, builds an skb with ip_make_skb, and then calls udp_send_skb. ip_route_output_flow selects the outgoing device and source IP, filling a flowi4 structure; it fails if routing is impossible. ip_make_skb constructs the IP header, slices the payload with __ip_append_dat, and may return ENOBUFS if the socket’s send buffer is exhausted. udp_send_skb adds the UDP header, computes the checksum, and passes the packet to the IP layer.
IP Layer (Sending)
ip_send_skbis the entry point; it calls __ip_local_out_sk to set length and checksum, then runs the NF_INET_LOCAL_OUT netfilter hook. dst_output_sk forwards the packet to ip_output. ip_output invokes the NF_INET_POST_ROUTING hook (e.g., for SNAT) and then calls ip_finish_output. ip_finish_output may re‑run routing if it changed, eventually calling ip_finish_output2 to look up the next‑hop neighbor via ARP ( __ipv4_neigh_lookup_noref) and create a neighbor entry if needed.
The neighbor information is used by dst_neigh_output to fill the destination MAC address and invoke dev_queue_xmit.
Device Transmission
dev_queue_xmitobtains the device’s queue discipline (qdisc). If none exists (e.g., loopback), it calls dev_hard_start_xmit directly; otherwise the traffic‑control (TC) subsystem processes the packet. dev_hard_start_xmit copies the skb for packet sniffers (e.g., tcpdump) and then invokes the driver’s ndo_start_xmit to transmit.
If ndo_start_xmit returns an error, the kernel schedules a NET_TX_SOFTIRQ handled by net_tx_action to retry later.
The driver places the skb into the NIC’s transmit queue, notifies the NIC, the NIC sends the frame, and raises a completion interrupt so the kernel can free the skb.
Reference: https://www.sobyte.net/post/2022-10/linux-net-snd-rcv/
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.)
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
