|  | #!/usr/bin/python3 | 
|  | # | 
|  | # nflatency     Trace netfilter hook latency. | 
|  | # | 
|  | # This attaches a kprobe and kretprobe to nf_hook_slow. | 
|  | # 2020-04-03 Casey Callendrello / <cdc@redhat.com> | 
|  |  | 
|  | import argparse | 
|  | import sys | 
|  | import time | 
|  |  | 
|  | from bcc import BPF | 
|  |  | 
|  | BPF_SRC = """ | 
|  | #include <linux/ip.h> | 
|  | #include <linux/ipv6.h> | 
|  | #include <linux/tcp.h> | 
|  | #include <linux/netfilter.h> | 
|  | #include <net/ip.h> | 
|  | #include <uapi/linux/bpf.h> | 
|  |  | 
|  | static struct tcphdr *skb_to_tcphdr(const struct sk_buff *skb) | 
|  | { | 
|  | // unstable API. verify logic in tcp_hdr() -> skb_transport_header(). | 
|  | return (struct tcphdr *)(skb->head + skb->transport_header); | 
|  | } | 
|  |  | 
|  | static inline struct iphdr *skb_to_iphdr(const struct sk_buff *skb) | 
|  | { | 
|  | // unstable API. verify logic in ip_hdr() -> skb_network_header(). | 
|  | return (struct iphdr *)(skb->head + skb->network_header); | 
|  | } | 
|  |  | 
|  | static inline struct ipv6hdr *skb_to_ip6hdr(const struct sk_buff *skb) | 
|  | { | 
|  | // unstable API. verify logic in ip_hdr() -> skb_network_header(). | 
|  | return (struct ipv6hdr *)(skb->head + skb->network_header); | 
|  | } | 
|  |  | 
|  | // for correlating between kprobe and kretprobe | 
|  | struct start_data { | 
|  | u8 hook; | 
|  | u8 pf; // netfilter protocol | 
|  | u8 tcp_state; | 
|  | u64 ts; | 
|  | }; | 
|  | BPF_PERCPU_ARRAY(sts, struct start_data, 1); | 
|  |  | 
|  | // the histogram keys | 
|  | typedef struct nf_lat_key { | 
|  | u8 proto; // see netfilter.h | 
|  | u8 hook; | 
|  | u8 tcp_state; | 
|  | } nf_lat_key_t; | 
|  |  | 
|  | typedef struct hist_key { | 
|  | nf_lat_key_t key; | 
|  | u64 slot; | 
|  | } hist_key_t; | 
|  | BPF_HISTOGRAM(dist, hist_key_t); | 
|  |  | 
|  |  | 
|  | int kprobe__nf_hook_slow(struct pt_regs *ctx, struct sk_buff *skb, struct nf_hook_state *state) { | 
|  | struct start_data data = {}; | 
|  | data.ts = bpf_ktime_get_ns(); | 
|  | data.hook = state->hook; | 
|  | data.pf = state->pf; | 
|  |  | 
|  | COND | 
|  |  | 
|  | u8 ip_proto; | 
|  | if (skb->protocol == htons(ETH_P_IP)) { | 
|  | struct iphdr *ip = skb_to_iphdr(skb); | 
|  | ip_proto = ip->protocol; | 
|  |  | 
|  | } else if (skb->protocol == htons(ETH_P_IPV6)) { | 
|  | struct ipv6hdr *ip = skb_to_ip6hdr(skb); | 
|  | ip_proto = ip->nexthdr; | 
|  | } | 
|  |  | 
|  | data.tcp_state = 0; | 
|  | if (ip_proto == 0x06) { //tcp | 
|  | struct tcphdr *tcp = skb_to_tcphdr(skb); | 
|  | u8 tcpflags = ((u_int8_t *)tcp)[13]; | 
|  |  | 
|  | // FIN or RST | 
|  | if (((tcpflags & 1) + (tcpflags & 4)) > 0) { | 
|  | data.tcp_state = 3; | 
|  | } | 
|  | // SYN / SACK | 
|  | else if ((tcpflags & 0x02) > 0) { | 
|  | data.tcp_state = 1; | 
|  | if ((tcpflags & 16) > 0) { // ACK | 
|  | data.tcp_state = 2; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | u32 idx = 0; | 
|  | sts.update(&idx, &data); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | int kretprobe__nf_hook_slow(struct pt_regs *ctx) { | 
|  | u32 idx = 0; | 
|  | struct start_data *s; | 
|  | s = sts.lookup(&idx); | 
|  | if (!s || s->ts == 0) { | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | s->ts = bpf_ktime_get_ns() - s->ts; | 
|  |  | 
|  | hist_key_t key = {}; | 
|  | key.key.hook = s->hook; | 
|  | key.key.proto = s->pf; | 
|  | key.key.tcp_state = s->tcp_state; | 
|  | key.slot = bpf_log2l(s->ts / FACTOR ); | 
|  | dist.increment(key); | 
|  |  | 
|  | s->ts = 0; | 
|  | sts.update(&idx, s); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  | """ | 
|  |  | 
|  | # constants from netfilter.h | 
|  | NF_HOOKS = { | 
|  | 0: "PRE_ROUTING", | 
|  | 1: "LOCAL_IN", | 
|  | 2: "FORWARD", | 
|  | 3: "LOCAL_OUT", | 
|  | 4: "POST_ROUTING", | 
|  | } | 
|  |  | 
|  | NF_PROTOS = { | 
|  | 0: "UNSPEC", | 
|  | 1: "INET", | 
|  | 2: "IPV4", | 
|  | 3: "ARP", | 
|  | 5: "NETDEV", | 
|  | 7: "BRIDGE", | 
|  | 10: "IPV6", | 
|  | 12: "DECNET", | 
|  | } | 
|  |  | 
|  | TCP_FLAGS = { | 
|  | 0: "other", | 
|  | 1: "SYN", | 
|  | 2: "SACK", | 
|  | 3: "FIN", | 
|  | } | 
|  |  | 
|  | EXAMPLES = """examples: | 
|  | nflatency                   # print netfilter latency histograms, 1 second refresh | 
|  | nflatency -p IPV4 -p IPV6   # print only for ipv4 and ipv6 | 
|  | nflatency -k PRE_ROUTING    # only record the PRE_ROUTING hook | 
|  | nflatency -i 5 -d 10        # run for 10 seconds, printing every 5 | 
|  | """ | 
|  |  | 
|  |  | 
|  | parser = argparse.ArgumentParser( | 
|  | description="Track latency added by netfilter hooks. Where possible, interesting TCP flags are included", | 
|  | formatter_class=argparse.RawDescriptionHelpFormatter, | 
|  | epilog=EXAMPLES) | 
|  | parser.add_argument("-p", "--proto", | 
|  | action='append', | 
|  | help="record this protocol only (multiple parameters allowed)", | 
|  | choices=NF_PROTOS.values()) | 
|  | parser.add_argument("-k", "--hook", | 
|  | action='append', | 
|  | help="record this netfilter hook only (multiple parameters allowed)", | 
|  | choices=NF_HOOKS.values()) | 
|  | parser.add_argument("-i", "--interval", type=int, | 
|  | help="summary interval, in seconds. Default is 10, unless --duration is supplied") | 
|  | parser.add_argument("-d", "--duration", type=int, | 
|  | help="total duration of trace, in seconds") | 
|  | parser.add_argument("--nano", action="store_true", | 
|  | help="bucket by nanoseconds instead of milliseconds") | 
|  |  | 
|  | def main(): | 
|  | args = parser.parse_args() | 
|  |  | 
|  | src = build_src(args) | 
|  | b = BPF(text=src) | 
|  | dist = b.get_table("dist") | 
|  |  | 
|  | seconds = 0 | 
|  | interval = 0 | 
|  | if not args.interval: | 
|  | interval = 1 | 
|  | if args.duration: | 
|  | interval = args.duration | 
|  | else: | 
|  | interval = args.interval | 
|  |  | 
|  | sys.stderr.write("Tracing netfilter hooks... Hit Ctrl-C to end.\n") | 
|  | while 1: | 
|  | try: | 
|  | dist.print_log2_hist( | 
|  | section_header="Bucket", | 
|  | bucket_fn=lambda k: (k.proto, k.hook, k.tcp_state), | 
|  | section_print_fn=bucket_desc) | 
|  | if args.duration and seconds >= args.duration: | 
|  | sys.exit(0) | 
|  | seconds += interval | 
|  | time.sleep(interval) | 
|  | except KeyboardInterrupt: | 
|  | sys.exit(1) | 
|  |  | 
|  |  | 
|  | def build_src(args): | 
|  | cond_src = "" | 
|  | if args.proto: | 
|  | predicate = " || ".join(map(lambda x: "data.pf == NFPROTO_%s" % x, args.proto)) | 
|  | cond_src = "if (!(%s)) { return 0; }\n" % predicate | 
|  | if args.hook: | 
|  | predicate = " || ".join(map(lambda x: "data.hook == NF_INET_%s" % x, args.hook)) | 
|  | cond_src = "%s    if (!(%s)) { return 0;}\n" % (cond_src, predicate) | 
|  |  | 
|  | factor = "1000" | 
|  | if args.nano: | 
|  | factor = "1" | 
|  |  | 
|  | return BPF_SRC.replace('COND', cond_src).replace('FACTOR', factor) | 
|  |  | 
|  |  | 
|  | def bucket_desc(bucket): | 
|  | return "%s %s (tcp %s)" % ( | 
|  | NF_PROTOS[bucket[0]], | 
|  | NF_HOOKS[bucket[1]], | 
|  | TCP_FLAGS[bucket[2]]) | 
|  |  | 
|  |  | 
|  | if __name__ == "__main__": | 
|  | main() |