Implements router advertisements activated by received routes.

The RAdv protocol could be configured to change its behavior based on
availability of routes, e.g., do not announce router lifetime when a
default route is not available.
This commit is contained in:
Ondrej Zajicek 2013-02-08 23:58:27 +01:00
parent d214ae4fdc
commit 36da2857bc
8 changed files with 225 additions and 35 deletions

View file

@ -691,8 +691,8 @@ This argument can be omitted if there exists only a single instance.
<p>You can also select just routes added by a specific protocol.
<cf>protocol <m/p/</cf>.
<p>If BIRD is configured to keep filtered routes (see </cf/import keep filtered/
option), you can show them instead of routes by using </cf/filtered/ switch.
<p>If BIRD is configured to keep filtered routes (see <cf/import keep filtered/
option), you can show them instead of routes by using <cf/filtered/ switch.
<p>The <cf/stats/ switch requests showing of route statistics (the
number of networks, number of routes before and after filtering). If
@ -2479,6 +2479,26 @@ interface definitions, prefix definitions and DNS definitions:
also as interface-specific options and there is a short
variant <cf>dnssl <m/domain/</cf> that just specifies one DNS
search domain.
<label id="dsc-trigger"> <tag>trigger <m/prefix/</tag>
RAdv protocol could be configured to change its behavior based
on availability of routes. When this option is used, the
protocol waits in suppressed state until a <it/trigger route/
(for the specified network) is exported to the protocol, the
protocol also returnsd to suppressed state if the
<it/trigger route/ disappears. Note that route export depends
on specified export filter, as usual. This option could be
used, e.g., for handling failover in multihoming scenarios.
During suppressed state, router advertisements are generated,
but with some fields zeroed. Exact behavior depends on which
fields are zeroed, this can be configured by
<cf/sensitive/ option for appropriate fields. By default, just
<cf/default lifetime/ (also called <cf/router lifetime/) is
zeroed, which means hosts cannot use the router as a default
router. <cf/preferred lifetime/ and <cf/valid lifetime/ could
also be configured as <cf/sensitive/ for a prefix, which would
cause autoconfigured IPs to be deprecated or even removed.
</descrip>
<p>Interface specific options:
@ -2525,11 +2545,12 @@ interface definitions, prefix definitions and DNS definitions:
This option specifies which value of Hop Limit should be used
by hosts. Valid values are 0-255, 0 means unspecified. Default: 64
<tag>default lifetime <m/expr/</tag>
<tag>default lifetime <m/expr/ [sensitive <m/switch/]</tag>
This option specifies the time (in seconds) how long (after
the receipt of RA) hosts may use the router as a default
router. 0 means do not use as a default router. Default: 3 *
<cf/max ra interval/.
router. 0 means do not use as a default router. For
<cf/sensitive/ option, see <ref id="dsc-trigger" name="trigger">.
Default: 3 * <cf/max ra interval/, <cf/sensitive/ yes.
<tag>rdnss local <m/switch/</tag>
Use only local (interface-specific) RDNSS definitions for this
@ -2561,18 +2582,20 @@ interface definitions, prefix definitions and DNS definitions:
This option specifies whether hosts may use the advertised
prefix for stateless autoconfiguration. Default: yes
<tag>valid lifetime <m/expr/</tag>
<tag>valid lifetime <m/expr/ [sensitive <m/switch/]</tag>
This option specifies the time (in seconds) how long (after
the receipt of RA) the prefix information is valid, i.e.,
autoconfigured IP addresses can be assigned and hosts with
that IP addresses are considered directly reachable. 0 means
the prefix is no longer valid. Default: 86400 (1 day)
the prefix is no longer valid. For <cf/sensitive/ option, see
<ref id="dsc-trigger" name="trigger">. Default: 86400 (1 day), <cf/sensitive/ no.
<tag>preferred lifetime <m/expr/</tag>
<tag>preferred lifetime <m/expr/ [sensitive <m/switch/]</tag>
This option specifies the time (in seconds) how long (after
the receipt of RA) IP addresses generated from the prefix
using stateless autoconfiguration remain preferred. Default:
14400 (4 hours)
using stateless autoconfiguration remain preferred. For
<cf/sensitive/ option, see <ref id="dsc-trigger" name="trigger">.
Default: 14400 (4 hours), <cf/sensitive/ no.
</descrip>

