1#!/usr/bin/env python 2# 3# xdp_macswap_count.py Swap Source and Destination MAC addresses on 4# incoming packets and transmit packets back on 5# same interface in XDP layer and count for which 6# protocol type 7# 8# Copyright (c) 2016 PLUMgrid 9# Copyright (c) 2016 Jan Ruth 10# Copyright (c) 2018 Andy Gospodarek 11# Licensed under the Apache License, Version 2.0 (the "License") 12 13from bcc import BPF 14import pyroute2 15import time 16import sys 17 18flags = 0 19def usage(): 20 print("Usage: {0} [-S] <ifdev>".format(sys.argv[0])) 21 print(" -S: use skb mode\n") 22 print("e.g.: {0} eth0\n".format(sys.argv[0])) 23 exit(1) 24 25if len(sys.argv) < 2 or len(sys.argv) > 3: 26 usage() 27 28if len(sys.argv) == 2: 29 device = sys.argv[1] 30 31if len(sys.argv) == 3: 32 if "-S" in sys.argv: 33 # XDP_FLAGS_SKB_MODE 34 flags |= 2 << 0 35 36 if "-S" == sys.argv[1]: 37 device = sys.argv[2] 38 else: 39 device = sys.argv[1] 40 41mode = BPF.XDP 42#mode = BPF.SCHED_CLS 43 44if mode == BPF.XDP: 45 ret = "XDP_TX" 46 ctxtype = "xdp_md" 47else: 48 ret = "TC_ACT_SHOT" 49 ctxtype = "__sk_buff" 50 51# load BPF program 52b = BPF(text = """ 53#define KBUILD_MODNAME "foo" 54#include <uapi/linux/bpf.h> 55#include <linux/in.h> 56#include <linux/if_ether.h> 57#include <linux/if_packet.h> 58#include <linux/if_vlan.h> 59#include <linux/ip.h> 60#include <linux/ipv6.h> 61 62 63BPF_TABLE("percpu_array", uint32_t, long, dropcnt, 256); 64 65static inline int parse_ipv4(void *data, u64 nh_off, void *data_end) { 66 struct iphdr *iph = data + nh_off; 67 68 if ((void*)&iph[1] > data_end) 69 return 0; 70 return iph->protocol; 71} 72 73static inline int parse_ipv6(void *data, u64 nh_off, void *data_end) { 74 struct ipv6hdr *ip6h = data + nh_off; 75 76 if ((void*)&ip6h[1] > data_end) 77 return 0; 78 return ip6h->nexthdr; 79} 80 81static void swap_src_dst_mac(void *data) 82{ 83 unsigned short *p = data; 84 unsigned short dst[3]; 85 86 dst[0] = p[0]; 87 dst[1] = p[1]; 88 dst[2] = p[2]; 89 p[0] = p[3]; 90 p[1] = p[4]; 91 p[2] = p[5]; 92 p[3] = dst[0]; 93 p[4] = dst[1]; 94 p[5] = dst[2]; 95} 96 97int xdp_prog1(struct CTXTYPE *ctx) { 98 99 void* data_end = (void*)(long)ctx->data_end; 100 void* data = (void*)(long)ctx->data; 101 102 struct ethhdr *eth = data; 103 104 // drop packets 105 int rc = RETURNCODE; // let pass XDP_PASS or redirect to tx via XDP_TX 106 long *value; 107 uint16_t h_proto; 108 uint64_t nh_off = 0; 109 uint32_t index; 110 111 nh_off = sizeof(*eth); 112 113 if (data + nh_off > data_end) 114 return rc; 115 116 h_proto = eth->h_proto; 117 118 if (h_proto == htons(ETH_P_8021Q) || h_proto == htons(ETH_P_8021AD)) { 119 struct vlan_hdr *vhdr; 120 121 vhdr = data + nh_off; 122 nh_off += sizeof(struct vlan_hdr); 123 if (data + nh_off > data_end) 124 return rc; 125 h_proto = vhdr->h_vlan_encapsulated_proto; 126 } 127 if (h_proto == htons(ETH_P_8021Q) || h_proto == htons(ETH_P_8021AD)) { 128 struct vlan_hdr *vhdr; 129 130 vhdr = data + nh_off; 131 nh_off += sizeof(struct vlan_hdr); 132 if (data + nh_off > data_end) 133 return rc; 134 h_proto = vhdr->h_vlan_encapsulated_proto; 135 } 136 137 if (h_proto == htons(ETH_P_IP)) 138 index = parse_ipv4(data, nh_off, data_end); 139 else if (h_proto == htons(ETH_P_IPV6)) 140 index = parse_ipv6(data, nh_off, data_end); 141 else 142 index = 0; 143 144 if (h_proto == IPPROTO_UDP) { 145 swap_src_dst_mac(data); 146 rc = XDP_TX; 147 } 148 149 value = dropcnt.lookup(&index); 150 if (value) 151 *value += 1; 152 153 return rc; 154} 155""", cflags=["-w", "-DRETURNCODE=%s" % ret, "-DCTXTYPE=%s" % ctxtype]) 156 157fn = b.load_func("xdp_prog1", mode) 158 159if mode == BPF.XDP: 160 b.attach_xdp(device, fn, flags) 161else: 162 ip = pyroute2.IPRoute() 163 ipdb = pyroute2.IPDB(nl=ip) 164 idx = ipdb.interfaces[device].index 165 ip.tc("add", "clsact", idx) 166 ip.tc("add-filter", "bpf", idx, ":1", fd=fn.fd, name=fn.name, 167 parent="ffff:fff2", classid=1, direct_action=True) 168 169dropcnt = b.get_table("dropcnt") 170prev = [0] * 256 171print("Printing drops per IP protocol-number, hit CTRL+C to stop") 172while 1: 173 try: 174 for k in dropcnt.keys(): 175 val = dropcnt.sum(k).value 176 i = k.value 177 if val: 178 delta = val - prev[i] 179 prev[i] = val 180 print("{}: {} pkt/s".format(i, delta)) 181 time.sleep(1) 182 except KeyboardInterrupt: 183 print("Removing filter from device") 184 break; 185 186if mode == BPF.XDP: 187 b.remove_xdp(device, flags) 188else: 189 ip.tc("del", "clsact", idx) 190 ipdb.release() 191