1
0
mirror of https://github.com/kellyjonbrazil/jc.git synced 2025-06-19 00:17:51 +02:00
Files
jc/jc/parsers/netstat.py

570 lines
16 KiB
Python
Raw Normal View History

2019-10-15 15:06:09 -07:00
"""jc - JSON CLI output utility netstat Parser
Usage:
2019-12-12 09:47:14 -08:00
2019-10-17 12:15:27 -07:00
Specify --netstat as the first argument if the piped input is coming from netstat
2019-10-15 15:06:09 -07:00
2019-12-12 09:35:42 -08:00
Compatibility:
2019-12-12 09:47:14 -08:00
2019-12-12 09:35:42 -08:00
'linux'
2019-11-07 14:49:21 -08:00
Examples:
2019-11-11 18:30:46 -08:00
$ sudo netstat -apee | jc --netstat -p
[
{
"proto": "tcp",
"recv_q": 0,
"send_q": 0,
"local_address": "localhost",
"foreign_address": "0.0.0.0",
"state": "LISTEN",
"user": "systemd-resolve",
"inode": 26958,
"program_name": "systemd-resolve",
"kind": "network",
"pid": 887,
"local_port": "domain",
"foreign_port": "*",
"transport_protocol": "tcp",
"network_protocol": "ipv4"
},
{
"proto": "tcp",
"recv_q": 0,
"send_q": 0,
"local_address": "0.0.0.0",
"foreign_address": "0.0.0.0",
"state": "LISTEN",
"user": "root",
"inode": 30499,
"program_name": "sshd",
"kind": "network",
"pid": 1186,
"local_port": "ssh",
"foreign_port": "*",
"transport_protocol": "tcp",
"network_protocol": "ipv4"
},
{
"proto": "tcp",
"recv_q": 0,
"send_q": 0,
"local_address": "localhost",
"foreign_address": "localhost",
"state": "ESTABLISHED",
"user": "root",
"inode": 46829,
"program_name": "sshd: root",
"kind": "network",
"pid": 2242,
"local_port": "ssh",
"foreign_port": "52186",
"transport_protocol": "tcp",
"network_protocol": "ipv4",
"foreign_port_num": 52186
},
{
"proto": "tcp",
"recv_q": 0,
"send_q": 0,
"local_address": "localhost",
"foreign_address": "localhost",
"state": "ESTABLISHED",
"user": "root",
"inode": 46828,
"program_name": "ssh",
"kind": "network",
"pid": 2241,
"local_port": "52186",
"foreign_port": "ssh",
"transport_protocol": "tcp",
"network_protocol": "ipv4",
"local_port_num": 52186
},
{
"proto": "tcp6",
"recv_q": 0,
"send_q": 0,
"local_address": "[::]",
"foreign_address": "[::]",
"state": "LISTEN",
"user": "root",
"inode": 30510,
"program_name": "sshd",
"kind": "network",
"pid": 1186,
"local_port": "ssh",
"foreign_port": "*",
"transport_protocol": "tcp",
"network_protocol": "ipv6"
},
{
"proto": "udp",
"recv_q": 0,
"send_q": 0,
"local_address": "localhost",
"foreign_address": "0.0.0.0",
"state": null,
"user": "systemd-resolve",
"inode": 26957,
"program_name": "systemd-resolve",
"kind": "network",
"pid": 887,
"local_port": "domain",
"foreign_port": "*",
"transport_protocol": "udp",
"network_protocol": "ipv4"
},
{
"proto": "raw6",
"recv_q": 0,
"send_q": 0,
"local_address": "[::]",
"foreign_address": "[::]",
"state": "7",
"user": "systemd-network",
"inode": 27001,
"program_name": "systemd-network",
"kind": "network",
"pid": 867,
"local_port": "ipv6-icmp",
"foreign_port": "*",
"transport_protocol": null,
"network_protocol": "ipv6"
},
{
"proto": "unix",
"refcnt": 2,
"flags": null,
"type": "DGRAM",
"state": null,
"inode": 33322,
"program_name": "systemd",
"path": "/run/user/1000/systemd/notify",
"kind": "socket",
"pid": 1607
},
{
"proto": "unix",
"refcnt": 2,
"flags": "ACC",
"type": "SEQPACKET",
"state": "LISTENING",
"inode": 20835,
"program_name": "init",
"path": "/run/udev/control",
"kind": "socket",
"pid": 1
},
...
]
$ sudo netstat -apee | jc --netstat -p -r
[
{
"proto": "tcp",
"recv_q": "0",
"send_q": "0",
"local_address": "localhost",
"foreign_address": "0.0.0.0",
"state": "LISTEN",
"user": "systemd-resolve",
"inode": "26958",
"program_name": "systemd-resolve",
"kind": "network",
"pid": "887",
"local_port": "domain",
"foreign_port": "*",
"transport_protocol": "tcp",
"network_protocol": "ipv4"
},
{
"proto": "tcp",
"recv_q": "0",
"send_q": "0",
"local_address": "0.0.0.0",
"foreign_address": "0.0.0.0",
"state": "LISTEN",
"user": "root",
"inode": "30499",
"program_name": "sshd",
"kind": "network",
"pid": "1186",
"local_port": "ssh",
"foreign_port": "*",
"transport_protocol": "tcp",
"network_protocol": "ipv4"
},
{
"proto": "tcp",
"recv_q": "0",
"send_q": "0",
"local_address": "localhost",
"foreign_address": "localhost",
"state": "ESTABLISHED",
"user": "root",
"inode": "46829",
"program_name": "sshd: root",
"kind": "network",
"pid": "2242",
"local_port": "ssh",
"foreign_port": "52186",
"transport_protocol": "tcp",
"network_protocol": "ipv4"
},
{
"proto": "tcp",
"recv_q": "0",
"send_q": "0",
"local_address": "localhost",
"foreign_address": "localhost",
"state": "ESTABLISHED",
"user": "root",
"inode": "46828",
"program_name": "ssh",
"kind": "network",
"pid": "2241",
"local_port": "52186",
"foreign_port": "ssh",
"transport_protocol": "tcp",
"network_protocol": "ipv4"
},
{
"proto": "tcp6",
"recv_q": "0",
"send_q": "0",
"local_address": "[::]",
"foreign_address": "[::]",
"state": "LISTEN",
"user": "root",
"inode": "30510",
"program_name": "sshd",
"kind": "network",
"pid": "1186",
"local_port": "ssh",
"foreign_port": "*",
"transport_protocol": "tcp",
"network_protocol": "ipv6"
},
{
"proto": "udp",
"recv_q": "0",
"send_q": "0",
"local_address": "localhost",
"foreign_address": "0.0.0.0",
"state": null,
"user": "systemd-resolve",
"inode": "26957",
"program_name": "systemd-resolve",
"kind": "network",
"pid": "887",
"local_port": "domain",
"foreign_port": "*",
"transport_protocol": "udp",
"network_protocol": "ipv4"
},
{
"proto": "raw6",
"recv_q": "0",
"send_q": "0",
"local_address": "[::]",
"foreign_address": "[::]",
"state": "7",
"user": "systemd-network",
"inode": "27001",
"program_name": "systemd-network",
"kind": "network",
"pid": "867",
"local_port": "ipv6-icmp",
"foreign_port": "*",
"transport_protocol": null,
"network_protocol": "ipv6"
},
{
"proto": "unix",
"refcnt": "2",
"flags": null,
"type": "DGRAM",
"state": null,
"inode": "33322",
"program_name": "systemd",
"path": "/run/user/1000/systemd/notify",
"kind": "socket",
"pid": " 1607"
},
{
"proto": "unix",
"refcnt": "2",
"flags": "ACC",
"type": "SEQPACKET",
"state": "LISTENING",
"inode": "20835",
"program_name": "init",
"path": "/run/udev/control",
"kind": "socket",
"pid": " 1"
},
...
]
2019-10-15 15:06:09 -07:00
"""
2019-10-17 12:07:01 -07:00
import string
2019-11-07 08:07:43 -08:00
import jc.utils
def process(proc_data):
2019-11-11 18:30:46 -08:00
"""
2019-11-12 11:28:10 -08:00
Final processing to conform to the schema.
Parameters:
2019-11-14 16:40:52 -08:00
2019-11-13 08:04:40 -08:00
proc_data: (dictionary) raw structured data to process
2019-11-12 11:28:10 -08:00
Returns:
dictionary structured data with the following schema:
2019-11-14 16:40:52 -08:00
2019-11-11 18:30:46 -08:00
[
{
"proto": string,
"recv_q": integer,
"send_q": integer,
"transport_protocol" string,
"network_protocol": string,
"local_address": string,
"local_port": string,
"local_port_num": integer,
"foreign_address": string,
"foreign_port": string,
"foreign_port_num": integer,
"state": string,
"program_name": string,
"pid": integer,
"user": string,
"security_context": string,
"refcnt": integer,
"flags": string,
"type": string,
"inode": integer,
"path": string,
"kind": string
}
]
"""
2019-11-07 14:43:42 -08:00
for entry in proc_data:
# integer changes
int_list = ['recv_q', 'send_q', 'pid', 'refcnt', 'inode']
for key in int_list:
if key in entry:
try:
key_int = int(entry[key])
entry[key] = key_int
except (ValueError):
entry[key] = None
if 'local_port' in entry:
try:
entry['local_port_num'] = int(entry['local_port'])
except (ValueError):
pass
if 'foreign_port' in entry:
try:
entry['foreign_port_num'] = int(entry['foreign_port'])
except (ValueError):
pass
2019-11-07 08:07:43 -08:00
return proc_data
def normalize_headers(header):
header = header.lower()
header = header.replace('local address', 'local_address')
header = header.replace('foreign address', 'foreign_address')
header = header.replace('pid/program name', 'program_name')
header = header.replace('security context', 'security_context')
header = header.replace('i-node', 'inode')
header = header.replace('-', '_')
return header
def parse_network(headers, entry):
# Count words in header
# if len of line is one less than len of header, then insert None in field 5
entry = entry.split(maxsplit=len(headers) - 1)
if len(entry) == len(headers) - 1:
entry.insert(5, None)
output_line = dict(zip(headers, entry))
output_line['kind'] = 'network'
2019-10-15 15:06:09 -07:00
2019-11-07 08:07:43 -08:00
return output_line
2019-10-18 09:57:22 -07:00
2019-11-05 22:42:48 -06:00
2019-11-07 08:07:43 -08:00
def parse_socket(header_text, headers, entry):
2019-10-17 08:03:56 -07:00
output_line = {}
# get the column # of first letter of "state"
2019-11-07 08:07:43 -08:00
state_col = header_text.find('state')
# get the program name column area
pn_start = header_text.find('program_name')
pn_end = header_text.find('path') - 1
2019-10-17 08:03:56 -07:00
# remove [ and ] from each line
2019-11-07 08:07:43 -08:00
entry = entry.replace('[ ]', '---')
entry = entry.replace('[', ' ').replace(']', ' ')
2019-11-11 15:53:22 -08:00
# find program_name column area and substitute spaces with \u2063 there
old_pn = entry[pn_start:pn_end]
2019-11-11 15:53:22 -08:00
new_pn = old_pn.replace(' ', '\u2063')
entry = entry.replace(old_pn, new_pn)
2019-11-07 08:07:43 -08:00
entry_list = entry.split(maxsplit=len(headers) - 1)
# check column # to see if state column is populated
2019-11-07 08:07:43 -08:00
if entry[state_col] in string.whitespace:
entry_list.insert(4, None)
2019-10-21 13:19:00 -07:00
2019-11-07 08:07:43 -08:00
output_line = dict(zip(headers, entry_list))
output_line['kind'] = 'socket'
2019-10-21 13:19:00 -07:00
2019-11-11 15:53:22 -08:00
# fix program_name field to turn \u2063 back to spaces
if 'program_name' in output_line:
if output_line['program_name']:
old_d_pn = output_line['program_name']
2019-11-11 15:53:22 -08:00
new_d_pn = old_d_pn.replace('\u2063', ' ')
output_line['program_name'] = new_d_pn
2019-11-07 08:07:43 -08:00
return output_line
2019-10-21 14:27:26 -07:00
2019-10-17 12:07:01 -07:00
2019-11-07 08:07:43 -08:00
def parse_post(raw_data):
2019-11-07 13:53:23 -08:00
# clean up trailing whitespace on each item in each entry
2019-11-07 10:52:02 -08:00
# flags --- = null
2019-11-07 13:53:23 -08:00
# program_name - = null
2019-11-07 13:58:37 -08:00
# split pid and program name and ip addresses and ports
# create network and transport protocol fields
2019-10-18 09:57:22 -07:00
2019-11-07 10:52:02 -08:00
for entry in raw_data:
2019-11-07 12:13:25 -08:00
for item in entry:
try:
entry[item] = entry[item].rstrip()
except (AttributeError):
# skips trying to rstrip Null entries
pass
2019-11-07 10:52:02 -08:00
if 'flags' in entry:
if entry['flags'] == '---':
entry['flags'] = None
2019-11-07 13:53:23 -08:00
2019-11-07 10:52:02 -08:00
if 'program_name' in entry:
entry['program_name'] = entry['program_name'].strip()
2019-11-07 10:52:02 -08:00
if entry['program_name'] == '-':
entry['program_name'] = None
2019-11-07 13:53:23 -08:00
if entry['program_name']:
pid = entry['program_name'].split('/', maxsplit=1)[0]
name = entry['program_name'].split('/', maxsplit=1)[1]
entry['pid'] = pid
entry['program_name'] = name
if 'local_address' in entry:
if entry['local_address']:
ladd = entry['local_address'].rsplit(':', maxsplit=1)[0]
lport = entry['local_address'].rsplit(':', maxsplit=1)[1]
entry['local_address'] = ladd
entry['local_port'] = lport
if 'foreign_address' in entry:
if entry['foreign_address']:
fadd = entry['foreign_address'].rsplit(':', maxsplit=1)[0]
fport = entry['foreign_address'].rsplit(':', maxsplit=1)[1]
entry['foreign_address'] = fadd
entry['foreign_port'] = fport
if 'proto' in entry and 'kind' in entry:
if entry['kind'] == 'network':
if entry['proto'].find('tcp') != -1:
entry['transport_protocol'] = 'tcp'
elif entry['proto'].find('udp') != -1:
entry['transport_protocol'] = 'udp'
else:
entry['transport_protocol'] = None
if entry['proto'].find('6') != -1:
entry['network_protocol'] = 'ipv6'
else:
entry['network_protocol'] = 'ipv4'
2019-11-07 08:07:43 -08:00
return raw_data
2019-10-17 12:07:01 -07:00
2019-10-17 08:03:56 -07:00
2019-11-07 08:07:43 -08:00
def parse(data, raw=False, quiet=False):
2019-11-11 18:30:46 -08:00
"""
2019-11-12 11:17:33 -08:00
Main text parsing function
2019-11-11 18:30:46 -08:00
2019-11-12 11:17:33 -08:00
Parameters:
2019-11-14 16:40:52 -08:00
2019-11-12 11:17:33 -08:00
data: (string) text data to parse
raw: (boolean) output preprocessed JSON if True
quiet: (boolean) suppress warning messages if True
2019-11-11 18:30:46 -08:00
2019-11-12 11:17:33 -08:00
Returns:
dictionary raw or processed structured data
2019-11-11 18:30:46 -08:00
"""
2019-11-14 16:40:52 -08:00
2019-11-07 08:07:43 -08:00
# compatible options: linux, darwin, cygwin, win32, aix, freebsd
compatible = ['linux']
2019-10-17 08:03:56 -07:00
2019-11-07 08:07:43 -08:00
if not quiet:
jc.utils.compatibility(__name__, compatible)
2019-10-18 09:57:22 -07:00
2019-10-15 15:06:09 -07:00
cleandata = data.splitlines()
2019-11-07 10:52:02 -08:00
cleandata = list(filter(None, cleandata))
2019-11-07 08:07:43 -08:00
raw_output = []
network = False
socket = False
headers = ''
network_list = []
socket_list = []
2019-10-15 15:06:09 -07:00
2019-10-17 08:03:56 -07:00
for line in cleandata:
2019-10-17 10:54:37 -07:00
2019-11-07 08:07:43 -08:00
if line.find('Active Internet') == 0:
network_list = []
network = True
socket = False
2019-10-17 08:03:56 -07:00
continue
2019-11-07 08:07:43 -08:00
if line.find('Active UNIX') == 0:
socket_list = []
network = False
socket = True
2019-10-17 08:03:56 -07:00
continue
2019-10-18 09:57:22 -07:00
2019-10-17 08:03:56 -07:00
if line.find('Proto') == 0:
2019-11-07 08:07:43 -08:00
header_text = normalize_headers(line)
headers = header_text.split()
2019-10-17 08:03:56 -07:00
continue
2019-11-07 08:07:43 -08:00
if network:
network_list.append(parse_network(headers, line))
continue
2019-10-18 09:57:22 -07:00
2019-11-07 08:07:43 -08:00
if socket:
socket_list.append(parse_socket(header_text, headers, line))
continue
for item in [network_list, socket_list]:
for entry in item:
raw_output.append(entry)
2019-10-15 15:06:09 -07:00
2019-11-07 08:07:43 -08:00
raw_output = parse_post(raw_output)
if raw:
return raw_output
else:
return process(raw_output)