xref: /aosp_15_r20/external/bcc/tools/tcpsubnet.py (revision 387f9dfdfa2baef462e92476d413c7bc2470293e)
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