Commit abdca97b authored by Gerald Combs's avatar Gerald Combs Committed by yonghong-song

tcpstates: Add systemd journal logging. (#2058)

* tcpstates: Add systemd journal logging.

Add a -Y/--journal flag to tcpstates.py, which logs events to the
systemd journal.

* tcpstates: Document systemd journal logging.

Update tcpstates_example.txt and tcpstates.8 to include the "-Y" flag.
parent 6c273106
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
.SH NAME .SH NAME
tcpstates \- Trace TCP session state changes with durations. Uses Linux eBPF/bcc. tcpstates \- Trace TCP session state changes with durations. Uses Linux eBPF/bcc.
.SH SYNOPSIS .SH SYNOPSIS
.B tcpstates [\-h] [\-T] [\-t] [\-w] [\-s] [\-D PORTS] [\-L PORTS] .B tcpstates [\-h] [\-T] [\-t] [\-w] [\-s] [\-D PORTS] [\-L PORTS] [\-Y]
.SH DESCRIPTION .SH DESCRIPTION
This tool traces TCP session state changes while tracing, and prints details This tool traces TCP session state changes while tracing, and prints details
including the duration in each state. This can help explain the latency of including the duration in each state. This can help explain the latency of
...@@ -41,6 +41,9 @@ Comma-separated list of local ports to trace (filtered in-kernel). ...@@ -41,6 +41,9 @@ Comma-separated list of local ports to trace (filtered in-kernel).
.TP .TP
\-D PORTS \-D PORTS
Comma-separated list of destination ports to trace (filtered in-kernel). Comma-separated list of destination ports to trace (filtered in-kernel).
.TP
\-Y
Log session state changes to the systemd journal.
.SH EXAMPLES .SH EXAMPLES
.TP .TP
Trace all TCP sessions, and show all state changes: Trace all TCP sessions, and show all state changes:
......
#!/usr/bin/python #!/usr/bin/python
# -*- coding: utf-8 -*-
# @lint-avoid-python-3-compatibility-imports # @lint-avoid-python-3-compatibility-imports
# #
# tcpstates Trace the TCP session state changes with durations. # tcpstates Trace the TCP session state changes with durations.
...@@ -20,7 +21,8 @@ import argparse ...@@ -20,7 +21,8 @@ import argparse
from socket import inet_ntop, AF_INET, AF_INET6 from socket import inet_ntop, AF_INET, AF_INET6
from struct import pack from struct import pack
import ctypes as ct import ctypes as ct
from time import strftime from time import strftime, time
from os import getuid
# arguments # arguments
examples = """examples: examples = """examples:
...@@ -29,6 +31,7 @@ examples = """examples: ...@@ -29,6 +31,7 @@ examples = """examples:
./tcpstates -T # include time column (HH:MM:SS) ./tcpstates -T # include time column (HH:MM:SS)
./tcpstates -w # wider colums (fit IPv6) ./tcpstates -w # wider colums (fit IPv6)
./tcpstates -stT # csv output, with times & timestamps ./tcpstates -stT # csv output, with times & timestamps
./tcpstates -Y # log events to the systemd journal
./tcpstates -L 80 # only trace local port 80 ./tcpstates -L 80 # only trace local port 80
./tcpstates -L 80,81 # only trace local ports 80 and 81 ./tcpstates -L 80,81 # only trace local ports 80 and 81
./tcpstates -D 80 # only trace remote port 80 ./tcpstates -D 80 # only trace remote port 80
...@@ -51,6 +54,8 @@ parser.add_argument("-D", "--remoteport", ...@@ -51,6 +54,8 @@ parser.add_argument("-D", "--remoteport",
help="comma-separated list of remote ports to trace.") help="comma-separated list of remote ports to trace.")
parser.add_argument("--ebpf", action="store_true", parser.add_argument("--ebpf", action="store_true",
help=argparse.SUPPRESS) help=argparse.SUPPRESS)
parser.add_argument("-Y", "--journal", action="store_true",
help="log session state changes to the systemd journal")
args = parser.parse_args() args = parser.parse_args()
debug = 0 debug = 0
...@@ -237,6 +242,14 @@ if args.csv: ...@@ -237,6 +242,14 @@ if args.csv:
header_string = "%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s" header_string = "%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s"
format_string = "%x,%d,%s,%s,%s,%s,%s,%d,%s,%s,%.3f" format_string = "%x,%d,%s,%s,%s,%s,%s,%d,%s,%s,%.3f"
if args.journal:
try:
from systemd import journal
except ImportError:
print("ERROR: Journal logging requires the systemd.journal module")
exit(1)
def tcpstate2str(state): def tcpstate2str(state):
# from include/net/tcp_states.h: # from include/net/tcp_states.h:
tcpstate = { tcpstate = {
...@@ -259,6 +272,44 @@ def tcpstate2str(state): ...@@ -259,6 +272,44 @@ def tcpstate2str(state):
else: else:
return str(state) return str(state)
def journal_fields(event, addr_family):
addr_pfx = 'IPV4'
if addr_family == AF_INET6:
addr_pfx = 'IPV6'
fields = {
# Standard fields described in systemd.journal-fields(7). journal.send
# will fill in CODE_LINE, CODE_FILE, and CODE_FUNC for us. If we're
# root and specify OBJECT_PID, systemd-journald will add other OBJECT_*
# fields for us.
'SYSLOG_IDENTIFIER': 'tcpstates',
'PRIORITY': 5,
'_SOURCE_REALTIME_TIMESTAMP': time() * 1000000,
'OBJECT_PID': str(event.pid),
'OBJECT_COMM': event.task.decode('utf-8', 'replace'),
# Custom fields, aka "stuff we sort of made up".
'OBJECT_' + addr_pfx + '_SOURCE_ADDRESS': inet_ntop(addr_family, pack("I", event.saddr)),
'OBJECT_TCP_SOURCE_PORT': str(event.ports >> 32),
'OBJECT_' + addr_pfx + '_DESTINATION_ADDRESS': inet_ntop(addr_family, pack("I", event.daddr)),
'OBJECT_TCP_DESTINATION_PORT': str(event.ports & 0xffffffff),
'OBJECT_TCP_OLD_STATE': tcpstate2str(event.oldstate),
'OBJECT_TCP_NEW_STATE': tcpstate2str(event.newstate),
'OBJECT_TCP_SPAN_TIME': str(event.span_us)
}
msg_format_string = (u"%(OBJECT_COMM)s " +
u"%(OBJECT_" + addr_pfx + "_SOURCE_ADDRESS)s " +
u"%(OBJECT_TCP_SOURCE_PORT)s → " +
u"%(OBJECT_" + addr_pfx + "_DESTINATION_ADDRESS)s " +
u"%(OBJECT_TCP_DESTINATION_PORT)s " +
u"%(OBJECT_TCP_OLD_STATE)s → %(OBJECT_TCP_NEW_STATE)s")
fields['MESSAGE'] = msg_format_string % (fields)
if getuid() == 0:
del fields['OBJECT_COMM'] # Handled by systemd-journald
return fields
# process event # process event
def print_ipv4_event(cpu, data, size): def print_ipv4_event(cpu, data, size):
event = ct.cast(data, ct.POINTER(Data_ipv4)).contents event = ct.cast(data, ct.POINTER(Data_ipv4)).contents
...@@ -282,6 +333,8 @@ def print_ipv4_event(cpu, data, size): ...@@ -282,6 +333,8 @@ def print_ipv4_event(cpu, data, size):
inet_ntop(AF_INET, pack("I", event.daddr)), event.ports & 0xffffffff, inet_ntop(AF_INET, pack("I", event.daddr)), event.ports & 0xffffffff,
tcpstate2str(event.oldstate), tcpstate2str(event.newstate), tcpstate2str(event.oldstate), tcpstate2str(event.newstate),
float(event.span_us) / 1000)) float(event.span_us) / 1000))
if args.journal:
journal.send(**journal_fields(event, AF_INET))
def print_ipv6_event(cpu, data, size): def print_ipv6_event(cpu, data, size):
event = ct.cast(data, ct.POINTER(Data_ipv6)).contents event = ct.cast(data, ct.POINTER(Data_ipv6)).contents
...@@ -305,6 +358,8 @@ def print_ipv6_event(cpu, data, size): ...@@ -305,6 +358,8 @@ def print_ipv6_event(cpu, data, size):
inet_ntop(AF_INET6, event.daddr), event.ports & 0xffffffff, inet_ntop(AF_INET6, event.daddr), event.ports & 0xffffffff,
tcpstate2str(event.oldstate), tcpstate2str(event.newstate), tcpstate2str(event.oldstate), tcpstate2str(event.newstate),
float(event.span_us) / 1000)) float(event.span_us) / 1000))
if args.journal:
journal.send(**journal_fields(event, AF_INET6))
# initialize BPF # initialize BPF
b = BPF(text=bpf_text) b = BPF(text=bpf_text)
......
...@@ -26,7 +26,8 @@ process context. If that's not the case, they may show kernel details. ...@@ -26,7 +26,8 @@ process context. If that's not the case, they may show kernel details.
USAGE: USAGE:
# tcpstates -h # tcpstates -h
usage: tcpstates [-h] [-T] [-t] [-w] [-s] [-L LOCALPORT] [-D REMOTEPORT] usage: tcpstates.py [-h] [-T] [-t] [-w] [-s] [-L LOCALPORT] [-D REMOTEPORT]
[-Y]
Trace TCP session state changes and durations Trace TCP session state changes and durations
...@@ -40,6 +41,7 @@ optional arguments: ...@@ -40,6 +41,7 @@ optional arguments:
comma-separated list of local ports to trace. comma-separated list of local ports to trace.
-D REMOTEPORT, --remoteport REMOTEPORT -D REMOTEPORT, --remoteport REMOTEPORT
comma-separated list of remote ports to trace. comma-separated list of remote ports to trace.
-Y, --journal log session state changes to the systemd journal
examples: examples:
./tcpstates # trace all TCP state changes ./tcpstates # trace all TCP state changes
...@@ -47,6 +49,7 @@ examples: ...@@ -47,6 +49,7 @@ examples:
./tcpstates -T # include time column (HH:MM:SS) ./tcpstates -T # include time column (HH:MM:SS)
./tcpstates -w # wider colums (fit IPv6) ./tcpstates -w # wider colums (fit IPv6)
./tcpstates -stT # csv output, with times & timestamps ./tcpstates -stT # csv output, with times & timestamps
./tcpstates -Y # log events to the systemd journal
./tcpstates -L 80 # only trace local port 80 ./tcpstates -L 80 # only trace local port 80
./tcpstates -L 80,81 # only trace local ports 80 and 81 ./tcpstates -L 80,81 # only trace local ports 80 and 81
./tcpstates -D 80 # only trace remote port 80 ./tcpstates -D 80 # only trace remote port 80
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment