1"""
2Get details about packets blocked by ufw (uses journald)
3"""
4
5import datetime
6import re
7from systemd import journal
8
9from logparse import config
10from logparse.formatting import *
11from logparse.load_parsers import Parser
12from logparse.util import resolve
13
14PROTOCOLS = ["TCP", "UDP", "UDP-Lite", "ICMP", "ICMPv6", "AH", "SCTP", "MH"]
15
16class Packet():
17 """
18 Class to hold variables for each packet. Also parses incoming log messages
19 on object initialisation.
20 """
21
22 def __init__(self, msg):
23 """
24 Determine fields in log message. If any of the fields are missing the
25 log is considered malformed and is discarded. Also the protocol can be
26 specified as either an integer or a string - see `man ufw`.
27 """
28 try:
29 self.inif, self.outif, self.mac, self.src, self.dst, self.len, \
30 self.proto, self.spt, self.dpt = \
31 re.search(r"IN=(?P<inif>\w*).*OUT=(?P<outif>\w*).*"
32 "MAC=(?P<mac>\S*).*SRC=(?P<src>\S*).*DST=(?P<dst>\S*)"
33 ".*LEN=(?P<length>\d*).*PROTO=(?P<proto>\S*)"
34 "(?:\sSPT=(?P<spt>\d*))?(?:\sDPT=(?P<dpt>\d*))?", msg
35 ).groupdict().values()
36 if self.proto and self.proto.isdigit():
37 self.proto = PROTOCOLS[int(self.proto)-1]
38 except Exception as e:
39 logger.warning("Malformed packet log: {0}. Error message: {1}"
40 .format(msg, str(e)))
41 return None
42
43class UfwJournald(Parser):
44
45 def __init__(self):
46 super().__init__()
47 self.name = "ufw"
48 self.info = "Get details about packets blocked by ufw"
49
50 def parse_log(self):
51
52 logger.debug("Starting ufw section")
53 section = Section("ufw")
54
55 # Find applicable log entries
56
57 j = journal.Reader()
58 j.this_machine()
59 j.add_match(_TRANSPORT='kernel')
60 j.add_match(PRIORITY=4)
61 j.seek_realtime(section.period.startdate)
62
63 logger.debug("Searching for messages")
64
65 blocked_packets = [Packet(entry["MESSAGE"]) for entry in j
66 if "MESSAGE" in entry and "UFW BLOCK" in entry["MESSAGE"]]
67
68 # Parse messages
69
70 logger.debug("Parsing messages")
71
72 inbound_interfaces = []
73 outbound_interfaces = []
74 n_inbound = n_outbond = 0
75 src_ips = []
76 dst_ips = []
77 src_ports = []
78 dst_ports = []
79 protocols = {'UDP': 0, 'TCP': 0}
80 src_macs = []
81
82 for pkt in blocked_packets:
83 if pkt.inif:
84 inbound_interfaces.append(pkt.inif)
85 elif pkt.outif:
86 outbound_interfaces.append(pkt.outif)
87 if pkt.src: src_ips.append(resolve(pkt.src,
88 config.prefs.get("ufw", "ufw-resolve-domains")))
89 if pkt.dst: dst_ips.append(resolve(pkt.dst,
90 config.prefs.get("ufw", "ufw-resolve-domains")))
91 if pkt.spt: src_ports.append(pkt.spt)
92 if pkt.dpt: dst_ports.append(pkt.dpt)
93 if pkt.proto: protocols[pkt.proto] += 1
94
95 # Format data objects
96
97 section.append_data(Data(subtitle="{} blocked ({} UDP, {} TCP)".format(
98 plural("packet", len(blocked_packets)),
99 protocols['UDP'], protocols['TCP'])))
100
101 src_port_data = Data(items=src_ports)
102 src_port_data.orderbyfreq()
103 src_port_data.subtitle = plural("source port", len(src_port_data.items))
104 src_port_data.truncl(config.prefs.getint("logparse", "maxlist"))
105 section.append_data(src_port_data)
106
107 dst_port_data= Data(items=dst_ports)
108 dst_port_data.orderbyfreq()
109 dst_port_data.subtitle = plural("destination port",
110 len(dst_port_data.items))
111 dst_port_data.truncl(config.prefs.getint("logparse", "maxlist"))
112 section.append_data(dst_port_data)
113
114 src_ips_data= Data(items=src_ips)
115 src_ips_data.orderbyfreq()
116 src_ips_data.subtitle = plural("source IP", len(src_ips_data.items))
117 src_ips_data.truncl(config.prefs.getint("logparse", "maxlist"))
118 section.append_data(src_ips_data)
119
120 dst_ips_data= Data(items=dst_ips)
121 dst_ips_data.orderbyfreq()
122 dst_ips_data.subtitle = plural("destination IP",
123 len(dst_ips_data.items))
124 dst_ips_data.truncl(config.prefs.getint("logparse", "maxlist"))
125 section.append_data(dst_ips_data)
126
127 logger.info("Finished ufw section")
128 return section
129
130 def check_dependencies(self):
131 """
132 Basic dependency check to determine if there are any logs to parse
133 """
134
135 ufw_cmdline = "ufw --version"
136 if self._check_dependency_command(ufw_cmdline)[0] != 0:
137 return (False, ["ufw"])
138 else:
139 return (True, None)