From 72aebd69f7cfbcaab33aab1fba48ed05d8bf9c61 Mon Sep 17 00:00:00 2001 From: Heiko Wundram Date: Fri, 11 Sep 2020 21:40:56 +0200 Subject: [PATCH] Update RADV routing protocol to work with device routes to assign prefixes to interfaces. --- proto/radv/config.Y | 1 + proto/radv/packets.c | 8 +- proto/radv/radv.c | 207 ++++++++++++++++++++++++------------------- proto/radv/radv.h | 11 ++- 4 files changed, 131 insertions(+), 96 deletions(-) diff --git a/proto/radv/config.Y b/proto/radv/config.Y index 84a2de0..3d8aef1 100644 --- a/proto/radv/config.Y +++ b/proto/radv/config.Y @@ -61,6 +61,7 @@ radv_proto_item: RADV_CFG->trigger_pxlen = $2.len; RADV_CFG->trigger_valid = 1; } + | TRIGGER FILTER bool { RADV_CFG->trigger_keep = !$3; } | PROPAGATE ROUTES bool { RADV_CFG->propagate_routes = $3; } ; diff --git a/proto/radv/packets.c b/proto/radv/packets.c index 7d54a82..0b1370f 100644 --- a/proto/radv/packets.c +++ b/proto/radv/packets.c @@ -351,14 +351,18 @@ radv_prepare_ra(struct radv_iface *ifa) if (radv_prepare_dnssl(ifa, &ic->dnssl_list, &buf, bufend) < 0) goto done; - if (p->fib_up) + if (cf->propagate_routes) { FIB_WALK(&p->routes, n) { struct radv_route *rt = (void *) n; /* Skip invalid routes that are past linger timeout but still not pruned */ - if (!rt->valid && (rt->changed + ic->route_linger_time <= now)) + if (!cf->trigger_keep && radv_match_trigger(cf, rt->n.prefix, rt->n.pxlen)) + continue; + else if (!rt->valid && (rt->changed + ic->route_linger_time <= now)) + continue; + else if (rt->prefixroute && rt->iface == ifa->iface) continue; if (radv_prepare_route(ifa, rt, &buf, bufend) < 0) diff --git a/proto/radv/radv.c b/proto/radv/radv.c index 22e4b2f..72ffa95 100644 --- a/proto/radv/radv.c +++ b/proto/radv/radv.c @@ -28,11 +28,14 @@ * processes asynchronous events (specified 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. + * The RAdv protocol could receive routes (through radv_rt_notify()), which it + * tracks and assigns device routes as prefixes to the corresponding interface. + * A configured trigger route is used to set up the active/inactive state of + * of the protocol, which in turn causes updates to interfaces when the settings + * change to remove/add prefixes. The routes that are collected by + * radv_rt_notify() are also used to export routes to the RA messages, where + * the routes that signal device prefixes are filtered on the respective + * interface that they are announced on. * * Supported standards: * - RFC 4861 - main RA standard @@ -90,9 +93,9 @@ static struct radv_prefix_config default_prefix = { static struct radv_prefix_config dead_prefix = { }; -/* Find a corresponding config for the given prefix */ +/* Find a corresponding config for the given prefix from address */ static struct radv_prefix_config * -radv_prefix_match(struct radv_iface *ifa, struct ifa *a) +radv_prefix_match_addr(struct radv_iface *ifa, struct ifa *a) { struct radv_proto *p = ifa->ra; struct radv_config *cf = (struct radv_config *) (p->p.cf); @@ -112,6 +115,25 @@ radv_prefix_match(struct radv_iface *ifa, struct ifa *a) return &default_prefix; } +/* Find a corresponding config for the given prefix from route */ +static struct radv_prefix_config * +radv_prefix_match_route(struct radv_iface *ifa, struct radv_route *rt) +{ + struct radv_proto *p = ifa->ra; + struct radv_config *cf = (struct radv_config *) (p->p.cf); + struct radv_prefix_config *pc; + + WALK_LIST(pc, ifa->cf->pref_list) + if ((rt->n.pxlen >= pc->pxlen) && ipa_in_net(rt->n.prefix, pc->prefix, pc->pxlen)) + return pc; + + WALK_LIST(pc, cf->pref_list) + if ((rt->n.pxlen >= pc->pxlen) && ipa_in_net(rt->n.prefix, pc->prefix, pc->pxlen)) + return pc; + + return &default_prefix; +} + /* * Go through the list of prefixes, compare them with configs and decide if we * want them or not. @@ -120,6 +142,7 @@ static void radv_prepare_prefixes(struct radv_iface *ifa) { struct radv_proto *p = ifa->ra; + struct radv_config *cf = (struct radv_config *) (p->p.cf); struct radv_prefix *pfx, *next; /* First mark all the prefixes as unused */ @@ -130,7 +153,7 @@ radv_prepare_prefixes(struct radv_iface *ifa) struct ifa *addr; WALK_LIST(addr, ifa->iface->addrs) { - struct radv_prefix_config *pc = radv_prefix_match(ifa, addr); + struct radv_prefix_config *pc = radv_prefix_match_addr(ifa, addr); if (!pc || pc->skip) continue; @@ -146,7 +169,7 @@ radv_prepare_prefixes(struct radv_iface *ifa) if (!existing) { - RADV_TRACE(D_EVENTS, "Adding new prefix %I/%d on %s", + RADV_TRACE(D_EVENTS, "Adding new prefix %I/%d from address on %s", addr->prefix, addr->pxlen, ifa->iface->name); existing = mb_allocz(ifa->pool, sizeof *existing); @@ -165,6 +188,53 @@ radv_prepare_prefixes(struct radv_iface *ifa) existing->cf = pc; } + /* Find all prefixes that exist as device routes. */ + FIB_WALK(&p->routes, n) + { + struct radv_route *rt = (void *) n; + + /* Invalid prefixes and explicitly rejected trigger routes don't make it here */ + if (!cf->trigger_keep && radv_match_trigger(cf, rt->n.prefix, rt->n.pxlen)) + continue; + else if (!rt->valid || !rt->prefixroute || rt->iface != ifa->iface) + continue; + + struct radv_prefix_config *pc = radv_prefix_match_route(ifa, rt); + + if (pc->skip) + continue; + + /* Do we have it already? */ + struct radv_prefix *existing = NULL; + WALK_LIST(pfx, ifa->prefixes) + if ((pfx->len == rt->n.pxlen) && ipa_equal(pfx->prefix, rt->n.prefix)) + { + existing = pfx; + break; + } + + if (!existing) + { + RADV_TRACE(D_EVENTS, "Adding new prefix %I/%d from route on %s", + rt->n.prefix, rt->n.pxlen, ifa->iface->name); + + existing = mb_allocz(ifa->pool, sizeof *existing); + existing->prefix = rt->n.prefix; + existing->len = rt->n.pxlen; + add_tail(&ifa->prefixes, NODE existing); + } + + /* + * Update the information (it may have changed, or even bring a prefix back + * to life). + */ + existing->valid = 1; + existing->changed = now; + existing->mark = 1; + existing->cf = pc; + } + FIB_WALK_END; + WALK_LIST_DELSAFE(pfx, next, ifa->prefixes) { if (pfx->valid && !pfx->mark) @@ -384,28 +454,6 @@ radv_ifa_notify(struct proto *P, unsigned flags UNUSED, 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 radv_proto *p = (struct radv_proto *) P; - struct radv_config *cf = (struct radv_config *) (P->cf); - - if (radv_net_match_trigger(cf, (*new)->net)) - return RIC_PROCESS; - - if (cf->propagate_routes) - return RIC_PROCESS; - else - return RIC_DROP; -} - static void radv_rt_notify(struct proto *P, rtable *tbl UNUSED, net *n, rte *new, rte *old UNUSED, ea_list *attrs) { @@ -413,27 +461,23 @@ radv_rt_notify(struct proto *P, rtable *tbl UNUSED, net *n, rte *new, rte *old U struct radv_config *cf = (struct radv_config *) (P->cf); struct radv_route *rt; eattr *ea; + struct radv_iface *notify_iface = NULL; + u8 notify_all = cf->propagate_routes; - if (radv_net_match_trigger(cf, n)) + if (radv_match_trigger(cf, n->n.prefix, n->n.pxlen)) { - u8 old_active = p->active; - p->active = !!new; - - if (p->active == old_active) - return; - - if (p->active) - RADV_TRACE(D_EVENTS, "Triggered"); - else - RADV_TRACE(D_EVENTS, "Suppressed"); + u8 active = !!new; + if (p->active != active) { + if (active) + RADV_TRACE(D_EVENTS, "Triggered"); + else + RADV_TRACE(D_EVENTS, "Suppressed"); - radv_iface_notify_all(p, RA_EV_CHANGE); - return; + p->active = active; + notify_all = 1; + } } - if (!cf->propagate_routes) - return; - /* * Some other route we want to send (or stop sending). Update the cache, * with marking a removed one as dead or creating a new one as needed. @@ -465,10 +509,14 @@ radv_rt_notify(struct proto *P, rtable *tbl UNUSED, net *n, rte *new, rte *old U lifetime_set = 1; } + u8 prefixroute = new->attrs->iface && new->attrs->dest == RTD_DEVICE && new->attrs->cast == RTC_UNICAST; + rt = fib_get(&p->routes, &n->n.prefix, n->n.pxlen); /* Ignore update if nothing changed */ if (rt->valid && + (rt->prefixroute == prefixroute) && + (rt->iface == new->attrs->iface) && (rt->preference == preference) && (rt->preference_set == preference_set) && (rt->lifetime == lifetime) && @@ -484,6 +532,18 @@ radv_rt_notify(struct proto *P, rtable *tbl UNUSED, net *n, rte *new, rte *old U rt->preference_set = preference_set; rt->lifetime = lifetime; rt->lifetime_set = lifetime_set; + + /* If route type changes, signal interfaces */ + u8 oldprefixroute = rt->prefixroute; + struct radv_iface *oldifa = radv_iface_find(p, rt->iface); + + rt->prefixroute = prefixroute; + rt->iface = new->attrs->iface; + if (rt->prefixroute) + notify_iface = radv_iface_find(p, rt->iface); + + if (!notify_all && oldprefixroute && oldifa && notify_iface != oldifa) + radv_iface_notify(oldifa, RA_EV_CHANGE); } else { @@ -496,13 +556,18 @@ radv_rt_notify(struct proto *P, rtable *tbl UNUSED, net *n, rte *new, rte *old U /* Invalidate the route */ rt->valid = 0; rt->changed = now; + if (rt->prefixroute) + notify_iface = radv_iface_find(p, rt->iface); /* Invalidated route will be pruned eventually */ bird_clock_t expires = rt->changed + cf->max_linger_time; p->prune_time = MIN(p->prune_time, expires); } - radv_iface_notify_all(p, RA_EV_CHANGE); + if (notify_all) + radv_iface_notify_all(p, RA_EV_CHANGE); + else if (notify_iface) + radv_iface_notify(notify_iface, RA_EV_CHANGE); } /* @@ -516,10 +581,6 @@ radv_prune_routes(struct radv_proto *p) bird_clock_t next = TIME_INFINITY; bird_clock_t expires = 0; - /* Should not happen */ - if (!p->fib_up) - return; - struct fib_iterator fit; FIB_ITERATE_INIT(&fit, &p->routes); @@ -548,25 +609,12 @@ again: p->prune_time = next; } -static int -radv_check_active(struct radv_proto *p) -{ - struct radv_config *cf = (struct radv_config *) (p->p.cf); - - if (! cf->trigger_valid) - return 1; - - return rt_examine(p->p.table, cf->trigger_prefix, cf->trigger_pxlen, - &(p->p), p->p.cf->out_filter); -} - static struct proto * radv_init(struct proto_config *c) { struct proto *P = proto_new(c, sizeof(struct radv_proto)); 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; @@ -574,21 +622,6 @@ radv_init(struct proto_config *c) return P; } -static void -radv_set_fib(struct radv_proto *p, int up) -{ - if (up == p->fib_up) - return; - - if (up) - fib_init(&p->routes, p->p.pool, sizeof(struct radv_route), 4, NULL); - else - fib_free(&p->routes); - - p->fib_up = up; - p->prune_time = TIME_INFINITY; -} - static int radv_start(struct proto *P) { @@ -599,8 +632,7 @@ radv_start(struct proto *P) p->valid = 1; p->active = !cf->trigger_valid; - p->fib_up = 0; - radv_set_fib(p, cf->propagate_routes); + fib_init(&p->routes, p->p.pool, sizeof(struct radv_route), 0, NULL); p->prune_time = TIME_INFINITY; return PS_UP; @@ -637,15 +669,8 @@ radv_reconfigure(struct proto *P, struct proto_config *c) struct radv_config *old = (struct radv_config *) (P->cf); struct radv_config *new = (struct radv_config *) c; - P->cf = c; /* radv_check_active() requires proper P->cf */ - p->active = radv_check_active(p); - - /* Allocate or free FIB */ - radv_set_fib(p, new->propagate_routes); - - /* We started to accept routes so we need to refeed them */ - if (!old->propagate_routes && new->propagate_routes) - proto_request_feeding(&p->p); + P->cf = c; + p->active = !new->trigger_valid || fib_find(&p->routes, &new->trigger_prefix, new->trigger_pxlen); struct iface *iface; WALK_LIST(iface, iface_list) diff --git a/proto/radv/radv.h b/proto/radv/radv.h index ab08139..d61fbe4 100644 --- a/proto/radv/radv.h +++ b/proto/radv/radv.h @@ -53,6 +53,7 @@ struct radv_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 */ + u8 trigger_keep; /* Keep the trigger route as a normal route */ u8 propagate_routes; /* Do we propagate more specific routes (RFC 4191)? */ u32 max_linger_time; /* Maximum of interface route_linger_time */ }; @@ -131,11 +132,13 @@ struct radv_dnssl_config struct radv_route { struct fib_node n; + struct iface *iface; /* Interface route is bound to */ u32 lifetime; /* Lifetime from an attribute */ u8 lifetime_set; /* Whether lifetime is defined */ u8 preference; /* Preference of the route, RA_PREF_* */ u8 preference_set; /* Whether preference is defined */ - u8 valid; /* Whethe route is valid or withdrawn */ + u8 valid; /* Whether route is valid or withdrawn */ + u8 prefixroute; /* Prefix route indicator */ bird_clock_t changed; /* Last time when the route changed */ }; @@ -145,7 +148,6 @@ struct radv_proto list iface_list; /* List of active ifaces */ u8 valid; /* Router is valid for forwarding, used for shutdown */ u8 active; /* Whether radv is active w.r.t. triggers */ - u8 fib_up; /* FIB table (routes) is initialized */ struct fib routes; /* FIB table of specific routes (struct radv_route) */ bird_clock_t prune_time; /* Next time of route table pruning */ }; @@ -214,6 +216,9 @@ int radv_process_domain(struct radv_dnssl_config *cf); void radv_send_ra(struct radv_iface *ifa); int radv_sk_open(struct radv_iface *ifa); - +static inline int radv_match_trigger(struct radv_config *cf, ip_addr prefix, uint pxlen) +{ + return cf->trigger_valid && (pxlen == cf->trigger_pxlen) && ipa_equal(prefix, cf->trigger_prefix); +} #endif /* _BIRD_RADV_H_ */ -- 2.20.1