mirror of
https://gitlab.nic.cz/labs/bird.git
synced 2024-11-13 22:58:42 +00:00
333ddd4f98
The MPLS subsystem manages MPLS labels and handles their allocation to MPLS-aware routing protocols. These labels are then attached to IP or VPN routes representing label switched paths -- LSPs. There was already a preliminary MPLS support consisting of MPLS label net_addr, MPLS routing tables with static MPLS routes, remote labels in next hops, and kernel protocol support. This patch adds the MPLS domain as a basic structure representing local label space with dynamic label allocator and configurable label ranges. To represent LSPs, allocated local labels can be attached as route attributes to IP or VPN routes with local labels as attributes. There are several steps for handling LSP routes in routing protocols -- deciding to which forwarding equivalence class (FEC) the LSP route belongs, allocating labels for new FECs, announcing MPLS routes for new FECs, attaching labels to LSP routes. The FEC map structure implements basic code for managing FECs in routing protocols, therefore existing protocols can be made MPLS-aware by adding FEC map and delegating most work related to local label management to it.
500 lines
11 KiB
C
500 lines
11 KiB
C
/*
|
|
* 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"
|
|
#include "filter/data.h"
|
|
#include "sysdep/unix/krt.h"
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
static void
|
|
rt_show_rte(struct cli *c, byte *ia, rte *e, struct rt_show_data *d, int primary)
|
|
{
|
|
byte from[IPA_MAX_TEXT_LENGTH+8];
|
|
byte tm[TM_DATETIME_BUFFER_SIZE], info[256];
|
|
rta *a = e->attrs;
|
|
int sync_error = d->kernel ? krt_get_sync_error(d->kernel, e) : 0;
|
|
void (*get_route_info)(struct rte *, byte *buf);
|
|
struct nexthop *nh;
|
|
|
|
tm_format_time(tm, &config->tf_route, e->lastmod);
|
|
if (ipa_nonzero(a->from) && !ipa_equal(a->from, a->nh.gw))
|
|
bsprintf(from, " from %I", a->from);
|
|
else
|
|
from[0] = 0;
|
|
|
|
/* Need to normalize the extended attributes */
|
|
if (d->verbose && !rta_is_cached(a) && a->eattrs)
|
|
ea_normalize(a->eattrs);
|
|
|
|
get_route_info = e->src->proto->proto->get_route_info;
|
|
if (get_route_info)
|
|
get_route_info(e, info);
|
|
else
|
|
bsprintf(info, " (%d)", a->pref);
|
|
|
|
if (d->last_table != d->tab)
|
|
rt_show_table(c, d);
|
|
|
|
cli_printf(c, -1007, "%-20s %s [%s %s%s]%s%s", ia, rta_dest_name(a->dest),
|
|
e->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;
|
|
char *onlink = (nh->flags & RNF_ONLINK) ? " onlink" : "";
|
|
char weight[16] = "";
|
|
|
|
if (nh->labels)
|
|
{
|
|
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)
|
|
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);
|
|
else
|
|
cli_printf(c, -1007, "\tdev %s%s%s",
|
|
nh->iface->name, mpls, onlink, weight);
|
|
}
|
|
|
|
if (d->verbose)
|
|
rta_show(c, a);
|
|
}
|
|
|
|
static void
|
|
rt_show_net(struct cli *c, net *n, struct rt_show_data *d)
|
|
{
|
|
rte *e, *ee;
|
|
byte ia[NET_MAX_TEXT_LENGTH+16+1];
|
|
struct channel *ec = d->tab->export_channel;
|
|
|
|
/* The Clang static analyzer complains that ec may be NULL.
|
|
* It should be ensured to be not NULL by rt_show_prepare_tables() */
|
|
ASSUME(!d->export_mode || ec);
|
|
|
|
int first = 1;
|
|
int first_show = 1;
|
|
int last_label = 0;
|
|
int pass = 0;
|
|
|
|
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;
|
|
|
|
/* Export channel is down, do not try to export routes to it */
|
|
if (ec && (ec->export_state == ES_DOWN))
|
|
goto skip;
|
|
|
|
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))
|
|
{
|
|
/* Special case for merged export */
|
|
rte *rt_free;
|
|
e = rt_export_merged(ec, n, &rt_free, c->show_pool, 1);
|
|
pass = 1;
|
|
|
|
if (!e)
|
|
{ e = ee; goto skip; }
|
|
}
|
|
else if (d->export_mode)
|
|
{
|
|
struct proto *ep = ec->proto;
|
|
int ic = ep->preexport ? ep->preexport(ec, e) : 0;
|
|
|
|
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) ||
|
|
(f_run(ec->out_filter, &e, c->show_pool, FF_SILENT) <= F_ACCEPT);
|
|
|
|
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->src->proto))
|
|
goto skip;
|
|
|
|
if (f_run(d->filter, &e, c->show_pool, 0) > F_ACCEPT)
|
|
goto skip;
|
|
|
|
if (d->stats < 2)
|
|
{
|
|
int label = (int) ea_get_int(e->attrs->eattrs, EA_MPLS_LABEL, (uint) -1);
|
|
|
|
if (first_show || (last_label != label))
|
|
{
|
|
if (label < 0)
|
|
net_format(n->n.addr, ia, sizeof(ia));
|
|
else
|
|
bsnprintf(ia, sizeof(ia), "%N mpls %d", n->n.addr, label);
|
|
}
|
|
else
|
|
ia[0] = 0;
|
|
|
|
rt_show_rte(c, ia, e, d, (e->net->routes == ee));
|
|
first_show = 0;
|
|
last_label = label;
|
|
}
|
|
|
|
d->show_counter++;
|
|
|
|
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 && !d->trie_walk)
|
|
fit_get(&d->tab->table->fib, &d->fit);
|
|
|
|
if (d->walk_lock)
|
|
rt_unlock_trie(d->tab->table, d->walk_lock);
|
|
|
|
/* 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;
|
|
struct rtable *tab = d->tab->table;
|
|
#ifdef DEBUGGING
|
|
unsigned max = 4;
|
|
#else
|
|
unsigned max = 64;
|
|
#endif
|
|
struct fib *fib = &tab->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)
|
|
{
|
|
/* We use either trie-based walk or fib-based walk */
|
|
d->trie_walk = tab->trie &&
|
|
(d->addr_mode == RSD_ADDR_IN) &&
|
|
net_val_match(tab->addr_type, NB_IP);
|
|
|
|
if (d->trie_walk && !d->walk_state)
|
|
d->walk_state = lp_allocz(c->parser_pool, sizeof (struct f_trie_walk_state));
|
|
|
|
if (d->trie_walk)
|
|
{
|
|
d->walk_lock = rt_lock_trie(tab);
|
|
trie_walk_init(d->walk_state, tab->trie, d->addr);
|
|
}
|
|
else
|
|
FIB_ITERATE_INIT(&d->fit, &tab->fib);
|
|
|
|
d->table_open = 1;
|
|
d->table_counter++;
|
|
d->kernel = rt_show_get_kernel(d);
|
|
|
|
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);
|
|
}
|
|
|
|
if (d->trie_walk)
|
|
{
|
|
/* Trie-based walk */
|
|
net_addr addr;
|
|
while (trie_walk_next(d->walk_state, &addr))
|
|
{
|
|
net *n = net_find(tab, &addr);
|
|
if (!n)
|
|
continue;
|
|
|
|
rt_show_net(c, n, d);
|
|
|
|
if (!--max)
|
|
return;
|
|
}
|
|
|
|
rt_unlock_trie(tab, d->walk_lock);
|
|
d->walk_lock = NULL;
|
|
}
|
|
else
|
|
{
|
|
/* fib-based walk */
|
|
FIB_ITERATE_START(fib, it, net, n)
|
|
{
|
|
if ((d->addr_mode == RSD_ADDR_IN) && (!net_in_netX(n->n.addr, d->addr)))
|
|
goto next;
|
|
|
|
if (!max--)
|
|
{
|
|
FIB_ITERATE_PUT(it);
|
|
return;
|
|
}
|
|
rt_show_net(c, n, d);
|
|
|
|
next:;
|
|
}
|
|
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, tab->name);
|
|
}
|
|
|
|
d->kernel = NULL;
|
|
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] && config->def_tables[i]->table)
|
|
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 || (d->addr_mode == RSD_ADDR_IN))
|
|
{
|
|
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;
|
|
d->kernel = rt_show_get_kernel(d);
|
|
|
|
if (d->addr_mode == RSD_ADDR_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");
|
|
}
|
|
}
|