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);
+int ip6_err_gen_icmpv6_unreach(struct sk_buff *skb, int nhs, int type,
+                              unsigned int data_len);
 
 #else
 
 
        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) {
        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:
 
 #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))
+           !ip6_err_gen_icmpv6_unreach(skb, iph->ihl * 4 + tpi->hdr_len,
+                                      type, data_len))
                return;
 #endif
 
 
  *  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)
+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;
 
-       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)
                return 1;
                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,
-                          0, &temp_saddr);
+                          info, &temp_saddr);
        else
                icmp6_send(skb2, ICMPV6_DEST_UNREACH, ICMPV6_ADDR_UNREACH,
-                          0, &temp_saddr);
+                          info, &temp_saddr);
        if (rt)
                ip6_rt_put(rt);
 
 
        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;
 
        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;
        }
 
        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;
 
        if (t->parms.iph.daddr == 0)