xref: /aosp_15_r20/external/bcc/tools/solisten.py (revision 387f9dfdfa2baef462e92476d413c7bc2470293e)
1#!/usr/bin/env python
2#
3# solisten      Trace TCP listen events
4#               For Linux, uses BCC, eBPF. Embedded C.
5#
6# USAGE: solisten.py [-h] [-p PID] [--show-netns]
7#
8# This is provided as a basic example of TCP connection & socket tracing.
9# It could be useful in scenarios where load balancers needs to be updated
10# dynamically as application is fully initialized.
11#
12# All IPv4 and IPv6 listen attempts are traced, even if they ultimately fail
13# or the the listening program is not willing to accept().
14#
15# Copyright (c) 2016 Jean-Tiare Le Bigot.
16# Licensed under the Apache License, Version 2.0 (the "License")
17#
18# 04-Mar-2016	Jean-Tiare Le Bigot	Created this.
19
20import os
21from socket import inet_ntop, AF_INET, AF_INET6, SOCK_STREAM, SOCK_DGRAM
22from struct import pack
23import argparse
24from bcc import BPF
25from bcc.utils import printb
26
27# Arguments
28examples = """Examples:
29    ./solisten.py              # Stream socket listen
30    ./solisten.py -p 1234      # Stream socket listen for specified PID only
31    ./solisten.py --netns 4242 # " for the specified network namespace ID only
32    ./solisten.py --show-netns # Show network ns ID (useful for containers)
33"""
34
35parser = argparse.ArgumentParser(
36    description="Stream sockets listen",
37    formatter_class=argparse.RawDescriptionHelpFormatter,
38    epilog=examples)
39parser.add_argument("--show-netns", action="store_true",
40    help="show network namespace")
41parser.add_argument("-p", "--pid", default=0, type=int,
42    help="trace this PID only")
43parser.add_argument("-n", "--netns", default=0, type=int,
44    help="trace this Network Namespace only")
45parser.add_argument("--ebpf", action="store_true",
46    help=argparse.SUPPRESS)
47
48
49# BPF Program
50bpf_text = """
51#include <net/net_namespace.h>
52#include <bcc/proto.h>
53#pragma clang diagnostic push
54#pragma clang diagnostic ignored "-Wenum-conversion"
55#include <net/inet_sock.h>
56#pragma clang diagnostic pop
57
58// Common structure for UDP/TCP IPv4/IPv6
59struct listen_evt_t {
60    u64 ts_us;
61    u32 pid;
62    int backlog;
63    u64 netns;
64    u64 proto;    // familiy << 16 | type
65    u64 lport;    // use only 16 bits
66    u64 laddr[2]; // IPv4: store in laddr[0]
67    char task[TASK_COMM_LEN];
68};
69BPF_PERF_OUTPUT(listen_evt);
70
71// Send an event for each IPv4 listen with PID, bound address and port
72int kprobe__inet_listen(struct pt_regs *ctx, struct socket *sock, int backlog)
73{
74        // cast types. Intermediate cast not needed, kept for readability
75        struct sock *sk = sock->sk;
76        struct inet_sock *inet = (struct inet_sock *)sk;
77
78        // Built event for userland
79        struct listen_evt_t evt = {
80            .ts_us = bpf_ktime_get_ns() / 1000,
81            .backlog = backlog,
82        };
83
84        // Get process comm. Needs LLVM >= 3.7.1
85        // see https://github.com/iovisor/bcc/issues/393
86        bpf_get_current_comm(evt.task, TASK_COMM_LEN);
87
88        // Get socket IP family
89        u16 family = sk->__sk_common.skc_family;
90        evt.proto = family << 16 | SOCK_STREAM;
91
92        // Get PID
93        evt.pid = bpf_get_current_pid_tgid() >> 32;
94
95        ##FILTER_PID##
96
97        // Get port
98        evt.lport = inet->inet_sport;
99        evt.lport = ntohs(evt.lport);
100
101        // Get network namespace id, if kernel supports it
102#ifdef CONFIG_NET_NS
103        evt.netns = sk->__sk_common.skc_net.net->ns.inum;
104#else
105        evt.netns = 0;
106#endif
107
108        ##FILTER_NETNS##
109
110        // Get IP
111        if (family == AF_INET) {
112            evt.laddr[0] = inet->inet_rcv_saddr;
113        } else if (family == AF_INET6) {
114            bpf_probe_read_kernel(evt.laddr, sizeof(evt.laddr),
115                           sk->__sk_common.skc_v6_rcv_saddr.in6_u.u6_addr32);
116        }
117
118        // Send event to userland
119        listen_evt.perf_submit(ctx, &evt, sizeof(evt));
120
121        return 0;
122};
123"""
124
125    # TODO: properties to unpack protocol / ip / pid / tgid ...
126
127# Format output
128def event_printer(show_netns):
129    def print_event(cpu, data, size):
130        # Decode event
131        event = b["listen_evt"].event(data)
132
133        pid = event.pid
134        proto_family = event.proto & 0xff
135        proto_type = event.proto >> 16 & 0xff
136
137        if proto_family == SOCK_STREAM:
138            protocol = "TCP"
139        elif proto_family == SOCK_DGRAM:
140            protocol = "UDP"
141        else:
142            protocol = "UNK"
143
144        address = ""
145        if proto_type == AF_INET:
146            protocol += "v4"
147            address = inet_ntop(AF_INET, pack("I", event.laddr[0]))
148        elif proto_type == AF_INET6:
149            address = inet_ntop(AF_INET6, event.laddr)
150            protocol += "v6"
151
152        # Display
153        if show_netns:
154            printb(b"%-7d %-12.12s %-12d %-6s %-8d %-5d %-39s" % (
155                pid, event.task, event.netns, protocol.encode(), event.backlog,
156                event.lport, address.encode(),
157            ))
158        else:
159            printb(b"%-7d %-12.12s %-6s %-8d %-5d %-39s" % (
160                pid, event.task, protocol.encode(), event.backlog,
161                event.lport, address.encode(),
162            ))
163
164    return print_event
165
166if __name__ == "__main__":
167    # Parse arguments
168    args = parser.parse_args()
169
170    pid_filter = ""
171    netns_filter = ""
172
173    if args.pid:
174        pid_filter = "if (evt.pid != %d) return 0;" % args.pid
175    if args.netns:
176        netns_filter = "if (evt.netns != %d) return 0;" % args.netns
177
178    bpf_text = bpf_text.replace("##FILTER_PID##", pid_filter)
179    bpf_text = bpf_text.replace("##FILTER_NETNS##", netns_filter)
180
181    if args.ebpf:
182        print(bpf_text)
183        exit()
184
185    # Initialize BPF
186    b = BPF(text=bpf_text)
187    b["listen_evt"].open_perf_buffer(event_printer(args.show_netns))
188
189    # Print headers
190    if args.show_netns:
191        print("%-7s %-12s %-12s %-6s %-8s %-5s %-39s" %
192              ("PID", "COMM", "NETNS", "PROTO", "BACKLOG", "PORT", "ADDR"))
193    else:
194        print("%-7s %-12s %-6s %-8s %-5s %-39s" %
195              ("PID", "COMM", "PROTO", "BACKLOG", "PORT", "ADDR"))
196
197    # Read events
198    while 1:
199        try:
200            b.perf_buffer_poll()
201        except KeyboardInterrupt:
202            exit()
203