Same Job, Different Shell Part 6: Active Connections and Sockets

Once addressing, routing, and DNS all check out, the next question is usually about a specific connection: is something listening on the port I expect, is a connection actually established, or is it stuck half-open. This is where socket-level tools come in.

Linux: ss over netstat

netstat is the tool everyone learned first, and it still works on plenty of boxes, but it’s deprecated upstream and increasingly absent by default; I had to explicitly install net-tools to get it in this sandbox, where ss was already there. ss talks to the kernel directly over netlink instead of parsing /proc/net/tcp, which is both faster on a busy host and the reason it’s the tool actually being maintained.

$ ss -tulpn
Netid State  Recv-Q Send-Q Local Address:Port  Peer Address:Port  Process
tcp   LISTEN 0      128        0.0.0.0:22           0.0.0.0:*     users:(("sshd",pid=812,fd=3))
tcp   LISTEN 0      511      127.0.0.1:5432          0.0.0.0:*     users:(("postgres",pid=1204,fd=6))

-t TCP, -u UDP, -l listening sockets only, -p show the owning process, -n don’t resolve port numbers to service names (skip this and port 22 prints as ssh, which is usually more readable, but slower on a box with a lot of sockets). That’s the “what’s listening” view. For active, established connections instead:

$ ss -tan state established
State  Recv-Q Send-Q  Local Address:Port    Peer Address:Port
ESTAB  0      0        10.10.10.5:48392   203.0.113.10:443

state established filters to just that TCP state, one of ss’s genuine advantages over netstat: it understands TCP state filtering natively (ss -tan state syn-sent, state time-wait, and so on) instead of you grepping the state column out of unfiltered output yourself.

The legacy equivalent, same information, older format:

$ netstat -tulpn
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      812/sshd

Nearly the same columns, same core data, ss’s real edge is speed on a host with thousands of sockets (it doesn’t walk /proc/net/tcp text) and the native state filtering above.

Windows: netstat still works, Get-NetTCPConnection is the structured version

Windows never deprecated netstat, so it’s still a completely normal first move:

C:\> netstat -ano

-a all connections and listening ports, -n numeric (don’t resolve hostnames), -o show the owning process ID. That last flag is the tell: Windows’ netstat gives you a PID, not a process name, so identifying what’s actually listening on a port is a two-step process:

C:\> netstat -ano | findstr :443
C:\> tasklist /fi "PID eq 4104"

Get-NetTCPConnection closes that gap in one pipeline. Here’s a real, trimmed slice of output from an ordinary desktop, joined to Get-Process for the name:

PS> Get-NetTCPConnection -State Established, Listen |
    ForEach-Object {
        $proc = Get-Process -Id $_.OwningProcess -ErrorAction SilentlyContinue
        $_ | Add-Member -NotePropertyName ProcessName -NotePropertyValue $proc.Name -PassThru
    } |
    Format-Table LocalAddress, LocalPort, RemoteAddress, RemotePort, State, OwningProcess, ProcessName -AutoSize

LocalAddress  LocalPort RemoteAddress   RemotePort       State OwningProcess ProcessName
------------  --------- -------------   ----------       ----- ------------- -----------
::                49665 ::                       0      Listen          1256 wininit
::                49667 ::                       0      Listen          4464 svchost
::                  445 ::                       0      Listen             4 System
::                  135 ::                       0      Listen          2340 svchost
::1               42050 ::                       0      Listen          8532 OneDrive.Sync.Service
192.168.1.50      65103 203.0.113.10           443 Established         26396 msedgewebview2
192.168.1.50      59987 192.168.2.50            22 Established         23760 ssh
127.0.0.1         58547 127.0.0.1            58546 Established         28416 local-service
127.0.0.1         58546 127.0.0.1            58547 Established         28416 local-service
192.168.1.50      56075 203.0.113.20           443 Established         18412 OneDrive

Two real things worth reading out of that rather than a clean two-row demo. First, several Listen entries show :: rather than 0.0.0.0, that’s IPv6’s “any address,” and it’s completely normal for common Windows services (svchost, wininit, System) to bind dual-stack by default even on a connection that isn’t otherwise using IPv6, don’t read that as a misconfiguration. Second, the matched loopback pair (127.0.0.1:58547 ↔ 127.0.0.1:58546 and its mirror image) is what local interprocess communication over TCP actually looks like in this table: two rows, same two ports, direction reversed, one process on both ends.

That’s genuinely more direct than the Linux side for this specific case: ss -p shows the process name inline without a second command, but Get-NetTCPConnection alone only gives you OwningProcess (a PID), and getting the name back requires the same join-to-Get-Process step shown above. Filtering by state is a first-class parameter either way:

PS> Get-NetTCPConnection -State Established
PS> Get-NetTCPConnection -State Listen

matching ss -tan state established in spirit if not in exact syntax. Valid -State values are worth knowing since they don’t all match Linux’s TCP state names one-to-one: Bound, Closed, CloseWait, Closing, DeleteTCB, Established, FinWait1, FinWait2, LastAck, Listen, SynReceived, SynSent, TimeWait. SynReceived is Windows’ name for what Linux calls SYN-RECV; otherwise they line up closely enough that translating a Linux TCP-state mental model to Windows is mostly just capitalization.

Real finding: net-tools isn’t always installed

I mentioned this above but it’s worth calling out on its own, since it directly confirms something the ss deep dive on this site already argued: netstat genuinely isn’t guaranteed to be there anymore. This sandbox (a fairly standard, current Ubuntu build) shipped ss and iproute2 but not net-tools at all; which netstat came back empty until I installed it by hand. If a runbook or script assumes netstat exists on every Linux box it’ll touch, that assumption is increasingly wrong. ss doesn’t have that problem, it’s part of iproute2, which is about as core as Linux networking tooling gets.

Quick reference

What you wantLinuxWindows
Listening ports + processss -tulpnnetstat -ano then tasklist /fi "PID eq <n>", or Get-NetTCPConnection -State Listen joined to Get-Process
Established connections onlyss -tan state establishedGet-NetTCPConnection -State Established
Legacy/always-present toolnetstat -tulpn (needs net-tools, not always installed)netstat -ano (always present)
Structured/scriptable outputss -tan (parse, or -j/-H on newer builds)Get-NetTCPConnection (real objects natively)
Filter by TCP state nativelyss ... state <state>Get-NetTCPConnection -State <state>

What’s next

Part 7 goes one layer down, from sockets to the link layer: ip neigh/arp against arp -a/Get-NetNeighbor, the tables that map an IP on your own subnet to the MAC address actually answering for it.

Cross-references: for ss’s full filter syntax, TCP internals (-i info flag, retransmit counts, congestion window), and the complete netstat-to-ss translation table, see Replacing netstat with ss: A Network Engineer’s Diagnostic Guide.