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