pxe: support pxe clients with custom vendor-class
From 606d638918edb0e0ec07fe27eb68d06fb5ebd981 Mon Sep 17 00:00:00 2001
From: Miao Wang <shankerwangmiao@gmail.com>
Date: Fri, 4 Dec 2020 09:59:37 +0800
Subject: [PATCH v2] pxe: support pxe clients with custom vendor-class
According to UEFI[1] and PXE[2] specs, PXE clients are required to have
`PXEClient` identfier in the vendor-class field of DHCP requests, and
PXE servers should also include that identifier in their responses.
However, the firmware of servers from a few vendors[3] are customized to
include a different identifier. This patch adds an option named
`dhcp-pxe-vendor` to provide a list of such identifiers. The identifier
used in responses sent from dnsmasq is identical to that in the coresponding
request.
[1]: https://uefi.org/sites/default/files/resources/UEFI%20Spec%202.8B%20May%202020.pdf
[2]: http://www.pix.net/software/pxeboot/archive/pxespec.pdf
[3]: For instance, TaiShan servers from Huawei, which are Arm64-based,
send `HW-Client` in PXE requests up to now.
Signed-off-by: Miao Wang <shankerwangmiao@gmail.com>
diff --git a/src/rfc2131.c b/src/rfc2131.c
index fc54aab..d678068 100644
--- a/src/rfc2131.c
+++ b/src/rfc2131.c
@@ -30,7 +30,7 @@
static unsigned int calc_time(struct dhcp_context *context, struct dhcp_config *config, unsigned char *opt);
static void option_put(struct dhcp_packet *mess, unsigned char *end, int opt, int len, unsigned int val);
static void option_put_string(struct dhcp_packet *mess, unsigned char *end,
- int opt, char *string, int null_term);
+ int opt, const char *string, int null_term);
static struct in_addr option_addr(unsigned char *opt);
static unsigned int option_uint(unsigned char *opt, int offset, int size);
static void log_packet(char *type, void *addr, unsigned char *ext_mac,
@@ -54,17 +54,19 @@
int vendor_class_len,
time_t now,
unsigned int lease_time,
- unsigned short fuzz);
+ unsigned short fuzz,
+ const char *pxevendor);
static void match_vendor_opts(unsigned char *opt, struct dhcp_opt *dopt);
static int do_encap_opts(struct dhcp_opt *opt, int encap, int flag, struct dhcp_packet *mess, unsigned char *end, int null_term);
-static void pxe_misc(struct dhcp_packet *mess, unsigned char *end, unsigned char *uuid);
+static void pxe_misc(struct dhcp_packet *mess, unsigned char *end, unsigned char *uuid, const char *pxevendor);
static int prune_vendor_opts(struct dhcp_netid *netid);
static struct dhcp_opt *pxe_opts(int pxe_arch, struct dhcp_netid *netid, struct in_addr local, time_t now);
struct dhcp_boot *find_boot(struct dhcp_netid *netid);
static int pxe_uefi_workaround(int pxe_arch, struct dhcp_netid *netid, struct dhcp_packet *mess, struct in_addr local, time_t now, int pxe);
static void apply_delay(u32 xid, time_t recvtime, struct dhcp_netid *netid);
+static int is_pxe_client(struct dhcp_packet *mess, size_t sz, const char **pxe_vendor);
size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
size_t sz, time_t now, int unicast_dest, int loopback,
@@ -76,6 +78,7 @@
struct dhcp_mac *mac;
struct dhcp_netid_list *id_list;
int clid_len = 0, ignore = 0, do_classes = 0, rapid_commit = 0, selecting = 0, pxearch = -1;
+ const char *pxevendor = NULL;
struct dhcp_packet *mess = (struct dhcp_packet *)daemon->dhcp_packet.iov_base;
unsigned char *end = (unsigned char *)(mess + 1);
unsigned char *real_end = (unsigned char *)(mess + 1);
@@ -647,7 +650,7 @@
clear_packet(mess, end);
do_options(context, mess, end, NULL, hostname, get_domain(mess->yiaddr),
- netid, subnet_addr, 0, 0, -1, NULL, vendor_class_len, now, 0xffffffff, 0);
+ netid, subnet_addr, 0, 0, -1, NULL, vendor_class_len, now, 0xffffffff, 0, NULL);
}
}
@@ -835,9 +838,8 @@
clid = NULL;
/* Check if client is PXE client. */
- if (daemon->enable_pxe &&
- (opt = option_find(mess, sz, OPTION_VENDOR_ID, 9)) &&
- strncmp(option_ptr(opt, 0), "PXEClient", 9) == 0)
+ if (daemon->enable_pxe &&
+ is_pxe_client(mess, sz, &pxevendor))
{
if ((opt = option_find(mess, sz, OPTION_PXE_UUID, 17)))
{
@@ -899,7 +901,7 @@
option_put(mess, end, OPTION_MESSAGE_TYPE, 1, DHCPACK);
option_put(mess, end, OPTION_SERVER_IDENTIFIER, INADDRSZ, htonl(context->local.s_addr));
- pxe_misc(mess, end, uuid);
+ pxe_misc(mess, end, uuid, pxevendor);
prune_vendor_opts(tagif_netid);
opt71.val = save71;
@@ -979,7 +981,7 @@
option_put(mess, end, OPTION_MESSAGE_TYPE, 1,
mess_type == DHCPDISCOVER ? DHCPOFFER : DHCPACK);
option_put(mess, end, OPTION_SERVER_IDENTIFIER, INADDRSZ, htonl(tmp->local.s_addr));
- pxe_misc(mess, end, uuid);
+ pxe_misc(mess, end, uuid, pxevendor);
prune_vendor_opts(tagif_netid);
if ((pxe && !workaround) || !redirect4011)
do_encap_opts(pxe_opts(pxearch, tagif_netid, tmp->local, now), OPTION_VENDOR_CLASS_OPT, DHOPT_VENDOR_MATCH, mess, end, 0);
@@ -1150,7 +1152,7 @@
option_put(mess, end, OPTION_LEASE_TIME, 4, time);
/* T1 and T2 are required in DHCPOFFER by HP's wacky Jetdirect client. */
do_options(context, mess, end, req_options, offer_hostname, get_domain(mess->yiaddr),
- netid, subnet_addr, fqdn_flags, borken_opt, pxearch, uuid, vendor_class_len, now, time, fuzz);
+ netid, subnet_addr, fqdn_flags, borken_opt, pxearch, uuid, vendor_class_len, now, time, fuzz, pxevendor);
return dhcp_packet_size(mess, agent_id, real_end);
@@ -1499,7 +1501,7 @@
if (rapid_commit)
option_put(mess, end, OPTION_RAPID_COMMIT, 0, 0);
do_options(context, mess, end, req_options, hostname, get_domain(mess->yiaddr),
- netid, subnet_addr, fqdn_flags, borken_opt, pxearch, uuid, vendor_class_len, now, time, fuzz);
+ netid, subnet_addr, fqdn_flags, borken_opt, pxearch, uuid, vendor_class_len, now, time, fuzz, pxevendor);
}
return dhcp_packet_size(mess, agent_id, real_end);
@@ -1566,7 +1568,7 @@
}
do_options(context, mess, end, req_options, hostname, get_domain(mess->ciaddr),
- netid, subnet_addr, fqdn_flags, borken_opt, pxearch, uuid, vendor_class_len, now, 0xffffffff, 0);
+ netid, subnet_addr, fqdn_flags, borken_opt, pxearch, uuid, vendor_class_len, now, 0xffffffff, 0, pxevendor);
*is_inform = 1; /* handle reply differently */
return dhcp_packet_size(mess, agent_id, real_end);
@@ -1948,7 +1950,7 @@
}
static void option_put_string(struct dhcp_packet *mess, unsigned char *end, int opt,
- char *string, int null_term)
+ const char *string, int null_term)
{
unsigned char *p;
size_t len = strlen(string);
@@ -2026,15 +2028,32 @@
dopt->flags &= ~DHOPT_VENDOR_MATCH;
if (opt && (dopt->flags & DHOPT_VENDOR))
{
- int i, len = 0;
- if (dopt->u.vendor_class)
- len = strlen((char *)dopt->u.vendor_class);
- for (i = 0; i <= (option_len(opt) - len); i++)
- if (len == 0 || memcmp(dopt->u.vendor_class, option_ptr(opt, i), len) == 0)
- {
- dopt->flags |= DHOPT_VENDOR_MATCH;
- break;
- }
+ const struct dhcp_pxe_vendor *pv;
+ struct dhcp_pxe_vendor dummy_vendor = {
+ .data = (char *)dopt->u.vendor_class,
+ .next = NULL,
+ };
+ if (dopt->flags & DHOPT_VENDOR_PXE)
+ pv = daemon->dhcp_pxe_vendors;
+ else
+ pv = &dummy_vendor;
+ for (; pv; pv = pv->next)
+ {
+ int i, len = 0, matched = 0;
+ if (pv->data)
+ len = strlen(pv->data);
+ for (i = 0; i <= (option_len(opt) - len); i++)
+ if (len == 0 || memcmp(pv->data, option_ptr(opt, i), len) == 0)
+ {
+ matched = 1;
+ break;
+ }
+ if (matched)
+ {
+ dopt->flags |= DHOPT_VENDOR_MATCH;
+ break;
+ }
+ }
}
}
}
@@ -2087,11 +2106,13 @@
return ret;
}
-static void pxe_misc(struct dhcp_packet *mess, unsigned char *end, unsigned char *uuid)
+static void pxe_misc(struct dhcp_packet *mess, unsigned char *end, unsigned char *uuid, const char *pxevendor)
{
unsigned char *p;
- option_put_string(mess, end, OPTION_VENDOR_ID, "PXEClient", 0);
+ if (!pxevendor)
+ pxevendor="PXEClient";
+ option_put_string(mess, end, OPTION_VENDOR_ID, pxevendor, 0);
if (uuid && (p = free_space(mess, end, OPTION_PXE_UUID, 17)))
memcpy(p, uuid, 17);
}
@@ -2308,6 +2329,29 @@
return boot;
}
+static int is_pxe_client(struct dhcp_packet *mess, size_t sz, const char **pxe_vendor)
+{
+ const unsigned char *opt = NULL;
+ ssize_t conf_len = 0;
+ const struct dhcp_pxe_vendor *conf = daemon->dhcp_pxe_vendors;
+ opt = option_find(mess, sz, OPTION_VENDOR_ID, 0);
+ if (!opt)
+ return 0;
+ for (; conf; conf = conf->next)
+ {
+ conf_len = strlen(conf->data);
+ if (option_len(opt) < conf_len)
+ continue;
+ if (strncmp(option_ptr(opt, 0), conf->data, conf_len) == 0)
+ {
+ if (pxe_vendor)
+ *pxe_vendor = conf->data;
+ return 1;
+ }
+ }
+ return 0;
+}
+
static void do_options(struct dhcp_context *context,
struct dhcp_packet *mess,
unsigned char *end,
@@ -2322,7 +2366,8 @@
int vendor_class_len,
time_t now,
unsigned int lease_time,
- unsigned short fuzz)
+ unsigned short fuzz,
+ const char *pxevendor)
{
struct dhcp_opt *opt, *config_opts = daemon->dhcp_opts;
struct dhcp_boot *boot;
@@ -2696,7 +2741,7 @@
if (context && pxe_arch != -1)
{
- pxe_misc(mess, end, uuid);
+ pxe_misc(mess, end, uuid, pxevendor);
if (!pxe_uefi_workaround(pxe_arch, tagif, mess, context->local, now, 0))
config_opts = pxe_opts(pxe_arch, tagif, context->local, now);
}