diff --git a/cdist/argparse.py b/cdist/argparse.py index 153e7864..f390a974 100644 --- a/cdist/argparse.py +++ b/cdist/argparse.py @@ -486,6 +486,10 @@ def get_parsers(): '-m', '--mode', help='Which modes should run', action='append', default=[], choices=['scan', 'trigger']) + parser['scan'].add_argument( + '--list', + action='store_true', + help='List the known hosts and exit') parser['scan'].add_argument( '--config', action='store_true', diff --git a/cdist/scan/commandline.py b/cdist/scan/commandline.py index dead5292..331694e4 100644 --- a/cdist/scan/commandline.py +++ b/cdist/scan/commandline.py @@ -21,26 +21,11 @@ import logging import sys +from datetime import datetime log = logging.getLogger("scan") -# CLI processing is defined outside of the main scan class to handle -# non-available optional scapy dependency (instead of crashing mid-flight). -def commandline(args): - log.debug(args) - - # Check if we have the optional scapy dependency available. - try: - import cdist.scan.scan as scan - except ModuleNotFoundError: - log.error('cdist scan requires scapy to be installed. Exiting.') - sys.exit(1) - - # Default operation mode. - if not args.mode: - # By default scan and trigger, but do not call any action. - args.mode = ['scan', 'trigger', ] - +def run(scan, args): # We run each component in a separate process since they # must not block on each other. processes = [] @@ -59,3 +44,47 @@ def commandline(args): for process in processes: process.join() + +def list(scan, args): + s = scan.Scanner(interfaces=args.interfaces, args=args) + hosts = s.list() + + # A full IPv6 addresses id composed of 8 blocks of 4 hexa chars + + # 6 colons. + ipv6_max_size = 8 * 4 + 10 + # We format dates as follow: YYYY-MM-DD HH:MM:SS + date_max_size = 8 + 2 + 6 + 2 + + print("{} | {}".format( + 'link-local address'.ljust(ipv6_max_size), + 'last seen'.ljust(date_max_size))) + print('=' * (ipv6_max_size + 3 + date_max_size)) + for addr in hosts: + last_seen = datetime.strftime( + datetime.strptime(hosts[addr]['last_seen'].strip(), '%Y-%m-%d %H:%M:%S.%f'), + '%Y-%m-%d %H:%M:%S') + print("{} | {}".format(addr.ljust(ipv6_max_size),last_seen.ljust(date_max_size))) + +# CLI processing is defined outside of the main scan class to handle +# non-available optional scapy dependency (instead of crashing mid-flight). +def commandline(args): + log.debug(args) + + # Check if we have the optional scapy dependency available. + try: + import cdist.scan.scan as scan + except ModuleNotFoundError: + log.error('cdist scan requires scapy to be installed. Exiting.') + sys.exit(1) + + # Set default operation mode. + if not args.mode: + # By default scan and trigger, but do not call any action. + args.mode = ['scan', 'trigger', ] + + # Print known hosts and exit is --list is specified - do not start + # the scanner. + if args.list: + list(scan, args) + else: + run(scan, args) diff --git a/cdist/scan/scan.py b/cdist/scan/scan.py index 633a5c06..f3976370 100644 --- a/cdist/scan/scan.py +++ b/cdist/scan/scan.py @@ -132,6 +132,23 @@ class Scanner(object): with open(fname, "w") as fd: fd.write(f"{now}\n") + def list(self): + hosts = dict() + for linklocal_addr in os.listdir(self.outdir): + workdir = os.path.join(self.outdir, linklocal_addr) + # We ignore any (unexpected) file in this directory. + if os.path.isdir(workdir): + last_seen='-' + last_seen_file = os.path.join(workdir, 'last_seen') + if os.path.isfile(last_seen_file): + with open(last_seen_file, "r") as fd: + last_seen = fd.readline() + + hosts[linklocal_addr] = {'last_seen': last_seen} + + return hosts + + def config(self): """ Configure a host