<?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>Forem: Nevin Shine</title>
    <description>The latest articles on Forem by Nevin Shine (@nevinshine).</description>
    <link>https://forem.com/nevinshine</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%2F3776795%2F41c8941c-6d39-4d3c-a5ff-6bc480971bbd.jpeg</url>
      <title>Forem: Nevin Shine</title>
      <link>https://forem.com/nevinshine</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/nevinshine"/>
    <language>en</language>
    <item>
      <title>Pushing XDP Limits: How I Built a Stateful L7 Firewall in eBPF</title>
      <dc:creator>Nevin Shine</dc:creator>
      <pubDate>Tue, 17 Feb 2026 05:37:49 +0000</pubDate>
      <link>https://forem.com/nevinshine/pushing-xdp-limits-how-i-built-a-stateful-l7-firewall-in-ebpf-3ej6</link>
      <guid>https://forem.com/nevinshine/pushing-xdp-limits-how-i-built-a-stateful-l7-firewall-in-ebpf-3ej6</guid>
      <description>&lt;p&gt;The conventional wisdom in Linux networking is straightforward: use &lt;strong&gt;XDP (Express Data Path)&lt;/strong&gt; for stateless packet filtering such as DDoS mitigation, and rely on &lt;code&gt;iptables&lt;/code&gt; or &lt;code&gt;nftables&lt;/code&gt; for complex stateful inspection.&lt;/p&gt;

&lt;p&gt;The reasoning is twofold. First, XDP executes extremely early in the networking pipeline — before &lt;code&gt;sk_buff&lt;/code&gt; allocation and before netfilter or conntrack hooks. Second, the eBPF verifier imposes strict safety constraints, particularly around loops, making deep packet inspection appear impractical.&lt;/p&gt;

&lt;p&gt;To explore these limitations, I built &lt;strong&gt;Hyperion&lt;/strong&gt;, a stateful L7 firewall that runs entirely in XDP. It performs TCP connection tracking, bounded payload inspection, and asynchronous telemetry export while sustaining approximately &lt;strong&gt;11.8 million packets per second&lt;/strong&gt;, compared to roughly 2.1M PPS for equivalent iptables drop rules.&lt;/p&gt;

&lt;p&gt;This article describes the key engineering challenges involved.&lt;/p&gt;




&lt;h2&gt;
  
  
  Challenge 1: Connection Tracking in XDP
&lt;/h2&gt;

&lt;p&gt;Because XDP executes before the kernel networking stack, it cannot access native &lt;code&gt;nf_conntrack&lt;/code&gt; state. Flow-based filtering therefore requires a custom implementation.&lt;/p&gt;

&lt;p&gt;Hyperion uses a &lt;code&gt;BPF_MAP_TYPE_LRU_HASH&lt;/code&gt; to store flow state:&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;flow_key&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;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;protocol&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;The LRU eviction policy is critical under load. When the map reaches capacity during high traffic, older flows are automatically removed, preventing memory exhaustion.&lt;/p&gt;




&lt;h2&gt;
  
  
  Challenge 2: Verifier Constraints and Payload Inspection
&lt;/h2&gt;

&lt;p&gt;Deep packet inspection requires iterating through packet payloads. The eBPF verifier allows loops only if they are provably bounded.&lt;/p&gt;

&lt;p&gt;Hyperion uses bounded loops combined with compile-time unrolling:&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="cp"&gt;#define MAX_SCAN_LEN 16
#define MAX_SIG_LEN 8
&lt;/span&gt;
&lt;span class="cp"&gt;#pragma unroll
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;MAX_SCAN_LEN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&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;if&lt;/span&gt; &lt;span class="p"&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;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;MAX_SIG_LEN&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;break&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;payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sc"&gt;'r'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&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;i&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;==&lt;/span&gt; &lt;span class="sc"&gt;'o'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&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;i&lt;/span&gt;&lt;span class="o"&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;'o'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&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;i&lt;/span&gt;&lt;span class="o"&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;'t'&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="mi"&gt;1&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;p&gt;Unrolling transforms the loop into a linear instruction sequence that the verifier can statically analyze for safety.&lt;/p&gt;




