An Inspektor Gadget gadget that extracts HTTP/1.x request/response exchanges from TCP connections and reports one event per exchange — method, path, status, sizes and latency — with the usual Inspektor Gadget process/container/Kubernetes enrichment.
It observes traffic passively: connections are never modified or delayed.
For every HTTP/1.x request the gadget correlates the request with its response and emits a single event:
| Column | Description |
|---|---|
method |
request method (GET, POST, …) |
path |
request target |
http_version |
protocol version, e.g. 1.1 |
status_code |
response status code (0 if no response was captured) |
content_type |
value of the Content-Type header (response, else request) |
host |
value of the request Host header |
request_size |
total request size in bytes (headers + body) |
response_size |
total response size in bytes (headers + body), 0 if unknown |
latency_ttfb_ns |
time from request start to the first response byte (nanoseconds) |
latency_total_ns |
time from request start to the last response byte (nanoseconds) |
is_chunked |
whether the response used chunked transfer-encoding |
is_websocket |
whether the exchange upgraded to a WebSocket |
request_body |
first bytes of the request body (up to 1024) |
response_body |
first bytes of the response body (up to 1024) |
src, dst |
client (request sender) and server (request receiver) endpoints |
Each event is enriched with the process and container that owns the connection:
comm, pid, the mount namespace, and — on Kubernetes or when using a container
runtime — the container name and the pod/namespace/labels. This is the standard
Inspektor Gadget process/container/Kubernetes enrichment.
src is always the client (which sent the request) and dst the server.
The gadget attaches three small eBPF programs:
- a
sock_opsprogram on the node's cgroup-v2 root that registers every established TCP socket, - an
sk_skbprogram that observes the data sockets receive, and - an
sk_msgprogram that observes the data sockets send.
Observing both the sent and received sides lets the gadget capture both ends of a connection, including connections where only one endpoint is on the node. When both endpoints are on the node the message is captured exactly once (no duplicates). The eBPF programs frame each message, pair each request with its response in the kernel, and compute the sizes and latency, emitting a single event per exchange with the two header blocks and a short body preview. A small WebAssembly module then only parses those header blocks to fill in method/path/host/status; it performs no correlation.
The owning process and container are resolved through Inspektor Gadget's socket enricher, which maps each socket to the task that created it (and therefore to its container/pod).
- Linux kernel 5.17 or newer with cgroup-v2 (a unified hierarchy mounted at
/sys/fs/cgroup) and sockmap/sk_skb/sk_msgsupport (enabled on stock distributions). igto run the gadget, and Docker to build the image.
Note: this gadget uses the
sock_ops,sk_skbandsk_msgeBPF program types. Yourig(and, on Kubernetes, the deployed Inspektor Gadget) must include support for attaching these program types. If yourigreports an unsupported program type when loading the gadget, upgrade to a build that includes it and setIG_VERSIONin.github/workflows/*.ymlaccordingly.
Build the OCI image (uses the ig builder container):
make build # or: sudo ig image build -t trace_http:latest .Run it and generate some HTTP traffic in another terminal. On a plain host, pass
--host so connections in the host network namespace are shown (without it, ig
reports only traffic that belongs to a container):
make run # or: sudo ig run trace_http:latest --host$ sudo ig run trace_http:latest --host
SRC DST COMM METHOD PATH HTTP STATUS REQUEST_SIZE RESPONSE_SIZE LATENCY_TTFB_NS LATENCY_TOTAL_NS
127.0.0.1:53578 127.0.0.1:8080 curl GET / 1.1 200 78 161 387835 448461
127.0.0.1:53578 127.0.0.1:8080 curl POST /submit 1.1 201 159 175 255168 283585
By default only request/response metadata (method, path, status, sizes, latency) is captured. Copying a body sample costs a per-message copy and widens each event, so it is opt-in per direction:
| Flag | Default | Description |
|---|---|---|
--req-body |
false |
Capture a sample of each request body. |
--res-body |
false |
Capture a sample of each response body. |
--req-body-len |
1024 |
Max request body bytes to capture (when --req-body; capped at 1024). |
--res-body-len |
1024 |
Max response body bytes to capture (when --res-body; capped at 1024). |
A body sample is captured from the first segment that carries body bytes (the
header segment or, if the body is delivered separately, the first body segment),
up to the configured length. request_body/response_body are empty unless the
matching flag is set.
Restrict the output with the standard Inspektor Gadget filters, which are matched against the process/container that owns the connection:
# only a specific container / pod
sudo ig run trace_http:latest --containername my-app
kubectl gadget run trace_http:latest --podname web-0 --namespace prod
# only a specific process
sudo ig run trace_http:latest --host --comm curl
sudo ig run trace_http:latest --host --pid 1234Use the standard ig flags, for example JSON output with a chosen set of fields:
sudo ig run trace_http:latest --host \
--fields method,path,status_code,request_size,response_size,latency_ttfb_ns,src,dst,k8s.podName \
-o jsonDeploy Inspektor Gadget in the cluster and run the gadget with
kubectl gadget (events are automatically enriched with the pod, namespace and
container):
kubectl gadget run ghcr.io/your-org/trace_http:latest- Only HTTP/1.x is parsed. Connections carrying other protocols are ignored (they still flow normally).
- One event is emitted per request/response exchange, when the response
completes. A request that never receives a response is reported on its own
(with
status_code0) once the connection closes. request_body/response_bodyhold a preview of the body (up to--req-body-len/--res-body-lenbytes, default 1024), captured from the first segment that carries body bytes when the matching--req-body/--res-bodyflag is set. They are empty by default.latency_total_nsandresponse_sizeare known for responses framed by aContent-Lengthor chunked transfer-encoding. For a response whose body ends only when the connection closes, onlylatency_ttfb_nsis reported.- A WebSocket upgrade is reported (
is_websocket), after which the connection carries WebSocket frames and is no longer parsed as HTTP.
Unit-test the userspace HTTP parser (and the reference correlation logic that the in-kernel pairing mirrors), no root or kernel required:
make unit # cd go && go test ./...Build and smoke-test the gadget on the current kernel:
make test # builds the image, loads it, and captures a requestThe eBPF program (program.bpf.c) is GPL-2.0. The rest of the project is
Apache-2.0; see LICENSE.