[ovs-dev] [PATCH 8/8] gre: Add support for path MTU discovery.

Jesse Gross jesse at nicira.com
Thu Mar 4 18:22:13 UTC 2010


This allows path MTU discovery to properly work when used with
bridging.  While there was previously support for PMTUD it used
the kernel's IP stack.  This works fine for routing but when
bridging it is possible that a complete network is operating over
the bridge that the kernel has no knowledge of and the ICMP
fragmentation needed packets are lost.

When a packet arrives that is above the MTU of the tunnel, an
ICMP message is synthesized and send back on the device that the
original packet came from.  This does not rely on the kernel IP
stack and is therefore independent of the routing table.  Both
IPv4 and IPv6 are supported, including over VLANs.  Other types
of packets that are over the MTU are encapsulated and the outer
packets are fragmented.

This entire functionality is a layer violation since bridging
operates at layer 2 and fragmentation is a function of layer 3.
For this reason it is possible to disable PMTUD, which will
provide complete transparency but will cause the outer IP packets
to be fragmented.
---
 datapath/linux-2.6/compat-2.6/ip_gre.c |  496 +++++++++++++++++++++++++++++---
 lib/netdev-linux.c                     |   17 +-
 2 files changed, 466 insertions(+), 47 deletions(-)

diff --git a/datapath/linux-2.6/compat-2.6/ip_gre.c b/datapath/linux-2.6/compat-2.6/ip_gre.c
index 68b0477..4c7aedb 100644
--- a/datapath/linux-2.6/compat-2.6/ip_gre.c
+++ b/datapath/linux-2.6/compat-2.6/ip_gre.c
@@ -1,4 +1,4 @@
-/* ip_gre driver port to Linux 2.6.18 and greater */
+/* ip_gre driver port to Linux 2.6.18 and greater plus enhancements */
 
 #include <linux/version.h>
 #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,22)
@@ -35,6 +35,7 @@
 #include <linux/tcp.h>
 #include <linux/udp.h>
 #include <linux/if_arp.h>
+#include <linux/if_vlan.h>
 #include <linux/mroute.h>
 #include <linux/init.h>
 #include <linux/in6.h>
@@ -49,6 +50,7 @@
 #include <net/icmp.h>
 #include <net/protocol.h>
 #include <net/ipip.h>
+#include <net/ipv6.h>
 #include <net/arp.h>
 #include <net/checksum.h>
 #include <net/dsfield.h>
@@ -58,7 +60,6 @@
 #include <net/netns/generic.h>
 
 #ifdef CONFIG_IPV6
-#include <net/ipv6.h>
 #include <net/ip6_fib.h>
 #include <net/ip6_route.h>
 #endif
@@ -148,6 +149,8 @@ static int ipgre_tunnel_init(struct net_device *dev);
 static void ipgre_tunnel_setup(struct net_device *dev);
 static void ipgre_tap_setup(struct net_device *dev);
 static int ipgre_tunnel_bind_dev(struct net_device *dev);
+static bool send_frag_needed(struct sk_buff *skb, struct net_device *dev,
+			     unsigned int mtu);
 
 #define HASH_SIZE  16
 
@@ -455,6 +458,77 @@ static unsigned int tunnel_hard_header_len(struct net_device *dev)
 #endif
 }
 
