<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Mohamed Zrouga</title>
    <description>The latest articles on DEV Community by Mohamed Zrouga (@zrouga).</description>
    <link>https://dev.to/zrouga</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2175334%2Fe47a09a0-b4cc-4518-9873-52b431dd2bc1.jpeg</url>
      <title>DEV Community: Mohamed Zrouga</title>
      <link>https://dev.to/zrouga</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/zrouga"/>
    <language>en</language>
    <item>
      <title>I'm not an ML engineer. I built one anyway.</title>
      <dc:creator>Mohamed Zrouga</dc:creator>
      <pubDate>Mon, 25 May 2026 13:02:14 +0000</pubDate>
      <link>https://dev.to/zrouga/im-not-an-ml-engineer-i-built-one-anyway-3feb</link>
      <guid>https://dev.to/zrouga/im-not-an-ml-engineer-i-built-one-anyway-3feb</guid>
      <description>&lt;p&gt;Not because I wanted to — but because every tool I tried on ARM edge devices either needed the cloud, needed a GPU, or needed more RAM than the service it was supposed to be watching.&lt;/p&gt;

&lt;p&gt;So this post isn't really about Cerberus. It's about the problem it forced me to actually understand: what does anomaly detection &lt;em&gt;require&lt;/em&gt; on constrained hardware, and how do you get there without black-box ML?&lt;/p&gt;

&lt;p&gt;Here's what I learned.&lt;/p&gt;

&lt;h2&gt;
  
  
  The observability stack was heavier than the workload
&lt;/h2&gt;

&lt;p&gt;When you're deploying on cloud VMs, the weight of your tooling is invisible. You have RAM to spare. You have fast networks. You have Prometheus scraping endpoints on a LAN that never goes offline.&lt;/p&gt;

&lt;p&gt;Drop the same assumptions onto an ARM gateway at a remote industrial site and things break differently. The telemetry pipeline competes with the workload for CPU cycles. The collector needs connectivity that doesn't exist. The ML inference endpoint is somewhere in a cloud region the device can't reach.&lt;/p&gt;

&lt;p&gt;The problem isn't the tools — they were built for a different environment. The problem is treating cloud-native observability as a default rather than a choice.&lt;/p&gt;

&lt;p&gt;Once I asked "what does edge observability actually &lt;em&gt;need&lt;/em&gt;?" the answer was much smaller than I expected:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Did traffic behavior change?
Is something probing unusual ports?
Are protocol patterns different from yesterday?
Is there unexplained traffic acceleration?
Which specific device changed?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's not a distributed tracing problem. It's a behavioral signal problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why eBPF is the right layer for this
&lt;/h2&gt;

&lt;p&gt;The kernel already sees everything. Every packet, every connection, every flag — it all passes through the network stack before any userspace process touches it.&lt;/p&gt;

&lt;p&gt;eBPF lets you attach small programs directly to that stack using TC (Traffic Control) or XDP hooks. Instead of running &lt;code&gt;tcpdump&lt;/code&gt; through a pipe, or copying full payloads into userspace for inspection, you write a kernel-side filter that extracts only the metadata you care about and hands it to you via a ring buffer.&lt;/p&gt;

&lt;p&gt;For Cerberus, that's roughly 208 bytes per event:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;network_event&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;__u8&lt;/span&gt;  &lt;span class="n"&gt;event_type&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;       &lt;span class="c1"&gt;// ARP / TCP / UDP / DNS / TLS / HTTP / ICMP&lt;/span&gt;
    &lt;span class="n"&gt;__u32&lt;/span&gt; &lt;span class="n"&gt;src_ip&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;__u32&lt;/span&gt; &lt;span class="n"&gt;dst_ip&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;__u16&lt;/span&gt; &lt;span class="n"&gt;src_port&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;__u16&lt;/span&gt; &lt;span class="n"&gt;dst_port&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;__u8&lt;/span&gt;  &lt;span class="n"&gt;tcp_flags&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;__u8&lt;/span&gt;  &lt;span class="n"&gt;l7_payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;  &lt;span class="c1"&gt;// first 128 bytes for L7 inspection&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The kernel filters. The ring buffer delivers. Userspace gets a clean event stream at near-zero overhead — no full payload copies, no extra processes, no agents fighting the workload for CPU.&lt;/p&gt;

&lt;p&gt;On ARM systems, this difference is measurable.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxogwuc4es97hqelp4cah.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxogwuc4es97hqelp4cah.png" alt="ML-Lite architecture flow" width="800" height="615"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What "ML-Lite" actually means
&lt;/h2&gt;

&lt;p&gt;Before I go further: I want to be clear that I'm not an ML engineer. What I built is better described as applied statistics with some online learning layered on top. I'm calling it ML-Lite because that's what it is, not because it sounds impressive.&lt;/p&gt;

&lt;p&gt;The instinct when building anomaly detection is to reach for a neural network or a heavy ML runtime. On constrained hardware that's a dead end — both because of resource cost and because the explainability disappears. An operator at 2am staring at an alert doesn't want a confidence score. They want to know &lt;em&gt;what changed&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;So the system works in three stages.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stage 1: Aggregate into windows
&lt;/h3&gt;

&lt;p&gt;Every 30 seconds, the event stream is compressed into a feature vector:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[packet_rate, dns_rate, tls_rate, syn_rate, entropy, unusual_ports]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the "network behavior as numbers" step. Each window becomes a compact snapshot of what the device was doing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stage 2: Build a baseline
&lt;/h3&gt;

&lt;p&gt;As windows accumulate, the system learns what normal looks like using three tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Median + MAD (Median Absolute Deviation)&lt;/strong&gt; — robust to outliers in a way mean/stddev aren't. If one window has a traffic spike, the baseline doesn't shift.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EWMA (Exponentially Weighted Moving Average)&lt;/strong&gt; — gives recent windows more weight than old ones, so the baseline adapts slowly over time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Centroid distance&lt;/strong&gt; — tracks how far the current feature vector is from the center of historical observations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The scoring formula for each feature is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;robust_z = |x - median| / MAD
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And entropy is computed as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;H(X) = -Σ p(x) log₂ p(x)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where &lt;code&gt;x&lt;/code&gt; is the distribution of destination ports. Normal traffic hits the same handful of ports repeatedly — entropy stays low. A port scan touches 22, 23, 80, 443, 445, 3389 in sequence — entropy spikes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stage 3: Explain the score
&lt;/h3&gt;

&lt;p&gt;This is the part I cared most about. The system doesn't just surface a number — it surfaces which features drove it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WHY?
+ High SYN rate
+ Port entropy spike
+ Traffic acceleration
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An operator can act on that immediately.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk0zjemh39bs3kgpxrc42.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk0zjemh39bs3kgpxrc42.png" alt="How Cerberus learns normal behavior" width="800" height="615"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The evolution wasn't planned
&lt;/h2&gt;

&lt;p&gt;The detection model went through several iterations, each adding a layer without replacing what came before:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;v1 — Statistical detection:&lt;/strong&gt; Median, MAD, thresholds, entropy. Worked. Noisy on IoT networks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;v2 — Adaptive learning:&lt;/strong&gt; EWMA, rolling baselines, per-device profiles. Reduced false positives significantly once the baseline had enough history.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;v3 — Isolation Forest:&lt;/strong&gt; Unsupervised ML, tree isolation, outlier scoring. Doesn't need labeled attack data. Effective for genuinely novel patterns.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;v4 — Tiny autoencoders:&lt;/strong&gt; Architecture is &lt;code&gt;9→16→4→16→9&lt;/code&gt;. The bottleneck (4 dimensions) forces the model to compress normal behavior into a compact representation. Reconstruction error is the anomaly signal — if the current window can't be reconstructed well, it's unusual.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;v5 (in progress) — Temporal Graph ML:&lt;/strong&gt; Device graphs, sequence analysis, behavior prediction. The goal is to model relationships between devices over time, not just each device in isolation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc12oaqykkuxvjkc385m7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc12oaqykkuxvjkc385m7.png" alt="Evolution of Cerberus ML-Lite" width="800" height="615"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The design constraint throughout: offline, CPU/ARM friendly, explainable, hackable. No iteration added a cloud dependency.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmxwfe75k746xdwxa9iue.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmxwfe75k746xdwxa9iue.png" alt="Why this is not AI magic" width="800" height="615"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest limitations
&lt;/h2&gt;

&lt;p&gt;Behavioral models come with real tradeoffs I haven't fully solved:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Baseline drift&lt;/strong&gt;: Normal behavior changes over time. A device that starts a new cron job at 3am will generate false positives until the baseline adapts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Encrypted traffic&lt;/strong&gt;: TLS SNI is visible at the handshake, but payload content isn't. The entropy signals still work on port distributions, but deep inspection has limits.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Noisy IoT environments&lt;/strong&gt;: Some IoT devices have genuinely chaotic traffic patterns. Per-device profiles help, but they need enough history to be meaningful.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cold start&lt;/strong&gt;: Until a device accumulates enough windows to build a stable baseline, scoring is unreliable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is not a full IDS. It's an operational visibility tool that can surface unusual behavior — with the reasoning attached.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'm still trying to understand
&lt;/h2&gt;

&lt;p&gt;This is where I'd genuinely value input from people with more ML background than me:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For edge anomaly detection, is unsupervised learning (Isolation Forest, autoencoders) fundamentally the right approach, or are there better-suited algorithms for streaming, low-resource environments?&lt;/li&gt;
&lt;li&gt;How do you handle baseline drift without introducing false negatives for genuine behavioral shifts?&lt;/li&gt;
&lt;li&gt;For temporal modeling on embedded systems, are there efficient graph-based approaches that don't require the full overhead of a GNN framework?&lt;/li&gt;
&lt;li&gt;Is there a better feature set for network behavioral anomaly detection beyond what's described here?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'm building in a space I find genuinely interesting but I'm not formally trained in — any feedback on the ML design is more valuable to me than stars.&lt;/p&gt;

&lt;h2&gt;
  
  
  Thank you
&lt;/h2&gt;

&lt;p&gt;Cerberus wouldn't be what it is without the people who showed up and contributed real work to it.&lt;/p&gt;

&lt;p&gt;Huge thanks to &lt;a href="https://github.com/SvenNellerz" rel="noopener noreferrer"&gt;@SvenNellerz&lt;/a&gt; and &lt;a href="https://github.com/alexmchughdev" rel="noopener noreferrer"&gt;@alexmchughdev&lt;/a&gt; — your contributions made the project meaningfully better and I genuinely appreciate it.&lt;/p&gt;

&lt;p&gt;The project also stands on &lt;a href="https://github.com/cilium/ebpf" rel="noopener noreferrer"&gt;cilium/ebpf&lt;/a&gt;, &lt;a href="https://github.com/tidwall/buntdb" rel="noopener noreferrer"&gt;BuntDB&lt;/a&gt;, and &lt;a href="https://github.com/hashicorp/golang-lru" rel="noopener noreferrer"&gt;golang-lru&lt;/a&gt; — solid libraries that made the Go + eBPF combination practical without CGO headaches.&lt;/p&gt;

&lt;p&gt;If you're working in this space — embedded Linux, ARM infrastructure, eBPF, IoT security, or lightweight ML — the repo is at &lt;a href="https://github.com/zrougamed/cerberus" rel="noopener noreferrer"&gt;github.com/zrougamed/cerberus&lt;/a&gt; and I'd genuinely love to hear how you're approaching the same problems.&lt;/p&gt;

</description>
      <category>machinelearning</category>
      <category>linux</category>
      <category>security</category>
      <category>go</category>
    </item>
    <item>
      <title>I Got Tired of Docker Eating My Raspberry Pi's RAM — So I Built My Own Container Orchestrator</title>
      <dc:creator>Mohamed Zrouga</dc:creator>
      <pubDate>Sat, 16 May 2026 23:33:55 +0000</pubDate>
      <link>https://dev.to/zrouga/i-got-tired-of-docker-eating-my-raspberry-pis-ram-so-i-built-my-own-container-orchestrator-3j3n</link>
      <guid>https://dev.to/zrouga/i-got-tired-of-docker-eating-my-raspberry-pis-ram-so-i-built-my-own-container-orchestrator-3j3n</guid>
      <description>&lt;p&gt;A few months ago I noticed something that genuinely annoyed me.&lt;/p&gt;

&lt;p&gt;I was running tiny services across my Raspberry Pi lab:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;webhook workers&lt;/li&gt;
&lt;li&gt;monitoring agents&lt;/li&gt;
&lt;li&gt;lightweight APIs&lt;/li&gt;
&lt;li&gt;ETL processing tasks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Small, focused workloads. The kind of things a Pi is actually good at.&lt;/p&gt;

