Skip to content

Commit 593377b

Browse files
committed
Update docs
1 parent bc076bf commit 593377b

File tree

2 files changed

+66
-526
lines changed

2 files changed

+66
-526
lines changed

docs/AFXDP_TRAFFIC_TESTING.md

Lines changed: 66 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
# AF_XDP Traffic Testing Guide
22

3-
This guide explains how to test AF_XDP with **real network traffic** instead of just testing infrastructure setup.
3+
This guide explains traffic testing with AF_XDP.
44

55
## Overview
66

7-
The tests in `src/lib/traffic_test.zig` actually:
8-
- Create virtual network interfaces (veth pairs)
9-
- Inject real Ethernet frames into the network stack
10-
- Receive packets via AF_XDP sockets
11-
- Process packets through the pipeline
12-
- Forward packets to other interfaces
13-
- Verify packet counts and stats
7+
The tests in `src/lib/traffic_test.zig` do the following:
8+
- Create virtual network interfaces (veth pairs)
9+
- Inject Ethernet frames into the network stack
10+
- Receive packets via AF_XDP sockets
11+
- Process packets through the pipeline
12+
- Forward packets to other interfaces
13+
- Verify packet counts and stats
1414

1515
---
1616

@@ -84,6 +84,64 @@ const stats = service.getStats();
8484
5. Forwarder swaps them (A→B and B→A)
8585
6. Verifies bidirectional traffic works
8686

87+
**Architecture:**
88+
89+
```
90+
Bidirectional L2 Forwarding Test
91+
92+
┌────────────────────────────────────────────────────────────┐
93+
│ Test Process │
94+
│ │
95+
│ ┌──────────────┐ ┌──────────────┐ │
96+
│ │ Injector A │ │ Injector B │ │
97+
│ │ (AF_PACKET) │ │ (AF_PACKET) │ │
98+
│ └──────┬───────┘ └───────┬──────┘ │
99+
│ │ │ │
100+
│ │ Send packets │ Send packets
101+
│ │ via raw socket │ via raw socket
102+
│ ▼ ▼ │
103+
└─────────┼────────────────────────────────────┼────────────┘
104+
│ │
105+
│ │
106+
┌─────────▼────────┐ ┌──────────────▼─────────┐
107+
│ veth_fwd_a │◄─────────►│ veth_fwd_b │
108+
│ │ Kernel │ │
109+
│ AF_XDP Socket #1 │ veth │ AF_XDP Socket #2 │
110+
│ │ pair │ │
111+
└─────────┬────────┘ └──────────┬─────────────┘
112+
│ │
113+
│ RX: Packets from B │ RX: Packets from A
114+
│ │
115+
▼ ▼
116+
┌─────────────────────────────────────────────────────────┐
117+
│ AF_XDP Service (Service.zig) │
118+
│ │
119+
│ ┌──────────────────────────────────────────────────┐ │
120+
│ │ L2 Forwarder Pipeline │ │
121+
│ │ │ │
122+
│ │ RX from A ──► Process ──► TX to B │ │
123+
│ │ │ │
124+
│ │ RX from B ──► Process ──► TX to A │ │
125+
│ │ │ │
126+
│ │ (Swaps source/destination interfaces) │ │
127+
│ └──────────────────────────────────────────────────┘ │
128+
└─────────────────────────────────────────────────────────┘
129+
130+
Flow Example:
131+
1. Injector A sends packet → veth_fwd_a
132+
2. AF_XDP RX on veth_fwd_a receives it
133+
3. L2 Forwarder processes: forward to veth_fwd_b
134+
4. AF_XDP TX on veth_fwd_b transmits it
135+
5. Packet appears on veth_fwd_b (visible to Injector B)
136+
137+
(Same flow happens in reverse: B → A)
138+
139+
Verification:
140+
✓ Service stats show RX packets on both interfaces
141+
✓ Service stats show TX packets on both interfaces
142+
✓ Confirms bidirectional forwarding works
143+
```
144+
87145
---
88146

89147
## Running the Tests
@@ -140,104 +198,6 @@ Service stats:
140198

