mirror of
https://github.com/kernelkit/infix.git
synced 2026-08-06 23:43:20 +02:00
confd: refactor dhcp client/server modeling of client-id
Instead of encoding hex data in regular strings, using an 'id:' prefix,
or guessing based on pattern in C code, we have decided for a breaking
change in the DHCP client model.
This commit introduces string and hex values for client-id in the models
for both DHCP client and server. The *breaking* part of the changes are
strictly related to how a user pass binary data as colon-separated hex
digits. The dedicated leaf node `client-id` is now reserved for string
values and the generic `list option` can be used to input user defined
string or hex values. Even non-conformant client-id options can now be
encoded. All described in detail in infix-dhcp-client.yang
The server model has been updated to be able to send generic options in
hex format, and the static host lease matcher for client-id now require
a 'str' or 'hex' modifier.
Note: dnsmasq is RFC compliant and will not match non-conforming option
61 (client-id) even though the client can send such payload.
To increase test coverage, the server_host test has been updated to
check both string and hex client-id. Matching on hostname is already
covered by other test(s).
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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}';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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[]
|
||||
|
||||
@@ -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}
|
||||
]
|
||||
}]
|
||||
|
||||
Reference in New Issue
Block a user