&lt;p&gt;But the infrastructure stack underneath them? It looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Docker Engine
 └─ containerd
     └─ runc
         └─ CNI plugins (all of them, even the ones I'd never touch)
             └─ orchestration layer
                 └─ service networking
                     └─ monitoring sidecars
                         └─ my actual 8MB process
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At some point I sat down and ran &lt;code&gt;ps aux&lt;/code&gt; and &lt;code&gt;free -h&lt;/code&gt; on a freshly booted node before deploying anything.&lt;/p&gt;

&lt;p&gt;The infrastructure was already using more RAM than my applications.&lt;/p&gt;

&lt;p&gt;That felt wrong. So I started pulling threads.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Actually Runs a Container?
&lt;/h2&gt;

&lt;p&gt;Strip everything back. What does "running a container" actually require?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;An OCI image unpacked to disk&lt;/li&gt;
&lt;li&gt;A rootfs (overlayfs layers)&lt;/li&gt;
&lt;li&gt;Linux namespaces (pid, net, mount, uts, ipc)&lt;/li&gt;
&lt;li&gt;cgroup resource limits ( cgroup V2 )&lt;/li&gt;
&lt;li&gt;A process in that environment&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's it. That's the whole thing.&lt;/p&gt;

&lt;p&gt;Everything else — the daemon, the socket, the plugin ecosystem, the CNI chain, the abstraction layers — exists to make that easier to manage at scale.&lt;/p&gt;

&lt;p&gt;Scale I don't have on a Raspberry Pi.&lt;/p&gt;

&lt;p&gt;So I asked: what's the absolute minimum secure OCI stack that can do this properly?&lt;/p&gt;

&lt;p&gt;That question became &lt;strong&gt;nyxd&lt;/strong&gt; &lt;a href="https://github.com/zrougamed/nyxd.git" rel="noopener noreferrer"&gt;https://github.com/zrougamed/nyxd.git&lt;/a&gt; . &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffgd43l12nt8hachdy00x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffgd43l12nt8hachdy00x.png" alt="nyx in action"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  nyxd: What It Is and What It Isn't
&lt;/h2&gt;

&lt;p&gt;nyxd is a lightweight container daemon I built in Go. It uses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;crun&lt;/strong&gt; as the OCI runtime (not runc — more on this shortly)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;overlayfs&lt;/strong&gt; directly via syscall for rootfs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;no CNI plugins&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;nftables&lt;/strong&gt; for NAT and port mapping&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;seccomp + NoNewPrivileges&lt;/strong&gt; enforced on every container&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is not:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Kubernetes&lt;/li&gt;
&lt;li&gt;another Docker replacement&lt;/li&gt;
&lt;li&gt;"another Podman"&lt;/li&gt;
&lt;li&gt;trying to be any of those things&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The philosophy is reduction. Every component you remove is an attack surface that disappears, a dependency you don't have to update, a CVE you'll never have to patch.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why crun Instead of runc
&lt;/h2&gt;

&lt;p&gt;This is the first question people would ask.&lt;/p&gt;

&lt;p&gt;Here's the honest answer: crun has a remarkably clean security history.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;crun CVE history (complete, as of 16-05-2026):&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;CVE&lt;/th&gt;
&lt;th&gt;Impact / Type&lt;/th&gt;
&lt;th&gt;Affected&lt;/th&gt;
&lt;th&gt;Fixed In&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CVE-2026-30892&lt;/td&gt;
&lt;td&gt;Local Privilege Escalation via crun exec -u 1 root parsing flaw.&lt;/td&gt;
&lt;td&gt;1.19 – 1.26&lt;/td&gt;
&lt;td&gt;1.27&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CVE-2025-24965&lt;/td&gt;
&lt;td&gt;Host Filesystem Escape via the krun architecture handler.&lt;/td&gt;
&lt;td&gt;&amp;lt; 1.20&lt;/td&gt;
&lt;td&gt;1.20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CVE-2022-27650&lt;/td&gt;
&lt;td&gt;Privilege jump inside container via leaked Inheritable Capabilities.&lt;/td&gt;
&lt;td&gt;&amp;lt; 1.4.4&lt;/td&gt;
&lt;td&gt;1.4.4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CVE-2019-18837&lt;/td&gt;
&lt;td&gt;Host Directory Traversal via malicious symlinks in a crafted image.&lt;/td&gt;
&lt;td&gt;&amp;lt; 0.10.5&lt;/td&gt;
&lt;td&gt;0.10.5&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Very few CVEs. In its entire history. &lt;/p&gt;

&lt;p&gt;Compare that to November 2025, when runc had three container-escape vulnerabilities disclosed in a single week: CVE-2025-31133, CVE-2025-52565, and CVE-2025-52881. All critical. All allowing container escape to host.&lt;/p&gt;

&lt;p&gt;Beyond security, crun has practical advantages for edge/ARM workloads:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lower memory footprint per container&lt;/li&gt;
&lt;li&gt;Faster startup (C runtime, not Go)&lt;/li&gt;
&lt;li&gt;Excellent cgroup v2 support from day one&lt;/li&gt;
&lt;li&gt;Smaller binary (~800KB vs runc's several MB)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On a Pi with 1-4GB RAM running 20+ containers, that adds up fast.&lt;/p&gt;




&lt;h2&gt;
  
  
  The CVE Surface Nobody Benchmarks
&lt;/h2&gt;

&lt;p&gt;Here's something I started thinking about that I hadn't seen anyone measure properly:&lt;/p&gt;

&lt;p&gt;People benchmark CPU and RAM constantly. Almost nobody benchmarks &lt;strong&gt;security surface area&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;But when you're running edge infrastructure — systems with limited ops coverage, longer uptimes, harder recovery paths — the CVE surface is arguably the most important operational metric.&lt;/p&gt;

&lt;p&gt;So let me be direct about the current state as of 16-05-2026:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;containernetworking/plugins CVE history (recent):&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;CVE&lt;/th&gt;
&lt;th&gt;Plugin&lt;/th&gt;
&lt;th&gt;Issue&lt;/th&gt;
&lt;th&gt;Fixed&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CVE-2025-67499&lt;/td&gt;
&lt;td&gt;portmap&lt;/td&gt;
&lt;td&gt;nftables backend intercepts unintended traffic&lt;/td&gt;
&lt;td&gt;v1.9.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CVE-2025-52881&lt;/td&gt;
&lt;td&gt;selinux dep&lt;/td&gt;
&lt;td&gt;container escape via procfs write misdirection&lt;/td&gt;
&lt;td&gt;v1.9.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CVE-2024-34156&lt;/td&gt;
&lt;td&gt;all&lt;/td&gt;
&lt;td&gt;Go stdlib encoding/gob&lt;/td&gt;
&lt;td&gt;v1.4.0-6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CVE-2023-45290&lt;/td&gt;
&lt;td&gt;all&lt;/td&gt;
&lt;td&gt;Go stdlib net/http&lt;/td&gt;
&lt;td&gt;v1.4.0-3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;And this is just the CNI plugin layer — the binaries a lot of stacks still exec on every container &lt;code&gt;ADD&lt;/code&gt;. nyxd's &lt;strong&gt;default&lt;/strong&gt; path doesn't run those plugins at all; I still think the table matters as a reminder of what you inherit when you opt into the full CNI distribution. Add Docker Engine, containerd, runc, BuildKit, and their respective dependency trees and you're tracking dozens of CVEs per year across a stack that most people never fully audit.&lt;/p&gt;

&lt;p&gt;The rough security surface comparison:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stack&lt;/th&gt;
&lt;th&gt;Binary count&lt;/th&gt;
&lt;th&gt;External deps&lt;/th&gt;
&lt;th&gt;Rough CVE exposure/yr&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;nyxd + crun&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;3 Go modules&lt;/td&gt;
&lt;td&gt;Very low&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Podman + crun&lt;/td&gt;
&lt;td&gt;~8&lt;/td&gt;
&lt;td&gt;Large&lt;/td&gt;
&lt;td&gt;Low-medium&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Docker Engine full&lt;/td&gt;
&lt;td&gt;15+&lt;/td&gt;
&lt;td&gt;Very large&lt;/td&gt;
&lt;td&gt;Medium-high&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;containerd + runc + full CNI&lt;/td&gt;
&lt;td&gt;20+&lt;/td&gt;
&lt;td&gt;Massive&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kubernetes node&lt;/td&gt;
&lt;td&gt;30+&lt;/td&gt;
&lt;td&gt;Enormous&lt;/td&gt;
&lt;td&gt;Very high&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The numbers aren't precise science. But the trend is real.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Simplicity is a security feature.&lt;/strong&gt; I genuinely believe that now.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Networking Decision
&lt;/h2&gt;

&lt;p&gt;Most of the container networking ecosystem exists to solve problems at scale. VXLAN overlays, BGP route propagation, multi-cluster service meshes.&lt;/p&gt;

&lt;p&gt;None of that applies to a Raspberry Pi lab.&lt;/p&gt;

&lt;p&gt;nyxd's &lt;strong&gt;default&lt;/strong&gt; networking is implemented &lt;strong&gt;inside the daemon&lt;/strong&gt;, in Go, using raw netlink — not by exec'ing the usual CNI plugin chain under &lt;code&gt;/opt/cni/bin&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What that stack actually does:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Bridge + veth&lt;/strong&gt; — brings up &lt;code&gt;nyxbr0&lt;/code&gt;, creates the pair, moves the peer into the container netns, names it &lt;code&gt;eth0&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;File-backed IPAM&lt;/strong&gt; — hands out addresses from the same &lt;code&gt;10.88.0.0/16&lt;/code&gt;-style slice you'd expect from a tiny lab bridge&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Loopback&lt;/strong&gt; — &lt;code&gt;lo&lt;/code&gt; up inside the netns&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Port publishing&lt;/strong&gt; — host→container maps via &lt;strong&gt;nftables&lt;/strong&gt; (we shell out to &lt;code&gt;nft&lt;/code&gt; for the rules; that's the one small networking helper we deliberately keep external)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What we don't use&lt;/strong&gt; (on that default path):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;bridge&lt;/code&gt; / &lt;code&gt;host-local&lt;/code&gt; / &lt;code&gt;loopback&lt;/code&gt; / &lt;code&gt;portmap&lt;/code&gt; &lt;strong&gt;binaries&lt;/strong&gt; from containernetworking/plugins&lt;/li&gt;
&lt;li&gt;Flannel&lt;/li&gt;
&lt;li&gt;Calico&lt;/li&gt;
&lt;li&gt;kube-proxy&lt;/li&gt;
&lt;li&gt;Weave&lt;/li&gt;
&lt;li&gt;Cilium (for this use case)&lt;/li&gt;
&lt;li&gt;Any overlay mesh networking&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you &lt;strong&gt;want&lt;/strong&gt; the traditional CNI exec path — because you already ship a conflist or you're mirroring another environment — &lt;strong&gt;&lt;code&gt;nyxd -net-driver=cni&lt;/code&gt;&lt;/strong&gt; is still there. Same supervisor and API; you bring &lt;code&gt;/opt/cni/bin&lt;/code&gt; and your plugins. That's optional complexity, not what you get out of the box.&lt;/p&gt;

&lt;p&gt;Zero CNI plugin binaries on the default path means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Nothing to install under &lt;code&gt;/opt/cni/bin&lt;/code&gt; on a fresh Pi&lt;/li&gt;
&lt;li&gt;Nothing to keep updated in that directory for homelab-sized deployments&lt;/li&gt;
&lt;li&gt;No binary-level CVE surface in &lt;em&gt;that&lt;/em&gt; layer for the stack I'm actually running day to day&lt;/li&gt;
&lt;li&gt;Faster container startup (no fork/exec chain per &lt;code&gt;CNI ADD&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Kernel Requirements (the honest list)
&lt;/h2&gt;

&lt;p&gt;Running nyxd requires these kernel modules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;overlay&lt;/span&gt;          &lt;span class="c"&gt;# overlayfs for container rootfs
&lt;/span&gt;&lt;span class="n"&gt;bridge&lt;/span&gt;           &lt;span class="c"&gt;# nyxbr0 bridge interface
&lt;/span&gt;&lt;span class="n"&gt;veth&lt;/span&gt;             &lt;span class="c"&gt;# virtual ethernet pairs
&lt;/span&gt;&lt;span class="n"&gt;br_netfilter&lt;/span&gt;     &lt;span class="c"&gt;# iptables/nftables sees bridged traffic
&lt;/span&gt;&lt;span class="n"&gt;ip_tables&lt;/span&gt;        &lt;span class="c"&gt;# iptables core
&lt;/span&gt;&lt;span class="n"&gt;iptable_nat&lt;/span&gt;      &lt;span class="c"&gt;# NAT table
&lt;/span&gt;&lt;span class="n"&gt;nf_nat&lt;/span&gt;           &lt;span class="c"&gt;# connection tracking NAT
&lt;/span&gt;&lt;span class="n"&gt;nf_conntrack&lt;/span&gt;     &lt;span class="c"&gt;# stateful packet tracking
&lt;/span&gt;&lt;span class="n"&gt;nft_masq&lt;/span&gt;         &lt;span class="c"&gt;# nftables masquerade
&lt;/span&gt;&lt;span class="n"&gt;seccomp&lt;/span&gt;          &lt;span class="c"&gt;# syscall filtering
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And these sysctls:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;net.ipv4.ip_forward&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;1&lt;/span&gt;
&lt;span class="py"&gt;net.bridge.bridge-nf-call-iptables&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;1&lt;/span&gt;
&lt;span class="py"&gt;net.ipv4.conf.all.rp_filter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All of this works on a standard Raspberry Pi OS kernel (6.1 LTS). No custom kernel needed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Benchmarking This Honestly
&lt;/h2&gt;

&lt;p&gt;Here's how I'm measuring whether nyxd actually delivers on its promises.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Startup latency:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hyperfine &lt;span class="nt"&gt;--warmup&lt;/span&gt; 3 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s1"&gt;'docker run --rm alpine true'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s1"&gt;'nyx run alpine true'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On my Pi 5: nyx vs Docker&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu4g46imxi4d978gzz4yj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu4g46imxi4d978gzz4yj.png" alt="nyx vs Docker Benchmark"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Attack surface check:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# On a Docker host: how many CNI plugins are sitting on disk&lt;/span&gt;
&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; /opt/cni/bin/ 2&amp;gt;/dev/null | &lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt;

&lt;span class="c"&gt;# Compare to nyxd default: no plugin dir required&lt;/span&gt;
lsof &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;pgrep &lt;span class="nt"&gt;-x&lt;/span&gt; nyxd&lt;span class="si"&gt;)&lt;/span&gt; | &lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt;   &lt;span class="c"&gt;# open file descriptors&lt;/span&gt;
ss &lt;span class="nt"&gt;-tlnp&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;nyxd                 &lt;span class="c"&gt;# listening sockets (usually just the Unix control socket)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Docker opens more sockets, more file descriptors, and maintains more persistent background connections than a Pi workload typically justifies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dependency tree:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# nyxd go.mod external deps&lt;/span&gt;
&lt;span class="nb"&gt;cat &lt;/span&gt;go.mod | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="s2"&gt;"^//"&lt;/span&gt;
&lt;span class="c"&gt;# 3 modules: image-spec, runtime-spec, golang.org/x/sys&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three. External. Modules. That's the entire dependency graph for the runtime and networking layer.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the Raspberry Pi Actually Taught Me
&lt;/h2&gt;

&lt;p&gt;The Pi has an interesting property: it forces you to care about things cloud infrastructure lets you ignore.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Thermal throttling&lt;/strong&gt; — when your CPU is running hot because your container daemon is doing background work, your actual workloads slow down. Less daemon overhead means cooler, more consistent performance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SD card / SSD wear&lt;/strong&gt; — fewer writes from logging, state management, and plugin communication extends storage life meaningfully on embedded deployments.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Boot time&lt;/strong&gt; — when a power cut hits an edge node, boot-to-operational time matters. A lighter stack comes up faster and can rejoin the network sooner.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Debugging under pressure&lt;/strong&gt; — when something breaks at 2AM on a remote node you can't physically access, a simpler stack is dramatically easier to reason about. Fewer layers means fewer places to look.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Power consumption&lt;/strong&gt; — I've measured ~1.5W difference in idle power between a full Docker stack and nyxd on a Pi 5. Across 10 nodes running 24/7, that's ~130kWh/year. Not enormous, but real.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where nyxd Is Right Now
&lt;/h2&gt;

&lt;p&gt;This section separates what the daemon and &lt;code&gt;nyx&lt;/code&gt; client exercise end-to-end from what exists mainly as libraries, stubs, or unfinished wiring.&lt;/p&gt;

&lt;h2&gt;
  
  
  Working (end-to-end in the daemon + &lt;code&gt;nyx&lt;/code&gt; client)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;OCI distribution pull&lt;/strong&gt; for public images: raw HTTP against registries, layer blobs, and &lt;strong&gt;SHA-256 digest verification on ingest&lt;/strong&gt; (digest mismatch fails the pull).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Overlayfs&lt;/strong&gt; upper/work/merged layout and teardown on container exit paths.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Container lifecycle via crun&lt;/strong&gt; from the supervisor: create/run (including the foreground attach path), &lt;strong&gt;stop&lt;/strong&gt;, &lt;strong&gt;kill&lt;/strong&gt;, &lt;strong&gt;delete&lt;/strong&gt;, and &lt;strong&gt;state&lt;/strong&gt; polling; the control API supports &lt;strong&gt;&lt;code&gt;nyx exec&lt;/code&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Restart policies&lt;/strong&gt;: &lt;strong&gt;always&lt;/strong&gt;, &lt;strong&gt;on-failure&lt;/strong&gt;, &lt;strong&gt;unless-stopped&lt;/strong&gt;, and &lt;strong&gt;never&lt;/strong&gt; (empty or unrecognized API values are normalized to conservative defaults in the control layer).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Structured JSON lines per container&lt;/strong&gt; under the daemon data directory (log collector plus &lt;code&gt;GET /v1/containers/{id}/logs&lt;/code&gt;), with optional plain-text decoding for attached clients.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Default in-process networking&lt;/strong&gt; (&lt;code&gt;-net-driver=native&lt;/code&gt;): bridge, veth, file-locked IPAM, &lt;strong&gt;nftables-based &lt;code&gt;-p&lt;/code&gt; / publish&lt;/strong&gt;, and host-side NAT — no CNI plugin binaries on disk.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Optional CNI exec path&lt;/strong&gt;: &lt;code&gt;-net-driver=cni&lt;/code&gt; with &lt;code&gt;-cni-bin-dir&lt;/code&gt;, &lt;code&gt;-cni-conf-dir&lt;/code&gt;, and &lt;code&gt;-network&lt;/code&gt; for a traditional plugin-driven stack instead of native.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Implemented but not product-complete (nuance)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Health checks (exec / HTTP / TCP)&lt;/strong&gt; — &lt;code&gt;internal/health&lt;/code&gt; implements a full checker (intervals, timeouts, retries, start period), but it is &lt;strong&gt;not hooked into &lt;code&gt;supervisor&lt;/code&gt;&lt;/strong&gt;: no checker goroutine started with each container, and no &lt;strong&gt;unhealthy → stop/restart&lt;/strong&gt; policy wiring. The code is &lt;strong&gt;library-ready&lt;/strong&gt;, not &lt;strong&gt;operator-ready&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prometheus-style metrics&lt;/strong&gt; — &lt;code&gt;internal/telemetry&lt;/code&gt; implements a text &lt;strong&gt;OpenMetrics/Prometheus-style&lt;/strong&gt; &lt;code&gt;/metrics&lt;/code&gt; handler, but &lt;strong&gt;&lt;code&gt;cmd/nyxd&lt;/code&gt; does not call &lt;code&gt;ServeMetrics&lt;/code&gt;&lt;/strong&gt;, so counters and gauges are not exposed unless another binary wires the package in. The format exists; the default daemon does not listen for scrape traffic.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Still being hardened / incomplete
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Registry auth beyond anonymous public pulls&lt;/strong&gt; — the current path centers on the &lt;strong&gt;Docker Hub anonymous token&lt;/strong&gt; flow; private registries, stored credentials, and arbitrary OAuth/OCI auth flows are &lt;strong&gt;not first-class&lt;/strong&gt; yet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compose&lt;/strong&gt; — YAML parsing is &lt;strong&gt;real&lt;/strong&gt; (&lt;code&gt;gopkg.in/yaml.v3&lt;/code&gt;): &lt;code&gt;internal/compose&lt;/code&gt; reads a &lt;strong&gt;strict subset&lt;/strong&gt; of compose-shaped fields, validates stacks, and can compute dependency order. What is missing is &lt;strong&gt;daemon-side orchestration&lt;/strong&gt; (no &lt;code&gt;nyx compose up&lt;/code&gt;-style command in the shipped mains): a parser is not a multi-service scheduler.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Control API and &lt;code&gt;nyx&lt;/code&gt; CLI&lt;/strong&gt; — Unix-socket HTTP for run, ps, logs, stop, remove, pull, images, and exec is usable, but error messages, edge cases, and long-term API stability are still evolving.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Seccomp&lt;/strong&gt; — generated bundles set &lt;strong&gt;capabilities&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;noNewPrivileges&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;masked paths&lt;/strong&gt;, and related hardening fields; there is &lt;strong&gt;no curated, versioned seccomp JSON checked into the bundle generator&lt;/strong&gt; yet. Anything beyond the explicit JSON is whatever &lt;strong&gt;crun&lt;/strong&gt; and the host apply by default.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;systemd-notify&lt;/code&gt;&lt;/strong&gt; — example units may use &lt;code&gt;Type=notify&lt;/code&gt;, but &lt;strong&gt;nyxd does not emit &lt;code&gt;READY=1&lt;/code&gt; (or reload state) via &lt;code&gt;sd_notify&lt;/code&gt;&lt;/strong&gt;. Production units should use &lt;strong&gt;&lt;code&gt;Type=simple&lt;/code&gt;&lt;/strong&gt; until notify is implemented, or notify must be added to the daemon.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NFT / port publishing&lt;/strong&gt; on unusual host sysctl values, exotic dual-stack setups, and odd bridge topologies — the native backend is real code, but it benefits from more soak time outside typical laptop and homelab bridges.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Bottom line
&lt;/h2&gt;

&lt;p&gt;nyxd is &lt;strong&gt;not&lt;/strong&gt; positioned as a drop-in, production-grade &lt;strong&gt;Docker replacement&lt;/strong&gt; today. The largest gaps between documentation and reality are &lt;strong&gt;health automation&lt;/strong&gt; (implemented package, not integrated) and &lt;strong&gt;metrics&lt;/strong&gt; (handler exists, not served by default). Registry authentication and compose &lt;strong&gt;orchestration&lt;/strong&gt; remain intentionally narrow.&lt;/p&gt;

&lt;p&gt;The stack &lt;strong&gt;does&lt;/strong&gt; run real workloads in lab settings along the paths above: pull, overlay, crun, native networking, structured logs, and restart policies. The architectural bet — &lt;strong&gt;native networking by default&lt;/strong&gt;, &lt;strong&gt;optional CNI&lt;/strong&gt; when operators want plugin ecosystems — remains coherent even where polish and operational breadth still lag.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Bigger Question
&lt;/h2&gt;

&lt;p&gt;I keep coming back to this:&lt;/p&gt;

&lt;p&gt;Are we over-engineering edge infrastructure?&lt;/p&gt;

&lt;p&gt;The modern container ecosystem was designed to solve orchestration at Google-scale. Kubernetes, CNI, CRI, OCI — these are all excellent standards that solved real problems.&lt;/p&gt;

&lt;p&gt;But those standards got adopted at every layer of the stack, including layers where the complexity isn't warranted.&lt;/p&gt;

&lt;p&gt;A Raspberry Pi running an Go API doesn't need the same infrastructure as a 10,000-node Kubernetes cluster.&lt;/p&gt;

&lt;p&gt;An industrial IoT gateway doesn't need BuildKit.&lt;/p&gt;

&lt;p&gt;A homelab monitoring stack doesn't need containerd's full plugin system.&lt;/p&gt;

&lt;p&gt;The standards are fine. The problem is using the full weight of the enterprise implementation everywhere, including at the edge where resource constraints and operational simplicity matter most.&lt;/p&gt;

&lt;p&gt;nyxd is my attempt to find where the floor is. How small can a correct, secure, production-capable OCI runtime stack actually be?&lt;/p&gt;

&lt;p&gt;I don't think we've found it yet.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try It / Follow Along
&lt;/h2&gt;

&lt;p&gt;nyxd is being developed openly. The codebase is Go 1.26.&lt;/p&gt;

&lt;p&gt;If you're running:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Raspberry Pi clusters&lt;/li&gt;
&lt;li&gt;ARM64 edge nodes&lt;/li&gt;
&lt;li&gt;Self-hosted systems&lt;/li&gt;
&lt;li&gt;VM , QEmu , Proxmox ...&lt;/li&gt;
&lt;li&gt;Any environment where RAM and attack surface actually matter&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'd genuinely love to hear what you're running and what constraints you're working within.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A few questions for the comments:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What does your container stack look like on ARM systems today?&lt;/li&gt;
&lt;li&gt;What's your idle memory baseline before deploying any workloads?&lt;/li&gt;
&lt;li&gt;Have you ever audited the CVE history of your CNI plugins?&lt;/li&gt;
&lt;li&gt;Would you trade orchestration features for a meaningfully smaller attack surface?&lt;/li&gt;
&lt;li&gt;Is your container stack heavier than your actual workloads?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If there's enough interest, follow-up posts will cover:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Full architecture walkthrough with the native network stack (and when I'd still flip on CNI)&lt;/li&gt;
&lt;li&gt;Memory profiling methodology for container daemons&lt;/li&gt;
&lt;li&gt;Security surface comparison methodology&lt;/li&gt;
&lt;li&gt;Deploying nyxd on a Pi cluster from scratch&lt;/li&gt;
&lt;li&gt;The case for writing your own IPAM in 200 lines of Go&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;nyxd is made by the community for the community - free to use for personal use&lt;/em&gt;&lt;/p&gt;

</description>
      <category>containers</category>
      <category>security</category>
      <category>raspberrypi</category>
      <category>homelab</category>
    </item>
    <item>
      <title>Meet Orion-Belt, Go ZeroTrust Bastion</title>
      <dc:creator>Mohamed Zrouga</dc:creator>
      <pubDate>Thu, 01 Jan 2026 20:46:39 +0000</pubDate>
      <link>https://dev.to/zrouga/meet-orion-belt-go-zerotrust-bastion-clp</link>
      <guid>https://dev.to/zrouga/meet-orion-belt-go-zerotrust-bastion-clp</guid>
      <description>&lt;p&gt;Stop opening Port 22 to the world. 🛑&lt;/p&gt;

&lt;p&gt;In the world of infrastructure, we’ve long accepted a "security tax." If you want your servers to be accessible, you either open holes in your firewall, maintain a complex VPN, or pay thousands for enterprise PAM (Privileged Access Management) tools. &lt;/p&gt;

&lt;p&gt;I felt there was a massive gap for a lightweight, developer-centric tool that follows &lt;strong&gt;Zero Trust&lt;/strong&gt; principles without the enterprise bloat. That’s why I built &lt;strong&gt;Orion-Belt&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Orion-Belt in Action
&lt;/h2&gt;

&lt;p&gt;Seeing is believing. Here is a quick look at &lt;code&gt;osh&lt;/code&gt; (the Orion-Belt SSH client) connecting to a machine that has &lt;strong&gt;zero inbound ports open&lt;/strong&gt;, while the gateway handles the heavy lifting of authentication and recording.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F74j5jjjdcyk6w7jq2wn6.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F74j5jjjdcyk6w7jq2wn6.gif" alt="Orion-Belt in Action" width="1200" height="415"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The "Security Tax" of Traditional Access
&lt;/h2&gt;

