How to Trace TCP Socket TCP_ULP (Upper Layer Protocol) Handshakes in Linux Using eBPF

Kernel TLS (kTLS) and Multipath TCP (MPTCP) achieve extreme throughput gains by offloading cryptographic record framing and subflow scheduling directly into the Linux kernel network stack. The foundation of this architecture is the TCP_ULP (Upper Layer Protocol) socket option, configured via setsockopt() with SOL_TCP. Attaching a ULP swaps the socket operations table with specialized kernel protocol handlers. Tracing ULP initialization and cryptographic handshakes using eBPF provides vital observability into kernel offload health.

The Role of TCP_ULP in Kernel Network Offloading

When an application attaches a ULP—such as tls or mptcp—the kernel executes tcp_set_ulp_id() to bind specialized upper-layer protocol callbacks:

/* Attaching Kernel TLS in userland */
setsockopt(sock_fd, SOL_TCP, TCP_ULP, "tls", sizeof("tls"));

Once bound, incoming and outgoing packets pass through specialized protocol hooks (e.g. hardware AES-GCM offloading). However, if the socket is not in an established state (TCP_ESTABLISHED), or if another ULP is already bound, the call fails with EINVAL or EBUSY. Tracing these attachment points pinpoints misconfigurations instantly.

Authoring a Custom bpftrace ULP Telemetry Script

To trace when sockets bind upper-layer protocols and measure registration success rates, write an eBPF script using bpftrace:

#!/usr/bin/env bpftrace

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

kprobe:tcp_set_ulp_id
{
  $sk = (struct sock *)arg0;
  $name = str(arg1);
  time("%H:%M:%S: TCP_ULP '%s' requested on sock %p\n", $name, $sk);
  @ulp_requests[$name] = count();
}

kretprobe:tcp_set_ulp_id
{
  if (retval != 0) {
    @ulp_failures[retval] = count();
    time("%H:%M:%S: TCP_ULP attachment failed with error %d\n", retval);
  } else {
    @ulp_successes = count();
  }
}

interval:s:10
{
  print(@ulp_requests);
  print(@ulp_successes);
  print(@ulp_failures);
  clear(@ulp_requests);
  clear(@ulp_successes);
  clear(@ulp_failures);
}

Save this script as trace_ulp.bt. It targets tcp_set_ulp_id(), capturing ULP protocol names, connection pointers, and return codes.

Executing and Evaluating Kernel Telemetry

Run the bpftrace monitoring script with administrative privileges:

sudo bpftrace trace_ulp.bt

Initiate HTTPS traffic through a web server configured with kTLS (e.g. NGINX with ssl_engine ktls). Evaluate the output telemetry:

  • ULP registration counts: Ensure that @ulp_requests["tls"] tracks active TLS handshake completion rates.
  • Failure analysis: Return codes such as -16 (EBUSY) indicate attempts to re-register a ULP on a socket already managed by an upper layer. Error -2 (ENOENT) indicates missing kernel modules (such as tls.ko).
  • Crypto offload validation: Check cat /proc/net/tls_stat to confirm that cryptographic crypto records are offloaded to NIC hardware or vectorized kernel crypto routines.

Host Prerequisites for Kernel TLS Offloading

To ensure smooth ULP operation across production hosts:

  1. Load required kernel modules via sudo modprobe tls.
  2. Ensure OpenSSL is compiled with enable-ktls and configured to set TCP_ULP.
  3. Verify NIC hardware offload support using ethtool -k eth0 | grep -i tls.

Get the best tech tips delivered straight to your inbox.

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