diff --git a/net/ipv6/ah6.c b/net/ipv6/ah6.c index ca1e0fb576acbaa564f78b18742fa4e72aaf0915..850fc05b52d8014fb46592eec161b06d2841fcf3 100644 --- a/net/ipv6/ah6.c +++ b/net/ipv6/ah6.c @@ -31,6 +31,7 @@ #include <net/ah.h> #include <linux/crypto.h> #include <linux/pfkeyv2.h> +#include <linux/string.h> #include <net/icmp.h> #include <net/ipv6.h> #include <net/xfrm.h> @@ -74,6 +75,45 @@ static int zero_out_mutable_opts(struct ipv6_opt_hdr *opthdr) return 0; } +/** + * ipv6_rearrange_rthdr - rearrange IPv6 routing header + * @iph: IPv6 header + * @rthdr: routing header + * + * Rearrange the destination address in @iph and the addresses in @rthdr + * so that they appear in the order they will at the final destination. + * See Appendix A2 of RFC 2402 for details. + */ +static void ipv6_rearrange_rthdr(struct ipv6hdr *iph, struct ipv6_rt_hdr *rthdr) +{ + int segments, segments_left; + struct in6_addr *addrs; + struct in6_addr final_addr; + + segments_left = rthdr->segments_left; + if (segments_left == 0) + return; + rthdr->segments_left = 0; + + /* The value of rthdr->hdrlen has been verified either by the system + * call if it is locally generated, or by ipv6_rthdr_rcv() for incoming + * packets. So we can assume that it is even and that segments is + * greater than or equal to segments_left. + * + * For the same reason we can assume that this option is of type 0. + */ + segments = rthdr->hdrlen >> 1; + + addrs = ((struct rt0_hdr *)rthdr)->addr; + ipv6_addr_copy(&final_addr, addrs + segments - 1); + + addrs += segments - segments_left; + memmove(addrs + 1, addrs, (segments_left - 1) * sizeof(*addrs)); + + ipv6_addr_copy(addrs, &iph->daddr); + ipv6_addr_copy(&iph->daddr, &final_addr); +} + static int ipv6_clear_mutable_options(struct ipv6hdr *iph, int len) { union { @@ -101,7 +141,7 @@ static int ipv6_clear_mutable_options(struct ipv6hdr *iph, int len) break; case NEXTHDR_ROUTING: - exthdr.rth->segments_left = 0; + ipv6_rearrange_rthdr(iph, exthdr.rth); break; default :