diff --git a/CHANGELOG b/CHANGELOG index 4e6a1fc..c18f1ae 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -63,6 +63,27 @@ version 2.67 Fix crash with empty DHCP string options when adding zero terminator. Thanks to Patrick McLean for the bug report. + Allow hostnames to start with a number, as allowed in + RFC-1123. Thanks to Kyle Mestery for the patch. + + Fixes to DHCP FQDN option handling: don't terminate FQDN + if domain not known and allow a FQDN option with blank + name to request that a FQDN option is returned in the + reply. Thanks to Roy Marples for the patch. + + Make --clear-on-reload apply to setting upstream servers + via DBus too. + + When the address which triggered the construction of an + advertised IPv6 prefix disappears, continue to advertise + the prefix for up to 2 hours, with the preferred lifetime + set to zero. This satisfies RFC 6204 4.3 L-13 and makes + things work better if a prefix disappears without being + deprecated first. Thanks to Uwe Schindler for persuasively + arguing for this. + + Fix MAC address enumeration on *BSD. Thanks to Brad Smith + for the bug report. version 2.66 Add the ability to act as an authoritative DNS diff --git a/man/dnsmasq.8 b/man/dnsmasq.8 index a519fdb..8e9137c 100644 --- a/man/dnsmasq.8 +++ b/man/dnsmasq.8 @@ -338,7 +338,8 @@ by '/', like the --server syntax, eg. Don't poll /etc/resolv.conf for changes. .TP .B --clear-on-reload -Whenever /etc/resolv.conf is re-read, clear the DNS cache. +Whenever /etc/resolv.conf is re-read or the upstream servers are set +via DBus, clear the DNS cache. This is useful when new nameservers may have different data than that held in cache. .TP diff --git a/src/bpf.c b/src/bpf.c index e75b0c6..d286970 100644 --- a/src/bpf.c +++ b/src/bpf.c @@ -111,7 +111,8 @@ int iface_enumerate(int family, void *parm, int (*callback)()) { int iface_index = if_nametoindex(addrs->ifa_name); - if (iface_index == 0 || !addrs->ifa_addr || !addrs->ifa_netmask) + if (iface_index == 0 || !addrs->ifa_addr || + (!addrs->ifa_netmask && family != AF_LINK)) continue; if (family == AF_INET) diff --git a/src/dbus.c b/src/dbus.c index 7379341..da28a28 100644 --- a/src/dbus.c +++ b/src/dbus.c @@ -305,8 +305,6 @@ static DBusMessage* dbus_read_servers_ex(DBusMessage *message, int strings) const char *addr_err; char *dup = NULL; - my_syslog(LOG_INFO, _("setting upstream servers from DBus")); - if (!dbus_message_iter_init(message, &iter)) { return dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS, @@ -478,6 +476,7 @@ DBusHandlerResult message_handler(DBusConnection *connection, { char *method = (char *)dbus_message_get_member(message); DBusMessage *reply = NULL; + int clear_cache = 0, new_servers = 0; if (dbus_message_is_method_call(message, DBUS_INTERFACE_INTROSPECTABLE, "Introspect")) { @@ -501,24 +500,34 @@ DBusHandlerResult message_handler(DBusConnection *connection, } else if (strcmp(method, "SetServers") == 0) { - my_syslog(LOG_INFO, _("setting upstream servers from DBus")); dbus_read_servers(message); - check_servers(); + new_servers = 1; } else if (strcmp(method, "SetServersEx") == 0) { reply = dbus_read_servers_ex(message, 0); - check_servers(); + new_servers = 1; } else if (strcmp(method, "SetDomainServers") == 0) { reply = dbus_read_servers_ex(message, 1); - check_servers(); + new_servers = 1; } else if (strcmp(method, "ClearCache") == 0) - clear_cache_and_reload(dnsmasq_time()); + clear_cache = 1; else return (DBUS_HANDLER_RESULT_NOT_YET_HANDLED); + + if (new_servers) + { + my_syslog(LOG_INFO, _("setting upstream servers from DBus")); + check_servers(); + if (option_bool(OPT_RELOAD)) + clear_cache = 1; + } + + if (clear_cache) + clear_cache_and_reload(dnsmasq_time()); method = user_data; /* no warning */ diff --git a/src/dhcp-common.c b/src/dhcp-common.c index 9321e92..e5e136a 100644 --- a/src/dhcp-common.c +++ b/src/dhcp-common.c @@ -720,7 +720,7 @@ void log_context(int family, struct dhcp_context *context) p += sprintf(p, ", "); if (indextoname(daemon->doing_dhcp6 ? daemon->dhcp6fd : daemon->icmp6fd, context->if_index, ifrn_name)) - sprintf(p, "constructed for %s", ifrn_name); + sprintf(p, "%s for %s", (context->flags & CONTEXT_OLD) ? "old prefix" : "constructed", ifrn_name); } else if (context->flags & CONTEXT_TEMPLATE) { @@ -731,7 +731,8 @@ void log_context(int family, struct dhcp_context *context) } #endif - if ((context->flags & CONTEXT_DHCP) || family == AF_INET) + if (!(context->flags & CONTEXT_OLD) && + ((context->flags & CONTEXT_DHCP) || family == AF_INET)) { inet_ntop(family, start, daemon->dhcp_buff, 256); inet_ntop(family, end, daemon->dhcp_buff3, 256); @@ -748,9 +749,9 @@ void log_context(int family, struct dhcp_context *context) } #ifdef HAVE_DHCP6 - if (context->flags & CONTEXT_RA_NAME) + if ((context->flags & CONTEXT_RA_NAME) && !(context->flags & CONTEXT_OLD)) my_syslog(MS_DHCP | LOG_INFO, _("DHCPv4-derived IPv6 names on %s%s"), daemon->addrbuff, template); - + if ((context->flags & CONTEXT_RA) || (option_bool(OPT_RA) && (context->flags & CONTEXT_DHCP) && family == AF_INET6)) my_syslog(MS_DHCP | LOG_INFO, _("router advertisement on %s%s"), daemon->addrbuff, template); #endif diff --git a/src/dhcp6.c b/src/dhcp6.c index 6cd30b5..7b6aaf5 100644 --- a/src/dhcp6.c +++ b/src/dhcp6.c @@ -220,7 +220,7 @@ static int complete_context6(struct in6_addr *local, int prefix, for (context = daemon->dhcp6; context; context = context->next) { if ((context->flags & CONTEXT_DHCP) && - !(context->flags & CONTEXT_TEMPLATE) && + !(context->flags & (CONTEXT_TEMPLATE | CONTEXT_OLD)) && prefix == context->prefix && is_same_net6(local, &context->start6, prefix) && is_same_net6(local, &context->end6, prefix)) @@ -552,7 +552,10 @@ static int construct_worker(struct in6_addr *local, int prefix, IN6_ARE_ADDR_EQUAL(&start6, &context->start6) && IN6_ARE_ADDR_EQUAL(&end6, &context->end6)) { - context->flags &= ~CONTEXT_GC; + int flags = context->flags; + context->flags &= ~(CONTEXT_GC | CONTEXT_OLD); + if (flags & CONTEXT_OLD) + log_context(AF_INET6, context); break; } @@ -565,6 +568,7 @@ static int construct_worker(struct in6_addr *local, int prefix, context->flags |= CONTEXT_CONSTRUCTED; context->if_index = if_index; context->local6 = *local; + context->saved_valid = 0; context->next = daemon->dhcp6; daemon->dhcp6 = context; @@ -587,35 +591,53 @@ static int construct_worker(struct in6_addr *local, int prefix, void dhcp_construct_contexts(time_t now) { - struct dhcp_context *tmp, *context, **up; + struct dhcp_context *context, *tmp, **up; struct cparam param; param.newone = 0; param.newname = 0; param.now = now; for (context = daemon->dhcp6; context; context = context->next) - { - context->if_index = 0; - if (context->flags & CONTEXT_CONSTRUCTED) - context->flags |= CONTEXT_GC; - } - + if (context->flags & CONTEXT_CONSTRUCTED) + context->flags |= CONTEXT_GC; + iface_enumerate(AF_INET6, ¶m, construct_worker); for (up = &daemon->dhcp6, context = daemon->dhcp6; context; context = tmp) { - tmp = context->next; - if (context->flags & CONTEXT_GC) + tmp = context->next; + + if (context->flags & CONTEXT_GC && !(context->flags & CONTEXT_OLD)) { - *up = context->next; - param.newone = 1; /* include deletion */ - if (context->flags & CONTEXT_RA_NAME) - param.newname = 1; - free(context); + + if ((context->flags & (CONTEXT_RA_ONLY | CONTEXT_RA_NAME | CONTEXT_RA_STATELESS)) || + option_bool(OPT_RA)) + { + /* previously constructed context has gone. advertise it's demise */ + context->flags |= CONTEXT_OLD; + context->address_lost_time = now; + if (context->saved_valid > 7200) /* 2 hours */ + context->saved_valid = 7200; + ra_start_unsolicted(now, context); + param.newone = 1; /* include deletion */ + + if (context->flags & CONTEXT_RA_NAME) + param.newname = 1; + + log_context(AF_INET6, context); + + up = &context->next; + } + else + { + /* we were never doing RA for this, so free now */ + *up = context->next; + free(context); + } } else - up = &context->next; + up = &context->next; } if (param.newone) diff --git a/src/dnsmasq.h b/src/dnsmasq.h index 5a82705..268b1b4 100644 --- a/src/dnsmasq.h +++ b/src/dnsmasq.h @@ -706,8 +706,8 @@ struct dhcp_context { struct in6_addr start6, end6; /* range of available addresses */ struct in6_addr local6; int prefix, if_index; - unsigned int valid, preferred; - time_t ra_time, ra_short_period_start; + unsigned int valid, preferred, saved_valid; + time_t ra_time, ra_short_period_start, address_lost_time; char *template_interface; #endif int flags; @@ -732,6 +732,8 @@ struct dhcp_context { #define CONTEXT_CONF_USED 16384 #define CONTEXT_USED 32768 #define CONTEXT_NOAUTH 65536 +#define CONTEXT_OLD 131072 + struct ping_result { struct in_addr addr; diff --git a/src/radv.c b/src/radv.c index 72a93cb..9fbede4 100644 --- a/src/radv.c +++ b/src/radv.c @@ -47,6 +47,7 @@ static int iface_search(struct in6_addr *local, int prefix, int scope, int if_index, int flags, int prefered, int valid, void *vparam); static int add_lla(int index, unsigned int type, char *mac, size_t maclen, void *parm); +static void new_timeout(struct dhcp_context *context, time_t now); static int hop_limit; @@ -187,9 +188,8 @@ static void send_ra(time_t now, int iface, char *iface_name, struct in6_addr *de { struct ra_packet *ra; struct ra_param parm; - struct ifreq ifr; struct sockaddr_in6 addr; - struct dhcp_context *context; + struct dhcp_context *context, *tmp, **up; struct dhcp_netid iface_id; struct dhcp_opt *opt_cfg; int done_dns = 0; @@ -228,12 +228,66 @@ static void send_ra(time_t now, int iface, char *iface_name, struct in6_addr *de context->netid.next = &context->netid; } - if (!iface_enumerate(AF_INET6, &parm, add_prefixes) || - !parm.found_context) + if (!iface_enumerate(AF_INET6, &parm, add_prefixes)) return; - strncpy(ifr.ifr_name, iface_name, IF_NAMESIZE); + /* Look for constructed contexts associated with addresses which have gone, + and advertise them with preferred_time == 0 RFC 6204 4.3 L-13 */ + for (up = &daemon->dhcp6, context = daemon->dhcp6; context; context = tmp) + { + tmp = context->next; + if (context->if_index == iface && (context->flags & CONTEXT_OLD)) + { + unsigned int old = difftime(now, context->address_lost_time); + + if (old > context->saved_valid) + { + /* We've advertised this enough, time to go */ + *up = context->next; + free(context); + } + else + { + struct prefix_opt *opt; + struct in6_addr local = context->start6; + int do_slaac = 0; + + parm.found_context = 1; + + /* zero net part of address */ + setaddr6part(&local, addr6part(&local) & ~((context->prefix == 64) ? (u64)-1LL : (1LLU << (128 - context->prefix)) - 1LLU)); + + if ((context->flags & + (CONTEXT_RA_ONLY | CONTEXT_RA_NAME | CONTEXT_RA_STATELESS))) + do_slaac = 1; + + if ((opt = expand(sizeof(struct prefix_opt)))) + { + opt->type = ICMP6_OPT_PREFIX; + opt->len = 4; + opt->prefix_len = context->prefix; + /* autonomous only if we're not doing dhcp, always set "on-link" */ + opt->flags = do_slaac ? 0xC0 : 0x80; + opt->valid_lifetime = htonl(context->saved_valid - old); + opt->preferred_lifetime = htonl(0); + opt->reserved = 0; + opt->prefix = local; + + inet_ntop(AF_INET6, &local, daemon->addrbuff, ADDRSTRLEN); + my_syslog(MS_DHCP | LOG_INFO, "RTR-ADVERT(%s) %s old prefix", iface_name, daemon->addrbuff); + } + + up = &context->next; + } + } + else + up = &context->next; + } + + if (!parm.found_context) + return; + #ifdef HAVE_LINUX_NETWORK /* Note that IPv6 MTU is not necessarilly the same as the IPv4 MTU available from SIOCGIFMTU */ @@ -291,7 +345,7 @@ static void send_ra(time_t now, int iface, char *iface_name, struct in6_addr *de put_opt6_char(ICMP6_OPT_DNSSL); put_opt6_char(len + 1); put_opt6_short(0); - put_opt6_long(1800); /* lifetime - twice RA retransmit */ + put_opt6_long(RA_INTERVAL * 2); /* lifetime - twice RA retransmit */ put_opt6(opt_cfg->val, opt_cfg->len); /* pad */ @@ -361,11 +415,13 @@ static int add_prefixes(struct in6_addr *local, int prefix, struct dhcp_context *context; for (context = daemon->dhcp6; context; context = context->next) - if (!(context->flags & CONTEXT_TEMPLATE) && + if (!(context->flags & (CONTEXT_TEMPLATE | CONTEXT_OLD)) && prefix == context->prefix && is_same_net6(local, &context->start6, prefix) && is_same_net6(local, &context->end6, prefix)) { + context->saved_valid = valid; + if ((context->flags & (CONTEXT_RA_ONLY | CONTEXT_RA_NAME | CONTEXT_RA_STATELESS))) { @@ -386,12 +442,12 @@ static int add_prefixes(struct in6_addr *local, int prefix, param->other = 1; } - /* find floor time, don't reduce below RA interval. */ + /* find floor time, don't reduce below 3 * RA interval. */ if (time > context->lease_time) { time = context->lease_time; - if (time < ((unsigned int)RA_INTERVAL)) - time = RA_INTERVAL; + if (time < ((unsigned int)(3 * RA_INTERVAL))) + time = 3 * RA_INTERVAL; } if (context->flags & CONTEXT_DEPRECATE) @@ -466,6 +522,7 @@ static int add_prefixes(struct in6_addr *local, int prefix, inet_ntop(AF_INET6, local, daemon->addrbuff, ADDRSTRLEN); my_syslog(MS_DHCP | LOG_INFO, "RTR-ADVERT(%s) %s", param->if_name, daemon->addrbuff); } + } } } @@ -520,16 +577,25 @@ time_t periodic_ra(time_t now) if (!context) break; - /* There's a context overdue, but we can't find an interface - associated with it, because it's for a subnet we dont - have an interface on. Probably we're doing DHCP on - a remote subnet via a relay. Zero the timer, since we won't - ever be able to send ra's and satistfy it. */ - if (iface_enumerate(AF_INET6, ¶m, iface_search)) + if ((context->flags & CONTEXT_OLD) && context->if_index != 0) + { + /* A context for an old address. We'll not find the interface by + looking for addresses, but we know it anyway, as long as we + sent at least one RA whilst the address was current. */ + param.iface = context->if_index; + new_timeout(context, now); + } + else if (iface_enumerate(AF_INET6, ¶m, iface_search)) + /* There's a context overdue, but we can't find an interface + associated with it, because it's for a subnet we dont + have an interface on. Probably we're doing DHCP on + a remote subnet via a relay. Zero the timer, since we won't + ever be able to send ra's and satistfy it. */ context->ra_time = 0; - else if (param.iface != 0 && - indextoname(daemon->icmp6fd, param.iface, interface) && - iface_check(AF_LOCAL, NULL, interface, NULL)) + + if (param.iface != 0 && + indextoname(daemon->icmp6fd, param.iface, interface) && + iface_check(AF_LOCAL, NULL, interface, NULL)) { struct iname *tmp; for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next) @@ -554,7 +620,7 @@ static int iface_search(struct in6_addr *local, int prefix, (void)valid; for (context = daemon->dhcp6; context; context = context->next) - if (!(context->flags & CONTEXT_TEMPLATE) && + if (!(context->flags & (CONTEXT_TEMPLATE | CONTEXT_OLD)) && prefix == context->prefix && is_same_net6(local, &context->start6, prefix) && is_same_net6(local, &context->end6, prefix) && @@ -568,12 +634,7 @@ static int iface_search(struct in6_addr *local, int prefix, if (!(flags & IFACE_TENTATIVE)) param->iface = if_index; - if (difftime(param->now, context->ra_short_period_start) < 60.0) - /* range 5 - 20 */ - context->ra_time = param->now + 5 + (rand16()/4400); - else - /* range 3/4 - 1 times RA_INTERVAL */ - context->ra_time = param->now + (3 * RA_INTERVAL)/4 + ((RA_INTERVAL * (unsigned int)rand16()) >> 18); + new_timeout(context, param->now); /* zero timers for other contexts on the same subnet, so they don't timeout independently */ @@ -588,6 +649,15 @@ static int iface_search(struct in6_addr *local, int prefix, return 1; /* keep searching */ } - + +static void new_timeout(struct dhcp_context *context, time_t now) +{ + if (difftime(now, context->ra_short_period_start) < 60.0) + /* range 5 - 20 */ + context->ra_time = now + 5 + (rand16()/4400); + else + /* range 3/4 - 1 times RA_INTERVAL */ + context->ra_time = now + (3 * RA_INTERVAL)/4 + ((RA_INTERVAL * (unsigned int)rand16()) >> 18); +} #endif diff --git a/src/rfc2131.c b/src/rfc2131.c index e7fa75f..357cf79 100644 --- a/src/rfc2131.c +++ b/src/rfc2131.c @@ -615,7 +615,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, return message ? 0 : dhcp_packet_size(mess, agent_id, real_end); } - if ((opt = option_find(mess, sz, OPTION_CLIENT_FQDN, 4))) + if ((opt = option_find(mess, sz, OPTION_CLIENT_FQDN, 3))) { /* http://tools.ietf.org/wg/dhc/draft-ietf-dhc-fqdn-option/draft-ietf-dhc-fqdn-option-10.txt */ int len = option_len(opt); @@ -645,7 +645,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, } if (fqdn_flags & 0x04) - while (*op != 0 && ((op + (*op) + 1) - pp) < len) + while (*op != 0 && ((op + (*op)) - pp) < len) { memcpy(pq, op+1, *op); pq += *op; @@ -2290,7 +2290,9 @@ static void do_options(struct dhcp_context *context, if (domain) len += strlen(domain) + 1; - + else if (fqdn_flags & 0x04) + len--; + if ((p = free_space(mess, end, OPTION_CLIENT_FQDN, len))) { *(p++) = fqdn_flags & 0x0f; /* MBZ bits to zero */ @@ -2301,8 +2303,10 @@ static void do_options(struct dhcp_context *context, { p = do_rfc1035_name(p, hostname); if (domain) - p = do_rfc1035_name(p, domain); - *p++ = 0; + { + p = do_rfc1035_name(p, domain); + *p++ = 0; + } } else { diff --git a/src/rfc3315.c b/src/rfc3315.c index c8ba3d0..56e04a1 100644 --- a/src/rfc3315.c +++ b/src/rfc3315.c @@ -120,7 +120,7 @@ static int dhcp6_maybe_relay(struct in6_addr *link_address, struct dhcp_netid ** !IN6_IS_ADDR_MULTICAST(link_address)) for (c = daemon->dhcp6; c; c = c->next) if ((c->flags & CONTEXT_DHCP) && - !(c->flags & CONTEXT_TEMPLATE) && + !(c->flags & (CONTEXT_TEMPLATE | CONTEXT_OLD)) && is_same_net6(link_address, &c->start6, c->prefix) && is_same_net6(link_address, &c->end6, c->prefix)) { @@ -1299,16 +1299,18 @@ struct dhcp_netid *add_options(struct state *state, struct in6_addr *fallback, s size_t len = strlen(state->hostname); if (state->send_domain) - len += strlen(state->send_domain) + 1; + len += strlen(state->send_domain) + 2; o = new_opt6(OPTION6_FQDN); - if ((p = expand(len + 3))) + if ((p = expand(len + 2))) { *(p++) = state->fqdn_flags; p = do_rfc1035_name(p, state->hostname); if (state->send_domain) - p = do_rfc1035_name(p, state->send_domain); - *p = 0; + { + p = do_rfc1035_name(p, state->send_domain); + *p = 0; + } } end_opt6(o); } diff --git a/src/slaac.c b/src/slaac.c index d130de7..0229d9e 100644 --- a/src/slaac.c +++ b/src/slaac.c @@ -38,7 +38,9 @@ void slaac_add_addrs(struct dhcp_lease *lease, time_t now, int force) lease->slaac_address = NULL; for (context = daemon->dhcp6; context; context = context->next) - if ((context->flags & CONTEXT_RA_NAME) && lease->last_interface == context->if_index) + if ((context->flags & CONTEXT_RA_NAME) && + !(context->flags & CONTEXT_OLD) && + lease->last_interface == context->if_index) { struct in6_addr addr = context->start6; if (lease->hwaddr_len == 6 && @@ -123,7 +125,7 @@ time_t periodic_slaac(time_t now, struct dhcp_lease *leases) time_t next_event = 0; for (context = daemon->dhcp6; context; context = context->next) - if ((context->flags & CONTEXT_RA_NAME)) + if ((context->flags & CONTEXT_RA_NAME) && !(context->flags & CONTEXT_OLD)) break; /* nothing configured */ diff --git a/src/util.c b/src/util.c index af4031c..94cc570 100644 --- a/src/util.c +++ b/src/util.c @@ -151,14 +151,13 @@ int legal_hostname(char *name) /* check for legal char a-z A-Z 0-9 - _ . */ { if ((c >= 'A' && c <= 'Z') || - (c >= 'a' && c <= 'z')) + (c >= 'a' && c <= 'z') || + (c >= '0' && c <= '9')) continue; - if (!first && - ((c >= '0' && c <= '9') || - c == '-' || c == '_')) + if (!first && (c == '-' || c == '_')) continue; - + /* end of hostname part */ if (c == '.') return 1;