Merge "wireguard: Add routes for allowedIPs" into tizen
[platform/upstream/connman.git] / vpn / plugins / wireguard.c
index de2dbda..25ec32d 100644 (file)
 #include "vpn.h"
 #include "wireguard.h"
 
+#define DNS_RERESOLVE_TIMEOUT 20
+#if defined TIZEN_EXT
+#define ADD_ALLOWED_IP_ROUTE_TIMEOUT 2
+#endif /* TIZEN_EXT */
+
+struct wireguard_info {
+       struct wg_device device;
+       struct wg_peer peer;
+       char *endpoint_fqdn;
+       char *port;
+       int reresolve_id;
+#if defined TIZEN_EXT
+       int allowed_ip_route_id;
+#endif /* TIZEN_EXT */
+};
+
+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;
@@ -76,14 +100,49 @@ static int parse_allowed_ips(const char *allowed_ips, wg_peer *peer)
        int i;
 
        curaip = NULL;
+#if defined TIZEN_EXT
+       /**
+        * Note: At API level we donot check Wireguard specific configurations,
+        * User can provide any space separated string for IP.
+        * Ideally it should be using "10.0.0.2/32, 10.0.0.3/32"
+        * However there can be cases like "10.0.0.2/32,10.0.0.3/32"
+        *
+        * So we need to parse the allowed_ips configuration properly.
+        */
+       tokens = g_strsplit(allowed_ips, ",", -1);
+#else
        tokens = g_strsplit(allowed_ips, ", ", -1);
+#endif
        for (i = 0; tokens[i]; i++) {
+#if defined TIZEN_EXT
+               int len = strlen(tokens[i]);
+               char *ptr = tokens[i];
+               int j = -1;
+
+               //skip forward spaces
+               while (++j < len && ptr[j] == ' ')
+                       ;// Do Nothing
+
+               if (!ptr[j])
+                       continue;
+
+               toks = g_strsplit(ptr + j, "/", -1);
+               if (g_strv_length(toks) != 2) {
+                       DBG("Ignore AllowedIPs value [%s]", ptr + j);
+                       g_strfreev(toks);
+                       continue;
+               }
+
+               DBG("Parsed AllowedIPs [%s]", ptr + j);
+#else
+
                toks = g_strsplit(tokens[i], "/", -1);
                if (g_strv_length(toks) != 2) {
                        DBG("Ignore AllowedIPs value %s", tokens[i]);
                        g_strfreev(toks);
                        continue;
                }
+#endif
 
                allowedip = g_malloc0(sizeof(*allowedip));
 
@@ -94,7 +153,11 @@ static int parse_allowed_ips(const char *allowed_ips, wg_peer *peer)
                        allowedip->family = AF_INET6;
                        memcpy(&allowedip->ip6, buf, sizeof(allowedip->ip6));
                } else {
+#if defined TIZEN_EXT
+                       DBG("Ignore AllowedIPs value [%s]", ptr + j);
+#else
                        DBG("Ignore AllowedIPs value %s", tokens[i]);
+#endif
                        g_free(allowedip);
                        g_strfreev(toks);
                        continue;
@@ -116,7 +179,7 @@ static int parse_allowed_ips(const char *allowed_ips, wg_peer *peer)
        return 0;
 }
 
-static int parse_endpoint(const char *host, const char *port, wg_peer *peer)
+static int parse_endpoint(const char *host, const char *port, struct sockaddr_u *addr)
 {
        struct addrinfo hints;
        struct addrinfo *result, *rp;
@@ -151,7 +214,7 @@ static int parse_endpoint(const char *host, const char *port, wg_peer *peer)
                return -EINVAL;
        }
 
-       memcpy(&peer->endpoint.addr, rp->ai_addr, rp->ai_addrlen);
+       memcpy(addr, rp->ai_addr, rp->ai_addrlen);
        freeaddrinfo(result);
 
        return 0;
@@ -225,7 +288,7 @@ static char *get_ifname(void)
        for (i = 0; i < 256; i++) {
                data.ifname = g_strdup_printf("wg%d", i);
                data.found = false;
-               __vpn_ipconfig_foreach(ifname_check_cb, &data);
+               vpn_ipconfig_foreach(ifname_check_cb, &data);
 
                if (!data.found)
                        return data.ifname;
@@ -236,10 +299,190 @@ static char *get_ifname(void)
        return NULL;
 }
 
