Skip to content

Commit

Permalink
Adds the draft of the XDP scheduler testing tool
Browse files Browse the repository at this point in the history
This commit contains the XDP scheduling framework. It consists of a
testing program called xdp_scheduler_tester used to test schedulers
using the XDP and DEQUEUE hooks. It does this using trace files that the
xdp_scheduler_tester program uses to check the XDP schedulers for
correctness.

The FIFO and PIFO schedulers are fully functional in this commit.
However, it defines flows as UDP port numbers until I have gotten the
five-tuple flow definitions to work. The WFQ uses my five-tuples flow
implementation. However, I have not gotten it past the BPF verifier in
this commit.

To trigger the WFQ BPF verifier issue, please run:
bash-5.1# ./xdp_scheduler_tester -f xdp_debug.trace

I have added the length keyword to this commit, which can resize the
packets. It is fully functional. However, the dequeue hook needs a size
parameter, which I set to 4096 octets. The question is, what would be a
suitable size?

The xdp_scheduler_tester program also includes a -v flag that prints out
the enqueued packet.

Signed-off-by: Frey Alfredsson <[email protected]>
  • Loading branch information
freysteinn committed Mar 28, 2022
1 parent ce71462 commit 8c1bbdf
Show file tree
Hide file tree
Showing 11 changed files with 1,297 additions and 0 deletions.
8 changes: 8 additions & 0 deletions xdp-scheduler-tester/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)

USER_TARGETS := xdp_scheduler_tester
BPF_TARGETS := $(patsubst %.c,%,$(wildcard *.bpf.c))

LIB_DIR = ../lib

include $(LIB_DIR)/common.mk
73 changes: 73 additions & 0 deletions xdp-scheduler-tester/bpf_local_helpers.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)

#define EEXIST 17 /* File exists */

#define BPF_MAP_TYPE_PIFO 31

/*
* bpf_packet_dequeue
*
* Dequeue the packet at the head of the PIFO in *map* and return a pointer
* to the packet (or NULL if the PIFO is empty).
*
* Returns
* On success, a pointer to the packet, or NULL if the PIFO is empty. The
* packet pointer must be freed using *bpf_packet_drop()* or returning
* the packet pointer. The *rank* pointer will be set to the rank of
* the dequeued packet on success, or a negative error code on error.
*/
static long (*bpf_packet_dequeue)(void *ctx, void *map, __u64 flags, __u64 *rank) = (void *) 194;


struct flow_address {
struct in6_addr ip;
__u16 port;
__u16 reserved;
};

struct network_tuple {
struct flow_address saddr;
struct flow_address daddr;
__u16 proto;
__u8 ipv;
__u8 reserved;
};

struct flow_state {
__u32 pkts;
__u32 finish_bytes;
};


static __always_inline void *
bpf_map_lookup_or_try_init(void *map, const void *key, const void *init)
{
void *val;
long err;

val = bpf_map_lookup_elem(map, key);
if (val)
return val;

err = bpf_map_update_elem(map, key, init, BPF_NOEXIST);
if (err && err != -EEXIST)
return NULL;

return bpf_map_lookup_elem(map, key);
}

static __always_inline int bpf_max(__u64 left, __u64 right)
{
return right > left ? right : left;
}


/*
* Maps an IPv4 address into an IPv6 address according to RFC 4291 sec 2.5.5.2
*/
static void map_ipv4_to_ipv6(struct in6_addr *ipv6, __be32 ipv4)
{
__builtin_memset(&ipv6->in6_u.u6_addr8[0], 0x00, 10);
__builtin_memset(&ipv6->in6_u.u6_addr8[10], 0xff, 2);
ipv6->in6_u.u6_addr32[3] = ipv4;
}
7 changes: 7 additions & 0 deletions xdp-scheduler-tester/xdp_debug.trace
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Used for debugging the xdp_scheduler_tester syntax
global bpf file=./xdp_scheduler_fifo.bpf.o