View file

@ -1429,6 +1429,12 @@ i_same(struct f_inst *f1, struct f_inst *f2)
int
f_run(struct filter *filter, struct rte **rte, struct ea_list **tmp_attrs, struct linpool *tmp_pool, int flags)
{
if (filter == FILTER_ACCEPT)
return F_ACCEPT;
if (filter == FILTER_REJECT)
return F_REJECT;
int rte_cow = ((*rte)->flags & REF_COW);
DBG( "Running filter `%s'...", filter->name );

View file

@ -235,6 +235,12 @@ static inline int rte_is_filtered(rte *r) { return !!(r->flags & REF_FILTERED);
#define RA_ACCEPTED 2 /* Announcement of first accepted route */
#define RA_ANY 3 /* Announcement of any route change */
/* Return value of import_control() callback */
#define RIC_ACCEPT 1 /* Accepted by protocol */
#define RIC_PROCESS 0 /* Process it through import filter */
#define RIC_REJECT -1 /* Rejected by protocol */
#define RIC_DROP -2 /* Silently dropped by protocol */
struct config;
void rt_init(void);
@ -250,6 +256,7 @@ rte *rte_get_temp(struct rta *);
void rte_update2(struct announce_hook *ah, net *net, rte *new, struct proto *src);
static inline void rte_update(rtable *tab, net *net, struct proto *p, struct proto *src, rte *new) { rte_update2(p->main_ahook, net, new, src); }
void rte_discard(rtable *tab, rte *old);
int rt_examine(rtable *t, ip_addr prefix, int pxlen, struct proto *p, struct filter *filter);
void rte_dump(rte *);
void rte_free(rte *);
rte *rte_do_cow(rte *);

View file

@ -213,7 +213,8 @@ export_filter(struct announce_hook *ah, rte *rt0, rte **rt_free, ea_list **tmpa,
goto reject;
stats->exp_updates_rejected++;
rte_trace_out(D_FILTERS, p, rt, "rejected by protocol");
if (v == RIC_REJECT)
rte_trace_out(D_FILTERS, p, rt, "rejected by protocol");
goto reject;
}
if (v > 0)
@ -1042,6 +1043,34 @@ rte_discard(rtable *t, rte *old) /* Non-filtered route deletion, used during gar
rte_update_unlock();
}
/* Check rtable for best route to given net whether it would be exported do p */
int
rt_examine(rtable *t, ip_addr prefix, int pxlen, struct proto *p, struct filter *filter)
{
net *n = net_find(t, prefix, pxlen);
rte *rt = n ? n->routes : NULL;
if (!rte_is_valid(rt))
return 0;
rte_update_lock();
/* Rest is stripped down export_filter() */
struct proto *src = rt->attrs->proto;
ea_list *tmpa = src->make_tmp_attrs ? src->make_tmp_attrs(rt, rte_update_pool) : NULL;
int v = p->import_control ? p->import_control(p, &rt, &tmpa, rte_update_pool) : 0;
if (v == RIC_PROCESS)
v = (f_run(filter, &rt, &tmpa, rte_update_pool, FF_FORCE_TMPATTR) <= F_ACCEPT);
/* Discard temporary rte */
if (rt != n->routes)
rte_free(rt);
rte_update_unlock();
return v > 0;
}
/**
* rte_dump - dump a route
* @e: &rte to be dumped
@ -2081,7 +2110,7 @@ rt_show_net(struct cli *c, net *n, struct rt_show_data *d)
ee = e;
rte_update_lock(); /* We use the update buffer for filtering */
tmpa = p0->make_tmp_attrs ? p0->make_tmp_attrs(e, rte_update_pool) : NULL;
ok = (d->filter == FILTER_ACCEPT || f_run(d->filter, &e, &tmpa, rte_update_pool, FF_FORCE_TMPATTR) <= F_ACCEPT);
ok = f_run(d->filter, &e, &tmpa, rte_update_pool, FF_FORCE_TMPATTR) <= F_ACCEPT;
if (p2 && p2 != p0) ok = 0;
if (ok && d->export_mode)
{
@ -2095,8 +2124,8 @@ rt_show_net(struct cli *c, net *n, struct rt_show_data *d)
'configure soft' command may change the export filter
and do not update routes */
if ((a = proto_find_announce_hook(p1, d->table)) && ((a->out_filter == FILTER_REJECT) ||
(a->out_filter && f_run(a->out_filter, &e, &tmpa, rte_update_pool, FF_FORCE_TMPATTR) > F_ACCEPT)))
if ((a = proto_find_announce_hook(p1, d->table)) &&
(f_run(a->out_filter, &e, &tmpa, rte_update_pool, FF_FORCE_TMPATTR) > F_ACCEPT))
ok = 0;
}
}

View file

@ -30,9 +30,9 @@ CF_KEYWORDS(RADV, PREFIX, INTERFACE, MIN, MAX, RA, DELAY, INTERVAL,
MANAGED, OTHER, CONFIG, LINK, MTU, REACHABLE, TIME, RETRANS,
TIMER, CURRENT, HOP, LIMIT, DEFAULT, VALID, PREFERRED, MULT,
LIFETIME, SKIP, ONLINK, AUTONOMOUS, RDNSS, DNSSL, NS, DOMAIN,
LOCAL)
LOCAL, TRIGGER, SENSITIVE)
%type<i> radv_mult
%type<i> radv_mult radv_sensitive
CF_GRAMMAR
@ -53,6 +53,11 @@ radv_proto_item:
| PREFIX radv_prefix { add_tail(&RADV_CFG->pref_list, NODE this_radv_prefix); }
| RDNSS { init_list(&radv_dns_list); } radv_rdnss { add_tail_list(&RADV_CFG->rdnss_list, &radv_dns_list); }
| DNSSL { init_list(&radv_dns_list); } radv_dnssl { add_tail_list(&RADV_CFG->dnssl_list, &radv_dns_list); }
| TRIGGER prefix {
RADV_CFG->trigger_prefix = $2.addr;
RADV_CFG->trigger_pxlen = $2.len;
RADV_CFG->trigger_valid = 1;
}
;
radv_proto_opts:
@ -78,6 +83,7 @@ radv_iface_start:
RADV_IFACE->min_delay = DEFAULT_MIN_DELAY;
RADV_IFACE->current_hop_limit = DEFAULT_CURRENT_HOP_LIMIT;
RADV_IFACE->default_lifetime = -1;
RADV_IFACE->default_lifetime_sensitive = 1;
};
radv_iface_item:
@ -90,7 +96,11 @@ radv_iface_item:
| REACHABLE TIME expr { RADV_IFACE->reachable_time = $3; if (($3 < 0) || ($3 > 3600000)) cf_error("Reachable time must be in range 0-3600000"); }
| RETRANS TIMER expr { RADV_IFACE->retrans_timer = $3; if ($3 < 0) cf_error("Retrans timer must be 0 or positive"); }
| CURRENT HOP LIMIT expr { RADV_IFACE->current_hop_limit = $4; if (($4 < 0) || ($4 > 255)) cf_error("Current hop limit must be in range 0-255"); }
| DEFAULT LIFETIME expr { RADV_IFACE->default_lifetime = $3; if (($3 < 0) || ($3 > 9000)) cf_error("Default lifetime must be in range 0-9000"); }
| DEFAULT LIFETIME expr radv_sensitive {
RADV_IFACE->default_lifetime = $3;
if (($3 < 0) || ($3 > 9000)) cf_error("Default lifetime must be in range 0-9000");
if ($4 != -1) RADV_IFACE->default_lifetime_sensitive = $4;
}
| PREFIX radv_prefix { add_tail(&RADV_IFACE->pref_list, NODE this_radv_prefix); }
| RDNSS { init_list(&radv_dns_list); } radv_rdnss { add_tail_list(&RADV_IFACE->rdnss_list, &radv_dns_list); }
| DNSSL { init_list(&radv_dns_list); } radv_dnssl { add_tail_list(&RADV_IFACE->dnssl_list, &radv_dns_list); }
@ -147,14 +157,25 @@ radv_prefix_item:
SKIP bool { RADV_PREFIX->skip = $2; }
| ONLINK bool { RADV_PREFIX->onlink = $2; }
| AUTONOMOUS bool { RADV_PREFIX->autonomous = $2; }
| VALID LIFETIME expr { RADV_PREFIX->valid_lifetime = $3; if ($3 < 0) cf_error("Valid lifetime must be 0 or positive"); }
| PREFERRED LIFETIME expr { RADV_PREFIX->preferred_lifetime = $3; if ($3 < 0) cf_error("Preferred lifetime must be 0 or positive"); }
| VALID LIFETIME expr radv_sensitive {
RADV_PREFIX->valid_lifetime = $3;
if ($3 < 0) cf_error("Valid lifetime must be 0 or positive");
if ($4 != -1) RADV_PREFIX->valid_lifetime_sensitive = $4;
}
| PREFERRED LIFETIME expr radv_sensitive {
RADV_PREFIX->preferred_lifetime = $3;
if ($3 < 0) cf_error("Preferred lifetime must be 0 or positive");
if ($4 != -1) RADV_PREFIX->preferred_lifetime_sensitive = $4;
}
;
radv_prefix_finish:
{
if (RADV_PREFIX->preferred_lifetime > RADV_PREFIX->valid_lifetime)
cf_error("Preferred lifetime must be at most Valid lifetime");
if (RADV_PREFIX->valid_lifetime_sensitive > RADV_PREFIX->preferred_lifetime_sensitive)
cf_error("Valid lifetime sensitive requires that Preferred lifetime is sensitive too");
};
radv_prefix_opts:
@ -268,6 +289,11 @@ radv_mult:
| MULT expr { $$ = 0; radv_mult_val = $2; if (($2 < 1) || ($2 > 254)) cf_error("Multiplier must be in range 1-254"); }
;
radv_sensitive:
/* empty */ { $$ = -1 }
| SENSITIVE bool { $$ = $2 }
;
CF_CODE
CF_END

