1*387f9dfdSAndroid Build Coastguard Worker#!/usr/bin/python3 2*387f9dfdSAndroid Build Coastguard Worker# 3*387f9dfdSAndroid Build Coastguard Worker# nflatency Trace netfilter hook latency. 4*387f9dfdSAndroid Build Coastguard Worker# 5*387f9dfdSAndroid Build Coastguard Worker# This attaches a kprobe and kretprobe to nf_hook_slow. 6*387f9dfdSAndroid Build Coastguard Worker# 2020-04-03 Casey Callendrello / <[email protected]> 7*387f9dfdSAndroid Build Coastguard Worker 8*387f9dfdSAndroid Build Coastguard Workerimport argparse 9*387f9dfdSAndroid Build Coastguard Workerimport sys 10*387f9dfdSAndroid Build Coastguard Workerimport time 11*387f9dfdSAndroid Build Coastguard Worker 12*387f9dfdSAndroid Build Coastguard Workerfrom bcc import BPF 13*387f9dfdSAndroid Build Coastguard Worker 14*387f9dfdSAndroid Build Coastguard WorkerBPF_SRC = """ 15*387f9dfdSAndroid Build Coastguard Worker#include <linux/ip.h> 16*387f9dfdSAndroid Build Coastguard Worker#include <linux/ipv6.h> 17*387f9dfdSAndroid Build Coastguard Worker#include <linux/tcp.h> 18*387f9dfdSAndroid Build Coastguard Worker#include <linux/netfilter.h> 19*387f9dfdSAndroid Build Coastguard Worker#include <net/ip.h> 20*387f9dfdSAndroid Build Coastguard Worker#include <uapi/linux/bpf.h> 21*387f9dfdSAndroid Build Coastguard Worker 22*387f9dfdSAndroid Build Coastguard Workerstatic struct tcphdr *skb_to_tcphdr(const struct sk_buff *skb) 23*387f9dfdSAndroid Build Coastguard Worker{ 24*387f9dfdSAndroid Build Coastguard Worker // unstable API. verify logic in tcp_hdr() -> skb_transport_header(). 25*387f9dfdSAndroid Build Coastguard Worker return (struct tcphdr *)(skb->head + skb->transport_header); 26*387f9dfdSAndroid Build Coastguard Worker} 27*387f9dfdSAndroid Build Coastguard Worker 28*387f9dfdSAndroid Build Coastguard Workerstatic inline struct iphdr *skb_to_iphdr(const struct sk_buff *skb) 29*387f9dfdSAndroid Build Coastguard Worker{ 30*387f9dfdSAndroid Build Coastguard Worker // unstable API. verify logic in ip_hdr() -> skb_network_header(). 31*387f9dfdSAndroid Build Coastguard Worker return (struct iphdr *)(skb->head + skb->network_header); 32*387f9dfdSAndroid Build Coastguard Worker} 33*387f9dfdSAndroid Build Coastguard Worker 34*387f9dfdSAndroid Build Coastguard Workerstatic inline struct ipv6hdr *skb_to_ip6hdr(const struct sk_buff *skb) 35*387f9dfdSAndroid Build Coastguard Worker{ 36*387f9dfdSAndroid Build Coastguard Worker // unstable API. verify logic in ip_hdr() -> skb_network_header(). 37*387f9dfdSAndroid Build Coastguard Worker return (struct ipv6hdr *)(skb->head + skb->network_header); 38*387f9dfdSAndroid Build Coastguard Worker} 39*387f9dfdSAndroid Build Coastguard Worker 40*387f9dfdSAndroid Build Coastguard Worker// for correlating between kprobe and kretprobe 41*387f9dfdSAndroid Build Coastguard Workerstruct start_data { 42*387f9dfdSAndroid Build Coastguard Worker u8 hook; 43*387f9dfdSAndroid Build Coastguard Worker u8 pf; // netfilter protocol 44*387f9dfdSAndroid Build Coastguard Worker u8 tcp_state; 45*387f9dfdSAndroid Build Coastguard Worker u64 ts; 46*387f9dfdSAndroid Build Coastguard Worker}; 47*387f9dfdSAndroid Build Coastguard WorkerBPF_PERCPU_ARRAY(sts, struct start_data, 1); 48*387f9dfdSAndroid Build Coastguard Worker 49*387f9dfdSAndroid Build Coastguard Worker// the histogram keys 50*387f9dfdSAndroid Build Coastguard Workertypedef struct nf_lat_key { 51*387f9dfdSAndroid Build Coastguard Worker u8 proto; // see netfilter.h 52*387f9dfdSAndroid Build Coastguard Worker u8 hook; 53*387f9dfdSAndroid Build Coastguard Worker u8 tcp_state; 54*387f9dfdSAndroid Build Coastguard Worker} nf_lat_key_t; 55*387f9dfdSAndroid Build Coastguard Worker 56*387f9dfdSAndroid Build Coastguard Workertypedef struct hist_key { 57*387f9dfdSAndroid Build Coastguard Worker nf_lat_key_t key; 58*387f9dfdSAndroid Build Coastguard Worker u64 slot; 59*387f9dfdSAndroid Build Coastguard Worker} hist_key_t; 60*387f9dfdSAndroid Build Coastguard WorkerBPF_HISTOGRAM(dist, hist_key_t); 61*387f9dfdSAndroid Build Coastguard Worker 62*387f9dfdSAndroid Build Coastguard Worker 63*387f9dfdSAndroid Build Coastguard Workerint kprobe__nf_hook_slow(struct pt_regs *ctx, struct sk_buff *skb, struct nf_hook_state *state) { 64*387f9dfdSAndroid Build Coastguard Worker struct start_data data = {}; 65*387f9dfdSAndroid Build Coastguard Worker data.ts = bpf_ktime_get_ns(); 66*387f9dfdSAndroid Build Coastguard Worker data.hook = state->hook; 67*387f9dfdSAndroid Build Coastguard Worker data.pf = state->pf; 68*387f9dfdSAndroid Build Coastguard Worker 69*387f9dfdSAndroid Build Coastguard Worker COND 70*387f9dfdSAndroid Build Coastguard Worker 71*387f9dfdSAndroid Build Coastguard Worker u8 ip_proto; 72*387f9dfdSAndroid Build Coastguard Worker if (skb->protocol == htons(ETH_P_IP)) { 73*387f9dfdSAndroid Build Coastguard Worker struct iphdr *ip = skb_to_iphdr(skb); 74*387f9dfdSAndroid Build Coastguard Worker ip_proto = ip->protocol; 75*387f9dfdSAndroid Build Coastguard Worker 76*387f9dfdSAndroid Build Coastguard Worker } else if (skb->protocol == htons(ETH_P_IPV6)) { 77*387f9dfdSAndroid Build Coastguard Worker struct ipv6hdr *ip = skb_to_ip6hdr(skb); 78*387f9dfdSAndroid Build Coastguard Worker ip_proto = ip->nexthdr; 79*387f9dfdSAndroid Build Coastguard Worker } 80*387f9dfdSAndroid Build Coastguard Worker 81*387f9dfdSAndroid Build Coastguard Worker data.tcp_state = 0; 82*387f9dfdSAndroid Build Coastguard Worker if (ip_proto == 0x06) { //tcp 83*387f9dfdSAndroid Build Coastguard Worker struct tcphdr *tcp = skb_to_tcphdr(skb); 84*387f9dfdSAndroid Build Coastguard Worker u8 tcpflags = ((u_int8_t *)tcp)[13]; 85*387f9dfdSAndroid Build Coastguard Worker 86*387f9dfdSAndroid Build Coastguard Worker // FIN or RST 87*387f9dfdSAndroid Build Coastguard Worker if (((tcpflags & 1) + (tcpflags & 4)) > 0) { 88*387f9dfdSAndroid Build Coastguard Worker data.tcp_state = 3; 89*387f9dfdSAndroid Build Coastguard Worker } 90*387f9dfdSAndroid Build Coastguard Worker // SYN / SACK 91*387f9dfdSAndroid Build Coastguard Worker else if ((tcpflags & 0x02) > 0) { 92*387f9dfdSAndroid Build Coastguard Worker data.tcp_state = 1; 93*387f9dfdSAndroid Build Coastguard Worker if ((tcpflags & 16) > 0) { // ACK 94*387f9dfdSAndroid Build Coastguard Worker data.tcp_state = 2; 95*387f9dfdSAndroid Build Coastguard Worker } 96*387f9dfdSAndroid Build Coastguard Worker } 97*387f9dfdSAndroid Build Coastguard Worker } 98*387f9dfdSAndroid Build Coastguard Worker 99*387f9dfdSAndroid Build Coastguard Worker u32 idx = 0; 100*387f9dfdSAndroid Build Coastguard Worker sts.update(&idx, &data); 101*387f9dfdSAndroid Build Coastguard Worker return 0; 102*387f9dfdSAndroid Build Coastguard Worker} 103*387f9dfdSAndroid Build Coastguard Worker 104*387f9dfdSAndroid Build Coastguard Workerint kretprobe__nf_hook_slow(struct pt_regs *ctx) { 105*387f9dfdSAndroid Build Coastguard Worker u32 idx = 0; 106*387f9dfdSAndroid Build Coastguard Worker struct start_data *s; 107*387f9dfdSAndroid Build Coastguard Worker s = sts.lookup(&idx); 108*387f9dfdSAndroid Build Coastguard Worker if (!s || s->ts == 0) { 109*387f9dfdSAndroid Build Coastguard Worker return 0; 110*387f9dfdSAndroid Build Coastguard Worker } 111*387f9dfdSAndroid Build Coastguard Worker 112*387f9dfdSAndroid Build Coastguard Worker s->ts = bpf_ktime_get_ns() - s->ts; 113*387f9dfdSAndroid Build Coastguard Worker 114*387f9dfdSAndroid Build Coastguard Worker hist_key_t key = {}; 115*387f9dfdSAndroid Build Coastguard Worker key.key.hook = s->hook; 116*387f9dfdSAndroid Build Coastguard Worker key.key.proto = s->pf; 117*387f9dfdSAndroid Build Coastguard Worker key.key.tcp_state = s->tcp_state; 118*387f9dfdSAndroid Build Coastguard Worker key.slot = bpf_log2l(s->ts / FACTOR ); 119*387f9dfdSAndroid Build Coastguard Worker dist.increment(key); 120*387f9dfdSAndroid Build Coastguard Worker 121*387f9dfdSAndroid Build Coastguard Worker s->ts = 0; 122*387f9dfdSAndroid Build Coastguard Worker sts.update(&idx, s); 123*387f9dfdSAndroid Build Coastguard Worker 124*387f9dfdSAndroid Build Coastguard Worker return 0; 125*387f9dfdSAndroid Build Coastguard Worker} 126*387f9dfdSAndroid Build Coastguard Worker""" 127*387f9dfdSAndroid Build Coastguard Worker 128*387f9dfdSAndroid Build Coastguard Worker# constants from netfilter.h 129*387f9dfdSAndroid Build Coastguard WorkerNF_HOOKS = { 130*387f9dfdSAndroid Build Coastguard Worker 0: "PRE_ROUTING", 131*387f9dfdSAndroid Build Coastguard Worker 1: "LOCAL_IN", 132*387f9dfdSAndroid Build Coastguard Worker 2: "FORWARD", 133*387f9dfdSAndroid Build Coastguard Worker 3: "LOCAL_OUT", 134*387f9dfdSAndroid Build Coastguard Worker 4: "POST_ROUTING", 135*387f9dfdSAndroid Build Coastguard Worker} 136*387f9dfdSAndroid Build Coastguard Worker 137*387f9dfdSAndroid Build Coastguard WorkerNF_PROTOS = { 138*387f9dfdSAndroid Build Coastguard Worker 0: "UNSPEC", 139*387f9dfdSAndroid Build Coastguard Worker 1: "INET", 140*387f9dfdSAndroid Build Coastguard Worker 2: "IPV4", 141*387f9dfdSAndroid Build Coastguard Worker 3: "ARP", 142*387f9dfdSAndroid Build Coastguard Worker 5: "NETDEV", 143*387f9dfdSAndroid Build Coastguard Worker 7: "BRIDGE", 144*387f9dfdSAndroid Build Coastguard Worker 10: "IPV6", 145*387f9dfdSAndroid Build Coastguard Worker 12: "DECNET", 146*387f9dfdSAndroid Build Coastguard Worker} 147*387f9dfdSAndroid Build Coastguard Worker 148*387f9dfdSAndroid Build Coastguard WorkerTCP_FLAGS = { 149*387f9dfdSAndroid Build Coastguard Worker 0: "other", 150*387f9dfdSAndroid Build Coastguard Worker 1: "SYN", 151*387f9dfdSAndroid Build Coastguard Worker 2: "SACK", 152*387f9dfdSAndroid Build Coastguard Worker 3: "FIN", 153*387f9dfdSAndroid Build Coastguard Worker} 154*387f9dfdSAndroid Build Coastguard Worker 155*387f9dfdSAndroid Build Coastguard WorkerEXAMPLES = """examples: 156*387f9dfdSAndroid Build Coastguard Worker nflatency # print netfilter latency histograms, 1 second refresh 157*387f9dfdSAndroid Build Coastguard Worker nflatency -p IPV4 -p IPV6 # print only for ipv4 and ipv6 158*387f9dfdSAndroid Build Coastguard Worker nflatency -k PRE_ROUTING # only record the PRE_ROUTING hook 159*387f9dfdSAndroid Build Coastguard Worker nflatency -i 5 -d 10 # run for 10 seconds, printing every 5 160*387f9dfdSAndroid Build Coastguard Worker""" 161*387f9dfdSAndroid Build Coastguard Worker 162*387f9dfdSAndroid Build Coastguard Worker 163*387f9dfdSAndroid Build Coastguard Workerparser = argparse.ArgumentParser( 164*387f9dfdSAndroid Build Coastguard Worker description="Track latency added by netfilter hooks. Where possible, interesting TCP flags are included", 165*387f9dfdSAndroid Build Coastguard Worker formatter_class=argparse.RawDescriptionHelpFormatter, 166*387f9dfdSAndroid Build Coastguard Worker epilog=EXAMPLES) 167*387f9dfdSAndroid Build Coastguard Workerparser.add_argument("-p", "--proto", 168*387f9dfdSAndroid Build Coastguard Worker action='append', 169*387f9dfdSAndroid Build Coastguard Worker help="record this protocol only (multiple parameters allowed)", 170*387f9dfdSAndroid Build Coastguard Worker choices=NF_PROTOS.values()) 171*387f9dfdSAndroid Build Coastguard Workerparser.add_argument("-k", "--hook", 172*387f9dfdSAndroid Build Coastguard Worker action='append', 173*387f9dfdSAndroid Build Coastguard Worker help="record this netfilter hook only (multiple parameters allowed)", 174*387f9dfdSAndroid Build Coastguard Worker choices=NF_HOOKS.values()) 175*387f9dfdSAndroid Build Coastguard Workerparser.add_argument("-i", "--interval", type=int, 176*387f9dfdSAndroid Build Coastguard Worker help="summary interval, in seconds. Default is 10, unless --duration is supplied") 177*387f9dfdSAndroid Build Coastguard Workerparser.add_argument("-d", "--duration", type=int, 178*387f9dfdSAndroid Build Coastguard Worker help="total duration of trace, in seconds") 179*387f9dfdSAndroid Build Coastguard Workerparser.add_argument("--nano", action="store_true", 180*387f9dfdSAndroid Build Coastguard Worker help="bucket by nanoseconds instead of milliseconds") 181*387f9dfdSAndroid Build Coastguard Worker 182*387f9dfdSAndroid Build Coastguard Workerdef main(): 183*387f9dfdSAndroid Build Coastguard Worker args = parser.parse_args() 184*387f9dfdSAndroid Build Coastguard Worker 185*387f9dfdSAndroid Build Coastguard Worker src = build_src(args) 186*387f9dfdSAndroid Build Coastguard Worker b = BPF(text=src) 187*387f9dfdSAndroid Build Coastguard Worker dist = b.get_table("dist") 188*387f9dfdSAndroid Build Coastguard Worker 189*387f9dfdSAndroid Build Coastguard Worker seconds = 0 190*387f9dfdSAndroid Build Coastguard Worker interval = 0 191*387f9dfdSAndroid Build Coastguard Worker if not args.interval: 192*387f9dfdSAndroid Build Coastguard Worker interval = 1 193*387f9dfdSAndroid Build Coastguard Worker if args.duration: 194*387f9dfdSAndroid Build Coastguard Worker interval = args.duration 195*387f9dfdSAndroid Build Coastguard Worker else: 196*387f9dfdSAndroid Build Coastguard Worker interval = args.interval 197*387f9dfdSAndroid Build Coastguard Worker 198*387f9dfdSAndroid Build Coastguard Worker sys.stderr.write("Tracing netfilter hooks... Hit Ctrl-C to end.\n") 199*387f9dfdSAndroid Build Coastguard Worker while 1: 200*387f9dfdSAndroid Build Coastguard Worker try: 201*387f9dfdSAndroid Build Coastguard Worker dist.print_log2_hist( 202*387f9dfdSAndroid Build Coastguard Worker section_header="Bucket", 203*387f9dfdSAndroid Build Coastguard Worker bucket_fn=lambda k: (k.proto, k.hook, k.tcp_state), 204*387f9dfdSAndroid Build Coastguard Worker section_print_fn=bucket_desc) 205*387f9dfdSAndroid Build Coastguard Worker if args.duration and seconds >= args.duration: 206*387f9dfdSAndroid Build Coastguard Worker sys.exit(0) 207*387f9dfdSAndroid Build Coastguard Worker seconds += interval 208*387f9dfdSAndroid Build Coastguard Worker time.sleep(interval) 209*387f9dfdSAndroid Build Coastguard Worker except KeyboardInterrupt: 210*387f9dfdSAndroid Build Coastguard Worker sys.exit(1) 211*387f9dfdSAndroid Build Coastguard Worker 212*387f9dfdSAndroid Build Coastguard Worker 213*387f9dfdSAndroid Build Coastguard Workerdef build_src(args): 214*387f9dfdSAndroid Build Coastguard Worker cond_src = "" 215*387f9dfdSAndroid Build Coastguard Worker if args.proto: 216*387f9dfdSAndroid Build Coastguard Worker predicate = " || ".join(map(lambda x: "data.pf == NFPROTO_%s" % x, args.proto)) 217*387f9dfdSAndroid Build Coastguard Worker cond_src = "if (!(%s)) { return 0; }\n" % predicate 218*387f9dfdSAndroid Build Coastguard Worker if args.hook: 219*387f9dfdSAndroid Build Coastguard Worker predicate = " || ".join(map(lambda x: "data.hook == NF_INET_%s" % x, args.hook)) 220*387f9dfdSAndroid Build Coastguard Worker cond_src = "%s if (!(%s)) { return 0;}\n" % (cond_src, predicate) 221*387f9dfdSAndroid Build Coastguard Worker 222*387f9dfdSAndroid Build Coastguard Worker factor = "1000" 223*387f9dfdSAndroid Build Coastguard Worker if args.nano: 224*387f9dfdSAndroid Build Coastguard Worker factor = "1" 225*387f9dfdSAndroid Build Coastguard Worker 226*387f9dfdSAndroid Build Coastguard Worker return BPF_SRC.replace('COND', cond_src).replace('FACTOR', factor) 227*387f9dfdSAndroid Build Coastguard Worker 228*387f9dfdSAndroid Build Coastguard Worker 229*387f9dfdSAndroid Build Coastguard Workerdef bucket_desc(bucket): 230*387f9dfdSAndroid Build Coastguard Worker return "%s %s (tcp %s)" % ( 231*387f9dfdSAndroid Build Coastguard Worker NF_PROTOS[bucket[0]], 232*387f9dfdSAndroid Build Coastguard Worker NF_HOOKS[bucket[1]], 233*387f9dfdSAndroid Build Coastguard Worker TCP_FLAGS[bucket[2]]) 234*387f9dfdSAndroid Build Coastguard Worker 235*387f9dfdSAndroid Build Coastguard Worker 236*387f9dfdSAndroid Build Coastguard Workerif __name__ == "__main__": 237*387f9dfdSAndroid Build Coastguard Worker main() 238