uss is a Go library and CLI tool for parsing Linux socket statistics from the ss command output. It supports both INET sockets (tcp, udp) and UNIX sockets (u_str, u_dgr, u_seq) with multiple output formats.
- Parse
ssoutput: Handles both INET and UNIX socket formats - Multiple output formats: Human-readable tables, CSV, JSON, and YAML
- Robust parsing: Handles IPv4, IPv6 (with brackets and scopes), interface suffixes, abstract sockets
- Process metadata extraction: Extracts users, UID, inode, cgroup, and more
- Reusable library: Clean API for use in other Go applications
- CLI tool: Built with Cobra for easy command-line use
go install github.com/mobydeck/uss/cmd/uss@latestgit clone https://github.com/mobydeck/uss
cd uss
go build -o uss ./cmd/ussBy default, uss reads from stdin and outputs human-readable format:
ss -tunap | ussUse the --ss flag to execute the ss command directly with custom flags:
# Listen on all TCP ports
uss --ss tnl
# Show all TCP and UDP sockets with process information
uss --ss tunap
# Get UNIX sockets with all details
uss --ss peanutlx
# With leading dash (also works)
uss --ss -tnlThe --ss flag automatically parses output even when the Netid column is missing (e.g., from ss -tnl), inferring it as tcp by default. The leading dash in ss flags is optional.
Human-readable (default):
ss -tunap | ussJSON:
ss -tunap | uss --jsonPretty JSON:
ss -tunap | uss --json --prettyYAML:
ss -peanutlx | uss --yamlCSV:
ss -tunap | uss --csv > sockets.csvYou can also read from a file instead of stdin:
ss -tunap > sockets.txt
uss --json sockets.txtBy default, uss hides raw/unparsed fields for cleaner output. These include:
processRaw- The unparsed process metadata stringlocal- The raw local address:port token (before parsing)peer- The raw peer address:port token (before parsing)
To include these raw fields in the output, use the -r or --raw flag:
# Include raw fields in JSON output
ss -tunap | uss --json --raw
# Include raw fields in CSV output
ss -tunap | uss --csv --raw
# Include raw fields in YAML output
ss -tunap | uss --yaml --raw
# Include raw fields in human-readable output
ss -tunap | uss --rawThis is useful when you need to see the original unparsed data or for debugging parsing issues.
Filter socket entries using the -q or --query flag:
- Format:
field=value1,value2[,valueN] - Multiple values per field: comma-separated (OR condition)
- Multiple fields: space or semicolon separated (AND condition)
Single field:
# Find all TCP sockets
ss -tunap | uss -q "netid=tcp"# Find sockets on port 22 or 80
ss -tunap | uss -q "port=22,80"Multiple fields (AND):
# TCP sockets on port 22 or 80 with uid 0 or 1000
ss -tunap | uss -q "netid=tcp port=22,80 uid=0,1000"# Using semicolon separator
ss -tunap | uss -q "netid=tcp;port=22,80;uid=0,1000"Wildcards:
# All LISTEN sockets
ss -tunap | uss -q "state=LISTEN*"# Unix sockets in /run directory
ss -peanutlx | uss -q "unixpath=*/run/*"Ranges:
# Ports between 1000 and 2000
ss -tunap | uss -q "port=1000-2000"# UID between 1000 and 2000
ss -tunap | uss -q "uid=1000-2000"Substring matching:
# Find sockets with "docker" in path
ss -peanutlx | uss -q "unixpath=docker"# Find sockets with specific cgroup
ss -tunap | uss -q "cgroup=systemd"Using --ss flag with output formats:
# Execute ss directly and output as JSON
uss --ss tunap --json
# Execute ss directly and output as YAML with filtering
uss --ss peanutlx --yaml -q unixpath=/run/*# Execute ss and export as CSV
uss --ss tunap --csv > all_sockets.csv
# Execute ss and output as pretty JSON
uss --ss tnl --json --prettyCombined queries with output format:
# TCP LISTEN sockets on ports 22 or 80 with uid 0, output as JSON
ss -tunap | uss -q "netid=tcp state=LISTEN port=22,80 uid=0" --json
# Filter and output as CSV
ss -tunap | uss -q "netid=tcp" --csv > tcp_sockets.csvCommon:
netid- Socket type (tcp, udp, u_str, etc.)state- Socket state (LISTEN, ESTAB, UNCONN, etc.)recvq- Receive queue sizesendq- Send queue size
INET-specific:
port- Local or peer port (convenience field, matches both)address- Local or peer address (convenience field, matches both)localport- Local portpeerport- Peer portlocaladdress- Local addresspeeraddress- Peer addressinterface- Network interface
UNIX-specific:
unixtype- Unix socket type (u_str, u_dgr, u_seq)unixpath- Unix socket path (supports wildcards/substring)unixid- Unix socket IDunixpeer- Unix peer socketunixpeerid- Unix peer socket ID
Metadata:
uid- User ID (numeric)inode- Inode number (numeric)cgroup- Control group pathv6only- IPv6-only flag (0 or 1)fwmark- Firewall marksk- Socket kernel identifierdev- Device identifier
- Exact match: Default for most values (
port=22) - Wildcard: Use
*(any sequence) or?(single char) (state=LISTEN*) - Range: Use
min-maxfor numeric fields (port=1000-2000) - Substring: For string fields, partial matches work (
unixpath=docker) - Case-insensitive: All string matching is case-insensitive (
state=listenmatchesLISTEN)
Parse INET sockets:
ss -tunap | ussParse UNIX sockets:
ss -peanutlx | ussGenerate CSV report:
ss -tunap | uss --csv > sockets.csvYou can combine uss with jq for powerful JSON-based filtering and querying:
Filter by port:
# Find all sockets listening on port 80
ss -tunap | uss --json | jq '.[] | select(.localPort == "80")'Filter by state and protocol:
# Find all LISTEN TCP sockets
ss -tunap | uss --json | jq '.[] | select(.state == "LISTEN" and .netid == "tcp")'Filter by UID and extract specific fields:
# Find sockets owned by user 1000, show only address and port
ss -tunap | uss --json | jq '.[] | select(.uid == 1000) | {addr: .localAddress, port: .localPort, state: .state}'Count sockets by state:
# How many sockets in each state
ss -tunap | uss --json | jq 'group_by(.state) | map({state: .[0].state, count: length})'Find listening ports:
# Get all listening ports
ss -tunap | uss --json | jq -r '.[] | select(.state == "LISTEN") | "\(.localAddress):\(.localPort)"'Filter by cgroup:
# Find all sockets in systemd cgroups
ss -tunap | uss --json | jq '.[] | select(.cgroup | startswith("/system.slice"))'Complex filtering:
# TCP sockets that are either LISTEN or ESTAB, and not owned by root
ss -tunap | uss --json | jq '.[] | select((.state == "LISTEN" or .state == "ESTAB") and (.uid // 0) != 0)'Extract process information:
# Get process names and PIDs for each socket
ss -tunap | uss --json | jq '.[] | select(.users | length > 0) | {port: .localPort, processes: .users[].name}'Format as table:
# Create a custom table output
ss -tunap | uss --json | jq -r '.[] | [.netid, .state, .localAddress, .localPort, (.users[0].name // "unknown")] | @tsv'Find sockets by pattern:
# Find all UNIX sockets in /run directory
ss -peanutlx | uss --json | jq '.[] | select(.unixPath | startswith("/run"))'# Find abstract sockets
ss -peanutlx | uss --json | jq '.[] | select(.unixPath | startswith("@"))'Export filtered results:
# Export only LISTEN TCP sockets to CSV-like format
ss -tunap | uss --json | jq -r '.[] | select(.state == "LISTEN" and .netid == "tcp") | [.netid, .state, .localAddress, .localPort, .uid] | @csv'import"github.com/mobydeck/uss"package main
import (
"fmt""os""strings""github.com/mobydeck/uss"
)
funcmain() {
// Sample ss outputinput:=`Netid State Recv-Q Send-Q Local Address:Port Peer Address:Porttcp LISTEN 0 128 0.0.0.0:22 0.0.0.0:*udp UNCONN 0 0 127.0.0.53%lo:53 0.0.0.0:*`// Parse the inputentries, err:=uss.Parse(strings.NewReader(input), uss.Options{Strict: false})
iferr!=nil {
fmt.Fprintf(os.Stderr, "Parse error: %v\n", err)
return
}
// Print resultsfor_, entry:=rangeentries {
fmt.Printf("%s %s %s:%s\n", entry.Netid, entry.State, entry.LocalAddress, entry.LocalPort)
}
}package main
import (
"os""strings""github.com/mobydeck/uss"
)
funcmain() {
input:=`...`// ss outputentries, _:=uss.Parse(strings.NewReader(input), uss.Options{})
// Render as pretty JSON (without raw fields)uss.RenderJSON(os.Stdout, entries, true, false)
// Or render as JSON with raw fields// uss.RenderJSON(os.Stdout, entries, true, true)// Or render as YAML// uss.RenderYAML(os.Stdout, entries, false)// Or render as CSV// uss.RenderCSV(os.Stdout, entries, false)// Or render as human-readable// uss.RenderHuman(os.Stdout, entries, false)
}package main
import (
"fmt""os""strings""github.com/mobydeck/uss"
)
funcmain() {
input:=`...`// ss outputentries, _:=uss.Parse(strings.NewReader(input), uss.Options{})
// Filter for TCP sockets on port 22 or 80filtered, err:=uss.Filter(entries, "netid=tcp port=22,80")
iferr!=nil {
fmt.Fprintf(os.Stderr, "Filter error: %v\n", err)
return
}
// Render filtered results as JSONuss.RenderJSON(os.Stdout, filtered, true, false)
}For programmatic filtering where you build conditions dynamically, use FilterByConditions:
package main
import (
"fmt""os""strings""github.com/mobydeck/uss"
)
funcmain() {
input:=`...`// ss outputentries, _:=uss.Parse(strings.NewReader(input), uss.Options{})
// Build conditions dynamicallyconditions:= []string{
"netid=tcp",
"state=LISTEN",
"port=22,80,443",
}
// Filter using conditions slicefiltered, err:=uss.FilterByConditions(entries, conditions)
iferr!=nil {
fmt.Fprintf(os.Stderr, "Filter error: %v\n", err)
return
}
// Use filtered resultsfor_, entry:=rangefiltered {
fmt.Printf("%s:%s (%s)\n", entry.LocalAddress, entry.LocalPort, entry.State)
}
}The Entry struct represents a single socket entry:
typeEntrystruct {
// Common fieldsNetidstringStatestringRecvQintSendQintProcessRawstring// Extracted metadataUsers []UserUID*intInode*uint64CGroup*stringV6Only*intFWMark*stringSk*stringDev*stringPeers*string// INET-specificLocalAddressstringLocalPortstringPeerAddressstringPeerPortstringLocalstringPeerstringInterfacestring// UNIX-specificUnixTypestringUnixPathstringUnixIDstringUnixPeerstringUnixPeerIDstring
}The User struct represents a process using a socket:
typeUserstruct {
NamestringPIDintFDint
}Control parsing behavior:
typeOptionsstruct {
Strictbool// if true, fail on malformed lines; if false, skip and continue
}- IPv4:
0.0.0.0:111,127.0.0.1:8080 - IPv4 with interface:
127.0.0.53%lo:53,0.0.0.0%incusbr0:67 - IPv6:
[::]:22,[fd42:2eff:9d69:efec::1]:53 - IPv6 with interface:
[fe80::1266:6aff:fecf:96d]%incusbr0:53 - Wildcard:
*:989,*:8090 - Special formats:
_:8090_,:*
- Filesystem paths:
/run/systemd/private,/var/run/docker.sock - Abstract sockets:
@/var/lib/incus/containers/satis/command,@5cbfc - Socket types:
u_str(stream),u_dgr(datagram),u_seq(seqpacket)
Extracts from the process field:
users:(("name",pid=N,fd=M),...)→ User listuid:<num>→ UIDino:<num>→ Inodecgroup:<path>→ CGroupv6only:<0|1>→ V6Only flagfwmark:<value>→ Firewall marksk:<value>→ Socket kernel identifierdev:<maj/min>→ Devicepeers:→ Peers marker
Run the test suite:
go test ./...Run with verbose output:
go test -v ./...MIT
Contributions are welcome! Please open an issue or submit a pull request.
Built for parsing Linux socket statistics efficiently in Go.