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