&lt;h2&gt;
  
  
  Challenge 3: Observability at High Packet Rates
&lt;/h2&gt;

&lt;p&gt;Visibility is essential for production deployments. However, traditional debugging mechanisms such as &lt;code&gt;bpf_trace_printk&lt;/code&gt; introduce significant overhead.&lt;/p&gt;

&lt;p&gt;Hyperion instead uses the &lt;strong&gt;BPF ring buffer&lt;/strong&gt;, which enables asynchronous event delivery to userspace. The kernel writes structured events into shared memory, while a userspace program consumes them independently.&lt;/p&gt;

&lt;p&gt;This design minimizes impact on packet processing performance.&lt;/p&gt;




&lt;h2&gt;
  
  
  Performance Observations
&lt;/h2&gt;

&lt;p&gt;Testing with &lt;code&gt;pktgen&lt;/code&gt; using 64-byte UDP floods produced the following results:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;System&lt;/th&gt;
&lt;th&gt;Throughput&lt;/th&gt;
&lt;th&gt;CPU Load&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;iptables drop&lt;/td&gt;
&lt;td&gt;~2.1M PPS&lt;/td&gt;
&lt;td&gt;100% SoftIRQ&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hyperion XDP&lt;/td&gt;
&lt;td&gt;~11.8M PPS&lt;/td&gt;
&lt;td&gt;~40% CPU&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The improvement primarily comes from eliminating SKB allocation and bypassing netfilter processing.&lt;/p&gt;




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

&lt;p&gt;While XDP is often considered suitable only for stateless filtering, it can support stateful inspection when designed within verifier constraints.&lt;/p&gt;

&lt;p&gt;Key techniques include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Efficient map-based flow tracking&lt;/li&gt;
&lt;li&gt;Bounded, verifier-safe payload scanning&lt;/li&gt;
&lt;li&gt;Asynchronous telemetry pipelines&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These approaches enable complex security logic to execute efficiently at the earliest point in the networking stack.&lt;/p&gt;

</description>
      <category>ebpf</category>
      <category>linux</category>
      <category>security</category>
      <category>performance</category>
    </item>
    <item>
      <title>Pushing XDP Limits: How I Built a Stateful L7 Firewall in eBPF</title>
      <dc:creator>Nevin Shine</dc:creator>
      <pubDate>Tue, 17 Feb 2026 05:37:49 +0000</pubDate>
      <link>https://forem.com/nevinshine/pushing-xdp-limits-how-i-built-a-stateful-l7-firewall-in-ebpf-2ope</link>
      <guid>https://forem.com/nevinshine/pushing-xdp-limits-how-i-built-a-stateful-l7-firewall-in-ebpf-2ope</guid>
      <description>&lt;p&gt;The conventional wisdom in Linux networking is straightforward: use &lt;strong&gt;XDP (Express Data Path)&lt;/strong&gt; for stateless packet filtering such as DDoS mitigation, and rely on &lt;code&gt;iptables&lt;/code&gt; or &lt;code&gt;nftables&lt;/code&gt; for complex stateful inspection.&lt;/p&gt;

&lt;p&gt;The reasoning is twofold. First, XDP executes extremely early in the networking pipeline — before &lt;code&gt;sk_buff&lt;/code&gt; allocation and before netfilter or conntrack hooks. Second, the eBPF verifier imposes strict safety constraints, particularly around loops, making deep packet inspection appear impractical.&lt;/p&gt;

&lt;p&gt;To explore these limitations, I built &lt;strong&gt;Hyperion&lt;/strong&gt;, a stateful L7 firewall that runs entirely in XDP. It performs TCP connection tracking, bounded payload inspection, and asynchronous telemetry export while sustaining approximately &lt;strong&gt;11.8 million packets per second&lt;/strong&gt;, compared to roughly 2.1M PPS for equivalent iptables drop rules.&lt;/p&gt;

&lt;p&gt;This article describes the key engineering challenges involved.&lt;/p&gt;




