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 want | Linux | Windows |
|---|---|---|
| Listening ports + process | ss -tulpn | netstat -ano then tasklist /fi "PID eq <n>", or Get-NetTCPConnection -State Listen joined to Get-Process |
| Established connections only | ss -tan state established | Get-NetTCPConnection -State Established |
| Legacy/always-present tool | netstat -tulpn (needs net-tools, not always installed) | netstat -ano (always present) |
| Structured/scriptable output | ss -tan (parse, or -j/-H on newer builds) | Get-NetTCPConnection (real objects natively) |
| Filter by TCP state natively | ss ... 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.