/* * ConnMan VPN daemon * * Copyright (C) 2019 Daniel Wagner. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #define CONNMAN_API_SUBJECT_TO_CHANGE #include #include #include #include #include #include #include #include #include "../vpn-provider.h" #include "../vpn.h" #include "vpn.h" #include "wireguard.h" #define DNS_RERESOLVE_TIMEOUT 20 struct wireguard_info { struct wg_device device; struct wg_peer peer; char *endpoint_fqdn; char *port; int reresolve_id; }; struct sockaddr_u { union { struct sockaddr sa; struct sockaddr_in sin; struct sockaddr_in6 sin6; }; }; static int parse_key(const char *str, wg_key key) { unsigned char *buf; size_t len; buf = g_base64_decode(str, &len); if (len != 32) { g_free(buf); return -EINVAL; } memcpy(key, buf, 32); g_free(buf); return 0; } static int parse_allowed_ips(const char *allowed_ips, wg_peer *peer) { struct wg_allowedip *curaip, *allowedip; char buf[INET6_ADDRSTRLEN]; char **tokens, **toks; char *send; int i; curaip = NULL; tokens = g_strsplit(allowed_ips, ", ", -1); for (i = 0; tokens[i]; i++) { toks = g_strsplit(tokens[i], "/", -1); if (g_strv_length(toks) != 2) { DBG("Ignore AllowedIPs value %s", tokens[i]); g_strfreev(toks); continue; } allowedip = g_malloc0(sizeof(*allowedip)); if (inet_pton(AF_INET, toks[0], buf) == 1) { allowedip->family = AF_INET; memcpy(&allowedip->ip4, buf, sizeof(allowedip->ip4)); } else if (inet_pton(AF_INET6, toks[0], buf) == 1) { allowedip->family = AF_INET6; memcpy(&allowedip->ip6, buf, sizeof(allowedip->ip6)); } else { DBG("Ignore AllowedIPs value %s", tokens[i]); g_free(allowedip); g_strfreev(toks); continue; } allowedip->cidr = g_ascii_strtoull(toks[1], &send, 10); if (!curaip) peer->first_allowedip = allowedip; else curaip->next_allowedip = allowedip; curaip = allowedip; } peer->last_allowedip = curaip; g_strfreev(tokens); return 0; } static int parse_endpoint(const char *host, const char *port, struct sockaddr_u *addr) { struct addrinfo hints; struct addrinfo *result, *rp; int sk; memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_DGRAM; hints.ai_flags = 0; hints.ai_protocol = 0; if (getaddrinfo(host, port, &hints, &result) < 0) { DBG("Failed to resolve host address"); return -EINVAL; } for (rp = result; rp; rp = rp->ai_next) { sk = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); if (sk < 0) continue; if (connect(sk, rp->ai_addr, rp->ai_addrlen) != -1) { /* success */ close(sk); break; } close(sk); } if (!rp) { freeaddrinfo(result); return -EINVAL; } memcpy(addr, rp->ai_addr, rp->ai_addrlen); freeaddrinfo(result); return 0; } static int parse_address(const char *address, const char *gateway, struct connman_ipaddress **ipaddress) { char buf[INET6_ADDRSTRLEN]; unsigned char prefixlen; char **tokens; char *end, *netmask; int err; tokens = g_strsplit(address, "/", -1); if (g_strv_length(tokens) != 2) { g_strfreev(tokens); return -EINVAL; } prefixlen = g_ascii_strtoull(tokens[1], &end, 10); if (inet_pton(AF_INET, tokens[0], buf) == 1) { netmask = g_strdup_printf("%d.%d.%d.%d", ((0xffffffff << (32 - prefixlen)) >> 24) & 0xff, ((0xffffffff << (32 - prefixlen)) >> 16) & 0xff, ((0xffffffff << (32 - prefixlen)) >> 8) & 0xff, ((0xffffffff << (32 - prefixlen)) >> 0) & 0xff); *ipaddress = connman_ipaddress_alloc(AF_INET); err = connman_ipaddress_set_ipv4(*ipaddress, tokens[0], netmask, gateway); g_free(netmask); } else if (inet_pton(AF_INET6, tokens[0], buf) == 1) { *ipaddress = connman_ipaddress_alloc(AF_INET6); err = connman_ipaddress_set_ipv6(*ipaddress, tokens[0], prefixlen, gateway); } else { DBG("Invalid Wireguard.Address value"); err = -EINVAL; } connman_ipaddress_set_p2p(*ipaddress, true); g_strfreev(tokens); if (err) connman_ipaddress_free(*ipaddress); return err; } struct ifname_data { char *ifname; bool found; }; static void ifname_check_cb(int index, void *user_data) { struct ifname_data *data = (struct ifname_data *)user_data; char *ifname; ifname = connman_inet_ifname(index); if (!g_strcmp0(ifname, data->ifname)) data->found = true; } static char *get_ifname(void) { struct ifname_data data; int i; for (i = 0; i < 256; i++) { data.ifname = g_strdup_printf("wg%d", i); data.found = false; vpn_ipconfig_foreach(ifname_check_cb, &data); if (!data.found) return data.ifname; g_free(data.ifname); } return NULL; } static bool sockaddr_cmp_addr(struct sockaddr_u *a, struct sockaddr_u *b) { if (a->sa.sa_family != b->sa.sa_family) return false; if (a->sa.sa_family == AF_INET) return !memcmp(&a->sin, &b->sin, sizeof(struct sockaddr_in)); else if (a->sa.sa_family == AF_INET6) return !memcmp(a->sin6.sin6_addr.s6_addr, b->sin6.sin6_addr.s6_addr, sizeof(a->sin6.sin6_addr.s6_addr)); return false; } static gboolean wg_dns_reresolve_cb(gpointer user_data) { struct wireguard_info *info = user_data; struct sockaddr_u addr; int err; DBG(""); err = parse_endpoint(info->endpoint_fqdn, info->port, &addr); if (err) return TRUE; if (sockaddr_cmp_addr(&addr, (struct sockaddr_u *)&info->peer.endpoint.addr)) return TRUE; if (addr.sa.sa_family == AF_INET) memcpy(&info->peer.endpoint.addr, &addr.sin, sizeof(info->peer.endpoint.addr4)); else memcpy(&info->peer.endpoint.addr, &addr.sin6, sizeof(info->peer.endpoint.addr6)); DBG("Endpoint address has changed, udpate WireGuard device"); err = wg_set_device(&info->device); if (err) DBG("Failed to update Endpoint address for WireGuard device %s", info->device.name); return TRUE; } static int wg_connect(struct vpn_provider *provider, struct connman_task *task, const char *if_name, vpn_provider_connect_cb_t cb, const char *dbus_sender, void *user_data) { struct connman_ipaddress *ipaddress = NULL; struct wireguard_info *info; const char *option, *gateway; char *ifname; int err = -EINVAL; info = g_malloc0(sizeof(struct wireguard_info)); info->peer.flags = WGPEER_HAS_PUBLIC_KEY | WGPEER_REPLACE_ALLOWEDIPS; info->device.flags = WGDEVICE_HAS_PRIVATE_KEY; info->device.first_peer = &info->peer; info->device.last_peer = &info->peer; vpn_provider_set_plugin_data(provider, info); option = vpn_provider_get_string(provider, "WireGuard.ListenPort"); if (option) { char *end; info->device.listen_port = g_ascii_strtoull(option, &end, 10); info->device.flags |= WGDEVICE_HAS_LISTEN_PORT; } option = vpn_provider_get_string(provider, "WireGuard.DNS"); if (option) { err = vpn_provider_set_nameservers(provider, option); if (err) goto done; } option = vpn_provider_get_string(provider, "WireGuard.PrivateKey"); if (!option) { DBG("WireGuard.PrivateKey is missing"); goto done; } err = parse_key(option, info->device.private_key); if (err) goto done; option = vpn_provider_get_string(provider, "WireGuard.PublicKey"); if (!option) { DBG("WireGuard.PublicKey is missing"); goto done; } err = parse_key(option, info->peer.public_key); if (err) goto done; option = vpn_provider_get_string(provider, "WireGuard.PresharedKey"); if (option) { info->peer.flags |= WGPEER_HAS_PRESHARED_KEY; err = parse_key(option, info->peer.preshared_key); if (err) goto done; } option = vpn_provider_get_string(provider, "WireGuard.AllowedIPs"); if (!option) { DBG("WireGuard.AllowedIPs is missing"); goto done; } err = parse_allowed_ips(option, &info->peer); if (err) goto done; option = vpn_provider_get_string(provider, "WireGuard.PersistentKeepalive"); if (option) { char *end; info->peer.persistent_keepalive_interval = g_ascii_strtoull(option, &end, 10); info->peer.flags |= WGPEER_HAS_PERSISTENT_KEEPALIVE_INTERVAL; } option = vpn_provider_get_string(provider, "WireGuard.EndpointPort"); if (!option) option = "51820"; gateway = vpn_provider_get_string(provider, "Host"); err = parse_endpoint(gateway, option, (struct sockaddr_u *)&info->peer.endpoint.addr); if (err) goto done; info->endpoint_fqdn = g_strdup(gateway); info->port = g_strdup(option); option = vpn_provider_get_string(provider, "WireGuard.Address"); if (!option) { DBG("Missing WireGuard.Address configuration"); goto done; } err = parse_address(option, gateway, &ipaddress); if (err) goto done; ifname = get_ifname(); if (!ifname) { DBG("Failed to find an usable device name"); err = -ENOENT; goto done; } stpncpy(info->device.name, ifname, sizeof(info->device.name) - 1); g_free(ifname); err = wg_add_device(info->device.name); if (err) { DBG("Failed to creating WireGuard device %s", info->device.name); goto done; } err = wg_set_device(&info->device); if (err) { DBG("Failed to configure WireGuard device %s", info->device.name); wg_del_device(info->device.name); } vpn_set_ifname(provider, info->device.name); if (ipaddress) vpn_provider_set_ipaddress(provider, ipaddress); done: if (cb) cb(provider, user_data, err); connman_ipaddress_free(ipaddress); if (!err) info->reresolve_id = g_timeout_add_seconds(DNS_RERESOLVE_TIMEOUT, wg_dns_reresolve_cb, info); return err; } static void wg_disconnect(struct vpn_provider *provider) { struct wireguard_info *info; info = vpn_provider_get_plugin_data(provider); if (!info) return; if (info->reresolve_id > 0) g_source_remove(info->reresolve_id); vpn_provider_set_plugin_data(provider, NULL); wg_del_device(info->device.name); g_free(info->endpoint_fqdn); g_free(info->port); g_free(info); } static struct vpn_driver vpn_driver = { .flags = VPN_FLAG_NO_TUN | VPN_FLAG_NO_DAEMON, .connect = wg_connect, .disconnect = wg_disconnect, }; static int wg_init(void) { return vpn_register("wireguard", &vpn_driver, NULL); } static void wg_exit(void) { vpn_unregister("wireguard"); } CONNMAN_PLUGIN_DEFINE(wireguard, "WireGuard VPN plugin", VERSION, CONNMAN_PLUGIN_PRIORITY_DEFAULT, wg_init, wg_exit)