// Based on proto/rip/rip.c #define LOCAL_DEBUG #include #include #include #include #include #include #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; ipublic_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); }