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 <shankerwangm...@gmail.com> --- man/dnsmasq.8 | 15 ++++++++ src/dnsmasq.h | 9 +++++ src/option.c | 27 ++++++++++++-- src/rfc2131.c | 97 +++++++++++++++++++++++++++++++++++++-------------- 4 files changed, 120 insertions(+), 28 deletions(-) diff --git a/man/dnsmasq.8 b/man/dnsmasq.8 index 2e5ef21..31ea0b4 100644 --- a/man/dnsmasq.8 +++ b/man/dnsmasq.8 @@ -1426,6 +1426,21 @@ to allow netbooting. This mode is enabled using the .B proxy keyword in .B dhcp-range. +.TP +.B --dhcp-pxe-vendor=<vendor>[,...] +According to UEFI and PXE specifications, DHCP packets between PXE clients and +proxy PXE servers should have +.I PXEClient +in their vendor-class field. However, the firmware of computers from a few +vendors is customized to carry a different identifier in that field. This option +is used to consider such identifiers valid for identifying PXE clients. For +instance + +.B --dhcp-pxe-vendor=PXEClient,HW-Client + +will enable dnsmasq to also provide proxy PXE service to those PXE clients with +.I HW-Client +in as their identifier. .TP .B \-X, --dhcp-lease-max=<number> Limits dnsmasq to the specified maximum number of DHCP leases. The diff --git a/src/dnsmasq.h b/src/dnsmasq.h index 6b44e53..e654059 100644 --- a/src/dnsmasq.h +++ b/src/dnsmasq.h @@ -768,6 +768,7 @@ struct dhcp_opt { #define DHOPT_RFC3925 2048 #define DHOPT_TAGOK 4096 #define DHOPT_ADDR6 8192 +#define DHOPT_VENDOR_PXE 16384 struct dhcp_boot { char *file, *sname, *tftp_sname; @@ -784,6 +785,8 @@ struct pxe_service { struct pxe_service *next; }; +#define DHCP_PXE_DEF_VENDOR "PXEClient" + #define MATCH_VENDOR 1 #define MATCH_USER 2 #define MATCH_CIRCUIT 3 @@ -799,6 +802,11 @@ struct dhcp_vendor { struct dhcp_vendor *next; }; +struct dhcp_pxe_vendor { + char *data; + struct dhcp_pxe_vendor *next; +}; + struct dhcp_mac { unsigned int mask; int hwaddr_len, hwaddr_type; @@ -967,6 +975,7 @@ extern struct daemon { struct ra_interface *ra_interfaces; struct dhcp_config *dhcp_conf; struct dhcp_opt *dhcp_opts, *dhcp_match, *dhcp_opts6, *dhcp_match6; + struct dhcp_pxe_vendor *dhcp_pxe_vendors; struct dhcp_vendor *dhcp_vendors; struct dhcp_mac *dhcp_macs; struct dhcp_boot *boot_config; diff --git a/src/option.c b/src/option.c index 0c38db3..0a9d18f 100644 --- a/src/option.c +++ b/src/option.c @@ -159,6 +159,7 @@ struct myoption { #define LOPT_SCRIPT_ARP 347 #define LOPT_DHCPTTL 348 #define LOPT_TFTP_MTU 349 +#define LOPT_PXE_VENDOR 350 #ifdef HAVE_GETOPT_LONG static const struct option opts[] = @@ -257,6 +258,7 @@ static const struct myoption opts[] = { "dhcp-circuitid", 1, 0, LOPT_CIRCUIT }, { "dhcp-remoteid", 1, 0, LOPT_REMOTE }, { "dhcp-subscrid", 1, 0, LOPT_SUBSCR }, + { "dhcp-pxe-vendor", 1, 0, LOPT_PXE_VENDOR }, { "interface-name", 1, 0, LOPT_INTNAME }, { "dhcp-hostsfile", 1, 0, LOPT_DHCP_HOST }, { "dhcp-optsfile", 1, 0, LOPT_DHCP_OPTS }, @@ -367,6 +369,7 @@ static struct { { LOPT_CIRCUIT, ARG_DUP, "set:<tag>,<circuit>", gettext_noop("Map RFC3046 circuit-id to tag."), NULL }, { LOPT_REMOTE, ARG_DUP, "set:<tag>,<remote>", gettext_noop("Map RFC3046 remote-id to tag."), NULL }, { LOPT_SUBSCR, ARG_DUP, "set:<tag>,<remote>", gettext_noop("Map RFC3993 subscriber-id to tag."), NULL }, + { LOPT_PXE_VENDOR, ARG_DUP, "<vendor>[,...]", gettext_noop("Specify vendor class to match for PXE requests."), NULL }, { 'J', ARG_DUP, "tag:<tag>...", gettext_noop("Don't do DHCP for hosts with tag set."), NULL }, { LOPT_BROADCAST, ARG_DUP, "[=tag:<tag>...]", gettext_noop("Force broadcast replies for hosts with tag set."), NULL }, { 'k', OPT_NO_FORK, NULL, gettext_noop("Do NOT fork into the background, do NOT run in debug mode."), NULL }, @@ -3336,8 +3339,8 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma new->val = opt_malloc(new->len); memcpy(new->val + 1, arg, new->len - 1); - new->u.vendor_class = (unsigned char *)"PXEClient"; - new->flags = DHOPT_VENDOR; + new->u.vendor_class = NULL; + new->flags = DHOPT_VENDOR | DHOPT_VENDOR_PXE; if (comma && atoi_check(comma, &timeout)) *(new->val) = timeout; @@ -3626,6 +3629,19 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma new->next = daemon->override_relays; daemon->override_relays = new; arg = comma; + } + break; + + case LOPT_PXE_VENDOR: /* --dhcp-pxe-vendor */ + { + while (arg) { + struct dhcp_pxe_vendor *new = opt_malloc(sizeof(struct dhcp_pxe_vendor)); + comma = split(arg); + new->data = opt_string_alloc(arg); + new->next = daemon->dhcp_pxe_vendors; + daemon->dhcp_pxe_vendors = new; + arg = comma; + } } break; @@ -4762,6 +4778,13 @@ void read_opts(int argc, char **argv, char *compile_opts) strcat(buff, daemon->authserver); daemon->hostmaster = opt_string_alloc(buff); } + + if (!daemon->dhcp_pxe_vendors) + { + daemon->dhcp_pxe_vendors = opt_malloc(sizeof(struct dhcp_pxe_vendor)); + daemon->dhcp_pxe_vendors->data = opt_string_alloc(DHCP_PXE_DEF_VENDOR); + daemon->dhcp_pxe_vendors->next = NULL; + } /* only one of these need be specified: the other defaults to the host-name */ if (option_bool(OPT_LOCALMX) || daemon->mxnames || daemon->mxtarget) diff --git a/src/rfc2131.c b/src/rfc2131.c index 3e97402..e408337 100644 --- a/src/rfc2131.c +++ b/src/rfc2131.c @@ -30,7 +30,7 @@ static struct in_addr server_id(struct dhcp_context *context, struct in_addr ove 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 i, int size); static void log_packet(char *type, void *addr, unsigned char *ext_mac, @@ -54,16 +54,18 @@ static void do_options(struct dhcp_context *context, 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 *opts, 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 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 *is_inform, int pxe, struct in_addr fallback) @@ -74,6 +76,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, struct dhcp_mac *mac; struct dhcp_netid_list *id_list; int clid_len = 0, ignore = 0, do_classes = 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); @@ -613,7 +616,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, clear_packet(mess, end, 0); 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); } } @@ -770,9 +773,8 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, 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))) { @@ -831,7 +833,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, 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; @@ -911,7 +913,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, 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); @@ -1068,7 +1070,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, 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); @@ -1406,7 +1408,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, option_put(mess, end, OPTION_SERVER_IDENTIFIER, INADDRSZ, ntohl(server_id(context, override, fallback).s_addr)); option_put(mess, end, OPTION_LEASE_TIME, 4, time); 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); @@ -1471,7 +1473,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, } 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); @@ -1846,7 +1848,7 @@ static void option_put(struct dhcp_packet *mess, unsigned char *end, int opt, in } 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); @@ -1924,15 +1926,32 @@ static void match_vendor_opts(unsigned char *opt, struct dhcp_opt *dopt) 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; + } + } } } } @@ -1985,11 +2004,13 @@ static int do_encap_opts(struct dhcp_opt *opt, int encap, int flag, 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); } @@ -2217,6 +2238,29 @@ struct dhcp_boot *find_boot(struct dhcp_netid *netid) 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, @@ -2231,7 +2275,8 @@ static void do_options(struct dhcp_context *context, 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; @@ -2605,7 +2650,7 @@ static void do_options(struct dhcp_context *context, 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); } -- 2.28.0 _______________________________________________ Dnsmasq-discuss mailing list Dnsmasq-discuss@lists.thekelleys.org.uk http://lists.thekelleys.org.uk/mailman/listinfo/dnsmasq-discuss