diff --git a/man/systemd.network.xml b/man/systemd.network.xml
index 229449c5ac..e8a866fcba 100644
--- a/man/systemd.network.xml
+++ b/man/systemd.network.xml
@@ -1723,6 +1723,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 874babc9ef..64ab386df3 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 5296f687a7..806dc98291 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 ed15d22178..0d8d644e85 100644
--- a/src/network/networkd-network-gperf.gperf
+++ b/src/network/networkd-network-gperf.gperf
@@ -162,6 +162,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 2be63f502e..26a8513035 100644
--- a/src/network/networkd-network.c
+++ b/src/network/networkd-network.c
@@ -162,6 +162,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 5c1fccbc41..3592b563c0 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 84f221d538..81b372fb6d 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 8ea809a7e1..8077b623fe 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 f454fd313e..a51d697c4e 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=