View file

@ -240,6 +240,7 @@ radv_prepare_ra(struct radv_iface *ifa)
{
struct proto_radv *ra = ifa->ra;
struct radv_config *cf = (struct radv_config *) (ra->p.cf);
struct radv_iface_config *ic = ifa->cf;
char *buf = ifa->sk->tbuf;
char *bufstart = buf;
@ -249,21 +250,22 @@ radv_prepare_ra(struct radv_iface *ifa)
pkt->type = ICMPV6_RA;
pkt->code = 0;
pkt->checksum = 0;
pkt->current_hop_limit = ifa->cf->current_hop_limit;
pkt->flags = (ifa->cf->managed ? OPT_RA_MANAGED : 0) |
(ifa->cf->other_config ? OPT_RA_OTHER_CFG : 0);
pkt->router_lifetime = htons(ifa->cf->default_lifetime);
pkt->reachable_time = htonl(ifa->cf->reachable_time);
pkt->retrans_timer = htonl(ifa->cf->retrans_timer);
pkt->current_hop_limit = ic->current_hop_limit;
pkt->flags = (ic->managed ? OPT_RA_MANAGED : 0) |
(ic->other_config ? OPT_RA_OTHER_CFG : 0);
pkt->router_lifetime = (ra->active || !ic->default_lifetime_sensitive) ?
htons(ic->default_lifetime) : 0;
pkt->reachable_time = htonl(ic->reachable_time);
pkt->retrans_timer = htonl(ic->retrans_timer);
buf += sizeof(*pkt);
if (ifa->cf->link_mtu)
if (ic->link_mtu)
{
struct radv_opt_mtu *om = (void *) buf;
om->type = OPT_MTU;
om->length = 1;
om->reserved = 0;
om->mtu = htonl(ifa->cf->link_mtu);
om->mtu = htonl(ic->link_mtu);
buf += sizeof (*om);
}
@ -288,26 +290,28 @@ radv_prepare_ra(struct radv_iface *ifa)
op->pxlen = addr->pxlen;
op->flags = (pc->onlink ? OPT_PX_ONLINK : 0) |
(pc->autonomous ? OPT_PX_AUTONOMOUS : 0);
op->valid_lifetime = htonl(pc->valid_lifetime);
op->preferred_lifetime = htonl(pc->preferred_lifetime);
op->valid_lifetime = (ra->active || !pc->valid_lifetime_sensitive) ?
htonl(pc->valid_lifetime) : 0;
op->preferred_lifetime = (ra->active || !pc->preferred_lifetime_sensitive) ?
htonl(pc->preferred_lifetime) : 0;
op->reserved = 0;
op->prefix = addr->prefix;
ipa_hton(op->prefix);
buf += sizeof(*op);
}
if (! ifa->cf->rdnss_local)
if (! ic->rdnss_local)
if (radv_prepare_rdnss(ifa, &cf->rdnss_list, &buf, bufend) < 0)
goto done;
if (radv_prepare_rdnss(ifa, &ifa->cf->rdnss_list, &buf, bufend) < 0)
if (radv_prepare_rdnss(ifa, &ic->rdnss_list, &buf, bufend) < 0)
goto done;
if (! ifa->cf->dnssl_local)
if (! ic->dnssl_local)
if (radv_prepare_dnssl(ifa, &cf->dnssl_list, &buf, bufend) < 0)
goto done;
if (radv_prepare_dnssl(ifa, &ifa->cf->dnssl_list, &buf, bufend) < 0)
if (radv_prepare_dnssl(ifa, &ic->dnssl_list, &buf, bufend) < 0)
goto done;
done:

