From b5c80d310527223b93e4133ac2f4c8c063c70a98 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 18 Dec 2020 17:13:13 +0100 Subject: xdp: T2666: switch to example code provided by xdp-tutorial --- src/xdp/xdp_prog_kern.c | 343 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 343 insertions(+) create mode 100644 src/xdp/xdp_prog_kern.c (limited to 'src/xdp/xdp_prog_kern.c') diff --git a/src/xdp/xdp_prog_kern.c b/src/xdp/xdp_prog_kern.c new file mode 100644 index 000000000..a1eb395af --- /dev/null +++ b/src/xdp/xdp_prog_kern.c @@ -0,0 +1,343 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#include +#include +#include +#include + +// The parsing helper functions from the packet01 lesson have moved here +#include "common/parsing_helpers.h" +#include "common/rewrite_helpers.h" + +/* Defines xdp_stats_map */ +#include "common/xdp_stats_kern_user.h" +#include "common/xdp_stats_kern.h" + +#ifndef memcpy +#define memcpy(dest, src, n) __builtin_memcpy((dest), (src), (n)) +#endif + +struct bpf_map_def SEC("maps") tx_port = { + .type = BPF_MAP_TYPE_DEVMAP, + .key_size = sizeof(int), + .value_size = sizeof(int), + .max_entries = 256, +}; + +struct bpf_map_def SEC("maps") redirect_params = { + .type = BPF_MAP_TYPE_HASH, + .key_size = ETH_ALEN, + .value_size = ETH_ALEN, + .max_entries = 1, +}; + +static __always_inline __u16 csum_fold_helper(__u32 csum) +{ + return ~((csum & 0xffff) + (csum >> 16)); +} + +/* + * The icmp_checksum_diff function takes pointers to old and new structures and + * the old checksum and returns the new checksum. It uses the bpf_csum_diff + * helper to compute the checksum difference. Note that the sizes passed to the + * bpf_csum_diff helper should be multiples of 4, as it operates on 32-bit + * words. + */ +static __always_inline __u16 icmp_checksum_diff( + __u16 seed, + struct icmphdr_common *icmphdr_new, + struct icmphdr_common *icmphdr_old) +{ + __u32 csum, size = sizeof(struct icmphdr_common); + + csum = bpf_csum_diff((__be32 *)icmphdr_old, size, (__be32 *)icmphdr_new, size, seed); + return csum_fold_helper(csum); +} + +/* Solution to packet03/assignment-1 */ +SEC("xdp_icmp_echo") +int xdp_icmp_echo_func(struct xdp_md *ctx) +{ + void *data_end = (void *)(long)ctx->data_end; + void *data = (void *)(long)ctx->data; + struct hdr_cursor nh; + struct ethhdr *eth; + int eth_type; + int ip_type; + int icmp_type; + struct iphdr *iphdr; + struct ipv6hdr *ipv6hdr; + __u16 echo_reply, old_csum; + struct icmphdr_common *icmphdr; + struct icmphdr_common icmphdr_old; + __u32 action = XDP_PASS; + + /* These keep track of the next header type and iterator pointer */ + nh.pos = data; + + /* 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_ICMP) + goto out; + } else if (eth_type == bpf_htons(ETH_P_IPV6)) { + ip_type = parse_ip6hdr(&nh, data_end, &ipv6hdr); + if (ip_type != IPPROTO_ICMPV6) + goto out; + } else { + goto out; + } + + /* + * We are using a special parser here which returns a stucture + * containing the "protocol-independent" part of an ICMP or ICMPv6 + * header. For purposes of this Assignment we are not interested in + * the rest of the structure. + */ + icmp_type = parse_icmphdr_common(&nh, data_end, &icmphdr); + if (eth_type == bpf_htons(ETH_P_IP) && icmp_type == ICMP_ECHO) { + /* Swap IP source and destination */ + swap_src_dst_ipv4(iphdr); + echo_reply = ICMP_ECHOREPLY; + } else if (eth_type == bpf_htons(ETH_P_IPV6) + && icmp_type == ICMPV6_ECHO_REQUEST) { + /* Swap IPv6 source and destination */ + swap_src_dst_ipv6(ipv6hdr); + echo_reply = ICMPV6_ECHO_REPLY; + } else { + goto out; + } + + /* Swap Ethernet source and destination */ + swap_src_dst_mac(eth); + + + /* Patch the packet and update the checksum.*/ + old_csum = icmphdr->cksum; + icmphdr->cksum = 0; + icmphdr_old = *icmphdr; + icmphdr->type = echo_reply; + icmphdr->cksum = icmp_checksum_diff(~old_csum, icmphdr, &icmphdr_old); + + /* Another, less generic, but a bit more efficient way to update the + * checksum is listed below. As only one 16-bit word changed, the sum + * can be patched using this formula: sum' = ~(~sum + ~m0 + m1), where + * sum' is a new sum, sum is an old sum, m0 and m1 are the old and new + * 16-bit words, correspondingly. In the formula above the + operation + * is defined as the following function: + * + * static __always_inline __u16 csum16_add(__u16 csum, __u16 addend) + * { + * csum += addend; + * return csum + (csum < addend); + * } + * + * So an alternative code to update the checksum might look like this: + * + * __u16 m0 = * (__u16 *) icmphdr; + * icmphdr->type = echo_reply; + * __u16 m1 = * (__u16 *) icmphdr; + * icmphdr->checksum = ~(csum16_add(csum16_add(~icmphdr->checksum, ~m0), m1)); + */ + + action = XDP_TX; + +out: + return xdp_stats_record_action(ctx, action); +} + +/* Solution to packet03/assignment-2 */ +SEC("xdp_redirect") +int xdp_redirect_func(struct xdp_md *ctx) +{ + void *data_end = (void *)(long)ctx->data_end; + void *data = (void *)(long)ctx->data; + struct hdr_cursor nh; + struct ethhdr *eth; + int eth_type; + int action = XDP_PASS; + unsigned char dst[ETH_ALEN] = { /* TODO: put your values here */ }; + unsigned ifindex = 0/* TODO: put your values here */; + + /* These keep track of the next header type and iterator pointer */ + nh.pos = data; + + /* Parse Ethernet and IP/IPv6 headers */ + eth_type = parse_ethhdr(&nh, data_end, ð); + if (eth_type == -1) + goto out; + + /* Set a proper destination address */ + memcpy(eth->h_dest, dst, ETH_ALEN); + action = bpf_redirect(ifindex, 0); + +out: + return xdp_stats_record_action(ctx, action); +} + +/* Solution to packet03/assignment-3 */ +SEC("xdp_redirect_map") +int xdp_redirect_map_func(struct xdp_md *ctx) +{ + void *data_end = (void *)(long)ctx->data_end; + void *data = (void *)(long)ctx->data; + struct hdr_cursor nh; + struct ethhdr *eth; + int eth_type; + int action = XDP_PASS; + unsigned char *dst; + + /* These keep track of the next header type and iterator pointer */ + nh.pos = data; + + /* Parse Ethernet and IP/IPv6 headers */ + eth_type = parse_ethhdr(&nh, data_end, ð); + if (eth_type == -1) + goto out; + + /* Do we know where to redirect this packet? */ + dst = bpf_map_lookup_elem(&redirect_params, eth->h_source); + if (!dst) + goto out; + + /* Set a proper destination address */ + memcpy(eth->h_dest, dst, ETH_ALEN); + action = bpf_redirect_map(&tx_port, 0, 0); + +out: + return xdp_stats_record_action(ctx, action); +} + +#define AF_INET 2 +#define AF_INET6 10 +#define IPV6_FLOWINFO_MASK bpf_htonl(0x0FFFFFFF) + +/* from include/net/ip.h */ +static __always_inline int ip_decrease_ttl(struct iphdr *iph) +{ + __u32 check = iph->check; + check += bpf_htons(0x0100); + iph->check = (__u16)(check + (check >= 0xFFFF)); + return --iph->ttl; +} + +/* Solution to packet03/assignment-4 */ +/* xdp_router is the name of the xdp program */ +SEC("xdp_router") +int xdp_router_func(struct xdp_md *ctx) +{ + /* this is the packet context*/ + void *data_end = (void *)(long)ctx->data_end; + void *data = (void *)(long)ctx->data; + struct bpf_fib_lookup fib_params = {}; + struct ethhdr *eth = data; + struct ipv6hdr *ip6h; + struct iphdr *iph; + __u16 h_proto; + __u64 nh_off; + int rc; + /* default action is to pass */ + int action = XDP_PASS; + + nh_off = sizeof(*eth); + if (data + nh_off > data_end) { + action = XDP_DROP; + goto out; + } + + /* determine if this is IP4 or IPv6 by looking at the Ethernet protocol field */ + h_proto = eth->h_proto; + if (h_proto == bpf_htons(ETH_P_IP)) { + /* IPv4 part of the code */ + iph = data + nh_off; + + if (iph + 1 > data_end) { + action = XDP_DROP; + goto out; + } + /* as a real router, we need to check the TTL to prevent never ending loops*/ + if (iph->ttl <= 1) + goto out; + + /* populate the fib_params fields to prepare for the lookup */ + fib_params.family = AF_INET; + fib_params.tos = iph->tos; + fib_params.l4_protocol = iph->protocol; + fib_params.sport = 0; + fib_params.dport = 0; + fib_params.tot_len = bpf_ntohs(iph->tot_len); + fib_params.ipv4_src = iph->saddr; + fib_params.ipv4_dst = iph->daddr; + } else if (h_proto == bpf_htons(ETH_P_IPV6)) { + /* IPv6 part of the code */ + struct in6_addr *src = (struct in6_addr *) fib_params.ipv6_src; + struct in6_addr *dst = (struct in6_addr *) fib_params.ipv6_dst; + + ip6h = data + nh_off; + if (ip6h + 1 > data_end) { + action = XDP_DROP; + goto out; + } + /* as a real router, we need to check the TTL to prevent never ending loops*/ + if (ip6h->hop_limit <= 1) + goto out; + + /* populate the fib_params fields to prepare for the lookup */ + fib_params.family = AF_INET6; + fib_params.flowinfo = *(__be32 *) ip6h & IPV6_FLOWINFO_MASK; + fib_params.l4_protocol = ip6h->nexthdr; + fib_params.sport = 0; + fib_params.dport = 0; + fib_params.tot_len = bpf_ntohs(ip6h->payload_len); + *src = ip6h->saddr; + *dst = ip6h->daddr; + } else { + goto out; + } + + fib_params.ifindex = ctx->ingress_ifindex; + + /* this is where the FIB lookup happens. If the lookup is successful */ + /* it will populate the fib_params.ifindex with the egress interface index */ + + rc = bpf_fib_lookup(ctx, &fib_params, sizeof(fib_params), 0); + switch (rc) { + case BPF_FIB_LKUP_RET_SUCCESS: /* lookup successful */ + /* we are a router, so we need to decrease the ttl */ + if (h_proto == bpf_htons(ETH_P_IP)) + ip_decrease_ttl(iph); + else if (h_proto == bpf_htons(ETH_P_IPV6)) + ip6h->hop_limit--; + /* set the correct new source and destionation mac addresses */ + /* can be found in fib_params.dmac and fib_params.smac */ + memcpy(eth->h_dest, fib_params.dmac, ETH_ALEN); + memcpy(eth->h_source, fib_params.smac, ETH_ALEN); + /* and done, now we set the action to bpf_redirect_map with fib_params.ifindex which is the egress port as paramater */ + action = bpf_redirect_map(&tx_port, fib_params.ifindex, 0); + break; + case BPF_FIB_LKUP_RET_BLACKHOLE: /* dest is blackholed; can be dropped */ + case BPF_FIB_LKUP_RET_UNREACHABLE: /* dest is unreachable; can be dropped */ + case BPF_FIB_LKUP_RET_PROHIBIT: /* dest not allowed; can be dropped */ + action = XDP_DROP; + break; + case BPF_FIB_LKUP_RET_NOT_FWDED: /* packet is not forwarded */ + case BPF_FIB_LKUP_RET_FWD_DISABLED: /* fwding is not enabled on ingress */ + case BPF_FIB_LKUP_RET_UNSUPP_LWT: /* fwd requires encapsulation */ + case BPF_FIB_LKUP_RET_NO_NEIGH: /* no neighbor entry for nh */ + case BPF_FIB_LKUP_RET_FRAG_NEEDED: /* fragmentation required to fwd */ + /* PASS */ + break; + } + +out: + /* and done, update stats and return action */ + return xdp_stats_record_action(ctx, action); +} + +SEC("xdp_pass") +int xdp_pass_func(struct xdp_md *ctx) +{ + return xdp_stats_record_action(ctx, XDP_PASS); +} + +char _license[] SEC("license") = "GPL"; -- cgit v1.2.3