From 00a09f3c367e79297f827b52ec5f16842db1ac4e Mon Sep 17 00:00:00 2001 From: Ondrej Zajicek Date: Sun, 15 Apr 2012 15:07:58 +0200 Subject: [PATCH 01/15] Implement RA_ACCEPTED mode of route propagation. --- nest/route.h | 7 +- nest/rt-table.c | 416 ++++++++++++++++++++++++++++----------------- proto/bgp/bgp.c | 7 +- proto/bgp/bgp.h | 1 + proto/bgp/config.Y | 10 +- 5 files changed, 281 insertions(+), 160 deletions(-) diff --git a/nest/route.h b/nest/route.h index 34536609..59daf803 100644 --- a/nest/route.h +++ b/nest/route.h @@ -219,11 +219,12 @@ typedef struct rte { } u; } rte; -#define REF_COW 1 /* Copy this rte on write */ +#define REF_COW 1 /* Copy this rte on write */ /* Types of route announcement, also used as flags */ -#define RA_OPTIMAL 1 /* Announcement of optimal route change */ -#define RA_ANY 2 /* Announcement of any route change */ +#define RA_OPTIMAL 1 /* Announcement of optimal route change */ +#define RA_ACCEPTED 2 /* Announcement of first accepted route */ +#define RA_ANY 3 /* Announcement of any route change */ struct config; diff --git a/nest/rt-table.c b/nest/rt-table.c index 310c1afd..578a6ba3 100644 --- a/nest/rt-table.c +++ b/nest/rt-table.c @@ -182,86 +182,73 @@ rte_trace_out(unsigned int flag, struct proto *p, rte *e, char *msg) rte_trace(p, e, '<', msg); } -static inline void -do_rte_announce(struct announce_hook *ah, int type UNUSED, net *net, rte *new, rte *old, ea_list *tmpa, int refeed) +static rte * +export_filter(struct announce_hook *ah, rte *rt0, rte **rt_free, ea_list **tmpa, int silent) { struct proto *p = ah->proto; struct filter *filter = ah->out_filter; struct proto_stats *stats = ah->stats; + ea_list *tmpb = NULL; + rte *rt; + int v; - rte *new0 = new; - rte *old0 = old; - int ok; + rt = rt0; + *rt_free = NULL; - if (new) + /* If called does not care for eattrs, we prepare one internally */ + if (!tmpa) { - stats->exp_updates_received++; - - char *drop_reason = NULL; - if ((ok = p->import_control ? p->import_control(p, &new, &tmpa, rte_update_pool) : 0) < 0) - { - stats->exp_updates_rejected++; - drop_reason = "rejected by protocol"; - } - else if (ok) - rte_trace_out(D_FILTERS, p, new, "forced accept by protocol"); - else if ((filter == FILTER_REJECT) || - (filter && f_run(filter, &new, &tmpa, rte_update_pool, FF_FORCE_TMPATTR) > F_ACCEPT)) - { - stats->exp_updates_filtered++; - drop_reason = "filtered out"; - } - if (drop_reason) - { - rte_trace_out(D_FILTERS, p, new, drop_reason); - if (new != new0) - rte_free(new); - new = NULL; - } - } - else - stats->exp_withdraws_received++; - - /* - * This is a tricky part - we don't know whether route 'old' was - * exported to protocol 'p' or was filtered by the export filter. - * We try tu run the export filter to know this to have a correct - * value in 'old' argument of rte_update (and proper filter value) - * - * FIXME - this is broken because 'configure soft' may change - * filters but keep routes. Refeed is expected to be called after - * change of the filters and with old == new, therefore we do not - * even try to run the filter on an old route, This may lead to - * 'spurious withdraws' but ensure that there are no 'missing - * withdraws'. - * - * This is not completely safe as there is a window between - * reconfiguration and the end of refeed - if a newly filtered - * route disappears during this period, proper withdraw is not - * sent (because old would be also filtered) and the route is - * not refeeded (because it disappeared before that). - */ - - if (old && !refeed) - { - if (filter == FILTER_REJECT) - old = NULL; - else - { - ea_list *tmpb = p->make_tmp_attrs ? p->make_tmp_attrs(old, rte_update_pool) : NULL; - ok = p->import_control ? p->import_control(p, &old, &tmpb, rte_update_pool) : 0; - if (ok < 0 || (!ok && filter && f_run(filter, &old, &tmpb, rte_update_pool, FF_FORCE_TMPATTR) > F_ACCEPT)) - { - if (old != old0) - rte_free(old); - old = NULL; - } - } + struct proto *src = rt->attrs->proto; + tmpb = src->make_tmp_attrs ? src->make_tmp_attrs(rt, rte_update_pool) : NULL; + tmpa = &tmpb; } - /* FIXME - This is broken because of incorrect 'old' value (see above) */ - if (!new && !old) - return; + v = p->import_control ? p->import_control(p, &rt, tmpa, rte_update_pool) : 0; + if (v < 0) + { + if (silent) + goto reject; + + stats->exp_updates_rejected++; + rte_trace_out(D_FILTERS, p, rt, "rejected by protocol"); + goto reject; + } + if (v > 0) + { + if (!silent) + rte_trace_out(D_FILTERS, p, rt, "forced accept by protocol"); + goto accept; + } + + v = filter && ((filter == FILTER_REJECT) || + (f_run(filter, &rt, tmpa, rte_update_pool, FF_FORCE_TMPATTR) > F_ACCEPT)); + if (v) + { + if (silent) + goto reject; + + stats->exp_updates_filtered++; + rte_trace_out(D_FILTERS, p, rt, "filtered out"); + goto reject; + } + + accept: + if (rt != rt0) + *rt_free = rt; + return rt; + + reject: + /* Discard temporary rte */ + if (rt != rt0) + rte_free(rt); + return NULL; +} + +static void +do_rt_notify(struct announce_hook *ah, net *net, rte *new, rte *old, ea_list *tmpa, int refeed) +{ + struct proto *p = ah->proto; + struct proto_stats *stats = ah->stats; if (new) stats->exp_updates_accepted++; @@ -297,10 +284,174 @@ do_rte_announce(struct announce_hook *ah, int type UNUSED, net *net, rte *new, r } else p->rt_notify(p, ah->table, net, new, old, new->attrs->eattrs); - if (new && new != new0) /* Discard temporary rte's */ - rte_free(new); - if (old && old != old0) - rte_free(old); + +} + + +static void +rt_notify_basic(struct announce_hook *ah, net *net, rte *new, rte *old, ea_list *tmpa, int refeed) +{ + // struct proto *p = ah->proto; + struct proto_stats *stats = ah->stats; + + rte *new_free = NULL; + rte *old_free = NULL; + + if (new) + stats->exp_updates_received++; + else + stats->exp_withdraws_received++; + + /* + * This is a tricky part - we don't know whether route 'old' was + * exported to protocol 'p' or was filtered by the export filter. + * We try to run the export filter to know this to have a correct + * value in 'old' argument of rte_update (and proper filter value) + * + * FIXME - this is broken because 'configure soft' may change + * filters but keep routes. Refeed is expected to be called after + * change of the filters and with old == new, therefore we do not + * even try to run the filter on an old route, This may lead to + * 'spurious withdraws' but ensure that there are no 'missing + * withdraws'. + * + * This is not completely safe as there is a window between + * reconfiguration and the end of refeed - if a newly filtered + * route disappears during this period, proper withdraw is not + * sent (because old would be also filtered) and the route is + * not refeeded (because it disappeared before that). + */ + + if (new) + new = export_filter(ah, new, &new_free, &tmpa, 0); + + if (old && !refeed) + old = export_filter(ah, old, &old_free, NULL, 1); + + /* FIXME - This is broken because of incorrect 'old' value (see above) */ + if (!new && !old) + return; + + do_rt_notify(ah, net, new, old, tmpa, refeed); + + /* Discard temporary rte's */ + if (new_free) + rte_free(new_free); + if (old_free) + rte_free(old_free); +} + +static void +rt_notify_accepted(struct announce_hook *ah, net *net, rte *new_changed, rte *old_changed, rte *before_old, + ea_list *tmpa, int feed) +{ + // struct proto *p = ah->proto; + struct proto_stats *stats = ah->stats; + + rte *new_best = NULL; + rte *old_best = NULL; + rte *new_free = NULL; + rte *old_free = NULL; + rte *r; + + /* Used to track whethe we met old_changed position. If it is NULL + it was the first and met it implicitly before current best route. */ + int old_meet = (old_changed && !before_old) ? 1 : 0; + + if (new_changed) + stats->exp_updates_received++; + else + stats->exp_withdraws_received++; + + /* First, find the new_best route - first accepted by filters */ + for (r=net->routes; r; r=r->next) + { + if (new_best = export_filter(ah, r, &new_free, &tmpa, 0)) + break; + + /* Note if we walked around the position of old_changed route */ + if (r == before_old) + old_meet = 1; + } + + /* + * Second, handle the feed case. That means we do not care for + * old_best. It is NULL for feed, and the new_best for refeed. + * For refeed, there is a hack similar to one in rt_notify_basic() + * to ensure withdraws in case of changed filters + */ + if (feed) + { + if (feed == 2) /* refeed */ + old_best = new_best ? new_best : net->routes; + else + old_best = NULL; + + if (!new_best && !old_best) + return; + + goto found; + } + + /* + * Now, we find the old_best route. Generally, it is the same as the + * new_best, unless new_best is the same as new_changed or + * old_changed is accepted before new_best. + * + * There are four cases: + * + * - We would find and accept old_changed before new_best, therefore + * old_changed is old_best. In remaining cases we suppose this + * is not true. + * + * - We found no new_best, therefore there is also no old_best and + * we ignore this withdraw. + * + * - We found new_best different than new_changed, therefore + * old_best is the same as new_best and we ignore this update. + * + * - We found new_best the same as new_changed, therefore it cannot + * be old_best and we have to continue search for old_best. + */ + + /* First case */ + if (old_meet) + if (old_best = export_filter(ah, old_changed, &old_free, NULL, 1)) + goto found; + + /* Second case */ + if (!new_best) + return; + + /* Third case, we use r insead of new_best, because export_filter() could change it */ + if (r != new_changed) + { + if (new_free) + rte_free(new_free); + return; + } + + /* Fourth case */ + for (; r; r=r->next) + { + if (old_best = export_filter(ah, r, &old_free, NULL, 1)) + goto found; + + if (r == before_old) + if (old_best = export_filter(ah, old_changed, &old_free, NULL, 1)) + goto found; + } + + /* Implicitly, old_best is NULL and new_best is non-NULL */ + + found: + do_rt_notify(ah, net, new_best, old_best, tmpa, (feed == 2)); + + /* Discard temporary rte's */ + if (new_free) + rte_free(new_free); + if (old_free) + rte_free(old_free); } /** @@ -333,7 +484,7 @@ do_rte_announce(struct announce_hook *ah, int type UNUSED, net *net, rte *new, r * the protocol gets called. */ static void -rte_announce(rtable *tab, unsigned type, net *net, rte *new, rte *old, ea_list *tmpa) +rte_announce(rtable *tab, unsigned type, net *net, rte *new, rte *old, rte *before_old, ea_list *tmpa) { struct announce_hook *a; @@ -352,11 +503,13 @@ rte_announce(rtable *tab, unsigned type, net *net, rte *new, rte *old, ea_list * { ASSERT(a->proto->core_state == FS_HAPPY || a->proto->core_state == FS_FEEDING); if (a->proto->accept_ra_types == type) - do_rte_announce(a, type, net, new, old, tmpa, 0); + if (type == RA_ACCEPTED) + rt_notify_accepted(a, net, new, old, before_old, tmpa, 0); + else + rt_notify_basic(a, net, new, old, tmpa, 0); } } - static inline int rte_validate(rte *e) { @@ -419,9 +572,10 @@ rte_recalculate(struct announce_hook *ah, net *net, rte *new, ea_list *tmpa, str struct proto *p = ah->proto; struct rtable *table = ah->table; struct proto_stats *stats = ah->stats; + rte *before_old = NULL; rte *old_best = net->routes; rte *old = NULL; - rte **k, *r, *s; + rte **k; k = &net->routes; /* Find and remove original route from the same protocol */ while (old = *k) @@ -466,8 +620,12 @@ rte_recalculate(struct announce_hook *ah, net *net, rte *new, ea_list *tmpa, str break; } k = &old->next; + before_old = old; } + if (!old) + before_old = NULL; + if (!old && !new) { stats->imp_withdraws_ignored++; @@ -484,82 +642,21 @@ rte_recalculate(struct announce_hook *ah, net *net, rte *new, ea_list *tmpa, str if (old) stats->imp_routes--; - rte_announce(table, RA_ANY, net, new, old, tmpa); - - if (src->rte_recalculate && src->rte_recalculate(table, net, new, old, old_best)) - goto do_recalculate; - - if (new && rte_better(new, old_best)) + if (new) { - /* The first case - the new route is cleary optimal, we link it - at the first position and announce it */ + for (k=&net->routes; *k; k=&(*k)->next) + if (rte_better(new, *k)) + break; - rte_trace_in(D_ROUTES, p, new, "added [best]"); - rte_announce(table, RA_OPTIMAL, net, new, old_best, tmpa); - new->next = net->routes; - net->routes = new; - } - else if (old == old_best) - { - /* The second case - the old best route disappeared, we add the - new route (if we have any) to the list (we don't care about - position) and then we elect the new optimal route and relink - that route at the first position and announce it. New optimal - route might be NULL if there is no more routes */ + new->lastmod = now; + new->next = *k; + *k = new; - do_recalculate: - /* Add the new route to the list */ - if (new) - { - rte_trace_in(D_ROUTES, p, new, "added"); - new->next = net->routes; - net->routes = new; - } - - /* Find new optimal route */ - r = NULL; - for (s=net->routes; s; s=s->next) - if (rte_better(s, r)) - r = s; - - /* Announce optimal route */ - rte_announce(table, RA_OPTIMAL, net, r, old_best, tmpa); - - /* And relink it (if there is any) */ - if (r) - { - k = &net->routes; - while (s = *k) - { - if (s == r) - { - *k = r->next; - break; - } - k = &s->next; - } - r->next = net->routes; - net->routes = r; - } - else if (table->gc_counter++ >= table->config->gc_max_ops && - table->gc_time + table->config->gc_min_time <= now) - rt_schedule_gc(table); - } - else if (new) - { - /* The third case - the new route is not better than the old - best route (therefore old_best != NULL) and the old best - route was not removed (therefore old_best == net->routes). - We just link the new route after the old best route. */ - - ASSERT(net->routes != NULL); - new->next = net->routes->next; - net->routes->next = new; - rte_trace_in(D_ROUTES, p, new, "added"); + rte_trace_in(D_ROUTES, p, new, net->routes == new ? "added [best]" : "added"); } /* Log the route removal */ - if (!new && old && (p->debug & D_ROUTES)) + if (!new && (p->debug & D_ROUTES)) { if (old != old_best) rte_trace_in(D_ROUTES, p, old, "removed"); @@ -569,6 +666,15 @@ rte_recalculate(struct announce_hook *ah, net *net, rte *new, ea_list *tmpa, str rte_trace_in(D_ROUTES, p, old, "removed [sole]"); } + rte_announce(table, RA_ANY, net, new, old, NULL, tmpa); + rte_announce(table, RA_OPTIMAL, net, net->routes, old_best, NULL, tmpa); + rte_announce(table, RA_ACCEPTED, net, new, old, before_old, tmpa); + + if (!net->routes && + (table->gc_counter++ >= table->config->gc_max_ops) && + (table->gc_time + table->config->gc_min_time <= now)) + rt_schedule_gc(table); + if (old) { if (p->rte_remove) @@ -577,7 +683,6 @@ rte_recalculate(struct announce_hook *ah, net *net, rte *new, ea_list *tmpa, str } if (new) { - new->lastmod = now; if (p->rte_insert) p->rte_insert(net, new); } @@ -709,7 +814,7 @@ rte_announce_i(rtable *tab, unsigned type, net *n, rte *new, rte *old) rte_update_lock(); src = new->attrs->proto; tmpa = src->make_tmp_attrs ? src->make_tmp_attrs(new, rte_update_pool) : NULL; - rte_announce(tab, type, n, new, old, tmpa); + rte_announce(tab, type, n, new, old, NULL, tmpa); rte_update_unlock(); } @@ -1259,12 +1364,15 @@ rt_commit(struct config *new, struct config *old) static inline void do_feed_baby(struct proto *p, int type, struct announce_hook *h, net *n, rte *e) { - struct proto *q = e->attrs->proto; + struct proto *src = e->attrs->proto; ea_list *tmpa; rte_update_lock(); - tmpa = q->make_tmp_attrs ? q->make_tmp_attrs(e, rte_update_pool) : NULL; - do_rte_announce(h, type, n, e, p->refeeding ? e : NULL, tmpa, p->refeeding); + tmpa = src->make_tmp_attrs ? src->make_tmp_attrs(e, rte_update_pool) : NULL; + if (type == RA_ACCEPTED) + rt_notify_accepted(h, n, e, NULL, NULL, tmpa, p->refeeding ? 2 : 1); + else + rt_notify_basic(h, n, e, p->refeeding ? e : NULL, tmpa, p->refeeding); rte_update_unlock(); } diff --git a/proto/bgp/bgp.c b/proto/bgp/bgp.c index 4dd4b7be..099a39a9 100644 --- a/proto/bgp/bgp.c +++ b/proto/bgp/bgp.c @@ -909,7 +909,7 @@ bgp_init(struct proto_config *C) struct proto *P = proto_new(C, sizeof(struct bgp_proto)); struct bgp_proto *p = (struct bgp_proto *) P; - P->accept_ra_types = RA_OPTIMAL; + P->accept_ra_types = c->secondary ? RA_ACCEPTED : RA_OPTIMAL; P->rt_notify = bgp_rt_notify; P->rte_better = bgp_rte_better; P->import_control = bgp_import_control; @@ -955,12 +955,14 @@ bgp_check_config(struct bgp_config *c) if (internal && c->rs_client) cf_error("Only external neighbor can be RS client"); + /* if (c->multihop && (c->gw_mode == GW_DIRECT)) cf_error("Multihop BGP cannot use direct gateway mode"); if (c->multihop && (ipa_has_link_scope(c->remote_ip) || ipa_has_link_scope(c->source_addr))) cf_error("Multihop BGP cannot be used with link-local addresses"); + */ /* Different default based on rs_client */ if (!c->missing_lladdr) @@ -968,7 +970,8 @@ bgp_check_config(struct bgp_config *c) /* Different default for gw_mode */ if (!c->gw_mode) - c->gw_mode = (c->multihop || internal) ? GW_RECURSIVE : GW_DIRECT; + // c->gw_mode = (c->multihop || internal) ? GW_RECURSIVE : GW_DIRECT; + c->gw_mode = GW_DIRECT; } static int diff --git a/proto/bgp/bgp.h b/proto/bgp/bgp.h index a8c5818e..87734425 100644 --- a/proto/bgp/bgp.h +++ b/proto/bgp/bgp.h @@ -43,6 +43,7 @@ struct bgp_config { u32 route_limit; /* Number of routes that may be imported, 0 means disable limit */ int passive; /* Do not initiate outgoing connection */ int interpret_communities; /* Hardwired handling of well-known communities */ + int secondary; /* Accept also non-best routes (i.e. RA_ACCEPTED) */ unsigned connect_retry_time; unsigned hold_time, initial_hold_time; unsigned keepalive_time; diff --git a/proto/bgp/config.Y b/proto/bgp/config.Y index 78ca52de..356415a2 100644 --- a/proto/bgp/config.Y +++ b/proto/bgp/config.Y @@ -25,7 +25,8 @@ CF_KEYWORDS(BGP, LOCAL, NEIGHBOR, AS, HOLD, TIME, CONNECT, RETRY, CLUSTER, ID, AS4, ADVERTISE, IPV4, CAPABILITIES, LIMIT, PASSIVE, PREFER, OLDER, MISSING, LLADDR, DROP, IGNORE, ROUTE, REFRESH, INTERPRET, COMMUNITIES, BGP_ORIGINATOR_ID, BGP_CLUSTER_LIST, IGP, - TABLE, GATEWAY, DIRECT, RECURSIVE, MED, TTL, SECURITY, DETERMINISTIC) + TABLE, GATEWAY, DIRECT, RECURSIVE, MED, TTL, SECURITY, DETERMINISTIC, + SECONDARY) CF_GRAMMAR @@ -73,19 +74,25 @@ bgp_proto: | bgp_proto STARTUP HOLD TIME expr ';' { BGP_CFG->initial_hold_time = $5; } | bgp_proto CONNECT RETRY TIME expr ';' { BGP_CFG->connect_retry_time = $5; } | bgp_proto KEEPALIVE TIME expr ';' { BGP_CFG->keepalive_time = $4; } +/* | bgp_proto MULTIHOP ';' { BGP_CFG->multihop = 64; } | bgp_proto MULTIHOP expr ';' { BGP_CFG->multihop = $3; if (($3<1) || ($3>255)) cf_error("Multihop must be in range 1-255"); } +*/ | bgp_proto NEXT HOP SELF ';' { BGP_CFG->next_hop_self = 1; } | bgp_proto MISSING LLADDR SELF ';' { BGP_CFG->missing_lladdr = MLL_SELF; } | bgp_proto MISSING LLADDR DROP ';' { BGP_CFG->missing_lladdr = MLL_DROP; } | bgp_proto MISSING LLADDR IGNORE ';' { BGP_CFG->missing_lladdr = MLL_IGNORE; } +/* | bgp_proto GATEWAY DIRECT ';' { BGP_CFG->gw_mode = GW_DIRECT; } | bgp_proto GATEWAY RECURSIVE ';' { BGP_CFG->gw_mode = GW_RECURSIVE; } +*/ | bgp_proto PATH METRIC bool ';' { BGP_CFG->compare_path_lengths = $4; } | bgp_proto MED METRIC bool ';' { BGP_CFG->med_metric = $4; } | bgp_proto IGP METRIC bool ';' { BGP_CFG->igp_metric = $4; } | bgp_proto PREFER OLDER bool ';' { BGP_CFG->prefer_older = $4; } +/* | bgp_proto DETERMINISTIC MED bool ';' { BGP_CFG->deterministic_med = $4; } +*/ | bgp_proto DEFAULT BGP_MED expr ';' { BGP_CFG->default_med = $4; } | bgp_proto DEFAULT BGP_LOCAL_PREF expr ';' { BGP_CFG->default_local_pref = $4; } | bgp_proto SOURCE ADDRESS ipa ';' { BGP_CFG->source_addr = $4; } @@ -101,6 +108,7 @@ bgp_proto: | bgp_proto ROUTE LIMIT expr ';' { BGP_CFG->route_limit = $4; } | bgp_proto PASSIVE bool ';' { BGP_CFG->passive = $3; } | bgp_proto INTERPRET COMMUNITIES bool ';' { BGP_CFG->interpret_communities = $4; } + | bgp_proto SECONDARY bool ';' { BGP_CFG->secondary = $3; } | bgp_proto IGP TABLE rtable ';' { BGP_CFG->igp_table = $4; } | bgp_proto TTL SECURITY bool ';' { BGP_CFG->ttl_security = $4; } ; From 553e4054609e7aa8dcb92849c92a6fea73354f0e Mon Sep 17 00:00:00 2001 From: Ondrej Filip Date: Thu, 19 Apr 2012 17:12:13 +0200 Subject: [PATCH 02/15] Small clean up in debug texts --- sysdep/linux/netlink/netlink.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sysdep/linux/netlink/netlink.c b/sysdep/linux/netlink/netlink.c index c8eed0f3..d9d22d4c 100644 --- a/sysdep/linux/netlink/netlink.c +++ b/sysdep/linux/netlink/netlink.c @@ -756,11 +756,11 @@ nl_parse_route(struct nlmsghdr *h, int scan) if (a[RTA_OIF]) memcpy(&oif, RTA_DATA(a[RTA_OIF]), sizeof(oif)); - DBG("KRT: Got %I/%d, type=%d, oif=%d, table=%d, prid=%d, proto=%s\n", dst, i->rtm_dst_len, i->rtm_type, oif, i->rtm_table, i->rtm_protocol, p->p.name); - p = nl_table_map[i->rtm_table]; /* Do we know this table? */ + DBG("KRT: Got %I/%d, type=%d, oif=%d, table=%d, prid=%d, proto=%s\n", dst, i->rtm_dst_len, i->rtm_type, oif, i->rtm_table, i->rtm_protocol, p ? p->p.name : "(none)"); if (!p) - SKIP("unknown table %d", i->rtm_table); + SKIP("unknown table %d\n", i->rtm_table); + #ifdef IPV6 if (a[RTA_IIF]) From 9b2b502be521b58a736f7b78644e89ee01b4418b Mon Sep 17 00:00:00 2001 From: Ondrej Zajicek Date: Fri, 20 Apr 2012 21:04:55 +0200 Subject: [PATCH 03/15] Fixes missing device attributes when exporting routes to kernel. Thanks to Howden Nick for the bugreport. --- sysdep/linux/netlink/netlink.c | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/sysdep/linux/netlink/netlink.c b/sysdep/linux/netlink/netlink.c index c8eed0f3..2b0d7633 100644 --- a/sysdep/linux/netlink/netlink.c +++ b/sysdep/linux/netlink/netlink.c @@ -588,9 +588,9 @@ krt_capable(rte *e) switch (a->dest) { case RTD_ROUTER: - if (ipa_has_link_scope(a->gw) && (a->iface == NULL)) - return 0; case RTD_DEVICE: + if (a->iface == NULL) + return 0; case RTD_BLACKHOLE: case RTD_UNREACHABLE: case RTD_PROHIBIT: @@ -653,20 +653,16 @@ nl_send_route(struct krt_proto *p, rte *e, struct ea_list *eattrs, int new) if (ea = ea_find(eattrs, EA_KRT_REALM)) nl_add_attr_u32(&r.h, sizeof(r), RTA_FLOW, ea->u.data); + /* a->iface != NULL checked in krt_capable() for router and device routes */ + switch (a->dest) { case RTD_ROUTER: r.r.rtm_type = RTN_UNICAST; + nl_add_attr_u32(&r.h, sizeof(r), RTA_OIF, a->iface->index); nl_add_attr_ipa(&r.h, sizeof(r), RTA_GATEWAY, a->gw); - - /* a->iface != NULL checked in krt_capable() */ - if (ipa_has_link_scope(a->gw)) - nl_add_attr_u32(&r.h, sizeof(r), RTA_OIF, a->iface->index); - break; case RTD_DEVICE: - if (!a->iface) - return -1; r.r.rtm_type = RTN_UNICAST; nl_add_attr_u32(&r.h, sizeof(r), RTA_OIF, a->iface->index); break; From b7f3df79054aca327654c1fb4739c4ff02e59e6e Mon Sep 17 00:00:00 2001 From: Ondrej Zajicek Date: Fri, 11 May 2012 12:01:27 +0200 Subject: [PATCH 04/15] Fixes a bug in RA_ACCEPTED handling. --- nest/rt-table.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/nest/rt-table.c b/nest/rt-table.c index 578a6ba3..32feafba 100644 --- a/nest/rt-table.c +++ b/nest/rt-table.c @@ -354,7 +354,7 @@ rt_notify_accepted(struct announce_hook *ah, net *net, rte *new_changed, rte *ol rte *old_free = NULL; rte *r; - /* Used to track whethe we met old_changed position. If it is NULL + /* Used to track whether we met old_changed position. If it is NULL it was the first and met it implicitly before current best route. */ int old_meet = (old_changed && !before_old) ? 1 : 0; @@ -1415,12 +1415,13 @@ again: return 0; } - if (p->accept_ra_types == RA_OPTIMAL) + if ((p->accept_ra_types == RA_OPTIMAL) || + (p->accept_ra_types == RA_ACCEPTED)) if (e) { if (p->core_state != FS_FEEDING) return 1; /* In the meantime, the protocol fell down. */ - do_feed_baby(p, RA_OPTIMAL, h, n, e); + do_feed_baby(p, p->accept_ra_types, h, n, e); max_feed--; } From fc06fb62443c135773ee4c05ed83925cc47b046d Mon Sep 17 00:00:00 2001 From: Ondrej Zajicek Date: Sat, 7 Jul 2012 10:40:00 +0200 Subject: [PATCH 05/15] Implements RDNSS and DNSSL support for RAdv. --- doc/bird.sgml | 14 ++++ lib/lists.h | 3 +- proto/radv/config.Y | 126 ++++++++++++++++++++++++++++-- proto/radv/packets.c | 181 ++++++++++++++++++++++++++++++++++++++++++- proto/radv/radv.c | 4 + proto/radv/radv.h | 31 +++++++- 6 files changed, 348 insertions(+), 11 deletions(-) diff --git a/doc/bird.sgml b/doc/bird.sgml index b43eb263..087a4ebf 100644 --- a/doc/bird.sgml +++ b/doc/bird.sgml @@ -2450,6 +2450,20 @@ protocol radv { prefix 2001:0DB8:2000::/48 { autonomous off; # Do not autoconfigure }; + + rdnss 2001:0DB8:1234::10; # Short form of RDNSS + + rdnss { + lifetime mult 10; + ns 2001:0DB8:1234::11; + ns 2001:0DB8:1234::12; + }; + + dnssl { + lifetime 3600; + domain "abc.com"; + domain "xyz.com"; + }; } diff --git a/lib/lists.h b/lib/lists.h index 6fab12c4..0b0fdbe3 100644 --- a/lib/lists.h +++ b/lib/lists.h @@ -34,7 +34,8 @@ typedef struct list { /* In fact two overlayed nodes */ #define HEAD(list) ((void *)((list).head)) #define TAIL(list) ((void *)((list).tail)) #define NODE_NEXT(n) ((void *)((NODE (n))->next)) -#define WALK_LIST(n,list) for(n=HEAD(list);(NODE (n))->next; n=NODE_NEXT(n)) +#define NODE_VALID(n) ((NODE (n))->next) +#define WALK_LIST(n,list) for(n=HEAD(list); NODE_VALID(n); n=NODE_NEXT(n)) #define WALK_LIST_DELSAFE(n,nxt,list) \ for(n=HEAD(list); nxt=NODE_NEXT(n); n=(void *) nxt) /* WALK_LIST_FIRST supposes that called code removes each processed node */ diff --git a/proto/radv/config.Y b/proto/radv/config.Y index 495d9a05..abccd2c7 100644 --- a/proto/radv/config.Y +++ b/proto/radv/config.Y @@ -14,32 +14,45 @@ CF_DEFINES #define RADV_CFG ((struct radv_config *) this_proto) #define RADV_IFACE ((struct radv_iface_config *) this_ipatt) #define RADV_PREFIX this_radv_prefix +#define RADV_RDNSS (&this_radv_rdnss) +#define RADV_DNSSL (&this_radv_dnssl) static struct radv_prefix_config *this_radv_prefix; +static struct radv_rdnss_config this_radv_rdnss; +static struct radv_dnssl_config this_radv_dnssl; +static list radv_dns_list; /* Used by radv_rdnss and radv_dnssl */ +static u8 radv_mult_val; /* Used by radv_mult for second return value */ + CF_DECLS 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, - LIFETIME, SKIP, ONLINK, AUTONOMOUS) + TIMER, CURRENT, HOP, LIMIT, DEFAULT, VALID, PREFERRED, MULT, + LIFETIME, SKIP, ONLINK, AUTONOMOUS, RDNSS, DNSSL, NS, DOMAIN, + LOCAL) +%type radv_mult CF_GRAMMAR -CF_ADDTO(proto, radv_proto '}') +CF_ADDTO(proto, radv_proto) radv_proto_start: proto_start RADV { this_proto = proto_config_new(&proto_radv, sizeof(struct radv_config), $1); init_list(&RADV_CFG->patt_list); init_list(&RADV_CFG->pref_list); + init_list(&RADV_CFG->rdnss_list); + init_list(&RADV_CFG->dnssl_list); }; radv_proto_item: proto_item - | PREFIX radv_prefix { add_tail(&RADV_CFG->pref_list, NODE this_radv_prefix); } | INTERFACE radv_iface + | 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); } ; radv_proto_opts: @@ -48,7 +61,7 @@ radv_proto_opts: ; radv_proto: - radv_proto_start proto_name '{' radv_proto_opts; + radv_proto_start proto_name '{' radv_proto_opts '}'; radv_iface_start: @@ -57,6 +70,8 @@ radv_iface_start: add_tail(&RADV_CFG->patt_list, NODE this_ipatt); init_list(&this_ipatt->ipn_list); init_list(&RADV_IFACE->pref_list); + init_list(&RADV_IFACE->rdnss_list); + init_list(&RADV_IFACE->dnssl_list); RADV_IFACE->min_ra_int = -1; /* undefined */ RADV_IFACE->max_ra_int = DEFAULT_MAX_RA_INT; @@ -77,6 +92,10 @@ radv_iface_item: | 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"); } | 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); } + | RDNSS LOCAL bool { RADV_IFACE->rdnss_local = $3; } + | DNSSL LOCAL bool { RADV_IFACE->dnssl_local = $3; } ; radv_iface_finish: @@ -152,6 +171,103 @@ radv_prefix: radv_prefix_start radv_prefix_opt_list radv_prefix_finish; + +radv_rdnss_node: ipa +{ + struct radv_rdnss_config *cf = cfg_allocz(sizeof(struct radv_rdnss_config)); + add_tail(&radv_dns_list, NODE cf); + + cf->server = $1; + cf->lifetime_mult = DEFAULT_DNS_LIFETIME_MULT; +}; + +radv_rdnss_start: +{ + RADV_RDNSS->lifetime = 0; + RADV_RDNSS->lifetime_mult = DEFAULT_DNS_LIFETIME_MULT; +}; + +radv_rdnss_item: + | NS radv_rdnss_node + | LIFETIME radv_mult { RADV_RDNSS->lifetime = $2; RADV_RDNSS->lifetime_mult = radv_mult_val; } + ; + +radv_rdnss_finish: +{ + if (EMPTY_LIST(radv_dns_list)) + cf_error("No nameserver in RDNSS section"); + + struct radv_rdnss_config *cf; + WALK_LIST(cf, radv_dns_list) + { + cf->lifetime = RADV_RDNSS->lifetime; + cf->lifetime_mult = RADV_RDNSS->lifetime_mult; + } +}; + +radv_rdnss_opts: + /* empty */ + | radv_rdnss_opts radv_rdnss_item ';' + ; + +radv_rdnss: + radv_rdnss_node + | '{' radv_rdnss_start radv_rdnss_opts '}' radv_rdnss_finish + ; + + +radv_dnssl_node: TEXT +{ + struct radv_dnssl_config *cf = cfg_allocz(sizeof(struct radv_dnssl_config)); + add_tail(&radv_dns_list, NODE cf); + + cf->domain = $1; + cf->lifetime_mult = DEFAULT_DNS_LIFETIME_MULT; + + if (radv_process_domain(cf) < 0) + cf_error("Invalid domain dame"); +}; + +radv_dnssl_start: +{ + RADV_DNSSL->lifetime = 0; + RADV_DNSSL->lifetime_mult = DEFAULT_DNS_LIFETIME_MULT; +}; + +radv_dnssl_item: + | DOMAIN radv_dnssl_node + | LIFETIME radv_mult { RADV_DNSSL->lifetime = $2; RADV_DNSSL->lifetime_mult = radv_mult_val; } + ; + +radv_dnssl_finish: +{ + if (EMPTY_LIST(radv_dns_list)) + cf_error("No domain in DNSSL section"); + + struct radv_dnssl_config *cf; + WALK_LIST(cf, radv_dns_list) + { + cf->lifetime = RADV_DNSSL->lifetime; + cf->lifetime_mult = RADV_DNSSL->lifetime_mult; + } +}; + +radv_dnssl_opts: + /* empty */ + | radv_dnssl_opts radv_dnssl_item ';' + ; + +radv_dnssl: + radv_dnssl_node + | '{' radv_dnssl_start radv_dnssl_opts '}' radv_dnssl_finish + ; + + +radv_mult: + expr { $$ = $1; radv_mult_val = 0; } + | MULT expr { $$ = 0; radv_mult_val = $2; if (($2 < 1) || ($2 > 254)) cf_error("Multiplier must be in range 1-254"); } + ; + CF_CODE CF_END diff --git a/proto/radv/packets.c b/proto/radv/packets.c index ac59ce94..6fdfcaa3 100644 --- a/proto/radv/packets.c +++ b/proto/radv/packets.c @@ -24,8 +24,10 @@ struct radv_ra_packet #define OPT_RA_MANAGED 0x80 #define OPT_RA_OTHER_CFG 0x40 -#define OPT_PREFIX 3 -#define OPT_MTU 5 +#define OPT_PREFIX 3 +#define OPT_MTU 5 +#define OPT_RDNSS 25 +#define OPT_DNSSL 31 struct radv_opt_prefix { @@ -50,6 +52,25 @@ struct radv_opt_mtu u32 mtu; }; +struct radv_opt_rdnss +{ + u8 type; + u8 length; + u16 reserved; + u32 lifetime; + ip_addr servers[]; +}; + +struct radv_opt_dnssl +{ + u8 type; + u8 length; + u16 reserved; + u32 lifetime; + char domain[]; +}; + + static struct radv_prefix_config default_prefix = { .onlink = 1, .autonomous = 1, @@ -57,6 +78,7 @@ static struct radv_prefix_config default_prefix = { .preferred_lifetime = DEFAULT_PREFERRED_LIFETIME }; + static struct radv_prefix_config * radv_prefix_match(struct radv_iface *ifa, struct ifa *a) { @@ -78,10 +100,146 @@ radv_prefix_match(struct radv_iface *ifa, struct ifa *a) return &default_prefix; } +static int +radv_prepare_rdnss(struct radv_iface *ifa, list *rdnss_list, char **buf, char *bufend) +{ + struct radv_rdnss_config *rcf = HEAD(*rdnss_list); + + while(NODE_VALID(rcf)) + { + struct radv_rdnss_config *rcf_base = rcf; + struct radv_opt_rdnss *op = (void *) *buf; + int max_i = (bufend - *buf - sizeof(struct radv_opt_rdnss)) / sizeof(ip_addr); + int i = 0; + + if (max_i < 1) + goto too_much; + + op->type = OPT_RDNSS; + op->reserved = 0; + + if (rcf->lifetime_mult) + op->lifetime = htonl(rcf->lifetime_mult * ifa->cf->max_ra_int); + else + op->lifetime = htonl(rcf->lifetime); + + while(NODE_VALID(rcf) && + (rcf->lifetime == rcf_base->lifetime) && + (rcf->lifetime_mult == rcf_base->lifetime_mult)) + { + if (i >= max_i) + goto too_much; + + op->servers[i] = rcf->server; + ipa_hton(op->servers[i]); + i++; + + rcf = NODE_NEXT(rcf); + } + + op->length = 1+2*i; + *buf += 8 * op->length; + } + + return 0; + + too_much: + log(L_WARN "%s: Too many RA options on interface %s", + ifa->ra->p.name, ifa->iface->name); + return -1; +} + +int +radv_process_domain(struct radv_dnssl_config *cf) +{ + /* Format of domain in search list is