Skip to content

Commit

Permalink
IPv6 support for tcp* tools (iovisor#582)
Browse files Browse the repository at this point in the history
* tcpretrans: support full IPv6 addresses, fix --lossprobe

* tcpaccept: support full IPv6 addresses, fix timestamps

* tcpconnect: support full IPv6 addresses, fix timestamps

* tcpconnlat: support full IPv6 addresses, fix timestamps
  • Loading branch information
markdrayton authored and 4ast committed Jun 26, 2016
1 parent 83064b0 commit 11de298
Show file tree
Hide file tree
Showing 9 changed files with 102 additions and 168 deletions.
6 changes: 2 additions & 4 deletions man/man8/tcpaccept.8
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,10 @@ IP
IP address family (4 or 6)
.TP
RADDR
Remote IP address. IPv4 as a dotted quad, IPv6 shows "..." then the last 4
bytes (check for newer versions of this tool for the full address).
Remote IP address.
.TP
LADDR
Local IP address. IPv4 as a dotted quad, IPv6 shows "..." then the last 4
bytes (check for newer versions of this tool for the full address).
Local IP address.
.TP
LPORT
Local port
Expand Down
6 changes: 2 additions & 4 deletions man/man8/tcpconnlat.8
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,10 @@ IP
IP address family (4 or 6).
.TP
SADDR
Source IP address. IPv4 as a dotted quad, IPv6 shows "..." then the last 4
bytes (check for newer versions of this tool for the full address).
Source IP address.
.TP
DADDR
Destination IP address. IPv4 as a dotted quad, IPv6 shows "..." then the last 4
bytes (check for newer versions of this tool for the full address).
Destination IP address.
.TP
DPORT
Destination port
Expand Down
6 changes: 2 additions & 4 deletions man/man8/tcpretrans.8
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,7 @@ IP
IP address family (4 or 6).
.TP
LADDR
Local IP address. IPv4 as a dotted quad, IPv6 shows "..." then the last 4
bytes (check for newer versions of this tool for the full address).
Local IP address.
.TP
LPORT
Local port.
Expand All @@ -57,8 +56,7 @@ T>
Type of event: R> == retransmit, L> == tail loss probe.
.TP
RADDR
Remote IP address. IPv4 as a dotted quad, IPv6 shows "..." then the last 4
bytes (check for newer versions of this tool for the full address).
Remote IP address.
.TP
RPORT
Remote port.
Expand Down
58 changes: 24 additions & 34 deletions tools/tcpaccept.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,6 @@
# This uses dynamic tracing of the kernel inet_csk_accept() socket function
# (from tcp_prot.accept), and will need to be modified to match kernel changes.
#
# IPv4 addresses are printed as dotted quads. For IPv6 addresses, the last four
# bytes are printed after "..."; check for future versions with better IPv6
# support.
#
# Copyright (c) 2015 Brendan Gregg.
# Licensed under the Apache License, Version 2.0 (the "License")
#
Expand All @@ -21,6 +17,8 @@

from __future__ import print_function
from bcc import BPF
from socket import inet_ntop, AF_INET, AF_INET6
from struct import pack
import argparse
import ctypes as ct

Expand Down Expand Up @@ -52,21 +50,20 @@
// XXX: switch some to u32's when supported
u64 ts_us;
u64 pid;
u64 ip;
u64 saddr;
u64 daddr;
u64 ip;
u64 lport;
char task[TASK_COMM_LEN];
};
BPF_PERF_OUTPUT(ipv4_events);
struct ipv6_data_t {
// XXX: update to transfer full ipv6 addrs
u64 ts_us;
u64 pid;
unsigned __int128 saddr;
unsigned __int128 daddr;
u64 ip;
u64 saddr;
u64 daddr;
u64 lport;
char task[TASK_COMM_LEN];
};
Expand Down Expand Up @@ -106,14 +103,10 @@
} else if (family == AF_INET6) {
struct ipv6_data_t data6 = {.pid = pid, .ip = 6};
data6.ts_us = bpf_ktime_get_ns() / 1000;
// just grab the last 4 bytes for now
u32 saddr = 0, daddr = 0;
bpf_probe_read(&saddr, sizeof(saddr),
&newsk->__sk_common.skc_v6_rcv_saddr.in6_u.u6_addr32[3]);
bpf_probe_read(&daddr, sizeof(daddr),
&newsk->__sk_common.skc_v6_daddr.in6_u.u6_addr32[3]);
data6.saddr = bpf_ntohl(saddr);
data6.daddr = bpf_ntohl(daddr);
bpf_probe_read(&data6.saddr, sizeof(data6.saddr),
&newsk->__sk_common.skc_v6_rcv_saddr.in6_u.u6_addr32);
bpf_probe_read(&data6.daddr, sizeof(data6.daddr),
&newsk->__sk_common.skc_v6_daddr.in6_u.u6_addr32);
data6.lport = lport;
bpf_get_current_comm(&data6.task, sizeof(data6.task));
ipv6_events.perf_submit(ctx, &data6, sizeof(data6));
Expand All @@ -135,45 +128,51 @@

# event data
TASK_COMM_LEN = 16 # linux/sched.h

class Data_ipv4(ct.Structure):
_fields_ = [
("ts_us", ct.c_ulonglong),
("pid", ct.c_ulonglong),
("ip", ct.c_ulonglong),
("saddr", ct.c_ulonglong),
("daddr", ct.c_ulonglong),
("ip", ct.c_ulonglong),
("lport", ct.c_ulonglong),
("task", ct.c_char * TASK_COMM_LEN)
]

class Data_ipv6(ct.Structure):
_fields_ = [
("ts_us", ct.c_ulonglong),
("pid", ct.c_ulonglong),
("saddr", (ct.c_ulonglong * 2)),
("daddr", (ct.c_ulonglong * 2)),
("ip", ct.c_ulonglong),
("saddr", ct.c_ulonglong),
("daddr", ct.c_ulonglong),
("lport", ct.c_ulonglong),
("task", ct.c_char * TASK_COMM_LEN)
]

# process event
def print_ipv4_event(cpu, data, size):
event = ct.cast(data, ct.POINTER(Data_ipv4)).contents
global start_ts
if args.timestamp:
if start_ts == 0:
start_ts = event.ts_us
print("%-9.3f" % ((event.ts_us - start_ts) / 100000), end="")
print("%-9.3f" % ((float(event.ts_us) - start_ts) / 1000000), end="")
print("%-6d %-12.12s %-2d %-16s %-16s %-4d" % (event.pid, event.task,
event.ip, inet_ntoa(event.daddr), inet_ntoa(event.saddr),
event.lport))
event.ip, inet_ntop(AF_INET, pack("I", event.daddr)),
inet_ntop(AF_INET, pack("I", event.saddr)), event.lport))

