Add the ability to configure virtual VLAN interfaces
associated with physical ethernet interfaces.

Signed-off-by: Justin Maggard <[email protected]>
---
 client/commands.c  |    9 ++
 include/inet.h     |    4 +
 plugins/ethernet.c |   22 ++-
 src/inet.c         |   56 +++++++
 src/service.c      |  409 +++++++++++++++++++++++++++++++++++++++++++++++++++-
 5 files changed, 495 insertions(+), 5 deletions(-)

diff --git a/client/commands.c b/client/commands.c
index 9208016..77dcb26 100644
--- a/client/commands.c
+++ b/client/commands.c
@@ -1044,6 +1044,14 @@ static int cmd_config(char *args[], int num, struct 
connman_option *options)
                                        config_return, g_strdup(service_name),
                                        NULL, NULL);
                        break;
+               case 'V':
+                       res = __connmanctl_dbus_set_property_array(connection,
+                                       path, "net.connman.Service",
+                                       config_return, g_strdup(service_name),
+                                       "VLAN.Configuration", DBUS_TYPE_STRING,
+                                       config_append_str, &append);
+                       index += append.values;
+                       break;
                default:
                        res = -EINVAL;
                        break;
@@ -2025,6 +2033,7 @@ static struct connman_option config_options[] = {
        {"autoconnect", 'a', "yes|no"},
        {"ipv4", 'i', "off|dhcp|manual <address> <netmask> <gateway>"},
        {"remove", 'r', "                 Remove service"},
+       {"vlan", 'V', "<vid> [<vid> ...]"},
        { NULL, }
 };
 
diff --git a/include/inet.h b/include/inet.h
index 6482934..0780747 100644
--- a/include/inet.h
+++ b/include/inet.h
@@ -39,6 +39,10 @@ char *connman_inet_ifname(int index);
 int connman_inet_ifup(int index);
 int connman_inet_ifdown(int index);
 
+int connman_inet_get_vlan_vid(const char *ifname);
+void connman_inet_add_vlan(const char *ifname, int vid);
+void connman_inet_del_vlan(const char *ifname);
+
 int connman_inet_set_address(int index, struct connman_ipaddress *ipaddress);
 int connman_inet_clear_address(int index, struct connman_ipaddress *ipaddress);
 int connman_inet_add_host_route(int index, const char *host, const char 
*gateway);
diff --git a/plugins/ethernet.c b/plugins/ethernet.c
index b8e52ce..7746270 100644
--- a/plugins/ethernet.c
+++ b/plugins/ethernet.c
@@ -25,6 +25,9 @@
 
 #include <errno.h>
 #include <net/if.h>
+#include <string.h>
+#include <stdio.h>
+#include <linux/sockios.h>
 
 #ifndef IFF_LOWER_UP
 #define IFF_LOWER_UP   0x10000
