0
0
mirror of https://gitlab.nic.cz/labs/bird.git synced 2024-09-18 19:35:20 +00:00

Merge commit 'bcff3ae7' into mq-aggregator-for-v3

This commit is contained in:
Maria Matejka 2023-11-09 11:06:07 +01:00
commit b979c6ce6e
10 changed files with 636 additions and 5 deletions

View File

@ -312,7 +312,7 @@ if test "$enable_mpls_kernel" != no ; then
fi
fi
all_protocols="aggregator $proto_bfd babel bgp mrt ospf perf pipe radv rip rpki static"
all_protocols="aggregator $proto_bfd babel bgp l3vpn mrt ospf perf pipe radv rip rpki static"
all_protocols=`echo $all_protocols | sed 's/ /,/g'`
@ -325,6 +325,7 @@ AH_TEMPLATE([CONFIG_BABEL], [Babel protocol])
AH_TEMPLATE([CONFIG_BFD], [BFD protocol])
AH_TEMPLATE([CONFIG_BGP], [BGP protocol])
AH_TEMPLATE([CONFIG_BMP], [BMP protocol])
AH_TEMPLATE([CONFIG_L3VPN], [L3VPN protocol])
AH_TEMPLATE([CONFIG_MRT], [MRT protocol])
AH_TEMPLATE([CONFIG_OSPF], [OSPF protocol])
AH_TEMPLATE([CONFIG_PIPE], [Pipe protocol])

View File

