Send connman mailing list submissions to
        [email protected]

To subscribe or unsubscribe via email, send a message with subject or
body 'help' to
        [email protected]

You can reach the person managing the list at
        [email protected]

When replying, please edit your Subject line so it is more specific
than "Re: Contents of connman digest..."

Today's Topics:

   1. [PATCH v1 04/11] vpn: Add provider only on success to hash table
      (Daniel Wagner)
   2. [PATCH v1 07/11] shared: Add Generic Netlink helpers for libmnl
      (Daniel Wagner)
   3. [PATCH v1 09/11] vpn: Introduce VPN_FLAG_NO_DAEMON (Daniel Wagner)


----------------------------------------------------------------------

Date: Tue,  5 Nov 2019 08:23:18 +0100
From: Daniel Wagner <[email protected]>
Subject: [PATCH v1 04/11] vpn: Add provider only on success to hash
        table
To: [email protected]
Cc: Daniel Wagner <[email protected]>
Message-ID: <[email protected]>

Don't insert the provider before we call
__vpn_provider_create_from_config(). Because if the provider creation
fails we will double free all the values, first when we leave
load_provider() in the error path and later when the
config->provider_table hash table is destroyed.
---
 vpn/vpn-config.c | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/vpn/vpn-config.c b/vpn/vpn-config.c
