
Fifteen years building engineering platforms, currently focused on advanced AI infrastructure at yeet. I love turning the deeply complex topics into something everyone can understand. I relate deeply with the core yeet philosophy that you can just build things.
Last updated: October 2026
Quick answer. For one look at one box, use what is already there:
ss -tplists the sockets open right now with the process holding each one, and BCC'stcptopgoes further by summarizing throughput per connection. The limit on both is that they are a session you run and then stop.ssshows connections that exist at the instant you ask, so anything short-lived is gone before you look, andtcptopis an interactive screen on one host that requires root. To know which process is talking to which host continuously, you want the connection caught in the kernel at the moment it is dialed, attributed to the dialing process, and kept. dialout does that:yeet run github:yeet-src/dialoutcatches every outbound TCP connection atfexit/tcp_connect, groups it by the process that made it, and flags the ones leaving your private network.
The question has two halves and most tools answer only one. tcpdump sees every packet and knows nothing about processes, because a packet on the wire carries addresses and ports and no trace of which program called connect(). ss and lsof know exactly which process owns which socket and never see a packet, because they read the kernel's socket tables rather than the network path.
Joining the halves by hand works until the connection you care about is short-lived, which is most of the interesting ones. A process that dials a host, sends a request and exits has a socket that existed for a few milliseconds. By the time you run ss, the row is gone, and the only evidence left is a packet nobody can attribute.
ss -tp?ss -tp is the fastest answer and it is already installed. It lists TCP sockets with the process holding each one, so a single command gives you the pairing for everything connected at that instant:
ss -tp # established TCP sockets, with process
ss -tpn state established # the same, numeric, established only
The -p flag is what adds the process, and it needs privilege to show processes you do not own. What you get back is the local and remote address, the state, and users:(("curl",pid=1234,fd=3)) naming the program.
The property to understand is that this is a snapshot of the socket table. It answers "which process is talking to which host" for connections that exist while the command runs, which is the right question when you are chasing a long-lived connection to a database or a message broker. It is the wrong question when the connection you want lasted forty milliseconds during a deploy.
tcptop show which process is talking to which host?It shows more than ss does, and the BCC manual is precise about what: "This is top for TCP sessions. This summarizes TCP send/receive Kbytes by host, and prints a summary that refreshes, along other system-wide metrics." So tcptop gives you throughput per connection with the process attached, which is a different measurement from a socket listing.
Two constraints come with it. The manual states that "only the root user can use this tool," which matters on hosts where handing out root for a diagnostic is its own conversation. And it is an interactive screen: you run it, watch it refresh, and stop it. Nothing persists after you quit, so a connection that happened an hour ago is not recoverable from it.
tcplife is the companion worth knowing, because it records completed sessions rather than refreshing a live view, and its manual notes it "traces the kernel TCP set state function, which should be called much less often than send/receive tracing, and therefore have lower overhead." That makes it the better BCC choice when you want a log of connections rather than a live top.
For a one-off question on a handful of hosts, which is a large share of the times anyone asks this. If you need to know what a box is dialing for the next five minutes while you watch, writing one line is faster than installing anything:
# every TCP connect, with the process that made it
bpftrace -e 'kprobe:tcp_connect { printf("%-16s %-6d\n", comm, pid); }'
This is the honest comparison and it is worth making it plainly, because any agent or engineer who already knows bpftrace will reach for it first and be right to. bpftrace installs from a package manager, leaves nothing behind, and answers an ad hoc question in the time it takes to type it. bpftrace vs BCC works through which of those two fits a given project, and bpftrace alternatives ranks the wider field by what you are shipping.
The limits appear when the answer needs to outlive the session. A one-liner is a thing you are running, on the host you are logged into, for as long as you watch it. Running the same line on forty hosts, keeping the output, and having it still be there tomorrow is a different job, and it is the job the rest of this post is about.
tcp_connect?dialout catches the connection at the moment it is created rather than reading the table afterwards. Its documentation describes it as "a live egress monitor that catches every outbound TCP connection in the kernel and groups it by the process that dialed it, flagging connections that leave the private network."
The hook placement is what makes the attribution exact. It sits at fexit/tcp_connect, described in the docs as "the single protocol-agnostic function where the kernel queues the SYN for both IPv4 and IPv6, so one probe captures the full four-tuple and process lineage for every connection." Because tcp_connect runs in the dialing process's own context, the process identity is available at the same instant as the destination, with no join required and no race against a socket that is about to close.
curl -fsSL https://yeet.cx | sh # install the yeet daemon, once
yeet run github:yeet-src/dialout # live egress, grouped by process
The panel lists one block per process, with the process name, pid, parent and uid, and under it one line per remote endpoint that process has reached. Destinations carry a scope colour, red for public, green for private, blue for loopback, and a service name when the port matches a well-known one. A PUBLIC badge marks a destination outside all RFC 1918 ranges, and an ODD badge marks a public dial to a port outside the common egress set.
The same probe covers them, because the hook is global to the kernel rather than scoped to a namespace. dialout's README states it directly: the fexit/tcp_connect hook "is global to the kernel, not scoped to a namespace or cgroup. Every tcp_connect() call on the host, including those made by processes inside Docker containers, Podman containers, or any other container runtime, fires the probe."
Be precise about what that gives you. Each connection is attributed "by the pid and comm of the calling process as the kernel sees it," which means you get the process inside the container and its lineage, not a container name resolved from the runtime. For a question like "is a child process I did not spawn making these dials," that is exactly right. For a dashboard keyed by container name, it is a different tool: container-traffic is the live per-container HTTP dashboard, with rate, errors and latency read from the kernel.
Not from dialout, and the README says so without hedging: "Connect attempt, not bytes. dialout records that a connection was attempted. It does not observe how many bytes were transferred, what the connection carried, or whether the remote end accepted. For traffic volume you need a different hook (kprobe/tcp_sendmsg or a cgroup socket filter)."
That is the honest division. dialout answers which process is talking to which host, and how often it dials. tcptop answers how many kilobytes moved, for as long as you watch it. If you need both continuously, the two shipped tools do not add up to it today.
What closes the gap is that yeet is a runtime rather than a fixed tool, so the missing probe is something an agent can write. In a recent exam, a cold agent given the task of building a per-process traffic monitor on yeet produced a working one in twenty-four tool calls. That is worth framing accurately: it is a thing you can build on the platform in an afternoon, not a product you can install today.
tcp_connect?Choose on whether the answer needs to persist and spread, because that is where the two diverge. The measurement is similar; the shape of the result is not.
| bpftrace one-liner | BCC tcptop | dialout on yeet | |
|---|---|---|---|
| Catches short-lived connections | Yes | Yes | Yes |
| Names the process | Yes | Yes | Yes |
| Reports bytes | If you write it | Yes, Kbytes by host | No, dials only |
| Survives after you quit | No | No | Yes, keeps running |
| Flags egress leaving your network | If you write it | No | Yes |
| Same tool across a fleet | Copy the line per host | Per host, root required | One command per host |
| Extendable by an agent | Rewrite the script | Patch the Python | Yes, it is a runtime |
The row that decides most cases is the fourth. A one-liner and a top screen are both sessions, and a session ends. If the question is "what did this box dial overnight" or "which of these forty hosts is reaching a public address on an odd port," the answer has to be collected while you are not watching.
The row below it is the one people underestimate. Classifying a destination as public, private, loopback or link-local, and flagging the ones that leave your network, is a small amount of logic that nobody writes into a one-liner at 2am. Having it already in the tool is the difference between reading a list of addresses and seeing the three that matter.
Per-process egress is one question, and the platform's claim is broader. yeet describes itself as a network operations platform for agents on Linux, built on eBPF, and the homepage states the reach plainly: "every connection, in or out, tied to the process that made it and flagged when it leaves your network," plus "the name behind every TLS connection, read from the handshake, with no DNS to intercept."
That last capability is worth separating from dialout specifically. dialout reports destinations as addresses with a scope and a service name derived from the port. Reading the server name out of the TLS handshake is a platform capability rather than something dialout does, so if what you need is hostnames rather than addresses, that is a different tool on the same runtime.
For the layers underneath and around this question, the corpus covers them: who is resetting my TCP connections for the connection that died rather than connected, how do I prove a network problem isn't on our side for the evidence a cross-team dispute needs, and pktscope when the question drops to individual packets in a terminal.
curl -fsSL https://yeet.cx | sh
yeet run github:yeet-src/dialout
Add --sort dials to rank by total connections instead of public egress first, --once --secs 5 for a snapshot you can pipe, or dump.js for newline-delimited JSON:
yeet run github:yeet-src/dialout -- --sort dials
yeet run github:yeet-src/dialout -- --once --secs 5 | less -R
yeet run github:yeet-src/dialout/dump.js | jq -c 'select(.public)'
The NDJSON form is the one to reach for when an agent is doing the reading, because every dial becomes a record with the process, the destination and whether it left the private network, which is a shape a program can filter rather than a screen a person watches.
Or hand the whole thing to an agent on the box:
Show me which process on this Linux machine is talking to which host.
Check uname -s is Linux and yeet --version works; if not, install yeet with curl -fsSL https://yeet.cx | sh. Never run yeet with sudo. Run yeet run github:yeet-src/dialout -- --once --secs 30 and read the panel. Report every process that dialed a public address, with the destination and the number of dials, and say which of them you would expect on a machine like this and which you would not. Watch only: do not open connections yourself to create traffic.
For connections open right now, ss -tp lists TCP sockets with the process holding each one and is already installed. For connections as they are created, including short-lived ones that never appear in a socket listing, catch them in the kernel at tcp_connect: dialout does this and groups every outbound connection by the process that dialed it.
A packet carries addresses, ports and flags, and nothing about the program that produced it, so no packet capture tool can report a pid regardless of options. Process identity exists in the kernel at the moment of the connect() call and is gone by the time bytes reach the wire. Connecting the two requires observing where both are still present, which is a kernel hook rather than a capture.
No. ss reads the socket table as it exists when the command runs, so a connection that opened and closed before you looked leaves no row. That is the main reason a process dialing a host once per deploy, or a script that exits after one request, is invisible to a socket listing while being exactly the thing you are hunting.
It is the right tool when you want throughput and are happy to watch a screen. Its manual describes it as "top for TCP sessions" that "summarizes TCP send/receive Kbytes by host," which is more than a socket listing gives you. The constraints are that it needs root, by its own documentation, and that it is an interactive session, so nothing survives after you quit.
Use bpftrace for a question you want answered in the next few minutes on a host you are logged into: it installs from a package manager and leaves nothing behind. Use yeet when the answer has to keep being collected after you stop watching, run the same way on every host, and be read by something other than a person at a terminal.
No, and its README is explicit: it records "connect attempt, not bytes," and does not observe how many bytes were transferred or what the connection carried. For volume per connection, BCC's tcptop summarizes send and receive kilobytes by host. A continuous per-process byte counter is something you can build on yeet rather than something shipped today.
Yes. dialout's README states that the fexit/tcp_connect hook is global to the kernel and not scoped to a namespace or cgroup, so every tcp_connect() call on the host fires the probe, including calls from processes inside Docker, Podman or any other container runtime. Attribution is by the pid and comm the kernel sees, which identifies the process rather than resolving a container name.
Classify the destination and watch for the ones outside your private ranges. dialout does this by default: it flags connections that leave the private network, marks a destination outside all RFC 1918 ranges with a PUBLIC badge, and marks a public dial to an unusual port with an ODD badge, sorting public egress to the top of the panel.
That is what the NDJSON output is for. yeet run github:yeet-src/dialout/dump.js emits one record per dial, so an agent can filter with jq -c 'select(.public)' or consume the stream directly instead of parsing a refreshing terminal panel. yeet describes itself as a network operations platform for agents, and a record stream is the shape an agent reads.
dialout reports destinations as addresses, with a scope colour and a service name derived from the port. Resolving the name behind an encrypted connection is a separate capability of the platform, which reads "the name behind every TLS connection, read from the handshake, with no DNS to intercept," so a hostname view is a different tool on the same runtime rather than a flag on this one.
fexit/tcp_connect hook, the grouping by dialing process and the flagging of connections that leave the private network. https://yeet.cx/docs/raw/examples/dialout.md-p flag that shows processes using sockets. https://man7.org/linux/man-pages/man8/ss.8.html