Skip to content

Commit 1c2080d

Browse files
committed
Adds the draft of the XDP scheduler testing tool
This commit contains the XDP scheduling framework. It consists of a testing program called xdq-tester used to test schedulers using the XDP and DEQUEUE hooks. It uses trace files written in Lua that the xdq-tester program uses to check the XDP schedulers for correctness. The FIFO, SPRIO, and WFQ are fully functional in this commit. The SPRIO and WFQ have an API to set the weights from the Lua scripts. This commit's FQ-CoDel contains sparse flow handling and testing, but not the CoDel part. Signed-off-by: Frey Alfredsson <[email protected]>
1 parent daefd11 commit 1c2080d

16 files changed

+2636
-0
lines changed

xdq-tester/Makefile

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
2+
3+
USER_TARGETS := xdq-tester
4+
BPF_TARGETS := $(patsubst %.c,%,$(wildcard *.bpf.c))
5+
6+
USER_LIBS = -llua -ldl -lm
7+
8+
LIB_DIR = ../lib
9+
10+
include $(LIB_DIR)/common.mk

xdq-tester/bpf_local_helpers.h

+115
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
2+
3+
#ifndef BPF_LOCAL_HELPERS_H_
4+
#define BPF_LOCAL_HELPERS_H_
5+
6+
#include "bpf_shared_data.h"
7+
8+
#define EEXIST 17 /* File exists */
9+
10+
#define BPF_MAP_TYPE_PIFO_GENERIC 31
11+
#define BPF_MAP_TYPE_PIFO_XDP 32
12+
13+
/*
14+
* bpf_packet_dequeue
15+
*
16+
* Dequeue the packet at the head of the PIFO in *map* and return a pointer
17+
* to the packet (or NULL if the PIFO is empty).
18+
*
19+
* Returns
20+
* On success, a pointer to the packet, or NULL if the PIFO is empty. The
21+
* packet pointer must be freed using *bpf_packet_drop()* or returning
22+
* the packet pointer. The *rank* pointer will be set to the rank of
23+
* the dequeued packet on success, or a negative error code on error.
24+
*/
25+
static long (*bpf_packet_dequeue)(void *ctx, void *map, __u64 flags, __u64 *rank) = (void *) 208;;
26+
static long (*bpf_packet_drop)(void *ctx, void *pkt) = (void *) 209;
27+
28+
struct parsing_context {
29+
void *data; // Start of eth hdr
30+
void *data_end; // End of safe acessible area
31+
struct hdr_cursor nh; // Position to parse next
32+
__u32 pkt_len; // Full packet length (headers+data)
33+
};
34+
35+
#pragma GCC diagnostic push
36+
#pragma GCC diagnostic ignored "-Wunused-function"
37+
static __always_inline void *
38+
bpf_map_lookup_or_try_init(void *map, const void *key, const void *init)
39+
{
40+
void *val;
41+
long err;
42+
43+
val = bpf_map_lookup_elem(map, key);
44+
if (val)
45+
return val;
46+
47+
err = bpf_map_update_elem(map, key, init, BPF_NOEXIST);
48+
if (err && err != -EEXIST)
49+
return NULL;
50+
51+
return bpf_map_lookup_elem(map, key);
52+
}
53+
54+
static __always_inline int bpf_max(__u64 left, __u64 right)
55+
{
56+
return right > left ? right : left;
57+
}
58+
59+
60+
/*
61+
* Maps an IPv4 address into an IPv6 address according to RFC 4291 sec 2.5.5.2
62+
*/
63+
static void map_ipv4_to_ipv6(struct in6_addr *ipv6, __be32 ipv4)
64+
{
65+
__builtin_memset(&ipv6->in6_u.u6_addr8[0], 0x00, 10);
66+
__builtin_memset(&ipv6->in6_u.u6_addr8[10], 0xff, 2);
67+
ipv6->in6_u.u6_addr32[3] = ipv4;
68+
}
69+
70+
/*
71+
* Five-tuple helpers
72+
*/
73+
74+
/* This function currently only supports UDP packets */
75+
static __always_inline int parse_packet(struct parsing_context *pctx, struct packet_info *p_info)
76+
{
77+
/* Parse Ethernet and IP/IPv6 headers */
78+
p_info->eth_type = parse_ethhdr(&pctx->nh, pctx->data_end, &p_info->eth);
79+
if (p_info->eth_type == bpf_htons(ETH_P_IP)) {
80+
p_info->ip_type = parse_iphdr(&pctx->nh, pctx->data_end, &p_info->iph);
81+
if (p_info->ip_type < 0)
82+
goto err;
83+
p_info->nt.ipv = 4;
84+
map_ipv4_to_ipv6(&p_info->nt.saddr.ip, p_info->iph->saddr);
85+
map_ipv4_to_ipv6(&p_info->nt.daddr.ip, p_info->iph->daddr);
86+
} else if (p_info->eth_type == bpf_htons(ETH_P_IPV6)) {
87+
p_info->ip_type = parse_ip6hdr(&pctx->nh, pctx->data_end, &p_info->ip6h);
88+
if (p_info->ip_type < 0)
89+
goto err;
90+
p_info->nt.ipv = 6;
91+
p_info->nt.saddr.ip = p_info->ip6h->saddr;
92+
p_info->nt.daddr.ip = p_info->ip6h->daddr;
93+
} else {
94+
goto err;
95+
}
96+
97+
/* Parse UDP header */
98+
if (p_info->ip_type != IPPROTO_UDP)
99+
goto err;
100+
if (parse_udphdr(&pctx->nh, pctx->data_end, &p_info->udph) < 0)
101+
goto err;
102+
103+
p_info->nt.proto = IPPROTO_UDP;
104+
p_info->nt.saddr.port = p_info->udph->source;
105+
p_info->nt.daddr.port = p_info->udph->dest;
106+
107+
return 0;
108+
err:
109+
bpf_printk("Failed to parse UDP packet");
110+
return -1;
111+
}
112+
113+
#pragma GCC diagnostic pop
114+
115+
#endif // BPF_LOCAL_HELPERS_H_