@ -379,7 +379,7 @@ CF_KEYWORDS(FUNCTION, PRINT, PRINTN, UNSET, RETURN,
%type <ecs> ec_kind
%type <fret> break_command
%type <i32> cnum
%type <e> pair_item ec_item lc_item set_item switch_item set_items switch_items switch_body
%type <e> pair_item ec_item lc_item set_item switch_item ec_items set_items switch_items switch_body
%type <trie> fprefix_set
%type <v> set_atom switch_atom fipa
%type <px> fprefix
@ -708,6 +708,11 @@ switch_item:
| switch_atom DDOT switch_atom { $$ = f_new_item($1, $3); }
;
ec_items:
ec_item
| ec_items ',' ec_item { $$ = f_merge_items($1, $3); }
;
set_items:
set_item
| set_items ',' set_item { $$ = f_merge_items($1, $3); }

View File

@ -48,6 +48,7 @@ enum protocol_class {
PROTOCOL_DEVICE,
PROTOCOL_DIRECT,
PROTOCOL_KERNEL,
PROTOCOL_L3VPN,
PROTOCOL_OSPF,
PROTOCOL_MRT,
PROTOCOL_PERF,
@ -105,7 +106,7 @@ void protos_dump_all(void);
extern struct protocol
proto_device, proto_radv, proto_rip, proto_static, proto_mrt,
proto_ospf, proto_perf, proto_aggregator,
proto_ospf, proto_perf, proto_l3vpn, proto_aggregator,
proto_pipe, proto_bgp, proto_bmp, proto_bfd, proto_babel, proto_rpki;
/*

View File

@ -480,8 +480,9 @@ typedef struct rta {
#define RTS_BABEL 13 /* Babel route */
#define RTS_RPKI 14 /* Route Origin Authorization */
#define RTS_PERF 15 /* Perf checker */
#define RTS_AGGREGATED 16 /* Aggregated route */
#define RTS_MAX 17
#define RTS_L3VPN 16 /* MPLS L3VPN */
#define RTS_AGGREGATED 17 /* Aggregated route */
#define RTS_MAX 18
#define RTD_NONE 0 /* Undefined next hop */
#define RTD_UNICAST 1 /* Next hop is neighbor router */
@ -762,6 +763,8 @@ int rt_flowspec_check(rtable *tab_ip, rtable *tab_flow, const net_addr *n, rta *
#define DEF_PREF_RIP 120 /* RIP */
#define DEF_PREF_BGP 100 /* BGP */
#define DEF_PREF_RPKI 100 /* RPKI */
#define DEF_PREF_L3VPN_IMPORT 80 /* L3VPN import -> lower than BGP */
#define DEF_PREF_L3VPN_EXPORT 120 /* L3VPN export -> higher than BGP */
#define DEF_PREF_INHERITED 10 /* Routes inherited from other routing daemons */
/*

View File

@ -76,6 +76,7 @@ const char * const rta_src_names[RTS_MAX] = {
[RTS_BABEL] = "Babel",
[RTS_RPKI] = "RPKI",
[RTS_PERF] = "Perf",
[RTS_L3VPN] = "L3VPN",
[RTS_AGGREGATED] = "aggregated",
};

1
proto/l3vpn/Doc Normal file
View File

@ -0,0 +1 @@
S l3vpn.c

6
proto/l3vpn/Makefile Normal file
View File

@ -0,0 +1,6 @@
src := l3vpn.c
obj := $(src-o-files)
$(all-daemon)
$(cf-local)
tests_objs := $(tests_objs) $(src-o-files)

101
proto/l3vpn/config.Y Normal file
View File

@ -0,0 +1,101 @@
/*
* BIRD -- BGP/MPLS IP Virtual Private Networks (L3VPN)
*
* (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.
*/
CF_HDR
#include "proto/l3vpn/l3vpn.h"
CF_DEFINES
#define L3VPN_CFG ((struct l3vpn_config *) this_proto)
static void
f_tree_only_rt(struct f_tree *t)
{
/* Parsed degenerate trees have link to the last node in t->right */
t->right = NULL;
while (t)
{
uint type1 = t->from.val.ec >> 48;
uint type2 = t->to.val.ec >> 48;
ASSERT(type1 == type2);
if (!ec_type_is_rt(type1))
cf_error("Extended community is not route target");
ASSERT(!t->right);
t = t->left;
}
}
CF_DECLS
CF_KEYWORDS(L3VPN, ROUTE, IMPORT, EXPORT, TARGET, RD, DISTINGUISHER)
%type <e> l3vpn_targets
%type <cc> l3vpn_channel_start l3vpn_channel
CF_GRAMMAR
proto: l3vpn_proto;
l3vpn_channel_start: net_type_base
{
/* Redefining proto_channel to change default values */
$$ = this_channel = channel_config_get(NULL, net_label[$1], $1, this_proto);
if (!this_channel->copy)
{
this_channel->out_filter = FILTER_ACCEPT;
this_channel->preference = net_val_match($1, NB_IP) ?
DEF_PREF_L3VPN_IMPORT :
DEF_PREF_L3VPN_EXPORT;
}
};
l3vpn_channel: l3vpn_channel_start channel_opt_list channel_end;
l3vpn_proto_start: proto_start L3VPN
{
this_proto = proto_config_new(&proto_l3vpn, $1);
};
l3vpn_proto_item:
proto_item
| l3vpn_channel
| mpls_channel
| RD VPN_RD { L3VPN_CFG->rd = $2; }
| ROUTE DISTINGUISHER VPN_RD { L3VPN_CFG->rd = $3; }
| IMPORT TARGET l3vpn_targets { L3VPN_CFG->import_target = $3; }
| EXPORT TARGET l3vpn_targets { L3VPN_CFG->export_target = $3; }
| ROUTE TARGET l3vpn_targets { L3VPN_CFG->import_target = L3VPN_CFG->export_target = $3; }
;
l3vpn_proto_opts:
/* empty */
| l3vpn_proto_opts l3vpn_proto_item ';'
;
l3vpn_proto:
l3vpn_proto_start proto_name '{' l3vpn_proto_opts '}';
l3vpn_targets:
ec_item { f_tree_only_rt($1); $$ = $1; }
| '[' ec_items ']' { f_tree_only_rt($2); $$ = build_tree($2); }
;
CF_CODE
CF_END

476
proto/l3vpn/l3vpn.c Normal file
View File

@ -0,0 +1,476 @@
/*
* BIRD -- BGP/MPLS IP Virtual Private Networks (L3VPN)
*
* (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: L3VPN
*
* The L3VPN protocol implements RFC 4364 BGP/MPLS VPNs using MPLS backbone.
* It works similarly to pipe. It connects IP table (one per VRF) with (global)
* VPN table. Routes passed from VPN table to IP table are stripped of RD and
* filtered by import targets, routes passed in the other direction are extended
* with RD, MPLS labels and export targets in extended communities. Separate
* MPLS channel is used to announce MPLS routes for the labels.
*
* Note that in contrast to the pipe protocol, L3VPN protocol has both IPv4 and
* IPv6 channels in one instance, Also both IP and VPN channels are presented to
* users as separate channels, although that will change in the future.
*
* The L3VPN protocol has different default preferences on IP and VPN sides.
* The reason is that in import direction (VPN->IP) routes should have lower
* preferences that ones received from local CE (perhaps by EBGP), while in
* export direction (IP->VPN) routes should have higher preferences that ones
* received from remote PEs (by IBGP).
*
* Supported standards:
* RFC 4364 - BGP/MPLS IP Virtual Private Networks (L3VPN)
*/
#undef LOCAL_DEBUG
#include "nest/bird.h"
#include "nest/iface.h"
#include "nest/protocol.h"
#include "nest/route.h"
#include "nest/mpls.h"
#include "nest/cli.h"
#include "conf/conf.h"
#include "filter/filter.h"
#include "filter/data.h"
#include "lib/string.h"
#include "l3vpn.h"
#include "proto/bgp/bgp.h"
/*
* TODO:
* - import/export target reconfiguration
* - check for simple nodes in export route
* - replace pair of channels with shared channel for one address family
* - improve route comparisons in VRFs
* - optional import/export target all
* - optional support for route origins
* - optional automatic assignment of RDs
* - MPLS-in-IP encapsulation
*/
#define EA_BGP_NEXT_HOP EA_CODE(PROTOCOL_BGP, BA_NEXT_HOP)
#define EA_BGP_EXT_COMMUNITY EA_CODE(PROTOCOL_BGP, BA_EXT_COMMUNITY)
#define EA_BGP_MPLS_LABEL_STACK EA_CODE(PROTOCOL_BGP, BA_MPLS_LABEL_STACK)
static inline const struct adata * ea_get_adata(ea_list *e, uint id)
{ eattr *a = ea_find(e, id); return a ? a->u.ptr : &null_adata; }
static inline int
mpls_valid_nexthop(const rta *a)
{
/* MPLS does not support special blackhole targets */
if (a->dest != RTD_UNICAST)
return 0;
/* MPLS does not support ARP / neighbor discovery */
for (const struct nexthop *nh = &a->nh; nh ; nh = nh->next)
if (ipa_zero(nh->gw) && (nh->iface->flags & IF_MULTIACCESS))
return 0;
return 1;
}
static int
l3vpn_import_targets(struct l3vpn_proto *p, const struct adata *list)
{
return (p->import_target_one) ?
ec_set_contains(list, p->import_target->from.val.ec) :
eclist_match_set(list, p->import_target);
}
static struct adata *
l3vpn_export_targets(struct l3vpn_proto *p, const struct adata *src)
{
u32 *s = int_set_get_data(src);
int len = int_set_get_size(src);
struct adata *dst = lp_alloc(tmp_linpool, sizeof(struct adata) + (len + p->export_target_length) * sizeof(u32));
u32 *d = int_set_get_data(dst);
int end = 0;
for (int i = 0; i < len; i += 2)
{
/* Remove existing route targets */
uint type = s[i] >> 16;
if (ec_type_is_rt(type))
continue;
d[end++] = s[i];
d[end++] = s[i+1];
}
/* Add new route targets */
memcpy(d + end, p->export_target_data, p->export_target_length * sizeof(u32));
end += p->export_target_length;
/* Set length */
dst->length = end * sizeof(u32);
return dst;
}
static void
l3vpn_add_ec(const struct f_tree *t, void *P)
{
struct l3vpn_proto *p = P;
ec_put(p->export_target_data, p->export_target_length, t->from.val.ec);
p->export_target_length += 2;
}
static void
l3vpn_prepare_targets(struct l3vpn_proto *p)
{
const struct f_tree *t = p->import_target;
p->import_target_one = !t->left && !t->right && (t->from.val.ec == t->to.val.ec);
uint len = 2 * tree_node_count(p->export_target);
p->export_target_data = mb_alloc(p->p.pool, len * sizeof(u32));
p->export_target_length = 0;
tree_walk(p->export_target, l3vpn_add_ec, p);
ASSERT(p->export_target_length == len);
}
/* Convert 64-bit RD to 32bit source ID, unfortunately it has collisions */
static inline struct rte_src * l3vpn_get_source(struct l3vpn_proto *p, u64 rd)
{ return rt_get_source(&p->p, (u32)(rd >> 32) ^ u32_hash(rd)); }
//{ return p->p.main_source; }
static void
l3vpn_rt_notify(struct proto *P, struct channel *c0, net *net, rte *new, rte *old UNUSED)
{
struct l3vpn_proto *p = (void *) P;
struct rte_src *src = NULL;
struct channel *dst = NULL;
int export;
const net_addr *n0 = net->n.addr;
net_addr *n = alloca(sizeof(net_addr_vpn6));
switch (c0->net_type)
{
case NET_IP4:
net_fill_vpn4(n, net4_prefix(n0), net4_pxlen(n0), p->rd);
src = p->p.main_source;
dst = p->vpn4_channel;
export = 1;
break;
case NET_IP6:
net_fill_vpn6(n, net6_prefix(n0), net6_pxlen(n0), p->rd);
src = p->p.main_source;
dst = p->vpn6_channel;
export = 1;
break;
case NET_VPN4:
net_fill_ip4(n, net4_prefix(n0), net4_pxlen(n0));
src = l3vpn_get_source(p, ((const net_addr_vpn4 *) n0)->rd);
dst = p->ip4_channel;
export = 0;
break;
case NET_VPN6:
net_fill_ip6(n, net6_prefix(n0), net6_pxlen(n0));
src = l3vpn_get_source(p, ((const net_addr_vpn6 *) n0)->rd);
dst = p->ip6_channel;
export = 0;
break;
case NET_MPLS:
return;
}
if (new)
{
const rta *a0 = new->attrs;
rta *a = alloca(RTA_MAX_SIZE);
*a = (rta) {
.source = RTS_L3VPN,
.scope = SCOPE_UNIVERSE,
.dest = a0->dest,
.pref = dst->preference,
.eattrs = a0->eattrs
};
nexthop_link(a, &a0->nh);
/* Do not keep original labels, we may assign new ones */
ea_unset_attr(&a->eattrs, tmp_linpool, 0, EA_MPLS_LABEL);
ea_unset_attr(&a->eattrs, tmp_linpool, 0, EA_MPLS_POLICY);
/* We are crossing VRF boundary, NEXT_HOP is no longer valid */
ea_unset_attr(&a->eattrs, tmp_linpool, 0, EA_BGP_NEXT_HOP);
ea_unset_attr(&a->eattrs, tmp_linpool, 0, EA_BGP_MPLS_LABEL_STACK);
if (export)
{
struct mpls_channel *mc = (void *) p->p.mpls_channel;
ea_set_attr_u32(&a->eattrs, tmp_linpool, EA_MPLS_POLICY, 0, EAF_TYPE_INT, mc->label_policy);
struct adata *ad = l3vpn_export_targets(p, ea_get_adata(a0->eattrs, EA_BGP_EXT_COMMUNITY));
ea_set_attr_ptr(&a->eattrs, tmp_linpool, EA_BGP_EXT_COMMUNITY, 0, EAF_TYPE_EC_SET, ad);
/* Replace MPLS-incompatible nexthop with lookup in VRF table */
if (!mpls_valid_nexthop(a) && p->p.vrf)
{
a->dest = RTD_UNICAST;
a->nh = (struct nexthop) { .iface = p->p.vrf };
}
}
/* Keep original IGP metric as a base for L3VPN metric */
if (!export)
a->igp_metric = a0->igp_metric;
rte *e = rte_get_temp(a, src);
rte_update2(dst, n, e, src);
}
else
{
rte_update2(dst, n, NULL, src);
}
}
static int
l3vpn_preexport(struct channel *C, rte *e)
{
struct l3vpn_proto *p = (void *) C->proto;
struct proto *pp = e->sender->proto;
if (pp == C->proto)
return -1; /* Avoid local loops automatically */
switch (C->net_type)
{
case NET_IP4:
case NET_IP6:
return 0;
case NET_VPN4:
case NET_VPN6:
return l3vpn_import_targets(p, ea_get_adata(e->attrs->eattrs, EA_BGP_EXT_COMMUNITY)) ? 0 : -1;
case NET_MPLS:
return -1;
default:
bug("invalid type");
}
}
static void
l3vpn_reload_routes(struct channel *C)
{
struct l3vpn_proto *p = (void *) C->proto;
/* Route reload on one channel is just refeed on the other */
switch (C->net_type)
{
case NET_IP4:
channel_request_feeding(p->vpn4_channel);
break;
case NET_IP6:
channel_request_feeding(p->vpn6_channel);
break;
case NET_VPN4:
channel_request_feeding(p->ip4_channel);
break;
case NET_VPN6:
channel_request_feeding(p->ip6_channel);
break;
case NET_MPLS:
/* FIXME */
break;
}
}
static inline u32
l3vpn_metric(rte *e)
{
u32 metric = ea_get_int(e->attrs->eattrs, EA_GEN_IGP_METRIC, e->attrs->igp_metric);
return MIN(metric, IGP_METRIC_UNKNOWN);
}
static int
l3vpn_rte_better(rte *new, rte *old)
{
/* This is hack, we should have full BGP-style comparison */
return l3vpn_metric(new) < l3vpn_metric(old);
}
static void
l3vpn_postconfig(struct proto_config *CF)
{
struct l3vpn_config *cf = (void *) CF;
if (!!proto_cf_find_channel(CF, NET_IP4) != !!proto_cf_find_channel(CF, NET_VPN4))
cf_error("For IPv4 L3VPN, both IPv4 and VPNv4 channels must be specified");
if (!!proto_cf_find_channel(CF, NET_IP6) != !!proto_cf_find_channel(CF, NET_VPN6))
cf_error("For IPv6 L3VPN, both IPv6 and VPNv6 channels must be specified");
if (!proto_cf_find_channel(CF, NET_MPLS))
cf_error("MPLS channel not specified");
if (!cf->rd)
cf_error("Route distinguisher not specified");
if (!cf->import_target && !cf->export_target)
cf_error("Route target not specified");
if (!cf->import_target)
cf_error("Import target not specified");
if (!cf->export_target)
cf_error("Export target not specified");
}
static struct proto *
l3vpn_init(struct proto_config *CF)
{
struct proto *P = proto_new(CF);
struct l3vpn_proto *p = (void *) P;
// struct l3vpn_config *cf = (void *) CF;
proto_configure_channel(P, &p->ip4_channel, proto_cf_find_channel(CF, NET_IP4));
proto_configure_channel(P, &p->ip6_channel, proto_cf_find_channel(CF, NET_IP6));
proto_configure_channel(P, &p->vpn4_channel, proto_cf_find_channel(CF, NET_VPN4));
proto_configure_channel(P, &p->vpn6_channel, proto_cf_find_channel(CF, NET_VPN6));
proto_configure_channel(P, &P->mpls_channel, proto_cf_find_channel(CF, NET_MPLS));
P->rt_notify = l3vpn_rt_notify;
P->preexport = l3vpn_preexport;
P->reload_routes = l3vpn_reload_routes;
P->rte_better = l3vpn_rte_better;
return P;
}
static int
l3vpn_start(struct proto *P)
{
struct l3vpn_proto *p = (void *) P;
struct l3vpn_config *cf = (void *) P->cf;
p->rd = cf->rd;
p->import_target = cf->import_target;
p->export_target = cf->export_target;
l3vpn_prepare_targets(p);
proto_setup_mpls_map(P, RTS_L3VPN, 1);
if (P->vrf_set)
P->mpls_map->vrf_iface = P->vrf;
return PS_UP;
}
static int
l3vpn_shutdown(struct proto *P)
{
// struct l3vpn_proto *p = (void *) P;
proto_shutdown_mpls_map(P, 1);
return PS_DOWN;
}
static int
l3vpn_reconfigure(struct proto *P, struct proto_config *CF)
{
struct l3vpn_proto *p = (void *) P;
struct l3vpn_config *cf = (void *) CF;
if (!proto_configure_channel(P, &p->ip4_channel, proto_cf_find_channel(CF, NET_IP4)) ||
!proto_configure_channel(P, &p->ip6_channel, proto_cf_find_channel(CF, NET_IP6)) ||
!proto_configure_channel(P, &p->vpn4_channel, proto_cf_find_channel(CF, NET_VPN4)) ||
!proto_configure_channel(P, &p->vpn6_channel, proto_cf_find_channel(CF, NET_VPN6)) ||
!proto_configure_channel(P, &P->mpls_channel, proto_cf_find_channel(CF, NET_MPLS)))
return 0;
if ((p->rd != cf->rd) ||
!same_tree(p->import_target, cf->import_target) ||
!same_tree(p->export_target, cf->export_target))
return 0;
/*
if (!same_tree(p->import_target, cf->import_target))
{
if (p->vpn4_channel && (p->vpn4_channel->channel_state == CS_UP))
channel_request_feeding(p->vpn4_channel);
if (p->vpn6_channel && (p->vpn6_channel->channel_state == CS_UP))
channel_request_feeding(p->vpn6_channel);
}
if (!same_tree(p->export_target, cf->export_target))
{
if (p->ip4_channel && (p->ip4_channel->channel_state == CS_UP))
channel_request_feeding(p->ip4_channel);
if (p->ip6_channel && (p->ip6_channel->channel_state == CS_UP))
channel_request_feeding(p->ip6_channel);
}
*/
proto_setup_mpls_map(P, RTS_L3VPN, 1);
return 1;
}
static void
l3vpn_copy_config(struct proto_config *dest UNUSED, struct proto_config *src UNUSED)
{
/* Just a shallow copy, not many items here */
}
static void
l3vpn_get_route_info(rte *rte, byte *buf)
{
u32 metric = l3vpn_metric(rte);
if (metric < IGP_METRIC_UNKNOWN)
bsprintf(buf, " (%u/%u)", rte->attrs->pref, metric);
else
bsprintf(buf, " (%u/?)", rte->attrs->pref);
}
struct protocol proto_l3vpn = {
.name = "L3VPN",
.template = "l3vpn%d",
.class = PROTOCOL_L3VPN,
.channel_mask = NB_IP | NB_VPN | NB_MPLS,
.proto_size = sizeof(struct l3vpn_proto),
.config_size = sizeof(struct l3vpn_config),
.postconfig = l3vpn_postconfig,
.init = l3vpn_init,
.start = l3vpn_start,
.shutdown = l3vpn_shutdown,
.reconfigure = l3vpn_reconfigure,
.copy_config = l3vpn_copy_config,
.get_route_info = l3vpn_get_route_info
};
void
l3vpn_build(void)
{
proto_build(&proto_l3vpn);
}

36
proto/l3vpn/l3vpn.h Normal file
View File

@ -0,0 +1,36 @@
/*
* BIRD -- BGP/MPLS IP Virtual Private Networks (L3VPN)
*
* (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.
*/
#ifndef _BIRD_L3VPN_H_
#define _BIRD_L3VPN_H_
struct l3vpn_config {
struct proto_config c;
u64 rd;
struct f_tree *import_target;
struct f_tree *export_target;
};
struct l3vpn_proto {
struct proto p;
struct channel *ip4_channel;
struct channel *ip6_channel;
struct channel *vpn4_channel;
struct channel *vpn6_channel;
u64 rd;
struct f_tree *import_target;
struct f_tree *export_target;
u32 *export_target_data;
uint export_target_length;
uint import_target_one;
};
#endif