diff --git a/man/systemd.network.xml b/man/systemd.network.xml index ceb0788d711..f7234537d86 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -1729,6 +1729,15 @@ + + MulticastToUnicast= + + Takes a boolean. Multicast to unicast works on top of the multicast snooping feature of + the bridge. Which means unicast copies are only delivered to hosts which are interested in it. + When unset, the kernel's default will be used. + + + HairPin= diff --git a/src/libsystemd/sd-netlink/netlink-types.c b/src/libsystemd/sd-netlink/netlink-types.c index 874babc9efd..64ab386df3f 100644 --- a/src/libsystemd/sd-netlink/netlink-types.c +++ b/src/libsystemd/sd-netlink/netlink-types.c @@ -412,17 +412,40 @@ static const NLTypeSystem rtnl_link_info_type_system = { }; static const struct NLType rtnl_prot_info_bridge_port_types[] = { - [IFLA_BRPORT_STATE] = { .type = NETLINK_TYPE_U8 }, - [IFLA_BRPORT_COST] = { .type = NETLINK_TYPE_U32 }, - [IFLA_BRPORT_PRIORITY] = { .type = NETLINK_TYPE_U16 }, - [IFLA_BRPORT_MODE] = { .type = NETLINK_TYPE_U8 }, - [IFLA_BRPORT_GUARD] = { .type = NETLINK_TYPE_U8 }, - [IFLA_BRPORT_PROTECT] = { .type = NETLINK_TYPE_U8 }, - [IFLA_BRPORT_FAST_LEAVE] = { .type = NETLINK_TYPE_U8 }, - [IFLA_BRPORT_LEARNING] = { .type = NETLINK_TYPE_U8 }, - [IFLA_BRPORT_UNICAST_FLOOD] = { .type = NETLINK_TYPE_U8 }, - [IFLA_BRPORT_PROXYARP] = { .type = NETLINK_TYPE_U8 }, - [IFLA_BRPORT_LEARNING_SYNC] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BRPORT_STATE] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BRPORT_COST] = { .type = NETLINK_TYPE_U32 }, + [IFLA_BRPORT_PRIORITY] = { .type = NETLINK_TYPE_U16 }, + [IFLA_BRPORT_MODE] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BRPORT_GUARD] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BRPORT_PROTECT] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BRPORT_FAST_LEAVE] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BRPORT_LEARNING] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BRPORT_UNICAST_FLOOD] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BRPORT_PROXYARP] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BRPORT_LEARNING_SYNC] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BRPORT_PROXYARP_WIFI] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BRPORT_ROOT_ID] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BRPORT_BRIDGE_ID] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BRPORT_DESIGNATED_PORT] = { .type = NETLINK_TYPE_U16 }, + [IFLA_BRPORT_DESIGNATED_COST] = { .type = NETLINK_TYPE_U16 }, + [IFLA_BRPORT_ID] = { .type = NETLINK_TYPE_U16 }, + [IFLA_BRPORT_NO] = { .type = NETLINK_TYPE_U16 }, + [IFLA_BRPORT_TOPOLOGY_CHANGE_ACK] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BRPORT_CONFIG_PENDING] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BRPORT_MESSAGE_AGE_TIMER] = { .type = NETLINK_TYPE_U64 }, + [IFLA_BRPORT_FORWARD_DELAY_TIMER] = { .type = NETLINK_TYPE_U64 }, + [IFLA_BRPORT_HOLD_TIMER] = { .type = NETLINK_TYPE_U64 }, + [IFLA_BRPORT_FLUSH] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BRPORT_MULTICAST_ROUTER] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BRPORT_PAD] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BRPORT_MCAST_FLOOD] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BRPORT_MCAST_TO_UCAST] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BRPORT_VLAN_TUNNEL] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BRPORT_BCAST_FLOOD] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BRPORT_GROUP_FWD_MASK] = { .type = NETLINK_TYPE_U16 }, + [IFLA_BRPORT_NEIGH_SUPPRESS] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BRPORT_ISOLATED] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BRPORT_BACKUP_PORT] = { .type = NETLINK_TYPE_U32 }, }; static const NLTypeSystem rtnl_prot_info_type_systems[] = { diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index 5296f687a78..806dc98291a 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -1421,7 +1421,12 @@ static int link_set_bridge(Link *link) { r = sd_netlink_message_append_u8(req, IFLA_BRPORT_UNICAST_FLOOD, link->network->unicast_flood); if (r < 0) return log_link_error_errno(link, r, "Could not append IFLA_BRPORT_UNICAST_FLOOD attribute: %m"); + } + if (link->network->multicast_to_unicast >= 0) { + r = sd_netlink_message_append_u8(req, IFLA_BRPORT_MCAST_TO_UCAST, link->network->multicast_to_unicast); + if (r < 0) + return log_link_error_errno(link, r, "Could not append IFLA_BRPORT_MCAST_TO_UCAST attribute: %m"); } if (link->network->cost != 0) { @@ -1429,6 +1434,7 @@ static int link_set_bridge(Link *link) { if (r < 0) return log_link_error_errno(link, r, "Could not append IFLA_BRPORT_COST attribute: %m"); } + if (link->network->priority != LINK_BRIDGE_PORT_PRIORITY_INVALID) { r = sd_netlink_message_append_u16(req, IFLA_BRPORT_PRIORITY, link->network->priority); if (r < 0) diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index 61c2e55fec8..945a3c5705c 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -163,6 +163,7 @@ Bridge.HairPin, config_parse_tristate, Bridge.FastLeave, config_parse_tristate, 0, offsetof(Network, fast_leave) Bridge.AllowPortToBeRoot, config_parse_tristate, 0, offsetof(Network, allow_port_to_be_root) Bridge.UnicastFlood, config_parse_tristate, 0, offsetof(Network, unicast_flood) +Bridge.MulticastToUnicast, config_parse_tristate, 0, offsetof(Network, multicast_to_unicast) Bridge.Priority, config_parse_bridge_port_priority, 0, offsetof(Network, priority) BridgeFDB.MACAddress, config_parse_fdb_hwaddr, 0, 0 BridgeFDB.VLANId, config_parse_fdb_vlan_id, 0, 0 diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index 0225f72bdbd..178cdff82b7 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -163,6 +163,7 @@ int network_load_one(Manager *manager, const char *filename) { .fast_leave = -1, .allow_port_to_be_root = -1, .unicast_flood = -1, + .multicast_to_unicast = -1, .priority = LINK_BRIDGE_PORT_PRIORITY_INVALID, .lldp_mode = LLDP_MODE_ROUTERS_ONLY, diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index 5c1fccbc41e..3592b563c09 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -183,6 +183,7 @@ struct Network { int fast_leave; int allow_port_to_be_root; int unicast_flood; + int multicast_to_unicast; uint32_t cost; uint16_t priority; diff --git a/test/fuzz/fuzz-network-parser/26-bridge-slave-interface-1.network b/test/fuzz/fuzz-network-parser/26-bridge-slave-interface-1.network index 84f221d5385..81b372fb6d0 100644 --- a/test/fuzz/fuzz-network-parser/26-bridge-slave-interface-1.network +++ b/test/fuzz/fuzz-network-parser/26-bridge-slave-interface-1.network @@ -9,3 +9,4 @@ Cost=400 HairPin = true FastLeave = true UnicastFlood = true +MulticastToUnicast = true diff --git a/test/fuzz/fuzz-network-parser/directives.network b/test/fuzz/fuzz-network-parser/directives.network index d8f556a6e5b..6afdd05e877 100644 --- a/test/fuzz/fuzz-network-parser/directives.network +++ b/test/fuzz/fuzz-network-parser/directives.network @@ -6,6 +6,7 @@ UnicastFlood= FastLeave= Priority= AllowPortToBeRoot= +MulticastToUnicast= [Match] KernelVersion= Type= diff --git a/test/fuzz/fuzz-unit-file/directives.service b/test/fuzz/fuzz-unit-file/directives.service index f5560ea2c2a..4d7526f6361 100644 --- a/test/fuzz/fuzz-unit-file/directives.service +++ b/test/fuzz/fuzz-unit-file/directives.service @@ -417,6 +417,7 @@ Group= GroupForwardMask= GroupPolicyExtension= HairPin= +MulticastToUnicast= HelloTimeSec= HomeAddress= Host= diff --git a/test/test-network/conf/26-bridge-slave-interface-1.network b/test/test-network/conf/26-bridge-slave-interface-1.network index 84f221d5385..81b372fb6d0 100644 --- a/test/test-network/conf/26-bridge-slave-interface-1.network +++ b/test/test-network/conf/26-bridge-slave-interface-1.network @@ -9,3 +9,4 @@ Cost=400 HairPin = true FastLeave = true UnicastFlood = true +MulticastToUnicast = true diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index 918eefa25f7..e709ef0dd12 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -61,6 +61,15 @@ class Utilities(): with open(os.path.join(os.path.join(os.path.join('/sys/class/net/', link), dev), attribute)) as f: return f.readline().strip() + def read_bridge_port_attr(self, bridge, link, attribute): + + path_bridge = os.path.join('/sys/devices/virtual/net', bridge) + path_port = 'lower_' + link + '/brport' + path = os.path.join(path_bridge, path_port) + + with open(os.path.join(path, attribute)) as f: + return f.readline().strip() + def link_exits(self, link): return os.path.exists(os.path.join('/sys/class/net', link)) @@ -735,10 +744,15 @@ class NetworkdNetWorkBrideTests(unittest.TestCase, Utilities): output = subprocess.check_output(['bridge', '-d', 'link', 'show', 'dummy98']).rstrip().decode('utf-8') print(output) - self.assertRegex(output, 'cost 400') - self.assertRegex(output, 'hairpin on') - self.assertRegex(output, 'flood on') - self.assertRegex(output, 'fastleave on') + + self.assertEqual(self.read_bridge_port_attr('bridge99', 'dummy98', 'hairpin_mode'), '1') + self.assertEqual(self.read_bridge_port_attr('bridge99', 'dummy98', 'path_cost'), '400') + self.assertEqual(self.read_bridge_port_attr('bridge99', 'dummy98', 'unicast_flood'), '1') + self.assertEqual(self.read_bridge_port_attr('bridge99', 'dummy98', 'multicast_fast_leave'), '1') + + # CONFIG_BRIDGE_IGMP_SNOOPING=y + 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') class NetworkdNetWorkLLDPTests(unittest.TestCase, Utilities): links = ['veth99']