blob: 6ff3d4d27e26d698cca5f10fd81813fda374ee8d [file] [log] [blame]
Jean-Tiare Le Bigota1ac2f92016-03-04 21:45:32 +01001#!/usr/bin/env python
2#
3# solisten Trace TCP listen events
4# For Linux, uses BCC, eBPF. Embedded C.
5#
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
21import socket
22import netaddr
23import 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
31 ./solisten.py --netns 4242 # Stream socket listen for specified network namespace ID only
32 ./solisten.py --show-netns # Show network namespace ID. Probably usefull if you run containers
33"""
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
48bpf_text = """
49#include <net/sock.h>
50#include <net/inet_sock.h>
51#include <net/net_namespace.h>
52#include <bcc/proto.h>
53
54// Endian conversion. We can't use kernel version here as it uses inline
55// assembly, neither libc version as we can't import it here. Adapted from both.
56#if defined(__LITTLE_ENDIAN)
57#define bcc_be32_to_cpu(x) ((u32)(__builtin_bswap32)((x)))
58#define bcc_be64_to_cpu(x) ((u64)(__builtin_bswap64)((x)))
59#elif defined(__BIG_ENDIAN)
60#define bcc_be32_to_cpu(x) (x)
61#define bcc_be64_to_cpu(x) (x)
62#else
63#error Host endianness not defined
64#endif
65
66// Common structure for UDP/TCP IPv4/IPv6
67struct listen_evt_t {
68 u64 ts_us;
69 u64 pid_tgid;
70 u64 backlog;
71 u64 netns;
72 u64 proto; // familiy << 16 | type
73 u64 lport; // use only 16 bits
74 u64 laddr[2]; // IPv4: store in laddr[0]
75 char task[TASK_COMM_LEN];
76};
77BPF_PERF_OUTPUT(listen_evt);
78
79// Send an event for each IPv4 listen with PID, bound address and port
80int kprobe__inet_listen(struct pt_regs *ctx, struct socket *sock, int backlog)
81{
82 // cast types. Intermediate cast not needed, kept for readability
83 struct sock *sk = sock->sk;
84 struct inet_sock *inet = inet_sk(sk);
85
86 // Built event for userland
87 struct listen_evt_t evt = {
88 .ts_us = bpf_ktime_get_ns() / 1000,
89 .backlog = backlog,
90 };
91
92 // Get process comm. Needs LLVM >= 3.7.1 see https://github.com/iovisor/bcc/issues/393
93 bpf_get_current_comm(evt.task, TASK_COMM_LEN);
94
95 // Get socket IP family
96 u16 family = sk->__sk_common.skc_family;
97 evt.proto = family << 16 | SOCK_STREAM;
98
99 // Get PID
100 evt.pid_tgid = bpf_get_current_pid_tgid();
101
102 ##FILTER_PID##
103
104 // Get port
105 bpf_probe_read(&evt.lport, sizeof(u16), &(inet->inet_sport));
106 evt.lport = ntohs(evt.lport);
107
108 // Get network namespace id, if kernel supports it
109#ifdef CONFIG_NET_NS
110 evt.netns = sk->__sk_common.skc_net.net->ns.inum;
111#else
112 evt.netns = 0;
113#endif
114
115 ##FILTER_NETNS##
116
117 // Get IP
118 if (family == AF_INET) {
119 bpf_probe_read(evt.laddr, sizeof(u32), &(inet->inet_rcv_saddr));
120 evt.laddr[0] = bcc_be32_to_cpu(evt.laddr[0]);
121 } else if (family == AF_INET6) {
122 bpf_probe_read(evt.laddr, sizeof(evt.laddr), sk->__sk_common.skc_v6_rcv_saddr.in6_u.u6_addr32);
123 evt.laddr[0] = bcc_be64_to_cpu(evt.laddr[0]);
124 evt.laddr[1] = bcc_be64_to_cpu(evt.laddr[1]);
125 }
126
127 // Send event to userland
128 listen_evt.perf_submit(ctx, &evt, sizeof(evt));
129
130 return 0;
131};
132"""
133
134# event data
135TASK_COMM_LEN = 16 # linux/sched.h
136class ListenEvt(ct.Structure):
137 _fields_ = [
138 ("ts_us", ct.c_ulonglong),
139 ("pid_tgid", ct.c_ulonglong),
140 ("backlog", ct.c_ulonglong),
141 ("netns", ct.c_ulonglong),
142 ("proto", ct.c_ulonglong),
143 ("lport", ct.c_ulonglong),
144 ("laddr", ct.c_ulonglong * 2),
145 ("task", ct.c_char * TASK_COMM_LEN)
146 ]
147
148 # TODO: properties to unpack protocol / ip / pid / tgid ...
149
150# Format output
151def event_printer(show_netns):
152 def print_event(cpu, data, size):
153 # Decode event
154 event = ct.cast(data, ct.POINTER(ListenEvt)).contents
155
156 pid = event.pid_tgid & 0xffffffff
157 proto_family = event.proto & 0xff
158 proto_type = event.proto >> 16 & 0xff
159
160 if proto_family == socket.SOCK_STREAM:
161 protocol = "TCP"
162 elif proto_family == socket.SOCK_DGRAM:
163 protocol = "UDP"
164 else:
165 protocol = "UNK"
166
167 address = ""
168 if proto_type == socket.AF_INET:
169 protocol += "v4"
170 address = netaddr.IPAddress(event.laddr[0])
171 elif proto_type == socket.AF_INET6:
172 address = netaddr.IPAddress(event.laddr[0]<<64 | event.laddr[1], version=6)
173 protocol += "v6"
174
175 # Display
176 if show_netns:
177 print("%-6d %-12.12s %-12s %-6s %-8s %-5s %-39s" % (
178 pid, event.task, event.netns, protocol, event.backlog,
179 event.lport, address,
180 ))
181 else:
182 print("%-6d %-12.12s %-6s %-8s %-5s %-39s" % (
183 pid, event.task, protocol, event.backlog,
184 event.lport, address,
185 ))
186
187 return print_event
188
189if __name__ == "__main__":
190 # Parse arguments
191 args = parser.parse_args()
192
193 pid_filter = ""
194 netns_filter = ""
195
196 if args.pid:
197 pid_filter = "if (evt.pid_tgid != %d) return 0;" % args.pid
198 if args.netns:
199 netns_filter = "if (evt.netns != %d) return 0;" % args.netns
200
201 bpf_text = bpf_text.replace("##FILTER_PID##", pid_filter)
202 bpf_text = bpf_text.replace("##FILTER_NETNS##", netns_filter)
203
204 # Initialize BPF
205 b = BPF(text=bpf_text)
206 b["listen_evt"].open_perf_buffer(event_printer(args.show_netns))
207
208 # Print headers
209 if args.show_netns:
210 print("%-6s %-12s %-12s %-6s %-8s %-5s %-39s" % ("PID", "COMM", "NETNS", "PROTO", "BACKLOG", "PORT", "ADDR"))
211 else:
212 print("%-6s %-12s %-6s %-8s %-5s %-39s" % ("PID", "COMM", "PROTO", "BACKLOG", "PORT", "ADDR"))
213
214 # Read events
215 while 1:
216 b.kprobe_poll()
217