diff options
author | Mikael Magnusson <mikma@users.sourceforge.net> | 2019-02-28 00:44:40 +0100 |
---|---|---|
committer | Mikael Magnusson <mikma@users.sourceforge.net> | 2023-08-28 23:55:08 +0200 |
commit | af457af45d66982fc1a074a1a0d8de2e291278eb (patch) | |
tree | 67623f5df191d92d8ff07e816f79633abdfb7b86 | |
parent | ad4702faad3b2bfc3320d5829011e61b7cffc066 (diff) |
Wireguard: Initial commit
Use 51820 (default wireguard port) as default tunnel type.
-rw-r--r-- | Makefile.in | 1 | ||||
-rw-r--r-- | configure.ac | 3 | ||||
-rw-r--r-- | nest/protocol.h | 5 | ||||
-rw-r--r-- | proto/wireguard/Makefile | 8 | ||||
-rw-r--r-- | proto/wireguard/config.Y | 191 | ||||
-rw-r--r-- | proto/wireguard/wireguard.c | 973 | ||||
-rw-r--r-- | proto/wireguard/wireguard.h | 66 |
7 files changed, 1245 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 c9c52038..de3cb50a 100644 --- a/configure.ac +++ b/configure.ac @@ -312,7 +312,7 @@ if test "$enable_mpls_kernel" != no ; then fi fi -all_protocols="$proto_bfd babel bgp mrt ospf perf pipe radv rip rpki static" +all_protocols="$proto_bfd babel bgp mrt ospf perf pipe radv rip rpki static wireguard" all_protocols=`echo $all_protocols | sed 's/ /,/g'` @@ -331,6 +331,7 @@ AH_TEMPLATE([CONFIG_RADV], [RAdv protocol]) AH_TEMPLATE([CONFIG_RIP], [RIP protocol]) AH_TEMPLATE([CONFIG_RPKI], [RPKI protocol]) AH_TEMPLATE([CONFIG_STATIC], [Static protocol]) +AH_TEMPLATE([CONFIG_WIREGUARD], [Wireguard protocol]) AC_MSG_CHECKING([protocols]) protocols=`echo "$with_protocols" | sed 's/,/ /g'` diff --git a/nest/protocol.h b/nest/protocol.h index dad0b781..4b06d54c 100644 --- a/nest/protocol.h +++ b/nest/protocol.h @@ -54,6 +54,7 @@ enum protocol_class { PROTOCOL_RIP, PROTOCOL_RPKI, PROTOCOL_STATIC, + PROTOCOL_WG, PROTOCOL__MAX }; @@ -104,7 +105,8 @@ void protos_dump_all(void); extern struct protocol proto_device, proto_radv, proto_rip, proto_static, proto_mrt, proto_ospf, proto_perf, - proto_pipe, proto_bgp, proto_bmp, proto_bfd, proto_babel, proto_rpki; + proto_pipe, proto_bgp, proto_bmp, proto_bfd, proto_babel, proto_rpki, + proto_wireguard; /* * Routing Protocol Instance @@ -478,6 +480,7 @@ struct channel_class { extern const struct channel_class channel_basic; extern const struct channel_class channel_bgp; +extern const struct channel_class channel_wg; struct channel_config { node n; diff --git a/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..a02130d0 --- /dev/null +++ b/proto/wireguard/wireguard.c @@ -0,0 +1,973 @@ +// 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; + + 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 */ |