sunvnet: generate ICMP PTMUD messages for smaller port MTUs
authorDavid L Stevens <david.stevens@oracle.com>
Mon, 29 Sep 2014 23:48:24 +0000 (19:48 -0400)
committerDavid S. Miller <davem@davemloft.net>
Tue, 30 Sep 2014 21:10:39 +0000 (17:10 -0400)
This patch sends ICMP and ICMPv6 messages for Path MTU Discovery when a remote
port MTU is smaller than the device MTU. This allows mixing newer VIO protocol
devices that support MTU negotiation with older devices that do not on the
same vswitch. It also allows Linux-Linux LDOMs to use 64K-1 data packets even
though Solaris vswitch is limited to <16K MTU.

Signed-off-by: David L Stevens <david.stevens@oracle.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/ethernet/sun/sunvnet.c

index 39804715bf220477131fe6ffab5174b21530862b..126269762ee71b915212477596f2706eec70599c 100644 (file)
 #include <linux/mutex.h>
 #include <linux/if_vlan.h>
 
+#if IS_ENABLED(CONFIG_IPV6)
+#include <linux/icmpv6.h>
+#endif
+
+#include <net/icmp.h>
+#include <net/route.h>
+
 #include <asm/vio.h>
 #include <asm/ldc.h>
 
@@ -913,8 +920,36 @@ static int vnet_start_xmit(struct sk_buff *skb, struct net_device *dev)
        if (unlikely(!skb))
                goto out_dropped;
 
-       if (skb->len > port->rmtu)
+       if (skb->len > port->rmtu) {
+               unsigned long localmtu = port->rmtu - ETH_HLEN;
+
+               if (vio_version_after_eq(&port->vio, 1, 3))
+                       localmtu -= VLAN_HLEN;
+
+               if (skb->protocol == htons(ETH_P_IP)) {
+                       struct flowi4 fl4;
+                       struct rtable *rt = NULL;
+
+                       memset(&fl4, 0, sizeof(fl4));
+                       fl4.flowi4_oif = dev->ifindex;
+                       fl4.flowi4_tos = RT_TOS(ip_hdr(skb)->tos);
+                       fl4.daddr = ip_hdr(skb)->daddr;
+                       fl4.saddr = ip_hdr(skb)->saddr;
+
+                       rt = ip_route_output_key(dev_net(dev), &fl4);
+                       if (!IS_ERR(rt)) {
+                               skb_dst_set(skb, &rt->dst);
+                               icmp_send(skb, ICMP_DEST_UNREACH,
+                                         ICMP_FRAG_NEEDED,
+                                         htonl(localmtu));
+                       }
+               }
+#if IS_ENABLED(CONFIG_IPV6)
+               else if (skb->protocol == htons(ETH_P_IPV6))
+                       icmpv6_send(skb, ICMPV6_PKT_TOOBIG, 0, localmtu);
+#endif
                goto out_dropped;
+       }
 
        spin_lock_irqsave(&port->vio.lock, flags);