141199
---
142200

143-
## Key Components Explained
144-
145-
### 1. Creating veth Pairs
146-
147-
**Why veth instead of dummy interfaces?**
148-
- Dummy interfaces don't actually pass traffic
149-
- veth pairs are connected: packet sent to one end appears on the other
150-
- Perfect for testing without physical NICs
151-
152-
```zig
153-
fn createVethPair(name_a: []const u8, name_b: []const u8) !void {
154-
// Runs: ip link add veth_a type veth peer name veth_b
155-
const argv = [_][]const u8{
156-
"ip", "link", "add", name_a, "type", "veth", "peer", "name", name_b,
157-
};
158-
var child = std.process.Child.init(&argv, std.heap.page_allocator);
159-
const result = try child.spawnAndWait();
160-
if (result != .Exited or result.Exited != 0) {
161-
return error.FailedToCreateVethPair;
162-
}
163-
}
164-
```
165-
166-
### 2. Packet Injection
167-
168-
**Uses AF_PACKET raw sockets** to inject Ethernet frames:
169-
170-
```zig
171-
fn injectPacket(ifname: []const u8, packet_data: []const u8) !void {
172-
// Create raw socket
173-
const sock_fd = try std.posix.socket(
174-
std.posix.AF.PACKET,
175-
std.posix.SOCK.RAW,
176-
@byteSwap(@as(u16, 0x0003)), // ETH_P_ALL
177-
);
178-
defer std.posix.close(sock_fd);
179-
180-
// Bind to interface
181-
var addr = std.mem.zeroes(std.posix.sockaddr.ll);
182-
addr.sll_family = std.posix.AF.PACKET;
183-
addr.sll_ifindex = @intCast(ifindex);
184-
185-
// Send raw Ethernet frame
186-
_ = try std.posix.sendto(sock_fd, packet_data, 0, @ptrCast(&addr), @sizeOf(@TypeOf(addr)));
187-
}
188-
```
189-
190-
**Why this works:**
191-
- AF_PACKET socket operates at Layer 2 (Ethernet)
192-
- Can send raw frames directly to network interface
193-
- Bypasses normal network stack (no routing, no TCP/IP processing)
194-
- Frame appears on wire (or veth peer) exactly as sent
195-
196-
### 3. Test Packet Structure
197-
198-
```zig
199-
fn buildTestPacket(buffer: []u8, src_mac: [6]u8, dst_mac: [6]u8, payload_byte: u8) []u8 {
200-
// Ethernet: 14 bytes
201-
@memcpy(buffer[0..6], &dst_mac);
202-
@memcpy(buffer[6..12], &src_mac);
203-
std.mem.writeInt(u16, buffer[12..14], 0x0800, .big); // EtherType: IPv4
204-
205-
// IPv4: 20 bytes
206-
buffer[14] = 0x45; // version=4, ihl=5
207-
// ... (see code for full header)
208-
209-
// UDP: 8 bytes
210-
std.mem.writeInt(u16, buffer[34..36], 12345, .big); // src port
211-
std.mem.writeInt(u16, buffer[36..38], 53, .big); // dst port
212-
213-
// Payload: 20 bytes
214-
@memset(buffer[42..62], payload_byte);
215-
216-
return buffer[0..62]; // Total: 62 bytes
217-
}
218-
```
219-
220-
### 4. Service Thread
221-
222-
**Why run service in separate thread?**
223-
224-
```zig
225-
var service_thread = try std.Thread.spawn(.{}, serviceRunThread, .{&service});
226-
227-
// Main thread injects packets while service thread receives them
228-
injectPacket(...);
229-
230-
// Later: stop and wait
231-
service.stop();
232-
service_thread.join();
233-
```
234-
235-
Without a separate thread, you'd have a chicken-and-egg problem:
236-
- Can't inject packets until service is running
237-
- Can't run service (blocking poll) while injecting packets
238-
239-
---
240-
241201
## Common Issues and Solutions
242202

243203
### Issue 1: "Permission Denied" / PERM Error

0 commit comments

Comments
 (0)