xdq-tester/bpf_shared_data.h

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#ifndef BPF_SHARED_DATA_H_
2+
#define BPF_SHARED_DATA_H_
3+
4+
struct flow_address {
5+
struct in6_addr ip;
6+
__u16 port;
7+
__u16 reserved;
8+
};
9+
10+
struct network_tuple {
11+
struct flow_address saddr;
12+
struct flow_address daddr;
13+
__u16 proto;
14+
__u8 ipv;
15+
__u8 reserved;
16+
};
17+
18+
struct flow_state {
19+
__u32 pkts;
20+
__u32 root_finish_bytes;
21+
__u32 finish_bytes;
22+
__u16 root_weight;
23+
__u16 weight;
24+
__u32 persistent;
25+
__u64 root_priority;
26+
};
27+
28+
struct fq_codel_flow_state {
29+
__u32 pkts;
30+
__u32 finish_bytes;
31+
__u32 total_bytes;
32+
__u32 grace_period;
33+
};
34+
35+
struct packet_info {
36+
struct ethhdr *eth;
37+
union {
38+
struct iphdr *iph;
39+
struct ipv6hdr *ip6h;
40+
};
41+
union {
42+
struct udphdr *udph;
43+
};
44+
struct network_tuple nt;
45+
int eth_type;
46+
int ip_type;
47+
};
48+
49+
#endif // BPF_SHARED_DATA_H_

xdq-tester/fifo.lua

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
-- SPDX-License-Identifier: GPL-2.0
2+
-- Copyright (c) 2022 Freysteinn Alfredsson <[email protected]>
3+
4+
-- FIFO scheduler
5+
config.bpf.file = "./sched_fifo.bpf.o"
6+
7+
-- Setup flows
8+
packet_flow1 = Udp:new()
9+
packet_flow1.udp.dest = 8080
10+
11+
packet_flow2 = Udp:new()
12+
packet_flow2.udp.dest = 8081
13+
14+
packet_flow3 = Udp:new()
15+
packet_flow3.udp.dest = 8082
16+
17+
18+
-- Test scheduler
19+
enqueue(packet_flow1)
20+
enqueue(packet_flow2)
21+
enqueue(packet_flow3)
22+
23+
dequeue_cmp(packet_flow1)
24+
dequeue_cmp(packet_flow2)
25+
dequeue_cmp(packet_flow3)

xdq-tester/fq_codel.lua

