diff options
author | Christian Breunig <christian@breunig.cc> | 2024-02-13 17:36:39 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-02-13 17:36:39 +0100 |
commit | bae2b7c506223d59cc136123444b22e10bea6c0b (patch) | |
tree | eb8762be0fb824daa96f385f265f99907c03dff0 | |
parent | 83bf14e34a986ba35904f3889341310f85ae88c4 (diff) | |
parent | dbfaa47b0036339e28ae8fd57c8007612e1b38f3 (diff) | |
download | vyos-1x-bae2b7c506223d59cc136123444b22e10bea6c0b.tar.gz vyos-1x-bae2b7c506223d59cc136123444b22e10bea6c0b.zip |
Merge pull request #2998 from sarthurdev/T5992
dhcpv6-server: T5992: Fix op-mode DHCP lease output + updates
-rw-r--r-- | op-mode-definitions/dhcp.xml.in | 54 | ||||
-rw-r--r-- | python/vyos/kea.py | 35 | ||||
-rwxr-xr-x | src/op_mode/dhcp.py | 93 |
3 files changed, 142 insertions, 40 deletions
diff --git a/op-mode-definitions/dhcp.xml.in b/op-mode-definitions/dhcp.xml.in index ceb321f3e..0db7471e5 100644 --- a/op-mode-definitions/dhcp.xml.in +++ b/op-mode-definitions/dhcp.xml.in @@ -80,6 +80,32 @@ </tagNode> </children> </node> + <node name="static-mappings"> + <properties> + <help>Show DHCP server static mappings</help> + </properties> + <command>${vyos_op_scripts_dir}/dhcp.py show_server_static_mappings --family inet</command> + <children> + <tagNode name="pool"> + <properties> + <help>Show DHCP server static mappings for a specific pool</help> + <completionHelp> + <path>service dhcp-server shared-network-name</path> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/dhcp.py show_server_static_mappings --family inet --pool $6</command> + </tagNode> + <tagNode name="sort"> + <properties> + <help>Show DHCP server static mappings sorted by the specified key</help> + <completionHelp> + <list>ip mac duid pool</list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/dhcp.py show_server_static_mappings --family inet --sort $6</command> + </tagNode> + </children> + </node> <node name="statistics"> <properties> <help>Show DHCP server statistics</help> @@ -130,7 +156,7 @@ <properties> <help>Show DHCPv6 server leases sorted by the specified key</help> <completionHelp> - <list>end iaid_duid ip last_communication pool remaining state type</list> + <list>end duid ip last_communication pool remaining state type</list> </completionHelp> </properties> <command>${vyos_op_scripts_dir}/dhcp.py show_server_leases --family inet6 --sort $6</command> @@ -146,6 +172,32 @@ </tagNode> </children> </node> + <node name="static-mappings"> + <properties> + <help>Show DHCPv6 server static mappings</help> + </properties> + <command>${vyos_op_scripts_dir}/dhcp.py show_server_static_mappings --family inet6</command> + <children> + <tagNode name="pool"> + <properties> + <help>Show DHCPv6 server static mappings for a specific pool</help> + <completionHelp> + <path>service dhcp-server shared-network-name</path> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/dhcp.py show_server_static_mappings --family inet6 --pool $6</command> + </tagNode> + <tagNode name="sort"> + <properties> + <help>Show DHCPv6 server static mappings sorted by the specified key</help> + <completionHelp> + <list>ip mac duid pool</list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/dhcp.py show_server_static_mappings --family inet6 --sort $6</command> + </tagNode> + </children> + </node> </children> </node> </children> diff --git a/python/vyos/kea.py b/python/vyos/kea.py index 7365c1f02..894ac9e9a 100644 --- a/python/vyos/kea.py +++ b/python/vyos/kea.py @@ -17,8 +17,6 @@ import json import os import socket -from datetime import datetime - from vyos.template import is_ipv6 from vyos.template import isc_static_route from vyos.template import netmask_from_cidr @@ -293,29 +291,6 @@ def kea6_parse_subnet(subnet, config): return out -def kea_parse_leases(lease_path): - contents = read_file(lease_path) - lines = contents.split("\n") - output = [] - - if len(lines) < 2: - return output - - headers = lines[0].split(",") - - for line in lines[1:]: - line_out = dict(zip(headers, line.split(","))) - - lifetime = int(line_out['valid_lifetime']) - expiry = int(line_out['expire']) - - line_out['start_timestamp'] = datetime.utcfromtimestamp(expiry - lifetime) - line_out['expire_timestamp'] = datetime.utcfromtimestamp(expiry) if expiry else None - - output.append(line_out) - - return output - def _ctrl_socket_command(path, command, args=None): if not os.path.exists(path): return None @@ -340,6 +315,16 @@ def _ctrl_socket_command(path, command, args=None): return json.loads(result.decode('utf-8')) +def kea_get_leases(inet): + ctrl_socket = f'/run/kea/dhcp{inet}-ctrl-socket' + + leases = _ctrl_socket_command(ctrl_socket, f'lease{inet}-get-all') + + if not leases or 'result' not in leases or leases['result'] != 0: + return [] + + return leases['arguments']['leases'] + def kea_get_active_config(inet): ctrl_socket = f'/run/kea/dhcp{inet}-ctrl-socket' diff --git a/src/op_mode/dhcp.py b/src/op_mode/dhcp.py index a64acec31..1d9ad0e76 100755 --- a/src/op_mode/dhcp.py +++ b/src/op_mode/dhcp.py @@ -29,8 +29,8 @@ from vyos.base import Warning from vyos.configquery import ConfigTreeQuery from vyos.kea import kea_get_active_config +from vyos.kea import kea_get_leases from vyos.kea import kea_get_pool_from_subnet_id -from vyos.kea import kea_parse_leases from vyos.utils.process import is_systemd_service_running time_string = "%a %b %d %H:%M:%S %Z %Y" @@ -38,7 +38,8 @@ time_string = "%a %b %d %H:%M:%S %Z %Y" config = ConfigTreeQuery() lease_valid_states = ['all', 'active', 'free', 'expired', 'released', 'abandoned', 'reset', 'backup'] sort_valid_inet = ['end', 'mac', 'hostname', 'ip', 'pool', 'remaining', 'start', 'state'] -sort_valid_inet6 = ['end', 'iaid_duid', 'ip', 'last_communication', 'pool', 'remaining', 'state', 'type'] +sort_valid_inet6 = ['end', 'duid', 'ip', 'last_communication', 'pool', 'remaining', 'state', 'type'] +mapping_sort_valid = ['mac', 'ip', 'pool', 'duid'] ArgFamily = typing.Literal['inet', 'inet6'] ArgState = typing.Literal['all', 'active', 'free', 'expired', 'released', 'abandoned', 'reset', 'backup'] @@ -77,8 +78,7 @@ def _get_raw_server_leases(family='inet', pool=None, sorted=None, state=[], orig :return list """ inet_suffix = '6' if family == 'inet6' else '4' - lease_file = f'/config/dhcp/dhcp{inet_suffix}-leases.csv' - leases = kea_parse_leases(lease_file) + leases = kea_get_leases(inet_suffix) if pool is None: pool = _get_dhcp_pools(family=family) @@ -89,28 +89,37 @@ def _get_raw_server_leases(family='inet', pool=None, sorted=None, state=[], orig data = [] for lease in leases: + lifetime = lease['valid-lft'] + expiry = (lease['cltt'] + lifetime) + + lease['start_timestamp'] = datetime.utcfromtimestamp(expiry - lifetime) + lease['expire_timestamp'] = datetime.utcfromtimestamp(expiry) if expiry else None + data_lease = {} - data_lease['ip'] = lease['address'] - lease_state_long = {'0': 'active', '1': 'rejected', '2': 'expired'} + data_lease['ip'] = lease['ip-address'] + lease_state_long = {0: 'active', 1: 'rejected', 2: 'expired'} data_lease['state'] = lease_state_long[lease['state']] - data_lease['pool'] = kea_get_pool_from_subnet_id(active_config, inet_suffix, lease['subnet_id']) if active_config else '-' + data_lease['pool'] = kea_get_pool_from_subnet_id(active_config, inet_suffix, lease['subnet-id']) if active_config else '-' data_lease['end'] = lease['expire_timestamp'].timestamp() if lease['expire_timestamp'] else None data_lease['origin'] = 'local' # TODO: Determine remote in HA if family == 'inet': - data_lease['mac'] = lease['hwaddr'] + data_lease['mac'] = lease['hw-address'] data_lease['start'] = lease['start_timestamp'].timestamp() data_lease['hostname'] = lease['hostname'] if family == 'inet6': data_lease['last_communication'] = lease['start_timestamp'].timestamp() - data_lease['iaid_duid'] = _format_hex_string(lease['duid']) - lease_types_long = {'0': 'non-temporary', '1': 'temporary', '2': 'prefix delegation'} - data_lease['type'] = lease_types_long[lease['lease_type']] + data_lease['duid'] = _format_hex_string(lease['duid']) + data_lease['type'] = lease['type'] + + if lease['type'] == 'IA_PD': + prefix_len = lease['prefix-len'] + data_lease['ip'] += f'/{prefix_len}' data_lease['remaining'] = '-' - if lease['expire']: + if lease['valid-lft'] > 0: data_lease['remaining'] = lease['expire_timestamp'] - datetime.utcnow() if data_lease['remaining'].days >= 0: @@ -172,11 +181,11 @@ def _get_formatted_server_leases(raw_data, family='inet'): remain = lease.get('remaining') lease_type = lease.get('type') pool = lease.get('pool') - host_identifier = lease.get('iaid_duid') + host_identifier = lease.get('duid') data_entries.append([ipaddr, state, start, end, remain, lease_type, pool, host_identifier]) headers = ['IPv6 address', 'State', 'Last communication', 'Lease expiration', 'Remaining', 'Type', 'Pool', - 'IAID_DUID'] + 'DUID'] output = tabulate(data_entries, headers, numalign='left') return output @@ -241,6 +250,47 @@ def _get_formatted_pool_statistics(pool_data, family='inet'): output = tabulate(data_entries, headers, numalign='left') return output +def _get_raw_server_static_mappings(family='inet', pool=None, sorted=None): + if pool is None: + pool = _get_dhcp_pools(family=family) + else: + pool = [pool] + + v = 'v6' if family == 'inet6' else '' + mappings = [] + for p in pool: + pool_config = config.get_config_dict(['service', f'dhcp{v}-server', 'shared-network-name', p], + get_first_key=True) + if 'subnet' in pool_config: + for subnet, subnet_config in pool_config['subnet'].items(): + if 'static-mapping' in subnet_config: + for name, mapping_config in subnet_config['static-mapping'].items(): + mapping = {'pool': p, 'subnet': subnet, 'name': name} + mapping.update(mapping_config) + mappings.append(mapping) + + if sorted: + if sorted == 'ip': + data.sort(key = lambda x:ip_address(x['ip-address'])) + else: + data.sort(key = lambda x:x[sorted]) + return mappings + +def _get_formatted_server_static_mappings(raw_data, family='inet'): + data_entries = [] + for entry in raw_data: + pool = entry.get('pool') + subnet = entry.get('subnet') + name = entry.get('name') + ip_addr = entry.get('ip-address', 'N/A') + mac_addr = entry.get('mac', 'N/A') + duid = entry.get('duid', 'N/A') + description = entry.get('description', 'N/A') + data_entries.append([pool, subnet, name, ip_addr, mac_addr, duid, description]) + + headers = ['Pool', 'Subnet', 'Name', 'IP Address', 'MAC Address', 'DUID', 'Description'] + output = tabulate(data_entries, headers, numalign='left') + return output def _verify(func): """Decorator checks if DHCP(v6) config exists""" @@ -294,6 +344,21 @@ def show_server_leases(raw: bool, family: ArgFamily, pool: typing.Optional[str], else: return _get_formatted_server_leases(lease_data, family=family) +@_verify +def show_server_static_mappings(raw: bool, family: ArgFamily, pool: typing.Optional[str], + sorted: typing.Optional[str]): + v = 'v6' if family == 'inet6' else '' + if pool and pool not in _get_dhcp_pools(family=family): + raise vyos.opmode.IncorrectValue(f'DHCP{v} pool "{pool}" does not exist!') + + if sorted and sorted not in mapping_sort_valid: + raise vyos.opmode.IncorrectValue(f'DHCP{v} sort "{sorted}" is invalid!') + + static_mappings = _get_raw_server_static_mappings(family=family, pool=pool, sorted=sorted) + if raw: + return static_mappings + else: + return _get_formatted_server_static_mappings(static_mappings, family=family) def _get_raw_client_leases(family='inet', interface=None): from time import mktime |