<?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: Alex Root</title>
    <description>The latest articles on DEV Community by Alex Root (@alex_root_48dfe8afa393367).</description>
    <link>https://dev.to/alex_root_48dfe8afa393367</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3707186%2Fdadafa2f-f9d4-4e10-acab-52898d63fbc1.png</url>
      <title>DEV Community: Alex Root</title>
      <link>https://dev.to/alex_root_48dfe8afa393367</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/alex_root_48dfe8afa393367"/>
    <language>en</language>
    <item>
      <title>Building a High-Performance eBPF/XDP Policy Engine in Rust</title>
      <dc:creator>Alex Root</dc:creator>
      <pubDate>Sun, 16 Aug 2026 18:56:14 +0000</pubDate>
      <link>https://dev.to/alex_root_48dfe8afa393367/building-a-high-performance-ebpfxdp-policy-engine-in-rust-4ia7</link>
      <guid>https://dev.to/alex_root_48dfe8afa393367/building-a-high-performance-ebpfxdp-policy-engine-in-rust-4ia7</guid>
      <description>&lt;p&gt;A Bit of Background&lt;/p&gt;

&lt;p&gt;The origins of eBPF (extended Berkeley Packet Filter) can be traced back to the original Berkeley Packet Filter (BPF), which was introduced in the early 1990s as a mechanism for filtering network packets. Over time, BPF evolved from a packet filtering framework into a general-purpose execution environment within the Linux kernel, ultimately becoming what we now know as eBPF.&lt;br&gt;
Today, eBPF is far more than a networking technology. It is widely used for observability, performance monitoring, security enforcement, networking, load balancing, and many other kernel-level tasks. Instead of requiring developers to write and maintain custom kernel modules, eBPF allows sandboxed programs to run safely inside the kernel.&lt;br&gt;
Before an eBPF program can be loaded, it must pass the kernel verifier. The verifier performs a static analysis to ensure that the program always terminates, accesses memory safely, and complies with the kernel's security constraints. This verification process enables eBPF to extend kernel functionality without compromising system stability or security.&lt;br&gt;
One of eBPF's greatest strengths is its flexibility. Programs can be attached to multiple execution points—known as hooks—throughout the kernel. Each hook exposes different information about the packet or system state and is designed for a particular class of workloads. Choosing the right hook depends on the required performance, the amount of available context, and the functionality that needs to be implemented.&lt;br&gt;
To better understand where these hooks fit into the networking stack, let's first look at the path a network packet takes through the Linux kernel.&lt;br&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flxd91lpa969cmby1u3yx.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flxd91lpa969cmby1u3yx.png" alt="Linux network packet processing path" width="650" height="842"&gt;&lt;/a&gt;&lt;br&gt;
The diagram above shows the packet processing path between the network interface and the Linux networking stack. XDP operates exclusively on the receive (RX) path, where packets are processed before they reach the kernel networking stack. TC (Traffic Control), on the other hand, provides both ingress and egress hook points, making it possible to process traffic in both directions.&lt;br&gt;
The earliest packet processing point is XDP (eXpress Data Path). An XDP program runs between the network device driver and the Linux networking stack, before the kernel allocates the sk_buff (socket buffer) structure. sk_buff is the primary kernel data structure used to represent a network packet as it moves through the Linux networking stack.&lt;br&gt;
Because XDP operates at such an early stage, it provides extremely low processing latency and is well suited for high-performance packet filtering, ACLs, DDoS protection, and packet redirection.&lt;br&gt;
Once an sk_buff has been created, the packet enters the Linux networking stack, where eBPF programs can be attached through the Traffic Control (TC) subsystem. Unlike XDP, TC operates on the sk_buff, giving the program access to additional packet context that is not available at the XDP layer. TC is also closely integrated with routing, QoS, conntrack, tunneling, and other kernel networking subsystems.&lt;br&gt;
This makes TC a convenient layer for implementing NAT, load balancing, packet modification, and more complex network processing.&lt;br&gt;
XDP and TC are not the only eBPF attachment points. Cgroup hooks can be used to enforce policies for processes and containers, while socket filters can be used to inspect traffic associated with individual sockets. Tracepoints, kprobes, and uprobes are widely used for tracing and performance monitoring.&lt;br&gt;
Although eBPF provides a wide range of attachment points, they are not interchangeable. The earlier a program runs in the networking stack, the lower the processing overhead, but the less context is available to it. As the packet moves further through the kernel, more context and functionality become available, but processing becomes more expensive.&lt;br&gt;
Therefore, choosing an appropriate hook is fundamentally a trade-off between performance and functionality.&lt;br&gt;
In Pulsar, XDP was chosen as the primary dataplane because it allows packet handling decisions to be made as early as possible, before the packet passes through most of the Linux networking stack. This makes XDP particularly well suited for high-performance ACLs and other early packet filtering mechanisms.&lt;br&gt;
At the same time, the architecture is not limited to a single processing layer. Features that require richer packet context or deeper integration with kernel networking subsystems are planned to be implemented at the TC layer.&lt;/p&gt;

&lt;p&gt;The Main Part&lt;/p&gt;

&lt;p&gt;Over the years, the Linux networking stack has evolved to include a wide range of mechanisms for managing network policy. In addition to iptables and nftables, there are tools and systems such as firewalld, ufw, Docker, Kubernetes, and others that provide their own policy management interfaces or automatically modify existing filtering and routing rules.&lt;br&gt;
As a result, administrators have to deal not only with the network policy itself, but also with the way that policy is implemented by a particular system. The same policy can be expressed in very different ways depending on the tool being used.&lt;br&gt;
When moving between different platforms or introducing additional components such as Docker or Kubernetes, the changes go beyond policy syntax. The points at which policies are applied, the order in which traffic is processed, and the interactions between different networking mechanisms can all change.&lt;br&gt;
A similar situation exists outside Linux as well. Many BSD systems and network devices use their own policy models and configuration approaches.&lt;br&gt;
For example, consider the following simple filtering policy:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Deny TCP traffic from 192.168.0.3 to 192.168.0.202:80.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The same policy can look completely different depending on the system being used.&lt;/p&gt;

&lt;p&gt;With iptables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;iptables &lt;span class="nt"&gt;-A&lt;/span&gt; INPUT &lt;span class="nt"&gt;-s&lt;/span&gt; 192.168.0.3 &lt;span class="nt"&gt;-d&lt;/span&gt; 192.168.0.202 &lt;span class="nt"&gt;-p&lt;/span&gt; tcp &lt;span class="nt"&gt;--dport&lt;/span&gt; 80 &lt;span class="nt"&gt;-j&lt;/span&gt; DROP    
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With nftables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ip saddr 192.168.0.3 ip daddr 192.168.0.202 tcp dport 80 drop.   
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With pf:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;block &lt;span class="k"&gt;in &lt;/span&gt;proto tcp from 192.168.0.3 to 192.168.0.202 port 80.    
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All of the examples above describe the same network policy, but its representation depends on the underlying enforcement mechanism.&lt;br&gt;
In the dataplane I am developing, the same policy is expressed through a single, unified interface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./ebpf-ctl acl add drop  src 192.168.0.3:any  dst 192.168.0.202:80  &lt;span class="nv"&gt;proto&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;tcp 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or configuration file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;acl&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;drop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;addresses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;192.168.0.3&lt;/span&gt;
        &lt;span class="na"&gt;dst&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;addresses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;192.168.0.202&lt;/span&gt;
        &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;number&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
          &lt;span class="na"&gt;proto&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tcp&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The goal is not to introduce yet another policy syntax, but to abstract policy definition away from its enforcement mechanism.&lt;/p&gt;

&lt;p&gt;The user works with a single declarative network policy model that is independent of the underlying enforcement mechanism. The control plane translates this policy definition into an internal representation consumed by the dataplane, while the CLI and YAML are simply different ways of expressing the same policy.&lt;/p&gt;

&lt;p&gt;Pulsar Architecture&lt;br&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsko3o7ba4n270kekynxv.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsko3o7ba4n270kekynxv.png" alt="The diagram below illustrates the lifecycle of a policy, from its definition to its enforcement.&lt;br&gt;
The system follows the classic control plane / dataplane architecture: the control plane is responsible for building and distributing policy state, while the dataplane is responsible for processing individual packets.&lt;br&gt;
" width="800" height="1069"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The diagram below illustrates the lifecycle of a policy, from its definition to its enforcement.&lt;br&gt;
The system follows the classic control plane / dataplane architecture: the control plane is responsible for building and distributing policy state, while the dataplane is responsible for processing individual packets.&lt;br&gt;
 Control Plane and Dataplane&lt;br&gt;
The primary mechanism for communication between the control plane and dataplane is BPF maps. The control plane uses them to pass prepared policy state to the dataplane: the control plane writes the policy state to the maps, while the dataplane reads it during packet processing.&lt;br&gt;
 Management Interfaces&lt;br&gt;
Policy definitions enter the system through either a declarative configuration file (config.yml) or a command-line client. Both interfaces are intentionally kept thin: they pass the policy definition to the daemon and contain no policy interpretation logic of their own.&lt;br&gt;
 Daemon&lt;br&gt;
The daemon is the core of the control plane. It converts the incoming policy definition into an internal policy model, performs validation and normalization, and then compiles the policy into a representation suitable for the dataplane.&lt;br&gt;
At the final stage, the resulting structures are written to BPF maps.&lt;br&gt;
 Dataplane&lt;br&gt;
The XDP program attached to the network interface accesses the BPF maps for every packet and makes the enforcement decision: whether to allow or drop the packet. It does not communicate directly with the control plane.&lt;br&gt;
The configuration flow shown in the diagram goes from top to bottom. At runtime, however, data flows differently: the XDP program reads the policy state directly from the BPF maps.&lt;br&gt;
 Unified Policy Semantics&lt;br&gt;
The same policy should have a consistent semantic representation regardless of whether it comes from the CLI, YAML, or another management interface.&lt;br&gt;
To achieve this, all semantic processing—parsing, validation, normalization, and compilation—is centralized in the daemon, while management interfaces remain simple adapters to the internal policy model.&lt;br&gt;
This separation allows management interfaces to evolve independently of the dataplane and makes it possible to introduce additional enforcement mechanisms without changing the underlying policy model.&lt;br&gt;
 BPF Maps as the Contract Between the Planes&lt;br&gt;
In this architecture, BPF maps are more than just data structures. They serve as a contract between the control plane and dataplane.&lt;br&gt;
The dataplane does not know where the policy came from, and the control plane does not need to know exactly how a packet will be processed. As long as both sides adhere to the same contract, the enforcement layer can be replaced—for example, with a TC/eBPF implementation—while the management interfaces can be extended without affecting the other plane.&lt;br&gt;
This also provides an important operational property: a failure of the control plane does not affect the dataplane. If the daemon terminates, the XDP program continues to enforce the last policy state loaded into the BPF maps. Packet filtering therefore continues to operate even when the control plane is unavailable.&lt;br&gt;
Current Capabilities&lt;br&gt;
At the moment, Pulsar implements L3/L4 ACLs, IPv4 and IPv6 filtering, VLAN/QinQ ACLs, and rate limiting. Policy management is handled by a userspace control plane that supports declarative policy definitions.&lt;br&gt;
Regardless of the management interface used, the dataplane receives the same internal representation of the policy.&lt;br&gt;
 Event Handling&lt;br&gt;
Special attention is also given to event handling. The eBPF dataplane can send selected events to userspace through a Ring Buffer, where they are converted into loggable events.&lt;br&gt;
The set of events to be logged is configurable, allowing the event generation mechanism to remain independent from how those events are ultimately represented or stored.&lt;br&gt;
 What Makes Pulsar Different&lt;br&gt;
