From 36da2857bc911924a250a234f38cf58c3b21f1bc Mon Sep 17 00:00:00 2001 From: Ondrej Zajicek Date: Fri, 8 Feb 2013 23:58:27 +0100 Subject: [PATCH] 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. --- doc/bird.sgml | 43 ++++++++++++++++----- filter/filter.c | 6 +++ nest/route.h | 7 ++++ nest/rt-table.c | 37 ++++++++++++++++-- proto/radv/config.Y | 36 +++++++++++++++--- proto/radv/packets.c | 32 +++++++++------- proto/radv/radv.c | 91 +++++++++++++++++++++++++++++++++++++++++++- proto/radv/radv.h | 8 ++++ 8 files changed, 225 insertions(+), 35 deletions(-) diff --git a/doc/bird.sgml b/doc/bird.sgml index f6f9aad7..762834e3 100644 --- a/doc/bird.sgml +++ b/doc/bird.sgml @@ -691,8 +691,8 @@ This argument can be omitted if there exists only a single instance.

You can also select just routes added by a specific protocol. protocol . -

If BIRD is configured to keep filtered routes (see If BIRD is configured to keep filtered routes (see The dnssl that just specifies one DNS search domain. + +

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 - default lifetime + default lifetime 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 * - . + Default: 3 * rdnss local 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 - valid lifetime + valid lifetime 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 . Default: 86400 (1 day), preferred lifetime + preferred lifetime 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 + . + Default: 14400 (4 hours), diff --git a/filter/filter.c b/filter/filter.c index 44fcf293..c35d0425 100644 --- a/filter/filter.c +++ b/filter/filter.c @@ -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 ); diff --git a/nest/route.h b/nest/route.h index 177baa38..8fd01a66 100644 --- a/nest/route.h +++ b/nest/route.h @@ -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 *); diff --git a/nest/rt-table.c b/nest/rt-table.c index 99175448..75bfa6ba 100644 --- a/nest/rt-table.c +++ b/nest/rt-table.c @@ -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; } } diff --git a/proto/radv/config.Y b/proto/radv/config.Y index abccd2c7..fbec5a0a 100644 --- a/proto/radv/config.Y +++ b/proto/radv/config.Y @@ -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 radv_mult +%type 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 diff --git a/proto/radv/packets.c b/proto/radv/packets.c index 6fdfcaa3..dd839536 100644 --- a/proto/radv/packets.c +++ b/proto/radv/packets.c @@ -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: diff --git a/proto/radv/radv.c b/proto/radv/radv.c index 5e7296a3..a6b9b16c 100644 --- a/proto/radv/radv.c +++ b/proto/radv/radv.c @@ -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 }; diff --git a/proto/radv/radv.h b/proto/radv/radv.h index 48af8c00..f80e4530 100644 --- a/proto/radv/radv.h +++ b/proto/radv/radv.h @@ -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