From c4cd95df68b573b63d234ecdb675228657d65353 Mon Sep 17 00:00:00 2001 From: Simon Kelley Date: Thu, 10 Oct 2013 20:58:11 +0100 Subject: [PATCH] Add --ra-param and remove --force-fast-ra --- CHANGELOG | 5 ++- man/dnsmasq.8 | 18 +++++--- src/dnsmasq.h | 10 ++++- src/option.c | 31 +++++++++++-- src/radv.c | 120 +++++++++++++++++++++++++++++++++++++++----------- 5 files changed, 147 insertions(+), 37 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index d76eb7a..9392514 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -90,8 +90,6 @@ version 2.67 smallest valid dhcp-range is sent. Thanks to Uwe Schindler for suggesting this. - Add --force-fast-ra option. Another thanks to Uwe Schindler. - Make --listen-address higher priority than --except-interface in all circumstances. Thanks to Thomas Hood for the bugreport. @@ -132,6 +130,9 @@ version 2.67 Update Spanish transalation. Thanks to Vicente Soriano. + Add --ra-param option. Thanks to Vladislav Grishenko for + inspiration on this. + version 2.66 Add the ability to act as an authoritative DNS diff --git a/man/dnsmasq.8 b/man/dnsmasq.8 index 2b1570a..2652f4b 100644 --- a/man/dnsmasq.8 +++ b/man/dnsmasq.8 @@ -1525,11 +1525,19 @@ the relevant link-local address of the machine running dnsmasq is sent as recursive DNS server. If provided, the DHCPv6 options dns-server and domain-search are used for RDNSS and DNSSL. .TP -.B --force-fast-ra -Normally, dnsmasq advertises a new IPv6 prefix frequently (every 10 seconds or so) for the first minute, and then -drops back to sending "maintenance" advertisements every 10 minutes or so. This option forces dnsmasq to be always in -frequent RA mode. It's a bug workaround for mobile devices which go deaf to RAs during sleep and therefore -lose conectivity; with frequent RAs they recover in a reasonable time after wakeup. +.B --ra-param=,[high|low],[[],] +Set non-default values for router advertisements sent via an +interface. The priority field for the router may be altered from the +default of medium with eg +.B --ra-param=eth0,high. +The interval between router advertisements may be set (in seconds) with +.B --ra-param=eth0,60. +The lifetime of the route may be changed or set to zero, which allows +a router to advertise prefixes but not a route via itself. +.B --ra-parm=eth0,0,0 +(A value of zero for the interval means the default value.) All three parameters may be set at once. +.B --ra-param=low,60,1200 +The interface field may include a wildcard. .TP .B --enable-tftp[=[,]] Enable the TFTP server function. This is deliberately limited to that diff --git a/src/dnsmasq.h b/src/dnsmasq.h index b652a91..d67ba59 100644 --- a/src/dnsmasq.h +++ b/src/dnsmasq.h @@ -221,8 +221,7 @@ struct event_desc { #define OPT_TFTP_LC 38 #define OPT_CLEVERBIND 39 #define OPT_TFTP 40 -#define OPT_FAST_RA 41 -#define OPT_LAST 42 +#define OPT_LAST 41 /* extra flags for my_syslog, we use a couple of facilities since they are known not to occupy the same bits as priorities, no matter how syslog.h is set up. */ @@ -701,6 +700,12 @@ struct prefix_class { }; #endif +struct ra_interface { + char *name; + int interval, lifetime, prio; + struct ra_interface *next; +}; + struct dhcp_context { unsigned int lease_time, addr_epoch; struct in_addr netmask, broadcast; @@ -825,6 +830,7 @@ extern struct daemon { unsigned long local_ttl, neg_ttl, max_ttl, max_cache_ttl, auth_ttl; struct hostsfile *addn_hosts; struct dhcp_context *dhcp, *dhcp6; + struct ra_interface *ra_interfaces; struct dhcp_config *dhcp_conf; struct dhcp_opt *dhcp_opts, *dhcp_match, *dhcp_opts6, *dhcp_match6; struct dhcp_vendor *dhcp_vendors; diff --git a/src/option.c b/src/option.c index 9b128cf..997703b 100644 --- a/src/option.c +++ b/src/option.c @@ -132,8 +132,8 @@ struct myoption { #ifdef OPTION6_PREFIX_CLASS #define LOPT_PREF_CLSS 321 #endif -#define LOPT_FAST_RA 322 #define LOPT_RELAY 323 +#define LOPT_RA_PARAM 324 #ifdef HAVE_GETOPT_LONG static const struct option opts[] = @@ -271,8 +271,8 @@ static const struct myoption opts[] = #ifdef OPTION6_PREFIX_CLASS { "dhcp-prefix-class", 1, 0, LOPT_PREF_CLSS }, #endif - { "force-fast-ra", 0, 0, LOPT_FAST_RA }, { "dhcp-relay", 1, 0, LOPT_RELAY }, + { "ra-param", 1, 0, LOPT_RA_PARAM }, { NULL, 0, 0, 0 } }; @@ -402,7 +402,6 @@ static struct { { LOPT_CONNTRACK, OPT_CONNTRACK, NULL, gettext_noop("Copy connection-track mark from queries to upstream connections."), NULL }, { LOPT_FQDN, OPT_FQDN_UPDATE, NULL, gettext_noop("Allow DHCP clients to do their own DDNS updates."), NULL }, { LOPT_RA, OPT_RA, NULL, gettext_noop("Send router-advertisements for interfaces doing DHCPv6"), NULL }, - { LOPT_FAST_RA, OPT_FAST_RA, NULL, gettext_noop("Always send frequent router-advertisements"), NULL }, { LOPT_DUID, ARG_ONE, ",", gettext_noop("Specify DUID_EN-type DHCPv6 server DUID"), NULL }, { LOPT_HOST_REC, ARG_DUP, ",
", gettext_noop("Specify host (A/AAAA and PTR) records"), NULL }, { LOPT_RR, ARG_DUP, ",,[]", gettext_noop("Specify arbitrary DNS resource record"), NULL }, @@ -418,6 +417,7 @@ static struct { #ifdef OPTION6_PREFIX_CLASS { LOPT_PREF_CLSS, ARG_DUP, "set:tag,", gettext_noop("Specify DHCPv6 prefix class"), NULL }, #endif + { LOPT_RA_PARAM, ARG_DUP, ",[high,|low,][,]", gettext_noop("Set priority, resend-interval and router-lifetime"), NULL }, { 0, 0, NULL, NULL, NULL } }; @@ -3215,6 +3215,31 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma #endif #ifdef HAVE_DHCP6 + case LOPT_RA_PARAM: /* --ra-param */ + if ((comma = split(arg))) + { + struct ra_interface *new = opt_malloc(sizeof(struct ra_interface)); + new->lifetime = -1; + new->prio = 0; + new->name = opt_string_alloc(arg); + if (strcasestr(comma, "high") == comma || strcasestr(comma, "low") == comma) + { + if (*comma == 'l' || *comma == 'L') + new->prio = 0x18; + else + new->prio = 0x08; + comma = split(comma); + } + arg = split(comma); + if (!atoi_check(comma, &new->interval) || + (arg && !atoi_check(arg, &new->lifetime))) + ret_err(_("bad RA-params")); + + new->next = daemon->ra_interfaces; + daemon->ra_interfaces = new; + } + break; + case LOPT_DUID: /* --dhcp-duid */ if (!(comma = split(arg)) || !atoi_check(arg, (int *)&daemon->duid_enterprise)) ret_err(_("bad DUID")); diff --git a/src/radv.c b/src/radv.c index b1f2bc1..d1147ba 100644 --- a/src/radv.c +++ b/src/radv.c @@ -32,11 +32,12 @@ struct ra_param { char *if_name; struct dhcp_netid *tags; struct in6_addr link_local, link_global; - unsigned int pref_time; + unsigned int pref_time, adv_interval; }; struct search_param { time_t now; int iface; + char name[IF_NAMESIZE+1]; }; static void send_ra(time_t now, int iface, char *iface_name, struct in6_addr *dest); @@ -47,7 +48,11 @@ 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 void new_timeout(struct dhcp_context *context, char *iface_name, time_t now); +static unsigned int calc_lifetime(struct ra_interface *ra); +static unsigned int calc_interval(struct ra_interface *ra); +static unsigned int calc_prio(struct ra_interface *ra); +static struct ra_interface *find_iface_param(char *iface); static int hop_limit; @@ -198,19 +203,20 @@ static void send_ra(time_t now, int iface, char *iface_name, struct in6_addr *de struct dhcp_context *context, *tmp, **up; struct dhcp_netid iface_id; struct dhcp_opt *opt_cfg; + struct ra_interface *ra_param = find_iface_param(iface_name); int done_dns = 0; #ifdef HAVE_LINUX_NETWORK FILE *f; #endif - + save_counter(0); ra = expand(sizeof(struct ra_packet)); ra->type = ND_ROUTER_ADVERT; ra->code = 0; ra->hop_limit = hop_limit; - ra->flags = 0x00; - ra->lifetime = htons(RA_INTERVAL * 3); /* AdvDefaultLifetime * 3 */ + ra->flags = calc_prio(ra_param); + ra->lifetime = htons(calc_lifetime(ra_param)); ra->reachable_time = 0; ra->retrans_time = 0; @@ -222,6 +228,7 @@ static void send_ra(time_t now, int iface, char *iface_name, struct in6_addr *de parm.first = 1; parm.now = now; parm.pref_time = 0; + parm.adv_interval = calc_interval(ra_param); /* set tag with name == interface */ iface_id.net = iface_name; @@ -335,7 +342,7 @@ static void send_ra(time_t now, int iface, char *iface_name, struct in6_addr *de put_opt6_char(ICMP6_OPT_RDNSS); put_opt6_char((opt_cfg->len/8) + 1); put_opt6_short(0); - put_opt6_long(RA_INTERVAL * 2); /* lifetime - twice RA retransmit */ + put_opt6_long(parm.adv_interval * 2); /* lifetime - twice RA retransmit */ /* zero means "self" */ for (i = 0; i < opt_cfg->len; i += IN6ADDRSZ, a++) if (IN6_IS_ADDR_UNSPECIFIED(a)) @@ -351,7 +358,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(RA_INTERVAL * 2); /* lifetime - twice RA retransmit */ + put_opt6_long(parm.adv_interval * 2); /* lifetime - twice RA retransmit */ put_opt6(opt_cfg->val, opt_cfg->len); /* pad */ @@ -366,7 +373,7 @@ static void send_ra(time_t now, int iface, char *iface_name, struct in6_addr *de put_opt6_char(ICMP6_OPT_RDNSS); put_opt6_char(3); put_opt6_short(0); - put_opt6_long(RA_INTERVAL * 2); /* lifetime - twice RA retransmit */ + put_opt6_long(parm.adv_interval * 2); /* lifetime - twice RA retransmit */ put_opt6(&parm.link_global, IN6ADDRSZ); } @@ -455,8 +462,8 @@ static int add_prefixes(struct in6_addr *local, int prefix, if (time > context->lease_time) { time = context->lease_time; - if (time < ((unsigned int)(3 * RA_INTERVAL))) - time = 3 * RA_INTERVAL; + if (time < ((unsigned int)(3 * param->adv_interval))) + time = 3 * param->adv_interval; } if (context->flags & CONTEXT_DEPRECATE) @@ -564,8 +571,7 @@ time_t periodic_ra(time_t now) struct search_param param; struct dhcp_context *context; time_t next_event; - char interface[IF_NAMESIZE+1]; - + param.now = now; param.iface = 0; @@ -586,13 +592,15 @@ time_t periodic_ra(time_t now) if (!context) break; - if ((context->flags & CONTEXT_OLD) && context->if_index != 0) + if ((context->flags & CONTEXT_OLD) && + context->if_index != 0 && + indextoname(daemon->icmp6fd, param.iface, param.name)) { /* 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. */ + looking for addresses, but we know it anyway, since the context is + constructed */ param.iface = context->if_index; - new_timeout(context, now); + new_timeout(context, param.name, now); } else if (iface_enumerate(AF_INET6, ¶m, iface_search)) /* There's a context overdue, but we can't find an interface @@ -603,15 +611,14 @@ time_t periodic_ra(time_t now) context->ra_time = 0; if (param.iface != 0 && - indextoname(daemon->icmp6fd, param.iface, interface) && - iface_check(AF_LOCAL, NULL, interface, NULL)) + iface_check(AF_LOCAL, NULL, param.name, NULL)) { struct iname *tmp; for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next) - if (tmp->name && wildcard_match(tmp->name, interface)) + if (tmp->name && wildcard_match(tmp->name, param.name)) break; if (!tmp) - send_ra(now, param.iface, interface, NULL); + send_ra(now, param.iface, param.name, NULL); } } return next_event; @@ -643,7 +650,14 @@ static int iface_search(struct in6_addr *local, int prefix, if (!(flags & IFACE_TENTATIVE)) param->iface = if_index; - new_timeout(context, param->now); + /* should never fail */ + if (!indextoname(daemon->icmp6fd, if_index, param->name)) + { + param->iface = 0; + return 0; + } + + new_timeout(context, param->name, param->now); /* zero timers for other contexts on the same subnet, so they don't timeout independently */ @@ -659,14 +673,70 @@ 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) +static void new_timeout(struct dhcp_context *context, char *iface_name, time_t now) { - if (difftime(now, context->ra_short_period_start) < 60.0 || option_bool(OPT_FAST_RA)) + 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); + { + /* range 3/4 - 1 times MaxRtrAdvInterval */ + unsigned int adv_interval = calc_interval(find_iface_param(iface_name)); + context->ra_time = now + (3 * adv_interval)/4 + ((adv_interval * (unsigned int)rand16()) >> 18); + } +} + +static struct ra_interface *find_iface_param(char *iface) +{ + struct ra_interface *ra; + + for (ra = daemon->ra_interfaces; ra; ra = ra->next) + if (wildcard_match(ra->name, iface)) + return ra; + + return NULL; +} + +static unsigned int calc_interval(struct ra_interface *ra) +{ + int interval = 600; + + if (ra && ra->interval != 0) + { + interval = ra->interval; + if (interval > 1800) + interval = 1800; + else if (interval < 4) + interval = 4; + } + + return (unsigned int)interval; +} + +static unsigned int calc_lifetime(struct ra_interface *ra) +{ + int lifetime, interval = (int)calc_interval(ra); + + if (!ra || ra->lifetime == -1) /* not specified */ + lifetime = 3 * interval; + else + { + lifetime = ra->lifetime; + if (lifetime < interval && lifetime != 0) + lifetime = interval; + else if (lifetime > 9000) + lifetime = 9000; + } + + return (unsigned int)lifetime; +} + +static unsigned int calc_prio(struct ra_interface *ra) +{ + if (ra) + return ra->prio; + + return 0; } #endif -- 2.39.2