The main distinction of Pulsar is not the number of networking features it currently implements, but the architectural separation between the control plane and dataplane.&lt;br&gt;
The control plane is responsible for defining, validating, and transforming policy, while the dataplane operates on the resulting prepared state to process packets.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw53dofudma10pqz4yq7g.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw53dofudma10pqz4yq7g.png" alt="The diagram shows the main flows between the control plane, data plane and event processing system." width="800" height="535"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Control Plane and Dataplane in Practice&lt;br&gt;
The control plane runs in userspace. The CLI client sends commands to the daemon over a Unix socket, and the daemon writes the resulting rules to BPF maps: IP ACL for address-based filtering, VLAN ACL, and Rate Limit for controlling traffic intensity.&lt;br&gt;
Because the policy state is stored in BPF maps, rules can be updated at runtime without reloading the XDP program.&lt;br&gt;
The daemon also controls event logging through the Select Event configuration map. It specifies which events should be logged, and the XDP module sends only the selected events to the Ring Buffer, from which the daemon reads and processes them.&lt;/p&gt;

&lt;p&gt;Dataplane&lt;br&gt;
The dataplane operates directly on the driver's receive path. A packet arriving from the NIC reaches XDP before sk_buff is allocated and before the packet enters the Linux networking stack.&lt;br&gt;
The XDP program matches the packet against the rules stored in the BPF maps and makes the corresponding decision.&lt;br&gt;
If the packet is allowed, it continues through the normal Linux receive path and is processed by the networking stack. Depending on the configuration, it may subsequently pass through additional processing points, including TC ingress.&lt;br&gt;
If the packet is denied, it is dropped as early as possible. This early-drop capability reduces the amount of work performed by subsequent layers of the networking stack, which is particularly important under high incoming traffic rates.&lt;br&gt;
 Traffic Processing in XDP&lt;br&gt;
At the current stage, the main packet-processing logic in Pulsar is implemented in the XDP module.&lt;br&gt;
For each packet, the XDP program accesses the BPF maps prepared by the control plane and uses the stored policy state to perform packet classification and enforcement.&lt;br&gt;
Three basic mechanisms are currently implemented:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;IP ACL&lt;/li&gt;
&lt;li&gt;VLAN ACL&lt;/li&gt;
&lt;li&gt;Rate Limiting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;IP ACL&lt;br&gt;
The IP ACL is responsible for filtering traffic based on IPv4/IPv6 and L4 parameters. It implements the basic allow/deny policies between sources and destinations.&lt;br&gt;
When processing a packet, the XDP program extracts the required fields and constructs a lookup key for the ACL map. Depending on the policy, these fields may include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; source IP address;&lt;/li&gt;
&lt;li&gt; destination IP address;&lt;/li&gt;
&lt;li&gt; transport protocol, such as TCP or UDP;&lt;/li&gt;
&lt;li&gt; source port;&lt;/li&gt;
&lt;li&gt; destination port.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Based on the matching rule, the dataplane can allow the packet or drop it. If logging is enabled for that rule, the drop event is also sent through the Ring Buffer.&lt;br&gt;
IPv4 and IPv6 are handled through a unified policy model. The control plane converts this model into the corresponding BPF map entries, so the dataplane does not need to parse configuration files or interpret policy definitions. It operates directly on the normalized policy state.&lt;br&gt;
Importantly, the IP ACL in Pulsar is stateless. Each packet is evaluated based on its current fields and the loaded policy rules, without requiring connection-state tracking. This keeps packet processing lightweight and well suited to the XDP layer.&lt;br&gt;
VLAN ACL&lt;br&gt;
The VLAN ACL is designed to filter traffic based on 802.1Q and QinQ tags. Unlike the IP ACL, it operates on VLAN tags present in the Ethernet frame rather than IP addresses and transport ports.&lt;br&gt;
This type of filtering is useful for several practical scenarios.&lt;br&gt;
Network segment isolation. If an interface receives traffic from multiple VLANs, unwanted VLANs can be rejected at the host before the packets enter the Linux networking stack.&lt;br&gt;
Protection against L2 configuration errors. For example, if a switch is misconfigured and traffic from one segment unexpectedly appears on another, a VLAN ACL can drop that traffic at an early stage.&lt;br&gt;
Reducing the risk of VLAN hopping and handling malformed QinQ encapsulation. Unexpected or explicitly forbidden VLAN tags can be rejected before the packet is passed further into the Linux networking stack.&lt;br&gt;
Unified declarative policy. In a traditional Linux environment, L2 and L3 filtering is often handled by different tools with different policy semantics. In Pulsar, VLAN policies are described using the same policy model as IP policies and loaded into the dataplane through the same control plane.&lt;br&gt;
VLAN ACLs are particularly useful on bare-metal servers, hypervisors, edge nodes, and other infrastructure where a single physical or logical interface may receive traffic from multiple isolated segments.&lt;br&gt;
There is, however, an important implementation detail to consider: network drivers can handle VLAN tags differently. In some configurations, the NIC may strip the VLAN tag in hardware and expose the VLAN information through packet metadata instead. Therefore, a correct VLAN ACL implementation needs to account for both VLAN tags present directly in the packet and VLAN information exposed through the XDP context.&lt;br&gt;
Rate Limiting&lt;br&gt;
Rate limiting in Pulsar is designed to control the amount of traffic allowed over a given period of time.&lt;br&gt;
Each rate-limiting rule has its own state stored in a BPF map. This state contains the information required to calculate the current limit, such as the number of available tokens and the timestamp of the last update.&lt;br&gt;
Comparison with Existing Solutions&lt;br&gt;
There are several different approaches to implementing network filtering and programmable dataplanes.&lt;br&gt;
Traditional solutions include iptables and nftables, while among modern eBPF-based platforms, Cilium and Calico are some of the closest architectural examples. There are also specialized eBPF dataplanes such as Katran, which focuses primarily on high-performance L4 load balancing.&lt;br&gt;
Traditional Linux firewalls such as iptables and nftables are deeply integrated with the Linux networking stack and use the Netfilter subsystem to process packets. Configuration is performed through userspace tools that translate the specified rules into Netfilter state.&lt;br&gt;
This approach is mature and well integrated into Linux, but the policy model and enforcement mechanism are closely tied to the particular firewall implementation.&lt;br&gt;
Cilium is perhaps the closest example of using eBPF to build a programmable network dataplane. Cilium uses eBPF at different stages of packet processing, including XDP and TC, depending on the feature and configuration.&lt;br&gt;
In addition to network policy, Cilium provides NAT, load balancing, routing, service networking, and many other capabilities. Therefore, in terms of the number of implemented features, Pulsar is currently far behind Cilium.&lt;br&gt;
Calico provides another example of an eBPF-based dataplane. Its control-plane components translate network policies into state consumed by the dataplane. eBPF is used for filtering, routing, and other networking functions.&lt;br&gt;
Like Cilium, Calico is a significantly more mature platform and is primarily focused on cloud-native and Kubernetes environments.&lt;br&gt;
Katran is another interesting example. Unlike Cilium and Calico, it is not a general-purpose network security platform. Instead, it is a high-performance L4 load-balancer dataplane built on eBPF/XDP.&lt;br&gt;
Its architecture demonstrates the performance benefits of moving packet processing to the earliest possible stages of the packet path. However, Katran's primary goal is fundamentally different from that of Pulsar.&lt;br&gt;
Against this background, Pulsar is still at an early stage of development and does not attempt to compete with existing platforms in terms of feature count.&lt;br&gt;
The current implementation focuses on building a programmable security dataplane in which security policy is separated from the underlying enforcement mechanism.&lt;br&gt;
The key architectural idea is that policy is defined independently of the dataplane, transformed by the control plane into a formalized state, and then enforced directly by an eBPF program.&lt;br&gt;
This separation allows the semantics of the policy and the packet-processing mechanism to evolve independently.&lt;br&gt;
Test Environment&lt;br&gt;
The test environment consisted of two single-board computers, an Orange Pi RV2 and an Orange Pi R2S, along with a MikroTik router.&lt;/p&gt;

&lt;p&gt;Both single-board computers use a SoC from the SpacemiT K1 (Ky X1) family and the RISC-V 64-bit architecture.&lt;br&gt;
The test setup consisted of:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Orange Pi RV2 (192.168.0.202) — the test machine running Pulsar.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Orange Pi R2S (192.168.0.3) — the test machine without Pulsar.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Verify That No Rules Are Currently Configured&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;root@orangepirv2:/home/orangepi/ebpf# ./target/debug/ebpf-ctl acl list
(l4_acl map is empty)
root@orangepirv2:/home/orangepi/ebpf#
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;Make sure that logging has been disabled:
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cat ./test.log 
2026-08-13T17:15:32.884095Z  WARN ebpf_daemon: Failed to load initial config config2.yml: io error: No such file or directory (os error 2)
2026-08-13T17:15:32.884379Z  WARN ebpf_daemon: Starting without configuration. Use 'ebpf-ctl reload &amp;lt;config&amp;gt;' to load later.
2026-08-13T17:15:32.995098Z  INFO ebpf_daemon::monitoring: Trying XDP program id: 237
2026-08-13T17:15:33.049185Z  INFO ebpf_daemon::monitoring: bpftool prog show id 237: 237: xdp  name xdp_dataplane  tag fa484001870657d5
    loaded_at 2026-08-12T07:52:31+0300  uid 0
    xlated 19776B  jited 8972B  memlock 20480B  map_ids 470,471,472,474,480,479
    btf_id 351
