From: Jussi Laakkonen <jussi.laakkonen@jolla.com>
To: connman@lists.linux.dev, Daniel Wagner <wagi@monom.org>
Subject: Re: [PATCH v2 1/2] vpn-provider: Implement connmand online state checking
Date: Thu, 19 Aug 2021 15:11:21 +0300 [thread overview]
Message-ID: <0fbd12e6-7e18-25fc-4d13-511289895168@jolla.com> (raw)
In-Reply-To: <20210818103137.17409-1-jussi.laakkonen@jolla.com>
Hi Daniel,
Sorry, I spotted some error with this as apparently our fork has
something again done differently somewhere. I'll send another version
when I find the cause. Does not seem to function correctly on top of 1.40.
Sorry for the inconvenience.
- Jussi
On 8/18/21 1:31 PM, Jussi Laakkonen wrote:
> Add a complete mechanism to track connmand state and query it if
> necessary to avoid connecting VPNs when there is either no connmand or
> no network to use. This also makes VPNs to disconnect when connmand
> loses its online state or disappears.
>
> The connmand state listener uses net.connman.Manager interface to get
> the state using GetProperties at startup. PropertyChanged signal is
> monitored for state changes to update the state. State is changed and
> queried when the D-Bus service listener is notified. Connmand state is
> tracked within vpnd with a boolean: "true" = online/ready, "false" =
> offline/idle.
>
> Also a feature to support delayed connecting of VPNs is added. It may
> happen in a situation where ConnMan status is not queried yet and
> a request to connect is received over D-Bus. In this case a timeout
> function is added to the main loop that runs with 1s interval. When the
> delayed connect function is running it keeps on trying until connmand
> state is online/ready. If connection request comes when the state is
> queried and it is not online/ready ENOLINK is returned as an error
> ("NoCarrier" D-Bus msg).
> ---
> Changes since v2:
> * Cleanups and fixes to follow coding style.
> * Refactor run_get_connman_state().
> * Replace g_try_new0() with g_new().
>
> vpn/vpn-provider.c | 362 ++++++++++++++++++++++++++++++++++++++++++++-
> 1 file changed, 361 insertions(+), 1 deletion(-)
>
> diff --git a/vpn/vpn-provider.c b/vpn/vpn-provider.c
> index 59c805c5..72245794 100644
> --- a/vpn/vpn-provider.c
> +++ b/vpn/vpn-provider.c
> @@ -3,7 +3,7 @@
> * ConnMan VPN daemon
> *
> * Copyright (C) 2012-2013 Intel Corporation. All rights reserved.
> - * Copyright (C) 2019 Jolla Ltd. All rights reserved.
> + * Copyright (C) 2019-2021 Jolla Ltd. 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
> @@ -24,6 +24,13 @@
> #include <config.h>
> #endif
>
> +#define STATE_INTERVAL_DEFAULT 0
> +
> +#define CONNMAN_STATE_ONLINE "online"
> +#define CONNMAN_STATE_OFFLINE "offline"
> +#define CONNMAN_STATE_READY "ready"
> +#define CONNMAN_STATE_IDLE "idle"
> +
> #include <errno.h>
> #include <stdio.h>
> #include <string.h>
> @@ -89,15 +96,55 @@ struct vpn_provider {
> struct connman_ipaddress *prev_ipv4_addr;
> struct connman_ipaddress *prev_ipv6_addr;
> void *plugin_data;
> + unsigned int do_connect_timeout;
> unsigned int auth_error_counter;
> unsigned int conn_error_counter;
> unsigned int signal_watch;
> };
>
> +struct vpn_provider_connect_data {
> + DBusConnection *conn;
> + DBusMessage *msg;
> + struct vpn_provider *provider;
> +};
> +
> +static unsigned int get_connman_state_timeout = 0;
> +
> +static guint connman_signal_watch;
> +static guint connman_service_watch;
> +
> +static bool connman_online = false;
> +static bool state_query_completed = false;
> +
> static void append_properties(DBusMessageIter *iter,
> struct vpn_provider *provider);
> static int vpn_provider_save(struct vpn_provider *provider);
>
> +static void get_connman_state();
> +
> +static void set_state(const char* new_state)
> +{
> + if (!new_state || !*new_state)
> + return;
> +
> + DBG("old state %s new state %s",
> + connman_online ?
> + CONNMAN_STATE_ONLINE "/" CONNMAN_STATE_READY :
> + CONNMAN_STATE_OFFLINE "/" CONNMAN_STATE_IDLE,
> + new_state);
> +
> + /* States "online" and "ready" mean connman is online */
> + if (!g_ascii_strcasecmp(new_state, CONNMAN_STATE_ONLINE) ||
> + !g_ascii_strcasecmp(new_state, CONNMAN_STATE_READY))
> + connman_online = true;
> + /* Everything else means connman is offline */
> + else
> + connman_online = false;
> +
> + DBG("set state %s connman_online=%s ", new_state,
> + connman_online ? "true" : "false");
> +}
> +
> static void free_route(gpointer data)
> {
> struct vpn_route *route = data;
> @@ -740,6 +787,64 @@ static DBusMessage *clear_property(DBusConnection *conn, DBusMessage *msg,
> return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
> }
>
> +static gboolean do_connect_timeout_function(gpointer data)
> +{
> + struct vpn_provider_connect_data *cdata = data;
> + struct vpn_provider *provider = cdata->provider;
> + int err;
> +
> + DBG("");
> +
> + /*
> + * Keep in main loop if no agents are present yet or if connman is not
> + * yet online.
> + */
> + if (!connman_agent_get_info(NULL, NULL, NULL) || !connman_online)
> + return G_SOURCE_CONTINUE;
> +
> + provider->do_connect_timeout = 0;
> + err = __vpn_provider_connect(provider, cdata->msg);
> +
> + if (err < 0 && err != -EINPROGRESS) {
> + g_dbus_send_message(cdata->conn,
> + __connman_error_failed(cdata->msg, -err));
> + cdata->msg = NULL;
> + }
> +
> + return G_SOURCE_REMOVE;
> +}
> +
> +static void do_connect_timeout_free(gpointer data)
> +{
> + struct vpn_provider_connect_data *cdata = data;
> +
> + if (cdata->msg)
> + g_dbus_send_message(cdata->conn,
> + __connman_error_operation_aborted(cdata->msg));
> +
> + dbus_connection_unref(cdata->conn);
> + g_free(data);
> +}
> +
> +static void do_connect_later(struct vpn_provider *provider,
> + DBusConnection *conn, DBusMessage *msg)
> +{
> + struct vpn_provider_connect_data *cdata =
> + g_new0(struct vpn_provider_connect_data, 1);
> +
> + cdata->conn = dbus_connection_ref(conn);
> + cdata->msg = dbus_message_ref(msg);
> + cdata->provider = provider;
> +
> + if (provider->do_connect_timeout)
> + g_source_remove(provider->do_connect_timeout);
> +
> + provider->do_connect_timeout =
> + g_timeout_add_seconds_full(G_PRIORITY_DEFAULT, 1,
> + do_connect_timeout_function, cdata,
> + do_connect_timeout_free);
> +}
> +
> static DBusMessage *do_connect(DBusConnection *conn, DBusMessage *msg,
> void *data)
> {
> @@ -748,6 +853,29 @@ static DBusMessage *do_connect(DBusConnection *conn, DBusMessage *msg,
>
> DBG("conn %p provider %p", conn, provider);
>
> + /* Check if any agents have been added, otherwise delay connecting */
> + if (!connman_agent_get_info(NULL, NULL, NULL)) {
> + DBG("Provider %s start delayed because no VPN agent is present",
> + provider->identifier);
> + do_connect_later(provider, conn, msg);
> + return NULL;
> + }
> +
> + if (!connman_online) {
> + if (state_query_completed) {
> + DBG("Provider %s not started because connman "
> + "not online/ready",
> + provider->identifier);
> + return __connman_error_failed(msg, ENOLINK);
> + } else {
> + DBG("Provider %s start delayed because connman "
> + "state not queried",
> + provider->identifier);
> + do_connect_later(provider, conn, msg);
> + return NULL;
> + }
> + }
> +
> err = __vpn_provider_connect(provider, msg);
> if (err < 0 && err != -EINPROGRESS)
> return __connman_error_failed(msg, -err);
> @@ -1269,6 +1397,9 @@ static void provider_destruct(struct vpn_provider *provider)
> {
> DBG("provider %p", provider);
>
> + if (provider->do_connect_timeout)
> + g_source_remove(provider->do_connect_timeout);
> +
> if (provider->notify_id != 0)
> g_source_remove(provider->notify_id);
>
> @@ -3214,6 +3345,213 @@ static void remove_unprovisioned_providers(void)
> g_strfreev(providers);
> }
>
> +static gboolean connman_property_changed(DBusConnection *conn,
> + DBusMessage *message,
> + void *user_data)
> +{
> + DBusMessageIter iter, value;
> + const char *key;
> + const char *signature = DBUS_TYPE_STRING_AS_STRING
> + DBUS_TYPE_VARIANT_AS_STRING;
> +
> + if (!dbus_message_has_signature(message, signature)) {
> + connman_error("vpn connman property signature \"%s\" "
> + "does not match expected \"%s\"",
> + dbus_message_get_signature(message),
> + signature);
> + return TRUE;
> + }
> +
> + if (!dbus_message_iter_init(message, &iter))
> + return TRUE;
> +
> + dbus_message_iter_get_basic(&iter, &key);
> +
> + dbus_message_iter_next(&iter);
> + dbus_message_iter_recurse(&iter, &value);
> +
> + DBG("key %s", key);
> +
> + if (g_str_equal(key, "State")) {
> + const char *str;
> +
> + if (dbus_message_iter_get_arg_type(&value) != DBUS_TYPE_STRING)
> + return TRUE;
> +
> + dbus_message_iter_get_basic(&value, &str);
> + set_state(str);
> + }
> +
> + return TRUE;
> +}
> +
> +static void get_connman_state_reply(DBusPendingCall *call, void *user_data)
> +{
> + DBusMessage *reply = NULL;
> + DBusError error;
> + DBusMessageIter iter, array, dict, value;
> +
> + const char *signature = DBUS_TYPE_ARRAY_AS_STRING
> + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
> + DBUS_TYPE_STRING_AS_STRING
> + DBUS_TYPE_VARIANT_AS_STRING
> + DBUS_DICT_ENTRY_END_CHAR_AS_STRING;
> +
> + const char *key;
> + const char *str;
> +
> + DBG("");
> +
> + if (!dbus_pending_call_get_completed(call))
> + goto done;
> +
> + reply = dbus_pending_call_steal_reply(call);
> +
> + dbus_error_init(&error);
> +
> + if (dbus_set_error_from_message(&error, reply)) {
> + connman_error("%s", error.message);
> +
> + /*
> + * In case of timeout re-add the state function to main
> + * event loop.
> + */
> + if (g_ascii_strcasecmp(error.name, DBUS_ERROR_TIMEOUT) == 0) {
> + DBG("D-Bus timeout, re-add get_connman_state()");
> + get_connman_state();
> + } else {
> + dbus_error_free(&error);
> + goto done;
> + }
> + }
> +
> + if (!dbus_message_has_signature(reply, signature)) {
> + connman_error("vpnd signature \"%s\" does not match "
> + "expected \"%s\"",
> + dbus_message_get_signature(reply),
> + signature);
> + goto done;
> + }
> +
> + if (!dbus_message_iter_init(reply, &array))
> + goto done;
> +
> + dbus_message_iter_recurse(&array, &dict);
> +
> + while (dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_DICT_ENTRY) {
> + dbus_message_iter_recurse(&dict, &iter);
> +
> + dbus_message_iter_get_basic(&iter, &key);
> +
> + dbus_message_iter_next(&iter);
> + dbus_message_iter_recurse(&iter, &value);
> +
> + if (g_ascii_strcasecmp(key, "State") == 0 &&
> + dbus_message_iter_get_arg_type(&value) ==
> + DBUS_TYPE_STRING) {
> + dbus_message_iter_get_basic(&value, &str);
> +
> + DBG("Got initial state %s", str);
> +
> + set_state(str);
> +
> + /* No need to process further */
> + break;
> + }
> +
> + dbus_message_iter_next(&dict);
> + }
> +
> + state_query_completed = true;
> +
> +done:
> + if (reply)
> + dbus_message_unref(reply);
> +
> + if (call)
> + dbus_pending_call_unref(call);
> +}
> +
> +static gboolean run_get_connman_state(gpointer user_data)
> +{
> + const char *path = "/";
> + const char *method = "GetProperties";
> + gboolean rval = FALSE;
> +
> + DBusMessage *msg = NULL;
> + DBusPendingCall *call = NULL;
> +
> + DBG("");
> +
> + msg = dbus_message_new_method_call(CONNMAN_SERVICE, path,
> + CONNMAN_MANAGER_INTERFACE, method);
> + if (!msg)
> + goto out;
> +
> + rval = g_dbus_send_message_with_reply(connection, msg, &call, -1);
> + if (!rval) {
> + connman_error("Cannot call %s on %s", method,
> + CONNMAN_MANAGER_INTERFACE);
> + goto out;
> + }
> +
> + if (!call) {
> + connman_error("set pending call failed");
> + rval = FALSE;
> + goto out;
> + }
> +
> + if (!dbus_pending_call_set_notify(call, get_connman_state_reply,
> + NULL, NULL)) {
> + connman_error("set notify to pending call failed");
> +
> + if (call)
> + dbus_pending_call_unref(call);
> +
> + rval = FALSE;
> + }
> +
> +out:
> + if (msg)
> + dbus_message_unref(msg);
> +
> + /* In case sending was success, unset timeout function id */
> + if (rval) {
> + DBG("unsetting get_connman_state_timeout id");
> + get_connman_state_timeout = 0;
> + }
> +
> + /* Return FALSE in case of success to remove from main event loop */
> + return !rval;
> +}
> +
> +static void get_connman_state()
> +{
> + if (get_connman_state_timeout)
> + return;
> +
> + get_connman_state_timeout = g_timeout_add(STATE_INTERVAL_DEFAULT,
> + run_get_connman_state, NULL);
> +}
> +
> +static void connman_service_watch_connected(DBusConnection *conn,
> + void *user_data)
> +{
> + DBG("");
> + get_connman_state();
> +}
> +
> +static void connman_service_watch_disconnected(DBusConnection *conn,
> + void *user_data)
> +{
> + DBG("");
> +
> + set_state(CONNMAN_STATE_IDLE);
> +
> + /* Set state query variable to initial state */
> + state_query_completed = false;
> +}
> +
> int __vpn_provider_init(void)
> {
> int err;
> @@ -3233,6 +3571,20 @@ int __vpn_provider_init(void)
>
> provider_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
> NULL, unregister_provider);
> +
> + connman_service_watch = g_dbus_add_service_watch(connection,
> + CONNMAN_SERVICE,
> + connman_service_watch_connected,
> + connman_service_watch_disconnected,
> + NULL, NULL);
> +
> + connman_signal_watch = g_dbus_add_signal_watch(connection,
> + CONNMAN_SERVICE, NULL,
> + CONNMAN_MANAGER_INTERFACE,
> + PROPERTY_CHANGED,
> + connman_property_changed,
> + NULL, NULL);
> +
> return 0;
> }
>
> @@ -3247,5 +3599,13 @@ void __vpn_provider_cleanup(void)
> g_hash_table_destroy(provider_hash);
> provider_hash = NULL;
>
> + if (get_connman_state_timeout) {
> + if (!g_source_remove(get_connman_state_timeout))
> + connman_error("connman state timeout not removed");
> + }
> +
> + g_dbus_remove_watch(connection, connman_service_watch);
> + g_dbus_remove_watch(connection, connman_signal_watch);
> +
> dbus_connection_unref(connection);
> }
>
next prev parent reply other threads:[~2021-08-19 12:11 UTC|newest]
Thread overview: 16+ messages / expand[flat|nested] mbox.gz Atom feed top
2021-08-17 15:14 [PATCH 0/2] Implement connmand state monitoring to vpnd Jussi Laakkonen
2021-08-17 15:14 ` [PATCH 1/2] vpn-provider: Implement connmand online state checking Jussi Laakkonen
2021-08-17 16:55 ` Daniel Wagner
2021-08-18 9:21 ` Jussi Laakkonen
2021-08-18 10:31 ` [PATCH v2 " Jussi Laakkonen
2021-08-19 12:11 ` Jussi Laakkonen [this message]
2021-08-19 12:30 ` Daniel Wagner
2021-08-19 12:38 ` Santtu Lakkala
2021-08-19 13:06 ` Daniel Wagner
2021-08-19 12:40 ` Jussi Laakkonen
2021-08-29 18:27 ` Daniel Wagner
2021-08-30 6:59 ` Jussi Laakkonen
2021-08-30 8:11 ` Daniel Wagner
2021-08-20 13:05 ` [PATCH v3 " Jussi Laakkonen
2021-08-17 15:14 ` [PATCH 2/2] vpn: Handle ENOLINK error in connect callback Jussi Laakkonen
2021-08-20 13:06 ` [PATCH v2 2/2] vpn: Refactor connect_reply() and handle NoCarrier -> ENOLINK error Jussi Laakkonen
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=0fbd12e6-7e18-25fc-4d13-511289895168@jolla.com \
--to=jussi.laakkonen@jolla.com \
--cc=connman@lists.linux.dev \
--cc=wagi@monom.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).