Skip to content

Commit

Permalink
Merge pull request #77 from LaakkonenJussi/jb62314
Browse files Browse the repository at this point in the history
[wireguard] Fix shutdown process and no-daemon VPN state machine. Fixes JB#62314
  • Loading branch information
LaakkonenJussi authored Sep 12, 2024
2 parents 3729ef8 + 118e5cb commit 037c5ce
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 26 deletions.
14 changes: 8 additions & 6 deletions connman/vpn/plugins/vpn.c
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ static int stop_vpn(struct vpn_provider *provider)
{
struct vpn_data *data = vpn_provider_get_data(provider);
struct vpn_driver_data *vpn_driver_data;
const struct vpn_driver *vpn_driver = NULL;
const char *name;
struct ifreq ifr;
int fd, err;
Expand All @@ -87,16 +88,19 @@ static int stop_vpn(struct vpn_provider *provider)
return -EINVAL;

vpn_driver_data = g_hash_table_lookup(driver_hash, name);
if (vpn_driver_data)
vpn_driver = vpn_driver_data->vpn_driver;

if (vpn_driver_data && vpn_driver_data->vpn_driver &&
vpn_driver_data->vpn_driver->flags & VPN_FLAG_NO_TUN) {
if (vpn_driver && vpn_driver->flags & VPN_FLAG_NO_TUN) {
/*
* Disconnect only VPNs with daemon, otherwise in error return
* there is a double free with vpn_died() or the failure state
* is overridden by changes made by disconnect to state.
*/
if (!(vpn_driver_data->vpn_driver->flags & VPN_FLAG_NO_DAEMON))
vpn_driver_data->vpn_driver->disconnect(data->provider);
if (!(vpn_driver->flags & VPN_FLAG_NO_DAEMON) &&
vpn_driver->disconnect)
vpn_driver->disconnect(data->provider);

return 0;
}

Expand Down Expand Up @@ -557,8 +561,6 @@ static gboolean update_provider_state(gpointer data)
vpn_data->watch = vpn_rtnl_add_newlink_watch(index,
vpn_newlink, provider);
connman_inet_ifup(index);
vpn_data->state = VPN_STATE_CONNECT;
vpn_provider_set_state(provider, VPN_PROVIDER_STATE_CONNECT);

return FALSE;
}
Expand Down
106 changes: 86 additions & 20 deletions connman/vpn/plugins/wireguard.c
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ struct wireguard_info {
GResolv *resolv;
guint resolv_id;
guint remove_resolv_id;
guint dying_id;
};

struct sockaddr_u {
Expand All @@ -90,6 +91,31 @@ struct {
{"WireGuard.PersistentKeepalive", true}
};

static struct wireguard_info *create_private_data(struct vpn_provider *provider)
{
struct wireguard_info *info;

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;
info->provider = vpn_provider_ref(provider);

return info;
}

static void free_private_data(struct wireguard_info *info)
{
if (vpn_provider_get_plugin_data(info->provider) == info)
vpn_provider_set_plugin_data(info->provider, NULL);

vpn_provider_unref(info->provider);
g_free(info->endpoint_fqdn);
g_free(info->port);
g_free(info);
}

static int parse_key(const char *str, wg_key key)
{
unsigned char *buf;
Expand Down Expand Up @@ -422,6 +448,8 @@ static void resolve_endpoint_cb(GResolvResultStatus status,
run_dns_reresolve(info);
}

static int disconnect(struct vpn_provider *provider, int error);

static gboolean wg_dns_reresolve_cb(gpointer user_data)
{
struct wireguard_info *info = user_data;
Expand Down Expand Up @@ -450,7 +478,7 @@ static gboolean wg_dns_reresolve_cb(gpointer user_data)
if (!info->resolv_id && err) {
connman_error("failed to start hostname lookup for %s, err %d",
info->endpoint_fqdn, err);
vpn_died(NULL, err, info->provider);
disconnect(info->provider, err);
}

return G_SOURCE_REMOVE;
Expand All @@ -464,7 +492,7 @@ static void run_dns_reresolve(struct wireguard_info *info)
if (vpn_provider_get_connection_errors(info->provider) >=
DNS_RERESOLVE_ERROR_LIMIT) {
connman_warn("reresolve error limit reached");
vpn_died(NULL, -ENONET, info->provider);
disconnect(info->provider, -ENONET);
info->reresolve_id = 0;
return;
}
Expand All @@ -484,12 +512,7 @@ static int wg_connect(struct vpn_provider *provider,
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;
info->provider = vpn_provider_ref(provider);
info = create_private_data(provider);

DBG("");

Expand Down Expand Up @@ -643,38 +666,75 @@ static int wg_connect(struct vpn_provider *provider,
goto done;
}

static void wg_disconnect(struct vpn_provider *provider)
struct wireguard_exit_data {
struct vpn_provider *provider;
int err;
};

static gboolean wg_died(gpointer user_data)
{
struct wireguard_exit_data *data = user_data;
struct wireguard_info *info;

DBG("");

/* No task for no daemon VPN - use vpn_died() with no task. */
vpn_died(NULL, data->err, data->provider);

info = vpn_provider_get_plugin_data(data->provider);
if (info)
free_private_data(info);

g_free(data);

return G_SOURCE_REMOVE;
}

/* Allow to overrule the exit code for vpn_died */
static int disconnect(struct vpn_provider *provider, int err)
{
struct wireguard_exit_data *data;
struct wireguard_info *info;
int exit_code;

DBG("");

info = vpn_provider_get_plugin_data(provider);
if (!info)
return;
return -ENODATA;

if (info->dying_id)
return -EALREADY;

if (info->reresolve_id > 0)
g_source_remove(info->reresolve_id);

if (info->resolv || info->resolv_id)
remove_resolv(info);

vpn_provider_set_plugin_data(provider, NULL);

vpn_provider_set_state(provider, VPN_PROVIDER_STATE_DISCONNECT);

exit_code = wg_del_device(info->device.name);

vpn_provider_unref(info->provider);
g_free(info->endpoint_fqdn);
g_free(info->port);
g_free(info);
/* Simulate a task-running VPN to issue vpn_died after exiting this */
data = g_malloc0(sizeof(struct wireguard_exit_data));
data->provider = provider;
data->err = err ? err : exit_code;

DBG("exiting with %d", exit_code);
info->dying_id = g_timeout_add(1, wg_died, data);

/* No task for no daemon VPN - use VPN died with no task. */
vpn_died(NULL, exit_code, provider);
return exit_code;
}

static void wg_disconnect(struct vpn_provider *provider)
{
int exit_code;

DBG("");

exit_code = disconnect(provider, 0);

DBG("exited with %d", exit_code);
}

static int wg_error_code(struct vpn_provider *provider, int exit_code)
Expand Down Expand Up @@ -711,12 +771,18 @@ static int wg_save(struct vpn_provider *provider, GKeyFile *keyfile)
return 0;
}

bool wg_uses_vpn_agent(struct vpn_provider *provider)
{
return false;
}

static struct vpn_driver vpn_driver = {
.flags = VPN_FLAG_NO_TUN | VPN_FLAG_NO_DAEMON,
.connect = wg_connect,
.disconnect = wg_disconnect,
.save = wg_save,
.error_code = wg_error_code
.error_code = wg_error_code,
.uses_vpn_agent = wg_uses_vpn_agent
};

static int wg_init(void)
Expand Down

0 comments on commit 037c5ce

Please sign in to comment.