summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile.in1
-rw-r--r--configure.ac3
-rw-r--r--nest/protocol.h5
-rw-r--r--proto/wireguard/Makefile8
-rw-r--r--proto/wireguard/config.Y191
-rw-r--r--proto/wireguard/wireguard.c974
-rw-r--r--proto/wireguard/wireguard.h66
7 files changed, 1246 insertions, 2 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/configure.ac b/configure.ac
index 0ed4d2d7..65897015 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 l3vpn mrt ospf perf pipe radv rip rpki static"
+all_protocols="aggregator $proto_bfd babel bgp l3vpn mrt ospf perf pipe radv rip rpki static wireguard"
all_protocols=`echo $all_protocols | sed 's/ /,/g'`
@@ -333,6 +333,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/nest/protocol.h b/nest/protocol.h
index c87d3814..ee55d316 100644
--- a/nest/protocol.h
+++ b/nest/protocol.h
@@ -57,6 +57,7 @@ enum protocol_class {
PROTOCOL_RIP,
PROTOCOL_RPKI,
PROTOCOL_STATIC,
+ PROTOCOL_WG,
PROTOCOL__MAX
};
@@ -107,7 +108,8 @@ void protos_dump_all(void);
extern struct protocol
proto_device, proto_radv, proto_rip, proto_static, proto_mrt,
proto_ospf, proto_perf, proto_l3vpn, 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
@@ -483,6 +485,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/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..0fae832a
--- /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 adata *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..1a50fb7c
--- /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);
+}
+
+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");
+
+ register_format_tunnel_encap(cf->tunnel_type, "WireGuard", wg_format_tunnel_encap);
+
+ 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;
+
+ 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 */