Skip to content

Commit

Permalink
ip_tunnels: Use skb-len to PMTU check.
Browse files Browse the repository at this point in the history
In path mtu check, ip header total length works for gre device
but not for gre-tap device.  Use skb len which is consistent
for all tunneling types.  This is old bug in gre.
This also fixes mtu calculation bug introduced by
commit c544193 (GRE: Refactor GRE tunneling code).

Reported-by: Timo Teras <timo.teras@iki.fi>
Signed-off-by: Pravin B Shelar <pshelar@nicira.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
  • Loading branch information
Pravin B Shelar authored and davem330 committed Jul 2, 2013
1 parent 784771e commit 23a3647
Showing 1 changed file with 55 additions and 44 deletions.
99 changes: 55 additions & 44 deletions net/ipv4/ip_tunnel.c
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,54 @@ int ip_tunnel_rcv(struct ip_tunnel *tunnel, struct sk_buff *skb,
}
EXPORT_SYMBOL_GPL(ip_tunnel_rcv);

static int tnl_update_pmtu(struct net_device *dev, struct sk_buff *skb,
struct rtable *rt, __be16 df)
{
struct ip_tunnel *tunnel = netdev_priv(dev);
int pkt_size = skb->len - tunnel->hlen;
int mtu;

if (df)
mtu = dst_mtu(&rt->dst) - dev->hard_header_len
- sizeof(struct iphdr) - tunnel->hlen;
else
mtu = skb_dst(skb) ? dst_mtu(skb_dst(skb)) : dev->mtu;

if (skb_dst(skb))
skb_dst(skb)->ops->update_pmtu(skb_dst(skb), NULL, skb, mtu);

if (skb->protocol == htons(ETH_P_IP)) {
if (!skb_is_gso(skb) &&
(df & htons(IP_DF)) && mtu < pkt_size) {
memset(IPCB(skb), 0, sizeof(*IPCB(skb)));
icmp_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED, htonl(mtu));
return -E2BIG;
}
}
#if IS_ENABLED(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) {
if ((tunnel->parms.iph.daddr &&
!ipv4_is_multicast(tunnel->parms.iph.daddr)) ||
rt6->rt6i_dst.plen == 128) {
rt6->rt6i_flags |= RTF_MODIFIED;
dst_metric_set(skb_dst(skb), RTAX_MTU, mtu);
}
}

if (!skb_is_gso(skb) && mtu >= IPV6_MIN_MTU &&
mtu < pkt_size) {
icmpv6_send(skb, ICMPV6_PKT_TOOBIG, 0, mtu);
return -E2BIG;
}
}
#endif
return 0;
}

void ip_tunnel_xmit(struct sk_buff *skb, struct net_device *dev,
const struct iphdr *tnl_params, const u8 protocol)
{
Expand All @@ -483,7 +531,6 @@ void ip_tunnel_xmit(struct sk_buff *skb, struct net_device *dev,
struct rtable *rt; /* Route to the other host */
unsigned int max_headroom; /* The extra header space needed */
__be32 dst;
int mtu;
int err;

inner_iph = (const struct iphdr *)skb_inner_network_header(skb);
Expand Down Expand Up @@ -560,51 +607,11 @@ void ip_tunnel_xmit(struct sk_buff *skb, struct net_device *dev,
dev->stats.collisions++;
goto tx_error;
}
df = tnl_params->frag_off;

if (df)
mtu = dst_mtu(&rt->dst) - dev->hard_header_len
- sizeof(struct iphdr);
else
mtu = skb_dst(skb) ? dst_mtu(skb_dst(skb)) : dev->mtu;

if (skb_dst(skb))
skb_dst(skb)->ops->update_pmtu(skb_dst(skb), NULL, skb, mtu);

if (skb->protocol == htons(ETH_P_IP)) {
df |= (inner_iph->frag_off&htons(IP_DF));

if (!skb_is_gso(skb) &&
(inner_iph->frag_off&htons(IP_DF)) &&
mtu < ntohs(inner_iph->tot_len)) {
memset(IPCB(skb), 0, sizeof(*IPCB(skb)));
icmp_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED, htonl(mtu));
ip_rt_put(rt);
goto tx_error;
}
}
#if IS_ENABLED(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) {
if ((tunnel->parms.iph.daddr &&
!ipv4_is_multicast(tunnel->parms.iph.daddr)) ||
rt6->rt6i_dst.plen == 128) {
rt6->rt6i_flags |= RTF_MODIFIED;
dst_metric_set(skb_dst(skb), RTAX_MTU, mtu);
}
}

if (!skb_is_gso(skb) && mtu >= IPV6_MIN_MTU &&
mtu < skb->len) {
icmpv6_send(skb, ICMPV6_PKT_TOOBIG, 0, mtu);
ip_rt_put(rt);
goto tx_error;
}
if (tnl_update_pmtu(dev, skb, rt, tnl_params->frag_off)) {
ip_rt_put(rt);
goto tx_error;
}
#endif

if (tunnel->net != dev_net(dev))
skb_scrub_packet(skb);
Expand All @@ -631,6 +638,10 @@ void ip_tunnel_xmit(struct sk_buff *skb, struct net_device *dev,
ttl = ip4_dst_hoplimit(&rt->dst);
}

df = tnl_params->frag_off;
if (skb->protocol == htons(ETH_P_IP))
df |= (inner_iph->frag_off&htons(IP_DF));

max_headroom = LL_RESERVED_SPACE(rt->dst.dev) + sizeof(struct iphdr)
+ rt->dst.header_len;
if (max_headroom > dev->needed_headroom) {
Expand Down

0 comments on commit 23a3647

Please sign in to comment.