From a8b5fae5581c03c5037c5fdc840be3e5bf984484 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 13 May 2019 22:00:42 +0200 Subject: T1378: extend version file with Git commit ID The Git commit ID will be crucial for the future when the full VyOS build can be reproduced by the one Git commit ID, thus start recording it in the version file. --- src/op_mode/version.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/op_mode') diff --git a/src/op_mode/version.py b/src/op_mode/version.py index ce3b3b54f..5aff0f767 100755 --- a/src/op_mode/version.py +++ b/src/op_mode/version.py @@ -51,7 +51,8 @@ version_output_tmpl = """ Version: VyOS {{version}} Built by: {{built_by}} Built on: {{built_on}} -Build ID: {{build_id}} +Build UUID: {{build_uuid}} +Build Commit ID: {{build_git}} Architecture: {{system_arch}} Boot via: {{boot_via}} -- cgit v1.2.3 From 7a27f6f93b58abd2fabc9e80e429e14a70ebd6aa Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 30 May 2019 23:30:01 +0200 Subject: [dhcp] T1416: fix DHCP server status view --- op-mode-definitions/dhcp.xml | 4 ++-- src/op_mode/show_dhcp.py | 23 ++++++++++++++++++----- 2 files changed, 20 insertions(+), 7 deletions(-) (limited to 'src/op_mode') diff --git a/op-mode-definitions/dhcp.xml b/op-mode-definitions/dhcp.xml index a7d09304e..85403af6f 100644 --- a/op-mode-definitions/dhcp.xml +++ b/op-mode-definitions/dhcp.xml @@ -22,7 +22,7 @@ Show DHCP leases for a specific pool - sudo ${vyos_op_scripts_dir}/show_dhcp.py --leases --pool $4 + sudo ${vyos_op_scripts_dir}/show_dhcp.py --leases --pool $6 @@ -36,7 +36,7 @@ Show DHCP server statistics for a specific pool - sudo ${vyos_op_scripts_dir}/show_dhcp.py --statistics --pool $4 + sudo ${vyos_op_scripts_dir}/show_dhcp.py --statistics --pool $6 diff --git a/src/op_mode/show_dhcp.py b/src/op_mode/show_dhcp.py index 4c4ee6355..652173dc1 100755 --- a/src/op_mode/show_dhcp.py +++ b/src/op_mode/show_dhcp.py @@ -55,15 +55,28 @@ def get_lease_data(lease): return data def get_leases(leases, state=None, pool=None): + # define variable for leases + leases_dict = {} + + # get leases from file leases = IscDhcpLeases(lease_file).get() - if state is not None: - leases = list(filter(lambda x: x.binding_state == 'active', leases)) + # convert leases list to dictionary to avoid records duplication - it's the fastest and easiest way to do this + for lease in leases: + leases_dict[lease.ip] = lease + + # filter leases by state + if state is 'active': + leases = list(filter(lambda x: x.active and x.valid, leases_dict.values())) + if state is 'free': + leases = list(filter(lambda x: x.binding_state == 'free', leases_dict.values())) + # filter lease by pool name if pool is not None: leases = list(filter(lambda x: in_pool(x, pool), leases)) - return list(map(get_lease_data, leases)) + # return sorted leases list + return sorted(list(map(get_lease_data, leases)), key = lambda k: k['ip']) def show_leases(leases): headers = ["IP address", "Hardware address", "Lease expiration", "Pool", "Client Name"] @@ -73,7 +86,7 @@ def show_leases(leases): lease_list.append([l["ip"], l["hardware_address"], l["expires"], l["pool"], l["hostname"]]) output = tabulate.tabulate(lease_list, headers) - + print(output) def get_pool_size(config, pool): @@ -146,7 +159,7 @@ if __name__ == '__main__': leases = len(get_leases(lease_file, state='active', pool=args.pool)) if size != 0: - use_percentage = round(leases / size) * 100 + use_percentage = round(leases / size * 100) else: use_percentage = 0 -- cgit v1.2.3 From 89995dcec3a2a6b9112f11cf1c9e4a1bcb4d66d6 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Sun, 23 Jun 2019 17:01:12 +0200 Subject: T1470: improve output of "show dhcpv6 server leases" - change DUID to IAID_DUID - format IAID_DUID as colon-separated hex list - implement functions: pool, sort, state - add op-mode definitions for pool, sort, state - add columns: State, Type, Last communication, Pool - implement json output - implement completionHelp function --- op-mode-definitions/dhcp.xml | 29 ++++++++++++ src/conf_mode/dhcpv6_server.py | 3 ++ src/op_mode/show_dhcpv6.py | 104 ++++++++++++++++++++++++++++++++++------- 3 files changed, 120 insertions(+), 16 deletions(-) (limited to 'src/op_mode') diff --git a/op-mode-definitions/dhcp.xml b/op-mode-definitions/dhcp.xml index 85403af6f..989c8274a 100644 --- a/op-mode-definitions/dhcp.xml +++ b/op-mode-definitions/dhcp.xml @@ -59,6 +59,35 @@ Show DHCPv6 server leases sudo ${vyos_op_scripts_dir}/show_dhcpv6.py --leases + + + + Show DHCPv6 server leases for a specific pool + + + + + sudo ${vyos_op_scripts_dir}/show_dhcpv6.py --leases --pool $6 + + + + Show DHCPv6 server leases sorted by the specified key + + + + + sudo ${vyos_op_scripts_dir}/show_dhcpv6.py --leases --sort $6 + + + + Show DHCPv6 server leases with a specific state + + + + + sudo ${vyos_op_scripts_dir}/show_dhcpv6.py --leases --state $6 + + diff --git a/src/conf_mode/dhcpv6_server.py b/src/conf_mode/dhcpv6_server.py index 37da3ef25..f5117de53 100755 --- a/src/conf_mode/dhcpv6_server.py +++ b/src/conf_mode/dhcpv6_server.py @@ -101,6 +101,9 @@ shared-network {{ network.name }} { {%- endfor %} } {%- endfor %} + on commit { + set shared-networkname = "{{ network.name }}"; + } } {%- endif %} {% endfor %} diff --git a/src/op_mode/show_dhcpv6.py b/src/op_mode/show_dhcpv6.py index bf73b92ea..f1f5a6a55 100755 --- a/src/op_mode/show_dhcpv6.py +++ b/src/op_mode/show_dhcpv6.py @@ -20,11 +20,42 @@ import argparse import ipaddress import tabulate import sys +import collections from vyos.config import Config from isc_dhcp_leases import Lease, IscDhcpLeases lease_file = "/config/dhcpdv6.leases" +pool_key = "shared-networkname" + +lease_display_fields = collections.OrderedDict() +lease_display_fields['ip'] = 'IPv6 address' +lease_display_fields['state'] = 'State' +lease_display_fields['last_comm'] = 'Last communication' +lease_display_fields['expires'] = 'Lease expiration' +lease_display_fields['type'] = 'Type' +lease_display_fields['pool'] = 'Pool' +lease_display_fields['iaid_duid'] = 'IAID_DUID' + +lease_valid_states = ['all', 'active', 'free', 'expired', 'released', 'abandoned', 'reset', 'backup'] + +def in_pool(lease, pool): + if pool_key in lease.sets: + if lease.sets[pool_key] == pool: + return True + + return False + +def format_hex_string(in_str): + out_str = "" + + # if input is divisible by 2, add : every 2 chars + if len(in_str) > 0 and len(in_str) % 2 == 0: + out_str = ':'.join(a+b for a,b in zip(in_str[::2], in_str[1::2])) + else: + out_str = in_str + + return out_str def get_lease_data(lease): data = {} @@ -35,27 +66,55 @@ def get_lease_data(lease): except: data["expires"] = "" - data["duid"] = lease.host_identifier_string + try: + data["last_comm"] = lease.last_communication.strftime("%Y/%m/%d %H:%M:%S") + except: + data["last_comm"] = "" + + # isc-dhcp records lease declarations as ia_{na|ta|pd} IAID_DUID {...} + # where IAID_DUID is the combined IAID and DUID + data["iaid_duid"] = format_hex_string(lease.host_identifier_string) + + lease_types_long = {"na": "non-temporary", "ta": "temporary", "pd": "prefix delegation"} + data["type"] = lease_types_long[lease.type] + + data["state"] = lease.binding_state data["ip"] = lease.ip + try: + data["pool"] = lease.sets[pool_key] + except: + data["pool"] = "" + return data -def get_leases(leases, state=None): +def get_leases(leases, state, pool=None, sort='ip'): leases = IscDhcpLeases(lease_file).get() - if state is not None: - leases = list(filter(lambda x: x.binding_state == 'active', leases)) + if state != 'all': + leases = list(filter(lambda x: x.binding_state == state, leases)) - return list(map(get_lease_data, leases)) + # filter lease by pool name + if pool is not None: + leases = list(filter(lambda x: in_pool(x, pool), leases)) -def show_leases(leases): - headers = ["IPv6 address", "Lease expiration", "DUID"] + leases = list(map(get_lease_data, leases)) + if sort == 'ip': + leases = sorted(leases, key = lambda k: int(ipaddress.IPv6Address(k['ip']))) + else: + leases = sorted(leases, key = lambda k: k[sort]) + + return leases +def show_leases(leases): lease_list = [] for l in leases: - lease_list.append([l["ip"], l["expires"], l["duid"]]) + lease_list_params = [] + for k in lease_display_fields.keys(): + lease_list_params.append(l[k]) + lease_list.append(lease_list_params) - output = tabulate.tabulate(lease_list, headers) + output = tabulate.tabulate(lease_list, lease_display_fields.values()) print(output) @@ -63,11 +122,14 @@ if __name__ == '__main__': parser = argparse.ArgumentParser() group = parser.add_mutually_exclusive_group() - group.add_argument("-l", "--leases", action="store_true", help="Show DHCP leases") - group.add_argument("-s", "--statistics", action="store_true", help="Show DHCP statistics") + group.add_argument("-l", "--leases", action="store_true", help="Show DHCPv6 leases") + group.add_argument("-s", "--statistics", action="store_true", help="Show DHCPv6 statistics") + group.add_argument("--allowed", type=str, choices=["pool", "sort", "state"], help="Show allowed values for argument") - parser.add_argument("-p", "--pool", type=str, action="store", help="Show lease for specific pool") - parser.add_argument("-j", "--json", action="store_true", default=False, help="Product JSON output") + parser.add_argument("-p", "--pool", type=str, help="Show lease for specific pool") + parser.add_argument("-S", "--sort", type=str, choices=lease_display_fields.keys(), default='ip', help="Sort by") + parser.add_argument("-t", "--state", type=str, choices=lease_valid_states, default="active", help="Lease state to show") + parser.add_argument("-j", "--json", action="store_true", default=False, help="Produce JSON output") args = parser.parse_args() @@ -78,9 +140,19 @@ if __name__ == '__main__': sys.exit(0) if args.leases: - leases = get_leases(lease_file, state='active') - show_leases(leases) + leases = get_leases(lease_file, args.state, args.pool, args.sort) + + if args.json: + print(json.dumps(leases, indent=4)) + else: + show_leases(leases) elif args.statistics: print("DHCPv6 statistics option is not available") + elif args.allowed == 'pool': + print(' '.join(c.list_effective_nodes("service dhcpv6-server shared-network-name"))) + elif args.allowed == 'sort': + print(' '.join(lease_display_fields.keys())) + elif args.allowed == 'state': + print(' '.join(lease_valid_states)) else: - print("Invalid option") + parser.print_help() -- cgit v1.2.3 From 9f20bee81c0a0f4632aa152297d0fdf89139d6af Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Fri, 19 Jul 2019 02:28:53 +0200 Subject: T1376: improve show_dhcp and show_dhcpv6 --- op-mode-definitions/dhcp.xml | 32 ++++++++-- src/op_mode/show_dhcp.py | 143 +++++++++++++++++++++++++++++++------------ src/op_mode/show_dhcpv6.py | 61 +++++++++++++++--- 3 files changed, 183 insertions(+), 53 deletions(-) (limited to 'src/op_mode') diff --git a/op-mode-definitions/dhcp.xml b/op-mode-definitions/dhcp.xml index 989c8274a..f142cdd0e 100644 --- a/op-mode-definitions/dhcp.xml +++ b/op-mode-definitions/dhcp.xml @@ -9,7 +9,7 @@ - Show DHCP information + Show DHCP server information @@ -20,10 +20,31 @@ - Show DHCP leases for a specific pool + Show DHCP server leases for a specific pool + + + sudo ${vyos_op_scripts_dir}/show_dhcp.py --leases --pool $6 + + + Show DHCP server leases sorted by the specified key + + + + + sudo ${vyos_op_scripts_dir}/show_dhcp.py --leases --sort $6 + + + + Show DHCP server leases with a specific state (can be multiple, comma-separated) + + + + + sudo ${vyos_op_scripts_dir}/show_dhcp.py --leases --state $(echo $6 | tr , " ") + @@ -35,6 +56,9 @@ Show DHCP server statistics for a specific pool + + + sudo ${vyos_op_scripts_dir}/show_dhcp.py --statistics --pool $6 @@ -80,12 +104,12 @@ - Show DHCPv6 server leases with a specific state + Show DHCPv6 server leases with a specific state (can be multiple, comma-separated) - sudo ${vyos_op_scripts_dir}/show_dhcpv6.py --leases --state $6 + sudo ${vyos_op_scripts_dir}/show_dhcpv6.py --leases --state $(echo $6 | tr , " ") diff --git a/src/op_mode/show_dhcp.py b/src/op_mode/show_dhcp.py index 652173dc1..c2a05f516 100755 --- a/src/op_mode/show_dhcp.py +++ b/src/op_mode/show_dhcp.py @@ -20,6 +20,9 @@ import argparse import ipaddress import tabulate import sys +import collections +import os +from datetime import datetime from vyos.config import Config from isc_dhcp_leases import Lease, IscDhcpLeases @@ -27,6 +30,18 @@ from isc_dhcp_leases import Lease, IscDhcpLeases lease_file = "/config/dhcpd.leases" pool_key = "shared-networkname" +lease_display_fields = collections.OrderedDict() +lease_display_fields['ip'] = 'IP address' +lease_display_fields['hardware_address'] = 'Hardware address' +lease_display_fields['state'] = 'State' +lease_display_fields['start'] = 'Lease start' +lease_display_fields['end'] = 'Lease expiration' +lease_display_fields['remaining'] = 'Remaining' +lease_display_fields['pool'] = 'Pool' +lease_display_fields['hostname'] = 'Hostname' + +lease_valid_states = ['all', 'active', 'free', 'expired', 'released', 'abandoned', 'reset', 'backup'] + def in_pool(lease, pool): if pool_key in lease.sets: if lease.sets[pool_key] == pool: @@ -34,17 +49,47 @@ def in_pool(lease, pool): return False +def utc_to_local(utc_dt): + return datetime.fromtimestamp((utc_dt - datetime(1970,1,1)).total_seconds()) + def get_lease_data(lease): data = {} - # End time may not be present in backup leases + # isc-dhcp lease times are in UTC so we need to convert them to local time to display try: - data["expires"] = lease.end.strftime("%Y/%m/%d %H:%M:%S") + data["start"] = utc_to_local(lease.start).strftime("%Y/%m/%d %H:%M:%S") except: - data["expires"] = "" + data["start"] = "" + + try: + data["end"] = utc_to_local(lease.end).strftime("%Y/%m/%d %H:%M:%S") + except: + data["end"] = "" + + try: + data["remaining"] = lease.end - datetime.utcnow() + # negative timedelta prints wrong so bypass it + if (data["remaining"].days >= 0): + # substraction gives us a timedelta object which can't be formatted with strftime + # so we use str(), split gets rid of the microseconds + data["remaining"] = str(data["remaining"]).split('.')[0] + else: + data["remaining"] = "" + except: + data["remaining"] = "" + + # currently not used but might come in handy + # todo: parse into datetime string + for prop in ['tstp', 'tsfp', 'atsfp', 'cltt']: + if prop in lease.data: + data[prop] = lease.data[prop] + else: + data[prop] = '' data["hardware_address"] = lease.ethernet data["hostname"] = lease.hostname + + data["state"] = lease.binding_state data["ip"] = lease.ip try: @@ -54,38 +99,53 @@ def get_lease_data(lease): return data -def get_leases(leases, state=None, pool=None): - # define variable for leases - leases_dict = {} - +def get_leases(leases, state, pool=None, sort='ip'): # get leases from file leases = IscDhcpLeases(lease_file).get() - # convert leases list to dictionary to avoid records duplication - it's the fastest and easiest way to do this + # filter leases by state + if 'all' not in state: + leases = list(filter(lambda x: x.binding_state in state, leases)) + + # filter leases by pool name + if pool is not None: + if config.exists_effective("service dhcp-server shared-network-name {0}".format(pool)): + leases = list(filter(lambda x: in_pool(x, pool), leases)) + else: + print("Pool {0} does not exist.".format(pool)) + sys.exit(0) + + # should maybe filter all state=active by lease.valid here? + + # sort by start time to dedupe (newest lease overrides older) + leases = sorted(leases, key = lambda lease: lease.start) + + # dedupe by converting to dict + leases_dict = {} for lease in leases: + # dedupe by IP leases_dict[lease.ip] = lease - # filter leases by state - if state is 'active': - leases = list(filter(lambda x: x.active and x.valid, leases_dict.values())) - if state is 'free': - leases = list(filter(lambda x: x.binding_state == 'free', leases_dict.values())) + # convert the lease data + leases = list(map(get_lease_data, leases_dict.values())) - # filter lease by pool name - if pool is not None: - leases = list(filter(lambda x: in_pool(x, pool), leases)) + # apply output/display sort + if sort == 'ip': + leases = sorted(leases, key = lambda lease: int(ipaddress.ip_address(lease['ip']))) + else: + leases = sorted(leases, key = lambda lease: lease[sort]) - # return sorted leases list - return sorted(list(map(get_lease_data, leases)), key = lambda k: k['ip']) + return leases def show_leases(leases): - headers = ["IP address", "Hardware address", "Lease expiration", "Pool", "Client Name"] - lease_list = [] for l in leases: - lease_list.append([l["ip"], l["hardware_address"], l["expires"], l["pool"], l["hostname"]]) + lease_list_params = [] + for k in lease_display_fields.keys(): + lease_list_params.append(l[k]) + lease_list.append(lease_list_params) - output = tabulate.tabulate(lease_list, headers) + output = tabulate.tabulate(lease_list, lease_display_fields.values()) print(output) @@ -98,7 +158,7 @@ def get_pool_size(config, pool): start = config.return_effective_value("service dhcp-server shared-network-name {0} subnet {1} range {2} start".format(pool, s, r)) stop = config.return_effective_value("service dhcp-server shared-network-name {0} subnet {1} range {2} stop".format(pool, s, r)) - size += int(ipaddress.IPv4Address(stop)) - int(ipaddress.IPv4Address(start)) + size += int(ipaddress.ip_address(stop)) - int(ipaddress.ip_address(start)) return size @@ -114,35 +174,33 @@ if __name__ == '__main__': group = parser.add_mutually_exclusive_group() group.add_argument("-l", "--leases", action="store_true", help="Show DHCP leases") group.add_argument("-s", "--statistics", action="store_true", help="Show DHCP statistics") + group.add_argument("--allowed", type=str, choices=["pool", "sort", "state"], help="Show allowed values for argument") - parser.add_argument("-e", "--expired", action="store_true", help="Show expired leases") - parser.add_argument("-p", "--pool", type=str, action="store", help="Show lease for specific pool") - parser.add_argument("-j", "--json", action="store_true", default=False, help="Product JSON output") + parser.add_argument("-p", "--pool", type=str, help="Show lease for specific pool") + parser.add_argument("-S", "--sort", type=str, choices=lease_display_fields.keys(), default='ip', help="Sort by") + parser.add_argument("-t", "--state", type=str, nargs="+", choices=lease_valid_states, default="active", help="Lease state to show (can specify multiple with spaces)") + parser.add_argument("-j", "--json", action="store_true", default=False, help="Produce JSON output") args = parser.parse_args() # Do nothing if service is not configured config = Config() if not config.exists_effective('service dhcp-server'): - print("DHCP service is not configured") + print("DHCP service is not configured.") sys.exit(0) + # if dhcp server is down, inactive leases may still be shown as active, so warn the user. + if os.system('systemctl -q is-active isc-dhcp-server.service') != 0: + print("WARNING: DHCP server is configured but not started. Data may be stale.") + if args.leases: - if args.expired: - if args.pool: - leases = get_leases(lease_file, state='free', pool=args.pool) - else: - leases = get_leases(lease_file, state='free') - else: - if args.pool: - leases = get_leases(lease_file, state='active', pool=args.pool) - else: - leases = get_leases(lease_file, state='active') + leases = get_leases(lease_file, args.state, args.pool, args.sort) if args.json: print(json.dumps(leases, indent=4)) else: show_leases(leases) + elif args.statistics: pools = [] @@ -156,7 +214,7 @@ if __name__ == '__main__': stats = [] for p in pools: size = get_pool_size(config, p) - leases = len(get_leases(lease_file, state='active', pool=args.pool)) + leases = len(get_leases(lease_file, state='active', pool=p)) if size != 0: use_percentage = round(leases / size * 100) @@ -176,5 +234,12 @@ if __name__ == '__main__': print(json.dumps(stats, indent=4)) else: show_pool_stats(stats) + + elif args.allowed == 'pool': + print(' '.join(config.list_effective_nodes("service dhcp-server shared-network-name"))) + elif args.allowed == 'sort': + print(' '.join(lease_display_fields.keys())) + elif args.allowed == 'state': + print(' '.join(lease_valid_states)) else: - print("Use either --leases or --statistics option") + parser.print_help() diff --git a/src/op_mode/show_dhcpv6.py b/src/op_mode/show_dhcpv6.py index f1f5a6a55..1a6ee62e6 100755 --- a/src/op_mode/show_dhcpv6.py +++ b/src/op_mode/show_dhcpv6.py @@ -21,6 +21,8 @@ import ipaddress import tabulate import sys import collections +import os +from datetime import datetime from vyos.config import Config from isc_dhcp_leases import Lease, IscDhcpLeases @@ -33,6 +35,7 @@ lease_display_fields['ip'] = 'IPv6 address' lease_display_fields['state'] = 'State' lease_display_fields['last_comm'] = 'Last communication' lease_display_fields['expires'] = 'Lease expiration' +lease_display_fields['remaining'] = 'Remaining' lease_display_fields['type'] = 'Type' lease_display_fields['pool'] = 'Pool' lease_display_fields['iaid_duid'] = 'IAID_DUID' @@ -57,20 +60,35 @@ def format_hex_string(in_str): return out_str +def utc_to_local(utc_dt): + return datetime.fromtimestamp((utc_dt - datetime(1970,1,1)).total_seconds()) + def get_lease_data(lease): data = {} - # End time may not be present in backup leases + # isc-dhcp lease times are in UTC so we need to convert them to local time to display try: - data["expires"] = lease.end.strftime("%Y/%m/%d %H:%M:%S") + data["expires"] = utc_to_local(lease.end).strftime("%Y/%m/%d %H:%M:%S") except: data["expires"] = "" try: - data["last_comm"] = lease.last_communication.strftime("%Y/%m/%d %H:%M:%S") + data["last_comm"] = utc_to_local(lease.last_communication).strftime("%Y/%m/%d %H:%M:%S") except: data["last_comm"] = "" + try: + data["remaining"] = lease.end - datetime.utcnow() + # negative timedelta prints wrong so bypass it + if (data["remaining"].days >= 0): + # substraction gives us a timedelta object which can't be formatted with strftime + # so we use str(), split gets rid of the microseconds + data["remaining"] = str(data["remaining"]).split('.')[0] + else: + data["remaining"] = "" + except: + data["remaining"] = "" + # isc-dhcp records lease declarations as ia_{na|ta|pd} IAID_DUID {...} # where IAID_DUID is the combined IAID and DUID data["iaid_duid"] = format_hex_string(lease.host_identifier_string) @@ -91,16 +109,35 @@ def get_lease_data(lease): def get_leases(leases, state, pool=None, sort='ip'): leases = IscDhcpLeases(lease_file).get() - if state != 'all': - leases = list(filter(lambda x: x.binding_state == state, leases)) + # filter leases by state + if 'all' not in state: + leases = list(filter(lambda x: x.binding_state in state, leases)) - # filter lease by pool name + # filter leases by pool name if pool is not None: - leases = list(filter(lambda x: in_pool(x, pool), leases)) + if config.exists_effective("service dhcp-server shared-network-name {0}".format(pool)): + leases = list(filter(lambda x: in_pool(x, pool), leases)) + else: + print("Pool {0} does not exist.".format(pool)) + sys.exit(0) - leases = list(map(get_lease_data, leases)) + # should maybe filter all state=active by lease.valid here? + + # sort by last_comm time to dedupe (newest lease overrides older) + leases = sorted(leases, key = lambda lease: lease.last_communication) + + # dedupe by converting to dict + leases_dict = {} + for lease in leases: + # dedupe by IP + leases_dict[lease.ip] = lease + + # convert the lease data + leases = list(map(get_lease_data, leases_dict.values())) + + # apply output/display sort if sort == 'ip': - leases = sorted(leases, key = lambda k: int(ipaddress.IPv6Address(k['ip']))) + leases = sorted(leases, key = lambda k: int(ipaddress.ip_address(k['ip']))) else: leases = sorted(leases, key = lambda k: k[sort]) @@ -128,7 +165,7 @@ if __name__ == '__main__': parser.add_argument("-p", "--pool", type=str, help="Show lease for specific pool") parser.add_argument("-S", "--sort", type=str, choices=lease_display_fields.keys(), default='ip', help="Sort by") - parser.add_argument("-t", "--state", type=str, choices=lease_valid_states, default="active", help="Lease state to show") + parser.add_argument("-t", "--state", type=str, nargs="+", choices=lease_valid_states, default="active", help="Lease state to show (can specify multiple with spaces)") parser.add_argument("-j", "--json", action="store_true", default=False, help="Produce JSON output") args = parser.parse_args() @@ -139,6 +176,10 @@ if __name__ == '__main__': print("DHCPv6 service is not configured") sys.exit(0) + # if dhcp server is down, inactive leases may still be shown as active, so warn the user. + if os.system('systemctl -q is-active isc-dhcpv6-server.service') != 0: + print("WARNING: DHCPv6 server is configured but not started. Data may be stale.") + if args.leases: leases = get_leases(lease_file, args.state, args.pool, args.sort) -- cgit v1.2.3