-struct wireguard_info {
-       struct wg_device device;
-       struct wg_peer peer;
-};
+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;
+}
+
+#if defined TIZEN_EXT
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <stdio.h>
+
+#define MAX_SIZE_ERROR_BUFFER 256
+#define MAX_CMD_SIZE 80
+
+typedef struct {
+       struct vpn_provider *provider;
+       struct wireguard_info *info;
+} wg_allowed_ip_route_cb_data_s;
+
+static int wg_execute_cmd(const char *format, ...)
+{
+       int status = 0;
+       int rv = 0;
+       char cmd[MAX_CMD_SIZE] = { 0, };
+       char error_buf[MAX_SIZE_ERROR_BUFFER] = {0, };
+       pid_t pid = 0;
+       va_list va_ptr;
+       gchar **args = NULL;
+
+       va_start(va_ptr, format);
+       vsnprintf(cmd, (unsigned int) MAX_CMD_SIZE, format, va_ptr);
+       va_end(va_ptr);
+
+       if (strlen(cmd) == 0)
+               return -EIO;
+
+       DBG("command: %s", cmd);
+
+       args = g_strsplit_set(cmd, " ", -1);
+
+       errno = 0;
+       if (!(pid = fork())) {
+               DBG("pid(%d), ppid (%d)", getpid(), getppid());
+
+               errno = 0;
+               if (execv(args[0], args) == -1) {
+                       DBG("Fail to execute command (%s)",
+                                       strerror_r(errno, error_buf, MAX_SIZE_ERROR_BUFFER));
+                       g_strfreev(args);
+                       exit(1);
+               }
+       } else if (pid > 0) {
+               if (waitpid(pid, &status, 0) == -1)
+                       DBG("wait pid (%u) status (%d)", pid, status);
+
+               if (WIFEXITED(status)) {
+                       rv = WEXITSTATUS(status);
+                       DBG("exited, status=%d", rv);
+
+               } else if (WIFSIGNALED(status)) {
+                       DBG("killed by signal %d", WTERMSIG(status));
+
+               } else if (WIFSTOPPED(status)) {
+                       DBG("stopped by signal %d", WSTOPSIG(status));
+
+               } else if (WIFCONTINUED(status)) {
+                       DBG("continued");
+               }
+
+               g_strfreev(args);
+               return rv;
+       }
+
+       DBG("failed to fork(%s)", strerror_r(errno, error_buf, MAX_SIZE_ERROR_BUFFER));
+       g_strfreev(args);
+
+       return -EIO;
+}
+
+static gboolean wg_add_allowed_ip_route_cb(gpointer user_data)
+{
+       wg_allowed_ip_route_cb_data_s *cb_data = user_data;
+       const char *allowed_ips;
+       char **tokens = NULL;
+       char **toks = NULL;
+       int i;
+
+       DBG("");
+
+       allowed_ips = vpn_provider_get_string(cb_data->provider, "WireGuard.AllowedIPs");
+       if (!allowed_ips) {
+               DBG("WireGuard.AllowedIPs is missing");
+               goto done;
+       }
+
+       tokens = g_strsplit(allowed_ips, ",", -1);
+       if (g_strv_length(tokens) == 0)
+               goto done;
+
+       for (i = 0; tokens[i]; i++) {
+               int len = strlen(tokens[i]);
+               char *ptr = tokens[i];
+               int j = -1;
+
+               //skip forward spaces
+               while (++j < len && ptr[j] == ' ')
+                       ;// Do Nothing
+
+               if (!ptr[j])
+                       continue;
+
+               toks = g_strsplit(ptr + j, "/", -1);
+               if (g_strv_length(toks) != 2) {
+                       DBG("Ignore AllowedIPs value [%s]", ptr + j);
+                       g_strfreev(toks);
+                       continue;
+               }
+               g_strfreev(toks);
+
+               DBG("Allowed IP: [%s], Device Name: [%s]", ptr + j, cb_data->info->device.name);
+
+               // TODO: Remove system call to add route entry present in wg_execute_cmd()
+               if (!g_strcmp0("0.0.0.0/0", ptr + j)) {
+                       // TODO: Update default route because user wants all data to be routed from wg0,
+                       // when 0.0.0.0/0 is passed in allowed ips list.
+                       // Should we replace the default route?
+                       // If yes, then how to recover default route when wg0 tunnel is removed.
+               } else {
+                       wg_execute_cmd("/usr/sbin/ip route add %s dev %s", ptr + j, cb_data->info->device.name);
+               }
+       }
+
+done:
+       if (tokens)
+               g_strfreev(tokens);
+
+       free(cb_data);
+
+       return FALSE;
+}
+#endif /* TIZEN_EXT */
+
+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,
@@ -323,10 +566,14 @@ static int wg_connect(struct vpn_provider *provider,
                option = "51820";
 
        gateway = vpn_provider_get_string(provider, "Host");
-       err = parse_endpoint(gateway, option, &info->peer);
+       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");
@@ -342,7 +589,7 @@ static int wg_connect(struct vpn_provider *provider,
                err = -ENOENT;
                goto done;
        }
-       stpncpy(info->device.name, ifname, sizeof(info->device.name));
+       stpncpy(info->device.name, ifname, sizeof(info->device.name) - 1);
        g_free(ifname);
 
        err = wg_add_device(info->device.name);
@@ -367,6 +614,29 @@ done:
 
        connman_ipaddress_free(ipaddress);
 
+#if defined TIZEN_EXT
+       if (!err) {
+               const char *allowed_ips = vpn_provider_get_string(provider, "WireGuard.AllowedIPs");
+               if (allowed_ips) {
+                       wg_allowed_ip_route_cb_data_s *cb_data =
+                               (wg_allowed_ip_route_cb_data_s *) calloc(1, sizeof(wg_allowed_ip_route_cb_data_s));
+                       if (cb_data) {
+                               cb_data->provider = provider;
+                               cb_data->info = info;
+
+                               info->allowed_ip_route_id =
+                                       g_timeout_add_seconds(ADD_ALLOWED_IP_ROUTE_TIMEOUT,
+                                                       wg_add_allowed_ip_route_cb, cb_data);
+                       }
+               }
+       }
+#endif /* TIZEN_EXT */
+
+       if (!err)
+               info->reresolve_id =
+                       g_timeout_add_seconds(DNS_RERESOLVE_TIMEOUT,
+                                               wg_dns_reresolve_cb, info);
+
        return err;
 }
 
