--- Makefile.am | 4 + man/systemd.netdev.xml | 72 +- src/libsystemd/sd-network/sd-network.c | 117 +++ src/network/networkctl.c | 153 ++++ src/network/networkd-link.c | 35 + src/network/networkd-manager.c | 36 + src/network/networkd-netdev-gperf.gperf | 3 + src/network/networkd-netdev-ufd-group.c | 298 +++++++ src/network/networkd-netdev-ufd-group.h | 85 ++ src/network/networkd-netdev.c | 36 + src/network/networkd-netdev.h | 6 + src/network/networkd-ufd-daemon.c | 1321 +++++++++++++++++++++++++++++++ src/network/networkd-ufd-daemon.h | 34 + src/network/networkd.c | 7 + src/network/networkd.h | 6 + src/systemd/sd-network.h | 20 + 16 files changed, 2231 insertions(+), 2 deletions(-) create mode 100644 src/network/networkd-netdev-ufd-group.c create mode 100644 src/network/networkd-netdev-ufd-group.h create mode 100644 src/network/networkd-ufd-daemon.c create mode 100644 src/network/networkd-ufd-daemon.h
diff --git a/Makefile.am b/Makefile.am index 45d7a34..604173b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -5575,6 +5575,8 @@ libsystemd_networkd_core_la_SOURCES = \ src/network/networkd-netdev-tuntap.h \ src/network/networkd-netdev-bond.h \ src/network/networkd-netdev-bridge.h \ + src/network/networkd-netdev-ufd-group.h \ + src/network/networkd-ufd-daemon.h \ src/network/networkd-netdev.c \ src/network/networkd-netdev-tunnel.c \ src/network/networkd-netdev-veth.c \ @@ -5586,6 +5588,8 @@ libsystemd_networkd_core_la_SOURCES = \ src/network/networkd-netdev-tuntap.c \ src/network/networkd-netdev-bond.c \ src/network/networkd-netdev-bridge.c \ + src/network/networkd-netdev-ufd-group.c \ + src/network/networkd-ufd-daemon.c \ src/network/networkd-link.c \ src/network/networkd-ipv4ll.c \ src/network/networkd-dhcp4.c \ diff --git a/man/systemd.netdev.xml b/man/systemd.netdev.xml index 7edec36..3c60441 100644 --- a/man/systemd.netdev.xml +++ b/man/systemd.netdev.xml @@ -168,8 +168,8 @@ <literal>ipip</literal>, <literal>gre</literal>, <literal>gretap</literal>, <literal>sit</literal>, <literal>vti</literal>, <literal>veth</literal>, - <literal>tun</literal>, <literal>tap</literal> and - <literal>dummy</literal> + <literal>tun</literal>, <literal>tap</literal>, + <literal>ufd</literal> and <literal>dummy</literal> are supported. This option is compulsory.</para> </listitem> </varlistentry> @@ -553,6 +553,52 @@ </refsect1> <refsect1> + <title>[UFDGroup] Section Options</title> + + <para>The <literal>[UFDGroup]</literal> section is used to define uplink failure detection group parameters. + The section only applies for netdevs of kind <literal>ufd</literal>, and accepts the following key:</para> + + <variablelist class='network-directives'> + + <varlistentry> + <term><varname>Id=</varname></term> + <listitem> + <para>Uplink failure detection group Id. This option is compulsory.</para> + </listitem> + </varlistentry> + </variablelist> + </refsect1> + + <refsect1> + <title>[UFDLink] Section Options</title> + + <para>The <literal>[UFDLink]</literal> section is used to define one or more uplink failure detection links. + The section only applies for netdevs of kind <literal>ufd</literal>, and accepts the following key:</para> + + <variablelist class='network-directives'> + + <varlistentry> + <term><varname>Name=</varname></term> + <listitem> + <para>An interface name or an enumeration of interface names separated by comma. + This option is compulsory.</para> + </listitem> + </varlistentry> + + <varlistentry> + <term><varname>Type=</varname></term> + <listitem> + <para>A string defining the link(s) type. + It can only take the following string values: <literal>uplink</literal> + or <literal>downlink</literal>. This option is compulsory.</para> + </listitem> + </varlistentry> + + </variablelist> + + </refsect1> + + <refsect1> <title>Example</title> <example> <title>/etc/systemd/network/bridge.netdev</title> @@ -645,6 +691,28 @@ Name=veth-peer</programlisting> </example> <example> + <title>/etc/systemd/network/ufd.netdev</title> + <programlisting>[NetDev] +Name=group1 +Kind=ufd + +[UFDGroup] +Id=45 + +[UFDLink] +Name=sw0p5,sw0p10 +Type=uplink + +[UFDLink] +Name=sw0p1 +Type=uplink + +[UFDLink] +Name=sw0p2 +Type=downlink</programlisting> + </example> + + <example> <title>/etc/systemd/network/dummy.netdev</title> <programlisting>[NetDev] Name=dummy-test diff --git a/src/libsystemd/sd-network/sd-network.c b/src/libsystemd/sd-network/sd-network.c index c735cac..d765c97 100644 --- a/src/libsystemd/sd-network/sd-network.c +++ b/src/libsystemd/sd-network/sd-network.c @@ -368,3 +368,120 @@ _public_ int sd_network_monitor_get_timeout(sd_network_monitor *m, uint64_t *tim *timeout_usec = (uint64_t) -1; return 0; } + +_public_ int sd_network_ufd_get_setup_state(int group_id, char **state) { + _cleanup_free_ char *s = NULL, *p = NULL; + int r; + + assert_return(state, -EINVAL); + + if (asprintf(&p, "/run/systemd/netif/ufd/groups/%d", group_id) < 0) + return -ENOMEM; + + r = parse_env_file(p, NEWLINE, "GROUP_STATE", &s, NULL); + if (r == -ENOENT) + return -ENODATA; + if (r < 0) + return r; + if (isempty(s)) + return -ENODATA; + + *state = s; + s = NULL; + + return 0; +} + +_public_ int sd_network_ufd_get_config_file(int group_id, char **filename) { + _cleanup_free_ char *s = NULL, *p = NULL; + int r; + + assert_return(filename, -EINVAL); + + if (asprintf(&p, "/run/systemd/netif/ufd/groups/%d", group_id) < 0) + return -ENOMEM; + + r = parse_env_file(p, NEWLINE, "CONFIG_FILE", &s, NULL); + if (r == -ENOENT) + return -ENODATA; + if (r < 0) + return r; + if (isempty(s)) + return -ENODATA; + + *filename = s; + s = NULL; + + return 0; +} + +static int network_get_ufd_strv(const char *key, int group_id, char ***ret) { + _cleanup_free_ char *p = NULL, *s = NULL; + _cleanup_strv_free_ char **a = NULL; + int r; + + assert_return(ret, -EINVAL); + + if (asprintf(&p, "/run/systemd/netif/ufd/groups/%d", group_id) < 0) + return -ENOMEM; + + r = parse_env_file(p, NEWLINE, key, &s, NULL); + if (r == -ENOENT) + return -ENODATA; + if (r < 0) + return r; + if (isempty(s)) { + *ret = NULL; + return 0; + } + + a = strv_split(s, " "); + if (!a) + return -ENOMEM; + + strv_uniq(a); + r = strv_length(a); + + *ret = a; + a = NULL; + + return r; +} + +_public_ int sd_network_ufd_get_uplinks(int group_id, char ***ret) { + return network_get_ufd_strv("UPLINKS", group_id, ret); +} + +_public_ int sd_network_ufd_get_downlinks(int group_id, char ***ret) { + return network_get_ufd_strv("DOWNLINKS", group_id, ret); +} + +_public_ int sd_network_ufd_get_group_list(char ***ret) { + _cleanup_free_ char *p = NULL, *s = NULL; + _cleanup_strv_free_ char **a = NULL; + int r; + + assert_return(ret, -EINVAL); + + r = parse_env_file("/run/systemd/netif/ufd/state", NEWLINE, "GROUP_LIST", &s, NULL); + if (r == -ENOENT) + return -ENODATA; + if (r < 0) + return r; + if (isempty(s)) { + *ret = NULL; + return 0; + } + + a = strv_split(s, " "); + if (!a) + return -ENOMEM; + + strv_uniq(a); + r = strv_length(a); + + *ret = a; + a = NULL; + + return 0; +} diff --git a/src/network/networkctl.c b/src/network/networkctl.c index aa83f32..89633b4 100644 --- a/src/network/networkctl.c +++ b/src/network/networkctl.c @@ -1008,6 +1008,157 @@ static int link_lldp_status(int argc, char *argv[], void *userdata) { return 0; } +static int ufd_show_one(int group_id) { + _cleanup_free_ char *setup_state = NULL; + _cleanup_free_ char *config_file = NULL; + _cleanup_strv_free_ char **up_links = NULL; + _cleanup_strv_free_ char **down_links = NULL; + const char *green; + const char *yellow; + const char *off_color; + int r; + + green = ansi_highlight_green(); + yellow = ansi_highlight_yellow(); + off_color = ansi_highlight_off(); + + r = sd_network_ufd_get_setup_state(group_id, &setup_state); + if (r < 0) { + log_error("UFD group: %d not found or problems reading UFD files", group_id); + return r; + } + + sd_network_ufd_get_config_file(group_id, &config_file); + sd_network_ufd_get_uplinks(group_id, &up_links); + sd_network_ufd_get_downlinks(group_id, &down_links); + + printf("%s%s%s %s: %d\n", + green, draw_special_char(DRAW_BLACK_CIRCLE), off_color, "UFD Group", group_id); + + printf("Config File: %s\n" + " State: %s%s%s\n", + strna(config_file), + green, strna(setup_state), off_color); + + printf(" Uplinks:\n"); + + if (!strv_isempty(up_links)) { + char **i; + + STRV_FOREACH(i, up_links) { + char ifname[IF_NAMESIZE+1] = ""; + int ifindex = atoi(*i); + + if (ifindex > 0) + printf(" %s%s%s %d: ", + yellow, draw_special_char(DRAW_ARROW), off_color, ifindex); + + if (if_indextoname(ifindex, ifname)) + printf("%s", ifname); + + printf("\n"); + } + } + else + printf(" List is empty\n"); + + printf(" Downlinks:\n"); + + if (!strv_isempty(down_links)) { + char **i; + + STRV_FOREACH(i, down_links) { + char ifname[IF_NAMESIZE+1] = ""; + int ifindex = atoi(*i); + + if (ifindex > 0) + printf(" %s%s%s %d: ", + yellow, draw_special_char(DRAW_ARROW), off_color, ifindex); + + if (if_indextoname(ifindex, ifname)) + printf("%s", ifname); + + printf("\n"); + } + } + else + printf(" List is empty\n"); + + return 0; +} + +static int ufd_show(int argc, char *argv[], void *userdata) { + char **in_arg; + int r; + + if (argc <= 1 && !arg_all) { + _cleanup_strv_free_ char **group_list = NULL; + + sd_network_ufd_get_group_list(&group_list); + + if (!strv_isempty(group_list)) { + char **i; + + STRV_FOREACH(i, group_list) { + const char *on_color = ansi_highlight_green(); + const char *off_color = ansi_highlight_off(); + + printf("%s%s%s %s: %s\n", + on_color, draw_special_char(DRAW_BLACK_CIRCLE), off_color, "UFD Group", *i); + } + } + else + printf("UFD Group list is empty\n"); + } + + if (arg_all) { + _cleanup_strv_free_ char **group_list = NULL; + + sd_network_ufd_get_group_list(&group_list); + + if (!strv_isempty(group_list)) { + char **i; + bool first = true; + + STRV_FOREACH(i, group_list) { + int group_id; + + group_id = atoi(*i); + + if (!first) + fputc('\n', stdout); + + first = false; + + r = ufd_show_one(group_id); + if (r < 0) { + log_error("UFD: Failed to print info"); + return r; + } + } + } + else + printf("UFD Group list is empty\n"); + } + + STRV_FOREACH(in_arg, argv + 1) { + int group_id; + + if (in_arg != argv + 1) + fputc('\n', stdout); + + group_id = atoi(*in_arg); + + r = ufd_show_one(group_id); + if (r < 0) { + log_error("UFD: Failed to print info"); + return r; + } + } + + return 0; +} + static void help(void) { printf("%s [OPTIONS...]\n\n" "Query and control the networking subsystem.\n\n" @@ -1020,6 +1171,7 @@ static void help(void) { " list List links\n" " status [LINK...] Show link status\n" " lldp Show lldp information\n" + " ufd [GROUP_ID...] Show Uplink failure detection groups\n" , program_invocation_short_name); } @@ -1086,6 +1238,7 @@ static int networkctl_main(int argc, char *argv[]) { { "list", VERB_ANY, 1, VERB_DEFAULT, list_links }, { "status", 1, VERB_ANY, 0, link_status }, { "lldp", VERB_ANY, 1, VERB_DEFAULT, link_lldp_status }, + { "ufd", 1, VERB_ANY, 0, ufd_show }, {} }; diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index 310eb6c..9ea9ea8 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -575,6 +575,18 @@ static int route_handler(sd_rtnl *rtnl, sd_rtnl_message *m, void *userdata) { if (link->link_messages == 0) { log_link_debug(link, "routes set"); link->static_configured = true; + + /* this link is now static configured, + so decrease the number of links that needs to be configured. */ + link->manager->links_to_configure --; + + /* check if all links are configured. If yes, start configuring the groups. */ + if ((0 == link->manager->links_to_configure) && (false == link->manager->groups_configured)) { + r = manager_enumerate_groups(link->manager); + if(r < 0) + log_error("Could not enumerate groups error: %s", strerror(-r)); + } + link_client_handler(link); } @@ -606,6 +618,18 @@ static int link_enter_set_routes(Link *link) { if (link->link_messages == 0) { link->static_configured = true; + + /* this link is now static configured, + so decrease the number of links that needs to be configured. */ + link->manager->links_to_configure --; + + /* check if all links are configured. If yes, start configuring the groups. */ + if ((0 == link->manager->links_to_configure) && (false == link->manager->groups_configured)) { + r = manager_enumerate_groups(link->manager); + if(r < 0) + log_error("Could not enumerate groups error: %s", strerror(-r)); + } + link_client_handler(link); } else log_link_debug(link, "setting routes"); @@ -1377,6 +1401,17 @@ static int link_initialized_and_synced(sd_rtnl *rtnl, sd_rtnl_message *m, &link->mac, &network); if (r == -ENOENT) { link_enter_unmanaged(link); + + /* this link is unmanaged, + so decrease the number of links that needs to be configured. */ + link->manager->links_to_configure --; + + /* check if all links are configured. If yes, start configuring the groups. */ + if ((0 == link->manager->links_to_configure) && (false == link->manager->groups_configured)) { + r = manager_enumerate_groups(link->manager); + if(r < 0) + log_error("Could not enumerate groups error: %s", strerror(-r)); + } return 1; } else if (r < 0) return r; diff --git a/src/network/networkd-manager.c b/src/network/networkd-manager.c index 4c90434..8423adb 100644 --- a/src/network/networkd-manager.c +++ b/src/network/networkd-manager.c @@ -83,6 +83,9 @@ int manager_new(Manager **ret) { if (!m) return -ENOMEM; + m->links_to_configure = 0; + m->groups_configured = false; + m->state_file = strdup("/run/systemd/netif/state"); if (!m->state_file) return -ENOMEM; @@ -126,6 +129,10 @@ int manager_new(Manager **ret) { if (!m->netdevs) return -ENOMEM; + m->group_netdevs = hashmap_new(&string_hash_ops); + if (!m->netdevs) + return -ENOMEM; + LIST_HEAD_INIT(m->networks); r = setup_default_address_pool(m); @@ -143,6 +150,7 @@ void manager_free(Manager *m) { NetDev *netdev; Link *link; AddressPool *pool; + Iterator i; if (!m) return; @@ -162,6 +170,15 @@ void manager_free(Manager *m) { while ((network = m->networks)) network_free(network); + /* Close groups. */ + netdev_clear_groups(); + + HASHMAP_FOREACH(netdev, m->group_netdevs, i) { + hashmap_remove(m->group_netdevs, netdev->ifname); + netdev_unref(netdev); + } + hashmap_free(m->group_netdevs); + while ((netdev = hashmap_first(m->netdevs))) netdev_unref(netdev); hashmap_free(m->netdevs); @@ -306,6 +323,23 @@ static int manager_rtnl_process_link(sd_rtnl *rtnl, sd_rtnl_message *message, vo return 1; } +int manager_enumerate_groups(Manager *m) { + NetDev *netdev; + Iterator i; + int r; + int k = 0; + + m->groups_configured = true; + + HASHMAP_FOREACH(netdev, m->group_netdevs, i) { + r = netdev_create_group(netdev); + if (r < 0) + k = r; + } + + return k; +} + int manager_rtnl_enumerate_links(Manager *m) { _cleanup_rtnl_message_unref_ sd_rtnl_message *req = NULL, *reply = NULL; sd_rtnl_message *link; @@ -329,6 +363,8 @@ int manager_rtnl_enumerate_links(Manager *m) { for (link = reply; link; link = sd_rtnl_message_next(link)) { int k; + m->links_to_configure ++; + k = manager_rtnl_process_link(m->rtnl, link, m); if (k < 0) r = k; diff --git a/src/network/networkd-netdev-gperf.gperf b/src/network/networkd-netdev-gperf.gperf index 963c47c..27d3d48 100644 --- a/src/network/networkd-netdev-gperf.gperf +++ b/src/network/networkd-netdev-gperf.gperf @@ -64,3 +64,6 @@ Bond.LACPTransmitRate, config_parse_bond_lacp_rate, 0, Bond.MIIMonitorSec, config_parse_sec, 0, offsetof(Bond, miimon) Bond.UpDelaySec, config_parse_sec, 0, offsetof(Bond, updelay) Bond.DownDelaySec, config_parse_sec, 0, offsetof(Bond, downdelay) +UFDGroup.Id, config_parse_int, 0, offsetof(UfdGroup, id) +UFDLink.Name, config_parse_ufd_link_name, 0, 0 +UFDLink.Type, config_parse_ufd_link_type, 0, 0 diff --git a/src/network/networkd-netdev-ufd-group.c b/src/network/networkd-netdev-ufd-group.c new file mode 100644 index 0000000..9571c3d --- /dev/null +++ b/src/network/networkd-netdev-ufd-group.c @@ -0,0 +1,298 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright (C) 2014 Intel Corporation. All rights reserved. + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "util.h" +#include "missing.h" +#include "conf-parser.h" +#include "network-internal.h" +#include "networkd-ufd-daemon.h" +#include "networkd-netdev-ufd-group.h" + +static const char* const ufd_type_table[_UFD_TYPE_MAX] = { + [UFD_TYPE_UP_LINK] = "uplink", + [UFD_TYPE_DOWN_LINK] = "downlink", +}; + +DEFINE_STRING_TABLE_LOOKUP(ufd_type, UfdType); + +/* clears previous UFD configuration. */ +void ufd_groups_clear(void) { + return ufd_daemon_close(); +} + +static void ufd_group_init(NetDev *n) { + UfdGroup *ufd = UFDGROUP(n); + + assert(n); + assert(ufd); + + /* init the list of links and the hashmap. */ + LIST_HEAD_INIT(ufd->links); + + ufd->ufd_links_by_section = hashmap_new(NULL); + if (!ufd->ufd_links_by_section) + log_error("UFD: Failed to create hashmap for UFD groups"); +} + +static int add_link_to_list(const char *const link_name, + char **ret) { + size_t len; + char *links = *ret; + + assert(link_name); + + len = strlen(link_name); + + if (!links) { + links = malloc(len + 1); + if (!links) + return -ENOMEM; + + strncpy(links, link_name, len + 1); + } + else { + links = realloc(links, strlen(links) + len + 2); + if (!links) + return -ENOMEM; + + strncat(links, ",", 1); + strncat(links, link_name, len); + } + + *ret = links; + + return 0; +} + +static int netdev_create_ufd_group(NetDev *netdev) { + UfdGroup *ufd_group = UFDGROUP(netdev); + UfdLink *ufd_link; + char *up_links = NULL; + char *down_links = NULL; + int r; + + assert(netdev); + assert(ufd_group); + + r = ufd_daemon_init(); + if (r < 0) + return r; + + LIST_FOREACH(links, ufd_link, ufd_group->links) { + switch (ufd_link->type) { + case UFD_TYPE_UP_LINK: + r = add_link_to_list(ufd_link->name, &up_links); + if (r < 0) { + free(up_links); + free(down_links); + return r; + } + + break; + + case UFD_TYPE_DOWN_LINK: + r = add_link_to_list(ufd_link->name, &down_links); + if (r < 0) { + free(up_links); + free(down_links); + return r; + } + + break; + + default: + assert_not_reached("UFD: Received invalid UFD link type."); + } + } + + r = ufd_daemon_set_group(ufd_group->id, netdev->filename, up_links, down_links); + if (r < 0) + log_error("UFD: Could not set Uplink failure detection group Id: %d error: %s", + ufd_group->id, strerror(-r)); + else + log_debug("UFD: Created uplink failure detection group Id: %d", ufd_group->id); + + free(up_links); + free(down_links); + + return r; +} + +static int ufd_link_new_static(UfdGroup *const ufd_group, + const unsigned section, + UfdLink **ret) { + _cleanup_ufdlink_free_ UfdLink *ufd_link = NULL; + + assert(ufd_group); + + /* check if the hashmap exists. */ + if (!ufd_group->ufd_links_by_section) + return -ENOMEM; + + /* search entry in hashmap first. */ + if (section) { + ufd_link = hashmap_get(ufd_group->ufd_links_by_section, UINT_TO_PTR(section)); + if (ufd_link) { + *ret = ufd_link; + ufd_link = NULL; + + return 0; + } + } + + ufd_link = new0(UfdLink, 1); + if (!ufd_link) + return -ENOMEM; + + ufd_link->group = ufd_group; + + LIST_PREPEND(links, ufd_group->links, ufd_link); + + if (section) { + ufd_link->section = section; + hashmap_put(ufd_group->ufd_links_by_section, + UINT_TO_PTR(ufd_link->section), ufd_link); + } + + *ret = ufd_link; + ufd_link = NULL; + + return 0; +} + + +void ufd_link_free(UfdLink *ufd_link) { + if (!ufd_link) + return; + + if (ufd_link->group) { + LIST_REMOVE(links, ufd_link->group->links, ufd_link); + + if (ufd_link->section) + hashmap_remove(ufd_link->group->ufd_links_by_section, + UINT_TO_PTR(ufd_link->section)); + } + + free(ufd_link->name); + + free(ufd_link); +} + +static void ufd_group_done(NetDev *n) { + UfdGroup *ufd = UFDGROUP(n); + UfdLink *ufd_link; + + assert(n); + assert(ufd); + + while ((ufd_link = ufd->links)) + ufd_link_free(ufd_link); + + hashmap_free(ufd->ufd_links_by_section); +} + +int config_parse_ufd_link_name(const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + UfdGroup *ufd_group = userdata; + _cleanup_ufdlink_free_ UfdLink *ufd_link = NULL; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = ufd_link_new_static(ufd_group, section_line, &ufd_link); + if (r < 0) { + log_error("UFD: Failed to allocate a new ufd_link: %s", strerror(-r)); + return r; + } + + r = config_parse_string(unit, filename, line, section, + section_line, lvalue, ltype, + rvalue, &ufd_link->name, userdata); + if (r < 0) { + log_error("UFD: Failed to parse ufd_link name: %s", strerror(-r)); + return r; + } + + ufd_link = NULL; + + return 0; +} + +int config_parse_ufd_link_type(const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + UfdGroup *ufd_group = userdata; + UfdType type; + _cleanup_ufdlink_free_ UfdLink *ufd_link = NULL; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = ufd_link_new_static(ufd_group, section_line, &ufd_link); + if (r < 0) { + log_error("UFD: Failed to allocate a new ufd_link: %s", strerror(-r)); + return r; + } + + if ((type = ufd_type_from_string(rvalue)) < 0) { + log_syntax(unit, LOG_ERR, filename, line, -type, + "UFD: Failed to parse Ufd type, ignoring: %s", rvalue); + return 0; + } + + ufd_link->type = type; + + ufd_link = NULL; + + return 0; +} + +const NetDevVTable ufd_group_vtable = { + .object_size = sizeof(UfdGroup), + .init = ufd_group_init, + .sections = "Match\0NetDev\0UFDGroup\0UFDLink\0", + .done = ufd_group_done, + .create_type = NETDEV_CREATE_GROUP, + .create = netdev_create_ufd_group, +}; diff --git a/src/network/networkd-netdev-ufd-group.h b/src/network/networkd-netdev-ufd-group.h new file mode 100644 index 0000000..f792142 --- /dev/null +++ b/src/network/networkd-netdev-ufd-group.h @@ -0,0 +1,85 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright (C) 2014 Intel Corporation. All rights reserved. + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#pragma once + +typedef struct UfdGroup UfdGroup; +typedef struct UfdLink UfdLink; + +#include "networkd-netdev.h" + +typedef enum UfdType { + UFD_TYPE_UP_LINK, + UFD_TYPE_DOWN_LINK, + _UFD_TYPE_MAX, + _UFD_TYPE_INVALID = -1 +}UfdType; + +struct UfdLink { + char *name; + UfdGroup *group; + + unsigned section; + UfdType type; + LIST_FIELDS(UfdLink, links); +}; + +struct UfdGroup { + NetDev meta; + + int id; + + LIST_HEAD(UfdLink, links); + Hashmap *ufd_links_by_section; +}; + +void ufd_link_free(UfdLink *ufd_link); +void ufd_groups_clear(void); + +DEFINE_TRIVIAL_CLEANUP_FUNC(UfdLink*, ufd_link_free); +#define _cleanup_ufdlink_free_ _cleanup_(ufd_link_freep) + +extern const NetDevVTable ufd_group_vtable; + +const char *ufd_type_to_string(UfdType d) _const_; +UfdType ufd_type_from_string(const char *d) _pure_; + +int config_parse_ufd_link_name(const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata); + +int config_parse_ufd_link_type(const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata); diff --git a/src/network/networkd-netdev.c b/src/network/networkd-netdev.c index 8119205..698331c 100644 --- a/src/network/networkd-netdev.c +++ b/src/network/networkd-netdev.c @@ -49,6 +49,7 @@ const NetDevVTable * const netdev_vtable[_NETDEV_KIND_MAX] = { [NETDEV_KIND_TUN] = &tun_vtable, [NETDEV_KIND_TAP] = &tap_vtable, [NETDEV_KIND_IP6TNL] = &ip6tnl_vtable, + [NETDEV_KIND_UFDGROUP] = &ufd_group_vtable, }; static const char* const netdev_kind_table[_NETDEV_KIND_MAX] = { @@ -70,6 +71,7 @@ static const char* const netdev_kind_table[_NETDEV_KIND_MAX] = { [NETDEV_KIND_TUN] = "tun", [NETDEV_KIND_TAP] = "tap", [NETDEV_KIND_IP6TNL] = "ip6tnl", + [NETDEV_KIND_UFDGROUP] = "ufd", }; DEFINE_STRING_TABLE_LOOKUP(netdev_kind, NetDevKind); @@ -592,6 +594,29 @@ static int netdev_create(NetDev *netdev, Link *link, return 0; } +void netdev_clear_groups(void) { + /* for now only Uplink failure detection groups are defined. */ + return ufd_groups_clear(); +} + +int netdev_create_group(NetDev *netdev) { + int r; + assert(netdev); + + switch (NETDEV_VTABLE(netdev)->create_type) { + case NETDEV_CREATE_GROUP: + r = NETDEV_VTABLE(netdev)->create(netdev); + if (r < 0) + return r; + break; + default: + assert_not_reached("Can not create group netdev"); + + } + + return 0; +} + /* the callback must be called, possibly after a timeout, as otherwise the Link will hang */ int netdev_join(NetDev *netdev, Link *link, sd_rtnl_message_handler_t callback) { int r; @@ -733,6 +758,17 @@ static int netdev_load_one(Manager *manager, const char *filename) { return 0; break; + + case NETDEV_CREATE_GROUP: + r = hashmap_put(manager->group_netdevs, netdev->ifname, netdev); + if (r < 0) { + log_error("Can not add Group '%s' to manager: %s", + netdev->ifname, strerror(-r)); + return 0; + } + + break; + default: break; } diff --git a/src/network/networkd-netdev.h b/src/network/networkd-netdev.h index 3756b1e..3674b59 100644 --- a/src/network/networkd-netdev.h +++ b/src/network/networkd-netdev.h @@ -57,6 +57,7 @@ typedef enum NetDevKind { NETDEV_KIND_DUMMY, NETDEV_KIND_TUN, NETDEV_KIND_TAP, + NETDEV_KIND_UFDGROUP, _NETDEV_KIND_MAX, _NETDEV_KIND_INVALID = -1 } NetDevKind; @@ -74,6 +75,7 @@ typedef enum NetDevCreateType { NETDEV_CREATE_INDEPENDENT, NETDEV_CREATE_MASTER, NETDEV_CREATE_STACKED, + NETDEV_CREATE_GROUP, _NETDEV_CREATE_MAX, _NETDEV_CREATE_INVALID = -1, } NetDevCreateType; @@ -111,6 +113,7 @@ struct NetDev { #include "networkd-netdev-tunnel.h" #include "networkd-netdev-dummy.h" #include "networkd-netdev-tuntap.h" +#include "networkd-netdev-ufd-group.h" struct NetDevVTable { /* How much memory does an object of this unit type need */ @@ -177,6 +180,7 @@ DEFINE_CAST(VETH, Veth); DEFINE_CAST(DUMMY, Dummy); DEFINE_CAST(TUN, TunTap); DEFINE_CAST(TAP, TunTap); +DEFINE_CAST(UFDGROUP, UfdGroup); int netdev_load(Manager *manager); void netdev_drop(NetDev *netdev); @@ -192,6 +196,8 @@ int netdev_set_ifindex(NetDev *netdev, sd_rtnl_message *newlink); int netdev_enslave(NetDev *netdev, Link *link, sd_rtnl_message_handler_t callback); int netdev_get_mac(const char *ifname, struct ether_addr **ret); int netdev_join(NetDev *netdev, Link *link, sd_rtnl_message_handler_t cb); +int netdev_create_group(NetDev *netdev); +void netdev_clear_groups(void); const char *netdev_kind_to_string(NetDevKind d) _const_; NetDevKind netdev_kind_from_string(const char *d) _pure_; diff --git a/src/network/networkd-ufd-daemon.c b/src/network/networkd-ufd-daemon.c new file mode 100644 index 0000000..106465f --- /dev/null +++ b/src/network/networkd-ufd-daemon.c @@ -0,0 +1,1321 @@ + +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright (C) 2014 Intel Corporation. All rights reserved. + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <pthread.h> +#include <errno.h> +#include <sys/socket.h> +#include <linux/genetlink.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <net/if.h> + +#include "sd-network.h" +#include "sd-rtnl.h" +#include "rtnl-util.h" +#include "util.h" +#include "ctype.h" +#include "socket-util.h" +#include "strv.h" +#include "networkd-ufd-daemon.h" + +#define UFD_UPLINK 0x01 +#define UFD_DOWNLINK 0x02 + +#define UFD_RCV_PORT_EVENT 0x00 +#define UFD_IGNORE_PORT_DOWN_EVENT 0x01 +#define UFD_IGNORE_PORT_UP_EVENT 0x02 + +typedef struct UfdDaemon UfdDaemon; +typedef struct UfddPortInfo UfddPortInfo; +typedef struct UfddGroup UfddGroup; +typedef struct UfddPort UfddPort; + +typedef enum PortState { + UFD_PORT_ADMIN_UP = 1, + UFD_PORT_ADMIN_DOWN, + UFD_PORT_OPER_UP, + UFD_PORT_OPER_DOWN, + _UFD_PORT_STATE_MAX, + _UFD_PORT_STATE_INVALID = -1 +} PortState; + +/* struct to hold uplink and downlink info */ +struct UfddPort { + unsigned int if_index; + unsigned char link_type; + unsigned char rcv_port_event; + PortState admin_state; + PortState ufd_state; + UfddPort *next_port; +}; + +/* struct to hold group info */ +struct UfddGroup { + char *state_file; + char *config_file; + unsigned int grp_id; + unsigned int act_uplink_count; + UfddPort *up_link_ports; + UfddPort *down_link_ports; + UfddGroup *next_group; +}; + +/* struct to hold port info */ +struct UfddPortInfo { + unsigned int if_idx; + UfddGroup *grp_info; + UfddPort *if_idx_info; + UfddPortInfo *next_if_idx; +}; + +struct UfdDaemon { + char *state_file; + sd_rtnl *rtnl; + sd_event *event; + UfddPortInfo *port_info; + UfddGroup *group_info; +}; + +/* Global UFD structure. */ +static UfdDaemon g_ufdd; + +static int ufd_port_get_state(int ifindex, + PortState *admin_state, + PortState *oper_state) { + _cleanup_rtnl_message_unref_ sd_rtnl_message *req = NULL, *reply = NULL; + sd_rtnl *rtnl = g_ufdd.rtnl; + sd_rtnl_message *link; + int r; + + assert(admin_state); + assert(oper_state); + assert(rtnl); + + r = sd_rtnl_message_new_link(rtnl, &req, RTM_GETLINK, 0); + if (r < 0) + return r; + + r = sd_rtnl_message_request_dump(req, true); + if (r < 0) + return r; + + r = sd_rtnl_call(rtnl, req, 0, &reply); + if (r < 0) + return r; + + for (link = reply; link; link = sd_rtnl_message_next(link)) { + int link_ifindex; + + r = sd_rtnl_message_link_get_ifindex(link, &link_ifindex); + if (r < 0 || ifindex <= 0) { + log_warning("UFD rtnl: received link message without valid ifindex"); + continue; + } + + if (link_ifindex == ifindex) { + unsigned int flags; + r = sd_rtnl_message_link_get_flags(link, &flags); + if (r < 0) { + log_error("UFD: Could not get link flags"); + return r; + } + + if ((flags & IFF_UP) == IFF_UP) + *admin_state = UFD_PORT_ADMIN_UP; + else + *admin_state = UFD_PORT_ADMIN_DOWN; + + if ((flags & IFF_RUNNING) == IFF_RUNNING) + *oper_state = UFD_PORT_OPER_UP; + else + *oper_state = UFD_PORT_OPER_DOWN; + + break; + } + } + + return 0; +} + +static int port_set_handler(sd_rtnl *rtnl, + sd_rtnl_message *m, + void *userdata) { + int r; + + r = sd_rtnl_message_get_errno(m); + if (r < 0) + log_error("UFD: Failed to set port state, error: %s", strerror(-r)); + + return 1; +} + +static int ufd_port_set_flags(unsigned int ifindex, + unsigned flags) { + _cleanup_rtnl_message_unref_ sd_rtnl_message *req = NULL; + sd_rtnl *rtnl = g_ufdd.rtnl; + int r; + + assert(rtnl); + + r = sd_rtnl_message_new_link(rtnl, &req, RTM_SETLINK, ifindex); + if (r < 0) { + log_error("UFD: Could not allocate RTM_SETLINK message: %s", strerror(-r)); + return r; + } + + r = sd_rtnl_message_link_set_flags(req, flags, IFF_UP); + if (r < 0) { + log_error("UFD: Could not set link flags: %s", strerror(-r)); + return r; + } + + r = sd_rtnl_call_async(rtnl, req, port_set_handler, NULL, 0, NULL); + if (r < 0) { + log_error("UFD: Could not send rtnetlink message: %s", strerror(-r)); + return r; + } + + return 0; +} + +static int ufd_port_info_add(unsigned int port_id, + UfddGroup *grp_ptr, + UfddPort *ufd_port_ptr) { + UfddPortInfo *port_info_ptr = NULL; + UfddPortInfo *cur_ptr = NULL; + + assert(grp_ptr); + assert(ufd_port_ptr); + + port_info_ptr = (UfddPortInfo *)calloc(1, sizeof(UfddPortInfo)); + if (!port_info_ptr) + return -ENOMEM; + + port_info_ptr->if_idx = port_id; + port_info_ptr->grp_info = grp_ptr; + port_info_ptr->if_idx_info = ufd_port_ptr; + port_info_ptr->next_if_idx = NULL; + + /* Add port info into g_ufdd.port_info database */ + if (!g_ufdd.port_info) { + g_ufdd.port_info = port_info_ptr; + } else { + cur_ptr = g_ufdd.port_info; + + while (cur_ptr->next_if_idx) + cur_ptr = cur_ptr->next_if_idx; + + cur_ptr->next_if_idx = port_info_ptr; + } + + return 0; +} + +/* To check if the new ports are already present in ufd_port_info database */ +static bool is_port_present(unsigned int *port_list, + unsigned int port_count, + unsigned int *group_id, + unsigned int *port_id) { + UfddPortInfo *cur_ptr = g_ufdd.port_info; + unsigned int *port_ptr = port_list; + unsigned int i = 0; + + assert(group_id); + assert(port_id); + + if (!port_list || 0 == port_count) + return false; + + if (!cur_ptr) + return false; + + /* Check if any of the new ports is present in g_ufdd.port_info */ + for (i = 0; i < port_count; i++) { + while (cur_ptr) { + if (cur_ptr->if_idx == *port_ptr) { + *group_id = cur_ptr->grp_info->grp_id; + *port_id = cur_ptr->if_idx; + + return true; + } + + cur_ptr = cur_ptr->next_if_idx; + } + + cur_ptr = g_ufdd.port_info; + port_ptr++; + } + + return false; +} + +/* Get group structure from g_ufdd.group_info DB */ +static UfddGroup *get_group(unsigned int group_id) { + UfddGroup *cur_ptr = g_ufdd.group_info; + + while (cur_ptr) { + if (cur_ptr->grp_id == group_id) + return cur_ptr; + + cur_ptr = cur_ptr->next_group; + } + + return NULL; +} + +/* Add Ports list to the Group */ +static int ufd_group_ports_add(UfddGroup *grp_ptr, + unsigned char link_type, + unsigned int *port_list, + unsigned int port_count) { + UfddPort *port_ptr = NULL; + UfddPort *ufd_port_ptr = NULL; + unsigned int act_up_port = 0; + unsigned int *port_to_add = port_list; + unsigned int i; + int r; + + assert(grp_ptr); + + if (!port_to_add || 0 == port_count) { + log_debug("UFD: No ports found to add in the group:%d", grp_ptr->grp_id); + return 0; + } + + if (UFD_UPLINK == link_type) { + ufd_port_ptr = grp_ptr->up_link_ports; + act_up_port = grp_ptr->act_uplink_count; + } else if (UFD_DOWNLINK == link_type) { + ufd_port_ptr = grp_ptr->down_link_ports; + } else { + log_error("UFD: Unknown link_type:%d", link_type); + return -EINVAL; + } + + /* Move the ptr to end of post list */ + if (ufd_port_ptr) { + while (ufd_port_ptr->next_port) + ufd_port_ptr = ufd_port_ptr->next_port; + } + + /* Add ports to the port list of the group */ + for (i = 0; i < port_count; i++) { + port_ptr = (UfddPort *)calloc(1, sizeof(UfddPort)); + if (!port_ptr) + return -ENOMEM; + + port_ptr->if_index = *port_to_add; + + r = ufd_port_get_state(port_ptr->if_index, &port_ptr->admin_state, &port_ptr->ufd_state); + if (r < 0) { + free(port_ptr); + return r; + } + + port_ptr->rcv_port_event = UFD_RCV_PORT_EVENT; + port_ptr->link_type = link_type; + port_ptr->next_port = NULL; + + if ((UFD_UPLINK == link_type) && (UFD_PORT_ADMIN_UP == port_ptr->admin_state) && + (UFD_PORT_OPER_UP == port_ptr->ufd_state)) + act_up_port++; + + if (ufd_port_ptr) { + ufd_port_ptr->next_port = port_ptr; + ufd_port_ptr = ufd_port_ptr->next_port; + } else { + ufd_port_ptr = port_ptr; + if (UFD_UPLINK == link_type) + grp_ptr->up_link_ports = port_ptr; + else + grp_ptr->down_link_ports = port_ptr; + } + + r = ufd_port_info_add(*port_to_add, grp_ptr, port_ptr); + if (r < 0) + return r; + + port_to_add++; + } + + if (UFD_UPLINK == port_ptr->link_type) { + grp_ptr->act_uplink_count = act_up_port; + log_debug("UFD: Active uplink port count:%d for group:%d", grp_ptr->act_uplink_count, grp_ptr->grp_id); + } + + return 0; +} + +/* Update downlink ports based on uplink events */ +static int update_downlink_ports(UfddPort *port_ptr, + PortState port_state) { + unsigned flags = 0; + int r; + + while (port_ptr) { + if ((port_ptr->admin_state == UFD_PORT_ADMIN_UP) && + (port_ptr->ufd_state != port_state)) { + + if (port_state == UFD_PORT_OPER_UP) { + port_ptr->ufd_state = UFD_PORT_OPER_UP; + port_ptr->rcv_port_event = UFD_IGNORE_PORT_UP_EVENT; + flags = IFF_UP | IFF_RUNNING; + } else { + port_ptr->ufd_state = UFD_PORT_OPER_DOWN; + port_ptr->rcv_port_event = UFD_IGNORE_PORT_DOWN_EVENT; + flags &= (unsigned int)~IFF_UP; + } + + r = ufd_port_set_flags(port_ptr->if_index, flags); + if (r < 0) + return r; + } + + port_ptr = port_ptr->next_port; + } + + return 0; +} + +/* Delete port info from g_ufdd.port_info DB */ +static void ufd_port_info_del(unsigned int port_id) { + UfddPortInfo *prev_port_ptr = NULL; + UfddPortInfo *cur_port_ptr = g_ufdd.port_info; + + while (cur_port_ptr) { + if (cur_port_ptr->if_idx == port_id) { + + if (cur_port_ptr == g_ufdd.port_info) { + g_ufdd.port_info = cur_port_ptr->next_if_idx; + } else { + prev_port_ptr->next_if_idx = cur_port_ptr->next_if_idx; + } + + free(cur_port_ptr); + cur_port_ptr = NULL; + + break; + } + + prev_port_ptr = cur_port_ptr; + cur_port_ptr = cur_port_ptr->next_if_idx; + } +} + +/* Delete port from the groupDB */ +static unsigned int ufd_group_port_del(UfddGroup *group_ptr, + char link_type, + unsigned int port_id) { + UfddPort *prev_port_ptr = NULL; + UfddPort *cur_port_ptr = NULL; + unsigned int act_uplink_port = 0; + + assert(group_ptr); + + cur_port_ptr = (link_type == UFD_UPLINK) ? group_ptr->up_link_ports : group_ptr->down_link_ports; + prev_port_ptr = cur_port_ptr; + + while (cur_port_ptr) { + if (cur_port_ptr->if_index == port_id) { + + if (cur_port_ptr == prev_port_ptr) { + if (UFD_UPLINK == link_type) + group_ptr->up_link_ports = cur_port_ptr->next_port; + else + group_ptr->down_link_ports = cur_port_ptr->next_port; + } else { + prev_port_ptr->next_port = cur_port_ptr->next_port; + } + + if ((UFD_PORT_OPER_UP == cur_port_ptr->ufd_state) && (UFD_UPLINK == cur_port_ptr->link_type)) + act_uplink_port = 1; + + log_debug("UFD: Deleted port:%d ufd_state:%d link_state:%d, uplink_port:%d", + port_id, cur_port_ptr->ufd_state, cur_port_ptr->link_type, act_uplink_port); + + free(cur_port_ptr); + cur_port_ptr = NULL; + + /* delete port information from port_info db */ + ufd_port_info_del(port_id); + + return act_uplink_port; + } + + prev_port_ptr = cur_port_ptr; + cur_port_ptr = cur_port_ptr->next_port; + } + + return act_uplink_port; +} + +/* Delete port list from GroupDB */ +static unsigned int ufd_group_port_list_del(UfddGroup *group_ptr, + char link_type, + unsigned int *port_list, + unsigned int port_count) { + unsigned int act_port_count = 0; + unsigned int i = 0; + unsigned int *port_to_del = port_list; + + assert(group_ptr); + + for (i = 0; i < port_count; i++) { + unsigned int count = 0; + + count = ufd_group_port_del(group_ptr, link_type, *port_to_del); + + act_port_count += count; + + port_to_del++; + } + + return act_port_count; +} + +/* Check if all the ports to be deleted are present in group */ +static bool ufd_group_port_check(UfddPort *port_ptr, + unsigned int *port_list, + unsigned int port_count) { + unsigned int port_found_cnt = 0; + unsigned int *port_to_check = port_list; + unsigned int i; + + for (i = 0; i < port_count; i++) { + UfddPort *cur_port_ptr = port_ptr; + + while (cur_port_ptr) { + if (cur_port_ptr->if_index == *port_to_check) { + port_found_cnt++; + break; + } + + cur_port_ptr = cur_port_ptr->next_port; + } + + port_to_check++; + } + + if (port_found_cnt != port_count) + return false; + else + return true; +} + +/* Delete complete portlist of the group */ +static void ufd_group_all_port_del(UfddGroup *group_ptr, + char link_type) { + UfddPort *prev_port_ptr = NULL; + UfddPort *cur_port_ptr = NULL; + + if (UFD_UPLINK == link_type) { + cur_port_ptr = group_ptr->up_link_ports; + group_ptr->up_link_ports = NULL; + } else { + cur_port_ptr = group_ptr->down_link_ports; + group_ptr->down_link_ports = NULL; + } + + while (cur_port_ptr) { + prev_port_ptr = cur_port_ptr; + cur_port_ptr = cur_port_ptr->next_port; + + ufd_port_info_del(prev_port_ptr->if_index); + + free(prev_port_ptr); + prev_port_ptr = NULL; + } +} + +/* Delete group */ +static int ufd_group_del(unsigned int group_id, + unsigned int *uplink_ports, + unsigned int uplink_count, + unsigned int *downlink_ports, + unsigned int downlink_count) { + UfddGroup *group_ptr = NULL; + UfddGroup *cur_grp_ptr = NULL; + UfddGroup *prev_grp_ptr = NULL; + UfddPort *cur_port_ptr = NULL; + unsigned int act_port_count = 0; + int r; + + group_ptr = get_group(group_id); + if (!group_ptr) + return -EINVAL; + + /* If uplink_count and downlink_count are zero, delete all uplink and downlink ports */ + if ((0 == uplink_count) && (0 == downlink_count)) { + log_debug("UFD: Deleting uplink & downlink ports for group:%d", group_id); + + ufd_group_all_port_del(group_ptr, UFD_UPLINK); + ufd_group_all_port_del(group_ptr, UFD_DOWNLINK); + + /* if pointer to up_link_ports and down_link_ports are NULL in the group, delete group */ + if ((!group_ptr->up_link_ports) && (!group_ptr->down_link_ports)) { + cur_grp_ptr = g_ufdd.group_info; + + while (cur_grp_ptr) { + if (cur_grp_ptr->grp_id == group_id) { + + if (cur_grp_ptr == g_ufdd.group_info) + g_ufdd.group_info = cur_grp_ptr->next_group; + else + prev_grp_ptr->next_group = cur_grp_ptr->next_group; + + unlink(cur_grp_ptr->state_file); + free(cur_grp_ptr->state_file); + free(cur_grp_ptr->config_file); + + free(cur_grp_ptr); + cur_grp_ptr = NULL; + + log_debug("UFD: Group: %d deleted", group_id); + + return 0; + } + + prev_grp_ptr = cur_grp_ptr; + cur_grp_ptr = cur_grp_ptr->next_group; + } + + } else { + log_debug("UFD: failed to delete group: %d", group_id); + return -EINVAL; + } + } + + /* delete uplink ports */ + if (uplink_count != 0) { + if (false == ufd_group_port_check(group_ptr->up_link_ports, uplink_ports, uplink_count)) { + log_error("UFD: Invalid uplink ports list group:%d", group_id); + return -EINVAL; + } + + act_port_count = ufd_group_port_list_del(group_ptr, UFD_UPLINK, uplink_ports, uplink_count); + group_ptr->act_uplink_count = group_ptr->act_uplink_count - act_port_count; + + log_debug("UFD: Active uplink ports number for group:%d is %d", group_id, group_ptr->act_uplink_count); + } + + /* delete downlink ports */ + if (downlink_count != 0) { + if (false == ufd_group_port_check(group_ptr->down_link_ports, downlink_ports, downlink_count)) { + log_error("UFD: Invalid uplink ports list group:%d", group_id); + return -EINVAL; + } + + ufd_group_port_list_del(group_ptr, UFD_DOWNLINK, downlink_ports, downlink_count); + } + + /* down downlink port states if no active/up Uplink ports */ + if (0 == group_ptr->act_uplink_count) { + cur_port_ptr = group_ptr->down_link_ports; + + while (cur_port_ptr) { + if (cur_port_ptr->ufd_state != UFD_PORT_OPER_DOWN) { + r = update_downlink_ports(cur_port_ptr, UFD_PORT_OPER_DOWN); + if (r < 0) { + log_error("UFD: Could not update downlink ports state, error: %s", strerror(-r)); + return r; + } + } + + cur_port_ptr = cur_port_ptr->next_port; + } + } + + return 0; +} + +static int verify_ports(unsigned int *uplink_ports, + unsigned int uplink_count, + unsigned int *downlink_ports, + unsigned int downlink_count) { + unsigned int group_nr = 0; + unsigned int port_id = 0; + unsigned int i; + unsigned int j; + + if (true == is_port_present(uplink_ports, uplink_count, &group_nr, &port_id)) { + log_error("UFD: Uplink Port:%d is already mapped to a group:%d", port_id, group_nr); + return -EEXIST; + } + + group_nr = 0; + port_id = 0; + + if (true == is_port_present(downlink_ports, downlink_count, &group_nr, &port_id)) { + log_error("UFD: Downlink Port:%d is already mapped to a group:%d", port_id, group_nr); + return -EEXIST; + } + + for (i = 0; i < uplink_count; i++) + for (j = 0; j < downlink_count; j++) + if (uplink_ports[i] == downlink_ports[j]) { + log_error("UFD: Uplink port %d found in the downlink port list", uplink_ports[i]); + return -EEXIST; + } + + return 0; +} + +/* Add group to the group DB */ +static int ufd_group_add(unsigned int group_id, + unsigned int *uplink_ports, + unsigned int uplink_count, + unsigned int *downlink_ports, + unsigned int downlink_count) { + UfddGroup *group_ptr = NULL; + UfddGroup *cur_ptr = NULL; + UfddPort *cur_port_ptr = NULL; + int r; + bool add_new_group = false; + + cur_ptr = g_ufdd.group_info; + + r = verify_ports(uplink_ports, uplink_count, downlink_ports, downlink_count); + if (r < 0) + return r; + + /* Check if group is already present */ + group_ptr = get_group(group_id); + + if (!group_ptr) { + /*if not present create*/ + group_ptr = (UfddGroup *)calloc(1, sizeof(UfddGroup)); + + if (!group_ptr) + return -ENOMEM; + + group_ptr->grp_id = group_id; + group_ptr->act_uplink_count = 0; + group_ptr->up_link_ports = NULL; + group_ptr->down_link_ports = NULL; + group_ptr->next_group = NULL; + + add_new_group = true; + } + + /* Add uplink ports to the group */ + r = ufd_group_ports_add(group_ptr, UFD_UPLINK, uplink_ports, uplink_count); + if (r < 0) { + int del_err; + log_error("UFD: Uplink Port addition to group:%d failed error: %s", group_id, strerror(-r)); + + del_err = ufd_group_del(group_id, uplink_ports, uplink_count, NULL, 0); + if (del_err < 0) + log_error("UFD: Uplink Port deletion failed. Error: %s", strerror(-del_err)); + + return r; + } + + /* Add downlink ports to the group */ + r = ufd_group_ports_add(group_ptr, UFD_DOWNLINK, downlink_ports, downlink_count); + if (r < 0) { + int del_err; + log_error("UFD: Downlink Port addition to group:%d failed error: %s", group_id, strerror(-r)); + + del_err = ufd_group_del(group_id, NULL, 0, downlink_ports, downlink_count); + if (del_err < 0) + log_error("UFD: Downlink Port deletion failed. Error: %s", strerror(-del_err)); + + return r; + } + + /* Add group to group_info DB if new group */ + if (add_new_group) { + if (!g_ufdd.group_info) { + g_ufdd.group_info = group_ptr; + } else { + while (cur_ptr->next_group) + cur_ptr = cur_ptr->next_group; + + cur_ptr->next_group = group_ptr; + } + + log_debug("UFD: Added UFD Group:%d to UFD database", group_id); + } + + /* down downlink port states if no active/up Uplink ports */ + if (group_ptr->act_uplink_count == 0) { + cur_port_ptr = group_ptr->down_link_ports; + + while (cur_port_ptr) { + if (cur_port_ptr->ufd_state != UFD_PORT_OPER_DOWN) { + r = update_downlink_ports(cur_port_ptr, UFD_PORT_OPER_DOWN); + if (r < 0) { + log_error("UFD: Could not update downlink ports state, error: %s", strerror(-r)); + return r; + } + } + + cur_port_ptr = cur_port_ptr->next_port; + } + } + + return 0; +} + +/* Update UFD DB based on the port event received */ +static int ufd_update_port_event(unsigned int port, + char port_event) { + UfddPortInfo *cur_port_info_ptr = NULL; + UfddGroup *group_ptr = NULL; + UfddPort *port_ptr = NULL; + bool is_port_found = false; + int r; + + cur_port_info_ptr = g_ufdd.port_info; + + while (cur_port_info_ptr) { + + if (cur_port_info_ptr->if_idx == port) { + group_ptr = cur_port_info_ptr->grp_info; + port_ptr = cur_port_info_ptr->if_idx_info; + is_port_found = true; + break; + } + + cur_port_info_ptr = cur_port_info_ptr->next_if_idx; + } + + if (!is_port_found) { + log_error("UFD: Port:%d not found", port); + return -EINVAL; + } + + if (!group_ptr) { + log_error("UFD: Group not found for port:%d", port); + return -EINVAL; + } + + if (!port_ptr) { + log_error("UFD: Port info not found for port:%d", port); + return -EINVAL; + } + + switch (port_event) { + case UFD_PORT_OPER_DOWN: + + if (UFD_RCV_PORT_EVENT == port_ptr->rcv_port_event) { + port_ptr->admin_state = UFD_PORT_ADMIN_UP; + } else { + port_ptr->rcv_port_event--; + break; + } + + if (UFD_PORT_OPER_UP == port_ptr->ufd_state) { + port_ptr->ufd_state = UFD_PORT_OPER_DOWN; + + if (UFD_UPLINK == port_ptr->link_type) { + + group_ptr->act_uplink_count--; + log_debug("UFD: Uplink count decremented: %d", group_ptr->act_uplink_count); + + if (0 == group_ptr->act_uplink_count) { + r = update_downlink_ports(group_ptr->down_link_ports, UFD_PORT_OPER_DOWN); + if (r < 0) { + log_error("UFD: Could not update downlink ports state, error: %s", + strerror(-r)); + return r; + } + } + } + } + + break; + + case UFD_PORT_OPER_UP: + + if (port_ptr->rcv_port_event != UFD_RCV_PORT_EVENT) { + port_ptr->rcv_port_event--; + break; + } + + if (port_ptr->admin_state != UFD_PORT_ADMIN_UP) + break; + + port_ptr->ufd_state = UFD_PORT_OPER_UP; + + if (UFD_UPLINK == port_ptr->link_type) { + group_ptr->act_uplink_count++; + log_debug("UFD: Uplink count incremented: %d", group_ptr->act_uplink_count); + + if (1 == group_ptr->act_uplink_count) { + r = update_downlink_ports(group_ptr->down_link_ports, UFD_PORT_OPER_UP); + if (r < 0) { + log_error("UFD: Could not update downlink ports state, error: %s", + strerror(-r)); + return r; + } + } + } + + break; + + case UFD_PORT_ADMIN_DOWN: + + if (UFD_RCV_PORT_EVENT == port_ptr->rcv_port_event) { + port_ptr->admin_state = UFD_PORT_ADMIN_DOWN; + } else { + port_ptr->rcv_port_event--; + break; + } + + port_ptr->ufd_state = UFD_PORT_OPER_DOWN; + + if (UFD_UPLINK == port_ptr->link_type) { + group_ptr->act_uplink_count--; + log_debug("UFD: Uplink count decremented: %d", group_ptr->act_uplink_count); + + if (0 == group_ptr->act_uplink_count) { + r = update_downlink_ports(group_ptr->down_link_ports, UFD_PORT_OPER_DOWN); + if (r < 0) { + log_error("UFD: Could not update downlink ports state, error: %s", + strerror(-r)); + return r; + } + } + } + + break; + + default: + log_error("UFD: Unknown port Event:%d received on port:%d", port_event, port); + return -EINVAL; + + } + + return 0; +} + +/* Cleanup for UFD db */ +static void ufd_db_cleanup(void) { + UfddGroup *cur_grp_ptr = NULL; + UfddGroup *tmp_grp_ptr = NULL; + UfddPortInfo *cur_port_ptr = NULL; + UfddPortInfo *temp_port_ptr = NULL; + + log_debug("UFD: Free database"); + + cur_grp_ptr = g_ufdd.group_info; + + /* cleanup of group_info DB */ + while (cur_grp_ptr) { + ufd_group_all_port_del(cur_grp_ptr, UFD_UPLINK); + ufd_group_all_port_del(cur_grp_ptr, UFD_DOWNLINK); + + tmp_grp_ptr = cur_grp_ptr; + cur_grp_ptr = cur_grp_ptr->next_group; + + unlink(tmp_grp_ptr->state_file); + free(tmp_grp_ptr->state_file); + free(tmp_grp_ptr->config_file); + + free(tmp_grp_ptr); + tmp_grp_ptr = NULL; + } + + cur_port_ptr = g_ufdd.port_info; + + /* Cleanup of port_info DB */ + while (cur_port_ptr) { + temp_port_ptr = cur_port_ptr; + cur_port_ptr = cur_port_ptr->next_if_idx; + + free(temp_port_ptr); + temp_port_ptr = NULL; + } +} + +static int ufd_rtnl_event(sd_rtnl *rtnl, + sd_rtnl_message *message, + void *userdata) { + unsigned int flags; + char state; + int r; + int ifindex; + + r = sd_rtnl_message_link_get_ifindex(message, &ifindex); + if (r < 0 || ifindex <= 0) { + log_warning("UFD rtnl: received link message without valid ifindex"); + return 0; + } + + r = sd_rtnl_message_link_get_flags(message, &flags); + if (r < 0) { + log_warning("UFD: Could not get link flags"); + return r; + } + + if ((flags & IFF_UP) == IFF_UP) { + if ((flags & IFF_RUNNING) == IFF_RUNNING) + state = UFD_PORT_OPER_UP; + else + state = UFD_PORT_OPER_DOWN; + } else { + state = UFD_PORT_ADMIN_DOWN; + } + + r = ufd_update_port_event(ifindex, state); + if (r < 0) { + log_error("UFD: Could not update port event"); + return 0; + } + + return 1; +} + +static int monitoring_init(UfdDaemon *ufdd) { + int r; + + assert(ufdd); + + r = sd_rtnl_open(&ufdd->rtnl, 1, RTNLGRP_LINK); + if (r < 0) + return r; + + r = sd_rtnl_attach_event(ufdd->rtnl, ufdd->event, 0); + if (r < 0) + return r; + + r = sd_rtnl_add_match(ufdd->rtnl, RTM_NEWLINK, ufd_rtnl_event, NULL); + if (r < 0) + return r; + + r = sd_rtnl_add_match(ufdd->rtnl, RTM_DELLINK, ufd_rtnl_event, NULL); + if (r < 0) + return r; + + return 0; +} + +static int parse_ports_list(char *port_list, + uint32_t **out_port_list, + uint32_t *port_num) { + char *temp_list = NULL; + char *token = NULL; + uint32_t port_count = 0; + uint32_t *port_ptr = NULL; + uint32_t ifindex; + uint32_t i = 0; + + assert(port_list); + + temp_list = (char *) malloc(strlen(port_list) + 1); + if (!temp_list) + return -ENOMEM; + + strncpy(temp_list, port_list, strlen(port_list) + 1); + + /* check for validity of the port_list first */ + token = strtok(port_list, ","); + while (token) { + ifindex = if_nametoindex(token); + + if (0 == ifindex) { + log_error("UFD got invalid portname = %s", token); + free(temp_list); + return -EINVAL; + } + + port_count++; + token = strtok(NULL, ","); + } + + port_ptr = (uint32_t *)malloc(port_count * sizeof(uint32_t)); + if (!port_ptr) { + free(temp_list); + return -ENOMEM; + } + + token = strtok(temp_list, ","); + while (token) { + ifindex = if_nametoindex(token); + *(port_ptr+i) = ifindex; + token = strtok(NULL, ","); + i++; + } + + *out_port_list = port_ptr; + *port_num = port_count; + + free(temp_list); + + return 0; +} + +static int ufd_daemon_save(void) { + _cleanup_free_ char *temp_path = NULL; + _cleanup_fclose_ FILE *f = NULL; + _cleanup_free_ char *group_list = NULL; + UfddGroup *ufd_group; + bool space; + int r; + + assert(g_ufdd.state_file); + + r = fopen_temporary(g_ufdd.state_file, &f, &temp_path); + if (r < 0) + return r; + + fchmod(fileno(f), 0644); + + fprintf(f, "# This is private data. Do not parse.\n"); + + fputs("GROUP_LIST=", f); + space = false; + + ufd_group = g_ufdd.group_info; + while (ufd_group) { + char num[32]; + + if (space) + fputc(' ', f); + + snprintf(num, sizeof(num), "%d", ufd_group->grp_id); + + fputs(num, f); + space = true; + + ufd_group = ufd_group->next_group; + } + + fprintf(f, "\n"); + + r = fflush_and_check(f); + if (r < 0) { + log_error("Failed to save ufd data to %s: %s", g_ufdd.state_file, strerror(-r)); + unlink(g_ufdd.state_file); + unlink(temp_path); + return r; + } + + if (rename(temp_path, g_ufdd.state_file) < 0) { + r = -errno; + unlink(g_ufdd.state_file); + unlink(temp_path); + return r; + } + + return 0; +} + +static int ufd_group_save(UfddGroup *ufd_group) { + _cleanup_free_ char *temp_path = NULL; + _cleanup_fclose_ FILE *f = NULL; + UfddPort *ufd_port; + bool space; + int r; + + assert(ufd_group); + assert(ufd_group->state_file); + + r = fopen_temporary(ufd_group->state_file, &f, &temp_path); + if (r < 0) + return r; + + fchmod(fileno(f), 0644); + + fprintf(f, + "# This is private data. Do not parse.\n" + "GROUP_STATE=configured\n"); + + if (ufd_group->config_file) + fprintf(f, "CONFIG_FILE=%s\n", ufd_group->config_file); + + fputs("UPLINKS=", f); + space = false; + + ufd_port = ufd_group->up_link_ports; + while (ufd_port) { + char num[32]; + + if (space) + fputc(' ', f); + + snprintf(num, sizeof(num), "%d", ufd_port->if_index); + + fputs(num, f); + space = true; + + ufd_port = ufd_port->next_port; + } + + fprintf(f, "\n"); + + fputs("DOWNLINKS=", f); + space = false; + + ufd_port = ufd_group->down_link_ports; + while (ufd_port) { + char num[32]; + + if (space) + fputc(' ', f); + + snprintf(num, sizeof(num), "%d", ufd_port->if_index); + + fputs(num, f); + + space = true; + + ufd_port = ufd_port->next_port; + } + + fprintf(f, "\n"); + + r = fflush_and_check(f); + if (r < 0) { + log_error("Failed to save group data to %s: %s", ufd_group->state_file, strerror(-r)); + unlink(ufd_group->state_file); + unlink(temp_path); + return r; + } + + if (rename(temp_path, ufd_group->state_file) < 0) { + r = -errno; + unlink(ufd_group->state_file); + unlink(temp_path); + return r; + } + + r = ufd_daemon_save(); + if (r < 0) + return r; + + return 0; +} + +int ufd_daemon_set_group(int group_id, + char *config_file, + char *up_links, + char *down_links) { + uint32_t uplink_count = 0; + uint32_t downlink_count = 0; + uint32_t *uplink_ptr = NULL; + uint32_t *downlink_ptr = NULL; + UfddGroup *ufd_group; + int r = 0; + + if (group_id < 0) { + log_error("UFD: Group ID should be a positive number. Got: %d", group_id); + return -EINVAL; + } + + ufd_group = get_group(group_id); + if (ufd_group) { + log_error("UFD: Group %d already configured", group_id); + return -EEXIST; + } + + if (!up_links) { + log_error("UFD: Uplink port list is empty for group %d", group_id); + return -EINVAL; + } + + if (!down_links) { + log_error("UFD: Downlink port list is empty for group %d", group_id); + return -EINVAL; + } + + r = parse_ports_list(up_links, &uplink_ptr, &uplink_count); + if (r < 0) { + free(uplink_ptr); + return r; + } + + r = parse_ports_list(down_links, &downlink_ptr, &downlink_count); + if (r < 0) { + free(uplink_ptr); + free(downlink_ptr); + return r; + } + + r = ufd_group_add(group_id, uplink_ptr, uplink_count, downlink_ptr, downlink_count); + if (r < 0) { + free(uplink_ptr); + free(downlink_ptr); + return r; + } + + ufd_group = get_group(group_id); + if (!ufd_group) { + log_error("UFD: Group %d not found", group_id); + free(uplink_ptr); + free(downlink_ptr); + return -EINVAL; + } + + r = asprintf(&ufd_group->state_file, "/run/systemd/netif/ufd/groups/%d", group_id); + if (r < 0) { + free(uplink_ptr); + free(downlink_ptr); + return -ENOMEM; + } + + ufd_group->config_file = malloc(strlen(config_file) + 1); + if (!ufd_group->config_file) { + free(uplink_ptr); + free(downlink_ptr); + return -ENOMEM; + } + + strncpy(ufd_group->config_file, config_file, strlen(config_file) + 1); + + ufd_group_save(ufd_group); + + free(uplink_ptr); + free(downlink_ptr); + + return r; +} + +int ufd_daemon_init(void) { + int r; + + if (!g_ufdd.rtnl) { + r = monitoring_init(&g_ufdd); + if (r < 0) { + log_error("UFD: Could not init the UFD daemon"); + return r; + } + + g_ufdd.state_file = strdup("/run/systemd/netif/ufd/state"); + if (!g_ufdd.state_file) { + log_error("UFD: Could not init the UFD daemon"); + return -ENOMEM; + } + } + + return 0; +} + +void ufd_daemon_close(void) { + sd_rtnl_unref(g_ufdd.rtnl); + ufd_db_cleanup(); + + unlink(g_ufdd.state_file); + free(g_ufdd.state_file); +} diff --git a/src/network/networkd-ufd-daemon.h b/src/network/networkd-ufd-daemon.h new file mode 100644 index 0000000..b625d6f --- /dev/null +++ b/src/network/networkd-ufd-daemon.h @@ -0,0 +1,34 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright (C) 2014 Intel Corporation. All rights reserved. + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#pragma once + +/* Init the Uplink failure detection daemon. */ +int ufd_daemon_init(void); + +/* Close the Uplink failure detection daemon. */ +void ufd_daemon_close(void); + +/* Set Uplink failure detection group. */ +int ufd_daemon_set_group(int group_id, + char *config_file, + char *up_links, + char *down_links); diff --git a/src/network/networkd.c b/src/network/networkd.c index ced319d..c475582 100644 --- a/src/network/networkd.c +++ b/src/network/networkd.c @@ -69,6 +69,13 @@ int main(int argc, char *argv[]) { log_error("Could not create runtime directory 'lldp': %s", strerror(-r)); + r = mkdir_safe_label("/run/systemd/netif/ufd", 0755, uid, gid); + if (r < 0) + log_error_errno(r, "Could not create runtime directory 'ufd': %m"); + + r = mkdir_safe_label("/run/systemd/netif/ufd/groups", 0755, uid, gid); + if (r < 0) + log_error_errno(r, "Could not create runtime directory 'ufd': %m"); r = drop_privileges(uid, gid, (1ULL << CAP_NET_ADMIN) | diff --git a/src/network/networkd.h b/src/network/networkd.h index 719a75b..bd40143 100644 --- a/src/network/networkd.h +++ b/src/network/networkd.h @@ -206,8 +206,13 @@ struct Manager { char *state_file; + unsigned links_to_configure; + bool groups_configured; + Hashmap *links; Hashmap *netdevs; + Hashmap *group_netdevs; + LIST_HEAD(Network, networks); LIST_HEAD(AddressPool, address_pools); @@ -226,6 +231,7 @@ bool manager_should_reload(Manager *m); int manager_rtnl_enumerate_links(Manager *m); int manager_rtnl_enumerate_addresses(Manager *m); +int manager_enumerate_groups(Manager *m); int manager_rtnl_listen(Manager *m); int manager_udev_listen(Manager *m); diff --git a/src/systemd/sd-network.h b/src/systemd/sd-network.h index 027730d..693db6a 100644 --- a/src/systemd/sd-network.h +++ b/src/systemd/sd-network.h @@ -141,6 +141,26 @@ int sd_network_monitor_get_events(sd_network_monitor *m); /* Get timeout for poll(), as usec value relative to CLOCK_MONOTONIC's epoch */ int sd_network_monitor_get_timeout(sd_network_monitor *m, uint64_t *timeout_usec); +/* Get setup state for uplink failure detection group_id. + * Possible states: + * configured: link configured successfully + * Possible return codes: + * -ENODATA: networkd is not aware of the group_id + */ +int sd_network_ufd_get_setup_state(int group_id, char **state); + +/* Get path to .ufd file applied to group_id */ +int sd_network_ufd_get_config_file(int group_id, char **filename); + +/* Get uplinks for an uplink failure detection group. */ +int sd_network_ufd_get_uplinks(int group_id, char ***uplinks); + +/* Get downlinks for an uplink failure detection group. */ +int sd_network_ufd_get_downlinks(int group_id, char ***downlinks); + +/* Get list of groups configured by the system */ +int sd_network_ufd_get_group_list(char ***groups); + _SD_END_DECLARATIONS; #endif -- 1.9.3 _______________________________________________ systemd-devel mailing list systemd-devel@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/systemd-devel