--- /dev/null
+/*
+ * BIRD -- Layer3 VPN Protocol Configuration
+ *
+ * (c) 2011-2013 Yandex, LLC
+ * Author: Alexander V. Chernikov <melifaro@yandex-team.ru>
+ *
+ * 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)
+
+CF_DECLS
+
+CF_KEYWORDS(L3VPN, RD, LABEL, AUTO)
+
+CF_GRAMMAR
+
+CF_ADDTO(proto, l3vpn_proto l3vpn_proto_end '}' )
+
+l3vpn_proto_start: proto_start L3VPN {
+ this_proto = proto_config_new(&proto_l3vpn, $1);
+};
+
+l3vpn_proto:
+ l3vpn_proto_start proto_name '{'
+ | l3vpn_proto proto_item ';'
+ | l3vpn_proto proto_channel ';' {
+ switch (((struct channel_config *)$2)->net_type) {
+ case NET_IP4:
+ case NET_IP6:
+ if (L3VPN_CFG->ip)
+ cf_error("Multiple IP channels not supported");
+ if (L3VPN_CFG->vpn &&
+ ((L3VPN_CFG->vpn->net_type == NET_VPN4 && ((struct channel_config *)$2)->net_type == NET_IP6)
+ || (L3VPN_CFG->vpn->net_type == NET_VPN6 && ((struct channel_config *)$2)->net_type == NET_IP4)))
+ cf_error("Mismatched IP/VPN families");
+
+ L3VPN_CFG->ip = $2;
+ break;
+ case NET_VPN4:
+ case NET_VPN6:
+ if (L3VPN_CFG->vpn)
+ cf_error("Multiple VPN channels not supported");
+
+ if (L3VPN_CFG->ip &&
+ ((L3VPN_CFG->ip->net_type == NET_IP4 && ((struct channel_config *)$2)->net_type == NET_VPN6)
+ || (L3VPN_CFG->ip->net_type == NET_IP6 && ((struct channel_config *)$2)->net_type == NET_VPN4)))
+ cf_error("Mismatched IP/VPN families");
+
+ L3VPN_CFG->vpn = $2;
+ break;
+ case NET_MPLS:
+ if (L3VPN_CFG->mpls)
+ cf_error("Multiple MPLS channels not supported");
+ else
+ L3VPN_CFG->mpls = $2;
+ break;
+ default:
+ cf_error("Unsupported channel type");
+ }
+}
+ | l3vpn_proto RD VPN_RD ';' { L3VPN_CFG->rd = $3; }
+;
+
+l3vpn_proto_end:
+{
+ if ((!L3VPN_CFG->ip) || (!L3VPN_CFG->vpn) || (!L3VPN_CFG->mpls))
+ cf_error("Need all IP, VPN and MPLS channels configured.");
+}
+
+
+CF_CODE
+
+CF_END
--- /dev/null
+#include "l3vpn.h"
+
+static void
+l3vpn_alloc_mpls_label(struct l3vpn_proto *p, struct l3vpn_ip_to_mpls *litm, ip_addr gw, struct iface *iface)
+{
+ u32 label;
+ net *n;
+
+ for (label = p->last_label + 1; label <= MPLS_LABEL_MAX; label++) {
+ net_addr_union nu = { .mpls = NET_ADDR_MPLS(label) };
+ n = net_get(p->mpls->table, &nu.n);
+ if (!n->routes)
+ goto have_label;
+ }
+
+ for (label = 16; label <= p->last_label; label++) {
+ net_addr_union nu = { .mpls = NET_ADDR_MPLS(label) };
+ n = net_get(p->mpls->table, &nu.n);
+ if (!n->routes)
+ goto have_label;
+ }
+
+ return;
+
+have_label:;
+ p->last_label = label;
+ rta a = {};
+ a.gw = gw;
+ a.src = p->p.main_source;
+ a.iface = iface;
+ rte *e = rte_get_temp(rta_lookup(&a));
+ e->net = n;
+ rte_update2(p->mpls, n, e, p->p.main_source);
+
+ litm->ad.length = sizeof(u32);
+ memcpy(litm->ad.data, &label, sizeof(u32));
+}
+
+static ea_list *
+l3vpn_get_mpls_label_ea(struct l3vpn_proto *p, ip_addr gw, struct iface *iface)
+{
+ net_addr_union nu;
+ net_fill_ip_host(&nu.n, gw);
+ struct l3vpn_ip_to_mpls *litm = fib_get(&p->iptompls, &nu.n);
+ if (!litm->ad.length)
+ l3vpn_alloc_mpls_label(p, litm, gw, iface);
+
+ if (!litm->ad.length)
+ return NULL;
+
+ return &litm->el;
+}
+
+static void
+l3vpn_iptompls_init(void *ptr)
+{
+ struct l3vpn_ip_to_mpls *litm = ptr;
+ litm->el.count = 1;
+ litm->ea.id = EA_GEN_MPLS_STACK;
+ litm->ea.type = EAF_TYPE_INT_SET;
+ litm->ea.u.ptr = &litm->ad;
+}
+
+static void
+l3vpn_rt_notify(struct proto *P, struct channel *ch, net *n, rte *new, rte *old, ea_list *ea)
+{
+ struct l3vpn_proto *p = (struct l3vpn_proto *) P;
+
+ if (!new && !old)
+ return;
+
+ if (ch == p->mpls) {
+ TRACE(D_EVENTS, "Ignoring MPLS route to %N", &n->n.addr[0]);
+ return;
+ }
+
+ if (new && ch == new->sender) {
+ TRACE(D_EVENTS, "Ignoring back-bounced route to %N", &n->n.addr[0]);
+ return;
+ }
+
+ net_addr_union new_dst = { .n = n->n.addr[0] };
+ if (ch == p->vpn) {
+ switch (new_dst.n.type) {
+ case NET_VPN4:
+ if (new_dst.vpn4.rd != p->rd) {
+ TRACE(D_EVENTS, "Ignoring route to %N with alien RD", &new_dst);
+ return; /* Ignoring routes with alien RD */
+ }
+ net_fill_ip4(&new_dst.n, net4_prefix(&new_dst.n), net4_pxlen(&new_dst.n));
+ break;
+ case NET_VPN6:
+ if (new_dst.vpn6.rd != p->rd) {
+ TRACE(D_EVENTS, "Ignoring route to %N with alien RD", &new_dst);
+ return; /* Ignoring routes with alien RD */
+ }
+ new_dst.vpn6.type = NET_IP6;
+ net_fill_ip6(&new_dst.n, net6_prefix(&new_dst.n), net6_pxlen(&new_dst.n));
+ break;
+ default:
+ ASSERT(0);
+ }
+ TRACE(D_EVENTS, "Converted VPN route %N to IP route %N", &n->n.addr[0], &new_dst);
+ }
+
+ if (ch == p->ip) {
+ switch (new_dst.n.type) {
+ case NET_IP4:
+ net_fill_vpn4(&new_dst.n, net4_prefix(&new_dst.n), net4_pxlen(&new_dst.n), p->rd);
+ break;
+ case NET_IP6:
+ net_fill_vpn6(&new_dst.n, net6_prefix(&new_dst.n), net6_pxlen(&new_dst.n), p->rd);
+ break;
+ default:
+ ASSERT(0);
+ }
+ TRACE(D_EVENTS, "Converted IP route %N to VPN route %N", &n->n.addr[0], &new_dst);
+ }
+
+ rte *e = NULL;
+ if (new) {
+ rta a;
+ memcpy(&a, new->attrs, sizeof(rta));
+ a.hostentry = NULL;
+
+ ea_list *mpls_ea = l3vpn_get_mpls_label_ea(p, a.gw, a.iface);
+
+ a.eattrs = mpls_ea;
+ mpls_ea->next = ea;
+
+ e = rte_get_temp(rta_lookup(&a));
+ e->pflags = 0;
+
+ /* Copy protocol specific embedded attributes. */
+ memcpy(&(e->u), &(new->u), sizeof(e->u));
+ e->pref = new->pref;
+ e->pflags = new->pflags;
+
+ /* FIXME: Add also VPN's MPLS label if ch == p->ip */
+ }
+
+ struct rte_src *src = (new ? new->attrs->src : old->attrs->src);
+
+ if (ch == p->ip) {
+ net *nn = net_get(p->vpn->table, &new_dst.n);
+ if (e) e->net = nn;
+ rte_update2(p->vpn, nn, e, src);
+ } else {
+ net *nn = net_get(p->ip->table, &new_dst.n);
+ if (e) e->net = nn;
+ rte_update2(p->ip, nn, e, src);
+ }
+}
+
+static struct proto *
+l3vpn_init(struct proto_config *CF)
+{
+ struct l3vpn_config *cf = (struct l3vpn_config *) CF;
+ struct proto *P = proto_new(CF);
+ struct l3vpn_proto *p = (struct l3vpn_proto *) P;
+
+ p->vpn = proto_add_channel(P, cf->vpn);
+ p->ip = proto_add_channel(P, cf->ip);
+ p->mpls = proto_add_channel(P, cf->mpls);
+ p->rd = cf->rd;
+ p->last_label = 16;
+
+ P->rt_notify = l3vpn_rt_notify;
+
+
+ return P;
+}
+
+static int
+l3vpn_start(struct proto *P)
+{
+ struct l3vpn_proto *p = (struct l3vpn_proto *) P;
+ fib_init(&p->iptompls, P->pool, p->ip->net_type, sizeof(struct l3vpn_ip_to_mpls),
+ OFFSETOF(struct l3vpn_ip_to_mpls, n), 0, l3vpn_iptompls_init);
+
+ return PS_UP;
+}
+
+struct protocol proto_l3vpn = {
+ .name = "L3VPN",
+ .template = "l3vpn%d",
+ .proto_size = sizeof(struct l3vpn_proto),
+ .config_size = sizeof(struct l3vpn_config),
+ .channel_mask = NB_IP4 | NB_IP6 | NB_VPN4 | NB_VPN6 | NB_MPLS,
+ .init = l3vpn_init,
+ .start = l3vpn_start,
+};