Commit bd081e4b authored by David S. Miller's avatar David S. Miller

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

Eric Dumazet says:

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

In commit ca15a078 ("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: default avatarDavid S. Miller <davem@davemloft.net>
parents adc01582 20e1954f
...@@ -14,9 +14,12 @@ static inline struct icmp6hdr *icmp6_hdr(const struct sk_buff *skb) ...@@ -14,9 +14,12 @@ static inline struct icmp6hdr *icmp6_hdr(const struct sk_buff *skb)
#if IS_ENABLED(CONFIG_IPV6) #if IS_ENABLED(CONFIG_IPV6)
extern void icmpv6_send(struct sk_buff *skb, u8 type, u8 code, __u32 info); 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_register_icmp_sender(ip6_icmp_send_t *fn);
extern int inet6_unregister_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 #else
......
...@@ -157,6 +157,7 @@ struct tnl_ptk_info { ...@@ -157,6 +157,7 @@ struct tnl_ptk_info {
__be16 proto; __be16 proto;
__be32 key; __be32 key;
__be32 seq; __be32 seq;
int hdr_len;
}; };
#define PACKET_RCVD 0 #define PACKET_RCVD 0
......
...@@ -79,6 +79,7 @@ struct icmphdr { ...@@ -79,6 +79,7 @@ struct icmphdr {
__be16 __unused; __be16 __unused;
__be16 mtu; __be16 mtu;
} frag; } frag;
__u8 reserved[4];
} un; } un;
}; };
......
...@@ -117,6 +117,7 @@ int gre_parse_header(struct sk_buff *skb, struct tnl_ptk_info *tpi, ...@@ -117,6 +117,7 @@ int gre_parse_header(struct sk_buff *skb, struct tnl_ptk_info *tpi,
if ((*(u8 *)options & 0xF0) != 0x40) if ((*(u8 *)options & 0xF0) != 0x40)
hdr_len += 4; hdr_len += 4;
} }
tpi->hdr_len = hdr_len;
return hdr_len; return hdr_len;
} }
EXPORT_SYMBOL(gre_parse_header); EXPORT_SYMBOL(gre_parse_header);
......
...@@ -144,6 +144,7 @@ static void ipgre_err(struct sk_buff *skb, u32 info, ...@@ -144,6 +144,7 @@ static void ipgre_err(struct sk_buff *skb, u32 info,
const struct iphdr *iph; const struct iphdr *iph;
const int type = icmp_hdr(skb)->type; const int type = icmp_hdr(skb)->type;
const int code = icmp_hdr(skb)->code; const int code = icmp_hdr(skb)->code;
unsigned int data_len = 0;
struct ip_tunnel *t; struct ip_tunnel *t;
switch (type) { switch (type) {
...@@ -169,6 +170,7 @@ static void ipgre_err(struct sk_buff *skb, u32 info, ...@@ -169,6 +170,7 @@ static void ipgre_err(struct sk_buff *skb, u32 info,
case ICMP_TIME_EXCEEDED: case ICMP_TIME_EXCEEDED:
if (code != ICMP_EXC_TTL) if (code != ICMP_EXC_TTL)
return; return;
data_len = icmp_hdr(skb)->un.reserved[1] * 4; /* RFC 4884 4.1 */
break; break;
case ICMP_REDIRECT: case ICMP_REDIRECT:
...@@ -187,6 +189,13 @@ static void ipgre_err(struct sk_buff *skb, u32 info, ...@@ -187,6 +189,13 @@ static void ipgre_err(struct sk_buff *skb, u32 info,
if (!t) if (!t)
return; 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 || if (t->parms.iph.daddr == 0 ||
ipv4_is_multicast(t->parms.iph.daddr)) ipv4_is_multicast(t->parms.iph.daddr))
return; return;
......
...@@ -388,7 +388,8 @@ static struct dst_entry *icmpv6_route_lookup(struct net *net, ...@@ -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 * 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 net *net = dev_net(skb->dev);
struct inet6_dev *idev = NULL; struct inet6_dev *idev = NULL;
...@@ -475,6 +476,8 @@ static void icmp6_send(struct sk_buff *skb, u8 type, u8 code, __u32 info) ...@@ -475,6 +476,8 @@ static void icmp6_send(struct sk_buff *skb, u8 type, u8 code, __u32 info)
memset(&fl6, 0, sizeof(fl6)); memset(&fl6, 0, sizeof(fl6));
fl6.flowi6_proto = IPPROTO_ICMPV6; fl6.flowi6_proto = IPPROTO_ICMPV6;
fl6.daddr = hdr->saddr; fl6.daddr = hdr->saddr;
if (force_saddr)
saddr = force_saddr;
if (saddr) if (saddr)
fl6.saddr = *saddr; fl6.saddr = *saddr;
fl6.flowi6_mark = mark; fl6.flowi6_mark = mark;
...@@ -551,10 +554,75 @@ static void icmp6_send(struct sk_buff *skb, u8 type, u8 code, __u32 info) ...@@ -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) 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); 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) static void icmpv6_echo_reply(struct sk_buff *skb)
{ {
struct net *net = dev_net(skb->dev); struct net *net = dev_net(skb->dev);
......
...@@ -39,7 +39,7 @@ void icmpv6_send(struct sk_buff *skb, u8 type, u8 code, __u32 info) ...@@ -39,7 +39,7 @@ void icmpv6_send(struct sk_buff *skb, u8 type, u8 code, __u32 info)
if (!send) if (!send)
goto out; goto out;
send(skb, type, code, info); send(skb, type, code, info, NULL);
out: out:
rcu_read_unlock(); rcu_read_unlock();
} }
......
...@@ -479,47 +479,12 @@ static void ipip6_tunnel_uninit(struct net_device *dev) ...@@ -479,47 +479,12 @@ static void ipip6_tunnel_uninit(struct net_device *dev)
dev_put(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) static int ipip6_err(struct sk_buff *skb, u32 info)
{ {
const struct iphdr *iph = (const struct iphdr *)skb->data; const struct iphdr *iph = (const struct iphdr *)skb->data;
const int type = icmp_hdr(skb)->type; const int type = icmp_hdr(skb)->type;
const int code = icmp_hdr(skb)->code; const int code = icmp_hdr(skb)->code;
unsigned int data_len = 0;
struct ip_tunnel *t; struct ip_tunnel *t;
int err; int err;
...@@ -544,6 +509,7 @@ static int ipip6_err(struct sk_buff *skb, u32 info) ...@@ -544,6 +509,7 @@ static int ipip6_err(struct sk_buff *skb, u32 info)
case ICMP_TIME_EXCEEDED: case ICMP_TIME_EXCEEDED:
if (code != ICMP_EXC_TTL) if (code != ICMP_EXC_TTL)
return 0; return 0;
data_len = icmp_hdr(skb)->un.reserved[1] * 4; /* RFC 4884 4.1 */
break; break;
case ICMP_REDIRECT: case ICMP_REDIRECT:
break; break;
...@@ -571,11 +537,11 @@ static int ipip6_err(struct sk_buff *skb, u32 info) ...@@ -571,11 +537,11 @@ static int ipip6_err(struct sk_buff *skb, u32 info)
goto out; 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; goto out;
err = 0; if (t->parms.iph.daddr == 0)
if (!ipip6_err_gen_icmpv6_unreach(skb))
goto out; goto out;
if (t->parms.iph.ttl == 0 && type == ICMP_TIME_EXCEEDED) if (t->parms.iph.ttl == 0 && type == ICMP_TIME_EXCEEDED)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment