/* * BIRD -- Table-to-Table Routing Protocol a.k.a Pipe * * (c) 1999--2000 Martin Mares * * Can be freely distributed and used under the terms of the GNU GPL. */ /** * DOC: Pipe * * The Pipe protocol is very simple. It just connects to two routing tables * using proto_add_announce_hook() and whenever it receives a rt_notify() * about a change in one of the tables, it converts it to a rte_update() * in the other one. * * 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. Route limits are * handled similarly, but on the import side of ahooks. */ #undef LOCAL_DEBUG #include "nest/bird.h" #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" #include "nest/cbor_shortcuts.h" #include "pipe.h" static void pipe_rt_notify(struct proto *P, struct channel *src_ch, net *n, rte *new, rte *old) { struct pipe_proto *p = (void *) P; struct channel *dst = (src_ch == p->pri) ? p->sec : p->pri; struct rte_src *src; rte *e; rta *a; if (!new && !old) return; if (dst->table->pipe_busy) { log(L_ERR "Pipe loop detected when sending %N to table %s", n->n.addr, dst->table->name); return; } if (new) { src = new->src; a = alloca(rta_size(new->attrs)); memcpy(a, new->attrs, rta_size(new->attrs)); a->cached = 0; a->hostentry = NULL; e = rte_get_temp(a, src); } else { e = NULL; src = old->src; } src_ch->table->pipe_busy = 1; rte_update2(dst, n->n.addr, e, src); src_ch->table->pipe_busy = 0; } static int pipe_preexport(struct channel *C, rte *e) { struct proto *pp = e->sender->proto; if (pp == C->proto) return -1; /* Avoid local loops automatically */ return 0; } static void pipe_reload_routes(struct channel *C) { struct pipe_proto *p = (void *) C->proto; /* Route reload on one channel is just refeed on the other */ channel_request_feeding((C == p->pri) ? p->sec : p->pri); } static void pipe_postconfig(struct proto_config *CF) { struct pipe_config *cf = (void *) CF; struct channel_config *cc = proto_cf_main_channel(CF); if (!cc->table) cf_error("Primary routing table not specified"); if (!cf->peer) cf_error("Secondary routing table not specified"); if (cc->table == cf->peer) cf_error("Primary table and peer table must be different"); if (cc->table->addr_type != cf->peer->addr_type) cf_error("Primary table and peer table must have the same type"); if (cc->rx_limit.action) cf_error("Pipe protocol does not support receive limits"); if (cc->in_keep_filtered) cf_error("Pipe protocol prohibits keeping filtered routes"); cc->debug = cf->c.debug; } static int pipe_configure_channels(struct pipe_proto *p, struct pipe_config *cf) { struct channel_config *cc = proto_cf_main_channel(&cf->c); struct channel_config pri_cf = { .name = "pri", .channel = cc->channel, .table = cc->table, .out_filter = cc->out_filter, .in_limit = cc->in_limit, .ra_mode = RA_ANY, .debug = cc->debug, .rpki_reload = cc->rpki_reload, }; struct channel_config sec_cf = { .name = "sec", .channel = cc->channel, .table = cf->peer, .out_filter = cc->in_filter, .in_limit = cc->out_limit, .ra_mode = RA_ANY, .debug = cc->debug, .rpki_reload = cc->rpki_reload, }; return proto_configure_channel(&p->p, &p->pri, &pri_cf) && proto_configure_channel(&p->p, &p->sec, &sec_cf); } static struct proto * pipe_init(struct proto_config *CF) { struct proto *P = proto_new(CF); struct pipe_proto *p = (void *) P; struct pipe_config *cf = (void *) CF; P->rt_notify = pipe_rt_notify; P->preexport = pipe_preexport; P->reload_routes = pipe_reload_routes; pipe_configure_channels(p, cf); return P; } static int pipe_reconfigure(struct proto *P, struct proto_config *CF) { struct pipe_proto *p = (void *) P; struct pipe_config *cf = (void *) CF; return pipe_configure_channels(p, cf); } static void pipe_copy_config(struct proto_config *dest UNUSED, struct proto_config *src UNUSED) { /* Just a shallow copy, not many items here */ } static void pipe_get_status(struct proto *P, byte *buf) { struct pipe_proto *p = (void *) P; bsprintf(buf, "%s <=> %s", p->pri->table->name, p->sec->table->name); } static void pipe_show_stats(struct pipe_proto *p) { struct proto_stats *s1 = &p->pri->stats; struct proto_stats *s2 = &p->sec->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_stats_cbor(struct cbor_writer *w, struct pipe_proto *p) { struct proto_stats *s1 = &p->pri->stats; struct proto_stats *s2 = &p->sec->stats; cbor_add_string(w, "stats"); cbor_open_block(w); cbor_string_int(w, "imported_routes", s1->imp_routes); cbor_string_int(w, "exported_routes", s2->imp_routes); cbor_add_string(w, "import_updates"); cbor_open_list_with_length(w, 5); cbor_add_int(w, s2->exp_updates_received); cbor_add_int(w, s2->exp_updates_rejected + s1->imp_updates_invalid); cbor_add_int(w, s2->exp_updates_filtered); cbor_add_int(w, s1->imp_updates_ignored); cbor_add_int(w, s1->imp_updates_accepted); cbor_add_string(w, "import_withdraws"); cbor_open_block_with_length(w, 5); cbor_add_int(w, s2->exp_withdraws_received); cbor_add_int(w, s1->imp_withdraws_invalid); cbor_add_int(w, -1); cbor_add_int(w, s1->imp_withdraws_ignored); cbor_add_int(w, s1->imp_withdraws_accepted); cbor_add_string(w, "export_updates"); cbor_open_block_with_length(w, 5); cbor_add_int(w, s1->exp_updates_received); cbor_add_int(w, s1->exp_updates_rejected + s2->imp_updates_invalid); cbor_add_int(w, s1->exp_updates_filtered); cbor_add_int(w, s2->imp_updates_ignored); cbor_add_int(w, s2->imp_updates_accepted); cbor_add_string(w, "export_withdraws"); cbor_open_block_with_length(w, 5); cbor_add_int(w, s1->exp_withdraws_received); cbor_add_int(w, s2->imp_withdraws_invalid); cbor_add_int(w, -1); cbor_add_int(w, s2->imp_withdraws_ignored); cbor_add_int(w, s2->imp_withdraws_accepted); cbor_close_block_or_list(w); } static const char *pipe_feed_state[] = { [ES_DOWN] = "down", [ES_FEEDING] = "feed", [ES_READY] = "up" }; static void pipe_show_proto_info(struct proto *P) { struct pipe_proto *p = (void *) P; cli_msg(-1006, " Channel %s", "main"); cli_msg(-1006, " Table: %s", p->pri->table->name); cli_msg(-1006, " Peer table: %s", p->sec->table->name); cli_msg(-1006, " Import state: %s", pipe_feed_state[p->sec->export_state]); cli_msg(-1006, " Export state: %s", pipe_feed_state[p->pri->export_state]); cli_msg(-1006, " Import filter: %s", filter_name(p->sec->out_filter)); cli_msg(-1006, " Export filter: %s", filter_name(p->pri->out_filter)); channel_show_limit(&p->pri->in_limit, "Import limit:"); channel_show_limit(&p->sec->in_limit, "Export limit:"); if (P->proto_state != PS_DOWN) pipe_show_stats(p); } static void pipe_show_proto_info_cbor(struct cbor_writer *w, struct proto *P) { struct pipe_proto *p = (void *) P; cbor_add_string(w, "pipe"); cbor_open_block(w); cbor_string_string(w, "table", p->pri->table->name); cbor_string_string(w, "peer_table", p->sec->table->name); cbor_string_string(w, "import_state", pipe_feed_state[p->sec->export_state]); cbor_string_string(w, "export_state", pipe_feed_state[p->pri->export_state]); cbor_string_string(w, "import_filter", filter_name(p->sec->out_filter)); cbor_string_string(w, "export_filter", filter_name(p->pri->out_filter)); channel_show_limit_cbor(w, &p->pri->in_limit, "import_limit"); channel_show_limit_cbor(w, &p->sec->in_limit, "export_limit"); if (P->proto_state != PS_DOWN) pipe_show_stats_cbor(w, p); cbor_close_block_or_list(w); } void pipe_update_debug(struct proto *P) { struct pipe_proto *p = (void *) P; p->pri->debug = p->sec->debug = p->p.debug; } struct protocol proto_pipe = { .name = "Pipe", .template = "pipe%d", .class = PROTOCOL_PIPE, .proto_size = sizeof(struct pipe_proto), .config_size = sizeof(struct pipe_config), .postconfig = pipe_postconfig, .init = pipe_init, .reconfigure = pipe_reconfigure, .copy_config = pipe_copy_config, .get_status = pipe_get_status, .show_proto_info = pipe_show_proto_info, .show_proto_info_cbor = pipe_show_proto_info_cbor }; void pipe_build(void) { proto_build(&proto_pipe); }