1*387f9dfdSAndroid Build Coastguard Worker#!/usr/bin/env python 2*387f9dfdSAndroid Build Coastguard Worker# @lint-avoid-python-3-compatibility-imports 3*387f9dfdSAndroid Build Coastguard Worker# 4*387f9dfdSAndroid Build Coastguard Worker# tcpsubnet Summarize TCP bytes sent to different subnets. 5*387f9dfdSAndroid Build Coastguard Worker# For Linux, uses BCC, eBPF. Embedded C. 6*387f9dfdSAndroid Build Coastguard Worker# 7*387f9dfdSAndroid Build Coastguard Worker# USAGE: tcpsubnet [-h] [-v] [-J] [-f FORMAT] [-i INTERVAL] [subnets] 8*387f9dfdSAndroid Build Coastguard Worker# 9*387f9dfdSAndroid Build Coastguard Worker# This uses dynamic tracing of kernel functions, and will need to be updated 10*387f9dfdSAndroid Build Coastguard Worker# to match kernel changes. 11*387f9dfdSAndroid Build Coastguard Worker# 12*387f9dfdSAndroid Build Coastguard Worker# This is an adaptation of tcptop from written by Brendan Gregg. 13*387f9dfdSAndroid Build Coastguard Worker# 14*387f9dfdSAndroid Build Coastguard Worker# WARNING: This traces all send at the TCP level, and while it 15*387f9dfdSAndroid Build Coastguard Worker# summarizes data in-kernel to reduce overhead, there may still be some 16*387f9dfdSAndroid Build Coastguard Worker# overhead at high TCP send/receive rates (eg, ~13% of one CPU at 100k TCP 17*387f9dfdSAndroid Build Coastguard Worker# events/sec. This is not the same as packet rate: funccount can be used to 18*387f9dfdSAndroid Build Coastguard Worker# count the kprobes below to find out the TCP rate). Test in a lab environment 19*387f9dfdSAndroid Build Coastguard Worker# first. If your send rate is low (eg, <1k/sec) then the overhead is 20*387f9dfdSAndroid Build Coastguard Worker# expected to be negligible. 21*387f9dfdSAndroid Build Coastguard Worker# 22*387f9dfdSAndroid Build Coastguard Worker# Copyright 2017 Rodrigo Manyari 23*387f9dfdSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License") 24*387f9dfdSAndroid Build Coastguard Worker# 25*387f9dfdSAndroid Build Coastguard Worker# 03-Oct-2017 Rodrigo Manyari Created this based on tcptop. 26*387f9dfdSAndroid Build Coastguard Worker# 13-Feb-2018 Rodrigo Manyari Fix pep8 errors, some refactoring. 27*387f9dfdSAndroid Build Coastguard Worker# 05-Mar-2018 Rodrigo Manyari Add date time to output. 28*387f9dfdSAndroid Build Coastguard Worker 29*387f9dfdSAndroid Build Coastguard Workerimport argparse 30*387f9dfdSAndroid Build Coastguard Workerimport json 31*387f9dfdSAndroid Build Coastguard Workerimport logging 32*387f9dfdSAndroid Build Coastguard Workerimport struct 33*387f9dfdSAndroid Build Coastguard Workerimport socket 34*387f9dfdSAndroid Build Coastguard Workerfrom bcc import BPF 35*387f9dfdSAndroid Build Coastguard Workerfrom datetime import datetime as dt 36*387f9dfdSAndroid Build Coastguard Workerfrom time import sleep 37*387f9dfdSAndroid Build Coastguard Worker 38*387f9dfdSAndroid Build Coastguard Worker# arguments 39*387f9dfdSAndroid Build Coastguard Workerexamples = """examples: 40*387f9dfdSAndroid Build Coastguard Worker ./tcpsubnet # Trace TCP sent to the default subnets: 41*387f9dfdSAndroid Build Coastguard Worker # 127.0.0.1/32,10.0.0.0/8,172.16.0.0/12, 42*387f9dfdSAndroid Build Coastguard Worker # 192.168.0.0/16,0.0.0.0/0 43*387f9dfdSAndroid Build Coastguard Worker ./tcpsubnet -f K # Trace TCP sent to the default subnets 44*387f9dfdSAndroid Build Coastguard Worker # aggregated in KBytes. 45*387f9dfdSAndroid Build Coastguard Worker ./tcpsubnet 10.80.0.0/24 # Trace TCP sent to 10.80.0.0/24 only 46*387f9dfdSAndroid Build Coastguard Worker ./tcpsubnet -J # Format the output in JSON. 47*387f9dfdSAndroid Build Coastguard Worker""" 48*387f9dfdSAndroid Build Coastguard Worker 49*387f9dfdSAndroid Build Coastguard Workerdefault_subnets = "127.0.0.1/32,10.0.0.0/8," \ 50*387f9dfdSAndroid Build Coastguard Worker "172.16.0.0/12,192.168.0.0/16,0.0.0.0/0" 51*387f9dfdSAndroid Build Coastguard Worker 52*387f9dfdSAndroid Build Coastguard Workerparser = argparse.ArgumentParser( 53*387f9dfdSAndroid Build Coastguard Worker description="Summarize TCP send and aggregate by subnet", 54*387f9dfdSAndroid Build Coastguard Worker formatter_class=argparse.RawDescriptionHelpFormatter, 55*387f9dfdSAndroid Build Coastguard Worker epilog=examples) 56*387f9dfdSAndroid Build Coastguard Workerparser.add_argument("subnets", help="comma separated list of subnets", 57*387f9dfdSAndroid Build Coastguard Worker type=str, nargs="?", default=default_subnets) 58*387f9dfdSAndroid Build Coastguard Workerparser.add_argument("-v", "--verbose", action="store_true", 59*387f9dfdSAndroid Build Coastguard Worker help="output debug statements") 60*387f9dfdSAndroid Build Coastguard Workerparser.add_argument("-J", "--json", action="store_true", 61*387f9dfdSAndroid Build Coastguard Worker help="format output in JSON") 62*387f9dfdSAndroid Build Coastguard Workerparser.add_argument("--ebpf", action="store_true", 63*387f9dfdSAndroid Build Coastguard Worker help=argparse.SUPPRESS) 64*387f9dfdSAndroid Build Coastguard Workerparser.add_argument("-f", "--format", default="B", 65*387f9dfdSAndroid Build Coastguard Worker help="[bkmBKM] format to report: bits, Kbits, Mbits, bytes, " + 66*387f9dfdSAndroid Build Coastguard Worker "KBytes, MBytes (default B)", choices=["b", "k", "m", "B", "K", "M"]) 67*387f9dfdSAndroid Build Coastguard Workerparser.add_argument("-i", "--interval", default=1, type=int, 68*387f9dfdSAndroid Build Coastguard Worker help="output interval, in seconds (default 1)") 69*387f9dfdSAndroid Build Coastguard Workerargs = parser.parse_args() 70*387f9dfdSAndroid Build Coastguard Worker 71*387f9dfdSAndroid Build Coastguard Workerlevel = logging.INFO 72*387f9dfdSAndroid Build Coastguard Workerif args.verbose: 73*387f9dfdSAndroid Build Coastguard Worker level = logging.DEBUG 74*387f9dfdSAndroid Build Coastguard Worker 75*387f9dfdSAndroid Build Coastguard Workerlogging.basicConfig(level=level) 76*387f9dfdSAndroid Build Coastguard Worker 77*387f9dfdSAndroid Build Coastguard Workerlogging.debug("Starting with the following args:") 78*387f9dfdSAndroid Build Coastguard Workerlogging.debug(args) 79*387f9dfdSAndroid Build Coastguard Worker 80*387f9dfdSAndroid Build Coastguard Worker# args checking 81*387f9dfdSAndroid Build Coastguard Workerif int(args.interval) <= 0: 82*387f9dfdSAndroid Build Coastguard Worker logging.error("Invalid interval, must be > 0. Exiting.") 83*387f9dfdSAndroid Build Coastguard Worker exit(1) 84*387f9dfdSAndroid Build Coastguard Workerelse: 85*387f9dfdSAndroid Build Coastguard Worker args.interval = int(args.interval) 86*387f9dfdSAndroid Build Coastguard Worker 87*387f9dfdSAndroid Build Coastguard Worker# map of supported formats 88*387f9dfdSAndroid Build Coastguard Workerformats = { 89*387f9dfdSAndroid Build Coastguard Worker "b": lambda x: (x * 8), 90*387f9dfdSAndroid Build Coastguard Worker "k": lambda x: ((x * 8) / 1024), 91*387f9dfdSAndroid Build Coastguard Worker "m": lambda x: ((x * 8) / pow(1024, 2)), 92*387f9dfdSAndroid Build Coastguard Worker "B": lambda x: x, 93*387f9dfdSAndroid Build Coastguard Worker "K": lambda x: x / 1024, 94*387f9dfdSAndroid Build Coastguard Worker "M": lambda x: x / pow(1024, 2) 95*387f9dfdSAndroid Build Coastguard Worker} 96*387f9dfdSAndroid Build Coastguard Worker 97*387f9dfdSAndroid Build Coastguard Worker# Let's swap the string with the actual numeric value 98*387f9dfdSAndroid Build Coastguard Worker# once here so we don't have to do it on every interval 99*387f9dfdSAndroid Build Coastguard WorkerformatFn = formats[args.format] 100*387f9dfdSAndroid Build Coastguard Worker 101*387f9dfdSAndroid Build Coastguard Worker# define the basic structure of the BPF program 102*387f9dfdSAndroid Build Coastguard Workerbpf_text = """ 103*387f9dfdSAndroid Build Coastguard Worker#include <uapi/linux/ptrace.h> 104*387f9dfdSAndroid Build Coastguard Worker#include <net/sock.h> 105*387f9dfdSAndroid Build Coastguard Worker#include <bcc/proto.h> 106*387f9dfdSAndroid Build Coastguard Worker 107*387f9dfdSAndroid Build Coastguard Workerstruct index_key_t { 108*387f9dfdSAndroid Build Coastguard Worker u32 index; 109*387f9dfdSAndroid Build Coastguard Worker}; 110*387f9dfdSAndroid Build Coastguard Worker 111*387f9dfdSAndroid Build Coastguard WorkerBPF_HASH(ipv4_send_bytes, struct index_key_t); 112*387f9dfdSAndroid Build Coastguard Worker 113*387f9dfdSAndroid Build Coastguard Workerint kprobe__tcp_sendmsg(struct pt_regs *ctx, struct sock *sk, 114*387f9dfdSAndroid Build Coastguard Worker struct msghdr *msg, size_t size) 115*387f9dfdSAndroid Build Coastguard Worker{ 116*387f9dfdSAndroid Build Coastguard Worker u16 family = sk->__sk_common.skc_family; 117*387f9dfdSAndroid Build Coastguard Worker 118*387f9dfdSAndroid Build Coastguard Worker if (family == AF_INET) { 119*387f9dfdSAndroid Build Coastguard Worker u32 dst = sk->__sk_common.skc_daddr; 120*387f9dfdSAndroid Build Coastguard Worker unsigned categorized = 0; 121*387f9dfdSAndroid Build Coastguard Worker __SUBNETS__ 122*387f9dfdSAndroid Build Coastguard Worker } 123*387f9dfdSAndroid Build Coastguard Worker return 0; 124*387f9dfdSAndroid Build Coastguard Worker} 125*387f9dfdSAndroid Build Coastguard Worker""" 126*387f9dfdSAndroid Build Coastguard Worker 127*387f9dfdSAndroid Build Coastguard Worker 128*387f9dfdSAndroid Build Coastguard Worker# Takes in a mask and returns the integer equivalent 129*387f9dfdSAndroid Build Coastguard Worker# e.g. 130*387f9dfdSAndroid Build Coastguard Worker# mask_to_int(8) returns 4278190080 131*387f9dfdSAndroid Build Coastguard Workerdef mask_to_int(n): 132*387f9dfdSAndroid Build Coastguard Worker return ((1 << n) - 1) << (32 - n) 133*387f9dfdSAndroid Build Coastguard Worker 134*387f9dfdSAndroid Build Coastguard Worker# Takes in a list of subnets and returns a list 135*387f9dfdSAndroid Build Coastguard Worker# of tuple-3 containing: 136*387f9dfdSAndroid Build Coastguard Worker# - The subnet info at index 0 137*387f9dfdSAndroid Build Coastguard Worker# - The addr portion as an int at index 1 138*387f9dfdSAndroid Build Coastguard Worker# - The mask portion as an int at index 2 139*387f9dfdSAndroid Build Coastguard Worker# 140*387f9dfdSAndroid Build Coastguard Worker# e.g. 141*387f9dfdSAndroid Build Coastguard Worker# parse_subnets([10.10.0.0/24]) returns 142*387f9dfdSAndroid Build Coastguard Worker# [ 143*387f9dfdSAndroid Build Coastguard Worker# ['10.10.0.0/24', 168427520, 4294967040], 144*387f9dfdSAndroid Build Coastguard Worker# ] 145*387f9dfdSAndroid Build Coastguard Workerdef parse_subnets(subnets): 146*387f9dfdSAndroid Build Coastguard Worker m = [] 147*387f9dfdSAndroid Build Coastguard Worker for s in subnets: 148*387f9dfdSAndroid Build Coastguard Worker parts = s.split("/") 149*387f9dfdSAndroid Build Coastguard Worker if len(parts) != 2: 150*387f9dfdSAndroid Build Coastguard Worker msg = "Subnet [%s] is invalid, please refer to the examples." % s 151*387f9dfdSAndroid Build Coastguard Worker raise ValueError(msg) 152*387f9dfdSAndroid Build Coastguard Worker netaddr_int = 0 153*387f9dfdSAndroid Build Coastguard Worker mask_int = 0 154*387f9dfdSAndroid Build Coastguard Worker try: 155*387f9dfdSAndroid Build Coastguard Worker netaddr_int = struct.unpack("!I", socket.inet_aton(parts[0]))[0] 156*387f9dfdSAndroid Build Coastguard Worker except: 157*387f9dfdSAndroid Build Coastguard Worker msg = ("Invalid net address in subnet [%s], " + 158*387f9dfdSAndroid Build Coastguard Worker "please refer to the examples.") % s 159*387f9dfdSAndroid Build Coastguard Worker raise ValueError(msg) 160*387f9dfdSAndroid Build Coastguard Worker try: 161*387f9dfdSAndroid Build Coastguard Worker mask_int = int(parts[1]) 162*387f9dfdSAndroid Build Coastguard Worker except: 163*387f9dfdSAndroid Build Coastguard Worker msg = "Invalid mask in subnet [%s]. Mask must be an int" % s 164*387f9dfdSAndroid Build Coastguard Worker raise ValueError(msg) 165*387f9dfdSAndroid Build Coastguard Worker if mask_int < 0 or mask_int > 32: 166*387f9dfdSAndroid Build Coastguard Worker msg = ("Invalid mask in subnet [%s]. Must be an " + 167*387f9dfdSAndroid Build Coastguard Worker "int between 0 and 32.") % s 168*387f9dfdSAndroid Build Coastguard Worker raise ValueError(msg) 169*387f9dfdSAndroid Build Coastguard Worker mask_int = mask_to_int(int(parts[1])) 170*387f9dfdSAndroid Build Coastguard Worker m.append([s, netaddr_int, mask_int]) 171*387f9dfdSAndroid Build Coastguard Worker return m 172*387f9dfdSAndroid Build Coastguard Worker 173*387f9dfdSAndroid Build Coastguard Workerdef generate_bpf_subnets(subnets): 174*387f9dfdSAndroid Build Coastguard Worker template = """ 175*387f9dfdSAndroid Build Coastguard Worker if (!categorized && (__NET_ADDR__ & __NET_MASK__) == 176*387f9dfdSAndroid Build Coastguard Worker (dst & __NET_MASK__)) { 177*387f9dfdSAndroid Build Coastguard Worker struct index_key_t key = {.index = __POS__}; 178*387f9dfdSAndroid Build Coastguard Worker ipv4_send_bytes.atomic_increment(key, size); 179*387f9dfdSAndroid Build Coastguard Worker categorized = 1; 180*387f9dfdSAndroid Build Coastguard Worker } 181*387f9dfdSAndroid Build Coastguard Worker """ 182*387f9dfdSAndroid Build Coastguard Worker bpf = '' 183*387f9dfdSAndroid Build Coastguard Worker for i, s in enumerate(subnets): 184*387f9dfdSAndroid Build Coastguard Worker branch = template 185*387f9dfdSAndroid Build Coastguard Worker branch = branch.replace("__NET_ADDR__", str(socket.htonl(s[1]))) 186*387f9dfdSAndroid Build Coastguard Worker branch = branch.replace("__NET_MASK__", str(socket.htonl(s[2]))) 187*387f9dfdSAndroid Build Coastguard Worker branch = branch.replace("__POS__", str(i)) 188*387f9dfdSAndroid Build Coastguard Worker bpf += branch 189*387f9dfdSAndroid Build Coastguard Worker return bpf 190*387f9dfdSAndroid Build Coastguard Worker 191*387f9dfdSAndroid Build Coastguard Workersubnets = [] 192*387f9dfdSAndroid Build Coastguard Workerif args.subnets: 193*387f9dfdSAndroid Build Coastguard Worker subnets = args.subnets.split(",") 194*387f9dfdSAndroid Build Coastguard Worker 195*387f9dfdSAndroid Build Coastguard Workersubnets = parse_subnets(subnets) 196*387f9dfdSAndroid Build Coastguard Worker 197*387f9dfdSAndroid Build Coastguard Workerlogging.debug("Packets are going to be categorized in the following subnets:") 198*387f9dfdSAndroid Build Coastguard Workerlogging.debug(subnets) 199*387f9dfdSAndroid Build Coastguard Worker 200*387f9dfdSAndroid Build Coastguard Workerbpf_subnets = generate_bpf_subnets(subnets) 201*387f9dfdSAndroid Build Coastguard Worker 202*387f9dfdSAndroid Build Coastguard Worker# initialize BPF 203*387f9dfdSAndroid Build Coastguard Workerbpf_text = bpf_text.replace("__SUBNETS__", bpf_subnets) 204*387f9dfdSAndroid Build Coastguard Worker 205*387f9dfdSAndroid Build Coastguard Workerlogging.debug("Done preprocessing the BPF program, " + 206*387f9dfdSAndroid Build Coastguard Worker "this is what will actually get executed:") 207*387f9dfdSAndroid Build Coastguard Workerlogging.debug(bpf_text) 208*387f9dfdSAndroid Build Coastguard Worker 209*387f9dfdSAndroid Build Coastguard Workerif args.ebpf: 210*387f9dfdSAndroid Build Coastguard Worker print(bpf_text) 211*387f9dfdSAndroid Build Coastguard Worker exit() 212*387f9dfdSAndroid Build Coastguard Worker 213*387f9dfdSAndroid Build Coastguard Workerb = BPF(text=bpf_text) 214*387f9dfdSAndroid Build Coastguard Worker 215*387f9dfdSAndroid Build Coastguard Workeripv4_send_bytes = b["ipv4_send_bytes"] 216*387f9dfdSAndroid Build Coastguard Worker 217*387f9dfdSAndroid Build Coastguard Workerif not args.json: 218*387f9dfdSAndroid Build Coastguard Worker print("Tracing... Output every %d secs. Hit Ctrl-C to end" % args.interval) 219*387f9dfdSAndroid Build Coastguard Worker 220*387f9dfdSAndroid Build Coastguard Worker# output 221*387f9dfdSAndroid Build Coastguard Workerexiting = 0 222*387f9dfdSAndroid Build Coastguard Workerwhile (1): 223*387f9dfdSAndroid Build Coastguard Worker 224*387f9dfdSAndroid Build Coastguard Worker try: 225*387f9dfdSAndroid Build Coastguard Worker sleep(args.interval) 226*387f9dfdSAndroid Build Coastguard Worker except KeyboardInterrupt: 227*387f9dfdSAndroid Build Coastguard Worker exiting = 1 228*387f9dfdSAndroid Build Coastguard Worker 229*387f9dfdSAndroid Build Coastguard Worker # IPv4: build dict of all seen keys 230*387f9dfdSAndroid Build Coastguard Worker keys = ipv4_send_bytes 231*387f9dfdSAndroid Build Coastguard Worker for k, v in ipv4_send_bytes.items(): 232*387f9dfdSAndroid Build Coastguard Worker if k not in keys: 233*387f9dfdSAndroid Build Coastguard Worker keys[k] = v 234*387f9dfdSAndroid Build Coastguard Worker 235*387f9dfdSAndroid Build Coastguard Worker # to hold json data 236*387f9dfdSAndroid Build Coastguard Worker data = {} 237*387f9dfdSAndroid Build Coastguard Worker 238*387f9dfdSAndroid Build Coastguard Worker # output 239*387f9dfdSAndroid Build Coastguard Worker now = dt.now() 240*387f9dfdSAndroid Build Coastguard Worker data['date'] = now.strftime('%x') 241*387f9dfdSAndroid Build Coastguard Worker data['time'] = now.strftime('%X') 242*387f9dfdSAndroid Build Coastguard Worker data['entries'] = {} 243*387f9dfdSAndroid Build Coastguard Worker if not args.json: 244*387f9dfdSAndroid Build Coastguard Worker print(now.strftime('[%x %X]')) 245*387f9dfdSAndroid Build Coastguard Worker for k, v in reversed(sorted(keys.items(), key=lambda keys: keys[1].value)): 246*387f9dfdSAndroid Build Coastguard Worker send_bytes = 0 247*387f9dfdSAndroid Build Coastguard Worker if k in ipv4_send_bytes: 248*387f9dfdSAndroid Build Coastguard Worker send_bytes = int(ipv4_send_bytes[k].value) 249*387f9dfdSAndroid Build Coastguard Worker subnet = subnets[k.index][0] 250*387f9dfdSAndroid Build Coastguard Worker send = formatFn(send_bytes) 251*387f9dfdSAndroid Build Coastguard Worker if args.json: 252*387f9dfdSAndroid Build Coastguard Worker data['entries'][subnet] = send 253*387f9dfdSAndroid Build Coastguard Worker else: 254*387f9dfdSAndroid Build Coastguard Worker print("%-21s %6d" % (subnet, send)) 255*387f9dfdSAndroid Build Coastguard Worker 256*387f9dfdSAndroid Build Coastguard Worker if args.json: 257*387f9dfdSAndroid Build Coastguard Worker print(json.dumps(data)) 258*387f9dfdSAndroid Build Coastguard Worker 259*387f9dfdSAndroid Build Coastguard Worker ipv4_send_bytes.clear() 260*387f9dfdSAndroid Build Coastguard Worker 261*387f9dfdSAndroid Build Coastguard Worker if exiting: 262*387f9dfdSAndroid Build Coastguard Worker exit(0) 263