@@ -93,7 +96,8 @@ static void add_network(struct connman_device *device,
                        struct ethernet_data *ethernet)
 {
        struct connman_network *network;
-       int index;
+       int index, vid;
+       char *ifname;
 
        network = connman_network_create("carrier",
                                        CONNMAN_NETWORK_TYPE_ETHERNET);
@@ -102,23 +106,33 @@ static void add_network(struct connman_device *device,
 
        index = connman_device_get_index(device);
        connman_network_set_index(network, index);
+       ifname = connman_inet_ifname(index);
+       if (!ifname)
+               return;
+       vid = connman_inet_get_vlan_vid(ifname);
 
-       connman_network_set_name(network, "Wired");
+       connman_network_set_name(network, vid >= 0 ? "VLAN" : "Wired");
 
        if (connman_device_add_network(device, network) < 0) {
                connman_network_unref(network);
+               g_free(ifname);
                return;
        }
 
-       if (!eth_tethering)
+       if (!eth_tethering) {
+               char group[16] = "cable";
                /*
                 * Prevent service from starting the reconnect
                 * procedure as we do not want the DHCP client
                 * to run when tethering.
                 */
-               connman_network_set_group(network, "cable");
+               if (vid >= 0)
+                       snprintf(group, sizeof(group), "vlan%d", vid);
+               connman_network_set_group(network, group);
+       }
 
        ethernet->network = network;
+       g_free(ifname);
 }
 
 static void remove_network(struct connman_device *device,
diff --git a/src/inet.c b/src/inet.c
index cd220ff..92e4d71 100644
--- a/src/inet.c
+++ b/src/inet.c
@@ -47,6 +47,7 @@
 #include <ctype.h>
 #include <ifaddrs.h>
 #include <linux/fib_rules.h>
+#include <linux/if_vlan.h>
 
 #include "connman.h"
 #include <gdhcp/gdhcp.h>
@@ -330,6 +331,61 @@ done:
        return err;
 }
 
+int connman_inet_get_vlan_vid(const char *ifname)
+{
+       struct vlan_ioctl_args vifr;
+       int vid;
+       int sk;
+
+       memset(&vifr, '\0', sizeof(vifr));
+
+       sk = socket(AF_INET, SOCK_STREAM, 0);
+       if (sk < 0)
+               return -errno;
+
+       vifr.cmd = GET_VLAN_VID_CMD;
+       strncpy(vifr.device1, ifname, sizeof(vifr.device1));
+       vid = ioctl(sk, SIOCSIFVLAN, &vifr) ? -errno : vifr.u.VID;
+       close(sk);
+
+       return vid;
+}
+
+void connman_inet_add_vlan(const char *ifname, int vid)
+{
+       struct vlan_ioctl_args vifr;
+       int sk;
+
+       memset(&vifr, '\0', sizeof(vifr));
+
+       sk = socket(AF_INET, SOCK_STREAM, 0);
+       if (sk < 0)
+               return;
+
+       vifr.cmd = ADD_VLAN_CMD;
+       vifr.u.VID = vid;
+       strncpy(vifr.device1, ifname, sizeof(vifr.device1));
+       ioctl(sk, SIOCSIFVLAN, &vifr);
+       close(sk);
+}
+
+void connman_inet_del_vlan(const char *ifname)
+{
+       struct vlan_ioctl_args vifr;
+       int sk;
+
+       memset(&vifr, '\0', sizeof(vifr));
+
+       sk = socket(AF_INET, SOCK_STREAM, 0);
+       if (sk < 0)
+               return;
+
+       vifr.cmd = DEL_VLAN_CMD;
+       strncpy(vifr.device1, ifname, sizeof(vifr.device1));
+       ioctl(sk, SIOCSIFVLAN, &vifr);
+       close(sk);
+}
+
 struct in6_ifreq {
        struct in6_addr ifr6_addr;
        __u32 ifr6_prefixlen;
diff --git a/src/service.c b/src/service.c
index 7538bdd..f9a7b52 100644
--- a/src/service.c
+++ b/src/service.c
@@ -30,6 +30,7 @@
 #include <gdbus.h>
 #include <ctype.h>
 #include <stdint.h>
+#include <stdlib.h>
 
 #include <connman/storage.h>
 #include <connman/setting.h>
@@ -97,6 +98,12 @@ struct connman_service {
        char *domainname;
        char **timeservers;
        char **timeservers_config;
+       int *vids;
+       int *vids_config;
+       char **vids_strings;
+       char **vids_config_strings;
+       int *vids2go;
+       int *vids_config2go;
        /* 802.1x settings from the config files */
        char *eap;
        char *identity;
@@ -133,6 +140,10 @@ static struct connman_ipconfig *create_ip4config(struct 
connman_service *service
 static struct connman_ipconfig *create_ip6config(struct connman_service 
*service,
                int index);
 
+static struct connman_service *lookup_by_identifier(const char *identifier);
+
+static int update_vids_strings(DBusMessageIter *iter,
+                               int *vids, char ***vids_strings);
 
 struct find_data {
        const char *path;
@@ -395,6 +406,11 @@ int __connman_service_load_modifiable(struct 
connman_service *service)
        return 0;
 }
 
+static int connman_service_id_is_vlan(struct connman_service *service)
+{
+       return (strstr(service->identifier, "_vlan") != NULL);
+}
+
 static int service_load(struct connman_service *service)
 {
        GKeyFile *keyfile;
@@ -532,6 +548,24 @@ static int service_load(struct connman_service *service)
                service->timeservers_config = NULL;
        }
 
+       service->vids_config_strings = g_key_file_get_string_list(keyfile,
+                       service->identifier, "VLAN", &length, NULL);
+       if ((service->vids_config_strings && !length) ||
+               connman_service_id_is_vlan(service)) {
+               g_strfreev(service->vids_config_strings);
+               service->vids_config_strings = NULL;
+       }
+       if (service->vids_config_strings && length > 0) {
+               service->vids_config2go = g_new(int, length + 1);
+               int i;
+               for (i = 0; i < length; i++)
+                       if ((service->vids_config2go[i] =
+                               atoi(service->vids_config_strings[i])) < 0)
+                               break;
+               service->vids_config2go[i] = -1;
+       } else
+               service->vids_config2go = NULL;
+
        service->domains = g_key_file_get_string_list(keyfile,
                        service->identifier, "Domains", &length, NULL);
        if (service->domains && length == 0) {
@@ -705,6 +739,17 @@ static int service_save(struct connman_service *service)
                g_key_file_remove_key(keyfile, service->identifier,
                                                        "Timeservers", NULL);
 
+       if (service->vids_config && !connman_service_id_is_vlan(service)) {
+               guint len = update_vids_strings(NULL,
+                                               service->vids_config,
+                                               &service->vids_config_strings);
+               g_key_file_set_string_list(keyfile, service->identifier,
+                                                               "VLAN",
+                               (const gchar **) service->vids_config_strings, 
len);
+       } else
+               g_key_file_remove_key(keyfile, service->identifier,
+                                                       "VLAN", NULL);
+
        if (service->domains) {
                guint len = g_strv_length(service->domains);
 
@@ -1724,6 +1769,250 @@ static void append_tsconfig(DBusMessageIter *iter, void 
*user_data)
        }
 }
 
+/* Delete VLAN IF */
+static void del_vlan(struct connman_service *service)
+{
+       char *vlanname;
+
+       if (!connman_service_id_is_vlan(service))
+               return;
+
+       vlanname = connman_inet_ifname(__connman_service_get_index(service));
+       if (!vlanname)
+               return;
+
+       connman_info("Delete VLAN id=%s %s", service->identifier, vlanname);
+
+       connman_inet_del_vlan(vlanname);
+
+       g_free(vlanname);
+}
+
+static struct connman_service *lookup_for_vlanbase(
+                       const struct connman_service *service)
+{
+       const char *group = strrchr(service->identifier, '_');
+       struct connman_service *base_service;
+
+       if (!group || strncmp(group, "_vlan", 5))
+               return NULL;
+
+       gchar *identifier = g_strdup_printf("%.*s_cable",
+                                       (int)(group - service->identifier),
+                                       service->identifier);
+       base_service = lookup_by_identifier(identifier);
+       g_free(identifier);
+
+       return base_service;
+}
+
+static struct connman_service *lookup_for_vlan(
+               const struct connman_service *base_service, const int vid)
+{
+       const char *group = strrchr(base_service->identifier, '_');
+       struct connman_service *vlan_service;
+
+       if (!group || !strncmp(group, "_vlan", 5))
+               return NULL;
+
+       gchar *identifier = g_strdup_printf("%.*s_vlan%d",
+                                       (int)(group - base_service->identifier),
+                                       base_service->identifier,
+                                       vid);
+       vlan_service = lookup_by_identifier(identifier);
+       g_free(identifier);
+
+       return vlan_service;
+}
+
+/* Delete child VLAN */
+static void del_vlan_by_vid(struct connman_service *service, const int vid)
+{
+       struct connman_service *vlan_service = lookup_for_vlan(service, vid);
+
+       if (vlan_service)
+               del_vlan(vlan_service);
+}
+
+/* Create NEW VLAN */
+static void add_vlan_by_vid(struct connman_service *service, const int vid)
+{
+       int index = __connman_service_get_index(service);
+       char *ifname = connman_inet_ifname(index);
+
+       if (!ifname)
+               return;
+
+       if (connman_inet_get_vlan_vid(ifname) >= 0) {
+               g_free(ifname);
+               return;
+       }
+
+       connman_info("Add VLAN base=%s %s.%d",
+                       service->identifier, ifname, vid);
+
+       connman_inet_add_vlan(ifname, vid);
+
+       g_free(ifname);
+}
+
+/* Rebuild VID string list */
+static int update_vids_strings(DBusMessageIter *iter,
+                               int *vids, char ***vids_strings)
+{
+       int i = 0;
+
+       if (*vids_strings) {
+               g_strfreev(*vids_strings);
+               *vids_strings = NULL;
+       }
+
+       if (vids) {
+               for (i = 0; vids[i] >= 0; i++) {
+                       *vids_strings = g_renew(char *, *vids_strings, i + 2);
+                       (*vids_strings)[i] = g_strdup_printf("%d", vids[i]);
+                       (*vids_strings)[i + 1] = NULL;
+
+                       if (iter)
+                               dbus_message_iter_append_basic(iter,
+                                               DBUS_TYPE_STRING,
+                                               &(*vids_strings)[i]);
+               }
+       }
+
+       return i;
+}
+
+static void set_vlans(struct connman_service *service,
+                       int **vids, int **vids2go, char ***vids_strings)
+{
+       connman_info("Set VLANs: id=%s vids=%p vids2go=%p",
+                       service->identifier, *vids, *vids2go);
+       /* Remove stale VLANs */
+       if (*vids) {
+               int *vidp;
+
+               for (vidp = *vids; *vidp >= 0; vidp++) {
+                       int *vidp2 = NULL;
+
+                       if (*vids2go)
+                               for (vidp2 = *vids2go; *vidp2 >= 0; vidp2++)
+                                       if (*vidp2 == *vidp)
+                                               break;
+
+                       if (!vidp2 || *vidp2 < 0)
+                               del_vlan_by_vid(service, *vidp);
+               }
+       }
+
+       /* Add VLANs (both new and existing). */
+       if (*vids2go) {
+               int *vidp;
+
+               for (vidp = *vids2go; *vidp >= 0; vidp++)
+                       add_vlan_by_vid(service, *vidp);
+
+       }
+       g_free(*vids);
+       *vids = *vids2go;
+       *vids2go = NULL;
+}
+
+static void set_vlan(DBusMessageIter *iter, void *user_data)
+{
+       struct connman_service *service = user_data;
+
+       set_vlans(service,
+                       &service->vids_config,
+                       &service->vids_config2go,
+                       &service->vids_config_strings);
+
+       set_vlans(service,
+                       &service->vids,
+                       &service->vids2go,
+                       &service->vids_strings);
+}
+
+static void set_vlanconfig(DBusMessageIter *iter, void *user_data)
+{
+       struct connman_service *service = user_data;
+
+       set_vlans(service,
+                       &service->vids_config,
+                       &service->vids_config2go,
+                       &service->vids_config_strings);
+}
+
+static void merge_vids(const int *vids, int **vids2go)
+{
+       int i, len = 0, clen;
+
+       if (!vids)
+               return;
+
+       if (*vids2go) {
+               while ((*vids2go)[len] >= 0)
+                       len++;
+       }
+
+       clen = len;
+
+       if (vids) {
+               /* Continue to get total length. */
+               for (i = 0; vids[i] >= 0; i++)
+                       len++;
+       }
+
+       *vids2go = g_renew(int, *vids2go, len + 1);
+
+       for (i = 0; vids[i] >= 0; i++) {
+               int j;
+
+               /* Search for duplicate */
+               for (j = 0; j < clen; j++)
+                       if ((*vids2go)[j] == vids[i])
+                               break;
+               if (j >= clen)
+                       (*vids2go)[clen++] = vids[i];
+       }
+
+       if (clen)
+               (*vids2go)[clen] = -1;
+       else {
+               g_free(*vids2go);
+               *vids2go = NULL;
+       }
+}
+
+static void add_vlan(DBusMessageIter *iter, void *user_data)
+{
+       struct connman_service *service = user_data;
+
+       if (service->vids2go || service->vids_config2go) {
+               merge_vids(service->vids, &service->vids2go);
+               merge_vids(service->vids_config, &service->vids_config2go);
+               set_vlan(iter, service);
+               service_save(service);
+       }
+       update_vids_strings(iter, service->vids,
+                               &service->vids_strings);
+       update_vids_strings(iter, service->vids_config,
+                               &service->vids_config_strings);
+}
+
+static void add_vlanconfig(DBusMessageIter *iter, void *user_data)
+{
+       struct connman_service *service = user_data;
+
+       if (service->vids_config2go) {
+               merge_vids(service->vids_config, &service->vids_config2go);
+               set_vlanconfig(iter, service);
+               service_save(service);
+       }
+       update_vids_strings(iter, service->vids_config,
+                               &service->vids_config_strings);
+}
+
 static void append_domainconfig(DBusMessageIter *iter, void *user_data)
 {
        struct connman_service *service = user_data;
@@ -1880,6 +2169,46 @@ static void append_provider(DBusMessageIter *iter, void 
*user_data)
                __connman_provider_append_properties(service->provider, iter);
 }
 
+static void rem_vid(int **list, int vid)
+{
+       int *vidp = *list;
+
+       if (!vidp)
+               return;
+
+       while (*vidp >= 0 && *vidp != vid)
+               vidp++;
+
+       if (*vidp == vid) {
+               do
+                       vidp[0] = vidp[1];
+               while (*vidp++ >= 0);
+
+               if (vidp - *list == 1) {
+                       g_free(*list);
+                       *list = NULL;
+               } else
+                       *list = g_renew(int, *list, vidp - *list);
+       }
+}
+
+/* Delete a VLAN service and remove it from its base's lists */
+static void delete_vlan(struct connman_service *service, int vid)
+{
+       struct connman_service *base_service = lookup_for_vlanbase(service);
+
+       if (base_service) {
+               rem_vid(&base_service->vids, vid);
+               update_vids_strings(NULL, service->vids,
+                                       &service->vids_strings);
+               rem_vid(&base_service->vids_config, vid);
+               update_vids_strings(NULL, service->vids_config,
+                                       &service->vids_config_strings);
+               service_save(base_service);
+       }
+
+       del_vlan(service);
+}
 
 static void settings_changed(struct connman_service *service,
                                struct connman_ipconfig *ipconfig)
@@ -2005,6 +2334,18 @@ static void timeservers_configuration_changed(struct 
connman_service *service)
                        append_tsconfig, service);
 }
 
+static void vlan_configuration_changed(struct connman_service *service)
+{
+       if (!allow_property_changed(service))
+               return;
+
+       connman_dbus_property_changed_array(service->path,
+                               CONNMAN_SERVICE_INTERFACE,
+                               "VLAN.Configuration",
+                               DBUS_TYPE_STRING,
+                               set_vlanconfig, service);
+}
+
 static void link_changed(struct connman_service *service)
 {
        if (!allow_property_changed(service))
@@ -2359,6 +2700,12 @@ static void append_properties(DBusMessageIter *dict, 
dbus_bool_t limited,
        connman_dbus_dict_append_array(dict, "Timeservers.Configuration",
                                DBUS_TYPE_STRING, append_tsconfig, service);
 
+       connman_dbus_dict_append_array(dict, "VLAN",
+                               DBUS_TYPE_STRING, add_vlan, service);
+
+       connman_dbus_dict_append_array(dict, "VLAN.Configuration",
+                               DBUS_TYPE_STRING, add_vlanconfig, service);
+
        connman_dbus_dict_append_array(dict, "Domains",
                                DBUS_TYPE_STRING, append_domain, service);
 
@@ -3327,6 +3674,49 @@ static DBusMessage *set_property(DBusConnection *conn,
                if (service == __connman_service_get_default())
                        __connman_timeserver_sync(service);
 
+       } else if (g_str_equal(name, "VLAN.Configuration") &&
+                       !connman_service_id_is_vlan(service)) {
+               DBusMessageIter entry;
+               int count = 0;
+
+               if (service->immutable)
+                       return __connman_error_not_supported(msg);
+
+               if (type != DBUS_TYPE_ARRAY)
+                       return __connman_error_invalid_arguments(msg);
+
+               g_strfreev(service->vids_config_strings);
+               service->vids_config_strings = NULL;
+               dbus_message_iter_recurse(&value, &entry);
+
+               while (dbus_message_iter_get_arg_type(&entry) == 
DBUS_TYPE_STRING) {
+                       const char *val;
+                       int vid;
+
+                       dbus_message_iter_get_basic(&entry, &val);
+                       dbus_message_iter_next(&entry);
+                       if ((vid = atoi(val)) < 0)
+                               return __connman_error_invalid_arguments(msg);
+
+                       if (service->vids_config2go) { /* Check duplicates */
+                               int i;
+
+                               for (i = 0; i < count; i++)
+                                       if (service->vids_config2go[i] == vid)
+                                               break;
+                               if (i < count)
+                                       continue;
+                       }
+
+                       service->vids_config2go = g_renew(int, 
+                               service->vids_config2go, count + 2);
+                       service->vids_config2go[count++] = vid;
+                       service->vids_config2go[count] = -1;
+               }
+
+               vlan_configuration_changed(service);
+
+               service_save(service);
        } else if (g_str_equal(name, "Domains.Configuration")) {
                DBusMessageIter entry;
                GString *str;
@@ -4008,7 +4398,16 @@ static DBusMessage *disconnect_service(DBusConnection 
*conn,
 
 bool __connman_service_remove(struct connman_service *service)
 {
-       if (service->type == CONNMAN_SERVICE_TYPE_ETHERNET ||
+       int index = __connman_service_get_index(service);
+       char *ifname = connman_inet_ifname(index);
+       int vid = -1;
+
+       if (ifname) {
+               vid = connman_inet_get_vlan_vid(ifname);
+               g_free(ifname);
+       }
+
+       if ((service->type == CONNMAN_SERVICE_TYPE_ETHERNET && vid < 0) ||
                        service->type == CONNMAN_SERVICE_TYPE_GADGET)
                return false;
 
@@ -4021,6 +4420,8 @@ bool __connman_service_remove(struct connman_service 
*service)
                return false;
 
        __connman_service_disconnect(service);
+       if (vid >= 0)
+               delete_vlan(service, vid);
 
        g_free(service->passphrase);
        service->passphrase = NULL;
@@ -4463,6 +4864,12 @@ static void service_free(gpointer user_data)
 
        g_strfreev(service->timeservers);
        g_strfreev(service->timeservers_config);
+       g_free(service->vids);
+       g_free(service->vids_config);
+       g_free(service->vids2go);
+       g_free(service->vids_config2go);
+       g_strfreev(service->vids_strings);
+       g_strfreev(service->vids_config_strings);
        g_strfreev(service->nameservers);
        g_strfreev(service->nameservers_config);
        g_strfreev(service->nameservers_auto);
-- 
1.7.10.4

_______________________________________________
connman mailing list
[email protected]
https://lists.connman.net/mailman/listinfo/connman

Reply via email to