#!/usr/bin/python # @lint-avoid-python-3-compatibility-imports # # sofdsnoop traces file descriptors passed via socket # For Linux, uses BCC, eBPF. Embedded C. # # USAGE: sofdsnoop # # Copyright (c) 2018 Jiri Olsa. # Licensed under the Apache License, Version 2.0 (the "License") # # 30-Jul-2018 Jiri Olsa Created this. from __future__ import print_function from bcc import ArgString, BPF import os import argparse from datetime import datetime, timedelta # arguments examples = """examples: ./sofdsnoop # trace passed file descriptors ./sofdsnoop -T # include timestamps ./sofdsnoop -p 181 # only trace PID 181 ./sofdsnoop -t 123 # only trace TID 123 ./sofdsnoop -d 10 # trace for 10 seconds only ./sofdsnoop -n main # only print process names containing "main" """ parser = argparse.ArgumentParser( description="Trace file descriptors passed via socket", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=examples) parser.add_argument("-T", "--timestamp", action="store_true", help="include timestamp on output") parser.add_argument("-p", "--pid", help="trace this PID only") parser.add_argument("-t", "--tid", help="trace this TID only") parser.add_argument("-n", "--name", type=ArgString, help="only print process names containing this name") parser.add_argument("-d", "--duration", help="total duration of trace in seconds") args = parser.parse_args() debug = 0 ACTION_SEND=0 ACTION_RECV=1 MAX_FD=10 if args.duration: args.duration = timedelta(seconds=int(args.duration)) # define BPF program bpf_text = """ #include #include #include #include #include #define MAX_FD 10 #define ACTION_SEND 0 #define ACTION_RECV 1 struct val_t { u64 id; u64 ts; int action; int sock_fd; int fd_cnt; int fd[MAX_FD]; char comm[TASK_COMM_LEN]; }; BPF_HASH(detach_ptr, u64, struct cmsghdr *); BPF_HASH(sock_fd, u64, int); BPF_PERF_OUTPUT(events); static void set_fd(int fd) { u64 id = bpf_get_current_pid_tgid(); sock_fd.update(&id, &fd); } static int get_fd(void) { u64 id = bpf_get_current_pid_tgid(); int *fd; fd = sock_fd.lookup(&id); return fd ? *fd : -1; } static void put_fd(void) { u64 id = bpf_get_current_pid_tgid(); sock_fd.delete(&id); } static int sent_1(struct pt_regs *ctx, struct val_t *val, int num, void *data) { val->fd_cnt = min(num, MAX_FD); if (bpf_probe_read_kernel(&val->fd[0], MAX_FD * sizeof(int), data)) return -1; events.perf_submit(ctx, val, sizeof(*val)); return 0; } #define SEND_1 \ if (sent_1(ctx, &val, num, (void *) data)) \ return 0; \ \ num -= MAX_FD; \ if (num < 0) \ return 0; \ \ data += MAX_FD; #define SEND_2 SEND_1 SEND_1 #define SEND_4 SEND_2 SEND_2 #define SEND_8 SEND_4 SEND_4 #define SEND_260 SEND_8 SEND_8 SEND_8 SEND_2 static int send(struct pt_regs *ctx, struct cmsghdr *cmsg, int action) { struct val_t val = { 0 }; int *data, num, fd; u64 tsp = bpf_ktime_get_ns(); data = (void *) ((char *) cmsg + sizeof(struct cmsghdr)); num = (cmsg->cmsg_len - sizeof(struct cmsghdr)) / sizeof(int); val.id = bpf_get_current_pid_tgid(); val.action = action; val.sock_fd = get_fd(); val.ts = tsp / 1000; if (bpf_get_current_comm(&val.comm, sizeof(val.comm)) != 0) return 0; SEND_260 return 0; } static bool allow_pid(u64 id) { u32 pid = id >> 32; // PID is higher part u32 tid = id; // Cast and get the lower part FILTER return 1; } int trace_scm_send_entry(struct pt_regs *ctx, struct socket *sock, struct msghdr *hdr) { struct cmsghdr *cmsg = NULL; if (!allow_pid(bpf_get_current_pid_tgid())) return 0; if (hdr->msg_controllen >= sizeof(struct cmsghdr)) cmsg = hdr->msg_control; if (!cmsg || (cmsg->cmsg_type != SCM_RIGHTS)) return 0; return send(ctx, cmsg, ACTION_SEND); }; int trace_scm_detach_fds_entry(struct pt_regs *ctx, struct msghdr *hdr) { struct cmsghdr *cmsg = NULL; u64 id = bpf_get_current_pid_tgid(); if (!allow_pid(id)) return 0; if (hdr->msg_controllen >= sizeof(struct cmsghdr)) cmsg = hdr->msg_control; if (!cmsg) return 0; detach_ptr.update(&id, &cmsg); return 0; }; int trace_scm_detach_fds_return(struct pt_regs *ctx) { struct cmsghdr **cmsgp; u64 id = bpf_get_current_pid_tgid(); if (!allow_pid(id)) return 0; cmsgp = detach_ptr.lookup(&id); if (!cmsgp) return 0; return send(ctx, *cmsgp, ACTION_RECV); } int syscall__sendmsg(struct pt_regs *ctx, u64 fd, u64 msg, u64 flags) { struct pt_regs p; if (!allow_pid(bpf_get_current_pid_tgid())) return 0; set_fd(fd); return 0; } int trace_sendmsg_return(struct pt_regs *ctx) { if (!allow_pid(bpf_get_current_pid_tgid())) return 0; put_fd(); return 0; } int syscall__recvmsg(struct pt_regs *ctx, u64 fd, u64 msg, u64 flags) { struct pt_regs p; if (!allow_pid(bpf_get_current_pid_tgid())) return 0; fd = fd; set_fd(fd); return 0; } int trace_recvmsg_return(struct pt_regs *ctx) { if (!allow_pid(bpf_get_current_pid_tgid())) return 0; put_fd(); return 0; } """ if args.tid: # TID trumps PID bpf_text = bpf_text.replace('FILTER', 'if (tid != %s) { return 0; }' % args.tid) elif args.pid: bpf_text = bpf_text.replace('FILTER', 'if (pid != %s) { return 0; }' % args.pid) else: bpf_text = bpf_text.replace('FILTER', '') # initialize BPF b = BPF(text=bpf_text) syscall_fnname = b.get_syscall_fnname("sendmsg") if BPF.ksymname(syscall_fnname) != -1: b.attach_kprobe(event=syscall_fnname, fn_name="syscall__sendmsg") b.attach_kretprobe(event=syscall_fnname, fn_name="trace_sendmsg_return") syscall_fnname = b.get_syscall_fnname("recvmsg") if BPF.ksymname(syscall_fnname) != -1: b.attach_kprobe(event=syscall_fnname, fn_name="syscall__recvmsg") b.attach_kretprobe(event=syscall_fnname, fn_name="trace_recvmsg_return") b.attach_kprobe(event="__scm_send", fn_name="trace_scm_send_entry") b.attach_kprobe(event="scm_detach_fds", fn_name="trace_scm_detach_fds_entry") b.attach_kretprobe(event="scm_detach_fds", fn_name="trace_scm_detach_fds_return") initial_ts = 0 # header if args.timestamp: print("%-14s" % ("TIME(s)"), end="") print("%-6s %-6s %-16s %-25s %-5s %s" % ("ACTION", "TID", "COMM", "SOCKET", "FD", "NAME")) def get_file(pid, fd): proc = "/proc/%d/fd/%d" % (pid, fd) try: return os.readlink(proc) except OSError as err: return "N/A" # process event def print_event(cpu, data, size): event = b["events"].event(data) tid = event.id & 0xffffffff; cnt = min(MAX_FD, event.fd_cnt); if args.name and bytes(args.name) not in event.comm: return for i in range(0, cnt): global initial_ts if not initial_ts: initial_ts = event.ts if args.timestamp: delta = event.ts - initial_ts print("%-14.9f" % (float(delta) / 1000000), end="") print("%-6s %-6d %-16s " % ("SEND" if event.action == ACTION_SEND else "RECV", tid, event.comm.decode()), end = '') sock = "%d:%s" % (event.sock_fd, get_file(tid, event.sock_fd)) print("%-25s " % sock, end = '') fd = event.fd[i] fd_file = get_file(tid, fd) if event.action == ACTION_SEND else "" print("%-5d %s" % (fd, fd_file)) # loop with callback to print_event b["events"].open_perf_buffer(print_event, page_cnt=64) start_time = datetime.now() while not args.duration or datetime.now() - start_time < args.duration: try: b.perf_buffer_poll(timeout=1000) except KeyboardInterrupt: exit()