&lt;h2&gt;
  
  
  Challenge 1: Connection Tracking in XDP
&lt;/h2&gt;

&lt;p&gt;Because XDP executes before the kernel networking stack, it cannot access native &lt;code&gt;nf_conntrack&lt;/code&gt; state. Flow-based filtering therefore requires a custom implementation.&lt;/p&gt;

&lt;p&gt;Hyperion uses a &lt;code&gt;BPF_MAP_TYPE_LRU_HASH&lt;/code&gt; to store flow state:&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;flow_key&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;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;protocol&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;The LRU eviction policy is critical under load. When the map reaches capacity during high traffic, older flows are automatically removed, preventing memory exhaustion.&lt;/p&gt;




&lt;h2&gt;
  
  
  Challenge 2: Verifier Constraints and Payload Inspection
&lt;/h2&gt;

&lt;p&gt;Deep packet inspection requires iterating through packet payloads. The eBPF verifier allows loops only if they are provably bounded.&lt;/p&gt;

&lt;p&gt;Hyperion uses bounded loops combined with compile-time unrolling:&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="cp"&gt;#define MAX_SCAN_LEN 16
#define MAX_SIG_LEN 8
&lt;/span&gt;
&lt;span class="cp"&gt;#pragma unroll
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;MAX_SCAN_LEN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&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;if&lt;/span&gt; &lt;span class="p"&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;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;MAX_SIG_LEN&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;break&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;payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sc"&gt;'r'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&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;i&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;==&lt;/span&gt; &lt;span class="sc"&gt;'o'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&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;i&lt;/span&gt;&lt;span class="o"&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;'o'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&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;i&lt;/span&gt;&lt;span class="o"&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;'t'&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="mi"&gt;1&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;p&gt;Unrolling transforms the loop into a linear instruction sequence that the verifier can statically analyze for safety.&lt;/p&gt;




&lt;h2&gt;
  
  
  Challenge 3: Observability at High Packet Rates
&lt;/h2&gt;

&lt;p&gt;Visibility is essential for production deployments. However, traditional debugging mechanisms such as &lt;code&gt;bpf_trace_printk&lt;/code&gt; introduce significant overhead.&lt;/p&gt;

&lt;p&gt;Hyperion instead uses the &lt;strong&gt;BPF ring buffer&lt;/strong&gt;, which enables asynchronous event delivery to userspace. The kernel writes structured events into shared memory, while a userspace program consumes them independently.&lt;/p&gt;

&lt;p&gt;This design minimizes impact on packet processing performance.&lt;/p&gt;




&lt;h2&gt;
  
  
  Performance Observations
&lt;/h2&gt;

&lt;p&gt;Testing with &lt;code&gt;pktgen&lt;/code&gt; using 64-byte UDP floods produced the following results:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;System&lt;/th&gt;
&lt;th&gt;Throughput&lt;/th&gt;
&lt;th&gt;CPU Load&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;iptables drop&lt;/td&gt;
&lt;td&gt;~2.1M PPS&lt;/td&gt;
&lt;td&gt;100% SoftIRQ&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hyperion XDP&lt;/td&gt;
&lt;td&gt;~11.8M PPS&lt;/td&gt;
&lt;td&gt;~40% CPU&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The improvement primarily comes from eliminating SKB allocation and bypassing netfilter processing.&lt;/p&gt;




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

&lt;p&gt;While XDP is often considered suitable only for stateless filtering, it can support stateful inspection when designed within verifier constraints.&lt;/p&gt;

&lt;p&gt;Key techniques include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Efficient map-based flow tracking&lt;/li&gt;
&lt;li&gt;Bounded, verifier-safe payload scanning&lt;/li&gt;
&lt;li&gt;Asynchronous telemetry pipelines&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These approaches enable complex security logic to execute efficiently at the earliest point in the networking stack.&lt;/p&gt;

</description>
      <category>ebpf</category>
      <category>linux</category>
      <category>security</category>
      <category>performance</category>
    </item>
  </channel>
</rss>
