Conversation
Go statically links crypto/tls and never calls OpenSSL's SSL_write, so its HTTPS requests were invisible to the existing uprobe. Add a second uprobe on crypto/tls.(*Conn).Write feeding the same ringbuf, so host/path/headers are recovered before encryption for any net/http client. - bpf: go_tls_write reads Go's register-based ABIInternal (amd64 ax/bx/cx/di, arm64 user_pt_regs.regs[i]) by hand, since BPF_UPROBE's PT_REGS_PARM* assume the C ABI. The *Conn pointer is the per-connection key, reusing the existing HTTP/1.x + HTTP/2/HPACK tracker. Entry-only (uretprobes corrupt Go stacks). - sslsnoop: discover Go binaries by scanning /proc/<pid>/exe, checking the ELF symbol table, and attaching per unique inode. Dedup by exe inode (not mount namespace — every process has a distinct executable); skip the agent's own binary (ebfw links crypto/tls via client-go). - e2e: build a native Go net/http client inside a Go container (no host Go toolchain assumed) and assert ebfw recovers its host, path, and a custom header. Self-skips without Docker. - metrics: add ebfw_go_uprobe_attached. Docs updated to drop the OpenSSL-only claim (Java/rustls/stripped-Go still uncovered).
Move the inline heredoc client into test/fixtures/gotls-client (main.go + go.mod) and build it via its own Dockerfile with `docker build --target bin --output`, mirroring the repo's binary-export pattern. e2e no longer inlines Go source or an ad-hoc `docker run go build`.
Remove EBFW_GOTLS_CLIENT; the Go crypto/tls client is always built from its fixture Dockerfile, and the checks self-skip only when Docker is absent.
The userspace <asm/ptrace.h> struct pt_regs names x86_64 registers with the `r` prefix; the bare ax/bx/cx/di names exist only in the kernel/vmlinux definition we don't include, so the amd64 BPF compile failed with "no member named 'ax'". Validated by cross-compiling the object for both __TARGET_ARCH_x86 and __TARGET_ARCH_arm64.
The loop only existed to dodge the uprobe-attach race. Instead, the client sleeps just past one discovery interval so the probe attaches while it is alive, then sends a single request — one capture is enough to prove host/path/header extraction, and it's deterministic.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Capture Go crypto/tls request plaintext via uprobe
What & why
ebfw's HTTPS L7 visibility came from an SSL_write uprobe, which only hooks OpenSSL's dynamically-linked libssl. Go statically links crypto/tls and never calls SSL_write, so every native Go net/http client was invisible — no host, path, or headers. This PR closes that gap by attaching a second uprobe to Go's TLS write path, so we recover the same L7 detail before encryption, with no proxy and no TLS MITM.
How it works
It's generic, not just the test fixture
Discovery is fully generic — any Go binary on the node that links crypto/tls is picked up. In testing, the agent captured the host's real tailscaled process alongside the fixture:
attached crypto/tls.(*Conn).Write uprobe via /proc/3196611/exe ...
HTTPS [pid=3196611 tailscaled] POST log.tailscale.com/c/tailnode.log.tailscale.io/107854...
Host: log.tailscale.com
Content-Length: 624
...
Limitations (documented)
Testing
Docs
README, ROADMAP, CLAUDE.md, and docs/{comparison,configuration,tests}.md updated to drop the "OpenSSL-only" claim and describe the two-uprobe model + its caveats.