[Dnsmasq-discuss] [PATCH] pxe: support pxe clients with custom vendor-class

Wang Shanker shankerwangmiao at gmail.com
Fri Dec 4 02:16:05 GMT 2020


Hi,

Thanks for your reminder. This patch was based on commit 4e7694d7, a
stale mirror on Github. I've rebased my patch and will send it out soon.

Cheer,

Miao Wang

> 2020年12月04日 06:51,Simon Kelley <simon at thekelleys.org.uk> 写道:
> 
> The patch looks fine in princple, but it doesn't apply to the current
> release (2.82) What version of dnsmasq were you patching?
> 
> Cheers,
> 
> Simon.
> 
> 
> 
> On 02/12/2020 12:23, Wang Shanker wrote:
>> 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 at 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);
>>     }
>> 
> 




More information about the Dnsmasq-discuss mailing list