2026-08-13T17:15:33.103932Z  WARN ebpf_daemon::monitoring: Skipping map id 470: Map id 470 is not events ringbuf (name=counters, type=percpu_array)
2026-08-13T17:15:33.158030Z  WARN ebpf_daemon::monitoring: Skipping map id 471: Map id 471 is not events ringbuf (name=event_mask, type=array)
2026-08-13T17:15:33.212209Z  INFO ebpf_daemon::monitoring: Opened events map
2026-08-13T17:15:33.212382Z  INFO ebpf_daemon::monitoring: Events ringbuf size: 16777216 bytes
2026-08-13T17:15:33.219468Z  INFO ebpf_daemon::monitoring: Starting ringbuf event loop
root@orangepirv2:/home/orangepi/ebpf# 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;./target/debug/ebpf-ctl event-log list
event-log mask: 0x0
enabled events:
  [ ] PacketDrop
  [ ] RateLimited
  [ ] ConntrackMiss
  [ ] BackendSelected
  [ ] SlowPath
  [ ] ServiceMatched
  [ ] VlanDetected
  [ ] PacketAllow
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;./target/debug/ebpf-ctl event-log enable  PacketDrop
enabled event-log: PacketDrop
root@orangepirv2:/home/orangepi/ebpf# ./target/debug/ebpf-ctl event-log list
event-log mask: 0x2
enabled events:
  [x] PacketDrop
  [ ] RateLimited
  [ ] ConntrackMiss
  [ ] BackendSelected
  [ ] SlowPath
  [ ] ServiceMatched
  [ ] VlanDetected
  [ ] PacketAllow
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;Check the Deny IP ACL rules
ICMP:
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;root@orangepirv2:/home/orangepi/ebpf# ./target/debug/ebpf-ctl acl add drop src 192.168.0.0/24:any dst 192.168.0.202:any proto icmp
ACL rule added
added rule Drop: icmp 192.168.0.0:any -&amp;gt; 192.168.0.202:any (prefix_len=304)
root@orangepirv2:~sudo tcpdump -ni end0 icmp
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on end0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
^C
0 packets captured
0 packets received by filter
0 packets dropped by kernel
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;read log file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;root@orangepirv2:/home/orangepi/ebpf# cat ./test.log 
…
2026-08-13T17:21:35.257433Z  INFO ebpf_daemon::monitoring: eBPF event timestamp_ns=545048648274746 event_kind=PacketDrop ifindex=2 src=192.168.0.3 dst=192.168.0.202 src_port=0 dst_port=0 protocol=ICMP aux0=0 aux1=0
2026-08-13T17:21:36.285353Z  INFO ebpf_daemon::monitoring: eBPF event timestamp_ns=545049676208365 event_kind=PacketDrop ifindex=2 src=192.168.0.3 dst=192.168.0.202 src_port=0 dst_port=0 protocol=ICMP aux0=0 aux1=0
2026-08-13T17:21:37.313289Z  INFO ebpf_daemon::monitoring: eBPF event timestamp_ns=545050704135651 event_kind=PacketDrop ifindex=2 src=192.168.0.3 dst=192.168.0.202 src_port=0 dst_port=0 protocol=ICMP aux0=0 aux1=0
2026-08-13T17:21:38.333321Z  INFO ebpf_daemon::monitoring: eBPF event timestamp_ns=545051724172595 event_kind=PacketDrop ifindex=2 src=192.168.0.3 dst=192.168.0.202 src_port=0 dst_port=0 protocol=ICMP aux0=0 aux1=0
2026-08-13T17:21:39.357436Z  INFO ebpf_daemon::monitoring: eBPF event timestamp_ns=545052748286770 event_kind=PacketDrop ifindex=2 src=192.168.0.3 dst=192.168.0.202 src_port=0 dst_port=0 protocol=ICMP aux0=0 aux1=0
2026-08-13T17:21:40.381334Z  INFO ebpf_daemon::monitoring: eBPF event timestamp_ns=545053772177407 event_kind=PacketDrop ifindex=2 src=192.168.0.3 dst=192.168.0.202 src_port=0 dst_port=0 protocol=ICMP aux0=0 aux1=0
2026-08-13T17:21:41.405289Z  INFO ebpf_daemon::monitoring: eBPF event timestamp_ns=545054796140251 event_kind=PacketDrop ifindex=2 src=192.168.0.3 dst=192.168.0.202 src_port=0 dst_port=0 protocol=ICMP aux0=0 aux1=0
2026-08-13T17:21:42.429270Z  INFO ebpf_daemon::monitoring: eBPF event timestamp_ns=545055820124679 event_kind=PacketDrop ifindex=2 src=192.168.0.3 dst=192.168.0.202 src_port=0 dst_port=0 protocol=ICMP aux0=0 aux1=0
2026-08-13T17:21:43.453263Z  INFO ebpf_daemon::monitoring: eBPF event timestamp_ns=545056844116064 event_kind=PacketDrop ifindex=2 src=192.168.0.3 dst=192.168.0.202 src_port=0 dst_port=0 protocol=ICMP aux0=0 aux1=0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the Orange pi R2S itself:&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="nv"&gt;$ &lt;/span&gt;ping 192.168.0.202
PING 192.168.0.202 &lt;span class="o"&gt;(&lt;/span&gt;192.168.0.202&lt;span class="o"&gt;)&lt;/span&gt; 56&lt;span class="o"&gt;(&lt;/span&gt;84&lt;span class="o"&gt;)&lt;/span&gt; bytes of data.
From 192.168.0.1: &lt;span class="nv"&gt;icmp_seq&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2 Redirect Host&lt;span class="o"&gt;(&lt;/span&gt;New nexthop: 192.168.0.202&lt;span class="o"&gt;)&lt;/span&gt;
^C
&lt;span class="nt"&gt;---&lt;/span&gt; 192.168.0.202 ping statistics &lt;span class="nt"&gt;---&lt;/span&gt;
9 packets transmitted, 0 received, 100% packet loss, &lt;span class="nb"&gt;time &lt;/span&gt;8196ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check TCP using iperf3 (for example, port 80):&lt;br&gt;
On OrangePi RV2:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;root@orangepirv2:/home/orangepi/ebpf# ./target/debug/ebpf-ctl acl add drop src 192.168.0.0/24:any dst 192.168.0.202:80 proto tcp
ACL rule added

./target/debug/ebpf-ctl acl list
&lt;span class="nb"&gt;id  &lt;/span&gt;plen  fam  src                                dst                                proto sport  dport  rate  action
0   304   4    192.168.0.0                        192.168.0.202                      icmp any    any    -     Drop
1   320   4    192.168.0.0                        192.168.0.202                      tcp  any    80     -     Drop

root@orangepirv2:~# iperf3 &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 80
&lt;span class="nt"&gt;-----------------------------------------------------------&lt;/span&gt;
Server listening on 80 &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="c"&gt;#1)&lt;/span&gt;
&lt;span class="nt"&gt;-----------------------------------------------------------&lt;/span&gt;
^Ciperf3: interrupt - the server has terminated
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
root@orangepirv2:/home/orangepi/ebpf# &lt;span class="nb"&gt;cat&lt;/span&gt; ./test.log 
…
&lt;span class="nv"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.0.3 &lt;span class="nv"&gt;dst&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.0.202 &lt;span class="nv"&gt;src_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;47284 &lt;span class="nv"&gt;dst_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;80 &lt;span class="nv"&gt;protocol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;TCP &lt;span class="nv"&gt;aux0&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;aux1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
2026-08-13T17:36:23.293385Z  INFO ebpf_daemon::monitoring: eBPF event &lt;span class="nv"&gt;timestamp_ns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;545936684167882 &lt;span class="nv"&gt;event_kind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;PacketDrop &lt;span class="nv"&gt;ifindex&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2 &lt;span class="nv"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.0.3 &lt;span class="nv"&gt;dst&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.0.202 &lt;span class="nv"&gt;src_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;47284 &lt;span class="nv"&gt;dst_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;80 &lt;span class="nv"&gt;protocol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;TCP &lt;span class="nv"&gt;aux0&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;aux1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
2026-08-13T17:36:24.317258Z  INFO ebpf_daemon::monitoring: eBPF event &lt;span class="nv"&gt;timestamp_ns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;545937708103844 &lt;span class="nv"&gt;event_kind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;PacketDrop &lt;span class="nv"&gt;ifindex&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2 &lt;span class="nv"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.0.3 &lt;span class="nv"&gt;dst&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.0.202 &lt;span class="nv"&gt;src_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;47284 &lt;span class="nv"&gt;dst_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;80 &lt;span class="nv"&gt;protocol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;TCP &lt;span class="nv"&gt;aux0&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;aux1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
2026-08-13T17:36:25.341274Z  INFO ebpf_daemon::monitoring: eBPF event &lt;span class="nv"&gt;timestamp_ns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;545938732107054 &lt;span class="nv"&gt;event_kind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;PacketDrop &lt;span class="nv"&gt;ifindex&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2 &lt;span class="nv"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.0.3 &lt;span class="nv"&gt;dst&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.0.202 &lt;span class="nv"&gt;src_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;47284 &lt;span class="nv"&gt;dst_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;80 &lt;span class="nv"&gt;protocol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;TCP &lt;span class="nv"&gt;aux0&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;aux1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
2026-08-13T17:36:26.365267Z  INFO ebpf_daemon::monitoring: eBPF event &lt;span class="nv"&gt;timestamp_ns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;545939756097723 &lt;span class="nv"&gt;event_kind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;PacketDrop &lt;span class="nv"&gt;ifindex&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2 &lt;span class="nv"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.0.3 &lt;span class="nv"&gt;dst&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.0.202 &lt;span class="nv"&gt;src_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;47284 &lt;span class="nv"&gt;dst_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;80 &lt;span class="nv"&gt;protocol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;TCP &lt;span class="nv"&gt;aux0&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;aux1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
2026-08-13T17:36:27.389307Z  INFO ebpf_daemon::monitoring: eBPF event &lt;span class="nv"&gt;timestamp_ns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;545940780144350 &lt;span class="nv"&gt;event_kind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;PacketDrop &lt;span class="nv"&gt;ifindex&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2 &lt;span class="nv"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.0.3 &lt;span class="nv"&gt;dst&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.0.202 &lt;span class="nv"&gt;src_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;47284 &lt;span class="nv"&gt;dst_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;80 &lt;span class="nv"&gt;protocol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;TCP &lt;span class="nv"&gt;aux0&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;aux1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
2026-08-13T17:36:29.405287Z  INFO ebpf_daemon::monitoring: eBPF event &lt;span class="nv"&gt;timestamp_ns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;545942796125120 &lt;span class="nv"&gt;event_kind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;PacketDrop &lt;span class="nv"&gt;ifindex&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2 &lt;span class="nv"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.0.3 &lt;span class="nv"&gt;dst&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.0.202 &lt;span class="nv"&gt;src_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;47284 &lt;span class="nv"&gt;dst_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;80 &lt;span class="nv"&gt;protocol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;TCP &lt;span class="nv"&gt;aux0&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;aux1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
root@orangepirv2:/home/orangepi/ebpf# 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Naturally, Orange Pi r2s:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;root@orangepir2s:/home/orangepi# iperf3 &lt;span class="nt"&gt;-c&lt;/span&gt; 192.168.0.202 &lt;span class="nt"&gt;-p80&lt;/span&gt;
^C- - - - - - - - - - - - - - - - - - - - - - - - -
&lt;span class="o"&gt;[&lt;/span&gt; ID] Interval           Transfer     Bitrate         Retr
iperf3: interrupt - the client has terminated
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;root@orangepirv2:/home/orangepi/ebpf# ./target/debug/ebpf-ctl acl add drop src 192.168.0.0/24:any dst 192.168.0.202:80 proto udp
ACL rule added
added rule Drop: udp 192.168.0.0:any -&amp;gt; 192.168.0.202:80 &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;prefix_len&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;320&lt;span class="o"&gt;)&lt;/span&gt;
root@orangepirv2:~# iperf3 &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 80 
&lt;span class="nt"&gt;-----------------------------------------------------------&lt;/span&gt;
Server listening on 80 &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="c"&gt;#1)&lt;/span&gt;
&lt;span class="nt"&gt;-----------------------------------------------------------&lt;/span&gt;
^Ciperf3: interrupt - the server has terminated
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;root@orangepirv2:/home/orangepi/ebpf# &lt;span class="nb"&gt;cat&lt;/span&gt; ./test.log 
…
2026-08-13T17:54:34.709969Z  WARN ebpf_daemon::monitoring: Skipping map &lt;span class="nb"&gt;id &lt;/span&gt;470: Map &lt;span class="nb"&gt;id &lt;/span&gt;470 is not events ringbuf &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;counters, &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;percpu_array&lt;span class="o"&gt;)&lt;/span&gt;
2026-08-13T17:54:34.763748Z  WARN ebpf_daemon::monitoring: Skipping map &lt;span class="nb"&gt;id &lt;/span&gt;471: Map &lt;span class="nb"&gt;id &lt;/span&gt;471 is not events ringbuf &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;event_mask, &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;array&lt;span class="o"&gt;)&lt;/span&gt;
2026-08-13T17:54:34.817658Z  INFO ebpf_daemon::monitoring: Opened events map
2026-08-13T17:54:34.817834Z  INFO ebpf_daemon::monitoring: Events ringbuf size: 16777216 bytes
2026-08-13T17:54:34.825118Z  INFO ebpf_daemon::monitoring: Starting ringbuf event loop
2026-08-13T17:54:50.010419Z  INFO ebpf_daemon::monitoring: eBPF event &lt;span class="nv"&gt;timestamp_ns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;547043401254143 &lt;span class="nv"&gt;event_kind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;PacketDrop &lt;span class="nv"&gt;ifindex&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2 &lt;span class="nv"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.0.3 &lt;span class="nv"&gt;dst&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.0.202 &lt;span class="nv"&gt;src_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;59158 &lt;span class="nv"&gt;dst_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;80 &lt;span class="nv"&gt;protocol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;UDP &lt;span class="nv"&gt;aux0&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;aux1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
root@orangepirv2:/home/orangepi/ebpf# 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;OrangePI R2S:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;iperf3 &lt;span class="nt"&gt;-c&lt;/span&gt; 192.168.0.202 &lt;span class="nt"&gt;-p80&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt;
Connecting to host 192.168.0.202, port 80
iperf3: error - unable to &lt;span class="nb"&gt;read &lt;/span&gt;from stream socket: Resource temporarily unavailable
root@orangepir2s:/home/orangepi# 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Check the Allow IP ACL. Here we create an allow rule, but for a specific host (with or without a rate limit):
Enable logging:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;root@orangepirv2:/home/orangepi/ebpf# ./target/debug/ebpf-ctl event-log &lt;span class="nb"&gt;enable &lt;/span&gt;PacketAllow
enabled event-log: PacketAllow
root@orangepirv2:/home/orangepi/ebpf# ./target/debug/ebpf-ctl event-log list
event-log mask: 0x102
enabled events:
  &lt;span class="o"&gt;[&lt;/span&gt;x] PacketDrop
  &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; RateLimited
  &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; ConntrackMiss
  &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; BackendSelected
  &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; SlowPath
  &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; ServiceMatched
  &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; VlanDetected
  &lt;span class="o"&gt;[&lt;/span&gt;x] PacketAllow
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;UDP traffic was not tested separately with iperf3 for the allow scenario because an iperf3 UDP test requires an additional TCP connection to control the session.&lt;/p&gt;