udp eth proto=1 dst port=8080 # In-line comment
udp eth proto=2 dst port=8081 payload length=32
dequeue udp eth proto=1 dst port=8080
dequeue udp eth proto=2 dst port=8081 payload length=32
7 changes: 7 additions & 0 deletions xdp-scheduler-tester/xdp_fifo.trace
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
global bpf file=./xdp_scheduler_fifo.bpf.o
udp dst port=8080
udp dst port=8081
udp dst port=8082
dequeue udp dst port=8080
dequeue udp dst port=8081
dequeue udp dst port=8082
7 changes: 7 additions & 0 deletions xdp-scheduler-tester/xdp_pifo.trace
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
global bpf file=./xdp_scheduler_pifo.bpf.o
udp dst port=8002
udp dst port=8000
udp dst port=8001
dequeue udp dst port=8000
dequeue udp dst port=8001
dequeue udp dst port=8002
44 changes: 44 additions & 0 deletions xdp-scheduler-tester/xdp_scheduler_fifo.bpf.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2022 Freysteinn Alfredsson <[email protected]> */

#include <vmlinux_local.h>
#include <linux/bpf.h>
#include <bpf/bpf_endian.h>
#include <bpf/bpf_helpers.h>
#include <xdp/parsing_helpers.h>

#include "bpf_local_helpers.h"

struct {
__uint(type, BPF_MAP_TYPE_PIFO);
__uint(key_size, sizeof(__u32));
__uint(value_size, sizeof(__u32));
__uint(max_entries, 1024);
} pifo_map SEC(".maps");

/* Simple FIFO */
SEC("xdp")
int enqueue_prog(struct xdp_md *xdp)
{
void *data = (void *)(long)xdp->data;
void *data_end = (void *)(long)xdp->data_end;
struct ethhdr *eth = data;

if (eth + 1 > data_end)
return XDP_DROP;

return bpf_redirect_map(&pifo_map, 0, 0);
}

SEC("dequeue")
void *dequeue_prog(struct dequeue_ctx *ctx)
{
__u64 prio = 0;
void *pkt = (void *) bpf_packet_dequeue(ctx, &pifo_map, 0, &prio);
if (!pkt)
return 0;

return pkt;
}

char _license[] SEC("license") = "GPL";
80 changes: 80 additions & 0 deletions xdp-scheduler-tester/xdp_scheduler_pifo.bpf.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2022 Freysteinn Alfredsson <[email protected]> */

#include <vmlinux_local.h>
#include <linux/bpf.h>
#include <bpf/bpf_endian.h>
#include <bpf/bpf_helpers.h>
#include <xdp/parsing_helpers.h>

#include "bpf_local_helpers.h"

struct {
__uint(type, BPF_MAP_TYPE_PIFO);
__uint(key_size, sizeof(__u32));
__uint(value_size, sizeof(__u32));
__uint(max_entries, 1024);
} pifo_map SEC(".maps");


/* Simple PIFO strict priority */
SEC("xdp")
int enqueue_prog(struct xdp_md *xdp)
{
void *data_end = (void *)(long)xdp->data_end;
void *data = (void *)(long)xdp->data;
struct hdr_cursor nh = { .pos = data };
struct ethhdr *eth;
int eth_type;
struct iphdr *iphdr;
struct ipv6hdr *ipv6hdr;
int ip_type;
struct udphdr *udphdr;
int udp_dst_port;
__u16 prio = 0;

/* Parse Ethernet and IP/IPv6 headers */
eth_type = parse_ethhdr(&nh, data_end, &eth);
if (eth_type == bpf_htons(ETH_P_IP)) {
ip_type = parse_iphdr(&nh, data_end, &iphdr);
if (ip_type != IPPROTO_UDP)
goto err;
} else if (eth_type == bpf_htons(ETH_P_IPV6)) {
ip_type = parse_ip6hdr(&nh, data_end, &ipv6hdr);
if (ip_type != IPPROTO_UDP)
goto err;
} else {
goto err;
}

/* Parse UDP header */
if (parse_udphdr(&nh, data_end, &udphdr) < 0)
goto err;
udp_dst_port = bpf_htons(udphdr->dest);

/* Calculate scheduling priority */
prio = 0;
if (udp_dst_port == 8001)
prio = 1;
else if (udp_dst_port > 8001)
prio = 2;


bpf_printk("XDP PIFO scheduled with priority %d", prio);
return bpf_redirect_map(&pifo_map, prio, 0);
err:
bpf_printk("XDP PIFO failed");
return XDP_DROP;
}

SEC("dequeue")
void *dequeue_prog(struct dequeue_ctx *ctx)
{
__u64 prio = 0;
void *pkt = (void *) bpf_packet_dequeue(ctx, &pifo_map, 0, &prio);
if (!pkt)
return 0;
return pkt;
}

char _license[] SEC("license") = "GPL";
Loading

0 comments on commit 8c1bbdf

Please sign in to comment.