Commit 20e1954f authored by Eric Dumazet's avatar Eric Dumazet Committed by David S. Miller

ipv6: RFC 4884 partial support for SIT/GRE tunnels

When receiving an ICMPv4 message containing extensions as
defined in RFC 4884, and translating it to ICMPv6 at SIT
or GRE tunnel, we need some extra manipulation in order
to properly forward the extensions.

This patch only takes care of Time Exceeded messages as they
are the ones that typically carry information from various
routers in a fabric during a traceroute session.

It also avoids complex skb logic if the data_len is not
a multiple of 8.

RFC states :

   The "original datagram" field MUST contain at least 128 octets.
   If the original datagram did not contain 128 octets, the
   "original datagram" field MUST be zero padded to 128 octets.

In practice routers use 128 bytes of original datagram, not more.

Initial translation was added in commit ca15a078
("sit: generate icmpv6 error when receiving icmpv4 error")
Signed-off-by: default avatarEric Dumazet <edumazet@google.com>
Cc: Oussama Ghorbel <ghorbel@pivasoftware.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 9b8c6d7b
...@@ -18,7 +18,8 @@ typedef void ip6_icmp_send_t(struct sk_buff *skb, u8 type, u8 code, __u32 info, ...@@ -18,7 +18,8 @@ typedef void ip6_icmp_send_t(struct sk_buff *skb, u8 type, u8 code, __u32 info,
const struct in6_addr *force_saddr); 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); int ip6_err_gen_icmpv6_unreach(struct sk_buff *skb, int nhs, int type,
unsigned int data_len);
#else #else
......
...@@ -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;
}; };
......
...@@ -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:
...@@ -189,7 +191,8 @@ static void ipgre_err(struct sk_buff *skb, u32 info, ...@@ -189,7 +191,8 @@ static void ipgre_err(struct sk_buff *skb, u32 info,
#if IS_ENABLED(CONFIG_IPV6) #if IS_ENABLED(CONFIG_IPV6)
if (tpi->proto == htons(ETH_P_IPV6) && if (tpi->proto == htons(ETH_P_IPV6) &&
!ip6_err_gen_icmpv6_unreach(skb, iph->ihl * 4 + tpi->hdr_len, type)) !ip6_err_gen_icmpv6_unreach(skb, iph->ihl * 4 + tpi->hdr_len,
type, data_len))
return; return;
#endif #endif
......
...@@ -564,16 +564,22 @@ void icmpv6_param_prob(struct sk_buff *skb, u8 code, int pos) ...@@ -564,16 +564,22 @@ void icmpv6_param_prob(struct sk_buff *skb, u8 code, int pos)
* Either an IPv4 header for SIT encap * Either an IPv4 header for SIT encap
* an IPv4 header + GRE header for GRE encap * an IPv4 header + GRE header for GRE encap
*/ */
int ip6_err_gen_icmpv6_unreach(struct sk_buff *skb, int nhs, int type) int ip6_err_gen_icmpv6_unreach(struct sk_buff *skb, int nhs, int type,
unsigned int data_len)
{ {
struct in6_addr temp_saddr; struct in6_addr temp_saddr;
struct rt6_info *rt; struct rt6_info *rt;
struct sk_buff *skb2; struct sk_buff *skb2;
u32 info = 0;
if (!pskb_may_pull(skb, nhs + sizeof(struct ipv6hdr) + 8)) if (!pskb_may_pull(skb, nhs + sizeof(struct ipv6hdr) + 8))
return 1; return 1;
skb2 = skb_clone(skb, GFP_ATOMIC); /* 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) if (!skb2)
return 1; return 1;
...@@ -588,12 +594,26 @@ int ip6_err_gen_icmpv6_unreach(struct sk_buff *skb, int nhs, int type) ...@@ -588,12 +594,26 @@ int ip6_err_gen_icmpv6_unreach(struct sk_buff *skb, int nhs, int type)
skb2->dev = rt->dst.dev; skb2->dev = rt->dst.dev;
ipv6_addr_set_v4mapped(ip_hdr(skb)->saddr, &temp_saddr); 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) if (type == ICMP_TIME_EXCEEDED)
icmp6_send(skb2, ICMPV6_TIME_EXCEED, ICMPV6_EXC_HOPLIMIT, icmp6_send(skb2, ICMPV6_TIME_EXCEED, ICMPV6_EXC_HOPLIMIT,
0, &temp_saddr); info, &temp_saddr);
else else
icmp6_send(skb2, ICMPV6_DEST_UNREACH, ICMPV6_ADDR_UNREACH, icmp6_send(skb2, ICMPV6_DEST_UNREACH, ICMPV6_ADDR_UNREACH,
0, &temp_saddr); info, &temp_saddr);
if (rt) if (rt)
ip6_rt_put(rt); ip6_rt_put(rt);
......
...@@ -484,6 +484,7 @@ static int ipip6_err(struct sk_buff *skb, u32 info) ...@@ -484,6 +484,7 @@ 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;
...@@ -508,6 +509,7 @@ static int ipip6_err(struct sk_buff *skb, u32 info) ...@@ -508,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;
...@@ -536,7 +538,7 @@ static int ipip6_err(struct sk_buff *skb, u32 info) ...@@ -536,7 +538,7 @@ static int ipip6_err(struct sk_buff *skb, u32 info)
} }
err = 0; err = 0;
if (!ip6_err_gen_icmpv6_unreach(skb, iph->ihl * 4, type)) if (!ip6_err_gen_icmpv6_unreach(skb, iph->ihl * 4, type, data_len))
goto out; goto out;
if (t->parms.iph.daddr == 0) if (t->parms.iph.daddr == 0)
......
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