From 2e5ea7a8304ed6ce504b3928e3b2f52574a61397 Mon Sep 17 00:00:00 2001 From: Ruben Herold Date: Mon, 31 Aug 2026 12:49:24 +0200 Subject: dhcp-server: T9267: add client-class matching on hostname/vendor-class-id Client classes could previously only match on relay agent information (Option 82). Add matching on the client-sent hostname (Option 12) and vendor class identifier (Option 60), each with an exact-value or substring form, so clients sharing a subnet can be told apart by e.g. device model without splitting the subnet into per-model pools. Also let a client-class deliver its own options (reusing the existing subnet/range option syntax), so a match can directly hand out a model-specific value such as a provisioning URL, independent of which pool the client's lease is drawn from. --- python/vyos/kea.py | 35 +++++++++++++++++++++++++++++++---- python/vyos/template.py | 10 ++++++++++ 2 files changed, 41 insertions(+), 4 deletions(-) (limited to 'python') diff --git a/python/vyos/kea.py b/python/vyos/kea.py index 436c134c8..e42bb6e1c 100644 --- a/python/vyos/kea.py +++ b/python/vyos/kea.py @@ -725,11 +725,32 @@ def kea_get_server_leases( return data -def _build_relay_hex_condition(sub_option_index, value): +def _kea_hex_literal(value): if value.startswith('0x'): - return f'relay4[{sub_option_index}].hex == {value}' - else: - return f'relay4[{sub_option_index}].hex == 0x{value.encode().hex().lower()}' + return value + return f'0x{value.encode().hex().lower()}' + + +def _build_hex_condition(kea_path, value): + return f'{kea_path}.hex == {_kea_hex_literal(value)}' + + +def _build_relay_hex_condition(sub_option_index, value): + return _build_hex_condition(f'relay4[{sub_option_index}]', value) + + +def _build_option_match_condition(option_number, match_config): + kea_path = f'option[{option_number}]' + + if 'substring' in match_config: + substring_config = match_config['substring'] + offset = substring_config.get('offset', '0') + value_hex = _kea_hex_literal(substring_config['value']) + # length is expressed in bytes; a hex literal is '0x' + 2 hex digits per byte + length = substring_config.get('length') or str((len(value_hex) - 2) // 2) + return f'substring({kea_path}.hex, {offset}, {length}) == {value_hex}' + + return _build_hex_condition(kea_path, match_config['value']) def kea_build_client_class_test(config): @@ -749,6 +770,12 @@ def kea_build_client_class_test(config): ) ) + if 'hostname' in config: + conditions.append(_build_option_match_condition(12, config['hostname'])) + + if 'vendor_class_id' in config: + conditions.append(_build_option_match_condition(60, config['vendor_class_id'])) + test = ' and '.join(conditions) return test diff --git a/python/vyos/template.py b/python/vyos/template.py index 2c07d79da..6323657ed 100755 --- a/python/vyos/template.py +++ b/python/vyos/template.py @@ -982,6 +982,7 @@ def kea_high_availability_json(config): @register_filter('kea_client_class_json') def kea_client_class_json(client_classes): from vyos.kea import kea_build_client_class_test + from vyos.kea import kea_parse_options from json import dumps out = [] @@ -994,6 +995,15 @@ def kea_client_class_json(client_classes): 'test': kea_build_client_class_test(config) } + if 'option' in config: + client_class['option-data'] = kea_parse_options(config['option']) + + if 'bootfile_name' in config['option']: + client_class['boot-file-name'] = config['option']['bootfile_name'] + + if 'bootfile_server' in config['option']: + client_class['next-server'] = config['option']['bootfile_server'] + out.append(client_class) return dumps(out, indent=4) -- cgit v1.2.3