summaryrefslogtreecommitdiff
path: root/src/xdp/xdp_prog_kern.c
diff options
context:
space:
mode:
authorChristian Poessinger <christian@poessinger.com>2020-12-18 17:13:13 +0100
committerChristian Poessinger <christian@poessinger.com>2020-12-19 21:56:19 +0100
commitb5c80d310527223b93e4133ac2f4c8c063c70a98 (patch)
tree48ae9e2752b4b94c07f767a019c99a8c2460c014 /src/xdp/xdp_prog_kern.c
parent22308b05dfbcd8da0230bbd8ba3bbc2a55da7786 (diff)
downloadvyos-1x-b5c80d310527223b93e4133ac2f4c8c063c70a98.tar.gz
vyos-1x-b5c80d310527223b93e4133ac2f4c8c063c70a98.zip
xdp: T2666: switch to example code provided by xdp-tutorial
Diffstat (limited to 'src/xdp/xdp_prog_kern.c')
-rw-r--r--src/xdp/xdp_prog_kern.c343
1 files changed, 343 insertions, 0 deletions
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 <linux/bpf.h>
+#include <linux/in.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_endian.h>
+
+// 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, &eth);
+ 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, &eth);
+ 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, &eth);
+ 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";