&lt;p&gt;However, UDP processing at the ACL level was directly verified using a Drop rule. The eBPF logs recorded a packet with protocol=UDP and dst_port=80, after which the packet was dropped.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./target/debug/ebpf-ctl acl add allow src 192.168.0.3:any dst 192.168.0.202:80 proto tcp
ACL rule added
added rule Allow: tcp 192.168.0.3:any -&amp;gt; 192.168.0.202:80 &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;prefix_len&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;320&lt;span class="o"&gt;)&lt;/span&gt;
root@orangepirv2:/home/orangepi/ebpf# ./target/debug/ebpf-ctl acl list
&lt;span class="nb"&gt;id  &lt;/span&gt;plen  fam  src                                dst                                proto sport  dport  rate  action
0   304   4    192.168.0.0                        192.168.0.202                      icmp any    any    -     Drop
1   320   4    192.168.0.0                        192.168.0.202                      tcp  any    80     -     Drop
2   320   4    192.168.0.0                        192.168.0.202                      udp  any    80     -     Drop
3   320   4    192.168.0.3                        192.168.0.202                      tcp  any    80     -     Allow
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;root@orangepirv2:/home/orangepi/ebpf# iperf3 &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 80  
&lt;span class="nt"&gt;-----------------------------------------------------------&lt;/span&gt;
Server listening on 80 &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="c"&gt;#1)&lt;/span&gt;
&lt;span class="nt"&gt;-----------------------------------------------------------&lt;/span&gt;
Accepted connection from 192.168.0.3, port 48720
&lt;span class="o"&gt;[&lt;/span&gt;  5] &lt;span class="nb"&gt;local &lt;/span&gt;192.168.0.202 port 80 connected to 192.168.0.3 port 48730
&lt;span class="o"&gt;[&lt;/span&gt; ID] Interval           Transfer     Bitrate
&lt;span class="o"&gt;[&lt;/span&gt;  5]   0.00-1.00   sec  58.0 MBytes   486 Mbits/sec                  
&lt;span class="o"&gt;[&lt;/span&gt;  5]   1.00-2.00   sec  59.9 MBytes   502 Mbits/sec                  
&lt;span class="o"&gt;[&lt;/span&gt;  5]   2.00-3.00   sec  60.1 MBytes   504 Mbits/sec                  
&lt;span class="o"&gt;[&lt;/span&gt;  5]   3.00-4.00   sec  59.5 MBytes   499 Mbits/sec                  
&lt;span class="o"&gt;[&lt;/span&gt;  5]   4.00-5.00   sec  58.6 MBytes   492 Mbits/sec                  
&lt;span class="o"&gt;[&lt;/span&gt;  5]   5.00-6.00   sec  58.5 MBytes   491 Mbits/sec                  
&lt;span class="o"&gt;[&lt;/span&gt;  5]   6.00-7.00   sec  59.2 MBytes   497 Mbits/sec                  
&lt;span class="o"&gt;[&lt;/span&gt;  5]   7.00-8.00   sec  60.1 MBytes   504 Mbits/sec                  
&lt;span class="o"&gt;[&lt;/span&gt;  5]   8.00-9.00   sec  60.2 MBytes   505 Mbits/sec                  
&lt;span class="o"&gt;[&lt;/span&gt;  5]   9.00-10.00  sec  59.4 MBytes   498 Mbits/sec                  
&lt;span class="o"&gt;[&lt;/span&gt;  5]  10.00-10.02  sec  1.25 MBytes   510 Mbits/sec                  
- - - - - - - - - - - - - - - - - - - - - - - - -
&lt;span class="o"&gt;[&lt;/span&gt; ID] Interval           Transfer     Bitrate
&lt;span class="o"&gt;[&lt;/span&gt;  5]   0.00-10.02  sec   595 MBytes   498 Mbits/sec                  receiver
&lt;span class="nt"&gt;-----------------------------------------------------------&lt;/span&gt;
Server listening on 80 &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="c"&gt;#2)&lt;/span&gt;
&lt;span class="nt"&gt;-----------------------------------------------------------&lt;/span&gt;
^Ciperf3: interrupt - the server has terminated
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;root@orangepirv2:/home/orangepi/ebpf# cat ./test.log | grep "192.168.0.3"
…
2026-08-13T18:24:00.442171Z  INFO ebpf_daemon::monitoring: eBPF event timestamp_ns=548786152157138 event_kind=PacketAllow ifindex=2 src=192.168.0.3 dst=192.168.0.202 src_port=32988 dst_port=80 protocol=TCP aux0=0 aux1=0
2026-08-13T18:24:00.442250Z  INFO ebpf_daemon::monitoring: eBPF event timestamp_ns=548786152166221 event_kind=PacketAllow ifindex=2 src=192.168.0.3 dst=192.168.0.202 src_port=32988 dst_port=80 protocol=TCP aux0=0 aux1=0
2026-08-13T18:24:00.442329Z  INFO ebpf_daemon::monitoring: eBPF event timestamp_ns=548786152174721 event_kind=PacketAllow ifindex=2 src=192.168.0.3 dst=192.168.0.202 src_port=32988 dst_port=80 protocol=TCP aux0=0 aux1=0
2026-08-13T18:24:00.442407Z  INFO ebpf_daemon::monitoring: eBPF event timestamp_ns=548786152184263 event_kind=PacketAllow ifindex=2 src=192.168.0.3 dst=192.168.0.202 src_port=32988 dst_port=80 protocol=TCP aux0=0 aux1=0
2026-08-13T18:24:00.442486Z  INFO ebpf_daemon::monitoring: eBPF event timestamp_ns=548786152192096 event_kind=PacketAllow ifindex=2 src=192.168.0.3 dst=192.168.0.202 src_port=32988 dst_port=80 protocol=TCP aux0=0 aux1=0
2026-08-13T18:24:00.442565Z  INFO ebpf_daemon::monitoring: eBPF event timestamp_ns=548786152200096 event_kind=PacketAllow ifindex=2 src=192.168.0.3 dst=192.168.0.202 src_port=32988 dst_port=80 protocol=TCP aux0=0 aux1=0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Regarding the rate limit (this will simply be checked via iperf3):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./target/debug/ebpf-ctl acl list
&lt;span class="nb"&gt;id  &lt;/span&gt;plen  fam  src                                dst                                proto sport  dport  rate  action
0   304   4    192.168.0.0                        192.168.0.202                      icmp any    any    -     Drop
1   320   4    192.168.0.0                        192.168.0.202                      tcp  any    80     -     Drop
2   320   4    192.168.0.0                        192.168.0.202                      udp  any    80     -     Drop
3   320   4    192.168.0.3                        192.168.0.202                      tcp  any    80     -     Allow
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;root@orangepirv2:/home/orangepi/ebpf# ./target/debug/ebpf-ctl acl del &lt;span class="nt"&gt;--id&lt;/span&gt; 3
Deleted ACL rule 3
deleted rule &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;root@orangepirv2:/home/orangepi/ebpf# ./target/debug/ebpf-ctl acl add allow src 192.168.0.3:any dst 192.168.0.202:80 proto tcp rate 1
ACL rule added
added rule Allow: tcp 192.168.0.3:any -&amp;gt; 192.168.0.202:80 &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;prefix_len&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;320 &lt;span class="nv"&gt;rate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1&lt;span class="o"&gt;)&lt;/span&gt;
root@orangepirv2:/home/orangepi/ebpf# ./target/debug/ebpf-ctl acl list
&lt;span class="nb"&gt;id  &lt;/span&gt;plen  fam  src                                dst                                proto sport  dport  rate  action
0   304   4    192.168.0.0                        192.168.0.202                      icmp any    any    -     Drop
1   320   4    192.168.0.0                        192.168.0.202                      tcp  any    80     -     Drop
2   320   4    192.168.0.0                        192.168.0.202                      udp  any    80     -     Drop
3   320   4    192.168.0.3                        192.168.0.202                      tcp  any    80     1     Allow
root@orangepirv2:/home/orangepi/ebpf# 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;According to the tests it will be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;root@orangepirv2:~# iperf3 &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 80  
&lt;span class="nt"&gt;-----------------------------------------------------------&lt;/span&gt;
Server listening on 80 &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="c"&gt;#1)&lt;/span&gt;
&lt;span class="nt"&gt;-----------------------------------------------------------&lt;/span&gt;
Accepted connection from 192.168.0.3, port 45004
&lt;span class="o"&gt;[&lt;/span&gt;  5] &lt;span class="nb"&gt;local &lt;/span&gt;192.168.0.202 port 80 connected to 192.168.0.3 port 45010
&lt;span class="o"&gt;[&lt;/span&gt; ID] Interval           Transfer     Bitrate
&lt;span class="o"&gt;[&lt;/span&gt;  5]   0.00-1.00   sec   256 KBytes  2.09 Mbits/sec                  
&lt;span class="o"&gt;[&lt;/span&gt;  5]   1.00-2.00   sec  0.00 Bytes  0.00 bits/sec                  
&lt;span class="o"&gt;[&lt;/span&gt;  5]   2.00-3.00   sec  0.00 Bytes  0.00 bits/sec                  
&lt;span class="o"&gt;[&lt;/span&gt;  5]   3.00-4.00   sec  0.00 Bytes  0.00 bits/sec                  
&lt;span class="o"&gt;[&lt;/span&gt;  5]   4.00-5.00   sec  0.00 Bytes  0.00 bits/sec                  
&lt;span class="o"&gt;[&lt;/span&gt;  5]   5.00-6.00   sec  0.00 Bytes  0.00 bits/sec                  
&lt;span class="o"&gt;[&lt;/span&gt;  5]   6.00-7.00   sec  0.00 Bytes  0.00 bits/sec                  
&lt;span class="o"&gt;[&lt;/span&gt;  5]   7.00-8.00   sec  0.00 Bytes  0.00 bits/sec                  
&lt;span class="o"&gt;[&lt;/span&gt;  5]   8.00-9.00   sec  0.00 Bytes  0.00 bits/sec                  
&lt;span class="o"&gt;[&lt;/span&gt;  5]   9.00-10.00  sec  0.00 Bytes  0.00 bits/sec                  
&lt;span class="o"&gt;[&lt;/span&gt;  5]  10.00-10.21  sec   100 KBytes  3.90 Mbits/sec                  
- - - - - - - - - - - - - - - - - - - - - - - - -
&lt;span class="o"&gt;[&lt;/span&gt; ID] Interval           Transfer     Bitrate
&lt;span class="o"&gt;[&lt;/span&gt;  5]   0.00-10.21  sec   356 KBytes   286 Kbits/sec                  receiver
&lt;span class="nt"&gt;-----------------------------------------------------------&lt;/span&gt;
Server listening on 80 &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="c"&gt;#2)&lt;/span&gt;
&lt;span class="nt"&gt;-----------------------------------------------------------&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let's compare this with the results from the same type of test performed earlier.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Testing Deny and Allow with VLAN ACL
&lt;/h3&gt;

&lt;p&gt;For a functional VLAN ACL test, a small isolated setup with two or three nodes is sufficient. A larger test environment with many VLANs, hosts, and policies would be necessary for evaluating dataplane performance and scalability as the number of rules and network segments increases.&lt;/p&gt;

&lt;p&gt;The VLAN test was performed on the physical end0 interface. The end0.300 and end0.100.200 VLAN subinterfaces were used for IP configuration and generating test traffic, while the actual VLAN header processing was performed by the XDP program on incoming traffic received through the physical interface.&lt;/p&gt;

&lt;h3&gt;
  
  
  MikroTik VLAN Configuration
&lt;/h3&gt;