&lt;p&gt;Most teams handle remote server access in one of three ways, and all of them have a "catch":&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Static SSH Keys:&lt;/strong&gt; Great until a laptop is stolen or an employee leaves. Auditing "who did what" is nearly impossible.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;The "Jump Box" (Bastion):&lt;/strong&gt; A single point of failure. If your bastion is compromised, your whole network is exposed.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;VPNs:&lt;/strong&gt; They give "flat" network access. Once a user is on the VPN, they can often see everything, violating the &lt;strong&gt;Principle of Least Privilege&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I wanted something that felt like a modern SaaS (like Teleport or Boundary) but remained &lt;strong&gt;self-hosted, open-source, and dead simple.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Feature Comparison: Why Orion-Belt?
&lt;/h2&gt;

&lt;p&gt;How does Orion-Belt stack up against the status quo?&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Orion-Belt (Open Source)&lt;/th&gt;
&lt;th&gt;Traditional SSH/VPN&lt;/th&gt;
&lt;th&gt;Enterprise Gateways&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Inbound Firewall Rules&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;❌ No (Reverse Tunnel)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;✅ Yes (Port 22/VPN)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;❌ No (Agent/Tunnel)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Session Recording&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;✅ Yes (Built-in)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;❌ No (Hard to config)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;✅ Yes (Built-in)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Access Control&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;ReBAC&lt;/strong&gt; (Fine-Grained)&lt;/td&gt;
&lt;td&gt;Coarse-Grained&lt;/td&gt;
&lt;td&gt;RBAC/ABAC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Temporary Access&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;✅ Yes (JIT Approval)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;❌ No&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;✅ Yes&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Protocol Support&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;SSH, SCP&lt;/td&gt;
&lt;td&gt;SSH, SCP (VPN allows more)&lt;/td&gt;
&lt;td&gt;SSH, Kubernetes, Databases, HTTP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cost&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Free (Self-Hosted)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;$$$ High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Architecture&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Lightweight Go Binary&lt;/td&gt;
&lt;td&gt;Standard Utilities&lt;/td&gt;
&lt;td&gt;Complex Microservices&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  How it Works (Under the Hood)
&lt;/h2&gt;

&lt;p&gt;Orion-Belt is built on a &lt;strong&gt;Reverse SSH Tunnel&lt;/strong&gt; architecture. Instead of you reaching &lt;em&gt;into&lt;/em&gt; your private network, your servers reach &lt;em&gt;out&lt;/em&gt; to the Orion-Belt gateway.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;The Agent:&lt;/strong&gt; A small Go binary runs on your target VMs. It creates an outbound connection to your Orion-Belt server. &lt;/li&gt;
&lt;li&gt; &lt;strong&gt;The Gateway:&lt;/strong&gt; The "Brain." It handles authentication, ReBAC (Relationship-Based Access Control), and session recording.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;The Client (&lt;code&gt;osh&lt;/code&gt; / &lt;code&gt;ocp&lt;/code&gt;):&lt;/strong&gt; CLI tools that feel like standard SSH/SCP but verify permissions with the gateway’s API first.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Because the connection is outbound from the server to the gateway, &lt;strong&gt;you can keep Port 22 closed.&lt;/strong&gt; This effectively hides your infrastructure from automated bot scans and 0-day SSH exploits.&lt;/p&gt;




&lt;h2&gt;
  
  
  Key Features for Modern Teams
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. ReBAC (Relationship-Based Access Control)
&lt;/h3&gt;

&lt;p&gt;Orion-Belt checks the &lt;em&gt;relationship&lt;/em&gt; between the user and the resource. This allows for fine-grained permissions that scale as your team grows.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Session DVR-Style Replay
&lt;/h3&gt;

&lt;p&gt;Compliance (SOC2/HIPAA) requires seeing what happened during a session. Orion-Belt records every keystroke at the gateway level. You can replay the entire session later to see exactly what commands were run.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. JIT (Just-In-Time) Temporary Access
&lt;/h3&gt;

&lt;p&gt;Need a developer to debug a production issue for one hour?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;osh &lt;span class="nt"&gt;--request-access&lt;/span&gt; prod-db-01 &lt;span class="nt"&gt;--duration&lt;/span&gt; 1h &lt;span class="nt"&gt;--reason&lt;/span&gt; &lt;span class="s2"&gt;"Investigating latency"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Admins get a notification, approve the request, and the access automatically expires. No "orphaned" keys left behind.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Architecture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Client &lt;span class="o"&gt;(&lt;/span&gt;osh/ocp&lt;span class="o"&gt;)&lt;/span&gt;
      │
      ▼
Orion-Belt Gateway Server &lt;span class="o"&gt;(&lt;/span&gt;ReBAC + Session Recording&lt;span class="o"&gt;)&lt;/span&gt;
      │
      ▼ &lt;span class="o"&gt;(&lt;/span&gt;Reverse SSH Tunnel&lt;span class="o"&gt;)&lt;/span&gt;
