Advertisement
Open Source Projects by Phil Schwartz

Creating a Python netstat viewer with colour-coded connection states

A small Python network utility can turn a crowded list of sockets into something that is easier to inspect at a glance. A netstat-style viewer shows local and remote addresses, ports, protocols, process identifiers and connection states, while colour makes unusual activity stand out before an administrator has to read every row.

This project is well suited to Linux developers who enjoy practical command-line tools. Python’s psutil library provides portable access to socket information, while ANSI terminal colours keep the interface lightweight. The result can run on a workstation, a server in a Sydney data centre or a Raspberry Pi connected through an Australian home network.

Choosing a reliable source of socket data

Traditional netstat is familiar, but many modern Linux distributions recommend ss instead. Reading /proc/net/tcp directly is possible, although it requires decoding hexadecimal addresses and maintaining separate logic for IPv4, IPv6 and process ownership. For a Python utility, psutil.net_connections() offers a clearer starting point.

Install the dependency with:

python3 -m pip install psutil

The call returns connection records containing fields such as fd, family, type, laddr, raddr, status and pid. A permission error may prevent access to process information, particularly on a locked-down production host. Running the viewer with suitable privileges can reveal more details, but the program should still operate gracefully when some records are unavailable.

Connection states describe the TCP lifecycle. LISTEN means a service is waiting for incoming traffic, ESTABLISHED represents an active session, and TIME_WAIT is a normal temporary state after a connection closes. CLOSE_WAIT, SYN_SENT and SYN_RECV can be useful diagnostic signals when they occur in unusual numbers.

Designing the colour scheme

Colours should communicate meaning rather than decorate the terminal. Green is a sensible choice for listening services, blue for established sessions, yellow for transitional or recently closed connections, and red for states that may deserve attention. The colour should never be the only source of information, so the plain state name remains visible in every row.

A small mapping keeps the policy easy to change:

COLORS = {
    "LISTEN": "\033[32m",
    "ESTABLISHED": "\033[34m",
    "TIME_WAIT": "\033[33m",
    "CLOSE_WAIT": "\033[31m",
    "SYN_SENT": "\033[33m",
    "SYN_RECV": "\033[33m",
}
RESET = "\033[0m"

Some terminals do not support ANSI sequences, and redirected output should remain readable. A production-ready version can add a --no-colour option, detect whether sys.stdout.isatty() is true, or use the colorama package for better Windows compatibility. Australian operators working across Linux laptops, cloud consoles and older jump hosts will appreciate an output mode that does not rely on terminal colour support.

Building the first working viewer

The following implementation converts psutil records into compact rows. It handles missing remote addresses, formats IPv6 endpoints correctly and displays the process ID when available.

#!/usr/bin/env python3
import argparse
import socket
import psutil

COLORS = {
    "LISTEN": "\033[32m",
    "ESTABLISHED": "\033[34m",
    "TIME_WAIT": "\033[33m",
    "CLOSE_WAIT": "\033[31m",
    "SYN_SENT": "\033[33m",
    "SYN_RECV": "\033[33m",
}
RESET = "\033[0m"

def endpoint(address):
    if not address:
        return "-"
    host, port = address[:2]
    if ":" in host:
        return f"[{host}]:{port}"
    return f"{host}:{port}"

def protocol(kind):
    return "tcp" if kind == socket.SOCK_STREAM else "udp"

def show_connections(use_colour=True):
    print(f"{'Proto':<6} {'Local address':<28} "
          f"{'Remote address':<28} {'State':<14} PID")
    for conn in psutil.net_connections(kind="inet"):
        state = conn.status or "NONE"
        colour = COLORS.get(state, "") if use_colour else ""
        pid = str(conn.pid) if conn.pid is not None else "-"
        row = (f"{protocol(conn.type):<6} "
               f"{endpoint(conn.laddr):<28} "
               f"{endpoint(conn.raddr):<28} "
               f"{state:<14} {pid}")
        print(f"{colour}{row}{RESET if colour else ''}")

parser = argparse.ArgumentParser()
parser.add_argument("--no-colour", action="store_true")
args = parser.parse_args()
show_connections(not args.no_colour)

The kind="inet" filter includes IPv4 and IPv6 Internet sockets while excluding Unix domain sockets. UDP entries may show an empty state because UDP does not use the same connected handshake as TCP. Treating that value as NONE prevents the display from becoming misleading or incomplete.

Making the output useful for investigation

A raw connection list is helpful, but filters turn it into a practical diagnostic tool. Options such as --state ESTABLISHED, --port 22, --pid 1234 and --listen allow an administrator to narrow the view. Sorting by local port or process ID also makes repeated checks easier, particularly when a host runs many containers or web services.

A refresh mode can approximate a lightweight watch command. Clearing the screen, collecting a new snapshot and sleeping for a configurable interval makes bursts of connections visible. The implementation should catch psutil.AccessDenied and psutil.NoSuchProcess, since processes can disappear between collecting socket data and reading their metadata.

For an Australian small business, a useful default view might highlight SSH sessions on a server hosted in Sydney while leaving routine web traffic blue. Home users connected through the NBN may see private addresses, carrier-grade NAT behaviour or changing public endpoints; those results should not automatically be labelled suspicious. A viewer reports observations, while interpretation still depends on the host and network design.

Packaging the utility for regular use

A clean command-line interface gives the program a longer life than a one-off script. Add --interval, --sort, --state, --port and --no-colour options, and keep the collection, formatting and filtering code in separate functions. This makes the project easier to test and allows a future JSON output mode for monitoring systems.

Tests can supply fake connection objects instead of depending on live sockets. Check IPv4 and IPv6 formatting, missing remote addresses, unknown states and disabled colour. It is also worth testing terminal widths: long IPv6 addresses and process names should be truncated or wrapped deliberately rather than corrupting the table.

A sensible distribution includes a short README, an open-source licence and examples for common Linux environments. The tool can complement ss, lsof and firewall logs without pretending to replace them. On cloud machines in Melbourne, Perth or Sydney, its low overhead makes it suitable for quick incident checks, while its plain-text output remains easy to save alongside system logs.