@@ -57,6 +57,7 @@ static int br_device_event(struct notifier_block *unused, unsigned long event, v
switch (event) {
case NETDEV_CHANGEMTU:
+ br_mtu_normalization(br, dev);
br_mtu_auto_adjust(br);
break;
@@ -514,6 +514,98 @@ void br_mtu_auto_adjust(struct net_bridge *br)
br_opt_toggle(br, BROPT_MTU_SET_BY_USER, false);
}
+struct br_hw_port {
+ struct list_head list;
+ struct net_device *dev;
+ int old_mtu;
+};
+
+static int br_hw_port_list_set_mtu(struct list_head *hw_port_list, int mtu)
+{
+ const struct br_hw_port *p;
+ int err;
+
+ list_for_each_entry(p, hw_port_list, list) {
+ if (p->dev->mtu == mtu)
+ continue;
+
+ err = dev_set_mtu(p->dev, mtu);
+ if (err)
+ goto rollback;
+ }
+
+ return 0;
+
+rollback:
+ list_for_each_entry_continue_reverse(p, hw_port_list, list) {
+ if (p->dev->mtu == p->old_mtu)
+ continue;
+
+ if (dev_set_mtu(p->dev, p->old_mtu))
+ netdev_err(p->dev, "Failed to restore MTU\n");
+ }
+
+ return err;
+}
+
+static void br_hw_port_list_free(struct list_head *hw_port_list)
+{
+ struct br_hw_port *p, *n;
+
+ list_for_each_entry_safe(p, n, hw_port_list, list)
+ kfree(p);
+}
+
+/* Make the hardware datapath to/from @br_if limited to a common MTU */
+void br_mtu_normalization(struct net_bridge *br, struct net_device *br_if)
+{
+ const struct net_bridge_port *p;
+ struct list_head hw_port_list;
+ int min_mtu = ETH_MAX_MTU;
+ int err;
+
+ INIT_LIST_HEAD(&hw_port_list);
+
+ /* Populate the list of ports that are part of the same hardware bridge
+ * as the newly added port
+ */
+ list_for_each_entry(p, &br->port_list, list) {
+ struct br_hw_port *hw_port;
+
+ if (!netdev_port_same_parent_id(p->dev, br_if))
+ continue;
+
+ if (min_mtu > p->dev->mtu)
+ min_mtu = p->dev->mtu;
+
+ hw_port = kzalloc(sizeof(*hw_port), GFP_KERNEL);
+ if (!hw_port)
+ goto out;
+
+ hw_port->dev = p->dev;
+ hw_port->old_mtu = p->dev->mtu;
+
+ list_add(&hw_port->list, &hw_port_list);
+ }
+
+ /* Attempt to configure the entire hardware bridge to the newly added
+ * interface's MTU first, regardless of whether the intention of the
+ * user was to raise or lower it.
+ */
+ err = br_hw_port_list_set_mtu(&hw_port_list, br_if->mtu);
+ if (!err)
+ goto out;
+
+ /* Clearly that didn't work out so well, so just set the minimum MTU on
+ * all hardware bridge ports now. If this fails too, then all ports will
+ * still have their old MTU rolled back anyway.
+ */
+ br_hw_port_list_set_mtu(&hw_port_list, min_mtu);
+
+out:
+ br_hw_port_list_free(&hw_port_list);
+}
+
static void br_set_gso_limits(struct net_bridge *br)
{
unsigned int gso_max_size = GSO_MAX_SIZE;
@@ -676,6 +768,7 @@ int br_add_if(struct net_bridge *br, struct net_device *dev,
if (changed_addr)
call_netdevice_notifiers(NETDEV_CHANGEADDR, br->dev);
+ br_mtu_normalization(br, dev);
br_mtu_auto_adjust(br);
br_set_gso_limits(br);
@@ -693,6 +693,7 @@ int br_add_if(struct net_bridge *br, struct net_device *dev,
struct netlink_ext_ack *extack);
int br_del_if(struct net_bridge *br, struct net_device *dev);
void br_mtu_auto_adjust(struct net_bridge *br);
+void br_mtu_normalization(struct net_bridge *br, struct net_device *br_if);
netdev_features_t br_features_recompute(struct net_bridge *br,
netdev_features_t features);
void br_port_flags_change(struct net_bridge_port *port, unsigned long mask);