-
Notifications
You must be signed in to change notification settings - Fork 3.9k
/
solisten.py
executable file
·202 lines (169 loc) · 5.96 KB
/
solisten.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
#!/usr/bin/env 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;
u32 pid;
int 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 = bpf_get_current_pid_tgid() >> 32;
##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
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"%-7d %-12.12s %-12d %-6s %-8d %-5d %-39s" % (
pid, event.task, event.netns, protocol.encode(), event.backlog,
event.lport, address.encode(),
))
else:
printb(b"%-7d %-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 != %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("%-7s %-12s %-12s %-6s %-8s %-5s %-39s" %
("PID", "COMM", "NETNS", "PROTO", "BACKLOG", "PORT", "ADDR"))
else:
print("%-7s %-12s %-6s %-8s %-5s %-39s" %
("PID", "COMM", "PROTO", "BACKLOG", "PORT", "ADDR"))
# Read events
while 1:
try:
b.perf_buffer_poll()
except KeyboardInterrupt:
exit()