diff --git a/src/confd/src/infix-dhcp-client.c b/src/confd/src/infix-dhcp-client.c index bf2e7b8a..a095a0f7 100644 --- a/src/confd/src/infix-dhcp-client.c +++ b/src/confd/src/infix-dhcp-client.c @@ -71,9 +71,9 @@ static char *hostname(struct lyd_node *cfg, char *str, size_t len) return str; } -static char *fqdn(const char *value, char *str, size_t len) +static char *fqdn(const char *val, char *str, size_t len) { - snprintf(str, len, "-F \"%s\" ", value); + snprintf(str, len, "-F \"%s\" ", val); return str; } @@ -104,26 +104,8 @@ static char *os_name_version(char *str, size_t len) return str; } -static bool is_hex(const char *s) -{ - while (s[0] && s[1]) { - if (!isxdigit(s[0]) || !isxdigit(s[1])) - return false; - - s += 2; - if (*s == '\0') - return true; - - if (*s != ':') - return false; - s++; - } - - return false; -} - static char *compose_option(struct lyd_node *cfg, const char *ifname, struct lyd_node *id, - const char *value, char *option, size_t len) + const char *val, const char *hex, char *option, size_t len) { const char *name = lyd_get_value(id); int num = dhcp_option_lookup(id); @@ -133,22 +115,24 @@ static char *compose_option(struct lyd_node *cfg, const char *ifname, struct lyd return NULL; } - if (value) { + if (val || hex) { switch (num) { case 81: /* fqdn */ - return fqdn(value, option, len); + if (!val) + return NULL; + return fqdn(val, option, len); case 12: /* hostname */ - if (!strcmp(value, "auto")) + if (val && !strcmp(val, "auto")) return hostname(cfg, option, len); /* fallthrough */ default: - if (is_hex(value)) { + if (hex) { snprintf(option, len, "-x %d:", num); - strlcat(option, value, len); + strlcat(option, hex, len); strlcat(option, " ", len); } else { /* string value */ - snprintf(option, len, "-x %d:'\"%s\"' ", num, value); + snprintf(option, len, "-x %d:'\"%s\"' ", num, val); } break; } @@ -175,11 +159,11 @@ static char *compose_option(struct lyd_node *cfg, const char *ifname, struct lyd } static char *compose_options(struct lyd_node *cfg, const char *ifname, char **options, - struct lyd_node *id, const char *value) + struct lyd_node *id, const char *val, const char *hex) { char opt[300]; - if (!compose_option(cfg, ifname, id, value, opt, sizeof(opt))) + if (!compose_option(cfg, ifname, id, val, hex, opt, sizeof(opt))) return *options; if (*options) { @@ -220,8 +204,9 @@ static char *dhcp_options(const char *ifname, struct lyd_node *cfg) LYX_LIST_FOR_EACH(lyd_child(cfg), option, "option") { struct lyd_node *id = lydx_get_child(option, "id"); const char *val = lydx_get_cattr(option, "value"); + const char *hex = lydx_get_cattr(option, "hex"); - options = compose_options(cfg, ifname, &options, id, val); + options = compose_options(cfg, ifname, &options, id, val, hex); } return options ?: fallback_options(ifname); @@ -249,16 +234,11 @@ static void add(const char *ifname, struct lyd_node *cfg) goto generr; strlcpy(cid, "-C -x 61:00", len); - if (is_hex(client_id)) { - strlcat(cid, ":", len); - strlcat(cid, client_id, len); - } else { - for (size_t i = 0; client_id[i]; i++) { - char hex[5]; + for (size_t i = 0; client_id[i]; i++) { + char hex[5]; - snprintf(hex, sizeof(hex), ":%02x", client_id[i]); - strlcat(cid, hex, len); - } + snprintf(hex, sizeof(hex), ":%02x", client_id[i]); + strlcat(cid, hex, len); } } diff --git a/src/confd/src/infix-dhcp-server.c b/src/confd/src/infix-dhcp-server.c index 1f2e866a..23f7b285 100644 --- a/src/confd/src/infix-dhcp-server.c +++ b/src/confd/src/infix-dhcp-server.c @@ -84,6 +84,7 @@ static int configure_options(FILE *fp, struct lyd_node *cfg, const char *tag) */ val = lydx_get_cattr(option, "name") ?: lydx_get_cattr(option, "string") + ?: lydx_get_cattr(option, "hex") ?: NULL; if (!val) { val = lydx_get_cattr(option, "address"); @@ -118,7 +119,7 @@ static const char *host_match(struct lyd_node *match, const char **id) { struct { const char *key; - const char *prefix; + const char *prefix; /* dnsmasq prefix */ } choice[] = { { "mac-address", NULL }, { "hostname", NULL }, @@ -129,14 +130,25 @@ static const char *host_match(struct lyd_node *match, const char **id) return NULL; for (size_t i = 0; i < NELEMS(choice); i++) { - struct lyd_node *node; + struct lyd_node *node, *sub; + const char *value; node = lydx_get_child(match, choice[i].key); if (!node) continue; *id = choice[i].prefix; - return lyd_get_value(node); + value = lyd_get_value(node); + if (value) + return value; + + /* The client-id setting is has a qualifier */ + sub = lydx_get_child(node, "str"); + if (sub) + return lyd_get_value(sub); + sub = lydx_get_child(node, "hex"); + if (sub) + return lyd_get_value(sub); } return NULL; diff --git a/src/confd/yang/infix-dhcp-client.yang b/src/confd/yang/infix-dhcp-client.yang index 1a576cb9..3d01aeb4 100644 --- a/src/confd/yang/infix-dhcp-client.yang +++ b/src/confd/yang/infix-dhcp-client.yang @@ -52,55 +52,120 @@ module infix-dhcp-client { */ container dhcp-client { - description - "DHCPv4 client configuration"; + description "DHCPv4 client configuration"; + leaf enabled { type boolean; default "true"; description "Globally enables the DHCP client function."; } + list client-if { - key "if-name"; description "List of interfaces requesting DHCPv4 configuration."; + key "if-name"; + leaf if-name { type if:interface-ref; mandatory true; description "Name of the interface."; } + leaf enabled { type boolean; default "true"; description "Enable DHCP client for this interface."; } + leaf client-id { type string; - description "Optional Client ID, option 61, default: MAC address."; + description "Optional Client ID, option 61, RFC 2132. + + When omitted the client sends its MAC address as client-id. + + RFC 2132 defines client-id as [ OPT | LEN | TYPE | DATA ], + where TYPE is 0x01 for MAC address and 0x00 for anything + else. Servers usually just do a binary match of the data + using the length field. This particular setting encodes + the value as an ASCII string without trailing zero. + + For full control of both type and data fields, use generic + option list instead, which supports entering raw HEX data."; } + leaf arping { type boolean; default "true"; description "ARP for lease to check for IP address collisions (slow)."; } + list option { key "id"; - description - "List of DHCP options to request (and accept). The default is an - empty list, meaning all supported options. To restrict the - client to only get IP address and default route, set this to: - 'subnet router'"; + description "List of DHCP options to request (and accept). + + The default is an empty list, meaning all supported options. To + restrict the client to only get IP address and default route, set + this to: 'subnet router'"; + + must "not(id = 'fqdn' and 'value/hex')" { + error-message "FQDN option must use string format"; + error-app-tag "invalid-fqdn-format"; + } + leaf id { type dhcp:options; description "DHCP option to request from, or inform server of."; } - leaf value { - type string; - description "Optional value, only used for non-flag request options. - Example: option:hostname, value:xyzzy - option:clientid, value:01:02:03:04:05:06:07:08:09:0a - option:0x51, value:xyzzy.example.com"; - must "../id != 'hostname' or re-match(., '[a-zA-Z0-9\\-_]{1,64}')"; + + choice value { + case value { + leaf value { + description "Optional value, to inform server, e.g., hostname. + + Example 1) + + option:12, value:xyzzy + + Example 2) + + option:hostname, value:xyzzy + + Will be sent as [12 | 05 | 78 | 79 | 80 | 80 | 79], + which the server can use for static host matching. + For the 'hostname' option the 'auto' keyword can be + used to send the hostname part from IETF system."; + type string; + must "../id != 'hostname' or re-match(., '[a-zA-Z0-9\\-_]{1,64}')"; + } + } + case hex { + leaf hex { + description "Optional binary value, to inform server, e.g., client-id. + + This is a raw payload option allowing full control of + what is sent in an option. Often used with options 61 + and option 43 (vendor-specific). + + Example 1) + + option:client-id, hex:00:c0:ff:ee + + Will be sent as : [61 | 03 | 00 | c0 | ff | ee], which + is the RFC conformant formatting of option 61. + + Example 2) + + option:client-id, hex:c0:ff:ee + + Will be sent as : [61 | 03 | c0 | ff | ee] meaning the + 'htype' will be set to 'c0', while the RFC only defines + '00' (string) and and '01' (mac) for this field, some + users may want full control over this option."; + type dhcp:octet-string; + } + } } } + leaf route-preference { type route-preference; default 5; diff --git a/src/confd/yang/infix-dhcp-common.yang b/src/confd/yang/infix-dhcp-common.yang index 2dd4f273..839aac30 100644 --- a/src/confd/yang/infix-dhcp-common.yang +++ b/src/confd/yang/infix-dhcp-common.yang @@ -106,4 +106,11 @@ module infix-dhcp-common { } } } + + typedef octet-string { + description "A generic string or a hex values."; + type string { + pattern '([0-9a-fA-F]{2}:)*[0-9a-fA-F]{2}'; + } + } } diff --git a/src/confd/yang/infix-dhcp-server.yang b/src/confd/yang/infix-dhcp-server.yang index 6a347e66..5c99d72a 100644 --- a/src/confd/yang/infix-dhcp-server.yang +++ b/src/confd/yang/infix-dhcp-server.yang @@ -25,16 +25,6 @@ module infix-dhcp-server { reference "internal"; } - typedef octet-string { - description "A generic string or a hex value if prefixed with 'id:'."; - type union { - type string { - pattern 'id:([0-9a-fA-F]{2}:)*[0-9a-fA-F]{2}'; - } - type string; - } - } - typedef dhcp-lease-time { description "The lease time in seconds, with a minimum of 2 minutes (120 seconds). For static host entries, the value 'infinite' may be used."; @@ -104,6 +94,15 @@ module infix-dhcp-server { } } + case hex-opt { + leaf hex { + description "Binary data as colon-separated pairs of hex digits. + + Example: c0:ff:ee"; + type dhcp:octet-string; + } + } + case default-opt { leaf string { description "Generic string value."; @@ -214,15 +213,30 @@ module infix-dhcp-server { } case client-id { - leaf client-id { - description "Match on client-id (string 'abc' or hex 'id:c0:ff:ee', DHCP option 61."; - type union { - type yang:mac-address; - type octet-string; + container client-id { + description "Match on client-id, DHCP option 61."; + choice id { + description "Format of the client-id value"; + default str; + case str { + leaf str { + description "String value for text-based client-id. + + Example: xyzzy"; + type string; + } + } + case hex { + leaf hex { + description "Binary data as colon-separated pairs of hex digits. + + Example: c0:ff:ee"; + type dhcp:octet-string; + } + } } } } - /* case option82 here ... */ } /* More advanced match rules, e.g., option82 + client-id are not supported. */ diff --git a/test/case/infix_dhcp/server_host/server_host.adoc b/test/case/infix_dhcp/server_host/server_host.adoc index 85a5d1c2..b6adf5ea 100644 --- a/test/case/infix_dhcp/server_host/server_host.adoc +++ b/test/case/infix_dhcp/server_host/server_host.adoc @@ -1,8 +1,8 @@ === DHCP Server Static Host ==== Description -Verify DHCP server can hand out static host leases based on -a very long client-id, ensuring no pool address is handed -out instead. +Verify DHCP server can hand out static host leases based on client-id, +both hexadecimal and a very long string, ensuring no pool address is +handed out instead. ==== Topology ifdef::topdoc[] diff --git a/test/case/infix_dhcp/server_host/test.py b/test/case/infix_dhcp/server_host/test.py index 71577582..32f622d7 100755 --- a/test/case/infix_dhcp/server_host/test.py +++ b/test/case/infix_dhcp/server_host/test.py @@ -1,9 +1,10 @@ #!/usr/bin/env python3 """DHCP Server Static Host -Verify DHCP server can hand out static host leases based on -a very long client-id, ensuring no pool address is handed -out instead. +Verify DHCP server can hand out static host leases based on client-id, +both hexadecimal and a very long string, ensuring no pool address is +handed out instead. + """ import infamy import infamy.iface as iface @@ -19,6 +20,7 @@ with infamy.Test() as test: ADDRESS2 = '192.168.2.22' GW2 = '192.168.2.1' HOSTNM1 = 'foo' + HOSTCID1 = '00:c0:ff:ee' # Infix DHCP server is RFC compliant HOSTNM11 = 'client1' HOSTCID2 = 'xyzzydissiegillespiefoobarterrawinklesouponastick' HOSTNM2 = 'bar' @@ -70,7 +72,7 @@ with infamy.Test() as test: "host": [{ "address": ADDRESS1, "match": { - "hostname": HOSTNM1 + "client-id": {"hex": HOSTCID1} }, "option": [ { @@ -94,7 +96,7 @@ with infamy.Test() as test: "host": [{ "address": ADDRESS2, "match": { - "client-id": HOSTCID2 + "client-id": {"str": HOSTCID2} }, "option": [ { @@ -120,7 +122,7 @@ with infamy.Test() as test: "if-name": client1["link"], "option": [ {"id": "router"}, - {"id": "hostname", "value": "auto"}, + {"id": "client-id", "hex": HOSTCID1}, {"id": 121} ] }]