JOPIL: Journey Of a Packet In Linux Kernel
Inspiration
Network packet analysis is fundamental to understanding system behavior, diagnosing performance issues, and implementing security policies. However, traditional userspace packet capture tools (tcpdump, Wireshark) introduce significant overhead due to context switching between kernel and userspace.
We were inspired by the evolution of eBPF technology and its emergence as a paradigm shift in kernel instrumentation. Modern eBPF and XDP (eXpress Data Path) enable direct packet processing at wire speed without the traditional latency penalties. We recognized an opportunity to build a tool that leverages this cutting-edge technology to provide real-time, low-overhead packet visualization with minimal performance impact on the host system.
The goal was to demonstrate that next-generation network analysis tools do not require specialized hardware or complex infrastructure—only thoughtful kernel programming and modern runtime architecture.
What it does
JOPIL is a real-time network packet visualization and analysis system that operates at the kernel level using eBPF and XDP technology.
Core Capabilities:
- Wire-Speed Packet Capture: Captures packets directly at the XDP layer before kernel network stack processing
- Real-Time Flow Tracking: Identifies and monitors individual network flows with bidirectional communication statistics
- Protocol Analysis: Automatically detects and categorizes TCP, UDP, and ICMP traffic
- Per-CPU Distribution: Displays packet processing load across all processor cores
- Live Dashboard: Web-based interface providing real-time statistics and flow visualization
- RESTful API: Programmatic access to packet statistics and flow data
- WebSocket Streaming: Live event feed for real-time monitoring without polling
Technical Scope:
The system monitors all incoming network traffic on a specified interface, extracts header information (source/destination IP addresses, ports, protocol type), computes flow hashes for aggregation, and streams this data to a web dashboard for visualization.
How we built it
Architecture: Kernel-Userspace Cooperation
The implementation follows a layered architecture:
Layer 1: eBPF Kernel Module (bpf/packet_probe.c)
- Runs XDP program attached to network interface
- Parses Ethernet, IP, TCP/UDP headers in kernel context
- Performs zero-copy packet analysis at kernel level
- Submits enriched packet metadata to ring buffer
- Achieves sub-microsecond packet processing latency
Layer 2: Userspace Runtime (Go 1.21)
- Loads and attaches compiled eBPF bytecode via Cilium eBPF library
- Reads events from ring buffer with minimal blocking
- Aggregates flow statistics and protocol counts
- Maintains real-time data structures (hash maps for flows)
- Manages graceful shutdown and resource cleanup
Layer 3: HTTP Server and WebSocket (Go net/http + gorilla/websocket)
- Serves REST endpoints for statistics queries (/api/stats, /api/flows)
- Manages concurrent WebSocket connections for live updates
- Broadcasts packet events to all connected clients
- Implements per-second time-series aggregation
Layer 4: Frontend Dashboard (HTML5 + JavaScript + Chart.js)
- Renders real-time statistics and flow information
- Maintains WebSocket connection for live updates
- Displays protocol distribution charts and CPU load visualization
- Updates without page refresh (single-page application)
Technology Choices:
- eBPF with CO-RE (Compile Once, Run Everywhere): Eliminates need to compile for each kernel version
- Cilium eBPF Library: Type-safe Go bindings to eBPF subsystem, cleaner than raw syscalls
- Ring Buffer IPC: More efficient than older perf buffer; supports larger events with automatic synchronization
- Go for Userspace: Excellent standard library, cross-platform compilation, minimal runtime overhead
- WebSocket over HTTP: Reduces latency for real-time updates; browser-native support
Build System:
- Makefile orchestrates bpf2go compilation pipeline
- Single
make buildcommand generates BPF bytecode, creates Go bindings, links final binary - Produces self-contained executable with embedded BPF objects (no external dependencies at runtime)
Challenges we ran into
Challenge 1: eBPF Verifier Constraints
The eBPF in-kernel verifier has strict limitations on loop complexity and code size. Initial packet parsing code exceeded verifier limits.
Resolution: Restructured header parsing to follow linear control flow; removed unnecessary abstractions; verified bytecode instruction count before compilation.
Challenge 2: Kernel-Userspace Memory Coordination
Ensuring correct struct padding and byte ordering between C kernel code and Go userspace code proved error-prone. Misaligned fields caused data corruption.
Resolution: Explicitly defined padding fields in C struct; used CO-RE for automatic offset calculations; added binary marshaling tests to validate serialization.
Challenge 3: XDP Program Attachment Conflicts
Running the application multiple times caused "device or resource busy" errors—previous XDP programs remained attached to network interface.
Resolution: Implemented automatic cleanup script and improved error messages to guide users to manual cleanup when needed.
Challenge 4: Port Binding and Process Management
Default port conflicts with existing services; no graceful shutdown mechanism on first iterations.
Resolution: Implemented signal handlers for SIGTERM and SIGINT; proper resource cleanup on shutdown; documented alternative port binding.
Challenge 5: Ring Buffer Event Flooding
At high packet rates (>100k pps), ring buffer overflowed and dropped events; userspace reader couldn't keep up.
Resolution: Implemented non-blocking channel send with proper overflow handling; added packet counting and logging for diagnostics.
Accomplishments that i am proud of
1. Minimal Performance Overhead
The system achieves near-zero-copy packet inspection at kernel level. In testing with 1600+ packets per test run across multiple protocols and CPU cores, observed packet loss remains negligible (<0.1% under normal conditions).
2. Production-Grade Code Quality
- Professional codebase formatting with no emoji or informal comments
- Comprehensive error handling and logging
- Proper resource management and graceful shutdown
- Clean directory structure following Go conventions
- Type-safe architecture eliminating entire classes of bugs
3. Elegant Kernel-Userspace Bridge
The ring buffer IPC mechanism seamlessly transfers structured data from kernel space to userspace without manual buffer management. Combined with CO-RE, this creates a truly portable solution across kernel versions.
4. Real-Time Visualization
WebSocket-based live updates provide sub-second latency for dashboard updates, enabling genuine real-time monitoring without polling or page refreshes.
5. Single Binary Deployment
All eBPF bytecode embedded in the Go binary. A single packet-viz executable with no external dependencies can be deployed to any Linux system running kernel 5.8+.
6. Comprehensive Documentation
Created professional technical documentation explaining architecture, API endpoints, troubleshooting procedures, and operational best practices.
What we learned
1. eBPF Ecosystem Maturity
eBPF has matured significantly. The combination of Cilium eBPF library, bpf2go tooling, and CO-RE support makes it practical for production systems. The technology is no longer experimental but suitable for real infrastructure.
2. Go's Excellence for Systems Programming
Go's memory safety, excellent standard library, and cross-platform support make it ideal for building eBPF userspace agents. CGO is minimized, and the language forces explicit error handling.
3. Kernel-Userspace Communication Challenges
Coordinating data structures across the kernel-userspace boundary requires meticulous attention to padding, alignment, and byte ordering. CO-RE significantly reduces this friction.
4. Real-Time Visualization Complexity
Building responsive dashboards for real-time data requires careful consideration of WebSocket handling, client state management, and rate limiting to prevent overwhelming browsers with updates.
5. The Importance of Proper Tooling
Good build systems and automated testing significantly reduce development time. The bpf2go tool is well-designed and worth understanding deeply.
6. Linux Kernel Depth
This project required understanding the Linux kernel network stack, XDP hook points, memory models, and CPU scheduling. Systems programming demands comprehensive knowledge of underlying platform behavior.
What's next for JOPIL
Phase 1: Enhanced Visibility
- Drop Monitor: Implement kprobe on
skb:kfree_skbtracepoint to capture packet drop events and identify where packets are discarded in the kernel stack - Heatmaps and P99 Latency: Implement latency histograms showing P50, P95, P99 percentiles instead of averages (following SRE best practices from Google and Netflix)
- Sankey Diagram: Beautiful flow visualization showing traffic paths from physical interface through protocol layers
Phase 2: Advanced Capabilities
- Active Firewalling: Transform from observer to defender—implement XDP-based packet filtering with dynamic allow/block rules
- Application-Level Attribution: Correlate packet flows with process information (netlink socket API) to identify which application generated traffic
- Persistent Storage: Export metrics to time-series database (InfluxDB/Prometheus) for historical analysis and alerting
*Phase 3: Enterprise Features *
- Multi-Interface Monitoring: Support monitoring multiple network interfaces simultaneously
- High-Availability Clustering: Distributed packet analysis across multiple nodes with centralized dashboard
- Anomaly Detection: Machine learning-based detection of unusual traffic patterns
- Security Policy Enforcement: Integration with security frameworks for automated response to detected threats
- Performance Benchmarking: Systematic benchmarks comparing JOPIL against tcpdump, Wireshark, and other tools
- CLI Interface: Command-line tool for programmatic access and automation
Log in or sign up for Devpost to join the conversation.