diff --git a/CHANGELOG b/CHANGELOG index 7395b29..1561fe1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -54,6 +54,17 @@ version 2.91 Fix broken dhcp-relay on *BSD. Thanks to Harold for finding this problem. + Add --dhcp-option-pxe config. This acts almost exactly like + --dhcp-option except that the defined option is only sent when + replying to PXE clients. More importantly, these options are sent + in reply PXE clients when dnsmasq in acting in PXE proxy mode. In + PXE proxy mode, the set of options sent is defined by the PXE standard + and the normal set of options is not sent. This config allows arbitrary + options in PXE-proxy replies. A typical use-case is to send option + 175 to iPXE. Thanks to Jason Berry for finding the requirement for + this. + + version 2.90 Fix reversion in --rev-server introduced in 2.88 which diff --git a/man/dnsmasq.8 b/man/dnsmasq.8 index 8573e0b..390c851 100644 --- a/man/dnsmasq.8 +++ b/man/dnsmasq.8 @@ -1395,7 +1395,7 @@ Options may be encapsulated (IPv4 only) within other options: for instance will send option 175, within which is the option 190. If multiple options are given which are encapsulated with the same option number then they will be correctly combined into one encapsulated option. -encap: and vendor: are may not both be set in the same \fB--dhcp-option\fP. +encap: and vendor: may not both be set in the same \fB--dhcp-option\fP. The final variant on encapsulated options is "Vendor-Identifying Vendor Options" as specified by RFC3925. These are denoted like this: @@ -1414,6 +1414,10 @@ except that the option will always be sent, even if the client does not ask for it in the parameter request list. This is sometimes needed, for example when sending options to PXELinux. .TP +.B --dhcp-option-pxe=[tag:,[tag:,]][encap:,][vi-encap:,],[[,]] +A variant of --dhcp-option which defines options only sent in reply to PXE clients. In addition, such options are +sent in reply to PXE clients when dnsmasq is acting as a PXE proxy, unlike other options. A typical use-case is option 175, sent to iPXE. +.TP .B --dhcp-no-override (IPv4 only) Disable re-use of the DHCP servername and filename fields as extra option space. If it can, dnsmasq moves the boot server and filename diff --git a/src/dhcp-common.c b/src/dhcp-common.c index 7c837c6..534d3e6 100644 --- a/src/dhcp-common.c +++ b/src/dhcp-common.c @@ -128,22 +128,41 @@ struct dhcp_netid *run_tag_if(struct dhcp_netid *tags) return tags; } +/* pxemode == 0 -> don't include dhcp-option-pxe options. + pxemode == 1 -> do include dhcp-option-pxe options. + pxemode == 2 -> include ONLY dhcp-option-pxe options. */ +int pxe_ok(struct dhcp_opt *opt, int pxemode) +{ + if (opt->flags & DHOPT_PXE_OPT) + { + if (pxemode != 0) + return 1; + } + else + { + if (pxemode != 2) + return 1; + } + + return 0; +} -struct dhcp_netid *option_filter(struct dhcp_netid *tags, struct dhcp_netid *context_tags, struct dhcp_opt *opts) +struct dhcp_netid *option_filter(struct dhcp_netid *tags, struct dhcp_netid *context_tags, struct dhcp_opt *opts, int pxemode) { struct dhcp_netid *tagif = run_tag_if(tags); struct dhcp_opt *opt; struct dhcp_opt *tmp; - + /* flag options which are valid with the current tag set (sans context tags) */ for (opt = opts; opt; opt = opt->next) { opt->flags &= ~DHOPT_TAGOK; if (!(opt->flags & (DHOPT_ENCAPSULATE | DHOPT_VENDOR | DHOPT_RFC3925)) && - match_netid(opt->netid, tagif, 0)) + match_netid(opt->netid, tagif, 0) && + pxe_ok(opt, pxemode)) opt->flags |= DHOPT_TAGOK; } - + /* now flag options which are valid, including the context tags, otherwise valid options are inhibited if we found a higher priority one above */ if (context_tags) @@ -163,7 +182,8 @@ struct dhcp_netid *option_filter(struct dhcp_netid *tags, struct dhcp_netid *con for (opt = opts; opt; opt = opt->next) if (!(opt->flags & (DHOPT_ENCAPSULATE | DHOPT_VENDOR | DHOPT_RFC3925 | DHOPT_TAGOK)) && - match_netid(opt->netid, tagif, 0)) + match_netid(opt->netid, tagif, 0) && + pxe_ok(opt, pxemode)) { struct dhcp_opt *tmp; for (tmp = opts; tmp; tmp = tmp->next) @@ -176,7 +196,9 @@ struct dhcp_netid *option_filter(struct dhcp_netid *tags, struct dhcp_netid *con /* now flag untagged options which are not overridden by tagged ones */ for (opt = opts; opt; opt = opt->next) - if (!(opt->flags & (DHOPT_ENCAPSULATE | DHOPT_VENDOR | DHOPT_RFC3925 | DHOPT_TAGOK)) && !opt->netid) + if (!(opt->flags & (DHOPT_ENCAPSULATE | DHOPT_VENDOR | DHOPT_RFC3925 | DHOPT_TAGOK)) && + !opt->netid && + pxe_ok(opt, pxemode)) { for (tmp = opts; tmp; tmp = tmp->next) if (tmp->opt == opt->opt && (tmp->flags & DHOPT_TAGOK)) @@ -186,7 +208,7 @@ struct dhcp_netid *option_filter(struct dhcp_netid *tags, struct dhcp_netid *con else if (!tmp->netid) my_syslog(MS_DHCP | LOG_WARNING, _("Ignoring duplicate dhcp-option %d"), tmp->opt); } - + /* Finally, eliminate duplicate options later in the chain, and therefore earlier in the config file. */ for (opt = opts; opt; opt = opt->next) if (opt->flags & DHOPT_TAGOK) diff --git a/src/dnsmasq.h b/src/dnsmasq.h index abb06c8..0ca11e4 100644 --- a/src/dnsmasq.h +++ b/src/dnsmasq.h @@ -958,6 +958,7 @@ struct dhcp_opt { #define DHOPT_TAGOK 4096 #define DHOPT_ADDR6 8192 #define DHOPT_VENDOR_PXE 16384 +#define DHOPT_PXE_OPT 32768 struct dhcp_boot { char *file, *sname, *tftp_sname; @@ -1782,8 +1783,9 @@ int do_snoop_script_run(void); void dhcp_common_init(void); ssize_t recv_dhcp_packet(int fd, struct msghdr *msg); struct dhcp_netid *run_tag_if(struct dhcp_netid *tags); +int pxe_ok(struct dhcp_opt *opt, int pxemode); struct dhcp_netid *option_filter(struct dhcp_netid *tags, struct dhcp_netid *context_tags, - struct dhcp_opt *opts); + struct dhcp_opt *opts, int pxemode); int match_netid(struct dhcp_netid *check, struct dhcp_netid *pool, int tagnotneeded); char *strip_hostname(char *hostname); void log_tags(struct dhcp_netid *netid, u32 xid); diff --git a/src/option.c b/src/option.c index 3cf3520..92f3582 100644 --- a/src/option.c +++ b/src/option.c @@ -192,6 +192,7 @@ struct myoption { #define LOPT_NO_DHCP4 383 #define LOPT_MAX_PROCS 384 #define LOPT_DNSSEC_LIMITS 385 +#define LOPT_PXE_OPT 386 #ifdef HAVE_GETOPT_LONG static const struct option opts[] = @@ -297,6 +298,7 @@ static const struct myoption opts[] = { "bridge-interface", 1, 0 , LOPT_BRIDGE }, { "shared-network", 1, 0, LOPT_SHARED_NET }, { "dhcp-option-force", 1, 0, LOPT_FORCE }, + { "dhcp-option-pxe", 1, 0, LOPT_PXE_OPT }, { "tftp-no-blocksize", 0, 0, LOPT_NOBLOCK }, { "log-dhcp", 0, 0, LOPT_LOG_OPTS }, { "log-async", 2, 0, LOPT_MAX_LOGS }, @@ -450,6 +452,7 @@ static struct { { 'o', OPT_ORDER, NULL, gettext_noop("Use nameservers strictly in the order given in %s."), RESOLVFILE }, { 'O', ARG_DUP, "", gettext_noop("Specify options to be sent to DHCP clients."), NULL }, { LOPT_FORCE, ARG_DUP, "", gettext_noop("DHCP option sent even if the client does not request it."), NULL}, + { LOPT_PXE_OPT, ARG_DUP, "", gettext_noop("DHCP option sent only to PCE clients."), NULL}, { 'p', ARG_ONE, "", gettext_noop("Specify port to listen for DNS requests on (defaults to 53)."), NULL }, { 'P', ARG_ONE, "", gettext_noop("Maximum supported UDP packet size for EDNS.0 (defaults to %s)."), "*" }, { 'q', ARG_DUP, NULL, gettext_noop("Log DNS queries."), NULL }, @@ -1941,7 +1944,10 @@ static int parse_dhcp_opt(char *errstr, char *arg, int flags) (new->len > 253 && (new->flags & (DHOPT_VENDOR | DHOPT_ENCAPSULATE))) || (new->len > 250 && (new->flags & DHOPT_RFC3925)))) goto_err(_("dhcp-option too long")); - + + if (flags == DHOPT_PXE_OPT && (new->flags & DHOPT_VENDOR)) + goto_err(_("No vendor-encap options allowed in dhcp-option-pxe")); + if (flags == DHOPT_MATCH) { if ((new->flags & (DHOPT_ENCAPSULATE | DHOPT_VENDOR)) || @@ -4261,12 +4267,14 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma case 'O': /* --dhcp-option */ case LOPT_FORCE: /* --dhcp-option-force */ + case LOPT_PXE_OPT: /* --dhcp-option-pxe */ case LOPT_OPTS: case LOPT_MATCH: /* --dhcp-match */ return parse_dhcp_opt(errstr, arg, option == LOPT_FORCE ? DHOPT_FORCE : (option == LOPT_MATCH ? DHOPT_MATCH : - (option == LOPT_OPTS ? DHOPT_BANK : 0))); + (option == LOPT_OPTS ? DHOPT_BANK : + (option == LOPT_PXE_OPT ? DHOPT_PXE_OPT : 0)))); case LOPT_NAME_MATCH: /* --dhcp-name-match */ { @@ -4661,8 +4669,8 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma daemon->override_relays = new; arg = comma; } - break; - + break; + case LOPT_PXE_VENDOR: /* --dhcp-pxe-vendor */ { while (arg) { diff --git a/src/radv.c b/src/radv.c index f39716c..f090931 100644 --- a/src/radv.c +++ b/src/radv.c @@ -452,7 +452,7 @@ static void send_ra_alias(time_t now, int iface, char *iface_name, struct in6_ad iface_enumerate(AF_LOCAL, &send_iface, (callback_t){.af_local=add_lla}); /* RDNSS, RFC 6106, use relevant DHCP6 options */ - (void)option_filter(parm.tags, NULL, daemon->dhcp_opts6); + (void)option_filter(parm.tags, NULL, daemon->dhcp_opts6, 0); for (opt_cfg = daemon->dhcp_opts6; opt_cfg; opt_cfg = opt_cfg->next) { diff --git a/src/rfc2131.c b/src/rfc2131.c index 68834ea..f494a55 100644 --- a/src/rfc2131.c +++ b/src/rfc2131.c @@ -40,6 +40,7 @@ static unsigned char *option_find1(unsigned char *p, unsigned char *end, int opt static size_t dhcp_packet_size(struct dhcp_packet *mess, unsigned char *agent_id, unsigned char *real_end); static void clear_packet(struct dhcp_packet *mess, unsigned char *end); static int in_list(unsigned char *list, int opt); +static unsigned char *free_space(struct dhcp_packet *mess, unsigned char *end, int opt, int len); static void do_options(struct dhcp_context *context, struct dhcp_packet *mess, unsigned char *end, @@ -67,6 +68,8 @@ 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); +static int do_opt(struct dhcp_opt *opt, unsigned char *p, struct dhcp_context *context, int null_term); +static void handle_encap(struct dhcp_packet *mess, unsigned char *end, unsigned char *req_options, int null_term, struct dhcp_netid *tagif, int pxemode); 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, @@ -955,13 +958,10 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, { struct dhcp_boot *boot; int redirect4011 = 0; + struct dhcp_opt *option; - if (tmp->netid.net) - { - tmp->netid.next = netid; - tagif_netid = run_tag_if(&tmp->netid); - } - + /* OK only dhcp-option-pxe options. */ + tagif_netid = option_filter(netid, tmp->netid.net ? &tmp->netid : NULL, daemon->dhcp_opts, 2); boot = find_boot(tagif_netid); mess->yiaddr.s_addr = 0; @@ -1004,7 +1004,24 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, 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); - + + /* dhcp-option-pxe ONLY */ + for (option = daemon->dhcp_opts; option; option = option->next) + { + int len; + unsigned char *p; + + if (!(option->flags & DHOPT_TAGOK)) + continue; + + len = do_opt(option, NULL, tmp, borken_opt); + + if ((p = free_space(mess, end, option->opt, len))) + do_opt(option, p, tmp, borken_opt); + } + + handle_encap(mess, end, req_options, borken_opt, tagif_netid, 2); + daemon->metrics[METRIC_PXE]++; log_packet("PXE", NULL, emac, emac_len, iface_name, ignore ? "proxy-ignored" : "proxy", NULL, mess->xid); log_tags(tagif_netid, ntohl(mess->xid)); @@ -2409,7 +2426,7 @@ static void do_options(struct dhcp_context *context, /* filter options based on tags, those we want get DHOPT_TAGOK bit set */ if (context) context->netid.next = NULL; - tagif = option_filter(netid, context && context->netid.net ? &context->netid : NULL, config_opts); + tagif = option_filter(netid, context && context->netid.net ? &context->netid : NULL, config_opts, pxe_arch != -1); /* logging */ if (option_bool(OPT_LOG_OPTS) && req_options) @@ -2699,19 +2716,55 @@ static void do_options(struct dhcp_context *context, } } - /* Now send options to be encapsulated in arbitrary options, + /* encapsulated options. */ + handle_encap(mess, end, req_options, null_term, tagif, pxe_arch != 1); + + force_encap = prune_vendor_opts(tagif); + + if (context && pxe_arch != -1) + { + 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); + } + + if ((force_encap || in_list(req_options, OPTION_VENDOR_CLASS_OPT)) && + do_encap_opts(config_opts, OPTION_VENDOR_CLASS_OPT, DHOPT_VENDOR_MATCH, mess, end, null_term) && + pxe_arch == -1 && !done_vendor_class && vendor_class_len != 0 && + (p = free_space(mess, end, OPTION_VENDOR_ID, vendor_class_len))) + /* If we send vendor encapsulated options, and haven't already sent option 60, + echo back the value we got from the client. */ + memcpy(p, daemon->dhcp_buff3, vendor_class_len); + + /* restore BOOTP anti-overload hack */ + if (!req_options || option_bool(OPT_NO_OVERRIDE)) + { + mess->file[0] = f0; + mess->sname[0] = s0; + } +} + +static void handle_encap(struct dhcp_packet *mess, unsigned char *end, unsigned char *req_options, + int null_term, struct dhcp_netid *tagif, int pxemode) +{ + /* Send options to be encapsulated in arbitrary options, eg dhcp-option=encap:172,17,....... Also handle vendor-identifying vendor-encapsulated options, dhcp-option = vi-encap:13,17,....... The may be more that one "outer" to do, so group all the options which match each outer in turn. */ + + struct dhcp_opt *opt, *config_opts = daemon->dhcp_opts; + unsigned char *p; + int i, len; + for (opt = config_opts; opt; opt = opt->next) opt->flags &= ~DHOPT_ENCAP_DONE; for (opt = config_opts; opt; opt = opt->next) { int flags; - + if ((flags = (opt->flags & (DHOPT_ENCAPSULATE | DHOPT_RFC3925)))) { int found = 0; @@ -2731,6 +2784,7 @@ static void do_options(struct dhcp_context *context, o->flags |= DHOPT_ENCAP_DONE; if (match_netid(o->netid, tagif, 1) && + pxe_ok(o, pxemode) && ((o->flags & DHOPT_FORCE) || in_list(req_options, outer))) { o->flags |= DHOPT_ENCAP_MATCH; @@ -2763,30 +2817,6 @@ static void do_options(struct dhcp_context *context, } } } - - force_encap = prune_vendor_opts(tagif); - - if (context && pxe_arch != -1) - { - 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); - } - - if ((force_encap || in_list(req_options, OPTION_VENDOR_CLASS_OPT)) && - do_encap_opts(config_opts, OPTION_VENDOR_CLASS_OPT, DHOPT_VENDOR_MATCH, mess, end, null_term) && - pxe_arch == -1 && !done_vendor_class && vendor_class_len != 0 && - (p = free_space(mess, end, OPTION_VENDOR_ID, vendor_class_len))) - /* If we send vendor encapsulated options, and haven't already sent option 60, - echo back the value we got from the client. */ - memcpy(p, daemon->dhcp_buff3, vendor_class_len); - - /* restore BOOTP anti-overload hack */ - if (!req_options || option_bool(OPT_NO_OVERRIDE)) - { - mess->file[0] = f0; - mess->sname[0] = s0; - } } static void apply_delay(u32 xid, time_t recvtime, struct dhcp_netid *netid) diff --git a/src/rfc3315.c b/src/rfc3315.c index 400d939..3efe324 100644 --- a/src/rfc3315.c +++ b/src/rfc3315.c @@ -1317,7 +1317,7 @@ static struct dhcp_netid *add_options(struct state *state, int do_refresh) { void *oro; /* filter options based on tags, those we want get DHOPT_TAGOK bit set */ - struct dhcp_netid *tagif = option_filter(state->tags, state->context_tags, daemon->dhcp_opts6); + struct dhcp_netid *tagif = option_filter(state->tags, state->context_tags, daemon->dhcp_opts6, 0); struct dhcp_opt *opt_cfg; int done_dns = 0, done_refresh = !do_refresh, do_encap = 0; int i, o, o1;