X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=net%2Fipv6%2Fah6.c;h=eda2737e572b17aa442c9beeb062088788f91fa2;hb=9bf4aaab3e101692164d49b7ca357651eb691cb6;hp=4f61bffd721dd3363f0a94dd637430c6e37d863f;hpb=5273a3df6485dc2ad6aa7ddd441b9a21970f003b;p=linux-2.6.git diff --git a/net/ipv6/ah6.c b/net/ipv6/ah6.c index 4f61bffd7..eda2737e5 100644 --- a/net/ipv6/ah6.c +++ b/net/ipv6/ah6.c @@ -26,11 +26,11 @@ #include #include -#include #include #include #include #include +#include #include #include #include @@ -74,136 +74,138 @@ bad: return 0; } -static int ipv6_clear_mutable_options(struct sk_buff *skb, u16 *nh_offset, int dir) +/** + * 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) { - u16 offset = sizeof(struct ipv6hdr); - struct ipv6_opt_hdr *exthdr = (struct ipv6_opt_hdr*)(skb->nh.raw + offset); - unsigned int packet_len = skb->tail - skb->nh.raw; - u8 nexthdr = skb->nh.ipv6h->nexthdr; - u8 nextnexthdr = 0; + 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; - *nh_offset = ((unsigned char *)&skb->nh.ipv6h->nexthdr) - skb->nh.raw; + addrs = ((struct rt0_hdr *)rthdr)->addr; + ipv6_addr_copy(&final_addr, addrs + segments - 1); - while (offset + 1 <= packet_len) { + addrs += segments - segments_left; + memmove(addrs + 1, addrs, (segments_left - 1) * sizeof(*addrs)); - switch (nexthdr) { + 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 { + struct ipv6hdr *iph; + struct ipv6_opt_hdr *opth; + struct ipv6_rt_hdr *rth; + char *raw; + } exthdr = { .iph = iph }; + char *end = exthdr.raw + len; + int nexthdr = iph->nexthdr; + + exthdr.iph++; + + while (exthdr.raw < end) { + switch (nexthdr) { case NEXTHDR_HOP: - *nh_offset = offset; - offset += ipv6_optlen(exthdr); - if (!zero_out_mutable_opts(exthdr)) { - LIMIT_NETDEBUG( - printk(KERN_WARNING "overrun hopopts\n")); - return 0; + case NEXTHDR_DEST: + if (!zero_out_mutable_opts(exthdr.opth)) { + LIMIT_NETDEBUG(printk( + KERN_WARNING "overrun %sopts\n", + nexthdr == NEXTHDR_HOP ? + "hop" : "dest")); + return -EINVAL; } - nexthdr = exthdr->nexthdr; - exthdr = (struct ipv6_opt_hdr*)(skb->nh.raw + offset); break; case NEXTHDR_ROUTING: - *nh_offset = offset; - offset += ipv6_optlen(exthdr); - ((struct ipv6_rt_hdr*)exthdr)->segments_left = 0; - nexthdr = exthdr->nexthdr; - exthdr = (struct ipv6_opt_hdr*)(skb->nh.raw + offset); + ipv6_rearrange_rthdr(iph, exthdr.rth); break; - case NEXTHDR_DEST: - *nh_offset = offset; - offset += ipv6_optlen(exthdr); - if (!zero_out_mutable_opts(exthdr)) { - LIMIT_NETDEBUG( - printk(KERN_WARNING "overrun destopt\n")); - return 0; - } - nexthdr = exthdr->nexthdr; - exthdr = (struct ipv6_opt_hdr*)(skb->nh.raw + offset); - break; - - case NEXTHDR_AUTH: - if (dir == XFRM_POLICY_OUT) { - memset(((struct ipv6_auth_hdr*)exthdr)->auth_data, 0, - (((struct ipv6_auth_hdr*)exthdr)->hdrlen - 1) << 2); - } - if (exthdr->nexthdr == NEXTHDR_DEST) { - offset += (((struct ipv6_auth_hdr*)exthdr)->hdrlen + 2) << 2; - exthdr = (struct ipv6_opt_hdr*)(skb->nh.raw + offset); - nextnexthdr = exthdr->nexthdr; - if (!zero_out_mutable_opts(exthdr)) { - LIMIT_NETDEBUG( - printk(KERN_WARNING "overrun destopt\n")); - return 0; - } - } - return nexthdr; default : - return nexthdr; + return 0; } + + nexthdr = exthdr.opth->nexthdr; + exthdr.raw += ipv6_optlen(exthdr.opth); } - return nexthdr; + return 0; } -int ah6_output(struct sk_buff *skb) +int ah6_output(struct sk_buff **pskb) { int err; - int hdr_len = sizeof(struct ipv6hdr); - struct dst_entry *dst = skb->dst; + int extlen; + struct dst_entry *dst = (*pskb)->dst; struct xfrm_state *x = dst->xfrm; - struct ipv6hdr *iph = NULL; + struct ipv6hdr *top_iph; struct ip_auth_hdr *ah; struct ah_data *ahp; - u16 nh_offset = 0; u8 nexthdr; + char tmp_base[8]; + struct { + struct in6_addr daddr; + char hdrs[0]; + } *tmp_ext; - if (skb->ip_summed == CHECKSUM_HW && skb_checksum_help(skb) == NULL) { - err = -EINVAL; - goto error_nolock; - } + top_iph = (struct ipv6hdr *)(*pskb)->data; + top_iph->payload_len = htons((*pskb)->len - sizeof(*top_iph)); - spin_lock_bh(&x->lock); - err = xfrm_check_output(x, skb, AF_INET6); - if (err) - goto error; + nexthdr = *(*pskb)->nh.raw; + *(*pskb)->nh.raw = IPPROTO_AH; - if (x->props.mode) { - iph = skb->nh.ipv6h; - skb->nh.ipv6h = (struct ipv6hdr*)skb_push(skb, x->props.header_len); - skb->nh.ipv6h->version = 6; - skb->nh.ipv6h->payload_len = htons(skb->len - sizeof(struct ipv6hdr)); - skb->nh.ipv6h->nexthdr = IPPROTO_AH; - ipv6_addr_copy(&skb->nh.ipv6h->saddr, - (struct in6_addr *) &x->props.saddr); - ipv6_addr_copy(&skb->nh.ipv6h->daddr, - (struct in6_addr *) &x->id.daddr); - ah = (struct ip_auth_hdr*)(skb->nh.ipv6h+1); - ah->nexthdr = IPPROTO_IPV6; - } else { - hdr_len = skb->h.raw - skb->nh.raw; - iph = kmalloc(hdr_len, GFP_ATOMIC); - if (!iph) { + /* When there are no extension headers, we only need to save the first + * 8 bytes of the base IP header. + */ + memcpy(tmp_base, top_iph, sizeof(tmp_base)); + + tmp_ext = NULL; + extlen = (*pskb)->h.raw - (unsigned char *)(top_iph + 1); + if (extlen) { + extlen += sizeof(*tmp_ext); + tmp_ext = kmalloc(extlen, GFP_ATOMIC); + if (!tmp_ext) { err = -ENOMEM; goto error; } - memcpy(iph, skb->data, hdr_len); - skb->nh.ipv6h = (struct ipv6hdr*)skb_push(skb, x->props.header_len); - memcpy(skb->nh.ipv6h, iph, hdr_len); - nexthdr = ipv6_clear_mutable_options(skb, &nh_offset, XFRM_POLICY_OUT); - if (nexthdr == 0) - goto error; - - skb->nh.raw[nh_offset] = IPPROTO_AH; - skb->nh.ipv6h->payload_len = htons(skb->len - sizeof(struct ipv6hdr)); - ah = (struct ip_auth_hdr*)(skb->nh.raw+hdr_len); - skb->h.raw = (unsigned char*) ah; - ah->nexthdr = nexthdr; + memcpy(tmp_ext, &top_iph->daddr, extlen); + err = ipv6_clear_mutable_options(top_iph, + extlen - sizeof(*tmp_ext) + + sizeof(*top_iph)); + if (err) + goto error_free_iph; } - skb->nh.ipv6h->priority = 0; - skb->nh.ipv6h->flow_lbl[0] = 0; - skb->nh.ipv6h->flow_lbl[1] = 0; - skb->nh.ipv6h->flow_lbl[2] = 0; - skb->nh.ipv6h->hop_limit = 0; + ah = (struct ip_auth_hdr *)(*pskb)->h.raw; + ah->nexthdr = nexthdr; + + top_iph->priority = 0; + top_iph->flow_lbl[0] = 0; + top_iph->flow_lbl[1] = 0; + top_iph->flow_lbl[2] = 0; + top_iph->hop_limit = 0; ahp = x->data; ah->hdrlen = (XFRM_ALIGN8(sizeof(struct ipv6_auth_hdr) + @@ -212,37 +214,18 @@ int ah6_output(struct sk_buff *skb) ah->reserved = 0; ah->spi = x->id.spi; ah->seq_no = htonl(++x->replay.oseq); - ahp->icv(ahp, skb, ah->auth_data); - - if (x->props.mode) { - skb->nh.ipv6h->hop_limit = iph->hop_limit; - skb->nh.ipv6h->priority = iph->priority; - skb->nh.ipv6h->flow_lbl[0] = iph->flow_lbl[0]; - skb->nh.ipv6h->flow_lbl[1] = iph->flow_lbl[1]; - skb->nh.ipv6h->flow_lbl[2] = iph->flow_lbl[2]; - if (x->props.flags & XFRM_STATE_NOECN) - IP6_ECN_clear(skb->nh.ipv6h); - } else { - memcpy(skb->nh.ipv6h, iph, hdr_len); - skb->nh.raw[nh_offset] = IPPROTO_AH; - skb->nh.ipv6h->payload_len = htons(skb->len - sizeof(struct ipv6hdr)); - kfree (iph); - } + ahp->icv(ahp, *pskb, ah->auth_data); - skb->nh.raw = skb->data; + err = 0; - x->curlft.bytes += skb->len; - x->curlft.packets++; - spin_unlock_bh(&x->lock); - if ((skb->dst = dst_pop(dst)) == NULL) { - err = -EHOSTUNREACH; - goto error_nolock; + memcpy(top_iph, tmp_base, sizeof(tmp_base)); + if (tmp_ext) { + memcpy(&top_iph->daddr, tmp_ext, extlen); +error_free_iph: + kfree(tmp_ext); } - return NET_XMIT_BYPASS; + error: - spin_unlock_bh(&x->lock); -error_nolock: - kfree_skb(skb); return err; } @@ -252,7 +235,6 @@ int ah6_input(struct xfrm_state *x, struct xfrm_decap_state *decap, struct sk_bu * Before process AH * [IPv6][Ext1][Ext2][AH][Dest][Payload] * |<-------------->| hdr_len - * |<------------------------>| cleared_hlen * * To erase AH: * Keeping copy of cleared headers. After AH processing, @@ -269,10 +251,7 @@ int ah6_input(struct xfrm_state *x, struct xfrm_decap_state *decap, struct sk_bu unsigned char *tmp_hdr = NULL; u16 hdr_len; u16 ah_hlen; - u16 cleared_hlen; - u16 nh_offset = 0; - u8 nexthdr = 0; - u8 *prevhdr; + int nexthdr; if (!pskb_may_pull(skb, sizeof(struct ip_auth_hdr))) goto out; @@ -284,17 +263,10 @@ int ah6_input(struct xfrm_state *x, struct xfrm_decap_state *decap, struct sk_bu goto out; hdr_len = skb->data - skb->nh.raw; - cleared_hlen = hdr_len; ah = (struct ipv6_auth_hdr*)skb->data; ahp = x->data; nexthdr = ah->nexthdr; ah_hlen = (ah->hdrlen + 2) << 2; - cleared_hlen += ah_hlen; - - if (nexthdr == NEXTHDR_DEST) { - struct ipv6_opt_hdr *dsthdr = (struct ipv6_opt_hdr*)(skb->data + ah_hlen); - cleared_hlen += ipv6_optlen(dsthdr); - } if (ah_hlen != XFRM_ALIGN8(sizeof(struct ipv6_auth_hdr) + ahp->icv_full_len) && ah_hlen != XFRM_ALIGN8(sizeof(struct ipv6_auth_hdr) + ahp->icv_trunc_len)) @@ -303,11 +275,12 @@ int ah6_input(struct xfrm_state *x, struct xfrm_decap_state *decap, struct sk_bu if (!pskb_may_pull(skb, ah_hlen)) goto out; - tmp_hdr = kmalloc(cleared_hlen, GFP_ATOMIC); + tmp_hdr = kmalloc(hdr_len, GFP_ATOMIC); if (!tmp_hdr) goto out; - memcpy(tmp_hdr, skb->nh.raw, cleared_hlen); - ipv6_clear_mutable_options(skb, &nh_offset, XFRM_POLICY_IN); + memcpy(tmp_hdr, skb->nh.raw, hdr_len); + if (ipv6_clear_mutable_options(skb->nh.ipv6h, hdr_len)) + goto out; skb->nh.ipv6h->priority = 0; skb->nh.ipv6h->flow_lbl[0] = 0; skb->nh.ipv6h->flow_lbl[1] = 0; @@ -331,13 +304,6 @@ int ah6_input(struct xfrm_state *x, struct xfrm_decap_state *decap, struct sk_bu skb->nh.raw = skb_pull(skb, ah_hlen); memcpy(skb->nh.raw, tmp_hdr, hdr_len); - if (nexthdr == NEXTHDR_DEST) { - memcpy(skb->nh.raw + hdr_len, - tmp_hdr + hdr_len + ah_hlen, - cleared_hlen - hdr_len - ah_hlen); - } - prevhdr = (u8*)(skb->nh.raw + nh_offset); - *prevhdr = nexthdr; skb->nh.ipv6h->payload_len = htons(skb->len - sizeof(struct ipv6hdr)); skb_pull(skb, hdr_len); skb->h.raw = skb->data; @@ -360,7 +326,7 @@ void ah6_err(struct sk_buff *skb, struct inet6_skb_parm *opt, struct ip_auth_hdr *ah = (struct ip_auth_hdr*)(skb->data+offset); struct xfrm_state *x; - if (type != ICMPV6_DEST_UNREACH || + if (type != ICMPV6_DEST_UNREACH && type != ICMPV6_PKT_TOOBIG) return;