#!/usr/bin/env python # # solisten Trace TCP listen events # For Linux, uses BCC, eBPF. Embedded C. # # USAGE: solisten.py [-h] [-p PID] [--show-netns] # # This is provided as a basic example of TCP connection & socket tracing. # It could be useful in scenarios where load balancers needs to be updated # dynamically as application is fully initialized. # # All IPv4 listen attempts are traced, even if they ultimately fail or the # the listening program is not willing to accept(). # # Copyright (c) 2016 Jean-Tiare Le Bigot. # Licensed under the Apache License, Version 2.0 (the "License") # # 04-Mar-2016 Jean-Tiare Le Bigot Created this. import os from socket import inet_ntop, AF_INET, AF_INET6, SOCK_STREAM, SOCK_DGRAM from struct import pack import argparse from bcc import BPF import ctypes as ct from bcc.utils import printb # Arguments examples = """Examples: ./solisten.py # Stream socket listen ./solisten.py -p 1234 # Stream socket listen for specified PID only ./solisten.py --netns 4242 # " for the specified network namespace ID only ./solisten.py --show-netns # Show network ns ID (useful for containers) """ parser = argparse.ArgumentParser( description="Stream sockets listen", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=examples) parser.add_argument("--show-netns", action="store_true", help="show network namespace") parser.add_argument("-p", "--pid", default=0, type=int, help="trace this PID only") parser.add_argument("-n", "--netns", default=0, type=int, help="trace this Network Namespace only") parser.add_argument("--ebpf", action="store_true", help=argparse.SUPPRESS) # BPF Program bpf_text = """ #include #include #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wenum-conversion" #include #pragma clang diagnostic pop // Common structure for UDP/TCP IPv4/IPv6 struct listen_evt_t { u64 ts_us; u64 pid_tgid; u64 backlog; u64 netns; u64 proto; // familiy << 16 | type u64 lport; // use only 16 bits u64 laddr[2]; // IPv4: store in laddr[0] char task[TASK_COMM_LEN]; }; BPF_PERF_OUTPUT(listen_evt); // Send an event for each IPv4 listen with PID, bound address and port int kprobe__inet_listen(struct pt_regs *ctx, struct socket *sock, int backlog) { // cast types. Intermediate cast not needed, kept for readability struct sock *sk = sock->sk; struct inet_sock *inet = (struct inet_sock *)sk; // Built event for userland struct listen_evt_t evt = { .ts_us = bpf_ktime_get_ns() / 1000, .backlog = backlog, }; // Get process comm. Needs LLVM >= 3.7.1 // see https://github.com/iovisor/bcc/issues/393 bpf_get_current_comm(evt.task, TASK_COMM_LEN); // Get socket IP family u16 family = sk->__sk_common.skc_family; evt.proto = family << 16 | SOCK_STREAM; // Get PID evt.pid_tgid = bpf_get_current_pid_tgid(); ##FILTER_PID## // Get port evt.lport = inet->inet_sport; evt.lport = ntohs(evt.lport); // Get network namespace id, if kernel supports it #ifdef CONFIG_NET_NS evt.netns = sk->__sk_common.skc_net.net->ns.inum; #else evt.netns = 0; #endif ##FILTER_NETNS## // Get IP if (family == AF_INET) { evt.laddr[0] = inet->inet_rcv_saddr; } else if (family == AF_INET6) { bpf_probe_read(evt.laddr, sizeof(evt.laddr), sk->__sk_common.skc_v6_rcv_saddr.in6_u.u6_addr32); } // Send event to userland listen_evt.perf_submit(ctx, &evt, sizeof(evt)); return 0; }; """ # event data TASK_COMM_LEN = 16 # linux/sched.h class ListenEvt(ct.Structure): _fields_ = [ ("ts_us", ct.c_ulonglong), ("pid_tgid", ct.c_ulonglong), ("backlog", ct.c_ulonglong), ("netns", ct.c_ulonglong), ("proto", ct.c_ulonglong), ("lport", ct.c_ulonglong), ("laddr", ct.c_ulonglong * 2), ("task", ct.c_char * TASK_COMM_LEN) ] # TODO: properties to unpack protocol / ip / pid / tgid ... # Format output def event_printer(show_netns): def print_event(cpu, data, size): # Decode event event = ct.cast(data, ct.POINTER(ListenEvt)).contents pid = event.pid_tgid & 0xffffffff proto_family = event.proto & 0xff proto_type = event.proto >> 16 & 0xff if proto_family == SOCK_STREAM: protocol = "TCP" elif proto_family == SOCK_DGRAM: protocol = "UDP" else: protocol = "UNK" address = "" if proto_type == AF_INET: protocol += "v4" address = inet_ntop(AF_INET, pack("I", event.laddr[0])) elif proto_type == AF_INET6: address = inet_ntop(AF_INET6, event.laddr) protocol += "v6" # Display if show_netns: printb(b"%-6d %-12.12s %-12s %-6s %-8s %-5s %-39s" % ( pid, event.task, event.netns, protocol, event.backlog, event.lport, address, )) else: printb(b"%-6d %-12.12s %-6s %-8s %-5s %-39s" % ( pid, event.task, protocol, event.backlog, event.lport, address, )) return print_event if __name__ == "__main__": # Parse arguments args = parser.parse_args() pid_filter = "" netns_filter = "" if args.pid: pid_filter = "if (evt.pid_tgid != %d) return 0;" % args.pid if args.netns: netns_filter = "if (evt.netns != %d) return 0;" % args.netns bpf_text = bpf_text.replace("##FILTER_PID##", pid_filter) bpf_text = bpf_text.replace("##FILTER_NETNS##", netns_filter) if args.ebpf: print(bpf_text) exit() # Initialize BPF b = BPF(text=bpf_text) b["listen_evt"].open_perf_buffer(event_printer(args.show_netns)) # Print headers if args.show_netns: print("%-6s %-12s %-12s %-6s %-8s %-5s %-39s" % ("PID", "COMM", "NETNS", "PROTO", "BACKLOG", "PORT", "ADDR")) else: print("%-6s %-12s %-6s %-8s %-5s %-39s" % ("PID", "COMM", "PROTO", "BACKLOG", "PORT", "ADDR")) # Read events while 1: try: b.perf_buffer_poll() except KeyboardInterrupt: exit()