def print_ipv6_event(cpu, data, size):
event = ct.cast(data, ct.POINTER(Data_ipv6)).contents
global start_ts
if args.timestamp:
if start_ts == 0:
start_ts = event.ts_us
print("%-9.3f" % ((event.ts_us - start_ts) / 100000), end="")
print("%-6d %-12.12s %-2d ...%-13x ...%-13x %-4d" % (event.pid,
event.task, event.ip, event.daddr, event.saddr, event.lport))
print("%-9.3f" % ((float(event.ts_us) - start_ts) / 1000000), end="")
print("%-6d %-12.12s %-2d %-16s %-16s %-4d" % (event.pid, event.task,
event.ip, inet_ntop(AF_INET6, event.daddr),
inet_ntop(AF_INET6, event.saddr), event.lport))

# initialize BPF
b = BPF(text=bpf_text)
Expand All @@ -186,15 +185,6 @@ def print_ipv6_event(cpu, data, size):

start_ts = 0

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

# read events
b["ipv4_events"].open_perf_buffer(print_ipv4_event)
b["ipv6_events"].open_perf_buffer(print_ipv6_event)
Expand Down
12 changes: 5 additions & 7 deletions tools/tcpaccept_example.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,11 @@ addresses changed to protect the innocent):
PID COMM IP RADDR LADDR LPORT
907 sshd 4 192.168.56.1 192.168.56.102 22
907 sshd 4 127.0.0.1 127.0.0.1 22
5389 perl 6 ...fec0ae21 ...fec0ae21 7001
5389 perl 6 1234:ab12:2040:5020:2299:0:5:0 1234:ab12:2040:5020:2299:0:5:0 7001