&lt;p&gt;Here is how I configured the VLANs on the MikroTik:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/interface vlan
add &lt;span class="nv"&gt;interface&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;bridge0 &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;vlan300 vlan-id&lt;span class="o"&gt;=&lt;/span&gt;300
/ip address
add &lt;span class="nv"&gt;address&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.100.1/24 &lt;span class="nv"&gt;interface&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;vlan300
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;How I configured VLAN on the OrangePi RV2 (I configured it similarly on the R2S):&lt;br&gt;
Standard VLAN:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ip &lt;span class="nb"&gt;link &lt;/span&gt;add &lt;span class="nb"&gt;link &lt;/span&gt;end0 name end0.300 &lt;span class="nb"&gt;type &lt;/span&gt;vlan &lt;span class="nb"&gt;id &lt;/span&gt;300 ip addr add 192.168.100.2/24 dev end0.300 ip &lt;span class="nb"&gt;link set &lt;/span&gt;end0.300 up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;QinQ on Mikrotik:&lt;br&gt;
External tag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/interface vlan
add &lt;span class="nv"&gt;interface&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;bridge0 &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;qinq100 vlan-id&lt;span class="o"&gt;=&lt;/span&gt;100 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Internal tag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;interface vlan
add &lt;span class="nv"&gt;interface&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;qinq100 &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;vlan200 vlan-id&lt;span class="o"&gt;=&lt;/span&gt;200
/ip address
add &lt;span class="nv"&gt;address&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.200.1/24 &lt;span class="nv"&gt;interface&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;vlan200
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On orangepi:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ip &lt;span class="nb"&gt;link &lt;/span&gt;add &lt;span class="nb"&gt;link &lt;/span&gt;end0 name end0.100 &lt;span class="nb"&gt;type &lt;/span&gt;vlan &lt;span class="nb"&gt;id &lt;/span&gt;100 ip &lt;span class="nb"&gt;link &lt;/span&gt;add &lt;span class="nb"&gt;link &lt;/span&gt;end0.100 name end0.100.200 &lt;span class="nb"&gt;type &lt;/span&gt;vlan &lt;span class="nb"&gt;id &lt;/span&gt;200 ip addr add 192.168.200.2/24 dev end0.100.200
и получаем на orangepi :
6: end0.100@end0: &amp;lt;BROADCAST,MULTICAST,UP,LOWER_UP&amp;gt; mtu 1500 qdisc noqueue state UP group default qlen 1000
    &lt;span class="nb"&gt;link&lt;/span&gt;/ether c0:74:2b:fa:72:0c brd ff:ff:ff:ff:ff:ff
    inet6 fe80::c274:2bff:fefa:720c/64 scope &lt;span class="nb"&gt;link 
       &lt;/span&gt;valid_lft forever preferred_lft forever
7: end0.100.200@end0.100: &amp;lt;BROADCAST,MULTICAST,UP,LOWER_UP&amp;gt; mtu 1500 qdisc noqueue state UP group default qlen 1000
    &lt;span class="nb"&gt;link&lt;/span&gt;/ether c0:74:2b:fa:72:0c brd ff:ff:ff:ff:ff:ff
    inet 192.168.200.2/24 scope global end0.100.200
       valid_lft forever preferred_lft forever
    inet6 fe80::c274:2bff:fefa:720c/64 scope &lt;span class="nb"&gt;link 
       &lt;/span&gt;valid_lft forever preferred_lft forever
9: end0.300@end0: &amp;lt;BROADCAST,MULTICAST,UP,LOWER_UP&amp;gt; mtu 1500 qdisc noqueue state UP group default qlen 1000
    &lt;span class="nb"&gt;link&lt;/span&gt;/ether c0:74:2b:fa:72:0c brd ff:ff:ff:ff:ff:ff
    inet 192.168.100.2/24 scope global end0.300
       valid_lft forever preferred_lft forever
    inet6 fe80::c274:2bff:fefa:720c/64 scope &lt;span class="nb"&gt;link 
       &lt;/span&gt;valid_lft forever preferred_lft forever
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;4.1 Deny VLAN ACL:&lt;/p&gt;

&lt;h3&gt;
  
  
  For a Clean Test:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;root@orangepirv2:/home/orangepi/ebpf# ./target/debug/ebpf-ctl acl del &lt;span class="nt"&gt;--id&lt;/span&gt; 0
error: Failed to delete rule: Failed to delete ACL rule
root@orangepirv2:/home/orangepi/ebpf# ./target/debug/ebpf-ctl acl list
&lt;span class="o"&gt;(&lt;/span&gt;l4_acl map is empty&lt;span class="o"&gt;)&lt;/span&gt;
root@orangepirv2:/home/orangepi/ebpf# ./target/debug/ebpf-ctl acl vlan list
&lt;span class="o"&gt;(&lt;/span&gt;vlan_acl map is empty&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;root@orangepirv2:/home/orangepi/ebpf# ./target/debug/ebpf-ctl event-log list
event-log mask: 0x2
enabled events:
  &lt;span class="o"&gt;[&lt;/span&gt;x] PacketDrop
  &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; RateLimited
  &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; ConntrackMiss
  &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; BackendSelected
  &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; SlowPath
  &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; ServiceMatched
  &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; VlanDetected
  &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; PacketAllow
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;root@orangepirv2:/home/orangepi/ebpf# ./target/debug/ebpf-ctl event-log &lt;span class="nb"&gt;enable &lt;/span&gt;VlanDetected
enabled event-log: VlanDetected
root@orangepirv2:/home/orangepi/ebpf# ./target/debug/ebpf-ctl acl vlan add 300 drop
Added VLAN rule: &lt;span class="nv"&gt;outer_vlan&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;300
added vlan rule &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;outer&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;300&lt;span class="o"&gt;)&lt;/span&gt;: drop
root@orangepirv2:/home/orangepi/ebpf#
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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; ./test.log 
…
&lt;span class="nv"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.100.1 &lt;span class="nv"&gt;dst&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.100.2 &lt;span class="nv"&gt;src_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;dst_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;protocol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ICMP &lt;span class="nv"&gt;aux0&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;300 &lt;span class="nv"&gt;aux1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
2026-08-13T19:19:27.799742Z  INFO ebpf_daemon::monitoring: eBPF event &lt;span class="nv"&gt;timestamp_ns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;552121190282679 &lt;span class="nv"&gt;event_kind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;PacketDrop &lt;span class="nv"&gt;ifindex&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2 &lt;span class="nv"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.100.1 &lt;span class="nv"&gt;dst&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.100.2 &lt;span class="nv"&gt;src_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;dst_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;protocol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ICMP &lt;span class="nv"&gt;aux0&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;aux1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
2026-08-13T19:19:28.813405Z  INFO ebpf_daemon::monitoring: eBPF event &lt;span class="nv"&gt;timestamp_ns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;552122204262459 &lt;span class="nv"&gt;event_kind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;VlanDetected &lt;span class="nv"&gt;ifindex&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2 &lt;span class="nv"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.100.1 &lt;span class="nv"&gt;dst&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.100.2 &lt;span class="nv"&gt;src_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;dst_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;protocol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ICMP &lt;span class="nv"&gt;aux0&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;300 &lt;span class="nv"&gt;aux1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
2026-08-13T19:19:28.813658Z  INFO ebpf_daemon::monitoring: eBPF event &lt;span class="nv"&gt;timestamp_ns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;552122204286126 &lt;span class="nv"&gt;event_kind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;PacketDrop &lt;span class="nv"&gt;ifindex&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2 &lt;span class="nv"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.100.1 &lt;span class="nv"&gt;dst&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.100.2 &lt;span class="nv"&gt;src_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;dst_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;protocol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ICMP &lt;span class="nv"&gt;aux0&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;aux1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
2026-08-13T19:19:29.837427Z  INFO ebpf_daemon::monitoring: eBPF event &lt;span class="nv"&gt;timestamp_ns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;552123228281300 &lt;span class="nv"&gt;event_kind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;VlanDetected &lt;span class="nv"&gt;ifindex&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2 &lt;span class="nv"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.100.1 &lt;span class="nv"&gt;dst&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.100.2 &lt;span class="nv"&gt;src_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;dst_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;protocol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ICMP &lt;span class="nv"&gt;aux0&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;300 &lt;span class="nv"&gt;aux1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
2026-08-13T19:19:29.837677Z  INFO ebpf_daemon::monitoring: eBPF event &lt;span class="nv"&gt;timestamp_ns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;552123228302716 &lt;span class="nv"&gt;event_kind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;PacketDrop &lt;span class="nv"&gt;ifindex&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2 &lt;span class="nv"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.100.1 &lt;span class="nv"&gt;dst&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.100.2 &lt;span class="nv"&gt;src_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;dst_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;protocol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ICMP &lt;span class="nv"&gt;aux0&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;aux1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
2026-08-13T19:19:30.861526Z  INFO ebpf_daemon::monitoring: eBPF event &lt;span class="nv"&gt;timestamp_ns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;552124252362097 &lt;span class="nv"&gt;event_kind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;VlanDetected &lt;span class="nv"&gt;ifindex&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2 &lt;span class="nv"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.100.1 &lt;span class="nv"&gt;dst&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.100.2 &lt;span class="nv"&gt;src_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;dst_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;protocol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ICMP &lt;span class="nv"&gt;aux0&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;300 &lt;span class="nv"&gt;aux1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
2026-08-13T19:19:30.861864Z  INFO ebpf_daemon::monitoring: eBPF event &lt;span class="nv"&gt;timestamp_ns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;552124252401680 &lt;span class="nv"&gt;event_kind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;PacketDrop &lt;span class="nv"&gt;ifindex&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2 &lt;span class="nv"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.100.1 &lt;span class="nv"&gt;dst&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.100.2 &lt;span class="nv"&gt;src_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;dst_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;protocol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ICMP &lt;span class="nv"&gt;aux0&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;aux1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
2026-08-13T19:19:31.885517Z  INFO ebpf_daemon::monitoring: eBPF event &lt;span class="nv"&gt;timestamp_ns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;552125276358813 &lt;span class="nv"&gt;event_kind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;VlanDetected &lt;span class="nv"&gt;ifindex&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2 &lt;span class="nv"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.100.1 &lt;span class="nv"&gt;dst&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.100.2 &lt;span class="nv"&gt;src_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;dst_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;protocol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ICMP &lt;span class="nv"&gt;aux0&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;300 &lt;span class="nv"&gt;aux1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
2026-08-13T19:19:31.885760Z  INFO ebpf_daemon::monitoring: eBPF event &lt;span class="nv"&gt;timestamp_ns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;552125276389521 &lt;span class="nv"&gt;event_kind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;PacketDrop &lt;span class="nv"&gt;ifindex&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2 &lt;span class="nv"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.100.1 &lt;span class="nv"&gt;dst&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.100.2 &lt;span class="nv"&gt;src_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;dst_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;protocol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ICMP &lt;span class="nv"&gt;aux0&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;aux1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
2026-08-13T19:19:32.909459Z  INFO ebpf_daemon::monitoring: eBPF event &lt;span class="nv"&gt;timestamp_ns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;552126300305529 &lt;span class="nv"&gt;event_kind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;VlanDetected &lt;span class="nv"&gt;ifindex&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2 &lt;span class="nv"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.100.1 &lt;span class="nv"&gt;dst&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.100.2 &lt;span class="nv"&gt;src_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;dst_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;protocol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ICMP &lt;span class="nv"&gt;aux0&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;300 &lt;span class="nv"&gt;aux1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
2026-08-13T19:19:32.909704Z  INFO ebpf_daemon::monitoring: eBPF event &lt;span class="nv"&gt;timestamp_ns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;552126300335362 &lt;span class="nv"&gt;event_kind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;PacketDrop &lt;span class="nv"&gt;ifindex&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2 &lt;span class="nv"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.100.1 &lt;span class="nv"&gt;dst&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.100.2 &lt;span class="nv"&gt;src_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;dst_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;protocol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ICMP &lt;span class="nv"&gt;aux0&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;aux1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
root@orangepirv2:/home/orangepi/ebpf# 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;root@orangepirv2:/home/orangepi/ebpf# ./target/debug/ebpf-ctl acl vlan add 100 drop inner 200

