/
netifmon
executable file
·96 lines (84 loc) · 3.42 KB
/
netifmon
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
#!/usr/bin/python3
# jbin - Joe's miscellaneous scripts, tools and configs
# netifmon: Monitor a network interface for IP address changes
# Copyright (C) 2024-2024 Johannes Bauer
#
# This file is part of jbin.
#
# jbin is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; this program is ONLY licensed under
# version 3 of the License, later versions are explicitly excluded.
#
# jbin is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with jbin. If not, see <http://www.gnu.org/licenses/>.
#
# Johannes Bauer <JohannesBauer@gmx.de>
import sys
import time
import datetime
import re
import subprocess
from FriendlyArgumentParser import FriendlyArgumentParser
class NetIfMonitor():
_ADDRESS_RE = re.compile(r"^\s+(inet|inet6) (?P<addr>[^\s]+)", flags = re.MULTILINE)
_IFNAME_RE = re.compile(r"^\d+:\s+(?P<ifname>[^:]+):", flags = re.MULTILINE)
def __init__(self, args):
self._args = args
self._last_addresses = { }
self._interfaces = self._args.ifname
if len(self._interfaces) == 0:
self._interfaces = self._list_all_interfaces()
def _list_all_interfaces(self):
interfaces = [ ]
output = subprocess.check_output([ "ip", "a" ]).decode("ascii")
for rematch in self._IFNAME_RE.finditer(output):
group = rematch.groupdict()
interfaces.append(group["ifname"])
interfaces.sort()
return interfaces
def _get_addresses(self, ifname):
addresses = [ ]
try:
output = subprocess.check_output([ "ip", "a", "s", ifname ], stderr = subprocess.DEVNULL).decode("ascii")
except subprocess.CalledProcessError:
# Possibly interface has vanished, simply return no addresses.
return addresses
for rematch in self._ADDRESS_RE.finditer(output):
group = rematch.groupdict()
addresses.append(group["addr"])
addresses.sort()
return addresses
def _get_all_addresses(self):
addresses = { }
for ifname in self._interfaces:
addresses[ifname] = self._get_addresses(ifname)
return addresses
def _log_addresses(self, ifname, addresses):
log_str = f"{datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}: {ifname} {' '.join(addresses)}"
with open(self._args.output_logfile, "a") as f:
print(log_str, file = f)
if self._args.verbose >= 1:
print(log_str)
def _check_interface(self):
all_addresses = self._get_all_addresses()
for (ifname, addresses) in sorted(all_addresses.items()):
if addresses != self._last_addresses.get(ifname):
self._log_addresses(ifname, addresses)
self._last_addresses = all_addresses
def run(self):
while True:
self._check_interface()
time.sleep(3)
parser = FriendlyArgumentParser(description = "Monitor a network interface for address changes.")
parser.add_argument("-o", "--output-logfile", metavar = "filename", default = "netifmon.log", help = "Filename to log to. Defaults to %(default)s.")
parser.add_argument("-v", "--verbose", action = "count", default = 0, help = "Increases verbosity. Can be specified multiple times to increase.")
parser.add_argument("ifname", nargs = "*", help = "Network interface(s) to monitor. If none specified, monitor all of them.")
args = parser.parse_args(sys.argv[1:])
nim = NetIfMonitor(args)
nim.run()