This output shows three connections, two to PID 907, an "sshd" process listening
on port 22, and one to a "perl" process listening on port 7001.

The sshd connections were IPv4, and the addresses are printed as dotted quads.
The perl connection was IPv6, and the last 4 bytes of each address is printed
(for now; check for updated versions).
This output shows three connections, two IPv4 connections to PID 907, an "sshd"
process listening on port 22, and one IPv6 connection to a "perl" process
listening on port 7001.

The overhead of this tool should be negligible, since it is only tracing the
kernel function performing accept. It is not tracing every packet and then
Expand All @@ -31,6 +28,7 @@ The -t option prints a timestamp column:
# ./tcpaccept -t
TIME(s) PID COMM IP RADDR LADDR LPORT
0.000 907 sshd 4 127.0.0.1 127.0.0.1 22
0.010 5389 perl 6 1234:ab12:2040:5020:2299:0:5:0 1234:ab12:2040:5020:2299:0:5:0 7001
0.992 907 sshd 4 127.0.0.1 127.0.0.1 22
1.984 907 sshd 4 127.0.0.1 127.0.0.1 22

Expand Down
70 changes: 22 additions & 48 deletions tools/tcpconnect.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,6 @@
# This uses dynamic tracing of kernel functions, and will need to be updated
# to match kernel changes.
#
# IPv4 addresses are printed as dotted quads. For IPv6 addresses, the last four
# bytes are printed after "..."; check for future versions with better IPv6
# support.
#
# Copyright (c) 2015 Brendan Gregg.
# Licensed under the Apache License, Version 2.0 (the "License")
#
Expand All @@ -24,8 +20,8 @@
from __future__ import print_function
from bcc import BPF
import argparse
import re
from struct import pack, unpack_from
from socket import inet_ntop, AF_INET, AF_INET6
from struct import pack
import ctypes as ct