View file

@ -30,6 +30,13 @@
* by RA_EV_* codes), and radv_timer(), which triggers sending RAs and
* computes the next timeout.
*
* The RAdv protocol could receive routes (through
* radv_import_control() and radv_rt_notify()), but only the
* configured trigger route is tracked (in &active var). When a radv
* protocol is reconfigured, the connected routing table is examined
* (in radv_check_active()) to have proper &active value in case of
* the specified trigger prefix was changed.
*
* Supported standards:
* - RFC 4861 - main RA standard
* - RFC 6106 - DNS extensions (RDDNS, DNSSL)
@ -93,6 +100,16 @@ radv_iface_notify(struct radv_iface *ifa, int event)
tm_start(ifa->timer, after);
}
static void
radv_iface_notify_all(struct proto_radv *ra, int event)
{
struct radv_iface *ifa;
WALK_LIST(ifa, ra->iface_list)
radv_iface_notify(ifa, event);
}
static struct radv_iface *
radv_iface_find(struct proto_radv *ra, struct iface *what)
{
@ -238,11 +255,68 @@ radv_ifa_notify(struct proto *p, unsigned flags, struct ifa *a)
radv_iface_notify(ifa, RA_EV_CHANGE);
}
static inline int radv_net_match_trigger(struct radv_config *cf, net *n)
{
return cf->trigger_valid &&
(n->n.pxlen == cf->trigger_pxlen) &&
ipa_equal(n->n.prefix, cf->trigger_prefix);
}
int
radv_import_control(struct proto *p, rte **new, ea_list **attrs UNUSED, struct linpool *pool UNUSED)
{
// struct proto_radv *ra = (struct proto_radv *) p;
struct radv_config *cf = (struct radv_config *) (p->cf);
if (radv_net_match_trigger(cf, (*new)->net))
return RIC_PROCESS;
return RIC_DROP;
}
static void
radv_rt_notify(struct proto *p, rtable *tbl UNUSED, net *n, rte *new, rte *old UNUSED, ea_list *attrs UNUSED)
{
struct proto_radv *ra = (struct proto_radv *) p;
struct radv_config *cf = (struct radv_config *) (p->cf);
if (radv_net_match_trigger(cf, n))
{
u8 old_active = ra->active;
ra->active = !!new;
if (ra->active == old_active)
return;
if (ra->active)
RADV_TRACE(D_EVENTS, "Triggered");
else
RADV_TRACE(D_EVENTS, "Suppressed");
radv_iface_notify_all(ra, RA_EV_CHANGE);
}
}
static int
radv_check_active(struct proto_radv *ra)
{
struct radv_config *cf = (struct radv_config *) (ra->p.cf);
if (! cf->trigger_valid)
return 1;
return rt_examine(ra->p.table, cf->trigger_prefix, cf->trigger_pxlen,
&(ra->p), ra->p.cf->out_filter);
}
static struct proto *
radv_init(struct proto_config *c)
{
struct proto *p = proto_new(c, sizeof(struct proto_radv));
p->accept_ra_types = RA_OPTIMAL;
p->import_control = radv_import_control;
p->rt_notify = radv_rt_notify;
p->if_notify = radv_if_notify;
p->ifa_notify = radv_ifa_notify;
return p;
@ -252,9 +326,10 @@ static int
radv_start(struct proto *p)
{
struct proto_radv *ra = (struct proto_radv *) p;
// struct radv_config *cf = (struct radv_config *) (p->cf);
struct radv_config *cf = (struct radv_config *) (p->cf);
init_list(&(ra->iface_list));
ra->active = !cf->trigger_valid;
return PS_UP;
}
@ -293,6 +368,9 @@ radv_reconfigure(struct proto *p, struct proto_config *c)
* causing nodes to temporary remove their default routes.
*/
p->cf = c; /* radv_check_active() requires proper p->cf */
ra->active = radv_check_active(ra);
struct iface *iface;
WALK_LIST(iface, iface_list)
{
@ -335,6 +413,14 @@ radv_copy_config(struct proto_config *dest, struct proto_config *src)
cfg_copy_list(&d->pref_list, &s->pref_list, sizeof(struct radv_prefix_config));
}
static void
radv_get_status(struct proto *p, byte *buf)
{
struct proto_radv *ra = (struct proto_radv *) p;
if (!ra->active)
strcpy(buf, "Suppressed");
}
struct protocol proto_radv = {
.name = "RAdv",
@ -343,5 +429,6 @@ struct protocol proto_radv = {
.start = radv_start,
.shutdown = radv_shutdown,
.reconfigure = radv_reconfigure,
.copy_config = radv_copy_config
.copy_config = radv_copy_config,
.get_status = radv_get_status
};

View file

@ -52,6 +52,10 @@ struct radv_config
list pref_list; /* Global list of prefix configs (struct radv_prefix_config) */
list rdnss_list; /* Global list of RDNSS configs (struct radv_rdnss_config) */
list dnssl_list; /* Global list of DNSSL configs (struct radv_dnssl_config) */
ip_addr trigger_prefix; /* Prefix of a trigger route, if defined */
u8 trigger_pxlen; /* Pxlen of a trigger route, if defined */
u8 trigger_valid; /* Whether a trigger route is defined */
};
struct radv_iface_config
@ -75,6 +79,7 @@ struct radv_iface_config
u32 retrans_timer;
u32 current_hop_limit;
u32 default_lifetime;
u8 default_lifetime_sensitive; /* Whether default_lifetime depends on trigger */
};
struct radv_prefix_config
@ -88,6 +93,8 @@ struct radv_prefix_config
u8 autonomous;
u32 valid_lifetime;
u32 preferred_lifetime;
u8 valid_lifetime_sensitive; /* Whether valid_lifetime depends on trigger */
u8 preferred_lifetime_sensitive; /* Whether preferred_lifetime depends on trigger */
};
struct radv_rdnss_config
@ -113,6 +120,7 @@ struct proto_radv
{
struct proto p;
list iface_list; /* List of active ifaces */
u8 active; /* Whether radv is active w.r.t. triggers */
};
struct radv_iface