Agent &lt;span class="o"&gt;(&lt;/span&gt;on your locked-down servers&lt;span class="o"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Quick Start
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Build from source:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/zrougamed/orion-belt.git
&lt;span class="nb"&gt;cd &lt;/span&gt;orion-belt &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; make build

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Start the Server:&lt;/strong&gt;&lt;br&gt;
The server acts as your central hub. It uses PostgreSQL to store sessions and permissions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Deploy the Agent:&lt;/strong&gt;&lt;br&gt;
Drop the agent binary on any server behind a firewall. Once it connects to the gateway, that server is accessible via &lt;code&gt;osh&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  I need your feedback!
&lt;/h2&gt;

&lt;p&gt;Orion-Belt is currently in &lt;strong&gt;Alpha&lt;/strong&gt;. It’s functional and stable, but I’m looking for early adopters to help shape the roadmap.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does this architecture fit your current workflow?&lt;/li&gt;
&lt;li&gt;What notification plugins would you like to see (Slack, Discord, Email)?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Check out the repo, leave a ⭐ if you like the concept, and let's discuss in the comments!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/zrougamed/orion-belt" rel="noopener noreferrer"&gt;https://github.com/zrougamed/orion-belt&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Final Thoughts&lt;/strong&gt;&lt;br&gt;
Infrastructure access doesn't need to be a choice between "easy" and "secure." By combining Go's performance with a Zero-Trust architecture, Orion-Belt makes high-end security accessible to everyone.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>security</category>
      <category>devops</category>
      <category>go</category>
    </item>
    <item>
      <title>Building a Security Test Lab with QEMU: From Zero to Network Monitoring</title>
      <dc:creator>Mohamed Zrouga</dc:creator>
      <pubDate>Fri, 26 Dec 2025 23:47:51 +0000</pubDate>
      <link>https://dev.to/zrouga/building-a-security-test-lab-with-qemu-from-zero-to-network-monitoring-4onm</link>
      <guid>https://dev.to/zrouga/building-a-security-test-lab-with-qemu-from-zero-to-network-monitoring-4onm</guid>
      <description>&lt;h2&gt;
  
  
  Why QEMU for Your Test Lab?
&lt;/h2&gt;

&lt;p&gt;As software engineers and security professionals, we constantly need isolated environments to test new tools, experiment with network configurations, or validate security measures before production deployment. While solutions like VirtualBox and VMware are popular, QEMU offers something special: lightweight, scriptable, headless operation perfect for automation and CI/CD pipelines.&lt;/p&gt;

&lt;p&gt;In this guide, I'll walk you through building a complete QEMU-based test lab focused on network security and infrastructure testing. By the end, you'll have a reproducible environment for testing firewalls, monitoring tools, and distributed systems.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We'll Build
&lt;/h2&gt;

&lt;p&gt;We're creating a multi-VM test lab that simulates a realistic network environment:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Gateway VM&lt;/strong&gt;: Acts as router/firewall &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Application VM&lt;/strong&gt;: Simulates production workloads&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Attacker VM&lt;/strong&gt;: For security testing and validation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This setup mirrors real-world scenarios where you need to test network policies, intrusion detection, or packet filtering.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before we start, ensure you have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A Linux host (Ubuntu/Debian in examples, but adaptable to any distro)&lt;/li&gt;
&lt;li&gt;At least 8GB RAM and 20GB free disk space&lt;/li&gt;
&lt;li&gt;Basic command-line familiarity&lt;/li&gt;
&lt;li&gt;Sudo access for network configuration&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1: Installing QEMU
&lt;/h2&gt;

&lt;p&gt;First, let's get QEMU installed with all necessary components:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Ubuntu/Debian&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;qemu-kvm qemu-utils libvirt-daemon-system virtinst bridge-utils

&lt;span class="c"&gt;# Verify installation&lt;/span&gt;
qemu-system-x86_64 &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For other distributions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Fedora/RHEL&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf &lt;span class="nb"&gt;install &lt;/span&gt;qemu-kvm qemu-img libvirt virt-install

&lt;span class="c"&gt;# Arch&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;pacman &lt;span class="nt"&gt;-S&lt;/span&gt; qemu libvirt virt-manager
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add your user to necessary groups to avoid constant sudo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;usermod &lt;span class="nt"&gt;-aG&lt;/span&gt; libvirt,kvm &lt;span class="nv"&gt;$USER&lt;/span&gt;
&lt;span class="c"&gt;# Log out and back in for changes to take effect&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Creating Your First VM - The Gateway
&lt;/h2&gt;

&lt;p&gt;Let's start with the gateway VM, which will be our network's control point.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating the Disk Image
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create a working directory&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/qemu-lab/images
&lt;span class="nb"&gt;cd&lt;/span&gt; ~/qemu-lab

&lt;span class="c"&gt;# Create a 20GB disk image (qcow2 format for snapshots)&lt;/span&gt;
qemu-img create &lt;span class="nt"&gt;-f&lt;/span&gt; qcow2 images/gateway.qcow2 20G
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why qcow2? It supports snapshots, copy-on-write, and compression - essential for test environments where you'll want to reset frequently.&lt;/p&gt;

&lt;h3&gt;
  
  
  Download a Minimal OS
&lt;/h3&gt;

&lt;p&gt;For security testing, I recommend Alpine Linux (lightweight) or Debian netinst:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Alpine Linux (very lightweight, boots in seconds)&lt;/span&gt;
wget https://dl-cdn.alpinelinux.org/alpine/v3.23/releases/x86_64/alpine-virt-3.23.0-x86_64.iso &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-O&lt;/span&gt; images/alpine.iso

&lt;span class="c"&gt;# Or Debian if you prefer more familiar tools&lt;/span&gt;
&lt;span class="c"&gt;# wget https://cdimage.debian.org/debian-cd/current/amd64/iso-cd/debian-13.2.0-amd64-netinst.iso \&lt;/span&gt;
&lt;span class="c"&gt;#   -O images/debian.iso&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  First Boot and Installation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;qemu-system-x86_64 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-name&lt;/span&gt; gateway &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-m&lt;/span&gt; 1024 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-smp&lt;/span&gt; 2 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-hda&lt;/span&gt; images/gateway.qcow2 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-cdrom&lt;/span&gt; images/alpine.iso &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-boot&lt;/span&gt; d &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-enable-kvm&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-nic&lt;/span&gt; user,model&lt;span class="o"&gt;=&lt;/span&gt;virtio &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-nographic&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Flag breakdown:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;-name gateway&lt;/code&gt;: Identifies your VM&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-m 1024&lt;/code&gt;: 1GB RAM (adjust based on your needs)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-smp 2&lt;/code&gt;: 2 CPU cores&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-hda&lt;/code&gt;: Primary disk&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-cdrom&lt;/code&gt;: Installation media&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-boot d&lt;/code&gt;: Boot from CD-ROM first&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-enable-kvm&lt;/code&gt;: Hardware acceleration (crucial for performance)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-nic user&lt;/code&gt;: Simple NAT networking for installation&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-nographic&lt;/code&gt;: Console mode (no GUI needed)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Quick Alpine Installation:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Once booted, login as root (no password)&lt;/span&gt;
setup-alpine

&lt;span class="c"&gt;# Follow prompts:&lt;/span&gt;
&lt;span class="c"&gt;# - Keyboard: us&lt;/span&gt;
&lt;span class="c"&gt;# - Hostname: gateway&lt;/span&gt;
&lt;span class="c"&gt;# - Network: eth0, dhcp&lt;/span&gt;
&lt;span class="c"&gt;# - Password: (set a strong one)&lt;/span&gt;
&lt;span class="c"&gt;# - Timezone: your timezone&lt;/span&gt;
&lt;span class="c"&gt;# - Disk: sda, sys (use entire disk)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After installation completes, power off the VM (type &lt;code&gt;poweroff&lt;/code&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Setting Up Advanced Networking
&lt;/h2&gt;

&lt;p&gt;This is where QEMU shines for security testing. We'll create an isolated network that simulates real infrastructure.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create a Network Configuration Script
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create scripts directory&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/qemu-lab/scripts

&lt;span class="c"&gt;# Network setup script&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ~/qemu-lab/scripts/setup-network.sh &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
#!/bin/bash

# Create an isolated bridge for our test lab
sudo ip link add name br-testlab type bridge
sudo ip addr add 10.0.100.1/24 dev br-testlab
sudo ip link set br-testlab up

# Enable IP forwarding (so gateway can route)
sudo sysctl -w net.ipv4.ip_forward=1

# Create TAP interfaces for VMs
for i in 0 1 2; do
  sudo ip tuntap add tap&lt;/span&gt;&lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="sh"&gt; mode tap user &lt;/span&gt;&lt;span class="nv"&gt;$USER&lt;/span&gt;&lt;span class="sh"&gt;
  sudo ip link set tap&lt;/span&gt;&lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="sh"&gt; master br-testlab
  sudo ip link set tap&lt;/span&gt;&lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="sh"&gt; up
done

echo "Test lab network ready: 10.0.100.0/24 on br-testlab"
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x ~/qemu-lab/scripts/setup-network.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you will end up with this network topology&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;10.0.100.1/24 Network
       |
   [Bridge](br-testlab)
    /   |    \
  TAP0 TAP1 TAP2
  VM1  VM2   VM3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Characteristics:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;VMs are on SAME subnet as host&lt;/li&gt;
&lt;li&gt;VMs are "visible" to external network&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Network Teardown Script
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ~/qemu-lab/scripts/teardown-network.sh &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
#!/bin/bash

# Remove TAP interfaces
for i in 0 1 2; do
  sudo ip link set tap&lt;/span&gt;&lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="sh"&gt; down 2&amp;gt;/dev/null
  sudo ip link delete tap&lt;/span&gt;&lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="sh"&gt; 2&amp;gt;/dev/null
done

# Remove bridge
sudo ip link set br-testlab down 2&amp;gt;/dev/null
sudo ip link delete br-testlab 2&amp;gt;/dev/null

echo "Test lab network removed"
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x ~/qemu-lab/scripts/teardown-network.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the setup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;~/qemu-lab/scripts/setup-network.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 4: Creating VM Launch Scripts
&lt;/h2&gt;

&lt;p&gt;Rather than typing long QEMU commands, let's create reusable launch scripts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Gateway VM Script
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ~/qemu-lab/scripts/run-gateway.sh &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
#!/bin/bash

qemu-system-x86_64 &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
  -name gateway &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
  -m 1024 &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
  -smp 2 &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
  -hda ~/qemu-lab/images/gateway.qcow2 &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
  -enable-kvm &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
  -netdev tap,id=net0,ifname=tap0,script=no,downscript=no &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
  -device virtio-net-pci,netdev=net0,mac=52:54:00:12:34:00 &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
  -netdev user,id=net1 &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
  -device virtio-net-pci,netdev=net1,mac=52:54:00:12:34:01 &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
  -nographic &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
  -serial mon:stdio
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x ~/qemu-lab/scripts/run-gateway.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gateway has TWO network interfaces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;net0&lt;/code&gt;: Connected to internal test network (10.0.100.0/24)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;net1&lt;/code&gt;: NAT to internet for updates&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Quick Clone Function for Additional VMs
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create application VM from gateway template&lt;/span&gt;
qemu-img create &lt;span class="nt"&gt;-f&lt;/span&gt; qcow2 &lt;span class="nt"&gt;-b&lt;/span&gt; images/gateway.qcow2 &lt;span class="nt"&gt;-F&lt;/span&gt; qcow2 images/app.qcow2

&lt;span class="c"&gt;# Create attacker VM&lt;/span&gt;
qemu-img create &lt;span class="nt"&gt;-f&lt;/span&gt; qcow2 &lt;span class="nt"&gt;-b&lt;/span&gt; images/gateway.qcow2 &lt;span class="nt"&gt;-F&lt;/span&gt; qcow2 images/attacker.qcow2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These are &lt;strong&gt;backing images&lt;/strong&gt; - they only store differences from the base, saving massive disk space.&lt;/p&gt;

&lt;h3&gt;
  
  
  Application VM Script
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ~/qemu-lab/scripts/run-app.sh &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
#!/bin/bash

qemu-system-x86_64 &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
  -name app-server &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
  -m 2048 &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
  -smp 2 &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
  -hda ~/qemu-lab/images/app.qcow2 &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
  -enable-kvm &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
  -netdev tap,id=net0,ifname=tap1,script=no,downscript=no &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
  -device virtio-net-pci,netdev=net0,mac=52:54:00:12:34:10 &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
  -nographic &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
  -serial mon:stdio
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x ~/qemu-lab/scripts/run-app.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Attacker VM Script
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ~/qemu-lab/scripts/run-attacker.sh &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
#!/bin/bash

qemu-system-x86_64 &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
  -name attacker &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
  -m 1024 &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
  -smp 2 &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
  -hda ~/qemu-lab/images/attacker.qcow2 &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
  -enable-kvm &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
  -netdev tap,id=net0,ifname=tap2,script=no,downscript=no &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
  -device virtio-net-pci,netdev=net0,mac=52:54:00:12:34:20 &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
  -nographic &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
  -serial mon:stdio
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x ~/qemu-lab/scripts/run-attacker.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 5: Network Configuration Inside VMs
&lt;/h2&gt;

&lt;p&gt;Start each VM and configure networking:&lt;/p&gt;

&lt;h3&gt;
  
  
  Gateway VM (10.0.100.1)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Start gateway&lt;/span&gt;
~/qemu-lab/scripts/run-gateway.sh

&lt;span class="c"&gt;# Inside VM - configure interfaces&lt;/span&gt;
&lt;span class="c"&gt;# For Alpine Linux:&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /etc/network/interfaces &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
auto lo
iface lo inet loopback

auto eth0
iface eth0 inet static
    address 10.0.100.1
    netmask 255.255.255.0

auto eth1
iface eth1 inet dhcp
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;&lt;span class="c"&gt;# Restart networking&lt;/span&gt;
rc-service networking restart

&lt;span class="c"&gt;# Enable forwarding permanently&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'net.ipv4.ip_forward = 1'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /etc/sysctl.conf
sysctl &lt;span class="nt"&gt;-p&lt;/span&gt;

&lt;span class="c"&gt;# Setup basic NAT (so internal VMs can reach internet via gateway)&lt;/span&gt;
apk add iptables
iptables &lt;span class="nt"&gt;-t&lt;/span&gt; nat &lt;span class="nt"&gt;-A&lt;/span&gt; POSTROUTING &lt;span class="nt"&gt;-o&lt;/span&gt; eth1 &lt;span class="nt"&gt;-j&lt;/span&gt; MASQUERADE
iptables &lt;span class="nt"&gt;-A&lt;/span&gt; FORWARD &lt;span class="nt"&gt;-i&lt;/span&gt; eth0 &lt;span class="nt"&gt;-o&lt;/span&gt; eth1 &lt;span class="nt"&gt;-j&lt;/span&gt; ACCEPT
iptables &lt;span class="nt"&gt;-A&lt;/span&gt; FORWARD &lt;span class="nt"&gt;-i&lt;/span&gt; eth1 &lt;span class="nt"&gt;-o&lt;/span&gt; eth0 &lt;span class="nt"&gt;-m&lt;/span&gt; state &lt;span class="nt"&gt;--state&lt;/span&gt; RELATED,ESTABLISHED &lt;span class="nt"&gt;-j&lt;/span&gt; ACCEPT

&lt;span class="c"&gt;# Save iptables rules&lt;/span&gt;
rc-update add iptables
/etc/init.d/iptables save
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Application VM (10.0.100.10)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Configure static IP&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /etc/network/interfaces &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
auto lo
iface lo inet loopback

auto eth0
iface eth0 inet static
    address 10.0.100.10
    netmask 255.255.255.0
    gateway 10.0.100.1
    dns-nameservers 8.8.8.8
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;rc-service networking restart
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Attacker VM (10.0.100.20)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Configure static IP&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /etc/network/interfaces &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
auto lo
iface lo inet loopback

auto eth0
iface eth0 inet static
    address 10.0.100.20
    netmask 255.255.255.0
    gateway 10.0.100.1
    dns-nameservers 8.8.8.8
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;rc-service networking restart

&lt;span class="c"&gt;# Install security testing tools&lt;/span&gt;
apk add nmap tcpdump hping3 netcat-openbsd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 6: Snapshots for Quick Resets
&lt;/h2&gt;

&lt;p&gt;One of QEMU's killer features - instant rollback:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create a "clean state" snapshot for each VM&lt;/span&gt;
qemu-img snapshot &lt;span class="nt"&gt;-c&lt;/span&gt; clean-install images/gateway.qcow2
qemu-img snapshot &lt;span class="nt"&gt;-c&lt;/span&gt; clean-install images/app.qcow2
qemu-img snapshot &lt;span class="nt"&gt;-c&lt;/span&gt; clean-install images/attacker.qcow2

&lt;span class="c"&gt;# After testing, rollback to clean state&lt;/span&gt;
qemu-img snapshot &lt;span class="nt"&gt;-a&lt;/span&gt; clean-install images/gateway.qcow2

&lt;span class="c"&gt;# List all snapshots&lt;/span&gt;
qemu-img snapshot &lt;span class="nt"&gt;-l&lt;/span&gt; images/gateway.qcow2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 7: Practical Use Cases
&lt;/h2&gt;

&lt;p&gt;Now your lab is ready! Here are some real-world scenarios:&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing Network Monitoring Tools
&lt;/h3&gt;

&lt;p&gt;On the gateway VM, install your monitoring solution:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Example: tcpdump for packet analysis&lt;/span&gt;
apk add tcpdump

&lt;span class="c"&gt;# Capture traffic between app and attacker&lt;/span&gt;
tcpdump &lt;span class="nt"&gt;-i&lt;/span&gt; eth0 &lt;span class="nt"&gt;-w&lt;/span&gt; /tmp/capture.pcap

&lt;span class="c"&gt;# Or install eBPF-based tools for advanced monitoring&lt;/span&gt;
&lt;span class="c"&gt;# (Great for testing tools like Cilium, Falco, or custom eBPF monitors like Cerberus)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Simulating Attack Scenarios
&lt;/h3&gt;

&lt;p&gt;From attacker VM:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Port scan the application&lt;/span&gt;
nmap &lt;span class="nt"&gt;-sV&lt;/span&gt; 10.0.100.10

&lt;span class="c"&gt;# Test firewall rules&lt;/span&gt;
hping3 &lt;span class="nt"&gt;-S&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 80 10.0.100.10

&lt;span class="c"&gt;# Monitor on gateway to see what's blocked&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Testing Firewall Rules
&lt;/h3&gt;

&lt;p&gt;On gateway, add restrictive rules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Block all traffic from attacker to app&lt;/span&gt;
iptables &lt;span class="nt"&gt;-I&lt;/span&gt; FORWARD &lt;span class="nt"&gt;-s&lt;/span&gt; 10.0.100.20 &lt;span class="nt"&gt;-d&lt;/span&gt; 10.0.100.10 &lt;span class="nt"&gt;-j&lt;/span&gt; DROP

&lt;span class="c"&gt;# Allow only SSH&lt;/span&gt;
iptables &lt;span class="nt"&gt;-I&lt;/span&gt; FORWARD &lt;span class="nt"&gt;-s&lt;/span&gt; 10.0.100.20 &lt;span class="nt"&gt;-d&lt;/span&gt; 10.0.100.10 &lt;span class="nt"&gt;-p&lt;/span&gt; tcp &lt;span class="nt"&gt;--dport&lt;/span&gt; 22 &lt;span class="nt"&gt;-j&lt;/span&gt; ACCEPT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Container Testing
&lt;/h3&gt;

&lt;p&gt;Install Docker on the app VM to test container networking and security policies in isolation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 8: Automation and Management
&lt;/h2&gt;

&lt;p&gt;Create a master control script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ~/qemu-lab/manage-lab.sh &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
#!/bin/bash

case "&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="sh"&gt;" in
  start)
    ./scripts/setup-network.sh
    echo "Starting VMs in tmux sessions..."
    tmux new-session -d -s gateway './scripts/run-gateway.sh'
    tmux new-session -d -s app './scripts/run-app.sh'
    tmux new-session -d -s attacker './scripts/run-attacker.sh'
    echo "Lab started. Attach with: tmux attach -t &amp;lt;gateway|app|attacker&amp;gt;"
    ;;
  stop)
    echo "Stopping all VMs..."
    tmux kill-session -t gateway 2&amp;gt;/dev/null
    tmux kill-session -t app 2&amp;gt;/dev/null
    tmux kill-session -t attacker 2&amp;gt;/dev/null
    ./scripts/teardown-network.sh
    ;;
  reset)
    echo "Resetting all VMs to clean snapshot..."
    qemu-img snapshot -a clean-install images/gateway.qcow2
    qemu-img snapshot -a clean-install images/app.qcow2
    qemu-img snapshot -a clean-install images/attacker.qcow2
    echo "Reset complete"
    ;;
  *)
    echo "Usage: &lt;/span&gt;&lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="sh"&gt; {start|stop|reset}"
    exit 1
    ;;