+static void icmp_err_frag(struct sk_buff *skb, struct ip_tunnel *t)
+{
+	int mtu = ntohs(icmp_hdr(skb)->un.frag.mtu);
+	int header_len = t->hlen + tunnel_hard_header_len(t->dev);
+	unsigned int orig_mac_header = skb_mac_header(skb) - skb->data;
+	unsigned int orig_nw_header = skb_network_header(skb) - skb->data;
+	__be16 encap_proto;
+
+	/* Add the size of the IP header since this is the smallest
+	 * packet size the we might do something with and we might as
+	 * well fail early if we don't have it.  Plus it allows us to
+	 * safely look at the VLAN header if there is one.  The final
+	 * size is checked before use. */
+	if (!pskb_may_pull(skb, header_len + sizeof(struct iphdr)))
+		return;
+
+	if (t->dev->type == ARPHRD_ETHER) {
+		skb_set_mac_header(skb, t->hlen);
+		encap_proto = eth_hdr(skb)->h_proto;
+
+		if (encap_proto == htons(ETH_P_8021Q)) {
+			header_len += VLAN_HLEN;
+			encap_proto =
+				   vlan_eth_hdr(skb)->h_vlan_encapsulated_proto;
+		}
+	} else {
+		encap_proto = *(__be16 *)((skb->data +
+					  (ip_hdr(skb)->ihl << 2)) + 2);
+	}
+
+	skb_set_network_header(skb, header_len);
+	skb->protocol = encap_proto;
+	mtu -= header_len;
+
+	if (skb->protocol == htons(ETH_P_IP)) {
+		if (mtu < 68) {
+			if (ntohs(ip_hdr(skb)->tot_len) >= 68)
+				mtu = 68;
+			else
+				goto out;
+		}
+
+		header_len += sizeof(struct iphdr);
+	} else if (skb->protocol == htons(ETH_P_IPV6)) {
+		if (mtu < IPV6_MIN_MTU) {
+			unsigned int packet_length = sizeof(struct ipv6hdr) +
+					      ntohs(ipv6_hdr(skb)->payload_len);
+
+			if (packet_length >= IPV6_MIN_MTU
+			    || ntohs(ipv6_hdr(skb)->payload_len) == 0)
+				mtu = IPV6_MIN_MTU;
+			else
+				goto out;
+		}
+
+		header_len += sizeof(struct ipv6hdr);
+	} else
+		goto out;
+
+	if (pskb_may_pull(skb, header_len)) {
+		__pskb_pull(skb, t->hlen);
+		send_frag_needed(skb, t->dev, mtu);
+		skb_push(skb, t->hlen);
+	}
+
+out:
+	skb_set_mac_header(skb, orig_mac_header);
+	skb_set_network_header(skb, orig_nw_header);
+	skb->protocol = htons(ETH_P_IP);
+}
+
 static void ipgre_err(struct sk_buff *skb, u32 info)
 {
 
@@ -477,9 +551,11 @@ static void ipgre_err(struct sk_buff *skb, u32 info)
 	const int type = icmp_hdr(skb)->type;
 	const int code = icmp_hdr(skb)->code;
 	struct ip_tunnel *t;
-	__be16 flags;
+	__be16 flags = p[0];
+	__be16 gre_proto = p[1];
+
+	WARN_ON_ONCE(skb_shared(skb));
 
-	flags = p[0];
 	if (flags&(GRE_CSUM|GRE_KEY|GRE_SEQ|GRE_ROUTING|GRE_VERSION)) {
 		if (flags&(GRE_VERSION|GRE_ROUTING))
 			return;
@@ -491,9 +567,11 @@ static void ipgre_err(struct sk_buff *skb, u32 info)
 	}
 
 	/* If only 8 bytes returned, keyed message will be dropped here */
-	if (skb_headlen(skb) < grehlen)
+	if (!pskb_may_pull(skb, grehlen))
 		return;
 
+	iph = (struct iphdr *)skb->data;
+
 	switch (type) {
 	default:
 	case ICMP_PARAMETERPROB:
@@ -502,12 +580,13 @@ static void ipgre_err(struct sk_buff *skb, u32 info)
 	case ICMP_DEST_UNREACH:
 		switch (code) {
 		case ICMP_SR_FAILED:
-		case ICMP_PORT_UNREACH:
 			/* Impossible event. */
+		case ICMP_PORT_UNREACH:
 			return;
 		case ICMP_FRAG_NEEDED:
-			/* Soft state for pmtu is maintained by IP core. */
-			return;
+			/* Soft state for pmtu is maintained by IP core but we
+			 * also want to relay the message back. */
+			break;
 		default:
 			/* All others are translated to HOST_UNREACH.
 			   rfc2003 contains "deep thoughts" about NET_UNREACH,
@@ -525,8 +604,9 @@ static void ipgre_err(struct sk_buff *skb, u32 info)
 	rcu_read_lock();
 	t = ipgre_tunnel_lookup(skb->dev, iph->daddr, iph->saddr,
 				flags & GRE_KEY ?
-				*(((__be32 *)p) + (grehlen / 4) - 1) : 0,
-				p[1]);
+				*(((__be32 *)skb->data) + (grehlen / 4) - 1)
+				: 0, gre_proto);
+
 	if (t == NULL || t->parms.iph.daddr == 0 ||
 	    ipv4_is_multicast(t->parms.iph.daddr))
 		goto out;
@@ -534,6 +614,12 @@ static void ipgre_err(struct sk_buff *skb, u32 info)
 	if (t->parms.iph.ttl == 0 && type == ICMP_TIME_EXCEEDED)
 		goto out;
 
+	if (code == ICMP_FRAG_NEEDED) {
+		/* Invalidates pointers. */
+		icmp_err_frag(skb, t);
+		goto out;
+	}
+
 	if (time_before(jiffies, t->err_time + IPTUNNEL_ERR_TIMEO))
 		t->err_count++;
 	else
@@ -547,18 +633,31 @@ out:
 static inline void ipgre_ecn_decapsulate(struct iphdr *iph, struct sk_buff *skb)
 {
 	if (INET_ECN_is_ce(iph->tos)) {
-		if (skb->protocol == htons(ETH_P_IP)) {
-			if (unlikely(!pskb_may_pull(skb, skb_network_header(skb)
-			    + sizeof(struct iphdr) - skb->data)))
+		__be16 protocol = skb->protocol;
+		unsigned int nw_header = skb_network_header(skb) - skb->data;
+
+		if (skb->dev->type == ARPHRD_ETHER
+		    && skb->protocol == htons(ETH_P_8021Q)) {
+			if (unlikely(!pskb_may_pull(skb, VLAN_ETH_HLEN)))
 				return;
 
-			IP_ECN_set_ce(ip_hdr(skb));
-		} else if (skb->protocol == htons(ETH_P_IPV6)) {
-			if (unlikely(!pskb_may_pull(skb, skb_network_header(skb)
-			    + sizeof(struct ipv6hdr) - skb->data)))
+			protocol = vlan_eth_hdr(skb)->h_vlan_encapsulated_proto;
+			nw_header += VLAN_HLEN;
+		}
+
+		if (protocol == htons(ETH_P_IP)) {
+			if (unlikely(!pskb_may_pull(skb, nw_header
+			    + sizeof(struct iphdr))))
 				return;
 
-			IP6_ECN_set_ce(ipv6_hdr(skb));
+			IP_ECN_set_ce((struct iphdr *)(nw_header + skb->data));
+		} else if (protocol == htons(ETH_P_IPV6)) {
+			if (unlikely(!pskb_may_pull(skb, nw_header
+			    + sizeof(struct ipv6hdr))))
+				return;
+
+			IP6_ECN_set_ce((struct ipv6hdr *)(nw_header
+							  + skb->data));
 		}
 	}
 }
@@ -720,6 +819,275 @@ drop_nolock:
 	return(0);
 }
 
+static bool check_ipv4_address(__be32 addr)
+{
+	if (ipv4_is_multicast(addr) || ipv4_is_lbcast(addr)
+	    || ipv4_is_loopback(addr) || ipv4_is_zeronet(addr))
+		return false;
+
+	return true;
+}
+
+static bool ipv4_should_icmp(struct sk_buff *skb)
+{
+	struct iphdr *old_iph = ip_hdr(skb);
+
+	/* Don't respond to L2 broadcast. */
+	if (is_multicast_ether_addr(eth_hdr(skb)->h_dest))
+		return false;
+
+	/* Don't respond to L3 broadcast or invalid addresses. */
+	if (!check_ipv4_address(old_iph->daddr) ||
+	    !check_ipv4_address(old_iph->saddr))
+		return false;
+
+	/* Only respond to the first fragment. */
+	if (old_iph->frag_off & htons(IP_OFFSET))
+		return false;
+
+	/* Don't respond to ICMP error messages. */
+	if (old_iph->protocol == IPPROTO_ICMP) {
+		u8 icmp_type, *icmp_typep;
+
+		icmp_typep = skb_header_pointer(skb, (u8 *)old_iph +
+						(old_iph->ihl << 2) +
+						offsetof(struct icmphdr, type) -
+						skb->data, sizeof(icmp_type),
+						&icmp_type);
+
+		if (!icmp_typep)
+			return false;
+
+		if (*icmp_typep > NR_ICMP_TYPES
+			|| (*icmp_typep <= ICMP_PARAMETERPROB
+				&& *icmp_typep != ICMP_ECHOREPLY
+				&& *icmp_typep != ICMP_ECHO))
+			return false;
+	}
+
+	return true;
+}
+
+static void ipv4_build_icmp(struct sk_buff *skb, struct sk_buff *nskb,
+			    unsigned int mtu, unsigned int payload_length)
+{
+	struct iphdr *iph, *old_iph = ip_hdr(skb);
+	struct icmphdr *icmph;
+	u8 *payload;
+
+	iph = (struct iphdr *)skb_put(nskb, sizeof(struct iphdr));
+	icmph = (struct icmphdr *)skb_put(nskb, sizeof(struct icmphdr));
+	payload = skb_put(nskb, payload_length);
+
+	/* IP */
+	iph->version		=	4;
+	iph->ihl		=	sizeof(struct iphdr) >> 2;
+	iph->tos		=	(old_iph->tos & IPTOS_TOS_MASK) |
+					IPTOS_PREC_INTERNETCONTROL;
+	iph->tot_len		=	htons(sizeof(struct iphdr)
+					      + sizeof(struct icmphdr)
+					      + payload_length);
+	get_random_bytes(&iph->id, sizeof iph->id);
+	iph->frag_off		=	0;
+	iph->ttl		=	IPDEFTTL;
+	iph->protocol		=	IPPROTO_ICMP;
+	iph->daddr		=	old_iph->saddr;
+	iph->saddr		=	old_iph->daddr;
+
+	ip_send_check(iph);
+
+	/* ICMP */
+	icmph->type		=	ICMP_DEST_UNREACH;
+	icmph->code		=	ICMP_FRAG_NEEDED;
+	icmph->un.gateway	=	htonl(mtu);
+	icmph->checksum		=	0;
+
+	nskb->csum = csum_partial((void *)icmph, sizeof *icmph, 0);
+	nskb->csum = skb_copy_and_csum_bits(skb, (u8 *)old_iph - skb->data,
+					    payload, payload_length,
+					    nskb->csum);
+	icmph->checksum = csum_fold(nskb->csum);
+}
+
+static bool ipv6_should_icmp(struct sk_buff *skb)
+{
+	struct ipv6hdr *old_ipv6h = ipv6_hdr(skb);
+	int addr_type;
+	int payload_off = (u8 *)(old_ipv6h + 1) - skb->data;
+	u8 nexthdr = ipv6_hdr(skb)->nexthdr;
+
+	/* Check source address is valid. */
+	addr_type = ipv6_addr_type(&old_ipv6h->saddr);
+	if (addr_type & IPV6_ADDR_MULTICAST || addr_type == IPV6_ADDR_ANY)
+		return false;
+
+	/* Don't reply to unspecified addresses. */
+	if (ipv6_addr_type(&old_ipv6h->daddr) == IPV6_ADDR_ANY)
+		return false;
+
+	/* Don't respond to ICMP error messages. */
+	payload_off = ipv6_skip_exthdr(skb, payload_off, &nexthdr);
+	if (payload_off < 0)
+		return false;
+
+	if (nexthdr == NEXTHDR_ICMP) {
+		u8 icmp_type, *icmp_typep;
+
+		icmp_typep = skb_header_pointer(skb, payload_off +
+						offsetof(struct icmp6hdr,
+							icmp6_type),
+						sizeof(icmp_type), &icmp_type);
+
+		if (!icmp_typep || !(*icmp_typep & ICMPV6_INFOMSG_MASK))
+			return false;
+	}
+
+	return true;
+}
+
+static void ipv6_build_icmp(struct sk_buff *skb, struct sk_buff *nskb,
+			    unsigned int mtu, unsigned int payload_length)
+{
+	struct ipv6hdr *ipv6h, *old_ipv6h = ipv6_hdr(skb);
+	struct icmp6hdr *icmp6h;
+	u8 *payload;
+
+	ipv6h = (struct ipv6hdr *)skb_put(nskb, sizeof(struct ipv6hdr));
+	icmp6h = (struct icmp6hdr *)skb_put(nskb, sizeof(struct icmp6hdr));
+	payload = skb_put(nskb, payload_length);
+
+	/* IPv6 */
+	ipv6h->version		=	6;
+	ipv6h->priority		=	0;
+	memset(&ipv6h->flow_lbl, 0, sizeof ipv6h->flow_lbl);
+	ipv6h->payload_len	=	htons(sizeof(struct icmp6hdr)
+					      + payload_length);
+	ipv6h->nexthdr		=	NEXTHDR_ICMP;
+	ipv6h->hop_limit	=	IPV6_DEFAULT_HOPLIMIT;
+	ipv6_addr_copy(&ipv6h->daddr, &old_ipv6h->saddr);
+	ipv6_addr_copy(&ipv6h->saddr, &old_ipv6h->daddr);
+
+	/* ICMPv6 */
+	icmp6h->icmp6_type	=	ICMPV6_PKT_TOOBIG;
+	icmp6h->icmp6_code	=	0;
+	icmp6h->icmp6_cksum	=	0;
+	icmp6h->icmp6_mtu	=	htonl(mtu);
+
+	nskb->csum = csum_partial((void *)icmp6h, sizeof *icmp6h, 0);
+	nskb->csum = skb_copy_and_csum_bits(skb, (u8 *)old_ipv6h - skb->data,
+					    payload, payload_length,
+					    nskb->csum);
+	icmp6h->icmp6_cksum = csum_ipv6_magic(&ipv6h->saddr, &ipv6h->daddr,
+						sizeof(struct icmp6hdr)
+						+ payload_length,
+						ipv6h->nexthdr, nskb->csum);
+}
+
+static bool send_frag_needed(struct sk_buff *skb, struct net_device *dev,
+			     unsigned int mtu)
+{
+	unsigned int eth_hdr_len = ETH_HLEN;
+	unsigned int total_length, header_length, payload_length;
+	struct ethhdr *eh, *old_eh = eth_hdr(skb);
+	struct sk_buff *nskb;
+	struct net_device_stats *stats;
+
+	/* Normal IP stack. */
+	if (!dev->br_port) {
+		if (skb->protocol == htons(ETH_P_IP)) {
+			icmp_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED,
+				  htonl(mtu));
+			return true;
+		} else {
+#ifdef CONFIG_IPV6
+			icmpv6_send(skb, ICMPV6_PKT_TOOBIG, 0, mtu, dev);
+			return true;
+#else
+			return false;
+#endif
+		}
+	}
+
+	/* Sanity check */
+	if (skb->protocol == htons(ETH_P_IP)) {
+		if (mtu < 68)
+			return false;
+
+		if (!ipv4_should_icmp(skb))
+			return true;
+	} else {
+		if (mtu < IPV6_MIN_MTU)
+			return false;
+
+		/* In theory we should do PMTUD on IPv6 multicast messages but
+		 * we don't have an address to send from so just fragment. */
+		if (ipv6_addr_type(&ipv6_hdr(skb)->daddr) & IPV6_ADDR_MULTICAST)
+			return false;
+
+		if (!ipv6_should_icmp(skb))
+			return true;
+	}
+
+	/* Allocate */
+	if (old_eh->h_proto == htons(ETH_P_8021Q))
+		eth_hdr_len = VLAN_ETH_HLEN;
+
+	payload_length = skb->len - eth_hdr_len;
+	if (skb->protocol == htons(ETH_P_IP)) {
+		header_length = sizeof(struct iphdr) + sizeof(struct icmphdr);
+		total_length = min_t(unsigned int, header_length +
+						   payload_length, 576);
+	} else {
+		header_length = sizeof(struct ipv6hdr) +
+				sizeof(struct icmp6hdr);
+		total_length = min_t(unsigned int, header_length +
+						  payload_length, IPV6_MIN_MTU);
+	}
+	total_length = min(total_length, dev->mtu);
+	payload_length = total_length - header_length;
+
+	nskb = netdev_alloc_skb(dev, NET_IP_ALIGN + eth_hdr_len
+				+ header_length + payload_length);
+	if (!nskb)
+		return false;
+
+	skb_reserve(nskb, NET_IP_ALIGN);
+
+	/* Ethernet / VLAN */
+	eh = (struct ethhdr *)skb_put(nskb, eth_hdr_len);
+	memcpy(eh->h_dest, old_eh->h_source, ETH_ALEN);
+	memcpy(eh->h_source, dev->dev_addr, ETH_ALEN);
+
+	if (old_eh->h_proto != htons(ETH_P_8021Q)) {
+		eh->h_proto = old_eh->h_proto;
+	} else {
+		struct vlan_ethhdr *vh = (struct vlan_ethhdr *)eh;
+
+		vh->h_vlan_proto = htons(ETH_P_8021Q);
+		vh->h_vlan_TCI = vlan_eth_hdr(skb)->h_vlan_TCI;
+		vh->h_vlan_encapsulated_proto = skb->protocol;;
+	}
+	nskb->protocol = eth_type_trans(nskb, dev);
+
+	/* Protocol */
+	if (skb->protocol == htons(ETH_P_IP))
+		ipv4_build_icmp(skb, nskb, mtu, payload_length);
+	else
+		ipv6_build_icmp(skb, nskb, mtu, payload_length);
+
+	/* Send */
+#ifdef HAVE_NETDEV_STATS
+	stats = &dev->stats;
+#else
+	stats = &((struct ip_tunnel *)netdev_priv(dev))->stat;
+#endif
+	stats->rx_packets++;
+	stats->rx_bytes += nskb->len;
+
+	netif_rx(nskb);
+	return true;
+}
+
 static netdev_tx_t ipgre_tunnel_xmit(struct sk_buff *skb, struct net_device *dev)
 {
 	struct ip_tunnel *tunnel = netdev_priv(dev);
@@ -727,7 +1095,8 @@ static netdev_tx_t ipgre_tunnel_xmit(struct sk_buff *skb, struct net_device *dev
 #ifdef HAVE_NETDEV_QUEUE_STATS
 	struct netdev_queue *txq = netdev_get_tx_queue(dev, 0);
 #endif
-	struct iphdr  *old_iph = ip_hdr(skb);
+	struct iphdr  *old_iph;
+	struct ipv6hdr *old_ipv6h;
 	struct iphdr  *tiph;
 	u8     tos;
 	__be16 df;
@@ -738,7 +1107,8 @@ static netdev_tx_t ipgre_tunnel_xmit(struct sk_buff *skb, struct net_device *dev
 	int    gre_hlen;
 	__be32 dst;
 	int    mtu;
-	u8   original_protocol;
+	__be16 original_protocol;
+	bool is_vlan = false;
 
 #ifdef HAVE_NETDEV_STATS
 	stats = &dev->stats;
@@ -746,8 +1116,23 @@ static netdev_tx_t ipgre_tunnel_xmit(struct sk_buff *skb, struct net_device *dev
 	stats = &tunnel->stat;
 #endif
 
+	WARN_ON_ONCE(skb_shared(skb));
+
 	/* Validate the protocol headers before we try to use them. */
 	original_protocol = skb->protocol;
+
+	if (dev->type == ARPHRD_ETHER && skb->protocol == htons(ETH_P_8021Q)) {
+		if (unlikely(!pskb_may_pull(skb, VLAN_ETH_HLEN)))
+			goto tx_error;
+
+		skb->protocol = vlan_eth_hdr(skb)->h_vlan_encapsulated_proto;
+		skb_set_network_header(skb, VLAN_ETH_HLEN);
+		is_vlan = true;
+	}
+
+	old_iph = ip_hdr(skb);
+	old_ipv6h = ipv6_hdr(skb);
+
 	if (skb->protocol == htons(ETH_P_IP)) {
 		if (unlikely(!pskb_may_pull(skb, skb_network_header(skb)
 		    + sizeof(struct iphdr) - skb->data)))
@@ -758,6 +1143,9 @@ static netdev_tx_t ipgre_tunnel_xmit(struct sk_buff *skb, struct net_device *dev
 			skb->protocol = 0;
 	}
 
+	if (dev->br_port)
+		skb_dst_drop(skb);
+
 	if (dev->type == ARPHRD_ETHER)
 		IPCB(skb)->flags = 0;
 
@@ -845,28 +1233,35 @@ static netdev_tx_t ipgre_tunnel_xmit(struct sk_buff *skb, struct net_device *dev
 	df = tiph->frag_off;
 	if (df)
 		mtu = dst_mtu(&rt->u.dst) - tunnel_hard_header_len(dev)
+			- (is_vlan ? VLAN_HLEN : 0)
 			- tunnel->hlen;
 	else
 		mtu = skb_dst(skb) ? dst_mtu(skb_dst(skb)) : dev->mtu;
 
+	if (skb->protocol == htons(ETH_P_IP))
+		mtu = max(mtu, 68);
+	if (skb->protocol == htons(ETH_P_IPV6))
+		mtu = max(mtu, IPV6_MIN_MTU);
+
 	if (skb_dst(skb))
 		skb_dst(skb)->ops->update_pmtu(skb_dst(skb), mtu);
 
-	/* XXX: Temporarily allow fragmentation since DF doesn't
-	 * do the right thing with bridging. */
-/*
 	if (skb->protocol == htons(ETH_P_IP)) {
 		df |= (old_iph->frag_off&htons(IP_DF));
 
 		if ((old_iph->frag_off&htons(IP_DF)) &&
 		    mtu < ntohs(old_iph->tot_len)) {
-			icmp_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED, htonl(mtu));
-			ip_rt_put(rt);
-			goto tx_error;
+			if (send_frag_needed(skb, dev, mtu)) {
+				ip_rt_put(rt);
+				goto tx_error;
+			}
 		}
-	}
+	} else if (skb->protocol == htons(ETH_P_IPV6)) {
+		unsigned int packet_length = skb->len
+					     - tunnel_hard_header_len(dev)
+					     - (is_vlan ? VLAN_HLEN : 0);
+
 #ifdef CONFIG_IPV6
-	else if (skb->protocol == htons(ETH_P_IPV6)) {
 		struct rt6_info *rt6 = (struct rt6_info *)skb_dst(skb);
 
 		if (rt6 && mtu < dst_mtu(skb_dst(skb)) && mtu >= IPV6_MIN_MTU) {
@@ -877,15 +1272,20 @@ static netdev_tx_t ipgre_tunnel_xmit(struct sk_buff *skb, struct net_device *dev
 				skb_dst(skb)->metrics[RTAX_MTU-1] = mtu;
 			}
 		}
+#endif
 
-		if (mtu >= IPV6_MIN_MTU && mtu < skb->len - tunnel->hlen + gre_hlen) {
-			icmpv6_send(skb, ICMPV6_PKT_TOOBIG, 0, mtu, dev);
-			ip_rt_put(rt);
-			goto tx_error;
+		/* IPv6 requires PMTUD if the packet is above the minimum MTU.*/
+		if (packet_length > IPV6_MIN_MTU)
+			df = htons(IP_DF);
+
+		if (mtu < packet_length - tunnel->hlen + gre_hlen) {
+			if (send_frag_needed(skb, dev, mtu)) {
+				ip_rt_put(rt);
+				goto tx_error;
+			}
 		}
 	}
-#endif
-*/
+
 	if (tunnel->err_count > 0) {
 		if (time_before(jiffies,
 				tunnel->err_time + IPTUNNEL_ERR_TIMEO)) {
@@ -898,7 +1298,7 @@ static netdev_tx_t ipgre_tunnel_xmit(struct sk_buff *skb, struct net_device *dev
 
 	max_headroom = LL_RESERVED_SPACE(tdev) + gre_hlen;
 
-	if (skb_headroom(skb) < max_headroom || skb_shared(skb)||
+	if (skb_headroom(skb) < max_headroom ||
 	    (skb_cloned(skb) && !skb_clone_writable(skb, 0))) {
 		struct sk_buff *new_skb = skb_realloc_headroom(skb, max_headroom);
 		if (!new_skb) {
@@ -928,7 +1328,7 @@ static netdev_tx_t ipgre_tunnel_xmit(struct sk_buff *skb, struct net_device *dev
 	skb_dst_set(skb, &rt->u.dst);
 
 	/*
-	 *	Push down and install the IPIP header.
+	 *	Push down and install the GRE header.
 	 */
 
 	iph 			=	ip_hdr(skb);
@@ -940,13 +1340,17 @@ static netdev_tx_t ipgre_tunnel_xmit(struct sk_buff *skb, struct net_device *dev
 	iph->daddr		=	rt->rt_dst;
 	iph->saddr		=	rt->rt_src;
 
+	/* Allow our local IP stack to fragment the outer packet even if the
+	 * DF bit is set.  If we got this far there is nothing more that we
+	 * can do with the inner packet. */
+	if (dev->br_port)
+		skb->local_df = 1;
+
 	if ((iph->ttl = tiph->ttl) == 0) {
 		if (skb->protocol == htons(ETH_P_IP))
 			iph->ttl = old_iph->ttl;
-#ifdef CONFIG_IPV6
 		else if (skb->protocol == htons(ETH_P_IPV6))
 			iph->ttl = ((struct ipv6hdr *)old_iph)->hop_limit;
-#endif
 		else
 			iph->ttl = dst_metric(&rt->u.dst, RTAX_HOPLIMIT);
 	}
@@ -1053,9 +1457,11 @@ static int ipgre_tunnel_bind_dev(struct net_device *dev)
 	if (mtu < 68)
 		mtu = 68;
 
-	/* XXX: Set MTU to the maximum possible value.  If we are bridged to a
-	* device with a larger MTU then packets will be dropped. */
-	mtu = 65482;
+	/* If we could be connected to a bridge set the normal Ethernet MTU
+	 * since all devices on the bridge are required to have the same MTU.
+	 * Even though this isn't our optimal MTU we can handle it. */
+	if (dev->type == ARPHRD_ETHER)
+		mtu = ETH_DATA_LEN;
 
 	return mtu;
 }
@@ -1343,7 +1749,7 @@ static void ethtool_getinfo(struct net_device *dev,
 }
 
 static struct ethtool_ops ethtool_ops = {
-	.get_drvinfo = ethtool_getinfo,
+	.get_drvinfo		= ethtool_getinfo,
 };
 
 #ifdef HAVE_NET_DEVICE_OPS
@@ -1876,7 +2282,7 @@ static int __init ipgre_init(void)
 {
 	int err;
 
-	printk(KERN_INFO "GRE over IPv4 tunneling driver\n");
+	printk("Open vSwitch GRE over IPv4, built "__DATE__" "__TIME__"\n");
 
 	if (inet_add_protocol(&ipgre_protocol, IPPROTO_GRE) < 0) {
 		printk(KERN_INFO "ipgre init: can't add protocol\n");
@@ -1885,7 +2291,7 @@ static int __init ipgre_init(void)
 
 	err = register_pernet_device(&ipgre_net_ops);
 	if (err < 0)
-		goto gen_device_failed;
+		goto pernet_device_failed;
 
 #ifndef GRE_IOCTL_ONLY
 	err = rtnl_link_register(&ipgre_link_ops);
@@ -1906,7 +2312,7 @@ tap_ops_failed:
 rtnl_link_failed:
 	unregister_pernet_device(&ipgre_net_ops);
 #endif
-gen_device_failed:
+pernet_device_failed:
 	inet_del_protocol(&ipgre_protocol, IPPROTO_GRE);
 	goto out;
 
diff --git a/lib/netdev-linux.c b/lib/netdev-linux.c
index e27299a..ade1387 100644
--- a/lib/netdev-linux.c
+++ b/lib/netdev-linux.c
@@ -128,6 +128,7 @@ struct gre_config {
     bool have_out_key;
     bool in_csum;
     bool out_csum;
+    bool pmtud;
 };
 
 static struct {
@@ -257,7 +258,6 @@ setup_gre_netlink(const char *name OVS_UNUSED,
     struct nlattr *info_data_hdr;
     uint16_t iflags = 0;
     uint16_t oflags = 0;
-    uint8_t pmtudisc = 0;
 
     VLOG_DBG("%s: attempting to create gre device using netlink", name);
 
@@ -316,7 +316,7 @@ setup_gre_netlink(const char *name OVS_UNUSED,
     nl_msg_put_u16(&request, IFLA_GRE_OFLAGS, oflags);
     nl_msg_put_u32(&request, IFLA_GRE_LOCAL, config->local_ip);
     nl_msg_put_u32(&request, IFLA_GRE_REMOTE, config->remote_ip);
-    nl_msg_put_u8(&request, IFLA_GRE_PMTUDISC, pmtudisc);
+    nl_msg_put_u8(&request, IFLA_GRE_PMTUDISC, config->pmtud);
     nl_msg_put_u8(&request, IFLA_GRE_TTL, IPDEFTTL);
     nl_msg_put_u8(&request, IFLA_GRE_TOS, config->tos);
 
@@ -340,6 +340,10 @@ error:
 #endif
 }
 
+#ifndef IP_DF
+#define IP_DF 0x4000
+#endif
+
 static int
 setup_gre_ioctl(const char *name, struct gre_config *config, bool create)
 {
@@ -376,6 +380,10 @@ setup_gre_ioctl(const char *name, struct gre_config *config, bool create)
         p.o_flags |= GRE_CSUM;
     }
 
+    if (config->pmtud) {
+        p.iph.frag_off = htons(IP_DF);
+    }
+
     strncpy(ifr.ifr_name, create ? GRE_IOCTL_DEVICE : name, IFNAMSIZ);
     ifr.ifr_ifru.ifru_data = (void *)&p;
 
@@ -489,6 +497,7 @@ setup_gre(const char *name, const struct shash *args, bool create)
     memset(&config, 0, sizeof config);
     config.in_csum = true;
     config.out_csum = true;
+    config.pmtud = true;
 
     SHASH_FOR_EACH (node, args) {
         if (!strcmp(node->name, "remote_ip")) {
@@ -521,6 +530,10 @@ setup_gre(const char *name, const struct shash *args, bool create)
                 config.in_csum = false;
                 config.out_csum = false;
             }
+        } else if (!strcmp(node->name, "pmtud")) {
+            if (!strcmp(node->data, "false")) {
+                config.pmtud = false;
+            }
         } else {
             VLOG_WARN("unknown gre argument '%s'", node->name);
         }
-- 
1.6.3.3





More information about the dev mailing list