DEV Community

SoftwareDevs mvpfactory.io
SoftwareDevs mvpfactory.io

Posted on • Originally published at mvpfactory.io

eBPF for Mobile API Observability: Zero-Overhead Request Tracing from Android to Your Backend Without Code Changes

---
title: "eBPF for Mobile API Observability: Zero-Touch Tracing from Android to Your Backend"
published: true
description: "Use eBPF TC hooks and uprobes on OpenSSL to trace Android OkHttp requests end-to-end at the kernel level  no code changes, no instrumentation overhead."
tags: [android, mobile, api, architecture]
canonical_url: https://mvpfactory.co/blog/ebpf-mobile-api-observability
---
Enter fullscreen mode Exit fullscreen mode

What We Are Building

By the end of this tutorial, you will have a mental model — and working probe code — for capturing Android OkHttp requests at the kernel level on your backend, correlating them with W3C traceparent headers, and assembling end-to-end flame graphs that include network transit, TLS handshake, and kernel queueing time. Zero application changes required.

This is the observability gap nobody talks about. A mobile user reports 800ms of slowness. Your APM shows green. Your backend p99 looks fine. The latency is hiding across your Android HTTP stack, the network, TLS negotiation, and server-side queueing — and nothing stitches those together by default.

eBPF changes this entirely.


Prerequisites

  • Linux backend (kernel 5.8+ recommended for ring buffer support)
  • OpenSSL or BoringSSL as your TLS terminator (Nginx, Envoy, or direct)
  • bpftrace installed for prototyping
  • Android app using OkHttp (any recent version)
  • Basic familiarity with C and distributed tracing concepts

Step 1 — Understand Your Three Attachment Points

eBPF lets you run sandboxed programs in the Linux kernel with near-zero overhead. For mobile API tracing, three hooks matter:

Attachment Point What It Captures Overhead
XDP Raw packets at NIC driver level Minimal — pre-kernel stack
TC hooks Ingress/egress at socket buffer level Low — post-routing
uprobes on OpenSSL/BoringSSL Plaintext data post-TLS handshake Low — userspace probe

XDP gives you packet-level metadata fastest. TC hooks give you structured socket buffer access. But here is the gotcha that will save you hours: for TLS traffic — which is everything from Android — uprobes on the TLS library are where the real work happens. The kernel never sees plaintext until after the handshake, so XDP and TC alone cannot read your HTTP headers.


Step 2 — Attach a Uprobe to SSL_read

Android's HTTP stack (OkHttp backed by BoringSSL) encrypts everything before it hits the wire. On your backend, attach a uprobe to SSL_write and SSL_read in your OpenSSL or BoringSSL shared library. These fire after the handshake, at the exact moment plaintext is passed into the TLS engine.

SEC("uprobe/SSL_write")
int trace_ssl_write(struct pt_regs *ctx) {
    int fd    = (int)PT_REGS_PARM1(ctx);
    void *buf = (void *)PT_REGS_PARM2(ctx);
    int num   = (int)PT_REGS_PARM3(ctx);

    // Read HTTP headers from buf, extract trace ID
    // Emit correlation event to ring buffer
    return 0;
}
Enter fullscreen mode Exit fullscreen mode

From buf, you extract the traceparent or X-Request-ID header that OkHttp injected. That is your correlation anchor — captured at the kernel boundary with no middleware involved.

Let me show you a pattern I use in every project: prototype this with bpftrace before building a production pipeline. You can attach to SSL_read in minutes and print header bytes to stdout. Validate your probe works before writing a single line of Go or Rust collector code.


Step 3 — Wire the Full Pipeline

Here is the minimal setup to get this working end-to-end:

Android side: OkHttp's Interceptor interface injects a W3C traceparent header on every outbound request. Standard distributed tracing, nothing exotic.

Backend pipeline:

  1. TC ingress hook captures the incoming TCP segment — records arrival timestamp, source IP, connection tuple
  2. Uprobe on SSL_read captures decrypted HTTP headers and extracts traceparent
  3. kprobes on socket operations track kernel queueing time before your application thread picks up the request
  4. All events flow into a perf ring buffer, consumed by a Go or Rust userspace daemon that assembles the full trace timeline

The result: a flame graph showing network transit time, TLS handshake duration, kernel queueing latency, and application processing time — all correlated to the exact Android request that triggered it.


Gotchas

Do not bundle the collector into your application process. Deploy it as a sidecar. Keeping eBPF programs and ring buffer consumers separate means you can update your observability stack independently, and it survives application restarts without losing trace data.

Inject traceparent before you touch any eBPF code. The docs do not mention this explicitly, but without the correlation header in OkHttp, your kernel-level capture has no anchor to join against your backend spans. It costs nothing and unlocks everything downstream.

Overhead is not a concern at this scale. eBPF programs run with JIT compilation and verifier-enforced safety. TC hook and uprobe combinations typically run in single-digit microseconds per request — well below any latency budget that matters at mobile scale. Application-level APM agents compound heap allocations and serialization overhead at p99. eBPF does not.


Conclusion

If you are running OpenSSL or BoringSSL on your backend, you already have everything you need to start. Open bpftrace, attach a uprobe to SSL_read, and watch your OkHttp headers appear at the kernel boundary. That moment — seeing plaintext HTTP headers in a kernel probe with zero application changes — is when this approach stops being theoretical.

Full end-to-end flame graphs from Android to your backend, no SDK changes, no instrumentation overhead. That is the gap eBPF closes.

Top comments (0)