From c0adf7e9fc0bb920175a639c6f56ed7b4190f3e4 Mon Sep 17 00:00:00 2001 From: Ondrej Zajicek Date: Thu, 15 Mar 2012 11:58:08 +0100 Subject: [PATCH] Better support for multitable protocols. The nest-protocol interaction is changed to better handle multitable protocols. Multitable protocols now declare that by 'multitable' field, which tells nest that a protocol handles things related to proto-rtable interaction (table locking, announce hook adding, reconfiguration of filters) itself. Filters and stats are moved to announce hooks, a protocol could have different filters and stats to different tables. The patch is based on one from Alexander V. Chernikov, thanks. --- nest/proto-hooks.c | 13 ++- nest/proto.c | 210 ++++++++++++++++++++++++++------------------- nest/protocol.h | 22 +++-- nest/route.h | 6 +- nest/rt-table.c | 82 +++++++----------- proto/bgp/bgp.c | 2 + proto/pipe/pipe.c | 189 ++++++++++++++++++++++++++++++---------- proto/pipe/pipe.h | 9 +- 8 files changed, 329 insertions(+), 204 deletions(-) diff --git a/nest/proto-hooks.c b/nest/proto-hooks.c index 2582c48b..e80f87ea 100644 --- a/nest/proto-hooks.c +++ b/nest/proto-hooks.c @@ -90,7 +90,7 @@ void dump_attrs(rte *e) * @p: protocol instance * * The start() hook is called by the core when it wishes to start - * the instance. + * the instance. Multitable protocols should lock their tables here. * * Result: new protocol state */ @@ -109,6 +109,17 @@ int start(struct proto *p) int shutdown(struct proto *p) { DUMMY; } +/** + * cleanup - request instance cleanup + * @p: protocol instance + * + * The cleanup() hook is called by the core when the protocol became + * hungry/down, i.e. all protocol ahooks and routes are flushed. + * Multitable protocols should unlock their tables here. + */ +void cleanup(struct proto *p) +{ DUMMY; } + /** * get_status - get instance status * @p: protocol instance diff --git a/nest/proto.c b/nest/proto.c index 0fc72ce1..9a7f123e 100644 --- a/nest/proto.c +++ b/nest/proto.c @@ -112,8 +112,6 @@ proto_new(struct proto_config *c, unsigned size) p->disabled = c->disabled; p->proto = pr; p->table = c->table->table; - p->in_filter = c->in_filter; - p->out_filter = c->out_filter; p->hash_key = random_u32(); c->proto = p; return p; @@ -126,49 +124,102 @@ proto_init_instance(struct proto *p) p->pool = rp_new(proto_pool, p->proto->name); p->attn = ev_new(p->pool); p->attn->data = p; - rt_lock_table(p->table); + + if (! p->proto->multitable) + rt_lock_table(p->table); } +extern pool *rt_table_pool; /** * proto_add_announce_hook - connect protocol to a routing table * @p: protocol instance * @t: routing table to connect to + * @in: input filter + * @out: output filter + * @stats: per-table protocol statistics * * This function creates a connection between the protocol instance @p * and the routing table @t, making the protocol hear all changes in * the table. * + * The announce hook is linked in the protocol ahook list and, if the + * protocol accepts routes, also in the table ahook list. Announce + * hooks are allocated from the routing table resource pool, they are + * unlinked from the table ahook list after the protocol went down, + * (in proto_schedule_flush()) and they are automatically freed after the + * protocol is flushed (in proto_fell_down()). + * * Unless you want to listen to multiple routing tables (as the Pipe * protocol does), you needn't to worry about this function since the * connection to the protocol's primary routing table is initialized * automatically by the core code. */ struct announce_hook * -proto_add_announce_hook(struct proto *p, struct rtable *t) +proto_add_announce_hook(struct proto *p, struct rtable *t, struct filter *in, + struct filter *out, struct proto_stats *stats) { struct announce_hook *h; - if (!p->rt_notify) - return NULL; DBG("Connecting protocol %s to table %s\n", p->name, t->name); PD(p, "Connected to table %s", t->name); - h = mb_alloc(p->pool, sizeof(struct announce_hook)); + + h = mb_allocz(rt_table_pool, sizeof(struct announce_hook)); h->table = t; h->proto = p; + h->in_filter = in; + h->out_filter = out; + h->stats = stats; + h->next = p->ahooks; p->ahooks = h; - add_tail(&t->hooks, &h->n); + + if (p->rt_notify) + add_tail(&t->hooks, &h->n); return h; } +/** + * proto_find_announce_hook - find announce hooks + * @p: protocol instance + * @t: routing table + * + * Returns pointer to announce hook or NULL + */ +struct announce_hook * +proto_find_announce_hook(struct proto *p, struct rtable *t) +{ + struct announce_hook *a; + + for (a = p->ahooks; a; a = a->next) + if (a->table == t) + return a; + + return NULL; +} + static void -proto_flush_hooks(struct proto *p) +proto_unlink_ahooks(struct proto *p) { struct announce_hook *h; - for(h=p->ahooks; h; h=h->next) - rem_node(&h->n); + if (p->rt_notify) + for(h=p->ahooks; h; h=h->next) + rem_node(&h->n); +} + +static void +proto_free_ahooks(struct proto *p) +{ + struct announce_hook *h, *hn; + + for(h = p->ahooks; h; h = hn) + { + hn = h->next; + mb_free(h); + } + p->ahooks = NULL; + p->main_ahook = NULL; } /** @@ -322,6 +373,8 @@ proto_init(struct proto_config *c) return q; } +int proto_reconfig_type; /* Hack to propagate type info to pipe reconfigure hook */ + static int proto_reconfigure(struct proto *p, struct proto_config *oc, struct proto_config *nc, int type) { @@ -336,23 +389,10 @@ proto_reconfigure(struct proto *p, struct proto_config *oc, struct proto_config (proto_get_router_id(nc) != proto_get_router_id(oc))) return 0; - int import_changed = (type != RECONFIG_SOFT) && ! filter_same(nc->in_filter, oc->in_filter); - int export_changed = (type != RECONFIG_SOFT) && ! filter_same(nc->out_filter, oc->out_filter); - - /* We treat a change in preferences by reimporting routes */ - if (nc->preference != oc->preference) - import_changed = 1; - - /* If the protocol in not UP, it has no routes and we can ignore such changes */ - if (p->proto_state != PS_UP) - import_changed = export_changed = 0; - - /* Without this hook we cannot reload routes and have to restart the protocol */ - if (import_changed && ! p->reload_routes) - return 0; p->debug = nc->debug; p->mrtdump = nc->mrtdump; + proto_reconfig_type = type; /* Execute protocol specific reconfigure hook */ if (! (p->proto->reconfigure && p->proto->reconfigure(p, nc))) @@ -362,14 +402,37 @@ proto_reconfigure(struct proto *p, struct proto_config *oc, struct proto_config PD(p, "Reconfigured"); p->cf = nc; p->name = nc->name; - p->in_filter = nc->in_filter; - p->out_filter = nc->out_filter; p->preference = nc->preference; + + /* Multitable protocols handle rest in their reconfigure hooks */ + if (p->proto->multitable) + return 1; + + /* Update filters in the main announce hook */ + if (p->main_ahook) + { + p->main_ahook->in_filter = nc->in_filter; + p->main_ahook->out_filter = nc->out_filter; + } + + /* Update routes when filters changed. If the protocol in not UP, + it has no routes and we can ignore such changes */ + if ((p->proto_state != PS_UP) || (type == RECONFIG_SOFT)) + return 1; + + int import_changed = ! filter_same(nc->in_filter, oc->in_filter); + int export_changed = ! filter_same(nc->out_filter, oc->out_filter); + + /* We treat a change in preferences by reimporting routes */ + if (nc->preference != oc->preference) + import_changed = 1; + if (import_changed || export_changed) log(L_INFO "Reloading protocol %s", p->name); - if (import_changed && ! p->reload_routes(p)) + /* If import filter changed, call reload hook */ + if (import_changed && ! (p->reload_routes && p->reload_routes(p))) { /* Now, the protocol is reconfigured. But route reload failed and we have to do regular protocol restart. */ @@ -553,6 +616,7 @@ void protos_dump_all(void) { struct proto *p; + struct announce_hook *a; debug("Protocols:\n"); @@ -560,10 +624,14 @@ protos_dump_all(void) { debug(" protocol %s state %s/%s\n", p->name, p_states[p->proto_state], c_states[p->core_state]); - if (p->in_filter) - debug("\tInput filter: %s\n", filter_name(p->in_filter)); - if (p->out_filter != FILTER_REJECT) - debug("\tOutput filter: %s\n", filter_name(p->out_filter)); + for (a = p->ahooks; a; a = a->next) + { + debug("\tTABLE %s\n", a->table->name); + if (a->in_filter) + debug("\tInput filter: %s\n", filter_name(a->in_filter)); + if (a->out_filter != FILTER_REJECT) + debug("\tOutput filter: %s\n", filter_name(a->out_filter)); + } if (p->disabled) debug("\tDISABLED\n"); else if (p->proto->dump) @@ -647,7 +715,10 @@ proto_fell_down(struct proto *p) log(L_ERR "Protocol %s is down but still has %d routes", p->name, p->stats.imp_routes); bzero(&p->stats, sizeof(struct proto_stats)); - rt_unlock_table(p->table); + proto_free_ahooks(p); + + if (! p->proto->multitable) + rt_unlock_table(p->table); if (p->proto->cleanup) p->proto->cleanup(p); @@ -686,7 +757,7 @@ proto_feed_initial(void *P) return; DBG("Feeding protocol %s\n", p->name); - proto_add_announce_hook(p, p->table); + if_feed_baby(p); proto_feed_more(P); } @@ -701,7 +772,7 @@ proto_schedule_flush(struct proto *p) DBG("%s: Scheduling flush\n", p->name); p->core_state = FS_FLUSHING; proto_relink(p); - proto_flush_hooks(p); + proto_unlink_ahooks(p); ev_schedule(proto_flush_event); } @@ -716,6 +787,11 @@ proto_schedule_feed(struct proto *p, int initial) if (!initial) p->stats.exp_routes = 0; + /* Connect protocol to routing table */ + if (initial && !p->proto->multitable) + p->main_ahook = proto_add_announce_hook(p, p->table, + p->cf->in_filter, p->cf->out_filter, &p->stats); + proto_relink(p); p->attn->hook = initial ? proto_feed_initial : proto_feed_more; ev_schedule(p->attn); @@ -855,9 +931,8 @@ proto_state_name(struct proto *p) } static void -proto_do_show_stats(struct proto *p) +proto_show_stats(struct proto_stats *s) { - struct proto_stats *s = &p->stats; cli_msg(-1006, " Routes: %u imported, %u exported, %u preferred", s->imp_routes, s->exp_routes, s->pref_routes); cli_msg(-1006, " Route change stats: received rejected filtered ignored accepted"); @@ -875,47 +950,17 @@ proto_do_show_stats(struct proto *p) s->exp_withdraws_received, s->exp_withdraws_accepted); } -#ifdef CONFIG_PIPE -static void -proto_do_show_pipe_stats(struct proto *p) +void +proto_show_basic_info(struct proto *p) { - struct proto_stats *s1 = &p->stats; - struct proto_stats *s2 = pipe_get_peer_stats(p); + // cli_msg(-1006, " Table: %s", p->table->name); + cli_msg(-1006, " Preference: %d", p->preference); + cli_msg(-1006, " Input filter: %s", filter_name(p->cf->in_filter)); + cli_msg(-1006, " Output filter: %s", filter_name(p->cf->out_filter)); - /* - * Pipe stats (as anything related to pipes) are a bit tricky. There - * are two sets of stats - s1 for routes going from the primary - * routing table to the secondary routing table ('exported' from the - * user point of view) and s2 for routes going in the other - * direction ('imported' from the user point of view). - * - * Each route going through a pipe is, technically, first exported - * to the pipe and then imported from that pipe and such operations - * are counted in one set of stats according to the direction of the - * route propagation. Filtering is done just in the first part - * (export). Therefore, we compose stats for one directon for one - * user direction from both import and export stats, skipping - * immediate and irrelevant steps (exp_updates_accepted, - * imp_updates_received, imp_updates_filtered, ...) - */ - - cli_msg(-1006, " Routes: %u imported, %u exported", - s2->imp_routes, s1->imp_routes); - cli_msg(-1006, " Route change stats: received rejected filtered ignored accepted"); - cli_msg(-1006, " Import updates: %10u %10u %10u %10u %10u", - s2->exp_updates_received, s2->exp_updates_rejected + s2->imp_updates_invalid, - s2->exp_updates_filtered, s2->imp_updates_ignored, s2->imp_updates_accepted); - cli_msg(-1006, " Import withdraws: %10u %10u --- %10u %10u", - s2->exp_withdraws_received, s2->imp_withdraws_invalid, - s2->imp_withdraws_ignored, s2->imp_withdraws_accepted); - cli_msg(-1006, " Export updates: %10u %10u %10u %10u %10u", - s1->exp_updates_received, s1->exp_updates_rejected + s1->imp_updates_invalid, - s1->exp_updates_filtered, s1->imp_updates_ignored, s1->imp_updates_accepted); - cli_msg(-1006, " Export withdraws: %10u %10u --- %10u %10u", - s1->exp_withdraws_received, s1->imp_withdraws_invalid, - s1->imp_withdraws_ignored, s1->imp_withdraws_accepted); + if (p->proto_state != PS_DOWN) + proto_show_stats(&p->stats); } -#endif void proto_cmd_show(struct proto *p, unsigned int verbose, int cnt) @@ -943,22 +988,11 @@ proto_cmd_show(struct proto *p, unsigned int verbose, int cnt) cli_msg(-1006, " Description: %s", p->cf->dsc); if (p->cf->router_id) cli_msg(-1006, " Router ID: %R", p->cf->router_id); - cli_msg(-1006, " Preference: %d", p->preference); - cli_msg(-1006, " Input filter: %s", filter_name(p->in_filter)); - cli_msg(-1006, " Output filter: %s", filter_name(p->out_filter)); - - if (p->proto_state != PS_DOWN) - { -#ifdef CONFIG_PIPE - if (proto_is_pipe(p)) - proto_do_show_pipe_stats(p); - else -#endif - proto_do_show_stats(p); - } if (p->proto->show_proto_info) p->proto->show_proto_info(p); + else + proto_show_basic_info(p); cli_msg(-1006, ""); } diff --git a/nest/protocol.h b/nest/protocol.h index a83c4ffc..187b8712 100644 --- a/nest/protocol.h +++ b/nest/protocol.h @@ -39,6 +39,7 @@ struct protocol { char *template; /* Template for automatic generation of names */ int name_counter; /* Counter for automatic name generation */ int attr_class; /* Attribute class known to this protocol */ + int multitable; /* Protocol handles all announce hooks itself */ unsigned preference; /* Default protocol preference */ void (*preconfig)(struct protocol *, struct config *); /* Just before configuring */ @@ -193,8 +194,7 @@ struct proto { void (*rte_remove)(struct network *, struct rte *); struct rtable *table; /* Our primary routing table */ - struct filter *in_filter; /* Input filter */ - struct filter *out_filter; /* Output filter */ + struct announce_hook *main_ahook; /* Primary announcement hook */ struct announce_hook *ahooks; /* Announcement hooks for this protocol */ struct fib_iterator *feed_iterator; /* Routing table iterator used during protocol feeding */ @@ -218,6 +218,9 @@ static inline void proto_copy_rest(struct proto_config *dest, struct proto_config *src, unsigned size) { memcpy(dest + 1, src + 1, size - sizeof(struct proto_config)); } + +void proto_show_basic_info(struct proto *p); + void proto_cmd_show(struct proto *, unsigned int, int); void proto_cmd_disable(struct proto *, unsigned int, int); void proto_cmd_enable(struct proto *, unsigned int, int); @@ -352,18 +355,13 @@ struct announce_hook { node n; struct rtable *table; struct proto *proto; + struct filter *in_filter; /* Input filter */ + struct filter *out_filter; /* Output filter */ + struct proto_stats *stats; /* Per-table protocol statistics */ struct announce_hook *next; /* Next hook for the same protocol */ }; -struct announce_hook *proto_add_announce_hook(struct proto *, struct rtable *); - -/* - * Some pipe-specific nest hacks - */ - -#ifdef CONFIG_PIPE -#include "proto/pipe/pipe.h" -#endif - +struct announce_hook *proto_add_announce_hook(struct proto *, struct rtable *, struct filter *, struct filter *, struct proto_stats *); +struct announce_hook *proto_find_announce_hook(struct proto *p, struct rtable *t); #endif diff --git a/nest/route.h b/nest/route.h index e6712c64..fcb4f283 100644 --- a/nest/route.h +++ b/nest/route.h @@ -12,6 +12,7 @@ #include "lib/lists.h" #include "lib/resource.h" #include "lib/timer.h" +#include "nest/protocol.h" struct protocol; struct proto; @@ -179,7 +180,7 @@ struct hostentry { typedef struct rte { struct rte *next; net *net; /* Network this RTE belongs to */ - struct proto *sender; /* Protocol instance that sent the route to the routing table */ + struct announce_hook *sender; /* Announce hook used to send the route to the routing table */ struct rta *attrs; /* Attributes of this route */ byte flags; /* Flags (REF_...) */ byte pflags; /* Protocol-specific flags */ @@ -234,7 +235,8 @@ static inline net *net_find(rtable *tab, ip_addr addr, unsigned len) { return (n static inline net *net_get(rtable *tab, ip_addr addr, unsigned len) { return (net *) fib_get(&tab->fib, &addr, len); } rte *rte_find(net *net, struct proto *p); rte *rte_get_temp(struct rta *); -void rte_update(rtable *tab, net *net, struct proto *p, struct proto *src, rte *new); +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); void rte_dump(rte *); void rte_free(rte *); diff --git a/nest/rt-table.c b/nest/rt-table.c index 377687de..3807ef8d 100644 --- a/nest/rt-table.c +++ b/nest/rt-table.c @@ -184,24 +184,16 @@ rte_trace_out(unsigned int flag, struct proto *p, rte *e, char *msg) } static inline void -do_rte_announce(struct announce_hook *a, int type UNUSED, net *net, rte *new, rte *old, ea_list *tmpa, int refeed) +do_rte_announce(struct announce_hook *ah, int type UNUSED, net *net, rte *new, rte *old, ea_list *tmpa, int refeed) { - struct proto *p = a->proto; - struct filter *filter = p->out_filter; - struct proto_stats *stats = &p->stats; + struct proto *p = ah->proto; + struct filter *filter = ah->out_filter; + struct proto_stats *stats = ah->stats; + rte *new0 = new; rte *old0 = old; int ok; -#ifdef CONFIG_PIPE - /* The secondary direction of the pipe */ - if (proto_is_pipe(p) && (p->table != a->table)) - { - filter = p->in_filter; - stats = pipe_get_peer_stats(p); - } -#endif - if (new) { stats->exp_updates_received++; @@ -294,18 +286,18 @@ do_rte_announce(struct announce_hook *a, int type UNUSED, net *net, rte *new, rt rte_trace_out(D_ROUTES, p, old, "removed"); } if (!new) - p->rt_notify(p, a->table, net, NULL, old, NULL); + p->rt_notify(p, ah->table, net, NULL, old, NULL); else if (tmpa) { ea_list *t = tmpa; while (t->next) t = t->next; t->next = new->attrs->eattrs; - p->rt_notify(p, a->table, net, new, old, tmpa); + p->rt_notify(p, ah->table, net, new, old, tmpa); t->next = NULL; } else - p->rt_notify(p, a->table, net, new, old, new->attrs->eattrs); + 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) @@ -375,7 +367,7 @@ rte_validate(rte *e) if ((n->n.pxlen > BITS_PER_IP_ADDRESS) || !ip_is_prefix(n->n.prefix,n->n.pxlen)) { log(L_WARN "Ignoring bogus prefix %I/%d received via %s", - n->n.prefix, n->n.pxlen, e->sender->name); + n->n.prefix, n->n.pxlen, e->sender->proto->name); return 0; } @@ -383,7 +375,7 @@ rte_validate(rte *e) if ((c < 0) || !(c & IADDR_HOST) || ((c & IADDR_SCOPE_MASK) <= SCOPE_LINK)) { log(L_WARN "Ignoring bogus route %I/%d received via %s", - n->n.prefix, n->n.pxlen, e->sender->name); + n->n.prefix, n->n.pxlen, e->sender->proto->name); return 0; } @@ -423,18 +415,15 @@ rte_same(rte *x, rte *y) } static void -rte_recalculate(rtable *table, net *net, struct proto *p, struct proto *src, rte *new, ea_list *tmpa) +rte_recalculate(struct announce_hook *ah, net *net, rte *new, ea_list *tmpa, struct proto *src) { - struct proto_stats *stats = &p->stats; + struct proto *p = ah->proto; + struct rtable *table = ah->table; + struct proto_stats *stats = ah->stats; rte *old_best = net->routes; rte *old = NULL; rte **k, *r, *s; -#ifdef CONFIG_PIPE - if (proto_is_pipe(p) && (p->table == table)) - stats = pipe_get_peer_stats(p); -#endif - k = &net->routes; /* Find and remove original route from the same protocol */ while (old = *k) { @@ -449,7 +438,7 @@ rte_recalculate(rtable *table, net *net, struct proto *p, struct proto *src, rte * ignore it completely (there might be 'spurious withdraws', * see FIXME in do_rte_announce()) */ - if (old->sender != p) + if (old->sender->proto != p) { if (new) { @@ -613,6 +602,7 @@ rte_update_unlock(void) /** * rte_update - enter a new update to a routing table * @table: table to be updated + * @ah: pointer to table announce hook * @net: network node * @p: protocol submitting the update * @src: protocol originating the update @@ -652,28 +642,17 @@ rte_update_unlock(void) */ void -rte_update(rtable *table, net *net, struct proto *p, struct proto *src, rte *new) +rte_update2(struct announce_hook *ah, net *net, rte *new, struct proto *src) { + struct proto *p = ah->proto; + struct proto_stats *stats = ah->stats; + struct filter *filter = ah->in_filter; ea_list *tmpa = NULL; - struct proto_stats *stats = &p->stats; - -#ifdef CONFIG_PIPE - if (proto_is_pipe(p) && (p->table == table)) - stats = pipe_get_peer_stats(p); -#endif rte_update_lock(); if (new) { - new->sender = p; - struct filter *filter = p->in_filter; - - /* Do not filter routes going through the pipe, - they are filtered in the export filter only. */ -#ifdef CONFIG_PIPE - if (proto_is_pipe(p)) - filter = FILTER_ACCEPT; -#endif + new->sender = ah; stats->imp_updates_received++; if (!rte_validate(new)) @@ -710,13 +689,13 @@ rte_update(rtable *table, net *net, struct proto *p, struct proto *src, rte *new else stats->imp_withdraws_received++; - rte_recalculate(table, net, p, src, new, tmpa); + rte_recalculate(ah, net, new, tmpa, src); rte_update_unlock(); return; drop: rte_free(new); - rte_recalculate(table, net, p, src, NULL, NULL); + rte_recalculate(ah, net, NULL, NULL, src); rte_update_unlock(); } @@ -739,7 +718,7 @@ void rte_discard(rtable *t, rte *old) /* Non-filtered route deletion, used during garbage collection */ { rte_update_lock(); - rte_recalculate(t, old->net, old->sender, old->attrs->proto, NULL, NULL); + rte_recalculate(old->sender, old->net, NULL, NULL, old->attrs->proto); rte_update_unlock(); } @@ -912,8 +891,8 @@ again: ncnt++; rescan: for (e=n->routes; e; e=e->next, rcnt++) - if (e->sender->core_state != FS_HAPPY && - e->sender->core_state != FS_FEEDING) + if (e->sender->proto->core_state != FS_HAPPY && + e->sender->proto->core_state != FS_FEEDING) { rte_discard(tab, e); rdel++; @@ -1026,7 +1005,7 @@ rt_next_hop_update_net(rtable *tab, net *n) *k = new; rte_announce_i(tab, RA_ANY, n, new, e); - rte_trace_in(D_ROUTES, new->sender, new, "updated"); + rte_trace_in(D_ROUTES, new->sender->proto, new, "updated"); /* Call a pre-comparison hook */ /* Not really an efficient way to compute this */ @@ -1066,7 +1045,7 @@ rt_next_hop_update_net(rtable *tab, net *n) if (new != old_best) { rte_announce_i(tab, RA_OPTIMAL, n, new, old_best); - rte_trace_in(D_ROUTES, new->sender, new, "updated [best]"); + rte_trace_in(D_ROUTES, new->sender->proto, new, "updated [best]"); } if (free_old_best) @@ -1693,6 +1672,7 @@ rt_show_net(struct cli *c, net *n, struct rt_show_data *d) { rte *e, *ee; byte ia[STD_ADDRESS_P_LENGTH+8]; + struct announce_hook *a; int ok; bsprintf(ia, "%I/%d", n->n.prefix, n->n.pxlen); @@ -1722,8 +1702,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 ((p1->out_filter == FILTER_REJECT) || - (p1->out_filter && f_run(p1->out_filter, &e, &tmpa, rte_update_pool, FF_FORCE_TMPATTR) > F_ACCEPT)) + 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))) ok = 0; } } diff --git a/proto/bgp/bgp.c b/proto/bgp/bgp.c index 4d3c32fb..4dd4b7be 100644 --- a/proto/bgp/bgp.c +++ b/proto/bgp/bgp.c @@ -1125,6 +1125,8 @@ bgp_show_proto_info(struct proto *P) struct bgp_proto *p = (struct bgp_proto *) P; struct bgp_conn *c = p->conn; + proto_show_basic_info(P); + cli_msg(-1006, " BGP state: %s", bgp_state_dsc(p)); cli_msg(-1006, " Neighbor address: %I%J", p->cf->remote_ip, p->cf->iface); cli_msg(-1006, " Neighbor AS: %u", p->remote_as); diff --git a/proto/pipe/pipe.c b/proto/pipe/pipe.c index fe8618b6..36b06d43 100644 --- a/proto/pipe/pipe.c +++ b/proto/pipe/pipe.c @@ -16,6 +16,17 @@ * * To avoid pipe loops, Pipe keeps a `being updated' flag in each routing * table. + * + * A pipe has two announce hooks, the first connected to the main + * table, the second connected to the peer table. When a new route is + * announced on the main table, it gets checked by an export filter in + * ahook 1, and, after that, it is announced to the peer table via + * rte_update(), an import filter in ahook 2 is called. When a new + * route is announced in the peer table, an export filter in ahook2 + * and an import filter in ahook 1 are used. Oviously, there is no + * need in filtering the same route twice, so both import filters + * are set to accept, while user configured 'import' and 'export' + * filters are used as export filters in ahooks 2 and 1. */ #undef LOCAL_DEBUG @@ -24,6 +35,7 @@ #include "nest/iface.h" #include "nest/protocol.h" #include "nest/route.h" +#include "nest/cli.h" #include "conf/conf.h" #include "filter/filter.h" #include "lib/string.h" @@ -34,7 +46,8 @@ static void pipe_rt_notify(struct proto *P, rtable *src_table, net *n, rte *new, rte *old, ea_list *attrs) { struct pipe_proto *p = (struct pipe_proto *) P; - rtable *dest = (src_table == P->table) ? p->peer : P->table; /* The other side of the pipe */ + struct announce_hook *ah = (src_table == P->table) ? p->peer_ahook : P->main_ahook; + rtable *dst_table = ah->table; struct proto *src; net *nn; @@ -44,13 +57,14 @@ pipe_rt_notify(struct proto *P, rtable *src_table, net *n, rte *new, rte *old, e if (!new && !old) return; - if (dest->pipe_busy) + if (dst_table->pipe_busy) { log(L_ERR "Pipe loop detected when sending %I/%d to table %s", - n->n.prefix, n->n.pxlen, dest->name); + n->n.prefix, n->n.pxlen, dst_table->name); return; } - nn = net_get(dest, n->n.prefix, n->n.pxlen); + + nn = net_get(dst_table, n->n.prefix, n->n.pxlen); if (new) { memcpy(&a, new->attrs, sizeof(rta)); @@ -85,14 +99,14 @@ pipe_rt_notify(struct proto *P, rtable *src_table, net *n, rte *new, rte *old, e } src_table->pipe_busy = 1; - rte_update(dest, nn, &p->p, (p->mode == PIPE_OPAQUE) ? &p->p : src, e); + rte_update2(ah, nn, e, (p->mode == PIPE_OPAQUE) ? &p->p : src); src_table->pipe_busy = 0; } static int pipe_import_control(struct proto *P, rte **ee, ea_list **ea UNUSED, struct linpool *p UNUSED) { - struct proto *pp = (*ee)->sender; + struct proto *pp = (*ee)->sender->proto; if (pp == P) return -1; /* Avoid local loops automatically */ @@ -112,20 +126,39 @@ pipe_reload_routes(struct proto *P) return 1; } +static struct proto * +pipe_init(struct proto_config *C) +{ + struct pipe_config *c = (struct pipe_config *) C; + struct proto *P = proto_new(C, sizeof(struct pipe_proto)); + struct pipe_proto *p = (struct pipe_proto *) P; + + p->mode = c->mode; + p->peer_table = c->peer->table; + P->accept_ra_types = (p->mode == PIPE_OPAQUE) ? RA_OPTIMAL : RA_ANY; + P->rt_notify = pipe_rt_notify; + P->import_control = pipe_import_control; + P->reload_routes = pipe_reload_routes; + + return P; +} + static int pipe_start(struct proto *P) { struct pipe_proto *p = (struct pipe_proto *) P; - struct announce_hook *a; - /* Clean up the secondary stats */ - bzero(&p->peer_stats, sizeof(struct proto_stats)); + /* Lock both tables, unlock is handled in pipe_cleanup() */ + rt_lock_table(P->table); + rt_lock_table(p->peer_table); - /* Lock the peer table, unlock is handled in pipe_cleanup() */ - rt_lock_table(p->peer); + /* Going directly to PS_UP - prepare for feeding, + connect the protocol to both routing tables */ - /* Connect the protocol also to the peer routing table. */ - a = proto_add_announce_hook(P, p->peer); + P->main_ahook = proto_add_announce_hook(P, P->table, + FILTER_ACCEPT, P->cf->out_filter, &P->stats); + p->peer_ahook = proto_add_announce_hook(P, p->peer_table, + FILTER_ACCEPT, P->cf->in_filter, &p->peer_stats); return PS_UP; } @@ -134,24 +167,15 @@ static void pipe_cleanup(struct proto *P) { struct pipe_proto *p = (struct pipe_proto *) P; - rt_unlock_table(p->peer); -} -static struct proto * -pipe_init(struct proto_config *C) -{ - struct pipe_config *c = (struct pipe_config *) C; - struct proto *P = proto_new(C, sizeof(struct pipe_proto)); - struct pipe_proto *p = (struct pipe_proto *) P; + bzero(&P->stats, sizeof(struct proto_stats)); + bzero(&p->peer_stats, sizeof(struct proto_stats)); - p->peer = c->peer->table; - p->mode = c->mode; - P->accept_ra_types = (p->mode == PIPE_OPAQUE) ? RA_OPTIMAL : RA_ANY; - P->rt_notify = pipe_rt_notify; - P->import_control = pipe_import_control; - P->reload_routes = pipe_reload_routes; + P->main_ahook = NULL; + p->peer_ahook = NULL; - return P; + rt_unlock_table(P->table); + rt_unlock_table(p->peer_table); } static void @@ -165,16 +189,34 @@ pipe_postconfig(struct proto_config *C) cf_error("Primary table and peer table must be different"); } +extern int proto_reconfig_type; + static int pipe_reconfigure(struct proto *P, struct proto_config *new) { - // struct pipe_proto *p = (struct pipe_proto *) P; - struct pipe_config *o = (struct pipe_config *) P->cf; - struct pipe_config *n = (struct pipe_config *) new; + struct pipe_proto *p = (struct pipe_proto *)P; + struct proto_config *old = P->cf; + struct pipe_config *oc = (struct pipe_config *) old; + struct pipe_config *nc = (struct pipe_config *) new; - if ((o->peer->table != n->peer->table) || (o->mode != n->mode)) + if ((oc->peer->table != nc->peer->table) || (oc->mode != nc->mode)) return 0; + /* Update output filters in ahooks */ + if (P->main_ahook) + P->main_ahook->out_filter = new->out_filter; + + if (p->peer_ahook) + p->peer_ahook->out_filter = new->in_filter; + + if ((P->proto_state != PS_UP) || (proto_reconfig_type == RECONFIG_SOFT)) + return 1; + + if ((new->preference != old->preference) + || ! filter_same(new->in_filter, old->in_filter) + || ! filter_same(new->out_filter, old->out_filter)) + proto_request_feeding(P); + return 1; } @@ -190,19 +232,80 @@ pipe_get_status(struct proto *P, byte *buf) { struct pipe_proto *p = (struct pipe_proto *) P; - bsprintf(buf, "%c> %s", (p->mode == PIPE_OPAQUE) ? '-' : '=', p->peer->name); + bsprintf(buf, "%c> %s", (p->mode == PIPE_OPAQUE) ? '-' : '=', p->peer_table->name); +} + +static void +pipe_show_stats(struct pipe_proto *p) +{ + struct proto_stats *s1 = &p->p.stats; + struct proto_stats *s2 = &p->peer_stats; + + /* + * Pipe stats (as anything related to pipes) are a bit tricky. There + * are two sets of stats - s1 for ahook to the primary routing and + * s2 for the ahook to the secondary routing table. The user point + * of view is that routes going from the primary routing table to + * the secondary routing table are 'exported', while routes going in + * the other direction are 'imported'. + * + * Each route going through a pipe is, technically, first exported + * to the pipe and then imported from that pipe and such operations + * are counted in one set of stats according to the direction of the + * route propagation. Filtering is done just in the first part + * (export). Therefore, we compose stats for one directon for one + * user direction from both import and export stats, skipping + * immediate and irrelevant steps (exp_updates_accepted, + * imp_updates_received, imp_updates_filtered, ...). + * + * Rule of thumb is that stats s1 have the correct 'polarity' + * (imp/exp), while stats s2 have switched 'polarity'. + */ + + cli_msg(-1006, " Routes: %u imported, %u exported", + s1->imp_routes, s2->imp_routes); + cli_msg(-1006, " Route change stats: received rejected filtered ignored accepted"); + cli_msg(-1006, " Import updates: %10u %10u %10u %10u %10u", + s2->exp_updates_received, s2->exp_updates_rejected + s1->imp_updates_invalid, + s2->exp_updates_filtered, s1->imp_updates_ignored, s1->imp_updates_accepted); + cli_msg(-1006, " Import withdraws: %10u %10u --- %10u %10u", + s2->exp_withdraws_received, s1->imp_withdraws_invalid, + s1->imp_withdraws_ignored, s1->imp_withdraws_accepted); + cli_msg(-1006, " Export updates: %10u %10u %10u %10u %10u", + s1->exp_updates_received, s1->exp_updates_rejected + s2->imp_updates_invalid, + s1->exp_updates_filtered, s2->imp_updates_ignored, s2->imp_updates_accepted); + cli_msg(-1006, " Export withdraws: %10u %10u --- %10u %10u", + s1->exp_withdraws_received, s2->imp_withdraws_invalid, + s2->imp_withdraws_ignored, s2->imp_withdraws_accepted); +} + +static void +pipe_show_proto_info(struct proto *P) +{ + struct pipe_proto *p = (struct pipe_proto *) P; + + // cli_msg(-1006, " Table: %s", P->table->name); + // cli_msg(-1006, " Peer table: %s", p->peer_table->name); + cli_msg(-1006, " Preference: %d", P->preference); + cli_msg(-1006, " Input filter: %s", filter_name(P->cf->in_filter)); + cli_msg(-1006, " Output filter: %s", filter_name(P->cf->out_filter)); + + if (P->proto_state != PS_DOWN) + pipe_show_stats(p); } struct protocol proto_pipe = { - name: "Pipe", - template: "pipe%d", - preference: DEF_PREF_PIPE, - postconfig: pipe_postconfig, - init: pipe_init, - start: pipe_start, - cleanup: pipe_cleanup, - reconfigure: pipe_reconfigure, - copy_config: pipe_copy_config, - get_status: pipe_get_status, + name: "Pipe", + template: "pipe%d", + multitable: 1, + preference: DEF_PREF_PIPE, + postconfig: pipe_postconfig, + init: pipe_init, + start: pipe_start, + cleanup: pipe_cleanup, + reconfigure: pipe_reconfigure, + copy_config: pipe_copy_config, + get_status: pipe_get_status, + show_proto_info: pipe_show_proto_info }; diff --git a/proto/pipe/pipe.h b/proto/pipe/pipe.h index fbd21291..50b31698 100644 --- a/proto/pipe/pipe.h +++ b/proto/pipe/pipe.h @@ -20,7 +20,8 @@ struct pipe_config { struct pipe_proto { struct proto p; - struct rtable *peer; + struct rtable *peer_table; + struct announce_hook *peer_ahook; /* Announce hook for direction peer->primary */ struct proto_stats peer_stats; /* Statistics for the direction peer->primary */ int mode; /* PIPE_OPAQUE or PIPE_TRANSPARENT */ }; @@ -31,10 +32,4 @@ extern struct protocol proto_pipe; static inline int proto_is_pipe(struct proto *p) { return p->proto == &proto_pipe; } -static inline struct rtable * pipe_get_peer_table(struct proto *P) -{ return ((struct pipe_proto *) P)->peer; } - -static inline struct proto_stats * pipe_get_peer_stats(struct proto *P) -{ return &((struct pipe_proto *) P)->peer_stats; } - #endif