From 144e7f1ea4e9d09adff484be183bf5535456c427 Mon Sep 17 00:00:00 2001 From: Frey Alfredsson Date: Wed, 6 Apr 2022 16:11:25 +0200 Subject: [PATCH] Adds the draft of the XDP scheduler testing tool 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 added a way to express priorities to flows from the trace files. For now, the WFQ uses my five-tuples flow implementation with a fixed weight of 256. The xdp_scheduler_tester program includes a -v flag that prints out the enqueued packet. This flag prints more information about each packet to determine if it is correct. It is helpful while changing the code, but I think I will change this behavior in the future because it does not add much for users of the tool. I have an issue with this commit because I cannot make the PIFO in the WFQ larger than 4095, making it unable to process packet priority as virtual-time-bytes. Another issue I will need help with is adding the libbpf logging to the project. I have added the logging.h and logging.c files to lib/util. However, we need to change the Makefiles to include the required dependencies for the logging.c file for it to work. The final issue, I am not sure if it is related to my code or if it is something that is an issue in the kernel. However, my kernel runs out of memory if I run the traces too often. bash-5.1# for i in {1..1000000}; do ./xdp_scheduler_tester --file=./xdp_wfq.trace; done Signed-off-by: Frey Alfredsson --- lib/util/logging.c | 92 +++ lib/util/logging.h | 35 + xdp-scheduler-tester/Makefile | 8 + xdp-scheduler-tester/bpf_local_helpers.h | 73 ++ xdp-scheduler-tester/xdp_debug.trace | 9 + xdp-scheduler-tester/xdp_fifo.trace | 7 + xdp-scheduler-tester/xdp_pifo.trace | 7 + xdp-scheduler-tester/xdp_scheduler_fifo.bpf.c | 44 ++ xdp-scheduler-tester/xdp_scheduler_pifo.bpf.c | 80 +++ xdp-scheduler-tester/xdp_scheduler_tester.c | 651 ++++++++++++++++++ xdp-scheduler-tester/xdp_scheduler_tester.h | 114 +++ xdp-scheduler-tester/xdp_scheduler_wfq.bpf.c | 197 ++++++ xdp-scheduler-tester/xdp_test.trace | 7 + xdp-scheduler-tester/xdp_wfq.trace | 22 + 14 files changed, 1346 insertions(+) create mode 100644 lib/util/logging.c create mode 100644 lib/util/logging.h create mode 100644 xdp-scheduler-tester/Makefile create mode 100644 xdp-scheduler-tester/bpf_local_helpers.h create mode 100644 xdp-scheduler-tester/xdp_debug.trace create mode 100644 xdp-scheduler-tester/xdp_fifo.trace create mode 100644 xdp-scheduler-tester/xdp_pifo.trace create mode 100644 xdp-scheduler-tester/xdp_scheduler_fifo.bpf.c create mode 100644 xdp-scheduler-tester/xdp_scheduler_pifo.bpf.c create mode 100644 xdp-scheduler-tester/xdp_scheduler_tester.c create mode 100644 xdp-scheduler-tester/xdp_scheduler_tester.h create mode 100644 xdp-scheduler-tester/xdp_scheduler_wfq.bpf.c create mode 100644 xdp-scheduler-tester/xdp_test.trace create mode 100644 xdp-scheduler-tester/xdp_wfq.trace diff --git a/lib/util/logging.c b/lib/util/logging.c new file mode 100644 index 00000000..dc0c23b0 --- /dev/null +++ b/lib/util/logging.c @@ -0,0 +1,92 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#include +#include + +#include +#include + +#include "logging.h" +#include "util.h" + +static enum logging_print_level log_level = LOG_INFO; + +static int print_func(enum logging_print_level level, const char *format, + va_list args) +{ + if (level > log_level) + return 0; + + return vfprintf(stderr, format, args); +} + +static int libbpf_print_func(enum libbpf_print_level level, const char *format, + va_list args) +{ + return print_func(level + 1, format, args); +} + +static int libbpf_silent_func(__unused enum libbpf_print_level level, + __unused const char *format, + __unused va_list args) +{ + return 0; +} + +static int libxdp_print_func(enum libxdp_print_level level, const char *format, + va_list args) +{ + return print_func(level + 1, format, args); +} + +static int libxdp_silent_func(__unused enum libxdp_print_level level, + __unused const char *format, + __unused va_list args) +{ + return 0; +} + +#define __printf(a, b) __attribute__((format(printf, a, b))) + +__printf(2, 3) void logging_print(enum logging_print_level level, + const char *format, ...) +{ + va_list args; + + va_start(args, format); + print_func(level, format, args); + va_end(args); +} + +void init_lib_logging(void) +{ + libbpf_set_print(libbpf_print_func); + libxdp_set_print(libxdp_print_func); +} + +void silence_libbpf_logging(void) +{ + if (log_level < LOG_VERBOSE) + libbpf_set_print(libbpf_silent_func); +} + +void silence_libxdp_logging(void) +{ + if (log_level < LOG_VERBOSE) + libxdp_set_print(libxdp_silent_func); +} + +enum logging_print_level set_log_level(enum logging_print_level level) +{ + enum logging_print_level old_level = log_level; + + log_level = level; + return old_level; +} + +enum logging_print_level increase_log_level(void) +{ + if (log_level < LOG_VERBOSE) + log_level++; + return log_level; +} diff --git a/lib/util/logging.h b/lib/util/logging.h new file mode 100644 index 00000000..16c4e744 --- /dev/null +++ b/lib/util/logging.h @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __LOGGING_H +#define __LOGGING_H + +/* This matches the libbpf logging levels, but with an additional VERBOSE level; + * we demote all libbpf messages by one level so debug messages only show up on + * VERBOSE. + */ +enum logging_print_level { + LOG_WARN, + LOG_INFO, + LOG_DEBUG, + LOG_VERBOSE, +}; + +extern void logging_print(enum logging_print_level level, const char *format, + ...) __attribute__((format(printf, 2, 3))); + +#define __pr(level, fmt, ...) \ + do { \ + logging_print(level, fmt, ##__VA_ARGS__); \ + } while (0) + +#define pr_warn(fmt, ...) __pr(LOG_WARN, fmt, ##__VA_ARGS__) +#define pr_info(fmt, ...) __pr(LOG_INFO, fmt, ##__VA_ARGS__) +#define pr_debug(fmt, ...) __pr(LOG_DEBUG, fmt, ##__VA_ARGS__) + +void init_lib_logging(void); +void silence_libbpf_logging(void); +void silence_libxdp_logging(void); +enum logging_print_level set_log_level(enum logging_print_level level); +enum logging_print_level increase_log_level(); + +#endif diff --git a/xdp-scheduler-tester/Makefile b/xdp-scheduler-tester/Makefile new file mode 100644 index 00000000..aa1582ff --- /dev/null +++ b/xdp-scheduler-tester/Makefile @@ -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 diff --git a/xdp-scheduler-tester/bpf_local_helpers.h b/xdp-scheduler-tester/bpf_local_helpers.h new file mode 100644 index 00000000..91dde6da --- /dev/null +++ b/xdp-scheduler-tester/bpf_local_helpers.h @@ -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; +static long (*bpf_packet_drop)(void *ctx, void *pkt) = (void *) 195; + +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; +} diff --git a/xdp-scheduler-tester/xdp_debug.trace b/xdp-scheduler-tester/xdp_debug.trace new file mode 100644 index 00000000..81778061 --- /dev/null +++ b/xdp-scheduler-tester/xdp_debug.trace @@ -0,0 +1,9 @@ +# 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 +udp eth proto=3 dst port=8082 repeat=2 +dequeue udp eth proto=1 dst port=8080 +dequeue udp eth proto=2 dst port=8081 payload length=32 +dequeue udp eth proto=3 dst port=8082 repeat=2 diff --git a/xdp-scheduler-tester/xdp_fifo.trace b/xdp-scheduler-tester/xdp_fifo.trace new file mode 100644 index 00000000..655b80d3 --- /dev/null +++ b/xdp-scheduler-tester/xdp_fifo.trace @@ -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 diff --git a/xdp-scheduler-tester/xdp_pifo.trace b/xdp-scheduler-tester/xdp_pifo.trace new file mode 100644 index 00000000..7cdf50a4 --- /dev/null +++ b/xdp-scheduler-tester/xdp_pifo.trace @@ -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 diff --git a/xdp-scheduler-tester/xdp_scheduler_fifo.bpf.c b/xdp-scheduler-tester/xdp_scheduler_fifo.bpf.c new file mode 100644 index 00000000..2a53254d --- /dev/null +++ b/xdp-scheduler-tester/xdp_scheduler_fifo.bpf.c @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2022 Freysteinn Alfredsson */ + +#include +#include +#include +#include +#include + +#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"; diff --git a/xdp-scheduler-tester/xdp_scheduler_pifo.bpf.c b/xdp-scheduler-tester/xdp_scheduler_pifo.bpf.c new file mode 100644 index 00000000..d72cec5b --- /dev/null +++ b/xdp-scheduler-tester/xdp_scheduler_pifo.bpf.c @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2022 Freysteinn Alfredsson */ + +#include +#include +#include +#include +#include + +#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, ð); + 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"; diff --git a/xdp-scheduler-tester/xdp_scheduler_tester.c b/xdp-scheduler-tester/xdp_scheduler_tester.c new file mode 100644 index 00000000..f1fa9bb3 --- /dev/null +++ b/xdp-scheduler-tester/xdp_scheduler_tester.c @@ -0,0 +1,651 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2022 Freysteinn Alfredsson */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +//#include + +#include "xdp_scheduler_tester.h" + +#define BPF_F_TEST_XDP_DO_REDIRECT (1U << 1) +#define BPF_PROG_TYPE_DEQUEUE 32 + +static const struct option long_options[] = { + {"file", required_argument, NULL, 'f' }, + {"verbose", no_argument, NULL, 'v' }, + {"help", no_argument, NULL, 'h' }, + {} +}; + + +/* + * Parser commands + */ + +/* Global commands */ +trace_cmd cmd_global_bpf[] = { + { "xdp_func", NULL, NULL, cmd_g_bpf_xdp_fn, CMD_ARG_STR }, + { "dequeue_func", NULL, NULL, cmd_g_bpf_dequeue_fn, CMD_ARG_STR }, + { "file", NULL, NULL, cmd_g_bpf_file_fn, CMD_ARG_STR }, + {} +}; + +trace_cmd cmd_global[] = { + { "bpf", cmd_global_bpf, NULL, NULL, CMD_SUB_CMD }, + {} +}; + + +/* UDP commands */ +trace_cmd cmd_udp_eth[] = { + { "proto", NULL, NULL, cmd_udp_eth_proto_fn, CMD_ARG_INT }, + {} +}; + +trace_cmd cmd_udp_dst[] = { + { "port", NULL, NULL, cmd_udp_dst_port_fn, CMD_ARG_INT }, + { "ip", NULL, NULL, cmd_udp_dst_ip_fn, CMD_ARG_STR }, + {} +}; + +trace_cmd cmd_udp_src[] = { + { "port", NULL, NULL, NULL, CMD_ARG_INT }, + { "ip", NULL, NULL, NULL, CMD_ARG_STR }, + {} +}; + +trace_cmd cmd_udp_payload[] = { + { "data", NULL, NULL, cmd_udp_p_data_fn, CMD_ARG_STR }, + { "length", NULL, NULL, cmd_udp_p_length_fn, CMD_ARG_INT }, + {} +}; + +trace_cmd cmd_udp[] = { + { "eth", cmd_udp_eth, NULL, NULL, CMD_SUB_CMD }, + { "dst", cmd_udp_dst, NULL, NULL, CMD_SUB_CMD }, + { "src", cmd_udp_src, NULL, NULL, CMD_SUB_CMD }, + { "payload", cmd_udp_payload, NULL, NULL, CMD_SUB_CMD }, + { "repeat", NULL, NULL, cmd_repeat_fn, CMD_ARG_INT }, + {} +}; + + +/* Dequeue commands */ +trace_cmd cmd_d_udp_eth[] = { + { "proto", NULL, NULL, cmd_d_udp_eth_proto_fn, CMD_ARG_INT }, + {} +}; + +trace_cmd cmd_d_udp_dst[] = { + { "port", NULL, NULL, cmd_d_udp_dst_port_fn, CMD_ARG_INT }, + { "ip", NULL, NULL, cmd_d_udp_dst_ip_fn, CMD_ARG_STR }, + {} +}; + +trace_cmd cmd_d_udp_src[] = { + { "port", NULL, NULL, NULL, CMD_ARG_INT }, + { "ip", NULL, NULL, NULL, CMD_ARG_STR }, + {} +}; + +trace_cmd cmd_d_udp_payload[] = { + { "data", NULL, NULL, cmd_d_udp_p_data_fn, CMD_ARG_STR }, + { "length", NULL, NULL, cmd_d_udp_p_length_fn, CMD_ARG_INT }, + {} +}; + +trace_cmd cmd_dequeue_udp[] = { + { "eth", cmd_d_udp_eth, NULL, NULL, CMD_SUB_CMD }, + { "dst", cmd_d_udp_dst, NULL, NULL, CMD_SUB_CMD }, + { "src", cmd_d_udp_src, NULL, NULL, CMD_SUB_CMD }, + { "payload", cmd_d_udp_payload, NULL, NULL, CMD_SUB_CMD }, + { "repeat", NULL, NULL, cmd_repeat_fn, CMD_ARG_INT }, + {} +}; + +trace_cmd cmd_dequeue[] = { + { "udp", cmd_dequeue_udp, cmd_d_udp_init_fn, cmd_d_udp_fn, CMD_SUB_CMD }, + {} +}; + +/* Main commands */ +trace_cmd cmd_main[] = { + { "global", cmd_global, NULL, NULL, CMD_SUB_CMD }, + { "udp", cmd_udp, cmd_udp_init_fn, cmd_udp_fn, CMD_SUB_CMD }, + { "dequeue", cmd_dequeue, NULL, NULL, CMD_SUB_CMD }, + {} +}; + + +static struct ipv6_udp_packet global_udp_pkt_v6 = { + .iph.version = 6, + .eth.h_proto = __bpf_constant_htons(ETH_P_IPV6), + .eth.h_source = {1, 0, 0, 0, 0, 1}, + .eth.h_dest = {1, 0, 0, 0, 0, 2}, + .iph.nexthdr = IPPROTO_UDP, + .iph.payload_len = bpf_htons(sizeof(struct ipv6_udp_packet) + - offsetof(struct ipv6_udp_packet, udp)), + .iph.hop_limit = 1, + .iph.saddr.s6_addr16 = {bpf_htons(0xfe80), 0, 0, 0, 0, 0, 0, bpf_htons(1)}, + .iph.daddr.s6_addr16 = {bpf_htons(0xfe80), 0, 0, 0, 0, 0, 0, bpf_htons(2)}, + .udp.source = bpf_htons(1), + .udp.dest = bpf_htons(1), + .udp.len = bpf_htons(sizeof(struct udphdr)), +}; + +void die(struct config *cfg, const char *format, ...) { + va_list args; + fprintf(stderr, "%s:%d:%s: ", cfg->line, cfg->line_nr, cfg->token); + + va_start(args, format); + vfprintf(stderr, format, args); + va_end(args); + fprintf(stderr, "\n"); + exit(EXIT_FAILURE); +} + +/* Get the mac address of the interface given interface name */ +void setmac(struct config *cfg) +{ + struct ipv6_udp_packet *pkt = cfg->global.pkt; + struct ifreq ifr; + int fd; + + fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd < 0) { + fprintf(stderr, "socket failed...\n"); + exit(EXIT_FAILURE); + } + ifr.ifr_addr.sa_family = AF_INET; + memcpy(&ifr.ifr_name, cfg->global.ifname, IFNAMSIZ); + if (ioctl(fd, SIOCGIFHWADDR, &ifr) < 0) { + fprintf(stderr, "ioctl failed leaving...\n"); + close(fd); + exit(EXIT_FAILURE); + } + for (int i = 0; i < 6 ; i++) { + pkt->eth.h_source[i] = (__u8)ifr.ifr_hwaddr.sa_data[i]; + } + close(fd); +} + +static __be16 calc_udp_cksum(const struct ipv6_udp_packet *pkt) +{ + __u32 chksum = pkt->iph.nexthdr + bpf_ntohs(pkt->iph.payload_len); + int i; + + for (i = 0; i < 8; i++) { + chksum += bpf_ntohs(pkt->iph.saddr.s6_addr16[i]); + chksum += bpf_ntohs(pkt->iph.daddr.s6_addr16[i]); + } + chksum += bpf_ntohs(pkt->udp.source); + chksum += bpf_ntohs(pkt->udp.dest); + chksum += bpf_ntohs(pkt->udp.len); + + while (chksum >> 16) + chksum = (chksum & 0xFFFF) + (chksum >> 16); + return bpf_htons(~chksum); +} + + +/* Global commands */ +static void set_bpf_func_name(char *func, char *value, struct config *cfg) +{ + if (func) { + free(func); + func = NULL; + } + func = strdup(value); + if (!func) + die(cfg, "%s", strerror(errno)); +} + +static void mac_to_string(char *dst, unsigned char *mac) +{ + snprintf(dst, 18, "%02x:%02x:%02x:%02x:%02x:%02x", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); +} + +static void print_pkt(struct ipv6_udp_packet *pkt, struct config *cfg) +{ + char src_mac[18]; + char dst_mac[18]; + mac_to_string(src_mac, pkt->eth.h_source); + mac_to_string(dst_mac, pkt->eth.h_dest); + char src_ip[INET6_ADDRSTRLEN + 1]; + char dst_ip[INET6_ADDRSTRLEN + 1]; + inet_ntop(AF_INET6, &pkt->iph.saddr, (char *) &src_ip, sizeof(src_ip)); + inet_ntop(AF_INET6, &pkt->iph.daddr, (char *) &dst_ip, sizeof(dst_ip)); + + printf("pkt( len: %lu\n", cfg->state.udp.pkt_size); + printf("\teth(\n"); + printf("\t\tproto: %x\n", __bpf_ntohs(pkt->eth.h_proto)); + printf("\t\tsrc: %s\n", src_mac); + printf("\t\tdst: %s\n", dst_mac); + printf("\t)\n"); + printf("\tipv6(\n"); + printf("\t\tversion: %d\n", (int) pkt->iph.version); + printf("\t\tpriority: %d\n", (int) pkt->iph.priority); + printf("\t\tflow_label: %x", (int) pkt->iph.flow_lbl[0]); + printf("%x", (int) pkt->iph.flow_lbl[1]); + printf("%x\n", (int) pkt->iph.flow_lbl[2]); + printf("\t\tpayload_length: %hu\n", __bpf_ntohs(pkt->iph.payload_len)); + printf("\t\tnext_header: %d\n", (unsigned int) pkt->iph.nexthdr); + printf("\t\thop_limit: %d\n", (unsigned int) pkt->iph.hop_limit); + printf("\t\tsrc_ip: %s\n", src_ip); + printf("\t\tdst_ip: %s\n", dst_ip); + printf("\t)\n"); + printf("\tudp(\n"); + printf("\t\tsrt_port: %hu\n", __bpf_htons(pkt->udp.source)); + printf("\t\tdst_port: %hu\n", __bpf_htons(pkt->udp.dest)); + printf("\t\tlength: %hu\n", __bpf_htons(pkt->udp.len)); + printf("\t\tcheck: %hu\n", __bpf_htons(pkt->udp.check)); + printf("\t\tpayload: "); + for (int i = 0; i < __bpf_htons(pkt->udp.len); i++) { + printf("%02x ", ((unsigned char *) pkt->payload)[i]); + } + printf("\n"); + printf("\t)\n"); + printf(" )\n"); +} + +void cmd_g_bpf_xdp_fn(void *parameter, struct config *cfg) +{ + char *func_value = parameter; + set_bpf_func_name(func_value, cfg->xdp_func, cfg); +} + +void cmd_g_bpf_dequeue_fn(void *parameter, struct config *cfg) +{ + char *func_value = parameter; + set_bpf_func_name(func_value, cfg->dequeue_func, cfg); +} + +static void set_bpf_fd(struct bpf_object *obj, char *func_name, int *prog_fd, struct config *cfg) +{ + struct bpf_program *prog = bpf_object__find_program_by_name(obj, func_name); + *prog_fd = bpf_program__fd(prog); + if (*prog_fd < 0 ) { + bpf_object__close(obj); + die(cfg, "Failed to run bpf_program__fd: %s", strerror(errno)); + } +} + +void cmd_g_bpf_file_fn(void *parameter, struct config *cfg) +{ + char *filename = (char *) parameter; + struct bpf_object *sched_bpf_obj; + char *xdp_func_name = cfg->xdp_func ? cfg->xdp_func : "enqueue_prog"; + char *dequeue_func_name = cfg->dequeue_func ? cfg->dequeue_func : "dequeue_prog"; + int err = 0; + + sched_bpf_obj = bpf_object__open_file(filename, NULL); + err = libbpf_get_error(sched_bpf_obj); + if (err) + die(cfg, "Failed to run bpf_object__open"); + + struct bpf_program *prog; + prog = bpf_object__find_program_by_name(sched_bpf_obj, dequeue_func_name); + if (!prog) { + bpf_object__close(sched_bpf_obj); + die(cfg, "Failed to run bpf_object_find_program_by_name"); + } + + bpf_program__set_type(prog, BPF_PROG_TYPE_DEQUEUE); + err = bpf_object__load(sched_bpf_obj); + if (err) { + bpf_object__close(sched_bpf_obj); + die(cfg, "Failed to run bpf_object__load"); + } + + set_bpf_fd(sched_bpf_obj, xdp_func_name, &cfg->xdp_prog_fd, cfg); + set_bpf_fd(sched_bpf_obj, dequeue_func_name, &cfg->dequeue_prog_fd, cfg); +} + +void cmd_repeat_fn(void *parameter, struct config *cfg) +{ + int repeat = *((int *) parameter); + if (cfg->is_repeat) + return; + if (repeat < 1) + die(cfg, "Repeat count can't be less than zero"); + + cfg->repeat = repeat; + cfg->is_repeat = true; +} + + +/* UDP commands */ +void cmd_udp_eth_proto_fn(void *parameter, struct config *cfg) +{ + int proto = *((int *) parameter); + struct ipv6_udp_packet *pkt = cfg->state.udp.pkt; + if (proto < 0 || proto > 65535) + die(cfg, "Ethernet protocol out of bounds %d", proto); + + pkt->eth.h_proto = __bpf_htons(proto); +} + +void cmd_udp_dst_port_fn(void *parameter, struct config *cfg) +{ + int port = *((int *) parameter); + struct ipv6_udp_packet *pkt = cfg->state.udp.pkt; + if (port < 0 || port > 65535) + die(cfg, "UDP port out of bounds %d", port); + + pkt->udp.dest = __bpf_htons(port); +} + +void cmd_udp_dst_ip_fn(void *parameter, struct config *cfg) +{ + char *ip = (char *) parameter; + if (!inet_pton(AF_INET6, ip, &cfg->state.udp.pkt->iph.daddr)) + die(cfg, "Failed to set dst IPv6 address to %s", ip); +} + +void cmd_udp_p_data_fn(void *parameter, struct config *cfg) +{ + die(cfg, "Command payload data not implemeneted"); +} + +void cmd_udp_p_length_fn(void *parameter, struct config *cfg) +{ + int udp_payload_length = *((int *) parameter); + int previous_length = cfg->state.udp.pkt->udp.len - sizeof(struct udphdr); + if (udp_payload_length < 0) + die(cfg, "The length parameter can not be negative: %d", udp_payload_length); + + cfg->state.udp.pkt_size = sizeof(struct ipv6_udp_packet) + udp_payload_length; + + cfg->state.udp.pkt = realloc(cfg->state.udp.pkt, cfg->state.udp.pkt_size); + if (!cfg->state.udp.pkt) + die(cfg, "Failed to allocate memory"); + + memset(cfg->state.udp.pkt->payload + previous_length, '\0', udp_payload_length); + cfg->state.udp.pkt->udp.len = bpf_htons(sizeof(struct udphdr) + udp_payload_length); +} + +void cmd_udp_init_fn(void *none, struct config *cfg) +{ + struct ipv6_udp_packet *pkt = malloc(sizeof(*pkt) + cfg->global.udp_payload_size); + memcpy(pkt, cfg->global.pkt, sizeof(*pkt) + cfg->global.udp_payload_size); + cfg->state.udp.pkt = pkt; + cfg->state.udp.pkt_size = sizeof(*pkt) + cfg->global.udp_payload_size; +} + +void cmd_udp_fn(void *none, struct config *cfg) +{ + int err; + struct ipv6_udp_packet *pkt = cfg->state.udp.pkt; + if (cfg->xdp_prog_fd <= 0) + die(cfg, "No XDP hook attached"); + + pkt->iph.payload_len = bpf_htons(cfg->state.udp.pkt_size + - offsetof(struct ipv6_udp_packet, udp)), + pkt->udp.check = calc_udp_cksum(pkt); + struct xdp_md ctx_in = { + .data_end = cfg->state.udp.pkt_size, + }; + DECLARE_LIBBPF_OPTS(bpf_test_run_opts, opts, + .data_in = pkt, + .data_size_in = cfg->state.udp.pkt_size, + .ctx_in = &ctx_in, + .ctx_size_in = sizeof(ctx_in), + .repeat = 1, + .flags = BPF_F_TEST_XDP_DO_REDIRECT, + ); + ctx_in.data_end = ctx_in.data + cfg->state.udp.pkt_size; + if (cfg->verbose) + print_pkt(pkt, cfg); + err = bpf_prog_test_run_opts(cfg->xdp_prog_fd, &opts); + if (err) + die(cfg, "Failed to run XDP hook"); + + free(cfg->state.udp.pkt); + cfg->state.udp.pkt_size = 0; + cfg->packet_cnt++; +} + + +/* Dequeue commands */ +void cmd_d_udp_eth_proto_fn(void *parameter, struct config *cfg) +{ + int proto = *((int *) parameter); + struct ipv6_udp_packet *pkt = cfg->state.udp.pkt; + __be16 pkt_proto = __bpf_ntohs(pkt->eth.h_proto); + if (pkt_proto != proto) + die(cfg, "Expected ethernet protocol %d but found %hd", proto, pkt_proto); +} + +void cmd_d_udp_dst_port_fn(void *parameter, struct config *cfg) +{ + int port = *((int *) parameter); + struct ipv6_udp_packet *pkt = cfg->state.udp.pkt; + int pkt_dport = __bpf_ntohs(pkt->udp.dest); + if (pkt_dport != port) + die(cfg, "Expected UDP destination port %d but found %d", port, pkt_dport); +} + +void cmd_d_udp_dst_ip_fn(void *parameter, struct config *cfg) +{ + char *ip = (char *) parameter; + struct in6_addr dst_ip; + char pkt_dst_ip[INET6_ADDRSTRLEN + 1]; + struct ipv6_udp_packet *pkt = cfg->state.udp.pkt; + if (!inet_pton(AF_INET6, ip, (char *) &dst_ip)) + die(cfg, "Failed to parse IPv6 address %s", ip); + + if (memcmp(&pkt->iph.daddr, &dst_ip, sizeof(struct in6_addr))) { + inet_ntop(AF_INET6, &pkt->iph.daddr, (char *) &pkt_dst_ip, + sizeof(pkt_dst_ip)); + die(cfg, "Expected IPv6 address %s but found %s", ip, pkt_dst_ip); + } +} + +void cmd_d_udp_p_data_fn(void *parameter, struct config *cfg) +{ + die(cfg, "Command payload data not implemeneted"); +} + +void cmd_d_udp_p_length_fn(void *parameter, struct config *cfg) +{ + int expected_payload_length = *((int *) parameter); + int current_payload_length = bpf_ntohs(cfg->state.udp.pkt->udp.len) - sizeof(struct udphdr); + if (current_payload_length != expected_payload_length) + die(cfg, "Expected a packet of length %d, but found %d", + expected_payload_length, current_payload_length); +} + +void cmd_d_udp_init_fn(void *parameter, struct config *cfg) +{ + const int packet_size = 4096; + int err; + struct ipv6_udp_packet *pkt = calloc(packet_size, 1); + if (cfg->dequeue_prog_fd <= 0) + die(cfg, "No DEQUEUE hook attached"); + + cfg->state.udp.pkt = pkt; + cfg->state.udp.pkt_size = packet_size; + DECLARE_LIBBPF_OPTS(bpf_test_run_opts, opts, + .data_out = pkt, + .data_size_out = packet_size, + .repeat = 1, + ); + + err = bpf_prog_test_run_opts(cfg->dequeue_prog_fd, &opts); + if (err) + die(cfg, "Failed to run DEQUEUE hook"); + + cfg->packet_cnt--; +} + +void cmd_d_udp_fn(void *none, struct config *cfg) +{ + free(cfg->state.udp.pkt); +} + + +char *parse_and_run_line(char *line, trace_cmd *cmd_category, struct config *cfg) +{ + char *token = NULL; + trace_cmd *cmd; + int has_unidentified = true; + while ((token = strtok(line, " \t\n"))) { + cfg->token = token; + if (line) + line = NULL; + if (token[0] == '#') + return NULL; + cmd = cmd_category; + has_unidentified = true; + + do { + int int_value = 0; + char str_value[MAX_LINE] = {0}; + void *parameter = NULL; + char token_format[MAX_TOKEN + 1]; + if (!cmd->command) + break; + strncpy(token_format, cmd->command, MAX_TOKEN - 1); + switch (cmd->type) { + case CMD_SUB_CMD: + if (strcmp(cmd->command, token)) + continue; + + if (cmd->init_func) + cmd->init_func(NULL, cfg); + if (cmd->next) + token = parse_and_run_line(NULL, cmd->next, cfg); + if (cmd->func) + cmd->func(parameter, cfg); + + if (!token) + return NULL; + cmd = cmd_category; + cmd--; // Reset offset for next loop + continue; + case CMD_ARG_INT: + strncat(token_format, " = %d", MAX_TOKEN - strlen(token_format)); // NOTE: Buffer overflow issue + if (!sscanf(token, token_format, &int_value)) + continue; + parameter = &int_value; + break; + case CMD_ARG_STR: + strncat(token_format, " = %s", MAX_TOKEN - strlen(token_format)); // NOTE: Buffer overflow issue + if (!sscanf(token, token_format, str_value)) + continue; + parameter = str_value; + break; + default: + /* We should never end in this case */ + die(cfg, "Unknown element type found!"); + } + if (cmd->func) + cmd->func(parameter, cfg); + has_unidentified = false; + break; + } while (++cmd); + + if (has_unidentified) + return token; + } + return NULL; +} + +void run_file(FILE *trace_file, struct config *cfg) +{ + char *token = NULL; + char *line; + cfg->line_nr = 1; + while (getline(&cfg->line, &cfg->line_buffer_length, trace_file) != -1) { + *strchr(cfg->line, '\n') = 0; + while (cfg->repeat >= 1) { + line = strdup(cfg->line); + token = parse_and_run_line(line, cmd_main, cfg); + if (token) + die(cfg, "Unknown command: '%s'", token); + cfg->repeat--; + free(line); + } + cfg->line_nr++; + cfg->repeat = 1; + cfg->is_repeat = false; + } +} + +void set_default_values(struct config *cfg) +{ + cfg->global.pkt = &global_udp_pkt_v6; + cfg->global.udp_payload_size = 0; + cfg->repeat = 1; + cfg->is_repeat = false; +} + +int main(int argc, char **argv) +{ + struct config cfg = {0}; + + //init_lib_logging(); + + set_default_values(&cfg); + + FILE *trace_file = stdin; + int opt; + while ((opt = getopt_long(argc, argv, "f:vh", long_options, NULL)) != -1) { + switch (opt) { + case 'f': + trace_file = fopen(optarg, "r"); + if (trace_file == NULL) { + fprintf(stderr, "Error opening file %s: %s\n", optarg, + strerror(errno)); + exit(EXIT_FAILURE); + } + break; + case 'v': + cfg.verbose = 1; + //set_log_level(LOG_VERBOSE); + break; + case 'h': + default: + //sample_usage(argv, long_options, __doc__, mask, error); + exit(EXIT_SUCCESS); + } + } + + run_file(trace_file, &cfg); + + if (cfg.packet_cnt) { + fprintf(stderr, "Failed: %d packets still remain\n", cfg.packet_cnt); + exit(EXIT_FAILURE); + } + + return EXIT_SUCCESS; +} diff --git a/xdp-scheduler-tester/xdp_scheduler_tester.h b/xdp-scheduler-tester/xdp_scheduler_tester.h new file mode 100644 index 00000000..5645947e --- /dev/null +++ b/xdp-scheduler-tester/xdp_scheduler_tester.h @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2022 Freysteinn Alfredsson + */ + +#ifndef XDP_SCHEDULER_TESTER_H_ +#define XDP_SCHEDULER_TESTER_H_ + +#define MAX_LINE (1 << 10) +#define MAX_TOKEN (1 << 6) + +#define DEFAULT_XDP_FUNC "enqueue_prog" +#define DEFAULT_DEQUEUE_FUNC "dequeue_prog" + +struct cmd_udp_state { + struct ipv6_udp_packet *pkt; + size_t pkt_size; +}; + +struct cmd_udp_dequeue_state { + // TODO: The current version dequeues a packet and checks its content. + // It should add what to check to this state and then dequeue a packet. + // This change makes it easier to add the repeat command for dequeued + // packets. +}; + +union cmd_state { + struct cmd_udp_state udp; +}; + +struct ipv6_udp_packet { + struct ethhdr eth; + struct ipv6hdr iph; + struct udphdr udp; + __u8 payload[]; +} __attribute__((__packed__)); + +struct config { + struct { + struct ipv6_udp_packet *pkt; + size_t udp_payload_size; + int ifindex; + char ifname[IFNAMSIZ]; + } global; + + union cmd_state state; + + // BPF + char *xdp_func; + char *dequeue_func; + int xdp_prog_fd; + int dequeue_prog_fd; + + int packet_cnt; + + char *token; + char *line; + size_t line_buffer_length; + int line_nr; + int verbose; + + int repeat; + bool is_repeat; +}; + + +typedef struct trace_cmd trace_cmd; +typedef void (*trace_fn)(void *parameter, struct config *cfg); + +enum command_type { + CMD_NULL = 0, + CMD_SUB_CMD, + CMD_ARG_INT, + CMD_ARG_STR, + CMD_ARG_IP, // NOTE: Implement argument ip +}; + +typedef struct trace_cmd { + char *command; + trace_cmd *next; + trace_fn init_func; + trace_fn func; + enum command_type type; +} trace_cmd; + + +void setmac(struct config *cfg); + +void cmd_g_bpf_xdp_fn(void *func, struct config *cfg); +void cmd_g_bpf_dequeue_fn(void *func, struct config *cfg); +void cmd_g_bpf_file_fn(void *filename, struct config *cfg); + +void cmd_repeat_fn(void *repeat, struct config *cfg); + +void cmd_udp_eth_proto_fn(void *proto, struct config *cfg); +void cmd_udp_dst_port_fn(void *port, struct config *cfg); +void cmd_udp_dst_ip_fn(void *ip, struct config *cfg); +void cmd_udp_p_data_fn(void *data, struct config *cfg); +void cmd_udp_p_length_fn(void *length, struct config *cfg); +void cmd_udp_init_fn(void *none, struct config *cfg); +void cmd_udp_fn(void *none, struct config *cfg); + +void cmd_d_udp_eth_proto_fn(void *proto, struct config *cfg); +void cmd_d_udp_dst_port_fn(void *port, struct config *cfg); +void cmd_d_udp_dst_ip_fn(void *ip, struct config *cfg); +void cmd_d_udp_p_data_fn(void *data, struct config *cfg); +void cmd_d_udp_p_length_fn(void *length, struct config *cfg); +void cmd_d_udp_init_fn(void *none, struct config *cfg); +void cmd_d_udp_fn(void *none, struct config *cfg); + +char *parse_and_run_line(char *line, trace_cmd *cmd_category, struct config *cfg); +void run_file(FILE *trace_file, struct config *cfg); +void set_default_values(struct config *cfg); + +#endif // XDP_SCHEDULER_TESTER_H_ diff --git a/xdp-scheduler-tester/xdp_scheduler_wfq.bpf.c b/xdp-scheduler-tester/xdp_scheduler_wfq.bpf.c new file mode 100644 index 00000000..d9a07e12 --- /dev/null +++ b/xdp-scheduler-tester/xdp_scheduler_wfq.bpf.c @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2022 Freysteinn Alfredsson */ + +/* + * Weighted Fair Queueing (WFQ) + * + * TODO: This code only supports UDP and IPv6. However, it does have some IPv4 handling + */ + +#include +#include +#include +#include +#include + +#include "bpf_local_helpers.h" + +struct parsing_context { + void *data; // Start of eth hdr + void *data_end; // End of safe acessible area + struct hdr_cursor nh; // Position to parse next + __u32 pkt_len; // Full packet length (headers+data) +}; + +struct packet_info { + struct ethhdr *eth; + union { + struct iphdr *iph; + struct ipv6hdr *ip6h; + }; + union { + struct udphdr *udph; + }; + struct network_tuple flow; + int eth_type; + int ip_type; +}; + +struct { + __uint(type, BPF_MAP_TYPE_PIFO); + __uint(key_size, sizeof(__u32)); + __uint(value_size, sizeof(__u32)); + __uint(max_entries, 4096); +} pifo_map SEC(".maps"); + +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __type(key, struct network_tuple); + __type(value, struct flow_state); + __uint(max_entries, 16384); +} flow_state SEC(".maps"); + +__u32 flow_count = 0; + +__u32 weight = 256; // TODO: Currently not used. Needs a per flow definition. +__u64 virtual_time_bytes = 0; + + +/* This function currently only supports UDP packets */ +static __always_inline int parse_packet(struct parsing_context *pctx, struct packet_info *p_info) +{ + /* Parse Ethernet and IP/IPv6 headers */ + p_info->eth_type = parse_ethhdr(&pctx->nh, pctx->data_end, &p_info->eth); + if (p_info->eth_type == bpf_htons(ETH_P_IP)) { + p_info->ip_type = parse_iphdr(&pctx->nh, pctx->data_end, &p_info->iph); + if (p_info->ip_type < 0) + goto err; + map_ipv4_to_ipv6(&p_info->flow.saddr.ip, p_info->iph->saddr); + map_ipv4_to_ipv6(&p_info->flow.daddr.ip, p_info->iph->daddr); + } else if (p_info->eth_type == bpf_htons(ETH_P_IPV6)) { + p_info->ip_type = parse_ip6hdr(&pctx->nh, pctx->data_end, &p_info->ip6h); + if (p_info->ip_type < 0) + goto err; + p_info->flow.saddr.ip = p_info->ip6h->saddr; + p_info->flow.daddr.ip = p_info->ip6h->daddr; + } else { + goto err; + } + + /* Parse UDP header */ + if (p_info->ip_type != IPPROTO_UDP) + goto err; + if (parse_udphdr(&pctx->nh, pctx->data_end, &p_info->udph) < 0) + goto err; + + p_info->flow.saddr.port = p_info->udph->source; + p_info->flow.daddr.port = p_info->udph->dest; + + return 0; +err: + bpf_printk("Failed to parse UDP packet"); + return -1; +} + +static __always_inline int schedule_packet(struct parsing_context *pctx) +{ + struct packet_info p_info = {}; + struct network_tuple nt; + struct flow_state *flow; + struct flow_state new_flow; + __u32 start_time_bytes; + __u32 prio = 0; + + /* Get flow */ + if (parse_packet(pctx, &p_info) < 0) + goto err; + + nt = p_info.flow; + + flow = bpf_map_lookup_elem(&flow_state, &nt); + if (!flow) { + new_flow.pkts = 0; + new_flow.finish_bytes = 0; + flow = &new_flow; + } + flow->pkts++; + + /* Calculate scheduling priority */ + start_time_bytes = bpf_max(virtual_time_bytes, flow->finish_bytes); + flow->finish_bytes = start_time_bytes + pctx->pkt_len * weight; + prio = start_time_bytes; + + if (bpf_map_update_elem(&flow_state, &nt, flow, BPF_ANY)) + goto err; + + bpf_printk("src: %pi6:%d --> dst: %pi6:%d", &nt.saddr.ip, (int) bpf_ntohs(nt.saddr.port), &nt.daddr.ip, (int) bpf_ntohs(nt.daddr.port)); + + bpf_printk("XDP WFQ scheduled with priority %d", prio); + return bpf_redirect_map(&pifo_map, prio, 0); +err: + bpf_printk("XDP DROP"); + return XDP_DROP; +} + + +/* Weighted Fair Queueing (WFQ) */ +SEC("xdp") +int enqueue_prog(struct xdp_md *xdp) +{ + struct parsing_context pctx = { + .data = (void *)(long)xdp->data, + .data_end = (void *)(long)xdp->data_end, + .pkt_len = (xdp->data_end - xdp->data) & 0xffff, + .nh = { .pos = (void *)(long)xdp->data }, + }; + return schedule_packet(&pctx); +} + +SEC("dequeue") +void *dequeue_prog(struct dequeue_ctx *ctx) +{ + struct parsing_context pctx; + struct packet_info p_info = {0}; + struct network_tuple nt; + struct flow_state *flow; + __u64 prio = 0; + + struct xdp_md *pkt = NULL; + + pkt = (void *)bpf_packet_dequeue(ctx, &pifo_map, 0, &prio); + if (!pkt) + goto err; + + pctx.data = (void *)(long) pkt->data; + pctx.data_end = (void *)(long) pkt->data_end; + pctx.nh.pos = (void *)(long) pkt->data; + + /* Get flow */ + if (parse_packet(&pctx, &p_info) < 0) + goto err; + + nt = p_info.flow; + + flow = bpf_map_lookup_elem(&flow_state, &nt); + if (!flow) + goto err; + + bpf_printk("Flow: pkts:%d, finish_bytes:%d", flow->pkts, flow->finish_bytes); + + flow->pkts--; + if (flow->pkts <= 0) + bpf_map_delete_elem(&flow_state, &nt); + + virtual_time_bytes = prio; + + bpf_printk("src: %pi6:%d --> dst: %pi6:%d", &nt.saddr.ip, (int) bpf_ntohs(nt.saddr.port), &nt.daddr.ip, (int) bpf_ntohs(nt.daddr.port)); + + bpf_printk("DEQUEUE WFQ with priority %d", prio); + return pkt; +err: + if (pkt) + bpf_packet_drop(ctx, pkt); + bpf_printk("DEQUEUE packet failed"); + return NULL; +} + +char _license[] SEC("license") = "GPL"; diff --git a/xdp-scheduler-tester/xdp_test.trace b/xdp-scheduler-tester/xdp_test.trace new file mode 100644 index 00000000..b1ec01d4 --- /dev/null +++ b/xdp-scheduler-tester/xdp_test.trace @@ -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 +dequeue udp dst port=8080 eth proto=1 +dequeue udp eth proto=2 dst port=8081 diff --git a/xdp-scheduler-tester/xdp_wfq.trace b/xdp-scheduler-tester/xdp_wfq.trace new file mode 100644 index 00000000..0e5c2cae --- /dev/null +++ b/xdp-scheduler-tester/xdp_wfq.trace @@ -0,0 +1,22 @@ +global bpf file=./xdp_scheduler_wfq.bpf.o + +# 1. Enqueue two packets using the same flow. +# Tests that no flows remain after the PIFO is empty. +udp dst port=8000 payload length=38 +dequeue udp dst port=8000 payload length=38 + + +# 2. Enqueue two flows +udp dst port=8000 payload length=38 repeat=2 +udp dst port=8001 payload length=38 +dequeue udp dst port=8000 payload length=38 +dequeue udp dst port=8001 payload length=38 +dequeue udp dst port=8000 payload length=38 + +# 3. Enqueue two flows where the first flow has a large packet size. +udp dst port=8000 payload payload length=138 repeat=2 +udp dst port=8001 payload length=38 repeat=2 +dequeue udp dst port=8000 payload length=138 +dequeue udp dst port=8001 payload length=38 +dequeue udp dst port=8001 payload length=38 +dequeue udp dst port=8000 payload length=138