1#!/usr/bin/env python 2# @lint-avoid-python-3-compatibility-imports 3# 4# sofdsnoop traces file descriptors passed via socket 5# For Linux, uses BCC, eBPF. Embedded C. 6# 7# USAGE: sofdsnoop 8# 9# Copyright (c) 2018 Jiri Olsa. 10# Licensed under the Apache License, Version 2.0 (the "License") 11# 12# 30-Jul-2018 Jiri Olsa Created this. 13 14from __future__ import print_function 15from bcc import ArgString, BPF 16import os 17import argparse 18from datetime import datetime, timedelta 19 20# arguments 21examples = """examples: 22 ./sofdsnoop # trace passed file descriptors 23 ./sofdsnoop -T # include timestamps 24 ./sofdsnoop -p 181 # only trace PID 181 25 ./sofdsnoop -t 123 # only trace TID 123 26 ./sofdsnoop -d 10 # trace for 10 seconds only 27 ./sofdsnoop -n main # only print process names containing "main" 28 29""" 30parser = argparse.ArgumentParser( 31 description="Trace file descriptors passed via socket", 32 formatter_class=argparse.RawDescriptionHelpFormatter, 33 epilog=examples) 34parser.add_argument("-T", "--timestamp", action="store_true", 35 help="include timestamp on output") 36parser.add_argument("-p", "--pid", 37 help="trace this PID only") 38parser.add_argument("-t", "--tid", 39 help="trace this TID only") 40parser.add_argument("-n", "--name", 41 type=ArgString, 42 help="only print process names containing this name") 43parser.add_argument("-d", "--duration", 44 help="total duration of trace in seconds") 45args = parser.parse_args() 46debug = 0 47 48ACTION_SEND=0 49ACTION_RECV=1 50MAX_FD=10 51 52if args.duration: 53 args.duration = timedelta(seconds=int(args.duration)) 54 55# define BPF program 56bpf_text = """ 57#include <uapi/linux/ptrace.h> 58#include <uapi/linux/limits.h> 59#include <linux/sched.h> 60#include <linux/socket.h> 61#include <net/sock.h> 62 63#define MAX_FD 10 64#define ACTION_SEND 0 65#define ACTION_RECV 1 66 67struct val_t { 68 u64 id; 69 u64 ts; 70 int action; 71 int sock_fd; 72 int fd_cnt; 73 int fd[MAX_FD]; 74 char comm[TASK_COMM_LEN]; 75}; 76 77BPF_HASH(detach_ptr, u64, struct cmsghdr *); 78BPF_HASH(sock_fd, u64, int); 79BPF_PERF_OUTPUT(events); 80 81static void set_fd(int fd) 82{ 83 u64 id = bpf_get_current_pid_tgid(); 84 85 sock_fd.update(&id, &fd); 86} 87 88static int get_fd(void) 89{ 90 u64 id = bpf_get_current_pid_tgid(); 91 int *fd; 92 93 fd = sock_fd.lookup(&id); 94 return fd ? *fd : -1; 95} 96 97static void put_fd(void) 98{ 99 u64 id = bpf_get_current_pid_tgid(); 100 101 sock_fd.delete(&id); 102} 103 104static int sent_1(struct pt_regs *ctx, struct val_t *val, int num, void *data) 105{ 106 val->fd_cnt = min(num, MAX_FD); 107 108 if (bpf_probe_read_kernel(&val->fd[0], MAX_FD * sizeof(int), data)) 109 return -1; 110 111 events.perf_submit(ctx, val, sizeof(*val)); 112 return 0; 113} 114 115#define SEND_1 \ 116 if (sent_1(ctx, &val, num, (void *) data)) \ 117 return 0; \ 118 \ 119 num -= MAX_FD; \ 120 if (num < 0) \ 121 return 0; \ 122 \ 123 data += MAX_FD; 124 125#define SEND_2 SEND_1 SEND_1 126#define SEND_4 SEND_2 SEND_2 127#define SEND_8 SEND_4 SEND_4 128#define SEND_260 SEND_8 SEND_8 SEND_8 SEND_2 129 130static int send(struct pt_regs *ctx, struct cmsghdr *cmsg, int action) 131{ 132 struct val_t val = { 0 }; 133 int *data, num, fd; 134 u64 tsp = bpf_ktime_get_ns(); 135 136 data = (void *) ((char *) cmsg + sizeof(struct cmsghdr)); 137 num = (cmsg->cmsg_len - sizeof(struct cmsghdr)) / sizeof(int); 138 139 val.id = bpf_get_current_pid_tgid(); 140 val.action = action; 141 val.sock_fd = get_fd(); 142 val.ts = tsp / 1000; 143 144 if (bpf_get_current_comm(&val.comm, sizeof(val.comm)) != 0) 145 return 0; 146 147 SEND_260 148 return 0; 149} 150 151static bool allow_pid(u64 id) 152{ 153 u32 pid = id >> 32; // PID is higher part 154 u32 tid = id; // Cast and get the lower part 155 156 FILTER 157 158 return 1; 159} 160 161int trace_scm_send_entry(struct pt_regs *ctx, struct socket *sock, struct msghdr *hdr) 162{ 163 struct cmsghdr *cmsg = NULL; 164 165 if (!allow_pid(bpf_get_current_pid_tgid())) 166 return 0; 167 168 if (hdr->msg_controllen >= sizeof(struct cmsghdr)) 169 cmsg = hdr->msg_control; 170 171 if (!cmsg || (cmsg->cmsg_type != SCM_RIGHTS)) 172 return 0; 173 174 return send(ctx, cmsg, ACTION_SEND); 175}; 176 177int trace_scm_detach_fds_entry(struct pt_regs *ctx, struct msghdr *hdr) 178{ 179 struct cmsghdr *cmsg = NULL; 180 u64 id = bpf_get_current_pid_tgid(); 181 182 if (!allow_pid(id)) 183 return 0; 184 185 if (hdr->msg_controllen >= sizeof(struct cmsghdr)) 186 cmsg = hdr->msg_control; 187 188 if (!cmsg) 189 return 0; 190 191 detach_ptr.update(&id, &cmsg); 192 return 0; 193}; 194 195int trace_scm_detach_fds_return(struct pt_regs *ctx) 196{ 197 struct cmsghdr **cmsgp; 198 u64 id = bpf_get_current_pid_tgid(); 199 200 if (!allow_pid(id)) 201 return 0; 202 203 cmsgp = detach_ptr.lookup(&id); 204 205 if (!cmsgp) 206 return 0; 207 208 return send(ctx, *cmsgp, ACTION_RECV); 209} 210 211int syscall__sendmsg(struct pt_regs *ctx, u64 fd, u64 msg, u64 flags) 212{ 213 struct pt_regs p; 214 215 if (!allow_pid(bpf_get_current_pid_tgid())) 216 return 0; 217 218 set_fd(fd); 219 return 0; 220} 221 222int trace_sendmsg_return(struct pt_regs *ctx) 223{ 224 if (!allow_pid(bpf_get_current_pid_tgid())) 225 return 0; 226 227 put_fd(); 228 return 0; 229} 230 231int syscall__recvmsg(struct pt_regs *ctx, u64 fd, u64 msg, u64 flags) 232{ 233 struct pt_regs p; 234 235 if (!allow_pid(bpf_get_current_pid_tgid())) 236 return 0; 237 238 fd = fd; 239 240 set_fd(fd); 241 return 0; 242} 243 244int trace_recvmsg_return(struct pt_regs *ctx) 245{ 246 if (!allow_pid(bpf_get_current_pid_tgid())) 247 return 0; 248 249 put_fd(); 250 return 0; 251} 252 253""" 254 255if args.tid: # TID trumps PID 256 bpf_text = bpf_text.replace('FILTER', 257 'if (tid != %s) { return 0; }' % args.tid) 258elif args.pid: 259 bpf_text = bpf_text.replace('FILTER', 260 'if (pid != %s) { return 0; }' % args.pid) 261else: 262 bpf_text = bpf_text.replace('FILTER', '') 263 264# initialize BPF 265b = BPF(text=bpf_text) 266 267syscall_fnname = b.get_syscall_fnname("sendmsg") 268if BPF.ksymname(syscall_fnname) != -1: 269 b.attach_kprobe(event=syscall_fnname, fn_name="syscall__sendmsg") 270 b.attach_kretprobe(event=syscall_fnname, fn_name="trace_sendmsg_return") 271 272syscall_fnname = b.get_syscall_fnname("recvmsg") 273if BPF.ksymname(syscall_fnname) != -1: 274 b.attach_kprobe(event=syscall_fnname, fn_name="syscall__recvmsg") 275 b.attach_kretprobe(event=syscall_fnname, fn_name="trace_recvmsg_return") 276 277b.attach_kprobe(event="__scm_send", fn_name="trace_scm_send_entry") 278b.attach_kprobe(event="scm_detach_fds", fn_name="trace_scm_detach_fds_entry") 279b.attach_kretprobe(event="scm_detach_fds", fn_name="trace_scm_detach_fds_return") 280 281initial_ts = 0 282 283# header 284if args.timestamp: 285 print("%-14s" % ("TIME(s)"), end="") 286print("%-6s %-6s %-16s %-25s %-5s %s" % 287 ("ACTION", "TID", "COMM", "SOCKET", "FD", "NAME")) 288 289def get_file(pid, fd): 290 proc = "/proc/%d/fd/%d" % (pid, fd) 291 try: 292 return os.readlink(proc) 293 except OSError as err: 294 return "N/A" 295 296# process event 297def print_event(cpu, data, size): 298 event = b["events"].event(data) 299 tid = event.id & 0xffffffff; 300 301 cnt = min(MAX_FD, event.fd_cnt); 302 303 if args.name and bytes(args.name) not in event.comm: 304 return 305 306 for i in range(0, cnt): 307 global initial_ts 308 309 if not initial_ts: 310 initial_ts = event.ts 311 312 if args.timestamp: 313 delta = event.ts - initial_ts 314 print("%-14.9f" % (float(delta) / 1000000), end="") 315 316 print("%-6s %-6d %-16s " % 317 ("SEND" if event.action == ACTION_SEND else "RECV", 318 tid, event.comm.decode()), end = '') 319 320 sock = "%d:%s" % (event.sock_fd, get_file(tid, event.sock_fd)) 321 print("%-25s " % sock, end = '') 322 323 fd = event.fd[i] 324 fd_file = get_file(tid, fd) if event.action == ACTION_SEND else "" 325 print("%-5d %s" % (fd, fd_file)) 326 327# loop with callback to print_event 328b["events"].open_perf_buffer(print_event, page_cnt=64) 329start_time = datetime.now() 330while not args.duration or datetime.now() - start_time < args.duration: 331 try: 332 b.perf_buffer_poll(timeout=1000) 333 except KeyboardInterrupt: 334 exit() 335