From 0d4ad91dd4fc831c31a9775b0eadf97fea6cd7f6 Mon Sep 17 00:00:00 2001 From: Alin Rauta Date: Tue, 17 Feb 2015 04:06:57 -0800 Subject: [PATCH 1/1] networkd: add support for Uplink Failure Detection Introduce BindCarrier= to indicate the set of links that determine if the current link should be brought UP or DOWN. [tomegun: add a bit to commit message] --- man/systemd.network.xml | 11 + src/libsystemd/sd-network/sd-network.c | 8 + src/network/networkctl.c | 43 ++- src/network/networkd-link.c | 394 ++++++++++++++++++++++- src/network/networkd-link.h | 3 + src/network/networkd-network-gperf.gperf | 1 + src/network/networkd-network.c | 1 + src/network/networkd.h | 2 +- src/systemd/sd-network.h | 6 + 9 files changed, 438 insertions(+), 31 deletions(-) diff --git a/man/systemd.network.xml b/man/systemd.network.xml index 485876b6a..60252e53a 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -279,6 +279,17 @@ + + BindCarrier= + + A port or a list of ports. When set, controls the + behaviour of the current interface. When all ports in the list + are in an operational down state, the current interface is brought + down. When at least one port has carrier, the current interface + is brought up. + + + Address= diff --git a/src/libsystemd/sd-network/sd-network.c b/src/libsystemd/sd-network/sd-network.c index 297fdac80..db1f6997c 100644 --- a/src/libsystemd/sd-network/sd-network.c +++ b/src/libsystemd/sd-network/sd-network.c @@ -261,6 +261,14 @@ _public_ int sd_network_link_get_domains(int ifindex, char ***ret) { return network_get_link_strv("DOMAINS", ifindex, ret); } +_public_ int sd_network_link_get_carrier_bound_to(int ifindex, char ***ret) { + return network_get_link_strv("CARRIER_BOUND_TO", ifindex, ret); +} + +_public_ int sd_network_link_get_carrier_bound_by(int ifindex, char ***ret) { + return network_get_link_strv("CARRIER_BOUND_BY", ifindex, ret); +} + _public_ int sd_network_link_get_wildcard_domain(int ifindex) { int r; _cleanup_free_ char *p = NULL, *s = NULL; diff --git a/src/network/networkctl.c b/src/network/networkctl.c index aa83f32f5..063751376 100644 --- a/src/network/networkctl.c +++ b/src/network/networkctl.c @@ -508,6 +508,8 @@ static int link_status_one( const char *driver = NULL, *path = NULL, *vendor = NULL, *model = NULL, *link = NULL; const char *on_color_operational, *off_color_operational, *on_color_setup, *off_color_setup; + _cleanup_strv_free_ char **carrier_bound_to = NULL; + _cleanup_strv_free_ char **carrier_bound_by = NULL; struct ether_addr e; unsigned iftype; int r, ifindex; @@ -606,12 +608,15 @@ static int link_status_one( sd_network_link_get_network_file(ifindex, &network); + sd_network_link_get_carrier_bound_to(ifindex, &carrier_bound_to); + sd_network_link_get_carrier_bound_by(ifindex, &carrier_bound_by); + printf("%s%s%s %i: %s\n", on_color_operational, draw_special_char(DRAW_BLACK_CIRCLE), off_color_operational, ifindex, name); - printf(" Link File: %s\n" - "Network File: %s\n" - " Type: %s\n" - " State: %s%s%s (%s%s%s)\n", + printf(" Link File: %s\n" + " Network File: %s\n" + " Type: %s\n" + " State: %s%s%s (%s%s%s)\n", strna(link), strna(network), strna(t), @@ -619,13 +624,13 @@ static int link_status_one( on_color_setup, strna(setup_state), off_color_setup); if (path) - printf(" Path: %s\n", path); + printf(" Path: %s\n", path); if (driver) - printf(" Driver: %s\n", driver); + printf(" Driver: %s\n", driver); if (vendor) - printf(" Vendor: %s\n", vendor); + printf(" Vendor: %s\n", vendor); if (model) - printf(" Model: %s\n", model); + printf(" Model: %s\n", model); if (have_mac) { _cleanup_free_ char *description = NULL; @@ -634,23 +639,29 @@ static int link_status_one( ieee_oui(hwdb, &e, &description); if (description) - printf(" HW Address: %s (%s)\n", ether_addr_to_string(&e, ea), description); + printf(" HW Address: %s (%s)\n", ether_addr_to_string(&e, ea), description); else - printf(" HW Address: %s\n", ether_addr_to_string(&e, ea)); + printf(" HW Address: %s\n", ether_addr_to_string(&e, ea)); } if (mtu > 0) - printf(" MTU: %u\n", mtu); + printf(" MTU: %u\n", mtu); - dump_addresses(rtnl, " Address: ", ifindex); - dump_gateways(rtnl, hwdb, " Gateway: ", ifindex); + dump_addresses(rtnl, " Address: ", ifindex); + dump_gateways(rtnl, hwdb, " Gateway: ", ifindex); if (!strv_isempty(dns)) - dump_list(" DNS: ", dns); + dump_list(" DNS: ", dns); if (!strv_isempty(domains)) - dump_list(" Domain: ", domains); + dump_list(" Domain: ", domains); if (!strv_isempty(ntp)) - dump_list(" NTP: ", ntp); + dump_list(" NTP: ", ntp); + + if (!strv_isempty(carrier_bound_to)) + dump_list("Carrier Bound To: ", carrier_bound_to); + + if (!strv_isempty(carrier_bound_by)) + dump_list("Carrier Bound By: ", carrier_bound_by); return 0; } diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index 0665e1fcf..842ca1ce6 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -272,6 +272,8 @@ static int link_new(Manager *manager, sd_rtnl_message *message, Link **ret) { static void link_free(Link *link) { Address *address; + Iterator i; + Link *carrier; if (!link) return; @@ -309,6 +311,14 @@ static void link_free(Link *link) { udev_device_unref(link->udev_device); + HASHMAP_FOREACH (carrier, link->bound_to_links, i) + hashmap_remove(link->bound_to_links, INT_TO_PTR(carrier->ifindex)); + hashmap_free(link->bound_to_links); + + HASHMAP_FOREACH (carrier, link->bound_by_links, i) + hashmap_remove(link->bound_by_links, INT_TO_PTR(carrier->ifindex)); + hashmap_free(link->bound_by_links); + free(link); } @@ -355,19 +365,6 @@ static void link_set_state(Link *link, LinkState state) { return; } -void link_drop(Link *link) { - if (!link || link->state == LINK_STATE_LINGER) - return; - - link_set_state(link, LINK_STATE_LINGER); - - log_link_debug(link, "link removed"); - - link_unref(link); - - return; -} - static void link_enter_unmanaged(Link *link) { assert(link); @@ -1148,13 +1145,319 @@ static int link_up(Link *link) { return 0; } +static int link_down_handler(sd_rtnl *rtnl, sd_rtnl_message *m, void *userdata) { + _cleanup_link_unref_ Link *link = userdata; + int r; + + assert(link); + + if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) + return 1; + + r = sd_rtnl_message_get_errno(m); + if (r < 0) + log_link_warning_errno(link, -r, "%-*s: could not bring down interface: %m", IFNAMSIZ, link->ifname); + + return 1; +} + +static int link_down(Link *link) { + _cleanup_rtnl_message_unref_ sd_rtnl_message *req = NULL; + int r; + + assert(link); + assert(link->manager); + assert(link->manager->rtnl); + + log_link_debug(link, "bringing link down"); + + r = sd_rtnl_message_new_link(link->manager->rtnl, &req, + RTM_SETLINK, link->ifindex); + if (r < 0) { + log_link_error(link, "Could not allocate RTM_SETLINK message"); + return r; + } + + r = sd_rtnl_message_link_set_flags(req, 0, IFF_UP); + if (r < 0) { + log_link_error(link, "Could not set link flags: %s", + strerror(-r)); + return r; + } + + r = sd_rtnl_call_async(link->manager->rtnl, req, link_down_handler, link, + 0, NULL); + if (r < 0) { + log_link_error(link, + "Could not send rtnetlink message: %s", + strerror(-r)); + return r; + } + + link_ref(link); + + return 0; +} + +static int link_handle_bound_to_list(Link *link) { + Link *l; + Iterator i; + int r; + bool required_up = false; + bool link_is_up = false; + + assert(link); + + if (hashmap_isempty(link->bound_to_links)) + return 0; + + if (link->flags & IFF_UP) + link_is_up = true; + + HASHMAP_FOREACH (l, link->bound_to_links, i) + if (link_has_carrier(l)) { + required_up = true; + break; + } + + if (!required_up && link_is_up) { + r = link_down(link); + if (r < 0) + return r; + } else if (required_up && !link_is_up) { + r = link_up(link); + if (r < 0) + return r; + } + + return 0; +} + +static int link_handle_bound_by_list(Link *link) { + Iterator i; + Link *l; + int r; + + assert(link); + + if (hashmap_isempty(link->bound_by_links)) + return 0; + + HASHMAP_FOREACH (l, link->bound_by_links, i) { + r = link_handle_bound_to_list(l); + if (r < 0) + return r; + } + + return 0; +} + +static int link_put_carrier(Link *link, Link *carrier, Hashmap **h) { + int r; + + assert(link); + assert(carrier); + + if (link == carrier) + return 0; + + if (hashmap_get(*h, INT_TO_PTR(carrier->ifindex))) + return 0; + + r = hashmap_ensure_allocated(h, NULL); + if (r < 0) + return r; + + r = hashmap_put(*h, INT_TO_PTR(carrier->ifindex), carrier); + if (r < 0) + return r; + + return 0; +} + +static int link_new_bound_by_list(Link *link) { + Manager *m; + Link *carrier; + Iterator i; + int r; + bool list_updated = false; + + assert(link); + assert(link->manager); + + m = link->manager; + + HASHMAP_FOREACH (carrier, m->links, i) { + if (!carrier->network) + continue; + + if (strv_isempty(carrier->network->bind_carrier)) + continue; + + if (strv_fnmatch(carrier->network->bind_carrier, link->ifname, 0)) { + r = link_put_carrier(link, carrier, &link->bound_by_links); + if (r < 0) + return r; + + list_updated = true; + } + } + + if (list_updated) + link_save(link); + + HASHMAP_FOREACH (carrier, link->bound_by_links, i) { + r = link_put_carrier(carrier, link, &carrier->bound_to_links); + if (r < 0) + return r; + + link_save(carrier); + } + + return 0; +} + +static int link_new_bound_to_list(Link *link) { + Manager *m; + Link *carrier; + Iterator i; + int r; + bool list_updated = false; + + assert(link); + assert(link->manager); + + if (!link->network) + return 0; + + if (strv_isempty(link->network->bind_carrier)) + return 0; + + m = link->manager; + + HASHMAP_FOREACH (carrier, m->links, i) { + if (strv_fnmatch(link->network->bind_carrier, carrier->ifname, 0)) { + r = link_put_carrier(link, carrier, &link->bound_to_links); + if (r < 0) + return r; + + list_updated = true; + } + } + + if (list_updated) + link_save(link); + + HASHMAP_FOREACH (carrier, link->bound_to_links, i) { + r = link_put_carrier(carrier, link, &carrier->bound_by_links); + if (r < 0) + return r; + + link_save(carrier); + } + + return 0; +} + +static int link_new_carrier_maps(Link *link) { + int r; + + r = link_new_bound_by_list(link); + if (r < 0) + return r; + + r = link_handle_bound_by_list(link); + if (r < 0) + return r; + + r = link_new_bound_to_list(link); + if (r < 0) + return r; + + r = link_handle_bound_to_list(link); + if (r < 0) + return r; + + return 0; +} + +static void link_free_bound_to_list(Link *link) { + Link *bound_to; + Iterator i; + + HASHMAP_FOREACH (bound_to, link->bound_to_links, i) { + hashmap_remove(link->bound_to_links, INT_TO_PTR(bound_to->ifindex)); + + if (hashmap_remove(bound_to->bound_by_links, INT_TO_PTR(link->ifindex))) + link_save(bound_to); + } + + return; +} + +static void link_free_bound_by_list(Link *link) { + Link *bound_by; + Iterator i; + + HASHMAP_FOREACH (bound_by, link->bound_by_links, i) { + hashmap_remove(link->bound_by_links, INT_TO_PTR(bound_by->ifindex)); + + if (hashmap_remove(bound_by->bound_to_links, INT_TO_PTR(link->ifindex))) { + link_save(bound_by); + link_handle_bound_to_list(bound_by); + } + } + + return; +} + +static void link_free_carrier_maps(Link *link) { + bool list_updated = false; + + assert(link); + + if (!hashmap_isempty(link->bound_to_links)) { + link_free_bound_to_list(link); + list_updated = true; + } + + if (!hashmap_isempty(link->bound_by_links)) { + link_free_bound_by_list(link); + list_updated = true; + } + + if (list_updated) + link_save(link); + + return; +} + +void link_drop(Link *link) { + if (!link || link->state == LINK_STATE_LINGER) + return; + + link_set_state(link, LINK_STATE_LINGER); + + link_free_carrier_maps(link); + + log_link_debug(link, "link removed"); + + link_unref(link); + + return; +} + static int link_joined(Link *link) { int r; assert(link); assert(link->network); - if (!(link->flags & IFF_UP)) { + if (!hashmap_isempty(link->bound_to_links)) { + r = link_handle_bound_to_list(link); + if (r < 0) + return r; + } else if (!(link->flags & IFF_UP)) { r = link_up(link); if (r < 0) { link_enter_failed(link); @@ -1429,6 +1732,14 @@ static int link_initialized_and_synced(sd_rtnl *rtnl, sd_rtnl_message *m, log_link_debug(link, "link state is up-to-date"); + r = link_new_bound_by_list(link); + if (r < 0) + return r; + + r = link_handle_bound_by_list(link); + if (r < 0) + return r; + r = network_get(link->manager, link->udev_device, link->ifname, &link->mac, &network); if (r == -ENOENT) { @@ -1452,6 +1763,10 @@ static int link_initialized_and_synced(sd_rtnl *rtnl, sd_rtnl_message *m, if (r < 0) return r; + r = link_new_bound_to_list(link); + if (r < 0) + return r; + r = link_configure(link); if (r < 0) return r; @@ -1729,6 +2044,10 @@ static int link_carrier_gained(Link *link) { } } + r = link_handle_bound_by_list(link); + if (r < 0) + return r; + return 0; } @@ -1743,6 +2062,10 @@ static int link_carrier_lost(Link *link) { return r; } + r = link_handle_bound_by_list(link); + if (r < 0) + return r; + return 0; } @@ -1782,16 +2105,26 @@ int link_update(Link *link, sd_rtnl_message *m) { link_ref(link); log_link_info(link, "link readded"); link_set_state(link, LINK_STATE_ENSLAVING); + + r = link_new_carrier_maps(link); + if (r < 0) + return r; } r = sd_rtnl_message_read_string(m, IFLA_IFNAME, &ifname); if (r >= 0 && !streq(ifname, link->ifname)) { log_link_info(link, "renamed to %s", ifname); + link_free_carrier_maps(link); + free(link->ifname); link->ifname = strdup(ifname); if (!link->ifname) return -ENOMEM; + + r = link_new_carrier_maps(link); + if (r < 0) + return r; } r = sd_rtnl_message_read_u32(m, IFLA_MTU, &mtu); @@ -2060,6 +2393,39 @@ int link_save(Link *link) { llmnr_support_to_string(link->network->llmnr)); } + if (!hashmap_isempty(link->bound_to_links)) { + Link *carrier; + Iterator i; + bool space = false; + + fputs("CARRIER_BOUND_TO=", f); + HASHMAP_FOREACH(carrier, link->bound_to_links, i) { + if (space) + fputc(' ', f); + fputs(carrier->ifname, f); + space = true; + } + + fputs("\n", f); + } + + if (!hashmap_isempty(link->bound_by_links)) { + Link *carrier; + Iterator i; + bool space = false; + + fputs("CARRIER_BOUND_BY=", f); + space = false; + HASHMAP_FOREACH(carrier, link->bound_by_links, i) { + if (space) + fputc(' ', f); + fputs(carrier->ifname, f); + space = true; + } + + fputs("\n", f); + } + if (link->dhcp_lease) { assert(link->network); diff --git a/src/network/networkd-link.h b/src/network/networkd-link.h index cec158e47..479098cb2 100644 --- a/src/network/networkd-link.h +++ b/src/network/networkd-link.h @@ -85,6 +85,9 @@ struct Link { sd_lldp *lldp; char *lldp_file; + + Hashmap *bound_by_links; + Hashmap *bound_to_links; }; Link *link_unref(Link *link); diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index fc277df94..b0c23a789 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -48,6 +48,7 @@ Network.LLMNR, config_parse_llmnr, 0, Network.NTP, config_parse_strv, 0, offsetof(Network, ntp) Network.IPForward, config_parse_address_family_boolean,0, offsetof(Network, ip_forward) Network.IPMasquerade, config_parse_bool, 0, offsetof(Network, ip_masquerade) +Network.BindCarrier, config_parse_strv, 0, offsetof(Network, bind_carrier) Address.Address, config_parse_address, 0, 0 Address.Peer, config_parse_address, 0, 0 Address.Broadcast, config_parse_broadcast, 0, 0 diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index c6cf00a5e..0ba0c7571 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -208,6 +208,7 @@ void network_free(Network *network) { strv_free(network->ntp); strv_free(network->dns); strv_free(network->domains); + strv_free(network->bind_carrier); netdev_unref(network->bridge); diff --git a/src/network/networkd.h b/src/network/networkd.h index bdb2f20e2..e75746f2c 100644 --- a/src/network/networkd.h +++ b/src/network/networkd.h @@ -151,7 +151,7 @@ struct Network { Hashmap *fdb_entries_by_section; bool wildcard_domain; - char **domains, **dns, **ntp; + char **domains, **dns, **ntp, **bind_carrier; LLMNRSupport llmnr; diff --git a/src/systemd/sd-network.h b/src/systemd/sd-network.h index 027730d11..4d96c867d 100644 --- a/src/systemd/sd-network.h +++ b/src/systemd/sd-network.h @@ -116,6 +116,12 @@ int sd_network_link_get_lldp(int ifindex, char **lldp); /* Get the DNS domain names for a given link. */ int sd_network_link_get_domains(int ifindex, char ***domains); +/* Get the CARRIERS to which current link is bound to. */ +int sd_network_link_get_carrier_bound_to(int ifindex, char ***carriers); + +/* Get the CARRIERS that are bound to current link. */ +int sd_network_link_get_carrier_bound_by(int ifindex, char ***carriers); + /* Returns whether or not domains that don't match any link should be resolved * on this link. 1 for yes, 0 for no and negative value for error */ int sd_network_link_get_wildcard_domain(int ifindex); -- 2.30.2