blob: c3563d29bd8ddedc9affb0b1aaaa771734374be3 [file] [log] [blame]
Jean-Tiare Le Bigota1ac2f92016-03-04 21:45:32 +01001#!/usr/bin/env python
2#
Sasha Goldshteinf41ae862016-10-19 01:14:30 +03003# solisten Trace TCP listen events
4# For Linux, uses BCC, eBPF. Embedded C.
Jean-Tiare Le Bigota1ac2f92016-03-04 21:45:32 +01005#
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 usefull 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
Paul Chaignon83320682017-03-25 13:40:46 +010021from socket import inet_ntop, AF_INET, AF_INET6, SOCK_STREAM, SOCK_DGRAM
22from struct import pack
Jean-Tiare Le Bigota1ac2f92016-03-04 21:45:32 +010023import 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
Sasha Goldshteinf41ae862016-10-19 01:14:30 +030031 ./solisten.py --netns 4242 # " for the specified network namespace ID only
32 ./solisten.py --show-netns # Show network ns ID (useful for containers)
Jean-Tiare Le Bigota1ac2f92016-03-04 21:45:32 +010033"""
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")
45
46
47# BPF Program
Sasha Goldshteinf41ae862016-10-19 01:14:30 +030048bpf_text = """
Jean-Tiare Le Bigota1ac2f92016-03-04 21:45:32 +010049#include <net/sock.h>
50#include <net/inet_sock.h>
51#include <net/net_namespace.h>
52#include <bcc/proto.h>
53
Jean-Tiare Le Bigota1ac2f92016-03-04 21:45:32 +010054// Common structure for UDP/TCP IPv4/IPv6
55struct listen_evt_t {
56 u64 ts_us;
57 u64 pid_tgid;
58 u64 backlog;
59 u64 netns;
60 u64 proto; // familiy << 16 | type
61 u64 lport; // use only 16 bits
62 u64 laddr[2]; // IPv4: store in laddr[0]
63 char task[TASK_COMM_LEN];
64};
65BPF_PERF_OUTPUT(listen_evt);
66
67// Send an event for each IPv4 listen with PID, bound address and port
68int kprobe__inet_listen(struct pt_regs *ctx, struct socket *sock, int backlog)
69{
70 // cast types. Intermediate cast not needed, kept for readability
71 struct sock *sk = sock->sk;
72 struct inet_sock *inet = inet_sk(sk);
73
74 // Built event for userland
75 struct listen_evt_t evt = {
76 .ts_us = bpf_ktime_get_ns() / 1000,
77 .backlog = backlog,
78 };
79
Sasha Goldshteinf41ae862016-10-19 01:14:30 +030080 // Get process comm. Needs LLVM >= 3.7.1
81 // see https://github.com/iovisor/bcc/issues/393
Jean-Tiare Le Bigota1ac2f92016-03-04 21:45:32 +010082 bpf_get_current_comm(evt.task, TASK_COMM_LEN);
83
84 // Get socket IP family
85 u16 family = sk->__sk_common.skc_family;
86 evt.proto = family << 16 | SOCK_STREAM;
87
88 // Get PID
89 evt.pid_tgid = bpf_get_current_pid_tgid();
90
91 ##FILTER_PID##
92
93 // Get port
94 bpf_probe_read(&evt.lport, sizeof(u16), &(inet->inet_sport));
95 evt.lport = ntohs(evt.lport);
96
97 // Get network namespace id, if kernel supports it
98#ifdef CONFIG_NET_NS
99 evt.netns = sk->__sk_common.skc_net.net->ns.inum;
100#else
101 evt.netns = 0;
102#endif
103
104 ##FILTER_NETNS##
105
106 // Get IP
107 if (family == AF_INET) {
108 bpf_probe_read(evt.laddr, sizeof(u32), &(inet->inet_rcv_saddr));
Jean-Tiare Le Bigotbf330142016-03-28 15:14:12 +0000109 evt.laddr[0] = be32_to_cpu(evt.laddr[0]);
Jean-Tiare Le Bigota1ac2f92016-03-04 21:45:32 +0100110 } else if (family == AF_INET6) {
Sasha Goldshteinf41ae862016-10-19 01:14:30 +0300111 bpf_probe_read(evt.laddr, sizeof(evt.laddr),
112 sk->__sk_common.skc_v6_rcv_saddr.in6_u.u6_addr32);
Jean-Tiare Le Bigota1ac2f92016-03-04 21:45:32 +0100113 }
114
115 // Send event to userland
116 listen_evt.perf_submit(ctx, &evt, sizeof(evt));
117
118 return 0;
119};
120"""
121
122# event data
123TASK_COMM_LEN = 16 # linux/sched.h
124class ListenEvt(ct.Structure):
125 _fields_ = [
126 ("ts_us", ct.c_ulonglong),
127 ("pid_tgid", ct.c_ulonglong),
128 ("backlog", ct.c_ulonglong),
129 ("netns", ct.c_ulonglong),
130 ("proto", ct.c_ulonglong),
131 ("lport", ct.c_ulonglong),
132 ("laddr", ct.c_ulonglong * 2),
133 ("task", ct.c_char * TASK_COMM_LEN)
134 ]
135
136 # TODO: properties to unpack protocol / ip / pid / tgid ...
137
138# Format output
139def event_printer(show_netns):
140 def print_event(cpu, data, size):
141 # Decode event
142 event = ct.cast(data, ct.POINTER(ListenEvt)).contents
143
144 pid = event.pid_tgid & 0xffffffff
145 proto_family = event.proto & 0xff
146 proto_type = event.proto >> 16 & 0xff
147
Paul Chaignon83320682017-03-25 13:40:46 +0100148 if proto_family == SOCK_STREAM:
Jean-Tiare Le Bigota1ac2f92016-03-04 21:45:32 +0100149 protocol = "TCP"
Paul Chaignon83320682017-03-25 13:40:46 +0100150 elif proto_family == SOCK_DGRAM:
Jean-Tiare Le Bigota1ac2f92016-03-04 21:45:32 +0100151 protocol = "UDP"
152 else:
153 protocol = "UNK"
154
155 address = ""
Paul Chaignon83320682017-03-25 13:40:46 +0100156 if proto_type == AF_INET:
Jean-Tiare Le Bigota1ac2f92016-03-04 21:45:32 +0100157 protocol += "v4"
Paul Chaignon83320682017-03-25 13:40:46 +0100158 address = inet_ntop(AF_INET, pack("I", event.laddr[0]))
159 elif proto_type == AF_INET6:
160 address = inet_ntop(AF_INET6, event.laddr)
Jean-Tiare Le Bigota1ac2f92016-03-04 21:45:32 +0100161 protocol += "v6"
162
163 # Display
164 if show_netns:
165 print("%-6d %-12.12s %-12s %-6s %-8s %-5s %-39s" % (
166 pid, event.task, event.netns, protocol, event.backlog,
167 event.lport, address,
168 ))
169 else:
170 print("%-6d %-12.12s %-6s %-8s %-5s %-39s" % (
171 pid, event.task, protocol, event.backlog,
172 event.lport, address,
173 ))
174
175 return print_event
176
177if __name__ == "__main__":
178 # Parse arguments
179 args = parser.parse_args()
180
181 pid_filter = ""
182 netns_filter = ""
183
184 if args.pid:
185 pid_filter = "if (evt.pid_tgid != %d) return 0;" % args.pid
186 if args.netns:
187 netns_filter = "if (evt.netns != %d) return 0;" % args.netns
188
189 bpf_text = bpf_text.replace("##FILTER_PID##", pid_filter)
190 bpf_text = bpf_text.replace("##FILTER_NETNS##", netns_filter)
191
192 # Initialize BPF
193 b = BPF(text=bpf_text)
194 b["listen_evt"].open_perf_buffer(event_printer(args.show_netns))
195
196 # Print headers
197 if args.show_netns:
Sasha Goldshteinf41ae862016-10-19 01:14:30 +0300198 print("%-6s %-12s %-12s %-6s %-8s %-5s %-39s" %
199 ("PID", "COMM", "NETNS", "PROTO", "BACKLOG", "PORT", "ADDR"))
Jean-Tiare Le Bigota1ac2f92016-03-04 21:45:32 +0100200 else:
Sasha Goldshteinf41ae862016-10-19 01:14:30 +0300201 print("%-6s %-12s %-6s %-8s %-5s %-39s" %
202 ("PID", "COMM", "PROTO", "BACKLOG", "PORT", "ADDR"))
Jean-Tiare Le Bigota1ac2f92016-03-04 21:45:32 +0100203
204 # Read events
205 while 1:
206 b.kprobe_poll()