esac
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x ~/qemu-lab/manage-lab.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Usage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Start entire lab&lt;/span&gt;
./manage-lab.sh start

&lt;span class="c"&gt;# Access any VM&lt;/span&gt;
tmux attach &lt;span class="nt"&gt;-t&lt;/span&gt; gateway

&lt;span class="c"&gt;# Reset everything to clean state&lt;/span&gt;
./manage-lab.sh reset

&lt;span class="c"&gt;# Shut down cleanly&lt;/span&gt;
./manage-lab.sh stop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Performance Tips
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Always use KVM acceleration&lt;/strong&gt; (&lt;code&gt;-enable-kvm&lt;/code&gt;) - it's 10-20x faster than emulation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Allocate appropriate resources&lt;/strong&gt; - Don't over-provision RAM&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use virtio drivers&lt;/strong&gt; - Much faster than emulated hardware&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;qcow2 for flexibility, raw for performance&lt;/strong&gt; - Use raw images if speed is critical&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CPU pinning for consistency&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="nt"&gt;-smp&lt;/span&gt; 2,sockets&lt;span class="o"&gt;=&lt;/span&gt;1,cores&lt;span class="o"&gt;=&lt;/span&gt;2,threads&lt;span class="o"&gt;=&lt;/span&gt;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Troubleshooting Common Issues
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;VM won't start with KVM error:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check if KVM is available&lt;/span&gt;
lsmod | &lt;span class="nb"&gt;grep &lt;/span&gt;kvm
&lt;span class="c"&gt;# If not, enable in BIOS (Intel VT-x or AMD-V)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Network not working:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Verify TAP interfaces are up&lt;/span&gt;
ip &lt;span class="nb"&gt;link &lt;/span&gt;show | &lt;span class="nb"&gt;grep &lt;/span&gt;tap

&lt;span class="c"&gt;# Check bridge configuration&lt;/span&gt;
bridge &lt;span class="nb"&gt;link &lt;/span&gt;show br-testlab
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Cannot connect to VMs:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# From host, ping the bridge IP&lt;/span&gt;
ping 10.0.100.1

&lt;span class="c"&gt;# Check iptables isn't blocking&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;iptables &lt;span class="nt"&gt;-L&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Performance is slow:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Verify KVM is actually being used&lt;/span&gt;
ps aux | &lt;span class="nb"&gt;grep &lt;/span&gt;qemu | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; kvm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Next Steps and Advanced Topics
&lt;/h2&gt;

&lt;p&gt;Your test lab is now ready for serious work! Consider exploring:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ansible automation&lt;/strong&gt; - Provision VMs automatically&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cloud-init integration&lt;/strong&gt; - Pre-configure VMs on first boot&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PXE boot testing&lt;/strong&gt; - Network installation scenarios&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-host clustering&lt;/strong&gt; - Test Kubernetes or Docker Swarm&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;VPN testing&lt;/strong&gt; - Simulate site-to-site connections&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;VLAN tagging&lt;/strong&gt; - Complex network segmentation&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;QEMU provides a powerful, scriptable foundation for testing network security tools and infrastructure. Unlike heavyweight alternatives, it integrates seamlessly into CI/CD pipelines, supports headless operation, and gives you complete control over the network topology.&lt;/p&gt;

&lt;p&gt;The lab we built mirrors production environments while remaining completely isolated and reproducible. Use it to validate security tools, test infrastructure changes, or experiment with new technologies risk-free.&lt;/p&gt;

&lt;p&gt;All scripts and configurations from this guide are available in my GitHub &lt;a href="https://github.com/zrougamed/blog" rel="noopener noreferrer"&gt;https://github.com/zrougamed/blog&lt;/a&gt; .&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What will you test first in your new lab?&lt;/strong&gt; Drop a comment below!&lt;/p&gt;




&lt;p&gt;About the author: I'm a Senior Software Engineer at Deltaflare, where we work on the Phoenix Platform protecting Critical National Infrastructure across energy, water, and transport sectors. I use QEMU extensively for testing security tools and validating infrastructure changes before production deployment in CNI environments. You can find me at &lt;a href="https://zrouga.email" rel="noopener noreferrer"&gt;https://zrouga.email&lt;/a&gt; or check out my open-source work on GitHub.&lt;/p&gt;

</description>
      <category>qemu</category>
      <category>security</category>
      <category>devops</category>
      <category>networking</category>
    </item>
    <item>
      <title>Building a Real-Time Network Monitor with eBPF: Lessons from Cerberus</title>
      <dc:creator>Mohamed Zrouga</dc:creator>
      <pubDate>Sat, 20 Dec 2025 18:09:12 +0000</pubDate>
      <link>https://dev.to/zrouga/building-a-real-time-network-monitor-with-ebpf-lessons-from-cerberus-mm</link>
      <guid>https://dev.to/zrouga/building-a-real-time-network-monitor-with-ebpf-lessons-from-cerberus-mm</guid>
      <description>&lt;p&gt;As a platform engineer working with critical infrastructure, I needed deep visibility into network behavior without the overhead of traditional packet capture tools. The result? &lt;a href="https://github.com/zrougamed/cerberus" rel="noopener noreferrer"&gt;Cerberus&lt;/a&gt; - an eBPF-based network monitor that processes packets at the kernel level with near-zero overhead.&lt;/p&gt;

&lt;p&gt;This post walks through the architecture, challenges, and lessons learned from building a production-grade network monitoring tool using eBPF and Go.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdwrfb9c0zaiprgquvsav.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdwrfb9c0zaiprgquvsav.png" alt="Cerberus in Action" width="800" height="428"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why eBPF for Network Monitoring?
&lt;/h2&gt;

&lt;p&gt;Traditional network monitoring tools like &lt;code&gt;tcpdump&lt;/code&gt; and &lt;code&gt;Wireshark&lt;/code&gt; are powerful but come with drawbacks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Context switches&lt;/strong&gt;: Every packet copies data from kernel to user space&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance overhead&lt;/strong&gt;: Processing in user space is slower&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security&lt;/strong&gt;: Full packet capture requires elevated privileges everywhere&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scalability&lt;/strong&gt;: High-traffic networks can overwhelm traditional tools&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;eBPF (Extended Berkeley Packet Filter) solves these by running sandboxed programs directly in the Linux kernel, allowing you to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Filter and process packets at line rate&lt;/li&gt;
&lt;li&gt;Extract only the data you need (no full packet copies)&lt;/li&gt;
&lt;li&gt;Aggregate statistics in kernel space&lt;/li&gt;
&lt;li&gt;Minimize context switches with ring buffers&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Architecture Overview
&lt;/h2&gt;

&lt;p&gt;Cerberus uses a two-layer architecture:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyn4bh5ae5d2slrih7ay3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyn4bh5ae5d2slrih7ay3.png" alt="Cerberus user space and Kernel space" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The eBPF Program: Kernel-Space Magic
&lt;/h2&gt;

&lt;p&gt;The heart of Cerberus is a TC (Traffic Control) classifier written in C that attaches to network interfaces. Here's what it does:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Packet Parsing at Line Rate
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;network_event&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;__u8&lt;/span&gt; &lt;span class="n"&gt;event_type&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;       &lt;span class="c1"&gt;// Event classification&lt;/span&gt;
    &lt;span class="n"&gt;__u8&lt;/span&gt; &lt;span class="n"&gt;src_mac&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;       &lt;span class="c1"&gt;// Source MAC&lt;/span&gt;
    &lt;span class="n"&gt;__u8&lt;/span&gt; &lt;span class="n"&gt;dst_mac&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;       &lt;span class="c1"&gt;// Destination MAC&lt;/span&gt;
    &lt;span class="n"&gt;__u32&lt;/span&gt; &lt;span class="n"&gt;src_ip&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;          &lt;span class="c1"&gt;// Source IP&lt;/span&gt;
    &lt;span class="n"&gt;__u32&lt;/span&gt; &lt;span class="n"&gt;dst_ip&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;          &lt;span class="c1"&gt;// Destination IP&lt;/span&gt;
    &lt;span class="n"&gt;__u16&lt;/span&gt; &lt;span class="n"&gt;src_port&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;        &lt;span class="c1"&gt;// Source port&lt;/span&gt;
    &lt;span class="n"&gt;__u16&lt;/span&gt; &lt;span class="n"&gt;dst_port&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;        &lt;span class="c1"&gt;// Destination port&lt;/span&gt;
    &lt;span class="n"&gt;__u8&lt;/span&gt; &lt;span class="n"&gt;protocol&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;         &lt;span class="c1"&gt;// IP protocol&lt;/span&gt;
    &lt;span class="n"&gt;__u8&lt;/span&gt; &lt;span class="n"&gt;tcp_flags&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;        &lt;span class="c1"&gt;// TCP flags&lt;/span&gt;
    &lt;span class="n"&gt;__u16&lt;/span&gt; &lt;span class="n"&gt;arp_op&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;          &lt;span class="c1"&gt;// ARP operation&lt;/span&gt;
    &lt;span class="n"&gt;__u8&lt;/span&gt; &lt;span class="n"&gt;icmp_type&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;        &lt;span class="c1"&gt;// ICMP type&lt;/span&gt;
    &lt;span class="n"&gt;__u8&lt;/span&gt; &lt;span class="n"&gt;l7_payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;   &lt;span class="c1"&gt;// Layer 7 inspection data&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;__attribute__&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;packed&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key Design Decision&lt;/strong&gt;: We only capture 75 bytes per event. This is enough for classification and L7 inspection without the overhead of full packet capture.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Multi-Protocol Detection