@@ -377,17 +647,132 @@ static void wg_disconnect(struct vpn_provider *provider)
        info = vpn_provider_get_plugin_data(provider);
        if (!info)
                return;
+
+#if defined TIZEN_EXT
+       if (info->allowed_ip_route_id > 0)
+               g_source_remove(info->allowed_ip_route_id);
+#endif /* TIZEN_EXT */
+
+       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);
 }
 
+#if defined TIZEN_EXT
+static int wg_save(struct vpn_provider *provider, GKeyFile *keyfile)
+{
+       const char *option;
+
+       DBG("");
+
+       /*
+        * The client/own device listen port.
+        */
+       option = vpn_provider_get_string(provider, "WireGuard.ListenPort");
+       if (option)
+               g_key_file_set_string(keyfile,
+                               vpn_provider_get_save_group(provider),
+                               "WireGuard.ListenPort",
+                               option);
+
+       /*
+        * comma separated DNS.
+        */
+       option = vpn_provider_get_string(provider, "WireGuard.DNS");
+       if (option)
+               g_key_file_set_string(keyfile,
+                               vpn_provider_get_save_group(provider),
+                               "WireGuard.DNS",
+                               option);
+
+       /*
+        * The client private key.
+        */
+       option = vpn_provider_get_string(provider, "WireGuard.PrivateKey");
+       if (option)
+               g_key_file_set_string(keyfile,
+                               vpn_provider_get_save_group(provider),
+                               "WireGuard.PrivateKey",
+                               option);
+
+       /*
+        * The server public key.
+        */
+       option = vpn_provider_get_string(provider, "WireGuard.PublicKey");
+       if (option)
+               g_key_file_set_string(keyfile,
+                               vpn_provider_get_save_group(provider),
+                               "WireGuard.PublicKey",
+                               option);
+
+       /*
+        * The preshared key.
+        */
+       option = vpn_provider_get_string(provider, "WireGuard.PresharedKey");
+       if (option)
+               g_key_file_set_string(keyfile,
+                               vpn_provider_get_save_group(provider),
+                               "WireGuard.PresharedKey",
+                               option);
+
+       /*
+        * Subnets accessed via VPN tunnel, 0.0.0.0/0 routes all traffic.
+        */
+       option = vpn_provider_get_string(provider, "WireGuard.AllowedIPs");
+       if (option)
+               g_key_file_set_string(keyfile,
+                               vpn_provider_get_save_group(provider),
+                               "WireGuard.AllowedIPs",
+                               option);
+
+       /*
+        * The time in seconds to emit periodic keep alive message.
+        */
+       option = vpn_provider_get_string(provider, "WireGuard.PersistentKeepalive");
+       if (option)
+               g_key_file_set_string(keyfile,
+                               vpn_provider_get_save_group(provider),
+                               "WireGuard.PersistentKeepalive",
+                               option);
+
+       /*
+        * The server listen port, default: 51820
+        */
+       option = vpn_provider_get_string(provider, "WireGuard.EndpointPort");
+       if (option)
+               g_key_file_set_string(keyfile,
+                               vpn_provider_get_save_group(provider),
+                               "WireGuard.EndpointPort",
+                               option);
+
+       /*
+        * Save Address: The internal IP of the client node
+        */
+       option = vpn_provider_get_string(provider, "WireGuard.Address");
+       if (option)
+               g_key_file_set_string(keyfile,
+                               vpn_provider_get_save_group(provider),
+                               "WireGuard.Address",
+                               option);
+
+       return 0;
+}
+#endif
+
 static struct vpn_driver vpn_driver = {
        .flags          = VPN_FLAG_NO_TUN | VPN_FLAG_NO_DAEMON,
        .connect        = wg_connect,
        .disconnect     = wg_disconnect,
+#if defined TIZEN_EXT
+       .save           = wg_save,
+#endif
 };
 
 static int wg_init(void)