diff options
Diffstat (limited to 'nest/mpls.c')
-rw-r--r-- | nest/mpls.c | 998 |
1 files changed, 998 insertions, 0 deletions
diff --git a/nest/mpls.c b/nest/mpls.c new file mode 100644 index 00000000..cd3a90d9 --- /dev/null +++ b/nest/mpls.c @@ -0,0 +1,998 @@ +/* + * BIRD Internet Routing Daemon -- MPLS Structures + * + * (c) 2022 Ondrej Zajicek <santiago@crfreenet.org> + * (c) 2022 CZ.NIC z.s.p.o. + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +/** + * DOC: MPLS + * + * 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. MPLS labels are also used + * in special MPLS routes (which use labels as network address) that are + * exported to MPLS routing table in kernel. The MPLS subsystem consists of MPLS + * domains (struct &mpls_domain), MPLS channels (struct &mpls_channel) and FEC + * maps (struct &mpls_fec_map). + * + * The MPLS domain represents one MPLS label address space, implements the label + * allocator, and handles associated configuration and management. The domain is + * declared in the configuration (struct &mpls_domain_config). There might be + * multiple MPLS domains representing separate label spaces, but in most cases + * one domain is enough. MPLS-aware protocols and routing tables are associated + * with a specific MPLS domain. + * + * The MPLS domain has configurable label ranges (struct &mpls_range), by + * default it has two ranges: static (16-1000) and dynamic (1000-10000). When + * a protocol wants to allocate labels, it first acquires a handle (struct + * &mpls_handle) for a specific range using mpls_new_handle(), and then it + * allocates labels from that with mpls_new_label(). When not needed, labels are + * freed by mpls_free_label() and the handle is released by mpls_free_handle(). + * Note that all labels and handles must be freed manually. + * + * Both MPLS domain and MPLS range are reference counted, so when deconfigured + * they could be freed just after all labels and ranges are freed. Users are + * expected to hold a reference to a MPLS domain for whole time they use + * something from that domain (e.g. &mpls_handle), but releasing reference to + * a range while holding associated handle is OK. + * + * The MPLS channel is subclass of a generic protocol channel. It has two + * distinct purposes - to handle per-protocol MPLS configuration (e.g. which + * MPLS domain is associated with the protocol, which label range is used by the + * protocol), and to announce MPLS routes to a routing table (as a regular + * protocol channel). + * + * The FEC map is a helper structure that maps forwarding equivalent classes + * (FECs) to MPLS labels. It is an internal matter of a routing protocol how to + * assign meaning to allocated labels, announce LSP routes and associated MPLS + * routes (i.e. ILM entries). But the common behavior is implemented in the FEC + * map, which can be used by the protocols that work with IP-prefix-based FECs. + * + * The FEC map keeps hash tables of FECs (struct &mpls_fec) based on network + * prefix, next hop eattr and assigned label. It has three labeling policies: + * static assignment (%MPLS_POLICY_STATIC), per-prefix policy (%MPLS_POLICY_PREFIX), + * and aggregating policy (%MPLS_POLICY_AGGREGATE). In per-prefix policy, each + * distinct LSP is a separate FEC and uses a separate label, which is kept even + * if the next hop of the LSP changes. In aggregating policy, LSPs with a same + * next hop form one FEC and use one label, but when a next hop (or remote + * label) of such LSP changes then the LSP must be moved to a different FEC and + * assigned a different label. + * + * The overall process works this way: A protocol wants to announce a LSP route, + * it does that by announcing e.g. IP route with %EA_MPLS_POLICY attribute. + * After the route is accepted by filters (which may also change the policy + * attribute or set a static label), the mpls_handle_rte() is called from + * rte_update2(), which applies selected labeling policy, finds existing FEC or + * creates a new FEC (which includes allocating new label and announcing related + * MPLS route by mpls_announce_fec()), and attach FEC label to the LSP route. + * After that, the LSP route is stored in routing table by rte_recalculate(). + * Changes in routing tables trigger mpls_rte_insert() and mpls_rte_remove() + * hooks, which refcount FEC structures and possibly trigger removal of FECs + * and withdrawal of MPLS routes. + * + * TODO: + * - show mpls labels CLI command + * - label range non-intersection check + * - better range reconfigurations (allow reduce ranges over unused labels) + * - protocols should do route refresh instead of resetart when reconfiguration + * requires changing labels (e.g. different label range) + * - registering static allocations + * - checking range in static allocations + * - special handling of reserved labels + */ + +#include "nest/bird.h" +#include "nest/route.h" +#include "nest/mpls.h" + +static struct mpls_range *mpls_new_range(struct mpls_domain *m, struct mpls_range_config *cf); +static struct mpls_range *mpls_find_range_(list *l, const char *name); +static int mpls_reconfigure_range(struct mpls_domain *m, struct mpls_range *r, struct mpls_range_config *cf); +static void mpls_remove_range(struct mpls_range *r); + + +/* + * MPLS domain + */ + +list mpls_domains; + +void +mpls_init(void) +{ + init_list(&mpls_domains); +} + +struct mpls_domain_config * +mpls_domain_config_new(struct symbol *s) +{ + struct mpls_domain_config *mc = cfg_allocz(sizeof(struct mpls_domain_config)); + struct mpls_range_config *rc; + + cf_define_symbol(new_config, s, SYM_MPLS_DOMAIN, mpls_domain, mc); + mc->name = s->name; + init_list(&mc->ranges); + + /* Predefined static range */ + rc = mpls_range_config_new(mc, NULL); + rc->name = "static"; + rc->start = 16; + rc->length = 984; + mc->static_range = rc; + + /* Predefined dynamic range */ + rc = mpls_range_config_new(mc, NULL); + rc->name = "dynamic"; + rc->start = 1000; + rc->length = 9000; + mc->dynamic_range = rc; + + add_tail(&new_config->mpls_domains, &mc->n); + + return mc; +} + +void +mpls_domain_postconfig(struct mpls_domain_config *cf UNUSED) +{ + /* Add label range non-intersection check */ +} + +static struct mpls_domain * +mpls_new_domain(struct mpls_domain_config *cf) +{ + struct pool *p = rp_new(&root_pool, "MPLS domain"); + struct mpls_domain *m = mb_allocz(p, sizeof(struct mpls_domain)); + + m->cf = cf; + m->name = cf->name; + m->pool = p; + + lmap_init(&m->labels, p); + lmap_set(&m->labels, 0); + + init_list(&m->ranges); + init_list(&m->handles); + + struct mpls_range_config *rc; + WALK_LIST(rc, cf->ranges) + mpls_new_range(m, rc); + + add_tail(&mpls_domains, &m->n); + cf->domain = m; + + return m; +} + +static struct mpls_domain * +mpls_find_domain_(list *l, const char *name) +{ + struct mpls_domain *m; + + WALK_LIST(m, *l) + if (!strcmp(m->name, name)) + return m; + + return NULL; +} + +static int +mpls_reconfigure_domain(struct mpls_domain *m, struct mpls_domain_config *cf) +{ + cf->domain = m; + m->cf->domain = NULL; + m->cf = cf; + m->name = cf->name; + + /* Reconfigure label ranges */ + list old_ranges; + init_list(&old_ranges); + add_tail_list(&old_ranges, &m->ranges); + init_list(&m->ranges); + + struct mpls_range_config *rc; + WALK_LIST(rc, cf->ranges) + { + struct mpls_range *r = mpls_find_range_(&old_ranges, rc->name); + + if (r && mpls_reconfigure_range(m, r, rc)) + { + rem_node(&r->n); + add_tail(&m->ranges, &r->n); + continue; + } + + mpls_new_range(m, rc); + } + + struct mpls_range *r, *r2; + WALK_LIST_DELSAFE(r, r2, old_ranges) + mpls_remove_range(r); + + add_tail_list(&m->ranges, &old_ranges); + + return 1; +} + +static void +mpls_free_domain(struct mpls_domain *m) +{ + ASSERT(m->use_count == 0); + ASSERT(m->label_count == 0); + ASSERT(EMPTY_LIST(m->handles)); + + struct config *cfg = m->removed; + + m->cf->domain = NULL; + rem_node(&m->n); + rfree(m->pool); + + config_del_obstacle(cfg); +} + +static void +mpls_remove_domain(struct mpls_domain *m, struct config *cfg) +{ + m->removed = cfg; + config_add_obstacle(cfg); + + if (!m->use_count) + mpls_free_domain(m); +} + +void +mpls_lock_domain(struct mpls_domain *m) +{ + m->use_count++; +} + +void +mpls_unlock_domain(struct mpls_domain *m) +{ + ASSERT(m->use_count > 0); + + m->use_count--; + if (!m->use_count && m->removed) + mpls_free_domain(m); +} + +void +mpls_preconfig(struct config *c) +{ + init_list(&c->mpls_domains); +} + +void +mpls_commit(struct config *new, struct config *old) +{ + list old_domains; + init_list(&old_domains); + add_tail_list(&old_domains, &mpls_domains); + init_list(&mpls_domains); + + struct mpls_domain_config *mc; + WALK_LIST(mc, new->mpls_domains) + { + struct mpls_domain *m = mpls_find_domain_(&old_domains, mc->name); + + if (m && mpls_reconfigure_domain(m, mc)) + { + rem_node(&m->n); + add_tail(&mpls_domains, &m->n); + continue; + } + + mpls_new_domain(mc); + } + + struct mpls_domain *m, *m2; + WALK_LIST_DELSAFE(m, m2, old_domains) + mpls_remove_domain(m, old); + + add_tail_list(&mpls_domains, &old_domains); +} + + +/* + * MPLS range + */ + +struct mpls_range_config * +mpls_range_config_new(struct mpls_domain_config *mc, struct symbol *s) +{ + struct mpls_range_config *rc = cfg_allocz(sizeof(struct mpls_range_config)); + + if (s) + cf_define_symbol(new_config, s, SYM_MPLS_RANGE, mpls_range, rc); + + rc->domain = mc; + rc->name = s ? s->name : NULL; + rc->start = (uint) -1; + rc->length = (uint) -1; + + add_tail(&mc->ranges, &rc->n); + + return rc; +} + +static struct mpls_range * +mpls_new_range(struct mpls_domain *m, struct mpls_range_config *cf) +{ + struct mpls_range *r = mb_allocz(m->pool, sizeof(struct mpls_range)); + + r->cf = cf; + r->name = cf->name; + r->lo = cf->start; + r->hi = cf->start + cf->length; + + add_tail(&m->ranges, &r->n); + cf->range = r; + + return r; +} + +static struct mpls_range * +mpls_find_range_(list *l, const char *name) +{ + struct mpls_range *r; + + WALK_LIST(r, *l) + if (!strcmp(r->name, name)) + return r; + + return NULL; +} + +static int +mpls_reconfigure_range(struct mpls_domain *m UNUSED, struct mpls_range *r, struct mpls_range_config *cf) +{ + if ((cf->start > r->lo) || (cf->start + cf->length < r->hi)) + return 0; + + cf->range = r; + r->cf->range = NULL; + r->cf = cf; + r->name = cf->name; + r->lo = cf->start; + r->hi = cf->start + cf->length; + + return 1; +} + +static void +mpls_free_range(struct mpls_range *r) +{ + ASSERT(r->use_count == 0); + ASSERT(r->label_count == 0); + + r->cf->range = NULL; + rem_node(&r->n); + mb_free(r); +} + +static void +mpls_remove_range(struct mpls_range *r) +{ + r->removed = 1; + + if (!r->use_count) + mpls_free_range(r); +} + +void +mpls_lock_range(struct mpls_range *r) +{ + r->use_count++; +} + +void +mpls_unlock_range(struct mpls_range *r) +{ + ASSERT(r->use_count > 0); + + r->use_count--; + if (!r->use_count && r->removed) + mpls_free_range(r); +} + + +/* + * MPLS handle + */ + +struct mpls_handle * +mpls_new_handle(struct mpls_domain *m, struct mpls_range *r) +{ + struct mpls_handle *h = mb_allocz(m->pool, sizeof(struct mpls_handle)); + + h->range = r; + mpls_lock_range(h->range); + + add_tail(&m->handles, &h->n); + + return h; +} + +void +mpls_free_handle(struct mpls_domain *m UNUSED, struct mpls_handle *h) +{ + ASSERT(h->label_count == 0); + + mpls_unlock_range(h->range); + rem_node(&h->n); + mb_free(h); +} + + +/* + * MPLS label + */ + +uint +mpls_new_label(struct mpls_domain *m, struct mpls_handle *h) +{ + struct mpls_range *r = h->range; + uint n = lmap_first_zero_in_range(&m->labels, r->lo, r->hi); + + if (n >= r->hi) + return 0; + + m->label_count++; + r->label_count++; + h->label_count++; + + lmap_set(&m->labels, n); + return n; +} + +void +mpls_free_label(struct mpls_domain *m, struct mpls_handle *h, uint n) +{ + struct mpls_range *r = h->range; + + ASSERT(lmap_test(&m->labels, n)); + lmap_clear(&m->labels, n); + + ASSERT(m->label_count); + m->label_count--; + + ASSERT(r->label_count); + r->label_count--; + + ASSERT(h->label_count); + h->label_count--; +} + + +/* + * MPLS channel + */ + +static void +mpls_channel_init(struct channel *C, struct channel_config *CC) +{ + struct mpls_channel *c = (void *) C; + struct mpls_channel_config *cc = (void *) CC; + + c->domain = cc->domain->domain; + c->range = cc->range->range; + c->label_policy = cc->label_policy; +} + +static int +mpls_channel_start(struct channel *C) +{ + struct mpls_channel *c = (void *) C; + + mpls_lock_domain(c->domain); + mpls_lock_range(c->range); + + return 0; +} + +/* +static void +mpls_channel_shutdown(struct channel *C) +{ + struct mpls_channel *c = (void *) C; + +} +*/ + +static void +mpls_channel_cleanup(struct channel *C) +{ + struct mpls_channel *c = (void *) C; + + mpls_unlock_range(c->range); + mpls_unlock_domain(c->domain); +} + +static int +mpls_channel_reconfigure(struct channel *C, struct channel_config *CC, int *import_changed UNUSED, int *export_changed UNUSED) +{ + struct mpls_channel *c = (void *) C; + struct mpls_channel_config *new = (void *) CC; + + if ((new->domain->domain != c->domain) || + (new->range->range != c->range) || + (new->label_policy != c->label_policy)) + return 0; + + return 1; +} + +void +mpls_channel_postconfig(struct channel_config *CC) +{ + struct mpls_channel_config *cc = (void *) CC; + + if (!cc->domain) + cf_error("MPLS domain not specified"); + + if (!cc->range) + cc->range = (cc->label_policy == MPLS_POLICY_STATIC) ? + cc->domain->static_range : cc->domain->dynamic_range; + + if (cc->range->domain != cc->domain) + cf_error("MPLS label range from different MPLS domain"); + + if (!cc->c.table) + cf_error("Routing table not specified"); +} + +struct channel_class channel_mpls = { + .channel_size = sizeof(struct mpls_channel), + .config_size = sizeof(struct mpls_channel_config), + .init = mpls_channel_init, + .start = mpls_channel_start, +// .shutdown = mpls_channel_shutdown, + .cleanup = mpls_channel_cleanup, + .reconfigure = mpls_channel_reconfigure, +}; + + +/* + * MPLS FEC map + */ + +#define NET_KEY(fec) fec->net, fec->path_id, fec->hash +#define NET_NEXT(fec) fec->next_k +#define NET_EQ(n1,i1,h1,n2,i2,h2) h1 == h2 && i1 == i2 && net_equal(n1, n2) +#define NET_FN(n,i,h) h + +#define NET_REHASH mpls_net_rehash +#define NET_PARAMS /8, *2, 2, 2, 8, 24 + + +#define RTA_KEY(fec) fec->rta, fec->class_id, fec->hash +#define RTA_NEXT(fec) fec->next_k +#define RTA_EQ(r1,i1,h1,r2,i2,h2) h1 == h2 && r1 == r2 && i1 == i2 +#define RTA_FN(r,i,h) h + +#define RTA_REHASH mpls_rta_rehash +#define RTA_PARAMS /8, *2, 2, 2, 8, 24 + + +#define LABEL_KEY(fec) fec->label +#define LABEL_NEXT(fec) fec->next_l +#define LABEL_EQ(l1,l2) l1 == l2 +#define LABEL_FN(l) u32_hash(l) + +#define LABEL_REHASH mpls_label_rehash +#define LABEL_PARAMS /8, *2, 2, 2, 8, 24 + + +HASH_DEFINE_REHASH_FN(NET, struct mpls_fec) +HASH_DEFINE_REHASH_FN(RTA, struct mpls_fec) +HASH_DEFINE_REHASH_FN(LABEL, struct mpls_fec) + + +static void mpls_withdraw_fec(struct mpls_fec_map *m, struct mpls_fec *fec); +static rta * mpls_get_key_rta(struct mpls_fec_map *m, const rta *src); + +struct mpls_fec_map * +mpls_fec_map_new(pool *pp, struct channel *C, uint rts) +{ + struct pool *p = rp_new(pp, "MPLS FEC map"); + struct mpls_fec_map *m = mb_allocz(p, sizeof(struct mpls_fec_map)); + struct mpls_channel *c = (void *) C; + + m->pool = p; + m->channel = C; + + m->domain = c->domain; + mpls_lock_domain(m->domain); + + m->handle = mpls_new_handle(c->domain, c->range); + + /* net_hash and rta_hash are initialized on-demand */ + HASH_INIT(m->label_hash, m->pool, 4); + + m->mpls_rts = rts; + m->mpls_scope = SCOPE_UNIVERSE; + + return m; +} + +void +mpls_fec_map_free(struct mpls_fec_map *m) +{ + /* Free stored rtas */ + if (m->rta_hash.data) + { + HASH_WALK(m->rta_hash, next_k, fec) + { + rta_free(fec->rta); + fec->rta = NULL; + } + HASH_WALK_END; + } + + /* Free allocated labels */ + HASH_WALK(m->label_hash, next_l, fec) + { + if (fec->policy != MPLS_POLICY_STATIC) + mpls_free_label(m->domain, m->handle, fec->label); + } + HASH_WALK_END; + + mpls_free_handle(m->domain, m->handle); + mpls_unlock_domain(m->domain); + + rfree(m->pool); +} + +static slab * +mpls_slab(struct mpls_fec_map *m, uint type) +{ + ASSERT(type <= NET_VPN6); + int pos = type ? (type - 1) : 0; + + if (!m->slabs[pos]) + m->slabs[pos] = sl_new(m->pool, sizeof(struct mpls_fec) + net_addr_length[pos + 1]); + + return m->slabs[pos]; +} + +struct mpls_fec * +mpls_find_fec_by_label(struct mpls_fec_map *m, u32 label) +{ + return HASH_FIND(m->label_hash, LABEL, label); +} + +struct mpls_fec * +mpls_get_fec_by_label(struct mpls_fec_map *m, u32 label) +{ + struct mpls_fec *fec = HASH_FIND(m->label_hash, LABEL, label); + + if (fec) + return fec; + + fec = sl_allocz(mpls_slab(m, 0)); + + fec->label = label; + fec->policy = MPLS_POLICY_STATIC; + + DBG("New FEC lab %u\n", fec->label); + + HASH_INSERT2(m->label_hash, LABEL, m->pool, fec); + + return fec; +} + +struct mpls_fec * +mpls_get_fec_by_net(struct mpls_fec_map *m, const net_addr *net, u32 path_id) +{ + if (!m->net_hash.data) + HASH_INIT(m->net_hash, m->pool, 4); + + u32 hash = net_hash(net) ^ u32_hash(path_id); + struct mpls_fec *fec = HASH_FIND(m->net_hash, NET, net, path_id, hash); + + if (fec) + return fec; + + fec = sl_allocz(mpls_slab(m, net->type)); + + fec->hash = hash; + fec->path_id = path_id; + net_copy(fec->net, net); + + fec->label = mpls_new_label(m->domain, m->handle); + fec->policy = MPLS_POLICY_PREFIX; + + DBG("New FEC net %u\n", fec->label); + + HASH_INSERT2(m->net_hash, NET, m->pool, fec); + HASH_INSERT2(m->label_hash, LABEL, m->pool, fec); + + return fec; +} + +struct mpls_fec * +mpls_get_fec_by_rta(struct mpls_fec_map *m, const rta *src, u32 class_id) +{ + if (!m->rta_hash.data) + HASH_INIT(m->rta_hash, m->pool, 4); + + rta *rta = mpls_get_key_rta(m, src); + u32 hash = rta->hash_key ^ u32_hash(class_id); + struct mpls_fec *fec = HASH_FIND(m->rta_hash, RTA, rta, class_id, hash); + + if (fec) + { + rta_free(rta); + return fec; + } + + fec = sl_allocz(mpls_slab(m, 0)); + + fec->hash = hash; + fec->class_id = class_id; + fec->rta = rta; + + fec->label = mpls_new_label(m->domain, m->handle); + fec->policy = MPLS_POLICY_AGGREGATE; + + DBG("New FEC rta %u\n", fec->label); + + HASH_INSERT2(m->rta_hash, RTA, m->pool, fec); + HASH_INSERT2(m->label_hash, LABEL, m->pool, fec); + + return fec; +} + +void +mpls_free_fec(struct mpls_fec_map *m, struct mpls_fec *fec) +{ + if (fec->state != MPLS_FEC_DOWN) + mpls_withdraw_fec(m, fec); + + DBG("Free FEC %u\n", fec->label); + + mpls_free_label(m->domain, m->handle, fec->label); + HASH_REMOVE2(m->label_hash, LABEL, m->pool, fec); + + switch (fec->policy) + { + case MPLS_POLICY_STATIC: + break; + + case MPLS_POLICY_PREFIX: + HASH_REMOVE2(m->net_hash, NET, m->pool, fec); + break; + + case MPLS_POLICY_AGGREGATE: + rta_free(fec->rta); + HASH_REMOVE2(m->rta_hash, RTA, m->pool, fec); + break; + + default: + bug("Unknown fec type"); + } + + sl_free(fec); +} + +static inline void mpls_lock_fec(struct mpls_fec_map *x UNUSED, struct mpls_fec *fec) +{ if (fec) fec->uc++; } + +static inline void mpls_unlock_fec(struct mpls_fec_map *x, struct mpls_fec *fec) +{ if (fec && !--fec->uc) mpls_free_fec(x, fec); } + +static inline void +mpls_damage_fec(struct mpls_fec_map *m UNUSED, struct mpls_fec *fec) +{ + if (fec->state == MPLS_FEC_CLEAN) + fec->state = MPLS_FEC_DIRTY; +} + +static rta * +mpls_get_key_rta(struct mpls_fec_map *m, const rta *src) +{ + rta *a = allocz(RTA_MAX_SIZE); + + a->source = m->mpls_rts; + a->scope = m->mpls_scope; + + if (!src->hostentry) + { + /* Just copy the nexthop */ + a->dest = src->dest; + nexthop_link(a, &src->nh); + } + else + { + /* Keep the hostentry */ + a->hostentry = src->hostentry; + + /* Keep the original labelstack */ + const u32 *labels = &src->nh.label[src->nh.labels - src->nh.labels_orig]; + a->nh.labels = a->nh.labels_orig = src->nh.labels_orig; + memcpy(a->nh.label, labels, src->nh.labels_orig * sizeof(u32)); + } + + return rta_lookup(a); +} + +static void +mpls_announce_fec(struct mpls_fec_map *m, struct mpls_fec *fec, const rta *src) +{ + rta *a = allocz(RTA_MAX_SIZE); + + a->source = m->mpls_rts; + a->scope = m->mpls_scope; + + if (!src->hostentry) + { + /* Just copy the nexthop */ + a->dest = src->dest; + nexthop_link(a, &src->nh); + } + else + { + const u32 *labels = &src->nh.label[src->nh.labels - src->nh.labels_orig]; + mpls_label_stack ms; + + /* Apply the hostentry with the original labelstack */ + ms.len = src->nh.labels_orig; + memcpy(ms.stack, labels, src->nh.labels_orig * sizeof(u32)); + rta_apply_hostentry(a, src->hostentry, &ms); + } + + net_addr_mpls n = NET_ADDR_MPLS(fec->label); + + rte *e = rte_get_temp(rta_lookup(a), m->channel->proto->main_source); + e->pflags = 0; + + fec->state = MPLS_FEC_CLEAN; + rte_update2(m->channel, (net_addr *) &n, e, m->channel->proto->main_source); +} + +static void +mpls_withdraw_fec(struct mpls_fec_map *m, struct mpls_fec *fec) +{ + net_addr_mpls n = NET_ADDR_MPLS(fec->label); + + fec->state = MPLS_FEC_DOWN; + rte_update2(m->channel, (net_addr *) &n, NULL, m->channel->proto->main_source); +} + +static void +mpls_apply_fec(rte *r, struct mpls_fec *fec, linpool *lp) +{ + struct ea_list *ea = lp_allocz(lp, sizeof(struct ea_list) + 2 * sizeof(eattr)); + + rta *old_attrs = r->attrs; + + if (rta_is_cached(old_attrs)) + r->attrs = rta_do_cow(r->attrs, lp); + + *ea = (struct ea_list) { + .next = r->attrs->eattrs, + .flags = EALF_SORTED, + .count = 2, + }; + + ea->attrs[0] = (struct eattr) { + .id = EA_MPLS_LABEL, + .type = EAF_TYPE_INT, + .u.data = fec->label, + }; + + ea->attrs[1] = (struct eattr) { + .id = EA_MPLS_POLICY, + .type = EAF_TYPE_INT, + .u.data = fec->policy, + }; + + r->attrs->eattrs = ea; + + if (rta_is_cached(old_attrs)) + { + r->attrs = rta_lookup(r->attrs); + rta_free(old_attrs); + } +} + + +void +mpls_handle_rte(struct mpls_fec_map *m, const net_addr *n, rte *r, linpool *lp, struct mpls_fec **locked_fec) +{ + ASSERT(!(r->flags & REF_COW)); + + struct mpls_fec *fec = NULL; + + + /* Select FEC for route */ + uint policy = ea_get_int(r->attrs->eattrs, EA_MPLS_POLICY, 0); + switch (policy) + { + case MPLS_POLICY_NONE: + return; + + case MPLS_POLICY_STATIC:; + uint label = ea_get_int(r->attrs->eattrs, EA_MPLS_LABEL, 0); + + if (label < 16) + return; + + fec = mpls_get_fec_by_label(m, label); + mpls_damage_fec(m, fec); + break; + + case MPLS_POLICY_PREFIX: + fec = mpls_get_fec_by_net(m, n, r->src->private_id); + mpls_damage_fec(m, fec); + break; + + case MPLS_POLICY_AGGREGATE:; + uint class = ea_get_int(r->attrs->eattrs, EA_MPLS_CLASS, 0); + fec = mpls_get_fec_by_rta(m, r->attrs, class); + break; + + default: + log(L_WARN "Route %N has invalid MPLS policy %u", n, policy); + return; + } + + /* Temporarily lock FEC */ + mpls_lock_fec(m, fec); + *locked_fec = fec; + + /* Apply FEC label to route */ + mpls_apply_fec(r, fec, lp); + + /* Announce MPLS rule for new/updated FEC */ + if (fec->state != MPLS_FEC_CLEAN) + mpls_announce_fec(m, fec, r->attrs); +} + +void +mpls_handle_rte_cleanup(struct mpls_fec_map *m, struct mpls_fec **locked_fec) +{ + /* Unlock temporarily locked FEC from mpls_handle_rte() */ + if (*locked_fec) + { + mpls_unlock_fec(m, *locked_fec); + *locked_fec = NULL; + } +} + +void +mpls_rte_insert(net *n UNUSED, rte *r) +{ + struct proto *p = r->src->proto; + struct mpls_fec_map *m = p->mpls_map; + + uint label = ea_get_int(r->attrs->eattrs, EA_MPLS_LABEL, 0); + if (label < 16) + return; + + struct mpls_fec *fec = mpls_find_fec_by_label(m, label); + if (!fec) + return; + + mpls_lock_fec(m, fec); +} + +void +mpls_rte_remove(net *n UNUSED, rte *r) +{ + struct proto *p = r->src->proto; + struct mpls_fec_map *m = p->mpls_map; + + uint label = ea_get_int(r->attrs->eattrs, EA_MPLS_LABEL, 0); + if (label < 16) + return; + + struct mpls_fec *fec = mpls_find_fec_by_label(m, label); + if (!fec) + return; + + mpls_unlock_fec(m, fec); +} |