How to Trace TCP Socket TCP_ZEROCOPY_RECEIVE Latency in Linux Using eBPF

High-throughput network applications operating on multi-gigabit interfaces frequently hit memory bus bottlenecks when transferring bulk payloads between kernel space and user buffers. Linux provides the TCP_ZEROCOPY_RECEIVE socket option via getsockopt(), enabling userland applications to read network payloads directly through memory-mapped pages without CPU buffer copies. Observing performance and tracing latency across this critical path using extended Berkeley Packet Filters (eBPF) provides vital visibility into kernel page remapping overhead.

Mechanics of TCP Zero-Copy Reception in the Linux Kernel

The standard network receive path copies packets from driver DMA ring buffers through network sk_buffs into application memory passed to recv(). In contrast, TCP_ZEROCOPY_RECEIVE requires the application to allocate an aligned virtual memory range using mmap(). The kernel replaces user page table entries with pages containing received payload data.

While this bypasses CPU memory copies, zero-copy reception introduces page table manipulation overhead, translation lookaside buffer (TLB) shootdowns, and strict alignment checks. If network packets arrive out of order or payload sizes fail to align with page boundaries, the kernel falls back to standard processing paths, introducing unexpected execution stalls.

Writing a Custom bpftrace Telemetry Script

To measure the execution duration of tcp_zerocopy_receive() and trace incoming payload page sizes, construct an eBPF instrumentation script using bpftrace:

#!/usr/bin/env bpftrace

#include <net/sock.h>
#include <linux/tcp.h>

kprobe:tcp_zerocopy_receive
{
  @start[tid] = nsecs;
}

kretprobe:tcp_zerocopy_receive
/@start[tid]/
{
  $duration = (nsecs - @start[tid]) / 1000;
  @latency_us = hist($duration);
  delete(@start[tid]);

  if (retval < 0) {
    @errors[retval] = count();
  } else {
    @bytes_received = hist(retval);
  }
}

interval:s:10
{
  print(@latency_us);
  print(@bytes_received);
  print(@errors);
  clear(@latency_us);
  clear(@bytes_received);
  clear(@errors);
}

Save this script as trace_zerocopy.bt and grant execution permissions. It captures nanosecond entry timestamps across thread IDs and calculates distribution histograms of total latency and return codes.

Executing Kernel Tracing on Production Interfaces

Execute the bpftrace tool with administrative privileges to inspect live application traffic:

sudo bpftrace trace_zerocopy.bt

Review the output histograms produced every 10 seconds. Key indicators to evaluate include:

  • Latency clustering: Standard page-mapping paths typically complete within 2 to 6 microseconds on modern hardware. Clusters exceeding 50 microseconds point to memory lock contention or heavy TLB shootdown cascades.
  • Negative return values: Errors such as -38 (ENOSYS) or -22 (EINVAL) reveal misaligned user memory buffers or missing TCP socket prerequisites (such as missing GRO configurations).
  • Payload distribution: Ensure byte transfers consistently match full 4KB or 2MB huge page boundaries to verify maximum hardware offload efficiency.

Kernel Tuning for Low-Latency Zero-Copy I/O

To maximise zero-copy performance and eliminate fallback penalties, tune socket buffer thresholds and hardware ring parameters:

  1. Enable Generic Receive Offload (GRO) on the target network interface using ethtool -K eth0 gro on to ensure packets coalesce into full MTU and super-packet buffers before kernel delivery.
  2. Align userland virtual memory allocations strictly to page multiples using posix_memalign() or mmap() with MAP_ANONYMOUS | MAP_SHARED.
  3. Pin high-throughput worker threads to dedicated CPU cores matching the NUMA node of the network interface controller to prevent cross-socket bus interconnect penalties.

Get the best tech tips delivered straight to your inbox.

Join thousands of readers mastering Apple, Google, Microsoft, and Linux.