summaryrefslogtreecommitdiff
path: root/python
diff options
context:
space:
mode:
authorRuben Herold <ruben@puettmann.net>2026-09-04 11:17:26 +0200
committerViacheslav Hletenko <seversss265@gmail.com>2026-10-15 16:05:57 +0300
commit46e06d8fd1f1dd378c128fe87549bdba165f640a (patch)
treebcdc5e8b15a3bbe20e57f66a340483d5b85be973 /python
parentda0bea0d10020bfd76d2f763965947db905cab9b (diff)
downloadvyos-1x-46e06d8fd1f1dd378c128fe87549bdba165f640a.tar.gz
vyos-1x-46e06d8fd1f1dd378c128fe87549bdba165f640a.zip
dhcp-server: T9267: make client-class substring matching contains-anywhere
The Mikrotik configuration referenced by T9267 uses matching-type=substring, documented to match the value anywhere within the option string. This PR's "substring" match instead compared a fixed-length slice at a fixed byte offset (default 0) for exact equality - effectively an anchored prefix match, not a substring search - which does not reliably reproduce the ticket's own use case of picking a device model string (e.g. "T42S") out of a longer, variably-formatted hostname. Rebuild "hostname substring"/"vendor-class-id substring" on Kea's match()+lcase() instead of substring()==, so a configured value now matches anywhere in the option content and case-insensitively (options like vendor-class-id are not guaranteed to arrive in a fixed case). Drop the "offset" leaf together with it, since it has no remaining purpose once matching is no longer anchored to a fixed position, and there is no released behavior to preserve. Raw hex input and values containing a literal single quote are rejected for substring matching in verify(), since neither can be safely embedded into the generated Kea regular expression; exact "value" matching is unaffected and still accepts both. Reported by alexk37 in review of PR #5436.
Diffstat (limited to 'python')
-rw-r--r--python/vyos/kea.py21
1 files changed, 13 insertions, 8 deletions
diff --git a/python/vyos/kea.py b/python/vyos/kea.py
index 71ec82045..268640fcf 100644
--- a/python/vyos/kea.py
+++ b/python/vyos/kea.py
@@ -739,18 +739,23 @@ def _build_relay_hex_condition(sub_option_index, value):
return _build_hex_condition(f'relay4[{sub_option_index}]', value)
+# characters with special meaning in Kea's (PCRE-like) match() regex syntax
+_KEA_REGEX_SPECIAL = re.compile(r'([.^$*+?()\[\]{}|\\])')
+
+
+def _build_contains_condition(kea_path, value):
+ # match() searches case-sensitively, so both sides are lower-cased;
+ # the option content is unknown at config-render time, so it has to be
+ # lower-cased by the expression itself via lcase()
+ pattern = _KEA_REGEX_SPECIAL.sub(r'\\\1', value.lower())
+ return f"match('.*{pattern}.*', lcase({kea_path}.hex))"
+
+
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, rounded up since Kea itself pads an odd number of hex digits
- # with a leading zero
- length = str((len(value_hex) - 1) // 2)
- return f'substring({kea_path}.hex, {offset}, {length}) == {value_hex}'
+ return _build_contains_condition(kea_path, match_config['substring']['value'])
return _build_hex_condition(kea_path, match_config['value'])