1#!/usr/bin/env python 2# @lint-avoid-python-3-compatibility-imports 3# 4# tcpconnect Trace TCP connect()s. 5# For Linux, uses BCC, eBPF. Embedded C. 6# 7# USAGE: tcpconnect [-h] [-t] [-p PID] [-P PORT [PORT ...]] 8# 9# All connection attempts are traced, even if they ultimately fail. 10# 11# This uses dynamic tracing of kernel functions, and will need to be updated 12# to match kernel changes. 13# 14# Copyright (c) 2015 Brendan Gregg. 15# Licensed under the Apache License, Version 2.0 (the "License") 16# 17# 25-Sep-2015 Brendan Gregg Created this. 18# 14-Feb-2016 " " Switch to bpf_perf_output. 19# 09-Jan-2019 Takuma Kume Support filtering by UID 20 21from __future__ import print_function 22from bcc import BPF 23from bcc.utils import printb 24import argparse 25from socket import inet_ntop, ntohs, AF_INET, AF_INET6 26from struct import pack 27import ctypes as ct 28 29# arguments 30examples = """examples: 31 ./tcpconnect # trace all TCP connect()s 32 ./tcpconnect -t # include timestamps 33 ./tcpconnect -p 181 # only trace PID 181 34 ./tcpconnect -P 80 # only trace port 80 35 ./tcpconnect -P 80,81 # only trace port 80 and 81 36 ./tcpconnect -U # include UID 37 ./tcpconnect -u 1000 # only trace UID 1000 38""" 39parser = argparse.ArgumentParser( 40 description="Trace TCP connects", 41 formatter_class=argparse.RawDescriptionHelpFormatter, 42 epilog=examples) 43parser.add_argument("-t", "--timestamp", action="store_true", 44 help="include timestamp on output") 45parser.add_argument("-p", "--pid", 46 help="trace this PID only") 47parser.add_argument("-P", "--port", 48 help="comma-separated list of destination ports to trace.") 49parser.add_argument("-U", "--print-uid", action="store_true", 50 help="include UID on output") 51parser.add_argument("-u", "--uid", 52 help="trace this UID only") 53parser.add_argument("--ebpf", action="store_true", 54 help=argparse.SUPPRESS) 55args = parser.parse_args() 56debug = 0 57 58# define BPF program 59bpf_text = """ 60#include <uapi/linux/ptrace.h> 61#include <net/sock.h> 62#include <bcc/proto.h> 63 64BPF_HASH(currsock, u32, struct sock *); 65 66// separate data structs for ipv4 and ipv6 67struct ipv4_data_t { 68 u64 ts_us; 69 u32 pid; 70 u32 uid; 71 u32 saddr; 72 u32 daddr; 73 u64 ip; 74 u16 dport; 75 char task[TASK_COMM_LEN]; 76}; 77BPF_PERF_OUTPUT(ipv4_events); 78 79struct ipv6_data_t { 80 u64 ts_us; 81 u32 pid; 82 u32 uid; 83 unsigned __int128 saddr; 84 unsigned __int128 daddr; 85 u64 ip; 86 u16 dport; 87 char task[TASK_COMM_LEN]; 88}; 89BPF_PERF_OUTPUT(ipv6_events); 90 91int trace_connect_entry(struct pt_regs *ctx, struct sock *sk) 92{ 93 u32 pid = bpf_get_current_pid_tgid(); 94 FILTER_PID 95 96 u32 uid = bpf_get_current_uid_gid(); 97 FILTER_UID 98 99 // stash the sock ptr for lookup on return 100 currsock.update(&pid, &sk); 101 102 return 0; 103}; 104 105static int trace_connect_return(struct pt_regs *ctx, short ipver) 106{ 107 int ret = PT_REGS_RC(ctx); 108 u32 pid = bpf_get_current_pid_tgid(); 109 110 struct sock **skpp; 111 skpp = currsock.lookup(&pid); 112 if (skpp == 0) { 113 return 0; // missed entry 114 } 115 116 if (ret != 0) { 117 // failed to send SYNC packet, may not have populated 118 // socket __sk_common.{skc_rcv_saddr, ...} 119 currsock.delete(&pid); 120 return 0; 121 } 122 123 // pull in details 124 struct sock *skp = *skpp; 125 u16 dport = skp->__sk_common.skc_dport; 126 127 FILTER_PORT 128 129 if (ipver == 4) { 130 struct ipv4_data_t data4 = {.pid = pid, .ip = ipver}; 131 data4.uid = bpf_get_current_uid_gid(); 132 data4.ts_us = bpf_ktime_get_ns() / 1000; 133 data4.saddr = skp->__sk_common.skc_rcv_saddr; 134 data4.daddr = skp->__sk_common.skc_daddr; 135 data4.dport = ntohs(dport); 136 bpf_get_current_comm(&data4.task, sizeof(data4.task)); 137 ipv4_events.perf_submit(ctx, &data4, sizeof(data4)); 138 139 } else /* 6 */ { 140 struct ipv6_data_t data6 = {.pid = pid, .ip = ipver}; 141 data6.uid = bpf_get_current_uid_gid(); 142 data6.ts_us = bpf_ktime_get_ns() / 1000; 143 bpf_probe_read(&data6.saddr, sizeof(data6.saddr), 144 skp->__sk_common.skc_v6_rcv_saddr.in6_u.u6_addr32); 145 bpf_probe_read(&data6.daddr, sizeof(data6.daddr), 146 skp->__sk_common.skc_v6_daddr.in6_u.u6_addr32); 147 data6.dport = ntohs(dport); 148 bpf_get_current_comm(&data6.task, sizeof(data6.task)); 149 ipv6_events.perf_submit(ctx, &data6, sizeof(data6)); 150 } 151 152 currsock.delete(&pid); 153 154 return 0; 155} 156 157int trace_connect_v4_return(struct pt_regs *ctx) 158{ 159 return trace_connect_return(ctx, 4); 160} 161 162int trace_connect_v6_return(struct pt_regs *ctx) 163{ 164 return trace_connect_return(ctx, 6); 165} 166""" 167 168# code substitutions 169if args.pid: 170 bpf_text = bpf_text.replace('FILTER_PID', 171 'if (pid != %s) { return 0; }' % args.pid) 172if args.port: 173 dports = [int(dport) for dport in args.port.split(',')] 174 dports_if = ' && '.join(['dport != %d' % ntohs(dport) for dport in dports]) 175 bpf_text = bpf_text.replace('FILTER_PORT', 176 'if (%s) { currsock.delete(&pid); return 0; }' % dports_if) 177if args.uid: 178 bpf_text = bpf_text.replace('FILTER_UID', 179 'if (uid != %s) { return 0; }' % args.uid) 180 181bpf_text = bpf_text.replace('FILTER_PID', '') 182bpf_text = bpf_text.replace('FILTER_PORT', '') 183bpf_text = bpf_text.replace('FILTER_UID', '') 184 185if debug or args.ebpf: 186 print(bpf_text) 187 if args.ebpf: 188 exit() 189 190# event data 191TASK_COMM_LEN = 16 # linux/sched.h 192 193class Data_ipv4(ct.Structure): 194 _fields_ = [ 195 ("ts_us", ct.c_ulonglong), 196 ("pid", ct.c_uint), 197 ("uid", ct.c_uint), 198 ("saddr", ct.c_uint), 199 ("daddr", ct.c_uint), 200 ("ip", ct.c_ulonglong), 201 ("dport", ct.c_ushort), 202 ("task", ct.c_char * TASK_COMM_LEN) 203 ] 204 205class Data_ipv6(ct.Structure): 206 _fields_ = [ 207 ("ts_us", ct.c_ulonglong), 208 ("pid", ct.c_uint), 209 ("uid", ct.c_uint), 210 ("saddr", (ct.c_ulonglong * 2)), 211 ("daddr", (ct.c_ulonglong * 2)), 212 ("ip", ct.c_ulonglong), 213 ("dport", ct.c_ushort), 214 ("task", ct.c_char * TASK_COMM_LEN) 215 ] 216 217# process event 218def print_ipv4_event(cpu, data, size): 219 event = ct.cast(data, ct.POINTER(Data_ipv4)).contents 220 global start_ts 221 if args.timestamp: 222 if start_ts == 0: 223 start_ts = event.ts_us 224 print("%-9.3f" % ((float(event.ts_us) - start_ts) / 1000000), end="") 225 if args.print_uid: 226 print("%-6d" % event.uid, end="") 227 printb(b"%-6d %-12.12s %-2d %-16s %-16s %-4d" % (event.pid, 228 event.task, event.ip, 229 inet_ntop(AF_INET, pack("I", event.saddr)).encode(), 230 inet_ntop(AF_INET, pack("I", event.daddr)).encode(), event.dport)) 231 232def print_ipv6_event(cpu, data, size): 233 event = ct.cast(data, ct.POINTER(Data_ipv6)).contents 234 global start_ts 235 if args.timestamp: 236 if start_ts == 0: 237 start_ts = event.ts_us 238 print("%-9.3f" % ((float(event.ts_us) - start_ts) / 1000000), end="") 239 if args.print_uid: 240 print("%-6d" % event.uid, end="") 241 printb(b"%-6d %-12.12s %-2d %-16s %-16s %-4d" % (event.pid, 242 event.task, event.ip, 243 inet_ntop(AF_INET6, event.saddr).encode(), inet_ntop(AF_INET6, event.daddr).encode(), 244 event.dport)) 245 246# initialize BPF 247b = BPF(text=bpf_text) 248b.attach_kprobe(event="tcp_v4_connect", fn_name="trace_connect_entry") 249b.attach_kprobe(event="tcp_v6_connect", fn_name="trace_connect_entry") 250b.attach_kretprobe(event="tcp_v4_connect", fn_name="trace_connect_v4_return") 251b.attach_kretprobe(event="tcp_v6_connect", fn_name="trace_connect_v6_return") 252 253# header 254if args.timestamp: 255 print("%-9s" % ("TIME(s)"), end="") 256if args.print_uid: 257 print("%-6s" % ("UID"), end="") 258print("%-6s %-12s %-2s %-16s %-16s %-4s" % ("PID", "COMM", "IP", "SADDR", 259 "DADDR", "DPORT")) 260 261start_ts = 0 262 263# read events 264b["ipv4_events"].open_perf_buffer(print_ipv4_event) 265b["ipv6_events"].open_perf_buffer(print_ipv6_event) 266while 1: 267 try: 268 b.perf_buffer_poll() 269 except KeyboardInterrupt: 270 exit() 271