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 listen attempts are traced, even if they ultimately fail or the 13# 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 25import ctypes as ct 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 u64 pid_tgid; 62 u64 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_tgid = bpf_get_current_pid_tgid(); 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(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# event data 126TASK_COMM_LEN = 16 # linux/sched.h 127class ListenEvt(ct.Structure): 128 _fields_ = [ 129 ("ts_us", ct.c_ulonglong), 130 ("pid_tgid", ct.c_ulonglong), 131 ("backlog", ct.c_ulonglong), 132 ("netns", ct.c_ulonglong), 133 ("proto", ct.c_ulonglong), 134 ("lport", ct.c_ulonglong), 135 ("laddr", ct.c_ulonglong * 2), 136 ("task", ct.c_char * TASK_COMM_LEN) 137 ] 138 139 # TODO: properties to unpack protocol / ip / pid / tgid ... 140 141# Format output 142def event_printer(show_netns): 143 def print_event(cpu, data, size): 144 # Decode event 145 event = ct.cast(data, ct.POINTER(ListenEvt)).contents 146 147 pid = event.pid_tgid & 0xffffffff 148 proto_family = event.proto & 0xff 149 proto_type = event.proto >> 16 & 0xff 150 151 if proto_family == SOCK_STREAM: 152 protocol = "TCP" 153 elif proto_family == SOCK_DGRAM: 154 protocol = "UDP" 155 else: 156 protocol = "UNK" 157 158 address = "" 159 if proto_type == AF_INET: 160 protocol += "v4" 161 address = inet_ntop(AF_INET, pack("I", event.laddr[0])) 162 elif proto_type == AF_INET6: 163 address = inet_ntop(AF_INET6, event.laddr) 164 protocol += "v6" 165 166 # Display 167 if show_netns: 168 print("%-6d %-12.12s %-12s %-6s %-8s %-5s %-39s" % ( 169 pid, event.task, event.netns, protocol, event.backlog, 170 event.lport, address, 171 )) 172 else: 173 print("%-6d %-12.12s %-6s %-8s %-5s %-39s" % ( 174 pid, event.task, protocol, event.backlog, 175 event.lport, address, 176 )) 177 178 return print_event 179 180if __name__ == "__main__": 181 # Parse arguments 182 args = parser.parse_args() 183 184 pid_filter = "" 185 netns_filter = "" 186 187 if args.pid: 188 pid_filter = "if (evt.pid_tgid != %d) return 0;" % args.pid 189 if args.netns: 190 netns_filter = "if (evt.netns != %d) return 0;" % args.netns 191 192 bpf_text = bpf_text.replace("##FILTER_PID##", pid_filter) 193 bpf_text = bpf_text.replace("##FILTER_NETNS##", netns_filter) 194 195 if args.ebpf: 196 print(bpf_text) 197 exit() 198 199 # Initialize BPF 200 b = BPF(text=bpf_text) 201 b["listen_evt"].open_perf_buffer(event_printer(args.show_netns)) 202 203 # Print headers 204 if args.show_netns: 205 print("%-6s %-12s %-12s %-6s %-8s %-5s %-39s" % 206 ("PID", "COMM", "NETNS", "PROTO", "BACKLOG", "PORT", "ADDR")) 207 else: 208 print("%-6s %-12s %-6s %-8s %-5s %-39s" % 209 ("PID", "COMM", "PROTO", "BACKLOG", "PORT", "ADDR")) 210 211 # Read events 212 while 1: 213 b.perf_buffer_poll() 214