summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile.in1
-rw-r--r--conf/cf-lex.l59
-rw-r--r--configure.ac3
-rw-r--r--filter/config.Y150
-rw-r--r--filter/data.c38
-rw-r--r--filter/data.h13
-rw-r--r--filter/decl.m424
-rw-r--r--filter/f-inst.c79
-rw-r--r--filter/filter.c2
-rw-r--r--lib/Makefile2
-rw-r--r--lib/README.base6462
-rw-r--r--lib/base64.c163
-rw-r--r--lib/base64.h11
-rw-r--r--lib/socket.h1
-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/protocol.h5
-rw-r--r--nest/route.h1
-rw-r--r--proto/bgp/attrs.c43
-rw-r--r--proto/bgp/bgp.h1
-rw-r--r--proto/bgp/config.Y4
-rw-r--r--proto/wireguard/Makefile8
-rw-r--r--proto/wireguard/config.Y191
-rw-r--r--proto/wireguard/wireguard.c974
-rw-r--r--proto/wireguard/wireguard.h66
-rw-r--r--sysdep/linux/Makefile2
-rw-r--r--sysdep/linux/wireguard.c1755
-rw-r--r--sysdep/linux/wireguard.h105
-rw-r--r--sysdep/unix/Makefile2
-rw-r--r--sysdep/unix/io.c77
-rw-r--r--sysdep/unix/main.c5
-rw-r--r--sysdep/unix/unix.h1
-rw-r--r--sysdep/unix/wg_user.c298
-rw-r--r--sysdep/unix/wg_user.h16
39 files changed, 5582 insertions, 13 deletions
diff --git a/Makefile.in b/Makefile.in
index 839efe24..7bcd7d43 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -165,6 +165,7 @@ $(objdir)/sysdep/paths.h: Makefile
$(Q)echo >$@ "/* Generated by Makefile, don't edit manually! */"
$(Q)echo >>$@ "#define PATH_CONFIG_FILE \"@CONFIG_FILE@\""
$(Q)echo >>$@ "#define PATH_CONTROL_SOCKET \"@CONTROL_SOCKET@\""
+ $(Q)echo >>$@ "#define PATH_RUNSTATEDIR \"$(runstatedir)\""
$(Q)if test -n "@iproutedir@" ; then echo >>$@ "#define PATH_IPROUTE_DIR \"@iproutedir@\"" ; fi
# Unit tests rules
diff --git a/conf/cf-lex.l b/conf/cf-lex.l
index 28479ff3..6ac3ab20 100644
--- a/conf/cf-lex.l
+++ b/conf/cf-lex.l
@@ -50,6 +50,7 @@
#include "conf/cf-parse.tab.h"
#include "lib/string.h"
#include "lib/hash.h"
+#include "lib/base64.h"
#include "conf/keywords.h"
@@ -111,10 +112,11 @@ static enum yytokentype cf_lex_symbol(const char *data);
%option nounput
%option noreject
-%x COMMENT CCOMM CLI QUOTED APOSTROPHED INCLUDE
+%x COMMENT CCOMM CLI QUOTED BQUOTED B64QUOTED APOSTROPHED INCLUDE
ALPHA [a-zA-Z_]
DIGIT [0-9]
+OIGIT [0-7]
XIGIT [0-9a-fA-F]
ALNUM [a-zA-Z_0-9]
WHITE [ \t]
@@ -343,6 +345,61 @@ else: {
<QUOTED>. BUFFER_PUSH(quoted_buffer) = yytext[0];
+[b]["] {
+ BEGIN(BQUOTED);
+ quoted_buffer_init();
+}
+
+<BQUOTED>\n cf_error("Unterminated byte string");
+<BQUOTED><<EOF>> cf_error("Unterminated byte string");
+<BQUOTED>["] {
+ BEGIN(INITIAL);
+ struct bytestring *bytes;
+
+ bytes = cfg_allocz(sizeof(*bytes) + quoted_buffer.used);
+ memcpy(bytes->data, quoted_buffer.data, quoted_buffer.used);
+ bytes->length = quoted_buffer.used;
+
+ cf_lval.bs = bytes;
+ return BYTETEXT;
+}
+
+<BQUOTED>. BUFFER_PUSH(quoted_buffer) = yytext[0];
+
+<BQUOTED>[\\]({OIGIT}|{OIGIT}{OIGIT}|{OIGIT}{OIGIT}{OIGIT}) {
+ char buf[4];
+ char *end = NULL;
+ memcpy(buf, yytext + 1, yyleng);
+ buf[yyleng] = 0;
+ BUFFER_PUSH(quoted_buffer) = bstrtoul10(buf, &end);
+}
+
+<BQUOTED>[\\][x]({XIGIT}|{XIGIT}{XIGIT}) {
+ char buf[3];
+ char *end = NULL;
+ memcpy(buf, yytext + 2, yyleng);
+ buf[yyleng] = 0;
+ BUFFER_PUSH(quoted_buffer) = bstrtoul16(buf, &end);
+}
+
+[b][6][4]["] {
+ BEGIN(B64QUOTED);
+ quoted_buffer_init();
+}
+
+<B64QUOTED>\n cf_error("Unterminated base64 string");
+<B64QUOTED><<EOF>> cf_error("Unterminated base64 string");
+<B64QUOTED>["] {
+ BEGIN(INITIAL);
+ cf_lval.bs = base64_decode_bs(cfg_mem, quoted_buffer.data, quoted_buffer.used, NULL);
+ if (!cf_lval.bs)
+ cf_error("Invalid base64 string");
+
+ return BYTETEXT;
+}
+
+<B64QUOTED>. BUFFER_PUSH(quoted_buffer) = yytext[0];
+
<INITIAL,COMMENT><<EOF>> { if (check_eof()) return END; }
{WHITE}+
diff --git a/configure.ac b/configure.ac
index c9c52038..de3cb50a 100644
--- a/configure.ac
+++ b/configure.ac
@@ -312,7 +312,7 @@ if test "$enable_mpls_kernel" != no ; then
fi
fi
-all_protocols="$proto_bfd babel bgp mrt ospf perf pipe radv rip rpki static"
+all_protocols="$proto_bfd babel bgp mrt ospf perf pipe radv rip rpki static wireguard"
all_protocols=`echo $all_protocols | sed 's/ /,/g'`
@@ -331,6 +331,7 @@ AH_TEMPLATE([CONFIG_RADV], [RAdv protocol])
AH_TEMPLATE([CONFIG_RIP], [RIP protocol])
AH_TEMPLATE([CONFIG_RPKI], [RPKI protocol])
AH_TEMPLATE([CONFIG_STATIC], [Static protocol])
+AH_TEMPLATE([CONFIG_WIREGUARD], [Wireguard protocol])
AC_MSG_CHECKING([protocols])
protocols=`echo "$with_protocols" | sed 's/,/ /g'`
diff --git a/filter/config.Y b/filter/config.Y
index dfabddf7..5abaca52 100644
--- a/filter/config.Y
+++ b/filter/config.Y
@@ -196,6 +196,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 bytestring *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 bytestring *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;
@@ -338,7 +470,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,
@@ -351,7 +483,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
@@ -368,7 +500,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 set_items switch_items switch_body sub_tlv_item
%type <trie> fprefix_set
%type <v> set_atom switch_atom fipa
%type <px> fprefix
@@ -447,6 +579,8 @@ type:
| CLIST { $$ = T_CLIST; }
| ECLIST { $$ = T_ECLIST; }
| LCLIST { $$ = T_LCLIST; }
+ | SUBTLV { $$ = T_SUBTLV; }
+ | TLVLIST { $$ = T_TLVLIST; }
| type SET {
switch ($1) {
case T_INT:
@@ -456,6 +590,7 @@ type:
case T_LC:
case T_RD:
case T_IP:
+ case T_SUBTLV:
$$ = T_SET;
break;
@@ -680,10 +815,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); }
;
@@ -692,6 +833,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); }
;
@@ -897,10 +1039,12 @@ term:
| term_dot_method
+ | EMPTY { $$ = f_new_inst(FI_CONSTANT, val_empty(T_EMPTY_LIST)); }
| '+' EMPTY '+' { $$ = f_new_inst(FI_CONSTANT, val_empty(T_PATH)); }
| '-' 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 89b75e56..d01b5764 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",
};
@@ -85,7 +89,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 *
@@ -296,6 +300,8 @@ val_same(const struct f_val *v1, const struct f_val *v2)
return same_tree(v1->val.t, v2->val.t);
case T_PREFIX_SET:
return trie_same(v1->val.ti, v2->val.ti);
+ case T_TLVLIST:
+ return tlvlist_same(v1->val.tl, v2->val.tl);
default:
bug("Invalid type in val_same(): %x", v1->type);
}
@@ -402,6 +408,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)
{
@@ -503,6 +521,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
@@ -563,6 +595,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);
@@ -597,6 +632,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;
default: buffer_print(buf, "[unknown type %x]", v->type); return;
}
diff --git a/filter/data.h b/filter/data.h
index 3430455a..73242f94 100644
--- a/filter/data.h
+++ b/filter/data.h
@@ -19,6 +19,7 @@
enum f_type {
/* Nothing. Simply nothing. */
T_VOID = 0,
+ T_EMPTY_LIST = 1,
T_NONE = 1, /* Special hack to represent missing arguments */
@@ -61,6 +62,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_SET = 0x80,
T_PREFIX_SET = 0x81,
@@ -90,6 +93,9 @@ struct f_val {
const struct adata *ad;
const struct f_path_mask *path_mask;
struct f_path_mask_item pmi;
+ struct te_subtlv st;
+ const struct te_tlv *tlv;
+ const struct te_tlvlist *tl;
} val;
};
@@ -308,6 +314,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);
@@ -316,6 +324,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 */
@@ -337,8 +346,12 @@ val_empty(enum f_type t)
case T_CLIST:
case T_ECLIST:
case T_LCLIST:
+ 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 }; // FIXME
+
default:
return (struct f_val) { };
}
diff --git a/filter/decl.m4 b/filter/decl.m4
index 57bf9454..93ff440e 100644
--- a/filter/decl.m4
+++ b/filter/decl.m4
@@ -594,6 +594,30 @@ f_const_promotion_(struct f_inst *arg, enum f_type want, int update)
if (update)
*c = f_const_empty_prefix_set;
return 1;
+ } else if ((c->type == T_EMPTY_LIST) && (want == T_CLIST)) {
+ *c = (struct f_val) {
+ .type = T_CLIST,
+ .val.ad = &null_adata,
+ };
+ return 1;
+ } else if ((c->type == T_EMPTY_LIST) && (want == T_ECLIST)) {
+ *c = (struct f_val) {
+ .type = T_ECLIST,
+ .val.ad = &null_adata,
+ };
+ return 1;
+ } else if ((c->type == T_EMPTY_LIST) && (want == T_LCLIST)) {
+ *c = (struct f_val) {
+ .type = T_LCLIST,
+ .val.ad = &null_adata,
+ };
+ return 1;
+ } else if ((c->type == T_EMPTY_LIST) && (want == T_TLVLIST)) {
+ *c = (struct f_val) {
+ .type = T_TLVLIST,
+ .val.tl = NULL,
+ };
+ return 1;
}
return 0;
diff --git a/filter/f-inst.c b/filter/f-inst.c
index a7bec81e..59f3415d 100644
--- a/filter/f-inst.c
+++ b/filter/f-inst.c
@@ -838,6 +838,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");
}
@@ -898,6 +906,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");
}
@@ -1280,6 +1298,25 @@
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("Can't add non-tlv");
+
+ 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);
@@ -1395,6 +1432,21 @@
RESULT(T_LCLIST, ad, [[ lclist_filter(fpool, v1.val.ad, &v2, 0) ]]);
}
+ INST(FI_TLVLIST_DEL_SET, 2, 1) {
+ ARG(1, T_TLVLIST);
+ ARG(2, T_SET);
+ METHOD_CONSTRUCTOR("delete");
+#if 0
+ /* v2.val is sub-TLV-set */
+ if (!subtlv_set_type(v2.val.t))
+ runtime("Mismatched set type");
+
+ RESULT(T_TLVLIST, tl, [[ tlvlist_filter(fpool, v1.val.tl, &v2, 0) ]]);
+#else
+ runtime("Can't delete TLVLIST");
+#endif
+ }
+
INST(FI_PATH_FILTER_SET, 2, 1) {
ARG(1, T_PATH);
ARG(2, T_SET);
@@ -1460,6 +1512,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 0
+ if (!subtlv_set_type(v2.val.t))
+ runtime("Mismatched set type");
+
+ RESULT(T_TLVLIST, tl, [[ tlvlist_filter(fpool, v1.val.tl, &v2, 1) ]]);
+#else
+ runtime("Can't filter tlv");
+#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("Can't filter tlv");
+#endif
+ }
+
INST(FI_ROA_CHECK_IMPLICIT, 0, 1) { /* ROA Check */
NEVER_CONSTANT;
RTC(1);
diff --git a/filter/filter.c b/filter/filter.c
index 65fb92a4..e2338be8 100644
--- a/filter/filter.c
+++ b/filter/filter.c
@@ -45,6 +45,8 @@
#include "filter/data.h"
+#undef DEBUGGING
+
/* Exception bits */
enum f_exception {
FE_RETURN = 0x1,
diff --git a/lib/Makefile b/lib/Makefile
index 812f721c..1534b794 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -1,4 +1,4 @@
-src := 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/README.base64 b/lib/README.base64
new file mode 100644
index 00000000..0c131123
--- /dev/null
+++ b/lib/README.base64
@@ -0,0 +1,62 @@
+base64.c was downloaded on 31 Aug 2021 from
+http://web.mit.edu/freebsd/head/contrib/wpa/src/utils/base64.c
+
+and the following text on 31 Aug 2021 from
+http://web.mit.edu/freebsd/head/contrib/wpa/
+
+wpa_supplicant and hostapd
+--------------------------
+
+Copyright (c) 2002-2012, Jouni Malinen <j@w1.fi> and contributors
+All Rights Reserved.
+
+These programs are licensed under the BSD license (the one with
+advertisement clause removed).
+
+If you are submitting changes to the project, please see CONTRIBUTIONS
+file for more instructions.
+
+
+This package may include either wpa_supplicant, hostapd, or both. See
+README file respective subdirectories (wpa_supplicant/README or
+hostapd/README) for more details.
+
+Source code files were moved around in v0.6.x releases and compared to
+earlier releases, the programs are now built by first going to a
+subdirectory (wpa_supplicant or hostapd) and creating build
+configuration (.config) and running 'make' there (for Linux/BSD/cygwin
+builds).
+
+
+License
+-------
+
+This software may be distributed, used, and modified under the terms of
+BSD license:
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+3. Neither the name(s) of the above-listed copyright holder(s) nor the
+ names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/lib/base64.c b/lib/base64.c
new file mode 100644
index 00000000..d5f3ae9b
--- /dev/null
+++ b/lib/base64.c
@@ -0,0 +1,163 @@
+/*
+ * Base64 encoding/decoding (RFC1341)
+ * Copyright (c) 2005-2011, Jouni Malinen <j@w1.fi>
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README.base64 for more details.
+ */
+
+#include "nest/bird.h"
+#include "conf/conf.h"
+#include "lib/resource.h"
+#include "lib/base64.h"
+
+static const unsigned char base64_table[65] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+/**
+ * base64_encode - Base64 encode
+ * @src: Data to be encoded
+ * @len: Length of the data to be encoded
+ * @out_len: Pointer to output length variable, or %NULL if not used
+ * Returns: Allocated buffer of out_len bytes of encoded data,
+ * or %NULL on failure
+ *
+ * Caller is responsible for freeing the returned buffer. Returned buffer is
+ * nul terminated to make it easier to use as a C string. The nul terminator is
+ * not included in out_len.
+ */
+struct bytestring * base64_encode(linpool *pool,
+ const unsigned char *src, size_t len,
+ size_t *out_len)
+{
+ unsigned char *pos;
+ struct bytestring *out;
+ const unsigned char *end, *in;
+ size_t olen;
+ int line_len;
+
+ olen = len * 4 / 3 + 4; /* 3-byte blocks to 4-byte */
+ olen += olen / 72; /* line feeds */
+ olen++; /* nul termination */
+ if (olen < len)
+ return NULL; /* integer overflow */
+ out = lp_alloc(pool, sizeof(struct bytestring) + olen);
+ if (out == NULL)
+ return NULL;
+
+ end = src + len;
+ in = src;
+ pos = out->data;
+ line_len = 0;
+ while (end - in >= 3) {
+ *pos++ = base64_table[in[0] >> 2];
+ *pos++ = base64_table[((in[0] & 0x03) << 4) | (in[1] >> 4)];
+ *pos++ = base64_table[((in[1] & 0x0f) << 2) | (in[2] >> 6)];
+ *pos++ = base64_table[in[2] & 0x3f];
+ in += 3;
+ line_len += 4;
+ if (line_len >= 72) {
+ *pos++ = '\n';
+ line_len = 0;
+ }
+ }
+
+ if (end - in) {
+ *pos++ = base64_table[in[0] >> 2];
+ if (end - in == 1) {
+ *pos++ = base64_table[(in[0] & 0x03) << 4];
+ *pos++ = '=';
+ } else {
+ *pos++ = base64_table[((in[0] & 0x03) << 4) |
+ (in[1] >> 4)];
+ *pos++ = base64_table[(in[1] & 0x0f) << 2];
+ }
+ *pos++ = '=';
+ line_len += 4;
+ }
+
+ if (line_len)
+ *pos++ = '\n';
+
+ *pos = '\0';
+ out->length = pos - out->data;
+ if (out_len)
+ *out_len = out->length;
+ return out;
+}
+
+
+/**
+ * base64_decode - Base64 decode
+ * @src: Data to be decoded
+ * @len: Length of the data to be decoded
+ * @out_len: Pointer to output length variable
+ * Returns: Allocated buffer of out_len bytes of decoded data,
+ * or %NULL on failure
+ *
+ * Caller is responsible for freeing the returned buffer.
+ */
+struct bytestring * base64_decode_bs(linpool *pool,
+ const unsigned char *src, size_t len,
+ size_t *out_len)
+{
+ unsigned char dtable[256], *pos, block[4], tmp;
+ size_t i, count, olen;
+ int pad = 0;
+ struct bytestring *out;
+
+ memset(dtable, 0x80, 256);
+ for (i = 0; i < sizeof(base64_table) - 1; i++)
+ dtable[base64_table[i]] = (unsigned char) i;
+ dtable['='] = 0;
+
+ count = 0;
+ for (i = 0; i < len; i++) {
+ if (dtable[src[i]] != 0x80)
+ count++;
+ }
+
+ if (count == 0 || count % 4)
+ return NULL;
+
+ olen = count / 4 * 3;
+ out = lp_alloc(pool, sizeof(struct bytestring) + olen);
+ if (out == NULL)
+ return NULL;
+ pos = out->data;
+
+ count = 0;
+ for (i = 0; i < len; i++) {
+ tmp = dtable[src[i]];
+ if (tmp == 0x80)
+ continue;
+
+ if (src[i] == '=')
+ pad++;
+ block[count] = tmp;
+ count++;
+ if (count == 4) {
+ *pos++ = (block[0] << 2) | (block[1] >> 4);
+ *pos++ = (block[1] << 4) | (block[2] >> 2);
+ *pos++ = (block[2] << 6) | block[3];
+ count = 0;
+ if (pad) {
+ if (pad == 1)
+ pos--;
+ else if (pad == 2)
+ pos -= 2;
+ else {
+ /* Invalid padding */
+ //os_free(out);
+ return NULL;
+ }
+ break;
+ }
+ }
+ }
+
+ out->length = pos - out->data;
+ if (out_len)
+ *out_len = out->length;
+ return out;
+}
diff --git a/lib/base64.h b/lib/base64.h
new file mode 100644
index 00000000..8ca4dd9b
--- /dev/null
+++ b/lib/base64.h
@@ -0,0 +1,11 @@
+#ifndef _BIRD_BASE64_H_
+#define _BIRD_BASE64_H_
+
+#include "lib/resource.h"
+
+struct bytestring * base64_encode_bs(linpool *pool, const unsigned char *src, size_t len,
+ size_t *out_len);
+struct bytestring * base64_decode_bs(linpool *pool, const unsigned char *src, size_t len,
+ size_t *out_len);
+
+#endif /* _BIRD_BASE64_H_ */
diff --git a/lib/socket.h b/lib/socket.h
index 0b6ac589..a7d5acb1 100644
--- a/lib/socket.h
+++ b/lib/socket.h
@@ -144,6 +144,7 @@ extern int sk_priority_control; /* Suggested priority for control traffic, shou
#define SK_UNIX 9
#define SK_SSH_ACTIVE 10 /* - - * * - ? - DA = host */
#define SK_SSH 11
+#define SK_UNIX_ACTIVE 12
/*
* Socket subtypes
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 5a244c75..b263a2b9 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 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 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 fcd5ac16..a874ffe1 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);
@@ -241,4 +247,65 @@ int lc_set_walk(const struct adata *list, uint *pos, lcomm *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/protocol.h b/nest/protocol.h
index dad0b781..4b06d54c 100644
--- a/nest/protocol.h
+++ b/nest/protocol.h
@@ -54,6 +54,7 @@ enum protocol_class {
PROTOCOL_RIP,
PROTOCOL_RPKI,
PROTOCOL_STATIC,
+ PROTOCOL_WG,
PROTOCOL__MAX
};
@@ -104,7 +105,8 @@ void protos_dump_all(void);
extern struct protocol
proto_device, proto_radv, proto_rip, proto_static, proto_mrt,
proto_ospf, proto_perf,
- proto_pipe, proto_bgp, proto_bmp, proto_bfd, proto_babel, proto_rpki;
+ proto_pipe, proto_bgp, proto_bmp, proto_bfd, proto_babel, proto_rpki,
+ proto_wireguard;
/*
* Routing Protocol Instance
@@ -478,6 +480,7 @@ struct channel_class {
extern const struct channel_class channel_basic;
extern const struct channel_class channel_bgp;
+extern const struct channel_class channel_wg;
struct channel_config {
node n;
diff --git a/nest/route.h b/nest/route.h
index 7aec7117..4b5ddcdf 100644
--- a/nest/route.h
+++ b/nest/route.h
@@ -543,6 +543,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/bgp/attrs.c b/proto/bgp/attrs.c
index de45cae0..0ca40581 100644
--- a/proto/bgp/attrs.c
+++ b/proto/bgp/attrs.c
@@ -21,6 +21,7 @@
#include "lib/resource.h"
#include "lib/string.h"
#include "lib/unaligned.h"
+#include "lib/tunnel_encaps.h"
#include "bgp.h"
@@ -916,6 +917,39 @@ bgp_decode_otc(struct bgp_parse_state *s, uint code UNUSED, uint flags, byte *da
static void
+bgp_export_tunnel_encap(struct bgp_export_state *s UNUSED, eattr *a)
+{
+ if (a->u.ptr->length == 0)
+ UNSET(a);
+
+ /*
+ * TODO: In situations where a tunnel could be encoded using a barebones TLV,
+ * it MUST be encoded using the corresponding Encapsulation Extended
+ * Community. (RFC9012 section 4.1)
+ */
+}
+
+static void
+bgp_decode_tunnel_encap(struct bgp_parse_state *s, uint code UNUSED, uint flags, byte *data, uint len, ea_list **to)
+{
+ bgp_set_attr_data(to, s->pool, BA_TUNNEL_ENCAP, flags, data, len);
+}
+
+static int
+bgp_encode_tunnel_encap(struct bgp_write_state *s UNUSED, eattr *a, byte *buf, uint size)
+{
+ return bgp_put_attr(buf, size, EA_ID(a->id), a->flags, a->u.ptr->data, a->u.ptr->length);
+}
+
+static void
+bgp_format_tunnel_encap(const eattr *a, byte *buf, uint size)
+{
+ int l = format_tunnel_encap(a->u.ptr, buf, size);
+ if (l > 0)
+ ADVANCE(buf, size, l);
+}
+
+static void
bgp_export_mpls_label_stack(struct bgp_export_state *s, eattr *a)
{
net_addr *n = s->route->net->n.addr;
@@ -1135,6 +1169,15 @@ static const struct bgp_attr_desc bgp_attr_table[] = {
.encode = bgp_encode_u32,
.decode = bgp_decode_otc,
},
+ [BA_TUNNEL_ENCAP] = {
+ .name = "tunnel_encap",
+ .type = EAF_TYPE_TUNNEL_ENCAP,
+ .flags = BAF_OPTIONAL | BAF_TRANSITIVE,
+ .export = bgp_export_tunnel_encap,
+ .encode = bgp_encode_tunnel_encap,
+ .decode = bgp_decode_tunnel_encap,
+ .format = bgp_format_tunnel_encap,
+ },
[BA_MPLS_LABEL_STACK] = {
.name = "mpls_label_stack",
.type = EAF_TYPE_INT_SET,
diff --git a/proto/bgp/bgp.h b/proto/bgp/bgp.h
index c11433ec..178ff827 100644
--- a/proto/bgp/bgp.h
+++ b/proto/bgp/bgp.h
@@ -702,6 +702,7 @@ byte *bgp_create_end_mark_(struct bgp_channel *c, byte *buf);
#define BA_EXT_COMMUNITY 0x10 /* RFC 4360 */
#define BA_AS4_PATH 0x11 /* RFC 6793 */
#define BA_AS4_AGGREGATOR 0x12 /* RFC 6793 */
+#define BA_TUNNEL_ENCAP 0x17 /* RFC 9012 */
#define BA_AIGP 0x1a /* RFC 7311 */
#define BA_LARGE_COMMUNITY 0x20 /* RFC 8092 */
#define BA_ONLY_TO_CUSTOMER 0x23 /* RFC 9234 */
diff --git a/proto/bgp/config.Y b/proto/bgp/config.Y
index d9ff24d8..2252d7fe 100644
--- a/proto/bgp/config.Y
+++ b/proto/bgp/config.Y
@@ -32,7 +32,7 @@ CF_KEYWORDS(BGP, LOCAL, NEIGHBOR, AS, HOLD, TIME, CONNECT, RETRY, KEEPALIVE,
LIVED, STALE, IMPORT, IBGP, EBGP, MANDATORY, INTERNAL, EXTERNAL, SETS,
DYNAMIC, RANGE, NAME, DIGITS, BGP_AIGP, AIGP, ORIGINATE, COST, ENFORCE,
FIRST, FREE, VALIDATE, BASE, ROLE, ROLES, PEER, PROVIDER, CUSTOMER,
- RS_SERVER, RS_CLIENT, REQUIRE, BGP_OTC, GLOBAL)
+ RS_SERVER, RS_CLIENT, REQUIRE, BGP_OTC, GLOBAL, BGP_TUNNEL_ENCAP)
%type <i> bgp_nh
%type <i32> bgp_afi
@@ -361,6 +361,8 @@ dynamic_attr: BGP_LARGE_COMMUNITY
{ $$ = f_new_dynamic_attr(EAF_TYPE_LC_SET, T_LCLIST, EA_CODE(PROTOCOL_BGP, BA_LARGE_COMMUNITY)); } ;
dynamic_attr: BGP_OTC
{ $$ = f_new_dynamic_attr(EAF_TYPE_INT, T_INT, EA_CODE(PROTOCOL_BGP, BA_ONLY_TO_CUSTOMER)); } ;
+dynamic_attr: BGP_TUNNEL_ENCAP
+ { $$ = f_new_dynamic_attr(EAF_TYPE_TUNNEL_ENCAP, T_TLVLIST, EA_CODE(PROTOCOL_BGP, BA_TUNNEL_ENCAP)); } ;
custom_attr: ATTRIBUTE BGP NUM type symbol ';' {
if($3 > 255 || $3 < 1)
diff --git a/proto/wireguard/Makefile b/proto/wireguard/Makefile
new file mode 100644
index 00000000..26a7970f
--- /dev/null
+++ b/proto/wireguard/Makefile
@@ -0,0 +1,8 @@
+WG := $(srcdir)/../WireGuard/contrib/examples/embeddable-wg-library
+
+src := wireguard.c
+obj := $(src-o-files)
+$(all-daemon)
+$(cf-local)
+
+tests_objs := $(tests_objs) $(src-o-files)
diff --git a/proto/wireguard/config.Y b/proto/wireguard/config.Y
new file mode 100644
index 00000000..6a435da3
--- /dev/null
+++ b/proto/wireguard/config.Y
@@ -0,0 +1,191 @@
+/*
+ * BIRD -- Wireguard Protocol Configuration
+ *
+ * (c) 1999 Martin Mares <mj@ucw.cz>
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+CF_HDR
+
+#define LOCAL_DEBUG
+#include "proto/wireguard/wireguard.h"
+
+CF_DEFINES
+
+#define WG_DEFAULT_TUNNEL_TYPE 51820
+
+#define WG_CFG ((struct wg_config *) this_proto)
+
+static struct peer_config *this_peer = NULL;
+
+typedef char wg_key_b64_string[45];
+int wg_key_from_base64(u8 key[32], const wg_key_b64_string base64);
+
+static struct f_tree *
+f_new_sub_tlv_wg(u32 type, const struct bytestring *bs)
+{
+ struct f_tree *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 = NULL;
+ v.u.tunnel_encap.length = 0;
+ if (bs && bs->length == sizeof(wg_key))
+ {
+#if 1
+ // encap type 0
+ v.u.tunnel_encap.data = bs->data;
+ v.u.tunnel_encap.length = bs->length;
+#else
+ // encap type 1
+ v.u.tunnel_encap.length = 4 + bs->length;
+ void *data = cfg_alloc(v.u.tunnel_encap.length);
+ memset(data, 0, 4);
+ memcpy(data + 4, bs->data, bs->length);
+ v.u.tunnel_encap.data = data;
+#endif
+ }
+ else if (bs)
+ {
+ cf_error( "Invalid WireGuard key" );
+ }
+ t->from.val.st = v;
+ t->to.val.st = v;
+ return t;
+}
+
+CF_DECLS
+
+%type <g> allowed_ip allowed_ips
+%type <bs> wg_key
+
+CF_KEYWORDS(WIREGUARD, TUNNEL_TYPE, PRIVATE_KEY, LISTEN_PORT, PUBLIC_KEY, ENDPOINT, ALLOWED_IPS)
+
+CF_GRAMMAR
+
+proto: wireguard_proto '}' ;
+
+sub_tlv_item:
+ '(' WIREGUARD ',' wg_key ')' { $$ = f_new_sub_tlv_wg(WG_DEFAULT_TUNNEL_TYPE, $4); }
+ | '(' WIREGUARD ',' cnum ',' wg_key ')' { $$ = f_new_sub_tlv_wg($4, $6); }
+ ;
+
+wireguard_proto_start: proto_start WIREGUARD {
+ this_proto = proto_config_new(&proto_wireguard, $1);
+ init_list(&WG_CFG->peers);
+ WG_CFG->tunnel_type = WG_DEFAULT_TUNNEL_TYPE;
+ }
+ ;
+
+wireguard_proto:
+ wireguard_proto_start proto_name '{'
+ | wireguard_proto wg_proto_channel ';'
+ | wireguard_proto proto_item ';'
+ | wireguard_proto TUNNEL_TYPE tunnel_type ';'
+ | wireguard_proto INTERFACE TEXT ';' { WG_CFG->ifname = $3; }
+ | wireguard_proto PRIVATE_KEY private_key ';'
+ | wireguard_proto LISTEN_PORT listen_port ';'
+ | wireguard_proto wg_peer ';'
+ ;
+
+wg_peer: wg_peer_start wg_peer_opt_list wg_peer_end;
+
+wg_peer_start: PEER { this_peer = peer_new(WG_CFG); }
+
+wg_peer_end: {
+ this_peer = NULL;
+}
+ ;
+
+wg_peer_item:
+ PUBLIC_KEY public_key
+ | ENDPOINT endpoint
+ | PORT port
+ | ALLOWED_IPS allowed_ips { this_peer->allowedips = $2; }
+ ;
+
+wg_peer_opts:
+ /* empty */
+ | wg_peer_opts wg_peer_item ';'
+ ;
+
+wg_peer_opt_list:
+ '{' wg_peer_opts '}'
+ ;
+
+tunnel_type: expr { WG_CFG->tunnel_type = $1; }
+
+private_key: wg_key { WG_CFG->private_key = $1->data; }
+
+listen_port: expr { WG_CFG->listen_port = $1; }
+
+public_key: wg_key { this_peer->public_key = $1->data; }
+
+endpoint: ipa { this_peer->endpoint = $1; }
+
+port: expr { this_peer->remote_port = $1; }
+
+allowed_ips: allowed_ip {
+ struct wg_allowedips *aips = cfg_alloc(sizeof(struct wg_allowedips));
+ aips->first_allowedip = $1;
+ aips->last_allowedip = $1;
+ $$ = aips;
+ }
+ | allowed_ips ',' allowed_ip {
+ struct wg_allowedips *aips = $1;
+ struct wg_allowedip *aip = $3;
+ aips->last_allowedip->next_allowedip = aip;
+ aips->last_allowedip = aip;
+ $$ = aips;
+ }
+ ;
+
+allowed_ip:
+ net_or_ipa {
+ struct wg_allowedip *aip = cfg_alloc(sizeof(struct wg_allowedip));
+ switch ($1.type)
+ {
+ case NET_IP4:
+ aip->family = AF_INET;
+ aip->ip4 = ipa_to_in4(net_prefix(&($1)));
+ aip->cidr = net_pxlen(&($1));
+ break;
+ case NET_IP6:
+ aip->family = AF_INET6;
+ aip->ip6 = ipa_to_in6(net_prefix(&($1)));
+ aip->cidr = net_pxlen(&($1));
+ break;
+ default:
+ cf_error( "Unknown net type: %d", $1.type );
+ }
+
+ aip->next_allowedip = NULL;
+ $$ = aip;
+ }
+ ;
+
+wg_proto_channel: wg_channel_start channel_opt_list wg_channel_end;
+
+wg_channel_start: net_type
+{
+ this_channel = channel_config_get(&channel_wg, net_label[$1], $1, this_proto);
+}
+
+wg_channel_end:
+{
+ this_channel = NULL;
+}
+
+wg_key: bytestring
+{
+ if ($1->length != sizeof(wg_key))
+ cf_error( "Invalid WireGuard key" );
+ $$ = $1;
+}
+
+CF_CODE
+
+CF_END
diff --git a/proto/wireguard/wireguard.c b/proto/wireguard/wireguard.c
new file mode 100644
index 00000000..8b1e7ecc
--- /dev/null
+++ b/proto/wireguard/wireguard.c
@@ -0,0 +1,974 @@
+// Based on proto/rip/rip.c
+
+#define LOCAL_DEBUG
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/un.h>
+#include <unistd.h>
+#include "lib/lists.h"
+#include "lib/ip.h"
+#include "lib/tunnel_encaps.h"
+#include "nest/protocol.h"
+#include "nest/iface.h"
+#include "sysdep/linux/wireguard.h"
+#include "sysdep/unix/unix.h"
+#include "sysdep/unix/wg_user.h"
+#include "wireguard.h"
+
+static ip_addr allowedip_to_ipa(struct wg_allowedip *allowedip);
+static int wg_format_tunnel_encap(const struct te_encap *encap, byte *buf, uint size);
+
+static
+int get_device(struct wg_proto *p, wg_device **pdev, const char *device_name)
+{
+ struct wg_config *c = (struct wg_config *) p->p.cf;
+
+ /* if (has_user_space(p)) */
+ /* return user_get_device(p, dev, device_name); */
+ /* else */
+ /* return wg_get_device(dev, device_name); */
+
+ if (p->dev)
+ {
+ wg_free_device(p->dev);
+ p->dev = NULL;
+ }
+
+ wg_device *dev = calloc(1, sizeof(wg_device));
+ strncpy(dev->name, device_name, sizeof(dev->name));
+// dev->flags = WGDEVICE_REPLACE_PEERS;
+ if (c->private_key)
+ {
+ dev->flags |= WGDEVICE_HAS_PRIVATE_KEY | WGDEVICE_HAS_PUBLIC_KEY;
+ memcpy(dev->private_key, c->private_key, sizeof(wg_key));
+ wg_generate_public_key(dev->public_key, c->private_key);
+ }
+ if (c->listen_port)
+ {
+ dev->flags |= WGDEVICE_HAS_LISTEN_PORT;
+ dev->listen_port = c->listen_port;
+ }
+ DBG("listen port %d\n", c->listen_port);
+
+ struct peer_config *pc = NULL;
+ WALK_LIST(pc,c->peers)
+ {
+ wg_peer *peer = calloc(1, sizeof(wg_peer));
+ if (!dev->first_peer)
+ dev->first_peer = peer;
+ if (dev->last_peer)
+ dev->last_peer->next_peer = peer;
+ dev->last_peer = peer;
+
+ peer->flags = WGPEER_REPLACE_ALLOWEDIPS;
+
+ if (pc->public_key)
+ {
+ peer->flags = WGPEER_HAS_PUBLIC_KEY;
+ memcpy(peer->public_key, pc->public_key, sizeof(wg_key));
+ }
+ peer->next_peer = NULL;
+
+ if (!ip6_equal(pc->endpoint, IPA_NONE))
+ sockaddr_fill((sockaddr*)&peer->endpoint.addr,
+ ipa_is_ip4(pc->endpoint) ? AF_INET : AF_INET6,
+ pc->endpoint, NULL, pc->remote_port);
+
+ peer->first_allowedip = NULL;
+ peer->last_allowedip = NULL;
+
+ struct wg_allowedip *aip;
+ wg_for_each_allowedip(pc->allowedips, aip)
+ {
+ struct wg_allowedip *new_aip = malloc(sizeof(struct wg_allowedip));
+ memcpy(new_aip, aip, sizeof(struct wg_allowedip));
+ new_aip->next_allowedip = NULL;
+
+ if (!peer->first_allowedip)
+ peer->first_allowedip = new_aip;
+
+ if (peer->last_allowedip)
+ peer->last_allowedip->next_allowedip = new_aip;
+
+ peer->last_allowedip = new_aip;
+ }
+ }
+
+ *pdev = dev;
+ return 0;
+}
+
+static int
+set_device(struct wg_proto *p)
+{
+ struct wg_config *c = (struct wg_config *) p->p.cf;
+
+ if (wg_has_userspace(c->ifname))
+ return wg_user_set_device(p->p.pool, c->ifname, p->dev);
+ else
+ {
+ WG_TRACE(D_EVENTS, "WG: wg_set_device");
+ return wg_set_device(p->dev);
+ }
+}
+
+static void
+wg_init_entry(struct fib *f UNUSED, void *e_)
+{
+ struct wg_entry *e UNUSED = e_;
+// DBG("wg_init_entry\n");
+
+ memset(e, 0, sizeof(struct wg_entry) - sizeof(struct fib_node));
+}
+
+static void
+wg_postconfig(struct proto_config *C UNUSED)
+{
+ struct wg_config *c = (struct wg_config *) C;
+
+ DBG("postconfig %d\n", c->tunnel_type);
+ register_format_tunnel_encap(c->tunnel_type, "WireGuard", wg_format_tunnel_encap);
+}
+
+static void
+wg_if_notify(struct proto *P, unsigned flags, struct iface *i)
+{
+ struct wg_proto *p = (struct wg_proto *) P;
+ struct wg_config *c = (struct wg_config *) P->cf;
+
+ DBG("WG: if_notify %p %s %d %s\n", i, i->name, flags, c->ifname);
+ if (c->ifname && !strcmp(i->name, c->ifname))
+ {
+ DBG("WG: found ifname\n");
+ p->iface = i;
+ }
+
+ if (flags & IF_CHANGE_UP)
+ {
+ DBG("WG: IF_CHANGE_UP %s\n", i->name);
+
+ int res = set_device(p);
+ WG_TRACE(D_EVENTS, "WG: wg_set_device %d", res);
+ }
+}
+
+static void
+dump(void *ptr, size_t len)
+{
+ unsigned char *data = ptr;
+
+ for (size_t i=0; i<len; i++)
+ {
+ fprintf(stderr, "%02x ", data[i]);
+ }
+ fprintf(stderr, "\n");
+}
+
+static void
+dump_peer(struct wg_peer *peer)
+{
+ wg_key_b64_string base64;
+ wg_key_to_base64(base64, peer->public_key);
+ DBG("WG: peer %s\n", base64);
+
+ struct wg_allowedip *allowedip = NULL;
+ wg_for_each_allowedip(peer, allowedip)
+ {
+ ip_addr ip = allowedip_to_ipa(allowedip);
+
+ DBG("allowedip %I/%d\n", ip, allowedip->cidr);
+ }
+}
+
+static wg_peer *
+add_peer(wg_device *dev, const wg_key *pubkey)
+{
+ struct wg_peer *peer = malloc(sizeof(struct wg_peer));
+ memset(peer, 0, sizeof(struct wg_peer));
+
+ peer->flags = WGPEER_HAS_PUBLIC_KEY;
+ memcpy(peer->public_key, pubkey, sizeof(wg_key));
+
+ if (dev->first_peer && dev->last_peer)
+ dev->last_peer->next_peer = peer;
+ else
+ dev->first_peer = peer;
+ dev->last_peer = peer;
+ return peer;
+}
+
+static void
+remove_marked_peer(struct wg_proto *p)
+{
+ wg_device *dev = p->dev;
+ struct wg_peer *peer = NULL;
+ struct wg_peer *prevpeer = NULL;
+
+ WG_TRACE(D_EVENTS, "WG: remove_marked_peer");
+ wg_for_each_peer(dev, peer)
+ {
+ if (peer->flags & WGPEER_REMOVE_ME)
+ {
+ if (!prevpeer)
+ {
+ DBG("WG: remove first peer\n");
+ dev->first_peer = peer->next_peer;
+ if (dev->last_peer == peer)
+ {
+ dev->last_peer = NULL;
+ dev->first_peer = NULL;
+ }
+ }
+ else
+ {
+ DBG("WG: remove middle peer\n");
+ // Remove
+ if (dev->last_peer == peer)
+ dev->last_peer = prevpeer;
+
+ prevpeer->next_peer = peer->next_peer;
+ }
+
+ free(peer);
+ return;
+ }
+
+ prevpeer = peer;
+ }
+ log(L_WARN "WG: marked peer not found");
+}
+
+static int
+set_peer_tunnel_ep(struct wg_proto *p, wg_peer *peer, ip_addr tunnel_ep_addr, u16 udp_dest_port)
+{
+ if (udp_dest_port != 0 && ipa_nonzero(tunnel_ep_addr) )
+ {
+ if (ipa_is_ip4(tunnel_ep_addr))
+ {
+ WG_TRACE(D_EVENTS, "WG: found ip4 ep");
+ peer->endpoint.addr4.sin_family = AF_INET;
+ put_ip4(&peer->endpoint.addr4.sin_addr.s_addr, ipa_to_ip4(tunnel_ep_addr));
+ put_u16(&peer->endpoint.addr4.sin_port, udp_dest_port);
+ }
+ else
+ {
+ WG_TRACE(D_EVENTS, "WG: found ip6 ep");
+ peer->endpoint.addr6.sin6_family = AF_INET6;
+ put_ip6(&peer->endpoint.addr6.sin6_addr, ipa_to_ip6(tunnel_ep_addr));
+ put_u16(&peer->endpoint.addr6.sin6_port, udp_dest_port);
+ }
+ }
+
+ return 0;
+}
+
+static ip_addr
+allowedip_to_ipa(struct wg_allowedip *allowedip)
+{
+ switch (allowedip->family)
+ {
+ case AF_INET:
+ return ipa_from_in4(allowedip->ip4);
+ break;
+ case AF_INET6:
+ return ipa_from_in6(allowedip->ip6);
+ }
+
+ return IPA_NONE;
+}
+
+static void
+init_allowed_ip(struct wg_allowedip *allowedip, u8 net_type, struct network *n)
+{
+ memset(allowedip, 0, sizeof(struct wg_allowedip));
+
+ if (net_type == NET_IP4)
+ {
+ allowedip->family = AF_INET;
+ allowedip->ip4.s_addr = ip4_to_u32(ip4_hton(net4_prefix(n->n.addr)));
+ }
+ else if (net_type == NET_IP6)
+ {
+ allowedip->family = AF_INET6;
+ ip6_addr addr = ip6_hton(net6_prefix(n->n.addr));
+ memcpy(allowedip->ip6.s6_addr, &addr, 16);
+ }
+
+ allowedip->cidr = net_pxlen(n->n.addr);
+}
+
+static struct wg_allowedip *
+create_allowed_ip_network(u8 net_type, struct network *n)
+{
+ struct wg_allowedip *allowedip = malloc(sizeof(struct wg_allowedip));
+ init_allowed_ip(allowedip, net_type, n);
+ return allowedip;
+}
+
+static int
+add_allowed_ip(struct wg_allowedip *allowedip, wg_peer *peer)
+{
+ if (peer->first_allowedip && peer->last_allowedip)
+ peer->last_allowedip->next_allowedip = allowedip;
+ else
+ peer->first_allowedip = allowedip;
+ peer->last_allowedip = allowedip;
+
+ return 0;
+}
+
+static bool
+remove_allowed_ip(wg_peer *peer, struct wg_allowedip *allowedip)
+{
+ struct wg_allowedip *ip = NULL;
+ struct wg_allowedip *previp = NULL;
+
+ wg_for_each_allowedip(peer, ip)
+ {
+ if (allowedip->family != ip->family)
+ {
+ DBG("WG: family no match\n");
+ previp = ip;
+ continue;
+ }
+
+ if (allowedip->cidr != ip->cidr)
+ {
+ DBG("WG: cidr no match\n");
+ previp = ip;
+ continue;
+ }
+
+ if (memcmp(&allowedip->ip6, &ip->ip6, sizeof(struct in6_addr)))
+ {
+ DBG("WG: ip no match\n");
+#if defined(LOCAL_DEBUG) || defined(GLOBAL_DEBUG)
+ dump(&allowedip->ip6, sizeof(struct in6_addr));
+ dump(&ip->ip6, sizeof(struct in6_addr));
+#endif
+ previp = ip;
+ continue;
+ }
+
+ DBG("WG: found ip\n");
+
+ if (!previp)
+ {
+ DBG("WG: remove first\n");
+ peer->first_allowedip = ip->next_allowedip;
+ if (peer->last_allowedip == ip)
+ {
+ peer->last_allowedip = NULL;
+ peer->first_allowedip = NULL;
+ }
+ }
+ else
+ {
+ DBG("WG: remove middle\n");
+ // Remove
+ if (peer->last_allowedip == ip)
+ peer->last_allowedip = previp;
+
+ previp->next_allowedip = ip->next_allowedip;
+ }
+
+ free(ip);
+ return true;
+ }
+
+ return false;
+}
+
+struct wg_tunnel_encap {
+ int requested_type;
+ struct te_encap encap;
+ struct te_endpoint ep;
+ u32 color;
+ u16 udp_dest_port;
+ u16 flags;
+ const wg_key *pubkey;
+};
+
+static int wg_visitor_format_tlv(int type, struct te_context *ctx)
+{
+ struct wg_tunnel_encap *info = ctx->opaque;
+ info->flags = 0;
+ return info->requested_type == type;
+}
+
+static int wg_visitor_format_encap(const struct te_encap *encap, struct te_context *ctx)
+{
+ struct wg_tunnel_encap *info = ctx->opaque;
+
+ if (encap->length == sizeof(wg_key))
+ info->pubkey = encap->data;
+ else if (encap->length == 4 + sizeof(wg_key))
+ info->pubkey = encap->data + 4;
+ else
+ return -1;
+
+ info->encap = *encap;
+ info->flags |= FLAG_BGP_TUNNEL_ENCAP_A_SUB_TLV_ENCAP;
+ return 0;
+}
+
+static int wg_visitor_format_ep(const struct te_endpoint *ep, struct te_context *ctx)
+{
+ struct wg_tunnel_encap *info = ctx->opaque;
+ info->ep = *ep;
+ info->flags |= FLAG_BGP_TUNNEL_ENCAP_A_SUB_TLV_TUNNEL_EP;
+ return 0;
+}
+
+static int wg_visitor_format_color(u32 color, struct te_context *ctx)
+{
+ struct wg_tunnel_encap *info = ctx->opaque;
+ info->color = color;
+ info->flags |= FLAG_BGP_TUNNEL_ENCAP_A_SUB_TLV_COLOR;
+ return 0;
+}
+
+static int wg_visitor_format_udp_dest_port(u16 udp_dest_port, struct te_context *ctx)
+{
+ struct wg_tunnel_encap *info = ctx->opaque;
+ info->udp_dest_port = udp_dest_port;
+ info->flags |= FLAG_BGP_TUNNEL_ENCAP_A_SUB_TLV_UDP_DEST_PORT;
+ return 0;
+}
+
+static
+int wg_decode_tunnel_encap(const struct adata *a, struct wg_tunnel_encap *encap)
+{
+ struct te_visitor wg_visitor = {
+ .visit_tlv = wg_visitor_format_tlv,
+ //.visit_subtlv = wg_visitor_format_subtlv,
+ .visit_encap = wg_visitor_format_encap,
+ .visit_ep = wg_visitor_format_ep,
+ .visit_color = wg_visitor_format_color,
+ .visit_udp_dest_port = wg_visitor_format_udp_dest_port,
+ };
+
+ return walk_tunnel_encap(a, &wg_visitor, encap);
+}
+
+static void
+wg_rt_notify(struct proto *P, struct channel *CH, struct network *n,
+ struct rte *new, struct rte *old UNUSED)
+{
+ struct wg_proto *p = (struct wg_proto *) P;
+ struct wg_config *c = (struct wg_config *) P->cf;
+ struct wg_channel *ch = (struct wg_channel *) CH;
+ struct iface *iface = NULL;
+ const char *ifname = NULL;
+
+// DBG("WG: notify\n");
+
+ if (new)
+ {
+ struct nexthop *nh = &new->attrs->nh;
+ iface = nh->iface;
+ ifname = iface ? iface->name : NULL;
+
+ if (iface && iface == p->iface)
+ {
+ struct eattr *t;
+
+ DBG("WG: found %p ifname %s iface %I %p\n", new->attrs, ifname, nh->gw, nh->next);
+
+ struct hostentry *he = new->attrs->hostentry;
+
+ DBG("WG: he %p src %p\n", he, he?he->src:NULL);
+ if (he && he->src)
+ {
+ struct eattr *t = ea_find(he->src->eattrs, EA_CODE(PROTOCOL_BGP, BA_TUNNEL_ENCAP));
+ DBG("WG: he %p %I %I %p %p %I\n", t, he->addr, he->link, he->next, he->src->hostentry, he->src->nh.gw);
+ }
+
+ DBG("WG: notify new %d %N\n",
+ new->attrs->dest, n->n.addr);
+
+ bool is_tunnel_ep = false;
+ struct wg_tunnel_encap encap = {.requested_type=c->tunnel_type};
+
+ t = ea_find(new->attrs->eattrs, EA_CODE(PROTOCOL_BGP, BA_TUNNEL_ENCAP));
+ if (t)
+ {
+ WG_TRACE(D_EVENTS, "WG: Set is tunnel");
+ is_tunnel_ep = true;
+ }
+ if (!t && he && he->src)
+ {
+ t = ea_find(he->src->eattrs, EA_CODE(PROTOCOL_BGP, BA_TUNNEL_ENCAP));
+ }
+ if (t && t->u.ptr && wg_decode_tunnel_encap(t->u.ptr, &encap) == 0 && (encap.flags & FLAG_BGP_TUNNEL_ENCAP_A_SUB_TLV_ENCAP))
+ {
+ bool add_ip = true;
+ struct wg_entry *en = fib_find(&ch->rtable, n->n.addr);
+
+ if (en)
+ {
+ if (memcpy(en->public_key, encap.pubkey, sizeof(wg_key) == 0))
+ {
+ add_ip = false;
+ }
+ }
+ else
+ {
+ struct wg_entry *en = fib_get(&ch->rtable, n->n.addr);
+ en->is_tunnel_ep = is_tunnel_ep;
+ memcpy(en->public_key, encap.pubkey, sizeof(wg_key));
+ }
+
+ WG_TRACE(D_EVENTS, "WG: Attr %x %x %d", t->flags, t->type, t->u.ptr->length);
+
+ struct wg_device *dev = p->dev;
+
+ if (dev != NULL)
+ {
+ bool dirty = false;
+ bool found = false;
+ struct wg_peer *peer = NULL;
+ wg_for_each_peer(dev, peer)
+ {
+ // Look for public key
+ size_t len = 32; // FIXME
+ // MIN(32, t->u.ptr->length)
+ if (memcmp(peer->public_key, encap.pubkey, len) != 0)
+ {
+ WG_TRACE(D_EVENTS, "WG: Not found");
+ continue;
+ }
+
+ WG_TRACE(D_EVENTS, "WG: Found");
+ found = true;
+ dirty = true;
+ break;
+ }
+
+ if (!found)
+ {
+ peer = add_peer(dev, encap.pubkey);
+ }
+
+ dump_peer(peer);
+ if (is_tunnel_ep && (encap.flags & FLAG_BGP_TUNNEL_ENCAP_A_SUB_TLV_TUNNEL_EP) && (encap.flags & FLAG_BGP_TUNNEL_ENCAP_A_SUB_TLV_UDP_DEST_PORT))
+ set_peer_tunnel_ep(p, peer, encap.ep.ip, encap.udp_dest_port);
+ if (add_ip)
+ {
+ struct wg_allowedip *allowed_n =
+ create_allowed_ip_network(ch->c.net_type, n);
+ add_allowed_ip(allowed_n, peer);
+ }
+ dirty = true;
+
+ if (dirty)
+ {
+ int res = set_device(p);
+ WG_TRACE(D_EVENTS, "WG: wg_set_device %d", res);
+ }
+ }
+ }
+ else
+ {
+ WG_TRACE(D_EVENTS, "WG: No Attr");
+ }
+
+ // old_metric = en->valid ? en->metric : -1;
+
+// en->valid = RIP_ENTRY_VALID;
+// en->metric = rt_metric;
+// en->tag = rt_tag;
+// en->from = (new->attrs->src->proto == P) ? new->u.rip.from : NULL;
+// en->iface = new->attrs->iface;
+// en->next_hop = new->attrs->gw;
+ }
+ }
+ else
+ {
+ DBG("WG: notify withdraw %N\n",
+ n->n.addr);
+
+ /* Withdraw */
+ struct wg_entry *en = fib_find(&ch->rtable, n->n.addr);
+
+ if (!en)
+ { // || en->valid != RIP_ENTRY_VALID)
+ DBG("WG: fib not found\n");
+ return;
+ }
+
+ struct wg_device *dev = p->dev;
+
+ if (dev != NULL)
+ {
+ bool marked_peer = false;
+ bool found = false;
+ struct wg_peer *peer = NULL;
+ wg_for_each_peer(dev, peer)
+ {
+ if (en->is_tunnel_ep && !marked_peer)
+ {
+ WG_TRACE(D_EVENTS, "WG: Is tunnel");
+ if (memcmp(peer->public_key, en->public_key, sizeof(wg_key)) == 0)
+ {
+ struct peer_config *pc = NULL;
+ bool remove_me = true;
+ WALK_LIST(pc,c->peers)
+ {
+ wg_key pc_key;
+ if (pc->public_key)
+ {
+ memcpy(pc_key, pc->public_key, sizeof(wg_key));
+ if (memcmp(pc_key, peer->public_key, sizeof(wg_key)) == 0)
+ {
+ /* Don't remove preconfigured peer */
+ remove_me = false;
+ break;
+ }
+ }
+ }
+
+ if (remove_me)
+ {
+ WG_TRACE(D_EVENTS, "WG: Remove peer");
+ peer->flags |= WGPEER_REMOVE_ME;
+ marked_peer = true;
+ continue;
+ }
+ }
+ }
+ // Remove from all peers
+ found = true;
+
+ if (found)
+ {
+ struct wg_allowedip *allowedip = alloca(sizeof(struct wg_allowedip));
+ init_allowed_ip(allowedip, ch->c.net_type, n);
+ dump_peer(peer);
+ if (remove_allowed_ip(peer, allowedip))
+ {
+ ip_addr ip = allowedip_to_ipa(allowedip);
+ WG_TRACE(D_EVENTS, "WG: removed %I/%d", ip, allowedip->cidr);
+ peer->flags |= WGPEER_REPLACE_ALLOWEDIPS;
+
+ dump_peer(peer);
+ }
+ }
+ }
+
+ if (marked_peer)
+ {
+ remove_marked_peer(p);
+ }
+ int res = set_device(p);
+ WG_TRACE(D_EVENTS, "WG: wg_set_device %d", res);
+
+ fib_delete(&ch->rtable, en);
+ en = NULL;
+/*
+ old_metric = en->metric;
+
+ en->valid = RIP_ENTRY_STALE;
+ en->metric = p->infinity;
+ en->tag = 0;
+ en->from = NULL;
+ en->iface = NULL;
+ en->next_hop = IPA_NONE;
+*/
+ }
+ }
+
+// TRACE(D_EVENTS, "wg notify %s %s", src_table->name, ifname?ifname:"(null)");
+}
+
+static void
+wg_reload_routes(struct channel *C)
+{
+ struct wg_proto *p UNUSED = (struct wg_proto *) C->proto;
+
+ DBG("reload routes\n");
+
+// TODO
+// WALK_LIST(c, p->channels)
+// channel_request_feeding(c);
+}
+
+static int
+wg_format_tunnel_encap_type_0(const struct te_encap *encap, byte *buf, uint size)
+{
+ byte *pos = buf;
+
+ wg_key_b64_string base64;
+ wg_key_to_base64(base64, encap->data);
+
+ int l = bsnprintf(pos, size, "%s", base64);
+ ADVANCE(pos, size, l);
+ return pos - buf;
+}
+
+static int
+wg_format_tunnel_encap_type_1(const struct te_encap *encap, byte *buf, uint size)
+{
+ byte *pos = buf;
+
+ if (encap->length != 4 + sizeof(wg_key))
+ {
+ DBG("wg_format_tunnel_encap: bad length %d\n", encap->length);
+ return -1;
+ }
+
+ uint32_t flags = ntohl(((uint32_t*)encap->data)[0]);
+
+ if (flags != 0) {
+ DBG("wg_format_tunnel_encap: unsupported flags %08x\n", encap->length);
+ return -1;
+ }
+
+ wg_key_b64_string base64;
+ wg_key_to_base64(base64, ((byte*)encap->data) + 4);
+
+ int l = bsnprintf(pos, size, "%s", base64);
+ ADVANCE(pos, size, l);
+ return pos - buf;
+}
+
+static int
+wg_format_tunnel_encap(const struct te_encap *encap, byte *buf, uint size)
+{
+ if (encap->length == sizeof(wg_key))
+ {
+ return wg_format_tunnel_encap_type_0(encap, buf, size);
+ }
+ else
+ {
+ return wg_format_tunnel_encap_type_1(encap, buf, size);
+ }
+}
+
+static struct proto *
+wg_init(struct proto_config *C)
+{
+ struct wg_config *c UNUSED = (struct wg_config *) C;
+ struct proto *P = proto_new(C);
+ struct wg_proto *p UNUSED = (struct wg_proto *) P;
+
+ DBG("init\n");
+
+ P->if_notify = wg_if_notify;
+ P->rt_notify = wg_rt_notify;
+ P->reload_routes = wg_reload_routes;
+// P->accept_ra_types = RA_ANY;
+
+ /* Add all channels */
+ struct wg_channel_config *cc;
+ WALK_LIST(cc, C->channels)
+ proto_add_channel(P, &cc->c);
+
+ return P;
+}
+
+
+static int
+wg_start(struct proto *P)
+{
+ struct wg_config *cf = (struct wg_config *) P->cf;
+ struct wg_proto *p = (struct wg_proto *) P;
+
+ WG_TRACE(D_EVENTS, "WG: start");
+
+ if (get_device(p, &p->dev, cf->ifname) >= 0)
+ {
+ int res = set_device(p);
+ WG_TRACE(D_EVENTS, "WG: wg_set_device %d", res);
+ }
+
+ struct wg_channel *ch;
+ WALK_LIST(ch,p->p.channels)
+ {
+ fib_init(&ch->rtable, P->pool, ch->c.net_type, sizeof(struct wg_entry),
+ OFFSETOF(struct wg_entry, n), 0, wg_init_entry);
+ }
+ return PS_UP;
+}
+
+static int
+wg_shutdown(struct proto *P)
+{
+ struct wg_config *cf = (struct wg_config*)P->cf;
+ struct wg_proto *p = (struct wg_proto*)P;
+
+ log_msg(L_INFO "wg_shutdown");
+ WG_TRACE(D_EVENTS, "WG: wg_shutdown");
+ if (get_device(p, &p->dev, cf->ifname) >= 0)
+ {
+ int res = set_device(p);
+ WG_TRACE(D_EVENTS, "WG: flush wg_set_device %d", res);
+ }
+
+ unregister_format_tunnel_encap(cf->tunnel_type, wg_format_tunnel_encap);
+
+ return PS_DOWN;
+}
+
+static void
+wg_dump(struct proto *P)
+{
+ struct wg_proto *p = (struct wg_proto *) P;
+ int i;
+
+ i = 0;
+
+ struct wg_channel *ch;
+ WALK_LIST(ch,p->p.channels)
+ {
+ FIB_WALK(&ch->rtable, struct wg_entry, en)
+ {
+ // struct wg_entry *en = (struct wg_entry *) e;
+ DBG("WG: entry #%d:\n",
+ i++);
+ }
+ FIB_WALK_END;
+ }
+
+ struct wg_peer *peer = NULL;
+
+ WG_TRACE(D_EVENTS, "WG: dump peers");
+ wg_for_each_peer(p->dev, peer)
+ {
+ dump_peer(peer);
+ }
+}
+
+static void
+wg_copy_config(struct proto_config *DEST, struct proto_config *SRC)
+{
+ struct wg_config *dest = (struct wg_config *)DEST;
+ struct wg_config *src = (struct wg_config *)SRC;
+
+ dest->ifname = src->ifname;
+ dest->socket_path = src->socket_path;
+ dest->private_key = src->private_key;
+ dest->listen_port = src->listen_port;
+
+ struct peer_config *spc = NULL;
+ WALK_LIST(spc,src->peers)
+ {
+ struct peer_config *dpc = cfg_allocz(sizeof(struct peer_config));
+ dpc->public_key = spc->public_key;
+ dpc->listen_port = spc->listen_port;
+ dpc->endpoint = spc->endpoint;
+ dpc->remote_port = spc->remote_port;
+ dpc->allowedips = spc->allowedips;
+ add_tail(&dest->peers, (node*)spc);
+ }
+}
+
+struct peer_config *peer_new(struct wg_config *c)
+{
+ struct peer_config *pc = cfg_allocz(sizeof(struct peer_config));
+ DBG("peer_new %p\n", pc);
+ add_tail(&c->peers, (node*)pc);
+ return pc;
+}
+
+
+static void
+wg_channel_init(struct channel *CH, struct channel_config *CHC UNUSED)
+{
+ struct proto *P = CH->proto;
+ struct wg_proto *p = (struct wg_proto *) P;
+
+ /* Create new instance */
+ WG_TRACE(D_EVENTS, "WG: wg_channel_init");
+}
+
+static int
+wg_channel_reconfigure(struct channel *CH, struct channel_config *CHC UNUSED,
+ int *import_changed UNUSED, int *export_changed UNUSED)
+{
+ struct proto *P = CH->proto;
+ struct wg_proto *p = (struct wg_proto *) P;
+
+ /* Try to reconfigure instance, returns success */
+ WG_TRACE(D_EVENTS, "WG: wg_channel_reconfigure");
+ return 1;
+}
+
+static int
+wg_channel_start(struct channel *CH)
+{
+ struct wg_channel *ch UNUSED = (struct wg_channel*)CH;
+ struct proto *P = CH->proto;
+ struct wg_proto *p = (struct wg_proto *) P;
+
+ /* Start the instance */
+ WG_TRACE(D_EVENTS, "WG: wg_channel_start");
+#if 0
+ fib_init(&ch->rtable, P->pool, ch->c.net_type, sizeof(struct wg_entry),
+ OFFSETOF(struct wg_entry, n), 0, wg_init_entry);
+#endif
+ return 1;
+}
+
+static void
+wg_channel_shutdown(struct channel *CH)
+{
+ struct wg_channel *ch UNUSED = (struct wg_channel*)CH;
+ struct proto *P = CH->proto;
+ struct wg_proto *p = (struct wg_proto *) P;
+
+ /* Stop the instance */
+ WG_TRACE(D_EVENTS, "WG: wg_channel_shutdown");
+}
+
+static void
+wg_channel_cleanup(struct channel *CH)
+{
+ struct wg_channel *ch UNUSED = (struct wg_channel*)CH;
+ struct proto *P = CH->proto;
+ struct wg_proto *p = (struct wg_proto *) P;
+
+ /* Channel finished flush */
+ WG_TRACE(D_EVENTS, "WG: wg_channel_cleanup");
+}
+
+
+const struct channel_class channel_wg = {
+ .channel_size = sizeof(struct wg_channel),
+ .config_size = sizeof(struct wg_channel_config),
+ .init = wg_channel_init,
+ .start = wg_channel_start,
+ .shutdown = wg_channel_shutdown,
+ .cleanup = wg_channel_cleanup,
+ .reconfigure = wg_channel_reconfigure,
+};
+
+struct protocol proto_wireguard = {
+ .name = "Wireguard",
+ .template = "wg%d",
+ .class = PROTOCOL_WG,
+ .channel_mask = NB_IP,
+ .proto_size = sizeof(struct wg_proto),
+ .config_size = sizeof(struct wg_config),
+ .postconfig = wg_postconfig,
+ .init = wg_init,
+ .start = wg_start,
+ .shutdown = wg_shutdown,
+ .dump = wg_dump,
+ .copy_config = wg_copy_config,
+/* .multitable = 1,
+ .preference = DEF_PREF_PIPE,
+ .cleanup = wg_cleanup,
+ .reconfigure = wg_reconfigure,
+ .get_status = wg_get_status,
+ .show_proto_info = wg_show_proto_info*/
+};
+
+
+void wireguard_build(void)
+{
+ proto_build(&proto_wireguard);
+}
diff --git a/proto/wireguard/wireguard.h b/proto/wireguard/wireguard.h
new file mode 100644
index 00000000..b98119c3
--- /dev/null
+++ b/proto/wireguard/wireguard.h
@@ -0,0 +1,66 @@
+#ifndef _BIRD_WIREGUARD_H
+#define _BIRD_WIREGUARD_H
+
+#include "nest/protocol.h"
+#include "sysdep/linux/wireguard.h"
+
+#ifdef LOCAL_DEBUG
+#define WG_FORCE_DEBUG 1
+#else
+#define WG_FORCE_DEBUG 0
+#endif
+#define WG_TRACE(flags, msg, args...) do { if ((p->p.debug & flags) || WG_FORCE_DEBUG) \
+ log(L_TRACE "%s: " msg, p->p.name , ## args ); } while(0)
+
+struct wg_allowedips {
+ struct wg_allowedip *first_allowedip;
+ struct wg_allowedip *last_allowedip;
+};
+
+struct peer_config {
+ node n;
+ const byte *public_key;
+ u16 listen_port;
+ ip_addr endpoint;
+ u16 remote_port;
+ struct wg_allowedips *allowedips;
+};
+
+struct wg_config {
+ struct proto_config c;
+ const char *ifname;
+ const char *socket_path;
+ const byte *private_key;
+ u16 tunnel_type;
+ u16 listen_port;
+ list peers;
+};
+
+struct wg_proto {
+ struct proto p;
+ struct iface *iface;
+ wg_key private_key;
+ wg_device *dev;
+};
+
+struct wg_channel_config {
+ struct channel_config c;
+};
+
+struct wg_channel {
+ struct channel c;
+
+ struct fib rtable;
+};
+
+struct wg_entry {
+ bool is_tunnel_ep;
+ wg_key public_key;
+ struct fib_node n;
+};
+
+extern const struct channel_class channel_wg;
+
+struct peer_config *peer_new(struct wg_config *c);
+
+#endif /* _BIRD_WIREGUARD_H */
diff --git a/sysdep/linux/Makefile b/sysdep/linux/Makefile
index 188ac8de..12bb26c1 100644
--- a/sysdep/linux/Makefile
+++ b/sysdep/linux/Makefile
@@ -1,4 +1,4 @@
-src := netlink.c
+src := netlink.c wireguard.c
obj := $(src-o-files)
$(all-daemon)
$(conf-y-targets): $(s)netlink.Y
diff --git a/sysdep/linux/wireguard.c b/sysdep/linux/wireguard.c
new file mode 100644
index 00000000..4941549a
--- /dev/null
+++ b/sysdep/linux/wireguard.c
@@ -0,0 +1,1755 @@
+// SPDX-License-Identifier: LGPL-2.1+
+/*
+ * Copyright (C) 2015-2020 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
+ * Copyright (C) 2008-2012 Pablo Neira Ayuso <pablo@netfilter.org>.
+ */
+
+#define _GNU_SOURCE
+
+#include <errno.h>
+#include <linux/genetlink.h>
+#include <linux/if_link.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <netinet/in.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <time.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <assert.h>
+
+#include "wireguard.h"
+
+/* wireguard.h netlink uapi: */
+
+#define WG_GENL_NAME "wireguard"
+#define WG_GENL_VERSION 1
+
+enum wg_cmd {
+ WG_CMD_GET_DEVICE,
+ WG_CMD_SET_DEVICE,
+ __WG_CMD_MAX
+};
+
+enum wgdevice_flag {
+ WGDEVICE_F_REPLACE_PEERS = 1U << 0
+};
+enum wgdevice_attribute {
+ WGDEVICE_A_UNSPEC,
+ WGDEVICE_A_IFINDEX,
+ WGDEVICE_A_IFNAME,
+ WGDEVICE_A_PRIVATE_KEY,
+ WGDEVICE_A_PUBLIC_KEY,
+ WGDEVICE_A_FLAGS,
+ WGDEVICE_A_LISTEN_PORT,
+ WGDEVICE_A_FWMARK,
+ WGDEVICE_A_PEERS,
+ __WGDEVICE_A_LAST
+};
+
+enum wgpeer_flag {
+ WGPEER_F_REMOVE_ME = 1U << 0,
+ WGPEER_F_REPLACE_ALLOWEDIPS = 1U << 1
+};
+enum wgpeer_attribute {
+ WGPEER_A_UNSPEC,
+ WGPEER_A_PUBLIC_KEY,
+ WGPEER_A_PRESHARED_KEY,
+ WGPEER_A_FLAGS,
+ WGPEER_A_ENDPOINT,
+ WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL,
+ WGPEER_A_LAST_HANDSHAKE_TIME,
+ WGPEER_A_RX_BYTES,
+ WGPEER_A_TX_BYTES,
+ WGPEER_A_ALLOWEDIPS,
+ WGPEER_A_PROTOCOL_VERSION,
+ __WGPEER_A_LAST
+};
+
+enum wgallowedip_attribute {
+ WGALLOWEDIP_A_UNSPEC,
+ WGALLOWEDIP_A_FAMILY,
+ WGALLOWEDIP_A_IPADDR,
+ WGALLOWEDIP_A_CIDR_MASK,
+ __WGALLOWEDIP_A_LAST
+};
+
+/* libmnl mini library: */
+
+#define MNL_SOCKET_AUTOPID 0
+#define MNL_ALIGNTO 4
+#define MNL_ALIGN(len) (((len)+MNL_ALIGNTO-1) & ~(MNL_ALIGNTO-1))
+#define MNL_NLMSG_HDRLEN MNL_ALIGN(sizeof(struct nlmsghdr))
+#define MNL_ATTR_HDRLEN MNL_ALIGN(sizeof(struct nlattr))
+
+enum mnl_attr_data_type {
+ MNL_TYPE_UNSPEC,
+ MNL_TYPE_U8,
+ MNL_TYPE_U16,
+ MNL_TYPE_U32,
+ MNL_TYPE_U64,
+ MNL_TYPE_STRING,
+ MNL_TYPE_FLAG,
+ MNL_TYPE_MSECS,
+ MNL_TYPE_NESTED,
+ MNL_TYPE_NESTED_COMPAT,
+ MNL_TYPE_NUL_STRING,
+ MNL_TYPE_BINARY,
+ MNL_TYPE_MAX,
+};
+
+#define mnl_attr_for_each(attr, nlh, offset) \
+ for ((attr) = mnl_nlmsg_get_payload_offset((nlh), (offset)); \
+ mnl_attr_ok((attr), (char *)mnl_nlmsg_get_payload_tail(nlh) - (char *)(attr)); \
+ (attr) = mnl_attr_next(attr))
+
+#define mnl_attr_for_each_nested(attr, nest) \
+ for ((attr) = mnl_attr_get_payload(nest); \
+ mnl_attr_ok((attr), (char *)mnl_attr_get_payload(nest) + mnl_attr_get_payload_len(nest) - (char *)(attr)); \
+ (attr) = mnl_attr_next(attr))
+
+#define mnl_attr_for_each_payload(payload, payload_size) \
+ for ((attr) = (payload); \
+ mnl_attr_ok((attr), (char *)(payload) + payload_size - (char *)(attr)); \
+ (attr) = mnl_attr_next(attr))
+
+#define MNL_CB_ERROR -1
+#define MNL_CB_STOP 0
+#define MNL_CB_OK 1
+
+typedef int (*mnl_attr_cb_t)(const struct nlattr *attr, void *data);
+typedef int (*mnl_cb_t)(const struct nlmsghdr *nlh, void *data);
+
+#ifndef MNL_ARRAY_SIZE
+#define MNL_ARRAY_SIZE(a) (sizeof(a)/sizeof((a)[0]))
+#endif
+
+static size_t mnl_ideal_socket_buffer_size(void)
+{
+ static size_t size = 0;
+
+ if (size)
+ return size;
+ size = (size_t)sysconf(_SC_PAGESIZE);
+ if (size > 8192)
+ size = 8192;
+ return size;
+}
+
+static size_t mnl_nlmsg_size(size_t len)
+{
+ return len + MNL_NLMSG_HDRLEN;
+}
+
+static struct nlmsghdr *mnl_nlmsg_put_header(void *buf)
+{
+ int len = MNL_ALIGN(sizeof(struct nlmsghdr));
+ struct nlmsghdr *nlh = buf;
+
+ memset(buf, 0, len);
+ nlh->nlmsg_len = len;
+ return nlh;
+}
+
+static void *mnl_nlmsg_put_extra_header(struct nlmsghdr *nlh, size_t size)
+{
+ char *ptr = (char *)nlh + nlh->nlmsg_len;
+ size_t len = MNL_ALIGN(size);
+ nlh->nlmsg_len += len;
+ memset(ptr, 0, len);
+ return ptr;
+}
+
+static void *mnl_nlmsg_get_payload(const struct nlmsghdr *nlh)
+{
+ return (void *)nlh + MNL_NLMSG_HDRLEN;
+}
+
+static void *mnl_nlmsg_get_payload_offset(const struct nlmsghdr *nlh, size_t offset)
+{
+ return (void *)nlh + MNL_NLMSG_HDRLEN + MNL_ALIGN(offset);
+}
+
+static bool mnl_nlmsg_ok(const struct nlmsghdr *nlh, int len)
+{
+ return len >= (int)sizeof(struct nlmsghdr) &&
+ nlh->nlmsg_len >= sizeof(struct nlmsghdr) &&
+ (int)nlh->nlmsg_len <= len;
+}
+
+static struct nlmsghdr *mnl_nlmsg_next(const struct nlmsghdr *nlh, int *len)
+{
+ *len -= MNL_ALIGN(nlh->nlmsg_len);
+ return (struct nlmsghdr *)((void *)nlh + MNL_ALIGN(nlh->nlmsg_len));
+}
+
+static void *mnl_nlmsg_get_payload_tail(const struct nlmsghdr *nlh)
+{
+ return (void *)nlh + MNL_ALIGN(nlh->nlmsg_len);
+}
+
+static bool mnl_nlmsg_seq_ok(const struct nlmsghdr *nlh, unsigned int seq)
+{
+ return nlh->nlmsg_seq && seq ? nlh->nlmsg_seq == seq : true;
+}
+
+static bool mnl_nlmsg_portid_ok(const struct nlmsghdr *nlh, unsigned int portid)
+{
+ return nlh->nlmsg_pid && portid ? nlh->nlmsg_pid == portid : true;
+}
+
+static uint16_t mnl_attr_get_type(const struct nlattr *attr)
+{
+ return attr->nla_type & NLA_TYPE_MASK;
+}
+
+static uint16_t mnl_attr_get_payload_len(const struct nlattr *attr)
+{
+ return attr->nla_len - MNL_ATTR_HDRLEN;
+}
+
+static void *mnl_attr_get_payload(const struct nlattr *attr)
+{
+ return (void *)attr + MNL_ATTR_HDRLEN;
+}
+
+static bool mnl_attr_ok(const struct nlattr *attr, int len)
+{
+ return len >= (int)sizeof(struct nlattr) &&
+ attr->nla_len >= sizeof(struct nlattr) &&
+ (int)attr->nla_len <= len;
+}
+
+static struct nlattr *mnl_attr_next(const struct nlattr *attr)
+{
+ return (struct nlattr *)((void *)attr + MNL_ALIGN(attr->nla_len));
+}
+
+static int mnl_attr_type_valid(const struct nlattr *attr, uint16_t max)
+{
+ if (mnl_attr_get_type(attr) > max) {
+ errno = EOPNOTSUPP;
+ return -1;
+ }
+ return 1;
+}
+
+static int __mnl_attr_validate(const struct nlattr *attr,
+ enum mnl_attr_data_type type, size_t exp_len)
+{
+ uint16_t attr_len = mnl_attr_get_payload_len(attr);
+ const char *attr_data = mnl_attr_get_payload(attr);
+
+ if (attr_len < exp_len) {
+ errno = ERANGE;
+ return -1;
+ }
+ switch(type) {
+ case MNL_TYPE_FLAG:
+ if (attr_len > 0) {
+ errno = ERANGE;
+ return -1;
+ }
+ break;
+ case MNL_TYPE_NUL_STRING:
+ if (attr_len == 0) {
+ errno = ERANGE;
+ return -1;
+ }
+ if (attr_data[attr_len-1] != '\0') {
+ errno = EINVAL;
+ return -1;
+ }
+ break;
+ case MNL_TYPE_STRING:
+ if (attr_len == 0) {
+ errno = ERANGE;
+ return -1;
+ }
+ break;
+ case MNL_TYPE_NESTED:
+
+ if (attr_len == 0)
+ break;
+
+ if (attr_len < MNL_ATTR_HDRLEN) {
+ errno = ERANGE;
+ return -1;
+ }
+ break;
+ default:
+
+ break;
+ }
+ if (exp_len && attr_len > exp_len) {
+ errno = ERANGE;
+ return -1;
+ }
+ return 0;
+}
+
+static const size_t mnl_attr_data_type_len[MNL_TYPE_MAX] = {
+ [MNL_TYPE_U8] = sizeof(uint8_t),
+ [MNL_TYPE_U16] = sizeof(uint16_t),
+ [MNL_TYPE_U32] = sizeof(uint32_t),
+ [MNL_TYPE_U64] = sizeof(uint64_t),
+ [MNL_TYPE_MSECS] = sizeof(uint64_t),
+};
+
+static int mnl_attr_validate(const struct nlattr *attr, enum mnl_attr_data_type type)
+{
+ int exp_len;
+
+ if (type >= MNL_TYPE_MAX) {
+ errno = EINVAL;
+ return -1;
+ }
+ exp_len = mnl_attr_data_type_len[type];
+ return __mnl_attr_validate(attr, type, exp_len);
+}
+
+static int mnl_attr_parse(const struct nlmsghdr *nlh, unsigned int offset,
+ mnl_attr_cb_t cb, void *data)
+{
+ int ret = MNL_CB_OK;
+ const struct nlattr *attr;
+
+ mnl_attr_for_each(attr, nlh, offset)
+ if ((ret = cb(attr, data)) <= MNL_CB_STOP)
+ return ret;
+ return ret;
+}
+
+static int mnl_attr_parse_nested(const struct nlattr *nested, mnl_attr_cb_t cb,
+ void *data)
+{
+ int ret = MNL_CB_OK;
+ const struct nlattr *attr;
+
+ mnl_attr_for_each_nested(attr, nested)
+ if ((ret = cb(attr, data)) <= MNL_CB_STOP)
+ return ret;
+ return ret;
+}
+
+static uint8_t mnl_attr_get_u8(const struct nlattr *attr)
+{
+ return *((uint8_t *)mnl_attr_get_payload(attr));
+}
+
+static uint16_t mnl_attr_get_u16(const struct nlattr *attr)
+{
+ return *((uint16_t *)mnl_attr_get_payload(attr));
+}
+
+static uint32_t mnl_attr_get_u32(const struct nlattr *attr)
+{
+ return *((uint32_t *)mnl_attr_get_payload(attr));
+}
+
+static uint64_t mnl_attr_get_u64(const struct nlattr *attr)
+{
+ uint64_t tmp;
+ memcpy(&tmp, mnl_attr_get_payload(attr), sizeof(tmp));
+ return tmp;
+}
+
+static const char *mnl_attr_get_str(const struct nlattr *attr)
+{
+ return mnl_attr_get_payload(attr);
+}
+
+static void mnl_attr_put(struct nlmsghdr *nlh, uint16_t type, size_t len,
+ const void *data)
+{
+ struct nlattr *attr = mnl_nlmsg_get_payload_tail(nlh);
+ uint16_t payload_len = MNL_ALIGN(sizeof(struct nlattr)) + len;
+ int pad;
+
+ attr->nla_type = type;
+ attr->nla_len = payload_len;
+ memcpy(mnl_attr_get_payload(attr), data, len);
+ nlh->nlmsg_len += MNL_ALIGN(payload_len);
+ pad = MNL_ALIGN(len) - len;
+ if (pad > 0)
+ memset(mnl_attr_get_payload(attr) + len, 0, pad);
+}
+
+static void mnl_attr_put_u16(struct nlmsghdr *nlh, uint16_t type, uint16_t data)
+{
+ mnl_attr_put(nlh, type, sizeof(uint16_t), &data);
+}
+
+static void mnl_attr_put_u32(struct nlmsghdr *nlh, uint16_t type, uint32_t data)
+{
+ mnl_attr_put(nlh, type, sizeof(uint32_t), &data);
+}
+
+static void mnl_attr_put_strz(struct nlmsghdr *nlh, uint16_t type, const char *data)
+{
+ mnl_attr_put(nlh, type, strlen(data)+1, data);
+}
+
+static struct nlattr *mnl_attr_nest_start(struct nlmsghdr *nlh, uint16_t type)
+{
+ struct nlattr *start = mnl_nlmsg_get_payload_tail(nlh);
+
+ start->nla_type = NLA_F_NESTED | type;
+ nlh->nlmsg_len += MNL_ALIGN(sizeof(struct nlattr));
+ return start;
+}
+
+static bool mnl_attr_put_check(struct nlmsghdr *nlh, size_t buflen,
+ uint16_t type, size_t len, const void *data)
+{
+ if (nlh->nlmsg_len + MNL_ATTR_HDRLEN + MNL_ALIGN(len) > buflen)
+ return false;
+ mnl_attr_put(nlh, type, len, data);
+ return true;
+}
+
+static bool mnl_attr_put_u8_check(struct nlmsghdr *nlh, size_t buflen,
+ uint16_t type, uint8_t data)
+{
+ return mnl_attr_put_check(nlh, buflen, type, sizeof(uint8_t), &data);
+}
+
+static bool mnl_attr_put_u16_check(struct nlmsghdr *nlh, size_t buflen,
+ uint16_t type, uint16_t data)
+{
+ return mnl_attr_put_check(nlh, buflen, type, sizeof(uint16_t), &data);
+}
+
+static bool mnl_attr_put_u32_check(struct nlmsghdr *nlh, size_t buflen,
+ uint16_t type, uint32_t data)
+{
+ return mnl_attr_put_check(nlh, buflen, type, sizeof(uint32_t), &data);
+}
+
+static struct nlattr *mnl_attr_nest_start_check(struct nlmsghdr *nlh, size_t buflen,
+ uint16_t type)
+{
+ if (nlh->nlmsg_len + MNL_ATTR_HDRLEN > buflen)
+ return NULL;
+ return mnl_attr_nest_start(nlh, type);
+}
+
+static void mnl_attr_nest_end(struct nlmsghdr *nlh, struct nlattr *start)
+{
+ start->nla_len = mnl_nlmsg_get_payload_tail(nlh) - (void *)start;
+}
+
+static void mnl_attr_nest_cancel(struct nlmsghdr *nlh, struct nlattr *start)
+{
+ nlh->nlmsg_len -= mnl_nlmsg_get_payload_tail(nlh) - (void *)start;
+}
+
+static int mnl_cb_noop(__attribute__((unused)) const struct nlmsghdr *nlh, __attribute__((unused)) void *data)
+{
+ return MNL_CB_OK;
+}
+
+static int mnl_cb_error(const struct nlmsghdr *nlh, __attribute__((unused)) void *data)
+{
+ const struct nlmsgerr *err = mnl_nlmsg_get_payload(nlh);
+
+ if (nlh->nlmsg_len < mnl_nlmsg_size(sizeof(struct nlmsgerr))) {
+ errno = EBADMSG;
+ return MNL_CB_ERROR;
+ }
+
+ if (err->error < 0)
+ errno = -err->error;
+ else
+ errno = err->error;
+
+ return err->error == 0 ? MNL_CB_STOP : MNL_CB_ERROR;
+}
+
+static int mnl_cb_stop(__attribute__((unused)) const struct nlmsghdr *nlh, __attribute__((unused)) void *data)
+{
+ return MNL_CB_STOP;
+}
+
+static const mnl_cb_t default_cb_array[NLMSG_MIN_TYPE] = {
+ [NLMSG_NOOP] = mnl_cb_noop,
+ [NLMSG_ERROR] = mnl_cb_error,
+ [NLMSG_DONE] = mnl_cb_stop,
+ [NLMSG_OVERRUN] = mnl_cb_noop,
+};
+
+static int __mnl_cb_run(const void *buf, size_t numbytes,
+ unsigned int seq, unsigned int portid,
+ mnl_cb_t cb_data, void *data,
+ const mnl_cb_t *cb_ctl_array,
+ unsigned int cb_ctl_array_len)
+{
+ int ret = MNL_CB_OK, len = numbytes;
+ const struct nlmsghdr *nlh = buf;
+
+ while (mnl_nlmsg_ok(nlh, len)) {
+
+ if (!mnl_nlmsg_portid_ok(nlh, portid)) {
+ errno = ESRCH;
+ return -1;
+ }
+
+ if (!mnl_nlmsg_seq_ok(nlh, seq)) {
+ errno = EPROTO;
+ return -1;
+ }
+
+ if (nlh->nlmsg_flags & NLM_F_DUMP_INTR) {
+ errno = EINTR;
+ return -1;
+ }
+
+ if (nlh->nlmsg_type >= NLMSG_MIN_TYPE) {
+ if (cb_data){
+ ret = cb_data(nlh, data);
+ if (ret <= MNL_CB_STOP)
+ goto out;
+ }
+ } else if (nlh->nlmsg_type < cb_ctl_array_len) {
+ if (cb_ctl_array && cb_ctl_array[nlh->nlmsg_type]) {
+ ret = cb_ctl_array[nlh->nlmsg_type](nlh, data);
+ if (ret <= MNL_CB_STOP)
+ goto out;
+ }
+ } else if (default_cb_array[nlh->nlmsg_type]) {
+ ret = default_cb_array[nlh->nlmsg_type](nlh, data);
+ if (ret <= MNL_CB_STOP)
+ goto out;
+ }
+ nlh = mnl_nlmsg_next(nlh, &len);
+ }
+out:
+ return ret;
+}
+
+static int mnl_cb_run2(const void *buf, size_t numbytes, unsigned int seq,
+ unsigned int portid, mnl_cb_t cb_data, void *data,
+ const mnl_cb_t *cb_ctl_array, unsigned int cb_ctl_array_len)
+{
+ return __mnl_cb_run(buf, numbytes, seq, portid, cb_data, data,
+ cb_ctl_array, cb_ctl_array_len);
+}
+
+static int mnl_cb_run(const void *buf, size_t numbytes, unsigned int seq,
+ unsigned int portid, mnl_cb_t cb_data, void *data)
+{
+ return __mnl_cb_run(buf, numbytes, seq, portid, cb_data, data, NULL, 0);
+}
+
+struct mnl_socket {
+ int fd;
+ struct sockaddr_nl addr;
+};
+
+static unsigned int mnl_socket_get_portid(const struct mnl_socket *nl)
+{
+ return nl->addr.nl_pid;
+}
+
+static struct mnl_socket *__mnl_socket_open(int bus, int flags)
+{
+ struct mnl_socket *nl;
+
+ nl = calloc(1, sizeof(struct mnl_socket));
+ if (nl == NULL)
+ return NULL;
+
+ nl->fd = socket(AF_NETLINK, SOCK_RAW | flags, bus);
+ if (nl->fd == -1) {
+ free(nl);
+ return NULL;
+ }
+
+ return nl;
+}
+
+static struct mnl_socket *mnl_socket_open(int bus)
+{
+ return __mnl_socket_open(bus, 0);
+}
+
+static int mnl_socket_bind(struct mnl_socket *nl, unsigned int groups, pid_t pid)
+{
+ int ret;
+ socklen_t addr_len;
+
+ nl->addr.nl_family = AF_NETLINK;
+ nl->addr.nl_groups = groups;
+ nl->addr.nl_pid = pid;
+
+ ret = bind(nl->fd, (struct sockaddr *) &nl->addr, sizeof (nl->addr));
+ if (ret < 0)
+ return ret;
+
+ addr_len = sizeof(nl->addr);
+ ret = getsockname(nl->fd, (struct sockaddr *) &nl->addr, &addr_len);
+ if (ret < 0)
+ return ret;
+
+ if (addr_len != sizeof(nl->addr)) {
+ errno = EINVAL;
+ return -1;
+ }
+ if (nl->addr.nl_family != AF_NETLINK) {
+ errno = EINVAL;
+ return -1;
+ }
+ return 0;
+}
+
+static ssize_t mnl_socket_sendto(const struct mnl_socket *nl, const void *buf,
+ size_t len)
+{
+ static const struct sockaddr_nl snl = {
+ .nl_family = AF_NETLINK
+ };
+ return sendto(nl->fd, buf, len, 0,
+ (struct sockaddr *) &snl, sizeof(snl));
+}
+
+static ssize_t mnl_socket_recvfrom(const struct mnl_socket *nl, void *buf,
+ size_t bufsiz)
+{
+ ssize_t ret;
+ struct sockaddr_nl addr;
+ struct iovec iov = {
+ .iov_base = buf,
+ .iov_len = bufsiz,
+ };
+ struct msghdr msg = {
+ .msg_name = &addr,
+ .msg_namelen = sizeof(struct sockaddr_nl),
+ .msg_iov = &iov,
+ .msg_iovlen = 1,
+ .msg_control = NULL,
+ .msg_controllen = 0,
+ .msg_flags = 0,
+ };
+ ret = recvmsg(nl->fd, &msg, 0);
+ if (ret == -1)
+ return ret;
+
+ if (msg.msg_flags & MSG_TRUNC) {
+ errno = ENOSPC;
+ return -1;
+ }
+ if (msg.msg_namelen != sizeof(struct sockaddr_nl)) {
+ errno = EINVAL;
+ return -1;
+ }
+ return ret;
+}
+
+static int mnl_socket_close(struct mnl_socket *nl)
+{
+ int ret = close(nl->fd);
+ free(nl);
+ return ret;
+}
+
+/* mnlg mini library: */
+
+struct mnlg_socket {
+ struct mnl_socket *nl;
+ char *buf;
+ uint16_t id;
+ uint8_t version;
+ unsigned int seq;
+ unsigned int portid;
+};
+
+static struct nlmsghdr *__mnlg_msg_prepare(struct mnlg_socket *nlg, uint8_t cmd,
+ uint16_t flags, uint16_t id,
+ uint8_t version)
+{
+ struct nlmsghdr *nlh;
+ struct genlmsghdr *genl;
+
+ nlh = mnl_nlmsg_put_header(nlg->buf);
+ nlh->nlmsg_type = id;
+ nlh->nlmsg_flags = flags;
+ nlg->seq = time(NULL);
+ nlh->nlmsg_seq = nlg->seq;
+
+ genl = mnl_nlmsg_put_extra_header(nlh, sizeof(struct genlmsghdr));
+ genl->cmd = cmd;
+ genl->version = version;
+
+ return nlh;
+}
+
+static struct nlmsghdr *mnlg_msg_prepare(struct mnlg_socket *nlg, uint8_t cmd,
+ uint16_t flags)
+{
+ return __mnlg_msg_prepare(nlg, cmd, flags, nlg->id, nlg->version);
+}
+
+static int mnlg_socket_send(struct mnlg_socket *nlg, const struct nlmsghdr *nlh)
+{
+ return mnl_socket_sendto(nlg->nl, nlh, nlh->nlmsg_len);
+}
+
+static int mnlg_cb_noop(const struct nlmsghdr *nlh, void *data)
+{
+ (void)nlh;
+ (void)data;
+ return MNL_CB_OK;
+}
+
+static int mnlg_cb_error(const struct nlmsghdr *nlh, void *data)
+{
+ const struct nlmsgerr *err = mnl_nlmsg_get_payload(nlh);
+ (void)data;
+
+ if (nlh->nlmsg_len < mnl_nlmsg_size(sizeof(struct nlmsgerr))) {
+ errno = EBADMSG;
+ return MNL_CB_ERROR;
+ }
+ /* Netlink subsystems returns the errno value with different signess */
+ if (err->error < 0)
+ errno = -err->error;
+ else
+ errno = err->error;
+
+ return err->error == 0 ? MNL_CB_STOP : MNL_CB_ERROR;
+}
+
+static int mnlg_cb_stop(const struct nlmsghdr *nlh, void *data)
+{
+ (void)data;
+ if (nlh->nlmsg_flags & NLM_F_MULTI && nlh->nlmsg_len == mnl_nlmsg_size(sizeof(int))) {
+ int error = *(int *)mnl_nlmsg_get_payload(nlh);
+ /* Netlink subsystems returns the errno value with different signess */
+ if (error < 0)
+ errno = -error;
+ else
+ errno = error;
+
+ return error == 0 ? MNL_CB_STOP : MNL_CB_ERROR;
+ }
+ return MNL_CB_STOP;
+}
+
+static const mnl_cb_t mnlg_cb_array[] = {
+ [NLMSG_NOOP] = mnlg_cb_noop,
+ [NLMSG_ERROR] = mnlg_cb_error,
+ [NLMSG_DONE] = mnlg_cb_stop,
+ [NLMSG_OVERRUN] = mnlg_cb_noop,
+};
+
+static int mnlg_socket_recv_run(struct mnlg_socket *nlg, mnl_cb_t data_cb, void *data)
+{
+ int err;
+
+ do {
+ err = mnl_socket_recvfrom(nlg->nl, nlg->buf,
+ mnl_ideal_socket_buffer_size());
+ if (err <= 0)
+ break;
+ err = mnl_cb_run2(nlg->buf, err, nlg->seq, nlg->portid,
+ data_cb, data, mnlg_cb_array, MNL_ARRAY_SIZE(mnlg_cb_array));
+ } while (err > 0);
+
+ return err;
+}
+
+static int get_family_id_attr_cb(const struct nlattr *attr, void *data)
+{
+ const struct nlattr **tb = data;
+ int type = mnl_attr_get_type(attr);
+
+ if (mnl_attr_type_valid(attr, CTRL_ATTR_MAX) < 0)
+ return MNL_CB_ERROR;
+
+ if (type == CTRL_ATTR_FAMILY_ID &&
+ mnl_attr_validate(attr, MNL_TYPE_U16) < 0)
+ return MNL_CB_ERROR;
+ tb[type] = attr;
+ return MNL_CB_OK;
+}
+
+static int get_family_id_cb(const struct nlmsghdr *nlh, void *data)
+{
+ uint16_t *p_id = data;
+ struct nlattr *tb[CTRL_ATTR_MAX + 1] = { 0 };
+
+ mnl_attr_parse(nlh, sizeof(struct genlmsghdr), get_family_id_attr_cb, tb);
+ if (!tb[CTRL_ATTR_FAMILY_ID])
+ return MNL_CB_ERROR;
+ *p_id = mnl_attr_get_u16(tb[CTRL_ATTR_FAMILY_ID]);
+ return MNL_CB_OK;
+}
+
+static struct mnlg_socket *mnlg_socket_open(const char *family_name, uint8_t version)
+{
+ struct mnlg_socket *nlg;
+ struct nlmsghdr *nlh;
+ int err;
+
+ nlg = malloc(sizeof(*nlg));
+ if (!nlg)
+ return NULL;
+ nlg->id = 0;
+
+ err = -ENOMEM;
+ nlg->buf = malloc(mnl_ideal_socket_buffer_size());
+ if (!nlg->buf)
+ goto err_buf_alloc;
+
+ nlg->nl = mnl_socket_open(NETLINK_GENERIC);
+ if (!nlg->nl) {
+ err = -errno;
+ goto err_mnl_socket_open;
+ }
+
+ if (mnl_socket_bind(nlg->nl, 0, MNL_SOCKET_AUTOPID) < 0) {
+ err = -errno;
+ goto err_mnl_socket_bind;
+ }
+
+ nlg->portid = mnl_socket_get_portid(nlg->nl);
+
+ nlh = __mnlg_msg_prepare(nlg, CTRL_CMD_GETFAMILY,
+ NLM_F_REQUEST | NLM_F_ACK, GENL_ID_CTRL, 1);
+ mnl_attr_put_strz(nlh, CTRL_ATTR_FAMILY_NAME, family_name);
+
+ if (mnlg_socket_send(nlg, nlh) < 0) {
+ err = -errno;
+ goto err_mnlg_socket_send;
+ }
+
+ errno = 0;
+ if (mnlg_socket_recv_run(nlg, get_family_id_cb, &nlg->id) < 0) {
+ errno = errno == ENOENT ? EPROTONOSUPPORT : errno;
+ err = errno ? -errno : -ENOSYS;
+ goto err_mnlg_socket_recv_run;
+ }
+
+ nlg->version = version;
+ errno = 0;
+ return nlg;
+
+err_mnlg_socket_recv_run:
+err_mnlg_socket_send:
+err_mnl_socket_bind:
+ mnl_socket_close(nlg->nl);
+err_mnl_socket_open:
+ free(nlg->buf);
+err_buf_alloc:
+ free(nlg);
+ errno = -err;
+ return NULL;
+}
+
+static void mnlg_socket_close(struct mnlg_socket *nlg)
+{
+ mnl_socket_close(nlg->nl);
+ free(nlg->buf);
+ free(nlg);
+}
+
+/* wireguard-specific parts: */
+
+struct string_list {
+ char *buffer;
+ size_t len;
+ size_t cap;
+};
+
+static int string_list_add(struct string_list *list, const char *str)
+{
+ size_t len = strlen(str) + 1;
+
+ if (len == 1)
+ return 0;
+
+ if (len >= list->cap - list->len) {
+ char *new_buffer;
+ size_t new_cap = list->cap * 2;
+
+ if (new_cap < list->len +len + 1)
+ new_cap = list->len + len + 1;
+ new_buffer = realloc(list->buffer, new_cap);
+ if (!new_buffer)
+ return -errno;
+ list->buffer = new_buffer;
+ list->cap = new_cap;
+ }
+ memcpy(list->buffer + list->len, str, len);
+ list->len += len;
+ list->buffer[list->len] = '\0';
+ return 0;
+}
+
+struct interface {
+ const char *name;
+ bool is_wireguard;
+};
+
+static int parse_linkinfo(const struct nlattr *attr, void *data)
+{
+ struct interface *interface = data;
+
+ if (mnl_attr_get_type(attr) == IFLA_INFO_KIND && !strcmp(WG_GENL_NAME, mnl_attr_get_str(attr)))
+ interface->is_wireguard = true;
+ return MNL_CB_OK;
+}
+
+static int parse_infomsg(const struct nlattr *attr, void *data)
+{
+ struct interface *interface = data;
+
+ if (mnl_attr_get_type(attr) == IFLA_LINKINFO)
+ return mnl_attr_parse_nested(attr, parse_linkinfo, data);
+ else if (mnl_attr_get_type(attr) == IFLA_IFNAME)
+ interface->name = mnl_attr_get_str(attr);
+ return MNL_CB_OK;
+}
+
+static int read_devices_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct string_list *list = data;
+ struct interface interface = { 0 };
+ int ret;
+
+ ret = mnl_attr_parse(nlh, sizeof(struct ifinfomsg), parse_infomsg, &interface);
+ if (ret != MNL_CB_OK)
+ return ret;
+ if (interface.name && interface.is_wireguard)
+ ret = string_list_add(list, interface.name);
+ if (ret < 0)
+ return ret;
+ if (nlh->nlmsg_type != NLMSG_DONE)
+ return MNL_CB_OK + 1;
+ return MNL_CB_OK;
+}
+
+static int fetch_device_names(struct string_list *list)
+{
+ struct mnl_socket *nl = NULL;
+ char *rtnl_buffer = NULL;
+ size_t message_len;
+ unsigned int portid, seq;
+ ssize_t len;
+ int ret = 0;
+ struct nlmsghdr *nlh;
+ struct ifinfomsg *ifm;
+
+ ret = -ENOMEM;
+ rtnl_buffer = calloc(mnl_ideal_socket_buffer_size(), 1);
+ if (!rtnl_buffer)
+ goto cleanup;
+
+ nl = mnl_socket_open(NETLINK_ROUTE);
+ if (!nl) {
+ ret = -errno;
+ goto cleanup;
+ }
+
+ if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) {
+ ret = -errno;
+ goto cleanup;
+ }
+
+ seq = time(NULL);
+ portid = mnl_socket_get_portid(nl);
+ nlh = mnl_nlmsg_put_header(rtnl_buffer);
+ nlh->nlmsg_type = RTM_GETLINK;
+ nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP;
+ nlh->nlmsg_seq = seq;
+ ifm = mnl_nlmsg_put_extra_header(nlh, sizeof(*ifm));
+ ifm->ifi_family = AF_UNSPEC;
+ message_len = nlh->nlmsg_len;
+
+ if (mnl_socket_sendto(nl, rtnl_buffer, message_len) < 0) {
+ ret = -errno;
+ goto cleanup;
+ }
+
+another:
+ if ((len = mnl_socket_recvfrom(nl, rtnl_buffer, mnl_ideal_socket_buffer_size())) < 0) {
+ ret = -errno;
+ goto cleanup;
+ }
+ if ((len = mnl_cb_run(rtnl_buffer, len, seq, portid, read_devices_cb, list)) < 0) {
+ /* Netlink returns NLM_F_DUMP_INTR if the set of all tunnels changed
+ * during the dump. That's unfortunate, but is pretty common on busy
+ * systems that are adding and removing tunnels all the time. Rather
+ * than retrying, potentially indefinitely, we just work with the
+ * partial results. */
+ if (errno != EINTR) {
+ ret = -errno;
+ goto cleanup;
+ }
+ }
+ if (len == MNL_CB_OK + 1)
+ goto another;
+ ret = 0;
+
+cleanup:
+ free(rtnl_buffer);
+ if (nl)
+ mnl_socket_close(nl);
+ return ret;
+}
+
+static int add_del_iface(const char *ifname, bool add)
+{
+ struct mnl_socket *nl = NULL;
+ char *rtnl_buffer;
+ ssize_t len;
+ int ret;
+ struct nlmsghdr *nlh;
+ struct ifinfomsg *ifm;
+ struct nlattr *nest;
+
+ rtnl_buffer = calloc(mnl_ideal_socket_buffer_size(), 1);
+ if (!rtnl_buffer) {
+ ret = -ENOMEM;
+ goto cleanup;
+ }
+
+ nl = mnl_socket_open(NETLINK_ROUTE);
+ if (!nl) {
+ ret = -errno;
+ goto cleanup;
+ }
+
+ if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) {
+ ret = -errno;
+ goto cleanup;
+ }
+
+ nlh = mnl_nlmsg_put_header(rtnl_buffer);
+ nlh->nlmsg_type = add ? RTM_NEWLINK : RTM_DELLINK;
+ nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | (add ? NLM_F_CREATE | NLM_F_EXCL : 0);
+ nlh->nlmsg_seq = time(NULL);
+ ifm = mnl_nlmsg_put_extra_header(nlh, sizeof(*ifm));
+ ifm->ifi_family = AF_UNSPEC;
+ mnl_attr_put_strz(nlh, IFLA_IFNAME, ifname);
+ nest = mnl_attr_nest_start(nlh, IFLA_LINKINFO);
+ mnl_attr_put_strz(nlh, IFLA_INFO_KIND, WG_GENL_NAME);
+ mnl_attr_nest_end(nlh, nest);
+
+ if (mnl_socket_sendto(nl, rtnl_buffer, nlh->nlmsg_len) < 0) {
+ ret = -errno;
+ goto cleanup;
+ }
+ if ((len = mnl_socket_recvfrom(nl, rtnl_buffer, mnl_ideal_socket_buffer_size())) < 0) {
+ ret = -errno;
+ goto cleanup;
+ }
+ if (mnl_cb_run(rtnl_buffer, len, nlh->nlmsg_seq, mnl_socket_get_portid(nl), NULL, NULL) < 0) {
+ ret = -errno;
+ goto cleanup;
+ }
+ ret = 0;
+
+cleanup:
+ free(rtnl_buffer);
+ if (nl)
+ mnl_socket_close(nl);
+ return ret;
+}
+
+int wg_set_device(wg_device *dev)
+{
+ int ret = 0;
+ wg_peer *peer = NULL;
+ wg_allowedip *allowedip = NULL;
+ struct nlattr *peers_nest, *peer_nest, *allowedips_nest, *allowedip_nest;
+ struct nlmsghdr *nlh;
+ struct mnlg_socket *nlg;
+
+ nlg = mnlg_socket_open(WG_GENL_NAME, WG_GENL_VERSION);
+ if (!nlg)
+ return -errno;
+
+again:
+ nlh = mnlg_msg_prepare(nlg, WG_CMD_SET_DEVICE, NLM_F_REQUEST | NLM_F_ACK);
+ mnl_attr_put_strz(nlh, WGDEVICE_A_IFNAME, dev->name);
+
+ if (!peer) {
+ uint32_t flags = 0;
+
+ if (dev->flags & WGDEVICE_HAS_PRIVATE_KEY)
+ mnl_attr_put(nlh, WGDEVICE_A_PRIVATE_KEY, sizeof(dev->private_key), dev->private_key);
+ if (dev->flags & WGDEVICE_HAS_LISTEN_PORT)
+ mnl_attr_put_u16(nlh, WGDEVICE_A_LISTEN_PORT, dev->listen_port);
+ if (dev->flags & WGDEVICE_HAS_FWMARK)
+ mnl_attr_put_u32(nlh, WGDEVICE_A_FWMARK, dev->fwmark);
+ if (dev->flags & WGDEVICE_REPLACE_PEERS)
+ flags |= WGDEVICE_F_REPLACE_PEERS;
+ if (flags)
+ mnl_attr_put_u32(nlh, WGDEVICE_A_FLAGS, flags);
+ }
+ if (!dev->first_peer)
+ goto send;
+ peers_nest = peer_nest = allowedips_nest = allowedip_nest = NULL;
+ peers_nest = mnl_attr_nest_start(nlh, WGDEVICE_A_PEERS);
+ for (peer = peer ? peer : dev->first_peer; peer; peer = peer->next_peer) {
+ uint32_t flags = 0;
+
+ peer_nest = mnl_attr_nest_start_check(nlh, mnl_ideal_socket_buffer_size(), 0);
+ if (!peer_nest)
+ goto toobig_peers;
+ if (!mnl_attr_put_check(nlh, mnl_ideal_socket_buffer_size(), WGPEER_A_PUBLIC_KEY, sizeof(peer->public_key), peer->public_key))
+ goto toobig_peers;
+ if (peer->flags & WGPEER_REMOVE_ME)
+ flags |= WGPEER_F_REMOVE_ME;
+ if (!allowedip) {
+ if (peer->flags & WGPEER_REPLACE_ALLOWEDIPS)
+ flags |= WGPEER_F_REPLACE_ALLOWEDIPS;
+ if (peer->flags & WGPEER_HAS_PRESHARED_KEY) {
+ if (!mnl_attr_put_check(nlh, mnl_ideal_socket_buffer_size(), WGPEER_A_PRESHARED_KEY, sizeof(peer->preshared_key), peer->preshared_key))
+ goto toobig_peers;
+ }
+ if (peer->endpoint.addr.sa_family == AF_INET) {
+ if (!mnl_attr_put_check(nlh, mnl_ideal_socket_buffer_size(), WGPEER_A_ENDPOINT, sizeof(peer->endpoint.addr4), &peer->endpoint.addr4))
+ goto toobig_peers;
+ } else if (peer->endpoint.addr.sa_family == AF_INET6) {
+ if (!mnl_attr_put_check(nlh, mnl_ideal_socket_buffer_size(), WGPEER_A_ENDPOINT, sizeof(peer->endpoint.addr6), &peer->endpoint.addr6))
+ goto toobig_peers;
+ }
+ if (peer->flags & WGPEER_HAS_PERSISTENT_KEEPALIVE_INTERVAL) {
+ if (!mnl_attr_put_u16_check(nlh, mnl_ideal_socket_buffer_size(), WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL, peer->persistent_keepalive_interval))
+ goto toobig_peers;
+ }
+ }
+ if (flags) {
+ if (!mnl_attr_put_u32_check(nlh, mnl_ideal_socket_buffer_size(), WGPEER_A_FLAGS, flags))
+ goto toobig_peers;
+ }
+ if (peer->first_allowedip) {
+ if (!allowedip)
+ allowedip = peer->first_allowedip;
+ allowedips_nest = mnl_attr_nest_start_check(nlh, mnl_ideal_socket_buffer_size(), WGPEER_A_ALLOWEDIPS);
+ if (!allowedips_nest)
+ goto toobig_allowedips;
+ for (; allowedip; allowedip = allowedip->next_allowedip) {
+ allowedip_nest = mnl_attr_nest_start_check(nlh, mnl_ideal_socket_buffer_size(), 0);
+ if (!allowedip_nest)
+ goto toobig_allowedips;
+ if (!mnl_attr_put_u16_check(nlh, mnl_ideal_socket_buffer_size(), WGALLOWEDIP_A_FAMILY, allowedip->family))
+ goto toobig_allowedips;
+ if (allowedip->family == AF_INET) {
+ if (!mnl_attr_put_check(nlh, mnl_ideal_socket_buffer_size(), WGALLOWEDIP_A_IPADDR, sizeof(allowedip->ip4), &allowedip->ip4))
+ goto toobig_allowedips;
+ } else if (allowedip->family == AF_INET6) {
+ if (!mnl_attr_put_check(nlh, mnl_ideal_socket_buffer_size(), WGALLOWEDIP_A_IPADDR, sizeof(allowedip->ip6), &allowedip->ip6))
+ goto toobig_allowedips;
+ }
+ if (!mnl_attr_put_u8_check(nlh, mnl_ideal_socket_buffer_size(), WGALLOWEDIP_A_CIDR_MASK, allowedip->cidr))
+ goto toobig_allowedips;
+ mnl_attr_nest_end(nlh, allowedip_nest);
+ allowedip_nest = NULL;
+ }
+ mnl_attr_nest_end(nlh, allowedips_nest);
+ allowedips_nest = NULL;
+ }
+
+ mnl_attr_nest_end(nlh, peer_nest);
+ peer_nest = NULL;
+ }
+ mnl_attr_nest_end(nlh, peers_nest);
+ peers_nest = NULL;
+ goto send;
+toobig_allowedips:
+ if (allowedip_nest)
+ mnl_attr_nest_cancel(nlh, allowedip_nest);
+ if (allowedips_nest)
+ mnl_attr_nest_end(nlh, allowedips_nest);
+ mnl_attr_nest_end(nlh, peer_nest);
+ mnl_attr_nest_end(nlh, peers_nest);
+ goto send;
+toobig_peers:
+ if (peer_nest)
+ mnl_attr_nest_cancel(nlh, peer_nest);
+ mnl_attr_nest_end(nlh, peers_nest);
+ goto send;
+send:
+ if (mnlg_socket_send(nlg, nlh) < 0) {
+ ret = -errno;
+ goto out;
+ }
+ errno = 0;
+ if (mnlg_socket_recv_run(nlg, NULL, NULL) < 0) {
+ ret = errno ? -errno : -EINVAL;
+ goto out;
+ }
+ if (peer)
+ goto again;
+
+out:
+ mnlg_socket_close(nlg);
+ errno = -ret;
+ return ret;
+}
+
+static int parse_allowedip(const struct nlattr *attr, void *data)
+{
+ wg_allowedip *allowedip = data;
+
+ switch (mnl_attr_get_type(attr)) {
+ case WGALLOWEDIP_A_UNSPEC:
+ break;
+ case WGALLOWEDIP_A_FAMILY:
+ if (!mnl_attr_validate(attr, MNL_TYPE_U16))
+ allowedip->family = mnl_attr_get_u16(attr);
+ break;
+ case WGALLOWEDIP_A_IPADDR:
+ if (mnl_attr_get_payload_len(attr) == sizeof(allowedip->ip4))
+ memcpy(&allowedip->ip4, mnl_attr_get_payload(attr), sizeof(allowedip->ip4));
+ else if (mnl_attr_get_payload_len(attr) == sizeof(allowedip->ip6))
+ memcpy(&allowedip->ip6, mnl_attr_get_payload(attr), sizeof(allowedip->ip6));
+ break;
+ case WGALLOWEDIP_A_CIDR_MASK:
+ if (!mnl_attr_validate(attr, MNL_TYPE_U8))
+ allowedip->cidr = mnl_attr_get_u8(attr);
+ break;
+ }
+
+ return MNL_CB_OK;
+}
+
+static int parse_allowedips(const struct nlattr *attr, void *data)
+{
+ wg_peer *peer = data;
+ wg_allowedip *new_allowedip = calloc(1, sizeof(wg_allowedip));
+ int ret;
+
+ if (!new_allowedip)
+ return MNL_CB_ERROR;
+ if (!peer->first_allowedip)
+ peer->first_allowedip = peer->last_allowedip = new_allowedip;
+ else {
+ peer->last_allowedip->next_allowedip = new_allowedip;
+ peer->last_allowedip = new_allowedip;
+ }
+ ret = mnl_attr_parse_nested(attr, parse_allowedip, new_allowedip);
+ if (!ret)
+ return ret;
+ if (!((new_allowedip->family == AF_INET && new_allowedip->cidr <= 32) || (new_allowedip->family == AF_INET6 && new_allowedip->cidr <= 128))) {
+ errno = EAFNOSUPPORT;
+ return MNL_CB_ERROR;
+ }
+ return MNL_CB_OK;
+}
+
+bool wg_key_is_zero(const wg_key key)
+{
+ volatile uint8_t acc = 0;
+ unsigned int i;
+
+ for (i = 0; i < sizeof(wg_key); ++i) {
+ acc |= key[i];
+ __asm__ ("" : "=r" (acc) : "0" (acc));
+ }
+ return 1 & ((acc - 1) >> 8);
+}
+
+static int parse_peer(const struct nlattr *attr, void *data)
+{
+ wg_peer *peer = data;
+
+ switch (mnl_attr_get_type(attr)) {
+ case WGPEER_A_UNSPEC:
+ break;
+ case WGPEER_A_PUBLIC_KEY:
+ if (mnl_attr_get_payload_len(attr) == sizeof(peer->public_key)) {
+ memcpy(peer->public_key, mnl_attr_get_payload(attr), sizeof(peer->public_key));
+ peer->flags |= WGPEER_HAS_PUBLIC_KEY;
+ }
+ break;
+ case WGPEER_A_PRESHARED_KEY:
+ if (mnl_attr_get_payload_len(attr) == sizeof(peer->preshared_key)) {
+ memcpy(peer->preshared_key, mnl_attr_get_payload(attr), sizeof(peer->preshared_key));
+ if (!wg_key_is_zero(peer->preshared_key))
+ peer->flags |= WGPEER_HAS_PRESHARED_KEY;
+ }
+ break;
+ case WGPEER_A_ENDPOINT: {
+ struct sockaddr *addr;
+
+ if (mnl_attr_get_payload_len(attr) < sizeof(*addr))
+ break;
+ addr = mnl_attr_get_payload(attr);
+ if (addr->sa_family == AF_INET && mnl_attr_get_payload_len(attr) == sizeof(peer->endpoint.addr4))
+ memcpy(&peer->endpoint.addr4, addr, sizeof(peer->endpoint.addr4));
+ else if (addr->sa_family == AF_INET6 && mnl_attr_get_payload_len(attr) == sizeof(peer->endpoint.addr6))
+ memcpy(&peer->endpoint.addr6, addr, sizeof(peer->endpoint.addr6));
+ break;
+ }
+ case WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL:
+ if (!mnl_attr_validate(attr, MNL_TYPE_U16))
+ peer->persistent_keepalive_interval = mnl_attr_get_u16(attr);
+ break;
+ case WGPEER_A_LAST_HANDSHAKE_TIME:
+ if (mnl_attr_get_payload_len(attr) == sizeof(peer->last_handshake_time))
+ memcpy(&peer->last_handshake_time, mnl_attr_get_payload(attr), sizeof(peer->last_handshake_time));
+ break;
+ case WGPEER_A_RX_BYTES:
+ if (!mnl_attr_validate(attr, MNL_TYPE_U64))
+ peer->rx_bytes = mnl_attr_get_u64(attr);
+ break;
+ case WGPEER_A_TX_BYTES:
+ if (!mnl_attr_validate(attr, MNL_TYPE_U64))
+ peer->tx_bytes = mnl_attr_get_u64(attr);
+ break;
+ case WGPEER_A_ALLOWEDIPS:
+ return mnl_attr_parse_nested(attr, parse_allowedips, peer);
+ }
+
+ return MNL_CB_OK;
+}
+
+static int parse_peers(const struct nlattr *attr, void *data)
+{
+ wg_device *device = data;
+ wg_peer *new_peer = calloc(1, sizeof(wg_peer));
+ int ret;
+
+ if (!new_peer)
+ return MNL_CB_ERROR;
+ if (!device->first_peer)
+ device->first_peer = device->last_peer = new_peer;
+ else {
+ device->last_peer->next_peer = new_peer;
+ device->last_peer = new_peer;
+ }
+ ret = mnl_attr_parse_nested(attr, parse_peer, new_peer);
+ if (!ret)
+ return ret;
+ if (!(new_peer->flags & WGPEER_HAS_PUBLIC_KEY)) {
+ errno = ENXIO;
+ return MNL_CB_ERROR;
+ }
+ return MNL_CB_OK;
+}
+
+static int parse_device(const struct nlattr *attr, void *data)
+{
+ wg_device *device = data;
+
+ switch (mnl_attr_get_type(attr)) {
+ case WGDEVICE_A_UNSPEC:
+ break;
+ case WGDEVICE_A_IFINDEX:
+ if (!mnl_attr_validate(attr, MNL_TYPE_U32))
+ device->ifindex = mnl_attr_get_u32(attr);
+ break;
+ case WGDEVICE_A_IFNAME:
+ if (!mnl_attr_validate(attr, MNL_TYPE_STRING)) {
+ strncpy(device->name, mnl_attr_get_str(attr), sizeof(device->name) - 1);
+ device->name[sizeof(device->name) - 1] = '\0';
+ }
+ break;
+ case WGDEVICE_A_PRIVATE_KEY:
+ if (mnl_attr_get_payload_len(attr) == sizeof(device->private_key)) {
+ memcpy(device->private_key, mnl_attr_get_payload(attr), sizeof(device->private_key));
+ device->flags |= WGDEVICE_HAS_PRIVATE_KEY;
+ }
+ break;
+ case WGDEVICE_A_PUBLIC_KEY:
+ if (mnl_attr_get_payload_len(attr) == sizeof(device->public_key)) {
+ memcpy(device->public_key, mnl_attr_get_payload(attr), sizeof(device->public_key));
+ device->flags |= WGDEVICE_HAS_PUBLIC_KEY;
+ }
+ break;
+ case WGDEVICE_A_LISTEN_PORT:
+ if (!mnl_attr_validate(attr, MNL_TYPE_U16))
+ device->listen_port = mnl_attr_get_u16(attr);
+ break;
+ case WGDEVICE_A_FWMARK:
+ if (!mnl_attr_validate(attr, MNL_TYPE_U32))
+ device->fwmark = mnl_attr_get_u32(attr);
+ break;
+ case WGDEVICE_A_PEERS:
+ return mnl_attr_parse_nested(attr, parse_peers, device);
+ }
+
+ return MNL_CB_OK;
+}
+
+static int read_device_cb(const struct nlmsghdr *nlh, void *data)
+{
+ return mnl_attr_parse(nlh, sizeof(struct genlmsghdr), parse_device, data);
+}
+
+static void coalesce_peers(wg_device *device)
+{
+ wg_peer *old_next_peer, *peer = device->first_peer;
+
+ while (peer && peer->next_peer) {
+ if (memcmp(peer->public_key, peer->next_peer->public_key, sizeof(wg_key))) {
+ peer = peer->next_peer;
+ continue;
+ }
+ if (!peer->first_allowedip) {
+ peer->first_allowedip = peer->next_peer->first_allowedip;
+ peer->last_allowedip = peer->next_peer->last_allowedip;
+ } else {
+ peer->last_allowedip->next_allowedip = peer->next_peer->first_allowedip;
+ peer->last_allowedip = peer->next_peer->last_allowedip;
+ }
+ old_next_peer = peer->next_peer;
+ peer->next_peer = old_next_peer->next_peer;
+ free(old_next_peer);
+ }
+}
+
+int wg_get_device(wg_device **device, const char *device_name)
+{
+ int ret = 0;
+ struct nlmsghdr *nlh;
+ struct mnlg_socket *nlg;
+
+try_again:
+ *device = calloc(1, sizeof(wg_device));
+ if (!*device)
+ return -errno;
+
+ nlg = mnlg_socket_open(WG_GENL_NAME, WG_GENL_VERSION);
+ if (!nlg) {
+ wg_free_device(*device);
+ *device = NULL;
+ return -errno;
+ }
+
+ nlh = mnlg_msg_prepare(nlg, WG_CMD_GET_DEVICE, NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP);
+ mnl_attr_put_strz(nlh, WGDEVICE_A_IFNAME, device_name);
+ if (mnlg_socket_send(nlg, nlh) < 0) {
+ ret = -errno;
+ goto out;
+ }
+ errno = 0;
+ if (mnlg_socket_recv_run(nlg, read_device_cb, *device) < 0) {
+ ret = errno ? -errno : -EINVAL;
+ goto out;
+ }
+ coalesce_peers(*device);
+
+out:
+ if (nlg)
+ mnlg_socket_close(nlg);
+ if (ret) {
+ wg_free_device(*device);
+ if (ret == -EINTR)
+ goto try_again;
+ *device = NULL;
+ }
+ errno = -ret;
+ return ret;
+}
+
+/* first\0second\0third\0forth\0last\0\0 */
+char *wg_list_device_names(void)
+{
+ struct string_list list = { 0 };
+ int ret = fetch_device_names(&list);
+
+ errno = -ret;
+ if (errno) {
+ free(list.buffer);
+ return NULL;
+ }
+ return list.buffer ?: strdup("\0");
+}
+
+int wg_add_device(const char *device_name)
+{
+ return add_del_iface(device_name, true);
+}
+
+int wg_del_device(const char *device_name)
+{
+ return add_del_iface(device_name, false);
+}
+
+void wg_free_device(wg_device *dev)
+{
+ wg_peer *peer, *np;
+ wg_allowedip *allowedip, *na;
+
+ if (!dev)
+ return;
+ for (peer = dev->first_peer, np = peer ? peer->next_peer : NULL; peer; peer = np, np = peer ? peer->next_peer : NULL) {
+ for (allowedip = peer->first_allowedip, na = allowedip ? allowedip->next_allowedip : NULL; allowedip; allowedip = na, na = allowedip ? allowedip->next_allowedip : NULL)
+ free(allowedip);
+ free(peer);
+ }
+ free(dev);
+}
+
+static void encode_base64(char dest[static 4], const uint8_t src[static 3])
+{
+ const uint8_t input[] = { (src[0] >> 2) & 63, ((src[0] << 4) | (src[1] >> 4)) & 63, ((src[1] << 2) | (src[2] >> 6)) & 63, src[2] & 63 };
+ unsigned int i;
+
+ for (i = 0; i < 4; ++i)
+ dest[i] = input[i] + 'A'
+ + (((25 - input[i]) >> 8) & 6)
+ - (((51 - input[i]) >> 8) & 75)
+ - (((61 - input[i]) >> 8) & 15)
+ + (((62 - input[i]) >> 8) & 3);
+
+}
+
+void wg_key_to_base64(wg_key_b64_string base64, const wg_key key)
+{
+ unsigned int i;
+
+ for (i = 0; i < 32 / 3; ++i)
+ encode_base64(&base64[i * 4], &key[i * 3]);
+ encode_base64(&base64[i * 4], (const uint8_t[]){ key[i * 3 + 0], key[i * 3 + 1], 0 });
+ base64[sizeof(wg_key_b64_string) - 2] = '=';
+ base64[sizeof(wg_key_b64_string) - 1] = '\0';
+}
+
+static int decode_base64(const char src[static 4])
+{
+ int val = 0;
+ unsigned int i;
+
+ for (i = 0; i < 4; ++i)
+ val |= (-1
+ + ((((('A' - 1) - src[i]) & (src[i] - ('Z' + 1))) >> 8) & (src[i] - 64))
+ + ((((('a' - 1) - src[i]) & (src[i] - ('z' + 1))) >> 8) & (src[i] - 70))
+ + ((((('0' - 1) - src[i]) & (src[i] - ('9' + 1))) >> 8) & (src[i] + 5))
+ + ((((('+' - 1) - src[i]) & (src[i] - ('+' + 1))) >> 8) & 63)
+ + ((((('/' - 1) - src[i]) & (src[i] - ('/' + 1))) >> 8) & 64)
+ ) << (18 - 6 * i);
+ return val;
+}
+
+int wg_key_from_base64(wg_key key, const wg_key_b64_string base64)
+{
+ unsigned int i;
+ int val;
+ volatile uint8_t ret = 0;
+
+ if (strlen(base64) != sizeof(wg_key_b64_string) - 1 || base64[sizeof(wg_key_b64_string) - 2] != '=') {
+ errno = EINVAL;
+ goto out;
+ }
+
+ for (i = 0; i < 32 / 3; ++i) {
+ val = decode_base64(&base64[i * 4]);
+ ret |= (uint32_t)val >> 31;
+ key[i * 3 + 0] = (val >> 16) & 0xff;
+ key[i * 3 + 1] = (val >> 8) & 0xff;
+ key[i * 3 + 2] = val & 0xff;
+ }
+ val = decode_base64((const char[]){ base64[i * 4 + 0], base64[i * 4 + 1], base64[i * 4 + 2], 'A' });
+ ret |= ((uint32_t)val >> 31) | (val & 0xff);
+ key[i * 3 + 0] = (val >> 16) & 0xff;
+ key[i * 3 + 1] = (val >> 8) & 0xff;
+ errno = EINVAL & ~((ret - 1) >> 8);
+out:
+ return -errno;
+}
+
+typedef int64_t fe[16];
+
+static __attribute__((noinline)) void memzero_explicit(void *s, size_t count)
+{
+ memset(s, 0, count);
+ __asm__ __volatile__("": :"r"(s) :"memory");
+}
+
+static void carry(fe o)
+{
+ int i;
+
+ for (i = 0; i < 16; ++i) {
+ o[(i + 1) % 16] += (i == 15 ? 38 : 1) * (o[i] >> 16);
+ o[i] &= 0xffff;
+ }
+}
+
+static void cswap(fe p, fe q, int b)
+{
+ int i;
+ int64_t t, c = ~(b - 1);
+
+ for (i = 0; i < 16; ++i) {
+ t = c & (p[i] ^ q[i]);
+ p[i] ^= t;
+ q[i] ^= t;
+ }
+
+ memzero_explicit(&t, sizeof(t));
+ memzero_explicit(&c, sizeof(c));
+ memzero_explicit(&b, sizeof(b));
+}
+
+static void pack(uint8_t *o, const fe n)
+{
+ int i, j, b;
+ fe m, t;
+
+ memcpy(t, n, sizeof(t));
+ carry(t);
+ carry(t);
+ carry(t);
+ for (j = 0; j < 2; ++j) {
+ m[0] = t[0] - 0xffed;
+ for (i = 1; i < 15; ++i) {
+ m[i] = t[i] - 0xffff - ((m[i - 1] >> 16) & 1);
+ m[i - 1] &= 0xffff;
+ }
+ m[15] = t[15] - 0x7fff - ((m[14] >> 16) & 1);
+ b = (m[15] >> 16) & 1;
+ m[14] &= 0xffff;
+ cswap(t, m, 1 - b);
+ }
+ for (i = 0; i < 16; ++i) {
+ o[2 * i] = t[i] & 0xff;
+ o[2 * i + 1] = t[i] >> 8;
+ }
+
+ memzero_explicit(m, sizeof(m));
+ memzero_explicit(t, sizeof(t));
+ memzero_explicit(&b, sizeof(b));
+}
+
+static void add(fe o, const fe a, const fe b)
+{
+ int i;
+
+ for (i = 0; i < 16; ++i)
+ o[i] = a[i] + b[i];
+}
+
+static void subtract(fe o, const fe a, const fe b)
+{
+ int i;
+
+ for (i = 0; i < 16; ++i)
+ o[i] = a[i] - b[i];
+}
+
+static void multmod(fe o, const fe a, const fe b)
+{
+ int i, j;
+ int64_t t[31] = { 0 };
+
+ for (i = 0; i < 16; ++i) {
+ for (j = 0; j < 16; ++j)
+ t[i + j] += a[i] * b[j];
+ }
+ for (i = 0; i < 15; ++i)
+ t[i] += 38 * t[i + 16];
+ memcpy(o, t, sizeof(fe));
+ carry(o);
+ carry(o);
+
+ memzero_explicit(t, sizeof(t));
+}
+
+static void invert(fe o, const fe i)
+{
+ fe c;
+ int a;
+
+ memcpy(c, i, sizeof(c));
+ for (a = 253; a >= 0; --a) {
+ multmod(c, c, c);
+ if (a != 2 && a != 4)
+ multmod(c, c, i);
+ }
+ memcpy(o, c, sizeof(fe));
+
+ memzero_explicit(c, sizeof(c));
+}
+
+static void clamp_key(uint8_t *z)
+{
+ z[31] = (z[31] & 127) | 64;
+ z[0] &= 248;
+}
+
+void wg_generate_public_key(wg_key public_key, const wg_key private_key)
+{
+ int i, r;
+ uint8_t z[32];
+ fe a = { 1 }, b = { 9 }, c = { 0 }, d = { 1 }, e, f;
+
+ memcpy(z, private_key, sizeof(z));
+ clamp_key(z);
+
+ for (i = 254; i >= 0; --i) {
+ r = (z[i >> 3] >> (i & 7)) & 1;
+ cswap(a, b, r);
+ cswap(c, d, r);
+ add(e, a, c);
+ subtract(a, a, c);
+ add(c, b, d);
+ subtract(b, b, d);
+ multmod(d, e, e);
+ multmod(f, a, a);
+ multmod(a, c, a);
+ multmod(c, b, e);
+ add(e, a, c);
+ subtract(a, a, c);
+ multmod(b, a, a);
+ subtract(c, d, f);
+ multmod(a, c, (const fe){ 0xdb41, 1 });
+ add(a, a, d);
+ multmod(c, c, a);
+ multmod(a, d, f);
+ multmod(d, b, (const fe){ 9 });
+ multmod(b, e, e);
+ cswap(a, b, r);
+ cswap(c, d, r);
+ }
+ invert(c, c);
+ multmod(a, a, c);
+ pack(public_key, a);
+
+ memzero_explicit(&r, sizeof(r));
+ memzero_explicit(z, sizeof(z));
+ memzero_explicit(a, sizeof(a));
+ memzero_explicit(b, sizeof(b));
+ memzero_explicit(c, sizeof(c));
+ memzero_explicit(d, sizeof(d));
+ memzero_explicit(e, sizeof(e));
+ memzero_explicit(f, sizeof(f));
+}
+
+void wg_generate_private_key(wg_key private_key)
+{
+ wg_generate_preshared_key(private_key);
+ clamp_key(private_key);
+}
+
+void wg_generate_preshared_key(wg_key preshared_key)
+{
+ ssize_t ret;
+ size_t i;
+ int fd;
+#if defined(__OpenBSD__) || (defined(__APPLE__) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_12) || (defined(__GLIBC__) && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 25)))
+ if (!getentropy(preshared_key, sizeof(wg_key)))
+ return;
+#endif
+#if defined(__NR_getrandom) && defined(__linux__)
+ if (syscall(__NR_getrandom, preshared_key, sizeof(wg_key), 0) == sizeof(wg_key))
+ return;
+#endif
+ fd = open("/dev/urandom", O_RDONLY);
+ assert(fd >= 0);
+ for (i = 0; i < sizeof(wg_key); i += ret) {
+ ret = read(fd, preshared_key + i, sizeof(wg_key) - i);
+ assert(ret > 0);
+ }
+ close(fd);
+}
diff --git a/sysdep/linux/wireguard.h b/sysdep/linux/wireguard.h
new file mode 100644
index 00000000..328fcb42
--- /dev/null
+++ b/sysdep/linux/wireguard.h
@@ -0,0 +1,105 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+/*
+ * Copyright (C) 2015-2020 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
+ */
+
+#ifndef WIREGUARD_H
+#define WIREGUARD_H
+
+#include <net/if.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <time.h>
+#include <stdint.h>
+#include <stdbool.h>
+
+typedef uint8_t wg_key[32];
+typedef char wg_key_b64_string[((sizeof(wg_key) + 2) / 3) * 4 + 1];
+
+/* Cross platform __kernel_timespec */
+struct timespec64 {
+ int64_t tv_sec;
+ int64_t tv_nsec;
+};
+
+typedef struct wg_allowedip {
+ uint16_t family;
+ union {
+ struct in_addr ip4;
+ struct in6_addr ip6;
+ };
+ uint8_t cidr;
+ struct wg_allowedip *next_allowedip;
+} wg_allowedip;
+
+enum wg_peer_flags {
+ WGPEER_REMOVE_ME = 1U << 0,
+ WGPEER_REPLACE_ALLOWEDIPS = 1U << 1,
+ WGPEER_HAS_PUBLIC_KEY = 1U << 2,
+ WGPEER_HAS_PRESHARED_KEY = 1U << 3,
+ WGPEER_HAS_PERSISTENT_KEEPALIVE_INTERVAL = 1U << 4
+};
+
+typedef union wg_endpoint {
+ struct sockaddr addr;
+ struct sockaddr_in addr4;
+ struct sockaddr_in6 addr6;
+} wg_endpoint;
+
+typedef struct wg_peer {
+ enum wg_peer_flags flags;
+
+ wg_key public_key;
+ wg_key preshared_key;
+
+ wg_endpoint endpoint;
+
+ struct timespec64 last_handshake_time;
+ uint64_t rx_bytes, tx_bytes;
+ uint16_t persistent_keepalive_interval;
+
+ struct wg_allowedip *first_allowedip, *last_allowedip;
+ struct wg_peer *next_peer;
+} wg_peer;
+
+enum wg_device_flags {
+ WGDEVICE_REPLACE_PEERS = 1U << 0,
+ WGDEVICE_HAS_PRIVATE_KEY = 1U << 1,
+ WGDEVICE_HAS_PUBLIC_KEY = 1U << 2,
+ WGDEVICE_HAS_LISTEN_PORT = 1U << 3,
+ WGDEVICE_HAS_FWMARK = 1U << 4
+};
+
+typedef struct wg_device {
+ char name[IFNAMSIZ];
+ uint32_t ifindex;
+
+ enum wg_device_flags flags;
+
+ wg_key public_key;
+ wg_key private_key;
+
+ uint32_t fwmark;
+ uint16_t listen_port;
+
+ struct wg_peer *first_peer, *last_peer;
+} wg_device;
+
+#define wg_for_each_device_name(__names, __name, __len) for ((__name) = (__names), (__len) = 0; ((__len) = strlen(__name)); (__name) += (__len) + 1)
+#define wg_for_each_peer(__dev, __peer) for ((__peer) = (__dev)->first_peer; (__peer); (__peer) = (__peer)->next_peer)
+#define wg_for_each_allowedip(__peer, __allowedip) for ((__allowedip) = (__peer)->first_allowedip; (__allowedip); (__allowedip) = (__allowedip)->next_allowedip)
+
+int wg_set_device(wg_device *dev);
+int wg_get_device(wg_device **dev, const char *device_name);
+int wg_add_device(const char *device_name);
+int wg_del_device(const char *device_name);
+void wg_free_device(wg_device *dev);
+char *wg_list_device_names(void); /* first\0second\0third\0forth\0last\0\0 */
+void wg_key_to_base64(wg_key_b64_string base64, const wg_key key);
+int wg_key_from_base64(wg_key key, const wg_key_b64_string base64);
+bool wg_key_is_zero(const wg_key key);
+void wg_generate_public_key(wg_key public_key, const wg_key private_key);
+void wg_generate_private_key(wg_key private_key);
+void wg_generate_preshared_key(wg_key preshared_key);
+
+#endif
diff --git a/sysdep/unix/Makefile b/sysdep/unix/Makefile
index d0d36b5f..ef422342 100644
--- a/sysdep/unix/Makefile
+++ b/sysdep/unix/Makefile
@@ -1,4 +1,4 @@
-src := alloc.c io.c krt.c log.c main.c random.c
+src := alloc.c io.c krt.c log.c main.c random.c wg_user.c
obj := $(src-o-files)
$(all-daemon)
$(cf-local)
diff --git a/sysdep/unix/io.c b/sysdep/unix/io.c
index 6aedcfb6..a3c4a29f 100644
--- a/sysdep/unix/io.c
+++ b/sysdep/unix/io.c
@@ -810,6 +810,8 @@ sk_free(resource *r)
{
sock *s = (sock *) r;
+ log(L_TRACE "sk_free %d %d", s->fd, s->type);
+
sk_free_bufs(s);
#ifdef HAVE_LIBSSH
@@ -1063,6 +1065,14 @@ sk_tcp_connected(sock *s)
s->tx_hook(s);
}
+static void
+sk_unix_connected(sock *s)
+{
+ sk_alloc_bufs(s);
+ s->type = SK_UNIX;
+ s->tx_hook(s);
+}
+
#ifdef HAVE_LIBSSH
static void
sk_ssh_connected(sock *s)
@@ -1529,6 +1539,64 @@ sk_open_unix(sock *s, char *name)
return 0;
}
+static void hexdump(const char *data, socklen_t size)
+{
+ char buf[1024]="";
+
+ for (unsigned int i = 0; i < size; i++)
+ {
+ sprintf(buf + i*3, "%02x ", data[i]);
+ }
+ log(L_TRACE "sk_connect_unix: %s", buf);
+}
+
+int
+sk_connect_unix(sock *s, char *name, socklen_t namelen)
+{
+ struct sockaddr_un sa;
+ int fd;
+
+ if (namelen > sizeof(sa.sun_path))
+ return -1;
+
+ fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (fd < 0)
+ return -2;
+
+ if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0)
+ {
+ close(fd);
+ return -3;
+ }
+
+ /* Path length checked in test_old_bird() */
+ memset(&sa, 0, sizeof(sa));
+ sa.sun_family = AF_UNIX;
+ memcpy(sa.sun_path, name, namelen);
+
+ //hexdump((const char*)&sa, sizeof(sa.sun_family) + namelen);
+
+ s->fd = fd;
+ s->type = SK_UNIX_ACTIVE;
+ s->ttx = ""; /* Force s->ttx != s->tpos */
+
+ if (connect(fd, (struct sockaddr *) &sa, sizeof(sa.sun_family) + namelen) >= 0)
+ sk_unix_connected(s);
+ else if (errno != EINTR && errno != EAGAIN && errno != EINPROGRESS &&
+ errno != ECONNREFUSED && errno != EHOSTUNREACH && errno != ENETUNREACH)
+ {
+ ERR2("connect");
+ }
+
+ sk_insert(s);
+ return 0;
+
+ err:
+ close(fd);
+ s->fd = -1;
+ return -1;
+}
+
#define CMSG_RX_SPACE MAX(CMSG4_SPACE_PKTINFO+CMSG4_SPACE_TTL, \
CMSG6_SPACE_PKTINFO+CMSG6_SPACE_TTL)
@@ -1894,7 +1962,10 @@ sk_read_noflush(sock *s, int revents)
}
}
else if (!c)
+ {
+ if (s->type == SK_UNIX) log(L_TRACE "Unix socket nothing to read");
s->err_hook(s, 0);
+ }
else
{
s->rpos += c;
@@ -1955,6 +2026,12 @@ sk_write_noflush(sock *s)
return 0;
}
+ case SK_UNIX_ACTIVE:
+ {
+ sk_unix_connected(s);
+ return 0;
+ }
+
#ifdef HAVE_LIBSSH
case SK_SSH_ACTIVE:
{
diff --git a/sysdep/unix/main.c b/sysdep/unix/main.c
index 23cadb14..683d26e1 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"
@@ -604,6 +605,7 @@ unlink_pid_file(void)
void
cmd_shutdown(void)
{
+ log_msg(L_INFO "cmd_shutdown");
if (cli_access_restricted())
return;
@@ -614,6 +616,7 @@ cmd_shutdown(void)
void
async_shutdown(void)
{
+ log_msg(L_INFO "async_shutdown");
DBG("Shutting down...\n");
order_shutdown(0);
}
@@ -621,6 +624,7 @@ async_shutdown(void)
void
sysdep_shutdown_done(void)
{
+ log_msg(L_INFO "sysdep_shutdown_done");
unlink_pid_file();
unlink(path_control_socket);
log_msg(L_FATAL "Shutdown completed");
@@ -897,6 +901,7 @@ main(int argc, char **argv)
if_init();
// roa_init();
config_init();
+ tunnel_encap_init();
uid_t use_uid = get_uid(use_user);
gid_t use_gid = get_gid(use_group);
diff --git a/sysdep/unix/unix.h b/sysdep/unix/unix.h
index ad85d1ea..6fa8df6e 100644
--- a/sysdep/unix/unix.h
+++ b/sysdep/unix/unix.h
@@ -109,6 +109,7 @@ void io_loop(void);
void io_log_dump(void);
int sk_open_unix(struct birdsock *s, char *name);
struct rfile *rf_open(struct pool *, const char *name, const char *mode);
+int sk_connect_unix(struct birdsock *s, char *name, socklen_t namelen);
void *rf_file(struct rfile *f);
int rf_fileno(struct rfile *f);
void test_old_bird(char *path);
diff --git a/sysdep/unix/wg_user.c b/sysdep/unix/wg_user.c
new file mode 100644
index 00000000..550f499e
--- /dev/null
+++ b/sysdep/unix/wg_user.c
@@ -0,0 +1,298 @@
+#define LOCAL_DEBUG
+
+#include <stdio.h>
+#include <stdbool.h>
+#include <sys/stat.h>
+#include <sys/un.h>
+#include <unistd.h>
+#include <errno.h>
+#include "lib/lists.h"
+#include "lib/ip.h"
+#include "lib/socket.h"
+#include "nest/iface.h"
+#include "sysdep/unix/unix.h"
+#include "sysdep/unix/wg_user.h"
+#include "sysdep/linux/wireguard.h"
+
+static
+socklen_t get_socket_path(const char *ifname, char *buf, uint size)
+{
+ int pos = 0;
+
+ if (size < 1)
+ return 0;
+
+#ifdef ANDROID
+ /* Abstract socket */
+ buf[pos++] = 0;
+#endif
+ bsnprintf(buf+pos, size-pos, SOCKET_PATH "%s.sock", ifname);
+ return pos + strlen(buf+pos);
+}
+
+bool wg_has_userspace(const char *ifname)
+{
+ struct stat sb;
+ char tmp[sizeof(struct sockaddr_un)];
+ socklen_t tmplen = get_socket_path(ifname, tmp, sizeof(tmp));
+
+ if (tmplen > 0 && tmp[0] == 0)
+ /* System with abstract socket (Android) always use WireGuard's userspace
+ implementation. */
+ return true;
+ else if (stat(tmp, &sb) == 0)
+ return (sb.st_mode & S_IFMT) == S_IFSOCK;
+ else
+ {
+ DBG(L_TRACE "WG: no socket %s", tmp);
+ log(L_TRACE "WG: no socket %s", tmp);
+ return false;
+ }
+}
+
+/* NULL=receiving turned off, returns 1 to clear rx buffer */
+static
+int user_rx_hook(struct birdsock *sk, uint size)
+{
+ if (size > 0)
+ {
+ char buf[1024]="";
+ buf[sizeof(buf) - 1] = '\0';
+ strncpy(buf, sk->rbuf, sizeof(buf) - 1);
+ log(L_TRACE "WG: RX %p %d '%s'", sk, size, buf);
+ /* TODO interpret received data */
+ }
+
+ /* Clear rx buffer */
+ return 1;
+}
+
+static
+void user_tx_hook(struct birdsock *bs)
+{
+ log(L_TRACE "WG: TX %p %d", bs, bs->tpos - bs->ttx);
+
+ uint size = (uintptr_t)bs->data;
+
+ if (size > 0)
+ {
+ int res = sk_send(bs, bs->tbsize - size);
+
+ /* Send data, <0=err, >0=ok, 0=sleep */
+ if (res < 0)
+ {
+ log(L_TRACE "WG: send %d", res);
+ }
+
+ bs->data = NULL;
+ }
+}
+
+/* errno or zero if EOF */
+static
+void user_err_hook(struct birdsock *bs, int err)
+{
+ /* if (err == 0) */
+ /* return; */
+
+ log(L_TRACE "WG: ERR %p %d %s", bs, err, bs->err);
+ rfree(bs);
+}
+
+static void
+wg_puts(const char *str, byte **buf, uint *size)
+{
+ int len = strlen(str);
+ if (0 < len && len < (int)*size)
+ {
+ strcpy(*buf, str);
+ *size -= len;
+ *buf += len;
+ }
+ else
+ *size = 0;
+}
+
+static void
+wg_put_str(const char *key, const char *value, byte **buf, uint *size)
+{
+ char tmp[128];
+
+ int len = snprintf(tmp, sizeof(tmp), "%s=%s\n", key, value);
+ if (0 < len && len < (int)*size)
+ {
+ strcpy(*buf, tmp);
+ *size -= len;
+ *buf += len;
+ }
+ else
+ *size = 0;
+}
+
+static void
+wg_put_u16(const char *key, u16 value, byte **buf, uint *size)
+{
+ char tmp[64];
+
+ int len = snprintf(tmp, sizeof(tmp), "%u", value);
+ if (len > 0)
+ wg_put_str(key, tmp, buf, size);
+ else
+ *size = 0;
+}
+
+static void
+wg_put_bool(const char *key, bool value, byte **buf, uint *size)
+{
+ if (value)
+ wg_put_str(key, "true", buf, size);
+}
+
+static void
+wg_put_key(const char *key, wg_key value, byte **buf, uint *size)
+{
+ char tmp[128];
+
+ for (uint i=0; i < sizeof(wg_key); i++)
+ bsnprintf(tmp+2*i, sizeof(tmp)-2*i, "%02x", value[i]);
+
+ wg_put_str(key, tmp, buf, size);
+}
+
+static void
+wg_put_endpoint(const wg_endpoint *endpoint, byte **buf, uint *size)
+{
+ char tmp[INET6_ADDRSTRLEN + 16];
+ ip_addr ip;
+ struct iface *ifa = NULL;
+ uint port = 0;
+
+ if (sockaddr_read((sockaddr*)&endpoint->addr, endpoint->addr.sa_family,
+ &ip, &ifa, &port) == 0)
+ {
+ char *pos = NULL;
+
+ if (ipa_is_ip4(ip))
+ pos = ip4_ntop(ipa_to_ip4(ip), tmp);
+ else
+ {
+ tmp[0] = '[';
+ pos = ip6_ntop(ipa_to_ip6(ip), tmp + 1);
+ if (ifa)
+ pos += bsprintf(pos, "%%%u", ifa->index);
+ *pos++ = ']';
+ }
+
+ bsprintf(pos, ":%u", port);
+ wg_put_str("endpoint", tmp, buf, size);
+ }
+}
+
+static void
+wg_put_allowedip(wg_allowedip *allowedip, byte **buf, uint *size)
+{
+ char tmp[INET6_ADDRSTRLEN + 10];
+ ip_addr ip = IP6_NONE;
+
+ switch (allowedip->family)
+ {
+ case AF_INET:
+ ip = ipa_from_in4(allowedip->ip4);
+ break;
+ case AF_INET6:
+ ip = ipa_from_in6(allowedip->ip6);
+ break;
+ default:
+ return;
+ }
+
+ int res = bsnprintf(tmp, sizeof(tmp), "%I/%u", ip, allowedip->cidr);
+
+ if (res < 0)
+ {
+ *size = 0;
+ return;
+ }
+
+ wg_put_str("allowed_ip", tmp, buf, size);
+}
+
+static int
+user_put_device(wg_device *dev, byte **buf, uint *size)
+{
+ wg_put_u16("set", 1, buf, size);
+ if (dev->flags & WGDEVICE_HAS_PRIVATE_KEY)
+ wg_put_key("private_key", dev->private_key, buf, size);
+#if 0
+ /* Setting listen_port causes dead-lock in wireguard-go. */
+ if (dev->flags & WGDEVICE_HAS_LISTEN_PORT)
+ wg_put_u16("listen_port", dev->listen_port, buf, size);
+#endif
+ if (dev->flags & WGDEVICE_REPLACE_PEERS)
+ wg_put_bool("replace_peers", true, buf, size);
+
+ wg_peer *peer = NULL;
+ wg_for_each_peer(dev, peer)
+ {
+ wg_put_key("public_key", peer->public_key, buf, size);
+ wg_put_endpoint(&peer->endpoint, buf, size);
+ if (peer->flags & WGPEER_REPLACE_ALLOWEDIPS)
+ wg_put_bool("replace_allowed_ips", true, buf, size);
+
+ wg_allowedip *allowedip = NULL;
+ wg_for_each_allowedip(peer, allowedip)
+ {
+ wg_put_allowedip(allowedip, buf, size);
+ }
+ }
+ wg_puts("\n", buf, size);
+
+ if (*size > 0)
+ return 0;
+ else
+ return -1;
+}
+
+int
+wg_user_set_device(struct pool *pool,
+ const char *ifname,
+ struct wg_device *dev)
+{
+ char path[sizeof(struct sockaddr_un)];
+ socklen_t pathlen = get_socket_path(ifname, path, sizeof(path));
+
+ struct birdsock *sock = sk_new(pool);
+ sock->rx_hook = user_rx_hook;
+ sock->tx_hook = user_tx_hook;
+ sock->err_hook = user_err_hook;
+ sock->fast_rx = 1;
+
+ uint tbsize = 8192;
+ sk_set_tbsize(sock, tbsize);
+ sk_set_rbsize(sock, 16);
+ byte *pos = sock->tbuf;
+ uint size = tbsize;
+ int len = user_put_device(dev, &pos, &size);
+
+ log(L_TRACE "WG: put %d %d %s", len, size, sock->tbuf);
+
+ if (len < 0)
+ {
+ rfree(sock);
+ return -1;
+ }
+
+ sock->data = (void*)(uintptr_t)size;
+
+ int res = sk_connect_unix(sock, path, pathlen);
+ log(L_TRACE "WG: socket %s %d %d %d %s %d %s %d", res<0?strerror(errno):NULL, res, res<0?errno:0, sock->fd, ifname, path[0], path + 1, pathlen);
+ DBG(L_TRACE "WG: socket %d %d %s", res, sock->fd, path);
+ if (res < 0)
+ {
+ rfree(sock);
+ return -1;
+ }
+
+ /* abort(); */
+ return -1;
+}
diff --git a/sysdep/unix/wg_user.h b/sysdep/unix/wg_user.h
new file mode 100644
index 00000000..5bd9a41a
--- /dev/null
+++ b/sysdep/unix/wg_user.h
@@ -0,0 +1,16 @@
+#ifndef _BIRD_WG_USER_H
+#define _BIRD_WG_USER_H
+
+#include "sysdep/config.h"
+
+#ifndef SOCKET_PATH
+#define SOCKET_PATH PATH_RUNSTATEDIR "/wireguard/"
+#endif /* SOCKET_PATH */
+
+struct pool;
+struct wg_device;
+
+bool wg_has_userspace(const char *ifname);
+int wg_user_set_device(struct pool *pool, const char *ifname, struct wg_device *dev);
+
+#endif /* _BIRD_WG_USER_H */