summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--filter/config.Y149
-rw-r--r--filter/data.c38
-rw-r--r--filter/data.h11
-rw-r--r--filter/decl.m43
-rw-r--r--filter/f-inst.c78
-rw-r--r--lib/Makefile2
-rw-r--r--lib/tunnel_encaps.c876
-rw-r--r--lib/tunnel_encaps.h63
-rw-r--r--lib/unaligned.h3
-rw-r--r--nest/Makefile2
-rw-r--r--nest/a-tlv.c421
-rw-r--r--nest/attrs.h67
-rw-r--r--nest/iface.h1
-rw-r--r--nest/route.h1
-rw-r--r--proto/aggregator/aggregator.c2
-rw-r--r--sysdep/unix/main.c2
16 files changed, 1712 insertions, 7 deletions
diff --git a/filter/config.Y b/filter/config.Y
index 9345cc21..4f857350 100644
--- a/filter/config.Y
+++ b/filter/config.Y
@@ -197,6 +197,138 @@ f_new_pair_set(int fa, int ta, int fb, int tb)
#define LC_ALL 0xFFFFFFFF
static struct f_tree *
+f_new_sub_tlv_int_item(u32 type, u32 v1)
+{
+ struct f_tree *t = f_new_tree();
+ t->right = t;
+ t->from.type = t->to.type = T_SUBTLV;
+ struct te_subtlv v;
+ v.type = type;
+ switch (type)
+ {
+ case TLV_COLOR:
+ v.u.color = v1;
+ break;
+ case TLV_UDP_DEST_PORT:
+ v.u.udp_dest_port = v1;
+ break;
+ default:
+ cf_error("Unsupported sub-TLV type: %d", type);
+ }
+ t->from.val.st = v;
+ t->to.val.st = v;
+ return t;
+}
+
+static struct f_tree *
+f_new_sub_tlv_tunnel_ep(u32 type, ip_addr ip)
+{
+ struct f_tree *t;
+
+ if (type != TLV_TUNNEL_ENDPOINT)
+ cf_error("Expected sub-TLV type %d, got %d", TLV_TUNNEL_ENDPOINT, type);
+
+ t = f_new_tree();
+ t->right = t;
+ t->from.type = t->to.type = T_SUBTLV;
+ struct te_subtlv v;
+ v.type = TLV_TUNNEL_ENDPOINT;
+ v.u.tunnel_endpoint.reserved = 0;
+ v.u.tunnel_endpoint.af = ipa_is_ip4(ip) ? AFI_IPV4 : AFI_IPV6;
+ v.u.tunnel_endpoint.ip = ip;
+ t->from.val.st = v;
+ t->to.val.st = v;
+ return t;
+}
+
+static struct f_tree *
+f_new_sub_tlv_encap(u32 st_type, u32 type, const struct adata *bytes)
+{
+ struct f_tree *t;
+
+ if (st_type != TLV_ENCAPSULATION)
+ cf_error("Expected sub-TLV type %d, got %d", TLV_ENCAPSULATION, type);
+
+ t = f_new_tree();
+ t->right = t;
+ t->from.type = t->to.type = T_SUBTLV;
+ struct te_subtlv v;
+ v.type = TLV_ENCAPSULATION;
+ v.u.tunnel_encap.type = type;
+ v.u.tunnel_encap.data = bytes->data;
+ v.u.tunnel_encap.length = bytes->length;
+ t->from.val.st = v;
+ t->to.val.st = v;
+ return t;
+}
+
+static struct f_tree *
+f_new_sub_tlv_unknown(u32 type, const struct adata *bytes)
+{
+ struct f_tree *t;
+
+ switch(type)
+ {
+ case TLV_TUNNEL_ENDPOINT:
+ case TLV_ENCAPSULATION:
+ case TLV_UDP_DEST_PORT:
+ case TLV_COLOR:
+ cf_error("Expected an unkonwn sub-TLV type got %d", type);
+ }
+
+ t = f_new_tree();
+ t->right = t;
+ t->from.type = t->to.type = T_SUBTLV;
+ struct te_subtlv v;
+ v.type = type;
+ v.u.unknown.data = bytes->data;
+ v.u.unknown.length = bytes->length;
+ t->from.val.st = v;
+ t->to.val.st = v;
+ return t;
+}
+
+static struct f_tree *
+f_new_sub_tlv_item(u32 type, struct f_inst *v1)
+{
+ struct f_val val;
+ enum filter_return ret = f_eval(f_linearize(v1, 1), cfg_mem, &val);
+
+ if (ret > F_RETURN)
+ cf_error("Runtime error while evaluating expression; see log for details");
+
+ switch(val.type)
+ {
+ case T_INT:
+ return f_new_sub_tlv_int_item(type, val.val.i);
+ case T_IP:
+ return f_new_sub_tlv_tunnel_ep(type, val.val.ip);
+ case T_BYTESTRING:
+ return f_new_sub_tlv_unknown(type, val.val.bs);
+ default:
+ cf_error("Integer or IP address expression expected");
+ }
+}
+
+static struct f_tree *
+f_new_sub_tlv_item2(u32 type, u32 v1, struct f_inst *v2)
+{
+ struct f_val val;
+ enum filter_return ret = f_eval(f_linearize(v2, 1), cfg_mem, &val);
+
+ if (ret > F_RETURN)
+ cf_error("Runtime error while evaluating expression; see log for details");
+
+ switch(val.type)
+ {
+ case T_BYTESTRING:
+ return f_new_sub_tlv_encap(type, v1, val.val.bs);
+ default:
+ cf_error("Integer or bytestring expression expected");
+ }
+}
+
+static struct f_tree *
f_new_ec_item(u32 kind, u32 ipv4_used, u32 key, u32 vf, u32 vt)
{
u64 fm, to;
@@ -358,7 +490,7 @@ CF_KEYWORDS_EXCLUSIVE(IN)
CF_KEYWORDS(FUNCTION, PRINT, PRINTN, UNSET, RETURN,
ACCEPT, REJECT, ERROR,
INT, BOOL, IP, PREFIX, RD, PAIR, QUAD, EC, LC,
- SET, STRING, BYTESTRING, BGPMASK, BGPPATH, CLIST, ECLIST, LCLIST,
+ SET, STRING, BYTESTRING, BGPMASK, BGPPATH, CLIST, ECLIST, LCLIST, TLVLIST,
IF, THEN, ELSE, CASE,
FOR, DO,
TRUE, FALSE, RT, RO, UNKNOWN, GENERIC,
@@ -371,7 +503,7 @@ CF_KEYWORDS(FUNCTION, PRINT, PRINTN, UNSET, RETURN,
EMPTY,
FILTER, WHERE, EVAL, ATTRIBUTE,
FROM_HEX,
- BT_ASSERT, BT_TEST_SUITE, BT_CHECK_ASSIGN, BT_TEST_SAME, FORMAT)
+ BT_ASSERT, BT_TEST_SUITE, BT_CHECK_ASSIGN, BT_TEST_SAME, SUBTLV, FORMAT)
%nonassoc THEN
%nonassoc ELSE
@@ -388,7 +520,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 ec_items 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 sub_tlv_item
%type <trie> fprefix_set
%type <v> set_atom switch_atom fipa
%type <px> fprefix
@@ -468,6 +600,8 @@ type:
| ECLIST { $$ = T_ECLIST; }
| LCLIST { $$ = T_LCLIST; }
| ROUTE { $$ = T_ROUTE; }
+ | SUBTLV { $$ = T_SUBTLV; }
+ | TLVLIST { $$ = T_TLVLIST; }
| type SET {
switch ($1) {
case T_INT:
@@ -477,6 +611,7 @@ type:
case T_LC:
case T_RD:
case T_IP:
+ case T_SUBTLV:
$$ = T_SET;
break;
@@ -701,10 +836,16 @@ lc_item:
{ $$ = f_new_lc_item($2, $10, $4, $12, $6, $14); }
;
+sub_tlv_item:
+ '(' SUBTLV ',' cnum ',' term ')' { $$ = f_new_sub_tlv_item($4, $6); }
+ | '(' SUBTLV ',' cnum ',' cnum ',' term ')' { $$ = f_new_sub_tlv_item2($4, $6, $8); }
+ ;
+
set_item:
pair_item
| ec_item
| lc_item
+ | sub_tlv_item
| set_atom { $$ = f_new_item($1, $1); }
| set_atom DDOT set_atom { $$ = f_new_item($1, $3); }
;
@@ -713,6 +854,7 @@ switch_item:
pair_item
| ec_item
| lc_item
+ | sub_tlv_item
| switch_atom { $$ = f_new_item($1, $1); }
| switch_atom DDOT switch_atom { $$ = f_new_item($1, $3); }
;
@@ -938,6 +1080,7 @@ term:
| '-' EMPTY '-' { $$ = f_new_inst(FI_CONSTANT, val_empty(T_CLIST)); }
| '-' '-' EMPTY '-' '-' { $$ = f_new_inst(FI_CONSTANT, val_empty(T_ECLIST)); }
| '-' '-' '-' EMPTY '-' '-' '-' { $$ = f_new_inst(FI_CONSTANT, val_empty(T_LCLIST)); }
+ | '-' '-' '-' '-' EMPTY '-' '-' '-' '-' { $$ = f_new_inst(FI_CONSTANT, val_empty(T_TLVLIST)); }
| PREPEND '(' term ',' term ')' { $$ = f_dispatch_method_x("prepend", $3->type, $3, $5); }
| ADD '(' term ',' term ')' { $$ = f_dispatch_method_x("add", $3->type, $3, $5); }
diff --git a/filter/data.c b/filter/data.c
index e268a8ec..d2347684 100644
--- a/filter/data.c
+++ b/filter/data.c
@@ -8,6 +8,7 @@
*
*/
+//#define LOCAL_DEBUG
#include "nest/bird.h"
#include "lib/lists.h"
#include "lib/resource.h"
@@ -16,6 +17,7 @@
#include "lib/unaligned.h"
#include "lib/net.h"
#include "lib/ip.h"
+#include "lib/tunnel_encaps.h"
#include "nest/route.h"
#include "nest/protocol.h"
#include "nest/iface.h"
@@ -55,6 +57,8 @@ static const char * const f_type_str[] = {
[T_ECLIST] = "eclist",
[T_LC] = "lc",
[T_LCLIST] = "lclist",
+ [T_SUBTLV] = "subtlv",
+ [T_TLVLIST] = "tlvlist",
[T_RD] = "rd",
[T_ROUTE] = "route",
@@ -89,7 +93,7 @@ f_type_element_type(enum f_type t)
const struct f_trie f_const_empty_trie = { .ipv4 = -1, };
const struct f_val f_const_empty_prefix_set = {
.type = T_PREFIX_SET,
- .val.ti = &f_const_empty_trie,
+ .val.ti = &f_const_empty_trie
};
static struct adata *
@@ -309,6 +313,8 @@ val_same(const struct f_val *v1, const struct f_val *v2)
return v1->val.rte == v2->val.rte;
case T_ROUTES_BLOCK:
return v1->val.ad == v2->val.ad;
+ case T_TLVLIST:
+ return tlvlist_same(v1->val.tl, v2->val.tl);
default:
bug("Invalid type in val_same(): %x", v1->type);
}
@@ -415,6 +421,18 @@ lclist_match_set(const struct adata *list, const struct f_tree *set)
return 0;
}
+static int
+tlvlist_match_set(const struct adata *list, const struct f_tree *set)
+{
+ if (!list)
+ return 0;
+
+ if (!subtlv_set_type(set))
+ return F_CMP_ERROR;
+
+ return 1;
+}
+
const struct adata *
clist_filter(struct linpool *pool, const struct adata *list, const struct f_val *set, int pos)
{
@@ -516,6 +534,20 @@ lclist_filter(struct linpool *pool, const struct adata *list, const struct f_val
return res;
}
+#if 0
+const struct te_tlvlist *
+tlvlist_filter(struct linpool *pool, const struct te_tlvlist *list, const struct f_val *set, int pos)
+{
+ if (!list)
+ return NULL;
+
+ /* FIXME */
+
+ return NULL;
+}
+#endif
+
+
/**
* val_in_range - implement |~| operator
* @v1: element
@@ -576,6 +608,9 @@ val_in_range(const struct f_val *v1, const struct f_val *v2)
if (v1->type == T_LCLIST)
return lclist_match_set(v1->val.ad, v2->val.t);
+ if (v1->type == T_TLVLIST)
+ return tlvlist_match_set(v1->val.ad, v2->val.t);
+
if (v1->type == T_PATH)
return as_path_match_set(v1->val.ad, v2->val.t);
@@ -640,6 +675,7 @@ val_format(const struct f_val *v, buffer *buf)
case T_CLIST: int_set_format(v->val.ad, 1, -1, buf2, 1000); buffer_print(buf, "(clist %s)", buf2); return;
case T_ECLIST: ec_set_format(v->val.ad, -1, buf2, 1000); buffer_print(buf, "(eclist %s)", buf2); return;
case T_LCLIST: lc_set_format(v->val.ad, -1, buf2, 1000); buffer_print(buf, "(lclist %s)", buf2); return;
+ case T_TLVLIST: tlv_set_format(v->val.tl, -1, buf2, 1000); buffer_print(buf, "(tlvlist %s)", buf2); return;
case T_PATH_MASK: pm_format(v->val.path_mask, buf); return;
case T_ROUTE: rte_format(v->val.rte, buf); return;
case T_ROUTES_BLOCK: rte_block_format(v->val.rte, buf); return;
diff --git a/filter/data.h b/filter/data.h
index 5dff7e85..a6923eb6 100644
--- a/filter/data.h
+++ b/filter/data.h
@@ -64,6 +64,8 @@ enum f_type {
T_RD = 0x2a, /* Route distinguisher for VPN addresses */
T_PATH_MASK_ITEM = 0x2b, /* Path mask item for path mask constructors */
T_BYTESTRING = 0x2c,
+ T_SUBTLV = 0x2d, /* Tunnel encapsulation sub-TLV */
+ T_TLVLIST = 0x2e, /* Tunnel encapsulation TLV list */
T_ROUTE = 0x78,
T_ROUTES_BLOCK = 0x79,
@@ -96,6 +98,9 @@ struct f_val {
const struct f_path_mask *path_mask;
struct f_path_mask_item pmi;
struct rte *rte;
+ struct te_subtlv st;
+ const struct te_tlv *tlv;
+ const struct te_tlvlist *tl;
} val;
};
@@ -315,6 +320,8 @@ static inline int lclist_set_type(const struct f_tree *set)
{ return !set || set->from.type == T_LC; }
static inline int path_set_type(const struct f_tree *set)
{ return !set || set->from.type == T_INT; }
+static inline int subtlv_set_type(const struct f_tree *set)
+{ return !set || set->from.type == T_SUBTLV; }
int clist_match_set(const struct adata *clist, const struct f_tree *set);
int eclist_match_set(const struct adata *list, const struct f_tree *set);
@@ -323,6 +330,7 @@ int lclist_match_set(const struct adata *list, const struct f_tree *set);
const struct adata *clist_filter(struct linpool *pool, const struct adata *list, const struct f_val *set, int pos);
const struct adata *eclist_filter(struct linpool *pool, const struct adata *list, const struct f_val *set, int pos);
const struct adata *lclist_filter(struct linpool *pool, const struct adata *list, const struct f_val *set, int pos);
+const struct te_tlvlist *tlvlist_filter(struct linpool *pool, const struct te_tlvlist *list, const struct f_val *set, int pos);
/* Special undef value for paths and clists */
@@ -347,6 +355,9 @@ val_empty(enum f_type t)
case T_EMPTY_LIST:
return (struct f_val) { .type = t, .val.ad = &null_adata };
+ case T_TLVLIST:
+ return (struct f_val) { .type = T_TLVLIST, .val.tl = NULL };
+
default:
return (struct f_val) { };
}
diff --git a/filter/decl.m4 b/filter/decl.m4
index a378eadf..397a6183 100644
--- a/filter/decl.m4
+++ b/filter/decl.m4
@@ -603,6 +603,9 @@ f_const_promotion_(struct f_inst *arg, enum f_type want, int update)
} else if ((c->type == T_EMPTY_LIST) && (want == T_LCLIST)) {
*c = val_empty(T_LCLIST);
return 1;
+ } else if ((c->type == T_EMPTY_LIST) && (want == T_TLVLIST)) {
+ *c = val_empty(T_TLVLIST);
+ return 1;
}
return 0;
diff --git a/filter/f-inst.c b/filter/f-inst.c
index 9cc46aa0..b26facb0 100644
--- a/filter/f-inst.c
+++ b/filter/f-inst.c
@@ -861,6 +861,14 @@
case EAF_TYPE_LC_SET:
RESULT_(T_LCLIST, ad, e->u.ptr);
break;
+ case EAF_TYPE_TUNNEL_ENCAP:
+ {
+ struct te_tlvlist *decoded_tl = tlvlist_decode_tunnel_encap(fpool, e->u.ptr);
+ if (!decoded_tl)
+ runtime( "Tunnel encapsulation decoder error" );
+ RESULT_(T_TLVLIST, tl, decoded_tl);
+ break;
+ }
default:
bug("Unknown dynamic attribute type");
}
@@ -927,6 +935,16 @@
}
break;
+ case EAF_TYPE_TUNNEL_ENCAP:
+ {
+ if (v1.type != T_TLVLIST)
+ runtime( "Setting tunnel encap attribute to non-tlvlist value %d", v1.type );
+ l->attrs[0].u.ptr = tlvlist_encode_tunnel_encap(fpool, v1.val.tl);
+ if (!l->attrs[0].u.ptr)
+ runtime( "Tunnel encapsulation encoder error" );
+ break;
+ }
+
default:
bug("Unknown dynamic attribute type");
}
@@ -1309,6 +1327,24 @@
RESULT(T_LCLIST, ad, [[ lc_set_union(fpool, v1.val.ad, v2.val.ad) ]]);
}
+ INST(FI_TLVLIST_ADD_SET, 2, 1) {
+ ARG(1, T_TLVLIST);
+ ARG(2, T_SET);
+ METHOD_CONSTRUCTOR("add");
+
+ if (!subtlv_set_type(v2.val.t))
+ runtime("Mismatched set type");
+
+ RESULT(T_TLVLIST, tl, [[ tlv_set_add(fpool, v1.val.tl, tlv_alloc(fpool, v2.val.t)) ]]);
+ }
+
+ INST(FI_TLVLIST_ADD_TLVLIST, 2, 1) {
+ ARG(1, T_TLVLIST);
+ ARG(2, T_TLVLIST);
+ METHOD_CONSTRUCTOR("add");
+ RESULT(T_TLVLIST, tl, [[ tlv_set_union(fpool, v1.val.tl, v2.val.tl) ]]);
+ }
+
INST(FI_PATH_DELETE_INT, 2, 1) {
ARG(1, T_PATH);
ARG(2, T_INT);
@@ -1424,6 +1460,21 @@
RESULT(T_LCLIST, ad, [[ lclist_filter(fpool, v1.val.ad, &v2, 0) ]]);
}
+ INST(FI_TLVLIST_DELETE_SET, 2, 1) {
+ ARG(1, T_TLVLIST);
+ ARG(2, T_SET);
+ METHOD_CONSTRUCTOR("delete");
+
+ if (!subtlv_set_type(v2.val.t))
+ runtime("Mismatched set type");
+
+#if 0
+ RESULT(T_TLVLIST, tl, [[ tlvlist_filter(fpool, v1.val.tl, &v2, 0) ]]);
+#else
+ runtime("FI_TLVLIST_DELETE_SET disabled");
+#endif
+ }
+
INST(FI_PATH_FILTER_SET, 2, 1) {
ARG(1, T_PATH);
ARG(2, T_SET);
@@ -1489,6 +1540,33 @@
RESULT(T_LCLIST, ad, [[ lclist_filter(fpool, v1.val.ad, &v2, 1) ]]);
}
+ INST(FI_TLVLIST_FILTER_SET, 2, 1) {
+ ARG(1, T_TLVLIST);
+ ARG(2, T_SET);
+ METHOD_CONSTRUCTOR("filter");
+
+ if (!subtlv_set_type(v2.val.t))
+ runtime("Mismatched set type");
+
+#if 0
+ RESULT(T_TLVLIST, tl, [[ tlvlist_filter(fpool, v1.val.tl, &v2, 1) ]]);
+#else
+ runtime("FI_TLVLIST_FILTER_SET disabled");
+#endif
+ }
+
+ INST(FI_TLVLIST_FILTER_TLVLIST, 2, 1) {
+ ARG(1, T_TLVLIST);
+ ARG(2, T_TLVLIST);
+ METHOD_CONSTRUCTOR("filter");
+
+#if 0
+ RESULT(T_TLVLIST, tl, [[ tlvlist_filter(fpool, v1.val.tl, &v2, 1) ]]);
+#else
+ runtime("FI_TLVLIST_FILTER_TLVLIST disabled");
+#endif
+ }
+
INST(FI_ROA_CHECK_IMPLICIT, 0, 1) { /* ROA Check */
NEVER_CONSTANT;
RTC(1);
diff --git a/lib/Makefile b/lib/Makefile
index 0310eca2..1534b794 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -1,4 +1,4 @@
-src := base64.c bitmap.c bitops.c blake2s.c blake2b.c checksum.c event.c flowspec.c idm.c ip.c lists.c mac.c md5.c mempool.c net.c patmatch.c printf.c resource.c sha1.c sha256.c sha512.c slab.c slists.c strtoul.c tbf.c timer.c xmalloc.c
+src := base64.c bitmap.c bitops.c blake2s.c blake2b.c checksum.c event.c flowspec.c idm.c ip.c lists.c mac.c md5.c mempool.c net.c patmatch.c printf.c resource.c sha1.c sha256.c sha512.c slab.c slists.c strtoul.c tbf.c timer.c tunnel_encaps.c xmalloc.c
obj := $(src-o-files)
$(all-daemon)
diff --git a/lib/tunnel_encaps.c b/lib/tunnel_encaps.c
new file mode 100644
index 00000000..d549694a
--- /dev/null
+++ b/lib/tunnel_encaps.c
@@ -0,0 +1,876 @@
+#define LOCAL_DEBUG
+
+#include <stdlib.h>
+#include "lib/tunnel_encaps.h"
+
+struct format_fn {
+ node n;
+ int type;
+ const char *name;
+ format_tunnel_encap_fn cb;
+};
+
+list format_fns;
+
+static format_tunnel_encap_fn lookup_format_tunnel_encap_cb(int type);
+static const char *lookup_format_tunnel_encap_name(int type);
+static int default_format_tunnel_encap(const struct te_encap *encap, byte *buf, uint size);
+
+static
+int walk_encap(const void *p, size_t sub_tlv_len, const struct te_visitor *visitor, struct te_context *ctx)
+{
+ struct te_encap encap;
+ memset(&encap, 0, sizeof(encap));
+
+ encap.type = 0;
+ encap.length = sub_tlv_len;
+ encap.data = (void *)p;
+ return visitor->visit_encap ? visitor->visit_encap(&encap, ctx) : 0;
+}
+
+static
+int walk_color(const void *p, size_t sub_tlv_len, const struct te_visitor *visitor, struct te_context *ctx)
+{
+ if (sub_tlv_len != 8)
+ {
+ DBG(L_TRACE "WG: color len error %d", sub_tlv_len);
+ return -1;
+ }
+
+ if (get_u16(p) != 0x030b)
+ {
+ DBG(L_TRACE "WG: color error %04x", get_u16(p));
+ return -1;
+ }
+
+ return visitor->visit_color ? visitor->visit_color(get_u32(p+4), ctx) : 0;
+}
+
+static
+int walk_udp_dest_port(const void *p, size_t sub_tlv_len, const struct te_visitor *visitor, struct te_context *ctx)
+{
+ if (sub_tlv_len != 2)
+ {
+ DBG(L_TRACE "WG: udp dest port len error %d", sub_tlv_len);
+ return -1;
+ }
+
+ return visitor->visit_udp_dest_port ? visitor->visit_udp_dest_port(get_u16(p), ctx) : 0;
+}
+
+static
+int walk_tunnel_ep(const void *p, size_t sub_tlv_len, const struct te_visitor *visitor, struct te_context *ctx)
+{
+ if (sub_tlv_len < 6)
+ {
+ DBG(L_TRACE "WG: tunnel ep len error");
+ return -1;
+ }
+
+ struct te_endpoint ep;
+ memset(&ep, 0, sizeof(ep));
+
+ ep.reserved = get_u32(p);
+ u16 af = get_u16(p + 4);
+ ep.af = af;
+ switch (af)
+ {
+ case 0:
+ if (sub_tlv_len != 6)
+ {
+ DBG(L_TRACE "WG: Fam 0 len error %d", sub_tlv_len);
+ return -1;
+ }
+ ep.ip = IP6_NONE;
+ break;
+ case AFI_IPV4:
+ if (sub_tlv_len != 10)
+ {
+ DBG(L_TRACE "WG: IPv4 len error %d", sub_tlv_len);
+ return -1;
+ }
+ ep.ip = ipa_from_ip4(get_ip4(p + 6));
+ break;
+ case AFI_IPV6:
+ if (sub_tlv_len != 22)
+ {
+ DBG(L_TRACE "WG: IPv6 len error %d", sub_tlv_len);
+ return -1;
+ }
+ ep.ip = ipa_from_ip6(get_ip6(p + 6));
+ break;
+ default:
+ DBG(L_TRACE "WG: Address family error %d", af);
+ return -1;
+ }
+
+ return visitor->visit_ep ? visitor->visit_ep(&ep, ctx) : 0;
+}
+
+static
+int walk_unknown(const void *p, int type, size_t sub_tlv_len, const struct te_visitor *visitor, struct te_context *ctx)
+{
+ struct te_unknown unknown;
+ memset(&unknown, 0, sizeof(unknown));
+
+ unknown.length = sub_tlv_len;
+ unknown.data = (void *)p;
+ return visitor->visit_unknown ? visitor->visit_unknown(type, &unknown, ctx) : 0;
+}
+
+static
+int walk_sub_tlv(const u8 *p, size_t len, const struct te_visitor *visitor, struct te_context *ctx)
+{
+ if (len < 2)
+ {
+ DBG(L_TRACE "WG: sub_tlv len error %d", len);
+ return -1;
+ }
+
+ const u8 *first = p;
+ const u8 *last = p + len;
+ int type = get_u8(p++);
+ u16 sub_tlv_len = 0;
+
+ if (type >= 0 && type <= 127)
+ {
+ sub_tlv_len = get_u8(p);
+ p++;
+ }
+ else if (type >= 128 && type <= 255)
+ {
+ if (len < 3)
+ {
+ DBG(L_TRACE "WG: sub_tlv len error %d", len);
+ return -1;
+ }
+
+ sub_tlv_len = get_u16(p);
+ p += 2;
+ }
+ else
+ {
+ DBG(L_TRACE "WG: sub_tlv type error %d", type);
+ return -1;
+ }
+
+ if (p + sub_tlv_len > last)
+ {
+ DBG(L_TRACE "WG: sub_tlv value len error %d", sub_tlv_len);
+ return -1;
+ }
+
+ if (visitor->visit_subtlv && visitor->visit_subtlv(type, ctx) < 0)
+ return p - first + sub_tlv_len;
+
+ int res = 0;
+
+ switch (type)
+ {
+ case BGP_TUNNEL_ENCAP_A_SUB_TLV_ENCAP:
+ res = walk_encap(p, sub_tlv_len, visitor, ctx);
+ break;
+ case BGP_TUNNEL_ENCAP_A_SUB_TLV_TUNNEL_EP:
+ res = walk_tunnel_ep(p, sub_tlv_len, visitor, ctx);
+ break;
+ case BGP_TUNNEL_ENCAP_A_SUB_TLV_COLOR:
+ res = walk_color(p, sub_tlv_len, visitor, ctx);
+ break;
+ case BGP_TUNNEL_ENCAP_A_SUB_TLV_UDP_DEST_PORT:
+ res = walk_udp_dest_port(p, sub_tlv_len, visitor, ctx);
+ break;
+ default:
+ res = walk_unknown(p, type, sub_tlv_len, visitor, ctx);
+ break;
+ }
+
+ if (res < 0)
+ {
+ DBG(L_TRACE "WG: Decode error");
+ return res;
+ }
+
+ /* TODO use return value? */
+ if (visitor->visit_subtlv_end)
+ visitor->visit_subtlv_end(type, ctx);
+
+ return p - first + sub_tlv_len;
+}
+
+static
+int walk_sub_tlvs(const u8 *p, size_t size, const struct te_visitor *visitor, struct te_context *ctx)
+{
+ const u8 *pos = p;
+
+ while (size > 0)
+ {
+ int l = walk_sub_tlv(pos, size, visitor, ctx);
+
+ if (l < 0)
+ {
+ DBG(L_TRACE "WG: decode error %d", l);
+ return l;
+ }
+
+ ADVANCE(pos, size, l);
+ }
+
+ return pos - p;
+}
+
+static
+int walk_tlv(const u8 *p, size_t len, const struct te_visitor *visitor, struct te_context *ctx)
+{
+ const u8 *pos = p;
+ int l;
+
+ if (len < 4)
+ {
+ DBG(L_TRACE "WG: tunnel_encap len error %d", len);
+ return -1;
+ }
+
+ u16 type = get_u16(pos);
+ ADVANCE(pos, len, 2);
+
+ u16 value_length = get_u16(pos);
+ ADVANCE(pos, len, 2);
+
+ if (len < value_length)
+ {
+ DBG(L_ERR "WG: tunnel encap len error %d", value_length);
+ return -1;
+ }
+
+ ctx->sub_tlvs_pos = pos;
+ ctx->sub_tlvs_len = value_length;
+
+ if (visitor->visit_tlv && visitor->visit_tlv(type, ctx) < 0)
+ return 0;
+
+ l = walk_sub_tlvs(pos, value_length, visitor, ctx);
+ if (l < 0)
+ {
+ DBG(L_ERR "WG: sub-TLVs decode error");
+ return l;
+ }
+
+ if (l != value_length)
+ {
+ DBG(L_ERR "WG: TLV length error");
+ return -1;
+ }
+
+ ADVANCE(pos, len, l);
+
+ if (visitor->visit_tlv_end && visitor->visit_tlv_end(ctx) < 0)
+ return 0;
+
+ ctx->sub_tlvs_pos = NULL;
+ ctx->sub_tlvs_len = 0;
+
+ return pos - p;
+}
+
+int walk_tunnel_encap(const struct adata *a, const struct te_visitor *visitor, void *opaque)
+{
+ const u8 *p = a->data;
+ const u8 *pos = p;
+ uint size = a->length;
+ struct te_context ctx = { .opaque = opaque, };
+
+ while (size > 0)
+ {
+ int l = walk_tlv(p, size, visitor, &ctx);
+
+ if (l < 0)
+ {
+ DBG(L_ERR "WG: TLV decode error");
+ return l;
+ }
+
+ ADVANCE(pos, size, l);
+ }
+
+ return 0;
+}
+
+struct count_subtlvs {
+ uint count;
+};
+
+static int
+visitor_count_subtlv(int UNUSED type, struct te_context *ctx){
+ struct count_subtlvs *data = ctx->opaque;
+
+ data->count++;
+ return 0;
+}
+
+int te_count_subtlvs(struct te_context *ctx)
+{
+ struct count_subtlvs data = { .count = 0, };
+ struct te_context new_ctx = { .opaque = &data, };
+ struct te_visitor visitor = { .visit_subtlv = visitor_count_subtlv, };
+
+ if (walk_sub_tlvs(ctx->sub_tlvs_pos, ctx->sub_tlvs_len, &visitor, &new_ctx) < 0)
+ return -1;
+
+ return data.count;
+}
+
+struct {
+ int type;
+ const char *name;
+} tunnel_types[] = {
+ {0, "Reserved"},
+ {1, "L2TPv3 over IP"},
+ {2, "GRE"},
+ {3, "Transmit tunnel endpoint (DEPRECATED)"},
+ {4, "IPsec in Tunnel-mode (DEPRECATED)"},
+ {5, "IP in IP tunnel with IPsec Transport Mode (DEPRECATED)"},
+ {6, "MPLS-in-IP tunnel with IPsec Transport Mode (DEPRECATED)"},
+ {7, "IP in IP"},
+ {8, "VXLAN Encapsulation"},
+ {9, "NVGRE Encapsulation"},
+ {10, "MPLS Encapsulation"},
+ {11, "MPLS in GRE Encapsulation"},
+ {12, "VXLAN GPE Encapsulation"},
+ {13, "MPLS in UDP Encapsulation"},
+ {14, "IPv6 Tunnel"},
+ {15, "SR TE Policy Type"},
+ {16, "Bare"},
+ {17, "SR Tunnel (DEPRECATED)"},
+ {18, "Cloud Security"},
+ {19, "Geneve Encapsulation"},
+ {20, "Any-Encapsulation"},
+ {21, "GTP Tunnel Type"},
+ {22, "Dynamic Path Selection (DPS) Tunnel Encapsulation"},
+ {23, "Originating PE (OPE)"},
+ {24, "Dynamic Path Selection (DPS) Policy"},
+};
+
+const uint num_tunnel_types = sizeof(tunnel_types)/sizeof(tunnel_types[0]);
+
+static const char *
+lookup_name(int type)
+{
+ for (uint i = 0; i < num_tunnel_types; i++)
+ {
+ if (tunnel_types[i].type == type)
+ return tunnel_types[i].name;
+ }
+
+ const char *name = lookup_format_tunnel_encap_name(type);
+ return name ? name : "Unassigned";
+}
+
+static int visitor_format_tlv(int type, struct te_context *ctx)
+{
+ struct te_buffer *buf = ctx->opaque;
+ const char *name = lookup_name(type);
+
+ int l = bsnprintf(buf->pos, buf->size, "%s{type: %s(%d)", buf->tlv?"}, ":"", name, type);
+ if (l < 0)
+ {
+ DBG(L_ERR "WG: bsnprintf error");
+ return -1;
+ }
+
+ buf->tlv++;
+ buf->tlv_type = type;
+ buf->subtlv=0;
+ ADVANCE(buf->pos, buf->size, l);
+ return 0;
+}
+
+static int visitor_format_subtlv(int UNUSED type, struct te_context *ctx)
+{
+ struct te_buffer *buf = ctx->opaque;
+
+ int l = bsnprintf(buf->pos, buf->size, ", ");
+ if (l < 0)
+ return -1;
+ ADVANCE(buf->pos, buf->size, l);
+ return 0;
+}
+
+static int visitor_format_encap(const struct te_encap *encap, struct te_context *ctx)
+{
+ struct te_buffer *buf = ctx->opaque;
+ int l = bsnprintf(buf->pos, buf->size, "encap: ");
+ if (l < 0)
+ return -1;
+ ADVANCE(buf->pos, buf->size, l);
+
+ l = lookup_format_tunnel_encap_cb(buf->tlv_type)(encap, buf->pos, buf->size);
+ if (l < 0)
+ l = default_format_tunnel_encap(encap, buf->pos, buf->size);
+
+ if (l < 0)
+ {
+ DBG(L_ERR "WG: Encapsulation format error");
+ return l;
+ }
+
+ ADVANCE(buf->pos, buf->size, l);
+ return 0;
+}
+
+static int visitor_format_ep(const struct te_endpoint *ep, struct te_context *ctx)
+{
+ struct te_buffer *buf = ctx->opaque;
+ int l = bsnprintf(buf->pos, buf->size, "ep: %I", ep->ip);
+ if (l < 0)
+ return -1;
+ ADVANCE(buf->pos, buf->size, l);
+ return 0;
+}
+
+static int visitor_format_color(u32 color, struct te_context *ctx)
+{
+ struct te_buffer *buf = ctx->opaque;
+ int l = bsnprintf(buf->pos, buf->size, "color: %d", color);
+ if (l < 0)
+ return -1;
+ ADVANCE(buf->pos, buf->size, l);
+ return 0;
+}
+
+static int visitor_format_udp_dest_port(u16 udp_dest_port, struct te_context *ctx)
+{
+ struct te_buffer *buf = ctx->opaque;
+ int l = bsnprintf(buf->pos, buf->size, "udp: %d", udp_dest_port);
+ if (l < 0)
+ return -1;
+ ADVANCE(buf->pos, buf->size, l);
+ return 0;
+}
+
+static int visitor_format_unknown(int type, const struct te_unknown *unknown, struct te_context *ctx)
+{
+ struct te_buffer *buf = ctx->opaque;
+ const byte *data = unknown->data;
+ int l = bsnprintf(buf->pos, buf->size, "unknown(%d): ", type);
+ if (l < 0)
+ return -1;
+ ADVANCE(buf->pos, buf->size, l);
+
+ int first = 1;
+ for (uint i = 0; i < unknown->length; i++)
+ {
+ if (buf->size < 4) {
+ DBG(L_ERR "WG: Format unknown sub-TLV error");
+ return -1;
+ }
+
+ int l = bsnprintf(buf->pos, buf->size, "%s%02x", first?"":" ", data[i]);
+ ADVANCE(buf->pos, buf->size, l);
+ first = 0;
+ }
+
+ return 0;
+}
+
+struct te_visitor format_visitor =
+{
+ .visit_tlv = visitor_format_tlv,
+ .visit_subtlv = visitor_format_subtlv,
+ .visit_encap = visitor_format_encap,
+ .visit_ep = visitor_format_ep,
+ .visit_color = visitor_format_color,
+ .visit_udp_dest_port = visitor_format_udp_dest_port,
+ .visit_unknown = visitor_format_unknown,
+};
+
+const struct te_visitor *te_get_format_visitor(void)
+{
+ return &format_visitor;
+}
+
+int format_tunnel_encap(const struct adata *a, byte *buf, uint size)
+{
+ struct te_buffer buffer = {.pos=buf, .size=size, .tlv=0, .subtlv=0};
+ int l = bsnprintf(buffer.pos, buffer.size, "[");
+ if (l < 0)
+ return -1;
+ ADVANCE(buffer.pos, buffer.size, l);
+
+ walk_tunnel_encap(a, &format_visitor, &buffer);
+
+ l = bsnprintf(buffer.pos, buffer.size, "%s]", buffer.tlv?"}":"");
+ if (l < 0)
+ return -1;
+ ADVANCE(buffer.pos, buffer.size, l);
+ return buffer.pos - buf;
+}
+
+static int default_format_tunnel_encap(const struct te_encap *encap, byte *buf, uint size)
+{
+ byte *pos = buf;
+ const byte *data = encap->data;
+ int first = 1;
+
+ for (uint i = 0; i < encap->length; i++)
+ {
+ if (size < 4) {
+ DBG(L_ERR "WG: Default format tunnel encap error");
+ return pos - buf;
+ }
+
+ int l = bsnprintf(pos, size, "%s%02x", first?"":" ", data[i]);
+ ADVANCE(pos, size, l);
+
+ first = 0;
+ }
+
+ return pos - buf;
+}
+
+static struct format_fn *lookup_format_tunnel_encap(int type)
+{
+ struct format_fn *n;
+ WALK_LIST(n, format_fns) {
+ if (n->type == type)
+ return n;
+ }
+
+ return NULL;
+}
+
+static const char *lookup_format_tunnel_encap_name(int type)
+{
+ struct format_fn *fn = lookup_format_tunnel_encap(type);
+
+ if (fn && fn->name)
+ return fn->name;
+
+ return NULL;
+}
+
+static format_tunnel_encap_fn lookup_format_tunnel_encap_cb(int type)
+{
+ struct format_fn *fn = lookup_format_tunnel_encap(type);;
+
+ if (fn && fn->cb)
+ return fn->cb;
+
+ DBG("lookup_format_tunnel_encap not found: %d\n", type);
+ return default_format_tunnel_encap;
+}
+
+struct tlv_buffer {
+ uint len;
+ uint size;
+ byte *p;
+ byte *tlv_len_p;
+ byte *subtlv_len_p;
+};
+
+static int calc_tlv(int type, struct te_context *ctx)
+{
+ struct tlv_buffer *buf = ctx->opaque;
+ /*
+ Header:
+ Tunnel Type (2 Octets)
+ Length (2 Octets)
+ Value (Variable)
+ */
+ buf->len += 4;
+
+ if (!buf->size)
+ return 0;
+
+ if (buf->len > buf->size)
+ return -1;
+
+ if (type <= 0 || type > 65535)
+ return -1;
+
+ put_u16(buf->p, type);
+ buf->p += 2;
+
+ buf->tlv_len_p = buf->p;
+
+ put_u16(buf->p, 0xcccc); /* Update in calc_tlv_end */
+ buf->p += 2;
+ return 0;
+}
+
+static int calc_tlv_end(struct te_context *ctx)
+{
+ struct tlv_buffer *buf = ctx->opaque;
+
+ if (!buf->size)
+ return 0;
+
+ if (!buf->tlv_len_p)
+ return -1;
+
+ put_u16(buf->tlv_len_p, buf->p - buf->tlv_len_p - 2);
+ buf->tlv_len_p = NULL;
+ return 0;
+}
+
+static int calc_subtlv(int type, struct te_context *ctx)
+{
+ struct tlv_buffer *buf = ctx->opaque;
+ /*
+ Sub-TLV Type (1 Octet)
+ Sub-TLV Length (1 or 2 Octets)
+ */
+ if (type >= 0 && type <= 127)
+ buf->len += 2;
+ else if (type >= 128 && type <= 255)
+ buf->len += 3;
+ else
+ {
+ DBG(L_ERR "calc_subtlv bad type %d\n", type );
+ return -1;
+ }
+
+ if (!buf->size)
+ return 0;
+
+ if (buf->len > buf->size)
+ return -1;
+
+ put_u8(buf->p, type);
+ buf->p++;
+
+ buf->subtlv_len_p = buf->p;
+
+ if (type >= 0 && type <= 127)
+ {
+ put_u8(buf->p, 0xdd); /* Update in calc_subtlv_end */
+ buf->p++;
+ }
+ else
+ {
+ put_u16(buf->p, 0xeeee); /* Update in calc_subtlv_end */
+ buf->p += 2;
+ }
+
+ return 0;
+}
+
+static int calc_subtlv_end(int type, struct te_context *ctx)
+{
+ struct tlv_buffer *buf = ctx->opaque;
+
+ if (!buf->size)
+ return 0;
+
+ if (!buf->subtlv_len_p)
+ return -1;
+
+ if (type >= 0 && type <= 127)
+ {
+ put_u8(buf->subtlv_len_p, buf->p - buf->subtlv_len_p - 1);
+ buf->subtlv_len_p = NULL;
+ }
+ else if (type >= 128 && type <= 255)
+ {
+ put_u16(buf->subtlv_len_p, buf->p - buf->subtlv_len_p - 2);
+ buf->subtlv_len_p = NULL;
+ }
+ else
+ return -1;
+
+ return 0;
+}
+
+static int calc_encap(const struct te_encap *encap, struct te_context *ctx)
+{
+ struct tlv_buffer *buf = ctx->opaque;
+ /*
+ Sub-TLV Value (Variable)
+ */
+ if (encap->type <= 0)
+ {
+ DBG(L_ERR "calc_encap bad type %d\n", encap->type );
+ return -1;
+ }
+
+ buf->len += encap->length;
+
+ if (!buf->size)
+ return 0;
+
+ if (buf->len > buf->size)
+ return -1;
+
+ memcpy(buf->p, encap->data, encap->length);
+ buf->p += encap->length;
+ return 0;
+}
+
+static int calc_ep(const struct te_endpoint *ep, struct te_context *ctx)
+{
+ struct tlv_buffer *buf = ctx->opaque;
+ /*
+ Sub-TLV Value (10 or 22 Octets)
+ */
+ if (ep->af == AFI_IPV4 && ipa_is_ip4(ep->ip))
+ buf->len += 10;
+ else if (ep->af == AFI_IPV6 && ipa_is_ip6(ep->ip))
+ buf->len += 22;
+ else if (ep->af == 0)
+ buf->len += 6;
+ else
+ {
+ DBG(L_ERR "calc_encap bad af %04x\n", ep->af );
+ return -1;
+ }
+
+ if (!buf->size)
+ return 0;
+
+ if (buf->len > buf->size)
+ return -1;
+
+ put_u32(buf->p, ep->reserved);
+ buf->p += 4;
+
+ put_u16(buf->p, ep->af);
+ buf->p += 2;
+
+ switch (ep->af)
+ {
+ case AFI_IPV4:
+ put_ip4(buf->p, ipa_to_ip4(ep->ip));
+ buf->p += 4;
+ break;
+ case AFI_IPV6:
+ put_ip6(buf->p, ipa_to_ip6(ep->ip));
+ buf->p += 16;
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int calc_color(u32 color, struct te_context *ctx)
+{
+ struct tlv_buffer *buf = ctx->opaque;
+ /*
+ Sub-TLV Value (8 Octets)
+ */
+ buf->len += 8;
+
+ if (!buf->size)
+ return 0;
+
+ if (buf->len > buf->size)
+ return -1;
+
+ put_u32(buf->p, 0x030b0000);
+ buf->p += 4;
+
+ put_u32(buf->p, color);
+ buf->p += 4;
+ return 0;
+}
+
+static int calc_udp_dest_port(u16 UNUSED udp_dest_port, struct te_context *ctx)
+{
+ struct tlv_buffer *buf = ctx->opaque;
+ /*
+ Sub-TLV Value (2 Octets)
+ */
+ buf->len += 2;
+
+ if (!buf->size)
+ return 0;
+
+ if (buf->len > buf->size)
+ return -1;
+
+ put_u16(buf->p, udp_dest_port);
+ buf->p += 2;
+ return 0;
+}
+
+static int calc_unknown(int UNUSED type, const struct te_unknown *unknown, struct te_context *ctx)
+{
+ struct tlv_buffer *buf = ctx->opaque;
+ /*
+ Sub-TLV Value (Variable)
+ */
+
+ buf->len += unknown->length;
+
+ if (!buf->size)
+ return 0;
+
+ if (buf->len > buf->size)
+ return -1;
+
+ memcpy(buf->p, unknown->data, unknown->length);
+ buf->p += unknown->length;
+ return 0;
+}
+
+struct te_visitor encode_visitor =
+{
+ .visit_tlv = calc_tlv,
+ .visit_tlv_end = calc_tlv_end,
+ .visit_subtlv = calc_subtlv,
+ .visit_subtlv_end = calc_subtlv_end,
+ .visit_encap = calc_encap,
+ .visit_ep = calc_ep,
+ .visit_color = calc_color,
+ .visit_udp_dest_port = calc_udp_dest_port,
+ .visit_unknown = calc_unknown,
+};
+
+const struct te_visitor *te_get_encode_visitor(void)
+{
+ return &encode_visitor;
+}
+
+int te_init_encode_visitor(struct tlv_buffer *buf, byte *p, uint size)
+{
+ if (!buf)
+ return sizeof(struct te_buffer);
+
+ memset(buf, 0, sizeof(struct te_buffer));
+ buf->size = size;
+ buf->p = p;
+
+ return 0;
+}
+
+uint te_get_encode_length(struct tlv_buffer *buf)
+{
+ return buf->len;
+}
+
+void register_format_tunnel_encap(int type, const char *name, format_tunnel_encap_fn cb)
+{
+ struct format_fn *fn = malloc(sizeof(struct format_fn));
+ memset(fn, 0, sizeof(*fn));
+ fn->type = type;
+ fn->name = name;
+ fn->cb = cb;
+ add_head(&format_fns, &fn->n);
+}
+
+void unregister_format_tunnel_encap(int type, format_tunnel_encap_fn cb)
+{
+ struct format_fn *n, *nxt;
+ WALK_LIST_DELSAFE(n, nxt, format_fns)
+ {
+ if (n->type == type && n->cb == cb)
+ {
+ rem_node(&n->n);
+ }
+ }
+}
+
+void tunnel_encap_init()
+{
+ init_list(&format_fns);
+}
diff --git a/lib/tunnel_encaps.h b/lib/tunnel_encaps.h
new file mode 100644
index 00000000..fc7bbda6
--- /dev/null
+++ b/lib/tunnel_encaps.h
@@ -0,0 +1,63 @@
+#ifndef _BIRD_TUNNEL_ENCAPS_
+#define _BIRD_TUNNEL_ENCAPS_
+
+#include "nest/attrs.h"
+#include "nest/route.h"
+
+#define BA_TUNNEL_ENCAP 0x17
+
+#define BGP_TUNNEL_ENCAP_A_SUB_TLV_ENCAP 1
+#define BGP_TUNNEL_ENCAP_A_SUB_TLV_COLOR 4
+#define BGP_TUNNEL_ENCAP_A_SUB_TLV_TUNNEL_EP 6
+#define BGP_TUNNEL_ENCAP_A_SUB_TLV_UDP_DEST_PORT 8
+
+#define FLAG_BGP_TUNNEL_ENCAP_A_SUB_TLV_ENCAP (1<<BGP_TUNNEL_ENCAP_A_SUB_TLV_ENCAP)
+#define FLAG_BGP_TUNNEL_ENCAP_A_SUB_TLV_COLOR (1<<BGP_TUNNEL_ENCAP_A_SUB_TLV_COLOR)
+#define FLAG_BGP_TUNNEL_ENCAP_A_SUB_TLV_TUNNEL_EP (1<<BGP_TUNNEL_ENCAP_A_SUB_TLV_TUNNEL_EP)
+#define FLAG_BGP_TUNNEL_ENCAP_A_SUB_TLV_UDP_DEST_PORT (1<<BGP_TUNNEL_ENCAP_A_SUB_TLV_UDP_DEST_PORT)
+
+struct te_context {
+ void *opaque;
+ const byte *sub_tlvs_pos;
+ uint sub_tlvs_len;
+};
+
+struct te_visitor {
+ int (*visit_tlv)(int type, struct te_context *ctx);
+ int (*visit_tlv_end)(struct te_context *ctx);
+ int (*visit_subtlv)(int type, struct te_context *ctx);
+ int (*visit_subtlv_end)(int type, struct te_context *ctx);
+ int (*visit_encap)(const struct te_encap *encap, struct te_context *ctx);
+ int (*visit_ep)(const struct te_endpoint *ep, struct te_context *ctx);
+ int (*visit_color)(u32 color, struct te_context *ctx);
+ int (*visit_udp_dest_port)(u16 port, struct te_context *ctx);
+ int (*visit_unknown)(int type, const struct te_unknown *unknown, struct te_context *ctx);
+};
+
+struct te_buffer {
+ byte *pos;
+ uint size;
+ int tlv;
+ int tlv_type;
+ int subtlv;
+};
+
+struct tlv_buffer;
+
+typedef int (*format_tunnel_encap_fn)(const struct te_encap *, byte *buf, uint size);
+
+void tunnel_encap_init(void);
+int walk_tunnel_encap(const struct adata *a, const struct te_visitor *visitor, void *opaque);
+int te_count_subtlvs(struct te_context *ctx);
+
+const struct te_visitor *te_get_format_visitor(void);
+int format_tunnel_encap(const struct adata *a, byte *buf, uint size);
+
+const struct te_visitor *te_get_encode_visitor(void);
+int te_init_encode_visitor(struct tlv_buffer *buf, byte *p, uint size);
+uint te_get_encode_length(struct tlv_buffer *buf);
+
+void register_format_tunnel_encap(int type, const char *name, format_tunnel_encap_fn cb);
+void unregister_format_tunnel_encap(int type, format_tunnel_encap_fn cb);
+
+#endif /* _BIRD_TUNNEL_ENCAPS_ */
diff --git a/lib/unaligned.h b/lib/unaligned.h
index dfe0906f..0d3c83f9 100644
--- a/lib/unaligned.h
+++ b/lib/unaligned.h
@@ -17,8 +17,9 @@
* if possible.
*/
+#include <memory.h>
+#include "sysdep/config.h"
#include "sysdep/unix/endian.h"
-#include "lib/string.h"
static inline u8
get_u8(const void *p)
diff --git a/nest/Makefile b/nest/Makefile
index ef0fdf67..aa75fed7 100644
--- a/nest/Makefile
+++ b/nest/Makefile
@@ -1,4 +1,4 @@
-src := a-path.c a-set.c cli.c cmds.c iface.c locks.c mpls.c neighbor.c password.c proto.c proto-build.c rt-attr.c rt-dev.c rt-fib.c rt-show.c rt-table.c
+src := a-path.c a-set.c a-tlv.c cli.c cmds.c iface.c locks.c mpls.c neighbor.c password.c proto.c proto-build.c rt-attr.c rt-dev.c rt-fib.c rt-show.c rt-table.c
obj := $(src-o-files)
$(all-daemon)
$(cf-local)
diff --git a/nest/a-tlv.c b/nest/a-tlv.c
new file mode 100644
index 00000000..13e05f43
--- /dev/null
+++ b/nest/a-tlv.c
@@ -0,0 +1,421 @@
+#include "lib/tunnel_encaps.h"
+#include "filter/data.h"
+
+static int
+walk_tlvlist(const struct te_tlvlist *set, const struct te_visitor *visitor, void *opaque)
+{
+ if (!set)
+ return 0;
+
+ struct te_tlv *t;
+ struct te_context ctx = { .opaque = opaque, };
+
+ WALK_LIST(t, set->tlv)
+ {
+ if (visitor->visit_tlv && visitor->visit_tlv(t->type, &ctx) < 0)
+ continue;
+
+ for (uint j=0; j < t->len; j++)
+ {
+ const struct te_subtlv *st = &t->st[j];
+
+ if (visitor->visit_subtlv && visitor->visit_subtlv(st->type, &ctx) < 0)
+ continue;
+
+ switch (st->type)
+ {
+ case TLV_ENCAPSULATION:
+ if (visitor->visit_encap && visitor->visit_encap(&st->u.tunnel_encap, &ctx) < 0)
+ return -1;
+ break;
+ case TLV_COLOR:
+ if (visitor->visit_color && visitor->visit_color(st->u.color, &ctx) < 0)
+ return -1;
+ break;
+ case TLV_TUNNEL_ENDPOINT:
+ if (visitor->visit_ep && visitor->visit_ep(&st->u.tunnel_endpoint, &ctx) < 0)
+ return -1;
+ break;
+ case TLV_UDP_DEST_PORT:
+ if (visitor->visit_udp_dest_port && visitor->visit_udp_dest_port(st->u.udp_dest_port, &ctx) < 0)
+ return -1;
+ break;
+ default:
+ if (visitor->visit_unknown && visitor->visit_unknown(st->type, &st->u.unknown, &ctx) < 0)
+ return -1;
+ break;
+ }
+
+ if (visitor->visit_subtlv_end)
+ visitor->visit_subtlv_end(st->type, &ctx); /* TODO use return value? */
+ }
+
+ if (visitor->visit_tlv_end)
+ visitor->visit_tlv_end(&ctx); /* TODO use return value? */
+ }
+
+ return 0;
+}
+
+static void calc_elems(const struct f_tree UNUSED *t, void *ptr)
+{
+ uint *len = ptr;
+ (*len)++;
+}
+
+static void put_elems(const struct f_tree *t, void *ptr)
+{
+ struct te_tlv *tlv = ptr;
+ const struct te_subtlv *st = &t->from.val.st;
+ tlv->len--;
+ tlv->st[tlv->len] = *st;
+ if (st->type == TLV_ENCAPSULATION)
+ tlv->type = st->u.tunnel_encap.type;
+ DBG("put_elems: type %d\n", st->type);
+}
+
+const struct te_tlv *tlv_alloc(struct linpool *pool, const struct f_tree *set)
+{
+ uint len = 0;
+
+ tree_walk(set, calc_elems, &len);
+
+ if (!len)
+ return NULL;
+
+ struct te_tlv *res = lp_alloc(pool, sizeof(struct te_tlv) + len * sizeof(struct te_subtlv));
+
+ res->len = len;
+ tree_walk(set, put_elems, res);
+
+ res->len = len; /* Len was reset during tree_walk. */
+
+ for (uint i=0; i < len; i++)
+ {
+ DBG("tlv_alloc: type[%d]: %d\n", i, res->st[i].type);
+ }
+ return res;
+}
+
+int tlv_set_format(const struct te_tlvlist *set, int UNUSED from, byte *buf, uint size)
+{
+ const struct te_visitor *visitor = te_get_format_visitor();
+ struct te_buffer buffer = {.pos=buf, .size=size, .tlv=0, .subtlv=0};
+
+ if (set)
+ walk_tlvlist(set, visitor, &buffer);
+
+ int l = bsnprintf(buffer.pos, buffer.size, "%s", buffer.tlv?"}":"<empty>");
+ if (l < 0)
+ return -1;
+ ADVANCE(buffer.pos, buffer.size, l);
+ return buffer.pos - buf;
+}
+
+int
+tlv_set_contains(const struct te_tlvlist *list, const struct te_tlv UNUSED *val)
+{
+ if (!list)
+ return 0;
+
+ /* FIXME */
+ return 0;
+}
+
+static void tlv_add_copy(struct linpool *pool, struct te_tlvlist *list, const struct te_tlv *t)
+{
+ uint size = sizeof(struct te_tlv) + t->len * sizeof(struct te_subtlv);
+ struct te_tlv *tlv = lp_alloc(pool, size);
+ memcpy(tlv, t, size);
+ memset(&tlv->n, 0, sizeof(tlv->n));
+ add_tail(&list->tlv, &tlv->n);
+ DBG("tlv_add_copy: src type %d, dst type %d\n", t->st[0].type, tlv->st[0].type);
+}
+
+const struct te_tlvlist *
+tlv_set_add(struct linpool *pool, const struct te_tlvlist *list, const struct te_tlv *val)
+{
+ if (tlv_set_contains(list, val))
+ return list;
+
+ struct te_tlvlist *res = lp_alloc(pool, sizeof(struct te_tlvlist));
+
+ init_list(&res->tlv);
+
+ DBG("tlv_set_add: first type %d, second type %d\n", list?((const struct te_tlv *)HEAD(list->tlv))->st[0].type:-1, val->st[0].type);
+
+ if (list)
+ {
+ struct te_tlv *n;
+ WALK_LIST(n, list->tlv)
+ {
+ tlv_add_copy(pool, res, n);
+ }
+ }
+
+ tlv_add_copy(pool, res, val);
+ return res;
+}
+
+const struct te_tlvlist *
+tlv_set_union(struct linpool UNUSED *pool, const struct te_tlvlist *l1, const struct te_tlvlist *l2)
+{
+ if (!l1)
+ return l2;
+ if (!l2)
+ return l1;
+
+ /* FIXME */
+ void *res = NULL;
+ return res;
+}
+
+static int
+subtlv_same(const struct te_subtlv *st1, const struct te_subtlv *st2)
+{
+ if (st1 == NULL && st2 == NULL)
+ return 1;
+
+ if (st1 == NULL || st2 == NULL)
+ return 0;
+
+ if (st1->type != st2->type)
+ return 0;
+
+ switch(st1->type)
+ {
+ case TLV_ENCAPSULATION:
+ if (st1->u.tunnel_encap.type != st2->u.tunnel_encap.type ||
+ st1->u.tunnel_encap.length != st2->u.tunnel_encap.length ||
+ memcmp(st1->u.tunnel_encap.data, st2->u.tunnel_encap.data, st1->u.tunnel_encap.length))
+ return 0;
+ break;
+ case TLV_COLOR:
+ if (st1->u.color != st2->u.color)
+ return 0;
+ break;
+ case TLV_TUNNEL_ENDPOINT:
+ if (st1->u.tunnel_endpoint.reserved != st2->u.tunnel_endpoint.reserved ||
+ st1->u.tunnel_endpoint.af != st2->u.tunnel_endpoint.af ||
+ st1->u.tunnel_endpoint.af && ipa_compare(st1->u.tunnel_endpoint.ip, st2->u.tunnel_endpoint.ip))
+ return 0;
+ break;
+ case TLV_UDP_DEST_PORT:
+ if (st1->u.udp_dest_port != st2->u.udp_dest_port)
+ return 0;
+ break;
+ default:
+ if (st1->u.unknown.length != st2->u.unknown.length ||
+ memcmp(st1->u.unknown.data, st2->u.unknown.data, st1->u.unknown.length))
+ return 0;
+ break;
+ }
+
+ return 1;
+}
+
+static int
+tlv_same(const struct te_tlv *t1, const struct te_tlv *t2)
+{
+ if (t1 == NULL && t2 == NULL)
+ return 1;
+
+ if (t1 == NULL || t2 == NULL)
+ return 0;
+
+ if (t1->type != t2->type ||
+ t1->len != t2->len)
+ return 0;
+
+ for (uint i=0; i < t1->len; i++)
+ {
+ if (!subtlv_same(&t1->st[i], &t2->st[i]))
+ return 0;
+ }
+
+ return 1;
+}
+
+int
+tlvlist_same(const struct te_tlvlist *tl1, const struct te_tlvlist *tl2)
+{
+ if (tl1 == NULL && tl2 == NULL)
+ return 1;
+
+ if (tl1 == NULL || tl2 == NULL)
+ return 0;
+
+ const struct te_tlv *t1 = HEAD(tl1->tlv);
+ const struct te_tlv *t2 = HEAD(tl2->tlv);
+
+ while (NODE_VALID(t1) || NODE_VALID(t2))
+ {
+ if (!NODE_VALID(t1) || !NODE_VALID(t2))
+ return 0;
+
+ /* Both are valid. */
+ if (!tlv_same(t1, t2))
+ return 0;
+ }
+
+ return 1;
+}
+
+static int
+tlvlist_calc_tunnel_encap_new(const struct te_tlvlist *t, struct adata *ad)
+{
+ if (t == NULL)
+ return 0;
+
+ uint size = ad ? ad->length : 0;
+ byte *p = ad ? ad->data : NULL;
+
+ uint buf_size = te_init_encode_visitor(NULL, NULL, 0);
+ struct tlv_buffer *buf = alloca(buf_size);
+
+ if (te_init_encode_visitor(buf, p, size) < 0)
+ return -1;
+
+ if (walk_tlvlist(t, te_get_encode_visitor(), buf) < 0)
+ return -1;
+
+ return te_get_encode_length(buf);
+}
+
+struct adata *
+tlvlist_encode_tunnel_encap(struct linpool *pool, const struct te_tlvlist *tl)
+{
+ int len = tlvlist_calc_tunnel_encap_new(tl, NULL);
+
+ if (len < 0)
+ return NULL;
+
+ struct adata *ad = lp_alloc(pool, sizeof(struct adata) + len);
+ ad->length = len;
+
+ if (tlvlist_calc_tunnel_encap_new(tl, ad) < 0)
+ return NULL;
+
+ return ad;
+}
+
+struct decoder_buf
+{
+ struct linpool *pool;
+ struct te_tlvlist *tl;
+ struct te_tlv *cur_tlv;
+ struct te_subtlv *cur_subtlv;
+ int cur_subtlv_index;
+};
+
+static int visitor_decode_tlv(int type, struct te_context *ctx)
+{
+ struct decoder_buf *buf = ctx->opaque;
+
+ int count = te_count_subtlvs(ctx);
+
+ if (count < 0)
+ return -1;
+
+ buf->cur_tlv = lp_allocz(buf->pool, sizeof(struct te_tlv) + count * sizeof(struct te_subtlv));
+ buf->cur_tlv->type = type;
+ return 0;
+}
+
+static int visitor_decode_tlv_end(struct te_context *ctx)
+{
+ struct decoder_buf *buf = ctx->opaque;
+
+ add_tail(&buf->tl->tlv, &buf->cur_tlv->n);
+ buf->cur_tlv->len = buf->cur_subtlv_index;
+ buf->cur_tlv = NULL;
+ return 0;
+}
+
+static int
+visitor_decode_subtlv(int type, struct te_context *ctx){
+ struct decoder_buf *buf = ctx->opaque;
+
+ buf->cur_subtlv = &buf->cur_tlv->st[buf->cur_subtlv_index];
+ buf->cur_subtlv->type = type;
+ return 0;
+}
+
+static int
+visitor_decode_subtlv_end(int UNUSED type, struct te_context *ctx){
+ struct decoder_buf *buf = ctx->opaque;
+
+ buf->cur_subtlv_index++;
+ return 0;
+}
+
+static int
+visitor_decode_encap(const struct te_encap *encap, struct te_context *ctx)
+{
+ struct decoder_buf *buf = ctx->opaque;
+
+ buf->cur_subtlv->u.tunnel_encap = *encap;
+ return 0;
+}
+
+static int
+visitor_decode_ep(const struct te_endpoint *ep, struct te_context *ctx)
+{
+ struct decoder_buf *buf = ctx->opaque;
+
+ buf->cur_subtlv->u.tunnel_endpoint = *ep;
+ return 0;
+}
+
+static int
+visitor_decode_color(u32 color, struct te_context *ctx)
+{
+ struct decoder_buf *buf = ctx->opaque;
+
+ buf->cur_subtlv->u.color = color;
+ return 0;
+}
+
+static int
+visitor_decode_udp_dest_port(u16 port, struct te_context *ctx)
+{
+ struct decoder_buf *buf = ctx->opaque;
+
+ buf->cur_subtlv->u.udp_dest_port = port;
+ return 0;
+}
+
+static int
+visitor_decode_unknown(int UNUSED type, const struct te_unknown *unknown, struct te_context *ctx)
+{
+ struct decoder_buf *buf = ctx->opaque;
+
+ buf->cur_subtlv->u.unknown = *unknown;
+ return 0;
+}
+
+struct te_tlvlist *tlvlist_decode_tunnel_encap(struct linpool *pool,
+ const struct adata *ad)
+{
+ struct te_tlvlist *tl = lp_alloc(pool, sizeof(struct te_tlvlist));
+ struct decoder_buf buf = { .pool = pool, .tl = tl, };
+ struct te_visitor visitor = {
+ .visit_tlv = visitor_decode_tlv,
+ .visit_tlv_end = visitor_decode_tlv_end,
+ .visit_subtlv = visitor_decode_subtlv,
+ .visit_subtlv_end = visitor_decode_subtlv_end,
+ .visit_encap = visitor_decode_encap,
+ .visit_ep = visitor_decode_ep,
+ .visit_color = visitor_decode_color,
+ .visit_udp_dest_port = visitor_decode_udp_dest_port,
+ .visit_unknown = visitor_decode_unknown,
+ };
+
+ init_list(&tl->tlv);
+
+ int res = walk_tunnel_encap(ad, &visitor, &buf);
+ if (res < 0)
+ return NULL;
+
+ return tl;
+}
diff --git a/nest/attrs.h b/nest/attrs.h
index aee3468b..99174b2b 100644
--- a/nest/attrs.h
+++ b/nest/attrs.h
@@ -30,6 +30,12 @@
struct f_val;
struct f_tree;
+struct te_subtlv;
+struct te_tlv;
+struct te_tlvlist;
+struct te_encap;
+struct te_endpoint;
+struct te_visitor;
int as_path_valid(byte *data, uint len, int bs, int sets, int confed, char *err, uint elen);
int as_path_16to32(byte *dst, const byte *src, uint len);
@@ -243,4 +249,65 @@ int rte_set_walk(const struct adata *list, u32 *pos, struct rte **val);
void ec_set_sort_x(struct adata *set); /* Sort in place */
+
+/* a-tlv.c */
+
+
+/* Tunnel Encapsulation TLV types */
+#define TLV_TUNNEL_TYPE 0x00 /* Reserved. Used internally only. */
+#define TLV_ENCAPSULATION 0x01
+#define TLV_COLOR 0x04
+#define TLV_TUNNEL_ENDPOINT 0x06
+#define TLV_UDP_DEST_PORT 0x08
+
+struct te_encap {
+ int type;
+ uint length;
+ const void *data;
+};
+
+struct te_unknown {
+ uint length;
+ const void *data;
+};
+
+struct te_endpoint {
+ u32 reserved;
+ u16 af;
+ ip_addr ip;
+};
+
+/* Tunnel Encapsulation TLV */
+struct te_subtlv {
+ int type;
+ union {
+ struct te_encap tunnel_encap;
+ struct te_endpoint tunnel_endpoint;
+ u32 color;
+ u16 udp_dest_port;
+ struct te_unknown unknown;
+ } u;
+};
+
+struct te_tlv {
+ node n;
+ uint len;
+ int type;
+ struct te_subtlv st[0];
+};
+
+struct te_tlvlist {
+ list tlv;
+};
+
+const struct te_tlv *tlv_alloc(struct linpool *pool, const struct f_tree *set);
+int tlv_set_format(const struct te_tlvlist *set, int from, byte *buf, uint size);
+int tlv_set_contains(const struct te_tlvlist *list, const struct te_tlv *val);
+const struct te_tlvlist *tlv_set_add(struct linpool *pool, const struct te_tlvlist *list, const struct te_tlv *val);
+const struct te_tlvlist *tlv_set_union(struct linpool *pool, const struct te_tlvlist *l1, const struct te_tlvlist *l2);
+int tlvlist_same(const struct te_tlvlist *tl1, const struct te_tlvlist *tl2);
+
+struct adata *tlvlist_encode_tunnel_encap(struct linpool *pool, const struct te_tlvlist *tl);
+struct te_tlvlist *tlvlist_decode_tunnel_encap(struct linpool *pool, const struct adata *ad);
+
#endif
diff --git a/nest/iface.h b/nest/iface.h
index f8e92850..daf84a7c 100644
--- a/nest/iface.h
+++ b/nest/iface.h
@@ -11,6 +11,7 @@
#include "lib/lists.h"
#include "lib/ip.h"
+#include "lib/net.h"
extern list iface_list;
diff --git a/nest/route.h b/nest/route.h
index 085c45b9..7b277eed 100644
--- a/nest/route.h
+++ b/nest/route.h
@@ -552,6 +552,7 @@ const char *ea_custom_name(uint ea);
#define EAF_TYPE_EC_SET 0x0e /* Set of pairs of u32's - ext. community list */
#define EAF_TYPE_LC_SET 0x12 /* Set of triplets of u32's - large community list */
#define EAF_TYPE_IFACE 0x16 /* Interface pointer stored in adata */
+#define EAF_TYPE_TUNNEL_ENCAP 0x1a /* Tunnel Encapsulation (encoding per RFC 9012) */
#define EAF_EMBEDDED 0x01 /* Data stored in eattr.u.data (part of type spec) */
#define EAF_VAR_LENGTH 0x02 /* Attribute length is variable (part of type spec) */
diff --git a/proto/aggregator/aggregator.c b/proto/aggregator/aggregator.c
index e5c2a176..950da103 100644
--- a/proto/aggregator/aggregator.c
+++ b/proto/aggregator/aggregator.c
@@ -560,6 +560,8 @@ aggregator_rt_notify(struct proto *P, struct channel *src_ch, net *net, rte *new
case T_PATH_MASK_ITEM:
case T_ROUTE:
case T_ROUTES_BLOCK:
+ case T_SUBTLV:
+ case T_TLVLIST:
bug("Invalid type %s in hashing", f_type_name(tmp_bucket->aggr_data[i].type));
case T_SET:
MX(t);
diff --git a/sysdep/unix/main.c b/sysdep/unix/main.c
index 0d7788bb..e4d7f925 100644
--- a/sysdep/unix/main.c
+++ b/sysdep/unix/main.c
@@ -30,6 +30,7 @@
#include "lib/event.h"
#include "lib/timer.h"
#include "lib/string.h"
+#include "lib/tunnel_encaps.h"
#include "nest/route.h"
#include "nest/protocol.h"
#include "nest/iface.h"
@@ -899,6 +900,7 @@ main(int argc, char **argv)
mpls_init();
// roa_init();
config_init();
+ tunnel_encap_init();
uid_t use_uid = get_uid(use_user);
gid_t use_gid = get_gid(use_group);