X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=net%2Fipv6%2Fah6.c;h=6778173a3dda85d666f1508c77aa7895dedd801a;hb=34a75f0025b9cf803b6a88db032e6ad6950c9313;hp=4f61bffd721dd3363f0a94dd637430c6e37d863f;hpb=5273a3df6485dc2ad6aa7ddd441b9a21970f003b;p=linux-2.6.git diff --git a/net/ipv6/ah6.c b/net/ipv6/ah6.c index 4f61bffd7..6778173a3 100644 --- a/net/ipv6/ah6.c +++ b/net/ipv6/ah6.c @@ -26,13 +26,14 @@ #include #include -#include #include #include #include #include +#include #include #include +#include #include #include @@ -74,136 +75,136 @@ 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; - *nh_offset = ((unsigned char *)&skb->nh.ipv6h->nexthdr) - skb->nh.raw; + segments_left = rthdr->segments_left; + if (segments_left == 0) + return; + rthdr->segments_left = 0; - while (offset + 1 <= packet_len) { + /* 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; - switch (nexthdr) { + 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 { + 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)) { + case NEXTHDR_DEST: + if (!zero_out_mutable_opts(exthdr.opth)) { LIMIT_NETDEBUG( - printk(KERN_WARNING "overrun hopopts\n")); - return 0; + 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) +static int ah6_output(struct xfrm_state *x, struct sk_buff *skb) { int err; - int hdr_len = sizeof(struct ipv6hdr); - struct dst_entry *dst = skb->dst; - struct xfrm_state *x = dst->xfrm; - struct ipv6hdr *iph = NULL; + int extlen; + 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 *)skb->data; + top_iph->payload_len = htons(skb->len - sizeof(*top_iph)); - spin_lock_bh(&x->lock); - err = xfrm_check_output(x, skb, AF_INET6); - if (err) - goto error; + nexthdr = *skb->nh.raw; + *skb->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 = skb->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 *)skb->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,47 +213,28 @@ int ah6_output(struct sk_buff *skb) ah->reserved = 0; ah->spi = x->id.spi; ah->seq_no = htonl(++x->replay.oseq); + xfrm_aevent_doreplay(x); 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); - } - - 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; } -int ah6_input(struct xfrm_state *x, struct xfrm_decap_state *decap, struct sk_buff *skb) +static int ah6_input(struct xfrm_state *x, struct sk_buff *skb) { /* * 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 free_out; skb->nh.ipv6h->priority = 0; skb->nh.ipv6h->flow_lbl[0] = 0; skb->nh.ipv6h->flow_lbl[1] = 0; @@ -322,8 +295,7 @@ int ah6_input(struct xfrm_state *x, struct xfrm_decap_state *decap, struct sk_bu skb_push(skb, skb->data - skb->nh.raw); ahp->icv(ahp, skb, ah->auth_data); if (memcmp(ah->auth_data, auth_data, ahp->icv_trunc_len)) { - LIMIT_NETDEBUG( - printk(KERN_WARNING "ipsec ah authentication error\n")); + LIMIT_NETDEBUG(KERN_WARNING "ipsec ah authentication error\n"); x->stats.integrity_failed++; goto free_out; } @@ -331,13 +303,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; @@ -353,14 +318,14 @@ out: return -EINVAL; } -void ah6_err(struct sk_buff *skb, struct inet6_skb_parm *opt, - int type, int code, int offset, __u32 info) +static void ah6_err(struct sk_buff *skb, struct inet6_skb_parm *opt, + int type, int code, int offset, __u32 info) { struct ipv6hdr *iph = (struct ipv6hdr*)skb->data; 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; @@ -368,14 +333,13 @@ void ah6_err(struct sk_buff *skb, struct inet6_skb_parm *opt, if (!x) return; - NETDEBUG(printk(KERN_DEBUG "pmtu discovery on SA AH/%08x/" - "%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x\n", - ntohl(ah->spi), NIP6(iph->daddr))); + NETDEBUG(KERN_DEBUG "pmtu discovery on SA AH/%08x/" NIP6_FMT "\n", + ntohl(ah->spi), NIP6(iph->daddr)); xfrm_state_put(x); } -static int ah6_init_state(struct xfrm_state *x, void *args) +static int ah6_init_state(struct xfrm_state *x) { struct ah_data *ahp = NULL; struct xfrm_algo_desc *aalg_desc; @@ -387,12 +351,13 @@ static int ah6_init_state(struct xfrm_state *x, void *args) if (x->aalg->alg_key_len > 512) goto error; - ahp = kmalloc(sizeof(*ahp), GFP_KERNEL); + if (x->encap) + goto error; + + ahp = kzalloc(sizeof(*ahp), GFP_KERNEL); if (ahp == NULL) return -ENOMEM; - memset(ahp, 0, sizeof(*ahp)); - ahp->key = x->aalg->alg_key; ahp->key_len = (x->aalg->alg_key_len+7)/8; ahp->tfm = crypto_alloc_tfm(x->aalg->alg_name, 0); @@ -406,7 +371,7 @@ static int ah6_init_state(struct xfrm_state *x, void *args) * we need for AH processing. This lookup cannot fail here * after a successful crypto_alloc_tfm(). */ - aalg_desc = xfrm_aalg_get_byname(x->aalg->alg_name); + aalg_desc = xfrm_aalg_get_byname(x->aalg->alg_name, 0); BUG_ON(!aalg_desc); if (aalg_desc->uinfo.auth.icv_fullbits/8 != @@ -435,10 +400,8 @@ static int ah6_init_state(struct xfrm_state *x, void *args) error: if (ahp) { - if (ahp->work_icv) - kfree(ahp->work_icv); - if (ahp->tfm) - crypto_free_tfm(ahp->tfm); + kfree(ahp->work_icv); + crypto_free_tfm(ahp->tfm); kfree(ahp); } return -EINVAL; @@ -451,14 +414,10 @@ static void ah6_destroy(struct xfrm_state *x) if (!ahp) return; - if (ahp->work_icv) { - kfree(ahp->work_icv); - ahp->work_icv = NULL; - } - if (ahp->tfm) { - crypto_free_tfm(ahp->tfm); - ahp->tfm = NULL; - } + kfree(ahp->work_icv); + ahp->work_icv = NULL; + crypto_free_tfm(ahp->tfm); + ahp->tfm = NULL; kfree(ahp); } @@ -479,7 +438,7 @@ static struct inet6_protocol ah6_protocol = { .flags = INET6_PROTO_NOPOLICY, }; -int __init ah6_init(void) +static int __init ah6_init(void) { if (xfrm_register_type(&ah6_type, AF_INET6) < 0) { printk(KERN_INFO "ipv6 ah init: can't add xfrm type\n");