&lt;/h3&gt;

&lt;p&gt;The eBPF program identifies 7 different protocol types:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Simplified version of the protocol detection logic&lt;/span&gt;
&lt;span class="n"&gt;SEC&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"tc"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;monitor_ingress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;__sk_buff&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;skb&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;skb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;data_end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;skb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;data_end&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;ethhdr&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;eth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;eth&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;data_end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;TC_ACT_OK&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// ARP detection&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;eth&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;h_proto&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;bpf_htons&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ETH_P_ARP&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;handle_arp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;skb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;eth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data_end&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// IP packet processing&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;eth&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;h_proto&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;bpf_htons&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ETH_P_IP&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;iphdr&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;eth&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;ip&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;data_end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;TC_ACT_OK&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ip&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;protocol&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;IPPROTO_TCP&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;handle_tcp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;skb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;eth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data_end&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;IPPROTO_UDP&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;handle_udp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;skb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;eth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data_end&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;IPPROTO_ICMP&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;handle_icmp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;skb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;eth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data_end&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;TC_ACT_OK&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Layer 7 Inspection
&lt;/h3&gt;

&lt;p&gt;Here's where it gets interesting. We extract application-layer data for DNS, HTTP, and TLS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// DNS query extraction&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;__always_inline&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;handle_dns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;__sk_buff&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;skb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                                       &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;udphdr&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;udp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                       &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;data_end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;dns_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;udp&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Copy up to 32 bytes of DNS payload&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dns_data&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;data_end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;bpf_probe_read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;l7_payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dns_data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;event_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;EVENT_DNS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;bpf_ringbuf_submit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;TC_ACT_OK&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// HTTP detection (simplified)&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;__always_inline&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;handle_http&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;__sk_buff&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;skb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                        &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;tcphdr&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;tcp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                        &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;data_end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;tcp&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Check for HTTP methods&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http_data&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;data_end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="n"&gt;bpf_probe_read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http_data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sc"&gt;'G'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sc"&gt;'E'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; 
            &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sc"&gt;'T'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sc"&gt;' '&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;event_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;EVENT_HTTP&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;bpf_probe_read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;l7_payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http_data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;TC_ACT_OK&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// TLS handshake detection&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;__always_inline&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;handle_tls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;__sk_buff&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;skb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                       &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;tcphdr&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;tcp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                       &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;data_end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;tls_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;tcp&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tls_data&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;data_end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;__u8&lt;/span&gt; &lt;span class="n"&gt;content_type&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;bpf_probe_read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;content_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tls_data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// 0x16 = Handshake&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content_type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mh"&gt;0x16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;event_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;EVENT_TLS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;bpf_probe_read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;l7_payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tls_data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;TC_ACT_OK&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Challenge #1: The Verifier&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The eBPF verifier is strict. Every memory access must be bounds-checked:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// This will fail verification:&lt;/span&gt;
&lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;packet&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// ❌ No bounds check&lt;/span&gt;

&lt;span class="c1"&gt;// This passes:&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;packet&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;offset&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;data_end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;packet&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// ✅ Verified safe&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  User-Space Processing: Go Application
&lt;/h2&gt;

&lt;p&gt;The Go application reads events from the ring buffer and performs higher-level analysis:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Ring Buffer Polling
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;NetworkMonitor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;pollEvents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InitRingBuf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"events"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;eventsChannel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Poll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;300&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// 300ms timeout&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;eventsChannel&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;processEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Smart Deduplication with LRU Cache
&lt;/h3&gt;

&lt;p&gt;To avoid alert fatigue, we only show new traffic patterns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;NetworkMonitor&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;deviceCache&lt;/span&gt;  &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;lru&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cache&lt;/span&gt;        &lt;span class="c"&gt;// Device tracking&lt;/span&gt;
    &lt;span class="n"&gt;patternCache&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;lru&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cache&lt;/span&gt;        &lt;span class="c"&gt;// Unique communication patterns&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;           &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;buntdb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DB&lt;/span&gt;        &lt;span class="c"&gt;// Persistent storage&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;NetworkMonitor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;processEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;parseNetworkEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// Track device&lt;/span&gt;
    &lt;span class="n"&gt;deviceKey&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SrcMAC&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deviceCache&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;deviceKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;announceNewDevice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deviceCache&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;deviceKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Track pattern (first occurrence only)&lt;/span&gt;
    &lt;span class="n"&gt;patternKey&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"%s:%s:%d:%s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SrcMAC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DstIP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DstPort&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EventType&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patternCache&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;patternKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;printTrafficPattern&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patternCache&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;patternKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Update statistics&lt;/span&gt;
    &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;updateStats&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Layer 7 Parsing in User Space
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;parseL7Payload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;NetworkEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EventType&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;EVENT_DNS&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;parseDNSQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;L7Payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;EVENT_HTTP&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;parseHTTPRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;L7Payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;EVENT_TLS&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"TLS"&lt;/span&gt;
    &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;parseDNSQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Skip DNS header (12 bytes)&lt;/span&gt;
    &lt;span class="n"&gt;offset&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;12&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;domain&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;offset&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;domain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sc"&gt;'.'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;domain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;offset&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Performance Optimizations
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Zero-Copy with Ring Buffers
&lt;/h3&gt;

&lt;p&gt;Ring buffers allow the kernel to write directly to memory shared with user space:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// No data copying - just reading shared memory&lt;/span&gt;
&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Poll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;300&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Batch Database Writes
&lt;/h3&gt;

&lt;p&gt;Instead of writing every event to disk, we batch them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;NetworkMonitor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;persistStats&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ticker&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewTicker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;30&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;ticker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;ticker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;C&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tx&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;buntdb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;mac&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stats&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deviceStats&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Marshal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stats&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mac&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. LRU Cache Tuning
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Balance memory vs. accuracy&lt;/span&gt;
&lt;span class="n"&gt;deviceCache&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;lru&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c"&gt;// Track 1000 devices&lt;/span&gt;
&lt;span class="n"&gt;patternCache&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;lru&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// Track 10k patterns&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Real-World Output
&lt;/h2&gt;

&lt;p&gt;Here's what you see when running Cerberus:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NEW DEVICE DETECTED!
   MAC:     dc:62:79:2f:39:28
   IP:      192.168.0.108
   Vendor:  Apple
   First Seen: 2024-12-06 16:51:12

[DNS] 192.168.0.100 (aa:bb:cc:dd:ee:ff) [Apple] → 8.8.8.8:53 [google.com]
[HTTP] 192.168.0.100 (aa:bb:cc:dd:ee:ff) [Apple] → 93.184.216.34:80 [GET /api/v1/users]
[TLS] 192.168.0.100 (aa:bb:cc:dd:ee:ff) [Apple] → 142.250.185.46:443 [TLS]
[TCP] 192.168.0.50 (11:22:33:44:55:66) [Raspberry Pi] → 192.168.0.200:22 (SSH)

╔════════════════════════════════════════════════════╗
║         NETWORK STATISTICS SUMMARY                 ║
╠════════════════════════════════════════════════════╣
║ Total Devices: 15                                  ║
║ Total Packets: 45821                               ║
║   - TCP:  38456                                    ║
║   - UDP:  6120                                     ║
║   - DNS:  892                                      ║
║   - HTTP: 156                                      ║
║   - TLS:  1834                                     ║
╚════════════════════════════════════════════════════╝
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Challenges and Solutions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Challenge #1: eBPF Complexity Limit
&lt;/h3&gt;

&lt;p&gt;eBPF programs are limited to ~1 million instructions. Solution: Keep parsing logic simple and move complex analysis to user space.&lt;/p&gt;

&lt;h3&gt;
  
  
  Challenge #2: Payload Size Tradeoff
&lt;/h3&gt;

&lt;p&gt;More payload = better L7 inspection, but higher overhead. Solution: 32 bytes is enough for DNS queries, HTTP methods, and TLS detection.&lt;/p&gt;

&lt;h3&gt;
  
  
  Challenge #3: TC vs XDP
&lt;/h3&gt;

&lt;p&gt;Initially considered XDP (eXpress Data Path) but TC offered better compatibility and easier development. XDP is faster but more restrictive.&lt;/p&gt;

&lt;h3&gt;
  
  
  Challenge #4: Cross-Kernel Compatibility
&lt;/h3&gt;

&lt;p&gt;Different kernel versions support different eBPF features. Solution: Use CO-RE (Compile Once, Run Everywhere) via libbpf.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Start simple&lt;/strong&gt;: Basic packet capture first, then add L7 inspection&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trust the verifier&lt;/strong&gt;: If it rejects your code, there's probably a real bug&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test incrementally&lt;/strong&gt;: Use &lt;code&gt;bpftool&lt;/code&gt; to inspect maps and programs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Memory matters&lt;/strong&gt;: Every byte in your event structure adds overhead&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User space is your friend&lt;/strong&gt;: Don't try to do everything in eBPF&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Production Considerations
&lt;/h2&gt;

&lt;p&gt;For critical infrastructure monitoring (my day job), I've learned:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Alert fatigue is real&lt;/strong&gt;: Smart deduplication is essential&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance monitoring&lt;/strong&gt;: Track your own overhead&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Graceful degradation&lt;/strong&gt;: Handle packet loss at high traffic volumes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security&lt;/strong&gt;: Limit L7 payload capture to metadata only&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;The roadmap for Cerberus includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Redis backend&lt;/strong&gt; for distributed deployments&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prometheus metrics&lt;/strong&gt; export&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anomaly detection&lt;/strong&gt; using pattern baselines&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IPv6 support&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extended L7 inspection&lt;/strong&gt; (128-256 byte payloads)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Web dashboard&lt;/strong&gt; for visualization&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/zrougamed/cerberus.git
&lt;span class="nb"&gt;cd &lt;/span&gt;cerberus
make
&lt;span class="nb"&gt;sudo&lt;/span&gt; ./build/cerberus
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Requirements:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Linux kernel 4.18+&lt;/li&gt;
&lt;li&gt;Go 1.24+&lt;/li&gt;
&lt;li&gt;Root privileges&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Contributing
&lt;/h2&gt;

&lt;p&gt;Cerberus is open source and looking for contributors! Whether you're interested in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;eBPF kernel programming&lt;/li&gt;
&lt;li&gt;Go application development&lt;/li&gt;
&lt;li&gt;Network protocol analysis&lt;/li&gt;
&lt;li&gt;Performance optimization&lt;/li&gt;
&lt;li&gt;Documentation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Check out the &lt;a href="https://github.com/zrougamed/cerberus" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt; and open an issue or PR.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://ebpf.io/" rel="noopener noreferrer"&gt;eBPF Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/aquasecurity/libbpfgo" rel="noopener noreferrer"&gt;libbpfgo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nakryiko.com/posts/bpf-portability-and-co-re/" rel="noopener noreferrer"&gt;BPF CO-RE&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.cilium.io/en/stable/bpf/" rel="noopener noreferrer"&gt;Cilium eBPF Guide&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Building Cerberus taught me that eBPF isn't just about performance - it's about rethinking how we approach observability. By moving intelligence into the kernel, we can monitor networks at scale without sacrificing visibility.&lt;/p&gt;

&lt;p&gt;The combination of eBPF's efficiency and Go's expressiveness creates a powerful platform for building modern monitoring tools. Whether you're securing critical infrastructure or just curious about network traffic, eBPF opens up possibilities that weren't feasible before.&lt;/p&gt;

