Fundamentals 10 min read

How tcpdump Captures Packets Inside the Linux Kernel

This article explains the internal mechanisms of tcpdump, showing how it registers a virtual protocol in the kernel's ptype_all list to intercept packets during both receive and transmit paths, how netfilter interacts with these paths, and provides guidance for building a custom packet‑capture program.

Refining Core Development Skills
Refining Core Development Skills
Refining Core Development Skills
How tcpdump Captures Packets Inside the Linux Kernel

tcpdump works by registering a virtual protocol in the kernel's ptype_all list, allowing a user‑space program to receive copies of packets that traverse the network device layer.

During packet reception, the function __netif_receive_skb_core iterates over ptype_all and calls deliver_skb , which eventually invokes packet_rcv in net/packet/af_packet.c to enqueue the packet on the packet socket's receive queue:

static int __netif_receive_skb_core(struct sk_buff *skb, bool pfmemalloc) {
    ...
    list_for_each_entry_rcu(ptype, &ptype_all, list) {
        if (!ptype->dev || ptype->dev == skb->dev) {
            if (pt_prev)
                ret = deliver_skb(skb, pt_prev, orig_dev);
            pt_prev = ptype;
        }
    }
}

static inline int deliver_skb(...){
    return pt_prev->func(skb, skb->dev, pt_prev, orig_dev);
}

static int packet_rcv(struct sk_buff *skb, ...){
    __skb_queue_tail(&sk->sk_receive_queue, skb);
    ...
}

Netfilter hooks are invoked earlier in the IP layer, for example in ip_rcv via NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING, ...) , so tcpdump can capture packets before they are filtered on receive.

On the transmit side, ip_local_out calls nf_hook(NFPROTO_IPV4, NF_INET_LOCAL_OUT, ...) to apply netfilter rules, and later dev_hard_start_xmit traverses ptype_all in dev_queue_xmit_nit , invoking the same virtual protocol hook after the packet has passed netfilter.

int ip_local_out(struct sk_buff *skb) {
    err = __ip_local_out(skb);
}

int __ip_local_out(struct sk_buff *skb) {
    ...
    return nf_hook(NFPROTO_IPV4, NF_INET_LOCAL_OUT, skb, NULL,
                   skb_dst(skb)->dev, dst_output);
}

int dev_hard_start_xmit(struct sk_buff *skb, struct net_device *dev, struct netdev_queue *txq) {
    ...
    if (!list_empty(&ptype_all))
        dev_queue_xmit_nit(skb, dev);
}

static void dev_queue_xmit_nit(struct sk_buff *skb, struct net_device *dev) {
    list_for_each_entry_rcu(ptype, &ptype_all, list) {
        if ((ptype->dev == dev || !ptype->dev) && (!skb_loop_sk(ptype, skb))) {
            if (pt_prev) {
                deliver_skb(skb2, pt_prev, skb->dev);
                pt_prev = ptype;
                continue;
            }
            ...
        }
    }
}

When tcpdump starts, it creates a raw packet socket with socket(AF_PACKET, SOCK_RAW, ETH_P_ALL) . The kernel’s packet_create function registers a hook ( packet_rcv ) via dev_add_pack , which adds the hook to ptype_all :

SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol) {
    ...
    retval = sock_create(family, type, protocol, &sock);
}

static int packet_create(struct net *net, struct socket *sock, int protocol, int kern) {
    ...
    po = pkt_sk(sk);
    po->prot_hook.func = packet_rcv;
    if (proto) {
        po->prot_hook.type = proto;
        register_prot_hook(sk);
    }
}

static void register_prot_hook(struct sock *sk) {
    struct packet_sock *po = pkt_sk(sk);
    dev_add_pack(&po->prot_hook);
}

void dev_add_pack(struct packet_type *pt) {
    struct list_head *head = ptype_head(pt);
    list_add_rcu(&pt->list, head);
}

static inline struct list_head *ptype_head(const struct packet_type *pt) {
    if (pt->type == htons(ETH_P_ALL))
        return &ptype_all;
    else
        return &ptype_base[ntohs(pt->type) & PTYPE_HASH_MASK];
}

In summary, tcpdump’s ability to capture packets stems from its registration of a virtual protocol in ptype_all . It can see packets before netfilter filtering on receive, but after netfilter on transmit. The article also shows a minimal C demo for building a custom packet‑capture tool.

Linux kernelpacket capturenetwork debuggingnetfilterlibpcaptcpdump
Refining Core Development Skills
Written by

Refining Core Development Skills

Fei has over 10 years of development experience at Tencent and Sogou. Through this account, he shares his deep insights on performance.

0 followers
Reader feedback

How this landed with the community

login 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.