From 7126cadf80fce1af2bb4aa33f8bcb7c6b5ff1a47 Mon Sep 17 00:00:00 2001 From: "Ondrej Zajicek (work)" Date: Tue, 7 Mar 2017 18:42:41 +0100 Subject: [PATCH] Static: Minor overhaul The patch fixes several bugs introduced in previous changes, simplifies the protocol by handing routes uniformly, introduces asynchronous route processing to avoid issues with separate notifications for each next-hop in ECMP routes, and makes reconfiguration faster by avoiding quadratic complexity. --- lib/buffer.h | 5 +- lib/buffer_test.c | 20 + proto/static/config.Y | 18 +- proto/static/static.c | 897 +++++++++++++++++++----------------------- proto/static/static.h | 45 ++- 5 files changed, 463 insertions(+), 522 deletions(-) diff --git a/lib/buffer.h b/lib/buffer.h index a8b11951..6fc18852 100644 --- a/lib/buffer.h +++ b/lib/buffer.h @@ -14,7 +14,7 @@ #include "sysdep/config.h" #define BUFFER(type) struct { type *data; uint used, size; } - +#define BUFFER_TYPE(v) typeof(* (v).data) #define BUFFER_SIZE(v) ((v).size * sizeof(* (v).data)) #define BUFFER_INIT(v,pool,isize) \ @@ -46,6 +46,9 @@ #define BUFFER_FLUSH(v) ({ (v).used = 0; }) +#define BUFFER_WALK(v,n) \ + for (BUFFER_TYPE(v) *_n = (v).data, n; _n < ((v).data + (v).used) && (n = *_n, 1); _n++) + #define BUFFER_SHALLOW_COPY(dst, src) \ ({ \ (dst).used = (src).used; \ diff --git a/lib/buffer_test.c b/lib/buffer_test.c index 55179e82..5b7de330 100644 --- a/lib/buffer_test.c +++ b/lib/buffer_test.c @@ -133,6 +133,25 @@ t_buffer_flush(void) return 1; } +static int +t_buffer_walk(void) +{ + int i; + + init_buffer(); + fill_expected_array(); + for (i = 0; i < MAX_NUM; i++) + BUFFER_PUSH(buf) = expected[i]; + + i = 0; + BUFFER_WALK(buf, v) + bt_assert(v == expected[i++]); + + bt_assert(i == MAX_NUM); + + return 1; +} + int main(int argc, char *argv[]) { @@ -142,6 +161,7 @@ main(int argc, char *argv[]) bt_test_suite(t_buffer_pop, "Fill whole buffer (PUSH), a half of elements POP and PUSH new elements"); bt_test_suite(t_buffer_resize, "Init a small buffer and try overfill"); bt_test_suite(t_buffer_flush, "Fill and flush all elements"); + bt_test_suite(t_buffer_walk, "Fill and walk through buffer"); return bt_exit_value(); } diff --git a/proto/static/config.Y b/proto/static/config.Y index 16c276ce..86fcedec 100644 --- a/proto/static/config.Y +++ b/proto/static/config.Y @@ -19,15 +19,9 @@ static struct f_inst **this_srt_last_cmd; static struct static_route * static_nexthop_new(void) { - struct static_route *nh; + struct static_route *nh = this_srt; - if (!this_snh) - { - /* First next hop */ - nh = this_srt; - rem_node(&this_srt->n); - } - else + if (this_snh) { /* Additional next hop */ nh = cfg_allocz(sizeof(struct static_route)); @@ -57,7 +51,7 @@ CF_ADDTO(proto, static_proto '}') static_proto_start: proto_start STATIC { this_proto = proto_config_new(&proto_static, $1); - static_init_config(STATIC_CFG); + init_list(&STATIC_CFG->routes); }; static_proto: @@ -74,13 +68,11 @@ stat_nexthop: this_snh = static_nexthop_new(); this_snh->via = $2; this_snh->iface = $3; - add_tail(&STATIC_CFG->neigh_routes, &this_snh->n); } | VIA TEXT { this_snh = static_nexthop_new(); this_snh->via = IPA_NONE; - this_snh->if_name = $2; - add_tail(&STATIC_CFG->iface_routes, &this_snh->n); + this_snh->iface = if_get_by_name($2); } | stat_nexthop MPLS label_stack { this_snh->label_count = $3[0]; @@ -102,7 +94,7 @@ stat_nexthops: stat_route0: ROUTE net_any { this_srt = cfg_allocz(sizeof(struct static_route)); - add_tail(&STATIC_CFG->other_routes, &this_srt->n); + add_tail(&STATIC_CFG->routes, &this_srt->n); this_srt->net = $2; this_srt_last_cmd = &(this_srt->cmds); this_srt->mp_next = NULL; diff --git a/proto/static/static.c b/proto/static/static.c index 63ee2518..dbe490f9 100644 --- a/proto/static/static.c +++ b/proto/static/static.c @@ -9,33 +9,32 @@ /** * DOC: Static * - * The Static protocol is implemented in a straightforward way. It keeps - * two lists of static routes: one containing interface routes and one - * holding the remaining ones. Interface routes are inserted and removed according - * to interface events received from the core via the if_notify() hook. Routes - * pointing to a neighboring router use a sticky node in the neighbor cache - * to be notified about gaining or losing the neighbor. Special - * routes like black holes or rejects are inserted all the time. + * The Static protocol is implemented in a straightforward way. It keeps a list + * of static routes. Routes of dest RTD_UNICAST have associated sticky node in + * the neighbor cache to be notified about gaining or losing the neighbor and + * about interface-related events (e.g. link down). They may also have a BFD + * request if associated with a BFD session. When a route is notified, + * static_decide() is used to see whether the route activeness is changed. In + * such case, the route is marked as dirty and scheduled to be announced or + * withdrawn, which is done asynchronously from event hook. Routes of other + * types (e.g. black holes) are announced all the time. * - * Multipath routes are tricky. Because these routes depends on - * several neighbors we need to integrate that to the neighbor - * notification handling, we use dummy static_route nodes, one for - * each nexthop. Therefore, a multipath route consists of a master - * static_route node (of dest RTD_MULTIPATH), which specifies prefix - * and is used in most circumstances, and a list of dummy static_route - * nodes (of dest RTD_NONE), which stores info about nexthops and are - * connected to neighbor entries and neighbor notifications. Dummy - * nodes are chained using mp_next, they aren't in other_routes list, - * and abuse if_name field for other purposes. + * Multipath routes are a bit tricky. To represent additional next hops, dummy + * static_route nodes are used, which are chained using @mp_next field and link + * to the master node by @mp_head field. Each next hop has a separate neighbor + * entry and an activeness state, but the master node is used for most purposes. + * Note that most functions DO NOT accept dummy nodes as arguments. * * The only other thing worth mentioning is that when asked for reconfiguration, * Static not only compares the two configurations, but it also calculates - * difference between the lists of static routes and it just inserts the - * newly added routes and removes the obsolete ones. + * difference between the lists of static routes and it just inserts the newly + * added routes, removes the obsolete ones and reannounces changed ones. */ #undef LOCAL_DEBUG +#include + #include "nest/bird.h" #include "nest/iface.h" #include "nest/protocol.h" @@ -51,107 +50,120 @@ static linpool *static_lp; static inline rtable * -p_igp_table(struct proto *p) +p_igp_table(struct static_proto *p) { - struct static_config *cf = (void *) p->cf; - return cf->igp_table ? cf->igp_table->table : p->main_channel->table; + struct static_config *cf = (void *) p->p.cf; + return cf->igp_table ? cf->igp_table->table : p->p.main_channel->table; } static void -static_install(struct proto *p, struct static_route *r) +static_announce_rte(struct static_proto *p, struct static_route *r) { - rta *ap = allocz(RTA_MAX_SIZE); - rte *e; - - if (!(r->state & STS_WANT) && (r->state & (STS_INSTALLED | STS_FORCE)) && r->dest != RTD_UNICAST) - goto drop; - - DBG("Installing static route %N, rtd=%d\n", r->net, r->dest); - ap->src = p->main_source; - ap->source = RTS_STATIC; - ap->scope = SCOPE_UNIVERSE; - ap->dest = r->dest; + rta *a = allocz(RTA_MAX_SIZE); + a->src = p->p.main_source; + a->source = RTS_STATIC; + a->scope = SCOPE_UNIVERSE; + a->dest = r->dest; if (r->dest == RTD_UNICAST) + { + struct static_route *r2; + struct nexthop *nhs = NULL; + + for (r2 = r; r2; r2 = r2->mp_next) { - struct nexthop *nhs = NULL; - struct static_route *r2; - int update = 0; + if (!r2->active) + continue; - r = r->mp_head; - for (r2 = r; r2; r2 = r2->mp_next) - { - if ((r2->state & STS_FORCE) || - (!!(r2->state & STS_INSTALLED) != !!(r2->state & STS_WANT))) - update++; + struct nexthop *nh = allocz(NEXTHOP_MAX_SIZE); + nh->gw = r2->via; + nh->iface = r2->neigh->iface; + nh->weight = r2->weight; + nh->labels = r2->label_count; + memcpy(nh->label, r2->label_stack, r2->label_count * sizeof(u32)); - if (r2->state & STS_WANT) - { - struct nexthop *nh = allocz(NEXTHOP_MAX_SIZE); - - nh->gw = r2->via; - nh->iface = r2->neigh ? r2->neigh->iface : r2->iface; - nh->weight = r2->weight; - nh->labels = r2->label_count; - memcpy(nh->label, r2->label_stack, r2->label_count * sizeof(u32)); - - r2->state |= STS_INSTALLED; - nexthop_insert(&nhs, nh); - } - else - r2->state = 0; - } - - if (!update) // Nothing changed - return; - - if (!nhs) // No nexthop to install - { -drop: - rte_update(p, r->net, NULL); - return; - } - - ap->dest = RTD_UNICAST; - nexthop_link(ap, nhs); + nexthop_insert(&nhs, nh); } - else - r->state |= STS_INSTALLED; + + if (!nhs) + goto withdraw; + + nexthop_link(a, nhs); + } if (r->dest == RTDX_RECURSIVE) - { - ap->nh.labels_orig = ap->nh.labels = r->label_count; - memcpy(ap->nh.label, r->label_stack, r->label_count * sizeof(u32)); - rta_set_recursive_next_hop(p->main_channel->table, ap, p_igp_table(p), r->via, IPA_NONE); - } + { + a->nh.labels_orig = a->nh.labels = r->label_count; + memcpy(a->nh.label, r->label_stack, r->label_count * sizeof(u32)); + rta_set_recursive_next_hop(p->p.main_channel->table, a, p_igp_table(p), r->via, IPA_NONE); + } + + /* Already announced */ + if (r->state == SRS_CLEAN) + return; /* We skip rta_lookup() here */ - - e = rte_get_temp(ap); + rte *e = rte_get_temp(a); e->pflags = 0; if (r->cmds) f_eval_rte(r->cmds, &e, static_lp); - rte_update(p, r->net, e); + rte_update(&p->p, r->net, e); + r->state = SRS_CLEAN; if (r->cmds) lp_flush(static_lp); + + return; + +withdraw: + if (r->state == SRS_DOWN) + return; + + rte_update(&p->p, r->net, NULL); + r->state = SRS_DOWN; +} + +static void +static_mark_rte(struct static_proto *p, struct static_route *r) +{ + if (r->state == SRS_DIRTY) + return; + + r->state = SRS_DIRTY; + BUFFER_PUSH(p->marked) = r; + + if (!ev_active(p->event)) + ev_schedule(p->event); +} + +static void +static_announce_marked(void *P) +{ + struct static_proto *p = P; + + BUFFER_WALK(p->marked, r) + static_announce_rte(P, r); + + BUFFER_FLUSH(p->marked); } static void static_bfd_notify(struct bfd_request *req); static void -static_update_bfd(struct proto *p, struct static_route *r) +static_update_bfd(struct static_proto *p, struct static_route *r) { + /* The @r is a RTD_UNICAST next hop, may be a dummy node */ + struct neighbor *nb = r->neigh; int bfd_up = (nb->scope > 0) && r->use_bfd; if (bfd_up && !r->bfd_req) { // ip_addr local = ipa_nonzero(r->local) ? r->local : nb->ifa->ip; - r->bfd_req = bfd_request_session(p->pool, r->via, nb->ifa->ip, nb->iface, + r->bfd_req = bfd_request_session(p->p.pool, r->via, nb->ifa->ip, nb->iface, static_bfd_notify, r); } @@ -163,186 +175,155 @@ static_update_bfd(struct proto *p, struct static_route *r) } static int -static_decide(struct static_config *cf, struct static_route *r) +static_decide(struct static_proto *p, struct static_route *r) { - /* r->dest != RTD_MULTIPATH, but may be RTD_NONE (part of multipath route) - the route also have to be valid (r->neigh != NULL) */ + /* The @r is a RTD_UNICAST next hop, may be a dummy node */ - r->state &= ~STS_WANT; + struct static_config *cf = (void *) p->p.cf; + uint old_active = r->active; if (r->neigh->scope < 0) - return 0; + goto fail; if (cf->check_link && !(r->neigh->iface->flags & IF_LINK_UP)) - return 0; + goto fail; - if (r->bfd_req && r->bfd_req->state != BFD_STATE_UP) - return 0; + if (r->bfd_req && (r->bfd_req->state != BFD_STATE_UP)) + goto fail; - r->state |= STS_WANT; - return 1; + r->active = 1; + return !old_active; + +fail: + r->active = 0; + return old_active; } - static void -static_add(struct proto *p, struct static_config *cf, struct static_route *r) +static_add_rte(struct static_proto *p, struct static_route *r) { - if (r->mp_head && r != r->mp_head) - return; + if (r->dest == RTD_UNICAST) + { + struct static_route *r2; + struct neighbor *n; - DBG("static_add(%N,%d)\n", r->net, r->dest); - switch (r->dest) + for (r2 = r; r2; r2 = r2->mp_next) { - case RTD_UNICAST: + n = ipa_nonzero(r2->via) ? + neigh_find2(&p->p, &r2->via, r2->iface, NEF_STICKY) : + neigh_find_iface(&p->p, r2->iface); + + if (!n) { - int count = 0; - struct static_route *r2; - - for (r2 = r; r2; r2 = r2->mp_next) - { - if (ipa_zero(r2->via)) // No struct neighbor for device routes - continue; - - struct neighbor *n = neigh_find2(p, &r2->via, r2->iface, NEF_STICKY); - if (n) - { - r2->chain = n->data; - n->data = r2; - r2->neigh = n; - - static_update_bfd(p, r2); - static_decide(cf,r2); - count++; - } - else - { - log(L_ERR "Static route destination %I is invalid. Ignoring.", r2->via); - r2->state = 0; - } - } - - if (count) - static_install(p, r); - - break; + log(L_WARN "Invalid next hop %I of static route %N", r2->via, r2->net); + continue; } - default: - static_install(p, r); + r2->neigh = n; + r2->chain = n->data; + n->data = r2; + + static_update_bfd(p, r2); + static_decide(p, r2); } + } + + static_announce_rte(p, r); } static void -static_rte_cleanup(struct proto *p UNUSED, struct static_route *r) +static_reset_rte(struct static_proto *p UNUSED, struct static_route *r) { - if (r->mp_head && (r != r->mp_head)) - return; - struct static_route *r2; - + for (r2 = r; r2; r2 = r2->mp_next) - if (r2->bfd_req) + { + r2->neigh = NULL; + r2->chain = NULL; + + r2->state = 0; + r2->active = 0; + + rfree(r2->bfd_req); + r2->bfd_req = NULL; + } +} + +static void +static_remove_rte(struct static_proto *p, struct static_route *r) +{ + if (r->state) + rte_update(&p->p, r->net, NULL); + + static_reset_rte(p, r); +} + + +static inline int +static_same_dest(struct static_route *x, struct static_route *y) +{ + if (x->dest != y->dest) + return 0; + + switch (x->dest) + { + case RTD_UNICAST: + for (; x && y; x = x->mp_next, y = y->mp_next) { - rfree(r2->bfd_req); - r2->bfd_req = NULL; + if (!ipa_equal(x->via, y->via) || + (x->iface != y->iface) || + (x->use_bfd != y->use_bfd) || + (x->weight != y->weight) || + (x->label_count != y->label_count)) + return 0; + + for (int i = 0; i < x->label_count; i++) + if (x->label_stack[i] != y->label_stack[i]) + return 0; } + return !x && !y; + + case RTDX_RECURSIVE: + return ipa_equal(x->via, y->via); + + default: + return 1; + } } -static int -static_start(struct proto *p) +static inline int +static_same_rte(struct static_route *or, struct static_route *nr) { - struct static_config *cf = (void *) p->cf; - struct static_route *r; - - DBG("Static: take off!\n"); - - if (!static_lp) - static_lp = lp_new(&root_pool, 1008); - - if (cf->igp_table) - rt_lock_table(cf->igp_table->table); - - /* We have to go UP before routes could be installed */ - proto_notify_state(p, PS_UP); - - WALK_LIST(r, cf->neigh_routes) - static_add(p, cf, r); - - WALK_LIST(r, cf->iface_routes) - static_add(p, cf, r); - - WALK_LIST(r, cf->other_routes) - static_install(p, r); - - return PS_UP; -} - -static int -static_shutdown(struct proto *p) -{ - struct static_config *cf = (void *) p->cf; - struct static_route *r; - - /* Just reset the flag, the routes will be flushed by the nest */ - WALK_LIST(r, cf->other_routes) - { - static_rte_cleanup(p, r); - r->state = 0; - } - WALK_LIST(r, cf->iface_routes) - r->state = 0; - WALK_LIST(r, cf->neigh_routes) - { - static_rte_cleanup(p, r); - r->state = 0; - } - - /* Handle failure during channel reconfigure */ - /* FIXME: This should be handled in a better way */ - cf = (void *) p->cf_new; - if (cf) - { - WALK_LIST(r, cf->other_routes) - r->state = 0; - WALK_LIST(r, cf->iface_routes) - r->state = 0; - WALK_LIST(r, cf->neigh_routes) - r->state = 0; - } - - return PS_DOWN; + /* Note that i_same() requires arguments in (new, old) order */ + return static_same_dest(or, nr) && i_same(nr->cmds, or->cmds); } static void -static_cleanup(struct proto *p) +static_reconfigure_rte(struct static_proto *p, struct static_route *or, struct static_route *nr) { - struct static_config *cf = (void *) p->cf; + if ((or->state == SRS_CLEAN) && !static_same_rte(or, nr)) + nr->state = SRS_DIRTY; + else + nr->state = or->state; - if (cf->igp_table) - rt_unlock_table(cf->igp_table->table); + static_add_rte(p, nr); + static_reset_rte(p, or); } -static void -static_update_rte(struct proto *p, struct static_route *r) -{ - if (r->dest != RTD_UNICAST) - return; - - static_decide((struct static_config *) p->cf, r); - static_install(p, r); -} static void static_neigh_notify(struct neighbor *n) { - struct proto *p = n->proto; + struct static_proto *p = (void *) n->proto; struct static_route *r; DBG("Static: neighbor notify for %I: iface %p\n", n->addr, n->iface); - for(r=n->data; r; r=r->chain) + for (r = n->data; r; r = r->chain) { static_update_bfd(p, r); - static_update_rte(p, r); + + if (static_decide(p, r)) + static_mark_rte(p, r->mp_head); } } @@ -350,84 +331,20 @@ static void static_bfd_notify(struct bfd_request *req) { struct static_route *r = req->data; - struct proto *p = r->neigh->proto; + struct static_proto *p = (void *) r->neigh->proto; // if (req->down) TRACE(D_EVENTS, "BFD session down for nbr %I on %s", XXXX); - static_update_rte(p, r); + if (static_decide(p, r)) + static_mark_rte(p, r->mp_head); } -static void -static_dump_rt(struct static_route *r) -{ - debug("%-1N: ", r->net); - if (r->dest == RTD_UNICAST) - if (ipa_zero(r->via)) - debug("dev %s\n", r->if_name); - else - debug("via %I\n", r->via); - else - debug("rtd %d\n", r->dest); -} - -static void -static_dump(struct proto *p) -{ - struct static_config *c = (void *) p->cf; - struct static_route *r; - - debug("Independent static nexthops:\n"); - WALK_LIST(r, c->neigh_routes) - static_dump_rt(r); - debug("Device static nexthops:\n"); - WALK_LIST(r, c->iface_routes) - static_dump_rt(r); - debug("Other static routes:\n"); - WALK_LIST(r, c->other_routes) - static_dump_rt(r); -} - -static void -static_if_notify(struct proto *p, unsigned flags, struct iface *i) -{ - struct static_route *r; - struct static_config *c = (void *) p->cf; - - if (flags & IF_CHANGE_UP) - { - WALK_LIST(r, c->iface_routes) - if (!strcmp(r->if_name, i->name)) - { - r->state |= STS_WANT; - r->iface = i; - static_install(p, r); - } - } - else if (flags & IF_CHANGE_DOWN) - { - WALK_LIST(r, c->iface_routes) - if (!strcmp(r->if_name, i->name)) - { - r->state &= ~STS_WANT; - r->iface = NULL; - static_install(p, r); - } - } -} - -int +static int static_rte_mergable(rte *pri UNUSED, rte *sec UNUSED) { return 1; } -void -static_init_config(struct static_config *c) -{ - init_list(&c->neigh_routes); - init_list(&c->iface_routes); - init_list(&c->other_routes); -} static void static_postconfig(struct proto_config *CF) @@ -438,21 +355,11 @@ static_postconfig(struct proto_config *CF) if (EMPTY_LIST(CF->channels)) cf_error("Channel not specified"); - - WALK_LIST(r, cf->neigh_routes) + WALK_LIST(r, cf->routes) if (r->net && (r->net->type != CF->net_type)) cf_error("Route %N incompatible with channel type", r->net); - - WALK_LIST(r, cf->iface_routes) - if (r->net && (r->net->type != CF->net_type)) - cf_error("Route %N incompatible with channel type", r->net); - - WALK_LIST(r, cf->other_routes) - if (r->net->type != CF->net_type) - cf_error("Route %N incompatible with channel type", r->net); } - static struct proto * static_init(struct proto_config *CF) { @@ -463,103 +370,84 @@ static_init(struct proto_config *CF) P->main_channel = proto_add_channel(P, proto_cf_main_channel(CF)); P->neigh_notify = static_neigh_notify; - P->if_notify = static_if_notify; P->rte_mergable = static_rte_mergable; return P; } -static inline int -static_same_dest(struct static_route *x, struct static_route *y) +static int +static_start(struct proto *P) { - if (x->dest != y->dest) - return 0; + struct static_proto *p = (void *) P; + struct static_config *cf = (void *) P->cf; + struct static_route *r; - switch (x->dest) - { - case RTD_UNICAST: - { - struct static_route *xc, *yc; - for (xc = x, yc = y; xc && yc; xc = xc->mp_next, yc = yc->mp_next) - { - if (ipa_nonzero(xc->via) && ipa_nonzero(yc->via)) - { - if (!ipa_equal(x->via, y->via) || - (x->iface != y->iface) || - (x->use_bfd != y->use_bfd) || - (x->weight != y->weight) || - (x->label_count != y->label_count)) - return 0; - for (int i=0; ilabel_count; i++) - if (x->label_stack[i] != y->label_stack[i]) - return 0; - } - else - if ((!x->if_name) || (!y->if_name) || - strcmp(x->if_name, y->if_name) || - (x->use_bfd != y->use_bfd) || - (x->weight != y->weight)) - return 0; + if (!static_lp) + static_lp = lp_new(&root_pool, 1008); - } - return 1; - } + if (cf->igp_table) + rt_lock_table(cf->igp_table->table); - case RTDX_RECURSIVE: - return ipa_equal(x->via, y->via); + p->event = ev_new(p->p.pool); + p->event->hook = static_announce_marked; + p->event->data = p; - default: - return 1; - } + BUFFER_INIT(p->marked, p->p.pool, 4); + + /* We have to go UP before routes could be installed */ + proto_notify_state(P, PS_UP); + + WALK_LIST(r, cf->routes) + static_add_rte(p, r); + + return PS_UP; } -static inline int -static_same_rte(struct static_route *x, struct static_route *y) +static int +static_shutdown(struct proto *P) { - return static_same_dest(x, y) && i_same(x->cmds, y->cmds); -} + struct static_proto *p = (void *) P; + struct static_config *cf = (void *) P->cf; + struct static_route *r; + /* Just reset the flag, the routes will be flushed by the nest */ + WALK_LIST(r, cf->routes) + static_reset_rte(p, r); + + return PS_DOWN; +} static void -static_match(struct proto *p, struct static_route *r, struct static_config *n) +static_cleanup(struct proto *P) { - struct static_route *t; + struct static_config *cf = (void *) P->cf; - if (r->mp_head && (r->mp_head != r)) - return; + if (cf->igp_table) + rt_unlock_table(cf->igp_table->table); +} - /* - * For given old route *r we find whether a route to the same - * network is also in the new route list. In that case, we keep the - * route and possibly update the route later if destination changed. - * Otherwise, we remove the route. - */ +static void +static_dump_rte(struct static_route *r) +{ + debug("%-1N: ", r->net); + if (r->dest == RTD_UNICAST) + if (r->iface && ipa_zero(r->via)) + debug("dev %s\n", r->iface->name); + else + debug("via %I%J\n", r->via, r->iface); + else + debug("rtd %d\n", r->dest); +} - if (r->neigh) - r->neigh->data = NULL; +static void +static_dump(struct proto *P) +{ + struct static_config *c = (void *) P->cf; + struct static_route *r; - WALK_LIST(t, n->neigh_routes) - if ((!t->mp_head || (t->mp_head == t)) && net_equal(r->net, t->net)) - goto found; - - WALK_LIST(t, n->iface_routes) - if ((!t->mp_head || (t->mp_head == t)) && net_equal(r->net, t->net)) - goto found; - - WALK_LIST(t, n->other_routes) - if (net_equal(r->net, t->net)) - goto found; - - r->state &= ~STS_WANT; - static_install(p, r); - return; - - found: - t->state = r->state; - - /* If destination is different, force reinstall */ - if (!static_same_rte(r, t)) - t->state |= STS_FORCE; + debug("Static routes:\n"); + WALK_LIST(r, c->routes) + static_dump_rte(r); } static inline rtable * @@ -568,97 +456,182 @@ cf_igp_table(struct static_config *cf) return cf->igp_table ? cf->igp_table->table : NULL; } -static int -static_reconfigure(struct proto *p, struct proto_config *CF) +static inline int +static_cmp_rte(const void *X, const void *Y) { - struct static_config *o = (void *) p->cf; + struct static_route *x = *(void **)X, *y = *(void **)Y; + return net_compare(x->net, y->net); +} + +static int +static_reconfigure(struct proto *P, struct proto_config *CF) +{ + struct static_proto *p = (void *) P; + struct static_config *o = (void *) P->cf; struct static_config *n = (void *) CF; - struct static_route *r; + struct static_route *r, *r2, *or, *nr; if (cf_igp_table(o) != cf_igp_table(n)) return 0; - if (!proto_configure_channel(p, &p->main_channel, proto_cf_main_channel(CF))) + if (!proto_configure_channel(P, &P->main_channel, proto_cf_main_channel(CF))) return 0; - /* Delete all obsolete routes and reset neighbor entries */ - WALK_LIST(r, o->other_routes) - static_match(p, r, n); - WALK_LIST(r, o->iface_routes) - static_match(p, r, n); - WALK_LIST(r, o->neigh_routes) - static_match(p, r, n); + p->p.cf = CF; - /* Now add all new routes, those not changed will be ignored by static_install() */ - WALK_LIST(r, n->neigh_routes) - static_add(p, n, r); - WALK_LIST(r, o->neigh_routes) - static_rte_cleanup(p, r); + /* Reset route lists in neighbor entries */ + WALK_LIST(r, o->routes) + for (r2 = r; r2; r2 = r2->mp_next) + if (r2->neigh) + r2->neigh->data = NULL; - WALK_LIST(r, n->iface_routes) - { - struct iface *ifa; - if ((ifa = if_find_by_name(r->if_name)) && (ifa->flags & IF_UP)) - { - r->iface = ifa; - static_install(p, r); - } - } + /* Reconfigure initial matching sequence */ + for (or = HEAD(o->routes), nr = HEAD(n->routes); + NODE_VALID(or) && NODE_VALID(nr) && net_equal(or->net, nr->net); + or = NODE_NEXT(or), nr = NODE_NEXT(nr)) + static_reconfigure_rte(p, or, nr); - WALK_LIST(r, n->other_routes) + if (!NODE_VALID(or) && !NODE_VALID(nr)) + return 1; + + /* Reconfigure remaining routes, sort them to find matching pairs */ + struct static_route *or2, *nr2, **orbuf, **nrbuf; + uint ornum = 0, nrnum = 0, orpos = 0, nrpos = 0, i; + + for (or2 = or; NODE_VALID(or2); or2 = NODE_NEXT(or2)) + ornum++; + + for (nr2 = nr; NODE_VALID(nr2); nr2 = NODE_NEXT(nr2)) + nrnum++; + + orbuf = xmalloc(ornum * sizeof(void *)); + nrbuf = xmalloc(nrnum * sizeof(void *)); + + for (i = 0, or2 = or; i < ornum; i++, or2 = NODE_NEXT(or2)) + orbuf[i] = or2; + + for (i = 0, nr2 = nr; i < nrnum; i++, nr2 = NODE_NEXT(nr2)) + nrbuf[i] = nr2; + + qsort(orbuf, ornum, sizeof(struct static_route *), static_cmp_rte); + qsort(nrbuf, nrnum, sizeof(struct static_route *), static_cmp_rte); + + while ((orpos < ornum) && (nrpos < nrnum)) { - r->state |= STS_WANT; - static_install(p, r); + int x = net_compare(orbuf[orpos]->net, nrbuf[nrpos]->net); + if (x < 0) + static_remove_rte(p, orbuf[orpos++]); + else if (x > 0) + static_add_rte(p, nrbuf[nrpos++]); + else + static_reconfigure_rte(p, orbuf[orpos++], nrbuf[nrpos++]); } - WALK_LIST(r, o->other_routes) - static_rte_cleanup(p, r); + while (orpos < ornum) + static_remove_rte(p, orbuf[orpos++]); + + while (nrpos < nrnum) + static_add_rte(p, nrbuf[nrpos++]); + + xfree(orbuf); + xfree(nrbuf); return 1; } -static void -static_copy_routes(list *dlst, list *slst) -{ - struct static_route *sr; - - init_list(dlst); - WALK_LIST(sr, *slst) - { - struct static_route *srr, *drr = NULL; - for (srr = sr->mp_head; srr; srr = srr->mp_next) - { - /* copy one route */ - struct static_route *dr = cfg_alloc(sizeof(struct static_route)); - if (drr) - drr->mp_next = dr; - else - add_tail(dlst, &(dr->n)); - - memcpy(dr, sr, sizeof(struct static_route)); - drr = dr; - } - } -} - static void static_copy_config(struct proto_config *dest, struct proto_config *src) { struct static_config *d = (struct static_config *) dest; struct static_config *s = (struct static_config *) src; - /* Copy route lists */ - static_copy_routes(&d->neigh_routes, &s->neigh_routes); - static_copy_routes(&d->iface_routes, &s->iface_routes); - static_copy_routes(&d->other_routes, &s->other_routes); + struct static_route *srt, *snh; + + /* Copy route list */ + init_list(&d->routes); + WALK_LIST(srt, s->routes) + { + struct static_route *drt = NULL, *dnh = NULL, **dnp = &drt; + + for (snh = srt; snh; snh = snh->mp_next) + { + dnh = cfg_alloc(sizeof(struct static_route)); + memcpy(dnh, snh, sizeof(struct static_route)); + + if (!drt) + add_tail(&d->routes, &(dnh->n)); + + *dnp = dnh; + dnp = &(dnh->mp_next); + + if (snh->mp_head) + dnh->mp_head = drt; + } + } } + +static const char * rta_dest_names[] = { + [RTD_NONE] = "", + [RTD_UNICAST] = "unicast", + [RTD_BLACKHOLE] = "blackhole", + [RTD_UNREACHABLE] = "unreachable", + [RTD_PROHIBIT] = "prohibited", +}; + +static void +static_show_rt(struct static_route *r) +{ + switch (r->dest) + { + case RTD_UNICAST: + { + struct static_route *r2; + + cli_msg(-1009, "%N", r->net); + for (r2 = r; r2; r2 = r2->mp_next) + { + if (r2->iface && ipa_zero(r2->via)) + cli_msg(-1009, "\tdev %s%s%s", r2->iface->name, + r2->bfd_req ? " (bfd)" : "", r2->active ? "" : " (dormant)"); + else + cli_msg(-1009, "\tvia %I%J%s%s", r2->via, r2->iface, + r2->bfd_req ? " (bfd)" : "", r2->active ? "" : " (dormant)"); + } + break; + } + + case RTD_NONE: + case RTD_BLACKHOLE: + case RTD_UNREACHABLE: + case RTD_PROHIBIT: + cli_msg(-1009, "%N\t%s", r->net, rta_dest_names[r->dest]); + break; + + case RTDX_RECURSIVE: + cli_msg(-1009, "%N\trecursive %I", r->net, r->via); + break; + } +} + +void +static_show(struct proto *P) +{ + struct static_config *c = (void *) P->cf; + struct static_route *r; + + WALK_LIST(r, c->routes) + static_show_rt(r); + cli_msg(0, ""); +} + + struct protocol proto_static = { .name = "Static", .template = "static%d", .preference = DEF_PREF_STATIC, .channel_mask = NB_ANY, - .proto_size = sizeof(struct proto), + .proto_size = sizeof(struct static_proto), .config_size = sizeof(struct static_config), .postconfig = static_postconfig, .init = static_init, @@ -669,59 +642,3 @@ struct protocol proto_static = { .reconfigure = static_reconfigure, .copy_config = static_copy_config }; - -static byte * -static_format_via(struct static_route *r) -{ - static byte via[IPA_MAX_TEXT_LENGTH + 25]; - - switch (r->dest) - { - case RTD_UNICAST: if (ipa_zero(r->via)) bsprintf(via, "dev %s", r->if_name); - else bsprintf(via, "via %I%J", r->via, r->iface); - break; - case RTD_BLACKHOLE: bsprintf(via, "blackhole"); break; - case RTD_UNREACHABLE: bsprintf(via, "unreachable"); break; - case RTD_PROHIBIT: bsprintf(via, "prohibited"); break; - case RTDX_RECURSIVE: bsprintf(via, "recursive %I", r->via); break; - default: bsprintf(via, "???"); - } - return via; -} - -static void -static_show_rt(struct static_route *r) -{ - if (r->mp_head && (r != r->mp_head)) - return; - if (r->mp_next) - { - cli_msg(-1009, "%N", r->net); - struct static_route *r2; - for (r2 = r; r2; r2 = r2->mp_next) - { - cli_msg(-1009, "\t%s weight %d%s%s", static_format_via(r2), r2->weight + 1, - r2->bfd_req ? " (bfd)" : "", (r2->state & STS_INSTALLED) ? "" : " (dormant)"); - if (r2->mp_next == r) - break; - } - } - else - cli_msg(-1009, "%N %s%s%s", r->net, static_format_via(r), - r->bfd_req ? " (bfd)" : "", (r->state & STS_INSTALLED) ? "" : " (dormant)"); -} - -void -static_show(struct proto *P) -{ - struct static_config *c = (void *) P->cf; - struct static_route *r; - - WALK_LIST(r, c->neigh_routes) - static_show_rt(r); - WALK_LIST(r, c->iface_routes) - static_show_rt(r); - WALK_LIST(r, c->other_routes) - static_show_rt(r); - cli_msg(0, ""); -} diff --git a/proto/static/static.h b/proto/static/static.h index aeb9660a..bfcbd8c3 100644 --- a/proto/static/static.h +++ b/proto/static/static.h @@ -11,49 +11,58 @@ #include "nest/route.h" #include "nest/bfd.h" +#include "lib/buffer.h" struct static_config { struct proto_config c; - list iface_routes; /* Routes to search on interface events */ - list neigh_routes; /* Routes to search on neighbor events */ - list other_routes; /* Non-nexthop routes */ + list routes; /* List of static routes (struct static_route) */ int check_link; /* Whether iface link state is used */ struct rtable_config *igp_table; /* Table used for recursive next hop lookups */ }; +struct static_proto { + struct proto p; -void static_init_config(struct static_config *); + struct event *event; /* Event for announcing updated routes */ + BUFFER(struct static_route *) marked; /* Routes marked for reannouncement */ +}; struct static_route { node n; - struct static_route *chain; /* Next for the same neighbor */ net_addr *net; /* Network we route */ - int dest; /* Destination type (RTD_*) */ ip_addr via; /* Destination router */ struct iface *iface; /* Destination iface, for link-local vias or device routes */ - struct neighbor *neigh; - byte *if_name; /* Name for device routes */ - struct static_route *mp_next; /* Nexthops for multipath routes */ + struct neighbor *neigh; /* Associated neighbor entry */ + struct static_route *chain; /* Next for the same neighbor */ struct static_route *mp_head; /* First nexthop of this route */ + struct static_route *mp_next; /* Nexthops for multipath routes */ struct f_inst *cmds; /* List of commands for setting attributes */ - u32 state; /* Current state: STS_* */ - int weight; /* Multipath next hop weight */ + byte dest; /* Destination type (RTD_*) */ + byte state; /* State of route announcement (SRS_*) */ + byte active; /* Next hop is active (nbr/iface/BFD available) */ + byte weight; /* Multipath next hop weight */ byte use_bfd; /* Configured to use BFD */ byte label_count; /* Number of labels in stack */ struct bfd_request *bfd_req; /* BFD request, if BFD is used */ u32 *label_stack; /* Label stack if label_count > 0 */ }; -#define STS_INSTALLED 0x1 -#define STS_WANT 0x2 -#define STS_FORCE 0x4 - -/* Dummy nodes (parts of multipath route) abuses masklen field for weight - and if_name field for a ptr to the master (RTD_MULTIPATH) node. */ - +/* + * Note that data fields neigh, chain, state, active and bfd_req are runtime + * data, not real configuration data. Must be handled carefully. + * + * Regular (i.e. dest == RTD_UNICAST) routes use static_route structure for + * additional next hops (fields mp_head, mp_next). Note that 'state' is for + * whole route, while 'active' is for each next hop. Also note that fields + * mp_head, mp_next, active are zero for other kinds of routes. + */ #define RTDX_RECURSIVE 0x7f /* Phony dest value for recursive routes */ +#define SRS_DOWN 0 /* Route is not announced */ +#define SRS_CLEAN 1 /* Route is active and announced */ +#define SRS_DIRTY 2 /* Route changed since announcement */ + void static_show(struct proto *); #endif