tcplife
Linux tcplife — Complete Learning Notes & Output Guide
tcplifeis an eBPF/BCC and libbpf performance-tracing tool that tracks the complete lifecycle of TCP sessions from establishment to closure. It logs a single summary line per connection upon termination, reporting the process identity, endpoints, transferred data volume (TX/RX), and total connection lifespan in milliseconds.
1. What is tcplife?
Developed by Brendan Gregg as part of the BCC (BPF Compiler Collection) and ported to modern libbpf-tools, tcplife provides connection-level session accountability.
Standard tools present fundamental operational trade-offs:
tcpdump: Captures every individual packet, causing massive CPU, storage, and I/O overhead on high-throughput nodes.- **
netstat/ss**: Provide point-in-time snapshots, completely missing short-lived connections that establish and terminate between polling intervals. tcptop: Aggregates throughput per second without recording connection lifespan or individual session termination metrics.
tcplife eliminates these drawbacks by hooking into the Linux kernel's TCP state machine. Instead of inspecting millions of packets across a connection, it records a timestamp when a socket enters TCP_ESTABLISHED and emits a single, high-efficiency event when the socket transitions to TCP_CLOSE.
It answers critical operational and application performance questions:
- How long are client and database TCP sessions lasting (lifespan in milliseconds)?
- Are backend services suffering from "connection churn" (repeatedly opening and closing TCP connections instead of reusing a persistent connection pool)?
- Which client or process is opening short-lived connections and transferring zero bytes (health-check probes or hanging handshakes)?
- What is the total volume of data uploaded (
TX_KB) versus downloaded (RX_KB) over a specific session?
2. Installation & Availability
tcplife is available through BCC packages or standalone libbpf-tools. It requires root privileges (sudo or CAP_BPF) and a kernel built with eBPF support (Linux 4.9+ minimum, 5.4+ recommended).
Debian / Ubuntu
sudo apt update
sudo apt install bpfcc-tools linux-headers-$(uname -r)
(Executables reside in /usr/sbin/ with a -bpfcc suffix, e.g., tcplife-bpfcc).
RHEL / Rocky / AlmaLinux / CentOS
sudo dnf install bcc-tools kernel-devel-$(uname -r)
(Executables reside in /usr/share/bcc/tools/tcplife).
Arch Linux
sudo pacman -S bcc-tools
# Or for the C/CO-RE version:
sudo pacman -S libbpf-tools
Verify the binary:
sudo tcplife -h 2>/dev/null || sudo tcplife-bpfcc -h
3. Basic Syntax & Command Flags
sudo tcplife [options]
Essential Command Flags
| Flag | Description | Practical Example |
|---|---|---|
| (no flag) | Trace all IPv4 and IPv6 TCP sessions system-wide. | sudo tcplife |
-T |
Include a human-readable 24-hour timestamp (HH:MM:SS) column. |
sudo tcplife -T |
-t |
Include a monotonic timestamp (seconds from tool start). | sudo tcplife -t |
-p <PID> |
Trace TCP connections owned strictly by a specific Process ID. | sudo tcplife -p 5410 |
-L <port> |
Filter connections matching a specific local port (comma-separated). | sudo tcplife -L 80,443 |
-D <port> |
Filter connections matching a specific remote/destination port. | sudo tcplife -D 5432 |
-s |
Trace only IPv4 connections (exclude IPv6). | sudo tcplife -4 or -s |
-w |
Trace only IPv6 connections. | sudo tcplife -6 or -w |
4. Anatomy of Default Output (tcplife -T)
Running tcplife with timestamps enabled displays session completions in real time:
sudo tcplife -T
Raw Output Example
TIME PID COMM LADDR LPORT RADDR RPORT TX_KB RX_KB MS
07:15:01 5410 postgres 192.168.1.50 5432 10.0.0.12 48912 142 1850 1450.25
07:15:02 8912 curl 192.168.1.50 52104 140.82.121.4 443 1 15 82.10
07:15:05 1120 kubelet 127.0.0.1 10248 127.0.0.1 38910 0 0 0.85
07:15:08 4515 nginx 192.168.1.50 80 203.0.113.15 59102 45 2 12.40
07:15:10 8912 python3 192.168.1.50 41022 10.0.0.8 9092 8 0 60012.80
5. Breakdown of Every Output Heading & Field
+----------+------+----------+---------------+-------+---------------+-------+-------+-------+---------+
| TIME | PID | COMM | LADDR | LPORT | RADDR | RPORT | TX_KB | RX_KB | MS |
+----------+------+----------+---------------+-------+---------------+-------+-------+-------+---------+
| 07:15:01 | 5410 | postgres | 192.168.1.50 | 5432 | 10.0.0.12 | 48912 | 142 | 1850 | 1450.25 |
+----------+------+----------+---------------+-------+---------------+-------+-------+-------+---------+
5.1 TIME
- Format:
HH:MM:SS(wall-clock time). - Meaning: The exact moment the TCP connection finished closing and was torn down by the kernel.
5.2 PID
- Format: Numeric integer (e.g.,
5410,8912). - Meaning: The Process ID associated with the task that held the socket file descriptor.
- Kernel Context: If the socket was closed asynchronously by the kernel network stack during teardown or orphan cleanup, this may attribute to the process that initiated the
close()system call.
5.3 COMM
- Format: String (e.g.,
postgres,curl,nginx,kubelet). - Meaning: The short executable command name of the process (first 16 characters of
task_struct->comm).
5.4 LADDR & LPORT (Local Endpoint)
LADDR: The local IP address (IPv4 or IPv6) bound to the socket.LPORT: The local TCP port number.- Server connections show their listening port (e.g.,
5432,80). - Outbound client connections show their assigned ephemeral port (e.g.,
52104).
5.5 RADDR & RPORT (Remote Endpoint)
RADDR: The remote peer's IP address.RPORT: The remote peer's TCP port number.
5.6 TX_KB (Transmitted Kilobytes)
- Format: Integer Kilobytes.
- Meaning: Total volume of data transmitted over the wire by the local host across the entire lifetime of this session.
- Calculation: Extracted from the kernel's internal TCP socket accounting structures (
tp->bytes_acked/ 1024).
5.7 RX_KB (Received Kilobytes)
- Format: Integer Kilobytes.
- Meaning: Total volume of payload data received from the remote peer across the entire lifetime of this session.
- Calculation: Extracted from socket receive counters (
tp->bytes_received/ 1024).
5.8 MS (Connection Duration in Milliseconds)
- Format: Floating-point decimal (e.g.,
1450.25,0.85,60012.80). - Meaning: The total lifespan of the connection in milliseconds:
$$\text{Lifespan} = \text{Timestamp}_{\text{TCP_CLOSE}} - \text{Timestamp}_{\text{TCP_ESTABLISHED}}$$
- Diagnostic Value: Instantly reveals session behavior:
< 5 ms: Rapid ephemeral connection (health check, micro-query, or un-pooled API call).1000 – 5000 ms: Short-lived transactional session.> 60000 ms: Long-lived stateful connection (keep-alive, database pool, streaming socket).
6. How tcplife Works Internally
tcplife avoids per-packet inspection by attaching eBPF programs directly to the kernel's internal TCP state engine:
+-------------------------------------------------------------------------+
| KERNEL SPACE |
| |
| 1. TCP State Transition (kprobe:tcp_set_state): |
| - Fires on every state transition (SYN_SENT -> ESTABLISHED, etc.) |
| |
| 2. When state == TCP_ESTABLISHED: |
| - Stores struct sock *sk as Key in BPF Hash Map. |
| - Value = { start_time: bpf_ktime_get_ns(), pid, comm } |
| |
| 3. Connection Active (Data Transfer Phase): |
| - ZERO eBPF execution during packet transit. |
| - Hardware offloads (TSO, GRO) run at full wire speed. |
| |
| 4. When state == TCP_CLOSE: |
| - Looks up socket pointer in BPF Hash Map. |
| - Calculates: delta_ms = (current_time - start_time) / 1,000,000 |
| - Reads struct tcp_sock: |
| tx_bytes = tp->bytes_acked; |
| rx_bytes = tp->bytes_received; |
| - Emits event via BPF Perf/Ring Buffer to user space. |
| - Deletes map entry (frees kernel memory). |
+-------------------------------------------------------------------------+
|
v (BPF Ring Buffer)
+-------------------------------------------------------------------------+
| USER SPACE |
| Python / C CLI: Formats IP addresses, converts bytes to KB, prints |
| single-line summary to stdout. |
+-------------------------------------------------------------------------+
7. tcplife vs. Related TCP Tracing Tools
+-------------------------------------------------------------------------+
| Tool | Instrumentation Point | Primary Output Style |
+---------------+----------------------------+----------------------------+
| tcpdump | AF_PACKET / libpcap | Every raw packet on wire |
| tcpconnect | sys_enter_connect (Active) | Outbound connection start |
| tcpaccept | sys_enter_accept (Passive) | Inbound connection start |
| tcptop | TCP send/receive calls | Throughput top-like table |
| tcplife | tcp_set_state() | Full session summary line |
+-------------------------------------------------------------------------+
| Dimension | tcplife |
tcpdump |
tcptop |
tcpconnect / tcpaccept |
|---|---|---|---|---|
| Overhead | Extremely Low | Very High ($>10\times$ CPU) | Low | Very Low |
| Output Frequency | 1 line per closed session | 1 line per packet | Refreshed every $N$ sec | 1 line per new session |
| Session Duration | Yes (MS column) |
Only via manual stream math | No | No |
| Transferred Volume | Yes (TX_KB, RX_KB) |
Yes (computes payloads) | Yes (KB/s throughput) | No |
| Identifies PID/COMM | Yes | No | Yes | Yes |
| Safe for 40G/100G | Yes | No (packet drops/lag) | Yes | Yes |
8. Real-World Troubleshooting Scenarios
Scenario A: Diagnosing Missing Connection Pools (Database Churn)
A backend microservice shows high CPU utilization and latency spikes on an upstream PostgreSQL database.
Filter tcplife to destination port 5432:
sudo tcplife -T -D 5432
Output:
TIME PID COMM LADDR LPORT RADDR RPORT TX_KB RX_KB MS
10:00:01 14201 python3 10.0.0.5 41002 10.0.0.20 5432 2 8 3.40
10:00:01 14201 python3 10.0.0.5 41004 10.0.0.20 5432 2 8 3.25
10:00:01 14201 python3 10.0.0.5 41006 10.0.0.20 5432 2 8 3.12
10:00:01 14201 python3 10.0.0.5 41008 10.0.0.20 5432 2 8 3.50
Diagnosis: python3 is establishing hundreds of connections per second, sending tiny payloads ($2\text{ KB}$), and tearing them down after only $3\text{ ms}$.
Root Cause: The application is creating a new TCP connection and performing a full TLS/auth handshake for every individual query rather than using a persistent connection pool (such as PgBouncer or an internal pool).
Scenario B: Detecting Asymmetric Transfers & Hanging Keep-Alives
An edge API gateway reports thread exhaustion.
Run tcplife targeting port 80:
sudo tcplife -T -L 80
Output:
TIME PID COMM LADDR LPORT RADDR RPORT TX_KB RX_KB MS
10:04:12 4515 nginx 192.168.1.50 80 198.51.100.22 51200 0 0 60002.15
10:04:12 4515 nginx 192.168.1.50 80 198.51.100.84 51202 0 0 60001.90
Diagnosis: Connections are remaining open for exactly $60,000\text{ ms}$ (60 seconds) with **0 TX_KB and 0 RX_KB**.
Root Cause: External clients are initiating TCP handshakes and then going silent (Slowloris attack or dead intermediate mobile links), occupying worker slots until Nginx's client_header_timeout or keepalive_timeout forcefully triggers TCP_CLOSE.
9. Important Interview Questions & Answers
Q: Why does tcplife hook tcp_set_state() rather than monitoring system calls like connect() and close()?
Answer: Monitoring system calls (sys_enter_connect, sys_enter_close) captures only user-space requests, not the actual network state. A process can call connect(), but the connection might fail due to a timeout or TCP RST, never reaching an established state. Conversely, a connection can close without a local close() call—such as when the remote peer initiates teardown with a FIN, when an unrecoverable network drop triggers a keep-alive timeout, or when the connection resets via RST. Hooking tcp_set_state() inside the kernel tracks the ground truth of the socket lifecycle regardless of how or why the state transition was triggered.
Q: How does tcplife accurately calculate TX_KB and RX_KB without tracking every read and write system call?
Answer: The Linux kernel's internal TCP socket data structure (struct tcp_sock) already maintains native cumulative counters:
bytes_acked: Tracks the total number of payload bytes sent and acknowledged by the peer.bytes_received: Tracks the total number of payload bytes delivered to the socket.
When the socket transitions to TCP_CLOSE, tcplife reads these pre-existing kernel counters directly from the socket struct. This offloads all accounting work to the kernel's normal TCP operation, requiring zero per-packet or per-syscall tracking.
Q: Does tcplife capture failed connection attempts?
Answer: By design, tcplife tracks connections that successfully reach the TCP_ESTABLISHED state. If an outbound connection is rejected immediately with an ICMP unreachable or a TCP RST, or if a client abandons a handshake during SYN_SENT, it never reaches TCP_ESTABLISHED and is not logged by default. To capture failed connection attempts, tools like tcpconnect or custom bpftrace scripts monitoring tcp_v4_connect and tcp_finish_connect are used instead.