GitHub - yuedongze/tlstat: Netstat for TLS · GitHub
/" data-turbo-transient="true" />
Skip to content
Search or jump to...
Search code, repositories, users, issues, pull requests...
-->
Search
Clear
Search syntax tips
Provide feedback
--><br>We read every piece of feedback, and take your input very seriously.
Include my email address so I can be contacted
Cancel
Submit feedback
Saved searches
Use saved searches to filter your results more quickly
-->
Name
Query
To see all available qualifiers, see our documentation.
Cancel
Create saved search
Sign in
/;ref_cta:Sign up;ref_loc:header logged out"}"<br>Sign up
Appearance settings
Resetting focus
You signed in with another tab or window. Reload to refresh your session.<br>You signed out in another tab or window. Reload to refresh your session.<br>You switched accounts on another tab or window. Reload to refresh your session.
Dismiss alert
{{ message }}
yuedongze
tlstat
Public
Notifications<br>You must be signed in to change notification settings
Fork
Star
main
BranchesTags
Go to file
CodeOpen more actions menu
Folders and files<br>NameNameLast commit message<br>Last commit date<br>Latest commit
History<br>6 Commits<br>6 Commits
bpf
bpf
images
images
internal
internal
.gitignore
.gitignore
Makefile
Makefile
README.md
README.md
design-doc.md
design-doc.md
go.mod
go.mod
go.sum
go.sum
main.go
main.go
View all files
Repository files navigation
tlstat
A live, htop-style terminal monitor for TLS handshakes and connections on a<br>Linux host, built with eBPF. It discovers TLS sessions (new and pre-existing),<br>parses the handshake for endpoints, server identity, and negotiated crypto,<br>counts bytes in/out live, and — for OpenSSL applications — captures the<br>cleartext before encryption , the one vantage point where plaintext exists.
tlstat — 6 connections, 3 TLS sort:recent<br>PID COMM REMOTE / SNI VER CIPHER WIRE ↑/↓ PLAIN ↑/↓ ST<br>423491 openssl example.com TLS 1.3 TLS_AES_256_GCM_SHA384 1.6K/6.4K 37B/874B TLS<br>...<br>process openssl pid=423491<br>flow 10.0.0.5:51234 → 93.184.x.x:443 outbound (client)<br>server example.com<br>version TLS 1.3<br>cipher TLS_AES_256_GCM_SHA384<br>symmetric AES-256-GCM<br>key exch x25519<br>signature offered: ecdsa_secp256r1_sha256, rsa_pss_rsae_sha256<br>─────────────────────────────────────────────────────────<br>plaintext SSL_write → (cleartext sent)<br>0000 47 45 54 20 2f 20 48 54 54 50 2f 31 2e 31 0d 0a |GET / HTTP/1.1..|
How it works
Two eBPF attachment strategies, fused into one connection table:
Wire observation — kprobes on tcp_sendmsg/tcp_recvmsg count bytes and<br>capture the first chunks of each direction; a tracepoint on<br>inet_sock_set_state tracks connection lifecycle. The captured bytes are<br>reassembled and parsed in userspace to recover the cleartext ClientHello<br>(SNI, offered ciphers, signature algorithms, ALPN) and ServerHello<br>(negotiated version, cipher, key-exchange group). On TLS 1.2 the<br>Certificate message is also cleartext, so the server cert (CN, SANs,<br>signature algorithm) is extracted too.
Library observation — uprobes on SSL_write/SSL_read in libssl.so<br>capture the plaintext an application hands to (or receives from) OpenSSL.<br>This is the only way to see decrypted content; it works for dynamically<br>linked OpenSSL programs and attaches to already-running processes.
Everything is keyed by the kernel struct sock *; plaintext is correlated back<br>to a connection via SSL_set_fd and /proc//fd → /proc/net/tcp.
Build & run
Requires a Linux kernel with BTF (/sys/kernel/btf/vmlinux), ~5.8+.
make deps # clang, llvm, libelf-dev, libbpf-dev (Debian/Ubuntu)<br>make build # compile eBPF (CO-RE) + Go binary<br>sudo ./tlstat # must be root: eBPF needs CAP_BPF/CAP_SYS_ADMIN
Keys: ↑/↓ select · enter peek plaintext · s cycle sort · q quit.
Flags: --libssl PATH (override the OpenSSL library), --interval DUR (poll<br>rate), --dump DUR (headless text mode, e.g. --dump 10s, for scripting/CI).
What it can and can't see
TLS 1.3 hides the server certificate on the wire. SNI (always cleartext)<br>is used as the primary server identity; the full certificate is recovered<br>only on TLS 1.2. TLS 1.3 rows show cert: encrypted.
Cleartext capture is OpenSSL-only in this version. GnuTLS, NSS, BoringSSL,<br>and Go's in-binary crypto/tls are not yet instrumented.
Pre-existing connections (established before tlstat started) missed their<br>handshake, so crypto/SNI show as unknown / pre-existing. Byte counts still<br>work, and plaintext works from the moment the uprobe attaches.
Handshake reassembly is best-effort from the first ~16 KB per direction;<br>fields past a truncation point may be unknown.
Encrypted ClientHello (ECH) would hide SNI — rare today.
Layout
bpf/tlstat.bpf.c eBPF programs (kprobes, tracepoint, uprobes)<br>bpf/tlstat.h structs shared with Go<br>internal/loader/ eBPF load/attach + ring buffer + map snapshots<br>internal/tlsparse/ TLS record/handshake parser + IANA name tables<br>internal/model/ connection table, correlation, plaintext join<br>internal/ui/ bubbletea...