index c88a99abb2c7..f56e51ee463a 100644
--- a/vpn/vpn-config.c
+++ b/vpn/vpn-config.c
@@ -261,9 +261,6 @@ static int load_provider(GKeyFile *keyfile, const char 
*group,
        config_provider->config_entry = g_strdup_printf("provider_%s",
                                                config_provider->ident);
 
-       g_hash_table_insert(config->provider_table,
-                               config_provider->ident, config_provider);
-
        err = __vpn_provider_create_from_config(
                                        config_provider->setting_strings,
                                        config_provider->config_ident,
@@ -274,6 +271,10 @@ static int load_provider(GKeyFile *keyfile, const char 
*group,
                goto err;
        }
 
+       g_hash_table_insert(config->provider_table, config_provider->ident,
+                               config_provider);
+
+
        connman_info("Added provider configuration %s",
                                                config_provider->ident);
        return 0;
-- 
2.23.0

------------------------------

Date: Tue,  5 Nov 2019 08:23:21 +0100
From: Daniel Wagner <[email protected]>
Subject: [PATCH v1 07/11] shared: Add Generic Netlink helpers for
        libmnl
To: [email protected]
Cc: Daniel Wagner <[email protected]>
Message-ID: <[email protected]>

mnlg.c and mnlg.h are a copy from iproute2.

The call to nl_dump_ext_ack() and nl_dump_ext_ack_done() have been
removed from the code to avoid additional dependencies.

git://git.kernel.org/pub/scm/network/iproute2/iproute2.git
d035cc1b4e83e2589ea2115cdc2fa7c6d3693a5a

The helpers are needed for the WireGuard VPN plugin.
---
 Makefile.plugins  |  12 +-
 configure.ac      |   3 +-
 src/shared/mnlg.c | 325 ++++++++++++++++++++++++++++++++++++++++++++++
 src/shared/mnlg.h |  27 ++++
 4 files changed, 362 insertions(+), 5 deletions(-)
 create mode 100644 src/shared/mnlg.c
 create mode 100644 src/shared/mnlg.h

diff --git a/Makefile.plugins b/Makefile.plugins
index 3d4e32f08810..a4d255f363d1 100644
--- a/Makefile.plugins
+++ b/Makefile.plugins
@@ -66,17 +66,21 @@ if WIREGUARD
 builtin_vpn_source = vpn/plugins/vpn.c vpn/plugins/vpn.h
 if WIREGUARD_BUILTIN
 builtin_vpn_modules += wireguard
-builtin_vpn_sources += vpn/plugins/wireguard.c
-builtin_vpn_cflags += -DWIREGUARD=\"@WIREGUARD@\"
+builtin_vpn_sources += src/shared/mnlg.h src/shared/mnlg.c \
+                       vpn/plugins/wireguard.c
+builtin_vpn_cflags += @LIBMNL_CFLAGS@ -DWIREGUARD=\"@WIREGUARD@\"
+builtin_vpn_libadd += @LIBMNL_LIBS@
 else
 vpn_plugin_LTLIBRARIES += vpn/plugins/wireguard.la
 vpn_plugin_objects += $(plugins_wireguard_la_OBJECTS)
-vpn_plugins_wireguard_la_SOURCES = vpn/plugins/wireguard.c
-vpn_plugins_wireguard_la_CFLAGS = $(plugin_cflags) \
+vpn_plugins_wireguard_la_SOURCES = src/shared/mnlg.h src/shared/mnlg.c \
+                                       vpn/plugins/wireguard.c
+vpn_plugins_wireguard_la_CFLAGS = $(plugin_cflags) @LIBMNL_CFLAGS@ \
                                        -DWIREGUARD=\"@WIREGUARD@\" \
                                        -DVPN_STATEDIR=\""$(vpn_statedir)"\" \
                                        -DSCRIPTDIR=\""$(build_scriptdir)"\"
 vpn_plugins_wireguard_la_LDFLAGS = $(plugin_ldflags)
+vpn_plugins_wireguard_la_LIBADD = @LIBMNL_LIBS@
 endif
 endif
 
diff --git a/configure.ac b/configure.ac
index 537ba5f4007f..3695ef5b2b60 100644
--- a/configure.ac
+++ b/configure.ac
@@ -288,7 +288,8 @@ fi
 AM_CONDITIONAL(XTABLES, test "${found_iptables}" != "no")
 
 found_libmnl="no"
-if (test "${firewall_type}" = "nftables"); then
+if (test "${firewall_type}" = "nftables" -o \
+               "${enable_wireguard}" != "no"); then
        PKG_CHECK_MODULES(LIBMNL, [libmnl >= 1.0.0], [found_libmnl="yes"],
                AC_MSG_ERROR([libmnl >= 1.0.0 not found]))
        AC_SUBST(LIBMNL_CFLAGS)
diff --git a/src/shared/mnlg.c b/src/shared/mnlg.c
new file mode 100644
index 000000000000..6b02059d4490
--- /dev/null
+++ b/src/shared/mnlg.c
@@ -0,0 +1,325 @@
+/*
+ *   mnlg.c    Generic Netlink helpers for libmnl
+ *
+ *              This program is free software; you can redistribute it and/or
+ *              modify it under the terms of the GNU General Public License
+ *              as published by the Free Software Foundation; either version
+ *              2 of the License, or (at your option) any later version.
+ *
+ * Authors:     Jiri Pirko <[email protected]>
+ */
+
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <time.h>
+#include <libmnl/libmnl.h>
+#include <linux/genetlink.h>
+
+#include "mnlg.h"
+
+#ifndef MNL_ARRAY_SIZE
+#define MNL_ARRAY_SIZE(a) (sizeof(a)/sizeof((a)[0]))
+#endif
+
+#ifndef ARRAY_SIZE
+#define ARRAY_SIZE(a) (sizeof(a)/sizeof((a)[0]))
+#endif
+
+
+struct mnlg_socket {
+       struct mnl_socket *nl;
+       char *buf;
+       uint32_t id;
+       uint8_t version;
+       unsigned int seq;
+       unsigned int portid;
+};
+
+static struct nlmsghdr *__mnlg_msg_prepare(struct mnlg_socket *nlg, uint8_t 
cmd,
+                                          uint16_t flags, uint32_t id,
+                                          uint8_t version)
+{
+       struct nlmsghdr *nlh;
+       struct genlmsghdr *genl;
+
+       nlh = mnl_nlmsg_put_header(nlg->buf);
+       nlh->nlmsg_type = id;
+       nlh->nlmsg_flags = flags;
+       nlg->seq = time(NULL);
+       nlh->nlmsg_seq = nlg->seq;
+
+       genl = mnl_nlmsg_put_extra_header(nlh, sizeof(struct genlmsghdr));
+       genl->cmd = cmd;
+       genl->version = version;
+
+       return nlh;
+}
+
+struct nlmsghdr *mnlg_msg_prepare(struct mnlg_socket *nlg, uint8_t cmd,
+                                 uint16_t flags)
+{
+       return __mnlg_msg_prepare(nlg, cmd, flags, nlg->id, nlg->version);
+}
+
+int mnlg_socket_send(struct mnlg_socket *nlg, const struct nlmsghdr *nlh)
+{
+       return mnl_socket_sendto(nlg->nl, nlh, nlh->nlmsg_len);
+}
+
+static int mnlg_cb_noop(const struct nlmsghdr *nlh, void *data)
+{
+       return MNL_CB_OK;
+}
+
+static int mnlg_cb_error(const struct nlmsghdr *nlh, void *data)
+{
+       const struct nlmsgerr *err = mnl_nlmsg_get_payload(nlh);
+
+       /* Netlink subsystems returns the errno value with different signess */
+       if (err->error < 0)
+               errno = -err->error;
+       else
+               errno = err->error;
+
+       return err->error == 0 ? MNL_CB_STOP : MNL_CB_ERROR;
+}
+
+static int mnlg_cb_stop(const struct nlmsghdr *nlh, void *data)
+{
+       int len = *(int *)NLMSG_DATA(nlh);
+
+       if (len < 0) {
+               errno = -len;
+               return MNL_CB_ERROR;
+       }
+       return MNL_CB_STOP;
+}
+
+static mnl_cb_t mnlg_cb_array[NLMSG_MIN_TYPE] = {
+       [NLMSG_NOOP]    = mnlg_cb_noop,
+       [NLMSG_ERROR]   = mnlg_cb_error,
+       [NLMSG_DONE]    = mnlg_cb_stop,
+       [NLMSG_OVERRUN] = mnlg_cb_noop,
+};
+
+int mnlg_socket_recv_run(struct mnlg_socket *nlg, mnl_cb_t data_cb, void *data)
+{
+       int err;
+
+       do {
+               err = mnl_socket_recvfrom(nlg->nl, nlg->buf,
+                                         MNL_SOCKET_BUFFER_SIZE);
+               if (err <= 0)
+                       break;
+               err = mnl_cb_run2(nlg->buf, err, nlg->seq, nlg->portid,
+                                 data_cb, data, mnlg_cb_array,
+                                 ARRAY_SIZE(mnlg_cb_array));
+       } while (err > 0);
+
+       return err;
+}
+
+struct group_info {
+       bool found;
+       uint32_t id;
+       const char *name;
+};
+
+static int parse_mc_grps_cb(const struct nlattr *attr, void *data)
+{
+       const struct nlattr **tb = data;
+       int type = mnl_attr_get_type(attr);
+
+       if (mnl_attr_type_valid(attr, CTRL_ATTR_MCAST_GRP_MAX) < 0)
+               return MNL_CB_OK;
+
+       switch (type) {
+       case CTRL_ATTR_MCAST_GRP_ID:
+               if (mnl_attr_validate(attr, MNL_TYPE_U32) < 0)
+                       return MNL_CB_ERROR;
+               break;
+       case CTRL_ATTR_MCAST_GRP_NAME:
+               if (mnl_attr_validate(attr, MNL_TYPE_STRING) < 0)
+                       return MNL_CB_ERROR;
+               break;
+       }
+       tb[type] = attr;
+       return MNL_CB_OK;
+}
+
+static void parse_genl_mc_grps(struct nlattr *nested,
+                              struct group_info *group_info)
+{
+       struct nlattr *pos;
+       const char *name;
+
+       mnl_attr_for_each_nested(pos, nested) {
+               struct nlattr *tb[CTRL_ATTR_MCAST_GRP_MAX + 1] = {};
+
+               mnl_attr_parse_nested(pos, parse_mc_grps_cb, tb);
+               if (!tb[CTRL_ATTR_MCAST_GRP_NAME] ||
+                   !tb[CTRL_ATTR_MCAST_GRP_ID])
+                       continue;
+
+               name = mnl_attr_get_str(tb[CTRL_ATTR_MCAST_GRP_NAME]);
+               if (strcmp(name, group_info->name) != 0)
+                       continue;
+
+               group_info->id = mnl_attr_get_u32(tb[CTRL_ATTR_MCAST_GRP_ID]);
+               group_info->found = true;
+       }
+}
+
+static int get_group_id_attr_cb(const struct nlattr *attr, void *data)
+{
+       const struct nlattr **tb = data;
+       int type = mnl_attr_get_type(attr);
+
+       if (mnl_attr_type_valid(attr, CTRL_ATTR_MAX) < 0)
+               return MNL_CB_ERROR;
+
+       if (type == CTRL_ATTR_MCAST_GROUPS &&
+           mnl_attr_validate(attr, MNL_TYPE_NESTED) < 0)
+               return MNL_CB_ERROR;
+       tb[type] = attr;
+       return MNL_CB_OK;
+}
+
+static int get_group_id_cb(const struct nlmsghdr *nlh, void *data)
+{
+       struct group_info *group_info = data;
+       struct nlattr *tb[CTRL_ATTR_MAX + 1] = {};
+       struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+
+       mnl_attr_parse(nlh, sizeof(*genl), get_group_id_attr_cb, tb);
+       if (!tb[CTRL_ATTR_MCAST_GROUPS])
+               return MNL_CB_ERROR;
+       parse_genl_mc_grps(tb[CTRL_ATTR_MCAST_GROUPS], group_info);
+       return MNL_CB_OK;
+}
+
+int mnlg_socket_group_add(struct mnlg_socket *nlg, const char *group_name)
+{
+       struct nlmsghdr *nlh;
+       struct group_info group_info;
+       int err;
+
+       nlh = __mnlg_msg_prepare(nlg, CTRL_CMD_GETFAMILY,
+                                NLM_F_REQUEST | NLM_F_ACK, GENL_ID_CTRL, 1);
+       mnl_attr_put_u16(nlh, CTRL_ATTR_FAMILY_ID, nlg->id);
+
+       err = mnlg_socket_send(nlg, nlh);
+       if (err < 0)
+               return err;
+
+       group_info.found = false;
+       group_info.name = group_name;
+       err = mnlg_socket_recv_run(nlg, get_group_id_cb, &group_info);
+       if (err < 0)
+               return err;
+
+       if (!group_info.found) {
+               errno = ENOENT;
+               return -1;
+       }
+
+       err = mnl_socket_setsockopt(nlg->nl, NETLINK_ADD_MEMBERSHIP,
+                                   &group_info.id, sizeof(group_info.id));
+       if (err < 0)
+               return err;
+
+       return 0;
+}
+
+static int get_family_id_attr_cb(const struct nlattr *attr, void *data)
+{
+       const struct nlattr **tb = data;
+       int type = mnl_attr_get_type(attr);
+
+       if (mnl_attr_type_valid(attr, CTRL_ATTR_MAX) < 0)
+               return MNL_CB_ERROR;
+
+       if (type == CTRL_ATTR_FAMILY_ID &&
+           mnl_attr_validate(attr, MNL_TYPE_U16) < 0)
+               return MNL_CB_ERROR;
+       tb[type] = attr;
+       return MNL_CB_OK;
+}
+
+static int get_family_id_cb(const struct nlmsghdr *nlh, void *data)
+{
+       uint32_t *p_id = data;
+       struct nlattr *tb[CTRL_ATTR_MAX + 1] = {};
+       struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+
+       mnl_attr_parse(nlh, sizeof(*genl), get_family_id_attr_cb, tb);
+       if (!tb[CTRL_ATTR_FAMILY_ID])
+               return MNL_CB_ERROR;
+       *p_id = mnl_attr_get_u16(tb[CTRL_ATTR_FAMILY_ID]);
+       return MNL_CB_OK;
+}
+
+struct mnlg_socket *mnlg_socket_open(const char *family_name, uint8_t version)
+{
+       struct mnlg_socket *nlg;
+       struct nlmsghdr *nlh;
+       int one = 1;
+       int err;
+
+       nlg = malloc(sizeof(*nlg));
+       if (!nlg)
+               return NULL;
+
+       nlg->buf = malloc(MNL_SOCKET_BUFFER_SIZE);
+       if (!nlg->buf)
+               goto err_buf_alloc;
+
+       nlg->nl = mnl_socket_open(NETLINK_GENERIC);
+       if (!nlg->nl)
+               goto err_mnl_socket_open;
+
+       /* Older kernels may no support capped/extended ACK reporting */
+       mnl_socket_setsockopt(nlg->nl, NETLINK_CAP_ACK, &one, sizeof(one));
+       mnl_socket_setsockopt(nlg->nl, NETLINK_EXT_ACK, &one, sizeof(one));
+
+       err = mnl_socket_bind(nlg->nl, 0, MNL_SOCKET_AUTOPID);
+       if (err < 0)
+               goto err_mnl_socket_bind;
+
+       nlg->portid = mnl_socket_get_portid(nlg->nl);
+
+       nlh = __mnlg_msg_prepare(nlg, CTRL_CMD_GETFAMILY,
+                                NLM_F_REQUEST | NLM_F_ACK, GENL_ID_CTRL, 1);
+       mnl_attr_put_strz(nlh, CTRL_ATTR_FAMILY_NAME, family_name);
+
+       err = mnlg_socket_send(nlg, nlh);
+       if (err < 0)
+               goto err_mnlg_socket_send;
+
+       err = mnlg_socket_recv_run(nlg, get_family_id_cb, &nlg->id);
+       if (err < 0)
+               goto err_mnlg_socket_recv_run;
+
+       nlg->version = version;
+       return nlg;
+
+err_mnlg_socket_recv_run:
+err_mnlg_socket_send:
+err_mnl_socket_bind:
+       mnl_socket_close(nlg->nl);
+err_mnl_socket_open:
+       free(nlg->buf);
+err_buf_alloc:
+       free(nlg);
+       return NULL;
+}
+
+void mnlg_socket_close(struct mnlg_socket *nlg)
+{
+       mnl_socket_close(nlg->nl);
+       free(nlg->buf);
+       free(nlg);
+}
diff --git a/src/shared/mnlg.h b/src/shared/mnlg.h
new file mode 100644
index 000000000000..4d1babf3b4c2
--- /dev/null
+++ b/src/shared/mnlg.h
@@ -0,0 +1,27 @@
+/*
+ *   mnlg.h    Generic Netlink helpers for libmnl
+ *
+ *              This program is free software; you can redistribute it and/or
+ *              modify it under the terms of the GNU General Public License
+ *              as published by the Free Software Foundation; either version
+ *              2 of the License, or (at your option) any later version.
+ *
+ * Authors:     Jiri Pirko <[email protected]>
+ */
+
+#ifndef _MNLG_H_
+#define _MNLG_H_
+
+#include <libmnl/libmnl.h>
+
+struct mnlg_socket;
+
+struct nlmsghdr *mnlg_msg_prepare(struct mnlg_socket *nlg, uint8_t cmd,
+                                 uint16_t flags);
+int mnlg_socket_send(struct mnlg_socket *nlg, const struct nlmsghdr *nlh);
+int mnlg_socket_recv_run(struct mnlg_socket *nlg, mnl_cb_t data_cb, void 
*data);
+int mnlg_socket_group_add(struct mnlg_socket *nlg, const char *group_name);
+struct mnlg_socket *mnlg_socket_open(const char *family_name, uint8_t version);
+void mnlg_socket_close(struct mnlg_socket *nlg);
+
+#endif /* _MNLG_H_ */
-- 
2.23.0

------------------------------

Date: Tue,  5 Nov 2019 08:23:23 +0100
From: Daniel Wagner <[email protected]>
Subject: [PATCH v1 09/11] vpn: Introduce VPN_FLAG_NO_DAEMON
To: [email protected]
Cc: Daniel Wagner <[email protected]>
Message-ID: <[email protected]>

Add VPN_FLAG_NO_DAEMON to skip forking and monitoring an external
task. Since the state machine inside vpn.c is driven by the external
events we need to fake the event via update_provider_state(). We can't
directly progress the state machine because we are still processing an
D-Bus message after calling vpn_driver->connect(). So defer the call
to update_provider_state via the idle loop.
---
 vpn/plugins/vpn.c | 51 +++++++++++++++++++++++++++++++++++++++++++----
 vpn/plugins/vpn.h |  3 ++-
 2 files changed, 49 insertions(+), 5 deletions(-)

diff --git a/vpn/plugins/vpn.c b/vpn/plugins/vpn.c
index eef8550e6a9c..96a06299be09 100644
--- a/vpn/plugins/vpn.c
+++ b/vpn/plugins/vpn.c
@@ -89,7 +89,7 @@ static int stop_vpn(struct vpn_provider *provider)
        vpn_driver_data = g_hash_table_lookup(driver_hash, name);
 
        if (vpn_driver_data && vpn_driver_data->vpn_driver &&
-                       vpn_driver_data->vpn_driver->flags == VPN_FLAG_NO_TUN)
+                       vpn_driver_data->vpn_driver->flags & VPN_FLAG_NO_TUN)
                return 0;
 
        memset(&ifr, 0, sizeof(ifr));
@@ -540,6 +540,27 @@ static void vpn_task_setup(gpointer user_data)
                connman_error("error setting uid %d %s", uid, strerror(errno));
 }
 
