tinytap

module
v0.6.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 1, 2026 License: MIT

README

tinytap

OpenSSF Scorecard

A tiny eBPF-based HTTP traffic capture tool for local development.

The TUI mode shown above (output = "tui" in the config file — see Configuration) shows the live request table (j/k to scroll), the detail panel (Enter to open, b to toggle the hex body view). Regenerate it with vhs scripts/tinytap.tape from the Mac host — see docs/recording-tui-gifs.md for the full hand-off procedure.

What it does today

tinytap attaches eBPF probes to a process's socket syscalls (accept4/read/write/close/recvfrom/sendto/recvmsg/sendmsg), parses the payload bytes as HTTP/1.1, pairs each request with its response, and renders the exchange live — either in the terminal TUI above or as a line-oriented stream:

12:47:57.005  python3[27122]  GET   /                        200    1304B     0.3ms
12:47:57.005  curl[1234]       GET   /api                     ABANDONED     12.3ms  (peer closed)

output = "auto" (the default) picks the TUI when stdout/stdin are an interactive terminal of at least 120x24; otherwise it prints guidance and exits rather than silently streaming — the line stream is opt-in via output = "stdout". output = "tui" forces the TUI (and exits the same way if the terminal can't host it); verbose = true hangs the full request/response headers under each stdout line. --version prints the build's version, commit, and date, and exits without needing root.

Configuration

Session settings (output, verbose, and process filters) live in a TOML config file, not CLI flags. tinytap config init writes one, fully populated with defaults, so tinytap config init && tinytap just works:

tinytap config init          # writes ./tinytap.toml
tinytap config init path/to/config.toml   # or a specific path
tinytap config init --force  # overwrite an existing file

Search order when --config <path> isn't given: ./tinytap.toml, then $XDG_CONFIG_HOME/tinytap/config.toml (falling back to ~/.config/tinytap/config.toml) — finding neither is not an error, the defaults below apply.

output = "auto"   # auto | stdout | tui
verbose = false

[filter]
pid  = []         # []uint32 — schema only, not yet enforced by the BPF program (#211)
comm = []         # []string — schema only, not yet enforced by the BPF program (#211)

The only CLI surface is one-shot actions, not session settings: --config <path> (point at an alternate config file), --version (build metadata, exits before any eBPF load), and config init (above).

Current limitations

  • HTTP/1.1 only — no HTTP/2, gRPC, or other protocols yet
  • TLS capture covers processes that have libssl.so loaded and call SSL_set_fd directly (nginx, Python's ssl module, etc.); clients that hand OpenSSL a custom BIO instead (e.g. curl) have their plaintext captured but not yet paired into an exchange (#179) — see docs/tls-compat.md
  • Single host — no cross-container attribution or cross-service correlation yet
  • Response bodies are sampled up to a fixed per-syscall cap, not captured in full (see docs/server-compat.md for exactly how each server's syscall pattern affects this)
  • sendfile-based transfers only carry payload bytes on amd64/arm64 — other architectures see the exchange but not the sampled body

See docs/server-compat.md for a server-by-server breakdown of what's currently visible.

Quick start

Build and run inside the Lima VM (see Toolchain below for setup):

# Regenerate Go bindings from C (only needed after editing bpf/*.c)
cd ~/tinytap/internal/loader/bpf && go generate

# Build
cd ~/tinytap && go build ./...

# Run (requires root — eBPF needs CAP_BPF/CAP_PERFMON or root)
sudo ./tinytap

Root isn't actually required — see Running without full root for the minimal setcap invocation.

Or via make:

make run       # orchestrated smoke test: starts a demo HTTP server, fires a request, shows the capture
make run-raw   # build + run with output = "stdout" against whatever's already running

Run make install once per checkout (or worktree) to install the pre-push hook that runs lint, tests, and coverage checks before every push.

Where tinytap Runs

There are two distinct environments to keep in mind, and they answer two different questions.

Where tinytap is built and developed

The development environment is Mac + Lima + Ubuntu VM, because eBPF only exists on Linux. See Toolchain for setup. This is private to the maintainer's workflow — it does not constrain users.

Where tinytap is executed

tinytap requires a Linux kernel. It cannot run natively on macOS or Windows, because eBPF is a Linux kernel technology. But that's less restrictive than it sounds, because Linux kernels are everywhere:

Where the user works How tinytap runs there
Linux desktop / laptop / workstation Native. Just run the binary.
Linux server (cloud VM, on-prem, dev box) Native. SSH in, run it.
Mac (Intel or Apple Silicon) Inside a Linux VM — Lima, Multipass, OrbStack, UTM, Docker Desktop's VM, etc.
Windows Inside WSL2 (which is a real Linux kernel).

This pattern — "Mac/Win developers run this through a Linux VM" — is the standard for eBPF tooling in general (bpftrace, Cilium, etc.). tinytap is not unusual here.

Containers are friends, not enemies

A common question: "if my dev stack runs in Docker on my Mac, can tinytap see inside the containers?"

Yes. A Docker container is just a process (or process tree) running on the host's Linux kernel, isolated by namespaces and cgroups. eBPF programs attach to kernel events — syscalls, kprobes, tracepoints — which fire for all processes, container or not. So:

Mac
└── Lima VM (Ubuntu)        ← tinytap runs here
    ├── tinytap (Go binary, sudo)
    └── Docker daemon
        ├── container: api-service
        ├── container: db
        └── container: cache

...tinytap, running in the VM as root, observes syscalls from the containerized processes too — the same way it would for a process running directly on the VM. This is the same reason htop on the host shows container processes: they're all just kernel processes.

For the user, this means tinytap doesn't need to be installed inside containers, doesn't need a sidecar, and doesn't require the application to be rebuilt with anything. One install on the host is enough.

(Container-aware attribution — turning a PID into "this is the api-service container" — is a planned feature, not yet built. The kernel sees the PIDs; mapping them back to container names requires reading from Docker/containerd. For now tinytap shows raw PIDs.)

Requirements
  • Linux kernel 5.8+ (tinytap's event transport is BPF_MAP_TYPE_RINGBUF, added in 5.8 — see Toolchain)
  • macOS/Windows users run tinytap inside a Linux VM (Lima, WSL2, etc.) — there is no native macOS/Windows build and none is planned, since eBPF is Linux-only
Running without full root

sudo ./tinytap is the simplest path, but tinytap doesn't need full root. Plaintext HTTP capture needs three Linux capabilities:

sudo setcap cap_dac_read_search,cap_perfmon,cap_bpf=eip ./tinytap
./tinytap

TLS capture (the libssl uprobes) needs one more, cap_sys_admin:

sudo setcap cap_dac_read_search,cap_perfmon,cap_bpf,cap_sys_admin=eip ./tinytap
./tinytap

See docs/capabilities.md for what each capability is for, why TLS needs the broader cap_sys_admin, how this was verified, and known gaps (older kernels, x86_64).

Status & Roadmap

Released so far: v0.1.0 (HTTP request/response visible), v0.2.0 (Bubble Tea TUI), v0.3.0 (filtering + test foundation), v0.4.0 (server capture & compatibility — see docs/server-compat.md), v0.5.0 (HTTPS support via libssl uprobes — see docs/tls-compat.md), v0.6.0 (production readiness). v0.7.0 (real-hardware bring-up) is in progress — see #198. (v0.4.0 has no corresponding git tag — git tag jumps from v0.3.0 to v0.5.0 — left alone rather than backfilled; see #206.)

Full roadmap (near-term steps and longer-term vision) lives in #19, kept out of the README so this stays focused on what tinytap does today.

Toolchain

Component Choice Why
eBPF lib github.com/cilium/ebpf Pure Go, modern, standard for new projects
Build bpf2go (part of cilium/ebpf) Generates Go bindings from C code
Compiler clang 17+ Standard for eBPF, supports BTF. clang-14 compiles cleanly but the emitted bpf_probe_read_user call fails the kernel verifier (R2 unbounded memory access) — CI pins 17 (#207); 15/16 untested
Go 1.24+
Kernel Linux 5.8+ Required for BPF_MAP_TYPE_RINGBUF, tinytap's event transport
Architecture amd64 + arm64 Need arm64 for Apple Silicon Lima VM
Release builds GoReleaser v2 Cross-compiles linux/amd64 + linux/arm64 on tag push (.goreleaser.yml) — a plain go build, since the bpf2go artifacts are already committed and embedded (#207), no clang/libbpf step needed at release time
Dev environment

Mac (Apple Silicon) + Lima with Ubuntu 24.04. Build and run inside the Lima VM. Edit code on Mac via VS Code's remote SSH or the auto-mounted filesystem.

Setup commands:

# Mac side
brew install lima
limactl start --name=tinytap template://ubuntu
limactl shell tinytap

# Inside the VM
sudo apt update
sudo apt install -y clang llvm libbpf-dev linux-headers-$(uname -r) \
  build-essential git pkg-config

# Go (apt version is old)
GO_VERSION=1.24.0
ARCH=$(dpkg --print-architecture)  # arm64 on Apple Silicon
wget https://go.dev/dl/go${GO_VERSION}.linux-${ARCH}.tar.gz
sudo tar -C /usr/local -xzf go${GO_VERSION}.linux-${ARCH}.tar.gz
echo 'export PATH=$PATH:/usr/local/go/bin' >> ~/.bashrc
source ~/.bashrc

License

MIT — see LICENSE. Exception: bpf/vmlinux.h is generated from the Linux kernel's BTF info and is distributed under the kernel's GPL-2.0 license instead.

References

Directories

Path Synopsis
cmd
tinytap command
Command tinytap attaches the BPF program, drains its ringbuf, parses the per-syscall events into HTTP messages, pairs requests with responses, and feeds the result to an output sink.
Command tinytap attaches the BPF program, drains its ringbuf, parses the per-syscall events into HTTP messages, pairs requests with responses, and feeds the result to an output sink.
internal
config
Package config loads tinytap's TOML session settings — the file-based replacement for the --output/-v/--verbose flags (#217).
Package config loads tinytap's TOML session settings — the file-based replacement for the --output/-v/--verbose flags (#217).
events
Package events defines the shape of a single observation from BPF and the protocol-agnostic decoder that turns a ringbuf record into an Event.
Package events defines the shape of a single observation from BPF and the protocol-agnostic decoder that turns a ringbuf record into an Event.
loader
Package loader owns the BPF program lifecycle: lock memory, load the generated bindings, attach every tracepoint the program declares, and expose a ringbuf.Reader for userspace consumption.
Package loader owns the BPF program lifecycle: lock memory, load the generated bindings, attach every tracepoint the program declares, and expose a ringbuf.Reader for userspace consumption.
loader/bpf
Package bpf holds the bpf2go-generated bindings for the kernel-side program in bpf/tinytap.bpf.c.
Package bpf holds the bpf2go-generated bindings for the kernel-side program in bpf/tinytap.bpf.c.
loader/bpf/fixture
Package fixture holds the bpf2go-generated bindings for the integration-test fixture program in fixture.bpf.c.
Package fixture holds the bpf2go-generated bindings for the integration-test fixture program in fixture.bpf.c.
output
Package output defines the seam between tinytap's capture pipeline and how observations are rendered.
Package output defines the seam between tinytap's capture pipeline and how observations are rendered.
output/stdout
Package stdout renders the capture pipeline's output as a line-oriented HTTP exchange log (#63): one self-contained summary line per paired request/response, written to stdout.
Package stdout renders the capture pipeline's output as a line-oriented HTTP exchange log (#63): one self-contained summary line per paired request/response, written to stdout.
output/tui
Package tui renders the capture pipeline as a live Bubble Tea table: one row per paired HTTP exchange, scrolling in as exchanges are captured.
Package tui renders the capture pipeline as a live Bubble Tea table: one row per paired HTTP exchange, scrolling in as exchanges are captured.
proc
Package proc looks up process metadata from a /proc-style filesystem.
Package proc looks up process metadata from a /proc-style filesystem.
protocols/http
Package http parses HTTP/1.x messages from BPF observation streams, pairs requests with responses on the same (pid, fd), and renders the one-line summary the demo emits.
Package http parses HTTP/1.x messages from BPF observation streams, pairs requests with responses on the same (pid, fd), and renders the one-line summary the demo emits.
tls
Package tls locates the OpenSSL/BoringSSL shared library loaded by a traced process and confirms it exports the symbols tinytap needs to hook (SSL_read, SSL_write, SSL_set_fd) in order to capture TLS plaintext without reading OpenSSL's internal struct layout.
Package tls locates the OpenSSL/BoringSSL shared library loaded by a traced process and confirms it exports the symbols tinytap needs to hook (SSL_read, SSL_write, SSL_set_fd) in order to capture TLS plaintext without reading OpenSSL's internal struct layout.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL