diff options
Diffstat (limited to 'proto')
-rw-r--r-- | proto/bgp/attrs.c | 43 | ||||
-rw-r--r-- | proto/bgp/bgp.h | 1 | ||||
-rw-r--r-- | proto/bgp/config.Y | 4 | ||||
-rw-r--r-- | proto/wireguard/Makefile | 8 | ||||
-rw-r--r-- | proto/wireguard/config.Y | 191 | ||||
-rw-r--r-- | proto/wireguard/wireguard.c | 974 | ||||
-rw-r--r-- | proto/wireguard/wireguard.h | 66 |
7 files changed, 1286 insertions, 1 deletions
diff --git a/proto/bgp/attrs.c b/proto/bgp/attrs.c index 9387ddba..dc764331 100644 --- a/proto/bgp/attrs.c +++ b/proto/bgp/attrs.c @@ -22,6 +22,7 @@ #include "lib/resource.h" #include "lib/string.h" #include "lib/unaligned.h" +#include "lib/tunnel_encaps.h" #include "bgp.h" @@ -917,6 +918,39 @@ bgp_decode_otc(struct bgp_parse_state *s, uint code UNUSED, uint flags, byte *da static void +bgp_export_tunnel_encap(struct bgp_export_state *s UNUSED, eattr *a) +{ + if (a->u.ptr->length == 0) + UNSET(a); + + /* + * TODO: In situations where a tunnel could be encoded using a barebones TLV, + * it MUST be encoded using the corresponding Encapsulation Extended + * Community. (RFC9012 section 4.1) + */ +} + +static void +bgp_decode_tunnel_encap(struct bgp_parse_state *s, uint code UNUSED, uint flags, byte *data, uint len, ea_list **to) +{ + bgp_set_attr_data(to, s->pool, BA_TUNNEL_ENCAP, flags, data, len); +} + +static int +bgp_encode_tunnel_encap(struct bgp_write_state *s UNUSED, eattr *a, byte *buf, uint size) +{ + return bgp_put_attr(buf, size, EA_ID(a->id), a->flags, a->u.ptr->data, a->u.ptr->length); +} + +static void +bgp_format_tunnel_encap(const eattr *a, byte *buf, uint size) +{ + int l = format_tunnel_encap(a->u.ptr, buf, size); + if (l > 0) + ADVANCE(buf, size, l); +} + +static void bgp_export_mpls_label_stack(struct bgp_export_state *s, eattr *a) { net_addr *n = s->route->net->n.addr; @@ -1136,6 +1170,15 @@ static const struct bgp_attr_desc bgp_attr_table[] = { .encode = bgp_encode_u32, .decode = bgp_decode_otc, }, + [BA_TUNNEL_ENCAP] = { + .name = "tunnel_encap", + .type = EAF_TYPE_TUNNEL_ENCAP, + .flags = BAF_OPTIONAL | BAF_TRANSITIVE, + .export = bgp_export_tunnel_encap, + .encode = bgp_encode_tunnel_encap, + .decode = bgp_decode_tunnel_encap, + .format = bgp_format_tunnel_encap, + }, [BA_MPLS_LABEL_STACK] = { .name = "mpls_label_stack", .type = EAF_TYPE_INT_SET, diff --git a/proto/bgp/bgp.h b/proto/bgp/bgp.h index 1ba3de5e..d8baf416 100644 --- a/proto/bgp/bgp.h +++ b/proto/bgp/bgp.h @@ -703,6 +703,7 @@ byte *bgp_create_end_mark_(struct bgp_channel *c, byte *buf); #define BA_EXT_COMMUNITY 0x10 /* RFC 4360 */ #define BA_AS4_PATH 0x11 /* RFC 6793 */ #define BA_AS4_AGGREGATOR 0x12 /* RFC 6793 */ +#define BA_TUNNEL_ENCAP 0x17 /* RFC 9012 */ #define BA_AIGP 0x1a /* RFC 7311 */ #define BA_LARGE_COMMUNITY 0x20 /* RFC 8092 */ #define BA_ONLY_TO_CUSTOMER 0x23 /* RFC 9234 */ diff --git a/proto/bgp/config.Y b/proto/bgp/config.Y index e7a2f5cb..3c999453 100644 --- a/proto/bgp/config.Y +++ b/proto/bgp/config.Y @@ -32,7 +32,7 @@ CF_KEYWORDS(BGP, LOCAL, NEIGHBOR, AS, HOLD, TIME, CONNECT, RETRY, KEEPALIVE, LIVED, STALE, IMPORT, IBGP, EBGP, MANDATORY, INTERNAL, EXTERNAL, SETS, DYNAMIC, RANGE, NAME, DIGITS, BGP_AIGP, AIGP, ORIGINATE, COST, ENFORCE, FIRST, FREE, VALIDATE, BASE, ROLE, ROLES, PEER, PROVIDER, CUSTOMER, - RS_SERVER, RS_CLIENT, REQUIRE, BGP_OTC, GLOBAL) + RS_SERVER, RS_CLIENT, REQUIRE, BGP_OTC, GLOBAL, BGP_TUNNEL_ENCAP) %type <i> bgp_nh %type <i32> bgp_afi @@ -364,6 +364,8 @@ dynamic_attr: BGP_LARGE_COMMUNITY { $$ = f_new_dynamic_attr(EAF_TYPE_LC_SET, T_LCLIST, EA_CODE(PROTOCOL_BGP, BA_LARGE_COMMUNITY)); } ; dynamic_attr: BGP_OTC { $$ = f_new_dynamic_attr(EAF_TYPE_INT, T_INT, EA_CODE(PROTOCOL_BGP, BA_ONLY_TO_CUSTOMER)); } ; +dynamic_attr: BGP_TUNNEL_ENCAP + { $$ = f_new_dynamic_attr(EAF_TYPE_TUNNEL_ENCAP, T_TLVLIST, EA_CODE(PROTOCOL_BGP, BA_TUNNEL_ENCAP)); } ; custom_attr: ATTRIBUTE BGP NUM type symbol ';' { if($3 > 255 || $3 < 1) diff --git a/proto/wireguard/Makefile b/proto/wireguard/Makefile new file mode 100644 index 00000000..26a7970f --- /dev/null +++ b/proto/wireguard/Makefile @@ -0,0 +1,8 @@ +WG := $(srcdir)/../WireGuard/contrib/examples/embeddable-wg-library + +src := wireguard.c +obj := $(src-o-files) +$(all-daemon) +$(cf-local) + +tests_objs := $(tests_objs) $(src-o-files) diff --git a/proto/wireguard/config.Y b/proto/wireguard/config.Y new file mode 100644 index 00000000..6a435da3 --- /dev/null +++ b/proto/wireguard/config.Y @@ -0,0 +1,191 @@ +/* + * BIRD -- Wireguard Protocol Configuration + * + * (c) 1999 Martin Mares <mj@ucw.cz> + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +CF_HDR + +#define LOCAL_DEBUG +#include "proto/wireguard/wireguard.h" + +CF_DEFINES + +#define WG_DEFAULT_TUNNEL_TYPE 51820 + +#define WG_CFG ((struct wg_config *) this_proto) + +static struct peer_config *this_peer = NULL; + +typedef char wg_key_b64_string[45]; +int wg_key_from_base64(u8 key[32], const wg_key_b64_string base64); + +static struct f_tree * +f_new_sub_tlv_wg(u32 type, const struct bytestring *bs) +{ + struct f_tree *t = f_new_tree(); + t->right = t; + t->from.type = t->to.type = T_SUBTLV; + struct te_subtlv v; + v.type = TLV_ENCAPSULATION; + v.u.tunnel_encap.type = type; + v.u.tunnel_encap.data = NULL; + v.u.tunnel_encap.length = 0; + if (bs && bs->length == sizeof(wg_key)) + { +#if 1 + // encap type 0 + v.u.tunnel_encap.data = bs->data; + v.u.tunnel_encap.length = bs->length; +#else + // encap type 1 + v.u.tunnel_encap.length = 4 + bs->length; + void *data = cfg_alloc(v.u.tunnel_encap.length); + memset(data, 0, 4); + memcpy(data + 4, bs->data, bs->length); + v.u.tunnel_encap.data = data; +#endif + } + else if (bs) + { + cf_error( "Invalid WireGuard key" ); + } + t->from.val.st = v; + t->to.val.st = v; + return t; +} + +CF_DECLS + +%type <g> allowed_ip allowed_ips +%type <bs> wg_key + +CF_KEYWORDS(WIREGUARD, TUNNEL_TYPE, PRIVATE_KEY, LISTEN_PORT, PUBLIC_KEY, ENDPOINT, ALLOWED_IPS) + +CF_GRAMMAR + +proto: wireguard_proto '}' ; + +sub_tlv_item: + '(' WIREGUARD ',' wg_key ')' { $$ = f_new_sub_tlv_wg(WG_DEFAULT_TUNNEL_TYPE, $4); } + | '(' WIREGUARD ',' cnum ',' wg_key ')' { $$ = f_new_sub_tlv_wg($4, $6); } + ; + +wireguard_proto_start: proto_start WIREGUARD { + this_proto = proto_config_new(&proto_wireguard, $1); + init_list(&WG_CFG->peers); + WG_CFG->tunnel_type = WG_DEFAULT_TUNNEL_TYPE; + } + ; + +wireguard_proto: + wireguard_proto_start proto_name '{' + | wireguard_proto wg_proto_channel ';' + | wireguard_proto proto_item ';' + | wireguard_proto TUNNEL_TYPE tunnel_type ';' + | wireguard_proto INTERFACE TEXT ';' { WG_CFG->ifname = $3; } + | wireguard_proto PRIVATE_KEY private_key ';' + | wireguard_proto LISTEN_PORT listen_port ';' + | wireguard_proto wg_peer ';' + ; + +wg_peer: wg_peer_start wg_peer_opt_list wg_peer_end; + +wg_peer_start: PEER { this_peer = peer_new(WG_CFG); } + +wg_peer_end: { + this_peer = NULL; +} + ; + +wg_peer_item: + PUBLIC_KEY public_key + | ENDPOINT endpoint + | PORT port + | ALLOWED_IPS allowed_ips { this_peer->allowedips = $2; } + ; + +wg_peer_opts: + /* empty */ + | wg_peer_opts wg_peer_item ';' + ; + +wg_peer_opt_list: + '{' wg_peer_opts '}' + ; + +tunnel_type: expr { WG_CFG->tunnel_type = $1; } + +private_key: wg_key { WG_CFG->private_key = $1->data; } + +listen_port: expr { WG_CFG->listen_port = $1; } + +public_key: wg_key { this_peer->public_key = $1->data; } + +endpoint: ipa { this_peer->endpoint = $1; } + +port: expr { this_peer->remote_port = $1; } + +allowed_ips: allowed_ip { + struct wg_allowedips *aips = cfg_alloc(sizeof(struct wg_allowedips)); + aips->first_allowedip = $1; + aips->last_allowedip = $1; + $$ = aips; + } + | allowed_ips ',' allowed_ip { + struct wg_allowedips *aips = $1; + struct wg_allowedip *aip = $3; + aips->last_allowedip->next_allowedip = aip; + aips->last_allowedip = aip; + $$ = aips; + } + ; + +allowed_ip: + net_or_ipa { + struct wg_allowedip *aip = cfg_alloc(sizeof(struct wg_allowedip)); + switch ($1.type) + { + case NET_IP4: + aip->family = AF_INET; + aip->ip4 = ipa_to_in4(net_prefix(&($1))); + aip->cidr = net_pxlen(&($1)); + break; + case NET_IP6: + aip->family = AF_INET6; + aip->ip6 = ipa_to_in6(net_prefix(&($1))); + aip->cidr = net_pxlen(&($1)); + break; + default: + cf_error( "Unknown net type: %d", $1.type ); + } + + aip->next_allowedip = NULL; + $$ = aip; + } + ; + +wg_proto_channel: wg_channel_start channel_opt_list wg_channel_end; + +wg_channel_start: net_type +{ + this_channel = channel_config_get(&channel_wg, net_label[$1], $1, this_proto); +} + +wg_channel_end: +{ + this_channel = NULL; +} + +wg_key: bytestring +{ + if ($1->length != sizeof(wg_key)) + cf_error( "Invalid WireGuard key" ); + $$ = $1; +} + +CF_CODE + +CF_END diff --git a/proto/wireguard/wireguard.c b/proto/wireguard/wireguard.c new file mode 100644 index 00000000..8b1e7ecc --- /dev/null +++ b/proto/wireguard/wireguard.c @@ -0,0 +1,974 @@ +// Based on proto/rip/rip.c + +#define LOCAL_DEBUG + +#include <stdio.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/un.h> +#include <unistd.h> +#include "lib/lists.h" +#include "lib/ip.h" +#include "lib/tunnel_encaps.h" +#include "nest/protocol.h" +#include "nest/iface.h" +#include "sysdep/linux/wireguard.h" +#include "sysdep/unix/unix.h" +#include "sysdep/unix/wg_user.h" +#include "wireguard.h" + +static ip_addr allowedip_to_ipa(struct wg_allowedip *allowedip); +static int wg_format_tunnel_encap(const struct te_encap *encap, byte *buf, uint size); + +static +int get_device(struct wg_proto *p, wg_device **pdev, const char *device_name) +{ + struct wg_config *c = (struct wg_config *) p->p.cf; + + /* if (has_user_space(p)) */ + /* return user_get_device(p, dev, device_name); */ + /* else */ + /* return wg_get_device(dev, device_name); */ + + if (p->dev) + { + wg_free_device(p->dev); + p->dev = NULL; + } + + wg_device *dev = calloc(1, sizeof(wg_device)); + strncpy(dev->name, device_name, sizeof(dev->name)); +// dev->flags = WGDEVICE_REPLACE_PEERS; + if (c->private_key) + { + dev->flags |= WGDEVICE_HAS_PRIVATE_KEY | WGDEVICE_HAS_PUBLIC_KEY; + memcpy(dev->private_key, c->private_key, sizeof(wg_key)); + wg_generate_public_key(dev->public_key, c->private_key); + } + if (c->listen_port) + { + dev->flags |= WGDEVICE_HAS_LISTEN_PORT; + dev->listen_port = c->listen_port; + } + DBG("listen port %d\n", c->listen_port); + + struct peer_config *pc = NULL; + WALK_LIST(pc,c->peers) + { + wg_peer *peer = calloc(1, sizeof(wg_peer)); + if (!dev->first_peer) + dev->first_peer = peer; + if (dev->last_peer) + dev->last_peer->next_peer = peer; + dev->last_peer = peer; + + peer->flags = WGPEER_REPLACE_ALLOWEDIPS; + + if (pc->public_key) + { + peer->flags = WGPEER_HAS_PUBLIC_KEY; + memcpy(peer->public_key, pc->public_key, sizeof(wg_key)); + } + peer->next_peer = NULL; + + if (!ip6_equal(pc->endpoint, IPA_NONE)) + sockaddr_fill((sockaddr*)&peer->endpoint.addr, + ipa_is_ip4(pc->endpoint) ? AF_INET : AF_INET6, + pc->endpoint, NULL, pc->remote_port); + + peer->first_allowedip = NULL; + peer->last_allowedip = NULL; + + struct wg_allowedip *aip; + wg_for_each_allowedip(pc->allowedips, aip) + { + struct wg_allowedip *new_aip = malloc(sizeof(struct wg_allowedip)); + memcpy(new_aip, aip, sizeof(struct wg_allowedip)); + new_aip->next_allowedip = NULL; + + if (!peer->first_allowedip) + peer->first_allowedip = new_aip; + + if (peer->last_allowedip) + peer->last_allowedip->next_allowedip = new_aip; + + peer->last_allowedip = new_aip; + } + } + + *pdev = dev; + return 0; +} + +static int +set_device(struct wg_proto *p) +{ + struct wg_config *c = (struct wg_config *) p->p.cf; + + if (wg_has_userspace(c->ifname)) + return wg_user_set_device(p->p.pool, c->ifname, p->dev); + else + { + WG_TRACE(D_EVENTS, "WG: wg_set_device"); + return wg_set_device(p->dev); + } +} + +static void +wg_init_entry(struct fib *f UNUSED, void *e_) +{ + struct wg_entry *e UNUSED = e_; +// DBG("wg_init_entry\n"); + + memset(e, 0, sizeof(struct wg_entry) - sizeof(struct fib_node)); +} + +static void +wg_postconfig(struct proto_config *C UNUSED) +{ + struct wg_config *c = (struct wg_config *) C; + + DBG("postconfig %d\n", c->tunnel_type); + register_format_tunnel_encap(c->tunnel_type, "WireGuard", wg_format_tunnel_encap); +} + +static void +wg_if_notify(struct proto *P, unsigned flags, struct iface *i) +{ + struct wg_proto *p = (struct wg_proto *) P; + struct wg_config *c = (struct wg_config *) P->cf; + + DBG("WG: if_notify %p %s %d %s\n", i, i->name, flags, c->ifname); + if (c->ifname && !strcmp(i->name, c->ifname)) + { + DBG("WG: found ifname\n"); + p->iface = i; + } + + if (flags & IF_CHANGE_UP) + { + DBG("WG: IF_CHANGE_UP %s\n", i->name); + + int res = set_device(p); + WG_TRACE(D_EVENTS, "WG: wg_set_device %d", res); + } +} + +static void +dump(void *ptr, size_t len) +{ + unsigned char *data = ptr; + + for (size_t i=0; i<len; i++) + { + fprintf(stderr, "%02x ", data[i]); + } + fprintf(stderr, "\n"); +} + +static void +dump_peer(struct wg_peer *peer) +{ + wg_key_b64_string base64; + wg_key_to_base64(base64, peer->public_key); + DBG("WG: peer %s\n", base64); + + struct wg_allowedip *allowedip = NULL; + wg_for_each_allowedip(peer, allowedip) + { + ip_addr ip = allowedip_to_ipa(allowedip); + + DBG("allowedip %I/%d\n", ip, allowedip->cidr); + } +} + +static wg_peer * +add_peer(wg_device *dev, const wg_key *pubkey) +{ + struct wg_peer *peer = malloc(sizeof(struct wg_peer)); + memset(peer, 0, sizeof(struct wg_peer)); + + peer->flags = WGPEER_HAS_PUBLIC_KEY; + memcpy(peer->public_key, pubkey, sizeof(wg_key)); + + if (dev->first_peer && dev->last_peer) + dev->last_peer->next_peer = peer; + else + dev->first_peer = peer; + dev->last_peer = peer; + return peer; +} + +static void +remove_marked_peer(struct wg_proto *p) +{ + wg_device *dev = p->dev; + struct wg_peer *peer = NULL; + struct wg_peer *prevpeer = NULL; + + WG_TRACE(D_EVENTS, "WG: remove_marked_peer"); + wg_for_each_peer(dev, peer) + { + if (peer->flags & WGPEER_REMOVE_ME) + { + if (!prevpeer) + { + DBG("WG: remove first peer\n"); + dev->first_peer = peer->next_peer; + if (dev->last_peer == peer) + { + dev->last_peer = NULL; + dev->first_peer = NULL; + } + } + else + { + DBG("WG: remove middle peer\n"); + // Remove + if (dev->last_peer == peer) + dev->last_peer = prevpeer; + + prevpeer->next_peer = peer->next_peer; + } + + free(peer); + return; + } + + prevpeer = peer; + } + log(L_WARN "WG: marked peer not found"); +} + +static int +set_peer_tunnel_ep(struct wg_proto *p, wg_peer *peer, ip_addr tunnel_ep_addr, u16 udp_dest_port) +{ + if (udp_dest_port != 0 && ipa_nonzero(tunnel_ep_addr) ) + { + if (ipa_is_ip4(tunnel_ep_addr)) + { + WG_TRACE(D_EVENTS, "WG: found ip4 ep"); + peer->endpoint.addr4.sin_family = AF_INET; + put_ip4(&peer->endpoint.addr4.sin_addr.s_addr, ipa_to_ip4(tunnel_ep_addr)); + put_u16(&peer->endpoint.addr4.sin_port, udp_dest_port); + } + else + { + WG_TRACE(D_EVENTS, "WG: found ip6 ep"); + peer->endpoint.addr6.sin6_family = AF_INET6; + put_ip6(&peer->endpoint.addr6.sin6_addr, ipa_to_ip6(tunnel_ep_addr)); + put_u16(&peer->endpoint.addr6.sin6_port, udp_dest_port); + } + } + + return 0; +} + +static ip_addr +allowedip_to_ipa(struct wg_allowedip *allowedip) +{ + switch (allowedip->family) + { + case AF_INET: + return ipa_from_in4(allowedip->ip4); + break; + case AF_INET6: + return ipa_from_in6(allowedip->ip6); + } + + return IPA_NONE; +} + +static void +init_allowed_ip(struct wg_allowedip *allowedip, u8 net_type, struct network *n) +{ + memset(allowedip, 0, sizeof(struct wg_allowedip)); + + if (net_type == NET_IP4) + { + allowedip->family = AF_INET; + allowedip->ip4.s_addr = ip4_to_u32(ip4_hton(net4_prefix(n->n.addr))); + } + else if (net_type == NET_IP6) + { + allowedip->family = AF_INET6; + ip6_addr addr = ip6_hton(net6_prefix(n->n.addr)); + memcpy(allowedip->ip6.s6_addr, &addr, 16); + } + + allowedip->cidr = net_pxlen(n->n.addr); +} + +static struct wg_allowedip * +create_allowed_ip_network(u8 net_type, struct network *n) +{ + struct wg_allowedip *allowedip = malloc(sizeof(struct wg_allowedip)); + init_allowed_ip(allowedip, net_type, n); + return allowedip; +} + +static int +add_allowed_ip(struct wg_allowedip *allowedip, wg_peer *peer) +{ + if (peer->first_allowedip && peer->last_allowedip) + peer->last_allowedip->next_allowedip = allowedip; + else + peer->first_allowedip = allowedip; + peer->last_allowedip = allowedip; + + return 0; +} + +static bool +remove_allowed_ip(wg_peer *peer, struct wg_allowedip *allowedip) +{ + struct wg_allowedip *ip = NULL; + struct wg_allowedip *previp = NULL; + + wg_for_each_allowedip(peer, ip) + { + if (allowedip->family != ip->family) + { + DBG("WG: family no match\n"); + previp = ip; + continue; + } + + if (allowedip->cidr != ip->cidr) + { + DBG("WG: cidr no match\n"); + previp = ip; + continue; + } + + if (memcmp(&allowedip->ip6, &ip->ip6, sizeof(struct in6_addr))) + { + DBG("WG: ip no match\n"); +#if defined(LOCAL_DEBUG) || defined(GLOBAL_DEBUG) + dump(&allowedip->ip6, sizeof(struct in6_addr)); + dump(&ip->ip6, sizeof(struct in6_addr)); +#endif + previp = ip; + continue; + } + + DBG("WG: found ip\n"); + + if (!previp) + { + DBG("WG: remove first\n"); + peer->first_allowedip = ip->next_allowedip; + if (peer->last_allowedip == ip) + { + peer->last_allowedip = NULL; + peer->first_allowedip = NULL; + } + } + else + { + DBG("WG: remove middle\n"); + // Remove + if (peer->last_allowedip == ip) + peer->last_allowedip = previp; + + previp->next_allowedip = ip->next_allowedip; + } + + free(ip); + return true; + } + + return false; +} + +struct wg_tunnel_encap { + int requested_type; + struct te_encap encap; + struct te_endpoint ep; + u32 color; + u16 udp_dest_port; + u16 flags; + const wg_key *pubkey; +}; + +static int wg_visitor_format_tlv(int type, struct te_context *ctx) +{ + struct wg_tunnel_encap *info = ctx->opaque; + info->flags = 0; + return info->requested_type == type; +} + +static int wg_visitor_format_encap(const struct te_encap *encap, struct te_context *ctx) +{ + struct wg_tunnel_encap *info = ctx->opaque; + + if (encap->length == sizeof(wg_key)) + info->pubkey = encap->data; + else if (encap->length == 4 + sizeof(wg_key)) + info->pubkey = encap->data + 4; + else + return -1; + + info->encap = *encap; + info->flags |= FLAG_BGP_TUNNEL_ENCAP_A_SUB_TLV_ENCAP; + return 0; +} + +static int wg_visitor_format_ep(const struct te_endpoint *ep, struct te_context *ctx) +{ + struct wg_tunnel_encap *info = ctx->opaque; + info->ep = *ep; + info->flags |= FLAG_BGP_TUNNEL_ENCAP_A_SUB_TLV_TUNNEL_EP; + return 0; +} + +static int wg_visitor_format_color(u32 color, struct te_context *ctx) +{ + struct wg_tunnel_encap *info = ctx->opaque; + info->color = color; + info->flags |= FLAG_BGP_TUNNEL_ENCAP_A_SUB_TLV_COLOR; + return 0; +} + +static int wg_visitor_format_udp_dest_port(u16 udp_dest_port, struct te_context *ctx) +{ + struct wg_tunnel_encap *info = ctx->opaque; + info->udp_dest_port = udp_dest_port; + info->flags |= FLAG_BGP_TUNNEL_ENCAP_A_SUB_TLV_UDP_DEST_PORT; + return 0; +} + +static +int wg_decode_tunnel_encap(const struct adata *a, struct wg_tunnel_encap *encap) +{ + struct te_visitor wg_visitor = { + .visit_tlv = wg_visitor_format_tlv, + //.visit_subtlv = wg_visitor_format_subtlv, + .visit_encap = wg_visitor_format_encap, + .visit_ep = wg_visitor_format_ep, + .visit_color = wg_visitor_format_color, + .visit_udp_dest_port = wg_visitor_format_udp_dest_port, + }; + + return walk_tunnel_encap(a, &wg_visitor, encap); +} + +static void +wg_rt_notify(struct proto *P, struct channel *CH, struct network *n, + struct rte *new, struct rte *old UNUSED) +{ + struct wg_proto *p = (struct wg_proto *) P; + struct wg_config *c = (struct wg_config *) P->cf; + struct wg_channel *ch = (struct wg_channel *) CH; + struct iface *iface = NULL; + const char *ifname = NULL; + +// DBG("WG: notify\n"); + + if (new) + { + struct nexthop *nh = &new->attrs->nh; + iface = nh->iface; + ifname = iface ? iface->name : NULL; + + if (iface && iface == p->iface) + { + struct eattr *t; + + DBG("WG: found %p ifname %s iface %I %p\n", new->attrs, ifname, nh->gw, nh->next); + + struct hostentry *he = new->attrs->hostentry; + + DBG("WG: he %p src %p\n", he, he?he->src:NULL); + if (he && he->src) + { + struct eattr *t = ea_find(he->src->eattrs, EA_CODE(PROTOCOL_BGP, BA_TUNNEL_ENCAP)); + DBG("WG: he %p %I %I %p %p %I\n", t, he->addr, he->link, he->next, he->src->hostentry, he->src->nh.gw); + } + + DBG("WG: notify new %d %N\n", + new->attrs->dest, n->n.addr); + + bool is_tunnel_ep = false; + struct wg_tunnel_encap encap = {.requested_type=c->tunnel_type}; + + t = ea_find(new->attrs->eattrs, EA_CODE(PROTOCOL_BGP, BA_TUNNEL_ENCAP)); + if (t) + { + WG_TRACE(D_EVENTS, "WG: Set is tunnel"); + is_tunnel_ep = true; + } + if (!t && he && he->src) + { + t = ea_find(he->src->eattrs, EA_CODE(PROTOCOL_BGP, BA_TUNNEL_ENCAP)); + } + if (t && t->u.ptr && wg_decode_tunnel_encap(t->u.ptr, &encap) == 0 && (encap.flags & FLAG_BGP_TUNNEL_ENCAP_A_SUB_TLV_ENCAP)) + { + bool add_ip = true; + struct wg_entry *en = fib_find(&ch->rtable, n->n.addr); + + if (en) + { + if (memcpy(en->public_key, encap.pubkey, sizeof(wg_key) == 0)) + { + add_ip = false; + } + } + else + { + struct wg_entry *en = fib_get(&ch->rtable, n->n.addr); + en->is_tunnel_ep = is_tunnel_ep; + memcpy(en->public_key, encap.pubkey, sizeof(wg_key)); + } + + WG_TRACE(D_EVENTS, "WG: Attr %x %x %d", t->flags, t->type, t->u.ptr->length); + + struct wg_device *dev = p->dev; + + if (dev != NULL) + { + bool dirty = false; + bool found = false; + struct wg_peer *peer = NULL; + wg_for_each_peer(dev, peer) + { + // Look for public key + size_t len = 32; // FIXME + // MIN(32, t->u.ptr->length) + if (memcmp(peer->public_key, encap.pubkey, len) != 0) + { + WG_TRACE(D_EVENTS, "WG: Not found"); + continue; + } + + WG_TRACE(D_EVENTS, "WG: Found"); + found = true; + dirty = true; + break; + } + + if (!found) + { + peer = add_peer(dev, encap.pubkey); + } + + dump_peer(peer); + if (is_tunnel_ep && (encap.flags & FLAG_BGP_TUNNEL_ENCAP_A_SUB_TLV_TUNNEL_EP) && (encap.flags & FLAG_BGP_TUNNEL_ENCAP_A_SUB_TLV_UDP_DEST_PORT)) + set_peer_tunnel_ep(p, peer, encap.ep.ip, encap.udp_dest_port); + if (add_ip) + { + struct wg_allowedip *allowed_n = + create_allowed_ip_network(ch->c.net_type, n); + add_allowed_ip(allowed_n, peer); + } + dirty = true; + + if (dirty) + { + int res = set_device(p); + WG_TRACE(D_EVENTS, "WG: wg_set_device %d", res); + } + } + } + else + { + WG_TRACE(D_EVENTS, "WG: No Attr"); + } + + // old_metric = en->valid ? en->metric : -1; + +// en->valid = RIP_ENTRY_VALID; +// en->metric = rt_metric; +// en->tag = rt_tag; +// en->from = (new->attrs->src->proto == P) ? new->u.rip.from : NULL; +// en->iface = new->attrs->iface; +// en->next_hop = new->attrs->gw; + } + } + else + { + DBG("WG: notify withdraw %N\n", + n->n.addr); + + /* Withdraw */ + struct wg_entry *en = fib_find(&ch->rtable, n->n.addr); + + if (!en) + { // || en->valid != RIP_ENTRY_VALID) + DBG("WG: fib not found\n"); + return; + } + + struct wg_device *dev = p->dev; + + if (dev != NULL) + { + bool marked_peer = false; + bool found = false; + struct wg_peer *peer = NULL; + wg_for_each_peer(dev, peer) + { + if (en->is_tunnel_ep && !marked_peer) + { + WG_TRACE(D_EVENTS, "WG: Is tunnel"); + if (memcmp(peer->public_key, en->public_key, sizeof(wg_key)) == 0) + { + struct peer_config *pc = NULL; + bool remove_me = true; + WALK_LIST(pc,c->peers) + { + wg_key pc_key; + if (pc->public_key) + { + memcpy(pc_key, pc->public_key, sizeof(wg_key)); + if (memcmp(pc_key, peer->public_key, sizeof(wg_key)) == 0) + { + /* Don't remove preconfigured peer */ + remove_me = false; + break; + } + } + } + + if (remove_me) + { + WG_TRACE(D_EVENTS, "WG: Remove peer"); + peer->flags |= WGPEER_REMOVE_ME; + marked_peer = true; + continue; + } + } + } + // Remove from all peers + found = true; + + if (found) + { + struct wg_allowedip *allowedip = alloca(sizeof(struct wg_allowedip)); + init_allowed_ip(allowedip, ch->c.net_type, n); + dump_peer(peer); + if (remove_allowed_ip(peer, allowedip)) + { + ip_addr ip = allowedip_to_ipa(allowedip); + WG_TRACE(D_EVENTS, "WG: removed %I/%d", ip, allowedip->cidr); + peer->flags |= WGPEER_REPLACE_ALLOWEDIPS; + + dump_peer(peer); + } + } + } + + if (marked_peer) + { + remove_marked_peer(p); + } + int res = set_device(p); + WG_TRACE(D_EVENTS, "WG: wg_set_device %d", res); + + fib_delete(&ch->rtable, en); + en = NULL; +/* + old_metric = en->metric; + + en->valid = RIP_ENTRY_STALE; + en->metric = p->infinity; + en->tag = 0; + en->from = NULL; + en->iface = NULL; + en->next_hop = IPA_NONE; +*/ + } + } + +// TRACE(D_EVENTS, "wg notify %s %s", src_table->name, ifname?ifname:"(null)"); +} + +static void +wg_reload_routes(struct channel *C) +{ + struct wg_proto *p UNUSED = (struct wg_proto *) C->proto; + + DBG("reload routes\n"); + +// TODO +// WALK_LIST(c, p->channels) +// channel_request_feeding(c); +} + +static int +wg_format_tunnel_encap_type_0(const struct te_encap *encap, byte *buf, uint size) +{ + byte *pos = buf; + + wg_key_b64_string base64; + wg_key_to_base64(base64, encap->data); + + int l = bsnprintf(pos, size, "%s", base64); + ADVANCE(pos, size, l); + return pos - buf; +} + +static int +wg_format_tunnel_encap_type_1(const struct te_encap *encap, byte *buf, uint size) +{ + byte *pos = buf; + + if (encap->length != 4 + sizeof(wg_key)) + { + DBG("wg_format_tunnel_encap: bad length %d\n", encap->length); + return -1; + } + + uint32_t flags = ntohl(((uint32_t*)encap->data)[0]); + + if (flags != 0) { + DBG("wg_format_tunnel_encap: unsupported flags %08x\n", encap->length); + return -1; + } + + wg_key_b64_string base64; + wg_key_to_base64(base64, ((byte*)encap->data) + 4); + + int l = bsnprintf(pos, size, "%s", base64); + ADVANCE(pos, size, l); + return pos - buf; +} + +static int +wg_format_tunnel_encap(const struct te_encap *encap, byte *buf, uint size) +{ + if (encap->length == sizeof(wg_key)) + { + return wg_format_tunnel_encap_type_0(encap, buf, size); + } + else + { + return wg_format_tunnel_encap_type_1(encap, buf, size); + } +} + +static struct proto * +wg_init(struct proto_config *C) +{ + struct wg_config *c UNUSED = (struct wg_config *) C; + struct proto *P = proto_new(C); + struct wg_proto *p UNUSED = (struct wg_proto *) P; + + DBG("init\n"); + + P->if_notify = wg_if_notify; + P->rt_notify = wg_rt_notify; + P->reload_routes = wg_reload_routes; +// P->accept_ra_types = RA_ANY; + + /* Add all channels */ + struct wg_channel_config *cc; + WALK_LIST(cc, C->channels) + proto_add_channel(P, &cc->c); + + return P; +} + + +static int +wg_start(struct proto *P) +{ + struct wg_config *cf = (struct wg_config *) P->cf; + struct wg_proto *p = (struct wg_proto *) P; + + WG_TRACE(D_EVENTS, "WG: start"); + + if (get_device(p, &p->dev, cf->ifname) >= 0) + { + int res = set_device(p); + WG_TRACE(D_EVENTS, "WG: wg_set_device %d", res); + } + + struct wg_channel *ch; + WALK_LIST(ch,p->p.channels) + { + fib_init(&ch->rtable, P->pool, ch->c.net_type, sizeof(struct wg_entry), + OFFSETOF(struct wg_entry, n), 0, wg_init_entry); + } + return PS_UP; +} + +static int +wg_shutdown(struct proto *P) +{ + struct wg_config *cf = (struct wg_config*)P->cf; + struct wg_proto *p = (struct wg_proto*)P; + + log_msg(L_INFO "wg_shutdown"); + WG_TRACE(D_EVENTS, "WG: wg_shutdown"); + if (get_device(p, &p->dev, cf->ifname) >= 0) + { + int res = set_device(p); + WG_TRACE(D_EVENTS, "WG: flush wg_set_device %d", res); + } + + unregister_format_tunnel_encap(cf->tunnel_type, wg_format_tunnel_encap); + + return PS_DOWN; +} + +static void +wg_dump(struct proto *P) +{ + struct wg_proto *p = (struct wg_proto *) P; + int i; + + i = 0; + + struct wg_channel *ch; + WALK_LIST(ch,p->p.channels) + { + FIB_WALK(&ch->rtable, struct wg_entry, en) + { + // struct wg_entry *en = (struct wg_entry *) e; + DBG("WG: entry #%d:\n", + i++); + } + FIB_WALK_END; + } + + struct wg_peer *peer = NULL; + + WG_TRACE(D_EVENTS, "WG: dump peers"); + wg_for_each_peer(p->dev, peer) + { + dump_peer(peer); + } +} + +static void +wg_copy_config(struct proto_config *DEST, struct proto_config *SRC) +{ + struct wg_config *dest = (struct wg_config *)DEST; + struct wg_config *src = (struct wg_config *)SRC; + + dest->ifname = src->ifname; + dest->socket_path = src->socket_path; + dest->private_key = src->private_key; + dest->listen_port = src->listen_port; + + struct peer_config *spc = NULL; + WALK_LIST(spc,src->peers) + { + struct peer_config *dpc = cfg_allocz(sizeof(struct peer_config)); + dpc->public_key = spc->public_key; + dpc->listen_port = spc->listen_port; + dpc->endpoint = spc->endpoint; + dpc->remote_port = spc->remote_port; + dpc->allowedips = spc->allowedips; + add_tail(&dest->peers, (node*)spc); + } +} + +struct peer_config *peer_new(struct wg_config *c) +{ + struct peer_config *pc = cfg_allocz(sizeof(struct peer_config)); + DBG("peer_new %p\n", pc); + add_tail(&c->peers, (node*)pc); + return pc; +} + + +static void +wg_channel_init(struct channel *CH, struct channel_config *CHC UNUSED) +{ + struct proto *P = CH->proto; + struct wg_proto *p = (struct wg_proto *) P; + + /* Create new instance */ + WG_TRACE(D_EVENTS, "WG: wg_channel_init"); +} + +static int +wg_channel_reconfigure(struct channel *CH, struct channel_config *CHC UNUSED, + int *import_changed UNUSED, int *export_changed UNUSED) +{ + struct proto *P = CH->proto; + struct wg_proto *p = (struct wg_proto *) P; + + /* Try to reconfigure instance, returns success */ + WG_TRACE(D_EVENTS, "WG: wg_channel_reconfigure"); + return 1; +} + +static int +wg_channel_start(struct channel *CH) +{ + struct wg_channel *ch UNUSED = (struct wg_channel*)CH; + struct proto *P = CH->proto; + struct wg_proto *p = (struct wg_proto *) P; + + /* Start the instance */ + WG_TRACE(D_EVENTS, "WG: wg_channel_start"); +#if 0 + fib_init(&ch->rtable, P->pool, ch->c.net_type, sizeof(struct wg_entry), + OFFSETOF(struct wg_entry, n), 0, wg_init_entry); +#endif + return 1; +} + +static void +wg_channel_shutdown(struct channel *CH) +{ + struct wg_channel *ch UNUSED = (struct wg_channel*)CH; + struct proto *P = CH->proto; + struct wg_proto *p = (struct wg_proto *) P; + + /* Stop the instance */ + WG_TRACE(D_EVENTS, "WG: wg_channel_shutdown"); +} + +static void +wg_channel_cleanup(struct channel *CH) +{ + struct wg_channel *ch UNUSED = (struct wg_channel*)CH; + struct proto *P = CH->proto; + struct wg_proto *p = (struct wg_proto *) P; + + /* Channel finished flush */ + WG_TRACE(D_EVENTS, "WG: wg_channel_cleanup"); +} + + +const struct channel_class channel_wg = { + .channel_size = sizeof(struct wg_channel), + .config_size = sizeof(struct wg_channel_config), + .init = wg_channel_init, + .start = wg_channel_start, + .shutdown = wg_channel_shutdown, + .cleanup = wg_channel_cleanup, + .reconfigure = wg_channel_reconfigure, +}; + +struct protocol proto_wireguard = { + .name = "Wireguard", + .template = "wg%d", + .class = PROTOCOL_WG, + .channel_mask = NB_IP, + .proto_size = sizeof(struct wg_proto), + .config_size = sizeof(struct wg_config), + .postconfig = wg_postconfig, + .init = wg_init, + .start = wg_start, + .shutdown = wg_shutdown, + .dump = wg_dump, + .copy_config = wg_copy_config, +/* .multitable = 1, + .preference = DEF_PREF_PIPE, + .cleanup = wg_cleanup, + .reconfigure = wg_reconfigure, + .get_status = wg_get_status, + .show_proto_info = wg_show_proto_info*/ +}; + + +void wireguard_build(void) +{ + proto_build(&proto_wireguard); +} diff --git a/proto/wireguard/wireguard.h b/proto/wireguard/wireguard.h new file mode 100644 index 00000000..b98119c3 --- /dev/null +++ b/proto/wireguard/wireguard.h @@ -0,0 +1,66 @@ +#ifndef _BIRD_WIREGUARD_H +#define _BIRD_WIREGUARD_H + +#include "nest/protocol.h" +#include "sysdep/linux/wireguard.h" + +#ifdef LOCAL_DEBUG +#define WG_FORCE_DEBUG 1 +#else +#define WG_FORCE_DEBUG 0 +#endif +#define WG_TRACE(flags, msg, args...) do { if ((p->p.debug & flags) || WG_FORCE_DEBUG) \ + log(L_TRACE "%s: " msg, p->p.name , ## args ); } while(0) + +struct wg_allowedips { + struct wg_allowedip *first_allowedip; + struct wg_allowedip *last_allowedip; +}; + +struct peer_config { + node n; + const byte *public_key; + u16 listen_port; + ip_addr endpoint; + u16 remote_port; + struct wg_allowedips *allowedips; +}; + +struct wg_config { + struct proto_config c; + const char *ifname; + const char *socket_path; + const byte *private_key; + u16 tunnel_type; + u16 listen_port; + list peers; +}; + +struct wg_proto { + struct proto p; + struct iface *iface; + wg_key private_key; + wg_device *dev; +}; + +struct wg_channel_config { + struct channel_config c; +}; + +struct wg_channel { + struct channel c; + + struct fib rtable; +}; + +struct wg_entry { + bool is_tunnel_ep; + wg_key public_key; + struct fib_node n; +}; + +extern const struct channel_class channel_wg; + +struct peer_config *peer_new(struct wg_config *c); + +#endif /* _BIRD_WIREGUARD_H */ |