From: Vojtech Vilimek Date: Wed, 13 Jul 2022 13:39:14 +0000 (+0200) Subject: Initial commit for new protocol `stats' X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=a0edbf88e4ef0d30b064ae2efbcea238d9c9ae50;p=thirdparty%2Fbird.git Initial commit for new protocol `stats' The protocol was created as a copy of pipe protocol. Many things left unchanged. --- diff --git a/configure.ac b/configure.ac index 321bed95a..87f0fe75c 100644 --- a/configure.ac +++ b/configure.ac @@ -304,7 +304,7 @@ if test "$enable_mpls_kernel" != no ; then fi fi -all_protocols="$proto_bfd babel bgp mrt ospf perf pipe radv rip rpki static" +all_protocols="$proto_bfd babel bgp mrt ospf perf pipe radv rip rpki static stats" all_protocols=`echo $all_protocols | sed 's/ /,/g'` diff --git a/nest/protocol.h b/nest/protocol.h index 026d42ab9..d4c38b907 100644 --- a/nest/protocol.h +++ b/nest/protocol.h @@ -84,7 +84,8 @@ void protos_dump_all(void); extern struct protocol proto_device, proto_radv, proto_rip, proto_static, proto_mrt, proto_ospf, proto_perf, - proto_pipe, proto_bgp, proto_bfd, proto_babel, proto_rpki; + proto_pipe, proto_bgp, proto_bfd, proto_babel, proto_rpki, + proto_stats; /* * Routing Protocol Instance diff --git a/proto/Doc b/proto/Doc index ef573d2af..f5eb5b35e 100644 --- a/proto/Doc +++ b/proto/Doc @@ -7,5 +7,6 @@ C pipe C radv C rip C rpki +C stats C static S ../nest/rt-dev.c diff --git a/proto/stats/Doc b/proto/stats/Doc new file mode 100644 index 000000000..cb890993e --- /dev/null +++ b/proto/stats/Doc @@ -0,0 +1 @@ +S stats.c diff --git a/proto/stats/Makefile b/proto/stats/Makefile new file mode 100644 index 000000000..19e972c96 --- /dev/null +++ b/proto/stats/Makefile @@ -0,0 +1,7 @@ +src := stats.c +obj := $(src-o-files) +$(all-daemon) +$(cf-local) +$(call proto-build,stats_build) + +tests_objs := $(tests_objs) $(src-o-files) diff --git a/proto/stats/config.Y b/proto/stats/config.Y new file mode 100644 index 000000000..cf9a9066c --- /dev/null +++ b/proto/stats/config.Y @@ -0,0 +1,63 @@ +/* + * BIRD -- Table-to-Table Protocol Configuration + * + * (c) 1999 Martin Mares + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +CF_HDR + +/* old: #include "proto/pipe/pipe.h" */ +#include "proto/stats/stats.h" + +CF_DEFINES + +/* old: #define PIPE_CFG ((struct pipe_config *) this_proto) */ +#define STATS_CFG ((struct stats_config *) this_proto) + +CF_DECLS + +/* TODO here add more keywords */ +/* old: CF_KEYWORDS(PIPE, PEER, TABLE, MAX, GENERATION) */ +CF_KEYWORDS(STATS, PEER, TABLE, MAX, GENERATION) + +CF_GRAMMAR + +proto: stats_proto '}' { this_channel = NULL; } ; + +stats_proto_start: proto_start STATS +{ + this_proto = proto_config_new(&proto_stats, $1); + STATS_CFG->max_generation = 16; +} +proto_name +{ + this_channel = proto_cf_main_channel(this_proto); + if (!this_channel) { + this_channel = channel_config_new(NULL, NULL, 0, this_proto); + this_channel->in_filter = FILTER_ACCEPT; + this_channel->out_filter = FILTER_ACCEPT; + } +}; + +stats_proto: + stats_proto_start '{' + | stats_proto proto_item ';' + | stats_proto channel_item_ ';' + | stats_proto IMPORT IN net_any imexport ';' { + if (this_channel->net_type && ($4->type != this_channel->net_type)) + cf_error("Incompatible export prefilter type"); + STATS_CFG->in_subprefix = $4; + this_channel->in_filter = $5; + } + | stats_proto PEER TABLE rtable ';' { STATS_CFG->peer = $4; } + | stats_proto MAX GENERATION expr ';' { + if (($4 < 1) || ($4 > 254)) cf_error("Max generation must be in range 1..254, got %u", $4); + STATS_CFG->max_generation = $4; + } + ; + +CF_CODE + +CF_END diff --git a/proto/stats/stats.c b/proto/stats/stats.c new file mode 100644 index 000000000..c4af70f1b --- /dev/null +++ b/proto/stats/stats.c @@ -0,0 +1,320 @@ +/* + * 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/rt.h" +#include "nest/cli.h" +#include "conf/conf.h" +#include "filter/filter.h" +#include "lib/string.h" + +#include "stats.h" + +#ifdef CONFIG_BGP +#include "proto/bgp/bgp.h" +#endif + +static void +stats_rt_notify(struct proto *P, struct channel *src_ch, const net_addr *n, rte *new, const rte *old) +{ + struct stats_proto *p = (void *) P; + struct channel *dst = (src_ch == p->pri) ? p->sec : p->pri; + + if (!new && !old) + return; + + if (new) + { + rte e0 = { + .attrs = new->attrs, + .src = new->src, + .generation = new->generation + 1, + }; + + ea_unset_attr(&e0.attrs, 0, &ea_gen_hostentry); + + rte_update(dst, n, &e0, new->src); + } + else + rte_update(dst, n, NULL, old->src); +} + +static int +stats_preexport(struct channel *c, rte *e) +{ + struct stats_proto *p = (void *) c->proto; + + /* Avoid direct loopbacks */ + if (e->sender == c->in_req.hook) + return -1; + + /* Indirection check */ + uint max_generation = ((struct stats_config *) p->p.cf)->max_generation; + if (e->generation >= max_generation) + { + log_rl(&p->rl_gen, L_ERR "Route overpiped (%u hops of %u configured in %s) in table %s: %N %s/%u:%u", + e->generation, max_generation, c->proto->name, + c->table->name, e->net, e->src->proto->name, e->src->private_id, e->src->global_id); + + return -1; + } + + return 0; +} + +static void +stats_reload_routes(struct channel *C) +{ + struct stats_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 +stats_postconfig(struct proto_config *CF) +{ + struct stats_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->out_subprefix && (cc->table->addr_type != cc->out_subprefix->type)) + cf_error("Export subprefix must match table type"); + + if (cf->in_subprefix && (cc->table->addr_type != cf->in_subprefix->type)) + cf_error("Import subprefix must match table type"); + + if (cc->rx_limit.action) + cf_error("Pipe protocol does not support receive limits"); + + if (cc->in_keep) + cf_error("Pipe protocol prohibits keeping filtered routes"); + + cc->debug = cf->c.debug; +} + +static int +stats_configure_channels(struct stats_proto *s, struct stats_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, + .out_subprefix = cc->out_subprefix, + .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, + .out_subprefix = cf->in_subprefix, + .in_limit = cc->out_limit, + .ra_mode = RA_ANY, + .debug = cc->debug, + .rpki_reload = cc->rpki_reload, + }; + + return + proto_configure_channel(&s->p, &s->pri, &pri_cf) && + proto_configure_channel(&s->p, &s->sec, &sec_cf); +} + +static struct proto * +stats_init(struct proto_config *CF) +{ + struct proto *P = proto_new(CF); + struct stats_proto *p = (void *) P; + struct stats_config *cf = (void *) CF; + + P->rt_notify = stats_rt_notify; + P->preexport = stats_preexport; + P->reload_routes = stats_reload_routes; + + p->rl_gen = (struct tbf) TBF_DEFAULT_LOG_LIMITS; + + stats_configure_channels(p, cf); + + return P; +} + +static int +stats_reconfigure(struct proto *P, struct proto_config *CF) +{ + struct stats_proto *p = (void *) P; + struct stats_config *cf = (void *) CF; + + return stats_configure_channels(p, cf); +} + +static void +stats_copy_config(struct proto_config *dest UNUSED, struct proto_config *src UNUSED) +{ + /* Just a shallow copy, not many items here */ +} + +static void +stats_get_status(struct proto *P, byte *buf) +{ + struct stats_proto *p = (void *) P; + + bsprintf(buf, "%s <=> %s", p->pri->table->name, p->sec->table->name); +} + +static void +stats_show_stats(struct stats_proto *p) +{ + struct channel_import_stats *s1i = &p->pri->import_stats; + struct channel_export_stats *s1e = &p->pri->export_stats; + struct channel_import_stats *s2i = &p->sec->import_stats; + struct channel_export_stats *s2e = &p->sec->export_stats; + + struct rt_import_stats *rs1i = p->pri->in_req.hook ? &p->pri->in_req.hook->stats : NULL; + struct rt_export_stats *rs1e = p->pri->out_req.hook ? &p->pri->out_req.hook->stats : NULL; + struct rt_import_stats *rs2i = p->sec->in_req.hook ? &p->sec->in_req.hook->stats : NULL; + struct rt_export_stats *rs2e = p->sec->out_req.hook ? &p->sec->out_req.hook->stats : NULL; + + u32 pri_routes = p->pri->in_limit.count; + u32 sec_routes = p->sec->in_limit.count; + + /* + * 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", + pri_routes, sec_routes); + cli_msg(-1006, " Route change stats: received rejected filtered ignored accepted"); + cli_msg(-1006, " Import updates: %10u %10u %10u %10u %10u", + rs2e->updates_received, s2e->updates_rejected + s1i->updates_invalid, + s2e->updates_filtered, rs1i->updates_ignored, rs1i->updates_accepted); + cli_msg(-1006, " Import withdraws: %10u %10u --- %10u %10u", + rs2e->withdraws_received, s1i->withdraws_invalid, + rs1i->withdraws_ignored, rs1i->withdraws_accepted); + cli_msg(-1006, " Export updates: %10u %10u %10u %10u %10u", + rs1e->updates_received, s1e->updates_rejected + s2i->updates_invalid, + s1e->updates_filtered, rs2i->updates_ignored, rs2i->updates_accepted); + cli_msg(-1006, " Export withdraws: %10u %10u --- %10u %10u", + rs1e->withdraws_received, s2i->withdraws_invalid, + rs2i->withdraws_ignored, rs2i->withdraws_accepted); +} + +static void +stats_show_proto_info(struct proto *P) +{ + struct stats_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", rt_export_state_name(rt_export_get_state(p->sec->out_req.hook))); + cli_msg(-1006, " Export state: %s", rt_export_state_name(rt_export_get_state(p->pri->out_req.hook))); + 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:", + (p->pri->limit_active & (1 << PLD_IN)), p->pri->limit_actions[PLD_IN]); + channel_show_limit(&p->sec->in_limit, "Export limit:", + (p->sec->limit_active & (1 << PLD_IN)), p->sec->limit_actions[PLD_IN]); + + if (P->proto_state != PS_DOWN) + stats_show_stats(p); +} + +void +stats_update_debug(struct proto *P) +{ + struct stats_proto *p = (void *) P; + + p->pri->debug = p->sec->debug = p->p.debug; +} + + +struct protocol proto_stats = { + .name = "Stats", + .template = "stat%d", + .proto_size = sizeof(struct stats_proto), + .config_size = sizeof(struct stats_config), + .postconfig = stats_postconfig, + .init = stats_init, + .reconfigure = stats_reconfigure, + .copy_config = stats_copy_config, + .get_status = stats_get_status, + .show_proto_info = stats_show_proto_info +}; + +void +stats_build(void) +{ + proto_build(&proto_stats); +} diff --git a/proto/stats/stats.h b/proto/stats/stats.h new file mode 100644 index 000000000..2634e51a2 --- /dev/null +++ b/proto/stats/stats.h @@ -0,0 +1,26 @@ +/* + * BIRD -- Table-to-Table Routing Protocol a.k.a Pipe + * + * (c) 1999 Martin Mares + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +#ifndef _BIRD_STATS_H_ +#define _BIRD_STATS_H_ + +struct stats_config { + struct proto_config c; + struct rtable_config *peer; /* Table we're connected to */ + const net_addr *in_subprefix; + u8 max_generation; +}; + +struct stats_proto { + struct proto p; + struct channel *pri; + struct channel *sec; + struct tbf rl_gen; +}; + +#endif