+
+static gboolean update_provider_state(gpointer data)
+{
+       struct vpn_provider *provider = data;
+       struct vpn_data *vpn_data;
+       int index;
+
+       DBG("");
+
+       vpn_data = vpn_provider_get_data(provider);
+
+       index = vpn_provider_get_index(provider);
+       DBG("index to watch %d", index);
+       vpn_provider_ref(provider);
+       vpn_data->watch = vpn_rtnl_add_newlink_watch(index,
+                                               vpn_newlink, provider);
+       connman_inet_ifup(index);
+
+       return FALSE;
+}
+
 static int vpn_connect(struct vpn_provider *provider,
                        vpn_provider_connect_cb_t cb,
                        const char *dbus_sender, void *user_data)
@@ -595,7 +616,7 @@ static int vpn_connect(struct vpn_provider *provider,
                goto exist_err;
        }
 
-       if (vpn_driver_data->vpn_driver->flags != VPN_FLAG_NO_TUN) {
+       if (!(vpn_driver_data->vpn_driver->flags & VPN_FLAG_NO_TUN)) {
                if (vpn_driver_data->vpn_driver->device_flags) {
                        tun_flags = 
vpn_driver_data->vpn_driver->device_flags(provider);
                }
@@ -604,6 +625,26 @@ static int vpn_connect(struct vpn_provider *provider,
                        goto exist_err;
        }
 
+
+       if (vpn_driver_data && vpn_driver_data->vpn_driver &&
+                       vpn_driver_data->vpn_driver->flags & 
VPN_FLAG_NO_DAEMON) {
+
+               ret = vpn_driver_data->vpn_driver->connect(provider,
+                                               NULL, NULL, NULL, NULL, NULL);
+               if (ret) {
+                       stop_vpn(provider);
+                       goto exist_err;
+               }
+
+               DBG("%s started with dev %s",
+                       vpn_driver_data->provider_driver.name, data->if_name);
+
+               data->state = VPN_STATE_CONNECT;
+
+               g_timeout_add(1, update_provider_state, provider);
+               return -EINPROGRESS;
+       }
+
        vpn_plugin_data =
                vpn_settings_get_vpn_plugin_config(vpn_driver_data->name);
        data->task = connman_task_create(vpn_driver_data->program,
@@ -689,7 +730,8 @@ static int vpn_disconnect(struct vpn_provider *provider)
                vpn_provider_set_state(provider, VPN_PROVIDER_STATE_DISCONNECT);
        }
 
-       connman_task_stop(data->task);
+       if (data->task)
+               connman_task_stop(data->task);
 
        return 0;
 }
@@ -713,7 +755,8 @@ static int vpn_remove(struct vpn_provider *provider)
                data->watch = 0;
        }
 
-       connman_task_stop(data->task);
+       if (data->task)
+               connman_task_stop(data->task);
 
        g_usleep(G_USEC_PER_SEC);
        stop_vpn(provider);
diff --git a/vpn/plugins/vpn.h b/vpn/plugins/vpn.h
index 265fd82f62e4..71e04f618fd2 100644
--- a/vpn/plugins/vpn.h
+++ b/vpn/plugins/vpn.h
@@ -28,7 +28,8 @@
 extern "C" {
 #endif
 
-#define VPN_FLAG_NO_TUN        1
+#define VPN_FLAG_NO_TUN     1
+#define VPN_FLAG_NO_DAEMON  2
 
 enum vpn_state {
        VPN_STATE_UNKNOWN       = 0,
-- 
2.23.0

------------------------------

Subject: Digest Footer

_______________________________________________
connman mailing list -- [email protected]
To unsubscribe send an email to [email protected]


------------------------------

End of connman Digest, Vol 49, Issue 4
**************************************

Reply via email to