2026-08-13T19:21:11.197702Z  INFO ebpf_daemon::monitoring: eBPF event &lt;span class="nv"&gt;timestamp_ns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;552224588524734 &lt;span class="nv"&gt;event_kind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;VlanDetected &lt;span class="nv"&gt;ifindex&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2 &lt;span class="nv"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.200.1 &lt;span class="nv"&gt;dst&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.200.2 &lt;span class="nv"&gt;src_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;dst_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;protocol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ICMP &lt;span class="nv"&gt;aux0&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;100 &lt;span class="nv"&gt;aux1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2
2026-08-13T19:21:11.198016Z  INFO ebpf_daemon::monitoring: eBPF event &lt;span class="nv"&gt;timestamp_ns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;552224588554442 &lt;span class="nv"&gt;event_kind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;PacketDrop &lt;span class="nv"&gt;ifindex&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2 &lt;span class="nv"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.200.1 &lt;span class="nv"&gt;dst&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.200.2 &lt;span class="nv"&gt;src_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;dst_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;protocol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ICMP &lt;span class="nv"&gt;aux0&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;aux1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
2026-08-13T19:21:12.209419Z  INFO ebpf_daemon::monitoring: eBPF event &lt;span class="nv"&gt;timestamp_ns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;552225600272714 &lt;span class="nv"&gt;event_kind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;VlanDetected &lt;span class="nv"&gt;ifindex&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2 &lt;span class="nv"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.200.1 &lt;span class="nv"&gt;dst&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.200.2 &lt;span class="nv"&gt;src_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;dst_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;protocol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ICMP &lt;span class="nv"&gt;aux0&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;100 &lt;span class="nv"&gt;aux1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2
2026-08-13T19:21:12.209665Z  INFO ebpf_daemon::monitoring: eBPF event &lt;span class="nv"&gt;timestamp_ns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;552225600299630 &lt;span class="nv"&gt;event_kind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;PacketDrop &lt;span class="nv"&gt;ifindex&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2 &lt;span class="nv"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.200.1 &lt;span class="nv"&gt;dst&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.200.2 &lt;span class="nv"&gt;src_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;dst_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;protocol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ICMP &lt;span class="nv"&gt;aux0&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;aux1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
2026-08-13T19:21:13.233435Z  INFO ebpf_daemon::monitoring: eBPF event &lt;span class="nv"&gt;timestamp_ns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;552226624286596 &lt;span class="nv"&gt;event_kind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;VlanDetected &lt;span class="nv"&gt;ifindex&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2 &lt;span class="nv"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.200.1 &lt;span class="nv"&gt;dst&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.200.2 &lt;span class="nv"&gt;src_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;dst_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;protocol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ICMP &lt;span class="nv"&gt;aux0&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;100 &lt;span class="nv"&gt;aux1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2
2026-08-13T19:21:13.233753Z  INFO ebpf_daemon::monitoring: eBPF event &lt;span class="nv"&gt;timestamp_ns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;552226624313137 &lt;span class="nv"&gt;event_kind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;PacketDrop &lt;span class="nv"&gt;ifindex&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2 &lt;span class="nv"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.200.1 &lt;span class="nv"&gt;dst&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.200.2 &lt;span class="nv"&gt;src_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;dst_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;protocol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ICMP &lt;span class="nv"&gt;aux0&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;aux1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
2026-08-13T19:21:14.257513Z  INFO ebpf_daemon::monitoring: eBPF event &lt;span class="nv"&gt;timestamp_ns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;552227648361060 &lt;span class="nv"&gt;event_kind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;VlanDetected &lt;span class="nv"&gt;ifindex&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2 &lt;span class="nv"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.200.1 &lt;span class="nv"&gt;dst&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.200.2 &lt;span class="nv"&gt;src_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;dst_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;protocol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ICMP &lt;span class="nv"&gt;aux0&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;100 &lt;span class="nv"&gt;aux1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2
2026-08-13T19:21:14.257769Z  INFO ebpf_daemon::monitoring: eBPF event &lt;span class="nv"&gt;timestamp_ns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;552227648388352 &lt;span class="nv"&gt;event_kind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;PacketDrop &lt;span class="nv"&gt;ifindex&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2 &lt;span class="nv"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.200.1 &lt;span class="nv"&gt;dst&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.200.2 &lt;span class="nv"&gt;src_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;dst_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;protocol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ICMP &lt;span class="nv"&gt;aux0&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;aux1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
2026-08-13T19:21:15.277840Z  INFO ebpf_daemon::monitoring: eBPF event &lt;span class="nv"&gt;timestamp_ns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;552228668675580 &lt;span class="nv"&gt;event_kind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;VlanDetected &lt;span class="nv"&gt;ifindex&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2 &lt;span class="nv"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.200.1 &lt;span class="nv"&gt;dst&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.200.2 &lt;span class="nv"&gt;src_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;dst_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;protocol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ICMP &lt;span class="nv"&gt;aux0&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;100 &lt;span class="nv"&gt;aux1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2
2026-08-13T19:21:15.278091Z  INFO ebpf_daemon::monitoring: eBPF event &lt;span class="nv"&gt;timestamp_ns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;552228668710455 &lt;span class="nv"&gt;event_kind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;PacketDrop &lt;span class="nv"&gt;ifindex&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2 &lt;span class="nv"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.200.1 &lt;span class="nv"&gt;dst&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.200.2 &lt;span class="nv"&gt;src_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;dst_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;protocol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ICMP &lt;span class="nv"&gt;aux0&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;aux1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
2026-08-13T19:21:16.301488Z  INFO ebpf_daemon::monitoring: eBPF event &lt;span class="nv"&gt;timestamp_ns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;552229692338134 &lt;span class="nv"&gt;event_kind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;VlanDetected &lt;span class="nv"&gt;ifindex&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2 &lt;span class="nv"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.200.1 &lt;span class="nv"&gt;dst&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.200.2 &lt;span class="nv"&gt;src_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;dst_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;protocol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ICMP &lt;span class="nv"&gt;aux0&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;100 &lt;span class="nv"&gt;aux1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2
2026-08-13T19:21:16.301757Z  INFO ebpf_daemon::monitoring: eBPF event &lt;span class="nv"&gt;timestamp_ns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;552229692367675 &lt;span class="nv"&gt;event_kind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;PacketDrop &lt;span class="nv"&gt;ifindex&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2 &lt;span class="nv"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.200.1 &lt;span class="nv"&gt;dst&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.200.2 &lt;span class="nv"&gt;src_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;dst_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;protocol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ICMP &lt;span class="nv"&gt;aux0&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;aux1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
2026-08-13T19:21:17.325564Z  INFO ebpf_daemon::monitoring: eBPF event &lt;span class="nv"&gt;timestamp_ns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;552230716399223 &lt;span class="nv"&gt;event_kind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;VlanDetected &lt;span class="nv"&gt;ifindex&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2 &lt;span class="nv"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.200.1 &lt;span class="nv"&gt;dst&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.200.2 &lt;span class="nv"&gt;src_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;dst_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;protocol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ICMP &lt;span class="nv"&gt;aux0&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;100 &lt;span class="nv"&gt;aux1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2
2026-08-13T19:21:17.325825Z  INFO ebpf_daemon::monitoring: eBPF event &lt;span class="nv"&gt;timestamp_ns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;552230716434556 &lt;span class="nv"&gt;event_kind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;PacketDrop &lt;span class="nv"&gt;ifindex&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2 &lt;span class="nv"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.200.1 &lt;span class="nv"&gt;dst&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.200.2 &lt;span class="nv"&gt;src_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;dst_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;protocol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ICMP &lt;span class="nv"&gt;aux0&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;aux1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Checking the rate limit on the VLAN:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./target/debug/ebpf-ctl acl vlan add 100 allow inner 200 rate 1
Added VLAN rule: &lt;span class="nv"&gt;outer_vlan&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;100
added vlan rule &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;outer&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;100 &lt;span class="nv"&gt;inner&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;200 &lt;span class="nv"&gt;rate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1&lt;span class="o"&gt;)&lt;/span&gt;: allow

iperf3 &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 80 
&lt;span class="nt"&gt;-----------------------------------------------------------&lt;/span&gt;
Server listening on 80 &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="c"&gt;#1)&lt;/span&gt;
&lt;span class="nt"&gt;-----------------------------------------------------------&lt;/span&gt;
Accepted connection from 192.168.200.3, port 39500
&lt;span class="o"&gt;[&lt;/span&gt;  5] &lt;span class="nb"&gt;local &lt;/span&gt;192.168.200.2 port 80 connected to 192.168.200.3 port 39508
&lt;span class="o"&gt;[&lt;/span&gt; ID] Interval           Transfer     Bitrate
&lt;span class="o"&gt;[&lt;/span&gt;  5]   0.00-1.00   sec  0.00 Bytes  0.00 bits/sec                  
&lt;span class="o"&gt;[&lt;/span&gt;  5]   1.00-2.00   sec  0.00 Bytes  0.00 bits/sec                  
&lt;span class="o"&gt;[&lt;/span&gt;  5]   2.00-3.00   sec  0.00 Bytes  0.00 bits/sec                  
&lt;span class="o"&gt;[&lt;/span&gt;  5]   3.00-4.00   sec  0.00 Bytes  0.00 bits/sec                  
&lt;span class="o"&gt;[&lt;/span&gt;  5]   4.00-5.00   sec  0.00 Bytes  0.00 bits/sec                  
&lt;span class="o"&gt;[&lt;/span&gt;  5]   5.00-6.00   sec  0.00 Bytes  0.00 bits/sec                  
&lt;span class="o"&gt;[&lt;/span&gt;  5]   6.00-7.00   sec  0.00 Bytes  0.00 bits/sec                  
&lt;span class="o"&gt;[&lt;/span&gt;  5]   7.00-8.00   sec  0.00 Bytes  0.00 bits/sec                  
&lt;span class="o"&gt;[&lt;/span&gt;  5]   8.00-9.00   sec  0.00 Bytes  0.00 bits/sec                  
&lt;span class="o"&gt;[&lt;/span&gt;  5]   9.00-10.00  sec  0.00 Bytes  0.00 bits/sec                  
- - - - - - - - - - - - - - - - - - - - - - - - -
&lt;span class="o"&gt;[&lt;/span&gt; ID] Interval           Transfer     Bitrate
&lt;span class="o"&gt;[&lt;/span&gt;  5]   0.00-10.00  sec  0.00 Bytes  0.00 bits/sec                  receiver
&lt;span class="nt"&gt;-----------------------------------------------------------&lt;/span&gt;
Server listening on 80 &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="c"&gt;#2)&lt;/span&gt;
&lt;span class="nt"&gt;-----------------------------------------------------------&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Check the application of rules from the configuration file:
&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="nb"&gt;cat&lt;/span&gt; ./config2.yml 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;acl&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;allow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;vlan&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="m"&gt;300&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;outer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;200&lt;/span&gt;
          &lt;span class="na"&gt;inner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="m"&gt;101&lt;/span&gt;
      &lt;span class="na"&gt;rate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;addresses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;192.168.0.2/32&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;192.168.100.0/24&lt;/span&gt;
      &lt;span class="na"&gt;dst&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;addresses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;192.168.0.202&lt;/span&gt;
        &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;number&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
          &lt;span class="na"&gt;proto&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tcp&lt;/span&gt;
      &lt;span class="na"&gt;rate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;

  &lt;span class="na"&gt;drop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;vlan&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="m"&gt;300&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;outer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;130&lt;/span&gt;
          &lt;span class="na"&gt;inner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;addresses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;fe80::/64&lt;/span&gt;
      &lt;span class="na"&gt;dst&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;addresses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;fe80::c274:2bff:fefa:720c&lt;/span&gt;
        &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;number&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;any&lt;/span&gt;
          &lt;span class="na"&gt;proto&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;icmp&lt;/span&gt;
      &lt;span class="na"&gt;rate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;addresses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;10.0.0.0/8&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;192.168.200.1/24&lt;/span&gt;
      &lt;span class="na"&gt;dst&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;addresses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;192.168.0.2&lt;/span&gt;
        &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;number&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;any&lt;/span&gt;
          &lt;span class="na"&gt;proto&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;icmp&lt;/span&gt;
      &lt;span class="na"&gt;rate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;

