2017-05-15 18:10:51 +08:00
|
|
|
/*
|
|
|
|
* BIRD -- Route Display Routines
|
|
|
|
*
|
|
|
|
* (c) 1998--2000 Martin Mares <mj@ucw.cz>
|
|
|
|
* (c) 2017 Jan Moskyto Matejka <mq@jmq.cz>
|
|
|
|
*
|
|
|
|
* Can be freely distributed and used under the terms of the GNU GPL.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#undef LOCAL_DEBUG
|
|
|
|
|
|
|
|
#include "nest/bird.h"
|
|
|
|
#include "nest/route.h"
|
|
|
|
#include "nest/protocol.h"
|
|
|
|
#include "nest/cli.h"
|
|
|
|
#include "nest/iface.h"
|
|
|
|
#include "filter/filter.h"
|
2019-12-19 23:34:35 +08:00
|
|
|
#include "sysdep/unix/krt.h"
|
2017-05-15 18:10:51 +08:00
|
|
|
|
|
|
|
static void
|
|
|
|
rt_show_table(struct cli *c, struct rt_show_data *d)
|
|
|
|
{
|
|
|
|
/* No table blocks in 'show route count' */
|
|
|
|
if (d->stats == 2)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (d->last_table) cli_printf(c, -1007, "");
|
|
|
|
cli_printf(c, -1007, "Table %s:", d->tab->table->name);
|
|
|
|
d->last_table = d->tab;
|
|
|
|
}
|
|
|
|
|
2019-12-19 23:34:35 +08:00
|
|
|
static inline struct krt_proto *
|
|
|
|
rt_show_get_kernel(struct rt_show_data *d)
|
|
|
|
{
|
|
|
|
struct proto_config *krt = d->tab->table->config->krt_attached;
|
|
|
|
return krt ? (struct krt_proto *) krt->proto : NULL;
|
|
|
|
}
|
|
|
|
|
2017-05-15 18:10:51 +08:00
|
|
|
static void
|
2019-10-25 19:28:51 +08:00
|
|
|
rt_show_rte(struct cli *c, byte *ia, rte *e, struct rt_show_data *d, int primary)
|
2017-05-15 18:10:51 +08:00
|
|
|
{
|
|
|
|
byte from[IPA_MAX_TEXT_LENGTH+8];
|
|
|
|
byte tm[TM_DATETIME_BUFFER_SIZE], info[256];
|
|
|
|
rta *a = e->attrs;
|
2019-12-19 23:34:35 +08:00
|
|
|
int sync_error = d->kernel ? krt_get_sync_error(d->kernel, e) : 0;
|
2018-05-29 18:08:12 +08:00
|
|
|
void (*get_route_info)(struct rte *, byte *buf);
|
2017-05-15 18:10:51 +08:00
|
|
|
struct nexthop *nh;
|
|
|
|
|
2017-06-06 22:47:30 +08:00
|
|
|
tm_format_time(tm, &config->tf_route, e->lastmod);
|
2017-05-15 18:10:51 +08:00
|
|
|
if (ipa_nonzero(a->from) && !ipa_equal(a->from, a->nh.gw))
|
|
|
|
bsprintf(from, " from %I", a->from);
|
|
|
|
else
|
|
|
|
from[0] = 0;
|
|
|
|
|
2018-05-29 18:08:12 +08:00
|
|
|
/* Need to normalize the extended attributes */
|
2019-03-15 00:22:22 +08:00
|
|
|
if (d->verbose && !rta_is_cached(a) && a->eattrs)
|
2018-05-29 18:08:12 +08:00
|
|
|
ea_normalize(a->eattrs);
|
2019-03-15 00:22:22 +08:00
|
|
|
|
|
|
|
get_route_info = a->src->proto->proto->get_route_info;
|
2017-05-15 18:10:51 +08:00
|
|
|
if (get_route_info)
|
2018-05-29 18:08:12 +08:00
|
|
|
get_route_info(e, info);
|
2017-05-15 18:10:51 +08:00
|
|
|
else
|
|
|
|
bsprintf(info, " (%d)", e->pref);
|
|
|
|
|
|
|
|
if (d->last_table != d->tab)
|
|
|
|
rt_show_table(c, d);
|
|
|
|
|
2017-12-08 22:16:47 +08:00
|
|
|
cli_printf(c, -1007, "%-20s %s [%s %s%s]%s%s", ia, rta_dest_name(a->dest),
|
2017-05-15 18:10:51 +08:00
|
|
|
a->src->proto->name, tm, from, primary ? (sync_error ? " !" : " *") : "", info);
|
|
|
|
|
|
|
|
if (a->dest == RTD_UNICAST)
|
|
|
|
for (nh = &(a->nh); nh; nh = nh->next)
|
|
|
|
{
|
|
|
|
char mpls[MPLS_MAX_LABEL_STACK*12 + 5], *lsp = mpls;
|
2017-07-05 05:36:21 +08:00
|
|
|
char *onlink = (nh->flags & RNF_ONLINK) ? " onlink" : "";
|
2017-12-08 22:16:47 +08:00
|
|
|
char weight[16] = "";
|
2017-05-15 18:10:51 +08:00
|
|
|
|
|
|
|
if (nh->labels)
|
2018-02-07 00:43:55 +08:00
|
|
|
{
|
2017-05-15 18:10:51 +08:00
|
|
|
lsp += bsprintf(lsp, " mpls %d", nh->label[0]);
|
|
|
|
for (int i=1;i<nh->labels; i++)
|
|
|
|
lsp += bsprintf(lsp, "/%d", nh->label[i]);
|
|
|
|
}
|
|
|
|
*lsp = '\0';
|
|
|
|
|
|
|
|
if (a->nh.next)
|
2017-12-08 22:16:47 +08:00
|
|
|
bsprintf(weight, " weight %d", nh->weight + 1);
|
|
|
|
|
|
|
|
if (ipa_nonzero(nh->gw))
|
|
|
|
cli_printf(c, -1007, "\tvia %I on %s%s%s%s",
|
|
|
|
nh->gw, nh->iface->name, mpls, onlink, weight);
|
2017-05-15 18:10:51 +08:00
|
|
|
else
|
2017-12-08 22:16:47 +08:00
|
|
|
cli_printf(c, -1007, "\tdev %s%s%s",
|
|
|
|
nh->iface->name, mpls, onlink, weight);
|
2017-05-15 18:10:51 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (d->verbose)
|
2018-05-29 18:08:12 +08:00
|
|
|
rta_show(c, a);
|
2017-05-15 18:10:51 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
rt_show_net(struct cli *c, net *n, struct rt_show_data *d)
|
|
|
|
{
|
|
|
|
rte *e, *ee;
|
|
|
|
byte ia[NET_MAX_TEXT_LENGTH+1];
|
|
|
|
struct channel *ec = d->tab->export_channel;
|
|
|
|
int first = 1;
|
|
|
|
int pass = 0;
|
|
|
|
|
|
|
|
bsnprintf(ia, sizeof(ia), "%N", n->n.addr);
|
|
|
|
|
|
|
|
for (e = n->routes; e; e = e->next)
|
|
|
|
{
|
|
|
|
if (rte_is_filtered(e) != d->filtered)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
d->rt_counter++;
|
|
|
|
d->net_counter += first;
|
|
|
|
first = 0;
|
|
|
|
|
|
|
|
if (pass)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
ee = e;
|
2019-03-15 00:22:22 +08:00
|
|
|
rte_make_tmp_attrs(&e, c->show_pool, NULL);
|
2017-05-15 18:10:51 +08:00
|
|
|
|
|
|
|
/* Export channel is down, do not try to export routes to it */
|
|
|
|
if (ec && (ec->export_state == ES_DOWN))
|
|
|
|
goto skip;
|
|
|
|
|
2019-09-09 08:55:32 +08:00
|
|
|
if (d->export_mode == RSEM_EXPORTED)
|
|
|
|
{
|
|
|
|
if (!bmap_test(&ec->export_map, ee->id))
|
|
|
|
goto skip;
|
|
|
|
|
|
|
|
// if (ec->ra_mode != RA_ANY)
|
|
|
|
// pass = 1;
|
|
|
|
}
|
|
|
|
else if ((d->export_mode == RSEM_EXPORT) && (ec->ra_mode == RA_MERGED))
|
2018-02-07 00:43:55 +08:00
|
|
|
{
|
2019-09-09 08:55:32 +08:00
|
|
|
/* Special case for merged export */
|
2017-05-15 18:10:51 +08:00
|
|
|
rte *rt_free;
|
2018-05-29 18:08:12 +08:00
|
|
|
e = rt_export_merged(ec, n, &rt_free, c->show_pool, 1);
|
2017-05-15 18:10:51 +08:00
|
|
|
pass = 1;
|
|
|
|
|
|
|
|
if (!e)
|
|
|
|
{ e = ee; goto skip; }
|
|
|
|
}
|
|
|
|
else if (d->export_mode)
|
|
|
|
{
|
|
|
|
struct proto *ep = ec->proto;
|
Terminology cleanup: The import_control hook is now called preexport.
Once upon a time, far far away, there were the old Bird developers
discussing what direction of route flow shall be called import and
export. They decided to say "import to protocol" and "export to table"
when speaking about a protocol. When speaking about a table, they
spoke about "importing to table" and "exporting to protocol".
The latter terminology was adopted in configuration, then also the
bird CLI in commit ea2ae6dd0 started to use it (in year 2009). Now
it's 2018 and the terminology is the latter. Import is from protocol to
table, export is from table to protocol. Anyway, there was still an
import_control hook which executed right before route export.
One thing is funny. There are two commits in April 1999 with just two
minutes between them. The older announces the final settlement
on config terminology, the newer uses the other definition. Let's see
their commit messages as the git-log tool shows them (the newer first):
commit 9e0e485e50ea74c4f1c5cb65bdfe6ce819c2cee2
Author: Martin Mares <mj@ucw.cz>
Date: Mon Apr 5 20:17:59 1999 +0000
Added some new protocol hooks (look at the comments for better explanation):
make_tmp_attrs Convert inline attributes to ea_list
store_tmp_attrs Convert ea_list to inline attributes
import_control Pre-import decisions
commit 5056c559c4eb253a4eee10cf35b694faec5265eb
Author: Martin Mares <mj@ucw.cz>
Date: Mon Apr 5 20:15:31 1999 +0000
Changed syntax of attaching filters to protocols to hopefully the final
version:
EXPORT <filter-spec> for outbound routes (i.e., those announced
by BIRD to the rest of the world).
IMPORT <filter-spec> for inbound routes (i.e., those imported
by BIRD from the rest of the world).
where <filter-spec> is one of:
ALL pass all routes
NONE drop all routes
FILTER <name> use named filter
FILTER { <filter> } use explicitly defined filter
For all protocols, the default is IMPORT ALL, EXPORT NONE. This includes
the kernel protocol, so that you need to add EXPORT ALL to get the previous
configuration of kernel syncer (as usually, see doc/bird.conf.example for
a bird.conf example :)).
Let's say RIP to this almost 19-years-old inconsistency. For now, if you
import a route, it is always from protocol to table. If you export a
route, it is always from table to protocol.
And they lived happily ever after.
2018-02-14 20:42:53 +08:00
|
|
|
int ic = ep->preexport ? ep->preexport(ep, &e, c->show_pool) : 0;
|
2017-05-15 18:10:51 +08:00
|
|
|
|
|
|
|
if (ec->ra_mode == RA_OPTIMAL || ec->ra_mode == RA_MERGED)
|
|
|
|
pass = 1;
|
|
|
|
|
|
|
|
if (ic < 0)
|
|
|
|
goto skip;
|
|
|
|
|
|
|
|
if (d->export_mode > RSEM_PREEXPORT)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* FIXME - This shows what should be exported according to current
|
|
|
|
* filters, but not what was really exported. 'configure soft'
|
|
|
|
* command may change the export filter and do not update routes.
|
|
|
|
*/
|
|
|
|
int do_export = (ic > 0) ||
|
2018-05-29 18:08:12 +08:00
|
|
|
(f_run(ec->out_filter, &e, c->show_pool, FF_SILENT) <= F_ACCEPT);
|
2017-05-15 18:10:51 +08:00
|
|
|
|
|
|
|
if (do_export != (d->export_mode == RSEM_EXPORT))
|
|
|
|
goto skip;
|
|
|
|
|
|
|
|
if ((d->export_mode == RSEM_EXPORT) && (ec->ra_mode == RA_ACCEPTED))
|
|
|
|
pass = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (d->show_protocol && (d->show_protocol != e->attrs->src->proto))
|
|
|
|
goto skip;
|
|
|
|
|
2018-05-29 18:08:12 +08:00
|
|
|
if (f_run(d->filter, &e, c->show_pool, 0) > F_ACCEPT)
|
2017-05-15 18:10:51 +08:00
|
|
|
goto skip;
|
|
|
|
|
|
|
|
if (d->stats < 2)
|
2019-10-25 19:28:51 +08:00
|
|
|
rt_show_rte(c, ia, e, d, (e->net->routes == ee));
|
2017-05-15 18:10:51 +08:00
|
|
|
|
|
|
|
d->show_counter++;
|
|
|
|
ia[0] = 0;
|
|
|
|
|
|
|
|
skip:
|
|
|
|
if (e != ee)
|
|
|
|
{
|
|
|
|
rte_free(e);
|
|
|
|
e = ee;
|
|
|
|
}
|
|
|
|
lp_flush(c->show_pool);
|
|
|
|
|
|
|
|
if (d->primary_only)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
rt_show_cleanup(struct cli *c)
|
|
|
|
{
|
|
|
|
struct rt_show_data *d = c->rover;
|
|
|
|
struct rt_show_data_rtable *tab;
|
|
|
|
|
|
|
|
/* Unlink the iterator */
|
|
|
|
if (d->table_open)
|
|
|
|
fit_get(&d->tab->table->fib, &d->fit);
|
|
|
|
|
|
|
|
/* Unlock referenced tables */
|
|
|
|
WALK_LIST(tab, d->tables)
|
|
|
|
rt_unlock_table(tab->table);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
rt_show_cont(struct cli *c)
|
|
|
|
{
|
|
|
|
struct rt_show_data *d = c->rover;
|
|
|
|
#ifdef DEBUGGING
|
|
|
|
unsigned max = 4;
|
|
|
|
#else
|
|
|
|
unsigned max = 64;
|
|
|
|
#endif
|
|
|
|
struct fib *fib = &d->tab->table->fib;
|
|
|
|
struct fib_iterator *it = &d->fit;
|
|
|
|
|
|
|
|
if (d->running_on_config && (d->running_on_config != config))
|
|
|
|
{
|
|
|
|
cli_printf(c, 8004, "Stopped due to reconfiguration");
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!d->table_open)
|
|
|
|
{
|
|
|
|
FIB_ITERATE_INIT(&d->fit, &d->tab->table->fib);
|
|
|
|
d->table_open = 1;
|
|
|
|
d->table_counter++;
|
2019-12-19 23:34:35 +08:00
|
|
|
d->kernel = rt_show_get_kernel(d);
|
2017-05-15 18:10:51 +08:00
|
|
|
|
|
|
|
d->show_counter_last = d->show_counter;
|
|
|
|
d->rt_counter_last = d->rt_counter;
|
|
|
|
d->net_counter_last = d->net_counter;
|
|
|
|
|
|
|
|
if (d->tables_defined_by & RSD_TDB_SET)
|
|
|
|
rt_show_table(c, d);
|
|
|
|
}
|
|
|
|
|
|
|
|
FIB_ITERATE_START(fib, it, net, n)
|
|
|
|
{
|
|
|
|
if (!max--)
|
|
|
|
{
|
|
|
|
FIB_ITERATE_PUT(it);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
rt_show_net(c, n, d);
|
|
|
|
}
|
|
|
|
FIB_ITERATE_END;
|
|
|
|
|
|
|
|
if (d->stats)
|
|
|
|
{
|
|
|
|
if (d->last_table != d->tab)
|
|
|
|
rt_show_table(c, d);
|
|
|
|
|
|
|
|
cli_printf(c, -1007, "%d of %d routes for %d networks in table %s",
|
|
|
|
d->show_counter - d->show_counter_last, d->rt_counter - d->rt_counter_last,
|
|
|
|
d->net_counter - d->net_counter_last, d->tab->table->name);
|
|
|
|
}
|
|
|
|
|
2019-12-19 23:34:35 +08:00
|
|
|
d->kernel = NULL;
|
2017-05-15 18:10:51 +08:00
|
|
|
d->table_open = 0;
|
|
|
|
d->tab = NODE_NEXT(d->tab);
|
|
|
|
|
|
|
|
if (NODE_VALID(d->tab))
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (d->stats && (d->table_counter > 1))
|
|
|
|
{
|
|
|
|
if (d->last_table) cli_printf(c, -1007, "");
|
|
|
|
cli_printf(c, 14, "Total: %d of %d routes for %d networks in %d tables",
|
|
|
|
d->show_counter, d->rt_counter, d->net_counter, d->table_counter);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
cli_printf(c, 0, "");
|
|
|
|
|
|
|
|
done:
|
|
|
|
rt_show_cleanup(c);
|
|
|
|
c->cont = c->cleanup = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct rt_show_data_rtable *
|
|
|
|
rt_show_add_table(struct rt_show_data *d, rtable *t)
|
|
|
|
{
|
|
|
|
struct rt_show_data_rtable *tab = cfg_allocz(sizeof(struct rt_show_data_rtable));
|
|
|
|
tab->table = t;
|
|
|
|
add_tail(&(d->tables), &(tab->n));
|
|
|
|
return tab;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void
|
|
|
|
rt_show_get_default_tables(struct rt_show_data *d)
|
|
|
|
{
|
|
|
|
struct channel *c;
|
|
|
|
struct rt_show_data_rtable *tab;
|
|
|
|
|
|
|
|
if (d->export_channel)
|
|
|
|
{
|
|
|
|
c = d->export_channel;
|
|
|
|
tab = rt_show_add_table(d, c->table);
|
|
|
|
tab->export_channel = c;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (d->export_protocol)
|
|
|
|
{
|
|
|
|
WALK_LIST(c, d->export_protocol->channels)
|
|
|
|
{
|
|
|
|
if (c->export_state == ES_DOWN)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
tab = rt_show_add_table(d, c->table);
|
|
|
|
tab->export_channel = c;
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (d->show_protocol)
|
|
|
|
{
|
|
|
|
WALK_LIST(c, d->show_protocol->channels)
|
|
|
|
rt_show_add_table(d, c->table);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int i=1; i<NET_MAX; i++)
|
|
|
|
if (config->def_tables[i])
|
|
|
|
rt_show_add_table(d, config->def_tables[i]->table);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void
|
|
|
|
rt_show_prepare_tables(struct rt_show_data *d)
|
|
|
|
{
|
|
|
|
struct rt_show_data_rtable *tab, *tabx;
|
|
|
|
|
|
|
|
/* Add implicit tables if no table is specified */
|
|
|
|
if (EMPTY_LIST(d->tables))
|
|
|
|
rt_show_get_default_tables(d);
|
|
|
|
|
|
|
|
WALK_LIST_DELSAFE(tab, tabx, d->tables)
|
|
|
|
{
|
|
|
|
/* Ensure there is defined export_channel for each table */
|
|
|
|
if (d->export_mode)
|
|
|
|
{
|
|
|
|
if (!tab->export_channel && d->export_channel &&
|
|
|
|
(tab->table == d->export_channel->table))
|
|
|
|
tab->export_channel = d->export_channel;
|
|
|
|
|
|
|
|
if (!tab->export_channel && d->export_protocol)
|
|
|
|
tab->export_channel = proto_find_channel_by_table(d->export_protocol, tab->table);
|
|
|
|
|
|
|
|
if (!tab->export_channel)
|
|
|
|
{
|
|
|
|
if (d->tables_defined_by & RSD_TDB_NMN)
|
|
|
|
cf_error("No export channel for table %s", tab->table->name);
|
|
|
|
|
|
|
|
rem_node(&(tab->n));
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Ensure specified network is compatible with each table */
|
|
|
|
if (d->addr && (tab->table->addr_type != d->addr->type))
|
|
|
|
{
|
|
|
|
if (d->tables_defined_by & RSD_TDB_NMN)
|
|
|
|
cf_error("Incompatible type of prefix/ip for table %s", tab->table->name);
|
|
|
|
|
|
|
|
rem_node(&(tab->n));
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Ensure there is at least one table */
|
|
|
|
if (EMPTY_LIST(d->tables))
|
|
|
|
cf_error("No valid tables");
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
rt_show(struct rt_show_data *d)
|
|
|
|
{
|
|
|
|
struct rt_show_data_rtable *tab;
|
|
|
|
net *n;
|
|
|
|
|
|
|
|
/* Filtered routes are neither exported nor have sensible ordering */
|
|
|
|
if (d->filtered && (d->export_mode || d->primary_only))
|
|
|
|
cf_error("Incompatible show route options");
|
|
|
|
|
|
|
|
rt_show_prepare_tables(d);
|
|
|
|
|
|
|
|
if (!d->addr)
|
|
|
|
{
|
|
|
|
WALK_LIST(tab, d->tables)
|
|
|
|
rt_lock_table(tab->table);
|
|
|
|
|
|
|
|
/* There is at least one table */
|
|
|
|
d->tab = HEAD(d->tables);
|
|
|
|
this_cli->cont = rt_show_cont;
|
|
|
|
this_cli->cleanup = rt_show_cleanup;
|
|
|
|
this_cli->rover = d;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
WALK_LIST(tab, d->tables)
|
|
|
|
{
|
|
|
|
d->tab = tab;
|
2019-12-19 23:34:35 +08:00
|
|
|
d->kernel = rt_show_get_kernel(d);
|
2017-05-15 18:10:51 +08:00
|
|
|
|
|
|
|
if (d->show_for)
|
|
|
|
n = net_route(tab->table, d->addr);
|
|
|
|
else
|
|
|
|
n = net_find(tab->table, d->addr);
|
|
|
|
|
|
|
|
if (n)
|
|
|
|
rt_show_net(this_cli, n, d);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (d->rt_counter)
|
|
|
|
cli_msg(0, "");
|
|
|
|
else
|
|
|
|
cli_msg(8001, "Network not found");
|
|
|
|
}
|
|
|
|
}
|