diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 1d9caf0..d68d15c 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -26,7 +26,7 @@ jobs:
build:
strategy:
matrix:
- os: [ubuntu-22.04, ubuntu-latest]
+ os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
diff --git a/Cargo.toml b/Cargo.toml
index 50c0d69..1035a6f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,7 @@
[workspace]
members = [
"lib/flash-rs",
+ "examples/arpresolver-rs",
"examples/firewall-rs",
"examples/helloworld-rs",
"examples/ip4ping-rs",
diff --git a/README.md b/README.md
index da6e1ad..ce2ef5c 100644
--- a/README.md
+++ b/README.md
@@ -1,57 +1,162 @@
-# FLASH Userspace Library
+# FLASH: Fast Linked AF_XDP Sockets for High Performance Network Services
-
+
[](http://commitizen.github.io/cz-cli/)

-FLASH: Fast Linked AF_XDP Sockets for High Performance Network Services
+FLASH is a high-speed userspace library that makes it easy to build efficient, unprivileged AF_XDP applications for modern cloud and edge deployments.
-A userspace library that lets you link isolated unprivileged AF_XDP network functions to boost performance using FLASH out-of-tree kernel. It’s also great for deploying network functions in containers without needing a custom kernel, but without chaining support.
+Seamlessly integrated with the [**FLASH kernel**](https://github.com/networkedsystemsIITB/flash-linux), it extends AF_XDP to enable true zero-copy packet sharing between network functions (NFs) and network devices, unlocking performance that surpasses traditional AF_XDP chaining solutions.
-## Baremetal Usage Instructions
+## Key Features
+- **Zero-Copy Packet Sharing**: Unlock unparalleled throughput and minimal latency with zero-copy data paths between NFs and network devices.
+- **Unprivileged Operation**: Run AF_XDP applications securely without root access simplifying deployment while maintaining isolation.
+- **Packet Isolation**: Ensure strong packet-level isolation between NFs, even when sharing memory powered by Rust and FLASH kernel safeguards.
+- **Backward Compatibility**: Chain existing AF_XDP applications in copy-based mode with no code changes — easy migration, no disruption.
+- **Flexible Deployment Options**: Deploy seamlessly on bare metal or in containers for consistent, isolated environments. Works on standard Linux kernels too (without zero-copy chaining support).
+- **Multi tenant Support**: Designed for shared environments — the OS remains in control of resources, ensuring safety and fairness when multiple users or tenants share the same host. Unlike DPDK, FLASH plays nicely in multi-tenant and cloud-native setups.
-Baremetal Deployment has been tested on Ubuntu 24.04 hosts and is expected to function similarly on Ubuntu 22.04 hosts. However, it may encounter build failures on Ubuntu 20.04 and older versions due to the absence of necessary libraries in the apt repository.
-For standalone NFs operations, Linux kernel versions 5.17.5 and later are recommended.
+## Getting Started
-### Building
+Clone the repositories and install the FLASH kernel for zero-copy chaining support.
+```bash
+git clone https://github.com/networkedsystemsIITB/flash.git
+git clone https://github.com/networkedsystemsIITB/flash-linux.git
+cd flash
+sudo ./usertools/flash_kernel/install.sh ../flash-linux
+```
+
+The `install.sh` script will build and install the kernel along with its modules.
+It requires the path to the flash-linux repository as the first argument.
+An optional second argument can be provided to specify the number of processors to use during the build.
+
+During execution, the script will prompt you to choose between a quick build and a full build. Select the quick build option for faster compilation.
+
+Follow the on-screen instructions provided in the terminal after installation to boot into the FLASH kernel.
+
+For more details, refer to the [FLASH Kernel Guide](./doc/flash_kernel/flash_kernel.md).
-The library is built on top of libbpf and libxdp. You can install the dependencies using the following commands:
+### Building the userspace library and examples
+
+FLASH has been tested on Ubuntu 24.04 and is expected to work similarly on Ubuntu 22.04.
+Older versions (e.g., Ubuntu 20.04 or earlier) may encounter build issues due to missing dependencies.
+
+> Recommended Kernel Version: 5.17.5 or later for standalone NF operations.
+
+Install the required dependencies using the following command:
```bash
-sudo apt install -y build-essential meson libbpf-dev pkg-config git gcc-multilib clang llvm lld m4 libpcap-dev libcjson-dev libncurses-dev
+sudo apt install -y build-essential meson libbpf-dev pkg-config git gcc-multilib clang llvm lld m4 libpcap-dev libcjson-dev libncurses-dev libnuma-dev
```
-The libxdp library is not available in the Ubuntu repositories. You can build it from source using the following commands:
+`libxdp` is not included in Ubuntu repositories — build it from source:
```bash
git clone https://github.com/xdp-project/xdp-tools.git
-make -j -C xdp-tools libxdp
+make PREFIX=/usr -j -C xdp-tools libxdp
sudo PREFIX=/usr make -j -C xdp-tools libxdp_install
```
-Once you have installed the dependencies, you can build the library using the following commands:
+Install Rust for using the Rust components of FLASH:
+
+```bash
+curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
+```
+
+Once dependencies are ready, build the library and examples:
```bash
make
```
-### Usage
+### Basic Usage
+
+FLASH provides two primary userspace components:
+1. **NF Libraries**: Used to build AF_XDP applications with FLASH support (available in C and Rust).
+2. **Monitor**: A control-plane application that manages AF_XDP socket configurations and enables unprivileged NF operation.
-The library offers a straightforward API for constructing and executing AF_XDP. Applications may utilize the library to construct and operate AF_XDP sockets either through the FLASH monitor, which manages the control plane, or directly via the library. The monitor enables applications to execute multiple AF_XDP applications simultaneously, whether sharing memory in privileged or non-privileged modes.
+#### Run a Sample L2FWD NF (Switch)
-#### Using Monitor
+You can test a sample NF on any Linux kernel (no FLASH kernel required):
```bash
sudo ./build/monitor/monitor
```
+A TUI will start, allowing you to configure AF_XDP.
+Configurations are stored as JSON and can be loaded/unloaded on demand.
+
+Load a sample configuration from [`config/simple_config.json`](./config/simple-config.json), updating interface names as needed:
+
+```console
+flash:/> load config config/simple_config.json
+```
+
+Then run the L2FWD example (no root needed):
+
+```bash
+./build/examples/l2fwd/l2fwd -u 0 -f 0 # C based NF
+# or
+./build/rust-target/release/l2fwd -u 0 -f 0 # Rust based NF
+```
+
+`-u 0` → UMEM ID
+`-f 0` → NF ID
+Both values are defined in the monitor configuration.
+
+
+### Chaining AF_XDP Applications with FLASH
+
+FLASH allows chaining multiple AF_XDP-based network functions (NFs) together including independent NFs written in different languages using either copy-based or zero-copy modes.
+
+- **Copy-Based Chaining (Legacy Compatible):**
+Works with any existing AF_XDP applications.
+Requires no code changes.
+FLASH-based NFs can also operate in this mode.
+Refer to the sysfs usage in [FLASH Kernel Guide](./doc/flash_kernel/flash_kernel.md) for copy-based setup instructions.
+
+- **Zero-Copy Chaining:**
+Achieved when multiple NFs share the same UMEM region.
+Automatically handled by FLASH-based NFs.
+
+#### Example: Linear Chaining Between Two AF_XDP Applications
+
+Let’s consider chaining two independent L2 forwarders, one written in C and another in Rust. They only share the same UMEM region for zero-copy operation.
+
+We can use [`config/chain-config.json`](./config/chain-config.json) as a starting point.
+
+a. Start the monitor and load the configuration:
+
+```console
+sudo ./build/monitor/monitor
+flash:/> load config config/chain-config.json
+```
-A TUI will be initiated, allowing configuration parameters to be passed to setup AF_XDP setups and chains. Configurations will be stored in a JSON file and loaded/unloaded on demand. Once the monitor has started and the configuration is properly set, NFs can commence running without the need for any privileges. A sample NF usage instruction is provided below.
+b. Start the first l2fwd application (C based):
```bash
-./build/examples/l2fwd/l2fwd -u 0 -f 1 -ax -- -s 0 -c 2 -e 3
+./build/examples/l2fwd/l2fwd -u 0 -f 0 -- -s 0 -c 1 -e 1
```
+c. Start the second l2fwd application (Rust based):
+
+```bash
+./build/rust-target/release/l2fwd -u 0 -f 1 -s 1 -c 2 -e 2
+```
+
+d. Chain them using the monitor TUI:
+
+```console
+flash:/> load route
+```
+The configuration file defines routes from `flash_id 0` → `flash_id 1`.
+Upon loading, the monitor programs the FLASH kernel with these redirection rules.
+
+You can then send packets to the first NF and observe them forwarded to the second at high throughput.
+
+To extend chaining, add more NFs to the configuration file and specify their connections accordingly.
+
+
## Docker Usage Instructions
Docker containers enable the consistent and isolated deployment of NFs in a portable development environment, facilitating the entry of NF developers into the setup process. To begin, you can create an image of the FLASH container.
@@ -73,7 +178,7 @@ docker run -rm -it --privileged -v /tmp/flash/:/tmp/flash/ --net=host flash:mon
If the monitor is ready and running, the NF can be initiated using the following command:
```bash
-docker run --rm -it -v /tmp/flash/:/tmp/flash/ flash:dev ./build/examples/l2fwd/l2fwd -u 0 -f 1 -ax -- -s 0 -c 2 -e 3
+docker run --rm -it -v /tmp/flash/:/tmp/flash/ flash:dev ./build/examples/l2fwd/l2fwd -u 0 -f 1
```
> `/tmp/flash` contains a UDS socket that is used by NFs to communicate with the monitor.
@@ -82,8 +187,4 @@ You can also use docker compose to deploy multiple NFs at the same time.
```bash
docker compose up -d
-```
-
-### Chaining NFs using FLASH Monitor
-
-To chain NFs you need to install a custom out-of-tree kernel. Checkout the instructions [here](./doc/flash_kernel/flash_kernel.rst).
+```
\ No newline at end of file
diff --git a/config/config-2.json b/config/chain-config.json
similarity index 96%
rename from config/config-2.json
rename to config/chain-config.json
index e5f621e..c4db979 100644
--- a/config/config-2.json
+++ b/config/chain-config.json
@@ -26,7 +26,7 @@
]
}
],
- "ifname": "ens23f0np0",
+ "ifname": "enp1s0",
"xdp_flags": "d",
"bind_flags": "z",
"mode": "b",
diff --git a/config/config.json b/config/complex-config.json
similarity index 76%
rename from config/config.json
rename to config/complex-config.json
index c05f27e..5ca1621 100644
--- a/config/config.json
+++ b/config/complex-config.json
@@ -11,23 +11,16 @@
{
"thread_id": 0,
"queue": 0
- }
- ]
- },
- {
- "nf_id": 1,
- "nf_ip": "192.168.0.2",
- "nf_port": 1234,
- "thread": [
+ },
{
- "thread_id": 0,
+ "thread_id": 1,
"queue": 1
}
]
},
{
- "nf_id": 2,
- "nf_ip": "192.168.0.3",
+ "nf_id": 1,
+ "nf_ip": "192.168.0.2",
"nf_port": 1234,
"thread": [
{
@@ -37,7 +30,7 @@
]
}
],
- "ifname": "ens23f0np0",
+ "ifname": "enp1s0",
"xdp_flags": "d",
"bind_flags": "z",
"mode": "b",
@@ -48,10 +41,11 @@
],
"route": {
"0": [
- 1,
2
],
- "1": [],
+ "1": [
+ 2
+ ],
"2": []
}
}
\ No newline at end of file
diff --git a/config/config-3.json b/config/config-3.json
deleted file mode 100644
index f81b112..0000000
--- a/config/config-3.json
+++ /dev/null
@@ -1,57 +0,0 @@
-{
- "umem": [
- {
- "umem_id": 0,
- "nf": [
- {
- "nf_id": 0,
- "nf_ip": "192.168.0.1",
- "nf_port": 1234,
- "thread": [
- {
- "thread_id": 0,
- "queue": 0
- }
- ]
- },
- {
- "nf_id": 1,
- "nf_ip": "192.168.0.2",
- "nf_port": 1234,
- "thread": [
- {
- "thread_id": 0,
- "queue": 1
- }
- ]
- },
- {
- "nf_id": 2,
- "nf_ip": "192.168.0.3",
- "nf_port": 1234,
- "thread": [
- {
- "thread_id": 0,
- "queue": 2
- }
- ]
- }
- ],
- "ifname": "ens23f0np0",
- "xdp_flags": "d",
- "bind_flags": "z",
- "mode": "b",
- "custom_xsk": false,
- "frags_enabled": false
- }
- ],
- "route": {
- "0": [
- 1
- ],
- "1": [
- 2
- ],
- "2": []
- }
-}
\ No newline at end of file
diff --git a/config/config-4.json b/config/config-4.json
deleted file mode 100644
index dc9d105..0000000
--- a/config/config-4.json
+++ /dev/null
@@ -1,71 +0,0 @@
-{
- "umem": [
- {
- "umem_id": 0,
- "nf": [
- {
- "nf_id": 0,
- "nf_ip": "192.168.0.1",
- "nf_port": 1234,
- "thread": [
- {
- "thread_id": 0,
- "queue": 0
- }
- ]
- },
- {
- "nf_id": 1,
- "nf_ip": "192.168.0.2",
- "nf_port": 1234,
- "thread": [
- {
- "thread_id": 0,
- "queue": 1
- }
- ]
- },
- {
- "nf_id": 2,
- "nf_ip": "192.168.0.3",
- "nf_port": 1234,
- "thread": [
- {
- "thread_id": 0,
- "queue": 2
- }
- ]
- },
- {
- "nf_id": 3,
- "nf_ip": "192.168.0.4",
- "nf_port": 1234,
- "thread": [
- {
- "thread_id": 0,
- "queue": 3
- }
- ]
- }
- ],
- "ifname": "ens23f0np0",
- "xdp_flags": "d",
- "bind_flags": "z",
- "mode": "b",
- "custom_xsk": false,
- "frags_enabled": false
- }
- ],
- "route": {
- "0": [
- 1
- ],
- "1": [
- 2
- ],
- "2": [
- 3
- ],
- "3": []
- }
-}
\ No newline at end of file
diff --git a/config/config-5.json b/config/config-5.json
deleted file mode 100644
index 632b7f7..0000000
--- a/config/config-5.json
+++ /dev/null
@@ -1,85 +0,0 @@
-{
- "umem": [
- {
- "umem_id": 0,
- "nf": [
- {
- "nf_id": 0,
- "nf_ip": "192.168.0.1",
- "nf_port": 1234,
- "thread": [
- {
- "thread_id": 0,
- "queue": 0
- }
- ]
- },
- {
- "nf_id": 1,
- "nf_ip": "192.168.0.2",
- "nf_port": 1234,
- "thread": [
- {
- "thread_id": 0,
- "queue": 1
- }
- ]
- },
- {
- "nf_id": 2,
- "nf_ip": "192.168.0.3",
- "nf_port": 1234,
- "thread": [
- {
- "thread_id": 0,
- "queue": 2
- }
- ]
- },
- {
- "nf_id": 3,
- "nf_ip": "192.168.0.4",
- "nf_port": 1234,
- "thread": [
- {
- "thread_id": 0,
- "queue": 3
- }
- ]
- },
- {
- "nf_id": 4,
- "nf_ip": "192.168.0.5",
- "nf_port": 1234,
- "thread": [
- {
- "thread_id": 0,
- "queue": 4
- }
- ]
- }
- ],
- "ifname": "ens23f0np0",
- "xdp_flags": "d",
- "bind_flags": "z",
- "mode": "b",
- "custom_xsk": false,
- "frags_enabled": false
- }
- ],
- "route": {
- "0": [
- 1
- ],
- "1": [
- 2
- ],
- "2": [
- 3
- ],
- "3": [
- 4
- ],
- "4": []
- }
-}
\ No newline at end of file
diff --git a/config/config-6.json b/config/config-6.json
deleted file mode 100644
index aa87318..0000000
--- a/config/config-6.json
+++ /dev/null
@@ -1,99 +0,0 @@
-{
- "umem": [
- {
- "umem_id": 0,
- "nf": [
- {
- "nf_id": 0,
- "nf_ip": "192.168.0.1",
- "nf_port": 1234,
- "thread": [
- {
- "thread_id": 0,
- "queue": 0
- }
- ]
- },
- {
- "nf_id": 1,
- "nf_ip": "192.168.0.2",
- "nf_port": 1234,
- "thread": [
- {
- "thread_id": 0,
- "queue": 1
- }
- ]
- },
- {
- "nf_id": 2,
- "nf_ip": "192.168.0.3",
- "nf_port": 1234,
- "thread": [
- {
- "thread_id": 0,
- "queue": 2
- }
- ]
- },
- {
- "nf_id": 3,
- "nf_ip": "192.168.0.4",
- "nf_port": 1234,
- "thread": [
- {
- "thread_id": 0,
- "queue": 3
- }
- ]
- },
- {
- "nf_id": 4,
- "nf_ip": "192.168.0.5",
- "nf_port": 1234,
- "thread": [
- {
- "thread_id": 0,
- "queue": 4
- }
- ]
- },
- {
- "nf_id": 5,
- "nf_ip": "192.168.0.6",
- "nf_port": 1234,
- "thread": [
- {
- "thread_id": 0,
- "queue": 5
- }
- ]
- }
- ],
- "ifname": "ens23f0np0",
- "xdp_flags": "d",
- "bind_flags": "z",
- "mode": "b",
- "custom_xsk": false,
- "frags_enabled": false
- }
- ],
- "route": {
- "0": [
- 1
- ],
- "1": [
- 2
- ],
- "2": [
- 3
- ],
- "3": [
- 4
- ],
- "4": [
- 5
- ],
- "5": []
- }
-}
\ No newline at end of file
diff --git a/config/config-7.json b/config/config-7.json
deleted file mode 100644
index f32532a..0000000
--- a/config/config-7.json
+++ /dev/null
@@ -1,113 +0,0 @@
-{
- "umem": [
- {
- "umem_id": 0,
- "nf": [
- {
- "nf_id": 0,
- "nf_ip": "192.168.0.1",
- "nf_port": 1234,
- "thread": [
- {
- "thread_id": 0,
- "queue": 0
- }
- ]
- },
- {
- "nf_id": 1,
- "nf_ip": "192.168.0.2",
- "nf_port": 1234,
- "thread": [
- {
- "thread_id": 0,
- "queue": 1
- }
- ]
- },
- {
- "nf_id": 2,
- "nf_ip": "192.168.0.3",
- "nf_port": 1234,
- "thread": [
- {
- "thread_id": 0,
- "queue": 2
- }
- ]
- },
- {
- "nf_id": 3,
- "nf_ip": "192.168.0.4",
- "nf_port": 1234,
- "thread": [
- {
- "thread_id": 0,
- "queue": 3
- }
- ]
- },
- {
- "nf_id": 4,
- "nf_ip": "192.168.0.5",
- "nf_port": 1234,
- "thread": [
- {
- "thread_id": 0,
- "queue": 4
- }
- ]
- },
- {
- "nf_id": 5,
- "nf_ip": "192.168.0.6",
- "nf_port": 1234,
- "thread": [
- {
- "thread_id": 0,
- "queue": 5
- }
- ]
- },
- {
- "nf_id": 6,
- "nf_ip": "192.168.0.7",
- "nf_port": 1234,
- "thread": [
- {
- "thread_id": 0,
- "queue": 6
- }
- ]
- }
- ],
- "ifname": "ens23f0np0",
- "xdp_flags": "d",
- "bind_flags": "z",
- "mode": "b",
- "custom_xsk": false,
- "frags_enabled": false
- }
- ],
- "route": {
- "0": [
- 1
- ],
- "1": [
- 2
- ],
- "2": [
- 3
- ],
- "3": [
- 4
- ],
- "4": [
- 5
- ],
- "5": [
- 6
- ],
- "6": []
- }
-}
\ No newline at end of file
diff --git a/config/config-8.json b/config/config-8.json
deleted file mode 100644
index 2b8494d..0000000
--- a/config/config-8.json
+++ /dev/null
@@ -1,124 +0,0 @@
-{
- "umem": [
- {
- "umem_id": 0,
- "nf": [
- {
- "nf_id": 0,
- "nf_ip": "192.168.0.1",
- "nf_port": 1234,
- "thread": [
- {
- "thread_id": 0,
- "queue": 0
- }
- ]
- },
- {
- "nf_id": 1,
- "nf_ip": "192.168.0.2",
- "nf_port": 1234,
- "thread": [
- {
- "thread_id": 0,
- "queue": 1
- }
- ]
- },
- {
- "nf_id": 2,
- "nf_ip": "192.168.0.3",
- "nf_port": 1234,
- "thread": [
- {
- "thread_id": 0,
- "queue": 2
- }
- ]
- },
- {
- "nf_id": 3,
- "nf_ip": "192.168.0.4",
- "nf_port": 1234,
- "thread": [
- {
- "thread_id": 0,
- "queue": 3
- }
- ]
- },
- {
- "nf_id": 4,
- "nf_ip": "192.168.0.5",
- "nf_port": 1234,
- "thread": [
- {
- "thread_id": 0,
- "queue": 4
- }
- ]
- },
- {
- "nf_id": 5,
- "nf_ip": "192.168.0.6",
- "nf_port": 1234,
- "thread": [
- {
- "thread_id": 0,
- "queue": 5
- }
- ]
- },
- {
- "nf_id": 6,
- "nf_ip": "192.168.0.7",
- "nf_port": 1234,
- "thread": [
- {
- "thread_id": 0,
- "queue": 6
- }
- ]
- },
- {
- "nf_id": 7,
- "nf_ip": "192.168.0.8",
- "nf_port": 1234,
- "thread": [
- {
- "thread_id": 0,
- "queue": 7
- }
- ]
- }
- ],
- "ifname": "ens23f0np0",
- "xdp_flags": "d",
- "bind_flags": "z",
- "mode": "b",
- "custom_xsk": false,
- "frags_enabled": false
- }
- ],
- "route": {
- "0": [
- 1
- ],
- "1": [
- 2
- ],
- "2": [
- 3
- ],
- "3": [
- 4
- ],
- "4": [
- 5
- ],
- "5": [
- 6
- ],
- "6": []
- }
-}
\ No newline at end of file
diff --git a/config/config-rr1.json b/config/config-rr1.json
deleted file mode 100644
index 0e60e69..0000000
--- a/config/config-rr1.json
+++ /dev/null
@@ -1,56 +0,0 @@
-{
- "umem": [
- {
- "umem_id": 0,
- "nf": [
- {
- "nf_id": 0,
- "nf_ip": "192.168.0.1",
- "nf_port": 1234,
- "thread": [
- {
- "thread_id": 0,
- "queue": 0
- }
- ]
- },
- {
- "nf_id": 1,
- "nf_ip": "192.168.0.2",
- "nf_port": 1234,
- "thread": [
- {
- "thread_id": 0,
- "queue": 1
- }
- ]
- },
- {
- "nf_id": 2,
- "nf_ip": "192.168.0.3",
- "nf_port": 1234,
- "thread": [
- {
- "thread_id": 0,
- "queue": 2
- }
- ]
- }
- ],
- "ifname": "ens23f0np0",
- "xdp_flags": "d",
- "bind_flags": "z",
- "mode": "b",
- "custom_xsk": false,
- "frags_enabled": false
- }
- ],
- "route": {
- "0": [
- 1,
- 2
- ],
- "1": [],
- "2": []
- }
-}
\ No newline at end of file
diff --git a/config/config-rr2.json b/config/config-rr2.json
deleted file mode 100644
index 04768cf..0000000
--- a/config/config-rr2.json
+++ /dev/null
@@ -1,72 +0,0 @@
-{
- "umem": [
- {
- "umem_id": 0,
- "nf": [
- {
- "nf_id": 0,
- "nf_ip": "192.168.0.1",
- "nf_port": 1234,
- "thread": [
- {
- "thread_id": 0,
- "queue": 0
- }
- ]
- },
- {
- "nf_id": 1,
- "nf_ip": "192.168.0.2",
- "nf_port": 1234,
- "thread": [
- {
- "thread_id": 0,
- "queue": 1
- }
- ]
- },
- {
- "nf_id": 2,
- "nf_ip": "192.168.0.3",
- "nf_port": 1234,
- "thread": [
- {
- "thread_id": 0,
- "queue": 2
- }
- ]
- },
- {
- "nf_id": 3,
- "nf_ip": "192.168.0.4",
- "nf_port": 1234,
- "thread": [
- {
- "thread_id": 0,
- "queue": 3
- }
- ]
- }
- ],
- "ifname": "ens23f0np0",
- "xdp_flags": "d",
- "bind_flags": "z",
- "mode": "b",
- "custom_xsk": false,
- "frags_enabled": false
- }
- ],
- "route": {
- "0": [
- 1,
- 2
- ],
- "1": [
- 3
- ],
- "2": [
- 3
- ],
- "3": []
- }
-}
\ No newline at end of file
diff --git a/config/config-1.json b/config/simple-config.json
similarity index 94%
rename from config/config-1.json
rename to config/simple-config.json
index 01f0f53..0730606 100644
--- a/config/config-1.json
+++ b/config/simple-config.json
@@ -15,7 +15,7 @@
]
}
],
- "ifname": "ens23f0np0",
+ "ifname": "enp1s0",
"xdp_flags": "d",
"bind_flags": "z",
"mode": "b",
diff --git a/doc/flash_kernel/flash_kernel.md b/doc/flash_kernel/flash_kernel.md
index babbab9..5dc3300 100644
--- a/doc/flash_kernel/flash_kernel.md
+++ b/doc/flash_kernel/flash_kernel.md
@@ -1,72 +1,340 @@
-
+
-# Introduction
+# FLASH Kernel Guide
-This documents describes how to install flash linux out-of-tree kernel in a system.
+The **FLASH Kernel** extends the Linux **AF_XDP** subsystem to provide zero-copy packet redirection and efficient in-kernel data paths for high-performance user-space networking.
-## System Requirements and Building flash kernel
+It introduces:
+- A high-speed, in-kernel redirection mechanism between AF_XDP sockets.
+- A sysfs control interface for managing AF_XDP sockets securely from user space.
-flash kernel is based on linux kernel v6.10.6. This documentation assumes that you are building and installing the kernel on a
-Ubuntu 24.04 LTS host. Other distributions may work, but this documentation assumes Ubuntu.
+#### Quick Links
+⚙️ [Kernel Installation](#kernel-installation)
+🧩 [Sysfs Interface](#sysfs-interface-for-af_xdp-socket-management)
+🔁 [Interrupt vs. Busy-Polling (`poll()` Usage)](#using-poll-for-interrupt-vs-busy-polling-mode)
+🧠 [Backpressure Handling (`poll()` & `recvfrom()`)](#backpressure-handling-with-poll-and-recvfrom)
+🚦 [TX Tracking per Flow (HOL Mitigation)](#tx-tracking-per-flow-mitigating-head-of-line-blocking)
+🧰 [Adding Driver Support](#adding-driver-support)
+🧹 [Uninstalling the FLASH Kernel](#uninstalling-the-flash-kernel)
-## Clone the flash kernel repository
-```
-git clone https://github.com/rickydebojeet/linux.git
-```
+## System Requirements
-Make sure to note the path to the flash kernel repository. This path will be used in the next steps.
+The FLASH kernel is based on Linux kernel v6.10.6. This documentation assumes that you are building and installing the kernel on an Ubuntu host. Other distributions may work, but this documentation assumes Ubuntu.
-## Install the kernel automatically
+We have tested the FLASH kernel on the following Ubuntu versions:
+- Ubuntu 22.04 LTS
+- Ubuntu 24.04 LTS
+- Ubuntu 25.04
+- Ubuntu 25.10
-Use the following steps to install the kernel automatically.
+> **Note:** Ubuntu 25.10 and newer ship with **GCC 15**, which is incompatible with the FLASH kernel.
+> Install GCC 14 before building to avoid compilation errors.
+The zero-copy redirection feature requires some support from NIC driver. Currently, the following the NIC drivers are supported:
+- Intel `ixgbe` driver (10GbE)
+- Intel `i40e` driver (40GbE)
+- Intel `ice` driver (100GbE and above)
+- Mellanox `mlx5` driver (10GbE and above)
+
+## Kernel Installation
+
+### Automated Installation (Recommended)
+
+You can use the `install.sh` script to build and install the FLASH kernel automatically.
+
+```bash
+git clone https://github.com/networkedsystemsIITB/flash.git
+git clone https://github.com/networkedsystemsIITB/flash-linux.git
+cd flash
+sudo ./usertools/flash_kernel/install.sh ../flash-linux
```
-sudo ./usertools/flash_kernel/install.sh
-cp -v /boot/config-$(uname -r) .config
-(yes "" || true) | make localmodconfig
+#### Build the kernel:
+
+```bash
+git clone https://github.com/networkedsystemsIITB/flash-linux.git
+cd flash-linux
+make olddefconfig
scripts/config --disable SYSTEM_TRUSTED_KEYS
scripts/config --disable SYSTEM_REVOCATION_KEYS
scripts/config --set-str CONFIG_SYSTEM_TRUSTED_KEYS ""
scripts/config --set-str CONFIG_SYSTEM_REVOCATION_KEYS ""
```
-Now you can build the kernel:
+For advanced configuration, see the [Linux kernel build guide](https://www.kernel.org/doc/Documentation/admin-guide/README.rst).
-```
+Now build:
+
+```bash
make -j$(nproc)
```
-After the kernel is built, you can install it:
+#### Install the kernel:
-```
+```bash
sudo make modules_install
sudo make install
```
-After the kernel is installed, you just need to reboot the system:
+After the kernel is installed, you just need to reboot the system and select the FLASH kernel from the GRUB menu:
-```
+```bash
sudo reboot
-```
\ No newline at end of file
+```
+
+After rebooting, you can verify that the FLASH kernel is running by executing:
+
+```bash
+uname -r
+```
+
+## FLASH Kernel Features
+
+> This section is intended for developers building network frameworks or libraries on top of the FLASH kernel.
+> Regular users should rely on the FLASH userspace library, which abstracts these details automatically.
+
+### Sysfs Interface for AF_XDP Socket Management
+
+FLASH kernel exposes a sysfs interface under `/sys/kernel/flash` allowing privileged users to:
+- Inspect active AF_XDP sockets
+- Configure redirection rules between sockets
+- Adjust per-socket parameters (e.g., TX tracking)
+
+Each AF_XDP socket is identified by a process-independent identifier called a flash-id, which enables cross-process management.
+
+When a new AF_XDP socket is created, FLASH automatically registers it in sysfs under a dedicated directory: `/sys/kernel/flash//`
+
+Below is an example sysfs layout for three sockets with flash-ids 1, 2, and 3:
+
+```bash
+/sys/kernel/flash/
+│
+├── tx_tracking # Global TX tracking control (0 or 1)
+│
+├── 1/
+│ ├── pid # Process ID owning this socket
+│ ├── procname # Process name
+│ ├── ifindex # Network interface index
+│ ├── qid # Queue ID
+│ └── next # Redirection targets
+│
+├── 2/
+│ └── ...
+│
+└── 3/
+ └── ...
+```
+
+Socket directories are dynamically created when a socket is registered and automatically removed upon closure.
+
+#### Configuring Redirections
+
+The `next` file defines downstream paths for packet redirection.
+
+- A value of -1 means the socket transmits packets directly to the NIC (no redirection).
+- Writing one or more flash-ids to this file defines new downstream redirection targets.
+
+**Examples:**
+
+a. Redirect socket 1 → socket 2:
+
+```console
+# cat /sys/kernel/flash/1/next
+-1
+# echo 2 | sudo tee /sys/kernel/flash/1/next
+# cat /sys/kernel/flash/1/next
+index flash_id
+0 2
+```
+
+b. Redirect socket 1 → sockets 2 and 3:
+
+```console
+# cat /sys/kernel/flash/1/next
+-1
+# echo "2 3" | sudo tee /sys/kernel/flash/1/next
+# cat /sys/kernel/flash/1/next
+index flash_id
+0 2
+1 3
+```
+
+c. Clear all redirections:
+
+```console
+# echo "-1" | sudo tee /sys/kernel/flash/1/next
+# cat /sys/kernel/flash/1/next
+-1
+```
+
+#### Runtime redirection semantics
+
+When sending packets from an AF_XDP socket:
+
+- Single target: All packets are forwarded to that target.
+- Multiple targets: The lower 16 bits of the packet descriptor’s flags field determine the destination index (default = 0). [index is the value shown in the `next` file]
+
+The redirection automatically happens in zero-copy if the sockets share UMEM. If the sockets do not share UMEM, FLASH falls back to copying packets between sockets.
+
+From user-space, this behavior is transparent; sending to redirected sockets behaves as with regular AF_XDP sockets.
+
+### Using `poll()` for Interrupt vs. Busy-Polling Mode
+
+AF_XDP sockets can operate in both interrupt-driven and busy-polling modes, allowing applications to balance CPU utilization and latency depending on workload characteristics.
+
+Recommended workflow:
+
+1. **Start in interrupt mode:** Use `poll()` with the `POLLIN` flag to block until packets arrive.
+2. **Switch to busy-polling:** When packet rates are consistently high, switch to a busy loop using `recvfrom()` to continuously process packets. This eliminates interrupt latency and can improve throughput.
+3. **Revert to interrupt mode:** When load decreases, return to interrupt mode to save CPU cycles.
+
+FLASH ensures that `poll()` correctly reflects packet readiness even when redirection chains are configured, enabling seamless transitions between modes without losing events or packets.
+
+### Backpressure Handling with `poll()` and `recvfrom()`
+
+Backpressure arises when downstream sockets (receivers) cannot process packets as fast as they are being produced. The FLASH Kernel introduces natural backpressure into the AF_XDP data path via the TX and CQ rings: packets are not transmitted to downstream sockets if their RX rings are full.
+
+In this scenario, the sender must retry transmissions until space becomes available. To avoid wasting CPU cycles through busy-waiting, applications should rely on the following readiness mechanism:
+
+- When congestion is detected on a sender socket, use `poll()` with the `POLLOUT` flag to sleep until the socket is ready to send again.
+- Receiver sockets should use `recvfrom()` with the `MSG_MORE` flag to implicitly signal the sender once they have freed space.
+
+The FLASH kernel ensure that the signal from the receiver propagates upstream through the redirection chain, waking up any blocked senders. This cooperative signaling model helps maintain steady throughput while preventing packet loss or excessive CPU usage under heavy load.
+
+### TX Tracking per Flow (Mitigating Head-of-Line Blocking)
+
+When multiple downstream sockets are configured for redirection, one slow or congested target can cause head-of-line (HOL) blocking, where faster flows are stalled by slower ones. To mitigate this, FLASH provides a global TX tracking mechanism, which can be enabled or disabled through the `tx_tracking` sysfs file.
+
+```bash
+echo 1 | sudo tee /sys/kernel/flash/tx_tracking # Enable TX tracking
+echo 0 | sudo tee /sys/kernel/flash/tx_tracking # Disable TX tracking
+```
+
+When enabled, FLASH tracks packet transmission status on a per-flow basis.
+As packets are transmitted successfully, the kernel writes back the `flash_id` of the downstream socket into the memory location specified by the packet descriptor before returning it to the completion queue. This allows user-space applications to identify which downstream path successfully transmitted each packet.
+
+Applications can use this feedback to implement:
+- Dynamic congestion control or flow rerouting,
+- Per-destination pacing or fair queuing, and
+- Custom recovery strategies to reduce the impact of HOL blocking.
+
+> Note: TX tracking adds slight overhead due to per-flow bookkeeping. It is recommended for applications that require advanced flow management or fairness across multiple redirection targets.
+
+## Adding Driver Support
+
+To enable zero-copy redirection for an AF_XDP-supported NIC, the driver must be updated slightly to support the FLASH kernel’s redirection mechanism.
+
+Packet redirection in FLASH occurs when packets are transmitted from an AF_XDP socket.
+Depending on the driver implementation, this is typically handled using one of the following APIs:
+- `xsk_tx_peek_desc()` and `xsk_tx_release()` APIs used by most standard AF_XDP drivers
+- `xsk_tx_peek_release_desc_batch()` API used by batch-oriented drivers for higher throughput
+
+### Supporting FLASH with `xsk_tx_peek_desc()` and `xsk_tx_release()`
+
+In the standard AF_XDP transmission flow:
+1. The driver calls `xsk_tx_peek_desc()` to fetch a descriptor for transmission.
+2. The NAPI TX poll function collects all such descriptors into a batch and transmits them.
+3. The TX ring is released after the batch using `xsk_tx_release()`.
+
+If no descriptor is returned by `xsk_tx_peek_desc()`, the driver stops processing further descriptors for transmission.
+
+In the FLASH kernel, when redirection is configured, the driver must not transmit packets to the NIC, but it should continue processing all remaining descriptors.
+
+To achieve this, the driver should check a flag in the AF_XDP socket’s pool structure:
+`pool->no_tx_out` — this boolean flag is set when redirection is active.
+
+**Example: Transmission Function with FLASH Support**
+
+```c
+bool xmit(struct xsk_buff_pool *pool, unsigned int budget)
+{
+ struct xdp_desc desc;
+
+ while (budget-- > 0) {
+ // Fetch a descriptor for transmission
+ if (!xsk_tx_peek_desc(pool, &desc))
+ break;
+
+ // If redirection is configured, skip NIC transmission
+ if (pool->no_tx_out)
+ continue;
+
+ // Proceed with normal transmission
+ }
+
+ // After processing all descriptors, trigger redirection if needed
+ if (pool->no_tx_out)
+ xsk_tx_release(pool);
+
+ return !budget;
+}
+```
+
+### Supporting FLASH with `xsk_tx_peek_release_desc_batch()`
+
+Drivers that use batch-oriented transmission can integrate FLASH support similarly.
+
+In the batch transmission flow:
+1. The driver calls `xsk_tx_peek_release_desc_batch()` to fetch and release a batch of transmission descriptors.
+2. The NIC driver then transmits the corresponding packets.
+
+In the FLASH kernel, when redirection is configured, the driver should skip NIC transmission but still process all descriptors by calling `xsk_tx_peek_release_desc_batch()`.
+The same `pool->no_tx_out` flag applies in this case as well.
+
+Example: Batch Transmission Function with FLASH Support
+
+```c
+bool xmit_batch(struct xsk_buff_pool *pool, unsigned int budget)
+{
+ struct xdp_desc *descs = pool->tx_descs;
+ unsigned int nb_pkts = 0;
+
+ // Fetch a batch of descriptors for transmission
+ nb_pkts = xsk_tx_peek_release_desc_batch(pool, budget);
+ if (!nb_pkts)
+ return true;
+
+ // If redirection is configured, skip NIC transmission
+ if (pool->no_tx_out)
+ return nb_pkts < budget;
+
+ // Proceed with normal transmission
+ return nb_pkts < budget;
+}
+```
+
+## Uninstalling the FLASH Kernel
+
+You can use the `uninstall.sh` script to remove the FLASH kernel and restore the previous kernel.
+
+```bash
+cd flash
+sudo ./usertools/flash_kernel/uninstall.sh
+```
+
+The script will ask for kernel version to uninstall and will update the GRUB configuration accordingly. After uninstalling, reboot the system to boot into the previous kernel.
+
+> Note: Make sure that the previous kernel is still installed on your system before uninstalling the FLASH kernel.
diff --git a/examples/arpresolver-rs/Cargo.toml b/examples/arpresolver-rs/Cargo.toml
new file mode 100644
index 0000000..fd6fd84
--- /dev/null
+++ b/examples/arpresolver-rs/Cargo.toml
@@ -0,0 +1,21 @@
+[package]
+name = "arpresolver"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies]
+clap = { version = "4.5.35", features = ["derive"] }
+core_affinity = "0.8.3"
+ctrlc = { version = "3.4.5", optional = true }
+flash = { path = "../../lib/flash-rs", features = ["clap"] }
+macaddr = "1.0.1"
+tracing = { version = "0.1.41", optional = true }
+tracing-subscriber = { version = "0.3.19", optional = true }
+
+[features]
+default = ["dep:ctrlc"]
+stats = ["flash/stats", "flash/tui"]
+tracing = ["dep:tracing", "dep:tracing-subscriber", "flash/tracing"]
+
+[lints.rust]
+unsafe_code = "forbid"
diff --git a/examples/arpresolver-rs/build.rs b/examples/arpresolver-rs/build.rs
new file mode 100644
index 0000000..6754864
--- /dev/null
+++ b/examples/arpresolver-rs/build.rs
@@ -0,0 +1,3 @@
+fn main() {
+ println!("cargo:rustc-link-lib=xdp");
+}
diff --git a/examples/arpresolver-rs/src/cli.rs b/examples/arpresolver-rs/src/cli.rs
new file mode 100644
index 0000000..ff946a2
--- /dev/null
+++ b/examples/arpresolver-rs/src/cli.rs
@@ -0,0 +1,59 @@
+#[cfg(feature = "stats")]
+use std::str::FromStr as _;
+
+use clap::Parser;
+use flash::FlashConfig;
+use macaddr::MacAddr6;
+
+#[cfg(feature = "stats")]
+use flash::tui::GridLayout;
+
+#[derive(Debug, Parser)]
+pub struct Cli {
+ #[command(flatten)]
+ pub flash_config: FlashConfig,
+
+ #[arg(
+ short = 'c',
+ long,
+ default_value_t = 0,
+ help = "Starting CPU core index for socket threads"
+ )]
+ pub cpu_start: usize,
+
+ #[arg(
+ short = 'e',
+ long,
+ default_value_t = 0,
+ help = "Ending CPU core index for socket threads (inclusive)"
+ )]
+ pub cpu_end: usize,
+
+ #[arg(short = 'M', long, help = "NF MAC address")]
+ pub nf_mac: MacAddr6,
+
+ #[arg(short = 'm', long, help = "Dest MAC address")]
+ pub mac_addr: Option,
+
+ #[cfg(feature = "stats")]
+ #[command(flatten)]
+ pub stats: StatsConfig,
+}
+
+#[cfg(feature = "stats")]
+#[derive(Debug, Parser)]
+pub struct StatsConfig {
+ #[arg(
+ short = 's',
+ long = "stats-cpu",
+ default_value_t = 1,
+ help = "CPU core index for stats thread"
+ )]
+ pub cpu: usize,
+
+ #[arg(short = 'F', long, default_value_t = 1, help = "Tui frames per second")]
+ pub fps: u64,
+
+ #[arg(short = 'l', long, default_value_t = GridLayout::default(), value_parser = GridLayout::from_str, help = "Tui layout")]
+ pub layout: GridLayout,
+}
diff --git a/examples/arpresolver-rs/src/main.rs b/examples/arpresolver-rs/src/main.rs
new file mode 100644
index 0000000..fa8821a
--- /dev/null
+++ b/examples/arpresolver-rs/src/main.rs
@@ -0,0 +1,167 @@
+mod cli;
+mod nf;
+
+use std::{
+ net::Ipv4Addr,
+ sync::{
+ Arc,
+ atomic::{AtomicBool, Ordering},
+ },
+ thread,
+};
+
+use clap::Parser;
+use flash::Socket;
+use macaddr::MacAddr6;
+
+#[cfg(feature = "stats")]
+use flash::tui::StatsDashboard;
+
+use crate::cli::Cli;
+
+fn socket_thread(
+ mut socket: Socket,
+ nf_mac: MacAddr6,
+ nf_ip: Ipv4Addr,
+ mac_addr: Option,
+ run: &Arc,
+) {
+ while run.load(Ordering::SeqCst) {
+ if !socket.poll().is_ok_and(|val| val) {
+ continue;
+ }
+
+ let Ok(descs) = socket.recv() else {
+ continue;
+ };
+
+ let mut descs_send = Vec::with_capacity(descs.len());
+ let mut descs_drop = Vec::with_capacity(descs.len());
+
+ for mut desc in descs {
+ let Ok(pkt) = socket.read_exact(&desc) else {
+ descs_drop.push(desc);
+ continue;
+ };
+
+ if nf::arp_resolve(pkt, nf_mac, nf_ip) {
+ desc.set_next(1);
+ }
+
+ if let Some(mac_addr) = mac_addr {
+ pkt[0..6].copy_from_slice(mac_addr.as_bytes());
+ }
+
+ descs_send.push(desc);
+ }
+
+ socket.send(descs_send);
+ socket.drop(descs_drop);
+ }
+}
+
+fn main() {
+ #[cfg(feature = "tracing")]
+ tracing_subscriber::fmt::init();
+
+ let cli = Cli::parse();
+
+ let (sockets, route) = match flash::connect(&cli.flash_config) {
+ Ok(t) => t,
+ Err(err) => {
+ eprintln!("{err}");
+ return;
+ }
+ };
+
+ if sockets.is_empty() {
+ eprintln!("no sockets received");
+ return;
+ }
+
+ #[cfg(feature = "tracing")]
+ tracing::debug!("Sockets: {:?}", sockets);
+
+ #[cfg(feature = "stats")]
+ let mut tui = match StatsDashboard::new(
+ sockets.iter().map(Socket::stats),
+ cli.stats.fps,
+ cli.stats.layout,
+ ) {
+ Ok(t) => t,
+ Err(err) => {
+ eprintln!("error creating tui: {err}");
+ return;
+ }
+ };
+
+ let cores = core_affinity::get_core_ids()
+ .unwrap_or_default()
+ .into_iter()
+ .filter(|core_id| core_id.id >= cli.cpu_start && core_id.id <= cli.cpu_end)
+ .collect::>();
+
+ if cores.is_empty() {
+ eprintln!("no cores found in range {}-{}", cli.cpu_start, cli.cpu_end);
+ return;
+ }
+
+ #[cfg(feature = "tracing")]
+ tracing::debug!("Cores: {:?}", cores);
+
+ #[cfg(feature = "stats")]
+ let Some(stats_core) = core_affinity::get_core_ids()
+ .unwrap_or_default()
+ .into_iter()
+ .find(|core_id| core_id.id == cli.stats.cpu)
+ else {
+ eprintln!("no core found for stats thread {}", cli.stats.cpu);
+ return;
+ };
+
+ let run = Arc::new(AtomicBool::new(true));
+
+ #[cfg(not(feature = "stats"))]
+ if let Err(err) = {
+ let r = run.clone();
+ ctrlc::set_handler(move || {
+ r.store(false, Ordering::SeqCst);
+ })
+ } {
+ eprintln!("error setting Ctrl-C handler: {err}");
+ return;
+ }
+
+ let handles = sockets
+ .into_iter()
+ .zip(cores.into_iter().cycle())
+ .map(|(socket, core_id)| {
+ let r = run.clone();
+ thread::spawn(move || {
+ core_affinity::set_for_current(core_id);
+ socket_thread(socket, cli.nf_mac, route.ip_addr, cli.mac_addr, &r);
+ })
+ })
+ .collect::>();
+
+ #[cfg(feature = "stats")]
+ if let Err(err) = thread::spawn(move || {
+ core_affinity::set_for_current(stats_core);
+ if let Err(err) = tui.run() {
+ eprintln!("error dumping stats: {err}");
+ }
+ })
+ .join()
+ {
+ eprintln!("error in stats thread: {err:?}");
+ }
+
+ #[cfg(feature = "stats")]
+ run.store(false, Ordering::SeqCst);
+
+ for handle in handles {
+ if let Err(err) = handle.join() {
+ eprintln!("error in thread: {err:?}");
+ }
+ }
+}
diff --git a/examples/arpresolver-rs/src/nf.rs b/examples/arpresolver-rs/src/nf.rs
new file mode 100644
index 0000000..5e4f0b7
--- /dev/null
+++ b/examples/arpresolver-rs/src/nf.rs
@@ -0,0 +1,47 @@
+use std::net::Ipv4Addr;
+
+use macaddr::MacAddr6;
+
+const ETHER_TYPE_ARP: u16 = 0x0806;
+
+const ARP_HTYPE_ETHERNET: u16 = 0x0001;
+const ARP_PTYPE_IPV4: u16 = 0x0800;
+const ARP_HLEN_ETHERNET: u8 = 6;
+const ARP_PLEN_IPV4: u8 = 4;
+const ARP_OPCODE_REQUEST: u16 = 1;
+const ARP_OPCODE_REPLY: u16 = 2;
+
+#[forbid(clippy::indexing_slicing)]
+#[inline]
+pub fn arp_resolve(pkt: &mut [u8; 42], nf_addr: MacAddr6, nf_ip: Ipv4Addr) -> bool {
+ if u16::from_be_bytes([pkt[12], pkt[13]]) != ETHER_TYPE_ARP
+ || u16::from_be_bytes([pkt[14], pkt[15]]) != ARP_HTYPE_ETHERNET
+ || u16::from_be_bytes([pkt[16], pkt[17]]) != ARP_PTYPE_IPV4
+ || pkt[18] != ARP_HLEN_ETHERNET
+ || pkt[19] != ARP_PLEN_IPV4
+ || u16::from_be_bytes([pkt[20], pkt[21]]) != ARP_OPCODE_REQUEST
+ {
+ return false;
+ }
+
+ if pkt[38..42] != nf_ip.octets() {
+ return false;
+ }
+
+ let mut tmp = [0u8; 6];
+ tmp.copy_from_slice(&pkt[6..12]);
+
+ pkt[0..6].copy_from_slice(&tmp);
+ pkt[32..38].copy_from_slice(&tmp);
+
+ pkt[6..12].copy_from_slice(&nf_addr.into_array());
+ pkt[22..28].copy_from_slice(&nf_addr.into_array());
+
+ pkt[20..22].copy_from_slice(&ARP_OPCODE_REPLY.to_be_bytes());
+
+ tmp[0..4].copy_from_slice(&pkt[28..32]);
+ pkt[38..42].swap_with_slice(&mut tmp[0..4]);
+ pkt[28..32].copy_from_slice(&tmp[0..4]);
+
+ true
+}
diff --git a/examples/arpresolver/main.c b/examples/arpresolver/main.c
index 9e9f751..71e4e49 100644
--- a/examples/arpresolver/main.c
+++ b/examples/arpresolver/main.c
@@ -28,7 +28,7 @@
#define PROTO_STRLEN 4
#define IFNAME_STRLEN 256
-bool done = false;
+volatile bool done = false;
struct config *cfg = NULL;
struct nf *nf;
@@ -43,6 +43,16 @@ struct appconf {
uint8_t *dest_ether_addr_octet;
} app_conf;
+// clang-format off
+static const char *arpresolver_options[] = {
+ "-c \tStart CPU (default: 0)",
+ "-e \tEnd CPU (default: 0)",
+ "-s \tStats CPU (default: 1)",
+ "-S \tEnable SR-IOV mode and set destination MAC address",
+ NULL
+};
+// clang-format on
+
static int hex2int(char ch)
{
if (ch >= '0' && ch <= '9')
@@ -66,7 +76,7 @@ static uint8_t *get_mac_addr(char *mac_addr)
return dest_ether_addr_octet;
}
-static void parse_app_args(int argc, char **argv, struct appconf *app_conf, int shift)
+static int parse_app_args(int argc, char **argv, struct appconf *app_conf, int shift)
{
int c;
opterr = 0;
@@ -80,8 +90,11 @@ static void parse_app_args(int argc, char **argv, struct appconf *app_conf, int
argc -= shift;
argv += shift;
- while ((c = getopt(argc, argv, "c:e:s:S:")) != -1)
+ while ((c = getopt(argc, argv, "hc:e:s:S:")) != -1)
switch (c) {
+ case 'h':
+ printf("Usage: %s -h\n", argv[-shift]);
+ return -1;
case 'c':
app_conf->cpu_start = atoi(optarg);
break;
@@ -96,8 +109,10 @@ static void parse_app_args(int argc, char **argv, struct appconf *app_conf, int
app_conf->sriov = true;
break;
default:
- abort();
+ printf("Usage: %s -h\n", argv[-shift]);
+ return -1;
}
+ return 0;
}
static void update_dest_mac(void *data)
@@ -129,69 +144,12 @@ static void swap_mac_addresses(void *data)
*dst_addr = tmp;
}
-static int get_mac_address(void)
-{
- int fd;
- struct ifreq ifr;
-
- // Open a socket
- fd = socket(AF_INET, SOCK_DGRAM, 0);
- if (fd == -1) {
- perror("socket");
- return -1;
- }
-
- // Copy interface name into ifreq structure
- strncpy(ifr.ifr_name, cfg->ifname, IF_NAMESIZE);
- ifr.ifr_name[IFNAMSIZ - 1] = '\0';
-
- // Perform IOCTL to get MAC address
- if (ioctl(fd, SIOCGIFHWADDR, &ifr) == -1) {
- perror("ioctl");
- close(fd);
- return -1;
- }
-
- close(fd);
-
- // Copy MAC address to src_mac array
- memcpy(src_mac, ifr.ifr_hwaddr.sa_data, ETH_ALEN);
-
- return 0; // Success
-}
-
-static void configure(void)
-{
- // Need to change so that we get IPS of all NFS, not just of our local dest
- // send_cmd(cfg->uds_sockfd, FLASH__GET_DST_IP_ADDR);
- // recv_data(cfg->uds_sockfd, &num_valid_ips, sizeof(int));
- // if (num_valid_ips != 1){
- // printf("Arp-resolver should be ran along with ip4ping only");
- // exit(1);
- // }
- // log_info("Number of Backends: %d", num_valid_ips);
- // recv_data(cfg->uds_sockfd, ip4ping_ip, INET_ADDRSTRLEN);
- // log_info("ip4ping_ip: %s", ip4ping_ip);
-
- // configuring src_mac
- if (get_mac_address() < 0) {
- printf("Error in ioctl: fetch mac address\n");
- exit(1);
- }
-}
-
static void int_exit(int sig)
{
log_info("Received Signal: %d", sig);
done = true;
}
-struct Args {
- int socket_id;
- int *next;
- int next_size;
-};
-
// handling IP4
struct __attribute__((packed)) arp_header {
unsigned short arp_hd;
@@ -205,88 +163,81 @@ struct __attribute__((packed)) arp_header {
unsigned char arp_dpa[4];
};
-static void *worker__stats(void *arg)
-{
- (void)arg;
-
- if (cfg->verbose) {
- unsigned int interval = cfg->stats_interval;
- setlocale(LC_ALL, "");
-
- for (int i = 0; i < cfg->total_sockets; i++)
- nf->thread[i]->socket->timestamp = flash__get_nsecs(cfg);
-
- while (!done) {
- sleep(interval);
- if (system("clear") != 0)
- log_error("Terminal clear error");
- for (int i = 0; i < cfg->total_sockets; i++) {
- flash__dump_stats(cfg, nf->thread[i]->socket);
- }
- }
- }
- return NULL;
-}
+struct sock_args {
+ int socket_id;
+};
static void *socket_routine(void *arg)
{
- struct Args *a = (struct Args *)arg;
+ int ret;
+ nfds_t nfds = 1;
+ struct socket *xsk;
+ struct pollfd fds[1] = {};
+ struct xskvec *xskvecs, *sendvecs, *dropvecs;
+ uint32_t i, nrecv, wsend, nsend, wdrop, ndrop;
+ struct sock_args *a = (struct sock_args *)arg;
+
int socket_id = a->socket_id;
- int *next = a->next;
- int next_size = a->next_size;
- // free(arg);
+
log_info("SOCKET_ID: %d", socket_id);
- // static __u32 nb_frags;
- int i, ret, nfds = 1, nrecv;
- struct pollfd fds[1] = {};
- struct xskmsghdr msg = {};
+ xsk = nf->thread[socket_id]->socket;
- log_info("2_NEXT_SIZE: %d", next_size);
+ xskvecs = calloc(cfg->xsk->batch_size, sizeof(struct xskvec));
+ if (!xskvecs) {
+ log_error("ERROR: Memory allocation failed for xskvecs");
+ return NULL;
+ }
- for (int i = 0; i < next_size; i++) {
- log_info("2_NEXT_ITEM_%d %d", i, next[i]);
+ sendvecs = calloc(cfg->xsk->batch_size, sizeof(struct xskvec));
+ if (!sendvecs) {
+ log_error("ERROR: Memory allocation failed for sendvecs");
+ free(xskvecs);
+ return NULL;
}
- msg.msg_iov = calloc(cfg->xsk->batch_size, sizeof(struct xskvec));
+ dropvecs = calloc(cfg->xsk->batch_size, sizeof(struct xskvec));
+ if (!dropvecs) {
+ log_error("ERROR: Memory allocation failed for dropvecs");
+ free(xskvecs);
+ free(sendvecs);
+ return NULL;
+ }
- fds[0].fd = nf->thread[socket_id]->socket->fd;
+ fds[0].fd = xsk->fd;
fds[0].events = POLLIN;
for (;;) {
- if (cfg->xsk->mode & FLASH__POLL) {
- ret = flash__poll(nf->thread[socket_id]->socket, fds, nfds, cfg->xsk->poll_timeout);
- if (ret <= 0 || ret > 1)
- continue;
- }
- nrecv = flash__recvmsg(cfg, nf->thread[socket_id]->socket, &msg);
+ ret = flash__poll(cfg, xsk, fds, nfds);
+ if (!(ret == 1 || ret == -2))
+ continue;
+
+ nrecv = flash__recvmsg(cfg, xsk, xskvecs, cfg->xsk->batch_size);
- struct xskvec *drop[nrecv];
- unsigned int tot_pkt_drop = 0;
- struct xskvec *send[nrecv];
- unsigned int tot_pkt_send = 0;
+ wsend = 0;
+ wdrop = 0;
for (i = 0; i < nrecv; i++) {
- struct xskvec *xv = &msg.msg_iov[i];
- void *pkt = xv->data;
- void *pkt_end = pkt + xv->len;
+ void *pkt = xskvecs[i].data;
+
+ void *pkt_end = pkt + xskvecs[i].len;
uint8_t tmp_mac[ETH_ALEN];
unsigned char buff_ip[4];
struct ethhdr *eth = pkt;
if ((void *)(eth + 1) > pkt_end) {
- drop[tot_pkt_drop++] = &msg.msg_iov[i];
+ dropvecs[wdrop++] = xskvecs[i];
continue;
}
struct arp_header *arp = (struct arp_header *)(eth + 1);
if ((void *)(arp + 1) > pkt_end) {
- drop[tot_pkt_drop++] = &msg.msg_iov[i];
+ dropvecs[wdrop++] = xskvecs[i];
continue;
}
if (ntohs(eth->h_proto) != ETH_P_ARP || (ntohs(arp->arp_op) != ARPOP_REQUEST)) {
- send[tot_pkt_send++] = &msg.msg_iov[i];
+ sendvecs[wsend++] = xskvecs[i];
if (app_conf.sriov) {
swap_mac_addresses(pkt);
update_dest_mac(pkt);
@@ -299,12 +250,7 @@ static void *socket_routine(void *arg)
char query_ip[IP_STRLEN];
inet_ntop(AF_INET, (struct in_addr *)buff_ip, query_ip, sizeof(query_ip));
- // if (strcmp(ip4ping_ip, query_ip) == 0){
- // goto send_arp_resp;
- // }
-
- drop[tot_pkt_drop++] = &msg.msg_iov[i];
- continue;
+ dropvecs[wdrop++] = xskvecs[i];
// send_arp_resp:
memcpy(tmp_mac, eth->h_dest, ETH_ALEN);
memcpy(eth->h_dest, eth->h_source, ETH_ALEN);
@@ -319,41 +265,65 @@ static void *socket_routine(void *arg)
memcpy(arp->arp_dha, arp->arp_sha, ETH_ALEN);
memcpy(arp->arp_sha, src_mac, ETH_ALEN);
- send[tot_pkt_send++] = &msg.msg_iov[i];
+ sendvecs[wsend++] = xskvecs[i];
}
if (nrecv) {
- size_t ret_send = flash__sendmsg(cfg, nf->thread[socket_id]->socket, send, tot_pkt_send);
- size_t ret_drop = flash__dropmsg(cfg, nf->thread[socket_id]->socket, drop, tot_pkt_drop);
- if (ret_send != tot_pkt_send || ret_drop != tot_pkt_drop) {
+ nsend = flash__sendmsg(cfg, xsk, sendvecs, wsend);
+ ndrop = flash__dropmsg(cfg, xsk, dropvecs, wdrop);
+ if (nsend != wsend || ndrop != wdrop) {
log_error("errno: %d/\"%s\"\n", errno, strerror(errno));
- exit(EXIT_FAILURE);
+ break;
}
}
if (done)
break;
}
- free(msg.msg_iov);
+ free(xskvecs);
+ free(sendvecs);
+ free(dropvecs);
return NULL;
}
int main(int argc, char **argv)
{
+ int shift;
+ struct sock_args *args;
+ struct stats_conf stats_cfg = { NULL };
cpu_set_t cpuset;
+ pthread_t socket_thread, stats_thread;
+
cfg = calloc(1, sizeof(struct config));
if (!cfg) {
log_error("ERROR: Memory allocation failed\n");
exit(EXIT_FAILURE);
}
- int n = flash__parse_cmdline_args(argc, argv, cfg);
- parse_app_args(argc, argv, &app_conf, n);
- flash__configure_nf(&nf, cfg);
- flash__populate_fill_ring(nf->thread, cfg->umem->frame_size, cfg->total_sockets, cfg->umem_offset, cfg->umem_scale);
+ cfg->app_name = "ARP Resolver";
+ cfg->app_options = arpresolver_options;
+ cfg->done = &done;
+
+ shift = flash__parse_cmdline_args(argc, argv, cfg);
+ if (shift < 0)
+ goto out_cfg;
+
+ if (parse_app_args(argc, argv, &app_conf, shift) < 0)
+ goto out_cfg;
+
+ if (flash__configure_nf(&nf, cfg) < 0)
+ goto out_cfg;
log_info("Control Plane Setup Done");
- configure();
+
+ struct ether_addr tmp_addr;
+ if (flash__get_macaddr(cfg, &tmp_addr) < 0) {
+ log_error("ERROR: Unable to get MAC address for interface %s", cfg->ifname);
+ goto out_cfg;
+ }
+
+ memcpy(src_mac, tmp_addr.ether_addr_octet, ETH_ALEN);
+
// Parse JSON
signal(SIGINT, int_exit);
signal(SIGTERM, int_exit);
@@ -361,49 +331,65 @@ int main(int argc, char **argv)
log_info("STARTING Data Path");
- for (int i = 0; i < cfg->total_sockets; i++) {
- struct Args *args = calloc(1, sizeof(struct Args));
- args->socket_id = i;
- args->next = nf->next;
- args->next_size = nf->next_size;
-
- log_info("2_NEXT_SIZE: %d", args->next_size);
+ args = calloc(cfg->total_sockets, sizeof(struct sock_args));
+ if (!args) {
+ log_error("ERROR: Memory allocation failed for sock_args");
+ goto out_cfg_close;
+ }
- for (int i = 0; i < args->next_size; i++) {
- log_info("2_NEXT_ITEM_%d %d", i, nf->next[i]);
- }
+ for (int i = 0; i < cfg->total_sockets; i++) {
+ args[i].socket_id = i;
- pthread_t socket_thread;
- if (pthread_create(&socket_thread, NULL, socket_routine, args)) {
+ if (pthread_create(&socket_thread, NULL, socket_routine, &args[i])) {
log_error("Error creating socket thread");
- exit(EXIT_FAILURE);
+ goto out_args;
}
+
CPU_ZERO(&cpuset);
CPU_SET((i % (app_conf.cpu_end - app_conf.cpu_start + 1)) + app_conf.cpu_start, &cpuset);
if (pthread_setaffinity_np(socket_thread, sizeof(cpu_set_t), &cpuset) != 0) {
log_error("ERROR: Unable to set thread affinity: %s\n", strerror(errno));
- exit(EXIT_FAILURE);
+ goto out_args;
}
- pthread_detach(socket_thread);
+ if (pthread_detach(socket_thread) != 0) {
+ log_error("ERROR: Unable to detach thread: %s", strerror(errno));
+ goto out_args;
+ }
}
- pthread_t stats_thread;
- if (pthread_create(&stats_thread, NULL, worker__stats, NULL)) {
+ stats_cfg.nf = nf;
+ stats_cfg.cfg = cfg;
+
+ if (pthread_create(&stats_thread, NULL, flash__stats_thread, &stats_cfg)) {
log_error("Error creating statistics thread");
- exit(EXIT_FAILURE);
+ goto out_args;
}
+
CPU_ZERO(&cpuset);
CPU_SET(app_conf.stats_cpu, &cpuset);
if (pthread_setaffinity_np(stats_thread, sizeof(cpu_set_t), &cpuset) != 0) {
log_error("ERROR: Unable to set thread affinity: %s\n", strerror(errno));
- exit(EXIT_FAILURE);
+ goto out_args;
}
- pthread_detach(stats_thread);
- wait_for_cmd(cfg);
+ if (pthread_detach(stats_thread) != 0) {
+ log_error("ERROR: Unable to detach thread: %s", strerror(errno));
+ goto out_args;
+ }
+ flash__wait(cfg);
flash__xsk_close(cfg, nf);
- return EXIT_SUCCESS;
+ exit(EXIT_SUCCESS);
+
+out_args:
+ done = true;
+ free(args);
+out_cfg_close:
+ sleep(1);
+ flash__xsk_close(cfg, nf);
+out_cfg:
+ free(cfg);
+ exit(EXIT_FAILURE);
}
\ No newline at end of file
diff --git a/examples/firewall-rs/Cargo.toml b/examples/firewall-rs/Cargo.toml
index e031f77..db462fb 100644
--- a/examples/firewall-rs/Cargo.toml
+++ b/examples/firewall-rs/Cargo.toml
@@ -7,7 +7,7 @@ edition = "2024"
clap = { version = "4.5.35", features = ["derive"] }
core_affinity = "0.8.3"
csv = "1.3.1"
-ctrlc = "3.4.5"
+ctrlc = { version = "3.4.5", optional = true }
flash = { path = "../../lib/flash-rs", features = ["clap"] }
macaddr = "1.0.1"
serde = { version = "1.0.219", features = ["derive"] }
@@ -15,7 +15,8 @@ tracing = { version = "0.1.41", optional = true }
tracing-subscriber = { version = "0.3.19", optional = true }
[features]
-default = []
+default = ["dep:ctrlc"]
+stats = ["flash/stats", "flash/tui"]
tracing = ["dep:tracing", "dep:tracing-subscriber", "flash/tracing"]
[lints.rust]
diff --git a/examples/firewall-rs/src/cli.rs b/examples/firewall-rs/src/cli.rs
index 6b0bffb..3c3448a 100644
--- a/examples/firewall-rs/src/cli.rs
+++ b/examples/firewall-rs/src/cli.rs
@@ -1,9 +1,15 @@
use std::path::PathBuf;
+#[cfg(feature = "stats")]
+use std::str::FromStr as _;
+
use clap::Parser;
use flash::FlashConfig;
use macaddr::MacAddr6;
+#[cfg(feature = "stats")]
+use flash::tui::GridLayout;
+
#[derive(Debug, Parser)]
pub struct Cli {
#[command(flatten)]
@@ -30,4 +36,26 @@ pub struct Cli {
#[arg(short = 'm', long, help = "Dest MAC address")]
pub mac_addr: Option,
+
+ #[cfg(feature = "stats")]
+ #[command(flatten)]
+ pub stats: StatsConfig,
+}
+
+#[cfg(feature = "stats")]
+#[derive(Debug, Parser)]
+pub struct StatsConfig {
+ #[arg(
+ short = 's',
+ long = "stats-cpu",
+ default_value_t = 1,
+ help = "CPU core index for stats thread"
+ )]
+ pub cpu: usize,
+
+ #[arg(short = 'F', long, default_value_t = 1, help = "Tui frames per second")]
+ pub fps: u64,
+
+ #[arg(short = 'l', long, default_value_t = GridLayout::default(), value_parser = GridLayout::from_str, help = "Tui layout")]
+ pub layout: GridLayout,
}
diff --git a/examples/firewall-rs/src/main.rs b/examples/firewall-rs/src/main.rs
index 165b204..c042e5a 100644
--- a/examples/firewall-rs/src/main.rs
+++ b/examples/firewall-rs/src/main.rs
@@ -13,6 +13,9 @@ use clap::Parser;
use flash::Socket;
use macaddr::MacAddr6;
+#[cfg(feature = "stats")]
+use flash::tui::StatsDashboard;
+
use crate::{cli::Cli, nf::Firewall};
fn socket_thread(
@@ -30,11 +33,22 @@ fn socket_thread(
continue;
};
- let (descs_send, descs_drop) = descs.into_iter().partition(|desc| {
- socket
- .read_exact(desc)
- .is_ok_and(|pkt| nf::firewall_filter(pkt, firewall, mac_addr))
- });
+ let mut descs_send = Vec::with_capacity(descs.len());
+ let mut descs_drop = Vec::with_capacity(descs.len());
+
+ for desc in descs {
+ if let Ok(pkt) = socket.read_exact(&desc)
+ && nf::firewall_filter(firewall, pkt)
+ {
+ if let Some(mac_addr) = mac_addr {
+ pkt[0..6].copy_from_slice(mac_addr.as_bytes());
+ }
+
+ descs_send.push(desc);
+ } else {
+ descs_drop.push(desc);
+ }
+ }
socket.send(descs_send);
socket.drop(descs_drop);
@@ -60,6 +74,22 @@ fn main() {
return;
}
+ #[cfg(feature = "tracing")]
+ tracing::debug!("Sockets: {:?}", sockets);
+
+ #[cfg(feature = "stats")]
+ let mut tui = match StatsDashboard::new(
+ sockets.iter().map(Socket::stats),
+ cli.stats.fps,
+ cli.stats.layout,
+ ) {
+ Ok(t) => t,
+ Err(err) => {
+ eprintln!("error creating tui: {err}");
+ return;
+ }
+ };
+
let firewall = match Firewall::new(cli.denylist) {
Ok(firewall) => Arc::new(firewall),
Err(err) => {
@@ -82,12 +112,25 @@ fn main() {
#[cfg(feature = "tracing")]
tracing::debug!("Cores: {:?}", cores);
+ #[cfg(feature = "stats")]
+ let Some(stats_core) = core_affinity::get_core_ids()
+ .unwrap_or_default()
+ .into_iter()
+ .find(|core_id| core_id.id == cli.stats.cpu)
+ else {
+ eprintln!("no core found for stats thread {}", cli.stats.cpu);
+ return;
+ };
+
let run = Arc::new(AtomicBool::new(true));
- let r = run.clone();
- if let Err(err) = ctrlc::set_handler(move || {
- r.store(false, Ordering::SeqCst);
- }) {
+ #[cfg(not(feature = "stats"))]
+ if let Err(err) = {
+ let r = run.clone();
+ ctrlc::set_handler(move || {
+ r.store(false, Ordering::SeqCst);
+ })
+ } {
eprintln!("error setting Ctrl-C handler: {err}");
return;
}
@@ -106,6 +149,21 @@ fn main() {
})
.collect::>();
+ #[cfg(feature = "stats")]
+ if let Err(err) = thread::spawn(move || {
+ core_affinity::set_for_current(stats_core);
+ if let Err(err) = tui.run() {
+ eprintln!("error dumping stats: {err}");
+ }
+ })
+ .join()
+ {
+ eprintln!("error in stats thread: {err:?}");
+ }
+
+ #[cfg(feature = "stats")]
+ run.store(false, Ordering::SeqCst);
+
for handle in handles {
if let Err(err) = handle.join() {
eprintln!("error in thread: {err:?}");
diff --git a/examples/firewall-rs/src/nf.rs b/examples/firewall-rs/src/nf.rs
index b6cd47d..fe37d99 100644
--- a/examples/firewall-rs/src/nf.rs
+++ b/examples/firewall-rs/src/nf.rs
@@ -3,7 +3,6 @@
use std::{net::Ipv4Addr, path::Path};
use csv::Reader;
-use macaddr::MacAddr6;
use serde::{Deserialize, de};
const ETHER_TYPE_IPV4: u16 = 0x0800;
@@ -78,30 +77,12 @@ impl Tuple5 {
}
#[inline]
-pub fn firewall_filter(
- pkt: &mut [u8; 54],
- firewall: &Firewall,
- mac_addr: Option,
-) -> bool {
- if u16::from_be_bytes([pkt[12], pkt[13]]) != ETHER_TYPE_IPV4 {
- return false;
+pub fn firewall_filter(firewall: &Firewall, pkt: &mut [u8; 54]) -> bool {
+ if u16::from_be_bytes([pkt[12], pkt[13]]) == ETHER_TYPE_IPV4
+ && let Some(tuple5) = Tuple5::new(pkt)
+ {
+ !firewall.blocked(&tuple5)
+ } else {
+ false
}
-
- let Some(tuple5) = Tuple5::new(pkt) else {
- return false;
- };
-
- if firewall.blocked(&tuple5) {
- return false;
- }
-
- if let Some(mac_addr) = mac_addr {
- let mut tmp = [0; 6];
-
- tmp.copy_from_slice(&pkt[0..6]);
- pkt[6..12].copy_from_slice(&tmp);
- pkt[0..6].copy_from_slice(mac_addr.as_bytes());
- }
-
- true
}
diff --git a/examples/firewall/main.c b/examples/firewall/main.c
index 601603a..9e313e5 100644
--- a/examples/firewall/main.c
+++ b/examples/firewall/main.c
@@ -25,7 +25,7 @@
#define IFNAME_STRLEN 256
#define NUM_INVALID_SESSIONS 1000
-bool done = false;
+volatile bool done = false;
struct config *cfg = NULL;
struct nf *nf;
@@ -41,11 +41,14 @@ struct appconf {
int stats_cpu;
} app_conf;
-struct Args {
- int socket_id;
- int *next;
- int next_size;
+// clang-format off
+static const char *firewall_options[] = {
+ "-c \tStart CPU (default: 0)",
+ "-e \tEnd CPU (default: 0)",
+ "-s \tStats CPU (default: 1)",
+ NULL
};
+// clang-format on
struct session_id {
uint32_t saddr;
@@ -70,7 +73,7 @@ static void *configure(void)
return NULL;
}
-static void parse_app_args(int argc, char **argv, struct appconf *app_conf, int shift)
+static int parse_app_args(int argc, char **argv, struct appconf *app_conf, int shift)
{
int c;
opterr = 0;
@@ -83,8 +86,11 @@ static void parse_app_args(int argc, char **argv, struct appconf *app_conf, int
argc -= shift;
argv += shift;
- while ((c = getopt(argc, argv, "c:e:s:")) != -1)
+ while ((c = getopt(argc, argv, "hc:e:s:")) != -1)
switch (c) {
+ case 'h':
+ printf("Usage: %s -h\n", argv[-shift]);
+ return -1;
case 'c':
app_conf->cpu_start = atoi(optarg);
break;
@@ -95,88 +101,85 @@ static void parse_app_args(int argc, char **argv, struct appconf *app_conf, int
app_conf->stats_cpu = atoi(optarg);
break;
default:
- abort();
+ printf("Usage: %s -h\n", argv[-shift]);
+ return -1;
}
+ return 0;
}
-static void *worker__stats(void *arg)
-{
- (void)arg;
-
- if (cfg->verbose) {
- unsigned int interval = cfg->stats_interval;
- setlocale(LC_ALL, "");
-
- for (int i = 0; i < cfg->total_sockets; i++)
- nf->thread[i]->socket->timestamp = flash__get_nsecs(cfg);
-
- while (!done) {
- sleep(interval);
- if (system("clear") != 0)
- log_error("Terminal clear error");
- for (int i = 0; i < cfg->total_sockets; i++) {
- flash__dump_stats(cfg, nf->thread[i]->socket);
- }
- }
- }
- return NULL;
-}
+struct sock_args {
+ int socket_id;
+};
static void *socket_routine(void *arg)
{
- struct Args *a = (struct Args *)arg;
+ int ret;
+ nfds_t nfds = 1;
+ struct socket *xsk;
+ struct pollfd fds[1] = {};
+ struct xskvec *xskvecs, *sendvecs, *dropvecs;
+ uint32_t i, nrecv, wsend, nsend, wdrop, ndrop;
+ struct sock_args *a = (struct sock_args *)arg;
+
int socket_id = a->socket_id;
- int *next = a->next;
- int next_size = a->next_size;
- // free(arg);
+
log_info("SOCKET_ID: %d", socket_id);
- // static __u32 nb_frags;
- int i, ret, nfds = 1, nrecv;
- struct pollfd fds[1] = {};
- struct xskmsghdr msg = {};
- log_info("2_NEXT_SIZE: %d", next_size);
+ xsk = nf->thread[socket_id]->socket;
- for (int i = 0; i < next_size; i++) {
- log_info("2_NEXT_ITEM_%d %d", i, next[i]);
+ xskvecs = calloc(cfg->xsk->batch_size, sizeof(struct xskvec));
+ if (!xskvecs) {
+ log_error("ERROR: Memory allocation failed for xskvecs");
+ return NULL;
}
- msg.msg_iov = calloc(cfg->xsk->batch_size, sizeof(struct xskvec));
+ sendvecs = calloc(cfg->xsk->batch_size, sizeof(struct xskvec));
+ if (!sendvecs) {
+ log_error("ERROR: Memory allocation failed for sendvecs");
+ free(xskvecs);
+ return NULL;
+ }
+
+ dropvecs = calloc(cfg->xsk->batch_size, sizeof(struct xskvec));
+ if (!dropvecs) {
+ log_error("ERROR: Memory allocation failed for dropvecs");
+ free(xskvecs);
+ free(sendvecs);
+ return NULL;
+ }
fds[0].fd = nf->thread[socket_id]->socket->fd;
fds[0].events = POLLIN;
for (;;) {
- if (cfg->xsk->mode & FLASH__POLL) {
- ret = flash__poll(nf->thread[socket_id]->socket, fds, nfds, cfg->xsk->poll_timeout);
- if (ret <= 0 || ret > 1)
- continue;
- }
- nrecv = flash__recvmsg(cfg, nf->thread[socket_id]->socket, &msg);
+ ret = flash__poll(cfg, xsk, fds, nfds);
+ if (!(ret == 1 || ret == -2))
+ continue;
- struct xskvec *drop[nrecv];
- unsigned int tot_pkt_drop = 0;
- struct xskvec *send[nrecv];
- unsigned int tot_pkt_send = 0;
+ nrecv = flash__recvmsg(cfg, xsk, xskvecs, cfg->xsk->batch_size);
+ wsend = 0;
+ wdrop = 0;
for (i = 0; i < nrecv; i++) {
- struct xskvec *xv = &msg.msg_iov[i];
+ struct xskvec *xv = &xskvecs[i];
+
void *pkt = xv->data;
void *pkt_end = pkt + xv->len;
+
struct ethhdr *eth = pkt;
if ((void *)(eth + 1) > pkt_end) {
- drop[tot_pkt_drop++] = &msg.msg_iov[i];
+ dropvecs[wdrop++] = xskvecs[i];
continue;
}
if (eth->h_proto != htons(ETH_P_IP)) {
- drop[tot_pkt_drop++] = &msg.msg_iov[i];
+ dropvecs[wdrop++] = xskvecs[i];
continue;
}
struct iphdr *iph = (void *)(eth + 1);
if ((void *)(iph + 1) > pkt_end) {
- drop[tot_pkt_drop++] = &msg.msg_iov[i];
+ dropvecs[wdrop++] = xskvecs[i];
continue;
}
@@ -188,7 +191,7 @@ static void *socket_routine(void *arg)
case IPPROTO_TCP:;
struct tcphdr *tcph = next;
if ((void *)(tcph + 1) > pkt_end) {
- drop[tot_pkt_drop++] = &msg.msg_iov[i];
+ dropvecs[wdrop++] = xskvecs[i];
continue;
}
@@ -200,7 +203,7 @@ static void *socket_routine(void *arg)
case IPPROTO_UDP:;
struct udphdr *udph = next;
if ((void *)(udph + 1) > pkt_end) {
- drop[tot_pkt_drop++] = &msg.msg_iov[i];
+ dropvecs[wdrop++] = xskvecs[i];
continue;
}
@@ -210,7 +213,7 @@ static void *socket_routine(void *arg)
break;
default:
- drop[tot_pkt_drop++] = &msg.msg_iov[i];
+ dropvecs[wdrop++] = xskvecs[i];
continue;
}
@@ -222,48 +225,64 @@ static void *socket_routine(void *arg)
sid.dport = *dport;
// Find murmurhash of sid
- uint32_t sid_hash = murmurhash((void*)&sid, sizeof(struct session_id), 0);
+ uint32_t sid_hash = murmurhash((void *)&sid, sizeof(struct session_id), 0);
bool invalid = false;
for (int i = 0; i < NUM_INVALID_SESSIONS; i++) {
if (invalid_sessions[i] == sid_hash) {
- drop[tot_pkt_drop++] = &msg.msg_iov[i];
+ dropvecs[wdrop++] = xskvecs[i];
invalid = true;
break;
}
}
- if (! invalid)
- send[tot_pkt_send++] = &msg.msg_iov[i];
+ if (!invalid)
+ sendvecs[wsend++] = xskvecs[i];
}
if (nrecv) {
- size_t ret_send = flash__sendmsg(cfg, nf->thread[socket_id]->socket, send, tot_pkt_send);
- size_t ret_drop = flash__dropmsg(cfg, nf->thread[socket_id]->socket, drop, tot_pkt_drop);
- if (ret_send != tot_pkt_send || ret_drop != tot_pkt_drop) {
+ nsend = flash__sendmsg(cfg, xsk, sendvecs, wsend);
+ ndrop = flash__dropmsg(cfg, xsk, dropvecs, wdrop);
+ if (nsend != wsend || ndrop != wdrop) {
log_error("errno: %d/\"%s\"\n", errno, strerror(errno));
- exit(EXIT_FAILURE);
+ break;
}
}
if (done)
break;
}
- free(msg.msg_iov);
+ free(xskvecs);
+ free(sendvecs);
+ free(dropvecs);
return NULL;
}
int main(int argc, char **argv)
{
+ int shift;
+ struct sock_args *args;
+ struct stats_conf stats_cfg = { NULL };
cpu_set_t cpuset;
+ pthread_t socket_thread, stats_thread;
+
cfg = calloc(1, sizeof(struct config));
if (!cfg) {
log_error("ERROR: Memory allocation failed\n");
exit(EXIT_FAILURE);
}
- int n = flash__parse_cmdline_args(argc, argv, cfg);
- parse_app_args(argc, argv, &app_conf, n);
- flash__configure_nf(&nf, cfg);
- flash__populate_fill_ring(nf->thread, cfg->umem->frame_size, cfg->total_sockets, cfg->umem_offset, cfg->umem_scale);
+ cfg->app_name = "Firewall Application";
+ cfg->app_options = firewall_options;
+ cfg->done = &done;
+
+ shift = flash__parse_cmdline_args(argc, argv, cfg);
+ if (shift < 0)
+ goto out_cfg;
+
+ if (parse_app_args(argc, argv, &app_conf, shift) < 0)
+ goto out_cfg;
+
+ if (flash__configure_nf(&nf, cfg) < 0)
+ goto out_cfg;
log_info("Control Plane Setup Done");
@@ -274,49 +293,63 @@ int main(int argc, char **argv)
log_info("STARTING Data Path");
+ args = calloc(cfg->total_sockets, sizeof(struct sock_args));
+ if (!args) {
+ log_error("ERROR: Memory allocation failed for sock_args");
+ goto out_cfg_close;
+ }
for (int i = 0; i < cfg->total_sockets; i++) {
- struct Args *args = calloc(1, sizeof(struct Args));
- args->socket_id = i;
- args->next = nf->next;
- args->next_size = nf->next_size;
-
- log_info("2_NEXT_SIZE: %d", args->next_size);
-
- for (int i = 0; i < args->next_size; i++) {
- log_info("2_NEXT_ITEM_%d %d", i, nf->next[i]);
- }
+ args[i].socket_id = i;
- pthread_t socket_thread;
- if (pthread_create(&socket_thread, NULL, socket_routine, args)) {
+ if (pthread_create(&socket_thread, NULL, socket_routine, &args[i])) {
log_error("Error creating socket thread");
- exit(EXIT_FAILURE);
+ goto out_args;
}
+
CPU_ZERO(&cpuset);
CPU_SET((i % (app_conf.cpu_end - app_conf.cpu_start + 1)) + app_conf.cpu_start, &cpuset);
if (pthread_setaffinity_np(socket_thread, sizeof(cpu_set_t), &cpuset) != 0) {
log_error("ERROR: Unable to set thread affinity: %s\n", strerror(errno));
- exit(EXIT_FAILURE);
+ goto out_args;
}
- pthread_detach(socket_thread);
+ if (pthread_detach(socket_thread) != 0) {
+ log_error("ERROR: Unable to detach thread: %s\n", strerror(errno));
+ goto out_args;
+ }
}
- pthread_t stats_thread;
- if (pthread_create(&stats_thread, NULL, worker__stats, NULL)) {
+ stats_cfg.nf = nf;
+ stats_cfg.cfg = cfg;
+
+ if (pthread_create(&stats_thread, NULL, flash__stats_thread, &stats_cfg)) {
log_error("Error creating statistics thread");
- exit(EXIT_FAILURE);
+ goto out_args;
}
CPU_ZERO(&cpuset);
CPU_SET(app_conf.stats_cpu, &cpuset);
if (pthread_setaffinity_np(stats_thread, sizeof(cpu_set_t), &cpuset) != 0) {
log_error("ERROR: Unable to set thread affinity: %s\n", strerror(errno));
- exit(EXIT_FAILURE);
+ goto out_args;
}
- pthread_detach(stats_thread);
- wait_for_cmd(cfg);
+ if (pthread_detach(stats_thread) != 0) {
+ log_error("ERROR: Unable to detach thread: %s\n", strerror(errno));
+ goto out_args;
+ }
+ flash__wait(cfg);
flash__xsk_close(cfg, nf);
- return EXIT_SUCCESS;
+ exit(EXIT_SUCCESS);
+
+out_args:
+ done = true;
+ free(args);
+out_cfg_close:
+ sleep(1);
+ flash__xsk_close(cfg, nf);
+out_cfg:
+ free(cfg);
+ exit(EXIT_FAILURE);
}
\ No newline at end of file
diff --git a/examples/helloworld-rs/src/main.rs b/examples/helloworld-rs/src/main.rs
index 16f6a87..3855497 100644
--- a/examples/helloworld-rs/src/main.rs
+++ b/examples/helloworld-rs/src/main.rs
@@ -22,5 +22,5 @@ fn main() {
}
#[cfg(feature = "tracing")]
- tracing::info!("sockets: {sockets:?}");
+ tracing::info!("Sockets: {sockets:?}");
}
diff --git a/examples/helloworld/main.c b/examples/helloworld/main.c
index fcbdbab..164e748 100644
--- a/examples/helloworld/main.c
+++ b/examples/helloworld/main.c
@@ -1,51 +1,97 @@
/* SPDX-License-Identifier: Apache-2.0
* Copyright (c) 2025 Debojeet Das
*
- * helloworld: A simple helloworld NF that shows control plane setup using Flash monitor
+ * helloworld: A simple helloworld NF that shows how to parse args for application
+ * and control plane setup using Flash monitor
*/
-
-#include
-#include
-#include
-
#include
#include
#include
-bool done = false;
+#include
+
struct config *cfg = NULL;
-struct nf *nf;
+struct nf *nf = NULL;
+
+struct appconf {
+ int count;
+ bool universe;
+} app_conf;
-static void int_exit(int sig)
+// clang-format off
+static const char *hw_options[] = {
+ "-n \tprint count",
+ "-u \t\tprint hello universe",
+ NULL
+};
+// clang-format on
+
+static int parse_app_args(int argc, char **argv, struct appconf *app_conf, int shift)
{
- log_info("Received Signal: %d", sig);
- done = true;
+ int c;
+ opterr = 0;
+
+ app_conf->count = 1;
+ app_conf->universe = false;
+
+ argc -= shift;
+ argv += shift;
+
+ while ((c = getopt(argc, argv, "hn:u")) != -1)
+ switch (c) {
+ case 'h':
+ printf("Usage: %s -h\n", argv[-shift]);
+ return -1;
+ case 'n':
+ app_conf->count = atoi(optarg);
+ break;
+ case 'u':
+ app_conf->universe = true;
+ break;
+ default:
+ printf("Usage: %s -h\n", argv[-shift]);
+ return -1;
+ }
+
+ return 0;
}
int main(int argc, char **argv)
{
+ int shift;
+
cfg = calloc(1, sizeof(struct config));
if (!cfg) {
- log_error("ERROR: Memory allocation failed\n");
+ log_error("ERROR: Memory allocation failed");
exit(EXIT_FAILURE);
}
- flash__parse_cmdline_args(argc, argv, cfg);
- flash__configure_nf(&nf, cfg);
- flash__populate_fill_ring(nf->thread, cfg->umem->frame_size, cfg->total_sockets, cfg->umem_offset, cfg->umem_scale);
+ cfg->app_name = "Hello World Application";
+ cfg->app_options = hw_options;
- log_info("Control Plane Setup Done");
+ shift = flash__parse_cmdline_args(argc, argv, cfg);
+ if (shift < 0)
+ goto out_cfg;
- signal(SIGINT, int_exit);
- signal(SIGTERM, int_exit);
- signal(SIGABRT, int_exit);
+ if (parse_app_args(argc, argv, &app_conf, shift) < 0)
+ goto out_cfg;
- log_info("All Setup Done!");
- log_info("Hello, World!");
+ if (flash__configure_nf(&nf, cfg) < 0)
+ goto out_cfg;
+
+ log_info("Control Plane setup done...");
+
+ const char *message = app_conf.universe ? "Hello Universe!" : "Hello World!";
+ for (int i = 0; i < app_conf.count; i++)
+ log_info("%s", message);
flash__xsk_close(cfg, nf);
log_info("Control plane setup is working");
return EXIT_SUCCESS;
+
+out_cfg:
+ free(cfg);
+ exit(EXIT_FAILURE);
}
diff --git a/examples/ip4ping-rs/Cargo.toml b/examples/ip4ping-rs/Cargo.toml
index 1d13a2b..0fd97e9 100644
--- a/examples/ip4ping-rs/Cargo.toml
+++ b/examples/ip4ping-rs/Cargo.toml
@@ -6,13 +6,15 @@ edition = "2024"
[dependencies]
clap = { version = "4.5.35", features = ["derive"] }
core_affinity = "0.8.3"
-ctrlc = "3.4.5"
+ctrlc = { version = "3.4.5", optional = true }
flash = { path = "../../lib/flash-rs", features = ["clap"] }
+macaddr = "1.0.1"
tracing = { version = "0.1.41", optional = true }
tracing-subscriber = { version = "0.3.19", optional = true }
[features]
-default = []
+default = ["dep:ctrlc"]
+stats = ["flash/stats", "flash/tui"]
tracing = ["dep:tracing", "dep:tracing-subscriber", "flash/tracing"]
[lints.rust]
diff --git a/examples/ip4ping-rs/src/cli.rs b/examples/ip4ping-rs/src/cli.rs
index 31aa504..cfc783e 100644
--- a/examples/ip4ping-rs/src/cli.rs
+++ b/examples/ip4ping-rs/src/cli.rs
@@ -1,5 +1,12 @@
+#[cfg(feature = "stats")]
+use std::str::FromStr as _;
+
use clap::Parser;
use flash::FlashConfig;
+use macaddr::MacAddr6;
+
+#[cfg(feature = "stats")]
+use flash::tui::GridLayout;
#[derive(Debug, Parser)]
pub struct Cli {
@@ -21,4 +28,29 @@ pub struct Cli {
help = "Ending CPU core index for socket threads (inclusive)"
)]
pub cpu_end: usize,
+
+ #[arg(short = 'm', long, help = "Dest MAC address")]
+ pub mac_addr: Option,
+
+ #[cfg(feature = "stats")]
+ #[command(flatten)]
+ pub stats: StatsConfig,
+}
+
+#[cfg(feature = "stats")]
+#[derive(Debug, Parser)]
+pub struct StatsConfig {
+ #[arg(
+ short = 's',
+ long = "stats-cpu",
+ default_value_t = 1,
+ help = "CPU core index for stats thread"
+ )]
+ pub cpu: usize,
+
+ #[arg(short = 'F', long, default_value_t = 1, help = "Tui frames per second")]
+ pub fps: u64,
+
+ #[arg(short = 'l', long, default_value_t = GridLayout::default(), value_parser = GridLayout::from_str, help = "Tui layout")]
+ pub layout: GridLayout,
}
diff --git a/examples/ip4ping-rs/src/main.rs b/examples/ip4ping-rs/src/main.rs
index b56fea5..0784743 100644
--- a/examples/ip4ping-rs/src/main.rs
+++ b/examples/ip4ping-rs/src/main.rs
@@ -11,10 +11,14 @@ use std::{
use clap::Parser;
use flash::Socket;
+use macaddr::MacAddr6;
+
+#[cfg(feature = "stats")]
+use flash::tui::StatsDashboard;
use crate::cli::Cli;
-fn socket_thread(mut socket: Socket, run: &Arc) {
+fn socket_thread(mut socket: Socket, mac_addr: Option, run: &Arc) {
while run.load(Ordering::SeqCst) {
if !socket.poll().is_ok_and(|val| val) {
continue;
@@ -24,9 +28,25 @@ fn socket_thread(mut socket: Socket, run: &Arc) {
continue;
};
- let (descs_send, descs_drop) = descs
- .into_iter()
- .partition(|desc| socket.read_exact(desc).is_ok_and(nf::echo_reply));
+ let mut descs_send = Vec::with_capacity(descs.len());
+ let mut descs_drop = Vec::with_capacity(descs.len());
+
+ for mut desc in descs {
+ let Ok(pkt) = socket.read_exact(&desc) else {
+ descs_drop.push(desc);
+ continue;
+ };
+
+ if nf::echo_reply(pkt) {
+ desc.set_next(1);
+ }
+
+ if let Some(mac_addr) = mac_addr {
+ pkt[0..6].copy_from_slice(mac_addr.as_bytes());
+ }
+
+ descs_send.push(desc);
+ }
socket.send(descs_send);
socket.drop(descs_drop);
@@ -55,6 +75,19 @@ fn main() {
#[cfg(feature = "tracing")]
tracing::debug!("Sockets: {:?}", sockets);
+ #[cfg(feature = "stats")]
+ let mut tui = match StatsDashboard::new(
+ sockets.iter().map(Socket::stats),
+ cli.stats.fps,
+ cli.stats.layout,
+ ) {
+ Ok(t) => t,
+ Err(err) => {
+ eprintln!("error creating tui: {err}");
+ return;
+ }
+ };
+
let cores = core_affinity::get_core_ids()
.unwrap_or_default()
.into_iter()
@@ -62,19 +95,32 @@ fn main() {
.collect::>();
if cores.is_empty() {
- eprintln!("No cores found in range {}-{}", cli.cpu_start, cli.cpu_end);
+ eprintln!("no cores found in range {}-{}", cli.cpu_start, cli.cpu_end);
return;
}
#[cfg(feature = "tracing")]
tracing::debug!("Cores: {:?}", cores);
+ #[cfg(feature = "stats")]
+ let Some(stats_core) = core_affinity::get_core_ids()
+ .unwrap_or_default()
+ .into_iter()
+ .find(|core_id| core_id.id == cli.stats.cpu)
+ else {
+ eprintln!("no core found for stats thread {}", cli.stats.cpu);
+ return;
+ };
+
let run = Arc::new(AtomicBool::new(true));
- let r = run.clone();
- if let Err(err) = ctrlc::set_handler(move || {
- r.store(false, Ordering::SeqCst);
- }) {
+ #[cfg(not(feature = "stats"))]
+ if let Err(err) = {
+ let r = run.clone();
+ ctrlc::set_handler(move || {
+ r.store(false, Ordering::SeqCst);
+ })
+ } {
eprintln!("error setting Ctrl-C handler: {err}");
return;
}
@@ -86,11 +132,26 @@ fn main() {
let r = run.clone();
thread::spawn(move || {
core_affinity::set_for_current(core_id);
- socket_thread(socket, &r);
+ socket_thread(socket, cli.mac_addr, &r);
})
})
.collect::>();
+ #[cfg(feature = "stats")]
+ if let Err(err) = thread::spawn(move || {
+ core_affinity::set_for_current(stats_core);
+ if let Err(err) = tui.run() {
+ eprintln!("error dumping stats: {err}");
+ }
+ })
+ .join()
+ {
+ eprintln!("error in stats thread: {err:?}");
+ }
+
+ #[cfg(feature = "stats")]
+ run.store(false, Ordering::SeqCst);
+
for handle in handles {
if let Err(err) = handle.join() {
eprintln!("error in thread: {err:?}");
diff --git a/examples/ip4ping/main.c b/examples/ip4ping/main.c
index cdc02a1..5dc876e 100644
--- a/examples/ip4ping/main.c
+++ b/examples/ip4ping/main.c
@@ -17,7 +17,7 @@
#include
#include
-bool done = false;
+volatile bool done = false;
struct config *cfg = NULL;
struct nf *nf;
@@ -35,6 +35,16 @@ struct appconf {
uint8_t *dest_ether_addr_octet;
} app_conf;
+// clang-format off
+static const char *ip4ping_options[] = {
+ "-c \tStart CPU (default: 0)",
+ "-e \tEnd CPU (default: 0)",
+ "-s \tStats CPU (default: 1)",
+ "-S \tEnable SR-IOV mode and set dest MAC address",
+ NULL
+};
+// clang-format on
+
static int hex2int(char ch)
{
if (ch >= '0' && ch <= '9')
@@ -58,7 +68,7 @@ static uint8_t *get_mac_addr(char *mac_addr)
return dest_ether_addr_octet;
}
-static void parse_app_args(int argc, char **argv, struct appconf *app_conf, int shift)
+static int parse_app_args(int argc, char **argv, struct appconf *app_conf, int shift)
{
int c;
opterr = 0;
@@ -72,8 +82,11 @@ static void parse_app_args(int argc, char **argv, struct appconf *app_conf, int
argc -= shift;
argv += shift;
- while ((c = getopt(argc, argv, "c:e:s:S:")) != -1)
+ while ((c = getopt(argc, argv, "hc:e:s:S:")) != -1)
switch (c) {
+ case 'h':
+ printf("Usage: %s -h\n", argv[-shift]);
+ return -1;
case 'c':
app_conf->cpu_start = atoi(optarg);
break;
@@ -88,8 +101,11 @@ static void parse_app_args(int argc, char **argv, struct appconf *app_conf, int
app_conf->sriov = true;
break;
default:
- abort();
+ printf("Usage: %s -h\n", argv[-shift]);
+ return -1;
}
+
+ return 0;
}
static void update_dest_mac(void *data)
@@ -139,44 +155,61 @@ static inline void csum_replace2(__sum16 *sum, __be16 old, __be16 new)
*sum = ~csum16_add(csum16_sub(~(*sum), old), new);
}
-struct Args {
+struct sock_args {
int socket_id;
- int *next;
- int next_size;
};
unsigned int count = 1;
static void *socket_routine(void *arg)
{
- struct Args *a = (struct Args *)arg;
+ int ret;
+ nfds_t nfds = 1;
+ struct socket *xsk;
+ struct xskvec *xskvecs, *sendvecs, *dropvecs;
+ struct pollfd fds[1] = {};
+ uint32_t i, nrecv, nsend, wsend, wdrop, ndrop;
+ struct sock_args *a = (struct sock_args *)arg;
+
int socket_id = a->socket_id;
log_info("SOCKET_ID: %d", socket_id);
- int i, ret, nfds = 1, nrecv;
- struct pollfd fds[1] = {};
- struct xskmsghdr msg = {};
+ xsk = nf->thread[socket_id]->socket;
- msg.msg_iov = calloc(cfg->xsk->batch_size, sizeof(struct xskvec));
+ xskvecs = calloc(cfg->xsk->batch_size, sizeof(struct xskvec));
+ if (!xskvecs) {
+ log_error("Failed to allocate xskvecs array");
+ return NULL;
+ }
+
+ sendvecs = calloc(cfg->xsk->batch_size, sizeof(struct xskvec));
+ if (!sendvecs) {
+ log_error("Failed to allocate sendvecs array");
+ free(xskvecs);
+ return NULL;
+ }
+
+ dropvecs = calloc(cfg->xsk->batch_size, sizeof(struct xskvec));
+ if (!dropvecs) {
+ log_error("Failed to allocate dropvecs array");
+ free(xskvecs);
+ free(sendvecs);
+ return NULL;
+ }
- fds[0].fd = nf->thread[socket_id]->socket->fd;
+ fds[0].fd = xsk->fd;
fds[0].events = POLLIN;
for (;;) {
- if (cfg->xsk->mode & FLASH__POLL) {
- ret = flash__poll(nf->thread[socket_id]->socket, fds, nfds, cfg->xsk->poll_timeout);
- if (ret <= 0 || ret > 1)
- continue;
- }
-
- nrecv = flash__recvmsg(cfg, nf->thread[socket_id]->socket, &msg);
+ ret = flash__poll(cfg, xsk, fds, nfds);
+ if (!(ret == 1 || ret == -2))
+ continue;
- unsigned int tot_pkt_drop = 0;
- unsigned int tot_pkt_send = 0;
- struct xskvec *drop[nrecv];
- struct xskvec *send[nrecv];
+ nrecv = flash__recvmsg(cfg, xsk, xskvecs, cfg->xsk->batch_size);
+ wsend = 0;
+ wdrop = 0;
for (i = 0; i < nrecv; i++) {
- struct xskvec *xv = &msg.msg_iov[i];
+ struct xskvec *xv = &xskvecs[i];
void *data = xv->data;
uint32_t len = xv->len;
@@ -185,22 +218,22 @@ static void *socket_routine(void *arg)
struct in_addr tmp_ip;
struct ethhdr *eth = (struct ethhdr *)data;
if ((void *)(eth + 1) > data_end)
- drop[tot_pkt_drop++] = &msg.msg_iov[i];
+ dropvecs[wdrop++] = xskvecs[i];
if (ntohs(eth->h_proto) != ETH_P_IP)
- drop[tot_pkt_drop++] = &msg.msg_iov[i];
+ dropvecs[wdrop++] = xskvecs[i];
struct iphdr *ip = (struct iphdr *)(eth + 1);
if ((void *)(ip + 1) > data_end)
- drop[tot_pkt_drop++] = &msg.msg_iov[i];
+ dropvecs[wdrop++] = xskvecs[i];
struct icmphdr *icmp = (struct icmphdr *)(ip + 1);
if ((void *)(icmp + 1) > data_end)
- drop[tot_pkt_drop++] = &msg.msg_iov[i];
+ dropvecs[wdrop++] = xskvecs[i];
if (ntohs(eth->h_proto) != ETH_P_IP || len < (sizeof(*eth) + sizeof(*ip) + sizeof(*icmp)) ||
ip->protocol != IPPROTO_ICMP || icmp->type != ICMP_ECHO) {
- send[tot_pkt_send++] = &msg.msg_iov[i];
+ sendvecs[wsend++] = xskvecs[i];
if (app_conf.sriov) {
swap_mac_addresses(data);
@@ -224,9 +257,10 @@ static void *socket_routine(void *arg)
}
if (nrecv) {
- size_t ret_send = flash__sendmsg(cfg, nf->thread[socket_id]->socket, send, tot_pkt_send);
- size_t ret_drop = flash__dropmsg(cfg, nf->thread[socket_id]->socket, drop, tot_pkt_drop);
- if (ret_send != tot_pkt_send || ret_drop != tot_pkt_drop) {
+ nsend = flash__sendmsg(cfg, xsk, sendvecs, wsend);
+ ndrop = flash__dropmsg(cfg, xsk, dropvecs, wdrop);
+
+ if (nsend != wsend || ndrop != wdrop) {
log_error("errno: %d/\"%s\"\n", errno, strerror(errno));
exit(EXIT_FAILURE);
}
@@ -235,46 +269,39 @@ static void *socket_routine(void *arg)
if (done)
break;
}
- free(msg.msg_iov);
- return NULL;
-}
-
-static void *worker__stats(void *arg)
-{
- (void)arg;
-
- if (cfg->verbose) {
- unsigned int interval = cfg->stats_interval;
- setlocale(LC_ALL, "");
-
- for (int i = 0; i < cfg->total_sockets; i++)
- nf->thread[i]->socket->timestamp = flash__get_nsecs(cfg);
-
- while (!done) {
- sleep(interval);
- if (system("clear") != 0)
- log_error("Terminal clear error");
- for (int i = 0; i < cfg->total_sockets; i++) {
- flash__dump_stats(cfg, nf->thread[i]->socket);
- }
- }
- }
+ free(xskvecs);
+ free(sendvecs);
+ free(dropvecs);
return NULL;
}
int main(int argc, char **argv)
{
+ int shift;
+ struct sock_args *args;
+ struct stats_conf stats_cfg = { NULL };
cpu_set_t cpuset;
+ pthread_t socket_thread, stats_thread;
+
cfg = calloc(1, sizeof(struct config));
if (!cfg) {
log_error("ERROR: Memory allocation failed\n");
exit(EXIT_FAILURE);
}
- int n = flash__parse_cmdline_args(argc, argv, cfg);
- parse_app_args(argc, argv, &app_conf, n);
- flash__configure_nf(&nf, cfg);
- flash__populate_fill_ring(nf->thread, cfg->umem->frame_size, cfg->total_sockets, cfg->umem_offset, cfg->umem_scale);
+ cfg->app_name = "IP4 Ping Application";
+ cfg->app_options = ip4ping_options;
+ cfg->done = &done;
+
+ shift = flash__parse_cmdline_args(argc, argv, cfg);
+ if (shift < 0)
+ goto out_cfg;
+
+ if (parse_app_args(argc, argv, &app_conf, shift) < 0)
+ goto out_cfg;
+
+ if (flash__configure_nf(&nf, cfg) < 0)
+ goto out_cfg;
log_info("Control Plane Setup Done");
@@ -284,49 +311,61 @@ int main(int argc, char **argv)
log_info("STARTING Data Path");
+ args = calloc(cfg->total_sockets, sizeof(struct sock_args));
+ if (!args) {
+ log_error("ERROR: Memory allocation failed for sock_args");
+ goto out_cfg_close;
+ }
for (int i = 0; i < cfg->total_sockets; i++) {
- struct Args *args = calloc(1, sizeof(struct Args));
- args->socket_id = i;
- args->next = nf->next;
- args->next_size = nf->next_size;
+ args[i].socket_id = i;
- log_info("2_NEXT_SIZE: %d", args->next_size);
-
- for (int i = 0; i < args->next_size; i++) {
- log_info("2_NEXT_ITEM_%d %d", i, nf->next[i]);
- }
-
- pthread_t socket_thread;
- if (pthread_create(&socket_thread, NULL, socket_routine, args)) {
+ if (pthread_create(&socket_thread, NULL, socket_routine, &args[i])) {
log_error("Error creating socket thread");
- exit(EXIT_FAILURE);
+ goto out_args;
}
+
CPU_ZERO(&cpuset);
CPU_SET((i % (app_conf.cpu_end - app_conf.cpu_start + 1)) + app_conf.cpu_start, &cpuset);
if (pthread_setaffinity_np(socket_thread, sizeof(cpu_set_t), &cpuset) != 0) {
log_error("ERROR: Unable to set thread affinity: %s\n", strerror(errno));
- exit(EXIT_FAILURE);
+ goto out_args;
}
- pthread_detach(socket_thread);
+ if (pthread_detach(socket_thread) != 0) {
+ log_error("ERROR: Unable to detach thread: %s\n", strerror(errno));
+ goto out_args;
+ }
}
+ stats_cfg.nf = nf;
+ stats_cfg.cfg = cfg;
- pthread_t stats_thread;
- if (pthread_create(&stats_thread, NULL, worker__stats, NULL)) {
+ if (pthread_create(&stats_thread, NULL, flash__stats_thread, &stats_cfg)) {
log_error("Error creating statistics thread");
- exit(EXIT_FAILURE);
+ goto out_args;
}
CPU_ZERO(&cpuset);
CPU_SET(app_conf.stats_cpu, &cpuset);
if (pthread_setaffinity_np(stats_thread, sizeof(cpu_set_t), &cpuset) != 0) {
log_error("ERROR: Unable to set thread affinity: %s\n", strerror(errno));
- exit(EXIT_FAILURE);
+ goto out_args;
}
- pthread_detach(stats_thread);
-
- wait_for_cmd(cfg);
+ if (pthread_detach(stats_thread) != 0) {
+ log_error("ERROR: Unable to detach thread: %s\n", strerror(errno));
+ goto out_args;
+ }
+ flash__wait(cfg);
flash__xsk_close(cfg, nf);
- return EXIT_SUCCESS;
+ exit(EXIT_SUCCESS);
+
+out_args:
+ done = true;
+ free(args);
+out_cfg_close:
+ sleep(1);
+ flash__xsk_close(cfg, nf);
+out_cfg:
+ free(cfg);
+ exit(EXIT_FAILURE);
}
diff --git a/examples/l2fwd-rs/Cargo.toml b/examples/l2fwd-rs/Cargo.toml
index a670c9f..8412719 100644
--- a/examples/l2fwd-rs/Cargo.toml
+++ b/examples/l2fwd-rs/Cargo.toml
@@ -6,14 +6,15 @@ edition = "2024"
[dependencies]
clap = { version = "4.5.35", features = ["derive"] }
core_affinity = "0.8.3"
-ctrlc = "3.4.5"
+ctrlc = { version = "3.4.5", optional = true }
flash = { path = "../../lib/flash-rs", features = ["clap"] }
macaddr = "1.0.1"
tracing = { version = "0.1.41", optional = true }
tracing-subscriber = { version = "0.3.19", optional = true }
[features]
-default = []
+default = ["dep:ctrlc"]
+stats = ["flash/stats", "flash/tui"]
tracing = ["dep:tracing", "dep:tracing-subscriber", "flash/tracing"]
[lints.rust]
diff --git a/examples/l2fwd-rs/src/cli.rs b/examples/l2fwd-rs/src/cli.rs
index 97f0590..e5de218 100644
--- a/examples/l2fwd-rs/src/cli.rs
+++ b/examples/l2fwd-rs/src/cli.rs
@@ -1,7 +1,13 @@
+#[cfg(feature = "stats")]
+use std::str::FromStr as _;
+
use clap::Parser;
use flash::FlashConfig;
use macaddr::MacAddr6;
+#[cfg(feature = "stats")]
+use flash::tui::GridLayout;
+
#[derive(Debug, Parser)]
pub struct Cli {
#[command(flatten)]
@@ -23,6 +29,28 @@ pub struct Cli {
)]
pub cpu_end: usize,
+ #[cfg(feature = "stats")]
+ #[command(flatten)]
+ pub stats: StatsConfig,
+
#[arg(short = 'm', long, help = "Dest MAC address")]
pub mac_addr: Option,
}
+
+#[cfg(feature = "stats")]
+#[derive(Debug, Parser)]
+pub struct StatsConfig {
+ #[arg(
+ short = 's',
+ long = "stats-cpu",
+ default_value_t = 1,
+ help = "CPU core index for stats thread"
+ )]
+ pub cpu: usize,
+
+ #[arg(short = 'F', long, default_value_t = 1, help = "Tui frames per second")]
+ pub fps: u64,
+
+ #[arg(short = 'l', long, default_value_t = GridLayout::default(), value_parser = GridLayout::from_str, help = "Tui layout")]
+ pub layout: GridLayout,
+}
diff --git a/examples/l2fwd-rs/src/main.rs b/examples/l2fwd-rs/src/main.rs
index 742ad29..9a1a138 100644
--- a/examples/l2fwd-rs/src/main.rs
+++ b/examples/l2fwd-rs/src/main.rs
@@ -12,6 +12,9 @@ use clap::Parser;
use flash::Socket;
use macaddr::MacAddr6;
+#[cfg(feature = "stats")]
+use flash::tui::StatsDashboard;
+
use crate::cli::Cli;
#[forbid(clippy::indexing_slicing)]
@@ -39,12 +42,18 @@ fn socket_thread(mut socket: Socket, mac_addr: Option, run: &Arc t,
+ Err(err) => {
+ eprintln!("error creating tui: {err}");
+ return;
+ }
+ };
+
let cores = core_affinity::get_core_ids()
.unwrap_or_default()
.into_iter()
@@ -80,19 +102,32 @@ fn main() {
.collect::>();
if cores.is_empty() {
- eprintln!("No cores found in range {}-{}", cli.cpu_start, cli.cpu_end);
+ eprintln!("no cores found in range {}-{}", cli.cpu_start, cli.cpu_end);
return;
}
#[cfg(feature = "tracing")]
tracing::debug!("Cores: {:?}", cores);
+ #[cfg(feature = "stats")]
+ let Some(stats_core) = core_affinity::get_core_ids()
+ .unwrap_or_default()
+ .into_iter()
+ .find(|core_id| core_id.id == cli.stats.cpu)
+ else {
+ eprintln!("no core found for stats thread {}", cli.stats.cpu);
+ return;
+ };
+
let run = Arc::new(AtomicBool::new(true));
- let r = run.clone();
- if let Err(err) = ctrlc::set_handler(move || {
- r.store(false, Ordering::SeqCst);
- }) {
+ #[cfg(not(feature = "stats"))]
+ if let Err(err) = {
+ let r = run.clone();
+ ctrlc::set_handler(move || {
+ r.store(false, Ordering::SeqCst);
+ })
+ } {
eprintln!("error setting Ctrl-C handler: {err}");
return;
}
@@ -109,6 +144,21 @@ fn main() {
})
.collect::>();
+ #[cfg(feature = "stats")]
+ if let Err(err) = thread::spawn(move || {
+ core_affinity::set_for_current(stats_core);
+ if let Err(err) = tui.run() {
+ eprintln!("error dumping stats: {err}");
+ }
+ })
+ .join()
+ {
+ eprintln!("error in stats thread: {err:?}");
+ }
+
+ #[cfg(feature = "stats")]
+ run.store(false, Ordering::SeqCst);
+
for handle in handles {
if let Err(err) = handle.join() {
eprintln!("error in thread: {err:?}");
diff --git a/examples/l2fwd/main.c b/examples/l2fwd/main.c
index 4f517a2..eb8bdf5 100644
--- a/examples/l2fwd/main.c
+++ b/examples/l2fwd/main.c
@@ -2,21 +2,20 @@
* Copyright (c) 2025 Debojeet Das
*
* l2fwd: A simple NF that forwards packets between two interfaces
+ * after swapping or modifying MAC addresses.
*/
-
#include
#include
#include
-#include
#include
#include
#include
#include
-bool done = false;
+volatile bool done = false;
struct config *cfg = NULL;
-struct nf *nf;
+struct nf *nf = NULL;
static void int_exit(int sig)
{
@@ -29,38 +28,25 @@ struct appconf {
int cpu_end;
int stats_cpu;
bool sriov;
- uint8_t *dest_ether_addr_octet;
+ uint8_t dest_ether_addr_octet[6];
} app_conf;
-static int hex2int(char ch)
-{
- if (ch >= '0' && ch <= '9')
- return ch - '0';
- if (ch >= 'A' && ch <= 'F')
- return ch - 'A' + 10;
- if (ch >= 'a' && ch <= 'f')
- return ch - 'a' + 10;
- return -1;
-}
-
-static uint8_t *get_mac_addr(char *mac_addr)
-{
- uint8_t *dest_ether_addr_octet = (uint8_t *)malloc(6 * sizeof(uint8_t));
- for (int i = 0; i < 6; i++) {
- dest_ether_addr_octet[i] = hex2int(mac_addr[0]) * 16;
- mac_addr++;
- dest_ether_addr_octet[i] += hex2int(mac_addr[0]);
- mac_addr += 2;
- }
- return dest_ether_addr_octet;
-}
+// clang-format off
+static const char *l2fwd_options[] = {
+ "-c \tStart CPU (default: 0)",
+ "-e \tEnd CPU (default: 0)",
+ "-s \tStats CPU (default: 1)",
+ "-S \tEnable SR-IOV mode and set dest MAC address",
+ NULL
+};
+// clang-format on
-static void parse_app_args(int argc, char **argv, struct appconf *app_conf, int shift)
+static int parse_app_args(int argc, char **argv, struct appconf *app_conf, int shift)
{
int c;
+ int ethaddr[6];
opterr = 0;
- // Default values
app_conf->cpu_start = 0;
app_conf->cpu_end = 0;
app_conf->stats_cpu = 1;
@@ -69,8 +55,11 @@ static void parse_app_args(int argc, char **argv, struct appconf *app_conf, int
argc -= shift;
argv += shift;
- while ((c = getopt(argc, argv, "c:e:s:S:")) != -1)
+ while ((c = getopt(argc, argv, "hc:e:s:S:")) != -1)
switch (c) {
+ case 'h':
+ printf("Usage: %s -h\n", argv[-shift]);
+ return -1;
case 'c':
app_conf->cpu_start = atoi(optarg);
break;
@@ -81,12 +70,21 @@ static void parse_app_args(int argc, char **argv, struct appconf *app_conf, int
app_conf->stats_cpu = atoi(optarg);
break;
case 'S':
- app_conf->dest_ether_addr_octet = get_mac_addr(optarg);
+ if (sscanf(optarg, "%x:%x:%x:%x:%x:%x", ðaddr[0], ðaddr[1], ðaddr[2], ðaddr[3], ðaddr[4],
+ ðaddr[5]) != 6) {
+ log_error("Invalid MAC address format: %s", optarg);
+ return -1;
+ }
+ for (int i = 0; i < 6; i++)
+ app_conf->dest_ether_addr_octet[i] = (uint8_t)ethaddr[i];
app_conf->sriov = true;
break;
default:
- abort();
+ printf("Usage: %s -h\n", argv[-shift]);
+ return -1;
}
+
+ return 0;
}
static void update_dest_mac(void *data)
@@ -118,157 +116,158 @@ static void swap_mac_addresses(void *data)
*dst_addr = tmp;
}
-struct Args {
+struct sock_args {
int socket_id;
- int *next;
- int next_size;
};
static void *socket_routine(void *arg)
{
- struct Args *a = (struct Args *)arg;
- int socket_id = a->socket_id;
- log_info("SOCKET_ID: %d", socket_id);
- static __u32 nb_frags;
- int i, ret, nfds = 1, nrecv;
+ int ret;
+ nfds_t nfds = 1;
+ struct socket *xsk;
+ struct xskvec *xskvecs;
struct pollfd fds[1] = {};
- struct xskmsghdr msg = {};
+ uint32_t i, nrecv, nsend, nb_frags = 0;
+ struct sock_args *a = (struct sock_args *)arg;
- msg.msg_iov = calloc(cfg->xsk->batch_size, sizeof(struct xskvec));
+ log_debug("Socket ID: %d", a->socket_id);
+ xsk = nf->thread[a->socket_id]->socket;
- fds[0].fd = nf->thread[socket_id]->socket->fd;
- fds[0].events = POLLIN;
+ xskvecs = calloc(cfg->xsk->batch_size, sizeof(struct xskvec));
+ if (!xskvecs) {
+ log_error("Failed to allocate xskvecs array");
+ return NULL;
+ }
- nf->thread[socket_id]->socket->idle_fd.fd = nf->thread[socket_id]->socket->fd;
- nf->thread[socket_id]->socket->idle_fd.events = POLLIN;
+ fds[0].fd = xsk->fd;
+ fds[0].events = POLLIN;
for (;;) {
- if (cfg->xsk->mode & FLASH__POLL) {
- ret = flash__poll(nf->thread[socket_id]->socket, fds, nfds, cfg->xsk->poll_timeout);
- if (ret <= 0 || ret > 1)
- continue;
- }
+ ret = flash__poll(cfg, xsk, fds, nfds);
+ if (!(ret == 1 || ret == -2))
+ continue;
- nrecv = flash__recvmsg(cfg, nf->thread[socket_id]->socket, &msg);
- struct xskvec *send[nrecv];
- unsigned int tot_pkt_send = 0;
+ nrecv = flash__recvmsg(cfg, xsk, xskvecs, cfg->xsk->batch_size);
for (i = 0; i < nrecv; i++) {
- struct xskvec *xv = &msg.msg_iov[i];
- bool eop = IS_EOP_DESC(xv->options);
-
- char *pkt = xv->data;
+ char *pkt = xskvecs[i].data;
if (!nb_frags++)
app_conf.sriov ? update_dest_mac(pkt) : swap_mac_addresses(pkt);
- send[tot_pkt_send++] = &msg.msg_iov[i];
- if (eop)
+ if (IS_EOP_DESC(xskvecs[i].options))
nb_frags = 0;
}
if (nrecv) {
- ret = flash__sendmsg(cfg, nf->thread[socket_id]->socket, send, tot_pkt_send);
- if (ret != nrecv) {
- log_error("errno: %d/\"%s\"\n", errno, strerror(errno));
- exit(EXIT_FAILURE);
+ nsend = flash__sendmsg(cfg, xsk, xskvecs, nrecv);
+ if (nsend != nrecv) {
+ log_error("errno: %d/\"%s\"", errno, strerror(errno));
+ break;
}
}
if (done)
break;
}
- free(msg.msg_iov);
- return NULL;
-}
-
-static void *worker__stats(void *arg)
-{
- (void)arg;
-
- if (cfg->verbose) {
- unsigned int interval = cfg->stats_interval;
- setlocale(LC_ALL, "");
-
- for (int i = 0; i < cfg->total_sockets; i++)
- nf->thread[i]->socket->timestamp = flash__get_nsecs(cfg);
- while (!done) {
- sleep(interval);
- if (system("clear") != 0)
- log_error("Terminal clear error");
- for (int i = 0; i < cfg->total_sockets; i++) {
- flash__dump_stats(cfg, nf->thread[i]->socket);
- }
- }
- }
+ free(xskvecs);
return NULL;
}
int main(int argc, char **argv)
{
+ int shift;
+ struct sock_args *args;
+ struct stats_conf stats_cfg = { NULL };
cpu_set_t cpuset;
+ pthread_t socket_thread, stats_thread;
+
cfg = calloc(1, sizeof(struct config));
if (!cfg) {
- log_error("ERROR: Memory allocation failed\n");
+ log_error("ERROR: Memory allocation failed");
exit(EXIT_FAILURE);
}
- int n = flash__parse_cmdline_args(argc, argv, cfg);
- parse_app_args(argc, argv, &app_conf, n);
- flash__configure_nf(&nf, cfg);
- flash__populate_fill_ring(nf->thread, cfg->umem->frame_size, cfg->total_sockets, cfg->umem_offset, cfg->umem_scale);
+ cfg->app_name = "L2 Forwarding Application";
+ cfg->app_options = l2fwd_options;
+ cfg->done = &done;
+
+ shift = flash__parse_cmdline_args(argc, argv, cfg);
+ if (shift < 0)
+ goto out_cfg;
+
+ if (parse_app_args(argc, argv, &app_conf, shift) < 0)
+ goto out_cfg;
- log_info("Control Plane Setup Done");
+ if (flash__configure_nf(&nf, cfg) < 0)
+ goto out_cfg;
+
+ log_info("Control Plane setup done...");
signal(SIGINT, int_exit);
signal(SIGTERM, int_exit);
signal(SIGABRT, int_exit);
- log_info("STARTING Data Path");
-
- for (int i = 0; i < cfg->total_sockets; i++) {
- struct Args *args = calloc(1, sizeof(struct Args));
- args->socket_id = i;
- args->next = nf->next;
- args->next_size = nf->next_size;
+ log_info("Starting Data Path...");
- log_info("2_NEXT_SIZE: %d", args->next_size);
+ args = calloc(cfg->total_sockets, sizeof(struct sock_args));
+ if (!args) {
+ log_error("ERROR: Memory allocation failed for sock_args");
+ goto out_cfg_close;
+ }
- for (int i = 0; i < args->next_size; i++) {
- log_info("2_NEXT_ITEM_%d %d", i, nf->next[i]);
- }
+ for (int i = 0; i < cfg->total_sockets; i++) {
+ args[i].socket_id = i;
- pthread_t socket_thread;
- if (pthread_create(&socket_thread, NULL, socket_routine, args)) {
+ if (pthread_create(&socket_thread, NULL, socket_routine, &args[i])) {
log_error("Error creating socket thread");
- exit(EXIT_FAILURE);
+ goto out_args;
}
+
CPU_ZERO(&cpuset);
CPU_SET((i % (app_conf.cpu_end - app_conf.cpu_start + 1)) + app_conf.cpu_start, &cpuset);
if (pthread_setaffinity_np(socket_thread, sizeof(cpu_set_t), &cpuset) != 0) {
- log_error("ERROR: Unable to set thread affinity: %s\n", strerror(errno));
- exit(EXIT_FAILURE);
+ log_error("ERROR: Unable to set thread affinity: %s", strerror(errno));
+ goto out_args;
}
- pthread_detach(socket_thread);
+ if (pthread_detach(socket_thread) != 0) {
+ log_error("ERROR: Unable to detach thread: %s", strerror(errno));
+ goto out_args;
+ }
}
- pthread_t stats_thread;
- if (pthread_create(&stats_thread, NULL, worker__stats, NULL)) {
+ stats_cfg.nf = nf;
+ stats_cfg.cfg = cfg;
+
+ if (pthread_create(&stats_thread, NULL, flash__stats_thread, &stats_cfg)) {
log_error("Error creating statistics thread");
- exit(EXIT_FAILURE);
+ goto out_args;
}
CPU_ZERO(&cpuset);
CPU_SET(app_conf.stats_cpu, &cpuset);
if (pthread_setaffinity_np(stats_thread, sizeof(cpu_set_t), &cpuset) != 0) {
- log_error("ERROR: Unable to set thread affinity: %s\n", strerror(errno));
- exit(EXIT_FAILURE);
+ log_error("ERROR: Unable to set thread affinity: %s", strerror(errno));
+ goto out_args;
}
- pthread_detach(stats_thread);
- wait_for_cmd(cfg);
+ if (pthread_detach(stats_thread) != 0) {
+ log_error("ERROR: Unable to detach thread: %s", strerror(errno));
+ goto out_args;
+ }
+ flash__wait(cfg);
flash__xsk_close(cfg, nf);
- return EXIT_SUCCESS;
+ exit(EXIT_SUCCESS);
+
+out_args:
+ done = true;
+ free(args);
+out_cfg_close:
+ sleep(1);
+ flash__xsk_close(cfg, nf);
+out_cfg:
+ free(cfg);
+ exit(EXIT_FAILURE);
}
diff --git a/examples/maglev-rs/Cargo.toml b/examples/maglev-rs/Cargo.toml
index 22255fa..0934e0b 100644
--- a/examples/maglev-rs/Cargo.toml
+++ b/examples/maglev-rs/Cargo.toml
@@ -6,7 +6,7 @@ edition = "2024"
[dependencies]
clap = { version = "4.5.35", features = ["derive"] }
core_affinity = "0.8.3"
-ctrlc = "3.4.5"
+ctrlc = { version = "3.4.5", optional = true }
flash = { path = "../../lib/flash-rs", features = ["clap"] }
fnv = "1.0.7"
macaddr = "1.0.1"
@@ -15,7 +15,8 @@ tracing-subscriber = { version = "0.3.19", optional = true }
twox-hash = "2.1.0"
[features]
-default = []
+default = ["dep:ctrlc"]
+stats = ["flash/stats", "flash/tui"]
tracing = ["dep:tracing", "dep:tracing-subscriber", "flash/tracing"]
[lints.rust]
diff --git a/examples/maglev-rs/src/cli.rs b/examples/maglev-rs/src/cli.rs
index 236d699..5f77a23 100644
--- a/examples/maglev-rs/src/cli.rs
+++ b/examples/maglev-rs/src/cli.rs
@@ -1,7 +1,15 @@
+use std::net::Ipv4Addr;
+
+#[cfg(feature = "stats")]
+use std::str::FromStr as _;
+
use clap::Parser;
use flash::FlashConfig;
use macaddr::MacAddr6;
+#[cfg(feature = "stats")]
+use flash::tui::GridLayout;
+
#[derive(Debug, Parser)]
pub struct Cli {
#[command(flatten)]
@@ -23,6 +31,31 @@ pub struct Cli {
)]
pub cpu_end: usize,
+ #[cfg(feature = "stats")]
+ #[command(flatten)]
+ pub stats: StatsConfig,
+
+ #[arg(short = 'F', long, help = "Fallback IPv4 address")]
+ pub fallback_ip: Option,
+
#[arg(short = 'm', long, help = "Dest MAC address for next NFs")]
pub next_mac: Vec,
}
+
+#[cfg(feature = "stats")]
+#[derive(Debug, Parser)]
+pub struct StatsConfig {
+ #[arg(
+ short = 's',
+ long = "stats-cpu",
+ default_value_t = 1,
+ help = "CPU core index for stats thread"
+ )]
+ pub cpu: usize,
+
+ #[arg(short = 'F', long, default_value_t = 1, help = "Tui frames per second")]
+ pub fps: u64,
+
+ #[arg(short = 'l', long, default_value_t = GridLayout::default(), value_parser = GridLayout::from_str, help = "Tui layout")]
+ pub layout: GridLayout,
+}
diff --git a/examples/maglev-rs/src/main.rs b/examples/maglev-rs/src/main.rs
index 42a5707..1ae88f3 100644
--- a/examples/maglev-rs/src/main.rs
+++ b/examples/maglev-rs/src/main.rs
@@ -4,6 +4,7 @@ mod nf;
use std::{
hash::BuildHasher,
+ net::Ipv4Addr,
sync::{
Arc,
atomic::{AtomicBool, Ordering},
@@ -12,10 +13,13 @@ use std::{
};
use clap::Parser;
-use flash::{Route, Socket};
+use flash::Socket;
use fnv::FnvBuildHasher;
use macaddr::MacAddr6;
+#[cfg(feature = "stats")]
+use flash::tui::StatsDashboard;
+
use crate::{cli::Cli, maglev::Maglev};
const MAGLEV_TABLE_SIZE: usize = 65537;
@@ -23,7 +27,7 @@ const MAGLEV_TABLE_SIZE: usize = 65537;
fn socket_thread(
mut socket: Socket,
maglev: &Arc>,
- route: &Arc,
+ next_ip: &Arc>,
next_mac: &Arc>,
run: &Arc,
) {
@@ -37,16 +41,18 @@ fn socket_thread(
};
let mut descs_send = Vec::with_capacity(descs.len());
- let mut descs_drop = Vec::new();
+ let mut descs_drop = Vec::with_capacity(descs.len());
for mut desc in descs {
- if let Ok(pkt) = socket.read_exact(&desc) {
- if let Some(idx) = nf::load_balance(pkt, maglev, route, next_mac) {
- desc.set_next(idx);
- descs_send.push(desc);
- } else {
- descs_drop.push(desc);
+ if let Ok(pkt) = socket.read_exact(&desc)
+ && let Some(idx) = nf::load_balance(pkt, maglev, next_ip)
+ {
+ if let Some(next_mac) = next_mac.get(idx).or_else(|| next_mac.first()) {
+ pkt[0..6].copy_from_slice(next_mac.as_bytes());
}
+
+ desc.set_next(idx);
+ descs_send.push(desc);
} else {
descs_drop.push(desc);
}
@@ -57,6 +63,7 @@ fn socket_thread(
}
}
+#[allow(clippy::too_many_lines)]
fn main() {
#[cfg(feature = "tracing")]
tracing_subscriber::fmt::init();
@@ -76,25 +83,44 @@ fn main() {
return;
}
- if route.next.is_empty() {
- eprintln!("empty route received");
- return;
- }
+ #[cfg(feature = "tracing")]
+ tracing::debug!("Sockets: {:?}", sockets);
+
+ #[cfg(feature = "stats")]
+ let mut tui = match StatsDashboard::new(
+ sockets.iter().map(Socket::stats),
+ cli.stats.fps,
+ cli.stats.layout,
+ ) {
+ Ok(t) => t,
+ Err(err) => {
+ eprintln!("error creating tui: {err}");
+ return;
+ }
+ };
+
+ let next_ip = if route.next.is_empty() {
+ if let Some(fb_ip) = cli.fallback_ip {
+ vec![fb_ip]
+ } else {
+ eprintln!("empty route and no fallback IP configured");
+ return;
+ }
+ } else {
+ route.next
+ };
- if cli.next_mac.len() > 1 && cli.next_mac.len() != route.next.len() {
+ if cli.next_mac.len() > 1 && cli.next_mac.len() != next_ip.len() {
eprintln!(
"number of next NF MACs ({}) does not match number of next NFs ({})",
cli.next_mac.len(),
- route.next.len()
+ next_ip.len()
);
return;
}
- let maglev = Arc::new(Maglev::::new(
- &route.next,
- MAGLEV_TABLE_SIZE,
- ));
- let route = Arc::new(route);
+ let maglev = Arc::new(Maglev::::new(&next_ip, MAGLEV_TABLE_SIZE));
+ let next_ip = Arc::new(next_ip);
let next_mac = Arc::new(cli.next_mac);
let cores = core_affinity::get_core_ids()
@@ -111,12 +137,25 @@ fn main() {
#[cfg(feature = "tracing")]
tracing::debug!("Cores: {:?}", cores);
+ #[cfg(feature = "stats")]
+ let Some(stats_core) = core_affinity::get_core_ids()
+ .unwrap_or_default()
+ .into_iter()
+ .find(|core_id| core_id.id == cli.stats.cpu)
+ else {
+ eprintln!("no core found for stats thread {}", cli.stats.cpu);
+ return;
+ };
+
let run = Arc::new(AtomicBool::new(true));
- let r = run.clone();
- if let Err(err) = ctrlc::set_handler(move || {
- r.store(false, Ordering::SeqCst);
- }) {
+ #[cfg(not(feature = "stats"))]
+ if let Err(err) = {
+ let r = run.clone();
+ ctrlc::set_handler(move || {
+ r.store(false, Ordering::SeqCst);
+ })
+ } {
eprintln!("error setting Ctrl-C handler: {err}");
return;
}
@@ -127,16 +166,31 @@ fn main() {
.map(|(socket, core_id)| {
let r = run.clone();
let maglev = maglev.clone();
- let route = route.clone();
- let next_macs = next_mac.clone();
+ let next_ip = next_ip.clone();
+ let next_mac = next_mac.clone();
thread::spawn(move || {
core_affinity::set_for_current(core_id);
- socket_thread(socket, &maglev, &route, &next_macs, &r);
+ socket_thread(socket, &maglev, &next_ip, &next_mac, &r);
})
})
.collect::>();
+ #[cfg(feature = "stats")]
+ if let Err(err) = thread::spawn(move || {
+ core_affinity::set_for_current(stats_core);
+ if let Err(err) = tui.run() {
+ eprintln!("error dumping stats: {err}");
+ }
+ })
+ .join()
+ {
+ eprintln!("error in stats thread: {err:?}");
+ }
+
+ #[cfg(feature = "stats")]
+ run.store(false, Ordering::SeqCst);
+
for handle in handles {
if let Err(err) = handle.join() {
eprintln!("error in thread: {err:?}");
diff --git a/examples/maglev-rs/src/nf.rs b/examples/maglev-rs/src/nf.rs
index bc80e0b..caf8f03 100644
--- a/examples/maglev-rs/src/nf.rs
+++ b/examples/maglev-rs/src/nf.rs
@@ -2,9 +2,6 @@
use std::{hash::BuildHasher, net::Ipv4Addr};
-use flash::Route;
-use macaddr::MacAddr6;
-
use crate::maglev::Maglev;
const ETHER_TYPE_IPV4: u16 = 0x0800;
@@ -42,8 +39,7 @@ impl Tuple5 {
pub fn load_balance(
pkt: &mut [u8; 54],
maglev: &Maglev,
- route: &Route,
- next_mac: &[MacAddr6],
+ next_ip: &[Ipv4Addr],
) -> Option {
if u16::from_be_bytes([pkt[12], pkt[13]]) != ETHER_TYPE_IPV4 {
return None;
@@ -55,7 +51,7 @@ pub fn load_balance(
// }
let idx = maglev.lookup(&tuple5);
- let next_ip = route.next.get(idx)?.octets();
+ let next_ip = next_ip.get(idx)?.octets();
let mut csum = u32::from(!u16::from_be_bytes([pkt[24], pkt[25]]));
csum = csum.wrapping_add(u32::from(u16::from_be_bytes([next_ip[0], next_ip[1]])));
@@ -71,13 +67,5 @@ pub fn load_balance(
pkt[24..26].copy_from_slice(&(!(csum as u16)).to_be_bytes());
pkt[30..34].copy_from_slice(&next_ip);
- if let Some(next_mac) = next_mac.get(idx).or_else(|| next_mac.first()) {
- let mut tmp = [0; 6];
- tmp.copy_from_slice(&pkt[0..6]);
-
- pkt[6..12].copy_from_slice(&tmp);
- pkt[0..6].copy_from_slice(next_mac.as_bytes());
- }
-
Some(idx)
}
diff --git a/examples/maglev/main.c b/examples/maglev/main.c
index 7777af1..1a482f4 100644
--- a/examples/maglev/main.c
+++ b/examples/maglev/main.c
@@ -19,7 +19,7 @@
#define PROTO_STRLEN 4
-bool done = false;
+volatile bool done = false;
struct config *cfg = NULL;
struct nf *nf;
@@ -41,15 +41,24 @@ struct appconf {
int cpu_start;
int cpu_end;
int stats_cpu;
+ int srv_port;
+ int bkd_port;
+ uint8_t mac_addr[6];
} app_conf;
-struct Args {
- int socket_id;
- int *next;
- int next_size;
+// clang-format off
+static const char *maglev_options[] = {
+ "-c \tStart CPU (default: 0)",
+ "-e \tEnd CPU (default: 0)",
+ "-s \tStats CPU (default: 1)",
+ "-S \tSet MAC address (default: 11:22:33:44:55:66)",
+ "-p \tService port (default: 80)",
+ "-P \tBackend port (default: 80)",
+ NULL
};
+// clang-format on
-static void parse_app_args(int argc, char **argv, struct appconf *app_conf, int shift)
+static int parse_app_args(int argc, char **argv, struct appconf *app_conf, int shift)
{
int c;
opterr = 0;
@@ -58,12 +67,27 @@ static void parse_app_args(int argc, char **argv, struct appconf *app_conf, int
app_conf->cpu_start = 0;
app_conf->cpu_end = 0;
app_conf->stats_cpu = 1;
+ app_conf->srv_port = 80;
+ app_conf->bkd_port = 80;
+
+ int ethaddr[6];
+ ethaddr[0] = 0x11;
+ ethaddr[1] = 0x22;
+ ethaddr[2] = 0x33;
+ ethaddr[3] = 0x44;
+ ethaddr[4] = 0x55;
+ ethaddr[5] = 0x66;
+ for (int i = 0; i < 6; i++)
+ app_conf->mac_addr[i] = (uint8_t)ethaddr[i];
argc -= shift;
argv += shift;
- while ((c = getopt(argc, argv, "c:e:s:")) != -1)
+ while ((c = getopt(argc, argv, "hc:e:s:S:p:P:")) != -1)
switch (c) {
+ case 'h':
+ printf("Usage: %s -h\n", argv[-shift]);
+ return -1;
case 'c':
app_conf->cpu_start = atoi(optarg);
break;
@@ -73,32 +97,26 @@ static void parse_app_args(int argc, char **argv, struct appconf *app_conf, int
case 's':
app_conf->stats_cpu = atoi(optarg);
break;
- default:
- abort();
- }
-}
-
-static void *worker__stats(void *arg)
-{
- (void)arg;
-
- if (cfg->verbose) {
- unsigned int interval = cfg->stats_interval;
- setlocale(LC_ALL, "");
-
- for (int i = 0; i < cfg->total_sockets; i++)
- nf->thread[i]->socket->timestamp = flash__get_nsecs(cfg);
-
- while (!done) {
- sleep(interval);
- if (system("clear") != 0)
- log_error("Terminal clear error");
- for (int i = 0; i < cfg->total_sockets; i++) {
- flash__dump_stats(cfg, nf->thread[i]->socket);
+ case 'S':
+ if (sscanf(optarg, "%x:%x:%x:%x:%x:%x", ðaddr[0], ðaddr[1], ðaddr[2], ðaddr[3], ðaddr[4],
+ ðaddr[5]) != 6) {
+ log_error("Invalid MAC address format: %s", optarg);
+ return -1;
}
+ for (int i = 0; i < 6; i++)
+ app_conf->mac_addr[i] = (uint8_t)ethaddr[i];
+ break;
+ case 'p':
+ app_conf->srv_port = atoi(optarg);
+ break;
+ case 'P':
+ app_conf->bkd_port = atoi(optarg);
+ break;
+ default:
+ printf("Usage: %s -h\n", argv[-shift]);
+ return -1;
}
- }
- return NULL;
+ return 0;
}
static void configure(struct maglev *mag, int num_bkds)
@@ -183,23 +201,49 @@ struct backend_entry {
struct backend_info value;
};
-static void load_services(void)
+static int load_services(void)
{
- send_cmd(cfg->uds_sockfd, FLASH__GET_IP_ADDR);
- recv_data(cfg->uds_sockfd, srv_addr, INET_ADDRSTRLEN);
+ int ret;
+ ret = flash__send_cmd(cfg->uds_sockfd, FLASH__GET_IP_ADDR);
+ if (ret < 0) {
+ log_error("Failed to send command to get NF IP address");
+ return -1;
+ }
+ ret = flash__recv_data(cfg->uds_sockfd, srv_addr, INET_ADDRSTRLEN);
+ if (ret < 0) {
+ log_error("Failed to receive NF IP address");
+ return -1;
+ }
log_info("NF IP: %s", srv_addr);
- send_cmd(cfg->uds_sockfd, FLASH__GET_DST_IP_ADDR);
- recv_data(cfg->uds_sockfd, &nbackends, sizeof(int));
+ ret = flash__send_cmd(cfg->uds_sockfd, FLASH__GET_DST_IP_ADDR);
+ if (ret < 0) {
+ log_error("Failed to send command to get Backend IP addresses");
+ return -1;
+ }
+ ret = flash__recv_data(cfg->uds_sockfd, &nbackends, sizeof(int));
+ if (ret < 0) {
+ log_error("Failed to receive number of backends");
+ return -1;
+ }
log_info("Number of Backends: %d", nbackends);
+ if (nbackends <= 0 || nbackends > MAX_BACKENDS) {
+ log_error("Invalid number of backends: %d", nbackends);
+ return -1;
+ }
for (int i = 0; i < nbackends; i++) {
- recv_data(cfg->uds_sockfd, bkd_addr[i], INET_ADDRSTRLEN);
+ log_info("Receiving Backend %d IP address", i);
+ ret = flash__recv_data(cfg->uds_sockfd, bkd_addr[i], INET_ADDRSTRLEN);
+ if (ret < 0) {
+ log_error("Failed to receive Backend IP address %d", i);
+ return -1;
+ }
log_info("Backend %d IP: %s", i, bkd_addr[i]);
}
char proto[PROTO_STRLEN];
unsigned srv_port, bkd_port;
- uint8_t mac_addr[6];
+ uint8_t *mac_addr;
struct service_info *srv_info;
struct backend_entry *bkd_entry;
struct in_addr addr;
@@ -209,25 +253,40 @@ static void load_services(void)
struct backend_entry *backend_entries;
struct hashmap srv_to_index;
- hashmap_init(&services, sizeof(struct service_id), sizeof(struct service_info), MAX_SERVICES);
- hashmap_init(&backends, sizeof(struct backend_id), sizeof(struct backend_info), MAX_BACKENDS);
- hashmap_init(&maglev_tables, sizeof(struct service_id), sizeof(struct maglev), MAX_SERVICES);
+ if (hashmap_init(&services, sizeof(struct service_id), sizeof(struct service_info), MAX_SERVICES) != 1) {
+ log_error("ERROR: unable to initialize services hashmap");
+ return -1;
+ }
+ if (hashmap_init(&backends, sizeof(struct backend_id), sizeof(struct backend_info), MAX_BACKENDS) != 1) {
+ log_error("ERROR: unable to initialize backends hashmap");
+ goto out_1;
+ }
+ if (hashmap_init(&maglev_tables, sizeof(struct service_id), sizeof(struct maglev), MAX_SERVICES) != 1) {
+ log_error("ERROR: unable to initialize maglev tables hashmap");
+ goto out_2;
+ }
service_entries = malloc(sizeof(struct service_entry) * nservices);
+ if (!service_entries) {
+ log_error("ERROR: unable to allocate memory for service entries");
+ goto out_3;
+ }
backend_entries = malloc(sizeof(struct backend_entry) * nbackends);
- hashmap_init(&srv_to_index, sizeof(struct service_id), sizeof(int), nservices);
-
- mac_addr[0] = 0x11;
- mac_addr[1] = 0x22;
- mac_addr[2] = 0x33;
- mac_addr[3] = 0x44;
- mac_addr[4] = 0x55;
- mac_addr[5] = 0x66;
+ if (!backend_entries) {
+ log_error("ERROR: unable to allocate memory for backend entries");
+ goto out_4;
+ }
+ if (hashmap_init(&srv_to_index, sizeof(struct service_id), sizeof(int), nservices) != 1) {
+ log_error("ERROR: unable to initialize service to index hashmap");
+ goto out_5;
+ }
+
+ mac_addr = app_conf.mac_addr;
// Manually add services and backends
// Service 1: UDP from 192.168.1.1:80 to backend 192.168.1.2:8080
// strcpy(srv_addr, "192.168.1.1"); Stored from main fn itself
- srv_port = 80;
- bkd_port = 8080;
+ srv_port = app_conf.srv_port;
+ bkd_port = app_conf.bkd_port;
strcpy(proto, "UDP");
for (int index = 0; index < nbackends; index++) {
bkd_entry = &backend_entries[index];
@@ -239,7 +298,7 @@ static void load_services(void)
inet_aton(bkd_addr[index], &addr);
bkd_entry->value.addr = addr.s_addr;
bkd_entry->value.port = htons(bkd_port);
- __builtin_memcpy(&bkd_entry->value.mac_addr, mac_addr, sizeof(mac_addr));
+ __builtin_memcpy(&bkd_entry->value.mac_addr, mac_addr, sizeof(app_conf.mac_addr));
srvindex = hashmap_lookup_elem(&srv_to_index, &bkd_entry->key.service);
if (!srvindex) {
@@ -249,8 +308,8 @@ static void load_services(void)
srv_info = &srv_entry->value;
if (hashmap_insert_elem(&srv_to_index, &srv_entry->key, &service_first_free) != 1) {
- fprintf(stderr, "ERROR: unable to add service index to hash map\n");
- exit(EXIT_FAILURE);
+ log_error("ERROR: unable to add service to service to index hashmap");
+ goto out_6;
}
service_first_free++;
@@ -263,17 +322,18 @@ static void load_services(void)
}
for (int i = 0; i < nservices; i++) {
- // printf("%u, %u\n", service_entries[i].key.vaddr, (__u32)(service_entries[i].key.vport));
+ log_info("Adding service %u:%u proto %u with %u backends", ntohl(service_entries[i].key.vaddr),
+ ntohs(service_entries[i].key.vport), service_entries[i].key.proto, service_entries[i].value.backends);
if (hashmap_insert_elem(&services, &service_entries[i].key, &service_entries[i].value) != 1) {
- fprintf(stderr, "ERROR: unable to add service to hash map\n");
- exit(EXIT_FAILURE);
+ log_error("ERROR: unable to add service to hashmap");
+ goto out_6;
}
}
for (int i = 0; i < nbackends; i++) {
if (hashmap_insert_elem(&backends, &backend_entries[i].key, &backend_entries[i].value) != 1) {
- fprintf(stderr, "ERROR: unable to add backend to hash map\n");
- exit(EXIT_FAILURE);
+ log_error("ERROR: unable to add backend to hashmap\n");
+ goto out_6;
}
}
@@ -283,69 +343,115 @@ static void load_services(void)
uint32_t num_bkds = service_entries[i].value.backends;
configure(lookup, num_bkds);
if (hashmap_insert_elem(&maglev_tables, &service_entries[i].key, lookup) != 1) {
- fprintf(stderr, "ERROR: unable to add maglev table to hash map\n");
- exit(EXIT_FAILURE);
+ log_error("ERROR: unable to add maglev table to hashmap\n");
+ goto out_6;
}
}
- printf("Added %u services and %u backends\n", nservices, nbackends);
+ log_info("Added %d services and %d backends", nservices, nbackends);
free(service_entries);
free(backend_entries);
hashmap_free(&srv_to_index);
- return;
+ return 0;
+
+out_6:
+ hashmap_free(&srv_to_index);
+out_5:
+ free(backend_entries);
+out_4:
+ free(service_entries);
+out_3:
+ hashmap_free(&maglev_tables);
+out_2:
+ hashmap_free(&backends);
+out_1:
+ hashmap_free(&services);
+ return -1;
}
+struct sock_args {
+ int socket_id;
+ int next_size;
+};
+
static void *socket_routine(void *arg)
{
- struct Args *a = (struct Args *)arg;
- int socket_id = a->socket_id;
- int i, ret, nfds = 1, nrecv;
+ int ret;
+ nfds_t nfds = 1;
+ struct socket *xsk;
+ struct xskvec *xskvecs, *dropvecs, *sendvecs;
struct pollfd fds[1] = {};
- struct xskmsghdr msg = {};
+ uint32_t i, nrecv, nsend, ndrop, wsend, wdrop;
+ struct sock_args *a = (struct sock_args *)arg;
- msg.msg_iov = calloc(cfg->xsk->batch_size, sizeof(struct xskvec));
+ log_debug("Socket ID: %d", a->socket_id);
+ xsk = nf->thread[a->socket_id]->socket;
+
+ xskvecs = calloc(cfg->xsk->batch_size, sizeof(struct xskvec));
+ if (!xskvecs) {
+ log_error("Failed to allocate xskvecs array");
+ return NULL;
+ }
+ dropvecs = calloc(cfg->xsk->batch_size, sizeof(struct xskvec));
+ if (!dropvecs) {
+ log_error("Failed to allocate dropvecs array");
+ free(xskvecs);
+ return NULL;
+ }
+ sendvecs = calloc(cfg->xsk->batch_size, sizeof(struct xskvec));
+ if (!sendvecs) {
+ log_error("Failed to allocate sendvecs array");
+ free(xskvecs);
+ free(dropvecs);
+ return NULL;
+ }
- fds[0].fd = nf->thread[socket_id]->socket->fd;
+ fds[0].fd = xsk->fd;
fds[0].events = POLLIN;
struct hashmap active_sessions;
- hashmap_init(&active_sessions, sizeof(struct session_id), sizeof(struct replace_info), MAX_SESSIONS);
+ ret = hashmap_init(&active_sessions, sizeof(struct session_id), sizeof(struct replace_info), MAX_SESSIONS);
+ if (ret != 1) {
+ log_error("ERROR: unable to initialize active sessions hashmap");
+ free(xskvecs);
+ free(dropvecs);
+ free(sendvecs);
+ return NULL;
+ }
for (;;) {
- if (cfg->xsk->mode & FLASH__POLL) {
- ret = flash__poll(nf->thread[socket_id]->socket, fds, nfds, cfg->xsk->poll_timeout);
- if (ret <= 0 || ret > 1)
- continue;
- }
- nrecv = flash__recvmsg(cfg, nf->thread[socket_id]->socket, &msg);
+ ret = flash__poll(cfg, xsk, fds, nfds);
+ if (!(ret == 1 || ret == -2))
+ continue;
+ nrecv = flash__recvmsg(cfg, xsk, xskvecs, cfg->xsk->batch_size);
- struct xskvec *drop[nrecv];
- unsigned int tot_pkt_drop = 0;
- struct xskvec *send[nrecv];
- unsigned int tot_pkt_send = 0;
+ wdrop = 0;
+ wsend = 0;
for (i = 0; i < nrecv; i++) {
- struct xskvec *xv = &msg.msg_iov[i];
+ struct xskvec *xv = &xskvecs[i];
+
void *pkt = xv->data;
void *pkt_end = pkt + xv->len;
+
struct ethhdr *eth = pkt;
if ((void *)(eth + 1) > pkt_end) {
- drop[tot_pkt_drop++] = &msg.msg_iov[i];
- log_info("INVALID PACKET Dropping packet: %d", tot_pkt_drop);
+ dropvecs[wdrop++] = xskvecs[i];
+ log_error("ERROR: invalid Ethernet frame");
continue;
}
if (eth->h_proto != htons(ETH_P_IP)) {
- drop[tot_pkt_drop++] = &msg.msg_iov[i];
- log_info("INVALID ETH PROTO Dropping packet: %d", tot_pkt_drop);
+ dropvecs[wdrop++] = xskvecs[i];
+ log_error("ERROR: not an IP packet");
continue;
}
struct iphdr *iph = (void *)(eth + 1);
if ((void *)(iph + 1) > pkt_end) {
- drop[tot_pkt_drop++] = &msg.msg_iov[i];
- log_info("INVALID IPHDR Dropping packet: %d", tot_pkt_drop);
+ dropvecs[wdrop++] = xskvecs[i];
+ log_error("ERROR: invalid IP header");
continue;
}
@@ -357,8 +463,8 @@ static void *socket_routine(void *arg)
case IPPROTO_TCP:;
struct tcphdr *tcph = next;
if ((void *)(tcph + 1) > pkt_end) {
- drop[tot_pkt_drop++] = &msg.msg_iov[i];
- log_info("INVALID TCPHDR Dropping packet: %d", tot_pkt_drop);
+ log_error("ERROR: invalid TCP header");
+ dropvecs[wdrop++] = xskvecs[i];
continue;
}
@@ -371,8 +477,8 @@ static void *socket_routine(void *arg)
case IPPROTO_UDP:;
struct udphdr *udph = next;
if ((void *)(udph + 1) > pkt_end) {
- drop[tot_pkt_drop++] = &msg.msg_iov[i];
- log_info("INVALID UDPHDR Dropping packet: %d", tot_pkt_drop);
+ dropvecs[wdrop++] = xskvecs[i];
+ log_error("ERROR: invalid UDP header");
continue;
}
@@ -383,8 +489,8 @@ static void *socket_routine(void *arg)
break;
default:
- drop[tot_pkt_drop++] = &msg.msg_iov[i];
- log_info("DEFAULT Dropping packet: %d", tot_pkt_drop);
+ dropvecs[wdrop++] = xskvecs[i];
+ log_error("ERROR: not a TCP/UDP packet");
continue;
}
@@ -407,11 +513,11 @@ static void *socket_routine(void *arg)
/* New session, apply load balancing logic */
struct service_id srvid = { .vaddr = iph->daddr, .vport = *dport, .proto = iph->protocol };
- printf("%u, %u, %u\n", srvid.vaddr, (__u32)(srvid.vport), (__u32)(srvid.proto));
struct service_info *srvinfo = hashmap_lookup_elem(&services, &srvid);
if (!srvinfo) {
- drop[tot_pkt_drop++] = &msg.msg_iov[i];
- log_info("ERROR: missing service --> DROPPING\n");
+ dropvecs[wdrop++] = xskvecs[i];
+ log_error("ERROR: service not found for %u:%u proto %u --> DROPPING", ntohl(srvid.vaddr),
+ ntohs(srvid.vport), srvid.proto);
continue;
}
@@ -422,8 +528,9 @@ static void *socket_routine(void *arg)
};
struct backend_info *bkdinfo = hashmap_lookup_elem(&backends, &bkdid);
if (!bkdinfo) {
- drop[tot_pkt_drop++] = &msg.msg_iov[i];
- log_info("ERROR: missing backend --> DROPPING\n");
+ dropvecs[wdrop++] = xskvecs[i];
+ log_error("ERROR: backend not found for service %u:%u proto %u and index %u --> DROPPING",
+ ntohl(srvid.vaddr), ntohs(srvid.vport), srvid.proto, bkdid.index);
continue;
}
@@ -436,7 +543,7 @@ static void *socket_routine(void *arg)
__builtin_memcpy(fwd_rep.mac_addr, &bkdinfo->mac_addr, sizeof(fwd_rep.mac_addr));
rep = &fwd_rep;
if (hashmap_insert_elem(&active_sessions, &sid, &fwd_rep) != 1) {
- fprintf(stderr, "ERROR: unable to add forward session to map\n");
+ log_error("ERROR: unable to add forward session to map\n");
goto insert;
}
@@ -451,7 +558,7 @@ static void *socket_routine(void *arg)
sid.saddr = bkdinfo->addr;
sid.sport = bkdinfo->port;
if (hashmap_insert_elem(&active_sessions, &sid, &bwd_rep) != 1) {
- fprintf(stderr, "ERROR: unable to add backward session to map\n");
+ log_error("ERROR: unable to add backward session to map\n");
goto insert;
}
@@ -487,44 +594,66 @@ static void *socket_routine(void *arg)
*l4check = csum_fold(csum);
xv->options = (rep->bkdindex << 16) | (xv->options & 0xFFFF);
- send[tot_pkt_send++] = &msg.msg_iov[i];
+ sendvecs[wsend++] = xskvecs[i];
}
if (nrecv) {
- size_t ret_send = flash__sendmsg(cfg, nf->thread[socket_id]->socket, send, tot_pkt_send);
- size_t ret_drop = flash__dropmsg(cfg, nf->thread[socket_id]->socket, drop, tot_pkt_drop);
-
- if (ret_send != tot_pkt_send || ret_drop != tot_pkt_drop) {
+ ndrop = flash__dropmsg(cfg, xsk, dropvecs, wdrop);
+ nsend = flash__sendmsg(cfg, xsk, sendvecs, wsend);
+ if (ndrop != wdrop || nsend != wsend) {
log_error("errno: %d/\"%s\"\n", errno, strerror(errno));
- exit(EXIT_FAILURE);
+ break;
}
}
if (done)
break;
}
- free(msg.msg_iov);
+ free(xskvecs);
+ free(dropvecs);
+ free(sendvecs);
hashmap_free(&active_sessions);
return NULL;
}
int main(int argc, char **argv)
{
+ int shift;
+ struct sock_args *args;
+ struct stats_conf stats_cfg = { NULL };
cpu_set_t cpuset;
+ pthread_t socket_thread, stats_thread;
+
cfg = calloc(1, sizeof(struct config));
if (!cfg) {
log_error("ERROR: Memory allocation failed\n");
exit(EXIT_FAILURE);
}
- int n = flash__parse_cmdline_args(argc, argv, cfg);
- parse_app_args(argc, argv, &app_conf, n);
- flash__configure_nf(&nf, cfg);
- flash__populate_fill_ring(nf->thread, cfg->umem->frame_size, cfg->total_sockets, cfg->umem_offset, cfg->umem_scale);
+ cfg->app_name = "maglev";
+ cfg->app_options = maglev_options;
+ cfg->done = &done;
+
+ shift = flash__parse_cmdline_args(argc, argv, cfg);
+ if (shift < 0) {
+ log_error("ERROR: Failed to parse command line arguments");
+ goto out_cfg;
+ }
+ if (parse_app_args(argc, argv, &app_conf, shift) < 0) {
+ log_error("ERROR: Failed to parse application arguments");
+ goto out_cfg;
+ }
+ if (flash__configure_nf(&nf, cfg) < 0) {
+ log_error("ERROR: Failed to configure NF");
+ goto out_cfg;
+ }
log_info("Control Plane Setup Done");
- load_services();
+ if (load_services() < 0) {
+ log_error("ERROR: Failed to load services");
+ goto out_cfg_close;
+ }
signal(SIGINT, int_exit);
signal(SIGTERM, int_exit);
@@ -532,50 +661,65 @@ int main(int argc, char **argv)
log_info("STARTING Data Path");
+ args = calloc(cfg->total_sockets, sizeof(struct sock_args));
+ if (!args) {
+ log_error("ERROR: Memory allocation failed for socket args");
+ goto out_cfg_close;
+ }
for (int i = 0; i < cfg->total_sockets; i++) {
- struct Args *args = calloc(1, sizeof(struct Args));
- args->socket_id = i;
- args->next = nf->next;
- args->next_size = nf->next_size;
-
- log_info("2_NEXT_SIZE: %d", args->next_size);
+ args[i].socket_id = i;
+ args[i].next_size = nf->next_size;
- for (int i = 0; i < args->next_size; i++) {
- log_info("2_NEXT_ITEM_%d %d", i, nf->next[i]);
- }
+ log_info("2_NEXT_SIZE: %d", args[i].next_size);
- pthread_t socket_thread;
- if (pthread_create(&socket_thread, NULL, socket_routine, args)) {
+ if (pthread_create(&socket_thread, NULL, socket_routine, &args[i])) {
log_error("Error creating socket thread");
- exit(EXIT_FAILURE);
+ goto out_args;
}
CPU_ZERO(&cpuset);
CPU_SET((i % (app_conf.cpu_end - app_conf.cpu_start + 1)) + app_conf.cpu_start, &cpuset);
if (pthread_setaffinity_np(socket_thread, sizeof(cpu_set_t), &cpuset) != 0) {
log_error("ERROR: Unable to set thread affinity: %s\n", strerror(errno));
- exit(EXIT_FAILURE);
+ goto out_args;
}
- pthread_detach(socket_thread);
+ if (pthread_detach(socket_thread) != 0) {
+ log_error("ERROR: Unable to detach thread: %s\n", strerror(errno));
+ goto out_args;
+ }
}
- pthread_t stats_thread;
- if (pthread_create(&stats_thread, NULL, worker__stats, NULL)) {
+ stats_cfg.nf = nf;
+ stats_cfg.cfg = cfg;
+
+ if (pthread_create(&stats_thread, NULL, flash__stats_thread, &stats_cfg)) {
log_error("Error creating statistics thread");
- exit(EXIT_FAILURE);
+ goto out_args;
}
CPU_ZERO(&cpuset);
CPU_SET(app_conf.stats_cpu, &cpuset);
if (pthread_setaffinity_np(stats_thread, sizeof(cpu_set_t), &cpuset) != 0) {
log_error("ERROR: Unable to set thread affinity: %s\n", strerror(errno));
- exit(EXIT_FAILURE);
+ goto out_args;
}
- pthread_detach(stats_thread);
-
- wait_for_cmd(cfg);
+ if (pthread_detach(stats_thread) != 0) {
+ log_error("ERROR: Unable to detach thread: %s\n", strerror(errno));
+ goto out_args;
+ }
+ flash__wait(cfg);
flash__xsk_close(cfg, nf);
- return EXIT_SUCCESS;
+ exit(EXIT_SUCCESS);
+
+out_args:
+ done = true;
+ free(args);
+out_cfg_close:
+ sleep(1);
+ flash__xsk_close(cfg, nf);
+out_cfg:
+ free(cfg);
+ exit(EXIT_FAILURE);
}
\ No newline at end of file
diff --git a/examples/meson.build b/examples/meson.build
index dc7278d..39055a2 100644
--- a/examples/meson.build
+++ b/examples/meson.build
@@ -1,6 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
# Copyright (c) 2025 Debojeet Das
+# C examples
dirs = [
'unit-tests',
'helloworld',
@@ -12,6 +13,7 @@ dirs = [
'firewall',
'arpresolver',
'mica',
+ 'txgen'
]
def_deps = [include, log, nf, params, uds]
@@ -26,4 +28,37 @@ foreach d : dirs
subdir(d)
install_headers(headers, subdir: meson.project_name().to_lower())
-endforeach
\ No newline at end of file
+endforeach
+
+if get_option('enable_rust') and cargo.found()
+ message('>>> Configuring Rust examples')
+
+ cargo_build_args = ['build', '--target-dir', rust_target_dir]
+
+ if get_option('buildtype') == 'release'
+ cargo_build_args += ['--release']
+ message('Rust: Building in release mode')
+ else
+ message('Rust: Building in debug mode')
+ endif
+
+ if get_option('buildtype') == 'debug' or get_option('buildtype') == 'debugoptimized'
+ cargo_build_args += ['-F', 'tracing']
+ endif
+
+ if get_option('enable_rust_stats')
+ cargo_build_args += ['-F', 'stats']
+ message('Rust: Enabling stats dashboard feature')
+ endif
+
+ rust_build = custom_target(
+ 'rust_workspace',
+ output: 'rust_build_complete',
+ command: [cargo] + cargo_build_args,
+ console: true,
+ build_by_default: true,
+ build_always_stale: true,
+ )
+
+ message('<<< Rust examples configured')
+endif
\ No newline at end of file
diff --git a/examples/mica/main.c b/examples/mica/main.c
index 4e8a71e..a44a660 100644
--- a/examples/mica/main.c
+++ b/examples/mica/main.c
@@ -5,42 +5,34 @@
#include
#include
#include
-#include
#include
#include
#include
#include
#include
-#include
#include
#include
#include
#include
-#include
#include "./ported-mica/hash.h"
#include "./ported-mica/mehcached.h"
-#define IP_STRLEN 16
-#define PROTO_STRLEN 4
-#define IFNAME_STRLEN 256
-#define MAX_VALID_SESSIONS 100
-
////// MICA PART ///////
#define NUM_KEYS 2000
#define VALUE_SIZE 256
size_t default_keys[NUM_KEYS];
-int keys_index = 0;
+int keys_index = NUM_KEYS - 1;
char default_value[VALUE_SIZE];
struct mehcached_table table_o;
struct mehcached_table *table;
///// MICA END ///////
-bool done = false;
+volatile bool done = false;
struct config *cfg = NULL;
struct nf *nf;
@@ -54,16 +46,21 @@ struct appconf {
int cpu_start;
int cpu_end;
int stats_cpu;
- int flag;
+ int num_get_ops;
bool sriov;
uint8_t *dest_ether_addr_octet;
} app_conf;
-struct Args {
- int socket_id;
- int *next;
- int next_size;
+// clang-format off
+static const char *mica_options[] = {
+ "-c \tStart CPU (default: 0)",
+ "-e \tEnd CPU (default: 0)",
+ "-s \tStats CPU (default: 1)",
+ "-o \tFraction of MICA GET operations (default: 0.5)",
+ "-S \tEnable SR-IOV mode and set dest MAC address",
+ NULL
};
+// clang-format on
static int hex2int(char ch)
{
@@ -117,7 +114,7 @@ static void swap_mac_addresses(void *data)
*dst_addr = tmp;
}
-static void *configure(void)
+static int configure(void)
{
const size_t page_size = 1048576 * 2;
const size_t num_numa_nodes = 1;
@@ -129,14 +126,17 @@ static void *configure(void)
table = &table_o;
size_t numa_nodes[] = { (size_t)-1 };
- // mehcached_table_init(table, 1, 1, 256, false, false, false, numa_nodes[0], numa_nodes, MEHCACHED_MTH_THRESHOLD_FIFO);
mehcached_table_init(table, (NUM_KEYS + MEHCACHED_ITEMS_PER_BUCKET - 1) / MEHCACHED_ITEMS_PER_BUCKET, 1,
NUM_KEYS * /*MEHCACHED_ROUNDUP64*/ (alloc_overhead + 8 + 8), false, false, false, numa_nodes[0],
numa_nodes, MEHCACHED_MTH_THRESHOLD_FIFO);
- assert(table);
- memset(default_value, 'A', 255);
- default_value[255] = '\0';
+ if (!table) {
+ log_error("Failed to initialize MICA table");
+ return -1;
+ }
+
+ memset(default_value, 'A', VALUE_SIZE - 1);
+ default_value[VALUE_SIZE - 1] = '\0';
for (size_t i = 0; i < NUM_KEYS; i++) {
size_t key = i;
@@ -144,14 +144,16 @@ static void *configure(void)
uint64_t key_hash = hash((const uint8_t *)&key, sizeof(key));
if (!mehcached_set(0, table, key_hash, (const uint8_t *)&key, sizeof(key), (const uint8_t *)&default_value,
- sizeof(default_value), 0, false))
- assert(false);
+ sizeof(default_value), 0, false)) {
+ log_error("Failed to set key %zu in MICA table", key);
+ return -1;
+ }
}
- return NULL;
+ return 0;
}
-static void parse_app_args(int argc, char **argv, struct appconf *app_conf, int shift)
+static int parse_app_args(int argc, char **argv, struct appconf *app_conf, int shift)
{
int c;
opterr = 0;
@@ -160,12 +162,16 @@ static void parse_app_args(int argc, char **argv, struct appconf *app_conf, int
app_conf->cpu_start = 0;
app_conf->cpu_end = 0;
app_conf->stats_cpu = 1;
+ app_conf->num_get_ops = 0.5 * NUM_KEYS;
argc -= shift;
argv += shift;
while ((c = getopt(argc, argv, "c:e:s:o:S:")) != -1)
switch (c) {
+ case 'h':
+ printf("Usage: %s -h\n", argv[-shift]);
+ return -1;
case 'c':
app_conf->cpu_start = atoi(optarg);
break;
@@ -176,38 +182,17 @@ static void parse_app_args(int argc, char **argv, struct appconf *app_conf, int
app_conf->stats_cpu = atoi(optarg);
break;
case 'o':
- app_conf->flag = atoi(optarg);
+ app_conf->num_get_ops = atof(optarg) * NUM_KEYS;
break;
case 'S':
app_conf->dest_ether_addr_octet = get_mac_addr(optarg);
app_conf->sriov = true;
break;
default:
- abort();
- }
-}
-
-static void *worker__stats(void *arg)
-{
- (void)arg;
-
- if (cfg->verbose) {
- unsigned int interval = cfg->stats_interval;
- setlocale(LC_ALL, "");
-
- for (int i = 0; i < cfg->total_sockets; i++)
- nf->thread[i]->socket->timestamp = flash__get_nsecs(cfg);
-
- while (!done) {
- sleep(interval);
- if (system("clear") != 0)
- log_error("Terminal clear error");
- for (int i = 0; i < cfg->total_sockets; i++) {
- flash__dump_stats(cfg, nf->thread[i]->socket);
- }
+ printf("Usage: %s -h\n", argv[-shift]);
+ return -1;
}
- }
- return NULL;
+ return 0;
}
static uint16_t iph_checksum(void *vdata, size_t length)
@@ -278,93 +263,161 @@ static uint16_t udph_checksum(struct udphdr *udph, struct iphdr *iph, uint8_t *p
return ~sum;
}
+struct sock_args {
+ int socket_id;
+};
+
static void *socket_routine(void *arg)
{
- struct Args *a = (struct Args *)arg;
- int socket_id = a->socket_id;
- // free(arg);
- log_info("SOCKET_ID: %d", socket_id);
- // static __u32 nb_frags;
- int i, ret, nfds = 1, nrecv;
+ nfds_t nfds = 1;
+ int ret;
+ struct socket *xsk;
+ struct xskvec *xskvecs, *sendvecs, *dropvecs;
struct pollfd fds[1] = {};
- struct xskmsghdr msg = {};
+ uint32_t i, nrecv, nsend, wdrop, wsend, ndrop;
+ struct sock_args *a = (struct sock_args *)arg;
- msg.msg_iov = calloc(cfg->xsk->batch_size, sizeof(struct xskvec));
+ xsk = nf->thread[a->socket_id]->socket;
- fds[0].fd = nf->thread[socket_id]->socket->fd;
+ xskvecs = calloc(cfg->xsk->batch_size, sizeof(struct xskvec));
+ if (!xskvecs) {
+ log_error("Failed to allocate xskvecs array");
+ return NULL;
+ }
+ dropvecs = calloc(cfg->xsk->batch_size, sizeof(struct xskvec));
+ if (!dropvecs) {
+ log_error("Failed to allocate dropvecs array");
+ free(xskvecs);
+ return NULL;
+ }
+ sendvecs = calloc(cfg->xsk->batch_size, sizeof(struct xskvec));
+ if (!sendvecs) {
+ log_error("Failed to allocate sendvecs array");
+ free(xskvecs);
+ free(dropvecs);
+ return NULL;
+ }
+
+ fds[0].fd = xsk->fd;
fds[0].events = POLLIN;
for (;;) {
- if (cfg->xsk->mode & FLASH__POLL) {
- ret = flash__poll(nf->thread[socket_id]->socket, fds, nfds, cfg->xsk->poll_timeout);
- if (ret <= 0 || ret > 1)
- continue;
- }
- nrecv = flash__recvmsg(cfg, nf->thread[socket_id]->socket, &msg);
+ ret = flash__poll(cfg, xsk, fds, nfds);
+ if (!(ret == 1 || ret == -2))
+ continue;
+
+ nrecv = flash__recvmsg(cfg, xsk, xskvecs, cfg->xsk->batch_size);
- struct xskvec *drop[nrecv];
- unsigned int tot_pkt_drop = 0;
- struct xskvec *send[nrecv];
- unsigned int tot_pkt_send = 0;
+ wsend = 0;
+ wdrop = 0;
for (i = 0; i < nrecv; i++) {
- struct xskvec *xv = &msg.msg_iov[i];
+ struct xskvec *xv = &xskvecs[i];
void *pkt = xv->data;
void *pkt_end = pkt + xv->len;
struct in_addr tmp_ip;
struct ethhdr *eth = pkt;
+
if ((void *)(eth + 1) > pkt_end) {
- drop[tot_pkt_drop++] = &msg.msg_iov[i];
+ dropvecs[wdrop++] = xskvecs[i];
+ log_error("Dropping packet: incomplete Ethernet header");
continue;
}
if (eth->h_proto != htons(ETH_P_IP)) {
- drop[tot_pkt_drop++] = &msg.msg_iov[i];
+ log_error("Dropping packet: not an IP packet");
+ dropvecs[wdrop++] = xskvecs[i];
continue;
}
struct iphdr *iph = (void *)(eth + 1);
if ((void *)(iph + 1) > pkt_end) {
- drop[tot_pkt_drop++] = &msg.msg_iov[i];
+ log_error("Dropping packet: incomplete IP header");
+ dropvecs[wdrop++] = xskvecs[i];
+ continue;
+ }
+
+ size_t hdrsize = iph->ihl * 4;
+ /* Sanity check packet field is valid */
+ if (hdrsize < sizeof(*iph)) {
+ log_error("Dropping packet: invalid IP header length");
+ dropvecs[wdrop++] = xskvecs[i];
+ continue;
+ }
+
+ if (iph->protocol != IPPROTO_UDP) {
+ log_error("Dropping packet: not a UDP packet");
+ dropvecs[wdrop++] = xskvecs[i];
+ continue;
+ }
+
+ /* Variable-length IPv4 header, need to use byte-based arithmetic */
+ if ((void *)iph + hdrsize > pkt_end) {
+ log_error("Dropping packet: incomplete IP header with options");
+ dropvecs[wdrop++] = xskvecs[i];
continue;
}
void *next = (void *)iph + (iph->ihl << 2);
+
// Assuming only UDP packets are coming
struct udphdr *udph = next;
+ if ((void *)(udph + 1) > pkt_end) {
+ log_error("Dropping packet: incomplete UDP header");
+ dropvecs[wdrop++] = xskvecs[i];
+ continue;
+ }
+
unsigned char *payload = (unsigned char *)(udph + 1);
int udp_length = ntohs(udph->len);
int payload_len = udp_length - sizeof(struct udphdr);
+ const size_t key_size = sizeof(size_t);
+
+ // if ((size_t)payload_len < key_size + VALUE_SIZE) {
+ // log_error("Dropping packet: payload too small for key+value");
+ // dropvecs[wdrop++] = xskvecs[i];
+ // continue;
+ // }
+
+ // if ((void *)payload + key_size + VALUE_SIZE > pkt_end) {
+ // log_error("Dropping packet: cannot read full key+value from payload");
+ // dropvecs[wdrop++] = xskvecs[i];
+ // continue;
+ // }
+
size_t key;
- char value[256];
+ char value[VALUE_SIZE];
- // get key
memcpy(&key, payload, sizeof(size_t));
- // Hardcoding so that half the packets are get, other half are store
- key = default_keys[keys_index];
+
+ // use the key from the default set, ignoring the one in the packet
keys_index = (keys_index + 1) % NUM_KEYS;
+ key = default_keys[keys_index];
// GET
- if (app_conf.flag == 0) {
+ if (keys_index < app_conf.num_get_ops) {
uint64_t key_hash = hash((const uint8_t *)&key, sizeof(key));
size_t value_length = sizeof(value);
- if (mehcached_get(0, table, key_hash, (const uint8_t *)&key, sizeof(key), (uint8_t *)&value,
- &value_length, NULL, false))
- assert(value_length == sizeof(value));
+ if (!mehcached_get(0, table, key_hash, (const uint8_t *)&key, sizeof(key), (uint8_t *)&value,
+ &value_length, NULL, false)) {
+ log_error("Failed to get key %zu from MICA table", key);
+ dropvecs[wdrop++] = xskvecs[i];
+ continue;
+ }
- // send value
- // memcpy(payload + sizeof(size_t), &value, 256);
- // // re-configuring the pkt to send
- // memcpy(tmp_mac, eth->h_dest, ETH_ALEN);
- // memcpy(eth->h_dest, eth->h_source, ETH_ALEN);
- // memcpy(eth->h_source, tmp_mac, ETH_ALEN);
-
- if (app_conf.sriov) {
- swap_mac_addresses(pkt);
- update_dest_mac(pkt);
+ if (value_length != sizeof(value)) {
+ log_error("Value length mismatch for key %zu: expected %zu, got %zu", key, sizeof(value),
+ value_length);
+ dropvecs[wdrop++] = xskvecs[i];
+ continue;
}
+ // send value
+ memcpy(payload + sizeof(size_t), &value, VALUE_SIZE);
+
+ app_conf.sriov ? update_dest_mac(pkt) : swap_mac_addresses(pkt);
+
memcpy(&tmp_ip, &iph->saddr, sizeof(tmp_ip));
memcpy(&iph->saddr, &iph->daddr, sizeof(tmp_ip));
memcpy(&iph->daddr, &tmp_ip, sizeof(tmp_ip));
@@ -380,101 +433,144 @@ static void *socket_routine(void *arg)
// Recalculate UDP checksum
udph->check = 0; // Must set to 0 before computing checksum
udph->check = udph_checksum(udph, iph, payload, payload_len);
- send[tot_pkt_send++] = &msg.msg_iov[i];
+ sendvecs[wsend++] = xskvecs[i];
}
// STORE
else {
- // memcpy(value, payload + sizeof(size_t), 256);
- memset(value, 'A', 255);
- value[255] = '\0';
+ memcpy(value, payload + key_size, VALUE_SIZE);
+ memset(value, 'A', VALUE_SIZE - 1);
+ value[VALUE_SIZE - 1] = '\0';
uint64_t key_hash = hash((const uint8_t *)&key, sizeof(key));
if (!mehcached_set(0, table, key_hash, (const uint8_t *)&key, sizeof(key), (const uint8_t *)&value,
- sizeof(value), 0, true))
- assert(false);
+ sizeof(value), 0, true)) {
+ log_error("Failed to set key %zu in MICA table", key);
+ dropvecs[wdrop++] = xskvecs[i];
+ continue;
+ }
- // send acknowledgement
- // memcpy(payload + sizeof(size_t), &value, 256);
- drop[tot_pkt_drop++] = &msg.msg_iov[i];
+ dropvecs[wdrop++] = xskvecs[i];
}
}
if (nrecv) {
- size_t ret_send = flash__sendmsg(cfg, nf->thread[socket_id]->socket, send, tot_pkt_send);
- size_t ret_drop = flash__dropmsg(cfg, nf->thread[socket_id]->socket, drop, tot_pkt_drop);
- if (ret_send != tot_pkt_send || ret_drop != tot_pkt_drop) {
+ nsend = flash__sendmsg(cfg, xsk, sendvecs, wsend);
+ ndrop = flash__dropmsg(cfg, xsk, dropvecs, wdrop);
+ if (ndrop != wdrop || nsend != wsend) {
log_error("errno: %d/\"%s\"\n", errno, strerror(errno));
- exit(EXIT_FAILURE);
+ break;
}
}
if (done)
break;
}
- free(msg.msg_iov);
+ free(xskvecs);
+ free(dropvecs);
+ free(sendvecs);
return NULL;
}
int main(int argc, char **argv)
{
+ int shift;
+ struct sock_args *args;
+ struct stats_conf stats_cfg = { NULL };
cpu_set_t cpuset;
+ pthread_t socket_thread, stats_thread;
+
cfg = calloc(1, sizeof(struct config));
if (!cfg) {
log_error("ERROR: Memory allocation failed\n");
exit(EXIT_FAILURE);
}
- int n = flash__parse_cmdline_args(argc, argv, cfg);
- parse_app_args(argc, argv, &app_conf, n);
- flash__configure_nf(&nf, cfg);
- flash__populate_fill_ring(nf->thread, cfg->umem->frame_size, cfg->total_sockets, cfg->umem_offset, cfg->umem_scale);
+ cfg->app_name = "MICA Application";
+ cfg->app_options = mica_options;
+ cfg->done = &done;
+
+ shift = flash__parse_cmdline_args(argc, argv, cfg);
+ if (shift < 0) {
+ log_error("ERROR: Failed to parse command line arguments");
+ goto out_cfg;
+ }
+ if (parse_app_args(argc, argv, &app_conf, shift) < 0) {
+ log_error("ERROR: Failed to parse application arguments");
+ goto out_cfg;
+ }
+ if (flash__configure_nf(&nf, cfg) < 0) {
+ log_error("ERROR: Failed to configure NF");
+ goto out_cfg;
+ }
log_info("Control Plane Setup Done");
- configure();
+ if (configure() < 0) {
+ log_error("ERROR: Failed to configure MICA");
+ goto out_cfg;
+ }
+
signal(SIGINT, int_exit);
signal(SIGTERM, int_exit);
signal(SIGABRT, int_exit);
log_info("STARTING Data Path");
+ args = calloc(cfg->total_sockets, sizeof(struct sock_args));
+ if (!args) {
+ log_error("ERROR: Memory allocation failed for sock_args");
+ goto out_cfg_close;
+ }
+
for (int i = 0; i < cfg->total_sockets; i++) {
- struct Args *args = calloc(1, sizeof(struct Args));
- args->socket_id = i;
- args->next = nf->next;
- args->next_size = nf->next_size;
+ args[i].socket_id = i;
- pthread_t socket_thread;
- if (pthread_create(&socket_thread, NULL, socket_routine, args)) {
+ if (pthread_create(&socket_thread, NULL, socket_routine, &args[i])) {
log_error("Error creating socket thread");
- exit(EXIT_FAILURE);
+ goto out_args;
}
CPU_ZERO(&cpuset);
CPU_SET((i % (app_conf.cpu_end - app_conf.cpu_start + 1)) + app_conf.cpu_start, &cpuset);
if (pthread_setaffinity_np(socket_thread, sizeof(cpu_set_t), &cpuset) != 0) {
log_error("ERROR: Unable to set thread affinity: %s\n", strerror(errno));
- exit(EXIT_FAILURE);
+ goto out_args;
}
- pthread_detach(socket_thread);
+ if (pthread_detach(socket_thread) != 0) {
+ log_error("ERROR: Unable to detach thread: %s\n", strerror(errno));
+ goto out_args;
+ }
}
+ stats_cfg.nf = nf;
+ stats_cfg.cfg = cfg;
- pthread_t stats_thread;
- if (pthread_create(&stats_thread, NULL, worker__stats, NULL)) {
+ if (pthread_create(&stats_thread, NULL, flash__stats_thread, &stats_cfg)) {
log_error("Error creating statistics thread");
- exit(EXIT_FAILURE);
+ goto out_args;
}
CPU_ZERO(&cpuset);
CPU_SET(app_conf.stats_cpu, &cpuset);
if (pthread_setaffinity_np(stats_thread, sizeof(cpu_set_t), &cpuset) != 0) {
log_error("ERROR: Unable to set thread affinity: %s\n", strerror(errno));
- exit(EXIT_FAILURE);
+ goto out_args;
+ }
+ if (pthread_detach(stats_thread) != 0) {
+ log_error("ERROR: Unable to detach stats thread: %s\n", strerror(errno));
+ goto out_args;
}
- pthread_detach(stats_thread);
-
- wait_for_cmd(cfg);
+ flash__wait(cfg);
flash__xsk_close(cfg, nf);
- return EXIT_SUCCESS;
+ exit(EXIT_SUCCESS);
+
+out_args:
+ done = true;
+ free(args);
+out_cfg_close:
+ sleep(1);
+ flash__xsk_close(cfg, nf);
+out_cfg:
+ free(cfg);
+ exit(EXIT_FAILURE);
}
\ No newline at end of file
diff --git a/examples/mica/ported-mica/table.h b/examples/mica/ported-mica/table.h
index dcb49c6..a54a48f 100644
--- a/examples/mica/ported-mica/table.h
+++ b/examples/mica/ported-mica/table.h
@@ -19,14 +19,12 @@
#include "alloc_malloc.h"
#include "alloc_dynamic.h"
#include "shm.h"
-#include
MEHCACHED_BEGIN
#define MEHCACHED_MAX_KEY_LENGTH (255)
#define MEHCACHED_MAX_VALUE_LENGTH (1048575)
-
#ifndef MEHCACHED_NO_EVICTION
// #define MEHCACHED_ITEMS_PER_BUCKET (7)
#define MEHCACHED_ITEMS_PER_BUCKET (15)
@@ -43,318 +41,259 @@ MEHCACHED_BEGIN
#define MEHCACHED_SINGLE_ALLOC
#ifdef MEHCACHED_COLLECT_STATS
-#define MEHCACHED_STAT_INC(table, name) do { __sync_add_and_fetch(&(table)->stats.name, 1); } while (0)
-#define MEHCACHED_STAT_DEC(table, name) do { __sync_sub_and_fetch(&(table)->stats.name, 1); } while (0)
+#define MEHCACHED_STAT_INC(table, name) \
+ do { \
+ __sync_add_and_fetch(&(table)->stats.name, 1); \
+ } while (0)
+#define MEHCACHED_STAT_DEC(table, name) \
+ do { \
+ __sync_sub_and_fetch(&(table)->stats.name, 1); \
+ } while (0)
#else
-#define MEHCACHED_STAT_INC(table, name) do { (void)table; } while (0)
-#define MEHCACHED_STAT_DEC(table, name) do { (void)table; } while (0)
+#define MEHCACHED_STAT_INC(table, name) \
+ do { \
+ (void)table; \
+ } while (0)
+#define MEHCACHED_STAT_DEC(table, name) \
+ do { \
+ (void)table; \
+ } while (0)
#endif
-typedef enum _MEHCACHED_RESULT
-{
- MEHCACHED_OK = 0,
- MEHCACHED_ERROR,
- MEHCACHED_FULL,
- MEHCACHED_EXIST,
- MEHCACHED_NOT_FOUND,
- MEHCACHED_PARTIAL_VALUE,
- MEHCACHED_NOT_PROCESSED,
+typedef enum _MEHCACHED_RESULT {
+ MEHCACHED_OK = 0,
+ MEHCACHED_ERROR,
+ MEHCACHED_FULL,
+ MEHCACHED_EXIST,
+ MEHCACHED_NOT_FOUND,
+ MEHCACHED_PARTIAL_VALUE,
+ MEHCACHED_NOT_PROCESSED,
} MEHCACHED_RESULT;
-struct mehcached_bucket
-{
- uint32_t version; // XXX: is uint32_t wide enough?
- uint32_t next_extra_bucket_index; // 1-base; 0 = no extra bucket
- uint64_t item_vec[MEHCACHED_ITEMS_PER_BUCKET];
+struct mehcached_bucket {
+ uint32_t version; // XXX: is uint32_t wide enough?
+ uint32_t next_extra_bucket_index; // 1-base; 0 = no extra bucket
+ uint64_t item_vec[MEHCACHED_ITEMS_PER_BUCKET];
- // 16: tag (1-base)
- // 8: alloc id
- // 40: item offset
- // item == 0: empty item
+ // 16: tag (1-base)
+ // 8: alloc id
+ // 40: item offset
+ // item == 0: empty item
- #define MEHCACHED_TAG_MASK (((uint64_t)1 << 16) - 1)
- #define MEHCACHED_TAG(item_vec) ((item_vec) >> 48)
+#define MEHCACHED_TAG_MASK (((uint64_t)1 << 16) - 1)
+#define MEHCACHED_TAG(item_vec) ((item_vec) >> 48)
#ifndef MEHCACHED_SINGLE_ALLOC
- #define MEHCACHED_ALLOC_ID_MASK (((uint64_t)1 << 8) - 1)
- #define MEHCACHED_ALLOC_ID(item_vec) (((item_vec) >> 40) & MEHCACHED_ALLOC_ID_MASK)
+#define MEHCACHED_ALLOC_ID_MASK (((uint64_t)1 << 8) - 1)
+#define MEHCACHED_ALLOC_ID(item_vec) (((item_vec) >> 40) & MEHCACHED_ALLOC_ID_MASK)
#else
- #define MEHCACHED_ALLOC_ID(item_vec) (0LU)
+#define MEHCACHED_ALLOC_ID(item_vec) (0LU)
#endif
-
- #define MEHCACHED_ITEM_OFFSET(item_vec) ((item_vec) & MEHCACHED_ITEM_OFFSET_MASK)
+#define MEHCACHED_ITEM_OFFSET(item_vec) ((item_vec) & MEHCACHED_ITEM_OFFSET_MASK)
#ifndef MEHCACHED_SINGLE_ALLOC
- #define MEHCACHED_ITEM_VEC(tag, alloc_id, item_offset) (((uint64_t)(tag) << 48) | ((uint64_t)(alloc_id) << 40) | (uint64_t)(item_offset))
+#define MEHCACHED_ITEM_VEC(tag, alloc_id, item_offset) \
+ (((uint64_t)(tag) << 48) | ((uint64_t)(alloc_id) << 40) | (uint64_t)(item_offset))
#else
- #define MEHCACHED_ITEM_VEC(tag, alloc_id, item_offset) (((uint64_t)(tag) << 48) | (uint64_t)(item_offset))
+#define MEHCACHED_ITEM_VEC(tag, alloc_id, item_offset) (((uint64_t)(tag) << 48) | (uint64_t)(item_offset))
#endif
};
-struct mehcached_item
-{
- struct mehcached_alloc_item alloc_item;
+struct mehcached_item {
+ struct mehcached_alloc_item alloc_item;
- uint32_t kv_length_vec; // key_length: 8, value_length: 24; kv_length_vec == 0: empty item
+ uint32_t kv_length_vec; // key_length: 8, value_length: 24; kv_length_vec == 0: empty item
- #define MEHCACHED_KEY_MASK (((uint32_t)1 << 8) - 1)
- #define MEHCACHED_KEY_LENGTH(kv_length_vec) ((kv_length_vec) >> 24)
+#define MEHCACHED_KEY_MASK (((uint32_t)1 << 8) - 1)
+#define MEHCACHED_KEY_LENGTH(kv_length_vec) ((kv_length_vec) >> 24)
- #define MEHCACHED_VALUE_MASK (((uint32_t)1 << 24) - 1)
- #define MEHCACHED_VALUE_LENGTH(kv_length_vec) ((kv_length_vec) & MEHCACHED_VALUE_MASK)
+#define MEHCACHED_VALUE_MASK (((uint32_t)1 << 24) - 1)
+#define MEHCACHED_VALUE_LENGTH(kv_length_vec) ((kv_length_vec) & MEHCACHED_VALUE_MASK)
- #define MEHCACHED_KV_LENGTH_VEC(key_length, value_length) (((uint32_t)(key_length) << 24) | (uint32_t)(value_length))
+#define MEHCACHED_KV_LENGTH_VEC(key_length, value_length) (((uint32_t)(key_length) << 24) | (uint32_t)(value_length))
- // the rest is meaningful only when kv_length_vec != 0
- uint32_t expire_time;
- uint64_t key_hash;
- uint8_t data[0];
+ // the rest is meaningful only when kv_length_vec != 0
+ uint32_t expire_time;
+ uint64_t key_hash;
+ uint8_t data[0];
};
#define MEHCACHED_MAX_POOLS (16)
-struct mehcached_table
-{
+struct mehcached_table {
#ifdef MEHCACHED_ALLOC_POOL
- struct mehcached_pool alloc[MEHCACHED_MAX_POOLS];
- uint8_t alloc_id_mask;
- uint64_t mth_threshold;
+ struct mehcached_pool alloc[MEHCACHED_MAX_POOLS];
+ uint8_t alloc_id_mask;
+ uint64_t mth_threshold;
#endif
#ifdef MEHCACHED_ALLOC_MALLOC
- struct mehcached_malloc alloc;
+ struct mehcached_malloc alloc;
#endif
#ifdef MEHCACHED_ALLOC_DYNAMIC
- struct mehcached_dynamic alloc;
+ struct mehcached_dynamic alloc;
#endif
- struct mehcached_bucket *buckets;
- struct mehcached_bucket *extra_buckets; // = (buckets + num_buckets); extra_buckets[0] is not used because index 0 indicates "no more extra bucket"
+ struct mehcached_bucket *buckets;
+ struct mehcached_bucket *
+ extra_buckets; // = (buckets + num_buckets); extra_buckets[0] is not used because index 0 indicates "no more extra bucket"
- uint8_t concurrent_access_mode;
+ uint8_t concurrent_access_mode;
- uint32_t num_buckets;
- uint32_t num_buckets_mask;
- uint32_t num_extra_buckets;
+ uint32_t num_buckets;
+ uint32_t num_buckets_mask;
+ uint32_t num_extra_buckets;
- struct
- {
- uint32_t lock;
- uint32_t head; // 1-base; 0 = no extra bucket
- } extra_bucket_free_list MEHCACHED_ALIGNED(64);
+ struct {
+ uint32_t lock;
+ uint32_t head; // 1-base; 0 = no extra bucket
+ } extra_bucket_free_list MEHCACHED_ALIGNED(64);
- uint8_t rshift;
+ uint8_t rshift;
#ifdef MEHCACHED_COLLECT_STATS
- struct
- {
- size_t count;
- size_t set_nooverwrite;
- size_t set_new;
- size_t set_inplace;
- size_t set_evicted;
- size_t get_found;
- size_t get_notfound;
- size_t test_found;
- size_t test_notfound;
- size_t delete_found;
- size_t delete_notfound;
- size_t cleanup;
- size_t move_to_head_performed;
- size_t move_to_head_skipped;
- size_t move_to_head_failed;
- } stats;
+ struct {
+ size_t count;
+ size_t set_nooverwrite;
+ size_t set_new;
+ size_t set_inplace;
+ size_t set_evicted;
+ size_t get_found;
+ size_t get_notfound;
+ size_t test_found;
+ size_t test_notfound;
+ size_t delete_found;
+ size_t delete_notfound;
+ size_t cleanup;
+ size_t move_to_head_performed;
+ size_t move_to_head_skipped;
+ size_t move_to_head_failed;
+ } stats;
#endif
} MEHCACHED_ALIGNED(64);
-struct mehcached_prefetch_state
-{
- struct mehcached_table *table;
- struct mehcached_bucket *bucket;
- uint64_t key_hash;
+struct mehcached_prefetch_state {
+ struct mehcached_table *table;
+ struct mehcached_bucket *bucket;
+ uint64_t key_hash;
};
-typedef enum _MEHCACHED_OPERATION
-{
- MEHCACHED_NOOP_READ = 0,
- MEHCACHED_NOOP_WRITE,
- MEHCACHED_ADD,
- MEHCACHED_SET,
- MEHCACHED_GET,
- MEHCACHED_TEST,
- MEHCACHED_DELETE,
- MEHCACHED_INCREMENT,
+typedef enum _MEHCACHED_OPERATION {
+ MEHCACHED_NOOP_READ = 0,
+ MEHCACHED_NOOP_WRITE,
+ MEHCACHED_ADD,
+ MEHCACHED_SET,
+ MEHCACHED_GET,
+ MEHCACHED_TEST,
+ MEHCACHED_DELETE,
+ MEHCACHED_INCREMENT,
} MEHCACHED_OPERATION;
-struct mehcached_request
-{
- // 0
- uint8_t operation; // of enum MEHCACHED_OPERATION type
- uint8_t result; // of enum MEHCACHED_RESULT type
- // 2
- uint16_t reserved0;
- // 4
- uint32_t kv_length_vec;
- // 8
- uint64_t key_hash;
- // 16
- uint32_t expire_time;
- // 20
- uint32_t reserved1;
- // 24
+struct mehcached_request {
+ // 0
+ uint8_t operation; // of enum MEHCACHED_OPERATION type
+ uint8_t result; // of enum MEHCACHED_RESULT type
+ // 2
+ uint16_t reserved0;
+ // 4
+ uint32_t kv_length_vec;
+ // 8
+ uint64_t key_hash;
+ // 16
+ uint32_t expire_time;
+ // 20
+ uint32_t reserved1;
+ // 24
};
+void mehcached_print_bucket(const struct mehcached_bucket *bucket);
-void
-mehcached_print_bucket(const struct mehcached_bucket *bucket);
-
-
-void
-mehcached_print_buckets(const struct mehcached_table *table);
-
-
-void
-mehcached_print_stats(const struct mehcached_table *table);
-
-
-void
-mehcached_reset_table_stats(struct mehcached_table *table);
-
-
-uint32_t
-mehcached_calc_bucket_index(const struct mehcached_table *table, uint64_t key_hash);
-
-
-uint16_t
-mehcached_calc_tag(uint64_t key_hash);
-
-
-void
-mehcached_set_item(struct mehcached_item *item, uint64_t key_hash, const uint8_t *key, uint32_t key_length, const uint8_t *value, uint32_t value_length, uint32_t expire_time);
-
-
-void
-mehcached_set_item_value(struct mehcached_item *item, const uint8_t *value, uint32_t value_length, uint32_t expire_time);
-
-
-bool
-mehcached_compare_keys(const uint8_t *key1, size_t key1_len, const uint8_t *key2, size_t key2_len);
-
-
-void
-mehcached_cleanup_all(uint8_t current_alloc_id, struct mehcached_table *table);
+void mehcached_print_buckets(const struct mehcached_table *table);
+void mehcached_print_stats(const struct mehcached_table *table);
-void
-mehcached_prefetch_table(struct mehcached_table *table, uint64_t key_hash, struct mehcached_prefetch_state *out_prefetch_state);
+void mehcached_reset_table_stats(struct mehcached_table *table);
+uint32_t mehcached_calc_bucket_index(const struct mehcached_table *table, uint64_t key_hash);
-void
-mehcached_prefetch_alloc(struct mehcached_prefetch_state *in_out_prefetch_state);
+uint16_t mehcached_calc_tag(uint64_t key_hash);
+void mehcached_set_item(struct mehcached_item *item, uint64_t key_hash, const uint8_t *key, uint32_t key_length, const uint8_t *value,
+ uint32_t value_length, uint32_t expire_time);
-bool
-mehcached_get(uint8_t current_alloc_id, struct mehcached_table *table, uint64_t key_hash, const uint8_t *key, size_t key_length, uint8_t *out_value, size_t *in_out_value_length, uint32_t *out_expire_time, bool readonly);
+void mehcached_set_item_value(struct mehcached_item *item, const uint8_t *value, uint32_t value_length, uint32_t expire_time);
+bool mehcached_compare_keys(const uint8_t *key1, size_t key1_len, const uint8_t *key2, size_t key2_len);
-bool
-mehcached_test(uint8_t current_alloc_id, struct mehcached_table *table, uint64_t key_hash, const uint8_t *key, size_t key_length);
+void mehcached_cleanup_all(uint8_t current_alloc_id, struct mehcached_table *table);
-bool
-mehcached_set(uint8_t current_alloc_id, struct mehcached_table *table, uint64_t key_hash, const uint8_t *key, size_t key_length, const uint8_t *value, size_t value_length, uint32_t expire_time, bool overwrite);
+void mehcached_prefetch_table(struct mehcached_table *table, uint64_t key_hash, struct mehcached_prefetch_state *out_prefetch_state);
+void mehcached_prefetch_alloc(struct mehcached_prefetch_state *in_out_prefetch_state);
-bool
-mehcached_delete(uint8_t current_alloc_id, struct mehcached_table *table, uint64_t key_hash, const uint8_t *key, size_t key_length);
+bool mehcached_get(uint8_t current_alloc_id, struct mehcached_table *table, uint64_t key_hash, const uint8_t *key, size_t key_length,
+ uint8_t *out_value, size_t *in_out_value_length, uint32_t *out_expire_time, bool readonly);
+bool mehcached_test(uint8_t current_alloc_id, struct mehcached_table *table, uint64_t key_hash, const uint8_t *key, size_t key_length);
-bool
-mehcached_increment(uint8_t current_alloc_id, struct mehcached_table *table, uint64_t key_hash, const uint8_t *key, size_t key_length, uint64_t increment, uint64_t *out_new_value, uint32_t expire_time);
+bool mehcached_set(uint8_t current_alloc_id, struct mehcached_table *table, uint64_t key_hash, const uint8_t *key, size_t key_length,
+ const uint8_t *value, size_t value_length, uint32_t expire_time, bool overwrite);
+bool mehcached_delete(uint8_t current_alloc_id, struct mehcached_table *table, uint64_t key_hash, const uint8_t *key,
+ size_t key_length);
-void
-mehcached_process_batch(uint8_t current_alloc_id, struct mehcached_table *table, struct mehcached_request *requests, size_t num_requests, const uint8_t *in_data, uint8_t *out_data, size_t *out_data_length, bool readonly);
+bool mehcached_increment(uint8_t current_alloc_id, struct mehcached_table *table, uint64_t key_hash, const uint8_t *key,
+ size_t key_length, uint64_t increment, uint64_t *out_new_value, uint32_t expire_time);
+void mehcached_process_batch(uint8_t current_alloc_id, struct mehcached_table *table, struct mehcached_request *requests,
+ size_t num_requests, const uint8_t *in_data, uint8_t *out_data, size_t *out_data_length, bool readonly);
-void
-mehcached_table_reset(struct mehcached_table *table);
+void mehcached_table_reset(struct mehcached_table *table);
+void mehcached_table_init(struct mehcached_table *table, size_t num_buckets, size_t num_pools, size_t pool_size,
+ bool concurrent_table_read, bool concurrent_table_write, bool concurrent_alloc_write, size_t table_numa_node,
+ size_t alloc_numa_nodes[], double mth_threshold);
-void
-mehcached_table_init(struct mehcached_table *table, size_t num_buckets, size_t num_pools, size_t pool_size, bool concurrent_table_read, bool concurrent_table_write, bool concurrent_alloc_write, size_t table_numa_node, size_t alloc_numa_nodes[], double mth_threshold);
-
-
-void
-mehcached_table_free(struct mehcached_table *table);
-
-
-uint32_t
-mehcached_read_version_begin(const struct mehcached_table *table MEHCACHED_UNUSED, const struct mehcached_bucket *bucket MEHCACHED_UNUSED);
+void mehcached_table_free(struct mehcached_table *table);
+uint32_t mehcached_read_version_begin(const struct mehcached_table *table MEHCACHED_UNUSED,
+ const struct mehcached_bucket *bucket MEHCACHED_UNUSED);
//uint64_t
-uint32_t
-mehcached_read_version_end(const struct mehcached_table *table MEHCACHED_UNUSED, const struct mehcached_bucket *bucket MEHCACHED_UNUSED);
-
-
-void
-mehcached_lock_bucket(const struct mehcached_table *table MEHCACHED_UNUSED, struct mehcached_bucket *bucket MEHCACHED_UNUSED);
-
-
-void
-mehcached_unlock_bucket(const struct mehcached_table *table MEHCACHED_UNUSED, struct mehcached_bucket *bucket MEHCACHED_UNUSED);
-
-
-void
-mehcached_lock_extra_bucket_free_list(struct mehcached_table *table);
-
-
-void
-mehcached_unlock_extra_bucket_free_list(struct mehcached_table *table);
-
-
-bool
-mehcached_has_extra_bucket(struct mehcached_bucket *bucket MEHCACHED_UNUSED);
-
-
-struct mehcached_bucket *
-mehcached_extra_bucket(const struct mehcached_table *table, uint32_t extra_bucket_index);
-
-
-bool
-mehcached_alloc_extra_bucket(struct mehcached_table *table, struct mehcached_bucket *bucket);
-
-
-void
-mehcached_free_extra_bucket(struct mehcached_table *table, struct mehcached_bucket *bucket);
+uint32_t mehcached_read_version_end(const struct mehcached_table *table MEHCACHED_UNUSED,
+ const struct mehcached_bucket *bucket MEHCACHED_UNUSED);
+void mehcached_lock_bucket(const struct mehcached_table *table MEHCACHED_UNUSED, struct mehcached_bucket *bucket MEHCACHED_UNUSED);
-void
-mehcached_fill_hole(struct mehcached_table *table, struct mehcached_bucket *bucket, size_t unused_item_index);
+void mehcached_unlock_bucket(const struct mehcached_table *table MEHCACHED_UNUSED, struct mehcached_bucket *bucket MEHCACHED_UNUSED);
+void mehcached_lock_extra_bucket_free_list(struct mehcached_table *table);
-size_t
-mehcached_find_empty(struct mehcached_table *table, struct mehcached_bucket *bucket, struct mehcached_bucket **located_bucket);
+void mehcached_unlock_extra_bucket_free_list(struct mehcached_table *table);
+bool mehcached_has_extra_bucket(struct mehcached_bucket *bucket MEHCACHED_UNUSED);
-size_t
-mehcached_find_empty_or_oldest(const struct mehcached_table *table, struct mehcached_bucket *bucket, struct mehcached_bucket **located_bucket);
+struct mehcached_bucket *mehcached_extra_bucket(const struct mehcached_table *table, uint32_t extra_bucket_index);
+bool mehcached_alloc_extra_bucket(struct mehcached_table *table, struct mehcached_bucket *bucket);
-size_t
-mehcached_find_item_index(const struct mehcached_table *table, struct mehcached_bucket *bucket, uint64_t key_hash, uint16_t tag, const uint8_t *key, size_t key_length, struct mehcached_bucket **located_bucket);
+void mehcached_free_extra_bucket(struct mehcached_table *table, struct mehcached_bucket *bucket);
+void mehcached_fill_hole(struct mehcached_table *table, struct mehcached_bucket *bucket, size_t unused_item_index);
-size_t
-mehcached_find_same_tag(const struct mehcached_table *table, struct mehcached_bucket *bucket, uint16_t tag, struct mehcached_bucket **located_bucket);
+size_t mehcached_find_empty(struct mehcached_table *table, struct mehcached_bucket *bucket, struct mehcached_bucket **located_bucket);
+size_t mehcached_find_empty_or_oldest(const struct mehcached_table *table, struct mehcached_bucket *bucket,
+ struct mehcached_bucket **located_bucket);
-void
-mehcached_cleanup_bucket(uint8_t current_alloc_id, struct mehcached_table *table, uint64_t old_tail, uint64_t new_tail);
+size_t mehcached_find_item_index(const struct mehcached_table *table, struct mehcached_bucket *bucket, uint64_t key_hash, uint16_t tag,
+ const uint8_t *key, size_t key_length, struct mehcached_bucket **located_bucket);
+size_t mehcached_find_same_tag(const struct mehcached_table *table, struct mehcached_bucket *bucket, uint16_t tag,
+ struct mehcached_bucket **located_bucket);
-void
-mehcached_table_free(struct mehcached_table *table);
+void mehcached_cleanup_bucket(uint8_t current_alloc_id, struct mehcached_table *table, uint64_t old_tail, uint64_t new_tail);
+void mehcached_table_free(struct mehcached_table *table);
MEHCACHED_END
\ No newline at end of file
diff --git a/examples/simple-firewall/config.json b/examples/simple-firewall/config.json
index dd3fe84..48b04ee 100644
--- a/examples/simple-firewall/config.json
+++ b/examples/simple-firewall/config.json
@@ -1,7 +1,7 @@
{
"valid_src": [
- {"src_addr": "192.168.1.1", "src_port": 3000},
- {"src_addr": "192.168.1.2", "src_port": 3000},
- {"src_addr": "192.168.1.3", "src_port": 3000}
+ {"src_addr": "192.168.1.1", "src_port": 1234},
+ {"src_addr": "192.168.1.2", "src_port": 1234},
+ {"src_addr": "192.168.1.3", "src_port": 1234}
]
}
diff --git a/examples/simple-firewall/main.c b/examples/simple-firewall/main.c
index d4e57c1..1a43a6a 100644
--- a/examples/simple-firewall/main.c
+++ b/examples/simple-firewall/main.c
@@ -23,14 +23,14 @@
#include
#include
-#define CONFIG_FILE "./examples/firewall/config.json"
+#define CONFIG_FILE "./examples/simple-firewall/config.json"
#define IP_STRLEN 16
#define PROTO_STRLEN 4
#define IFNAME_STRLEN 256
#define MAX_VALID_SESSIONS 100
-bool done = false;
+volatile bool done = false;
struct config *cfg = NULL;
struct nf *nf;
@@ -46,11 +46,14 @@ struct appconf {
int stats_cpu;
} app_conf;
-struct Args {
- int socket_id;
- int *next;
- int next_size;
+// clang-format off
+static const char *firewall_options[] = {
+ "-c \tStart CPU (default: 0)",
+ "-e \tEnd CPU (default: 0)",
+ "-s \tStats CPU (default: 1)",
+ NULL
};
+// clang-format on
struct session_id {
uint32_t saddr;
@@ -67,14 +70,14 @@ int num_sessions = 0;
char load_balancer_addr[IP_STRLEN];
unsigned int load_balancer_port = 80;
-static void read_json_config(void)
+static int read_json_config(void)
{
struct in_addr addr;
inet_aton(load_balancer_addr, &addr);
FILE *file = fopen(CONFIG_FILE, "r");
if (!file) {
- perror("Failed to open file");
- return;
+ log_error("Failed to open file: %s", CONFIG_FILE);
+ return -1;
}
// Get file size
@@ -84,17 +87,17 @@ static void read_json_config(void)
char *json_data = (char *)malloc(file_size + 1);
if (!json_data) {
- perror("Memory allocation failed");
+ log_error("Memory allocation failed for JSON data");
fclose(file);
- return;
+ return -1;
}
size_t read_size = fread(json_data, 1, file_size, file);
if (read_size != file_size) {
- perror("Failed to read entire file");
+ log_error("Failed to read entire file: %s", CONFIG_FILE);
free(json_data);
fclose(file);
- exit(1);
+ return -1;
}
json_data[file_size] = '\0';
@@ -103,8 +106,8 @@ static void read_json_config(void)
cJSON *json = cJSON_Parse(json_data);
free(json_data);
if (!json) {
- printf("Error parsing JSON\n");
- return;
+ log_error("Error parsing JSON");
+ return -1;
}
cJSON *valid_src = cJSON_GetObjectItem(json, "valid_src");
@@ -113,8 +116,9 @@ static void read_json_config(void)
int size = cJSON_GetArraySize(valid_src);
num_sessions = size;
if (num_sessions > MAX_VALID_SESSIONS) {
- printf("num_sessions > MAX_VALID_SESSIONS\n");
- exit(1);
+ log_error("Number of sessions (%d) exceeds maximum allowed (%d)", num_sessions, MAX_VALID_SESSIONS);
+ cJSON_Delete(json); // Clean up
+ return -1;
}
for (int i = 0; i < num_sessions; i++) {
cJSON *entry = cJSON_GetArrayItem(valid_src, i);
@@ -127,38 +131,67 @@ static void read_json_config(void)
valid_sessions[i].proto = IPPROTO_UDP;
valid_sessions[i].daddr = addr.s_addr;
valid_sessions[i].dport = htons(load_balancer_port);
+ log_info("Valid session added: %s:%d -> %s:%d (proto: %d)", src_addr->valuestring, src_port->valueint,
+ load_balancer_addr, load_balancer_port, valid_sessions[i].proto);
}
}
} else {
- printf("Error: valid_src is not a valid array\n");
+ log_error("Error: valid_src is not a valid array");
+ cJSON_Delete(json); // Clean up
+ return -1;
}
cJSON_Delete(json); // Clean up
+ return 0;
}
-static void *configure(void)
+static int configure(void)
{
- int nbackends;
- send_cmd(cfg->uds_sockfd, FLASH__GET_DST_IP_ADDR);
- recv_data(cfg->uds_sockfd, &nbackends, sizeof(int));
+ int nbackends, ret;
+ ret = flash__send_cmd(cfg->uds_sockfd, FLASH__GET_DST_IP_ADDR);
+ if (ret < 0) {
+ log_error("Failed to send command to UDS socket");
+ return -1;
+ }
+ ret = flash__recv_data(cfg->uds_sockfd, &nbackends, sizeof(int));
+ if (ret < 0) {
+ log_error("Failed to receive data from UDS socket");
+ return -1;
+ }
if (nbackends != 1) {
- printf("Firewall is linked to %d load balancers", nbackends);
- exit(1);
+ log_error("Firewall is linked to %d load balancers", nbackends);
+ return -1;
+ }
+ ret = flash__recv_data(cfg->uds_sockfd, load_balancer_addr, INET_ADDRSTRLEN);
+ if (ret < 0) {
+ log_error("Failed to receive data from UDS socket");
+ return -1;
}
- recv_data(cfg->uds_sockfd, load_balancer_addr, INET_ADDRSTRLEN);
- read_json_config();
+ ret = read_json_config();
+ if (ret < 0) {
+ log_error("Failed to read JSON config");
+ return -1;
+ }
- hashmap_init(&valid_sessions_map, sizeof(struct session_id), sizeof(int), MAX_VALID_SESSIONS);
+ ret = hashmap_init(&valid_sessions_map, sizeof(struct session_id), sizeof(int), MAX_VALID_SESSIONS);
+ if (ret != 1) {
+ log_error("ERROR: unable to initialize valid sessions hashmap");
+ return -1;
+ }
for (int session_num = 0; session_num < num_sessions; session_num++) {
struct session_id *key = &valid_sessions[session_num];
int val = 1;
- hashmap_insert_elem(&valid_sessions_map, (void *)key, (void *)&val);
+ ret = hashmap_insert_elem(&valid_sessions_map, (void *)key, (void *)&val);
+ if (ret != 1) {
+ log_error("ERROR: unable to add valid session to hashmap");
+ return -1;
+ }
}
- return NULL;
+ return 0;
}
-static void parse_app_args(int argc, char **argv, struct appconf *app_conf, int shift)
+static int parse_app_args(int argc, char **argv, struct appconf *app_conf, int shift)
{
int c;
opterr = 0;
@@ -171,8 +204,11 @@ static void parse_app_args(int argc, char **argv, struct appconf *app_conf, int
argc -= shift;
argv += shift;
- while ((c = getopt(argc, argv, "c:e:s:")) != -1)
+ while ((c = getopt(argc, argv, "hc:e:s:")) != -1)
switch (c) {
+ case 'h':
+ printf("Usage: %s -h\n", argv[-shift]);
+ return -1;
case 'c':
app_conf->cpu_start = atoi(optarg);
break;
@@ -183,88 +219,85 @@ static void parse_app_args(int argc, char **argv, struct appconf *app_conf, int
app_conf->stats_cpu = atoi(optarg);
break;
default:
- abort();
+ printf("Usage: %s -h\n", argv[-shift]);
+ return -1;
}
+ return 0;
}
-static void *worker__stats(void *arg)
-{
- (void)arg;
-
- if (cfg->verbose) {
- unsigned int interval = cfg->stats_interval;
- setlocale(LC_ALL, "");
-
- for (int i = 0; i < cfg->total_sockets; i++)
- nf->thread[i]->socket->timestamp = flash__get_nsecs(cfg);
-
- while (!done) {
- sleep(interval);
- if (system("clear") != 0)
- log_error("Terminal clear error");
- for (int i = 0; i < cfg->total_sockets; i++) {
- flash__dump_stats(cfg, nf->thread[i]->socket);
- }
- }
- }
- return NULL;
-}
+struct sock_args {
+ int socket_id;
+};
static void *socket_routine(void *arg)
{
- struct Args *a = (struct Args *)arg;
+ int ret;
+ nfds_t nfds = 1;
+ struct socket *xsk;
+ struct xskvec *xskvecs, *sendvecs, *dropvecs;
+ struct pollfd fds[1] = {};
+ uint32_t i, nrecv, nsend, ndrop, wdrop, wsend;
+ struct sock_args *a = (struct sock_args *)arg;
+
int socket_id = a->socket_id;
- int *next = a->next;
- int next_size = a->next_size;
- // free(arg);
+
+ xsk = nf->thread[socket_id]->socket;
log_info("SOCKET_ID: %d", socket_id);
- // static __u32 nb_frags;
- int i, ret, nfds = 1, nrecv;
- struct pollfd fds[1] = {};
- struct xskmsghdr msg = {};
- log_info("2_NEXT_SIZE: %d", next_size);
+ xskvecs = calloc(cfg->xsk->batch_size, sizeof(struct xskvec));
+ if (!xskvecs) {
+ log_error("ERROR: Memory allocation failed for xskvecs");
+ return NULL;
+ }
- for (int i = 0; i < next_size; i++) {
- log_info("2_NEXT_ITEM_%d %d", i, next[i]);
+ sendvecs = calloc(cfg->xsk->batch_size, sizeof(struct xskvec));
+ if (!sendvecs) {
+ log_error("ERROR: Memory allocation failed for sendvecs");
+ free(xskvecs);
+ return NULL;
}
- msg.msg_iov = calloc(cfg->xsk->batch_size, sizeof(struct xskvec));
+ dropvecs = calloc(cfg->xsk->batch_size, sizeof(struct xskvec));
+ if (!dropvecs) {
+ log_error("ERROR: Memory allocation failed for dropvecs");
+ free(xskvecs);
+ free(sendvecs);
+ return NULL;
+ }
- fds[0].fd = nf->thread[socket_id]->socket->fd;
+ fds[0].fd = xsk->fd;
fds[0].events = POLLIN;
for (;;) {
- if (cfg->xsk->mode & FLASH__POLL) {
- ret = flash__poll(nf->thread[socket_id]->socket, fds, nfds, cfg->xsk->poll_timeout);
- if (ret <= 0 || ret > 1)
- continue;
- }
- nrecv = flash__recvmsg(cfg, nf->thread[socket_id]->socket, &msg);
-
- struct xskvec *drop[nrecv];
- unsigned int tot_pkt_drop = 0;
- struct xskvec *send[nrecv];
- unsigned int tot_pkt_send = 0;
+ ret = flash__poll(cfg, xsk, fds, nfds);
+ if (!(ret == 1 || ret == -2))
+ continue;
+ nrecv = flash__recvmsg(cfg, xsk, xskvecs, cfg->xsk->batch_size);
+ wsend = 0;
+ wdrop = 0;
for (i = 0; i < nrecv; i++) {
- struct xskvec *xv = &msg.msg_iov[i];
+ struct xskvec *xv = &xskvecs[i];
void *pkt = xv->data;
void *pkt_end = pkt + xv->len;
+
struct ethhdr *eth = pkt;
if ((void *)(eth + 1) > pkt_end) {
- drop[tot_pkt_drop++] = &msg.msg_iov[i];
+ dropvecs[wdrop++] = xskvecs[i];
+ log_error("Packet too short for Ethernet header");
continue;
}
if (eth->h_proto != htons(ETH_P_IP)) {
- drop[tot_pkt_drop++] = &msg.msg_iov[i];
+ dropvecs[wdrop++] = xskvecs[i];
+ log_error("Unsupported Ethernet protocol");
continue;
}
struct iphdr *iph = (void *)(eth + 1);
if ((void *)(iph + 1) > pkt_end) {
- drop[tot_pkt_drop++] = &msg.msg_iov[i];
+ dropvecs[wdrop++] = xskvecs[i];
+ log_error("Packet too short for IP header");
continue;
}
@@ -276,7 +309,8 @@ static void *socket_routine(void *arg)
case IPPROTO_TCP:;
struct tcphdr *tcph = next;
if ((void *)(tcph + 1) > pkt_end) {
- drop[tot_pkt_drop++] = &msg.msg_iov[i];
+ dropvecs[wdrop++] = xskvecs[i];
+ log_error("Packet too short for TCP header");
continue;
}
@@ -288,7 +322,8 @@ static void *socket_routine(void *arg)
case IPPROTO_UDP:;
struct udphdr *udph = next;
if ((void *)(udph + 1) > pkt_end) {
- drop[tot_pkt_drop++] = &msg.msg_iov[i];
+ dropvecs[wdrop++] = xskvecs[i];
+ log_error("Packet too short for UDP header");
continue;
}
@@ -298,7 +333,8 @@ static void *socket_routine(void *arg)
break;
default:
- drop[tot_pkt_drop++] = &msg.msg_iov[i];
+ dropvecs[wdrop++] = xskvecs[i];
+ log_error("Unsupported IP protocol: %d", iph->protocol);
continue;
}
@@ -310,94 +346,126 @@ static void *socket_routine(void *arg)
sid.dport = *dport;
if (hashmap_lookup_elem(&valid_sessions_map, (void *)&sid) == NULL) {
- drop[tot_pkt_drop++] = &msg.msg_iov[i];
+ dropvecs[wdrop++] = xskvecs[i];
continue;
}
- send[tot_pkt_send++] = &msg.msg_iov[i];
+ sendvecs[wsend++] = xskvecs[i];
}
if (nrecv) {
- size_t ret_send = flash__sendmsg(cfg, nf->thread[socket_id]->socket, send, tot_pkt_send);
- size_t ret_drop = flash__dropmsg(cfg, nf->thread[socket_id]->socket, drop, tot_pkt_drop);
- if (ret_send != tot_pkt_send || ret_drop != tot_pkt_drop) {
+ nsend = flash__sendmsg(cfg, xsk, sendvecs, wsend);
+ ndrop = flash__dropmsg(cfg, xsk, dropvecs, wdrop);
+ if (nsend != wsend || ndrop != wdrop) {
log_error("errno: %d/\"%s\"\n", errno, strerror(errno));
- exit(EXIT_FAILURE);
+ break;
}
}
if (done)
break;
}
- free(msg.msg_iov);
+ free(xskvecs);
+ free(sendvecs);
+ free(dropvecs);
+ hashmap_free(&valid_sessions_map);
return NULL;
}
int main(int argc, char **argv)
{
+ int shift;
+ struct sock_args *args;
+ struct stats_conf stats_cfg = { NULL };
cpu_set_t cpuset;
+ pthread_t socket_thread, stats_thread;
+
cfg = calloc(1, sizeof(struct config));
if (!cfg) {
log_error("ERROR: Memory allocation failed\n");
exit(EXIT_FAILURE);
}
- int n = flash__parse_cmdline_args(argc, argv, cfg);
- parse_app_args(argc, argv, &app_conf, n);
- flash__configure_nf(&nf, cfg);
- flash__populate_fill_ring(nf->thread, cfg->umem->frame_size, cfg->total_sockets, cfg->umem_offset, cfg->umem_scale);
+ cfg->app_name = "simple-firewall";
+ cfg->app_options = firewall_options;
+ cfg->done = &done;
+
+ shift = flash__parse_cmdline_args(argc, argv, cfg);
+ if (shift < 0)
+ goto out_cfg;
+
+ if (parse_app_args(argc, argv, &app_conf, shift) < 0)
+ goto out_cfg;
+
+ if (flash__configure_nf(&nf, cfg) < 0)
+ goto out_cfg;
log_info("Control Plane Setup Done");
- configure();
+ if (configure() < 0) {
+ log_error("Error configuring the application");
+ goto out_cfg;
+ }
+
signal(SIGINT, int_exit);
signal(SIGTERM, int_exit);
signal(SIGABRT, int_exit);
log_info("STARTING Data Path");
+ args = calloc(cfg->total_sockets, sizeof(struct sock_args));
+ if (!args) {
+ log_error("ERROR: Memory allocation failed for sock_args");
+ goto out_cfg_close;
+ }
for (int i = 0; i < cfg->total_sockets; i++) {
- struct Args *args = calloc(1, sizeof(struct Args));
- args->socket_id = i;
- args->next = nf->next;
- args->next_size = nf->next_size;
+ args[i].socket_id = i;
- log_info("2_NEXT_SIZE: %d", args->next_size);
-
- for (int i = 0; i < args->next_size; i++) {
- log_info("2_NEXT_ITEM_%d %d", i, nf->next[i]);
- }
-
- pthread_t socket_thread;
- if (pthread_create(&socket_thread, NULL, socket_routine, args)) {
+ if (pthread_create(&socket_thread, NULL, socket_routine, &args[i])) {
log_error("Error creating socket thread");
- exit(EXIT_FAILURE);
+ goto out_args;
}
CPU_ZERO(&cpuset);
CPU_SET((i % (app_conf.cpu_end - app_conf.cpu_start + 1)) + app_conf.cpu_start, &cpuset);
if (pthread_setaffinity_np(socket_thread, sizeof(cpu_set_t), &cpuset) != 0) {
log_error("ERROR: Unable to set thread affinity: %s\n", strerror(errno));
- exit(EXIT_FAILURE);
+ goto out_args;
}
- pthread_detach(socket_thread);
+ if (pthread_detach(socket_thread) != 0) {
+ log_error("ERROR: Unable to detach thread: %s", strerror(errno));
+ goto out_args;
+ }
}
- pthread_t stats_thread;
- if (pthread_create(&stats_thread, NULL, worker__stats, NULL)) {
+ stats_cfg.nf = nf;
+ stats_cfg.cfg = cfg;
+
+ if (pthread_create(&stats_thread, NULL, flash__stats_thread, &stats_cfg)) {
log_error("Error creating statistics thread");
- exit(EXIT_FAILURE);
+ goto out_args;
}
CPU_ZERO(&cpuset);
CPU_SET(app_conf.stats_cpu, &cpuset);
if (pthread_setaffinity_np(stats_thread, sizeof(cpu_set_t), &cpuset) != 0) {
log_error("ERROR: Unable to set thread affinity: %s\n", strerror(errno));
- exit(EXIT_FAILURE);
+ goto out_args;
+ }
+ if (pthread_detach(stats_thread) != 0) {
+ log_error("ERROR: Unable to detach thread: %s", strerror(errno));
+ goto out_args;
}
- pthread_detach(stats_thread);
-
- wait_for_cmd(cfg);
+ flash__wait(cfg);
flash__xsk_close(cfg, nf);
- return EXIT_SUCCESS;
-}
\ No newline at end of file
+ exit(EXIT_SUCCESS);
+out_args:
+ done = true;
+ free(args);
+out_cfg_close:
+ sleep(1);
+ flash__xsk_close(cfg, nf);
+out_cfg:
+ free(cfg);
+ exit(EXIT_FAILURE);
+}
diff --git a/examples/simplefwd-rs/Cargo.toml b/examples/simplefwd-rs/Cargo.toml
index 1995e64..a3146a9 100644
--- a/examples/simplefwd-rs/Cargo.toml
+++ b/examples/simplefwd-rs/Cargo.toml
@@ -6,13 +6,15 @@ edition = "2024"
[dependencies]
clap = { version = "4.5.35", features = ["derive"] }
core_affinity = "0.8.3"
-ctrlc = "3.4.5"
+ctrlc = { version = "3.4.5", optional = true }
flash = { path = "../../lib/flash-rs", features = ["clap"] }
+macaddr = "1.0.1"
tracing = { version = "0.1.41", optional = true }
tracing-subscriber = { version = "0.3.19", optional = true }
[features]
-default = []
+default = ["dep:ctrlc"]
+stats = ["flash/stats", "flash/tui"]
tracing = ["dep:tracing", "dep:tracing-subscriber", "flash/tracing"]
[lints.rust]
diff --git a/examples/simplefwd-rs/src/cli.rs b/examples/simplefwd-rs/src/cli.rs
index 31aa504..e5de218 100644
--- a/examples/simplefwd-rs/src/cli.rs
+++ b/examples/simplefwd-rs/src/cli.rs
@@ -1,5 +1,12 @@
+#[cfg(feature = "stats")]
+use std::str::FromStr as _;
+
use clap::Parser;
use flash::FlashConfig;
+use macaddr::MacAddr6;
+
+#[cfg(feature = "stats")]
+use flash::tui::GridLayout;
#[derive(Debug, Parser)]
pub struct Cli {
@@ -21,4 +28,29 @@ pub struct Cli {
help = "Ending CPU core index for socket threads (inclusive)"
)]
pub cpu_end: usize,
+
+ #[cfg(feature = "stats")]
+ #[command(flatten)]
+ pub stats: StatsConfig,
+
+ #[arg(short = 'm', long, help = "Dest MAC address")]
+ pub mac_addr: Option,
+}
+
+#[cfg(feature = "stats")]
+#[derive(Debug, Parser)]
+pub struct StatsConfig {
+ #[arg(
+ short = 's',
+ long = "stats-cpu",
+ default_value_t = 1,
+ help = "CPU core index for stats thread"
+ )]
+ pub cpu: usize,
+
+ #[arg(short = 'F', long, default_value_t = 1, help = "Tui frames per second")]
+ pub fps: u64,
+
+ #[arg(short = 'l', long, default_value_t = GridLayout::default(), value_parser = GridLayout::from_str, help = "Tui layout")]
+ pub layout: GridLayout,
}
diff --git a/examples/simplefwd-rs/src/main.rs b/examples/simplefwd-rs/src/main.rs
index 8957e58..9ea3956 100644
--- a/examples/simplefwd-rs/src/main.rs
+++ b/examples/simplefwd-rs/src/main.rs
@@ -10,20 +10,43 @@ use std::{
use clap::Parser;
use flash::Socket;
+use macaddr::MacAddr6;
+
+#[cfg(feature = "stats")]
+use flash::tui::StatsDashboard;
use crate::cli::Cli;
-fn socket_thread(mut socket: Socket, run: &Arc) {
+fn socket_thread(mut socket: Socket, mac_addr: Option, run: &Arc) {
while run.load(Ordering::SeqCst) {
if !socket.poll().is_ok_and(|val| val) {
continue;
}
let Ok(descs) = socket.recv() else {
- break;
+ continue;
+ };
+
+ let Some(mac_addr) = mac_addr else {
+ socket.send(descs);
+ continue;
};
- socket.send(descs);
+ let mut descs_send = Vec::with_capacity(descs.len());
+ let mut descs_drop = Vec::with_capacity(descs.len());
+
+ for desc in descs {
+ let Ok(pkt) = socket.read_exact::<6>(&desc) else {
+ descs_drop.push(desc);
+ continue;
+ };
+
+ pkt[0..6].copy_from_slice(mac_addr.as_bytes());
+ descs_send.push(desc);
+ }
+
+ socket.send(descs_send);
+ socket.drop(descs_drop);
}
}
@@ -49,6 +72,19 @@ fn main() {
#[cfg(feature = "tracing")]
tracing::info!("Sockets: {sockets:?}");
+ #[cfg(feature = "stats")]
+ let mut tui = match StatsDashboard::new(
+ sockets.iter().map(Socket::stats),
+ cli.stats.fps,
+ cli.stats.layout,
+ ) {
+ Ok(t) => t,
+ Err(err) => {
+ eprintln!("error creating tui: {err}");
+ return;
+ }
+ };
+
let cores = core_affinity::get_core_ids()
.unwrap_or_default()
.into_iter()
@@ -56,19 +92,32 @@ fn main() {
.collect::>();
if cores.is_empty() {
- eprintln!("No cores found in range {}-{}", cli.cpu_start, cli.cpu_end);
+ eprintln!("no cores found in range {}-{}", cli.cpu_start, cli.cpu_end);
return;
}
#[cfg(feature = "tracing")]
tracing::debug!("Cores: {:?}", cores);
+ #[cfg(feature = "stats")]
+ let Some(stats_core) = core_affinity::get_core_ids()
+ .unwrap_or_default()
+ .into_iter()
+ .find(|core_id| core_id.id == cli.stats.cpu)
+ else {
+ eprintln!("no core found for stats thread {}", cli.stats.cpu);
+ return;
+ };
+
let run = Arc::new(AtomicBool::new(true));
- let r = run.clone();
- if let Err(err) = ctrlc::set_handler(move || {
- r.store(false, Ordering::SeqCst);
- }) {
+ #[cfg(not(feature = "stats"))]
+ if let Err(err) = {
+ let r = run.clone();
+ ctrlc::set_handler(move || {
+ r.store(false, Ordering::SeqCst);
+ })
+ } {
eprintln!("error setting Ctrl-C handler: {err}");
return;
}
@@ -80,11 +129,26 @@ fn main() {
let r = run.clone();
thread::spawn(move || {
core_affinity::set_for_current(core_id);
- socket_thread(socket, &r);
+ socket_thread(socket, cli.mac_addr, &r);
})
})
.collect::>();
+ #[cfg(feature = "stats")]
+ if let Err(err) = thread::spawn(move || {
+ core_affinity::set_for_current(stats_core);
+ if let Err(err) = tui.run() {
+ eprintln!("error dumping stats: {err}");
+ }
+ })
+ .join()
+ {
+ eprintln!("error in stats thread: {err:?}");
+ }
+
+ #[cfg(feature = "stats")]
+ run.store(false, Ordering::SeqCst);
+
for handle in handles {
if let Err(err) = handle.join() {
eprintln!("error in thread: {err:?}");
diff --git a/examples/simplefwd/main.c b/examples/simplefwd/main.c
index d01d45e..ba72e68 100644
--- a/examples/simplefwd/main.c
+++ b/examples/simplefwd/main.c
@@ -3,20 +3,17 @@
*
* simplefwd: A simple NF that forwards packets without modification
*/
-
#include
#include
-#include
-#include
#include
#include
#include
#include
-bool done = false;
+volatile bool done = false;
struct config *cfg = NULL;
-struct nf *nf;
+struct nf *nf = NULL;
static void int_exit(int sig)
{
@@ -30,12 +27,20 @@ struct appconf {
int stats_cpu;
} app_conf;
-static void parse_app_args(int argc, char **argv, struct appconf *app_conf, int shift)
+// clang-format off
+static const char *l2fwd_options[] = {
+ "-c \tStart CPU (default: 0)",
+ "-e \tEnd CPU (default: 0)",
+ "-s \tStats CPU (default: 1)",
+ NULL
+};
+// clang-format on
+
+static int parse_app_args(int argc, char **argv, struct appconf *app_conf, int shift)
{
int c;
opterr = 0;
- // Default values
app_conf->cpu_start = 0;
app_conf->cpu_end = 0;
app_conf->stats_cpu = 1;
@@ -43,8 +48,11 @@ static void parse_app_args(int argc, char **argv, struct appconf *app_conf, int
argc -= shift;
argv += shift;
- while ((c = getopt(argc, argv, "c:e:s:")) != -1)
+ while ((c = getopt(argc, argv, "hc:e:s:")) != -1)
switch (c) {
+ case 'h':
+ printf("Usage: %s -h\n", argv[-shift]);
+ return -1;
case 'c':
app_conf->cpu_start = atoi(optarg);
break;
@@ -55,164 +63,171 @@ static void parse_app_args(int argc, char **argv, struct appconf *app_conf, int
app_conf->stats_cpu = atoi(optarg);
break;
default:
- abort();
+ printf("Usage: %s -h\n", argv[-shift]);
+ return -1;
}
+
+ return 0;
}
-static void do_noting(void *data)
+static void do_nothing(void *data)
{
/* This is stupid but it makes sure that compiler doesn't through any errors */
(void)data;
}
-struct Args {
+struct sock_args {
int socket_id;
- int *next;
- int next_size;
};
static void *socket_routine(void *arg)
{
- struct Args *a = (struct Args *)arg;
- int socket_id = a->socket_id;
- log_info("SOCKET_ID: %d", socket_id);
- static __u32 nb_frags;
- int i, ret, nfds = 1, nrecv;
+ int ret;
+ nfds_t nfds = 1;
+ struct socket *xsk;
+ struct xskvec *xskvecs;
struct pollfd fds[1] = {};
- struct xskmsghdr msg = {};
+ uint32_t i, nrecv, nsend, nb_frags = 0;
+ struct sock_args *a = (struct sock_args *)arg;
- msg.msg_iov = calloc(cfg->xsk->batch_size, sizeof(struct xskvec));
+ log_debug("Socket ID: %d", a->socket_id);
+ xsk = nf->thread[a->socket_id]->socket;
- fds[0].fd = nf->thread[socket_id]->socket->fd;
+ xskvecs = calloc(cfg->xsk->batch_size, sizeof(struct xskvec));
+ if (!xskvecs) {
+ log_error("Failed to allocate xskvecs array");
+ return NULL;
+ }
+
+ fds[0].fd = xsk->fd;
fds[0].events = POLLIN;
for (;;) {
- if (cfg->xsk->mode & FLASH__POLL) {
- ret = flash__poll(nf->thread[socket_id]->socket, fds, nfds, cfg->xsk->poll_timeout);
- if (ret <= 0 || ret > 1)
- continue;
- }
+ ret = flash__poll(cfg, xsk, fds, nfds);
+ if (!(ret == 1 || ret == -2))
+ continue;
- nrecv = flash__recvmsg(cfg, nf->thread[socket_id]->socket, &msg);
- struct xskvec *send[nrecv];
- unsigned int tot_pkt_send = 0;
+ nrecv = flash__recvmsg(cfg, xsk, xskvecs, cfg->xsk->batch_size);
for (i = 0; i < nrecv; i++) {
- struct xskvec *xv = &msg.msg_iov[i];
- bool eop = IS_EOP_DESC(xv->options);
-
- char *pkt = xv->data;
+ char *pkt = xskvecs[i].data;
if (!nb_frags++)
- do_noting(pkt);
+ do_nothing(pkt);
- send[tot_pkt_send++] = &msg.msg_iov[i];
- if (eop)
+ if (IS_EOP_DESC(xskvecs[i].options))
nb_frags = 0;
}
if (nrecv) {
- ret = flash__sendmsg(cfg, nf->thread[socket_id]->socket, send, tot_pkt_send);
- if (ret != nrecv) {
- log_error("errno: %d/\"%s\"\n", errno, strerror(errno));
- exit(EXIT_FAILURE);
+ nsend = flash__sendmsg(cfg, xsk, xskvecs, nrecv);
+ if (nsend != nrecv) {
+ log_error("errno: %d/\"%s\"", errno, strerror(errno));
+ break;
}
}
if (done)
break;
}
- free(msg.msg_iov);
- return NULL;
-}
-
-static void *worker__stats(void *arg)
-{
- (void)arg;
-
- if (cfg->verbose) {
- unsigned int interval = cfg->stats_interval;
- setlocale(LC_ALL, "");
-
- for (int i = 0; i < cfg->total_sockets; i++)
- nf->thread[i]->socket->timestamp = flash__get_nsecs(cfg);
- while (!done) {
- sleep(interval);
- if (system("clear") != 0)
- log_error("Terminal clear error");
- for (int i = 0; i < cfg->total_sockets; i++) {
- flash__dump_stats(cfg, nf->thread[i]->socket);
- }
- }
- }
+ free(xskvecs);
return NULL;
}
int main(int argc, char **argv)
{
+ int shift;
+ struct sock_args *args;
+ struct stats_conf stats_cfg = { NULL };
cpu_set_t cpuset;
+ pthread_t socket_thread, stats_thread;
+
cfg = calloc(1, sizeof(struct config));
if (!cfg) {
- log_error("ERROR: Memory allocation failed\n");
+ log_error("ERROR: Memory allocation failed");
exit(EXIT_FAILURE);
}
- int n = flash__parse_cmdline_args(argc, argv, cfg);
- parse_app_args(argc, argv, &app_conf, n);
- flash__configure_nf(&nf, cfg);
- flash__populate_fill_ring(nf->thread, cfg->umem->frame_size, cfg->total_sockets, cfg->umem_offset, cfg->umem_scale);
+ cfg->app_name = "L2 Forwarding Application";
+ cfg->app_options = l2fwd_options;
+ cfg->done = &done;
- log_info("Control Plane Setup Done");
+ shift = flash__parse_cmdline_args(argc, argv, cfg);
+ if (shift < 0)
+ goto out_cfg;
+
+ if (parse_app_args(argc, argv, &app_conf, shift) < 0)
+ goto out_cfg;
+
+ if (flash__configure_nf(&nf, cfg) < 0)
+ goto out_cfg;
+
+ log_info("Control Plane setup done...");
signal(SIGINT, int_exit);
signal(SIGTERM, int_exit);
signal(SIGABRT, int_exit);
- log_info("STARTING Data Path");
-
- for (int i = 0; i < cfg->total_sockets; i++) {
- struct Args *args = calloc(1, sizeof(struct Args));
- args->socket_id = i;
- args->next = nf->next;
- args->next_size = nf->next_size;
+ log_info("Starting Data Path...");
- log_info("2_NEXT_SIZE: %d", args->next_size);
+ args = calloc(cfg->total_sockets, sizeof(struct sock_args));
+ if (!args) {
+ log_error("ERROR: Memory allocation failed for sock_args");
+ goto out_cfg_close;
+ }
- for (int i = 0; i < args->next_size; i++) {
- log_info("2_NEXT_ITEM_%d %d", i, nf->next[i]);
- }
+ for (int i = 0; i < cfg->total_sockets; i++) {
+ args[i].socket_id = i;
- pthread_t socket_thread;
- if (pthread_create(&socket_thread, NULL, socket_routine, args)) {
+ if (pthread_create(&socket_thread, NULL, socket_routine, &args[i])) {
log_error("Error creating socket thread");
- exit(EXIT_FAILURE);
+ goto out_args;
}
+
CPU_ZERO(&cpuset);
CPU_SET((i % (app_conf.cpu_end - app_conf.cpu_start + 1)) + app_conf.cpu_start, &cpuset);
if (pthread_setaffinity_np(socket_thread, sizeof(cpu_set_t), &cpuset) != 0) {
- log_error("ERROR: Unable to set thread affinity: %s\n", strerror(errno));
- exit(EXIT_FAILURE);
+ log_error("ERROR: Unable to set thread affinity: %s", strerror(errno));
+ goto out_args;
}
- pthread_detach(socket_thread);
+ if (pthread_detach(socket_thread) != 0) {
+ log_error("ERROR: Unable to detach thread: %s", strerror(errno));
+ goto out_args;
+ }
}
- pthread_t stats_thread;
- if (pthread_create(&stats_thread, NULL, worker__stats, NULL)) {
+ stats_cfg.nf = nf;
+ stats_cfg.cfg = cfg;
+
+ if (pthread_create(&stats_thread, NULL, flash__stats_thread, &stats_cfg)) {
log_error("Error creating statistics thread");
- exit(EXIT_FAILURE);
+ goto out_args;
}
CPU_ZERO(&cpuset);
CPU_SET(app_conf.stats_cpu, &cpuset);
if (pthread_setaffinity_np(stats_thread, sizeof(cpu_set_t), &cpuset) != 0) {
- log_error("ERROR: Unable to set thread affinity: %s\n", strerror(errno));
- exit(EXIT_FAILURE);
+ log_error("ERROR: Unable to set thread affinity: %s", strerror(errno));
+ goto out_args;
}
- pthread_detach(stats_thread);
- wait_for_cmd(cfg);
+ if (pthread_detach(stats_thread) != 0) {
+ log_error("ERROR: Unable to detach thread: %s", strerror(errno));
+ goto out_args;
+ }
+ flash__wait(cfg);
flash__xsk_close(cfg, nf);
- return EXIT_SUCCESS;
+ exit(EXIT_SUCCESS);
+
+out_args:
+ done = true;
+ free(args);
+out_cfg_close:
+ sleep(1);
+ flash__xsk_close(cfg, nf);
+out_cfg:
+ free(cfg);
+ exit(EXIT_FAILURE);
}
diff --git a/examples/txgen/main.c b/examples/txgen/main.c
new file mode 100644
index 0000000..6afd199
--- /dev/null
+++ b/examples/txgen/main.c
@@ -0,0 +1,395 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright (c) 2025 Debojeet Das
+ *
+ * txgen: A packet generator that transmits Ethernet+IPv4+UDP frames with
+ * configurable addresses and ports.
+ */
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+volatile bool done = false;
+struct config *cfg = NULL;
+struct nf *nf = NULL;
+uint8_t *packet_template = NULL;
+
+static void int_exit(int sig)
+{
+ log_info("Received Signal: %d", sig);
+ done = true;
+}
+
+struct appconf {
+ int cpu_start;
+ int cpu_end;
+ int stats_cpu;
+ bool custom_src_ether_addr;
+ bool custom_dest_ether_addr;
+ uint8_t src_ether_addr_octet[6];
+ uint8_t dest_ether_addr_octet[6];
+ uint32_t src_ip;
+ uint32_t dest_ip;
+ uint16_t src_port;
+ uint16_t dest_port;
+ uint16_t payload_len;
+} app_conf;
+
+// clang-format off
+static const char *txgen_options[] = {
+ "-c \tStart CPU (default: 0)",
+ "-e \tEnd CPU (default: 0)",
+ "-s \tStats CPU (default: 1)",
+ "-S \tSrc MAC address to use (default: NIC MAC address)",
+ "-D \tDest MAC address to use (default: NIC MAC address)",
+ "-A \tSrc IPv4 address to use (default: 192.168.1.1)",
+ "-B \tDest IPv4 address to use (default: 192.168.2.1)",
+ "-P \tSrc port to use (default: 1234)",
+ "-Q \tDest port to use (default: 5678)",
+ "-L \tPayload length (default: 5 bytes)",
+ NULL
+};
+// clang-format on
+
+static int parse_mac(const char *str, uint8_t *mac)
+{
+ int vals[6];
+ if (sscanf(str, "%x:%x:%x:%x:%x:%x", &vals[0], &vals[1], &vals[2], &vals[3], &vals[4], &vals[5]) != 6) {
+ log_error("Invalid MAC address: %s", str);
+ return -1;
+ }
+ for (int i = 0; i < 6; i++)
+ mac[i] = (uint8_t)vals[i];
+
+ return 0;
+}
+
+static int parse_ip(const char *str, uint32_t *ip)
+{
+ struct in_addr addr;
+ if (inet_aton(str, &addr) == 0) {
+ log_error("Invalid IPv4 address: %s", str);
+ return -1;
+ }
+ *ip = addr.s_addr;
+
+ return 0;
+}
+
+static int parse_app_args(int argc, char **argv, struct appconf *app_conf, int shift)
+{
+ int c;
+ opterr = 0;
+
+ app_conf->cpu_start = 0;
+ app_conf->cpu_end = 0;
+ app_conf->stats_cpu = 1;
+ app_conf->src_ip = htonl(0xC0A80101);
+ app_conf->dest_ip = htonl(0xC0A80201);
+ app_conf->src_port = htons(1234);
+ app_conf->dest_port = htons(5678);
+ app_conf->payload_len = 5;
+ app_conf->custom_src_ether_addr = false;
+ app_conf->custom_dest_ether_addr = false;
+
+ argc -= shift;
+ argv += shift;
+
+ while ((c = getopt(argc, argv, "hc:e:s:S:D:A:B:P:Q:L:")) != -1)
+ switch (c) {
+ case 'h':
+ printf("Usage: %s -h\n", argv[-shift]);
+ return -1;
+ case 'c':
+ app_conf->cpu_start = atoi(optarg);
+ break;
+ case 'e':
+ app_conf->cpu_end = atoi(optarg);
+ break;
+ case 's':
+ app_conf->stats_cpu = atoi(optarg);
+ break;
+ case 'S':
+ if (parse_mac(optarg, app_conf->src_ether_addr_octet) < 0)
+ return -1;
+ app_conf->custom_src_ether_addr = true;
+ break;
+ case 'D':
+ if (parse_mac(optarg, app_conf->dest_ether_addr_octet) < 0)
+ return -1;
+ app_conf->custom_dest_ether_addr = true;
+ break;
+ case 'A':
+ if (parse_ip(optarg, &app_conf->src_ip) < 0)
+ return -1;
+ break;
+ case 'B':
+ if (parse_ip(optarg, &app_conf->dest_ip) < 0)
+ return -1;
+ break;
+ case 'P':
+ app_conf->src_port = htons(atoi(optarg));
+ break;
+ case 'Q':
+ app_conf->dest_port = htons(atoi(optarg));
+ break;
+ case 'L':
+ app_conf->payload_len = atoi(optarg);
+ if (app_conf->payload_len > 1500) {
+ log_error("Invalid payload length: %d.", app_conf->payload_len);
+ return -1;
+ }
+ break;
+ default:
+ printf("Usage: %s -h\n", argv[-shift]);
+ return -1;
+ }
+
+ return 0;
+}
+
+static uint16_t csum16(const void *data, size_t len)
+{
+ const uint16_t *buf = (const uint16_t *)data;
+ uint32_t sum = 0;
+
+ while (len > 1) {
+ sum += *buf++;
+ len -= 2;
+ }
+
+ if (len)
+ sum += *(const uint8_t *)buf;
+
+ while (sum >> 16)
+ sum = (sum & 0xFFFF) + (sum >> 16);
+
+ return (uint16_t)(~sum);
+}
+
+static int setup_packet(void *data)
+{
+ struct ether_header *eth = (struct ether_header *)data;
+ struct iphdr *ip = (struct iphdr *)(eth + 1);
+ struct udphdr *udp = (struct udphdr *)(ip + 1);
+ struct ether_addr tmp_addr;
+
+ if (app_conf.custom_src_ether_addr)
+ memcpy(eth->ether_shost, app_conf.src_ether_addr_octet, ETH_ALEN);
+ else {
+ if (flash__get_macaddr(cfg, &tmp_addr) < 0) {
+ log_error("Failed to get source MAC address");
+ return -1;
+ }
+ memcpy(eth->ether_shost, tmp_addr.ether_addr_octet, ETH_ALEN);
+ }
+
+ if (app_conf.custom_dest_ether_addr)
+ memcpy(eth->ether_dhost, app_conf.dest_ether_addr_octet, ETH_ALEN);
+ else {
+ if (flash__get_macaddr(cfg, &tmp_addr) < 0) {
+ log_error("Failed to get destination MAC address");
+ return -1;
+ }
+ memcpy(eth->ether_dhost, tmp_addr.ether_addr_octet, ETH_ALEN);
+ }
+ eth->ether_type = htons(ETH_P_IP);
+
+ ip->ihl = 5;
+ ip->version = 4;
+ ip->tos = 0;
+ ip->tot_len = htons(sizeof(struct iphdr) + sizeof(struct udphdr) + app_conf.payload_len);
+ ip->id = htons(0x1234);
+ ip->frag_off = 0;
+ ip->ttl = 64;
+ ip->protocol = IPPROTO_UDP;
+ ip->check = 0;
+ ip->saddr = app_conf.src_ip;
+ ip->daddr = app_conf.dest_ip;
+ ip->check = csum16(ip, sizeof(struct iphdr));
+
+ udp->source = app_conf.src_port;
+ udp->dest = app_conf.dest_port;
+ udp->len = htons(sizeof(struct udphdr) + app_conf.payload_len);
+ udp->check = 0;
+
+ char *payload = (char *)(udp + 1);
+ memset(payload, 'A', app_conf.payload_len);
+
+ return 0;
+}
+
+struct sock_args {
+ int socket_id;
+};
+
+static void *socket_routine(void *arg)
+{
+ struct socket *xsk;
+ struct xskvec *xskvecs;
+ uint32_t i, nalloc, nsend;
+ struct sock_args *a = (struct sock_args *)arg;
+
+ log_debug("Socket ID: %d", a->socket_id);
+ xsk = nf->thread[a->socket_id]->socket;
+
+ xskvecs = calloc(cfg->xsk->batch_size, sizeof(struct xskvec));
+ if (!xskvecs) {
+ log_error("Failed to allocate xskvecs array");
+ return NULL;
+ }
+
+ size_t packet_size = sizeof(struct ether_header) + sizeof(struct iphdr) + sizeof(struct udphdr) + app_conf.payload_len;
+
+ for (;;) {
+ nalloc = flash__allocmsg(cfg, xsk, xskvecs, cfg->xsk->batch_size);
+ for (i = 0; i < nalloc; i++) {
+ memcpy(xskvecs[i].data, packet_template, packet_size);
+ xskvecs[i].len = packet_size;
+ xskvecs[i].options = 0;
+ }
+
+ if (nalloc) {
+ nsend = flash__sendmsg(cfg, xsk, xskvecs, nalloc);
+ if (nsend != nalloc) {
+ log_error("errno: %d/\"%s\"", errno, strerror(errno));
+ break;
+ }
+ }
+
+ if (done)
+ break;
+ }
+
+ free(xskvecs);
+ return NULL;
+}
+
+int main(int argc, char **argv)
+{
+ int shift;
+ struct sock_args *args;
+ struct stats_conf stats_cfg = { NULL };
+ cpu_set_t cpuset;
+ pthread_t socket_thread, stats_thread;
+ size_t packet_size;
+
+ cfg = calloc(1, sizeof(struct config));
+ if (!cfg) {
+ log_error("ERROR: Memory allocation failed");
+ exit(EXIT_FAILURE);
+ }
+
+ cfg->app_name = "Traffic Generation Application";
+ cfg->app_options = txgen_options;
+ cfg->done = &done;
+
+ shift = flash__parse_cmdline_args(argc, argv, cfg);
+ if (shift < 0)
+ goto out_cfg;
+
+ if (parse_app_args(argc, argv, &app_conf, shift) < 0)
+ goto out_cfg;
+
+ if (cfg->rx_first) {
+ log_error("ERROR: tx_first should be enabled in txgen");
+ goto out_cfg;
+ }
+
+ if (flash__configure_nf(&nf, cfg) < 0)
+ goto out_cfg;
+
+ log_info("Control Plane setup done...");
+
+ packet_size = sizeof(struct ether_header) + sizeof(struct iphdr) + sizeof(struct udphdr) + app_conf.payload_len;
+ log_debug("Packet size: %zu bytes", packet_size);
+
+ packet_template = (uint8_t *)calloc(1, packet_size);
+ if (!packet_template) {
+ log_error("ERROR: Memory allocation failed for packet template");
+ goto out_cfg_close;
+ }
+
+ if (setup_packet(packet_template) < 0) {
+ log_error("ERROR: Failed to setup packet template");
+ goto out_pkt;
+ }
+
+ signal(SIGINT, int_exit);
+ signal(SIGTERM, int_exit);
+ signal(SIGABRT, int_exit);
+
+ log_info("Starting Data Path...");
+
+ args = calloc(cfg->total_sockets, sizeof(struct sock_args));
+ if (!args) {
+ log_error("ERROR: Memory allocation failed for sock_args");
+ goto out_pkt;
+ }
+
+ for (int i = 0; i < cfg->total_sockets; i++) {
+ args[i].socket_id = i;
+
+ if (pthread_create(&socket_thread, NULL, socket_routine, &args[i])) {
+ log_error("Error creating socket thread");
+ goto out_args;
+ }
+
+ CPU_ZERO(&cpuset);
+ CPU_SET((i % (app_conf.cpu_end - app_conf.cpu_start + 1)) + app_conf.cpu_start, &cpuset);
+ if (pthread_setaffinity_np(socket_thread, sizeof(cpu_set_t), &cpuset) != 0) {
+ log_error("ERROR: Unable to set thread affinity: %s", strerror(errno));
+ goto out_args;
+ }
+
+ if (pthread_detach(socket_thread) != 0) {
+ log_error("ERROR: Unable to detach thread: %s", strerror(errno));
+ goto out_args;
+ }
+ }
+
+ stats_cfg.nf = nf;
+ stats_cfg.cfg = cfg;
+
+ if (pthread_create(&stats_thread, NULL, flash__stats_thread, &stats_cfg)) {
+ log_error("Error creating statistics thread");
+ goto out_args;
+ }
+ CPU_ZERO(&cpuset);
+ CPU_SET(app_conf.stats_cpu, &cpuset);
+ if (pthread_setaffinity_np(stats_thread, sizeof(cpu_set_t), &cpuset) != 0) {
+ log_error("ERROR: Unable to set thread affinity: %s", strerror(errno));
+ goto out_args;
+ }
+
+ if (pthread_detach(stats_thread) != 0) {
+ log_error("ERROR: Unable to detach thread: %s", strerror(errno));
+ goto out_args;
+ }
+
+ flash__wait(cfg);
+ flash__xsk_close(cfg, nf);
+
+ exit(EXIT_SUCCESS);
+
+out_args:
+ done = true;
+ free(args);
+out_pkt:
+ free(packet_template);
+out_cfg_close:
+ sleep(1);
+ flash__xsk_close(cfg, nf);
+out_cfg:
+ free(cfg);
+ exit(EXIT_FAILURE);
+}
diff --git a/examples/txgen/meson.build b/examples/txgen/meson.build
new file mode 100644
index 0000000..d90acbe
--- /dev/null
+++ b/examples/txgen/meson.build
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: Apache-2.0
+# Copyright (c) 2025 Debojeet Das
+
+sources = files('main.c')
+
+executable('txgen', sources, c_args: cflags, install: true, dependencies: deps)
\ No newline at end of file
diff --git a/examples/unit-tests/backpressure.c b/examples/unit-tests/backpressure.c
index b23546f..d5f91f7 100644
--- a/examples/unit-tests/backpressure.c
+++ b/examples/unit-tests/backpressure.c
@@ -20,7 +20,7 @@
#define TEST_PORT 8080
-bool done = false;
+volatile bool done = false;
struct config *cfg = NULL;
struct nf *nf;
struct test_stats *stats_arr;
@@ -32,21 +32,27 @@ static void int_exit(int sig)
}
struct testHeader {
- __u8 lastHop;
- __u8 hopCount;
- __u64 pktId;
- __u16 old_dst;
+ uint8_t lastHop;
+ uint8_t hopCount;
+ uint64_t pktId;
+ uint16_t old_dst;
+ int sender_nf_id;
+ int sender_next_size;
};
+#define MAX_NFS 16
+struct nf_info {
+ int sender_next_size;
+ bool first_packet_received;
+ uint64_t expected_mod_value;
+ uint64_t next_expected_pkt_id;
+} nf_info_arr[MAX_NFS] = { 0 };
+
struct test_stats {
- __u64 pkt_count;
- __u64 even_next; // Next expected even packet ID
- __u64 odd_next; // Next expected odd packet ID
- __u64 pkt_dropped;
- __u64 pkt_corrupted;
- __u64 pkt_correct;
- __u64 even;
- __u64 odd;
+ uint64_t pkt_count;
+ uint64_t pkt_dropped;
+ uint64_t pkt_corrupted;
+ uint64_t pkt_correct;
};
struct appconf {
@@ -107,7 +113,21 @@ static void burn_cycles(__u64 cycles_to_burn)
}
}
-static void parse_app_args(int argc, char **argv, struct appconf *app_conf, int shift)
+// clang-format off
+static const char *backpressure_options[] = {
+ "-c \tStart CPU (default: 0)",
+ "-e \tEnd CPU (default: 0)",
+ "-s \tStats CPU (default: 1)",
+ "-h \tNumber of hops (default: 1)",
+ "-B \tBurn cycles (default: 10000000)",
+ "-v\t\tEnable variable-length packets (default: disabled)",
+ "-a \tVariable start value (default: 0)",
+ "-z \tVariable end value (default: 0)",
+ NULL
+};
+// clang-format on
+
+static int parse_app_args(int argc, char **argv, struct appconf *app_conf, int shift)
{
int c;
opterr = 0;
@@ -152,8 +172,10 @@ static void parse_app_args(int argc, char **argv, struct appconf *app_conf, int
app_conf->variable_end = atoi(optarg);
break;
default:
- abort();
+ printf("Usage: %s -h\n", argv[-shift]);
+ return -1;
}
+ return 0;
}
static void process_packets(void *data, __u32 *len, struct test_stats *stats)
@@ -163,11 +185,13 @@ static void process_packets(void *data, __u32 *len, struct test_stats *stats)
struct ethhdr *eth = (struct ethhdr *)pos;
if ((void *)(eth + 1) > data_end) {
+ log_error("Ethernet header is not valid");
stats->pkt_dropped++;
return;
}
if (eth->h_proto != htons(ETH_P_IP)) {
+ log_error("Ethernet protocol is not IP");
stats->pkt_dropped++;
return;
}
@@ -178,6 +202,7 @@ static void process_packets(void *data, __u32 *len, struct test_stats *stats)
size_t hdrsize;
if ((void *)iph + 1 > data_end) {
+ log_error("IP header is not valid");
stats->pkt_dropped++;
return;
}
@@ -185,17 +210,20 @@ static void process_packets(void *data, __u32 *len, struct test_stats *stats)
hdrsize = iph->ihl * 4;
/* Sanity check packet field is valid */
if (hdrsize < sizeof(*iph)) {
+ log_error("IP header size is invalid");
stats->pkt_dropped++;
return;
}
if (iph->protocol != IPPROTO_UDP) {
+ log_error("IP protocol is not UDP");
stats->pkt_dropped++;
return;
}
/* Variable-length IPv4 header, need to use byte-based arithmetic */
if (pos + hdrsize > data_end) {
+ log_error("IP header is not valid");
stats->pkt_dropped++;
return;
}
@@ -206,6 +234,7 @@ static void process_packets(void *data, __u32 *len, struct test_stats *stats)
struct udphdr *udphdr = pos;
if ((void *)udphdr + 1 > data_end) {
+ log_error("UDP header is not valid");
stats->pkt_dropped++;
return;
}
@@ -214,155 +243,188 @@ static void process_packets(void *data, __u32 *len, struct test_stats *stats)
payload_len = ntohs(udphdr->len) - sizeof(struct udphdr);
size_t testHeaderLen = sizeof(struct testHeader);
+ void *payload_end = pos + payload_len;
+
+ struct testHeader *testHeader = NULL;
/* First NF */
if (ntohs(udphdr->dest) != TEST_PORT) {
- // Shift the data to add the test header. Can we do this without memmove??
- memmove(pos + testHeaderLen, pos, payload_len);
-
- // Add test header and update the old length
- struct testHeader *testHeader = pos;
+ // Append test header at the end of the UDP payload
+ testHeader = (struct testHeader *)payload_end;
testHeader->lastHop = app_conf.hops;
testHeader->hopCount = 1;
- testHeader->pktId = stats->pkt_count++;
+ testHeader->old_dst = udphdr->dest;
+
*len += testHeaderLen;
+ udphdr->len = htons(ntohs(udphdr->len) + testHeaderLen);
+ iph->tot_len = htons(ntohs(iph->tot_len) + testHeaderLen);
- // update the udp header
- testHeader->old_dst = udphdr->dest;
udphdr->dest = htons(TEST_PORT);
- udphdr->len = htons(ntohs(udphdr->len) + testHeaderLen);
- // update the ip payload length
- iph->tot_len = htons(ntohs(iph->tot_len) + testHeaderLen);
+ stats->pkt_correct++;
} else {
- struct testHeader *testHeader = pos;
+ // check if the test header is present
+ if (payload_len < testHeaderLen) {
+ stats->pkt_dropped++;
+ log_error("ERROR: Test header not found in packet");
+ return;
+ }
+
+ // testHeader is at the end of the UDP payload
+ testHeader = (struct testHeader *)(payload_end - testHeaderLen);
testHeader->hopCount++;
- if (testHeader->pktId % 2 == 0) { // Even packet
- if (testHeader->pktId != stats->even_next) {
- if (testHeader->pktId < stats->even_next) {
- stats->pkt_corrupted++;
- stats->even_next = testHeader->pktId + 2;
- } else {
- stats->pkt_corrupted++;
- stats->even_next = testHeader->pktId + 2;
- }
- } else {
- stats->even++;
- stats->pkt_correct++;
- stats->even_next += 2;
+ uint64_t received_pktId = testHeader->pktId;
+ int sender_nf_id = testHeader->sender_nf_id;
+ int sender_next_size = testHeader->sender_next_size;
+
+ if (sender_nf_id < 0 || sender_nf_id >= MAX_NFS) {
+ log_error("ERROR: Invalid sender NF ID %d", sender_nf_id);
+ stats->pkt_corrupted++;
+ goto test_header_update;
+ }
+ if (sender_next_size <= 0) {
+ log_error("ERROR: Invalid sender next size %d", sender_next_size);
+ stats->pkt_corrupted++;
+ goto test_header_update;
+ }
+
+ struct nf_info *sender_info = &nf_info_arr[sender_nf_id];
+
+ if (!sender_info->first_packet_received) {
+ sender_info->first_packet_received = true;
+ sender_info->sender_next_size = sender_next_size;
+ sender_info->expected_mod_value = received_pktId % sender_next_size;
+ sender_info->next_expected_pkt_id = received_pktId + sender_next_size;
+ stats->pkt_correct++; // first packet is always correct
+ } else {
+ if (sender_next_size != sender_info->sender_next_size) {
+ log_error("ERROR: nf_next_size mismatch for NF ID %d: expected %d, got %d", sender_nf_id,
+ sender_info->sender_next_size, sender_next_size);
+ stats->pkt_corrupted++;
+ goto test_header_update;
+ }
+
+ if (received_pktId % sender_next_size != sender_info->expected_mod_value) {
+ log_error("ERROR: pktId %% sender_next_size mismatch for NF ID %d: expected %lu, got %lu",
+ sender_nf_id, sender_info->expected_mod_value, received_pktId % sender_next_size);
+ stats->pkt_corrupted++;
+ goto test_header_update;
}
- } else { // Odd packet
- if (testHeader->pktId != stats->odd_next) {
- if (testHeader->pktId < stats->odd_next) {
+
+ uint64_t next_expected_pkt_id = sender_info->next_expected_pkt_id;
+
+ if (received_pktId != next_expected_pkt_id) {
+ if (received_pktId < next_expected_pkt_id) {
+ log_error("ERROR: Received pktId %lu is less than expected %lu for NF ID %d", received_pktId,
+ next_expected_pkt_id, sender_nf_id);
stats->pkt_corrupted++;
- stats->odd_next = testHeader->pktId + 2;
} else {
- stats->pkt_corrupted++;
- stats->odd_next = testHeader->pktId + 2;
+ sender_info->next_expected_pkt_id = received_pktId + sender_next_size;
+ log_info("Received pktId %lu, updating next_expected_pkt_id to %lu for NF ID %d",
+ received_pktId, sender_info->next_expected_pkt_id, sender_nf_id);
+ stats->pkt_dropped += (received_pktId - next_expected_pkt_id) / sender_next_size;
}
} else {
- stats->odd++;
+ sender_info->next_expected_pkt_id += sender_next_size;
stats->pkt_correct++;
- stats->odd_next += 2;
}
}
+ }
- if (testHeader->lastHop == testHeader->hopCount) {
- uint8_t tmp_mac[ETH_ALEN];
- struct in_addr tmp_ip;
- unsigned short tmp_port;
- payload_len -= testHeaderLen;
+test_header_update:
+ testHeader->pktId = stats->pkt_count++;
+ testHeader->sender_nf_id = cfg->nf_id;
+ testHeader->sender_next_size = nf->next_size;
- tmp_port = testHeader->old_dst;
+ if (testHeader->lastHop == testHeader->hopCount) {
+ uint8_t tmp_mac[ETH_ALEN];
+ struct in_addr tmp_ip;
+ unsigned short tmp_port;
+ payload_len -= testHeaderLen;
- // Shift the data to remove the test header
- memmove(pos, pos + testHeaderLen, payload_len);
+ tmp_port = testHeader->old_dst;
- // update the udp header
- udphdr->dest = tmp_port;
- udphdr->len = htons(ntohs(udphdr->len) - testHeaderLen);
- *len -= testHeaderLen;
+ udphdr->dest = tmp_port;
+ udphdr->len = htons(ntohs(udphdr->len) - testHeaderLen);
+ *len -= testHeaderLen;
- tmp_port = udphdr->dest;
- udphdr->dest = udphdr->source;
- udphdr->source = tmp_port;
+ tmp_port = udphdr->dest;
+ udphdr->dest = udphdr->source;
+ udphdr->source = tmp_port;
- // update the ip payload length
- iph->tot_len = htons(ntohs(iph->tot_len) - testHeaderLen);
+ iph->tot_len = htons(ntohs(iph->tot_len) - testHeaderLen);
- memcpy(tmp_mac, eth->h_dest, ETH_ALEN);
- memcpy(eth->h_dest, eth->h_source, ETH_ALEN);
- memcpy(eth->h_source, tmp_mac, ETH_ALEN);
+ memcpy(tmp_mac, eth->h_dest, ETH_ALEN);
+ memcpy(eth->h_dest, eth->h_source, ETH_ALEN);
+ memcpy(eth->h_source, tmp_mac, ETH_ALEN);
- memcpy(&tmp_ip, &iph->saddr, sizeof(tmp_ip));
- memcpy(&iph->saddr, &iph->daddr, sizeof(tmp_ip));
- memcpy(&iph->daddr, &tmp_ip, sizeof(tmp_ip));
- }
+ memcpy(&tmp_ip, &iph->saddr, sizeof(tmp_ip));
+ memcpy(&iph->saddr, &iph->daddr, sizeof(tmp_ip));
+ memcpy(&iph->daddr, &tmp_ip, sizeof(tmp_ip));
}
return;
}
-struct Args {
+struct sock_args {
int socket_id;
- int *next;
int next_size;
};
static void *socket_routine(void *arg)
{
- struct Args *a = (struct Args *)arg;
- int socket_id = a->socket_id;
- int *next = a->next;
- int next_size = a->next_size;
- log_info("SOCKET_ID: %d", socket_id);
- static __u32 nb_frags;
- int i, ret, nfds = 1, nrecv;
+ nfds_t nfds = 1;
+ int ret, next_size;
+ struct socket *xsk;
+ struct xskvec *xskvecs, *sendvecs;
struct pollfd fds[1] = {};
- struct xskmsghdr msg = {};
+ uint32_t i, nrecv, nsend, count, nb_frags = 0, wsend;
+ struct sock_args *a = (struct sock_args *)arg;
- log_info("2_NEXT_SIZE: %d", next_size);
+ next_size = a->next_size;
- for (int i = 0; i < next_size; i++) {
- log_info("2_NEXT_ITEM_%d %d", i, next[i]);
- }
+ log_debug("SOCKET_ID: %d", a->socket_id);
+ xsk = nf->thread[a->socket_id]->socket;
cfg->xsk->poll_timeout = -1;
- msg.msg_iov = calloc(cfg->xsk->batch_size, sizeof(struct xskvec));
+ xskvecs = calloc(cfg->xsk->batch_size, sizeof(struct xskvec));
+ if (!xskvecs) {
+ log_error("ERROR: Memory allocation failed for xskvecs");
+ return NULL;
+ }
- fds[0].fd = nf->thread[socket_id]->socket->fd;
- fds[0].events = POLLIN;
+ sendvecs = calloc(cfg->xsk->batch_size, sizeof(struct xskvec));
+ if (!sendvecs) {
+ log_error("ERROR: Memory allocation failed for sendvecs");
+ free(xskvecs);
+ return NULL;
+ }
- nf->thread[socket_id]->socket->idle_fd.fd = nf->thread[socket_id]->socket->fd;
- nf->thread[socket_id]->socket->idle_fd.events = POLLIN;
+ fds[0].fd = xsk->fd;
+ fds[0].events = POLLIN;
- unsigned int count = 0;
+ count = 0;
for (;;) {
- if (cfg->xsk->mode & FLASH__POLL) {
- ret = flash__poll(nf->thread[socket_id]->socket, fds, nfds, cfg->xsk->poll_timeout);
- if (ret != 1)
- continue;
- }
+ ret = flash__poll(cfg, xsk, fds, nfds);
+ if (!(ret == 1 || ret == -2))
+ continue;
- nrecv = flash__recvmsg(cfg, nf->thread[socket_id]->socket, &msg);
+ nrecv = flash__recvmsg(cfg, xsk, xskvecs, cfg->xsk->batch_size);
- struct xskvec *send[nrecv];
- unsigned int tot_pkt_send = 0;
- for (i = 0; i < nrecv; i++) {
- struct xskvec *xv = &msg.msg_iov[i];
- bool eop = IS_EOP_DESC(xv->options);
+ wsend = 0;
+ for (i = 0; i < nrecv; i++) {
if (next_size != 0) {
- xv->options = ((count % next_size) << 16) | (xv->options & 0xFFFF);
+ xskvecs[i].options = ((count % next_size) << 16) | (xskvecs[i].options & 0xFFFF);
count++;
}
- char *pkt = xv->data;
+ char *pkt = xskvecs[i].data;
if (!nb_frags++)
- process_packets(pkt, &xv->len, &stats_arr[socket_id]);
+ process_packets(pkt, &xskvecs[i].len, &stats_arr[a->socket_id]);
if (app_conf.variable) {
__u64 random_cycles =
@@ -373,29 +435,33 @@ static void *socket_routine(void *arg)
burn_cycles(app_conf.burn_cycles);
}
- send[tot_pkt_send++] = &msg.msg_iov[i];
- if (eop)
+ sendvecs[wsend++] = xskvecs[i];
+
+ if (IS_EOP_DESC(xskvecs[i].options))
nb_frags = 0;
}
if (nrecv) {
- ret = flash__sendmsg(cfg, nf->thread[socket_id]->socket, send, tot_pkt_send);
- if (ret != nrecv) {
+ nsend = flash__sendmsg(cfg, xsk, sendvecs, wsend);
+ if (nsend != nrecv) {
log_error("errno: %d/\"%s\"\n", errno, strerror(errno));
- exit(EXIT_FAILURE);
+ break;
}
}
if (done)
break;
}
- free(msg.msg_iov);
+ free(xskvecs);
+ free(sendvecs);
return NULL;
}
-static void *worker__stats(void *arg)
+static void *worker__stats(void *conf)
{
- (void)arg;
+ struct stats_conf *arg = (struct stats_conf *)conf;
+ struct nf *nf = arg->nf;
+ struct config *cfg = arg->cfg;
if (cfg->verbose) {
unsigned int interval = cfg->stats_interval;
@@ -410,11 +476,9 @@ static void *worker__stats(void *arg)
log_error("Terminal clear error");
for (int i = 0; i < cfg->total_sockets; i++) {
flash__dump_stats(cfg, nf->thread[i]->socket);
- printf("%-18s %'-14llu\n", "dropped", stats_arr[i].pkt_dropped);
- printf("%-18s %'-14llu\n", "corrupt", stats_arr[i].pkt_corrupted);
- printf("%-18s %'-14llu\n", "correct", stats_arr[i].pkt_correct);
- printf("%-18s %'-14llu\n", "even", stats_arr[i].even);
- printf("%-18s %'-14llu\n", "odd", stats_arr[i].odd);
+ printf("%-18s %'-14lu\n", "dropped", stats_arr[i].pkt_dropped);
+ printf("%-18s %'-14lu\n", "corrupt", stats_arr[i].pkt_corrupted);
+ printf("%-18s %'-14lu\n", "correct", stats_arr[i].pkt_correct);
}
}
}
@@ -423,21 +487,37 @@ static void *worker__stats(void *arg)
int main(int argc, char **argv)
{
+ int shift;
+ struct sock_args *args;
+ struct stats_conf stats_cfg = { NULL };
cpu_set_t cpuset;
+ pthread_t socket_thread, stats_thread;
+
cfg = calloc(1, sizeof(struct config));
if (!cfg) {
log_error("ERROR: Memory allocation failed\n");
exit(EXIT_FAILURE);
}
- int n = flash__parse_cmdline_args(argc, argv, cfg);
- parse_app_args(argc, argv, &app_conf, n);
- flash__configure_nf(&nf, cfg);
- flash__populate_fill_ring(nf->thread, cfg->umem->frame_size, cfg->total_sockets, cfg->umem_offset, cfg->umem_scale);
+ cfg->app_name = "Backpressure Application";
+ cfg->app_options = backpressure_options;
+ cfg->done = &done;
+
+ shift = flash__parse_cmdline_args(argc, argv, cfg);
+ if (shift < 0)
+ goto out_cfg;
+
+ if (parse_app_args(argc, argv, &app_conf, shift) < 0)
+ goto out_cfg;
+
+ if (flash__configure_nf(&nf, cfg) < 0)
+ goto out_cfg;
stats_arr = calloc(cfg->total_sockets, sizeof(struct test_stats));
- stats_arr->even_next = 0;
- stats_arr->odd_next = 1;
+ if (!stats_arr) {
+ log_error("ERROR: Memory allocation failed for stats_arr");
+ goto out_cfg;
+ }
log_info("Control Plane Setup Done");
@@ -447,49 +527,67 @@ int main(int argc, char **argv)
log_info("STARTING Data Path");
- for (int i = 0; i < cfg->total_sockets; i++) {
- struct Args *args = calloc(1, sizeof(struct Args));
- args->socket_id = i;
- args->next = nf->next;
- args->next_size = nf->next_size;
+ args = calloc(cfg->total_sockets, sizeof(struct sock_args));
+ if (!args) {
+ log_error("ERROR: Memory allocation failed for sock_args");
+ goto out_cfg_close;
+ }
- log_info("2_NEXT_SIZE: %d", args->next_size);
+ for (int i = 0; i < cfg->total_sockets; i++) {
+ args[i].socket_id = i;
+ args[i].next_size = nf->next_size;
- for (int i = 0; i < args->next_size; i++) {
- log_info("2_NEXT_ITEM_%d %d", i, nf->next[i]);
- }
+ log_info("2_NEXT_SIZE: %d", args[i].next_size);
- pthread_t socket_thread;
- if (pthread_create(&socket_thread, NULL, socket_routine, args)) {
+ if (pthread_create(&socket_thread, NULL, socket_routine, &args[i])) {
log_error("Error creating socket thread");
- exit(EXIT_FAILURE);
+ goto out_args;
}
CPU_ZERO(&cpuset);
CPU_SET((i % (app_conf.cpu_end - app_conf.cpu_start + 1)) + app_conf.cpu_start, &cpuset);
if (pthread_setaffinity_np(socket_thread, sizeof(cpu_set_t), &cpuset) != 0) {
log_error("ERROR: Unable to set thread affinity: %s\n", strerror(errno));
- exit(EXIT_FAILURE);
+ goto out_args;
}
- pthread_detach(socket_thread);
+ if (pthread_detach(socket_thread) != 0) {
+ log_error("ERROR: Unable to detach thread: %s", strerror(errno));
+ goto out_args;
+ }
}
- pthread_t stats_thread;
- if (pthread_create(&stats_thread, NULL, worker__stats, NULL)) {
+ stats_cfg.nf = nf;
+ stats_cfg.cfg = cfg;
+
+ if (pthread_create(&stats_thread, NULL, worker__stats, &stats_cfg)) {
log_error("Error creating statistics thread");
- exit(EXIT_FAILURE);
+ goto out_args;
}
CPU_ZERO(&cpuset);
CPU_SET(app_conf.stats_cpu, &cpuset);
if (pthread_setaffinity_np(stats_thread, sizeof(cpu_set_t), &cpuset) != 0) {
log_error("ERROR: Unable to set thread affinity: %s\n", strerror(errno));
- exit(EXIT_FAILURE);
+ goto out_args;
+ }
+ if (pthread_detach(stats_thread) != 0) {
+ log_error("ERROR: Unable to detach thread: %s", strerror(errno));
+ goto out_args;
}
- pthread_detach(stats_thread);
- wait_for_cmd(cfg);
+ flash__wait(cfg);
flash__xsk_close(cfg, nf);
return EXIT_SUCCESS;
+
+out_args:
+ done = true;
+ free(args);
+out_cfg_close:
+ free(stats_arr);
+ sleep(1);
+ flash__xsk_close(cfg, nf);
+out_cfg:
+ free(cfg);
+ exit(EXIT_FAILURE);
}
diff --git a/examples/unit-tests/correctness.c b/examples/unit-tests/correctness.c
index d649176..1f212d1 100644
--- a/examples/unit-tests/correctness.c
+++ b/examples/unit-tests/correctness.c
@@ -21,11 +21,10 @@
#define TEST_PORT 8080
-bool done = false;
+volatile bool done = false;
struct config *cfg = NULL;
-struct nf *nf;
+struct nf *nf = NULL;
struct test_stats *stats_arr;
-// bool *bool_array;
static void int_exit(int sig)
{
@@ -34,21 +33,27 @@ static void int_exit(int sig)
}
struct testHeader {
- __u8 lastHop;
- __u8 hopCount;
- __u64 pktId;
- __u16 old_dst;
+ uint8_t lastHop;
+ uint8_t hopCount;
+ uint64_t pktId;
+ uint16_t old_dst;
+ int sender_nf_id;
+ int sender_next_size;
};
+#define MAX_NFS 16
+struct nf_info {
+ int sender_next_size;
+ bool first_packet_received;
+ uint64_t expected_mod_value;
+ uint64_t next_expected_pkt_id;
+} nf_info_arr[MAX_NFS] = { 0 };
+
struct test_stats {
- __u64 pkt_count;
- __u64 even_next; // Next expected even packet ID
- __u64 odd_next; // Next expected odd packet ID
- __u64 pkt_dropped;
- __u64 pkt_corrupted;
- __u64 pkt_correct;
- __u64 even;
- __u64 odd;
+ uint64_t pkt_count;
+ uint64_t pkt_dropped;
+ uint64_t pkt_corrupted;
+ uint64_t pkt_correct;
};
struct appconf {
@@ -58,7 +63,17 @@ struct appconf {
int hops;
} app_conf;
-static void parse_app_args(int argc, char **argv, struct appconf *app_conf, int shift)
+// clang-format off
+static const char *correctness_options[] = {
+ "-c \tStart CPU (default: 0)",
+ "-e \tEnd CPU (default: 0)",
+ "-s \tStats CPU (default: 1)",
+ "-h \tNumber of hops (default: 1)",
+ NULL
+};
+// clang-format on
+
+static int parse_app_args(int argc, char **argv, struct appconf *app_conf, int shift)
{
int c;
opterr = 0;
@@ -67,6 +82,7 @@ static void parse_app_args(int argc, char **argv, struct appconf *app_conf, int
app_conf->cpu_start = 0;
app_conf->cpu_end = 0;
app_conf->stats_cpu = 1;
+ app_conf->hops = 1;
argc -= shift;
argv += shift;
@@ -86,27 +102,10 @@ static void parse_app_args(int argc, char **argv, struct appconf *app_conf, int
app_conf->hops = atoi(optarg);
break;
default:
- abort();
+ printf("Usage: %s -h\n", argv[-shift]);
+ return -1;
}
-}
-
-static void __hex_dump(void *pkt, size_t length)
-{
- const unsigned char *address = (unsigned char *)pkt;
- size_t line_size = 32;
- int i = 0;
-
- while (length-- > 0) {
- printf("%02X ", *address++);
- if (!(++i % line_size) || (length == 0 && i % line_size)) {
- if (length == 0) {
- while (i++ % line_size)
- printf("__ ");
- }
- printf("\n");
- }
- }
- printf("\n");
+ return 0;
}
static void process_packets(void *data, __u32 *len, struct test_stats *stats)
@@ -114,12 +113,6 @@ static void process_packets(void *data, __u32 *len, struct test_stats *stats)
void *pos = data;
void *data_end = data + *len;
- // if (*before1_1 < 2) {
- // printf("before: %lld, len %d\n", stats->pkt_count, *len);
- // __hex_dump(data, *len);
- // *before1_1 = *before1_1 + 1;
- // }
-
struct ethhdr *eth = (struct ethhdr *)pos;
if ((void *)(eth + 1) > data_end) {
stats->pkt_dropped++;
@@ -173,241 +166,192 @@ static void process_packets(void *data, __u32 *len, struct test_stats *stats)
payload_len = ntohs(udphdr->len) - sizeof(struct udphdr);
size_t testHeaderLen = sizeof(struct testHeader);
+ void *payload_end = pos + payload_len;
+
+ struct testHeader *testHeader = NULL;
/* First NF */
if (ntohs(udphdr->dest) != TEST_PORT) {
- // Shift the data to add the test header. Can we do this without memmove??
- memmove(pos + testHeaderLen, pos, payload_len);
-
- // Add test header and update the old length
- struct testHeader *testHeader = pos;
+ // Append test header at the end of the UDP payload
+ testHeader = (struct testHeader *)payload_end;
testHeader->lastHop = app_conf.hops;
testHeader->hopCount = 1;
- testHeader->pktId = stats->pkt_count++;
+ testHeader->old_dst = udphdr->dest;
+
*len += testHeaderLen;
+ udphdr->len = htons(ntohs(udphdr->len) + testHeaderLen);
+ iph->tot_len = htons(ntohs(iph->tot_len) + testHeaderLen);
- // update the udp header
- testHeader->old_dst = udphdr->dest;
udphdr->dest = htons(TEST_PORT);
- udphdr->len = htons(ntohs(udphdr->len) + testHeaderLen);
- // update the ip payload length
- iph->tot_len = htons(ntohs(iph->tot_len) + testHeaderLen);
+ stats->pkt_correct++;
} else {
- struct testHeader *testHeader = pos;
+ // testHeader is at the end of the UDP payload
+ testHeader = (struct testHeader *)(payload_end - testHeaderLen);
testHeader->hopCount++;
- // Verify if the pktId is equal to the pkt_count++ and update the pkt_count
- // if (testHeader->pktId != stats->pkt_count) {
- // if (testHeader->pktId < stats->pkt_count) {
- // stats->pkt_corrupted++;
- // stats->pkt_count = testHeader->pktId + 1;
- // } else {
- // stats->pkt_corrupted++;
- // stats->pkt_count = testHeader->pktId + 1;
- // }
- // } else {
- // stats->pkt_count = testHeader->pktId + 1;
- // stats->pkt_correct++;
- // }
-
- // if (bool_array[testHeader->pktId]) {
- // stats->pkt_corrupted++;
- // } else {
- // bool_array[testHeader->pktId] = true;
- // stats->pkt_correct++;
- // if (testHeader->pktId % 2 == 0) {
- // stats->even++;
- // } else {
- // stats->odd++;
- // }
- // }
-
- if (testHeader->pktId % 2 == 0) { // Even packet
- if (testHeader->pktId != stats->even_next) {
- __hex_dump(data, *len);
- if (testHeader->pktId < stats->even_next) {
- stats->pkt_corrupted++;
- stats->even_next = testHeader->pktId + 2;
- } else {
- stats->pkt_corrupted++;
- stats->even_next = testHeader->pktId + 2;
- }
- } else {
- stats->even++;
- stats->pkt_correct++;
- stats->even_next += 2;
+ uint64_t received_pktId = testHeader->pktId;
+ int sender_nf_id = testHeader->sender_nf_id;
+ int sender_next_size = testHeader->sender_next_size;
+
+ if (sender_nf_id < 0 || sender_nf_id >= MAX_NFS) {
+ log_error("ERROR: Invalid sender NF ID %d", sender_nf_id);
+ stats->pkt_corrupted++;
+ goto test_header_update;
+ }
+ if (sender_next_size <= 0) {
+ log_error("ERROR: Invalid sender next size %d", sender_next_size);
+ stats->pkt_corrupted++;
+ goto test_header_update;
+ }
+
+ struct nf_info *sender_info = &nf_info_arr[sender_nf_id];
+
+ if (!sender_info->first_packet_received) {
+ sender_info->first_packet_received = true;
+ sender_info->sender_next_size = sender_next_size;
+ sender_info->expected_mod_value = received_pktId % sender_next_size;
+ sender_info->next_expected_pkt_id = received_pktId + sender_next_size;
+ stats->pkt_correct++; // first packet is always correct
+ } else {
+ if (sender_next_size != sender_info->sender_next_size) {
+ log_error("ERROR: nf_next_size mismatch for NF ID %d: expected %d, got %d", sender_nf_id,
+ sender_info->sender_next_size, sender_next_size);
+ stats->pkt_corrupted++;
+ goto test_header_update;
}
- } else { // Odd packet
- if (testHeader->pktId != stats->odd_next) {
- if (testHeader->pktId < stats->odd_next) {
- __hex_dump(data, *len);
+
+ if (received_pktId % sender_next_size != sender_info->expected_mod_value) {
+ log_error("ERROR: pktId %% sender_next_size mismatch for NF ID %d: expected %lu, got %lu",
+ sender_nf_id, sender_info->expected_mod_value, received_pktId % sender_next_size);
+ stats->pkt_corrupted++;
+ goto test_header_update;
+ }
+
+ uint64_t next_expected_pkt_id = sender_info->next_expected_pkt_id;
+
+ if (received_pktId != next_expected_pkt_id) {
+ if (received_pktId < next_expected_pkt_id) {
stats->pkt_corrupted++;
- stats->odd_next = testHeader->pktId + 2;
} else {
- stats->pkt_corrupted++;
- stats->odd_next = testHeader->pktId + 2;
+ sender_info->next_expected_pkt_id = received_pktId + sender_next_size;
+ stats->pkt_dropped += (received_pktId - next_expected_pkt_id) / sender_next_size;
}
} else {
- stats->odd++;
+ sender_info->next_expected_pkt_id += sender_next_size;
stats->pkt_correct++;
- stats->odd_next += 2;
}
}
+ }
- if (testHeader->lastHop == testHeader->hopCount) {
- uint8_t tmp_mac[ETH_ALEN];
- struct in_addr tmp_ip;
- unsigned short tmp_port;
- payload_len -= testHeaderLen;
+test_header_update:
+ testHeader->pktId = stats->pkt_count++;
+ testHeader->sender_nf_id = cfg->nf_id;
+ testHeader->sender_next_size = nf->next_size;
- tmp_port = testHeader->old_dst;
+ if (testHeader->lastHop == testHeader->hopCount) {
+ uint8_t tmp_mac[ETH_ALEN];
+ struct in_addr tmp_ip;
+ unsigned short tmp_port;
+ payload_len -= testHeaderLen;
- // Shift the data to remove the test header
- memmove(pos, pos + testHeaderLen, payload_len);
+ tmp_port = testHeader->old_dst;
- // update the udp header
- udphdr->dest = tmp_port;
- udphdr->len = htons(ntohs(udphdr->len) - testHeaderLen);
- *len -= testHeaderLen;
+ udphdr->dest = tmp_port;
+ udphdr->len = htons(ntohs(udphdr->len) - testHeaderLen);
+ *len -= testHeaderLen;
- tmp_port = udphdr->dest;
- udphdr->dest = udphdr->source;
- udphdr->source = tmp_port;
+ tmp_port = udphdr->dest;
+ udphdr->dest = udphdr->source;
+ udphdr->source = tmp_port;
- // update the ip payload length
- iph->tot_len = htons(ntohs(iph->tot_len) - testHeaderLen);
+ iph->tot_len = htons(ntohs(iph->tot_len) - testHeaderLen);
- memcpy(tmp_mac, eth->h_dest, ETH_ALEN);
- memcpy(eth->h_dest, eth->h_source, ETH_ALEN);
- memcpy(eth->h_source, tmp_mac, ETH_ALEN);
+ memcpy(tmp_mac, eth->h_dest, ETH_ALEN);
+ memcpy(eth->h_dest, eth->h_source, ETH_ALEN);
+ memcpy(eth->h_source, tmp_mac, ETH_ALEN);
- memcpy(&tmp_ip, &iph->saddr, sizeof(tmp_ip));
- memcpy(&iph->saddr, &iph->daddr, sizeof(tmp_ip));
- memcpy(&iph->daddr, &tmp_ip, sizeof(tmp_ip));
- }
+ memcpy(&tmp_ip, &iph->saddr, sizeof(tmp_ip));
+ memcpy(&iph->saddr, &iph->daddr, sizeof(tmp_ip));
+ memcpy(&iph->daddr, &tmp_ip, sizeof(tmp_ip));
}
- // if (*after1_1 < 2) {
- // printf("after:\n");
- // __hex_dump(data, *len);
- // *after1_1 = *after1_1 + 1;
- // }
-
return;
}
-struct Args {
+struct sock_args {
int socket_id;
- int *next;
int next_size;
};
static void *socket_routine(void *arg)
{
- struct Args *a = (struct Args *)arg;
- int socket_id = a->socket_id;
- int *next = a->next;
- int next_size = a->next_size;
- // free(arg);
- log_info("SOCKET_ID: %d", socket_id);
- static __u32 nb_frags;
- int i, ret, nfds = 1, nrecv;
+ nfds_t nfds = 1;
+ int ret, next_size;
+ struct socket *xsk;
+ struct xskvec *xskvecs;
struct pollfd fds[1] = {};
- struct xskmsghdr msg = {};
+ uint32_t i, nrecv, nsend, count, nb_frags = 0;
+ struct sock_args *a = (struct sock_args *)arg;
- // int idle_timeout = 1;
- // uint64_t idle_timestamp = 0;
+ next_size = a->next_size;
- log_info("2_NEXT_SIZE: %d", next_size);
+ log_debug("SOCKET_ID: %d", a->socket_id);
+ xsk = nf->thread[a->socket_id]->socket;
- for (int i = 0; i < next_size; i++) {
- log_info("2_NEXT_ITEM_%d %d", i, next[i]);
+ xskvecs = calloc(cfg->xsk->batch_size, sizeof(struct xskvec));
+ if (!xskvecs) {
+ log_error("ERROR: Memory allocation failed for xskvecs");
+ return NULL;
}
- cfg->xsk->poll_timeout = -1;
-
- msg.msg_iov = calloc(cfg->xsk->batch_size, sizeof(struct xskvec));
-
- fds[0].fd = nf->thread[socket_id]->socket->fd;
+ fds[0].fd = xsk->fd;
fds[0].events = POLLIN;
- nf->thread[socket_id]->socket->idle_fd.fd = nf->thread[socket_id]->socket->fd;
- nf->thread[socket_id]->socket->idle_fd.events = POLLIN;
-
- unsigned int count = 0;
+ count = 0;
for (;;) {
- if (cfg->xsk->mode & FLASH__POLL) {
- ret = flash__poll(nf->thread[socket_id]->socket, fds, nfds, cfg->xsk->poll_timeout);
- if (ret != 1)
- continue;
- }
-
- // ret = flash__poll(nf->thread[socket_id]->socket, fds, nfds, cfg->xsk->poll_timeout);
- // if (ret <= 0 || ret > 1)
- // continue;
+ ret = flash__poll(cfg, xsk, fds, nfds);
+ if (!(ret == 1 || ret == -2))
+ continue;
- nrecv = flash__recvmsg(cfg, nf->thread[socket_id]->socket, &msg);
+ nrecv = flash__recvmsg(cfg, xsk, xskvecs, cfg->xsk->batch_size);
- // if (nrecv == 0) {
- // uint64_t tstamp = rdtsc();
-
- // if (idle_timeout && idle_timestamp == 0) {
- // idle_timestamp = tstamp + ((get_timer_hz(cfg) / MS_PER_S) * idle_timeout);
- // continue;
- // }
-
- // if (idle_timestamp && (tstamp > idle_timestamp)) {
- // idle_timestamp = 0;
-
- // ret = flash__poll(nf->thread[socket_id]->socket, fds, nfds, cfg->xsk->poll_timeout);
- // if (ret)
- // nrecv = flash__recvmsg(cfg, nf->thread[socket_id]->socket, &msg);
- // else
- // continue;
- // }
- // } else
- // idle_timestamp = 0;
-
- struct xskvec *send[nrecv];
- unsigned int tot_pkt_send = 0;
for (i = 0; i < nrecv; i++) {
- struct xskvec *xv = &msg.msg_iov[i];
- bool eop = IS_EOP_DESC(xv->options);
-
if (next_size != 0) {
- xv->options = ((count % next_size) << 16) | (xv->options & 0xFFFF);
+ xskvecs[i].options = ((count % next_size) << 16) | (xskvecs[i].options & 0xFFFF);
count++;
}
- char *pkt = xv->data;
+
+ char *pkt = xskvecs[i].data;
if (!nb_frags++)
- process_packets(pkt, &xv->len, &stats_arr[socket_id]);
+ process_packets(pkt, &xskvecs[i].len, &stats_arr[a->socket_id]);
- send[tot_pkt_send++] = &msg.msg_iov[i];
- if (eop)
+ if (IS_EOP_DESC(xskvecs[i].options))
nb_frags = 0;
}
if (nrecv) {
- ret = flash__sendmsg(cfg, nf->thread[socket_id]->socket, send, tot_pkt_send);
- if (ret != nrecv) {
+ nsend = flash__sendmsg(cfg, xsk, xskvecs, nrecv);
+ if (nsend != nrecv) {
log_error("errno: %d/\"%s\"\n", errno, strerror(errno));
- exit(EXIT_FAILURE);
+ break;
}
}
if (done)
break;
}
- free(msg.msg_iov);
+ free(xskvecs);
return NULL;
}
-static void *worker__stats(void *arg)
+static void *worker__stats(void *conf)
{
- (void)arg;
+ struct stats_conf *arg = (struct stats_conf *)conf;
+ struct nf *nf = arg->nf;
+ struct config *cfg = arg->cfg;
if (cfg->verbose) {
unsigned int interval = cfg->stats_interval;
@@ -422,11 +366,9 @@ static void *worker__stats(void *arg)
log_error("Terminal clear error");
for (int i = 0; i < cfg->total_sockets; i++) {
flash__dump_stats(cfg, nf->thread[i]->socket);
- printf("%-18s %'-14llu\n", "dropped", stats_arr[i].pkt_dropped);
- printf("%-18s %'-14llu\n", "corrupt", stats_arr[i].pkt_corrupted);
- printf("%-18s %'-14llu\n", "correct", stats_arr[i].pkt_correct);
- printf("%-18s %'-14llu\n", "even", stats_arr[i].even);
- printf("%-18s %'-14llu\n", "odd", stats_arr[i].odd);
+ printf("%-18s %'-14lu\n", "dropped", stats_arr[i].pkt_dropped);
+ printf("%-18s %'-14lu\n", "corrupt", stats_arr[i].pkt_corrupted);
+ printf("%-18s %'-14lu\n", "correct", stats_arr[i].pkt_correct);
}
}
}
@@ -435,26 +377,38 @@ static void *worker__stats(void *arg)
int main(int argc, char **argv)
{
+ int shift;
+ struct sock_args *args;
+ struct stats_conf stats_cfg = { NULL };
cpu_set_t cpuset;
+ pthread_t socket_thread, stats_thread;
+
cfg = calloc(1, sizeof(struct config));
if (!cfg) {
log_error("ERROR: Memory allocation failed\n");
exit(EXIT_FAILURE);
}
- int n = flash__parse_cmdline_args(argc, argv, cfg);
- parse_app_args(argc, argv, &app_conf, n);
- flash__configure_nf(&nf, cfg);
- flash__populate_fill_ring(nf->thread, cfg->umem->frame_size, cfg->total_sockets, cfg->umem_offset, cfg->umem_scale);
+ cfg->app_name = "Correctness Test Application";
+ cfg->app_options = correctness_options;
+ cfg->done = &done;
+
+ shift = flash__parse_cmdline_args(argc, argv, cfg);
+
+ if (shift < 0)
+ goto out_cfg;
+
+ if (parse_app_args(argc, argv, &app_conf, shift) < 0)
+ goto out_cfg;
+
+ if (flash__configure_nf(&nf, cfg) < 0)
+ goto out_cfg;
stats_arr = calloc(cfg->total_sockets, sizeof(struct test_stats));
- stats_arr->even_next = 0;
- stats_arr->odd_next = 1;
- // bool_array = calloc(UINT32_MAX, sizeof(bool));
- // if (!bool_array) {
- // fprintf(stderr, "ERROR: Unable to allocate memory for boolean array\n");
- // exit(EXIT_FAILURE);
- // }
+ if (!stats_arr) {
+ log_error("ERROR: Memory allocation failed for stats_arr");
+ goto out_cfg;
+ }
log_info("Control Plane Setup Done");
@@ -464,49 +418,67 @@ int main(int argc, char **argv)
log_info("STARTING Data Path");
- for (int i = 0; i < cfg->total_sockets; i++) {
- struct Args *args = calloc(1, sizeof(struct Args));
- args->socket_id = i;
- args->next = nf->next;
- args->next_size = nf->next_size;
+ args = calloc(cfg->total_sockets, sizeof(struct sock_args));
+ if (!args) {
+ log_error("ERROR: Memory allocation failed for sock_args");
+ goto out_cfg_close;
+ }
- log_info("2_NEXT_SIZE: %d", args->next_size);
+ for (int i = 0; i < cfg->total_sockets; i++) {
+ args[i].socket_id = i;
+ args[i].next_size = nf->next_size;
- for (int i = 0; i < args->next_size; i++) {
- log_info("2_NEXT_ITEM_%d %d", i, nf->next[i]);
- }
+ log_info("2_NEXT_SIZE: %d", args[i].next_size);
- pthread_t socket_thread;
- if (pthread_create(&socket_thread, NULL, socket_routine, args)) {
+ if (pthread_create(&socket_thread, NULL, socket_routine, &args[i])) {
log_error("Error creating socket thread");
- exit(EXIT_FAILURE);
+ goto out_args;
}
CPU_ZERO(&cpuset);
CPU_SET((i % (app_conf.cpu_end - app_conf.cpu_start + 1)) + app_conf.cpu_start, &cpuset);
if (pthread_setaffinity_np(socket_thread, sizeof(cpu_set_t), &cpuset) != 0) {
log_error("ERROR: Unable to set thread affinity: %s\n", strerror(errno));
- exit(EXIT_FAILURE);
+ goto out_args;
}
- pthread_detach(socket_thread);
+ if (pthread_detach(socket_thread) != 0) {
+ log_error("ERROR: Unable to detach thread: %s", strerror(errno));
+ goto out_args;
+ }
}
- pthread_t stats_thread;
- if (pthread_create(&stats_thread, NULL, worker__stats, NULL)) {
+ stats_cfg.nf = nf;
+ stats_cfg.cfg = cfg;
+
+ if (pthread_create(&stats_thread, NULL, worker__stats, &stats_cfg)) {
log_error("Error creating statistics thread");
- exit(EXIT_FAILURE);
+ goto out_args;
}
CPU_ZERO(&cpuset);
CPU_SET(app_conf.stats_cpu, &cpuset);
if (pthread_setaffinity_np(stats_thread, sizeof(cpu_set_t), &cpuset) != 0) {
log_error("ERROR: Unable to set thread affinity: %s\n", strerror(errno));
- exit(EXIT_FAILURE);
+ goto out_args;
+ }
+ if (pthread_detach(stats_thread) != 0) {
+ log_error("ERROR: Unable to detach thread: %s", strerror(errno));
+ goto out_args;
}
- pthread_detach(stats_thread);
- wait_for_cmd(cfg);
+ flash__wait(cfg);
flash__xsk_close(cfg, nf);
return EXIT_SUCCESS;
+
+out_args:
+ done = true;
+ free(args);
+out_cfg_close:
+ free(stats_arr);
+ sleep(1);
+ flash__xsk_close(cfg, nf);
+out_cfg:
+ free(cfg);
+ exit(EXIT_FAILURE);
}
diff --git a/examples/unit-tests/fwddrop.c b/examples/unit-tests/fwddrop.c
index e514bc7..b426f5e 100644
--- a/examples/unit-tests/fwddrop.c
+++ b/examples/unit-tests/fwddrop.c
@@ -1,26 +1,20 @@
/* SPDX-License-Identifier: Apache-2.0
* Copyright (c) 2025 Debojeet Das
*
- * fwddrop: unit-test to check forward and drop capabilities of Flash framework
- * We store pointers to msg.iov we want to drop in one array, and those we wish to send in another array
+ * fwddrop: unit-test to check forward and drop capabilities of Flash library
*/
-
-#include
-#include
-
#include
#include
#include
-#include
#include
-#include
-#include
-#include
+
+#include
+#include
#include
-bool done = false;
+volatile bool done = false;
struct config *cfg = NULL;
-struct nf *nf;
+struct nf *nf = NULL;
static void int_exit(int sig)
{
@@ -32,23 +26,42 @@ struct appconf {
int cpu_start;
int cpu_end;
int stats_cpu;
+ int fwd_ratio;
+ bool sriov;
+ uint8_t dest_ether_addr_octet[6];
} app_conf;
-static void parse_app_args(int argc, char **argv, struct appconf *app_conf, int shift)
+// clang-format off
+static const char *fwddrop_options[] = {
+ "-c \tStart CPU (default: 0)",
+ "-e \tEnd CPU (default: 0)",
+ "-s \tStats CPU (default: 1)",
+ "-r \tForward ratio percentage (default: 50)",
+ "-S \tEnable SR-IOV mode and set dest MAC address",
+ NULL
+};
+// clang-format on
+
+static int parse_app_args(int argc, char **argv, struct appconf *app_conf, int shift)
{
int c;
+ int ethaddr[6];
opterr = 0;
- // Default values
app_conf->cpu_start = 0;
app_conf->cpu_end = 0;
app_conf->stats_cpu = 1;
+ app_conf->sriov = false;
+ app_conf->fwd_ratio = 50;
argc -= shift;
argv += shift;
- while ((c = getopt(argc, argv, "c:e:s:")) != -1)
+ while ((c = getopt(argc, argv, "hc:e:s:r:S:")) != -1)
switch (c) {
+ case 'h':
+ printf("Usage: %s -h\n", argv[-shift]);
+ return -1;
case 'c':
app_conf->cpu_start = atoi(optarg);
break;
@@ -58,179 +71,237 @@ static void parse_app_args(int argc, char **argv, struct appconf *app_conf, int
case 's':
app_conf->stats_cpu = atoi(optarg);
break;
+ case 'r':
+ app_conf->fwd_ratio = atoi(optarg);
+ if (app_conf->fwd_ratio < 0 || app_conf->fwd_ratio > 100) {
+ log_error("Invalid forward ratio: %d. Must be between 0 and 100.", app_conf->fwd_ratio);
+ return -1;
+ }
+ break;
+ case 'S':
+ if (sscanf(optarg, "%x:%x:%x:%x:%x:%x", ðaddr[0], ðaddr[1], ðaddr[2], ðaddr[3], ðaddr[4],
+ ðaddr[5]) != 6) {
+ log_error("Invalid MAC address format: %s", optarg);
+ return -1;
+ }
+ for (int i = 0; i < 6; i++)
+ app_conf->dest_ether_addr_octet[i] = (uint8_t)ethaddr[i];
+ app_conf->sriov = true;
+ break;
default:
- abort();
+ printf("Usage: %s -h\n", argv[-shift]);
+ return -1;
}
+
+ return 0;
}
-struct Args {
+static void update_dest_mac(void *data)
+{
+ struct ether_header *eth = (struct ether_header *)data;
+ struct ether_addr *dst_addr = (struct ether_addr *)ð->ether_dhost;
+ struct ether_addr tmp = {
+ .ether_addr_octet = {
+ app_conf.dest_ether_addr_octet[0],
+ app_conf.dest_ether_addr_octet[1],
+ app_conf.dest_ether_addr_octet[2],
+ app_conf.dest_ether_addr_octet[3],
+ app_conf.dest_ether_addr_octet[4],
+ app_conf.dest_ether_addr_octet[5],
+ },
+ };
+ *dst_addr = tmp;
+}
+
+static void swap_mac_addresses(void *data)
+{
+ struct ether_header *eth = (struct ether_header *)data;
+ struct ether_addr *src_addr = (struct ether_addr *)ð->ether_shost;
+ struct ether_addr *dst_addr = (struct ether_addr *)ð->ether_dhost;
+ struct ether_addr tmp;
+
+ tmp = *src_addr;
+ *src_addr = *dst_addr;
+ *dst_addr = tmp;
+}
+
+struct sock_args {
int socket_id;
- int *next;
- int next_size;
};
-unsigned int count = 1;
-
static void *socket_routine(void *arg)
{
- struct Args *a = (struct Args *)arg;
- int socket_id = a->socket_id;
- int *next = a->next;
- int next_size = a->next_size;
- log_info("SOCKET_ID: %d", socket_id);
- int i, ret, nfds = 1, nrecv;
+ int ret;
+ nfds_t nfds = 1;
+ struct socket *xsk;
struct pollfd fds[1] = {};
- struct xskmsghdr msg = {};
+ struct xskvec *xskvecs, *sendvecs, *dropvecs;
+ uint32_t i, nrecv, wsend, nsend, wdrop, ndrop, pcount, nb_frags = 0;
+ struct sock_args *a = (struct sock_args *)arg;
- log_info("2_NEXT_SIZE: %d", next_size);
+ log_debug("Socket ID: %d", a->socket_id);
+ xsk = nf->thread[a->socket_id]->socket;
- for (int i = 0; i < next_size; i++) {
- log_info("2_NEXT_ITEM_%d %d", i, next[i]);
+ xskvecs = calloc(cfg->xsk->batch_size, sizeof(struct xskvec));
+ if (!xskvecs) {
+ log_error("Failed to allocate xskvecs array");
+ return NULL;
}
- msg.msg_iov = calloc(cfg->xsk->batch_size, sizeof(struct xskvec));
-
- fds[0].fd = nf->thread[socket_id]->socket->fd;
- fds[0].events = POLLIN;
- for (;;) {
- if (cfg->xsk->mode & FLASH__POLL) {
- ret = flash__poll(nf->thread[socket_id]->socket, fds, nfds, cfg->xsk->poll_timeout);
- if (ret <= 0 || ret > 1)
- continue;
- }
+ sendvecs = calloc(cfg->xsk->batch_size, sizeof(struct xskvec));
+ if (!sendvecs) {
+ log_error("Failed to allocate sendvecs array");
+ return NULL;
+ }
- nrecv = flash__recvmsg(cfg, nf->thread[socket_id]->socket, &msg);
+ dropvecs = calloc(cfg->xsk->batch_size, sizeof(struct xskvec));
+ if (!dropvecs) {
+ log_error("Failed to allocate dropvecs array");
+ return NULL;
+ }
- struct xskvec *drop[nrecv];
- struct xskvec *send[nrecv];
- unsigned int tot_pkt_drop = 0;
- unsigned int tot_pkt_send = 0;
+ fds[0].fd = xsk->fd;
+ fds[0].events = POLLIN;
+ for (;;) {
+ ret = flash__poll(cfg, xsk, fds, nfds);
+ if (!(ret == 1 || ret == -2))
+ continue;
+
+ nrecv = flash__recvmsg(cfg, xsk, xskvecs, cfg->xsk->batch_size);
+ wsend = 0;
+ wdrop = 0;
+ pcount = 0;
for (i = 0; i < nrecv; i++) {
- struct xskvec *xv = &msg.msg_iov[i];
- void *data = xv->data;
+ char *pkt = xskvecs[i].data;
- uint8_t tmp_mac[ETH_ALEN];
- struct ethhdr *eth = (struct ethhdr *)data;
+ if (!nb_frags++)
+ app_conf.sriov ? update_dest_mac(pkt) : swap_mac_addresses(pkt);
- memcpy(tmp_mac, eth->h_dest, ETH_ALEN);
- memcpy(eth->h_dest, eth->h_source, ETH_ALEN);
- memcpy(eth->h_source, tmp_mac, ETH_ALEN);
+ if (IS_EOP_DESC(xskvecs[i].options))
+ nb_frags = 0;
- /* fwd 50% packets and drop 50% packets */
- if (count == 1) {
- send[tot_pkt_send++] = &msg.msg_iov[i];
- count = 0;
+ if ((int)(pcount * 100 / nrecv) < app_conf.fwd_ratio) {
+ sendvecs[wsend++] = xskvecs[i];
} else {
- drop[tot_pkt_drop++] = &msg.msg_iov[i];
- count = 1;
+ dropvecs[wdrop++] = xskvecs[i];
}
+ pcount++;
}
if (nrecv) {
- size_t ret_send = flash__sendmsg(cfg, nf->thread[socket_id]->socket, send, tot_pkt_send);
- size_t ret_drop = flash__dropmsg(cfg, nf->thread[socket_id]->socket, drop, tot_pkt_drop);
- if (ret_send != tot_pkt_send || ret_drop != tot_pkt_drop) {
- log_error("errno: %d/\"%s\"\n", errno, strerror(errno));
- exit(EXIT_FAILURE);
+ nsend = flash__sendmsg(cfg, xsk, sendvecs, wsend);
+ ndrop = flash__dropmsg(cfg, xsk, dropvecs, wdrop);
+ if (nsend != wsend || ndrop != wdrop) {
+ log_error("errno: %d/\"%s\"", errno, strerror(errno));
+ break;
}
}
if (done)
break;
}
- free(msg.msg_iov);
- return NULL;
-}
-
-static void *worker__stats(void *arg)
-{
- (void)arg;
-
- if (cfg->verbose) {
- unsigned int interval = cfg->stats_interval;
- setlocale(LC_ALL, "");
-
- for (int i = 0; i < cfg->total_sockets; i++)
- nf->thread[i]->socket->timestamp = flash__get_nsecs(cfg);
- while (!done) {
- sleep(interval);
- if (system("clear") != 0)
- log_error("Terminal clear error");
- for (int i = 0; i < cfg->total_sockets; i++) {
- flash__dump_stats(cfg, nf->thread[i]->socket);
- }
- }
- }
+ free(xskvecs);
+ free(sendvecs);
+ free(dropvecs);
return NULL;
}
int main(int argc, char **argv)
{
+ int shift;
+ struct sock_args *args;
+ struct stats_conf stats_cfg = { NULL };
cpu_set_t cpuset;
+ pthread_t socket_thread, stats_thread;
+
cfg = calloc(1, sizeof(struct config));
if (!cfg) {
- log_error("ERROR: Memory allocation failed\n");
+ log_error("ERROR: Memory allocation failed");
exit(EXIT_FAILURE);
}
- int n = flash__parse_cmdline_args(argc, argv, cfg);
- parse_app_args(argc, argv, &app_conf, n);
- flash__configure_nf(&nf, cfg);
- flash__populate_fill_ring(nf->thread, cfg->umem->frame_size, cfg->total_sockets, cfg->umem_offset, cfg->umem_scale);
+ cfg->app_name = "Unit Test: Forward and Drop Application";
+ cfg->app_options = fwddrop_options;
+ cfg->done = &done;
+
+ shift = flash__parse_cmdline_args(argc, argv, cfg);
+ if (shift < 0)
+ goto out_cfg;
+
+ if (parse_app_args(argc, argv, &app_conf, shift) < 0)
+ goto out_cfg;
+
+ if (flash__configure_nf(&nf, cfg) < 0)
+ goto out_cfg;
- log_info("Control Plane Setup Done");
+ log_info("Control Plane setup done...");
signal(SIGINT, int_exit);
signal(SIGTERM, int_exit);
signal(SIGABRT, int_exit);
- log_info("STARTING Data Path");
+ log_info("Starting Data Path...");
- for (int i = 0; i < cfg->total_sockets; i++) {
- struct Args *args = calloc(1, sizeof(struct Args));
- args->socket_id = i;
- args->next = nf->next;
- args->next_size = nf->next_size;
-
- log_info("2_NEXT_SIZE: %d", args->next_size);
+ args = calloc(cfg->total_sockets, sizeof(struct sock_args));
+ if (!args) {
+ log_error("ERROR: Memory allocation failed for sock_args");
+ goto out_cfg_close;
+ }
- for (int i = 0; i < args->next_size; i++) {
- log_info("2_NEXT_ITEM_%d %d", i, nf->next[i]);
- }
+ for (int i = 0; i < cfg->total_sockets; i++) {
+ args[i].socket_id = i;
- pthread_t socket_thread;
- if (pthread_create(&socket_thread, NULL, socket_routine, args)) {
+ if (pthread_create(&socket_thread, NULL, socket_routine, &args[i])) {
log_error("Error creating socket thread");
- exit(EXIT_FAILURE);
+ goto out_args;
}
+
CPU_ZERO(&cpuset);
CPU_SET((i % (app_conf.cpu_end - app_conf.cpu_start + 1)) + app_conf.cpu_start, &cpuset);
if (pthread_setaffinity_np(socket_thread, sizeof(cpu_set_t), &cpuset) != 0) {
- log_error("ERROR: Unable to set thread affinity: %s\n", strerror(errno));
- exit(EXIT_FAILURE);
+ log_error("ERROR: Unable to set thread affinity: %s", strerror(errno));
+ goto out_args;
}
- pthread_detach(socket_thread);
+ if (pthread_detach(socket_thread) != 0) {
+ log_error("ERROR: Unable to detach thread: %s", strerror(errno));
+ goto out_args;
+ }
}
- pthread_t stats_thread;
- if (pthread_create(&stats_thread, NULL, worker__stats, NULL)) {
+ stats_cfg.nf = nf;
+ stats_cfg.cfg = cfg;
+
+ if (pthread_create(&stats_thread, NULL, flash__stats_thread, &stats_cfg)) {
log_error("Error creating statistics thread");
- exit(EXIT_FAILURE);
+ goto out_args;
}
CPU_ZERO(&cpuset);
CPU_SET(app_conf.stats_cpu, &cpuset);
if (pthread_setaffinity_np(stats_thread, sizeof(cpu_set_t), &cpuset) != 0) {
- log_error("ERROR: Unable to set thread affinity: %s\n", strerror(errno));
- exit(EXIT_FAILURE);
+ log_error("ERROR: Unable to set thread affinity: %s", strerror(errno));
+ goto out_args;
}
- pthread_detach(stats_thread);
- wait_for_cmd(cfg);
+ if (pthread_detach(stats_thread) != 0) {
+ log_error("ERROR: Unable to detach thread: %s", strerror(errno));
+ goto out_args;
+ }
+ flash__wait(cfg);
flash__xsk_close(cfg, nf);
- return EXIT_SUCCESS;
+ exit(EXIT_SUCCESS);
+
+out_args:
+ done = true;
+ free(args);
+out_cfg_close:
+ sleep(1);
+ flash__xsk_close(cfg, nf);
+out_cfg:
+ free(cfg);
+ exit(EXIT_FAILURE);
}
diff --git a/examples/unit-tests/fwdrr.c b/examples/unit-tests/fwdrr.c
index a0b7072..e30164e 100644
--- a/examples/unit-tests/fwdrr.c
+++ b/examples/unit-tests/fwdrr.c
@@ -3,20 +3,18 @@
*
* fwdrr: A simple NF that forwards packets to many destinations in a round-robin fashion
*/
-
#include
#include
#include
-#include
#include
#include
#include
#include
-bool done = false;
+volatile bool done = false;
struct config *cfg = NULL;
-struct nf *nf;
+struct nf *nf = NULL;
static void int_exit(int sig)
{
@@ -29,38 +27,25 @@ struct appconf {
int cpu_end;
int stats_cpu;
bool sriov;
- uint8_t *dest_ether_addr_octet;
+ uint8_t dest_ether_addr_octet[6];
} app_conf;
-static int hex2int(char ch)
-{
- if (ch >= '0' && ch <= '9')
- return ch - '0';
- if (ch >= 'A' && ch <= 'F')
- return ch - 'A' + 10;
- if (ch >= 'a' && ch <= 'f')
- return ch - 'a' + 10;
- return -1;
-}
-
-static uint8_t *get_mac_addr(char *mac_addr)
-{
- uint8_t *dest_ether_addr_octet = (uint8_t *)malloc(6 * sizeof(uint8_t));
- for (int i = 0; i < 6; i++) {
- dest_ether_addr_octet[i] = hex2int(mac_addr[0]) * 16;
- mac_addr++;
- dest_ether_addr_octet[i] += hex2int(mac_addr[0]);
- mac_addr += 2;
- }
- return dest_ether_addr_octet;
-}
+// clang-format off
+static const char *fwdrr_options[] = {
+ "-c \tStart CPU (default: 0)",
+ "-e \tEnd CPU (default: 0)",
+ "-s \tStats CPU (default: 1)",
+ "-S \tEnable SR-IOV mode and set dest MAC address",
+ NULL
+};
+// clang-format on
-static void parse_app_args(int argc, char **argv, struct appconf *app_conf, int shift)
+static int parse_app_args(int argc, char **argv, struct appconf *app_conf, int shift)
{
int c;
+ int ethaddr[6];
opterr = 0;
- // Default values
app_conf->cpu_start = 0;
app_conf->cpu_end = 0;
app_conf->stats_cpu = 1;
@@ -69,8 +54,11 @@ static void parse_app_args(int argc, char **argv, struct appconf *app_conf, int
argc -= shift;
argv += shift;
- while ((c = getopt(argc, argv, "c:e:s:S:")) != -1)
+ while ((c = getopt(argc, argv, "hc:e:s:S:")) != -1)
switch (c) {
+ case 'h':
+ printf("Usage: %s -h\n", argv[-shift]);
+ return -1;
case 'c':
app_conf->cpu_start = atoi(optarg);
break;
@@ -81,12 +69,21 @@ static void parse_app_args(int argc, char **argv, struct appconf *app_conf, int
app_conf->stats_cpu = atoi(optarg);
break;
case 'S':
- app_conf->dest_ether_addr_octet = get_mac_addr(optarg);
+ if (sscanf(optarg, "%x:%x:%x:%x:%x:%x", ðaddr[0], ðaddr[1], ðaddr[2], ðaddr[3], ðaddr[4],
+ ðaddr[5]) != 6) {
+ log_error("Invalid MAC address format: %s", optarg);
+ return -1;
+ }
+ for (int i = 0; i < 6; i++)
+ app_conf->dest_ether_addr_octet[i] = (uint8_t)ethaddr[i];
app_conf->sriov = true;
break;
default:
- abort();
+ printf("Usage: %s -h\n", argv[-shift]);
+ return -1;
}
+
+ return 0;
}
static void update_dest_mac(void *data)
@@ -94,15 +91,15 @@ static void update_dest_mac(void *data)
struct ether_header *eth = (struct ether_header *)data;
struct ether_addr *dst_addr = (struct ether_addr *)ð->ether_dhost;
struct ether_addr tmp = {
- .ether_addr_octet = {
- app_conf.dest_ether_addr_octet[0],
- app_conf.dest_ether_addr_octet[1],
- app_conf.dest_ether_addr_octet[2],
- app_conf.dest_ether_addr_octet[3],
- app_conf.dest_ether_addr_octet[4],
- app_conf.dest_ether_addr_octet[5],
- },
- };
+ .ether_addr_octet = {
+ app_conf.dest_ether_addr_octet[0],
+ app_conf.dest_ether_addr_octet[1],
+ app_conf.dest_ether_addr_octet[2],
+ app_conf.dest_ether_addr_octet[3],
+ app_conf.dest_ether_addr_octet[4],
+ app_conf.dest_ether_addr_octet[5],
+ },
+ };
*dst_addr = tmp;
}
@@ -118,167 +115,169 @@ static void swap_mac_addresses(void *data)
*dst_addr = tmp;
}
-struct Args {
+struct sock_args {
int socket_id;
- int *next;
int next_size;
};
static void *socket_routine(void *arg)
{
- struct Args *a = (struct Args *)arg;
- int socket_id = a->socket_id;
- int *next = a->next;
- int next_size = a->next_size;
- // free(arg);
- log_info("SOCKET_ID: %d", socket_id);
- static __u32 nb_frags;
- int i, ret, nfds = 1, nrecv;
+ nfds_t nfds = 1;
+ int ret, next_size;
+ struct socket *xsk;
+ struct xskvec *xskvecs;
struct pollfd fds[1] = {};
- struct xskmsghdr msg = {};
+ uint32_t i, nrecv, nsend, count, nb_frags = 0;
+ struct sock_args *a = (struct sock_args *)arg;
- log_info("2_NEXT_SIZE: %d", next_size);
+ log_debug("Socket ID: %d", a->socket_id);
+ xsk = nf->thread[a->socket_id]->socket;
+ next_size = a->next_size;
- for (int i = 0; i < next_size; i++) {
- log_info("2_NEXT_ITEM_%d %d", i, next[i]);
+ xskvecs = calloc(cfg->xsk->batch_size, sizeof(struct xskvec));
+ if (!xskvecs) {
+ log_error("Failed to allocate xskvecs array");
+ return NULL;
}
- msg.msg_iov = calloc(cfg->xsk->batch_size, sizeof(struct xskvec));
-
- fds[0].fd = nf->thread[socket_id]->socket->fd;
+ fds[0].fd = xsk->fd;
fds[0].events = POLLIN;
- unsigned int count = 0;
+
+ count = 0;
for (;;) {
- if (cfg->xsk->mode & FLASH__POLL) {
- ret = flash__poll(nf->thread[socket_id]->socket, fds, nfds, cfg->xsk->poll_timeout);
- if (ret <= 0 || ret > 1)
- continue;
- }
+ ret = flash__poll(cfg, xsk, fds, nfds);
+ if (!(ret == 1 || ret == -2))
+ continue;
- nrecv = flash__recvmsg(cfg, nf->thread[socket_id]->socket, &msg);
- struct xskvec *send[nrecv];
- unsigned int tot_pkt_send = 0;
+ nrecv = flash__recvmsg(cfg, xsk, xskvecs, cfg->xsk->batch_size);
for (i = 0; i < nrecv; i++) {
- struct xskvec *xv = &msg.msg_iov[i];
- bool eop = IS_EOP_DESC(xv->options);
+ char *pkt = xskvecs[i].data;
if (next_size != 0) {
- xv->options = ((count % next_size) << 16) | (xv->options & 0xFFFF);
+ xskvecs[i].options = ((count % next_size) << 16) | (xskvecs[i].options & 0xFFFF);
count++;
}
- char *pkt = xv->data;
if (!nb_frags++)
app_conf.sriov ? update_dest_mac(pkt) : swap_mac_addresses(pkt);
- send[tot_pkt_send++] = &msg.msg_iov[i];
- if (eop)
+ if (IS_EOP_DESC(xskvecs[i].options))
nb_frags = 0;
}
if (nrecv) {
- ret = flash__sendmsg(cfg, nf->thread[socket_id]->socket, send, tot_pkt_send);
- if (ret != nrecv) {
- log_error("errno: %d/\"%s\"\n", errno, strerror(errno));
- exit(EXIT_FAILURE);
+ nsend = flash__sendmsg(cfg, xsk, xskvecs, nrecv);
+ if (nsend != nrecv) {
+ log_error("errno: %d/\"%s\"", errno, strerror(errno));
+ break;
}
}
if (done)
break;
}
- free(msg.msg_iov);
- return NULL;
-}
-static void *worker__stats(void *arg)
-{
- (void)arg;
-
- if (cfg->verbose) {
- unsigned int interval = cfg->stats_interval;
- setlocale(LC_ALL, "");
-
- for (int i = 0; i < cfg->total_sockets; i++)
- nf->thread[i]->socket->timestamp = flash__get_nsecs(cfg);
-
- while (!done) {
- sleep(interval);
- if (system("clear") != 0)
- log_error("Terminal clear error");
- for (int i = 0; i < cfg->total_sockets; i++) {
- flash__dump_stats(cfg, nf->thread[i]->socket);
- }
- }
- }
+ free(xskvecs);
return NULL;
}
int main(int argc, char **argv)
{
+ int shift;
+ struct sock_args *args;
+ struct stats_conf stats_cfg = { NULL };
cpu_set_t cpuset;
+ pthread_t socket_thread, stats_thread;
+
cfg = calloc(1, sizeof(struct config));
if (!cfg) {
- log_error("ERROR: Memory allocation failed\n");
+ log_error("ERROR: Memory allocation failed");
exit(EXIT_FAILURE);
}
- int n = flash__parse_cmdline_args(argc, argv, cfg);
- parse_app_args(argc, argv, &app_conf, n);
- flash__configure_nf(&nf, cfg);
- flash__populate_fill_ring(nf->thread, cfg->umem->frame_size, cfg->total_sockets, cfg->umem_offset, cfg->umem_scale);
+ cfg->app_name = "Round-Robin Forwarding Application";
+ cfg->app_options = fwdrr_options;
+ cfg->done = &done;
+
+ shift = flash__parse_cmdline_args(argc, argv, cfg);
+ if (shift < 0)
+ goto out_cfg;
+
+ if (parse_app_args(argc, argv, &app_conf, shift) < 0)
+ goto out_cfg;
- log_info("Control Plane Setup Done");
+ if (flash__configure_nf(&nf, cfg) < 0)
+ goto out_cfg;
+
+ log_info("Control Plane setup done...");
signal(SIGINT, int_exit);
signal(SIGTERM, int_exit);
signal(SIGABRT, int_exit);
- log_info("STARTING Data Path");
+ log_info("Starting Data Path...");
- for (int i = 0; i < cfg->total_sockets; i++) {
- struct Args *args = calloc(1, sizeof(struct Args));
- args->socket_id = i;
- args->next = nf->next;
- args->next_size = nf->next_size;
+ args = calloc(cfg->total_sockets, sizeof(struct sock_args));
+ if (!args) {
+ log_error("ERROR: Memory allocation failed for sock_args");
+ goto out_cfg_close;
+ }
- log_info("2_NEXT_SIZE: %d", args->next_size);
+ for (int i = 0; i < cfg->total_sockets; i++) {
+ args[i].socket_id = i;
+ args[i].next_size = nf->next_size;
- for (int i = 0; i < args->next_size; i++) {
- log_info("2_NEXT_ITEM_%d %d", i, nf->next[i]);
- }
+ log_debug("Next Size ::: %d", args[i].next_size);
- pthread_t socket_thread;
- if (pthread_create(&socket_thread, NULL, socket_routine, args)) {
+ if (pthread_create(&socket_thread, NULL, socket_routine, &args[i])) {
log_error("Error creating socket thread");
- exit(EXIT_FAILURE);
+ goto out_args;
}
+
CPU_ZERO(&cpuset);
CPU_SET((i % (app_conf.cpu_end - app_conf.cpu_start + 1)) + app_conf.cpu_start, &cpuset);
if (pthread_setaffinity_np(socket_thread, sizeof(cpu_set_t), &cpuset) != 0) {
- log_error("ERROR: Unable to set thread affinity: %s\n", strerror(errno));
- exit(EXIT_FAILURE);
+ log_error("ERROR: Unable to set thread affinity: %s", strerror(errno));
+ goto out_args;
}
- pthread_detach(socket_thread);
+ if (pthread_detach(socket_thread) != 0) {
+ log_error("ERROR: Unable to detach thread: %s", strerror(errno));
+ goto out_args;
+ }
}
- pthread_t stats_thread;
- if (pthread_create(&stats_thread, NULL, worker__stats, NULL)) {
+ stats_cfg.nf = nf;
+ stats_cfg.cfg = cfg;
+
+ if (pthread_create(&stats_thread, NULL, flash__stats_thread, &stats_cfg)) {
log_error("Error creating statistics thread");
- exit(EXIT_FAILURE);
+ goto out_args;
}
CPU_ZERO(&cpuset);
CPU_SET(app_conf.stats_cpu, &cpuset);
if (pthread_setaffinity_np(stats_thread, sizeof(cpu_set_t), &cpuset) != 0) {
- log_error("ERROR: Unable to set thread affinity: %s\n", strerror(errno));
- exit(EXIT_FAILURE);
+ log_error("ERROR: Unable to set thread affinity: %s", strerror(errno));
+ goto out_args;
}
- pthread_detach(stats_thread);
- wait_for_cmd(cfg);
+ if (pthread_detach(stats_thread) != 0) {
+ log_error("ERROR: Unable to detach thread: %s", strerror(errno));
+ goto out_args;
+ }
+ flash__wait(cfg);
flash__xsk_close(cfg, nf);
- return EXIT_SUCCESS;
+ exit(EXIT_SUCCESS);
+
+out_args:
+ done = true;
+ free(args);
+out_cfg_close:
+ sleep(1);
+ flash__xsk_close(cfg, nf);
+out_cfg:
+ free(cfg);
+ exit(EXIT_FAILURE);
}
diff --git a/examples/unit-tests/meson.build b/examples/unit-tests/meson.build
index b79c542..15756b2 100644
--- a/examples/unit-tests/meson.build
+++ b/examples/unit-tests/meson.build
@@ -17,4 +17,7 @@ ring_benchmark = files('ring-benchmark.c')
executable('ring-benchmark', ring_benchmark, c_args: cflags, install: true, dependencies: deps)
backpressure = files('backpressure.c')
-executable('backpressure', backpressure, c_args: cflags, install: true, dependencies: deps)
\ No newline at end of file
+executable('backpressure', backpressure, c_args: cflags, install: true, dependencies: deps)
+
+multi_flow_tx = files('multi-flow-tx.c')
+executable('multi-flow-tx', multi_flow_tx, c_args: cflags, install: true, dependencies: deps)
\ No newline at end of file
diff --git a/examples/unit-tests/multi-flow-tx.c b/examples/unit-tests/multi-flow-tx.c
new file mode 100644
index 0000000..b488bcb
--- /dev/null
+++ b/examples/unit-tests/multi-flow-tx.c
@@ -0,0 +1,512 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright (c) 2025 Debojeet Das
+ */
+
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#define TEST_PORT 8080
+
+volatile bool done = false;
+struct config *cfg = NULL;
+struct nf *nf;
+struct test_stats *stats_arr;
+
+static void int_exit(int sig)
+{
+ log_info("Received Signal: %d", sig);
+ done = true;
+}
+
+struct testHeader {
+ uint8_t lastHop;
+ uint8_t hopCount;
+ uint64_t pktId;
+ uint16_t old_dst;
+};
+
+struct test_stats {
+ uint64_t pkt_count;
+};
+
+struct appconf {
+ int cpu_start;
+ int cpu_end;
+ int stats_cpu;
+ int hops;
+ __u64 burn_cycles;
+ bool variable;
+ __u64 variable_start;
+ __u64 variable_end;
+} app_conf;
+
+#if defined(__ARM_ARCH_ISA_A64)
+// ARM64 based implementation
+static inline __u64 rdtsc(void)
+{
+ __u64 cntvct;
+ asm volatile("mrs %0, cntvct_el0; " : "=r"(cntvct)::"memory");
+ return cntvct;
+}
+
+static inline __u64 rdtsc_precise(void)
+{
+ __u64 cntvct;
+ asm volatile("isb; mrs %0, cntvct_el0; isb; " : "=r"(cntvct)::"memory");
+ return cntvct;
+}
+#elif defined(__x86_64__)
+// AMD64 based implementation
+static inline __u64 rdtsc(void)
+{
+ union {
+ __u64 tsc_64;
+ struct {
+ __u32 lo_32;
+ __u32 hi_32;
+ };
+ } tsc;
+
+ asm volatile("rdtsc" : "=a"(tsc.lo_32), "=d"(tsc.hi_32));
+
+ return tsc.tsc_64;
+}
+
+static inline __u64 rdtsc_precise(void)
+{
+ asm volatile("mfence");
+ return rdtsc();
+}
+#endif
+
+static void burn_cycles(__u64 cycles_to_burn)
+{
+ __u64 start = rdtsc();
+ while ((rdtsc() - start) < cycles_to_burn) {
+ // Burn cycles
+ }
+}
+
+static const char *flow_bp_options[] = { "-c \tStart CPU (default: 0)",
+ "-e \tEnd CPU (default: 0)",
+ "-s \tStats CPU (default: 1)",
+ "-h \tNumber of hops (default: 1)",
+ "-B \tBurn cycles (default: 0)",
+ "-v\t\tEnable variable-length packets (default: disabled)",
+ "-a \tVariable start value (default: 0)",
+ "-z \tVariable end value (default: 0)",
+ NULL };
+
+static int parse_app_args(int argc, char **argv, struct appconf *app_conf, int shift)
+{
+ int c;
+ opterr = 0;
+
+ // Default values
+ app_conf->cpu_start = 0;
+ app_conf->cpu_end = 0;
+ app_conf->stats_cpu = 1;
+ app_conf->hops = 1;
+ app_conf->burn_cycles = 0;
+ app_conf->variable = false;
+ app_conf->variable_start = 0;
+ app_conf->variable_end = 0;
+
+ argc -= shift;
+ argv += shift;
+
+ while ((c = getopt(argc, argv, "c:e:s:h:B:va:z:")) != -1)
+ switch (c) {
+ case 'c':
+ app_conf->cpu_start = atoi(optarg);
+ break;
+ case 'e':
+ app_conf->cpu_end = atoi(optarg);
+ break;
+ case 's':
+ app_conf->stats_cpu = atoi(optarg);
+ break;
+ case 'h':
+ app_conf->hops = atoi(optarg);
+ break;
+ case 'B':
+ app_conf->burn_cycles = atoi(optarg);
+ break;
+ case 'v':
+ app_conf->variable = true;
+ break;
+ case 'a':
+ app_conf->variable_start = atoi(optarg);
+ break;
+ case 'z':
+ app_conf->variable_end = atoi(optarg);
+ break;
+ default:
+ printf("Usage: %s -h\n", argv[-shift]);
+ return -1;
+ }
+ return 0;
+}
+
+static void process_packets(void *data, __u32 *len)
+{
+ void *pos = data;
+ void *data_end = data + *len;
+
+ struct ethhdr *eth = (struct ethhdr *)pos;
+ if ((void *)(eth + 1) > data_end) {
+ log_error("Ethernet header is not valid");
+ return;
+ }
+
+ if (eth->h_proto != htons(ETH_P_IP)) {
+ log_error("Ethernet protocol is not IP");
+ return;
+ }
+
+ pos = eth + 1;
+
+ struct iphdr *iph = pos;
+ size_t hdrsize;
+
+ if ((void *)iph + 1 > data_end) {
+ log_error("IP header is not valid");
+ return;
+ }
+
+ hdrsize = iph->ihl * 4;
+ /* Sanity check packet field is valid */
+ if (hdrsize < sizeof(*iph)) {
+ log_error("IP header size is invalid");
+ return;
+ }
+
+ if (iph->protocol != IPPROTO_UDP) {
+ log_error("IP protocol is not UDP");
+ return;
+ }
+
+ /* Variable-length IPv4 header, need to use byte-based arithmetic */
+ if (pos + hdrsize > data_end) {
+ log_error("IP header is not valid");
+ return;
+ }
+
+ pos += hdrsize;
+
+ size_t payload_len;
+ struct udphdr *udphdr = pos;
+
+ if ((void *)udphdr + 1 > data_end) {
+ log_error("UDP header is not valid");
+ return;
+ }
+
+ pos = udphdr + 1;
+ payload_len = ntohs(udphdr->len) - sizeof(struct udphdr);
+
+ size_t testHeaderLen = sizeof(struct testHeader);
+ void *payload_end = pos + payload_len;
+
+ struct testHeader *testHeader = NULL;
+
+ /* First NF */
+ if (ntohs(udphdr->dest) != TEST_PORT) {
+ // Append test header at the end of the UDP payload
+ testHeader = (struct testHeader *)payload_end;
+ testHeader->lastHop = app_conf.hops;
+ testHeader->hopCount = 1;
+ testHeader->old_dst = udphdr->dest;
+
+ *len += testHeaderLen;
+ udphdr->len = htons(ntohs(udphdr->len) + testHeaderLen);
+ iph->tot_len = htons(ntohs(iph->tot_len) + testHeaderLen);
+
+ udphdr->dest = htons(TEST_PORT);
+ } else {
+ // check if the test header is present
+ if (payload_len < testHeaderLen) {
+ log_error("ERROR: Test header not found in packet");
+ return;
+ }
+
+ // testHeader is at the end of the UDP payload
+ testHeader = (struct testHeader *)(payload_end - testHeaderLen);
+ testHeader->hopCount++;
+ }
+
+ if (testHeader->lastHop == testHeader->hopCount) {
+ uint8_t tmp_mac[ETH_ALEN];
+ struct in_addr tmp_ip;
+ unsigned short tmp_port;
+ payload_len -= testHeaderLen;
+
+ tmp_port = testHeader->old_dst;
+
+ udphdr->dest = tmp_port;
+ udphdr->len = htons(ntohs(udphdr->len) - testHeaderLen);
+ *len -= testHeaderLen;
+
+ tmp_port = udphdr->dest;
+ udphdr->dest = udphdr->source;
+ udphdr->source = tmp_port;
+
+ iph->tot_len = htons(ntohs(iph->tot_len) - testHeaderLen);
+
+ memcpy(tmp_mac, eth->h_dest, ETH_ALEN);
+ memcpy(eth->h_dest, eth->h_source, ETH_ALEN);
+ memcpy(eth->h_source, tmp_mac, ETH_ALEN);
+
+ memcpy(&tmp_ip, &iph->saddr, sizeof(tmp_ip));
+ memcpy(&iph->saddr, &iph->daddr, sizeof(tmp_ip));
+ memcpy(&iph->daddr, &tmp_ip, sizeof(tmp_ip));
+ }
+
+ return;
+}
+
+struct sock_args {
+ int socket_id;
+ int next_size;
+};
+
+static void *socket_routine(void *arg)
+{
+ nfds_t nfds = 1;
+ int ret, next_size;
+ struct socket *xsk;
+ struct xskvec *xskvecs, *sendvecs, *dropvecs;
+ struct pollfd fds[1] = {};
+ uint32_t i, nrecv, nsend, count, nb_frags = 0, wsend, wdrop, ndrop;
+ struct sock_args *a = (struct sock_args *)arg;
+
+ next_size = a->next_size;
+
+ log_debug("SOCKET_ID: %d", a->socket_id);
+ xsk = nf->thread[a->socket_id]->socket;
+
+ cfg->xsk->poll_timeout = -1;
+
+ xskvecs = calloc(cfg->xsk->batch_size, sizeof(struct xskvec));
+ if (!xskvecs) {
+ log_error("ERROR: Memory allocation failed for xskvecs");
+ return NULL;
+ }
+
+ sendvecs = calloc(cfg->xsk->batch_size, sizeof(struct xskvec));
+ if (!sendvecs) {
+ log_error("ERROR: Memory allocation failed for sendvecs");
+ free(xskvecs);
+ return NULL;
+ }
+
+ dropvecs = calloc(cfg->xsk->batch_size, sizeof(struct xskvec));
+ if (!dropvecs) {
+ log_error("ERROR: Memory allocation failed for dropvecs");
+ free(xskvecs);
+ free(sendvecs);
+ return NULL;
+ }
+
+ fds[0].fd = xsk->fd;
+ fds[0].events = POLLIN;
+
+ count = 0;
+ for (;;) {
+ ret = flash__poll(cfg, xsk, fds, nfds);
+ if (!(ret == 1 || ret == -2))
+ continue;
+
+ nrecv = flash__recvmsg(cfg, xsk, xskvecs, cfg->xsk->batch_size);
+
+ for (i = 0; i < nrecv; i++) {
+ if (next_size != 0) {
+ xskvecs[i].options = ((count % next_size) << 16) | (xskvecs[i].options & 0xFFFF);
+ count++;
+ }
+ char *pkt = xskvecs[i].data;
+
+ if (!nb_frags++)
+ process_packets(pkt, &xskvecs[i].len);
+
+ if (IS_EOP_DESC(xskvecs[i].options))
+ nb_frags = 0;
+
+ if (app_conf.variable) {
+ __u64 random_cycles =
+ app_conf.variable_start + (rand() % (app_conf.variable_end - app_conf.variable_start));
+ burn_cycles(random_cycles);
+
+ } else {
+ burn_cycles(app_conf.burn_cycles);
+ }
+ }
+
+ flash__track_tx_and_drop(cfg, xsk, xskvecs, nrecv, sendvecs, &wsend, dropvecs, &wdrop);
+
+ if (nrecv) {
+ nsend = flash__sendmsg(cfg, xsk, sendvecs, wsend);
+ if (nsend != wsend) {
+ log_error("errno: %d/\"%s\"\n", errno, strerror(errno));
+ break;
+ }
+ ndrop = flash__dropmsg(cfg, xsk, dropvecs, wdrop);
+ if (ndrop != wdrop) {
+ log_error("errno: %d/\"%s\"\n", errno, strerror(errno));
+ break;
+ }
+ }
+
+ if (done)
+ break;
+ }
+ free(xskvecs);
+ free(sendvecs);
+ free(dropvecs);
+ return NULL;
+}
+
+static void *worker__stats(void *conf)
+{
+ struct stats_conf *arg = (struct stats_conf *)conf;
+ struct nf *nf = arg->nf;
+ struct config *cfg = arg->cfg;
+
+ if (cfg->verbose) {
+ unsigned int interval = cfg->stats_interval;
+ setlocale(LC_ALL, "");
+
+ for (int i = 0; i < cfg->total_sockets; i++)
+ nf->thread[i]->socket->timestamp = flash__get_nsecs(cfg);
+
+ while (!done) {
+ sleep(interval);
+ if (system("clear") != 0)
+ log_error("Terminal clear error");
+ for (int i = 0; i < cfg->total_sockets; i++) {
+ flash__dump_stats(cfg, nf->thread[i]->socket);
+ }
+ }
+ }
+ return NULL;
+}
+
+int main(int argc, char **argv)
+{
+ int shift;
+ struct sock_args *args;
+ struct stats_conf stats_cfg = { NULL };
+ cpu_set_t cpuset;
+ pthread_t socket_thread, stats_thread;
+
+ cfg = calloc(1, sizeof(struct config));
+ if (!cfg) {
+ log_error("ERROR: Memory allocation failed\n");
+ exit(EXIT_FAILURE);
+ }
+
+ cfg->app_name = "Flow Backpressure Application";
+ cfg->app_options = flow_bp_options;
+ cfg->done = &done;
+
+ shift = flash__parse_cmdline_args(argc, argv, cfg);
+ if (shift < 0)
+ goto out_cfg;
+
+ if (parse_app_args(argc, argv, &app_conf, shift) < 0)
+ goto out_cfg;
+
+ if (flash__configure_nf(&nf, cfg) < 0)
+ goto out_cfg;
+
+ stats_arr = calloc(cfg->total_sockets, sizeof(struct test_stats));
+ if (!stats_arr) {
+ log_error("ERROR: Memory allocation failed for stats_arr");
+ goto out_cfg;
+ }
+
+ log_info("Control Plane Setup Done");
+
+ signal(SIGINT, int_exit);
+ signal(SIGTERM, int_exit);
+ signal(SIGABRT, int_exit);
+
+ log_info("STARTING Data Path");
+
+ args = calloc(cfg->total_sockets, sizeof(struct sock_args));
+ if (!args) {
+ log_error("ERROR: Memory allocation failed for sock_args");
+ goto out_cfg_close;
+ }
+
+ for (int i = 0; i < cfg->total_sockets; i++) {
+ args[i].socket_id = i;
+ args[i].next_size = nf->next_size;
+
+ log_info("2_NEXT_SIZE: %d", args[i].next_size);
+
+ if (pthread_create(&socket_thread, NULL, socket_routine, &args[i])) {
+ log_error("Error creating socket thread");
+ goto out_args;
+ }
+ CPU_ZERO(&cpuset);
+ CPU_SET((i % (app_conf.cpu_end - app_conf.cpu_start + 1)) + app_conf.cpu_start, &cpuset);
+ if (pthread_setaffinity_np(socket_thread, sizeof(cpu_set_t), &cpuset) != 0) {
+ log_error("ERROR: Unable to set thread affinity: %s\n", strerror(errno));
+ goto out_args;
+ }
+
+ if (pthread_detach(socket_thread) != 0) {
+ log_error("ERROR: Unable to detach thread: %s", strerror(errno));
+ goto out_args;
+ }
+ }
+
+ stats_cfg.nf = nf;
+ stats_cfg.cfg = cfg;
+
+ if (pthread_create(&stats_thread, NULL, worker__stats, &stats_cfg)) {
+ log_error("Error creating statistics thread");
+ goto out_args;
+ }
+ CPU_ZERO(&cpuset);
+ CPU_SET(app_conf.stats_cpu, &cpuset);
+ if (pthread_setaffinity_np(stats_thread, sizeof(cpu_set_t), &cpuset) != 0) {
+ log_error("ERROR: Unable to set thread affinity: %s\n", strerror(errno));
+ goto out_args;
+ }
+ if (pthread_detach(stats_thread) != 0) {
+ log_error("ERROR: Unable to detach thread: %s", strerror(errno));
+ goto out_args;
+ }
+
+ flash__wait(cfg);
+
+ flash__xsk_close(cfg, nf);
+
+ return EXIT_SUCCESS;
+
+out_args:
+ done = true;
+ free(args);
+out_cfg_close:
+ free(stats_arr);
+ sleep(1);
+ flash__xsk_close(cfg, nf);
+out_cfg:
+ free(cfg);
+ exit(EXIT_FAILURE);
+}
diff --git a/examples/unit-tests/userspace-chain.c b/examples/unit-tests/userspace-chain.c
index 398fcea..75b7813 100644
--- a/examples/unit-tests/userspace-chain.c
+++ b/examples/unit-tests/userspace-chain.c
@@ -22,8 +22,8 @@ struct nf *nf;
#define FLASH_MAX_SOCKETS 8
///////////// owner ring buffer /////////////
-#define struct_size(p, member, count) \
- ({ \
+#define struct_size(p, member, count) \
+ ({ \
size_t __size = sizeof(*(p)) + (count) * sizeof((p)->member[0]); \
(__size < sizeof(*(p))) ? SIZE_MAX : __size; \
})
@@ -76,10 +76,17 @@ struct guest_queue *guest_queues[FLASH_MAX_SOCKETS][FLASH_MAX_SOCKETS];
///////////// guest ring buffer operations /////////////
-#define guest_cpu_relax() \
- do { \
+#if defined(__ARM_ARCH_ISA_A64)
+#define guest_cpu_relax() \
+ do { \
+ asm volatile("yield\n" : : : "memory"); \
+ } while (0)
+#elif defined(__x86_64__)
+#define guest_cpu_relax() \
+ do { \
asm volatile("pause\n" : : : "memory"); \
} while (0)
+#endif
static inline __u32 guest_move_prod_head(struct guest_queue *r, __u32 n, __u32 *old_head, __u32 *new_head)
{
@@ -477,10 +484,10 @@ static void *socket_routine(void *arg)
} else {
ret = guest_bulk_enqueue_rxtx(guest_queues[socket_id][socket_id + 1], descs, tot_pkt_send);
- #ifdef STATS
- nf->thread[socket_id]->socket->ring_stats.tx_npkts += ret;
- nf->thread[socket_id]->socket->ring_stats.tx_frags += ret;
- #endif
+#ifdef STATS
+ nf->thread[socket_id]->socket->ring_stats.tx_npkts += ret;
+ nf->thread[socket_id]->socket->ring_stats.tx_frags += ret;
+#endif
}
if (ret != nrecv) {
log_error("errno: %d/\"%s\"\n", errno, strerror(errno));
@@ -589,7 +596,7 @@ int main(int argc, char **argv)
}
pthread_detach(stats_thread);
- wait_for_cmd(cfg);
+ flash__wait(cfg);
flash__xsk_close(cfg, nf);
diff --git a/lib/flash-rs/Cargo.toml b/lib/flash-rs/Cargo.toml
index 94b0c22..c4fa301 100644
--- a/lib/flash-rs/Cargo.toml
+++ b/lib/flash-rs/Cargo.toml
@@ -2,16 +2,20 @@
name = "flash"
version = "0.1.0"
edition = "2024"
+rust-version = "1.88"
description = "Flash userspace library for AF_XDP network function chaining"
-repository = "https://github.com/rickydebojeet/flash"
+repository = "https://github.com/networkedsystemsIITB/flash"
license = "Apache-2.0"
[dependencies]
bitflags = "2.9.0"
+chrono = { version = "0.4.42", optional = true }
clap = { version = "4.5.35", features = ["derive"], optional = true }
libc = "0.2.171"
libxdp-sys = "0.2.1"
quanta = "0.12.5"
+ratatui = { version = "0.29.0", optional = true }
+ringbuffer = { version = "0.15.0", optional = true }
thiserror = "2.0.12"
tracing = { version = "0.1.41", optional = true }
uds = "0.4.2"
@@ -19,5 +23,7 @@ uds = "0.4.2"
[features]
default = []
clap = ["dep:clap"]
+pool = ["dep:ringbuffer"]
stats = []
+tui = ["stats", "dep:chrono", "dep:ratatui"]
tracing = ["dep:tracing"]
diff --git a/lib/flash-rs/src/client.rs b/lib/flash-rs/src/client.rs
index 45392f5..183cd23 100644
--- a/lib/flash-rs/src/client.rs
+++ b/lib/flash-rs/src/client.rs
@@ -1,23 +1,25 @@
use std::{net::Ipv4Addr, str::FromStr, sync::Arc};
use crate::{
- FlashError, Socket,
- config::{BindFlags, FlashConfig, Mode, PollConfig, XskConfig},
- mem::Umem,
+ config::{BindFlags, FlashConfig, Mode, PollConfig, SocketConfig, XskConfig},
+ error::FlashResult,
+ fd::SocketFd,
+ mem::{PollOutStatus, Umem},
uds::UdsClient,
- xsk::{Fd, SocketShared},
+ xsk::Socket,
};
#[cfg(feature = "stats")]
-use crate::{config::XdpFlags, xsk::Stats};
+use crate::{config::XdpFlags, stats::Stats};
+#[derive(Debug)]
pub struct Route {
pub ip_addr: Ipv4Addr,
pub next: Vec,
}
#[allow(clippy::missing_errors_doc, clippy::too_many_lines)]
-pub fn connect(config: &FlashConfig) -> Result<(Vec, Route), FlashError> {
+pub fn connect(config: &FlashConfig) -> FlashResult<(Vec, Route)> {
let mut uds_client = UdsClient::new()?;
let (umem_fd, total_sockets, umem_size, umem_scale) =
@@ -52,43 +54,35 @@ pub fn connect(config: &FlashConfig) -> Result<(Vec, Route), FlashError>
#[cfg(feature = "tracing")]
tracing::debug!("Mode: {mode:?}");
- let poll_timeout = if mode.contains(Mode::FLASH_POLL) {
- uds_client.get_poll_timeout()?
- } else {
- 0
- };
+ // let poll_timeout = if mode.contains(Mode::FLASH_POLL) {
+ // uds_client.get_poll_timeout()?
+ // } else {
+ // 0
+ // };
- #[cfg(feature = "tracing")]
- tracing::debug!("Poll Timeout: {poll_timeout}");
+ // #[cfg(feature = "tracing")]
+ // tracing::debug!("Poll Timeout: {poll_timeout}");
let mut socket_info = Vec::with_capacity(total_sockets);
for _ in 0..total_sockets {
- #[cfg(feature = "stats")]
+ #[cfg(any(feature = "stats", feature = "tracing"))]
let (fd, ifqueue) = uds_client.create_socket()?;
- #[cfg(not(feature = "stats"))]
+ #[cfg(not(any(feature = "stats", feature = "tracing")))]
let (fd, _) = uds_client.create_socket()?;
- let fd = Fd::new(fd, poll_timeout)?;
-
#[cfg(feature = "tracing")]
- {
- #[cfg(feature = "stats")]
- tracing::debug!(
- "Socket: {} :: FD: {fd:?} Ifqueue: {ifqueue}",
- socket_info.len()
- );
-
- #[cfg(not(feature = "stats"))]
- tracing::debug!("Socket: {} :: FD: {fd:?}", socket_info.len());
- }
+ tracing::debug!(
+ "Socket: {} :: FD: {fd:?} Ifqueue: {ifqueue}",
+ socket_info.len()
+ );
#[cfg(feature = "stats")]
- socket_info.push((fd, ifqueue));
+ socket_info.push((SocketFd::new(fd), ifqueue));
#[cfg(not(feature = "stats"))]
- socket_info.push(fd);
+ socket_info.push(SocketFd::new(fd));
}
#[cfg(feature = "stats")]
@@ -109,8 +103,12 @@ pub fn connect(config: &FlashConfig) -> Result<(Vec, Route), FlashError>
uds_client.set_nonblocking()?;
let xsk_config = XskConfig::new(bind_flags, mode);
+ let next_size = uds_client.get_route_info()?;
+
let poll_config = PollConfig::new(
config.smart_poll,
+ config.sleep_poll,
+ next_size != 0,
config.idle_timeout,
config.idleness,
config.bp_timeout,
@@ -118,37 +116,39 @@ pub fn connect(config: &FlashConfig) -> Result<(Vec, Route), FlashError>
xsk_config.batch_size,
)?;
- let socket_shared = Arc::new(SocketShared::new(xsk_config, poll_config, uds_client));
+ let (pollout_fd, pollout_size) = uds_client.get_pollout_status()?;
+ let prev_nf = uds_client.get_prev_nf()?;
- #[cfg(feature = "stats")]
- let mut sockets = socket_info
- .into_iter()
- .map(|(fd, ifqueue)| {
- Socket::new(
- fd.clone(),
- Umem::new(umem_fd, umem_size, umem_scale, umem_offset)?,
- Stats::new(fd, ifname.clone(), ifqueue, xdp_flags.clone()),
- socket_shared.clone(),
- )
- })
- .collect::, _>>()?;
+ let pollout_status = PollOutStatus::new(pollout_fd, pollout_size, config.nf_id, prev_nf)?;
+
+ let socket_config = Arc::new(SocketConfig::new(
+ xsk_config,
+ poll_config,
+ pollout_status,
+ uds_client,
+ ));
- #[cfg(not(feature = "stats"))]
- let mut sockets = socket_info
+ let sockets = socket_info
.into_iter()
- .map(|fd| {
+ .enumerate()
+ .map(|(i, socket_data)| {
+ #[cfg(feature = "stats")]
+ let (fd, ifqueue) = socket_data;
+ #[cfg(not(feature = "stats"))]
+ let fd = socket_data;
+
Socket::new(
fd.clone(),
- Umem::new(umem_fd, umem_size, umem_scale, umem_offset)?,
- socket_shared.clone(),
+ Umem::new(umem_fd, umem_size)?,
+ i,
+ umem_scale,
+ umem_offset,
+ #[cfg(feature = "stats")]
+ Stats::new(fd, ifname.clone(), ifqueue, xdp_flags.clone()),
+ socket_config.clone(),
)
})
.collect::, _>>()?;
- sockets
- .iter_mut()
- .enumerate()
- .try_for_each(|(i, socket)| socket.populate_fq(i))?;
-
Ok((sockets, route))
}
diff --git a/lib/flash-rs/src/config/common.rs b/lib/flash-rs/src/config/common.rs
index ea0ffad..cb9937b 100644
--- a/lib/flash-rs/src/config/common.rs
+++ b/lib/flash-rs/src/config/common.rs
@@ -3,11 +3,12 @@ use std::time::Duration;
use super::FlashConfig;
impl FlashConfig {
- #[allow(clippy::must_use_candidate)]
+ #[allow(clippy::must_use_candidate, clippy::too_many_arguments)]
pub fn new(
- umem_id: u32,
- nf_id: u32,
+ umem_id: usize,
+ nf_id: usize,
smart_poll: bool,
+ sleep_poll: bool,
idle_timeout: Duration,
idleness: f32,
bp_timeout: Duration,
@@ -17,6 +18,7 @@ impl FlashConfig {
umem_id,
nf_id,
smart_poll,
+ sleep_poll,
idle_timeout,
idleness,
bp_timeout,
diff --git a/lib/flash-rs/src/config/config_noclap.rs b/lib/flash-rs/src/config/config.rs
similarity index 71%
rename from lib/flash-rs/src/config/config_noclap.rs
rename to lib/flash-rs/src/config/config.rs
index 6a8f8da..6eff0f6 100644
--- a/lib/flash-rs/src/config/config_noclap.rs
+++ b/lib/flash-rs/src/config/config.rs
@@ -2,9 +2,10 @@ use std::time::Duration;
#[derive(Debug)]
pub struct FlashConfig {
- pub(crate) umem_id: u32,
- pub(crate) nf_id: u32,
+ pub(crate) umem_id: usize,
+ pub(crate) nf_id: usize,
pub(crate) smart_poll: bool,
+ pub(crate) sleep_poll: bool,
pub(crate) idle_timeout: Duration,
pub(crate) idleness: f32,
pub(crate) bp_timeout: Duration,
diff --git a/lib/flash-rs/src/config/config_clap.rs b/lib/flash-rs/src/config/config_clap.rs
index b599c71..cefb957 100644
--- a/lib/flash-rs/src/config/config_clap.rs
+++ b/lib/flash-rs/src/config/config_clap.rs
@@ -5,10 +5,10 @@ use clap::Parser;
#[derive(Debug, Parser)]
pub struct FlashConfig {
#[arg(short, long, help = "Umem id used to connect to monitor")]
- pub(crate) umem_id: u32,
+ pub(crate) umem_id: usize,
#[arg(short = 'f', long, help = "NF id used to connect to monitor")]
- pub(crate) nf_id: u32,
+ pub(crate) nf_id: usize,
#[arg(
short = 'p',
@@ -18,6 +18,14 @@ pub struct FlashConfig {
)]
pub(crate) smart_poll: bool,
+ #[arg(
+ short = 'P',
+ long,
+ default_value_t = false,
+ help = "Enable periodic sleep mode"
+ )]
+ pub(crate) sleep_poll: bool,
+
#[arg(
short,
long,
@@ -46,7 +54,7 @@ pub struct FlashConfig {
#[arg(
short = 'B',
long,
- default_value_t = 0.5,
+ default_value_t = 1.0,
help = "Backpressure sensitivity [0.0 = low (0 pkts), 1.0 = high (2048 pkts)]"
)]
pub(crate) bp_sense: f32,
diff --git a/lib/flash-rs/src/config/mod.rs b/lib/flash-rs/src/config/mod.rs
index c834206..30de253 100644
--- a/lib/flash-rs/src/config/mod.rs
+++ b/lib/flash-rs/src/config/mod.rs
@@ -1,24 +1,20 @@
mod common;
mod error;
mod poll;
+mod socket;
mod xsk;
-#[cfg(feature = "clap")]
-mod config_clap;
+#[cfg_attr(feature = "clap", path = "config_clap.rs")]
+#[allow(clippy::module_inception)]
+mod config;
-#[cfg(not(feature = "clap"))]
-mod config_noclap;
+pub(crate) use {
+ poll::{PollConfig, PollMode},
+ socket::SocketConfig,
+ xsk::{BindFlags, Mode, XskConfig},
+};
-pub(crate) use poll::PollConfig;
-pub(crate) use xsk::{BindFlags, Mode, XskConfig};
-
-pub use error::ConfigError;
-
-#[cfg(feature = "clap")]
-pub use config_clap::FlashConfig;
-
-#[cfg(not(feature = "clap"))]
-pub use config_noclap::FlashConfig;
+pub use {config::FlashConfig, error::ConfigError};
#[cfg(feature = "stats")]
pub use xsk::XdpFlags;
diff --git a/lib/flash-rs/src/config/poll.rs b/lib/flash-rs/src/config/poll.rs
index 2bb5702..7f34e1e 100644
--- a/lib/flash-rs/src/config/poll.rs
+++ b/lib/flash-rs/src/config/poll.rs
@@ -4,8 +4,17 @@ use libxdp_sys::XSK_RING_PROD__DEFAULT_NUM_DESCS;
use super::error::{ConfigError, ConfigResult};
+#[derive(Debug, PartialEq)]
+pub(crate) enum PollMode {
+ Smart,
+ Sleep,
+ None,
+}
+
#[derive(Debug)]
pub(crate) struct PollConfig {
+ pub(crate) mode: PollMode,
+ pub(crate) next_not_empty: bool,
pub(crate) idle_timeout: Duration,
pub(crate) idle_threshold: u32,
pub(crate) bp_timeout: Duration,
@@ -16,32 +25,41 @@ impl PollConfig {
#[allow(
clippy::cast_possible_truncation,
clippy::cast_precision_loss,
- clippy::cast_sign_loss
+ clippy::cast_sign_loss,
+ clippy::too_many_arguments
)]
pub(crate) fn new(
smart_poll: bool,
+ sleep_poll: bool,
+ next_not_empty: bool,
idle_timeout: Duration,
idleness: f32,
bp_timeout: Duration,
bp_sense: f32,
batch_size: u32,
- ) -> ConfigResult