&lt;p&gt;What would you build with eBPF? Let me know in the comments!&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Mohamed Zrouga is a Senior Platform Engineer at Deltaflare, specializing in critical infrastructure protection and DevSecOps. Connect on &lt;a href="https://github.com/zrougamed" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; or visit &lt;a href="https://zrouga.email" rel="noopener noreferrer"&gt;zrouga.email&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>go</category>
      <category>opensource</category>
      <category>networking</category>
      <category>kernel</category>
    </item>
    <item>
      <title>Build a Secure Multi-Tenant SSO System with Keycloak, Go &amp; React: Step-by-Step Guide</title>
      <dc:creator>Mohamed Zrouga</dc:creator>
      <pubDate>Sat, 07 Jun 2025 19:55:54 +0000</pubDate>
      <link>https://dev.to/zrouga/build-a-secure-multi-tenant-sso-system-with-keycloak-go-react-step-by-step-guide-218m</link>
      <guid>https://dev.to/zrouga/build-a-secure-multi-tenant-sso-system-with-keycloak-go-react-step-by-step-guide-218m</guid>
      <description>&lt;p&gt;Managing authentication across multiple tenants can be a nightmare—different domains, identity providers, and security models all add complexity. In this guide, you’ll learn how to build a production-ready multi-tenant SSO system using &lt;strong&gt;Keycloak&lt;/strong&gt;, &lt;strong&gt;React&lt;/strong&gt;, and &lt;strong&gt;Go&lt;/strong&gt;, enabling your users to log in with Google, GitHub, or Microsoft accounts, while keeping each tenant securely isolated.&lt;/p&gt;




&lt;h2&gt;
  
  
  Stack Overview
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Tech&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Identity&lt;/td&gt;
&lt;td&gt;Keycloak&lt;/td&gt;
&lt;td&gt;Central identity provider (OAuth2, JWT, social login, multi-tenancy)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Frontend&lt;/td&gt;
&lt;td&gt;React&lt;/td&gt;
&lt;td&gt;User and admin interfaces&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backend&lt;/td&gt;
&lt;td&gt;Go&lt;/td&gt;
&lt;td&gt;Stateless API with JWT validation and business logic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data&lt;/td&gt;
&lt;td&gt;PostgreSQL&lt;/td&gt;
&lt;td&gt;Stores tenants, users, and metadata&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DevOps&lt;/td&gt;
&lt;td&gt;Docker Compose&lt;/td&gt;
&lt;td&gt;Container orchestration for local setup&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Architecture: A Layered Security Model
&lt;/h2&gt;

&lt;p&gt;The solution follows a cleanly separated, layered architecture:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Identity Providers Layer&lt;/strong&gt;: Social login via Google, Microsoft, and GitHub&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frontend Layer&lt;/strong&gt;: 

&lt;ul&gt;
&lt;li&gt;React user interface (&lt;code&gt;http://localhost:3000&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Admin dashboard (&lt;code&gt;http://localhost:3001&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Authentication Layer&lt;/strong&gt;: 

&lt;ul&gt;
&lt;li&gt;Keycloak (&lt;code&gt;http://localhost:8080&lt;/code&gt;) handles all identity and access flows&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Backend Layer&lt;/strong&gt;: 

&lt;ul&gt;
&lt;li&gt;Go API (&lt;code&gt;http://localhost:8081&lt;/code&gt;) that verifies JWTs and executes tenant-aware logic&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Data Layer&lt;/strong&gt;: 

&lt;ul&gt;
&lt;li&gt;PostgreSQL database for user, tenant, and relationship data&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;




&lt;h2&gt;
  
  
  Why This Stack?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjfwpsuujcqgf0pwnk9s6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjfwpsuujcqgf0pwnk9s6.png" alt="Image description" width="800" height="247"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Keycloak
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Open-source IAM platform with OAuth2, OpenID Connect, social login, and multi-tenancy.&lt;/li&gt;
&lt;li&gt;Saves you from writing brittle auth code.&lt;/li&gt;
&lt;li&gt;Highly customizable with realms, clients, and mappers.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  React
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Modern, component-driven framework.&lt;/li&gt;
&lt;li&gt;Perfect separation of user frontend and admin interface.&lt;/li&gt;
&lt;li&gt;Great developer experience and large ecosystem.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Go
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Lightweight and performant.&lt;/li&gt;
&lt;li&gt;Excellent standard library for JWT handling and HTTP.&lt;/li&gt;
&lt;li&gt;Strong concurrency model for scalable APIs.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Authentication Flow
&lt;/h2&gt;

&lt;p&gt;Here’s how the secure OAuth2-based login works:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;User starts login from the React frontend.&lt;/li&gt;
&lt;li&gt;Keycloak redirects to the selected identity provider.&lt;/li&gt;
&lt;li&gt;User authenticates, and the OAuth callback returns to Keycloak.&lt;/li&gt;
&lt;li&gt;Keycloak issues a JWT with user and tenant context.&lt;/li&gt;
&lt;li&gt;Frontend includes the JWT in API requests.&lt;/li&gt;
&lt;li&gt;Go backend validates the token and extracts tenant info.&lt;/li&gt;
&lt;li&gt;Tenant-isolated operations run based on JWT claims.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbxlvcq5h105v7h2ygrhw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbxlvcq5h105v7h2ygrhw.png" alt="Image description" width="800" height="211"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This setup ensures centralized auth with stateless APIs and strong tenant isolation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Multi-Tenancy: Key Considerations
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concern&lt;/th&gt;
&lt;th&gt;Solution&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Tenant Isolation&lt;/td&gt;
&lt;td&gt;Users and data are scoped to their own organization.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flexible Auth&lt;/td&gt;
&lt;td&gt;Each tenant chooses which social login providers to enable.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Self-Service Admin&lt;/td&gt;
&lt;td&gt;Admin panel allows tenant admins to manage users without overlap.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scalable Infrastructure&lt;/td&gt;
&lt;td&gt;Each component is containerized and independently scalable.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Security Best Practices
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;JWT Validation&lt;/strong&gt;: Go backend checks tokens against Keycloak’s JWKS endpoint.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CORS Configuration&lt;/strong&gt;: Proper headers configured in both Keycloak and backend.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Environment Variables&lt;/strong&gt;: All sensitive values are managed via &lt;code&gt;.env&lt;/code&gt; files.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Isolated Networks&lt;/strong&gt;: Docker containers communicate over private networks.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;Clone and boot the system using Docker Compose:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Clone and navigate to the repo
git clone https://github.com/zrougamed/multitenant-sso
cd multitenant-sso

# Start all services
docker-compose up -d
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Access services at:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keycloak: &lt;a href="http://localhost:8080" rel="noopener noreferrer"&gt;http://localhost:8080&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;User frontend: &lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Admin panel: &lt;a href="http://localhost:3001" rel="noopener noreferrer"&gt;http://localhost:3001&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Go API: &lt;a href="http://localhost:8081" rel="noopener noreferrer"&gt;http://localhost:8081&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Configuring OAuth Providers
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Google
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;a href="https://console.cloud.google.com/" rel="noopener noreferrer"&gt;Google Cloud Console&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Create OAuth credentials&lt;/li&gt;
&lt;li&gt;Set redirect URI:
&lt;code&gt;http://localhost:8080/realms/{realm}/broker/google/endpoint&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  GitHub
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;a href="https://github.com/settings/developers" rel="noopener noreferrer"&gt;GitHub Developer Settings&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Set callback URI:
&lt;code&gt;http://localhost:8080/realms/{realm}/broker/github/endpoint&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Microsoft
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Register an app via &lt;a href="https://portal.azure.com" rel="noopener noreferrer"&gt;Azure App Registrations&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Set redirect URI:
&lt;code&gt;http://localhost:8080/realms/{realm}/broker/azure/endpoint&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Common Challenges and Solutions
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Problem&lt;/th&gt;
&lt;th&gt;Solution&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Port Conflicts&lt;/td&gt;
&lt;td&gt;Modify &lt;code&gt;docker-compose.yml&lt;/code&gt; or Keycloak config.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CORS Errors&lt;/td&gt;
&lt;td&gt;Ensure correct CORS settings in backend and Keycloak.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JWT Validation Fails&lt;/td&gt;
&lt;td&gt;Double-check audience (&lt;code&gt;aud&lt;/code&gt;), issuer (&lt;code&gt;iss&lt;/code&gt;), and realm configuration.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Extending the System
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Add more identity providers: Twitter, Facebook, etc.&lt;/li&gt;
&lt;li&gt;Add Role-Based Access Control (RBAC) via Keycloak groups and roles.&lt;/li&gt;
&lt;li&gt;Implement API rate limiting in Go middleware.&lt;/li&gt;
&lt;li&gt;Add monitoring with Prometheus, Grafana, or ELK.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Production Considerations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;High Availability&lt;/strong&gt;: Cluster Keycloak and PostgreSQL with redundancy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SSL/TLS&lt;/strong&gt;: Use reverse proxy like NGINX or Traefik for HTTPS.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backups&lt;/strong&gt;: Regular backups for Keycloak and PostgreSQL.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitoring&lt;/strong&gt;: Alerting on auth failures, performance, and token expiry issues.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Building a secure, scalable, multi-tenant SSO system doesn’t have to be painful. By using &lt;strong&gt;Keycloak&lt;/strong&gt; for identity management, &lt;strong&gt;React&lt;/strong&gt; for the frontend, and &lt;strong&gt;Go&lt;/strong&gt; for the API, you get a powerful and maintainable architecture that scales with your needs.&lt;/p&gt;

&lt;p&gt;The system supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multi-tenant user isolation&lt;/li&gt;
&lt;li&gt;Social logins via OAuth&lt;/li&gt;
&lt;li&gt;Stateless APIs with secure JWTs&lt;/li&gt;
&lt;li&gt;Admin management for each tenant&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Source Code&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
👉 &lt;a href="https://github.com/zrougamed/multitenant-sso" rel="noopener noreferrer"&gt;GitHub Repository – multitenant-sso&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Questions or feedback?&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Open an issue on GitHub or connect with me on &lt;a href="https://www.linkedin.com/in/zrouga-mohamed/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;/p&gt;

</description>
      <category>oauth</category>
      <category>devops</category>
      <category>sso</category>
      <category>programming</category>
    </item>
    <item>
      <title>Open Source Notification Scheduler in Go! [Looking for Contributors]</title>
      <dc:creator>Mohamed Zrouga</dc:creator>
      <pubDate>Mon, 16 Dec 2024 02:37:43 +0000</pubDate>
      <link>https://dev.to/zrouga/open-source-notification-scheduler-in-go-looking-for-contributors-1o8j</link>
      <guid>https://dev.to/zrouga/open-source-notification-scheduler-in-go-looking-for-contributors-1o8j</guid>
      <description>&lt;p&gt;Hi Dev! 👋&lt;/p&gt;

&lt;p&gt;I’m excited to share my latest open-source project, Dynamic Notification System! 🎉&lt;/p&gt;

&lt;p&gt;This is a Go-based extensible notification scheduler that supports multiple channels, including:&lt;/p&gt;

&lt;p&gt;📨 Slack&lt;br&gt;
🔔 Discord&lt;br&gt;
📩 Telegram&lt;br&gt;
📧 SMTP&lt;br&gt;
🖥️ Webhooks&lt;br&gt;
And many more!&lt;br&gt;
With dynamic plugin support, the system allows you to add new notification channels effortlessly. It’s designed for cross-platform compatibility (Linux, macOS, Windows) and supports Go 1.23+.&lt;/p&gt;

&lt;p&gt;Why Contribute?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Learn and improve your skills in Go and dynamic plugin systems.&lt;/li&gt;
&lt;li&gt;Work on cross-platform builds for Linux, macOS, and Windows.&lt;/li&gt;
&lt;li&gt;Be part of a growing open-source project and build your portfolio.&lt;/li&gt;
&lt;li&gt;Help the community by adding new features and optimizing existing ones.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;How You Can Help&lt;br&gt;
We’re looking for contributors to:&lt;/p&gt;

&lt;p&gt;Add new notification channels like Twilio, Signal, etc.&lt;br&gt;
Improve documentation and examples.&lt;br&gt;
Fix bugs and optimize builds.&lt;br&gt;
No matter your experience level, we’d love your input! &lt;/p&gt;

&lt;p&gt;Links&lt;br&gt;
🔗 GitHub Repository: &lt;a href="https://github.com/zrougamed/dynamic-notification-system" rel="noopener noreferrer"&gt;Dynamic Notification System&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Feel free to check out the project, try it, and let us know your thoughts! Contributions, feedback, and stars 🌟 are all greatly appreciated. Let’s build something amazing together! 💪&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>development</category>
      <category>go</category>
      <category>ui</category>
    </item>
  </channel>
</rss>
