Networking
net/ is the largest actively-developed subsystem in the kernel, the one with the strictest process,
and the one with the best observability. It is also, for a driver-shaped contribution, one of the
more approachable — there are hundreds of NIC drivers and a steady supply of small real work.
The one idea that unlocks it: a packet is a struct sk_buff, and it travels the entire stack as
one object. Layers do not copy it. They move pointers into it.
Orient Yourself First
cd ~/kernel/linux
./scripts/get_maintainer.pl --scm --status -f net/core/dev.c
git log --oneline --since="6 months ago" -- net/ | wc -l
git log --oneline --since="6 months ago" -- drivers/net/ | wc -l
git log --since="1 year ago" --format='%cN' -- net/ | sort | uniq -c | sort -rn | head
ls net/
ls Documentation/networking/ | head -30
$EDITOR Documentation/process/maintainer-netdev.rst # READ THIS BEFORE PATCHING
Predict first: how many commits land in net/ and drivers/net/ in six months? It is a larger
number than any other subsystem, and it explains why the process here is stricter than elsewhere.
Why It Matters
| Because | Consequence |
|---|---|
| It handles millions of packets per second per core | Every per-packet cost is multiplied enormously |
| It is entirely driven by external, untrusted input | Every parsing bug is a remote attack surface |
| It runs in softirq context on the hot path | Cannot sleep, cannot allocate freely, cannot take mutexes |
| It is programmable at multiple layers | XDP, tc-BPF, netfilter, and sockmap all intercept the same packet |
| Its interfaces are uapi | Netlink, sockets, and /proc/net are all frozen contracts |
Where the Code Is
application: send() / recv()
├── net/socket.c the syscall layer
├── net/ipv4/tcp*.c the protocol
├── net/ipv4/ip_output.c routing and IP
├── net/core/dev.c THE DEVICE LAYER -- the pivot point
├── net/sched/ qdiscs: queueing and shaping
└── drivers/net/ the NIC driver
└── hardware
| Area | What lives there |
|---|---|
net/core/dev.c | The centre. netif_receive_skb, dev_queue_xmit, NAPI. Read this file. |
net/core/skbuff.c | The sk_buff itself: allocation, cloning, splitting |
net/ipv4/, net/ipv6/ | The protocols: TCP, UDP, ICMP, routing |
net/socket.c, net/core/sock.c | The socket layer, and the syscall entry points |
net/sched/ | qdiscs and traffic classifiers; also where tc-BPF hooks live |
net/netfilter/ | The hooks, conntrack, and nftables |
net/bpf/, net/core/filter.c | XDP and BPF program helpers |
drivers/net/ethernet/ | Real NIC drivers. Hundreds of them. |
drivers/net/virtio_net.c, veth.c | What you can actually run in a VM |
$EDITOR Documentation/networking/napi.rst
$EDITOR Documentation/networking/scaling.rst
$EDITOR Documentation/networking/skbuff.rst 2>/dev/null || ls Documentation/networking/ | head -40
The Structures
struct sk_buff ONE PACKET, from the wire to the socket
├── head / data / tail / end ← four pointers into ONE buffer
├── len, data_len total, and how much is in frags
├── protocol, pkt_type
├── dev ──▶ struct net_device
├── sk ──▶ struct sock (once it reaches a socket)
├── network_header, transport_header, mac_header ← OFFSETS
├── cb[48] scratch space for the CURRENT layer
└── skb_shared_info (at the end of the buffer)
├── frags[] page fragments -- the data need not be
│ in the linear part at all
└── frag_list for GSO/GRO chains
struct net_device ONE INTERFACE
├── netdev_ops ──▶ ndo_open, ndo_start_xmit, ndo_stop, ...
├── ethtool_ops
├── _rx[] / _tx[] per-queue receive and transmit state
└── napi_list the poll contexts
struct sock ONE SOCKET (the protocol-independent part)
├── sk_receive_queue / sk_write_queue
├── sk_prot ──▶ struct proto tcp_prot, udp_prot, ...
└── sk_data_ready, sk_write_space ← the callbacks that wake readers
rg -n "struct sk_buff \{" -A 80 include/linux/skbuff.h | head -90
rg -n "struct net_device_ops \{" -A 40 include/linux/netdevice.h
rg -n "struct sock \{" -A 60 include/net/sock.h | head -70
The Concepts
| Chapter | Answers |
|---|---|
| The sk_buff | What is a packet, and why is it never copied? |
| The RX and TX Paths | How does a packet get from the wire to a socket, and back? |
| Sockets, Filtering, and XDP | Who is allowed to intercept it, and where? |
Then Lab 13 attaches an XDP program and counts packets before
the kernel has built an sk_buff at all.
How to Read It
1. Documentation/networking/napi.rst -- the receive model, in ten minutes.
2. struct sk_buff, and specifically the FOUR POINTERS. Draw them. Nothing
else in this subsystem makes sense until you have.
3. ONE PATH, receive:
driver's NAPI poll ──▶ napi_gro_receive ──▶ netif_receive_skb
──▶ ip_rcv ──▶ netfilter hooks ──▶ ip_local_deliver
──▶ tcp_v4_rcv ──▶ the socket's receive queue ──▶ wake the reader
4. THE SECOND PATH, transmit, which is NOT a mirror image:
tcp_sendmsg ──▶ ip_queue_xmit ──▶ dev_queue_xmit ──▶ qdisc
──▶ ndo_start_xmit ──▶ the NIC
5. net/core/dev.c is the pivot. Everything above it is protocol;
everything below is device.
Observing It
Networking has the best observability in the kernel, and one feature in particular is disproportionately useful.
# Interface and queue state
ip -s link; ip -s -s link show dev eth0
ethtool -S eth0 2>/dev/null | head -30 # driver-level counters
cat /proc/net/softnet_stat # per-CPU: processed, dropped, time_squeeze
cat /proc/net/snmp /proc/net/netstat | head
# Sockets
ss -tinm 2>/dev/null | head -20 # per-socket TCP internals
# Tracepoints
ls /sys/kernel/tracing/events/net/ /sys/kernel/tracing/events/tcp/ /sys/kernel/tracing/events/skb/
# THE ONE TO KNOW: every dropped packet, WITH A REASON
sudo bpftrace -e 'tracepoint:skb:kfree_skb { @[args.reason] = count(); }'
rg -n "SKB_DROP_REASON_" include/net/dropreason-core.h | head -30
Tip:
kfree_skbcarries a drop reason — an enum naming why the packet was discarded: checksum failure, no socket, netfilter drop, queue full, and dozens more. Before this existed, "the packet vanished" was a day of work. Now it is one command. It is the single highest-value thing in this chapter.
The Process Here Is Different
netdev is the strictest subsystem in the kernel about process, and violating its rules is the most
common way a good patch gets bounced.
$EDITOR Documentation/process/maintainer-netdev.rst
| Rule | Detail |
|---|---|
| Say which tree in the subject | [PATCH net] for fixes to the current release; [PATCH net-next] for features. Not optional. |
net-next closes during the merge window | Check git describe and the list before sending a feature |
| Patchwork is authoritative | Your patch's state there is how the maintainers track it |
| Reverse Christmas tree | Local variable declarations sorted longest-line-first. Networking only, and enforced. |
| A different block-comment style | net/ opens block comments differently from the rest of the tree. Match the file. |
Fixes need Fixes: | Rigorously enforced; stable backports depend on it |
| No cover letter for a single patch | And a required one for a series |
What a Good First Contribution Looks Like
| Target | Why it is plausible |
|---|---|
A driver fix in drivers/net/ethernet/<vendor>/ | Hundreds of drivers, many lightly maintained, real bugs |
| Adding drop reasons | Converting a bare kfree_skb() to kfree_skb_reason() with the right enum is small, mechanical, and genuinely valuable |
Selftests in tools/testing/selftests/net/ | Large, active, and always wanting more coverage |
Documentation in Documentation/networking/ | Big and uneven |
| A syzbot networking bug | Steady supply, with reproducers |
An ethtool counter or a tracepoint | Additive, low risk, useful |
veth/netdevsim/virtio_net work | Testable entirely in a VM, with no hardware |
Tip: The drop-reason conversion is an unusually good first contribution: the change is mechanical, the correct enum value requires you to actually understand the code path, reviewers can verify it easily, and every one makes a real debugging session shorter for someone. Find candidates with:
rg -n "\bkfree_skb\(" net/ drivers/net/ | wc -l rg -n "\bkfree_skb\(" net/ipv4/ | head -20
Common Misconceptions
| Misconception | Reality |
|---|---|
| "Each layer copies the packet" | Nothing copies. Layers move skb->data and record header offsets. |
| "An interrupt per packet" | Under load, NAPI switches to polling — the interrupt is disabled and a softirq drains the ring |
| "The receive path runs in my process" | It runs in softirq context, on whichever CPU took the interrupt. Your process is woken at the end. |
| "The TX path is RX in reverse" | It is not. Different queueing, different locking, different context. |
"skb->len is the data in the buffer" | len is the total; data_len is how much lives in page frags. The linear part may be nearly empty. |
| "XDP is just a faster BPF hook" | It runs before an sk_buff exists. That is the entire performance argument, and the entire limitation. |
| "Dropped packets are a mystery" | kfree_skb has a reason enum. One command. |
"ping measures the network" | It measures the network plus two scheduling delays plus two softirq latencies |
Validation / Self-check
- What is a
sk_buffand why does the stack never copy the packet? - Name the four pointers in an
sk_buffand what each delimits. - What is the difference between
skb->lenandskb->data_len? - Which file is the pivot between protocol and device, and what are the two functions to read in it?
- What is NAPI and what problem does it solve? What changes under load?
- In what context does the receive path run, and what does that forbid?
- Why is the transmit path not a mirror image of receive?
- What is
kfree_skb's drop reason, and why is it the most valuable observability feature here? - Give four rules
netdevenforces that other subsystems do not. - Name four plausible first contributions here, and say which needs no hardware.
- Where does XDP run relative to
sk_buffallocation, and what does that buy and cost? - What does
/proc/net/softnet_stat'stime_squeezecolumn mean?
Next: The sk_buff — the one structure everything here is about.