Merge branch 'ipv6-better-traceroute-sit-gre'

Eric Dumazet says:

====================
ipv6: better traceroute support in sit and gre

In commit ca15a078bd ("sit: generate icmpv6 error when receiving icmpv4
error"), Oussama Ghorbel added minimal support for SIT tunnels to
generate ICMPv6 messages when receiving ICMPv4 messages.

This patch series extends this to GRE tunnels.

It also adds support for ICMPV6_TIME_EXCEED.

Partial support for RFC 4884 is added, to forward ICMP Multi-part
extensions eventually found in the ICMPv4 message.

v2: replaced an overlapping memcpy() by memmove()
====================

Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
David S. Miller 2016-06-18 22:11:39 -07:00
commit bd081e4b02
8 changed files with 92 additions and 43 deletions

View file

@ -14,9 +14,12 @@ static inline struct icmp6hdr *icmp6_hdr(const struct sk_buff *skb)
#if IS_ENABLED(CONFIG_IPV6)
extern void icmpv6_send(struct sk_buff *skb, u8 type, u8 code, __u32 info);
typedef void ip6_icmp_send_t(struct sk_buff *skb, u8 type, u8 code, __u32 info);
typedef void ip6_icmp_send_t(struct sk_buff *skb, u8 type, u8 code, __u32 info,
const struct in6_addr *force_saddr);
extern int inet6_register_icmp_sender(ip6_icmp_send_t *fn);
extern int inet6_unregister_icmp_sender(ip6_icmp_send_t *fn);
int ip6_err_gen_icmpv6_unreach(struct sk_buff *skb, int nhs, int type,
unsigned int data_len);
#else

View file

@ -157,6 +157,7 @@ struct tnl_ptk_info {
__be16 proto;
__be32 key;
__be32 seq;
int hdr_len;
};
#define PACKET_RCVD 0

View file

@ -79,6 +79,7 @@ struct icmphdr {
__be16 __unused;
__be16 mtu;
} frag;
__u8 reserved[4];
} un;
};

View file

@ -117,6 +117,7 @@ int gre_parse_header(struct sk_buff *skb, struct tnl_ptk_info *tpi,
if ((*(u8 *)options & 0xF0) != 0x40)
hdr_len += 4;
}
tpi->hdr_len = hdr_len;
return hdr_len;
}
EXPORT_SYMBOL(gre_parse_header);

View file

@ -144,6 +144,7 @@ static void ipgre_err(struct sk_buff *skb, u32 info,
const struct iphdr *iph;
const int type = icmp_hdr(skb)->type;
const int code = icmp_hdr(skb)->code;
unsigned int data_len = 0;
struct ip_tunnel *t;
switch (type) {
@ -169,6 +170,7 @@ static void ipgre_err(struct sk_buff *skb, u32 info,
case ICMP_TIME_EXCEEDED:
if (code != ICMP_EXC_TTL)
return;
data_len = icmp_hdr(skb)->un.reserved[1] * 4; /* RFC 4884 4.1 */
break;
case ICMP_REDIRECT:
@ -187,6 +189,13 @@ static void ipgre_err(struct sk_buff *skb, u32 info,
if (!t)
return;
#if IS_ENABLED(CONFIG_IPV6)
if (tpi->proto == htons(ETH_P_IPV6) &&
!ip6_err_gen_icmpv6_unreach(skb, iph->ihl * 4 + tpi->hdr_len,
type, data_len))
return;
#endif
if (t->parms.iph.daddr == 0 ||
ipv4_is_multicast(t->parms.iph.daddr))
return;

View file

@ -388,7 +388,8 @@ static struct dst_entry *icmpv6_route_lookup(struct net *net,
/*
* Send an ICMP message in response to a packet in error
*/
static void icmp6_send(struct sk_buff *skb, u8 type, u8 code, __u32 info)
static void icmp6_send(struct sk_buff *skb, u8 type, u8 code, __u32 info,
const struct in6_addr *force_saddr)
{
struct net *net = dev_net(skb->dev);
struct inet6_dev *idev = NULL;
@ -475,6 +476,8 @@ static void icmp6_send(struct sk_buff *skb, u8 type, u8 code, __u32 info)
memset(&fl6, 0, sizeof(fl6));
fl6.flowi6_proto = IPPROTO_ICMPV6;
fl6.daddr = hdr->saddr;
if (force_saddr)
saddr = force_saddr;
if (saddr)
fl6.saddr = *saddr;
fl6.flowi6_mark = mark;
@ -551,10 +554,75 @@ static void icmp6_send(struct sk_buff *skb, u8 type, u8 code, __u32 info)
*/
void icmpv6_param_prob(struct sk_buff *skb, u8 code, int pos)
{
icmp6_send(skb, ICMPV6_PARAMPROB, code, pos);
icmp6_send(skb, ICMPV6_PARAMPROB, code, pos, NULL);
kfree_skb(skb);
}
/* Generate icmpv6 with type/code ICMPV6_DEST_UNREACH/ICMPV6_ADDR_UNREACH
* if sufficient data bytes are available
* @nhs is the size of the tunnel header(s) :
* Either an IPv4 header for SIT encap
* an IPv4 header + GRE header for GRE encap
*/
int ip6_err_gen_icmpv6_unreach(struct sk_buff *skb, int nhs, int type,
unsigned int data_len)
{
struct in6_addr temp_saddr;
struct rt6_info *rt;
struct sk_buff *skb2;
u32 info = 0;
if (!pskb_may_pull(skb, nhs + sizeof(struct ipv6hdr) + 8))
return 1;
/* RFC 4884 (partial) support for ICMP extensions */
if (data_len < 128 || (data_len & 7) || skb->len < data_len)
data_len = 0;
skb2 = data_len ? skb_copy(skb, GFP_ATOMIC) : skb_clone(skb, GFP_ATOMIC);
if (!skb2)
return 1;
skb_dst_drop(skb2);
skb_pull(skb2, nhs);
skb_reset_network_header(skb2);
rt = rt6_lookup(dev_net(skb->dev), &ipv6_hdr(skb2)->saddr, NULL, 0, 0);
if (rt && rt->dst.dev)
skb2->dev = rt->dst.dev;
ipv6_addr_set_v4mapped(ip_hdr(skb)->saddr, &temp_saddr);
if (data_len) {
/* RFC 4884 (partial) support :
* insert 0 padding at the end, before the extensions
*/
__skb_push(skb2, nhs);
skb_reset_network_header(skb2);
memmove(skb2->data, skb2->data + nhs, data_len - nhs);
memset(skb2->data + data_len - nhs, 0, nhs);
/* RFC 4884 4.5 : Length is measured in 64-bit words,
* and stored in reserved[0]
*/
info = (data_len/8) << 24;
}
if (type == ICMP_TIME_EXCEEDED)
icmp6_send(skb2, ICMPV6_TIME_EXCEED, ICMPV6_EXC_HOPLIMIT,
info, &temp_saddr);
else
icmp6_send(skb2, ICMPV6_DEST_UNREACH, ICMPV6_ADDR_UNREACH,
info, &temp_saddr);
if (rt)
ip6_rt_put(rt);
kfree_skb(skb2);
return 0;
}
EXPORT_SYMBOL(ip6_err_gen_icmpv6_unreach);
static void icmpv6_echo_reply(struct sk_buff *skb)
{
struct net *net = dev_net(skb->dev);

View file

@ -39,7 +39,7 @@ void icmpv6_send(struct sk_buff *skb, u8 type, u8 code, __u32 info)
if (!send)
goto out;
send(skb, type, code, info);
send(skb, type, code, info, NULL);
out:
rcu_read_unlock();
}

View file

@ -479,47 +479,12 @@ static void ipip6_tunnel_uninit(struct net_device *dev)
dev_put(dev);
}
/* Generate icmpv6 with type/code ICMPV6_DEST_UNREACH/ICMPV6_ADDR_UNREACH
* if sufficient data bytes are available
*/
static int ipip6_err_gen_icmpv6_unreach(struct sk_buff *skb)
{
int ihl = ((const struct iphdr *)skb->data)->ihl*4;
struct rt6_info *rt;
struct sk_buff *skb2;
if (!pskb_may_pull(skb, ihl + sizeof(struct ipv6hdr) + 8))
return 1;
skb2 = skb_clone(skb, GFP_ATOMIC);
if (!skb2)
return 1;
skb_dst_drop(skb2);
skb_pull(skb2, ihl);
skb_reset_network_header(skb2);
rt = rt6_lookup(dev_net(skb->dev), &ipv6_hdr(skb2)->saddr, NULL, 0, 0);
if (rt && rt->dst.dev)
skb2->dev = rt->dst.dev;
icmpv6_send(skb2, ICMPV6_DEST_UNREACH, ICMPV6_ADDR_UNREACH, 0);
if (rt)
ip6_rt_put(rt);
kfree_skb(skb2);
return 0;
}
static int ipip6_err(struct sk_buff *skb, u32 info)
{
const struct iphdr *iph = (const struct iphdr *)skb->data;
const int type = icmp_hdr(skb)->type;
const int code = icmp_hdr(skb)->code;
unsigned int data_len = 0;
struct ip_tunnel *t;
int err;
@ -544,6 +509,7 @@ static int ipip6_err(struct sk_buff *skb, u32 info)
case ICMP_TIME_EXCEEDED:
if (code != ICMP_EXC_TTL)
return 0;
data_len = icmp_hdr(skb)->un.reserved[1] * 4; /* RFC 4884 4.1 */
break;
case ICMP_REDIRECT:
break;
@ -571,11 +537,11 @@ static int ipip6_err(struct sk_buff *skb, u32 info)
goto out;
}
if (t->parms.iph.daddr == 0)
err = 0;
if (!ip6_err_gen_icmpv6_unreach(skb, iph->ihl * 4, type, data_len))
goto out;
err = 0;
if (!ipip6_err_gen_icmpv6_unreach(skb))
if (t->parms.iph.daddr == 0)
goto out;
if (t->parms.iph.ttl == 0 && type == ICMP_TIME_EXCEEDED)