diff options
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 a7cf0a5d..cbdf2ca3 100644 --- a/configure.ac +++ b/configure.ac @@ -312,7 +312,7 @@ if test "$enable_mpls_kernel" != no ; then fi fi -all_protocols="aggregator $proto_bfd babel bgp mrt ospf perf pipe radv rip rpki static" +all_protocols="aggregator $proto_bfd babel bgp mrt ospf perf pipe radv rip rpki static wireguard" all_protocols=`echo $all_protocols | sed 's/ /,/g'` @@ -332,6 +332,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 a15683f5..ed1970b0 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; @@ -357,7 +489,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, @@ -370,7 +502,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 @@ -387,7 +519,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 @@ -467,6 +599,8 @@ type: | ECLIST { $$ = T_ECLIST; } | LCLIST { $$ = T_LCLIST; } | ROUTE { $$ = T_ROUTE; } + | SUBTLV { $$ = T_SUBTLV; } + | TLVLIST { $$ = T_TLVLIST; } | type SET { switch ($1) { case T_INT: @@ -476,6 +610,7 @@ type: case T_LC: case T_RD: case T_IP: + case T_SUBTLV: $$ = T_SET; break; @@ -700,10 +835,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); } ; @@ -712,6 +853,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); } ; @@ -927,10 +1069,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 e268a8ec..d2347684 100644 --- a/filter/data.c +++ b/filter/data.c @@ -8,6 +8,7 @@ * */ +//#define LOCAL_DEBUG #include "nest/bird.h" #include "lib/lists.h" #include "lib/resource.h" @@ -16,6 +17,7 @@ #include "lib/unaligned.h" #include "lib/net.h" #include "lib/ip.h" +#include "lib/tunnel_encaps.h" #include "nest/route.h" #include "nest/protocol.h" #include "nest/iface.h" @@ -55,6 +57,8 @@ static const char * const f_type_str[] = { [T_ECLIST] = "eclist", [T_LC] = "lc", [T_LCLIST] = "lclist", + [T_SUBTLV] = "subtlv", + [T_TLVLIST] = "tlvlist", [T_RD] = "rd", [T_ROUTE] = "route", @@ -89,7 +93,7 @@ f_type_element_type(enum f_type t) const struct f_trie f_const_empty_trie = { .ipv4 = -1, }; const struct f_val f_const_empty_prefix_set = { .type = T_PREFIX_SET, - .val.ti = &f_const_empty_trie, + .val.ti = &f_const_empty_trie }; static struct adata * @@ -309,6 +313,8 @@ val_same(const struct f_val *v1, const struct f_val *v2) return v1->val.rte == v2->val.rte; case T_ROUTES_BLOCK: return v1->val.ad == v2->val.ad; + case T_TLVLIST: + return tlvlist_same(v1->val.tl, v2->val.tl); default: bug("Invalid type in val_same(): %x", v1->type); } @@ -415,6 +421,18 @@ lclist_match_set(const struct adata *list, const struct f_tree *set) return 0; } +static int +tlvlist_match_set(const struct adata *list, const struct f_tree *set) +{ + if (!list) + return 0; + + if (!subtlv_set_type(set)) + return F_CMP_ERROR; + + return 1; +} + const struct adata * clist_filter(struct linpool *pool, const struct adata *list, const struct f_val *set, int pos) { @@ -516,6 +534,20 @@ lclist_filter(struct linpool *pool, const struct adata *list, const struct f_val return res; } +#if 0 +const struct te_tlvlist * +tlvlist_filter(struct linpool *pool, const struct te_tlvlist *list, const struct f_val *set, int pos) +{ + if (!list) + return NULL; + + /* FIXME */ + + return NULL; +} +#endif + + /** * val_in_range - implement |~| operator * @v1: element @@ -576,6 +608,9 @@ val_in_range(const struct f_val *v1, const struct f_val *v2) if (v1->type == T_LCLIST) return lclist_match_set(v1->val.ad, v2->val.t); + if (v1->type == T_TLVLIST) + return tlvlist_match_set(v1->val.ad, v2->val.t); + if (v1->type == T_PATH) return as_path_match_set(v1->val.ad, v2->val.t); @@ -640,6 +675,7 @@ val_format(const struct f_val *v, buffer *buf) case T_CLIST: int_set_format(v->val.ad, 1, -1, buf2, 1000); buffer_print(buf, "(clist %s)", buf2); return; case T_ECLIST: ec_set_format(v->val.ad, -1, buf2, 1000); buffer_print(buf, "(eclist %s)", buf2); return; case T_LCLIST: lc_set_format(v->val.ad, -1, buf2, 1000); buffer_print(buf, "(lclist %s)", buf2); return; + case T_TLVLIST: tlv_set_format(v->val.tl, -1, buf2, 1000); buffer_print(buf, "(tlvlist %s)", buf2); return; case T_PATH_MASK: pm_format(v->val.path_mask, buf); return; case T_ROUTE: rte_format(v->val.rte, buf); return; case T_ROUTES_BLOCK: rte_block_format(v->val.rte, buf); return; diff --git a/filter/data.h b/filter/data.h index 0a521ec5..811678f2 100644 --- a/filter/data.h +++ b/filter/data.h @@ -20,6 +20,7 @@ enum f_type { /* Nothing. Simply nothing. */ T_VOID = 0, + T_EMPTY_LIST = 1, T_NONE = 1, /* Special hack to represent missing arguments */ @@ -62,6 +63,8 @@ enum f_type { T_RD = 0x2a, /* Route distinguisher for VPN addresses */ T_PATH_MASK_ITEM = 0x2b, /* Path mask item for path mask constructors */ T_BYTESTRING = 0x2c, + T_SUBTLV = 0x2d, /* Tunnel encapsulation sub-TLV */ + T_TLVLIST = 0x2e, /* Tunnel encapsulation TLV list */ T_ROUTE = 0x78, T_ROUTES_BLOCK = 0x79, @@ -94,6 +97,9 @@ struct f_val { const struct f_path_mask *path_mask; struct f_path_mask_item pmi; struct rte *rte; + struct te_subtlv st; + const struct te_tlv *tlv; + const struct te_tlvlist *tl; } val; }; @@ -313,6 +319,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); @@ -321,6 +329,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 */ @@ -342,8 +351,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 4356a735..739cbf5d 100644 --- a/filter/f-inst.c +++ b/filter/f-inst.c @@ -858,6 +858,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"); } @@ -918,6 +926,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"); } @@ -1300,6 +1318,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); @@ -1415,6 +1452,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); @@ -1480,6 +1532,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 560778a8..7dad5dbb 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 aee3468b..99174b2b 100644 --- a/nest/attrs.h +++ b/nest/attrs.h @@ -30,6 +30,12 @@ struct f_val; struct f_tree; +struct te_subtlv; +struct te_tlv; +struct te_tlvlist; +struct te_encap; +struct te_endpoint; +struct te_visitor; int as_path_valid(byte *data, uint len, int bs, int sets, int confed, char *err, uint elen); int as_path_16to32(byte *dst, const byte *src, uint len); @@ -243,4 +249,65 @@ int rte_set_walk(const struct adata *list, u32 *pos, struct rte **val); void ec_set_sort_x(struct adata *set); /* Sort in place */ + +/* a-tlv.c */ + + +/* Tunnel Encapsulation TLV types */ +#define TLV_TUNNEL_TYPE 0x00 /* Reserved. Used internally only. */ +#define TLV_ENCAPSULATION 0x01 +#define TLV_COLOR 0x04 +#define TLV_TUNNEL_ENDPOINT 0x06 +#define TLV_UDP_DEST_PORT 0x08 + +struct te_encap { + int type; + uint length; + const void *data; +}; + +struct te_unknown { + uint length; + const void *data; +}; + +struct te_endpoint { + u32 reserved; + u16 af; + ip_addr ip; +}; + +/* Tunnel Encapsulation TLV */ +struct te_subtlv { + int type; + union { + struct te_encap tunnel_encap; + struct te_endpoint tunnel_endpoint; + u32 color; + u16 udp_dest_port; + struct te_unknown unknown; + } u; +}; + +struct te_tlv { + node n; + uint len; + int type; + struct te_subtlv st[0]; +}; + +struct te_tlvlist { + list tlv; +}; + +const struct te_tlv *tlv_alloc(struct linpool *pool, const struct f_tree *set); +int tlv_set_format(const struct te_tlvlist *set, int from, byte *buf, uint size); +int tlv_set_contains(const struct te_tlvlist *list, const struct te_tlv *val); +const struct te_tlvlist *tlv_set_add(struct linpool *pool, const struct te_tlvlist *list, const struct te_tlv *val); +const struct te_tlvlist *tlv_set_union(struct linpool *pool, const struct te_tlvlist *l1, const struct te_tlvlist *l2); +int tlvlist_same(const struct te_tlvlist *tl1, const struct te_tlvlist *tl2); + +struct adata *tlvlist_encode_tunnel_encap(struct linpool *pool, const struct te_tlvlist *tl); +struct te_tlvlist *tlvlist_decode_tunnel_encap(struct linpool *pool, const struct adata *ad); + #endif diff --git a/nest/iface.h b/nest/iface.h index f8e92850..daf84a7c 100644 --- a/nest/iface.h +++ b/nest/iface.h @@ -11,6 +11,7 @@ #include "lib/lists.h" #include "lib/ip.h" +#include "lib/net.h" extern list iface_list; diff --git a/nest/protocol.h b/nest/protocol.h index 596e810e..7d41dd13 100644 --- a/nest/protocol.h +++ b/nest/protocol.h @@ -55,6 +55,7 @@ enum protocol_class { PROTOCOL_RIP, PROTOCOL_RPKI, PROTOCOL_STATIC, + PROTOCOL_WG, PROTOCOL__MAX }; @@ -105,7 +106,8 @@ void protos_dump_all(void); extern struct protocol proto_device, proto_radv, proto_rip, proto_static, proto_mrt, proto_ospf, proto_perf, proto_aggregator, - proto_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 @@ -479,6 +481,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 4fe43e6f..828ac804 100644 --- a/nest/route.h +++ b/nest/route.h @@ -546,6 +546,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 1ba3de5e..d8baf416 100644 --- a/proto/bgp/bgp.h +++ b/proto/bgp/bgp.h @@ -703,6 +703,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 f589fd84..35c19961 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 @@ -363,6 +363,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 */ |