Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

34 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

      ___    __________ ________   ______ __________  __   __   _______     ______
 ,,  // \\   \__    __/ |  ____/  /  ___/ \__    __/ |  | |  | |   ___  \  /  __   \
(_,\/ \_/ \     |  |    |  |___   \  \       |  |    |  | |  | |  |   \  \ |  |  |  |
  \ \_/_\_/>    |  |    |   ___|   \  \      |  |    |  | |  | |  |   |  | |  |  |  |
  /_/  /_/      |  |    |  |_____  /  /      |  |    |  |_|  | |  |___/  / |  |__|  |
                |__|    |_______/ /__/       |__|     \_____/  |________/   \______/

Testudo

Your Linux network, in one quiet terminal.
A friendly, terminal-native home for everything happening on your box - flows, DNS, latency, firewall, routes, NAT, devices - all in one Go binary.

Quick Start »  ·  Features  ·  Web UI  ·  Docs  ·  Report an issue »

Go Linux License Status Pure Go

Bubble Tea Lip Gloss SQLite gopacket netlink AF_PACKET iptables IPFIX opsanio LLDP SNMP mDNS ARP Sentry Guacamole

Live flow analytics across every interface, replayable sessions, anomaly detection, firewall and routing control, NAT, network discovery, IPFIX flow export to opsanio, and a unified web console - all driven by a single Bubble Tea TUI and mirrored to a small embedded HTTP UI. Whatever you need to look at, there's a tab for it.

  • Author: Noah Zeumer <github.com/noahzmr>
  • License: MPL-2.0 with branding restrictions (see LICENSE and NOTICE)
  • Status: active development, Linux-first (Ubuntu / Debian) - hello, come on in

Why It Matters

If you've ever debugged a flaky network, you know the drill. One terminal for tcpdump, another for iftop, a third for ss, nmap in a fourth, iptables and nft in a fifth, ip route in a sixth, and somewhere off to the side a Grafana tab that doesn't quite line up with any of them. Each of those tools is great on its own. They just don't talk to each other - and the moment the problem stops, the evidence is gone.

Testudo is a gentler take on the same job. One place to look. One memory across time. Less ceremony.

  • One TUI instead of six terminals. Every signal - flows, DNS, latency, firewall hits, route changes - lives in the same place.
  • "What was the network doing at 03:14?" A session ID is enough. Open it in replay and scroll.
  • No always-on tcpdump. PCAPs are captured only when something interesting actually happens, then rotated.
  • No external metrics stack. Testudo keeps its own time-series in SQLite. One file. One binary. Done.
  • One engine, two faces. The TUI and the web UI are the same thing over different transports - a feature in one shows up in the other for free.

Underneath: pure-Go netlink, AF_PACKET, and /proc. No cgo, no external broker, no external metrics backend. One binary, one config, one event bus - and you're off.


What does "Testudo" mean?

Testudo is Latin for tortoise - and the word lives in two worlds that both describe this tool.

1. Biology - the tortoise genus

Testudo is the scientific genus name for the palearctic (European) land tortoises. The best-known species are Testudo hermanni (Hermann's tortoise) and Testudo graeca (the Greek tortoise).

The tortoise is slow - and slow is exactly the symptom this tool was built to diagnose.

The reason most operators reach for a packet analyzer in the first place is some variation of "the network feels slow." Latency creeping up. DNS taking just a bit too long. A flow that used to be 100 ms now sitting at 400 ms. A retransmission rate that no dashboard surfaces. Testudo is built for those questions: where is it slow, when did it start being slow, and what was the network doing the moment it got slow.

2. Military history - the Roman Testudo formation

In the legions, a Testudo was a tight tactical formation in which soldiers closed ranks and locked their shields together - overhead and on the flanks - to create a gap-free shell that absorbed arrow volleys and falling stones.

That formation maps almost one-to-one onto how Testudo is built:

  • Shell => a hardened observation layer around the host networking stack.
  • Formation => many small subsystems (collectors, analyzers, engines) interlocking through one event bus - no single subsystem is exposed alone.
  • Coverage without gaps => every observable signal (flow, DNS, ICMP, ARP, LLDP, SNMP, firewall, route, NAT) is captured by some shield; nothing falls through.
  • Endurance => low-overhead, replayable, made to run quietly on a box for weeks.

Mascot

A turtle peeking out from under its shell - half forensic, half cozy. The terminal-native identity is intentional: this is a tool you live inside, not one you click through.


Table of Contents


Features

Live Observability

  • ICMP latency, RTT, jitter, packet loss - per target, with rolling 20-sample statistics.
  • DNS resolver health probing - per-name latency and failure-rate tracking.
  • Multi-interface AF_PACKET capture - physical, wireless, VLAN, bridge, tunnel, VPN, container, and virtual interfaces (eth0, wlan0, tun0, wg0, docker0, br0, …).
  • Process-to-flow correlation - via /proc/net/* and /proc/<pid>/fd - every flow is tagged with the process that owns it.
  • DNS reverse correlation - flows are annotated with the name that originally resolved to the far end.
  • Service catalog - well-known ports mapped to protocol names (HTTP, HTTPS, SSH, DNS, NTP, MySQL, PostgreSQL, Redis, and more).
  • Interface throughput accounting - RX and TX per direction, per interface.

Flow Engine

The flow engine aggregates packets into intelligent flow summaries rather than persisting raw packets, and enriches each flow with process ownership, DNS resolution, service classification, NAT/firewall state, and an IP scope/class label on every endpoint.

┌ Live Flows ────────────────────────────────────────────────────┐
│ IFACE    PROCESS   A => B                       SCOPE          │
│ eth0     firefox   192.168.1.20 => 142.250.1.1  prv·C→pub·B    │
│ wg0      ssh       10.0.0.10 => 10.0.0.1         prv·A→prv·A    │
│ docker0  redis     172.18.0.2 => 172.18.0.5      prv·B→prv·B    │
└─────────────────────────────────────────────────────────────────┘

Tracked per flow: source/destination, ports, protocol, throughput, retransmissions, packet loss, ingress interface, egress interface, process ownership, NAT state, firewall state, and per-endpoint IP classification.

IP Address Labels

Every IP shown in the Flows and Devices views - in both the TUI and the web UI - is tagged with its routability scope and, for IPv4, its classful network (A-E). One classifier (internal/netlabel) feeds both interfaces, so the labels are always identical.

Scope Meaning Ranges Colour
public Globally routable everything not below amber
private Routed inside an org, never on the public net RFC1918, IPv6 ULA fc00::/7, CGNAT 100.64.0.0/10 green
internal Never crosses a router loopback, link-local, unspecified, limited broadcast dim
multicast One-to-many group address 224.0.0.0/4, ff00::/8 cyan

The network class is the historical classful bucket derived from the first octet (A = 0-127, B = 128-191, C = 192-223, D = multicast, E = reserved); it is shown only for IPv4. Labels render as a compact scope·class tag in the TUI (prv·C, pub·B, int·A) and as a coloured pill beside each address in the web UI, with the precise reason (RFC1918, CGNAT, loopback, …) available on hover.

Network Discovery

Testudo runs layered discovery - passive listeners are always on when discovery is enabled; active probes are opt-in. The goal is "find every reachable device on every connected subnet, even the ones that drop ICMP."

┌ Devices ─────────────────────────────────────────────────────────────────────┐
│ HOSTNAME       IP             SCOPE    TYPE       SOURCE        STATUS        │
│ router.local   192.168.1.1    prv·C    Router     lldp+snmp     Active        │
│ sw-core-01     192.168.1.2    prv·C    Switch     lldp          Active        │
│ ap-floor3      192.168.1.5    prv·C    AP         lldp          Active        │
│ nas01          192.168.1.10   prv·C    NAS        snmp          Active        │
│ printer01      192.168.1.40   prv·C    Printer    arp-sweep     Idle          │
│ ?              192.168.1.77   prv·C    Unknown    arp-sweep     Active        │
└────────────────────────────────────────────────────────────────────────────────┘

Each device is tracked with IP, MAC, hostname, vendor (via embedded OUI table), interface, source, device type, first/last-seen timestamps, and - for managed gear - system name, description, object ID, contact, location, uptime, interface count, LLDP chassis/port IDs and capabilities.

Discovery layers (latest optimization pass):

Layer Mode What it does Cost
ARP cache Passive Reads /proc/net/arp every tick - zero traffic. None
Flow observe Passive Every new flow contributes endpoint visibility. None
LLDP listen Passive AF_PACKET listener on ethertype 0x88cc - neighbours announce themselves; ideal for switches/APs/phones. None
ARP sweep Active Broadcast ARP for every host in every connected /22 - catches ping-shy devices that ICMP misses. One-shot
ICMP sweep Active Echo to each host in the local subnets (capped by --max-subnet-bits). One-shot
SNMPv2c Active Hand-rolled BER GET on UDP/161 - pulls sysName, sysDescr, sysObjectID, sysContact, sysLocation, sysUpTime, ifNumber. Bounded
mDNS query Active One-shot _services._dns-sd._udp.local query for service-advertising hosts. One-shot
TCP/UDP probe Active Targeted port probes - service hints (HTTP, SSH, SMB, …). Targeted
NetBIOS Active NetBIOS name-service query for legacy Windows hosts. Targeted

Why this layout matters:

  • ARP sweep is the single biggest coverage win. ICMP echo is dropped by a long tail of consumer devices (Windows firewall defaults, IoT silence, "ping-shy" servers); ARP, by contrast, has to answer for the host to be reachable on the LAN. The ARP sweep is parallelized across interfaces and capped at /20 (4096 hosts) so a misconfigured /16 doesn't generate 64k frames in one tick.
  • LLDP gives free identification. When a directly-connected switch or AP speaks LLDP, you get its chassis ID, port description, system name, system description and capabilities - without sending a single packet.
  • SNMPv2c reaches managed gear that doesn't speak LLDP. The probe is hand-rolled (~250 lines, no external dependency) and runs with bounded concurrency plus a per-host timeout (default 1 s) so SNMP-dark hosts don't stall the sweep.
  • Subnet expansion is capped. MaxSubnetBits = 10 (default) gives /22 = 1024 hosts. Set to 8 for strict /24 behaviour, or 12 for /20. Anything wider is silently skipped to avoid burying the local NIC.
  • Per-interface goroutine isolation. Each interface gets its own listener goroutine; a stuck or erroring interface can't starve the others.

On-demand nmap scan. Beyond the always-on layers above, an operator can trigger a targeted nmap scan against a single IP or a CIDR block - press N in the Devices tab (TUI) or POST /api/device/nmap-scan (web). The target is validated as an IP/CIDR before it ever reaches the command line, the scan uses nmap's default host discovery (so ICMP-shy hosts are still found via ARP/TCP probes) followed by a fast top-ports TCP sweep (-F), and every host that comes back up is folded into the inventory under the nmap source. If the nmap binary isn't on PATH the feature degrades gracefully with a clear "nmap not found" message rather than an error.

Interface Management

Interfaces can be enabled, disabled, switched to DHCP, assigned static IPs, gateways, and DNS servers - directly from the TUI or web UI.

┌ Interfaces ─────────────────────────────┐
│ eth0                                    │
│ Status:  UP                             │
│ Mode:    DHCP                           │
│ IPv4:    192.168.1.20/24                │
│ Gateway: 192.168.1.1                    │
│ RX: 122 MB/s     TX: 12 MB/s            │
└─────────────────────────────────────────┘

Firewall Management

Current backend: iptables / nftables. firewalld support is on the roadmap.

Supported operations: inspect rules, inspect counters, inspect blocked traffic, hit counts, create rules, remove rules, replay firewall events.

┌ Firewall Rules ─────────────────────────────────────┐
│ Chain: INPUT                                        │
│ DROP tcp -- 0.0.0.0/0 -> 22/tcp                     │
│ Hits: 1221                                          │
│ Blocked Traffic: 82 MB                              │
└─────────────────────────────────────────────────────┘

Routing Management

Inspect routing tables, add static routes, remove routes, and replay route changes from the historical log.

┌ Static Routes ──────────────────────────────────────┐
│ 10.0.0.0/24    via 192.168.1.1   dev eth0          │
│ 172.16.0.0/16  via 10.0.0.1      dev tun0          │
└─────────────────────────────────────────────────────┘

NAT & Port Forwarding

Create and remove port forwards, inspect NAT and masquerading rules, and observe forwarding counters.

┌ NAT Rules ──────────────────────────────────────────┐
│ DNAT tcp 0.0.0.0:443 -> 192.168.1.10:443           │
│ Hits: 8421                                          │
│ Forwarded: 12.4 GB                                  │
└─────────────────────────────────────────────────────┘

Active Probing

A layered probe pipeline runs alongside discovery and capture so latency, loss, and service-health signal keep flowing even when packet capture is off:

  • External ICMP & DNS - the baseline. Configurable targets and names; single-sample RTT and resolution latency feed the live aggregator.
  • Internal DNS - queries LAN resolvers directly (not via the stub) so a slow systemd-resolved upstream is visible separately from external slowness. Upstreams are auto-detected from /etc/resolv.conf and /run/systemd/resolve/resolv.conf; the loopback stub 127.0.0.53 is skipped.
  • Top talkers - tracks the busiest LAN hosts from the flow table and probes them on a 30 s rotation. Each tick fires both an ICMP echo and a TCP connect to the host's busiest observed service port - so "host pings but service is down" surfaces immediately.
  • LAN reachability - pings every entry in the discovery inventory every 60 s. Works without flow capture, giving you per-host RTT for the whole known LAN.
  • HTTP endpoints - GET with httptrace against configured URLs (or auto-derived https://<name>/ for each entry in DNSNames). Reports TTFB plus a status-code-class severity (5xx → ERROR, 4xx → INFO).
  • TLS certificate expiry - dials each host:port on a 6-hour cadence, reads the leaf cert's NotAfter. WARN inside 14 days, CRITICAL inside 3. The single most common preventable outage.
  • Continuous traceroute - per-target trace on a 5-minute cadence. Raises anomalies on hop-set changes (route flap detection) and emits per-hop RTT events.
  • Bufferbloat probe - off by default, gated by --bufferbloat. Each run takes a 3 s idle ping baseline, saturates the link with a throughput download for BufferbloatDuration, and reports the loaded-vs-idle RTT delta. Severity ladder: 30 / 100 / 300 ms → INFO / WARN / ERROR.

Interface & Wireless Health

  • Per-interface stability - polls every non-loopback interface every 5 s, emits anomalies on UP/RUNNING transitions, growing Rx/TxErrors, growing Rx/TxDropped, and collision growth.

  • WiFi monitoring (nl80211 native) - talks nl80211 over netlink directly via github.com/mdlayher/wifi, so the collector works on every modern driver without the iw userspace package and without root on most distros. Surfaces per-radio SSID, BSSID, channel + frequency + width (incl. 6 GHz Wi-Fi 6E), band, TX/RX bitrate (Mbit/s), signal + signal average + noise floor + SNR, TX power, link quality, station-level RX/TX bytes/packets, retries, TX failures, beacon loss, and connected-since. The iw shell-out and /proc/net/wireless remain as automatic fallbacks for unusual drivers, with the source labelled inline ("nl80211" / "iw" / "proc") so the operator knows which backend filled each card. Anomalies fire on low signal, lost association, growing retries, growing TX failures, and growing beacon loss. Unassociated radios still appear in the iface table so they don't silently disappear.

  • L2 monitor - per-interface multicast/broadcast burst detection (catches ARP storms, runaway mDNS) and ARP-table churn (IP→MAC reassignment surfaces IP conflicts or rogue devices).

  • L3 state introspection (netlink) - a slow-cadence collector dumps the kernel neighbour table via RTNETLINK RTM_GETNEIGH for both address families (ARP for IPv4, NDP for IPv6), carrying resolution state (REACHABLE / STALE / FAILED / INCOMPLETE / …) the legacy /proc/net/arp parse hides. Duplicate-IP detection flags any address answered by more than one MAC (conflict / rogue device → KindDuplicateIP, LAN-grade penalty), and the unreachable-neighbour ratio feeds the Stability sub-score. The same collector dumps the nf_conntrack table via NFNL (IPCTNL_MSG_CT_GET): per-flow original/reply tuples (the reply differs once NAT rewrites it), protocol, state, NAT mark, byte/packet counters, and timeout - with a write-gated, audit-logged flush to kill a stuck or translated flow. Both surface in TUI and web (Devices → neighbour table, NAT → conntrack), persist to the neighbours / conntrack_samples tables for replay, and ride the conntrack snapshot into incident bundles.

  • Netlink push, not poll - a dedicated watcher subscribes to the RTNETLINK multicast groups (RTNLGRP_LINK / IPV4_IFADDR / IPV6_IFADDR / IPV4_ROUTE / IPV6_ROUTE) and turns each kernel state change into a bus event (KindLinkStateChange / KindAddrChange / KindRouteChange) the instant it is emitted - link-flap and default-route-change detection drops from "up to one poll interval" to sub-second, and replay timelines gain precise change timestamps instead of poll-quantised ones. A burst of rapid transitions is coalesced (250 ms window) into a single "eth0 flapped 6× in 2s" anomaly rather than six rows, and a slow reconcile timer (60 s) re-reads full state and diffs it as a safety net for any dropped multicast message. The push-derived flap-rate and default-route churn feed the Stability sub-score. The watcher reads only - if the kernel refuses the subscription (restricted namespace / missing capability) it soft-fails to reconcile-only ("polled") and reports itself degraded; the Interfaces / Routes tabs in both the TUI and web carry a live / polled indicator so the freshness of the feed is always visible.

Per-Device LAN Analytics

  • Per-device bandwidth history - rolling 10-min × 5 s buckets per LAN host, derived from the live flow aggregator. Renders as a TX sparkline next to each row in the Devices tab.
  • LAN ↔ LAN matrix - east-west traffic by host pair, sorted by bytes. New section in the Talkers tab; the diagnostic for "the LAN feels slow."
  • Chatter anomaly - per-device TX baseline (median over the last 5 min) with a 3× alert threshold and 5-min cooldown. Catches exfil patterns, runaway containers, looped services. Suppressed for devices below 1 KB/s baseline to avoid noise.

Anomaly Detection & Alerting

A continuous analysis engine watches latency, jitter, packet loss, DNS timing, retransmissions, firewall drops, route instability, bandwidth spikes, NAT exhaustion, per-device chatter, link-state flaps, interface error counters, ARP-table churn, multicast bursts, and TLS cert expiry. Anomalies are routed to a four-level severity ladder.

Level Meaning
INFO Informational
WARN Degraded condition
ERROR Operational issue
CRITICAL Severe degradation
┌ Alerts ─────────────────────────────────────────────┐
│ [WARN] DNS latency exceeded threshold              │
│ [CRIT] Packet loss burst detected                  │
│ [INFO] Route changed on eth0                       │
│ [WARN] Firewall rule dropping excessive traffic    │
└─────────────────────────────────────────────────────┘

CRITICAL anomalies trigger the incident engine, which snapshots context (top flows, route state, firewall state, recent metrics) into a JSON bundle for forensic replay.

Replay Engine

Replay mode reconstructs past sessions from persisted metrics, flows, alerts, and route/firewall snapshots.

testudo replay session-2026-05-23

Timeline navigation, flow replay, DNS replay, firewall replay, route replay, NAT replay, topology replay, and full incident reconstruction. Example timeline:

19:42:11  Session started
19:44:02  WARN     DNS latency spike
19:44:15  WARN     Upload saturation
19:44:18  CRITICAL Packet loss burst
19:44:21  ERROR    Firewall drops increasing

Selective PCAP Capture

Raw packets are not persisted by default. Captures are triggered selectively by anomaly events - packet loss bursts, firewall anomalies, DNS failures, retransmission spikes, route instability, NAT exhaustion - and rotated according to retention policy. There is no always-on full-capture mode by design.

TUI Visualizations

  • Latency timelines
  • Packet-loss heatmaps
  • Throughput graphs
  • Protocol-distribution graphs
  • Alert timelines
  • Firewall hit charts
  • Flow-activity graphs
Latency Timeline

▁▁▂▂▃▄▅▆▇▆▅▄▃▂▁

Web Interface

The web UI mirrors every TUI view: dashboard, flows, devices, interfaces, routes, firewall, NAT, alerts, settings. Authentication uses bcrypt-hashed credentials stored locally; sessions are cookie-based with an 8-hour TTL. Default user: testudo (rotate with testudo user passwd).

Integrations

  • Sentry - optional, DSN-gated panic and error reporting.
  • Apache Guacamole - URL deep-link helper for SSH/RDP/VNC handoff from the discovered device inventory.
  • IPFIX export - optional IETF IPFIX (RFC 7011) flow export to an external collector (e.g. opsanio, ipfixcol2, nProbe, Elastic); configured from the Settings tab.
  • MaxMind GeoIP - optional geolocation/ASN enrichment. When enabled, observed public IPs are annotated with country, city, ASN + organisation, and anonymity signals (proxy / Tor / hosting) from local .mmdb files or a sidecar mmdb-server. The engine reconciles the config against the live enricher on a 2 s cadence (no restart needed), results are LRU- and SQLite-cached, and a license key enables periodic auto-download of the configured editions. Looked up over the web UI at GET /api/ip/{addr}.

TUI & Web Interface

The TUI is the canonical interface. The web UI is the same data, the same engine, exposed over HTTP for operators running Testudo on a headless box. Both surface the same tabs - in the same order as the web topbar previewed in the header above:

Tab Purpose
Dashboard Network Quality grade, bandwidth per interface (live upload/download speed), ICMP/DNS sparklines, top flows, and a grouped Key Indicators KPI strip (Network · Health · Security · Infrastructure) on the web UI
Flows Live multi-interface flow table with process / DNS / service enrichment; capture controls; Process → Iface → Service → Host Sankey diagram (web UI) with a bytes/packets metric toggle and click-to-filter
Devices Discovered devices, vendor, open ports; Scan + Connect (Guacamole / native URI); n toggles the full ARP/NDP neighbour table (f filters by family) with state + duplicate-IP highlighting; N runs an on-demand nmap scan against an entered IP / CIDR and folds the results into the inventory
Interfaces Per-interface state, MTU, hardware address, addresses, RX/TX, controls
Routes Routing table, add/remove static routes
Firewall iptables / nftables rule view with hit counters, Testudo-managed rules, add/remove
NAT NAT and port-forwarding rules with counters, add/remove; live conntrack table (c to focus) showing NAT'd reply tuples, with F to flush a stuck flow (write-gated)
TCPDump Selective PCAP capture with a BPF filter wizard (proto, host, port, raw filter)
Talkers Top hosts, top processes, top services - all ranked by bytes
Probes Interactive runner for ICMP / TCP / UDP / DNS / throughput / traceroute probes
Alerts Live alert log with severity filter and free-text search
History Read-only browse of past sessions persisted to SQLite; anomaly timeline + snapshots
Settings Live-tunable thresholds, netops & integrations, IPFIX flow export (e.g. to opsanio)
Health Live results of every probe collector: top talkers, internal DNS, HTTP, TLS certs, traceroute, bufferbloat, interfaces, WiFi

Modal configuration is supported in both UIs for firewall rules, NAT rules, port forwarding, interface configuration, route configuration, and alert configuration.

┌ Add Firewall Rule ────────────────────────────────┐
│ Chain:      INPUT                                 │
│ Protocol:   TCP                                   │
│ Port:       443                                   │
│ Action:     ACCEPT                                │
│                                                   │
│ [ Save ]        [ Cancel ]                        │
└───────────────────────────────────────────────────┘

Architecture

Runtime Architecture

flowchart TD
    ICMP[ICMP collector] --> Bus
    DNS[DNS collector] --> Bus
    Cap[AF_PACKET capture<br/>multi-interface] --> Bus
    Disc[Discovery scanner] --> Inventory
    Bus[Event Bus] --> MetricAgg[Metrics aggregator]
    Bus --> FlowAgg[Flow aggregator]
    Bus --> Analyzers[Anomaly analyzers]
    Bus --> Incidents[Incident engine]
    Bus --> Store[(SQLite store)]
    Bus --> TUI
    Bus --> Web[Web UI]
    Settings((Settings store)) -.->|live snapshots| Analyzers
    FlowAgg --> Correlators[Process · DNS · Service]
    Inventory --> Store
    Inventory --> TUI
    Inventory --> Web
    NetOps((netlink ops)) <--> TUI
    NetOps <--> Web
    NetOps --> Kernel[(Linux kernel)]
    Probes((User probes)) --> TUI
    Probes --> CLI
Loading

Event-Driven Architecture

flowchart LR
    Collectors --> EventBus
    EventBus --> FlowEngine
    EventBus --> AlertEngine
    EventBus --> FirewallEngine
    EventBus --> RouteEngine
    EventBus --> NATEngine
    EventBus --> ReplayEngine
    EventBus --> StorageEngine
    EventBus --> TUI
    EventBus --> WebUI
Loading

Every subsystem speaks to one another exclusively through the event bus. There are no direct cross-module calls in the data plane. This isolation is what makes replay possible: a session is reconstructed by feeding persisted events back into the same bus.

Storage Pipeline

flowchart LR
    Packets[Raw packets] --> Ring[Layer 1<br/>Live ring buffer]
    Ring --> Flows[Layer 2<br/>Flow aggregation]
    Flows --> Metrics[Layer 3<br/>SQLite metrics]
    Anomaly{CRITICAL<br/>anomaly?}
    Ring --> Anomaly
    Anomaly -->|yes| PCAP[Layer 4<br/>Selective PCAP]
    Anomaly -->|no| Drop[Discard]
    Metrics --> Replay[Replay engine]
    PCAP --> Replay
Loading

Unified UI Architecture

flowchart TD
    TUI --> CoreEngine
    WebUI --> CoreEngine
    CoreEngine --> FlowEngine
    CoreEngine --> FirewallEngine
    CoreEngine --> RouteEngine
    CoreEngine --> ReplayEngine
    CoreEngine --> AlertEngine
    CoreEngine --> StorageEngine
Loading

The TUI and the web UI are siblings. They consume snapshots from the same core engine, which means a feature shipped in one is automatically available in the other.


System Design Philosophy

Testudo is built around ten principles that constrain every implementation decision:

  1. Live-first observability. The default mode is live. Historical analysis is layered on top, not the other way around.
  2. Historical replayability. Anything you can observe live, you can reconstruct after the fact from a session ID.
  3. Flow aggregation over packet storage. Raw packets are ephemeral. Flow summaries are what get persisted.
  4. Selective PCAP capture. Raw packets are kept only when an anomaly justifies it. There is no "always-on full capture" mode by design.
  5. Event-driven processing. All subsystems talk through one event bus. No direct cross-module data-plane calls.
  6. Low-overhead operation. Rolling buffers, bounded queues, compressed time-series, no unbounded memory growth.
  7. Linux-first architecture. Pure-Go netlink, AF_PACKET, /proc parsing. No cgo, no libpcap, no external metrics backend required.
  8. Terminal-first interaction. The TUI is the canonical interface. The web UI mirrors it; it does not extend it.
  9. Reproducible diagnostics. Every probe and every operator action is captured as an event for later inspection.
  10. Modular subsystem isolation. A subsystem can be removed or replaced without touching another subsystem's code.

Storage Layers

The storage pipeline has four layers, each one less volatile than the last:

Layer Medium Lifetime Purpose
1 In-memory ring Seconds to minutes Live rendering, instant anomaly replay
2 Flow aggregator Minutes to hours Active flow tracking, enriched summaries
3 SQLite Days to months Metrics, alerts, route/firewall history
4 Selective PCAP Days (incident-bound) Forensic packet evidence

A typical FlowSummary looks like:

type FlowSummary struct {
    Interface   string
    SrcIP       string
    DstIP       string
    Protocol    string
    BytesIn     uint64
    BytesOut    uint64
    AvgLatency  float64
    PacketLoss  float64
    DNSName     string
    ProcessName string
}

Prerequisites

Before building or running Testudo, ensure you have:

  • Go: 1.25 or newer
  • OS: Linux (Ubuntu / Debian recommended; Fedora, Arch, openSUSE on the roadmap)
  • Kernel privileges: CAP_NET_RAW and CAP_NET_ADMIN on the resulting binary (or run as root - not recommended)
  • Disk: a few hundred MB for SQLite session history and incident-triggered PCAPs
  • Optional: a Sentry DSN for crash reporting, an Apache Guacamole instance for SSH/RDP/VNC handoff

Testudo is pure Go. There is no cgo dependency, no libpcap, no libsqlite3. A clean machine needs only the Go toolchain to build.


Quick Start

  1. Clone:

    git clone https://github.com/noahzmr/testudo.git
    cd testudo
  2. Build, mark executable, and install system-wide - one line that puts testudo on your $PATH:

    go build -o testudo ./cmd/testudo && chmod +x testudo && sudo mv testudo /usr/local/bin/
  3. Grant capabilities (one-time, after install):

    sudo setcap cap_net_raw,cap_net_admin=+ep /usr/local/bin/testudo

    The capabilities are held by a thin privileged helper, not the whole process - see Privilege Separation below.

  4. Run from anywhere in your terminal:

    testudo                              # live TUI, default targets, no capture
    testudo live --capture               # add multi-interface capture
    testudo web                          # start the HTTP UI on 127.0.0.1:8080
    testudo discover --active --lldp     # one-shot layered network scan
    testudo replay session-2026-05-23    # open a past session in replay mode
  5. Log in to the web UI (if started). On the first testudo web invocation the server provisions a default user testudo with a freshly-generated random password printed once to stderr. Rotate it any time with:

    testudo user passwd

Privilege Separation

Testudo no longer runs the web server, TUI, collectors, and analyzers in one PID holding CAP_NET_RAW+CAP_NET_ADMIN. Instead:

  • A thin privileged helper (testudo __helper, a re-exec of the same binary) holds the capabilities and performs only the narrow set of operations that need them - netlink/nftables mutations (route/iface/addr/MTU/NAT/filter/conntrack) and raw-socket opening. It talks to the engine over an anonymous SOCK_SEQPACKET socket, file descriptors travel back via SCM_RIGHTS, and the helper authenticates the engine with SO_PEERCRED.
  • The engine - including the web server and TUI - runs unprivileged. After spawning the helper it sets PR_SET_NO_NEW_PRIVS, clears its capability bounding set, and drops all capabilities (capset). A web-plane compromise no longer inherits raw-socket / netlink-write power.
  • The helper applies a seccomp-bpf denylist (blocking execve, ptrace, module loading, mount/namespace manipulation, raw memory injection) so its attack surface stays small even while holding caps.
  • The netops.Writer gained a backend seam: every call site (ListIfaces, AddRoute, AddPortForward, …) keeps its exact signature; only the backend swaps between in-process (directBackend) and helper-RPC (helperBackend).

Every privileged mutation is audit-logged (audit_log: ts, op, args, peer_uid, result) and viewable read-only in both UIs. Each collector is supervised: a panic recovers into a bounded-backoff restart and a degraded health state rather than crashing the engine; after the restart budget is spent the subsystem is marked permanently failed (no crash-loop). A missing capability soft-fails to an unprivileged state carrying the exact setcap hint. The Health tab (TUI) and the web dashboard surface this as a first-class subsystem-status table, and the Network Quality card shows a "⚠ reduced coverage" badge when a core signal collector (ICMP/DNS/capture) is degraded - so an A grade isn't mistaken for "all good" while collectors are down.

Privilege separation is on by default; pass --privsep=false to testudo live for the legacy single-process behaviour.


Build Instructions

Build

go build -o testudo ./cmd/testudo

Run from source

go run ./cmd/testudo

Install system-wide

Drop the binary into a directory that's already on your $PATH so you can call testudo from any shell:

go build -o testudo ./cmd/testudo \
  && chmod +x testudo \
  && sudo mv testudo /usr/local/bin/

Verify:

which testudo            # /usr/local/bin/testudo
testudo --version

Grant Capabilities (one-time, after install)

sudo setcap cap_net_raw,cap_net_admin=+ep /usr/local/bin/testudo
Capability Used for
CAP_NET_RAW Raw ICMP, AF_PACKET capture, ARP sweep, LLDP listener, traceroute, ICMP discovery
CAP_NET_ADMIN Netlink writes, nftables, promisc-mode interface configuration

Verify:

getcap /usr/local/bin/testudo
# /usr/local/bin/testudo cap_net_admin,cap_net_raw=ep

Uninstall

sudo rm /usr/local/bin/testudo

Cross-compile

Testudo is pure Go and cross-compiles cleanly to any GOOS=linux target:

GOOS=linux GOARCH=amd64 go build -o testudo-linux-amd64 ./cmd/testudo
GOOS=linux GOARCH=arm64 go build -o testudo-linux-arm64 ./cmd/testudo

Tests

go test ./...

CLI Reference & Options

testudo <subcommand> [flags]
Subcommand Description
live Launch the live TUI (default if no subcommand is given)
web Start the HTTP UI
sessions List recorded replay sessions
replay <id> Open a replay session
ifaces List interfaces and their state
routes Show the routing table
nat list List NAT and port-forwarding rules
discover One-shot network scan
probe <host> Diagnostic probe against a host
doctor Layered connectivity diagnosis - reports the first failing layer (link → address → route → gateway → DNS → WAN → captive portal) as the root cause; --json for scripts, exit code 2 when broken
user passwd Rotate the local web-UI password

Common flags

Flag Default Description
--capture off Enable multi-interface AF_PACKET capture
--iface=<csv> auto-discover Capture only on the named interfaces (e.g. wlp1s0,wg0)
--exclude-iface=<csv> none Skip the named interfaces during auto-discovery
--allow-netops-write off Permit route/interface/NAT writes from the TUI
--bufferbloat off Enable the bufferbloat probe (saturates link periodically to measure loaded-RTT delta)
--bufferbloat-target first ICMP target Ping target during the bufferbloat probe
--bufferbloat-interval 1h Gap between bufferbloat runs
--listen=<addr> 127.0.0.1:8080 Web UI listen address (web subcommand)
--active off Active discovery - ARP broadcast sweep + ICMP sweep + mDNS query + SNMPv2c (discover)
--lldp on Passive LLDP listener for directly-connected neighbours (discover)
--snmp-community=<str> public SNMPv2c read community; empty string disables SNMP (discover)
--snmp-timeout=<dur> 1s Per-host SNMP UDP/161 deadline (discover)
--max-subnet-bits=<int> 10 Cap subnet expansion for active sweeps; 10 = /22 = 1024 hosts (discover)
--wait=<dur> 6s Discovery dwell time / LLDP listen window (discover)
--config=<path> ~/.testudo/settings.json Override the persistent settings path
--log-level=<level> info debug / info / warn / error
--no-color off Disable ANSI color in the TUI
--version - Print version and exit
--help - Print help and exit

Examples

testudo                                       # live TUI, default targets, no capture
testudo live --capture                        # add multi-interface capture (auto-discover)
testudo live --iface=wlp1s0,wg0               # capture on specific interfaces
testudo live --allow-netops-write             # permit route/interface/NAT writes from TUI
testudo web --listen=0.0.0.0:8443             # bind the web UI to all interfaces
testudo discover --active --lldp --wait 8s    # full layered scan: ARP sweep + ICMP + mDNS + SNMP + LLDP
testudo discover --snmp-community=monitoring  # SNMPv2c probe with a non-default community
testudo discover --max-subnet-bits=8          # restrict active sweeps to /24
testudo replay session-2026-05-23             # open a past session in replay mode

Configuration System

Testudo ships with intelligent defaults. Every threshold is live-tunable from the Settings tab in the TUI, from the Settings panel in the web UI, and from the persisted config file at ~/.testudo/settings.json.

Anomaly Thresholds

Setting Default Description
Packet loss 2 % Rolling 20-sample loss percentage
DNS latency 120 ms Per-query DNS warning threshold
Jitter 20 ms Mean RTT delta over the last 20 samples
RTT 150 ms Single-sample ICMP round-trip warning
Retransmissions 5 % TCP retransmission warning threshold
Incident cooldown 60 s Minimum seconds between incident bundle triggers

Retention & Capture

Setting Default Description
Replay retention 30 days Session metrics kept in SQLite
PCAP retention 7 days Incident-triggered PCAP rotation horizon
Smart PCAP capture enabled Trigger captures from CRITICAL anomalies
Capture interfaces auto-discover Comma-separated interface override
Interface exclusions none Interfaces to skip during auto-discovery

Discovery

Setting Default Description
DiscoveryEnabled true Master toggle for the discovery scanner (passive listeners + scheduler)
DiscoveryActive false Enable active probes: ARP broadcast sweep, ICMP sweep, mDNS query, SNMPv2c GET
DiscoveryInterval 60 s Cadence of one discovery round
DiscoveryMaxSubnetBits 10 Prefix-expansion cap; 10 = /22 (1024 hosts), 8 = /24, 12 = /20, wider is silently skipped
LLDPEnabled true Passive LLDP listener (needs CAP_NET_RAW; soft-fails per interface when missing)
SNMPCommunity public SNMPv2c read community; empty string disables SNMP probing entirely
SNMPTimeout 1 s Per-host SNMP UDP/161 deadline

Active Probes & Health

Setting Default Description
TopTalkersEnabled true Periodically probe top-N LAN talkers (ICMP + TCP to the busiest observed service port)
TopTalkersInterval 30 s Top-talkers probe cadence
TopTalkersMaxHosts 5 Hard cap on simultaneously probed hosts
LANReachEnabled true Continuous ICMP to every device in the discovery inventory
LANReachInterval 60 s Inventory ping cadence
DNSInternalEnabled true Probe LAN resolvers directly (not via the stub)
DNSInternalServers auto Explicit resolver list; empty = parse /etc/resolv.conf + /run/systemd/resolve/resolv.conf
HTTPEndpoints auto URLs to GET; empty = derived https://<name>/ per DNSNames
HTTPInterval 30 s HTTP probe cadence
HTTPTimeout 5 s Per-endpoint deadline
TLSCertTargets auto host:port list; empty = <name>:443 per DNSNames
TLSCertInterval 6 h Cert-expiry check cadence
TLSCertWarnDays 14 Days remaining that fire WARN
TLSCertCritDays 3 Days remaining that fire CRITICAL
TracerouteEnabled true Continuous traceroute per target
TracerouteInterval 5 min Trace cadence
TracerouteHops 16 Max hops per trace
BufferbloatEnabled false Saturate link + measure loaded-RTT delta (heavy; toggle via --bufferbloat)
BufferbloatInterval 1 h Gap between runs
BufferbloatDuration 10 s Length of the loaded phase
IfaceHealthEnabled true Per-interface error / drop / link-state monitor
IfaceHealthInterval 5 s Poll cadence
WiFiEnabled true Per-radio WiFi snapshot via nl80211 (SSID/BSSID/channel/bitrate/station counters)
WiFiInterval 10 s Poll cadence
WiFiMinSignal -75 dBm "Weak signal" anomaly threshold
L2Enabled true Multicast/broadcast burst detection + ARP-table churn
L2Interval 10 s Sample cadence
L2MulticastThreshold 1000 pkts Per-tick burst threshold
DeviceChatterEnabled true Per-LAN-device TX-rate anomaly
DeviceChatterFactor 3.0 Multiplier above the 5-min baseline that fires WARN

Integrations

Setting Default Description
Sentry DSN unset Enable Sentry panic/error reporting
Guacamole base unset Base URL for the Guacamole instance
IPFIXEnabled false Export flow records over IETF IPFIX (RFC 7011)
IPFIXEndpoint unset Collector address (host:port) for IPFIX export
IPFIXIntervalSec 30 Seconds between IPFIX data exports
IPFIXDomainID auto Observation Domain ID (derived from hostname when left at 0)
MaxMindEnabled false Master switch for MaxMind GeoIP enrichment of public IPs
MaxMindDBDir unset Directory holding the .mmdb files (or a sidecar mmdb-server)
MaxMindAccountID unset MaxMind account ID (required for some commercial editions)
MaxMindLicenseKey unset License key for auto-download; empty disables updating
MaxMindEditions unset Comma-separated edition IDs to auto-download
MaxMindAutoUpdate false Periodically re-download the configured editions
MaxMindRefreshHours 0 Refresh cadence in hours; 0 => 7-day default

Settings View

┌ Settings ───────────────────────────────────────────┐
│ Packet Loss Threshold:     2 %                     │
│ DNS Warning Threshold:     120 ms                  │
│ Replay Retention:          30 days                 │
│ Smart PCAP Capture:        Enabled                 │
└─────────────────────────────────────────────────────┘

Network Quality Grade

The dashboard's most prominent element is a single letter grade (A+ through F) with a 0-100 score next to it. It's meant to be the one thing you glance at to answer "is the network OK right now?" before you dive into any specific tab.

┌ Network Quality ──────────────────────────────────────┐
│                                                       │
│   ┌─────┐    92 / 100                                 │
│   │  A  │    Very good                                │
│   └─────┘                                             │
│                                                       │
│   LOSS    ████████████████  0.1%                      │
│   RTT     ███████████████░  18ms                      │
│   JITTER  ████████████████  4ms                       │
│   DNS     ███████████████░  22ms                      │
│   LAN     ████████████████  1ms                       │
│   HTTP    ██████████████░░  120ms                     │
│   STAB    ████████████████  0.0%                      │
│   WIFI    █████████████░░░  -65dBm                    │
│                                                       │
└───────────────────────────────────────────────────────┘

What goes into the grade

Eleven live measurements, each pulled from the metrics aggregator (plus kernel, firewall, neighbour, conntrack, and per-flow TCP counters). Every measurement is scaled into its own 0-100 sub-score and the sub-scores are combined with weights:

Sub-score Weight What it measures
Packet loss 20 % Average loss percentage across WAN-side targets (external ICMP + WAN top-talker probes)
RTT 15 % Average round-trip latency across WAN-side targets
Jitter 5 % Rolling RTT variation across WAN-side targets
DNS latency 10 % Average resolution time across external + internal resolvers
LAN 15 % Reachability to LAN-side hosts (blends LAN loss + LAN RTT); a duplicate-IP conflict in the neighbour table applies a hard penalty for as long as it persists
HTTP 5 % Configured / auto-derived HTTP endpoints - blends failure rate and TTFB
Stab 10 % Per-interface error / drop ratio blended with the unreachable-neighbour ratio (FAILED + INCOMPLETE ARP/NDP entries - a failed gateway neighbour is imminent connectivity loss), plus push-derived link flap-rate and default-route churn from the RTNETLINK watcher (each with a 2/min comfort line - a 300 ms flap a 5 s poll would miss now drags the grade, and an uplink that keeps re-electing its default route registers as instability)
WiFi 10 % Average signal level (dBm) across associated wireless interfaces; -60 dBm = 100, -90 dBm = 0 (linear)
Firewall 5 % DROP/REJECT velocity (drops/sec) across Testudo-managed blocking rules, diffed between snapshots; 0 = 100, 10 drops/sec = 50
NAT 5 % Conntrack table utilisation (live entries ÷ nf_conntrack_max); 70 % = 50, near-saturation drags the grade and fires the NAT-exhaustion anomaly with real numbers
Congestion 5 % Flow-weighted per-flow retransmission rate from tcp_info (sourced pure-Go via INET_DIAG / ss -ti, or eBPF when built with -tags ebpf) - busy flows dominate the weight, so this replaces the blunt system-wide /proc/net/snmp retransmission number. Anchored to the retransmissions threshold; neutral 100 when no active TCP flows carry telemetry

On top of the weighted sub-scores, a detected PMTU black-hole / frag-needed condition (a flow retransmitting without forward progress - the classic "some sites won't load" fault) applies a fixed -15 penalty so the letter reflects it even when the averages look fine. The worst active-flow RTT can also sharpen the RTT sub-score downward on a busy host - the path the user actually cares about, not just the probe target.

Loss / RTT / Jitter still anchor the grade because those are what users feel, but the grade is no longer blind to the rest of the stack: a slow LAN host, a 5xx HTTP endpoint, a flapping NIC, a wireless radio at the edge of coverage, a firewall rule suddenly eating traffic, a duplicate IP fighting over an address, a single connection drowning in retransmits, a PMTU black-hole silently dropping large packets, or a conntrack table about to overflow all surface immediately. Each sub-score returns a neutral 100 when its data source is empty, so a box without WiFi, without HTTP endpoints, without managed DROP rules, without a loaded conntrack table, or without active TCP flows doesn't pay a penalty for it.

Target classification

Because the metrics aggregator stores every probe result under the same (target, RTT) shape, the grade has to route each target into the right sub-score so HTTP TTFB doesn't pollute the ICMP RTT number and an offline LAN host doesn't drag WAN loss:

Pattern Routed to
http://… / https://… HTTP
RFC1918 / link-local IP, or *.lan/.local/.home/.internal LAN
trace:…, wifi:…, bufferbloat:… (synthetic probe targets) excluded - own scoring
Everything else Loss / RTT / Jitter (WAN)

How a measurement becomes a sub-score

Each sub-score is anchored to the matching threshold from the Settings tab (see Anomaly Thresholds). The mapping is linear in two segments:

Measured value Sub-score
0 (perfect) 100
threshold (comfort) 50
2 × threshold or worse 0

So with the default threshold for packet loss of 2 %:

Real-world loss Loss sub-score
0 % 100
0.5 % ~87
1.0 % 75
2.0 % 50 (comfort)
3.0 % 25 (painful)
4.0 % or more 0

The same shape applies to RTT (threshold 150 ms), Jitter (20 ms) and DNS (120 ms). Tune the thresholds in Settings if your environment is faster or slower than the defaults - the grade re-scales automatically.

Empty inputs map to a neutral 100. "Nothing measured yet" should not paint the dashboard red on first start.

From total score to letter

The four sub-scores are weighted, summed, rounded, and run through this ladder:

Score range Letter Verdict Colour Roughly means…
95 - 100 A+ Excellent green Effectively at the noise floor. Nothing to investigate.
90 - 94 A Very good green Healthy. Small natural jitter, no loss.
85 - 89 A- Good green Normal day-to-day - typical wired LAN or solid Wi-Fi.
80 - 84 B+ OK yellow One sub-score brushing its comfort line; everything still usable.
70 - 79 B Acceptable yellow Background noise that humans notice (slight lag, occasional stutter).
60 - 69 C Degraded orange One or more sub-scores past the threshold; calls and streams will hiccup.
50 - 59 D Poor red Multiple sub-scores in the painful zone; expect complaints.
0 - 49 F Failing red Network is meaningfully broken; open the Alerts tab and start digging.

Baseline-relative modifier - is tonight worse than normal?

Absolute thresholds answer "is this bad?"; they can't answer "is this unusual?". Testudo learns a per-target, per-hour-of-day baseline (a rolling quality_rollup table keyed (target, day-of-week, hour) holding p50/p95/p99/loss/jitter, merged with an exponential moving average so recent weeks dominate and old data decays). The dashboard compares the live RTT against the baseline for this hour and shows a badge per target:

RTT to 1.1.1.1   18ms  (≈ normal)
RTT to 1.1.1.1   55ms  (3.1× normal ▲)

When the live metric is far worse than the learned normal even if the absolute threshold isn't breached yet, the grade is nudged down (up to −25 points) as an early warning - the letter drops before users start complaining. Within the normal envelope the modifier is neutral. An empty baseline (first run, or a freshly-added target) applies no penalty, consistent with the "nothing measured → nothing wrong" contract. When the network legitimately changes (new ISP, moved desk), use Settings → Reset learned baselines (write-gated) to clear the stale rows.

Tail latency: p50 / p95 / p99

The RTT and jitter sub-scores are fed by the live percentile set, now p50 / p95 / p99 (previously only avg/p95). p99 surfaces the tail - the occasional 400 ms spike a healthy average hides - so the grade reflects what a latency-sensitive call or game actually experiences.

Bufferbloat letter (A–F)

The bufferbloat probe's idle-vs-loaded RTT delta is mapped to its own A–F letter shown next to the grade:

Loaded-vs-idle delta Letter Meaning
< 30 ms A Imperceptible.
30 – 100 ms B Mild - noticeable under load.
100 – 200 ms C Significant.
200 – 300 ms D Heavy - calls/gaming stutter under load.
≥ 300 ms F Severe - VoIP/gaming unusable.

ISP-degradation isolation - where's the problem?

When latency climbs, Testudo decomposes the path (reusing the traceroute hops plus the gateway/WAN reference RTTs) into first-hop / gateway / WAN / target segments and names the one contributing the dominant incremental delay:

Degradation isolated to: WAN (gateway healthy)

A healthy path reports "path healthy (no segment dominates)". This turns "the internet is slow" into "the ISP path is slow but your LAN and router are fine" - the difference between opening a ticket with your ISP and rebooting your access point.

When to expect each grade

Some realistic ballparks - useful for calibrating your own expectations:

Environment Typical grade Why
Wired LAN to a local gateway A+ / A Sub-millisecond RTT, no loss, DNS resolved by the local resolver.
Good home Wi-Fi to the internet A / A- 10-30 ms RTT, sub-percent loss, DNS in the 20-50 ms range.
Office VPN over a healthy ISP A- / B+ 30-80 ms RTT, occasional micro-jitter, DNS sometimes >80 ms.
Saturated uplink (someone's uploading) B / C RTT and jitter both climb; loss stays low until the queue overflows.
Wi-Fi at the edge of coverage C / D Retransmissions push effective loss up; jitter doubles or triples.
Misconfigured DNS, healthy link B+ / B Loss/RTT/jitter look great but DNS latency drags the total down.
Real outage in progress F Loss spikes, RTT explodes or times out, DNS fails to resolve.

A persistent grade below B usually means something deserves an open ticket - either reality has changed (new neighbour on the spectrum, ISP route flap, NIC negotiating down) or the thresholds are set for a quieter network than the one you actually have.

Practical reading tips

  • Watch the trend, not the single number. A dashboard sitting at A- all day is healthier than one bouncing between A+ and C.
  • Look at the sub-scores when the letter drops. Each sub-score line shows the raw value and an ok / over flag - the one tagged over is the metric to investigate first.
  • The grade is per-host. It reflects how this Linux box is experiencing the network. Two Testudo instances on the same LAN can legitimately show different grades.
  • Replay sessions show the grade for that moment in time. Scrolling back through a session is the cleanest way to find when the grade first dropped.

Directory Structure

testudo/
├── cmd/
│   └── testudo/                ← CLI entry point + subcommand registry
├── internal/
│   ├── tui/                    ← Bubble Tea TUI
│   ├── web/                    ← HTTP UI + embedded assets
│   ├── auth/                   ← bcrypt web-UI credentials
│   ├── engine/                 ← lifecycle orchestrator + collector supervision
│   ├── events/                 ← event bus + severity ladder
│   ├── collectors/             ← ICMP, DNS, HTTP, TLS, traceroute, WiFi, bufferbloat, …
│   ├── capture/                ← multi-interface AF_PACKET + ring buffer + PCAP
│   ├── telemetry/              ← per-flow TCP telemetry (INET_DIAG, optional eBPF)
│   ├── flows/                  ← flow aggregator + correlators + device matrix
│   ├── analyzers/              ← anomaly detectors
│   ├── incidents/             ← alert log + incident bundles
│   ├── netops/                 ← netlink/nftables: firewall, route, NAT, iface, conntrack, neigh, dns
│   ├── privsep/                ← privileged helper, cap-drop, seccomp, FD passing
│   ├── discovery/              ← ARP / ICMP / mDNS / LLDP / SNMP scanner
│   ├── topology/               ← passive topology graph
│   ├── probes/                 ← one-shot diagnostic probe runner
│   ├── doctor/                 ← layered connectivity diagnosis
│   ├── health/                 ← subsystem-status tracking
│   ├── quality/                ← baseline rollup + Network Quality grade
│   ├── metrics/                ← rolling per-target stats
│   ├── ipfix/                  ← IETF IPFIX (RFC 7011) flow exporter
│   ├── services/               ← well-known port → service mapping
│   ├── replay/                 ← session reconstruction
│   ├── storage/                ← SQLite persistence + audit log
│   ├── integrations/
│   │   ├── sentry/             ← optional panic reporting
│   │   ├── guacamole/          ← SSH/RDP/VNC deep-link helper
│   │   └── maxmind/            ← optional GeoIP / ASN enrichment
│   └── config/                 ← defaults + persistent settings
├── storage/
│   ├── captures/               ← incident-triggered PCAP (gitignored)
│   ├── metrics/                ← downsampled metrics export
│   └── sessions/               ← per-session artifacts
├── docs/
│   └── images/                 ← screenshots referenced in README
├── README.md
├── DEVELOPER.md
├── LICENSE
├── NOTICE
├── COPYRIGHT_HEADER.txt
├── go.mod
└── go.sum

Development Guide

Module Overview

Package Responsibility
internal/tui Bubble Tea application - tabs, modals, browser, replay UI
internal/web HTTP UI, embedded assets, sessions, snapshot endpoint
internal/auth Local web-UI users, bcrypt password hashing and rotation
internal/engine Lifecycle orchestrator - wires all subsystems together, supervises/restarts collectors
internal/events Non-blocking fan-out event bus, four-level severity
internal/collectors ICMP, DNS (external + internal), HTTP-endpoint, TLS-cert, top-talkers (ICMP + TCP), LAN-reachability, traceroute, bufferbloat, WiFi, iface-health, L2 (multicast burst + ARP churn), tcp_info probes
internal/capture Multi-interface AF_PACKET capture, ring buffer, rotated PCAP writer
internal/telemetry Per-flow TCP telemetry (RTT / RTX / cwnd) via INET_DIAG, with an optional eBPF backend (-tags ebpf)
internal/flows Interface-tagged five-tuple aggregator, correlators, per-device bandwidth history, LAN host-to-host matrix
internal/netops Netlink + nftables/iptables backend: firewall, route, NAT/port-forward, interface, conntrack, neighbour, and DNS operations behind a write-gated Writer
internal/privsep Privileged helper process, capability dropping, seccomp-bpf denylist, SCM_RIGHTS FD passing
internal/discovery ARP, ICMP, mDNS, LLDP, SNMP scanner with device inventory + vendor OUI lookup
internal/topology Passive topology graph (nodes / edges) built from flow + discovery data
internal/probes One-shot diagnostic probe runner (ICMP / TCP / UDP / DNS / throughput / traceroute)
internal/doctor Layered bottom-up connectivity diagnosis with root-cause verdict
internal/health Per-subsystem status (OK / degraded / failed) with remediation hints
internal/quality Per-(target, day, hour) baseline rollup, baseline-relative scoring, bufferbloat grading
internal/analyzers Anomaly detectors - packet loss, latency spike, jitter, DNS burst, firewall drops, route instability, bandwidth spike, NAT exhaustion, retransmissions, per-device chatter
internal/incidents Severity escalation, alert log, incident bundles
internal/metrics Rolling per-target / per-DNS counters and bandwidth windows
internal/ipfix IETF IPFIX (RFC 7011) flow exporter + lifecycle manager
internal/services Well-known port → service-name mapping
internal/replay Session reconstruction from persisted events
internal/storage SQLite persistence (sessions, samples, flows, anomalies, incidents, baselines, audit log)
internal/integrations/sentry Optional panic/error reporting
internal/integrations/guacamole URL deep-link helper for SSH/RDP/VNC handoff
internal/integrations/maxmind Optional MaxMind GeoIP / ASN enrichment - local .mmdb or sidecar mmdb-server, auto-update, LRU + SQLite cache
internal/config Defaults, thresholds, persistent settings store
cmd/testudo Command-line entry point and subcommand registry

Coding Standards

  • Avoid global state. All subsystem state is owned by a struct and passed explicitly.
  • context.Context everywhere. Every cancellable operation accepts a context as its first argument.
  • Channels over locks. Coordination uses channels; locks are reserved for short, contended critical sections.
  • Never block the render loop. The TUI render goroutine must remain responsive; analysis runs on workers.
  • Isolated analyzers. Each anomaly detector is a self-contained unit that consumes events and emits anomalies.
  • Modular subsystems. A subsystem should be removable without touching another subsystem's code.
  • Structured logging. Every log line is structured key/value, never freeform text.

Performance Rules

  • Never render raw packets directly to the UI.
  • Minimize allocations on hot paths (capture, aggregation, event dispatch).
  • Use rolling buffers with fixed capacity for live data.
  • Compress historical metrics before persisting.
  • Avoid unbounded memory growth - every buffer has a documented ceiling.
  • Process asynchronously where possible; the event bus is the synchronization point.

Event-Driven Flow

Every observable signal in Testudo travels the same path:

Collector ──► EventBus ──► Subscribers ──► (UI, Storage, Analyzers, Replay)

A subscriber never calls back into a collector. If a subscriber needs to act on the network, it emits an OpsRequest event and a netops subsystem handles it. This keeps the data plane and the control plane cleanly separated.

For the full developer walkthrough - adding subsystems, release process, test conventions - see DEVELOPER.md.


Further Documentation

This README is the top-level tour. The rest of the project is documented in a small set of focused Markdown files - each one is meant to be read on its own, no chasing forward-references required.

Top-level

Document Audience What's inside
README.md everyone This file - overview, install, features, configuration, glossary, roadmap.
DEVELOPER.md contributors Build & run, repository layout, architecture in 60 seconds, adding a subsystem, testing, releasing.
LICENSE everyone MPL-2.0 license text in full.
NOTICE everyone Branding restrictions, required attribution when redistributing.
COPYRIGHT_HEADER.txt contributors The standard copyright header that every new source file must carry.

docs/ - deep-dives

The docs/ directory hosts the longer technical writeups. Start at docs/README.md for the index, or jump straight to a topic:

Document Audience Summary
docs/README.md everyone Index of the docs folder with one-line summaries.
docs/architecture.md engineers Subsystem map (Mermaid), data flow, module boundaries, lifecycle.
docs/storage.md engineers, operators The four storage layers (live ring => flow aggregator => SQLite => selective PCAP) and their lifetimes.
docs/replay.md operators Session capture, the replay engine, timeline navigation, what's persisted and what isn't.
docs/firewall.md operators nftables (default) and iptables (fallback) backends; chain semantics; common rule recipes.
docs/topology.md operators Passive topology graph - nodes, edges, sources (ARP / LLDP / SNMP / flow observation).
docs/alerts.md operators Severity levels, default thresholds, the anomaly engine, incident bundles.

Pointing readers to the right place

If you want to… Read…
Get up and running fast Quick Start in this README
Understand what each tab does TUI & Web Interface in this README
Read the grade on the dashboard Network Quality Grade in this README
Learn networking vocabulary Glossary in this README
Understand how subsystems wire together docs/architecture.md
Know what Testudo keeps on disk docs/storage.md
Write a firewall rule from the TUI docs/firewall.md
Investigate an incident after the fact docs/replay.md + docs/alerts.md
Contribute code DEVELOPER.md + CONTRIBUTING section
Fork or rebrand LICENSE + NOTICE + the Branding section

Screens

TUI

The canonical terminal interface - every tab from the table above, rendered live.

Dashboard - Network Quality grade, per-interface bandwidth, ICMP latency sparklines Flows - live multi-interface flow table with process / DNS / service enrichment
TUI Dashboard TUI Flows
Devices - discovered hosts with vendor, scope, and the ARP/NDP neighbour table Interfaces - per-interface state, MTU, hardware/IP addresses, RX/TX
TUI Devices TUI Interfaces
Routes - full routing table with add/remove of static routes Firewall - managed rules with per-rule hit counters
TUI Routes TUI Firewall
NAT - NAT/port-forward rules plus the live conntrack table TCPDump - selective PCAP capture with a BPF filter wizard
TUI NAT TUI TCPDump
Talkers - top hosts, processes, and services ranked by bytes Probes - interactive ICMP / TCP / UDP / DNS / throughput / traceroute runner
TUI Talkers TUI Probes
Alerts - live alert log with severity filter and free-text search Health - supervised subsystem status and the privileged-mutation audit log
TUI Alerts TUI Health

Detail views & modals

A handful of the TUI's deeper panels and pop-over modals - the dashboard's per-target latency/DNS breakdown, the Health tab's full probe-collector results, the Devices nmap-scan prompt, the TCPDump capture wizard, and the fuller discovered-device inventory.

Dashboard - metrics - ICMP/DNS latency, per-target & per-resolver detail, top flows Health - probe collectors - top talkers, internal DNS, HTTP, TLS certs, traceroute
TUI Dashboard metrics TUI Health probes
Devices - nmap scan - on-demand nmap against an entered IP / CIDR TCPDump - new capture - the BPF filter wizard (iface, proto, host, port, raw filter)
TUI Devices nmap scan TUI TCPDump new capture
Devices - inventory - discovered hosts with vendor, scope, protocols, and neighbour
TUI Devices inventory

Web UI

The same engine and the same data, exposed over HTTP for headless boxes.

Dashboard - Network Quality grade and the grouped Key Indicators KPIs Flows - Process → Iface → Service → Host Sankey with capture controls
Web Dashboard Web Flows
Flows - table - live flow rows with process / DNS / service columns Talkers - top hosts / processes / services ranked by bytes
Web Flows table Web Talkers
Devices - discovered devices, scan, and neighbour table Interfaces - live kernel interface state and addressing
Web Devices Web Interfaces
Routes - routing table with add/remove Firewall - filter rules with per-rule hit counters
Web Routes Web Firewall
NAT - port-forwarding and live conntrack flows TCPDump - selective PCAP capture with the BPF filter wizard
Web NAT Web TCPDump
Alerts - live alert log with severity filter and search Health - supervised subsystem health and the privileged-mutation audit log
Web Alerts Web Health

Glossary - Networking Terms for Beginners

New to networking? No worries - this section is a quick, plain-language cheat sheet for the terms that show up around Testudo and in network troubleshooting generally. You can read it top-to-bottom or just grep it when something looks unfamiliar.

Tip: if you only learn three things from this glossary, make them IP address, port, and packet. Almost everything else is built on those.

The absolute basics

Term What it actually means
Packet A small chunk of data sent across the network. Networks don't send files - they send thousands of packets that get reassembled.
IP address The "street address" of a device on a network. IPv4 looks like 192.168.1.10; IPv6 looks like 2a01:abcd::1.
MAC address The hardware address baked into a network card. Looks like aa:bb:cc:dd:ee:ff. Used on the local cable/Wi-Fi, not across the internet.
Port A number from 0-65535 that says which app on a device should receive a packet. Web is 80/443, SSH is 22, DNS is 53.
Protocol The "language" two devices agree to speak. Common ones: TCP, UDP, ICMP.
Interface A network port on your device. eth0 is wired, wlan0 is Wi-Fi, wg0 is a WireGuard VPN, docker0 is a container bridge, etc.
Host Any device on the network - a server, laptop, phone, printer, fridge, whatever.

Common protocols you'll see in Testudo

Term Plain-language version
TCP Reliable conversation. Both sides confirm what was received. Used for the web, SSH, file transfer.
UDP Fire-and-forget messages. No confirmation. Used for DNS lookups, voice, video, games.
ICMP The "are you alive?" protocol. ping and traceroute both use it.
DNS The phone book. Turns youtube.com into an IP address your machine can actually reach.
ARP "Who has IP 192.168.1.5? Tell me your MAC." Used so devices on the same LAN can find each other.
mDNS Multicast DNS - DNS but for the local network. How printers, Chromecasts and your laptop announce themselves to your home Wi-Fi.
LLDP Link Layer Discovery Protocol. Switches and APs broadcast "hi, I'm switch SW-CORE-01, port 12, I'm a switch with bridge capability." Testudo listens for this for free identification.
SNMP Simple Network Management Protocol. The classic way to ask routers and switches "what's your name? how long have you been up? how many interfaces do you have?"

How traffic is named and counted

Term What it means
Flow / 5-tuple A single conversation between two endpoints, identified by (source IP, source port, dest IP, dest port, protocol). One TCP connection = one flow.
Throughput How many bytes per second are moving. Usually quoted in Mb/s or MB/s.
RTT Round-Trip Time. How long a packet takes to go to the other side and come back. The fundamental "is it slow?" number.
Jitter How steady RTT is. Low RTT but jittery = unstable. Bad for voice and video.
Packet loss What fraction of packets never made it. Anything above ~2 % is noticeable; above ~5 % is painful.
Retransmission TCP noticed a packet got lost and sent it again. High retransmissions = something on the path is dropping traffic.
Saturation The link is at its bandwidth limit. Symptoms: latency spikes, packet loss, queue buildup.

Capturing and looking at packets

Term What it means
PCAP A file format for saved raw packets. The thing tcpdump and Wireshark write. Testudo only saves PCAPs when something interesting happens.
tcpdump The classic command-line packet sniffer. Testudo replaces "always-on tcpdump" with selective capture.
Wireshark The classic GUI packet sniffer.
AF_PACKET The Linux kernel API that lets a program read every packet that crosses an interface. Testudo uses it directly - no libpcap needed.
libpcap The C library most packet tools use to capture packets. Testudo deliberately avoids it (pure Go).
Promiscuous mode An interface setting that says "give me every packet you see, not just ones addressed to me." Needed for full capture on a switch port that's mirroring traffic.

Routing, NAT, and firewalls

Term What it means
Routing table The list of rules that tells your machine "to reach network X, send the packet to gateway Y on interface Z."
Gateway The next-hop router your machine sends traffic to when the destination isn't on the same subnet. Usually 192.168.1.1 at home.
NAT Network Address Translation. The trick that lets dozens of devices behind one home router share a single public IP.
Port forwarding (DNAT) "Anything that arrives at my public IP on port 443, send it to the web server at 192.168.1.10:443." A specific kind of NAT.
Masquerading Source NAT, the variant home routers use to rewrite outbound traffic so it looks like it came from the router.
Firewall The kernel's "rules about which packets are allowed in/out." On Linux today that's iptables (older) or nftables (newer). firewalld is a friendly wrapper.
Chain A list of firewall rules evaluated in order. INPUT is for traffic destined to your box, OUTPUT is for traffic leaving it, FORWARD is for traffic passing through.
ACCEPT / DROP The two main verdicts a firewall rule can deliver. ACCEPT lets it through, DROP silently throws it away.
Conntrack The kernel's connection-tracking table. Remembers "I've seen this TCP flow before" so reply packets get matched to the right NAT/firewall state.

Networks and subnets (the slash numbers)

Term What it means
Subnet A range of IP addresses that share the same local network. 192.168.1.0/24 means "all addresses from 192.168.1.0 to 192.168.1.255."
CIDR / /24 The slash number says how many bits are fixed in the network address. /24 = 256 hosts, /22 = 1024 hosts, /16 = 65 536 hosts. Smaller number = bigger network.
Broadcast A special address that means "everyone on this subnet." ARP requests are broadcast.
Loopback The 127.0.0.1 interface - your machine talking to itself. Always there, never on the wire.

Linux-specific bits you'll see in this README

Term What it means
/proc The Linux "virtual filesystem" that exposes kernel state as files. /proc/net/arp, /proc/net/tcp, /proc/<pid>/fd etc. Testudo reads these directly.
Netlink The kernel API for talking to networking subsystems (routes, interfaces, firewall). Used instead of running ip or iptables in a subshell.
Capabilities (CAP_*) Fine-grained permissions that replace "needs root." CAP_NET_RAW lets a program open raw sockets without being root.
setcap The command that grants a binary a capability. Testudo uses it so you don't have to run the tool as root.
VLAN Virtual LAN. A way to run several logical networks over one physical cable. Identified by a tag number (e.g. VLAN 100).
Bridge A virtual switch inside Linux. Containers and VMs usually plug into one (docker0, br0).
Tunnel / VPN An encrypted "fake cable" between two machines over the public internet. WireGuard, OpenVPN, IPsec are common implementations.

Putting it all together - a worked example

When you open a web page, here is what just happened in glossary terms:

  1. Your browser asks DNS to turn example.com into an IP address.
  2. The kernel checks its routing table to decide which interface and gateway to send the packet through.
  3. On the local network, it uses ARP to find the MAC address of that gateway.
  4. The packet (one of many) is NAT-ed by the home router so the reply can find its way back.
  5. The remote server replies with TCP packets carrying the HTML, which the kernel reassembles.
  6. Conntrack remembers the flow so subsequent reply packets are matched to the same conversation.
  7. Testudo (if running) sees the whole thing as a single flow with an RTT, throughput, jitter and packet-loss number attached.

That's the entire short version of "how the internet works" - and Testudo's job is to let you watch each of those steps as they happen, or after they've already happened.


Roadmap

Roughly ordered by what's next. Each milestone is shaped around one theme so users and contributors can tell at a glance whether a release affects them.

Legend

Symbol Meaning
+ New feature
! Infrastructure change
# UI / Visualization
Headline goal for the release

Engineering specs. The deeper diagnostics work was scoped under the same contract for every item: viewable and editable in both the TUI and the web UI, and every new measurement feeds the Network Quality grade. Most of that roadmap has now shipped - per-rule nftables counters, conntrack/NEIGH introspection, netlink push-vs-poll, the rollup/baseline quality table, per-flow TCP telemetry (with an optional eBPF backend), and privilege separation are all in the tree. IPv6 across the full data path is the main remaining item (tracked under v1.0 below).

v0.2 - Cross-Platform Compatibility (next up)

The single biggest thing holding Testudo back today is being pinned to Ubuntu/Debian. v0.2 is about getting it to just work on the rest of the Linux ecosystem and on macOS.

  • Verified install, build, and live capture on Fedora, openSUSE, Arch Linux, RHEL / Rocky / Alma, Alpine, and NixOS.
  • + macOS port - BPF/PF_ROUTE capture path replacing the Linux AF_PACKET socket; route, interface and firewall (pf) read-only views on the same TUI/Web UI.
  • + firewalld backend alongside iptables / nftables for RHEL-family installs.
  • ! Per-distro packaging: .deb, .rpm, AUR PKGBUILD, Alpine apk, Homebrew tap.
  • ! CI matrix that builds and smoke-tests on every supported distro per PR.
  • # Per-platform capability hints in the welcome banner ("on macOS use sudo ./testudo or grant the com.apple.security.network.client entitlement").

v0.3 - IPFIX Export & opsanio Integration

Testudo already speaks IETF IPFIX (RFC 7011) for flow export. v0.3 hardens that exporter for production collectors and ships first-class integration with opsanio - the autonubil GmbH monitoring appliance line that Testudo is designed to feed.

  • First-class opsanio integration. opsanio is autonubil's turnkey on-prem monitoring appliance. Testudo can already export flow records to it over IPFIX; v0.3 makes it a single Settings toggle, with auto-discovery of an opsanio collector on the local subnet and a pre-built information element template matched to opsanio's dashboards. See https://autonubil.de/home#appliances for the appliance line-up.
  • + IPFIX exporter parity with other common collectors (ipfixcol2, nProbe, Elastic ingest, ntopng) - same template, same field set.
  • + Configurable IPFIX template profiles: minimal (5-tuple + bytes/packets), standard (+ ifindex, ToS, TCP flags), forensic (+ DNS name, process name, NAT state).
  • + IPFIX flow sampling and rate-limiting for high-throughput links.
  • # Live "IPFIX export" panel in the dashboard: target collector, records/sec, last error.

v0.4 - Forensic Depth

  • + Per-flow PCAP slicing during incident bundles.
  • + Topology diff between replay sessions.
  • # Incident overlay on the dashboard sparkline and the quality grade.
  • ! Compressed metrics export format (zstd).

v0.5 - Distributed Operation

  • + Multi-host session aggregation.
  • + Read-only federation for the web UI.
  • ! Optional remote PostgreSQL backend for long-horizon retention.
  • # Cross-host topology view.

v1.0 - Production Hardening

  • + IPv6 parity across discovery and firewall modules.
  • ! Long-running stability soak harness.
  • # Operator-mode keybinding overlay in the TUI.
  • + Stable plugin API for third-party collectors.
  • + Stable plugin API for third-party collectors

Contributing

Contributions are welcome. The Development Guide above and DEVELOPER.md describe how the project is organized and what kinds of changes fit its design.

The short version:

  1. Fork the repository and create a feature branch.
  2. Add or extend a subsystem under internal/ - keep it removable.
  3. Wire it into the event bus, not into another subsystem.
  4. Include tests under the same package (*_test.go).
  5. Run go test ./... and go vet ./... before opening a PR.
  6. Stamp every new source file with the standard header from COPYRIGHT_HEADER.txt.
  7. Open a PR with a focused description; small PRs land faster than sweeping ones.

Contributions are accepted under the same MPL-2.0 license as the rest of the project.


Support

Testudo is built and maintained in the open by one person. If it saved you a late-night debugging session - or six terminals - and you'd like to say thanks, you can buy me a coffee. Every bit is appreciated and goes straight back into keeping the tortoise moving.

Not in a position to donate? That's completely fine - starring the repo, filing a good bug report, or opening a PR helps just as much.


License

Testudo is distributed under the Mozilla Public License Version 2.0 (LICENSE), with additional branding restrictions documented in NOTICE.

Copyright (c) 2026 Noah Zeumer.

Why MPL-2.0

MPL-2.0 was chosen deliberately. It strikes the balance Testudo needs:

  • File-level copyleft. Modifications to MPL-licensed files must remain open under the same license, but linking Testudo against proprietary code is permitted. This keeps the core honest without making the project hostile to commercial adopters.
  • Commercial use is welcome. Companies can deploy, modify, and ship Testudo internally or as part of larger commercial offerings.
  • Modifications stay visible. Anyone who improves an MPL-licensed file must publish the modified source - bug fixes and improvements flow back to the community.
  • Compatible with permissive ecosystems. MPL-2.0 plays well with Apache-2.0 and BSD-licensed dependencies, which makes up most of the Go ecosystem.

What You Can Do

  • Use Testudo in production, including commercial deployments.
  • Modify the source code for your own needs.
  • Fork the repository.
  • Redistribute the source or compiled binaries.
  • Build derivative works.
  • Contribute changes back upstream.

What You Cannot Do

  • Remove the Testudo branding, ASCII banner, or attribution from forks or redistributions.
  • Re-release Testudo under a different product name as if it were a new project.
  • Strip the NOTICE file or its branding clauses.
  • Misrepresent authorship of the original work.
  • Remove copyright headers from individual source files.

The code license and the branding restrictions are deliberately separated: MPL-2.0 governs the code, the NOTICE file governs the brand. Read both before forking.


Branding

The name Testudo, the ASCII logo, and the project identity are property of Noah Zeumer and are not granted under the MPL-2.0 source-code license. The full branding terms - including what attribution is required when redistributing, when a derivative work must be renamed, and how the ASCII banner may and may not be reused - are documented in NOTICE.

In short: fork freely, modify freely, redistribute freely. If you ship a substantively different product, give it a different name.


Acknowledgments

Testudo stands on the shoulders of giants in the Go and Linux ecosystems:

  • Bubble Tea - the Elm-inspired TUI runtime that drives the terminal interface.
  • Lip Gloss - styling and layout for the TUI.
  • gopacket - packet decoding for the AF_PACKET capture pipeline.
  • mdlayher/netlink and vishvananda/netlink - pure-Go netlink for cgo-free kernel interaction.
  • google/nftables - the nftables backend for the firewall subsystem.
  • AF_PACKET, netfilter, conntrack, and /proc - the Linux primitives that make any of this possible.
  • IEEE 802.1AB (LLDP) and SNMPv2c - the open standards that let Testudo identify managed devices without sending a probe.
  • modernc.org/sqlite - pure-Go SQLite, the embedded persistence backend.
  • nmap - the on-demand targeted scanner invoked from the Devices tab.
  • MaxMind GeoIP2 / GeoLite2 - optional geolocation and ASN enrichment of public IPs.
  • Sentry and Apache Guacamole - optional integrations for crash reporting and console handoff.

And to every operator who has stared at six terminals during an incident and thought "there has to be a better way" - this is for you.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages