enhance tools/tcpaccept (#2254)

- add option `-T': include time column on output (HH:MM:SS)
- add option `-P PORT': only trace port(s) specified
- add RPORT colume on output
diff --git a/man/man8/tcpaccept.8 b/man/man8/tcpaccept.8
index 837717b..6e340bd 100644
--- a/man/man8/tcpaccept.8
+++ b/man/man8/tcpaccept.8
@@ -1,8 +1,8 @@
-.TH tcpaccept 8  "2015-08-25" "USER COMMANDS"
+.TH tcpaccept 8  "2019-03-08" "USER COMMANDS"
 .SH NAME
 tcpaccept \- Trace TCP passive connections (accept()). Uses Linux eBPF/bcc.
 .SH SYNOPSIS
-.B tcpaccept [\-h] [\-t] [\-x] [\-p PID]
+.B tcpaccept [\-h] [\-T] [\-t] [\-p PID] [\-P PORTS]
 .SH DESCRIPTION
 This tool traces passive TCP connections (eg, via an accept() syscall;
 connect() are active connections). This can be useful for general
@@ -22,11 +22,17 @@
 \-h
 Print usage message.
 .TP
+\-T
+Include a time column on output (HH:MM:SS).
+.TP
 \-t
 Include a timestamp column.
 .TP
 \-p PID
 Trace this process ID only (filtered in-kernel).
+.TP
+\-P PORTS
+Comma-separated list of local ports to trace (filtered in-kernel).
 .SH EXAMPLES
 .TP
 Trace all passive TCP connections (accept()s):
@@ -37,11 +43,18 @@
 #
 .B tcpaccept \-t
 .TP
+Trace connections to local ports 80 and 81 only:
+#
+.B tcpaccept \-P 80,81
+.TP
 Trace PID 181 only:
 #
 .B tcpaccept \-p 181
 .SH FIELDS
 .TP
+TIME
+Time of the event, in HH:MM:SS format.
+.TP
 TIME(s)
 Time of the event, in seconds.
 .TP
@@ -57,6 +70,9 @@
 RADDR
 Remote IP address.
 .TP
+RPORT
+Remote port
+.TP
 LADDR
 Local IP address.
 .TP
diff --git a/tools/tcpaccept.py b/tools/tcpaccept.py
index f606b73..169b0f3 100755
--- a/tools/tcpaccept.py
+++ b/tools/tcpaccept.py
@@ -4,7 +4,7 @@
 # tcpaccept Trace TCP accept()s.
 #           For Linux, uses BCC, eBPF. Embedded C.
 #
-# USAGE: tcpaccept [-h] [-t] [-p PID]
+# USAGE: tcpaccept [-h] [-T] [-t] [-p PID] [-P PORTS]
 #
 # 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.
@@ -21,21 +21,27 @@
 from struct import pack
 import argparse
 from bcc.utils import printb
+from time import strftime
 
 # arguments
 examples = """examples:
     ./tcpaccept           # trace all TCP accept()s
     ./tcpaccept -t        # include timestamps
+    ./tcpaccept -P 80,81  # only trace port 80 and 81
     ./tcpaccept -p 181    # only trace PID 181
 """
 parser = argparse.ArgumentParser(
     description="Trace TCP accepts",
     formatter_class=argparse.RawDescriptionHelpFormatter,
     epilog=examples)
+parser.add_argument("-T", "--time", action="store_true",
+    help="include time column on output (HH:MM:SS)")
 parser.add_argument("-t", "--timestamp", action="store_true",
     help="include timestamp on output")
 parser.add_argument("-p", "--pid",
     help="trace this PID only")
+parser.add_argument("-P", "--port",
+    help="comma-separated list of local ports to trace")
 parser.add_argument("--ebpf", action="store_true",
     help=argparse.SUPPRESS)
 args = parser.parse_args()
@@ -55,6 +61,7 @@
     u32 daddr;
     u64 ip;
     u16 lport;
+    u16 dport;
     char task[TASK_COMM_LEN];
 };
 BPF_PERF_OUTPUT(ipv4_events);
@@ -66,6 +73,7 @@
     unsigned __int128 daddr;
     u64 ip;
     u16 lport;
+    u16 dport;
     char task[TASK_COMM_LEN];
 };
 BPF_PERF_OUTPUT(ipv6_events);
@@ -126,9 +134,13 @@
         return 0;
 
     // pull in details
-    u16 family = 0, lport = 0;
+    u16 family = 0, lport = 0, dport;
     family = newsk->__sk_common.skc_family;
     lport = newsk->__sk_common.skc_num;
+    dport = newsk->__sk_common.skc_dport;
+    dport = ntohs(dport);
+
+    ##FILTER_PORT##
 
     if (family == AF_INET) {
         struct ipv4_data_t data4 = {.pid = pid, .ip = 4};
@@ -136,6 +148,7 @@
         data4.saddr = newsk->__sk_common.skc_rcv_saddr;
         data4.daddr = newsk->__sk_common.skc_daddr;
         data4.lport = lport;
+        data4.dport = dport;
         bpf_get_current_comm(&data4.task, sizeof(data4.task));
         ipv4_events.perf_submit(ctx, &data4, sizeof(data4));
 
@@ -147,6 +160,7 @@
         bpf_probe_read(&data6.daddr, sizeof(data6.daddr),
             &newsk->__sk_common.skc_v6_daddr.in6_u.u6_addr32);
         data6.lport = lport;
+        data6.dport = dport;
         bpf_get_current_comm(&data6.task, sizeof(data6.task));
         ipv6_events.perf_submit(ctx, &data6, sizeof(data6));
     }
@@ -168,9 +182,12 @@
     ##FILTER_PID##
 
     // pull in details
-    u16 family = 0, lport = 0;
+    u16 family = 0, lport = 0, dport;
     family = args->family;
     lport = args->sport;
+    dport = args->dport;
+
+    ##FILTER_PORT##
 
     if (family == AF_INET) {
         struct ipv4_data_t data4 = {.pid = pid, .ip = 4};
@@ -178,6 +195,7 @@
         __builtin_memcpy(&data4.saddr, args->saddr, sizeof(data4.saddr));
         __builtin_memcpy(&data4.daddr, args->daddr, sizeof(data4.daddr));
         data4.lport = lport;
+        data4.dport = dport;
         bpf_get_current_comm(&data4.task, sizeof(data4.task));
         ipv4_events.perf_submit(args, &data4, sizeof(data4));
     } else if (family == AF_INET6) {
@@ -186,6 +204,7 @@
         __builtin_memcpy(&data6.saddr, args->saddr, sizeof(data6.saddr));
         __builtin_memcpy(&data6.daddr, args->daddr, sizeof(data6.daddr));
         data6.lport = lport;
+        data6.dport = dport;
         bpf_get_current_comm(&data6.task, sizeof(data6.task));
         ipv6_events.perf_submit(args, &data6, sizeof(data6));
     }
@@ -207,35 +226,48 @@
         'if (pid != %s) { return 0; }' % args.pid)
 else:
     bpf_text = bpf_text.replace('##FILTER_PID##', '')
+if args.port:
+    lports = [int(lport) for lport in args.port.split(',')]
+    lports_if = ' && '.join(['lport != %d' % lport for lport in lports])
+    bpf_text = bpf_text.replace('##FILTER_PORT##',
+        'if (%s) { return 0; }' % lports_if)
 if debug or args.ebpf:
     print(bpf_text)
     if args.ebpf:
         exit()
 
+bpf_text = bpf_text.replace('##FILTER_PORT##', '')
+
 # process event
 def print_ipv4_event(cpu, data, size):
     event = b["ipv4_events"].event(data)
     global start_ts
+    if args.time:
+        print("%-9s" % strftime("%H:%M:%S"), end="")
     if args.timestamp:
         if start_ts == 0:
             start_ts = event.ts_us
         print("%-9.3f" % ((float(event.ts_us) - start_ts) / 1000000), end="")
-    printb(b"%-6d %-12.12s %-2d %-16s %-16s %-4d" % (event.pid,
+    printb(b"%-7d %-12.12s %-2d %-16s %-5d %-16s %-5d" % (event.pid,
         event.task, event.ip,
         inet_ntop(AF_INET, pack("I", event.daddr)).encode(),
+        event.dport,
         inet_ntop(AF_INET, pack("I", event.saddr)).encode(),
         event.lport))
 
 def print_ipv6_event(cpu, data, size):
     event = b["ipv6_events"].event(data)
     global start_ts
+    if args.time:
+        print("%-9s" % strftime("%H:%M:%S"), end="")
     if args.timestamp:
         if start_ts == 0:
             start_ts = event.ts_us
         print("%-9.3f" % ((float(event.ts_us) - start_ts) / 1000000), end="")
-    printb(b"%-6d %-12.12s %-2d %-16s %-16s %-4d" % (event.pid,
+    printb(b"%-7d %-12.12s %-2d %-16s %-5d %-16s %-5d" % (event.pid,
         event.task, event.ip,
         inet_ntop(AF_INET6, event.daddr).encode(),
+        event.dport,
         inet_ntop(AF_INET6, event.saddr).encode(),
         event.lport))
 
@@ -243,10 +275,12 @@
 b = BPF(text=bpf_text)
 
 # header
+if args.time:
+    print("%-9s" % ("TIME"), end="")
 if args.timestamp:
     print("%-9s" % ("TIME(s)"), end="")
-print("%-6s %-12s %-2s %-16s %-16s %-4s" % ("PID", "COMM", "IP", "RADDR",
-    "LADDR", "LPORT"))
+print("%-7s %-12s %-2s %-16s %-5s %-16s %-5s" % ("PID", "COMM", "IP", "RADDR",
+    "RPORT", "LADDR", "LPORT"))
 
 start_ts = 0
 
diff --git a/tools/tcpaccept_example.txt b/tools/tcpaccept_example.txt
index f86c439..2adee45 100644
--- a/tools/tcpaccept_example.txt
+++ b/tools/tcpaccept_example.txt
@@ -6,10 +6,10 @@
 addresses changed to protect the innocent):
 
 # ./tcpaccept
-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  1234:ab12:2040:5020:2299:0:5:0 1234:ab12:2040:5020:2299:0:5:0 7001
+PID    COMM         IP RADDR            RPORT  LADDR            LPORT
+907    sshd         4  192.168.56.1     32324  192.168.56.102   22
+907    sshd         4  127.0.0.1        39866  127.0.0.1        22
+5389   perl         6  1234:ab12:2040:5020:2299:0:5:0 52352 1234:ab12:2040:5020:2299:0:5:0 7001
 
 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
@@ -26,26 +26,29 @@
 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
+TIME(s)  PID    COMM         IP RADDR            RPORT LADDR            LPORT
+0.000    907    sshd         4  127.0.0.1        53700 127.0.0.1        22
+0.010    5389   perl         6  1234:ab12:2040:5020:2299:0:5:0 40614 1234:ab12:2040:5020:2299:0:5:0 7001
+0.992    907    sshd         4  127.0.0.1        32548 127.0.0.1        22
+1.984    907    sshd         4  127.0.0.1        51250 127.0.0.1        22
 
 
 USAGE message:
 
 # ./tcpaccept -h
-usage: tcpaccept [-h] [-t] [-p PID]
+usage: tcpaccept [-h] [-T] [-t] [-p PID] [-P PORTS]
 
 Trace TCP accepts
 
 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, --time              include time column on output (HH:MM:SS)
+  -t, --timestamp         include timestamp on output
+  -p PID, --pid PID       trace this PID only
+  -P PORTS, --port PORTS  comma-separated list of local ports to trace
 
 examples:
     ./tcpaccept           # trace all TCP accept()s
     ./tcpaccept -t        # include timestamps
+    ./tcpaccept -P 80,81  # only trace port 80 and 81
     ./tcpaccept -p 181    # only trace PID 181