Skip to content

Commit

Permalink
examples/tracing: add tcpv4tracer
Browse files Browse the repository at this point in the history
This allows tracking TCP IPv4 connections by tracking TCP connects,
closes and accepts.

This is different from existing tools like tcpconnect or tcpaccept in
that:

* It includes more information like network namespace or source ports
for tcpconnects or remote ports for tcpaccepts
* It traces tcp_close allowing to see when a connection ends
* It doesn't currently support IPV6
  • Loading branch information
iaguis committed Oct 18, 2016
1 parent 2035edb commit 8b6ced9
Showing 1 changed file with 305 additions and 0 deletions.
305 changes: 305 additions & 0 deletions examples/tracing/tcpv4tracer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,305 @@
#!/usr/bin/python
#
# tcpv4tracer Trace TCP IPv4 connections.
# For Linux, uses BCC, eBPF. Embedded C.
#
# USAGE: tcpv4tracer [-h] [-p PID] [-n NETNS]
#
# Copyright 2016 Kinvolk GmbH
#
# Licensed under the Apache License, Version 2.0 (the "License")
from __future__ import print_function
from bcc import BPF

import argparse
import ctypes

parser = argparse.ArgumentParser(
description="Trace TCP IPv4 connections",
formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument("-p", "--pid",
help="trace this PID only")
parser.add_argument("-n", "--netns", default=0, type=int,
help="trace this Network Namespace only")
args = parser.parse_args()

# define BPF program
bpf_text = """
#include <uapi/linux/ptrace.h>
#include <net/sock.h>
#include <net/inet_sock.h>
#include <net/net_namespace.h>
#include <bcc/proto.h>
#define TCP_EVENT_TYPE_CONNECT 1
#define TCP_EVENT_TYPE_ACCEPT 2
#define TCP_EVENT_TYPE_CLOSE 3
struct tcp_event_t {
u32 type;
u32 pid;
char comm[TASK_COMM_LEN];
u32 saddr;
u32 daddr;
u16 sport;
u16 dport;
u32 netns;
};
BPF_PERF_OUTPUT(tcp_event);
BPF_HASH(connectsock, u32, struct sock *);
BPF_HASH(closesock, u32, struct sock *);
int kprobe__tcp_v4_connect(struct pt_regs *ctx, struct sock *sk)
{
u32 pid = bpf_get_current_pid_tgid();
##FILTER_PID##
// stash the sock ptr for lookup on return
connectsock.update(&pid, &sk);
return 0;
};
int kretprobe__tcp_v4_connect(struct pt_regs *ctx)
{
int ret = PT_REGS_RC(ctx);
u32 pid = bpf_get_current_pid_tgid();
struct sock **skpp;
skpp = connectsock.lookup(&pid);
if (skpp == 0) {
return 0; // missed entry
}
if (ret != 0) {
// failed to send SYNC packet, may not have populated
// socket __sk_common.{skc_rcv_saddr, ...}
connectsock.delete(&pid);
return 0;
}
// pull in details
struct sock *skp = *skpp;
struct ns_common *ns;
u32 saddr = 0, daddr = 0, net_ns_inum = 0;
u16 sport = 0, dport = 0;
bpf_probe_read(&sport, sizeof(sport), &((struct inet_sock *)skp)->inet_sport);
bpf_probe_read(&saddr, sizeof(saddr), &skp->__sk_common.skc_rcv_saddr);
bpf_probe_read(&daddr, sizeof(daddr), &skp->__sk_common.skc_daddr);
bpf_probe_read(&dport, sizeof(dport), &skp->__sk_common.skc_dport);
// Get network namespace id, if kernel supports it
#ifdef CONFIG_NET_NS
possible_net_t skc_net;
bpf_probe_read(&skc_net, sizeof(skc_net), &skp->__sk_common.skc_net);
bpf_probe_read(&net_ns_inum, sizeof(net_ns_inum), &skc_net.net->ns.inum);
#else
net_ns_inum = 0;
#endif
##FILTER_NETNS##
// output
struct tcp_event_t evt = {
.type = TCP_EVENT_TYPE_CONNECT,
.pid = pid,
.saddr = saddr,
.daddr = daddr,
.sport = ntohs(sport),
.dport = ntohs(dport),
.netns = net_ns_inum,
};
bpf_get_current_comm(&evt.comm, sizeof(evt.comm));
// do not send event if IP address is 0.0.0.0 or port is 0
if (evt.saddr != 0 && evt.daddr != 0 && evt.sport != 0 && evt.dport != 0) {
tcp_event.perf_submit(ctx, &evt, sizeof(evt));
}
connectsock.delete(&pid);
return 0;
}
int kprobe__tcp_close(struct pt_regs *ctx, struct sock *sk)
{
u32 pid = bpf_get_current_pid_tgid();
##FILTER_PID##
// stash the sock ptr for lookup on return
closesock.update(&pid, &sk);
return 0;
};
int kretprobe__tcp_close(struct pt_regs *ctx)
{
u32 pid = bpf_get_current_pid_tgid();
struct sock **skpp;
skpp = closesock.lookup(&pid);
if (skpp == 0) {
return 0; // missed entry
}
// pull in details
struct sock *skp = *skpp;
u32 saddr = 0, daddr = 0, net_ns_inum = 0;
u16 sport = 0, dport = 0;
bpf_probe_read(&saddr, sizeof(saddr), &skp->__sk_common.skc_rcv_saddr);
bpf_probe_read(&daddr, sizeof(daddr), &skp->__sk_common.skc_daddr);
bpf_probe_read(&sport, sizeof(sport), &((struct inet_sock *)skp)->inet_sport);
bpf_probe_read(&dport, sizeof(dport), &skp->__sk_common.skc_dport);
// Get network namespace id, if kernel supports it
#ifdef CONFIG_NET_NS
possible_net_t skc_net;
bpf_probe_read(&skc_net, sizeof(skc_net), &skp->__sk_common.skc_net);
bpf_probe_read(&net_ns_inum, sizeof(net_ns_inum), &skc_net.net->ns.inum);
#else
net_ns_inum = 0;
#endif
##FILTER_NETNS##
// output
struct tcp_event_t evt = {
.type = TCP_EVENT_TYPE_CLOSE,
.pid = pid,
.saddr = saddr,
.daddr = daddr,
.sport = ntohs(sport),
.dport = ntohs(dport),
.netns = net_ns_inum,
};
bpf_get_current_comm(&evt.comm, sizeof(evt.comm));
// do not send event if IP address is 0.0.0.0 or port is 0
if (evt.saddr != 0 && evt.daddr != 0 && evt.sport != 0 && evt.dport != 0) {
tcp_event.perf_submit(ctx, &evt, sizeof(evt));
}
closesock.delete(&pid);
return 0;
}
int kretprobe__inet_csk_accept(struct pt_regs *ctx)
{
struct sock *newsk = (struct sock *)PT_REGS_RC(ctx);
u32 pid = bpf_get_current_pid_tgid();
##FILTER_PID##
if (newsk == NULL) {
return 0;
}
// check this is TCP
u8 protocol = 0;
// workaround for reading the sk_protocol bitfield:
bpf_probe_read(&protocol, 1, (void *)((long)&newsk->sk_wmem_queued) - 3);
if (protocol != IPPROTO_TCP)
return 0;
// pull in details
u16 family = 0, lport = 0, dport = 0;
u32 net_ns_inum = 0;
bpf_probe_read(&family, sizeof(family), &newsk->__sk_common.skc_family);
bpf_probe_read(&lport, sizeof(lport), &newsk->__sk_common.skc_num);
bpf_probe_read(&dport, sizeof(dport), &newsk->__sk_common.skc_dport);
// Get network namespace id, if kernel supports it
#ifdef CONFIG_NET_NS
possible_net_t skc_net;
bpf_probe_read(&skc_net, sizeof(skc_net), &newsk->__sk_common.skc_net);
bpf_probe_read(&net_ns_inum, sizeof(net_ns_inum), &skc_net.net->ns.inum);
#else
net_ns_inum = 0;
#endif
##FILTER_NETNS##
if (family == AF_INET) {
struct tcp_event_t evt = {.type = TCP_EVENT_TYPE_ACCEPT, .pid = pid, .netns = net_ns_inum};
bpf_probe_read(&evt.saddr, sizeof(u32),
&newsk->__sk_common.skc_rcv_saddr);
bpf_probe_read(&evt.daddr, sizeof(u32),
&newsk->__sk_common.skc_daddr);
evt.sport = lport;
evt.dport = ntohs(dport);
bpf_get_current_comm(&evt.comm, sizeof(evt.comm));
tcp_event.perf_submit(ctx, &evt, sizeof(evt));
}
// else drop
return 0;
}
"""

TASK_COMM_LEN = 16 # linux/sched.h
class TCPEvt(ctypes.Structure):
_fields_ = [
("type", ctypes.c_uint),
("pid", ctypes.c_uint),
("comm", ctypes.c_char * TASK_COMM_LEN),
("saddr", ctypes.c_uint),
("daddr", ctypes.c_uint),
("sport", ctypes.c_ushort),
("dport", ctypes.c_ushort),
("netns", ctypes.c_uint),
]

def print_event(cpu, data, size):
event = ctypes.cast(data, ctypes.POINTER(TCPEvt)).contents
if event.type == 1:
type_str = "connect"
elif event.type == 2:
type_str = "accept"
elif event.type == 3:
type_str = "close"
else:
type_str = "unknown-" + str(event.type)

print("%-12s %-6s %-16s %-16s %-16s %-6s %-6s %-8s" % (type_str, event.pid, event.comm.decode('utf-8'),
inet_ntoa(event.saddr),
inet_ntoa(event.daddr),
event.sport,
event.dport,
event.netns))

pid_filter = ""
netns_filter = ""

if args.pid:
pid_filter = 'if (pid != %d) { return 0; }' % args.pid
if args.netns:
netns_filter = 'if (net_ns_inum != %d) { return 0; }' % args.netns

bpf_text = bpf_text.replace('##FILTER_PID##', pid_filter)
bpf_text = bpf_text.replace('##FILTER_NETNS##', netns_filter)

# initialize BPF
b = BPF(text=bpf_text)

# header
print("%-12s %-6s %-16s %-16s %-16s %-6s %-6s %-8s" % ("TYPE", "PID", "COMM", "SADDR", "DADDR",
"SPORT", "DPORT", "NETNS"))

def inet_ntoa(addr):
dq = ''
for i in range(0, 4):
dq = dq + str(addr & 0xff)
if (i != 3):
dq = dq + '.'
addr = addr >> 8
return dq

b["tcp_event"].open_perf_buffer(print_event)
while True:
b.kprobe_poll()

0 comments on commit 8b6ced9

Please sign in to comment.