Skip to content

Commit

Permalink
tools/tcpconnect: add option -c to count connects
Browse files Browse the repository at this point in the history
Add -c to count all active connections per dest ip/port so we can
easily spot the heavy outbound connection attempts.

    # ./tcpconnect.py -c
    Tracing connect ... Hit Ctrl-C to end
    ^C
    LADDR                 RADDR                    RPORT              CONNECTS
    192.168.10.50         172.217.21.194           443                70
    192.168.10.50         172.213.11.195           443                34
    192.168.10.50         172.212.22.194           443                21
    [...]
  • Loading branch information
boat0 authored and yonghong-song committed Aug 1, 2019
1 parent 237a4b4 commit 9518a5b
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 47 deletions.
16 changes: 12 additions & 4 deletions man/man8/tcpconnect.8
Expand Up @@ -2,7 +2,7 @@
.SH NAME
tcpconnect \- Trace TCP active connections (connect()). Uses Linux eBPF/bcc.
.SH SYNOPSIS
.B tcpconnect [\-h] [\-t] [\-x] [\-p PID] [-P PORT]
.B tcpconnect [\-h] [\-c] [\-t] [\-x] [\-p PID] [-P PORT]
.SH DESCRIPTION
This tool traces active TCP connections (eg, via a connect() syscall;
accept() are passive connections). This can be useful for general
Expand All @@ -25,6 +25,9 @@ Print usage message.
\-t
Include a timestamp column.
.TP
\-c
Count connects per src ip and dest ip/port.
.TP
\-p PID
Trace this process ID only (filtered in-kernel).
.TP
Expand Down Expand Up @@ -53,16 +56,18 @@ Trace PID 181 only:
Trace ports 80 and 81 only:
#
.B tcpconnect \-P 80,81
.SH FIELDS
.TP
Trace all TCP connects, and include UID:
#
.B tcpconnect \-U
.SH FIELDS
.TP
Trace UID 1000 only:
#
.B tcpconnect \-u 1000
.TP
Count connects per src ip and dest ip/port:
#
.B tcpconnect \-c
.SH FIELDS
.TP
TIME(s)
Expand All @@ -88,11 +93,14 @@ Destination IP address.
.TP
DPORT
Destination port
.TP
CONNECTS
Accumulated active connections since start.
.SH OVERHEAD
This traces the kernel tcp_v[46]_connect functions and prints output for each
event. As the rate of this is generally expected to be low (< 1000/s), the
overhead is also expected to be negligible. If you have an application that
is calling a high rate of connects()s, such as a proxy server, then test and
is calling a high rate of connect()s, such as a proxy server, then test and
understand this overhead before use.
.SH SOURCE
This is from bcc.
Expand Down
153 changes: 117 additions & 36 deletions tools/tcpconnect.py
Expand Up @@ -4,7 +4,7 @@
# tcpconnect Trace TCP connect()s.
# For Linux, uses BCC, eBPF. Embedded C.
#
# USAGE: tcpconnect [-h] [-t] [-p PID] [-P PORT [PORT ...]]
# USAGE: tcpconnect [-h] [-c] [-t] [-p PID] [-P PORT [PORT ...]]
#
# All connection attempts are traced, even if they ultimately fail.
#
Expand All @@ -17,13 +17,15 @@
# 25-Sep-2015 Brendan Gregg Created this.
# 14-Feb-2016 " " Switch to bpf_perf_output.
# 09-Jan-2019 Takuma Kume Support filtering by UID
# 30-Jul-2019 Xiaozhou Liu Count connects.

from __future__ import print_function
from bcc import BPF
from bcc.utils import printb
import argparse
from socket import inet_ntop, ntohs, AF_INET, AF_INET6
from struct import pack
from time import sleep

# arguments
examples = """examples:
Expand All @@ -34,6 +36,7 @@
./tcpconnect -P 80,81 # only trace port 80 and 81
./tcpconnect -U # include UID
./tcpconnect -u 1000 # only trace UID 1000
./tcpconnect -c # count connects per src ip and dest ip/port
"""
parser = argparse.ArgumentParser(
description="Trace TCP connects",
Expand All @@ -49,6 +52,8 @@
help="include UID on output")
parser.add_argument("-u", "--uid",
help="trace this UID only")
parser.add_argument("-c", "--count", action="store_true",
help="count connects per src ip and dest ip/port")
parser.add_argument("--ebpf", action="store_true",
help=argparse.SUPPRESS)
args = parser.parse_args()
Expand Down Expand Up @@ -87,6 +92,21 @@
};
BPF_PERF_OUTPUT(ipv6_events);
// separate flow keys per address family
struct ipv4_flow_key_t {
u32 saddr;
u32 daddr;
u16 dport;
};
BPF_HASH(ipv4_count, struct ipv4_flow_key_t);
struct ipv6_flow_key_t {
unsigned __int128 saddr;
unsigned __int128 daddr;
u16 dport;
};
BPF_HASH(ipv6_count, struct ipv6_flow_key_t);
int trace_connect_entry(struct pt_regs *ctx, struct sock *sk)
{
u64 pid_tgid = bpf_get_current_pid_tgid();
Expand Down Expand Up @@ -130,26 +150,9 @@
FILTER_PORT
if (ipver == 4) {
struct ipv4_data_t data4 = {.pid = pid, .ip = ipver};
data4.uid = bpf_get_current_uid_gid();
data4.ts_us = bpf_ktime_get_ns() / 1000;
data4.saddr = skp->__sk_common.skc_rcv_saddr;
data4.daddr = skp->__sk_common.skc_daddr;
data4.dport = ntohs(dport);
bpf_get_current_comm(&data4.task, sizeof(data4.task));
ipv4_events.perf_submit(ctx, &data4, sizeof(data4));
IPV4_CODE
} else /* 6 */ {
struct ipv6_data_t data6 = {.pid = pid, .ip = ipver};
data6.uid = bpf_get_current_uid_gid();
data6.ts_us = bpf_ktime_get_ns() / 1000;
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));
IPV6_CODE
}
currsock.delete(&tid);
Expand All @@ -168,7 +171,58 @@
}
"""

struct_init = { 'ipv4':
{ 'count' :
"""
struct ipv4_flow_key_t flow_key = {};
flow_key.saddr = skp->__sk_common.skc_rcv_saddr;
flow_key.daddr = skp->__sk_common.skc_daddr;
flow_key.dport = ntohs(dport);
ipv4_count.increment(flow_key);""",
'trace' :
"""
struct ipv4_data_t data4 = {.pid = pid, .ip = ipver};
data4.uid = bpf_get_current_uid_gid();
data4.ts_us = bpf_ktime_get_ns() / 1000;
data4.saddr = skp->__sk_common.skc_rcv_saddr;
data4.daddr = skp->__sk_common.skc_daddr;
data4.dport = ntohs(dport);
bpf_get_current_comm(&data4.task, sizeof(data4.task));
ipv4_events.perf_submit(ctx, &data4, sizeof(data4));"""
},
'ipv6':
{ 'count' :
"""
struct ipv6_flow_key_t flow_key = {};
bpf_probe_read(&flow_key.saddr, sizeof(flow_key.saddr),
skp->__sk_common.skc_v6_rcv_saddr.in6_u.u6_addr32);
bpf_probe_read(&flow_key.daddr, sizeof(flow_key.daddr),
skp->__sk_common.skc_v6_daddr.in6_u.u6_addr32);
flow_key.dport = ntohs(dport);
ipv6_count.increment(flow_key);""",
'trace' :
"""
struct ipv6_data_t data6 = {.pid = pid, .ip = ipver};
data6.uid = bpf_get_current_uid_gid();
data6.ts_us = bpf_ktime_get_ns() / 1000;
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));"""
}
}

# code substitutions
if args.count:
bpf_text = bpf_text.replace("IPV4_CODE", struct_init['ipv4']['count'])
bpf_text = bpf_text.replace("IPV6_CODE", struct_init['ipv6']['count'])
else:
bpf_text = bpf_text.replace("IPV4_CODE", struct_init['ipv4']['trace'])
bpf_text = bpf_text.replace("IPV6_CODE", struct_init['ipv6']['trace'])

if args.pid:
bpf_text = bpf_text.replace('FILTER_PID',
'if (pid != %s) { return 0; }' % args.pid)
Expand Down Expand Up @@ -219,28 +273,55 @@ def print_ipv6_event(cpu, data, size):
inet_ntop(AF_INET6, event.saddr).encode(), inet_ntop(AF_INET6, event.daddr).encode(),
event.dport))

def depict_cnt(counts_tab, l3prot='ipv4'):
for k, v in sorted(counts_tab.items(), key=lambda counts: counts[1].value, reverse=True):
depict_key = ""
if l3prot == 'ipv4':
depict_key = "%-25s %-25s %-20s" % ((inet_ntop(AF_INET, pack('I', k.saddr))),
inet_ntop(AF_INET, pack('I', k.daddr)), k.dport)
else:
depict_key = "%-25s %-25s %-20s" % ((inet_ntop(AF_INET6, k.saddr)),
inet_ntop(AF_INET6, k.daddr), k.dport)

print ("%s %-10d" % (depict_key, v.value))

# initialize BPF
b = BPF(text=bpf_text)
b.attach_kprobe(event="tcp_v4_connect", fn_name="trace_connect_entry")
b.attach_kprobe(event="tcp_v6_connect", fn_name="trace_connect_entry")
b.attach_kretprobe(event="tcp_v4_connect", fn_name="trace_connect_v4_return")
b.attach_kretprobe(event="tcp_v6_connect", fn_name="trace_connect_v6_return")

# header
if args.timestamp:
print("%-9s" % ("TIME(s)"), end="")
if args.print_uid:
print("%-6s" % ("UID"), end="")
print("%-6s %-12s %-2s %-16s %-16s %-4s" % ("PID", "COMM", "IP", "SADDR",
"DADDR", "DPORT"))

start_ts = 0

# read events
b["ipv4_events"].open_perf_buffer(print_ipv4_event)
b["ipv6_events"].open_perf_buffer(print_ipv6_event)
while 1:
print("Tracing connect ... Hit Ctrl-C to end")
if args.count:
try:
b.perf_buffer_poll()
while 1:
sleep(99999999)
except KeyboardInterrupt:
exit()
pass

# header
print("\n%-25s %-25s %-20s %-10s" % (
"LADDR", "RADDR", "RPORT", "CONNECTS"))
depict_cnt(b["ipv4_count"])
depict_cnt(b["ipv6_count"], l3prot='ipv6')
# read events
else:
# header
if args.timestamp:
print("%-9s" % ("TIME(s)"), end="")
if args.print_uid:
print("%-6s" % ("UID"), end="")
print("%-6s %-12s %-2s %-16s %-16s %-4s" % ("PID", "COMM", "IP", "SADDR",
"DADDR", "DPORT"))

start_ts = 0

# read events
b["ipv4_events"].open_perf_buffer(print_ipv4_event)
b["ipv6_events"].open_perf_buffer(print_ipv6_event)
while 1:
try:
b.perf_buffer_poll()
except KeyboardInterrupt:
exit()
28 changes: 21 additions & 7 deletions tools/tcpconnect_example.txt
Expand Up @@ -55,22 +55,35 @@ UID PID COMM IP SADDR DADDR DPORT
1000 31338 telnet 6 ::1 ::1 23
1000 31338 telnet 4 127.0.0.1 127.0.0.1 23

To spot heavy outbound connections quickly one can use the -c flag. It will
count all active connections per source ip and destination ip/port.

# ./tcpconnect.py -c
Tracing connect ... Hit Ctrl-C to end
^C
LADDR RADDR RPORT CONNECTS
192.168.10.50 172.217.21.194 443 70
192.168.10.50 172.213.11.195 443 34
192.168.10.50 172.212.22.194 443 21
[...]


USAGE message:

# ./tcpconnect -h
usage: tcpconnect [-h] [-t] [-p PID] [-P PORT]
usage: tcpconnect [-h] [-c] [-t] [-p PID] [-P PORT]

Trace TCP connects

optional arguments:
-h, --help show this help message and exit
-t, --timestamp include timestamp on output
-p PID, --pid PID trace this PID only
-h, --help show this help message and exit
-t, --timestamp include timestamp on output
-p PID, --pid PID trace this PID only
-P PORT, --port PORT
comma-separated list of destination ports to trace.
-U, --print-uid include UID on output
-u UID, --uid UID trace this UID only
comma-separated list of destination ports to trace.
-U, --print-uid include UID on output
-u UID, --uid UID trace this UID only
-c, --count count connects per src ip and dest ip/port

examples:
./tcpconnect # trace all TCP connect()s
Expand All @@ -80,3 +93,4 @@ examples:
./tcpconnect -P 80,81 # only trace port 80 and 81
./tcpconnect -U # include UID
./tcpconnect -u 1000 # only trace UID 1000
./tcpconnect -c # count connects per src ip and dest ip/port

0 comments on commit 9518a5b

Please sign in to comment.