# arguments
Expand Down Expand Up @@ -58,9 +54,9 @@
// XXX: switch some to u32's when supported
u64 ts_us;
u64 pid;
u64 ip;
u64 saddr;
u64 daddr;
u64 ip;
u64 dport;
char task[TASK_COMM_LEN];
};
Expand All @@ -69,9 +65,9 @@
struct ipv6_data_t {
u64 ts_us;
u64 pid;
unsigned __int128 saddr;
unsigned __int128 daddr;
u64 ip;
u64 saddr[2];
u64 daddr[2];
u64 dport;
char task[TASK_COMM_LEN];
};
Expand Down Expand Up @@ -125,15 +121,10 @@
} else /* 6 */ {
struct ipv6_data_t data6 = {.pid = pid, .ip = ipver};
data6.ts_us = bpf_ktime_get_ns() / 1000;
// just grab the last 4 bytes for now
bpf_probe_read(&data6.saddr[0], sizeof(data6.saddr[0]),
&skp->__sk_common.skc_v6_rcv_saddr.in6_u.u6_addr32[0]);
bpf_probe_read(&data6.saddr[1], sizeof(data6.saddr[1]),
&skp->__sk_common.skc_v6_rcv_saddr.in6_u.u6_addr32[2]);
bpf_probe_read(&data6.daddr[0], sizeof(data6.daddr[0]),
&skp->__sk_common.skc_v6_daddr.in6_u.u6_addr32[0]);
bpf_probe_read(&data6.daddr[1], sizeof(data6.daddr[1]),
&skp->__sk_common.skc_v6_daddr.in6_u.u6_addr32[2]);
bpf_probe_read(&data6.saddr, sizeof(data6.saddr),
&skp->__sk_common.skc_v6_rcv_saddr.in6_u.u6_addr32);
bpf_probe_read(&data6.daddr, sizeof(data6.daddr),
&skp->__sk_common.skc_v6_daddr.in6_u.u6_addr32);
data6.dport = ntohs(dport);
bpf_get_current_comm(&data6.task, sizeof(data6.task));
ipv6_events.perf_submit(ctx, &data6, sizeof(data6));
Expand Down Expand Up @@ -166,49 +157,51 @@

# event data
TASK_COMM_LEN = 16 # linux/sched.h

class Data_ipv4(ct.Structure):
_fields_ = [
("ts_us", ct.c_ulonglong),
("pid", ct.c_ulonglong),
("ip", ct.c_ulonglong),
("saddr", ct.c_ulonglong),
("daddr", ct.c_ulonglong),
("ip", ct.c_ulonglong),
("dport", ct.c_ulonglong),
("task", ct.c_char * TASK_COMM_LEN)
]

class Data_ipv6(ct.Structure):
_fields_ = [
("ts_us", ct.c_ulonglong),
("pid", ct.c_ulonglong),
("saddr", (ct.c_ulonglong * 2)),
("daddr", (ct.c_ulonglong * 2)),
("ip", ct.c_ulonglong),
("saddr", ct.c_ulonglong * 2),
("daddr", ct.c_ulonglong * 2),
("dport", ct.c_ulonglong),
("task", ct.c_char * TASK_COMM_LEN)
]

# process event
def print_ipv4_event(cpu, data, size):
event = ct.cast(data, ct.POINTER(Data_ipv4)).contents
global start_ts
if args.timestamp:
if start_ts == 0:
start_ts = event.ts_us
print("%-9.3f" % ((event.ts_us - start_ts) / 100000), end="")
print("%-9.3f" % ((float(event.ts_us) - start_ts) / 1000000), end="")
print("%-6d %-12.12s %-2d %-16s %-16s %-4d" % (event.pid, event.task,
event.ip, inet_ntoa(event.saddr), inet_ntoa(event.daddr),
event.dport))
event.ip, inet_ntop(AF_INET, pack("I", event.saddr)),
inet_ntop(AF_INET, pack("I", event.daddr)), event.dport))

def print_ipv6_event(cpu, data, size):
event = ct.cast(data, ct.POINTER(Data_ipv6)).contents
global start_ts
if args.timestamp:
if start_ts == 0:
start_ts = event.ts_us
print("%-9.3f" % ((event.ts_us - start_ts) / 100000), end="")
print("%-9.3f" % ((float(event.ts_us) - start_ts) / 1000000), end="")
print("%-6d %-12.12s %-2d %-16s %-16s %-4d" % (event.pid,
event.task, event.ip,
inet6_ntoa(event.saddr[1] << 64 | event.saddr[0]),
inet6_ntoa(event.daddr[1] << 64 | event.daddr[0]),
event.dport))
event.task, event.ip, inet_ntop(AF_INET6, event.saddr),
inet_ntop(AF_INET6, event.daddr), event.dport))

# initialize BPF
b = BPF(text=bpf_text)
Expand All @@ -225,25 +218,6 @@ def print_ipv6_event(cpu, data, size):

start_ts = 0

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

def inet6_ntoa(addr):
# see RFC4291 summary in RFC5952 section 2
s = ":".join(["%x" % x for x in unpack_from(">HHHHHHHH",
pack("QQ", addr & 0xffffffff, addr >> 64))])

# compress left-most zero run only (change to most for RFC5952):
s = re.sub(r'(^|:)0:(0:)+', r'::', s, 1)
return s

# read events
b["ipv4_events"].open_perf_buffer(print_ipv4_event)
b["ipv6_events"].open_perf_buffer(print_ipv6_event)
Expand Down
Loading

0 comments on commit 11de298

Please sign in to comment.