1#!/usr/bin/python 2# @lint-avoid-python-3-compatibility-imports 3# 4# tcpdrop Trace TCP kernel-dropped packets/segments. 5# For Linux, uses BCC, eBPF. Embedded C. 6# 7# This provides information such as packet details, socket state, and kernel 8# stack trace for packets/segments that were dropped via tcp_drop(). 9# 10# USAGE: tcpdrop [-4 | -6] [-h] 11# 12# This uses dynamic tracing of kernel functions, and will need to be updated 13# to match kernel changes. 14# 15# Copyright 2018 Netflix, Inc. 16# Licensed under the Apache License, Version 2.0 (the "License") 17# 18# 30-May-2018 Brendan Gregg Created this. 19 20from __future__ import print_function 21from bcc import BPF 22import argparse 23from time import strftime 24from socket import inet_ntop, AF_INET, AF_INET6 25from struct import pack 26from time import sleep 27from bcc import tcp 28 29# arguments 30examples = """examples: 31 ./tcpdrop # trace kernel TCP drops 32 ./tcpdrop -4 # trace IPv4 family only 33 ./tcpdrop -6 # trace IPv6 family only 34""" 35parser = argparse.ArgumentParser( 36 description="Trace TCP drops by the kernel", 37 formatter_class=argparse.RawDescriptionHelpFormatter, 38 epilog=examples) 39group = parser.add_mutually_exclusive_group() 40group.add_argument("-4", "--ipv4", action="store_true", 41 help="trace IPv4 family only") 42group.add_argument("-6", "--ipv6", action="store_true", 43 help="trace IPv6 family only") 44parser.add_argument("--ebpf", action="store_true", 45 help=argparse.SUPPRESS) 46args = parser.parse_args() 47debug = 0 48 49# define BPF program 50bpf_text = """ 51#include <uapi/linux/ptrace.h> 52#include <uapi/linux/tcp.h> 53#include <uapi/linux/ip.h> 54#include <net/sock.h> 55#include <bcc/proto.h> 56 57BPF_STACK_TRACE(stack_traces, 1024); 58 59// separate data structs for ipv4 and ipv6 60struct ipv4_data_t { 61 u32 pid; 62 u64 ip; 63 u32 saddr; 64 u32 daddr; 65 u16 sport; 66 u16 dport; 67 u8 state; 68 u8 tcpflags; 69 u32 stack_id; 70}; 71BPF_PERF_OUTPUT(ipv4_events); 72 73struct ipv6_data_t { 74 u32 pid; 75 u64 ip; 76 unsigned __int128 saddr; 77 unsigned __int128 daddr; 78 u16 sport; 79 u16 dport; 80 u8 state; 81 u8 tcpflags; 82 u32 stack_id; 83}; 84BPF_PERF_OUTPUT(ipv6_events); 85 86static struct tcphdr *skb_to_tcphdr(const struct sk_buff *skb) 87{ 88 // unstable API. verify logic in tcp_hdr() -> skb_transport_header(). 89 return (struct tcphdr *)(skb->head + skb->transport_header); 90} 91 92static inline struct iphdr *skb_to_iphdr(const struct sk_buff *skb) 93{ 94 // unstable API. verify logic in ip_hdr() -> skb_network_header(). 95 return (struct iphdr *)(skb->head + skb->network_header); 96} 97 98// from include/net/tcp.h: 99#ifndef tcp_flag_byte 100#define tcp_flag_byte(th) (((u_int8_t *)th)[13]) 101#endif 102 103int trace_tcp_drop(struct pt_regs *ctx, struct sock *sk, struct sk_buff *skb) 104{ 105 if (sk == NULL) 106 return 0; 107 u32 pid = bpf_get_current_pid_tgid() >> 32; 108 109 // pull in details from the packet headers and the sock struct 110 u16 family = sk->__sk_common.skc_family; 111 char state = sk->__sk_common.skc_state; 112 u16 sport = 0, dport = 0; 113 struct tcphdr *tcp = skb_to_tcphdr(skb); 114 struct iphdr *ip = skb_to_iphdr(skb); 115 u8 tcpflags = ((u_int8_t *)tcp)[13]; 116 sport = tcp->source; 117 dport = tcp->dest; 118 sport = ntohs(sport); 119 dport = ntohs(dport); 120 121 FILTER_FAMILY 122 123 if (family == AF_INET) { 124 struct ipv4_data_t data4 = {}; 125 data4.pid = pid; 126 data4.ip = 4; 127 data4.saddr = ip->saddr; 128 data4.daddr = ip->daddr; 129 data4.dport = dport; 130 data4.sport = sport; 131 data4.state = state; 132 data4.tcpflags = tcpflags; 133 data4.stack_id = stack_traces.get_stackid(ctx, 0); 134 ipv4_events.perf_submit(ctx, &data4, sizeof(data4)); 135 136 } else if (family == AF_INET6) { 137 struct ipv6_data_t data6 = {}; 138 data6.pid = pid; 139 data6.ip = 6; 140 // The remote address (skc_v6_daddr) was the source 141 bpf_probe_read_kernel(&data6.saddr, sizeof(data6.saddr), 142 sk->__sk_common.skc_v6_daddr.in6_u.u6_addr32); 143 // The local address (skc_v6_rcv_saddr) was the destination 144 bpf_probe_read_kernel(&data6.daddr, sizeof(data6.daddr), 145 sk->__sk_common.skc_v6_rcv_saddr.in6_u.u6_addr32); 146 data6.dport = dport; 147 data6.sport = sport; 148 data6.state = state; 149 data6.tcpflags = tcpflags; 150 data6.stack_id = stack_traces.get_stackid(ctx, 0); 151 ipv6_events.perf_submit(ctx, &data6, sizeof(data6)); 152 } 153 // else drop 154 155 return 0; 156} 157""" 158 159if debug or args.ebpf: 160 print(bpf_text) 161 if args.ebpf: 162 exit() 163if args.ipv4: 164 bpf_text = bpf_text.replace('FILTER_FAMILY', 165 'if (family != AF_INET) { return 0; }') 166elif args.ipv6: 167 bpf_text = bpf_text.replace('FILTER_FAMILY', 168 'if (family != AF_INET6) { return 0; }') 169else: 170 bpf_text = bpf_text.replace('FILTER_FAMILY', '') 171 172# process event 173def print_ipv4_event(cpu, data, size): 174 event = b["ipv4_events"].event(data) 175 print("%-8s %-7d %-2d %-20s > %-20s %s (%s)" % ( 176 strftime("%H:%M:%S"), event.pid, event.ip, 177 "%s:%d" % (inet_ntop(AF_INET, pack('I', event.saddr)), event.sport), 178 "%s:%s" % (inet_ntop(AF_INET, pack('I', event.daddr)), event.dport), 179 tcp.tcpstate[event.state], tcp.flags2str(event.tcpflags))) 180 for addr in stack_traces.walk(event.stack_id): 181 sym = b.ksym(addr, show_offset=True) 182 print("\t%s" % sym) 183 print("") 184 185def print_ipv6_event(cpu, data, size): 186 event = b["ipv6_events"].event(data) 187 print("%-8s %-7d %-2d %-20s > %-20s %s (%s)" % ( 188 strftime("%H:%M:%S"), event.pid, event.ip, 189 "%s:%d" % (inet_ntop(AF_INET6, event.saddr), event.sport), 190 "%s:%d" % (inet_ntop(AF_INET6, event.daddr), event.dport), 191 tcp.tcpstate[event.state], tcp.flags2str(event.tcpflags))) 192 for addr in stack_traces.walk(event.stack_id): 193 sym = b.ksym(addr, show_offset=True) 194 print("\t%s" % sym) 195 print("") 196 197# initialize BPF 198b = BPF(text=bpf_text) 199if b.get_kprobe_functions(b"tcp_drop"): 200 b.attach_kprobe(event="tcp_drop", fn_name="trace_tcp_drop") 201else: 202 print("ERROR: tcp_drop() kernel function not found or traceable. " 203 "Older kernel versions not supported.") 204 exit() 205stack_traces = b.get_table("stack_traces") 206 207# header 208print("%-8s %-7s %-2s %-20s > %-20s %s (%s)" % ("TIME", "PID", "IP", 209 "SADDR:SPORT", "DADDR:DPORT", "STATE", "FLAGS")) 210 211# read events 212b["ipv4_events"].open_perf_buffer(print_ipv4_event) 213b["ipv6_events"].open_perf_buffer(print_ipv6_event) 214while 1: 215 try: 216 b.perf_buffer_poll() 217 except KeyboardInterrupt: 218 exit() 219