mirror of
https://gitlab.nic.cz/labs/bird.git
synced 2024-12-22 09:41:54 +00:00
Merge branch 'mq-aggregator-for-v3' into thread-next
This commit is contained in:
commit
00e40a6b80
@ -307,7 +307,7 @@ if test "$enable_mpls_kernel" != no ; then
|
||||
fi
|
||||
|
||||
# temporarily removed "mrt" from all_protocols to speed up 3.0-alpha1 release
|
||||
all_protocols="aggregator bfd babel bgp ospf perf pipe radv rip rpki static"
|
||||
all_protocols="aggregator bfd babel bgp l3vpn ospf perf pipe radv rip rpki static"
|
||||
all_protocols=`echo $all_protocols | sed 's/ /,/g'`
|
||||
|
||||
if test "$with_protocols" = all ; then
|
||||
@ -319,6 +319,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])
|
||||
|
@ -392,7 +392,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
|
||||
@ -747,6 +747,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); }
|
||||
|
27
lib/route.h
27
lib/route.h
@ -225,8 +225,9 @@ struct nexthop_adata {
|
||||
#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 /* A standard next hop */
|
||||
@ -355,6 +356,13 @@ static inline eattr *ea_find_by_name(ea_list *l, const char *name)
|
||||
(ea ? *((const ip_addr *) ea->u.ptr->data) : (_def)); \
|
||||
})
|
||||
|
||||
#define ea_get_adata(_l, _ident) ({ \
|
||||
struct ea_class *cls = ea_class_find((_ident)); \
|
||||
ASSERT_DIE(!(cls->type & EAF_EMBEDDED)); \
|
||||
const eattr *ea = ea_find((_l), cls->id); \
|
||||
(ea ? ea->u.ptr : &null_adata); \
|
||||
})
|
||||
|
||||
eattr *ea_walk(struct ea_walk_state *s, uint id, uint max);
|
||||
void ea_dump(ea_list *);
|
||||
int ea_same(ea_list *x, ea_list *y); /* Test whether two ea_lists are identical */
|
||||
@ -516,15 +524,18 @@ int nexthop_is_sorted(struct nexthop_adata *x);
|
||||
|
||||
#define NEXTHOP_IS_REACHABLE(nhad) ((nhad)->ad.length > NEXTHOP_DEST_SIZE)
|
||||
|
||||
static inline struct nexthop_adata *
|
||||
rte_get_nexthops(rte *r)
|
||||
{
|
||||
eattr *nhea = ea_find(r->attrs, &ea_gen_nexthop);
|
||||
return nhea ? SKIP_BACK(struct nexthop_adata, ad, nhea->u.ptr) : NULL;
|
||||
}
|
||||
|
||||
/* Route has regular, reachable nexthop (i.e. not RTD_UNREACHABLE and like) */
|
||||
static inline int rte_is_reachable(rte *r)
|
||||
{
|
||||
eattr *nhea = ea_find(r->attrs, &ea_gen_nexthop);
|
||||
if (!nhea)
|
||||
return 0;
|
||||
|
||||
struct nexthop_adata *nhad = (void *) nhea->u.ptr;
|
||||
return NEXTHOP_IS_REACHABLE(nhad);
|
||||
struct nexthop_adata *nhad = rte_get_nexthops(r);
|
||||
return nhad && NEXTHOP_IS_REACHABLE(nhad);
|
||||
}
|
||||
|
||||
static inline int nhea_dest(eattr *nhea)
|
||||
|
@ -94,7 +94,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;
|
||||
|
||||
/*
|
||||
|
@ -737,6 +737,8 @@ void ea_show_nexthop_list(struct cli *c, struct nexthop_adata *nhad);
|
||||
#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 */
|
||||
#define DEF_PREF_UNKNOWN 0 /* Routes with no preference set */
|
||||
|
||||
|
@ -92,6 +92,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
1
proto/l3vpn/Doc
Normal file
@ -0,0 +1 @@
|
||||
S l3vpn.c
|
6
proto/l3vpn/Makefile
Normal file
6
proto/l3vpn/Makefile
Normal 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
101
proto/l3vpn/config.Y
Normal 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
|
508
proto/l3vpn/l3vpn.c
Normal file
508
proto/l3vpn/l3vpn.c
Normal file
@ -0,0 +1,508 @@
|
||||
/*
|
||||
* 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/pipe/pipe.h"
|
||||
|
||||
#include <stdbool.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
|
||||
*/
|
||||
|
||||
static struct ea_class *ea_bgp_next_hop,
|
||||
*ea_bgp_ext_community,
|
||||
*ea_bgp_mpls_label_stack;
|
||||
|
||||
static inline int
|
||||
mpls_valid_nexthop(struct nexthop_adata *nhad)
|
||||
{
|
||||
/* MPLS does not support special blackhole targets */
|
||||
if (!NEXTHOP_IS_REACHABLE(nhad))
|
||||
return 0;
|
||||
|
||||
/* MPLS does not support ARP / neighbor discovery */
|
||||
NEXTHOP_WALK(nh, nhad)
|
||||
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, const net_addr *n0, rte *new, const rte *old UNUSED)
|
||||
{
|
||||
struct l3vpn_proto *p = (void *) P;
|
||||
struct rte_src *src = NULL;
|
||||
struct channel *dst = NULL;
|
||||
int export;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
const struct adata *ecad = ea_get_adata(new->attrs, ea_bgp_ext_community);
|
||||
struct nexthop_adata *nhad_orig = rte_get_nexthops(new);
|
||||
|
||||
if (new)
|
||||
{
|
||||
new->src = src;
|
||||
|
||||
ea_set_attr_u32(&new->attrs, &ea_gen_source, 0, RTS_L3VPN);
|
||||
ea_set_attr_u32(&new->attrs, &ea_gen_preference, 0, dst->preference);
|
||||
|
||||
/* Do not keep original labels, we may assign new ones */
|
||||
ea_unset_attr(&new->attrs, 0, &ea_gen_mpls_label);
|
||||
ea_unset_attr(&new->attrs, 0, &ea_gen_mpls_policy);
|
||||
|
||||
/* We are crossing VRF boundary, NEXT_HOP is no longer valid */
|
||||
ea_unset_attr(&new->attrs, 0, ea_bgp_next_hop);
|
||||
ea_unset_attr(&new->attrs, 0, ea_bgp_mpls_label_stack);
|
||||
|
||||
/* Hostentry also validn't */
|
||||
ea_unset_attr(&new->attrs, 0, &ea_gen_hostentry);
|
||||
|
||||
if (export)
|
||||
{
|
||||
struct mpls_channel *mc = (void *) p->p.mpls_channel;
|
||||
ea_set_attr_u32(&new->attrs, &ea_gen_mpls_policy, 0, mc->label_policy);
|
||||
|
||||
ea_set_attr(&new->attrs, EA_LITERAL_DIRECT_ADATA(
|
||||
ea_bgp_ext_community, 0, l3vpn_export_targets(p, ecad)));
|
||||
|
||||
/* Replace MPLS-incompatible nexthop with lookup in VRF table */
|
||||
if (!nhad_orig || !mpls_valid_nexthop(nhad_orig) && p->p.vrf)
|
||||
{
|
||||
struct nexthop_adata nhad = {
|
||||
.nh.iface = p->p.vrf,
|
||||
.ad.length = sizeof nhad - sizeof nhad.ad,
|
||||
};
|
||||
ea_set_attr_data(&new->attrs, &ea_gen_nexthop, 0, nhad.ad.data, nhad.ad.length);
|
||||
}
|
||||
|
||||
/* Drop original IGP metric on export;
|
||||
* kept on import as a base for L3VPN metric */
|
||||
ea_unset_attr(&new->attrs, 0, &ea_gen_igp_metric);
|
||||
}
|
||||
|
||||
rte_update(dst, n, new, src);
|
||||
}
|
||||
else
|
||||
{
|
||||
rte_update(dst, n, NULL, src);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
l3vpn_preexport(struct channel *C, rte *e)
|
||||
{
|
||||
struct l3vpn_proto *p = (void *) C->proto;
|
||||
|
||||
if (&C->in_req == e->sender->req)
|
||||
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, ea_bgp_ext_community)) ? 0 : -1;
|
||||
|
||||
case NET_MPLS:
|
||||
return -1;
|
||||
|
||||
default:
|
||||
bug("invalid type");
|
||||
}
|
||||
}
|
||||
|
||||
/* TODO: unify the code between l3vpn and pipe */
|
||||
void pipe_import_by_refeed_free(struct channel_feeding_request *cfr);
|
||||
|
||||
static int
|
||||
l3vpn_reload_routes(struct channel *C, struct channel_import_request *cir)
|
||||
{
|
||||
struct l3vpn_proto *p = (void *) C->proto;
|
||||
struct channel *feed = NULL;
|
||||
|
||||
/* Route reload on one channel is just refeed on the other */
|
||||
switch (C->net_type)
|
||||
{
|
||||
case NET_IP4:
|
||||
feed = p->vpn4_channel;
|
||||
break;
|
||||
|
||||
case NET_IP6:
|
||||
feed = p->vpn6_channel;
|
||||
break;
|
||||
|
||||
case NET_VPN4:
|
||||
feed = p->ip4_channel;
|
||||
break;
|
||||
|
||||
case NET_VPN6:
|
||||
feed = p->ip6_channel;
|
||||
break;
|
||||
|
||||
case NET_MPLS:
|
||||
/* FIXME */
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (cir->trie)
|
||||
{
|
||||
struct import_to_export_reload *reload = lp_alloc(cir->trie->lp, sizeof *reload);
|
||||
*reload = (struct import_to_export_reload) {
|
||||
.cir = cir,
|
||||
.cfr = {
|
||||
.type = CFRT_AUXILIARY,
|
||||
.done = pipe_import_by_refeed_free,
|
||||
.trie = cir->trie,
|
||||
},
|
||||
};
|
||||
channel_request_feeding(feed, &reload->cfr);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Route reload on one channel is just refeed on the other */
|
||||
channel_request_feeding_dynamic(feed, CFRT_DIRECT);
|
||||
cir->done(cir);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
l3vpn_rte_better(const rte *new, const rte *old)
|
||||
{
|
||||
/* This is hack, we should have full BGP-style comparison */
|
||||
return rt_get_igp_metric(new) < rt_get_igp_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)
|
||||
{
|
||||
ASSERT_DIE(the_bird_locked());
|
||||
|
||||
/* Resolve registered BGP attribute classes once */
|
||||
static bool bgp_attributes_resolved = 0;
|
||||
if (!bgp_attributes_resolved)
|
||||
{
|
||||
ea_bgp_next_hop = ea_class_find_by_name("bgp_next_hop");
|
||||
ea_bgp_ext_community = ea_class_find_by_name("bgp_ext_community");
|
||||
ea_bgp_mpls_label_stack = ea_class_find_by_name("bgp_mpls_label_stack");
|
||||
bgp_attributes_resolved = 1;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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(const rte *rte, byte *buf)
|
||||
{
|
||||
u32 metric = rt_get_igp_metric(rte);
|
||||
u32 pref = rt_get_preference(rte);
|
||||
|
||||
if (metric < IGP_METRIC_UNKNOWN)
|
||||
bsprintf(buf, " (%u/%u)", pref, metric);
|
||||
else
|
||||
bsprintf(buf, " (%u/?)", pref);
|
||||
}
|
||||
|
||||
struct rte_owner_class l3vpn_rte_owner_class = {
|
||||
.get_route_info = l3vpn_get_route_info,
|
||||
.rte_better = l3vpn_rte_better,
|
||||
};
|
||||
|
||||
struct protocol proto_l3vpn = {
|
||||
.name = "L3VPN",
|
||||
.template = "l3vpn%d",
|
||||
.channel_mask = NB_IP | NB_VPN | NB_MPLS,
|
||||
.proto_size = sizeof(struct l3vpn_proto),
|
||||
.config_size = sizeof(struct l3vpn_config),
|
||||
.startup = PROTOCOL_STARTUP_CONNECTOR,
|
||||
.postconfig = l3vpn_postconfig,
|
||||
.init = l3vpn_init,
|
||||
.start = l3vpn_start,
|
||||
.shutdown = l3vpn_shutdown,
|
||||
.reconfigure = l3vpn_reconfigure,
|
||||
.copy_config = l3vpn_copy_config,
|
||||
};
|
||||
|
||||
void
|
||||
l3vpn_build(void)
|
||||
{
|
||||
proto_build(&proto_l3vpn);
|
||||
}
|
36
proto/l3vpn/l3vpn.h
Normal file
36
proto/l3vpn/l3vpn.h
Normal 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
|
Loading…
Reference in New Issue
Block a user