#!/usr/bin/env python # # xdp_macswap_count.py Swap Source and Destination MAC addresses on # incoming packets and transmit packets back on # same interface in XDP layer and count for which # protocol type # # Copyright (c) 2016 PLUMgrid # Copyright (c) 2016 Jan Ruth # Copyright (c) 2018 Andy Gospodarek # Licensed under the Apache License, Version 2.0 (the "License") from bcc import BPF import pyroute2 import time import sys flags = 0 def usage(): print("Usage: {0} [-S] ".format(sys.argv[0])) print(" -S: use skb mode\n") print("e.g.: {0} eth0\n".format(sys.argv[0])) exit(1) if len(sys.argv) < 2 or len(sys.argv) > 3: usage() if len(sys.argv) == 2: device = sys.argv[1] if len(sys.argv) == 3: if "-S" in sys.argv: # XDP_FLAGS_SKB_MODE flags |= 2 << 0 if "-S" == sys.argv[1]: device = sys.argv[2] else: device = sys.argv[1] mode = BPF.XDP #mode = BPF.SCHED_CLS if mode == BPF.XDP: ret = "XDP_TX" ctxtype = "xdp_md" else: ret = "TC_ACT_SHOT" ctxtype = "__sk_buff" # load BPF program b = BPF(text = """ #define KBUILD_MODNAME "foo" #include #include #include #include #include #include #include BPF_TABLE("percpu_array", uint32_t, long, dropcnt, 256); static inline int parse_ipv4(void *data, u64 nh_off, void *data_end) { struct iphdr *iph = data + nh_off; if ((void*)&iph[1] > data_end) return 0; return iph->protocol; } static inline int parse_ipv6(void *data, u64 nh_off, void *data_end) { struct ipv6hdr *ip6h = data + nh_off; if ((void*)&ip6h[1] > data_end) return 0; return ip6h->nexthdr; } static void swap_src_dst_mac(void *data) { unsigned short *p = data; unsigned short dst[3]; dst[0] = p[0]; dst[1] = p[1]; dst[2] = p[2]; p[0] = p[3]; p[1] = p[4]; p[2] = p[5]; p[3] = dst[0]; p[4] = dst[1]; p[5] = dst[2]; } int xdp_prog1(struct CTXTYPE *ctx) { void* data_end = (void*)(long)ctx->data_end; void* data = (void*)(long)ctx->data; struct ethhdr *eth = data; // drop packets int rc = RETURNCODE; // let pass XDP_PASS or redirect to tx via XDP_TX long *value; uint16_t h_proto; uint64_t nh_off = 0; uint32_t index; nh_off = sizeof(*eth); if (data + nh_off > data_end) return rc; h_proto = eth->h_proto; if (h_proto == htons(ETH_P_8021Q) || h_proto == htons(ETH_P_8021AD)) { struct vlan_hdr *vhdr; vhdr = data + nh_off; nh_off += sizeof(struct vlan_hdr); if (data + nh_off > data_end) return rc; h_proto = vhdr->h_vlan_encapsulated_proto; } if (h_proto == htons(ETH_P_8021Q) || h_proto == htons(ETH_P_8021AD)) { struct vlan_hdr *vhdr; vhdr = data + nh_off; nh_off += sizeof(struct vlan_hdr); if (data + nh_off > data_end) return rc; h_proto = vhdr->h_vlan_encapsulated_proto; } if (h_proto == htons(ETH_P_IP)) index = parse_ipv4(data, nh_off, data_end); else if (h_proto == htons(ETH_P_IPV6)) index = parse_ipv6(data, nh_off, data_end); else index = 0; if (h_proto == IPPROTO_UDP) { swap_src_dst_mac(data); rc = XDP_TX; } value = dropcnt.lookup(&index); if (value) *value += 1; return rc; } """, cflags=["-w", "-DRETURNCODE=%s" % ret, "-DCTXTYPE=%s" % ctxtype]) fn = b.load_func("xdp_prog1", mode) if mode == BPF.XDP: b.attach_xdp(device, fn, flags) else: ip = pyroute2.IPRoute() ipdb = pyroute2.IPDB(nl=ip) idx = ipdb.interfaces[device].index ip.tc("add", "clsact", idx) ip.tc("add-filter", "bpf", idx, ":1", fd=fn.fd, name=fn.name, parent="ffff:fff2", classid=1, direct_action=True) dropcnt = b.get_table("dropcnt") prev = [0] * 256 print("Printing drops per IP protocol-number, hit CTRL+C to stop") while 1: try: for k in dropcnt.keys(): val = dropcnt.sum(k).value i = k.value if val: delta = val - prev[i] prev[i] = val print("{}: {} pkt/s".format(i, delta)) time.sleep(1) except KeyboardInterrupt: print("Removing filter from device") break; if mode == BPF.XDP: b.remove_xdp(device, flags) else: ip.tc("del", "clsact", idx) ipdb.release()