diff options
author | Mikael Magnusson <mikma@users.sourceforge.net> | 2019-02-28 00:44:40 +0100 |
---|---|---|
committer | Mikael Magnusson <mikma@users.sourceforge.net> | 2021-11-06 00:07:40 +0100 |
commit | 83446cbab725301e58dc41336c31c446996b4f28 (patch) | |
tree | 0442ddbba403669bd6d4eb1fcf9fafe80f66e825 /proto/wireguard | |
parent | 7eb6a44491501e3f403fde2e26affb673f5b5b11 (diff) |
Wireguard: Initial commit
Wireguard: Debug
Wireguard: Implement tunnel encode decode
Wireguard: Add remote endpoint
Wireguard: Refactor into peer and allowed ips functions
Wireguard: Clean up config.Y
Wireguard: Extended color community
Wireguard: Allow multiple channels
Wireguard: Add peer config settings
Wireguard: Set up wireguard device
Add private key and listen port items.
Wireguard: Add peer list
Wireguard: Move key conversion
Wireguard: Use recursive tunnel encaps
Wireguard: Add user space support
Wireguard: Reinit wg device at shutdown
Wireguard: Add channel hooks
Wireguard: Implement copy_config
Wireguard: Fixes
Wireguard: Add tunnel_type config parameter
Use 51820 (default wireguard port) as default tunnel type.
Wireguard: Rename remote endpoint to tunnel endpoint
Adopt to draft-ietf-idr-tunnel-encaps-13.txt
by renaming emote endpoint to tunnel endpoint.
Wireguard: Fix discarded const qualifiers
Wireguard: Remove bgp include
Wireguard: Generalize tunnel encapsulation
Wireguard: Add struct tunnel_encap
Wireguard: Remove wg peer on withdraw
Wireguard: Refactor remove_allowed_ip
Wireguard: Dump peers
Wireguard: Fix duplicate allowedip entries
Wireguard: Dump peers
Wireguard: Don't add endpoint if not set
Wireguard: Replace debug with DBG
Wireguard: Replace log with WG_TRACE
Wireguard: Refactor add_allowed_ip
Wireguard: Don't replace peers
Wireguard: Don't fix listen_port update
Wireguard: Move wireguard formatting from tunnel_encaps library
Wireguard: Change from eattr to adata in decode and format
wireguard: support multiple TLVs
Wireguard: use visitor in wireguard
Wireguard: WIP add wireguard sub-TLV to parser
Wireguard: update debug msg
wireguard: register name
Wireguard: WIP
Wireguard: implement allowed_ips instead of allowed_ip
Wireguard: fix free peer, and peer config syntax
Wireguard: clean up
Wireguard: change key options to bytestring
Wireguard: Add EA_GET
Diffstat (limited to 'proto/wireguard')
-rw-r--r-- | proto/wireguard/Makefile | 8 | ||||
-rw-r--r-- | proto/wireguard/config.Y | 181 | ||||
-rw-r--r-- | proto/wireguard/wireguard.c | 872 | ||||
-rw-r--r-- | proto/wireguard/wireguard.h | 66 |
4 files changed, 1127 insertions, 0 deletions
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..f2a567e5 --- /dev/null +++ b/proto/wireguard/config.Y @@ -0,0 +1,181 @@ +/* + * 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)) + { + v.u.tunnel_encap.data = bs->data; + v.u.tunnel_encap.length = bs->length; + } + 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..f6579482 --- /dev/null +++ b/proto/wireguard/wireguard.c @@ -0,0 +1,872 @@ +// 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(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; +}; + +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)) + 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)) { + const wg_key *pubkey = encap.encap.data; + bool add_ip = true; + struct wg_entry *en = fib_find(&ch->rtable, n->n.addr); + + if (en) { + if (memcpy(en->public_key, 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, 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, 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, *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(const struct te_encap *encap, byte *buf, uint size) +{ + byte *pos = buf; + + if (encap->length != sizeof(wg_key)) { + DBG("wg_format_tunnel_encap: bad length %d\n", encap->length); + return -1; + } + + 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 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"); +} + + +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*/ +}; diff --git a/proto/wireguard/wireguard.h b/proto/wireguard/wireguard.h new file mode 100644 index 00000000..b6ea76b9 --- /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 struct channel_class channel_wg; + +struct peer_config *peer_new(struct wg_config *c); + +#endif /* _BIRD_WIREGUARD_H */ |