@@ -3633,11 +3633,13 @@ static int macsec_change_mtu(struct net_device *dev, int new_mtu)
{
struct macsec_dev *macsec = macsec_priv(dev);
unsigned int extra = macsec->secy.icv_len + macsec_extra_len(true);
+ unsigned int max_mtu = macsec->real_dev->mtu - extra;
- if (macsec->real_dev->mtu - extra < new_mtu)
+ if (new_mtu > max_mtu)
return -ERANGE;
dev->mtu = new_mtu;
+ __vlan_constrain_mtu(dev, max_mtu);
return 0;
}
@@ -4018,7 +4020,7 @@ static int macsec_newlink(struct net *net, struct net_device *dev,
if (real_dev->type != ARPHRD_ETHER)
return -EINVAL;
- dev->priv_flags |= IFF_MACSEC;
+ dev->priv_flags |= IFF_MACSEC | IFF_NO_VLAN_ROOM;
macsec->real_dev = real_dev;
@@ -749,4 +749,44 @@ static inline unsigned long compare_vlan_header(const struct vlan_hdr *h1,
(__force u32)h2->h_vlan_encapsulated_proto);
#endif
}
+
+/* max_mtu is not necessarily the same as dev->max_mtu */
+static inline void __vlan_constrain_mtu(struct net_device *dev, int max_mtu)
+{
+ if (dev->mtu > max_mtu - VLAN_HLEN)
+ dev->priv_flags |= IFF_NO_VLAN_ROOM;
+ else
+ dev->priv_flags &= ~IFF_NO_VLAN_ROOM;
+}
+
+/**
+ * vlan_constrain_mtu - reduce MTU for upper VLAN devices if there's no L2 room
+ * @dev: a lower device having a VLAN constrained L2
+ *
+ * Sets IFF_NO_VLAN_ROOM based on the device's current and max MTU.
+ *
+ * Normally MTU does not account for an outer VLAN tag, since MTU is an L3
+ * constraint. Thus, this should only be called by devices that cannot expand
+ * L2 to accommodate one. For example, in order to support VLANs without IP
+ * fragmentation inside various tunnel encapsulations where the inner L2 size
+ * contributes towards the outer L3 size.
+ *
+ * This can also be useful for supporting VLANs, using a reduced MTU, on
+ * hardware which is VLAN challenged.
+ */
+static inline void vlan_constrain_mtu(struct net_device *dev)
+{
+ __vlan_constrain_mtu(dev, dev->max_mtu);
+}
+
+/**
+ * vlan_constrained_change_mtu - ndo_change_mtu for VLAN challenged devices
+ * @dev: the device to update
+ * @new_mtu: the new MTU
+ *
+ * This handler updates MTU and maintains the IFF_NO_VLAN_ROOM flag based on
+ * the newly requested MTU and the maximum supported by the device.
+ */
+int vlan_constrained_change_mtu(struct net_device *dev, int new_mtu);
+
#endif /* !(_LINUX_IF_VLAN_H_) */
@@ -1516,6 +1516,7 @@ struct net_device_ops {
* @IFF_LIVE_ADDR_CHANGE: device supports hardware address
* change when it's running
* @IFF_MACVLAN: Macvlan device
+ * @IFF_NO_VLAN_ROOM: space constrained L2, upper VLAN devices must reduce MTU
* @IFF_L3MDEV_MASTER: device is an L3 master device
* @IFF_NO_QUEUE: device can run without qdisc attached
* @IFF_OPENVSWITCH: device is a Open vSwitch master
@@ -1549,6 +1550,7 @@ enum netdev_priv_flags {
IFF_SUPP_NOFCS = 1<<14,
IFF_LIVE_ADDR_CHANGE = 1<<15,
IFF_MACVLAN = 1<<16,
+ IFF_NO_VLAN_ROOM = 1<<17,
IFF_L3MDEV_MASTER = 1<<18,
IFF_NO_QUEUE = 1<<19,
IFF_OPENVSWITCH = 1<<20,
@@ -1581,6 +1583,7 @@ enum netdev_priv_flags {
#define IFF_SUPP_NOFCS IFF_SUPP_NOFCS
#define IFF_LIVE_ADDR_CHANGE IFF_LIVE_ADDR_CHANGE
#define IFF_MACVLAN IFF_MACVLAN
+#define IFF_NO_VLAN_ROOM IFF_NO_VLAN_ROOM
#define IFF_L3MDEV_MASTER IFF_L3MDEV_MASTER
#define IFF_NO_QUEUE IFF_NO_QUEUE
#define IFF_OPENVSWITCH IFF_OPENVSWITCH
@@ -4855,8 +4858,7 @@ static inline void netif_keep_dst(struct net_device *dev)
/* return true if dev can't cope with mtu frames that need vlan tag insertion */
static inline bool netif_reduces_vlan_mtu(struct net_device *dev)
{
- /* TODO: reserve and use an additional IFF bit, if we get more users */
- return dev->priv_flags & IFF_MACSEC;
+ return dev->priv_flags & IFF_NO_VLAN_ROOM;
}
extern struct pernet_operations __net_initdata loopback_net_ops;
@@ -149,10 +149,19 @@ static int vlan_dev_change_mtu(struct net_device *dev, int new_mtu)
return -ERANGE;
dev->mtu = new_mtu;
+ __vlan_constrain_mtu(dev, max_mtu);
return 0;
}
+int vlan_constrained_change_mtu(struct net_device *dev, int new_mtu)
+{
+ dev->mtu = new_mtu;
+ vlan_constrain_mtu(dev);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(vlan_constrained_change_mtu);
+
void vlan_dev_set_ingress_priority(const struct net_device *dev,
u32 skb_prio, u16 vlan_prio)
{
@@ -181,6 +181,8 @@ static int vlan_newlink(struct net *src_net, struct net_device *dev,
dev->mtu = max_mtu;
else if (dev->mtu > max_mtu)
return -EINVAL;
+ else if (dev->mtu > max_mtu - VLAN_HLEN)
+ dev->priv_flags |= IFF_NO_VLAN_ROOM;
err = vlan_changelink(dev, tb, data, extack);
if (!err)
Normally MTU does not account for an outer VLAN tag, since MTU is an L3 constraint. Some Ethernet devices, however, have a size constrained L2 that cannot expand to accommodate a VLAN tag. The MACsec virtual device is an existing example that limits the MTU of upper VLAN devices via netif_reduces_vlan_mtu(), but there are other devices that should do so too. For example, virtual tunnel devices that provide L2 overlays inside L3 networks where the inner L2 headers contribute towards the outer L3 size. Generalize netif_reduces_vlan_mtu() using a new device private flag to indicate that the lower device does not have sufficient room to accommodate a VLAN tag and convert MACsec to use the new flag. Also apply this concept to the VLAN virtual device itself, since physical devices do not generally allocate L2 room for nested VLANs. Note that if the lower device is manually configured to a smaller MTU value than the maximum it supports, then there is sufficient room and IFF_NO_VLAN_ROOM should not be set. Signed-off-by: Edwin Peer <edwin.peer@broadcom.com> --- drivers/net/macsec.c | 6 ++++-- include/linux/if_vlan.h | 40 +++++++++++++++++++++++++++++++++++++++ include/linux/netdevice.h | 6 ++++-- net/8021q/vlan_dev.c | 9 +++++++++ net/8021q/vlan_netlink.c | 2 ++ 5 files changed, 59 insertions(+), 4 deletions(-)