&lt;span class="na"&gt;logging&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;PacketDrop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;RateLimited&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="na"&gt;ConntrackMiss&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="na"&gt;BackendSelected&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="na"&gt;SlowPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="na"&gt;ServiceMatched&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;VlanDetected&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;PacketAllow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And this is how Pulsar itself reads it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cat ./test.log 
2026-08-13T20:11:00.641182Z  INFO ebpf_daemon::manager::maps: rate_limit: global max_tokens=65536, refill_per_sec=64000, per_rule_multiplier=256
2026-08-13T20:11:00.641442Z  INFO ebpf_daemon::manager::maps: monitoring: ringbuf_bytes=16777216, poll_interval_ms=100
2026-08-13T20:11:00.641509Z  INFO ebpf_daemon::manager::maps: acl.default_action: allow
2026-08-13T20:11:00.760323Z  INFO ebpf_daemon::monitoring: Trying XDP program id: 245
2026-08-13T20:11:00.814464Z  INFO ebpf_daemon::monitoring: bpftool prog show id 245: 245: xdp  name xdp_dataplane  tag fa484001870657d5
    loaded_at 2026-08-13T23:09:05+0300  uid 0
    xlated 19776B  jited 9000B  memlock 20480B  map_ids 483,484,485,487,493,492
    btf_id 360
2026-08-13T20:11:00.868271Z  WARN ebpf_daemon::monitoring: Skipping map id 483: Map id 483 is not events ringbuf (name=counters, type=percpu_array)
2026-08-13T20:11:00.922074Z  WARN ebpf_daemon::monitoring: Skipping map id 484: Map id 484 is not events ringbuf (name=event_mask, type=array)
2026-08-13T20:11:00.975972Z  INFO ebpf_daemon::monitoring: Opened events map
2026-08-13T20:11:00.976150Z  INFO ebpf_daemon::monitoring: Events ringbuf size: 16777216 bytes
2026-08-13T20:11:00.983324Z  INFO ebpf_daemon::monitoring: Starting ringbuf event loop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;root@orangepirv2:/home/orangepi/ebpf# ./target/debug/ebpf-ctl acl list
&lt;span class="nb"&gt;id  &lt;/span&gt;plen  fam  src                                dst                                proto sport  dport  rate  action
0   304   4    10.0.0.0                           192.168.0.2                        icmp any    any    -     Drop
1   320   4    192.168.0.2                        192.168.0.202                      tcp  any    80     1     Allow
2   320   4    192.168.100.0                      192.168.0.202                      tcp  any    80     1     Allow
3   304   4    192.168.200.0                      192.168.0.2                        icmp any    any    -     Drop
4   304   6    fe80:0:0:0:0:0:0:0                 fe80:0:0:0:c274:2bff:fefa:720c     icmp any    any    -     Drop
root@orangepirv2:/home/orangepi/ebpf# ./target/debug/ebpf-ctl acl vlan list
&lt;span class="nb"&gt;id    &lt;/span&gt;outer_vlan inner_vlan action   rate  
0     100        101        allow    1     
1     130        100        drop     0     
2     100        200        allow    1     
3     250        -          drop     0     
4     300        -          allow    1 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;root@orangepirv2:/home/orangepi/ebpf# ./target/debug/ebpf-ctl event-log list
event-log mask: 0xc2
enabled events:
  &lt;span class="o"&gt;[&lt;/span&gt;x] PacketDrop
  &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; RateLimited
  &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; ConntrackMiss
  &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; BackendSelected
  &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; SlowPath
  &lt;span class="o"&gt;[&lt;/span&gt;x] ServiceMatched
  &lt;span class="o"&gt;[&lt;/span&gt;x] VlanDetected
  &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; PacketAllow
root@orangepirv2:/home/orangepi/ebpf#

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

&lt;/div&gt;



&lt;p&gt;What Still Needs to Be Done&lt;br&gt;
Current Implementation Limitations&lt;/p&gt;

&lt;p&gt;At the current stage, the implementation of several events and features related to stateful packet processing is not yet complete:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ConntrackMiss&lt;/li&gt;
&lt;li&gt;BackendSelected&lt;/li&gt;
&lt;li&gt;SlowPath&lt;/li&gt;
&lt;li&gt;ServiceMatched&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These features are related to connection state tracking, backend selection, and service traffic processing. They are planned to be implemented at the Traffic Control (TC) layer, where stateful processing will be handled, including conntrack, load balancing, and NAT.&lt;br&gt;
For now, the events listed above are used as placeholders and do not represent fully implemented functionality.&lt;br&gt;
The project is available under an open-source license on GitHub:&lt;br&gt;
&lt;a href="https://github.com/AlexRoot00/Pulsar/tree/master" rel="noopener noreferrer"&gt;https://github.com/AlexRoot00/Pulsar/tree/master&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>I Bought a Robot Vacuum, Not a Cloud Spy</title>
      <dc:creator>Alex Root</dc:creator>
      <pubDate>Mon, 12 Jan 2026 19:21:08 +0000</pubDate>
      <link>https://dev.to/alex_root_48dfe8afa393367/i-bought-a-robot-vacuum-not-a-cloud-spy-3978</link>
      <guid>https://dev.to/alex_root_48dfe8afa393367/i-bought-a-robot-vacuum-not-a-cloud-spy-3978</guid>
      <description>&lt;p&gt;Background: Buying a Used Robot&lt;/p&gt;

&lt;p&gt;I didn’t buy this robot vacuum because I urgently needed automated cleaning.&lt;br&gt;
I bought it used — on purpose.&lt;br&gt;
Buying a brand-new device comes with invisible obligations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;warranty&lt;/li&gt;
&lt;li&gt;the mental barrier of “don’t touch it, you might break something”&lt;/li&gt;
&lt;li&gt;the expectation that it must “work off its price” before you can experiment.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A new device feels like something you’re borrowing from the manufacturer.&lt;br&gt;
A used device is different.&lt;br&gt;
When hardware is already second-hand, already “imperfect”, you’re free.&lt;br&gt;
You’re not breaking a product — you’re giving it a second life.&lt;br&gt;
This mindset matters a lot for engineering projects.&lt;/p&gt;

&lt;p&gt;Why This Model&lt;/p&gt;

&lt;p&gt;The robot is a Xiaomi Robot Mop Essential.&lt;br&gt;
On the local market inside my country, I paid $25, including delivery.&lt;br&gt;
A new one costs around $125.&lt;br&gt;
For $25 I got:&lt;br&gt;
a fully functional robotic platform&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;motors, sensors, MCU, Wi-Fi&lt;/li&gt;
&lt;li&gt;something i could open without guilt
For $125 I would have gotten the same hardware… plus obligations.
The choice was obvious.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Expectations vs Reality&lt;/p&gt;

&lt;p&gt;To be clear: this robot does its job well.&lt;br&gt;
It drives. It cleans. It creates internal maps and attempts to send them to the cloud.&lt;br&gt;
It does randomly crash into walls — literally.&lt;br&gt;
The robot builds its maps by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;bumping into obstacles with its bumper&lt;/li&gt;
&lt;li&gt;calculating position from wheel encoders&lt;/li&gt;
&lt;li&gt;using basic orientation and motion sensors&lt;/li&gt;
&lt;li&gt;No LiDAR. No magic. Just contact, math, and retries.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And it works surprisingly well.&lt;br&gt;
The problem was never cleaning quality.&lt;br&gt;
The problem was control.&lt;/p&gt;

&lt;p&gt;Mechanical Issues and Repairs&lt;/p&gt;

&lt;p&gt;This project didn’t start as reverse engineering.&lt;br&gt;
It started with very practical, very physical problems.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The Wheel Problem&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The robot was used, and it showed it.&lt;br&gt;
One of the metal bushings had literally destroyed the plastic seat in the wheel.&lt;br&gt;
Instead of a clean fit, the bushing had “eaten” its way through the plastic, leaving a loose, unusable connection.&lt;/p&gt;

&lt;p&gt;My fix was simple but effective:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lubricate the metal bushing.&lt;/li&gt;
&lt;li&gt;Fill the damaged plastic seat with a small tube of cyanoacrylate glue (superglue).&lt;/li&gt;
&lt;li&gt;Wait about 2 hours for full curing.&lt;/li&gt;
&lt;li&gt;Remove the bushing and reinstall.
Result: firm fit, no wobble, wheel restored.
This was my first signal: this thing is repairable.&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;Wet Surface Traction (Unsolved)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One mechanical issue still remains: traction on wet surfaces.&lt;br&gt;
The robot sometimes slips during mopping.&lt;/p&gt;

&lt;p&gt;Possible reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;insufficient downforce&lt;/li&gt;
&lt;li&gt;poor contact area&lt;/li&gt;
&lt;li&gt;unsuitable wheel tread design&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I’m experimenting with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;different TPU filaments&lt;/li&gt;
&lt;li&gt;various 3D-printed tread patterns&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Goal: reliable traction without overloading motors.&lt;br&gt;
This problem is open, and that’s fine — some solutions require iteration.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Wet Cleaning System&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The water nozzles were clogged.&lt;br&gt;
Fix: a thin needle normally used for cleaning 3D-printer nozzles.&lt;/p&gt;

&lt;p&gt;While I was there, I also repaired the small water pump feeding the mop cloth.&lt;br&gt;
No replacement parts, no manuals — just understanding the system.&lt;/p&gt;

&lt;p&gt;From Buttons to Control: Where the Real Problem Started&lt;/p&gt;

&lt;p&gt;After fixing the mechanical issues, the robot worked… almost.&lt;br&gt;
To start cleaning, I still had to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;walk up to it&lt;/li&gt;
&lt;li&gt;press a physical button&lt;/li&gt;
&lt;li&gt;watch it desperately try to connect to Wi-Fi&lt;/li&gt;
&lt;li&gt;and eventually give up&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Factory resets didn’t help.&lt;br&gt;
Creating a fresh Mi Home account didn’t help.&lt;br&gt;
Different phones didn’t help.&lt;/p&gt;

&lt;p&gt;The robot wanted Wi-Fi, but it never fully joined.&lt;br&gt;
It built internal maps, sent data to the cloud, and occasionally learned your walls by crashing into them — literally.&lt;br&gt;
At some point, I realized something absurd:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The robot was Wi-Fi enabled, cloud-dependent — and completely unusable without the cloud.&lt;/li&gt;
&lt;li&gt;That was the breaking point.&lt;/li&gt;
&lt;li&gt;I didn’t buy a robot assistant to turn it into a cloud-controlled spy.
I wanted control, not stupid permissions.
So I did what any curious engineer would do: I opened it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Inside the Xiaomi Robot Mop Essential&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%2Ffi2cwdj4nfctm8rtgvqj.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%2Ffi2cwdj4nfctm8rtgvqj.png" alt=" " width="550" height="801"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The robot has a very clear internal hierarchy: two brains.&lt;br&gt;
The Main MCU — The Real Robot&lt;br&gt;
The MCU is the heart of the machine. It’s responsible for everything that actually matters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Motor control&lt;/li&gt;
&lt;li&gt;Wheel encoders&lt;/li&gt;
&lt;li&gt;Bump sensors&lt;/li&gt;
&lt;li&gt;gyroscope and orientation&lt;/li&gt;
&lt;li&gt;navigation logic&lt;/li&gt;
&lt;li&gt;map building&lt;/li&gt;
&lt;/ul&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%2Ffwd1jcrvf3qle2xhyxku.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%2Ffwd1jcrvf3qle2xhyxku.png" alt=" " width="558" height="1280"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Based on available technical data and analysis of similar Xiaomi robots, this MCU typically features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a 32-bit RISC core for control logic&lt;/li&gt;
&lt;li&gt;a dedicated DSP core for real-time sensor processing&lt;/li&gt;
&lt;li&gt;hardware blocks optimized for navigation and SLAM-like calculations&lt;/li&gt;
&lt;li&gt;external Flash memory for firmware and maps&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The MCU doesn’t care about Wi-Fi. It doesn’t know what a cloud is.&lt;br&gt;
It just wants to clean floors.&lt;br&gt;
The ESP32-WROOM — The Permission Relay&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%2F6mj4r3ng6whxrf4q982e.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%2F6mj4r3ng6whxrf4q982e.png" alt=" " width="558" height="1280"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The ESP32 module’s job is limited but critical:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Wi-Fi connectivity&lt;/li&gt;
&lt;li&gt;communication with Xiaomi cloud&lt;/li&gt;
&lt;li&gt;OTA updates&lt;/li&gt;
&lt;li&gt;relaying commands and permissions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It does not drive motors.&lt;br&gt;
It does not decide where the robot goes.&lt;br&gt;
It mostly acts as a translator and gatekeeper between the cloud and the real robot.&lt;/p&gt;

