diff mbox series

[09/12] wireguard: Fix shutdown, ensure one exit and set no agent is used

Message ID 20250124185916.1546471-10-jussi.laakkonen@jolla.com (mailing list archive)
State New
Headers show
Series Improve WireGuard disconnect, error and hostname lookup | expand

Commit Message

Jussi Laakkonen Jan. 24, 2025, 6:59 p.m. UTC
Split disconnect into wg_disconnect() and disconnect(). Use disconnect()
to properly disconnect when error occurs with the appropriate error
code. Do the same in wg_disconnect() by using the error code to be from
device removal.

The shutdown process requires, that in order to do the proper transition
to IDLE, vpn_died() is to be called. This is normally called by the task
that has been killed so simulate this behavior in WireGuard by adding a
delayed call for that. It is to be executed only once as all the cleanup
is to be done at that last step.

Add a function to declare that WireGuard does not use a VPN agent. This
ensures that the state transition is done according to state machine by
__vpn_provider_connect().

Add separate create/free for struct wireguard_info for VPN consistency.
---
 vpn/plugins/wireguard.c | 106 ++++++++++++++++++++++++++++++++--------
 1 file changed, 86 insertions(+), 20 deletions(-)
diff mbox series

Patch

diff --git a/vpn/plugins/wireguard.c b/vpn/plugins/wireguard.c
index 4a3d9ec7..307b3b37 100644
--- a/vpn/plugins/wireguard.c
+++ b/vpn/plugins/wireguard.c
@@ -65,6 +65,7 @@  struct wireguard_info {
 	GResolv *resolv;
 	guint resolv_id;
 	guint remove_resolv_id;
+	guint dying_id;
 };
 
 struct sockaddr_u {
@@ -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;
@@ -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;
@@ -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;
@@ -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;
 	}
@@ -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("");
 
@@ -643,8 +666,34 @@  error:
 	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;
 
@@ -652,7 +701,10 @@  static void wg_disconnect(struct vpn_provider *provider)
 
 	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);
@@ -660,21 +712,29 @@  static void wg_disconnect(struct vpn_provider *provider)
 	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)
@@ -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)