Permalink
Cannot retrieve contributors at this time
#!/usr/bin/python | |
# | |
# solisten Trace TCP listen events | |
# For Linux, uses BCC, eBPF. Embedded C. | |
# | |
# USAGE: solisten.py [-h] [-p PID] [--show-netns] | |
# | |
# This is provided as a basic example of TCP connection & socket tracing. | |
# It could be useful in scenarios where load balancers needs to be updated | |
# dynamically as application is fully initialized. | |
# | |
# All IPv4 and IPv6 listen attempts are traced, even if they ultimately fail | |
# or the the listening program is not willing to accept(). | |
# | |
# Copyright (c) 2016 Jean-Tiare Le Bigot. | |
# Licensed under the Apache License, Version 2.0 (the "License") | |
# | |
# 04-Mar-2016 Jean-Tiare Le Bigot Created this. | |
import os | |
from socket import inet_ntop, AF_INET, AF_INET6, SOCK_STREAM, SOCK_DGRAM | |
from struct import pack | |
import argparse | |
from bcc import BPF | |
from bcc.utils import printb | |
# Arguments | |
examples = """Examples: | |
./solisten.py # Stream socket listen | |
./solisten.py -p 1234 # Stream socket listen for specified PID only | |
./solisten.py --netns 4242 # " for the specified network namespace ID only | |
./solisten.py --show-netns # Show network ns ID (useful for containers) | |
""" | |
parser = argparse.ArgumentParser( | |
description="Stream sockets listen", | |
formatter_class=argparse.RawDescriptionHelpFormatter, | |
epilog=examples) | |
parser.add_argument("--show-netns", action="store_true", | |
help="show network namespace") | |
parser.add_argument("-p", "--pid", default=0, type=int, | |
help="trace this PID only") | |
parser.add_argument("-n", "--netns", default=0, type=int, | |
help="trace this Network Namespace only") | |
parser.add_argument("--ebpf", action="store_true", | |
help=argparse.SUPPRESS) | |
# BPF Program | |
bpf_text = """ | |
#include <net/net_namespace.h> | |
#include <bcc/proto.h> | |
#pragma clang diagnostic push | |
#pragma clang diagnostic ignored "-Wenum-conversion" | |
#include <net/inet_sock.h> | |
#pragma clang diagnostic pop | |
// Common structure for UDP/TCP IPv4/IPv6 | |
struct listen_evt_t { | |
u64 ts_us; | |
u64 pid_tgid; | |
u64 backlog; | |
u64 netns; | |
u64 proto; // familiy << 16 | type | |
u64 lport; // use only 16 bits | |
u64 laddr[2]; // IPv4: store in laddr[0] | |
char task[TASK_COMM_LEN]; | |
}; | |
BPF_PERF_OUTPUT(listen_evt); | |
// Send an event for each IPv4 listen with PID, bound address and port | |
int kprobe__inet_listen(struct pt_regs *ctx, struct socket *sock, int backlog) | |
{ | |
// cast types. Intermediate cast not needed, kept for readability | |
struct sock *sk = sock->sk; | |
struct inet_sock *inet = (struct inet_sock *)sk; | |
// Built event for userland | |
struct listen_evt_t evt = { | |
.ts_us = bpf_ktime_get_ns() / 1000, | |
.backlog = backlog, | |
}; | |
// Get process comm. Needs LLVM >= 3.7.1 | |
// see https://github.com/iovisor/bcc/issues/393 | |
bpf_get_current_comm(evt.task, TASK_COMM_LEN); | |
// Get socket IP family | |
u16 family = sk->__sk_common.skc_family; | |
evt.proto = family << 16 | SOCK_STREAM; | |
// Get PID | |
evt.pid_tgid = bpf_get_current_pid_tgid(); | |
##FILTER_PID## | |
// Get port | |
evt.lport = inet->inet_sport; | |
evt.lport = ntohs(evt.lport); | |
// Get network namespace id, if kernel supports it | |
#ifdef CONFIG_NET_NS | |
evt.netns = sk->__sk_common.skc_net.net->ns.inum; | |
#else | |
evt.netns = 0; | |
#endif | |
##FILTER_NETNS## | |
// Get IP | |
if (family == AF_INET) { | |
evt.laddr[0] = inet->inet_rcv_saddr; | |
} else if (family == AF_INET6) { | |
bpf_probe_read_kernel(evt.laddr, sizeof(evt.laddr), | |
sk->__sk_common.skc_v6_rcv_saddr.in6_u.u6_addr32); | |
} | |
// Send event to userland | |
listen_evt.perf_submit(ctx, &evt, sizeof(evt)); | |
return 0; | |
}; | |
""" | |
# TODO: properties to unpack protocol / ip / pid / tgid ... | |
# Format output | |
def event_printer(show_netns): | |
def print_event(cpu, data, size): | |
# Decode event | |
event = b["listen_evt"].event(data) | |
pid = event.pid_tgid & 0xffffffff | |
proto_family = event.proto & 0xff | |
proto_type = event.proto >> 16 & 0xff | |
if proto_family == SOCK_STREAM: | |
protocol = "TCP" | |
elif proto_family == SOCK_DGRAM: | |
protocol = "UDP" | |
else: | |
protocol = "UNK" | |
address = "" | |
if proto_type == AF_INET: | |
protocol += "v4" | |
address = inet_ntop(AF_INET, pack("I", event.laddr[0])) | |
elif proto_type == AF_INET6: | |
address = inet_ntop(AF_INET6, event.laddr) | |
protocol += "v6" | |
# Display | |
if show_netns: | |
printb(b"%-6d %-12.12s %-12d %-6s %-8d %-5d %-39s" % ( | |
pid, event.task, event.netns, protocol.encode(), event.backlog, | |
event.lport, address.encode(), | |
)) | |
else: | |
printb(b"%-6d %-12.12s %-6s %-8d %-5d %-39s" % ( | |
pid, event.task, protocol.encode(), event.backlog, | |
event.lport, address.encode(), | |
)) | |
return print_event | |
if __name__ == "__main__": | |
# Parse arguments | |
args = parser.parse_args() | |
pid_filter = "" | |
netns_filter = "" | |
if args.pid: | |
pid_filter = "if (evt.pid_tgid != %d) return 0;" % args.pid | |
if args.netns: | |
netns_filter = "if (evt.netns != %d) return 0;" % args.netns | |
bpf_text = bpf_text.replace("##FILTER_PID##", pid_filter) | |
bpf_text = bpf_text.replace("##FILTER_NETNS##", netns_filter) | |
if args.ebpf: | |
print(bpf_text) | |
exit() | |
# Initialize BPF | |
b = BPF(text=bpf_text) | |
b["listen_evt"].open_perf_buffer(event_printer(args.show_netns)) | |
# Print headers | |
if args.show_netns: | |
print("%-6s %-12s %-12s %-6s %-8s %-5s %-39s" % | |
("PID", "COMM", "NETNS", "PROTO", "BACKLOG", "PORT", "ADDR")) | |
else: | |
print("%-6s %-12s %-6s %-8s %-5s %-39s" % | |
("PID", "COMM", "PROTO", "BACKLOG", "PORT", "ADDR")) | |
# Read events | |
while 1: | |
try: | |
b.perf_buffer_poll() | |
except KeyboardInterrupt: | |
exit() |