diff --git a/man/networkctl.xml b/man/networkctl.xml index 955d199949..1fc94d7eda 100644 --- a/man/networkctl.xml +++ b/man/networkctl.xml @@ -118,21 +118,22 @@ carrier - the link has a carrier, or for bond master, all bonding slave network interfaces are - enslaved to the master. + the link has a carrier, or for bond or bridge master, all bonding or bridge slave + network interfaces are enslaved to the master. degraded the link has carrier and addresses valid on the local link configured, or for bond - master, one of the bonding slave network interfaces is in off, no-carrier, or dormant + or bridge master, one of the bonding or bridge slave network interfaces is in off, + no-carrier, or dormant enslaved - the link has carrier and is enslaved to other network interfaces + the link has carrier and is enslaved to bond or bridge master network interface diff --git a/man/systemd.network.xml b/man/systemd.network.xml index fc2ac4b505..2058ba74a3 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -318,10 +318,10 @@ LinkLocalAddressing= - Enables link-local address autoconfiguration. Accepts - yes, no, - ipv4, or ipv6. Defaults to - ipv6. + Enables link-local address autoconfiguration. Accepts yes, + no, ipv4, or ipv6. If + Bridge= is set, defaults to no, and if not, + defaults to ipv6. diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index e6a1cfd5d2..22b639bad6 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -158,7 +158,7 @@ static bool link_ipv6_enabled(Link *link) { if (!socket_ipv6_is_supported()) return false; - if (link->network->bridge || link->network->bond) + if (link->network->bond) return false; if (manager_sysctl_ipv6_enabled(link->manager) == 0) @@ -328,8 +328,39 @@ static int link_enable_ipv6(Link *link) { return 0; } -void link_update_operstate(Link *link, bool also_update_bond_master) { +static bool link_is_enslaved(Link *link) { + if (link->flags & IFF_SLAVE) + /* Even if the link is not managed by networkd, honor IFF_SLAVE flag. */ + return true; + + if (!link->enslaved_raw) + return false; + + if (!link->network) + return false; + + if (link->network->bridge) + /* TODO: support the case when link is not managed by networkd. */ + return true; + + return false; +} + +static void link_update_master_operstate(Link *link, NetDev *netdev) { + Link *master; + + if (!netdev) + return; + + if (link_get(link->manager, netdev->ifindex, &master) < 0) + return; + + link_update_operstate(master, true); +} + +void link_update_operstate(Link *link, bool also_update_master) { LinkOperationalState operstate; + Iterator i; assert(link); @@ -338,7 +369,6 @@ void link_update_operstate(Link *link, bool also_update_bond_master) { else if (link_has_carrier(link)) { Address *address; uint8_t scope = RT_SCOPE_NOWHERE; - Iterator i; /* if we have carrier, check what addresses we have */ SET_FOREACH(address, link->addresses, i) { @@ -373,15 +403,13 @@ void link_update_operstate(Link *link, bool also_update_bond_master) { operstate = LINK_OPERSTATE_OFF; if (IN_SET(operstate, LINK_OPERSTATE_DEGRADED, LINK_OPERSTATE_CARRIER) && - link->flags & IFF_SLAVE) + link_is_enslaved(link)) operstate = LINK_OPERSTATE_ENSLAVED; - if (IN_SET(operstate, LINK_OPERSTATE_CARRIER, LINK_OPERSTATE_ENSLAVED, LINK_OPERSTATE_ROUTABLE) && - !hashmap_isempty(link->bond_slaves)) { - Iterator i; + if (IN_SET(operstate, LINK_OPERSTATE_CARRIER, LINK_OPERSTATE_ENSLAVED, LINK_OPERSTATE_ROUTABLE)) { Link *slave; - HASHMAP_FOREACH(slave, link->bond_slaves, i) { + HASHMAP_FOREACH(slave, link->slaves, i) { link_update_operstate(slave, false); if (IN_SET(slave->operstate, @@ -396,13 +424,9 @@ void link_update_operstate(Link *link, bool also_update_bond_master) { link_dirty(link); } - if (also_update_bond_master && link->network && link->network->bond) { - Link *master; - - if (link_get(link->manager, link->network->bond->ifindex, &master) < 0) - return; - - link_update_operstate(master, true); + if (also_update_master && link->network) { + link_update_master_operstate(link, link->network->bond); + link_update_master_operstate(link, link->network->bridge); } } @@ -592,8 +616,8 @@ static void link_detach_from_manager(Link *link) { } static Link *link_free(Link *link) { + Link *carrier, *master; Address *address; - Link *carrier; Route *route; Iterator i; @@ -659,7 +683,17 @@ static Link *link_free(Link *link) { hashmap_remove(link->bound_by_links, INT_TO_PTR(carrier->ifindex)); hashmap_free(link->bound_by_links); - hashmap_free(link->bond_slaves); + hashmap_free(link->slaves); + + if (link->network) { + if (link->network->bond && + link_get(link->manager, link->network->bond->ifindex, &master) >= 0) + (void) hashmap_remove(master->slaves, INT_TO_PTR(link->ifindex)); + + if (link->network->bridge && + link_get(link->manager, link->network->bridge->ifindex, &master) >= 0) + (void) hashmap_remove(master->slaves, INT_TO_PTR(link->ifindex)); + } return mfree(link); } @@ -953,23 +987,20 @@ void link_check_ready(Link *link) { !link->ipv4ll_route) return; - if (!link->network->bridge) { + if (link_ipv6ll_enabled(link) && + in_addr_is_null(AF_INET6, (const union in_addr_union*) &link->ipv6ll_address)) + return; - if (link_ipv6ll_enabled(link) && - in_addr_is_null(AF_INET6, (const union in_addr_union*) &link->ipv6ll_address)) - return; + if ((link_dhcp4_enabled(link) && !link_dhcp6_enabled(link) && + !link->dhcp4_configured) || + (link_dhcp6_enabled(link) && !link_dhcp4_enabled(link) && + !link->dhcp6_configured) || + (link_dhcp4_enabled(link) && link_dhcp6_enabled(link) && + !link->dhcp4_configured && !link->dhcp6_configured)) + return; - if ((link_dhcp4_enabled(link) && !link_dhcp6_enabled(link) && - !link->dhcp4_configured) || - (link_dhcp6_enabled(link) && !link_dhcp4_enabled(link) && - !link->dhcp6_configured) || - (link_dhcp4_enabled(link) && link_dhcp6_enabled(link) && - !link->dhcp4_configured && !link->dhcp6_configured)) - return; - - if (link_ipv6_accept_ra_enabled(link) && !link->ndisc_configured) - return; - } + if (link_ipv6_accept_ra_enabled(link) && !link->ndisc_configured) + return; if (link->state != LINK_STATE_CONFIGURED) link_enter_configured(link); @@ -1414,7 +1445,7 @@ int link_set_mtu(Link *link, uint32_t mtu) { return log_link_error_errno(link, r, "Could not allocate RTM_SETLINK message: %m"); /* If IPv6 not configured (no static IPv6 address and IPv6LL autoconfiguration is disabled) - * for this interface, or if it is a bridge slave, then disable IPv6 else enable it. */ + * for this interface, then disable IPv6 else enable it. */ (void) link_enable_ipv6(link); /* IPv6 protocol requires a minimum MTU of IPV6_MTU_MIN(1280) bytes @@ -1665,23 +1696,22 @@ static int link_set_bond(Link *link) { return r; } -static int link_append_bond_slave(Link *link) { +static int link_append_to_master(Link *link, NetDev *netdev) { Link *master; int r; assert(link); - assert(link->network); - assert(link->network->bond); + assert(netdev); - r = link_get(link->manager, link->network->bond->ifindex, &master); + r = link_get(link->manager, netdev->ifindex, &master); if (r < 0) return r; - r = hashmap_ensure_allocated(&master->bond_slaves, NULL); + r = hashmap_ensure_allocated(&master->slaves, NULL); if (r < 0) return r; - r = hashmap_put(master->bond_slaves, INT_TO_PTR(link->ifindex), link); + r = hashmap_put(master->slaves, INT_TO_PTR(link->ifindex), link); if (r < 0) return r; @@ -2478,6 +2508,10 @@ static int link_joined(Link *link) { r = link_set_bridge(link); if (r < 0) log_link_error_errno(link, r, "Could not set bridge message: %m"); + + r = link_append_to_master(link, link->network->bridge); + if (r < 0) + log_link_error_errno(link, r, "Failed to add to bridge master's slave list: %m"); } if (link->network->bond) { @@ -2485,7 +2519,7 @@ static int link_joined(Link *link) { if (r < 0) log_link_error_errno(link, r, "Could not set bond message: %m"); - r = link_append_bond_slave(link); + r = link_append_to_master(link, link->network->bond); if (r < 0) log_link_error_errno(link, r, "Failed to add to bond master's slave list: %m"); } @@ -2511,6 +2545,8 @@ static int netdev_join_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *li assert(link); assert(link->network); + assert(link->enslaving > 0); + assert(!link->enslaved_raw); link->enslaving--; @@ -2525,8 +2561,10 @@ static int netdev_join_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *li } else log_link_debug(link, "Joined netdev"); - if (link->enslaving <= 0) + if (link->enslaving == 0) { + link->enslaved_raw = true; link_joined(link); + } return 1; } @@ -2543,12 +2581,8 @@ static int link_enter_join_netdev(Link *link) { link_set_state(link, LINK_STATE_CONFIGURING); link_dirty(link); - - if (!link->network->bridge && - !link->network->bond && - !link->network->vrf && - hashmap_isempty(link->network->stacked_netdevs)) - return link_joined(link); + link->enslaving = 0; + link->enslaved_raw = false; if (link->network->bond) { if (link->network->bond->state == NETDEV_STATE_READY && @@ -2560,6 +2594,8 @@ static int link_enter_join_netdev(Link *link) { LOG_NETDEV_INTERFACE(link->network->bond), LOG_LINK_MESSAGE(link, "Enslaving by '%s'", link->network->bond->ifname)); + link->enslaving++; + r = netdev_join(link->network->bond, link, netdev_join_handler); if (r < 0) { log_struct_errno(LOG_WARNING, r, @@ -2569,8 +2605,6 @@ static int link_enter_join_netdev(Link *link) { link_enter_failed(link); return r; } - - link->enslaving++; } if (link->network->bridge) { @@ -2579,6 +2613,8 @@ static int link_enter_join_netdev(Link *link) { LOG_NETDEV_INTERFACE(link->network->bridge), LOG_LINK_MESSAGE(link, "Enslaving by '%s'", link->network->bridge->ifname)); + link->enslaving++; + r = netdev_join(link->network->bridge, link, netdev_join_handler); if (r < 0) { log_struct_errno(LOG_WARNING, r, @@ -2588,8 +2624,6 @@ static int link_enter_join_netdev(Link *link) { link_enter_failed(link); return r; } - - link->enslaving++; } if (link->network->vrf) { @@ -2598,6 +2632,8 @@ static int link_enter_join_netdev(Link *link) { LOG_NETDEV_INTERFACE(link->network->vrf), LOG_LINK_MESSAGE(link, "Enslaving by '%s'", link->network->vrf->ifname)); + link->enslaving++; + r = netdev_join(link->network->vrf, link, netdev_join_handler); if (r < 0) { log_struct_errno(LOG_WARNING, r, @@ -2607,8 +2643,6 @@ static int link_enter_join_netdev(Link *link) { link_enter_failed(link); return r; } - - link->enslaving++; } HASHMAP_FOREACH(netdev, link->network->stacked_netdevs, i) { @@ -2623,6 +2657,8 @@ static int link_enter_join_netdev(Link *link) { LOG_NETDEV_INTERFACE(netdev), LOG_LINK_MESSAGE(link, "Enslaving by '%s'", netdev->ifname)); + link->enslaving++; + r = netdev_join(netdev, link, netdev_join_handler); if (r < 0) { log_struct_errno(LOG_WARNING, r, @@ -2632,10 +2668,11 @@ static int link_enter_join_netdev(Link *link) { link_enter_failed(link); return r; } - - link->enslaving++; } + if (link->enslaving == 0) + return link_joined(link); + return 0; } diff --git a/src/network/networkd-link.h b/src/network/networkd-link.h index 37be3e3bb3..8b88d3de5d 100644 --- a/src/network/networkd-link.h +++ b/src/network/networkd-link.h @@ -76,6 +76,8 @@ typedef struct Link { unsigned routing_policy_rule_messages; unsigned routing_policy_rule_remove_messages; unsigned enslaving; + /* link_is_enslaved() has additional checks. So, it is named _raw. */ + bool enslaved_raw; Set *addresses; Set *addresses_foreign; @@ -129,7 +131,7 @@ typedef struct Link { Hashmap *bound_by_links; Hashmap *bound_to_links; - Hashmap *bond_slaves; + Hashmap *slaves; } Link; typedef int (*link_netlink_message_handler_t)(sd_netlink*, sd_netlink_message*, Link*); diff --git a/src/network/networkd-manager.c b/src/network/networkd-manager.c index 8eb6e86c33..00f6545e24 100644 --- a/src/network/networkd-manager.c +++ b/src/network/networkd-manager.c @@ -1427,9 +1427,6 @@ void manager_free(Manager *m) { sd_netlink_unref(m->genl); sd_resolve_unref(m->resolve); - while ((network = m->networks)) - network_free(network); - while ((link = hashmap_first(m->dhcp6_prefixes))) manager_dhcp6_prefix_remove_all(m, link); hashmap_free(m->dhcp6_prefixes); @@ -1445,6 +1442,9 @@ void manager_free(Manager *m) { m->links_requesting_uuid = set_free(m->links_requesting_uuid); set_free(m->duids_requesting_uuid); + while ((network = m->networks)) + network_free(network); + hashmap_free(m->networks_by_name); m->netdevs = hashmap_free_with_destructor(m->netdevs, netdev_unref); diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index a83df49038..dff542e194 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -141,7 +141,10 @@ static int network_verify(Network *network) { } if (network->link_local < 0) - network->link_local = ADDRESS_FAMILY_IPV6; + network->link_local = network->bridge ? ADDRESS_FAMILY_NO : ADDRESS_FAMILY_IPV6; + + if (network->ipv6_accept_ra < 0 && network->bridge) + network->ipv6_accept_ra = false; /* IPMasquerade=yes implies IPForward=yes */ if (network->ip_masquerade) diff --git a/test/test-network/conf/bridge99-ignore-carrier-loss.network b/test/test-network/conf/bridge99-ignore-carrier-loss.network index 922d57091e..d4741d687d 100644 --- a/test/test-network/conf/bridge99-ignore-carrier-loss.network +++ b/test/test-network/conf/bridge99-ignore-carrier-loss.network @@ -4,4 +4,9 @@ Name=bridge99 [Network] Address=192.168.0.15/24 Gateway=192.168.0.1 +IPv6AcceptRA=no IgnoreCarrierLoss=true + +[RoutingPolicyRule] +To=8.8.8.8 +Table=100 diff --git a/test/test-network/conf/bridge99.network b/test/test-network/conf/bridge99.network index 39e48ce2e1..1a00ec10dc 100644 --- a/test/test-network/conf/bridge99.network +++ b/test/test-network/conf/bridge99.network @@ -4,3 +4,4 @@ Name=bridge99 [Network] Address=192.168.0.15/24 Gateway=192.168.0.1 +IPv6AcceptRA=no diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index bc90aaeb26..fa5e08e750 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -1198,8 +1198,7 @@ class NetworkdNetWorkBridgeTests(unittest.TestCase, Utilities): output = subprocess.check_output(['ip', 'addr', 'show', 'bridge99']).rstrip().decode('utf-8') print(output) - self.assertRegex(output, '192.168.0.15') - self.assertRegex(output, '192.168.0.1') + self.assertRegex(output, '192.168.0.15/24') output = subprocess.check_output(['bridge', '-d', 'link', 'show', 'dummy98']).rstrip().decode('utf-8') print(output) @@ -1212,13 +1211,37 @@ class NetworkdNetWorkBridgeTests(unittest.TestCase, Utilities): if (os.path.exists('/sys/devices/virtual/net/bridge00/lower_dummy98/brport/multicast_to_unicast')): self.assertEqual(self.read_bridge_port_attr('bridge99', 'dummy98', 'multicast_to_unicast'), '1') + output = subprocess.check_output(['networkctl', 'status', 'test1']).rstrip().decode('utf-8') + self.assertRegex(output, 'State: enslaved \(configured\)') + + output = subprocess.check_output(['networkctl', 'status', 'dummy98']).rstrip().decode('utf-8') + self.assertRegex(output, 'State: enslaved \(configured\)') + + output = subprocess.check_output(['networkctl', 'status', 'bridge99']).rstrip().decode('utf-8') + self.assertRegex(output, 'State: routable \(configured\)') + self.assertEqual(subprocess.call(['ip', 'address', 'add', '192.168.0.16/24', 'dev', 'bridge99']), 0) time.sleep(1) + output = subprocess.check_output(['ip', 'addr', 'show', 'bridge99']).rstrip().decode('utf-8') + print(output) + self.assertRegex(output, '192.168.0.16/24') + + output = subprocess.check_output(['networkctl', 'status', 'bridge99']).rstrip().decode('utf-8') + self.assertRegex(output, 'State: routable \(configured\)') + self.assertEqual(subprocess.call(['ip', 'link', 'del', 'test1']), 0) + time.sleep(3) + + output = subprocess.check_output(['networkctl', 'status', 'bridge99']).rstrip().decode('utf-8') + self.assertRegex(output, 'State: degraded \(configured\)') + self.assertEqual(subprocess.call(['ip', 'link', 'del', 'dummy98']), 0) time.sleep(3) + output = subprocess.check_output(['networkctl', 'status', 'bridge99']).rstrip().decode('utf-8') + self.assertRegex(output, 'State: no-carrier \(configured\)') + output = subprocess.check_output(['ip', 'address', 'show', 'bridge99']).rstrip().decode('utf-8') print(output) self.assertRegex(output, 'NO-CARRIER') @@ -1248,6 +1271,48 @@ class NetworkdNetWorkBridgeTests(unittest.TestCase, Utilities): self.assertRegex(output, 'inet 192.168.0.15/24 brd 192.168.0.255 scope global bridge99') self.assertRegex(output, 'inet 192.168.0.16/24 scope global secondary bridge99') + subprocess.call(['ip', 'rule', 'del', 'table', '100']) + + def test_bridge_ignore_carrier_loss_frequent_loss_and_gain(self): + self.copy_unit_to_networkd_unit_path('26-bridge.netdev', '26-bridge-slave-interface-1.network', + 'bridge99-ignore-carrier-loss.network') + self.start_networkd() + + self.assertTrue(self.link_exits('bridge99')) + + self.assertEqual(subprocess.call(['ip', 'link', 'add', 'dummy98', 'type', 'dummy']), 0) + self.assertEqual(subprocess.call(['ip', 'link', 'set', 'dummy98', 'up']), 0) + self.assertEqual(subprocess.call(['ip', 'link', 'del', 'dummy98']), 0) + + self.assertEqual(subprocess.call(['ip', 'link', 'add', 'dummy98', 'type', 'dummy']), 0) + self.assertEqual(subprocess.call(['ip', 'link', 'set', 'dummy98', 'up']), 0) + self.assertEqual(subprocess.call(['ip', 'link', 'del', 'dummy98']), 0) + + self.assertEqual(subprocess.call(['ip', 'link', 'add', 'dummy98', 'type', 'dummy']), 0) + self.assertEqual(subprocess.call(['ip', 'link', 'set', 'dummy98', 'up']), 0) + self.assertEqual(subprocess.call(['ip', 'link', 'del', 'dummy98']), 0) + + self.assertEqual(subprocess.call(['ip', 'link', 'add', 'dummy98', 'type', 'dummy']), 0) + self.assertEqual(subprocess.call(['ip', 'link', 'set', 'dummy98', 'up']), 0) + + time.sleep(3) + + output = subprocess.check_output(['ip', 'address', 'show', 'bridge99']).rstrip().decode('utf-8') + print(output) + self.assertRegex(output, 'inet 192.168.0.15/24 brd 192.168.0.255 scope global bridge99') + + output = subprocess.check_output(['networkctl', 'status', 'bridge99']).rstrip().decode('utf-8') + self.assertRegex(output, 'State: routable \(configured\)') + + output = subprocess.check_output(['networkctl', 'status', 'dummy98']).rstrip().decode('utf-8') + self.assertRegex(output, 'State: enslaved \(configured\)') + + output = subprocess.check_output(['ip', 'rule', 'list', 'table', '100']).rstrip().decode('utf-8') + print(output) + self.assertEqual(output, '0: from all to 8.8.8.8 lookup 100') + + subprocess.call(['ip', 'rule', 'del', 'table', '100']) + class NetworkdNetWorkLLDPTests(unittest.TestCase, Utilities): links = ['veth99']