1/* 2 * xfrm6_output.c - Common IPsec encapsulation code for IPv6. 3 * Copyright (C) 2002 USAGI/WIDE Project 4 * Copyright (c) 2004 Herbert Xu <herbert@gondor.apana.org.au> 5 * 6 * This program is free software; you can redistribute it and/or 7 * modify it under the terms of the GNU General Public License 8 * as published by the Free Software Foundation; either version 9 * 2 of the License, or (at your option) any later version. 10 */ 11 12#include <linux/compiler.h> 13#include <linux/skbuff.h> 14#include <linux/spinlock.h> 15#include <linux/icmpv6.h> 16#include <linux/netfilter_ipv6.h> 17#include <net/ipv6.h> 18#include <net/xfrm.h> 19 20int xfrm6_find_1stfragopt(struct xfrm_state *x, struct sk_buff *skb, 21 u8 **prevhdr) 22{ 23 return ip6_find_1stfragopt(skb, prevhdr); 24} 25 26EXPORT_SYMBOL(xfrm6_find_1stfragopt); 27 28static int xfrm6_tunnel_check_size(struct sk_buff *skb) 29{ 30 int mtu, ret = 0; 31 struct dst_entry *dst = skb->dst; 32 33 mtu = dst_mtu(dst); 34 if (mtu < IPV6_MIN_MTU) 35 mtu = IPV6_MIN_MTU; 36 37 if (skb->len > mtu) { 38 skb->dev = dst->dev; 39 icmpv6_send(skb, ICMPV6_PKT_TOOBIG, 0, mtu, skb->dev); 40 ret = -EMSGSIZE; 41 } 42 43 return ret; 44} 45 46static int xfrm6_output_one(struct sk_buff *skb) 47{ 48 struct dst_entry *dst = skb->dst; 49 struct xfrm_state *x = dst->xfrm; 50 int err; 51 52 if (skb->ip_summed == CHECKSUM_PARTIAL) { 53 err = skb_checksum_help(skb); 54 if (err) 55 goto error_nolock; 56 } 57 58 if (x->props.mode == XFRM_MODE_TUNNEL) { 59 err = xfrm6_tunnel_check_size(skb); 60 if (err) 61 goto error_nolock; 62 } 63 64 do { 65 spin_lock_bh(&x->lock); 66 err = xfrm_state_check(x, skb); 67 if (err) 68 goto error; 69 70 err = x->mode->output(x, skb); 71 if (err) 72 goto error; 73 74 err = x->type->output(x, skb); 75 if (err) 76 goto error; 77 78 x->curlft.bytes += skb->len; 79 x->curlft.packets++; 80 if (x->props.mode == XFRM_MODE_ROUTEOPTIMIZATION) 81 x->lastused = get_seconds(); 82 83 spin_unlock_bh(&x->lock); 84 85 skb_reset_network_header(skb); 86 87 if (!(skb->dst = dst_pop(dst))) { 88 err = -EHOSTUNREACH; 89 goto error_nolock; 90 } 91 dst = skb->dst; 92 x = dst->xfrm; 93 } while (x && (x->props.mode != XFRM_MODE_TUNNEL)); 94 95 IP6CB(skb)->flags |= IP6SKB_XFRM_TRANSFORMED; 96 err = 0; 97 98out_exit: 99 return err; 100error: 101 spin_unlock_bh(&x->lock); 102error_nolock: 103 kfree_skb(skb); 104 goto out_exit; 105} 106 107static int xfrm6_output_finish2(struct sk_buff *skb) 108{ 109 int err; 110 111 while (likely((err = xfrm6_output_one(skb)) == 0)) { 112 nf_reset(skb); 113 114 err = nf_hook(PF_INET6, NF_IP6_LOCAL_OUT, &skb, NULL, 115 skb->dst->dev, dst_output); 116 if (unlikely(err != 1)) 117 break; 118 119 if (!skb->dst->xfrm) 120 return dst_output(skb); 121 122 err = nf_hook(PF_INET6, NF_IP6_POST_ROUTING, &skb, NULL, 123 skb->dst->dev, xfrm6_output_finish2); 124 if (unlikely(err != 1)) 125 break; 126 } 127 128 return err; 129} 130 131static int xfrm6_output_finish(struct sk_buff *skb) 132{ 133 struct sk_buff *segs; 134 135 if (!skb_is_gso(skb)) 136 return xfrm6_output_finish2(skb); 137 138 skb->protocol = htons(ETH_P_IPV6); 139 segs = skb_gso_segment(skb, 0); 140 kfree_skb(skb); 141 if (unlikely(IS_ERR(segs))) 142 return PTR_ERR(segs); 143 144 do { 145 struct sk_buff *nskb = segs->next; 146 int err; 147 148 segs->next = NULL; 149 err = xfrm6_output_finish2(segs); 150 151 if (unlikely(err)) { 152 while ((segs = nskb)) { 153 nskb = segs->next; 154 segs->next = NULL; 155 kfree_skb(segs); 156 } 157 return err; 158 } 159 160 segs = nskb; 161 } while (segs); 162 163 return 0; 164} 165 166int xfrm6_output(struct sk_buff *skb) 167{ 168 return NF_HOOK(PF_INET6, NF_IP6_POST_ROUTING, skb, NULL, skb->dst->dev, 169 xfrm6_output_finish); 170} 171