&lt;p&gt;Listening Instead of Guessing: UART Sniffing&lt;br&gt;
(No photos of how I connected the UARTs from the Orange Pi in MITM mode — I didn’t think they’d be useful at the time.) &lt;br&gt;
To fully understand the system, I connected an Orange Pi RV2 as a man-in-the-middle.&lt;br&gt;
Wiring scheme:&lt;br&gt;
&lt;code&gt;UART5 RX and UART5 TX → MCU&lt;/code&gt;&lt;br&gt;
&lt;code&gt;UART7 TX and UART7 RX → ESP32&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Why not just an ESP32?&lt;/p&gt;

&lt;p&gt;Because I needed three UART ports simultaneously:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MCU → ESP32 communication&lt;/li&gt;
&lt;li&gt;ESP32 → MCU communication&lt;/li&gt;
&lt;li&gt;MCU diagnostic port (which I connected later, after freeing UART5 while keeping UART7 dedicated to command transmission)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Orange Pi quietly sniffed all traffic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;no injections&lt;/li&gt;
&lt;li&gt;no modifications&lt;/li&gt;
&lt;li&gt;just observation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I read both the diagnostic UART and the official ESP32 UART simultaneously in a single terminal window.&lt;br&gt;
This let me see exactly what happened when I sent commands, and compare the robot’s reactions to the actual status updates.&lt;/p&gt;

&lt;p&gt;I noticed:&lt;br&gt;
The official UART (ESP32 ↔ MCU) reports only two types of errors:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Incorrect command (command doesn’t apply to this MCU)&lt;/li&gt;
&lt;li&gt;Wrong command
The diagnostic UART shows much more: motor currents, encoder values, gyro readings, and internal flags.
Example diagnostic output when starting cleaning:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Oc[3]=410  Ic[3]=-43  k p=101  
Oc[3]=363  Ic[3]=-58  
p offsg 213  
gyrod=10029  
sg 181  
crc=15fb  
STL start clean  
default map enable:1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Commands sent to the MCU via official UART produce status codes (ok, error…) numerically.&lt;br&gt;
The diagnostic UART shows the robot’s internal response in real time.&lt;br&gt;
What Actually Happens During Startup&lt;br&gt;
Here’s a simplified version of the handshake:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;MCU -&amp;gt; ESP32 : platform_name&lt;/code&gt;&lt;br&gt;
&lt;code&gt;ESP32 -&amp;gt; MCU : ok&lt;/code&gt;&lt;br&gt;
&lt;code&gt;MCU -&amp;gt; ESP32 : firmware_id&lt;/code&gt;&lt;br&gt;
&lt;code&gt;ESP32 -&amp;gt; MCU : ok&lt;/code&gt;&lt;br&gt;
&lt;code&gt;MCU -&amp;gt; ESP32 : mac_device ?&lt;/code&gt;&lt;br&gt;
&lt;code&gt;ESP32 -&amp;gt; MCU : &amp;lt;device_id&amp;gt;&lt;/code&gt;&lt;br&gt;
(success handshake)&lt;br&gt;
&lt;code&gt;MCU -&amp;gt; ESP32 : “Can I request OTA updates?”&lt;/code&gt;&lt;br&gt;
&lt;code&gt;ESP32 -&amp;gt; MCU : ok&lt;/code&gt;&lt;br&gt;
ESP32 can now send commands and listen to response from MCU.&lt;br&gt;
Normal operation begins.&lt;br&gt;
No cryptography. No authentication at this layer. No magic.&lt;/p&gt;

&lt;p&gt;This alone tells you everything:&lt;br&gt;
The MCU asks for permission.&lt;br&gt;
The ESP32 simply relays answers from the cloud.&lt;br&gt;
The robot doesn’t talk to the internet - it asks if it’s allowed to. &lt;br&gt;
Replacing the Cloud with My Own ESP32&lt;/p&gt;

&lt;p&gt;Once it became clear that the MCU was merely asking for permission from the ESP32, the solution was obvious:&lt;br&gt;
If the original ESP32 only replies “ok” to the MCU… then I can reply “ok” too.&lt;br&gt;
Instead of the original chain:&lt;br&gt;
MCU &amp;lt;-&amp;gt; Xiaomi Cloud &amp;lt;-&amp;gt; App&lt;br&gt;
I wanted:&lt;br&gt;
MCU &amp;lt;-&amp;gt; My ESP32 &amp;lt;-&amp;gt; Optional UI / Terminal / Custom Features&lt;br&gt;
Same expectations. Same protocol.&lt;br&gt;
No cloud. No spying.&lt;br&gt;
The factory ESP32 was removed.&lt;br&gt;
My own ESP32 took its place.&lt;br&gt;
Not to fully emulate the cloud — but to get it out of the way.&lt;br&gt;
Now, the MCU can clean floors, build maps, and respond to commands without ever contacting the internet.&lt;/p&gt;

&lt;p&gt;Minimal ESP32 Firmware Architecture.&lt;br&gt;
The firmware I built is modular, with clear responsibilities:&lt;br&gt;
Core system (critical for multitasking and reliability):&lt;br&gt;
dispatcher — task manager&lt;br&gt;
scheduler — time and event scheduling&lt;br&gt;
multicore — leveraging both cores of the ESP32&lt;br&gt;
Hardware &amp;amp; peripherals:&lt;br&gt;
uart — read both diagnostic and official MCU ports simultaneously&lt;br&gt;
ext_flash — work with external SPI flash&lt;br&gt;
display — optional screen for status&lt;br&gt;
buttons &amp;amp; leds — physical interface&lt;br&gt;
Communication &amp;amp; protocols:&lt;br&gt;
miio_miot — simplified implementation of Xiaomi MIOT protocol, enough to relay commands and monitor state&lt;br&gt;
http — web interface for control&lt;br&gt;
auth — authentication for local UI&lt;br&gt;
wifi_mgr — optional Wi-Fi management&lt;br&gt;
Utility &amp;amp; support:&lt;/p&gt;

&lt;p&gt;terminal — serial terminal for debugging&lt;/p&gt;

&lt;p&gt;logger — log all UART traffic and events&lt;br&gt;
sysinfo — system diagnostics&lt;br&gt;
time_sync — keep time in sync&lt;br&gt;
storage — configuration and map persistence&lt;br&gt;
ota — optional firmware updates(ESP32)&lt;/p&gt;

&lt;p&gt;Dual UART Monitoring&lt;br&gt;
One of the key features: reading two UARTs simultaneously.&lt;br&gt;
Official MCU &amp;lt;-&amp;gt; ESP32 UART: status, commands, simple errors&lt;br&gt;
Diagnostic UART: detailed motor currents, encoder readings, gyro data, internal flags&lt;br&gt;
This allowed me to:&lt;br&gt;
verify that commands were correctly interpreted&lt;br&gt;
see detailed internal responses&lt;br&gt;
log all activity for debugging and development&lt;br&gt;
The result is a full local control system, where nothing is hidden behind the cloud.&lt;/p&gt;

&lt;p&gt;Current Status&lt;/p&gt;

&lt;p&gt;At the moment, the ESP32 firmware is still under development. Most of the core system is functional — about 80% complete.&lt;/p&gt;

&lt;p&gt;The modules are in place, dual UART monitoring works, and the dispatcher, scheduler, and multicore systems are running. Basic MIOT-like protocol handling (JSON API) is already implemented.&lt;/p&gt;

&lt;p&gt;Some modules, like OTA, are already integrated, which will make future modifications — including connecting the display and enhancing the interface — much easier to implement.&lt;/p&gt;

&lt;p&gt;Once finished, the robot will have full local control, reliable telemetry, and optional display feedback — all without ever needing the cloud.&lt;/p&gt;

&lt;p&gt;Physical Mods: Replacing Buttons with a “Head”&lt;br&gt;
Original Button Modul&lt;br&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%2Fgn9oi3jo73g9vsd5207y.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%2Fgn9oi3jo73g9vsd5207y.png" alt="Original button modul" width="720" height="1280"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My parallel connection to button / uart to esp32 and diagnostic port&lt;br&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%2Ftuafzgh4fe5mwghj751v.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%2Ftuafzgh4fe5mwghj751v.png" alt=" " width="540" height="730"&gt;&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%2Fjuf8k6hwua16iedmbvib.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%2Fjuf8k6hwua16iedmbvib.png" alt=" " width="720" height="1280"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Temporary installation of buttons and LEDs&lt;br&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%2Foj6akershtnlfm25bce9.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%2Foj6akershtnlfm25bce9.png" alt=" " width="540" height="730"&gt;&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%2F7zmhlho0yuhe1fj77u1k.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%2F7zmhlho0yuhe1fj77u1k.png" alt=" " width="540" height="658"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once the software and UART control were in place, the next step was physical accessibility.&lt;br&gt;
My NodeMCU board and button cluster couldn’t fit comfortably in the original housing if I wanted to add a display and retain easy access to the interface.&lt;br&gt;
The solution: create a small “tower” or “head” on top of the robot.&lt;br&gt;
What I did&lt;br&gt;
-Relocated the original button block to the new head&lt;br&gt;
-Kept parallel functionality: the robot still responds to factory buttons&lt;br&gt;
-Replaced the original 8 factory LEDs with 2 custom RGB LEDs for clear status indication&lt;br&gt;
Integrated a small display for additional information ( planned for future update)&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%2Fzi1pwm22iz7dvjozytns.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%2Fzi1pwm22iz7dvjozytns.png" alt=" " width="800" height="348"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This physical modification achieves several goals:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Makes interacting with the robot intuitive even without an app&lt;/li&gt;
&lt;li&gt;Keeps visual status indicators simple and visible&lt;/li&gt;
&lt;li&gt;Allows me to retain full MCU control while optionally using the original buttons&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Think of it as a “command tower” — a small cockpit for my robot that gives it a personality and a clear interface.&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%2F0vnbnrogy2q1vzba7at9.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%2F0vnbnrogy2q1vzba7at9.png" alt=" " width="800" height="348"&gt;&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%2Fjg3t1u427vb3j9u0r9sh.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%2Fjg3t1u427vb3j9u0r9sh.png" alt=" " width="800" height="348"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What I’d Change if Designing This Robot&lt;/p&gt;

&lt;p&gt;If I were designing the Xiaomi Robot Mop Essential from scratch, here’s what I would do:&lt;br&gt;
-Provide full local control out of the box, without requiring cloud connection&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Make cloud telemetry optional and clearly permission-based&lt;/li&gt;
&lt;li&gt;Expose an official UART or API for advanced users&lt;/li&gt;
&lt;li&gt;Allow easy replacement or expansion of LEDs and buttons for custom interfaces&lt;/li&gt;
&lt;li&gt;Include a modular design for attaching displays or additional sensors&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This would let engineers and hobbyists:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Understand what the robot is doing&lt;/li&gt;
&lt;li&gt;Customize behavior safely&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Avoid turning a simple cleaning robot into an unintended cloud spy.😁&lt;/p&gt;

</description>
      <category>hardware</category>
      <category>iot</category>
      <category>embedded</category>
      <category>diy</category>
    </item>
  </channel>
</rss>