+170
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
-- SPDX-License-Identifier: GPL-2.0
2+
-- Copyright (c) 2022 Freysteinn Alfredsson <[email protected]>
3+
4+
-- Fair Queuing with Controlled Delay (FQ_CoDel)
5+
config.bpf.file = "./sched_fq_codel.bpf.o"
6+
7+
8+
-- Setup flows
9+
-- We use this flow to test sparse flow handling
10+
packet_sparse_flow_tester = Udp:new()
11+
packet_sparse_flow_tester.udp.dest = 8000
12+
13+
-- The background stream flow increments the time bytes
14+
-- so that we can test our sparse flow tester when time has passed
15+
packet_flow_background_stream = Udp:new()
16+
packet_flow_background_stream.udp.dest = 8001
17+
-- Make the packet the size of full a quantom (1522 - 62)
18+
packet_flow_background_stream.udp.payload = create_payload(1460)
19+
20+
21+
-- Test scheduler
22+
23+
--
24+
-- 1. Sparse flow tests
25+
--
26+
-- In our implementation of FQ-CoDel, the time_bytes variable is the only thing
27+
-- that connects sparse flows. Therefore, we can test all possible scenarios
28+
-- using only two flows. One background flow that we only use to advance time.
29+
-- And the flow that we use for testing.
30+
31+
function make_sparse(flow)
32+
-- The background flow needs two packets to be a stream:
33+
-- * The first packet will be sparse.
34+
-- * The second packet exceeds the sparse quantom.
35+
flow.udp.payload = create_payload(1460)
36+
enqueue(flow) -- Sparse
37+
enqueue(flow) -- Stream
38+
dequeue_cmp(flow) -- Dequeue sparse
39+
dequeue_cmp(flow) -- Dequeue sparse
40+
-- Note that the type_bytes has not advanced at this point but will after the
41+
-- next dequeued packet.
42+
end
43+
44+
-- 1.1 Test when a sparse flow ends while sparse
45+
function fq_codel_sparse_test1()
46+
-- This test does the following:
47+
-- 1. Creates a sparse flow with a couple of packets.
48+
-- 2. Advance time_bytes and expire the sparse flow.
49+
-- 3. Creates a new sparse flow with a couple of packets.
50+
-- 4. Advance time_bytes and expire the new sparse flow.
51+
-- In steps two and four the test confirms that the sparse flows
52+
-- were still sparse.
53+
make_sparse(packet_flow_background_stream)
54+
55+
-- Prime the background stream so it can update the time_bytes variable later.
56+
enqueue(packet_flow_background_stream) -- Prime for updating time_bytes
57+
enqueue(packet_flow_background_stream) -- Make sure the flow is not recycled after update
58+
59+
-- Make the packet the size of half a quantom (1522/2 - 62)
60+
-- The flow will cease being a sparse flow after two packets.
61+
packet_sparse_flow_tester.udp.payload = create_payload(699)
62+
63+
-- The sparse flow gets a full quantom of packets.
64+
enqueue(packet_sparse_flow_tester) -- Sparse 1
65+
enqueue(packet_sparse_flow_tester) -- Sparse 2
66+
67+
-- Remove all sparse packets.
68+
dequeue_cmp(packet_sparse_flow_tester) -- Dequeue sparse
69+
dequeue_cmp(packet_sparse_flow_tester) -- Dequeue sparse
70+
71+
-- Advance time_bytes
72+
dequeue_cmp(packet_flow_background_stream) -- Advances time_bytes one quantom
73+
-- Our FQ-CoDel algorithm should have expired the sparse_flow_tester
74+
-- flow at this point, but not the background stream.
75+
76+
-- Test that the sparse_flow_tester is indeed expired.
77+
enqueue(packet_sparse_flow_tester) -- Add sparse packet with a higher priority
78+
dequeue_cmp(packet_sparse_flow_tester) -- Dequeue the sparse packet
79+
dequeue_cmp(packet_flow_background_stream) -- Advances time_bytes one quantom
80+
-- Our FQ-CoDel algorithm should have expired both the sparse_flow_tester
81+
-- flow and the background stream at this point.
82+
end
83+
84+
-- 1.2 Test a sparse flow when the time_bytes advances while the flow is sparse
85+
function fq_codel_sparse_test2()
86+
-- This test does the following:
87+
-- 1. Creates a sparse flow with a couple of packets.
88+
-- 2. Advances time_bytes by a half a quantom
89+
-- 3. Adds a couple of packets to the sparse flow.
90+
-- In steps one and three the test confirms that the sparse flow
91+
-- is still sparse.
92+
make_sparse(packet_flow_background_stream)
93+
94+
-- Make the packet the size of half a quantom (1522/2 - 62)
95+
packet_flow_background_stream.udp.payload = create_payload(699)
96+
97+
-- Make each packet 50 bytes for our sparse flow
98+
packet_sparse_flow_tester.udp.payload = create_payload(38)
99+
100+
-- Keep in mind that the last background packet ends at a full quantom. Therefore,
101+
-- if we want to update the time_bytes by a half a quantom, we will need to enqueue
102+
-- and deqeueu a half a quantom packet.
103+
enqueue(packet_flow_background_stream) -- Used to advance time_bytes by half a quantom
104+
enqueue(packet_flow_background_stream) -- Used to advance time_bytes by half a quantom
105+
enqueue(packet_flow_background_stream) -- Make sure the flow is not recycled after update
106+
dequeue_cmp(packet_flow_background_stream) -- Advances time_bytes by a half a quantom
107+
108+
-- Confirm that the sparse flow has a higher priority than the background stream.
109+
enqueue(packet_sparse_flow_tester) -- Add a sparse packet
110+
enqueue(packet_sparse_flow_tester) -- Add a sparse packet
111+
dequeue_cmp(packet_sparse_flow_tester) -- Dequeue the sparse packet
112+
dequeue_cmp(packet_sparse_flow_tester) -- Dequeue the sparse packet
113+
114+
dequeue_cmp(packet_flow_background_stream) -- Advances time_bytes by a half a quantom
115+
116+
-- Confirm that the sparse flow has a higher priority than the stream.
117+
enqueue(packet_sparse_flow_tester) -- Add a sparse packet
118+
enqueue(packet_sparse_flow_tester) -- Add a sparse packet
119+
dequeue_cmp(packet_sparse_flow_tester) -- Dequeue the sparse packet
120+
dequeue_cmp(packet_sparse_flow_tester) -- Dequeue the sparse packet
121+
122+
-- Recycle both flows.
123+
dequeue_cmp(packet_flow_background_stream) -- Recycle both flows
124+
end
125+
126+
-- 1.3 Test a flow that becomes a stream.
127+
function fq_codel_sparse_test3()
128+
-- This test does the following:
129+
-- 1. Creates a sparse flow and adds a full quantom to it.
130+
-- 2. Adds packets to the flow to make it a stream.
131+
-- 3. Advances time_bytes by a half a quantom.
132+
-- 4. Adds packets to the stream
133+
-- In steps two and four the test confirms that the flow is a stream.
134+
make_sparse(packet_flow_background_stream)
135+
136+
-- Make the packet the size of half a quantom (1522/2 - 62)
137+
packet_sparse_flow_tester.udp.payload = create_payload(699)
138+
139+
-- Make the packet the size of half a quantom (1522/2 - 62)
140+
packet_flow_background_stream.udp.payload = create_payload(699)
141+
142+
-- Keep in mind that the last background packet ends at a full quantom. Therefore,
143+
-- if we want to update the time_bytes by a half a quantom, we will need to enqueue
144+
-- and deqeueu a half a quantom packet.
145+
enqueue(packet_flow_background_stream) -- Used to advance time_bytes by half a quantom
146+
enqueue(packet_flow_background_stream) -- Used to advance time_bytes by half a quantom
147+
enqueue(packet_flow_background_stream) -- Make sure the flow is not recycled after update
148+
dequeue_cmp(packet_flow_background_stream) -- Advances time_bytes by a half a quantom
149+
150+
-- Make the sparse_flow_tester flow a stream.
151+
enqueue(packet_sparse_flow_tester) -- Add sparse packet
152+
enqueue(packet_sparse_flow_tester) -- Add sparse packet
153+
enqueue(packet_sparse_flow_tester) -- Make the flow a stream
154+
enqueue(packet_sparse_flow_tester) -- Add stream packet
155+
156+
-- Dequeue the sparse flow packets.
157+
dequeue_cmp(packet_sparse_flow_tester)
158+
dequeue_cmp(packet_sparse_flow_tester)
159+
160+
-- Confirm that both flows are streams with equal priority.
161+
dequeue_cmp(packet_sparse_flow_tester)
162+
dequeue_cmp(packet_flow_background_stream)
163+
dequeue_cmp(packet_sparse_flow_tester)
164+
dequeue_cmp(packet_flow_background_stream)
165+
end
166+
167+
-- Run tests
168+
fq_codel_sparse_test1()
169+
fq_codel_sparse_test2()
170+
fq_codel_sparse_test3()

0 commit comments

Comments
 (0)