Two swsusp patches:
[linux-flexiantxendom0-3.2.10.git] / net / ipv6 / mobile_ip6 / exthdrs.c
1 /*
2  *      Extension Header handling and adding code
3  *
4  *      Authors:
5  *      Sami Kivisaari          <skivisaa@cc.hut.fi>    
6  *
7  *      $Id: s.exthdrs.c 1.73 03/10/02 14:20:42+03:00 henkku@mart10.hut.mediapoli.com $
8  *
9  *      This program is free software; you can redistribute it and/or
10  *      modify it under the terms of the GNU General Public License
11  *      as published by the Free Software Foundation; either version
12  *      2 of the License, or (at your option) any later version.
13  */
14
15 #include <linux/types.h>
16 #include <linux/slab.h>
17
18 #include <net/ipv6.h>
19 #include <net/ip6_route.h>
20 #include <net/addrconf.h>
21 #include <net/mipv6.h>
22
23 #include "debug.h"
24 #include "stats.h"
25 #include "mobhdr.h"
26 #include "bcache.h"
27 #include "config.h"
28
29 /**
30  * mipv6_append_home_addr - Add Home Address Option
31  * @opt: buffer for Home Address Option
32  * @offset: offset from beginning of @opt
33  * @addr: address for HAO
34  *
35  * Adds a Home Address Option to a packet.  Option is stored in
36  * @offset from beginning of @opt.  The option is created but the
37  * original source address in IPv6 header is left intact.  The source
38  * address will be changed from home address to CoA after the checksum
39  * has been calculated in getfrag.  Padding is done automatically, and
40  * @opt must have allocated space for both actual option and pad.
41  * Returns offset from @opt to end of options.
42  **/
43 int mipv6_append_home_addr(__u8 *opt, int offset, struct in6_addr *addr)
44 {
45         int pad;
46         struct mipv6_dstopt_homeaddr *ho;
47
48         DEBUG(DBG_DATADUMP, "HAO: %x:%x:%x:%x:%x:%x:%x:%x",
49               NIPV6ADDR(addr));
50
51         pad = (6 - offset) & 7;
52         mipv6_add_pad(opt + offset, pad);
53
54         ho = (struct mipv6_dstopt_homeaddr *)(opt + offset + pad);
55         ho->type = MIPV6_TLV_HOMEADDR;
56         ho->length = sizeof(*ho) - 2;
57         ipv6_addr_copy(&ho->addr, addr); 
58
59         return offset + pad + sizeof(*ho);
60 }
61
62 /**
63  * mipv6_handle_homeaddr - Home Address Destination Option handler
64  * @skb: packet buffer
65  * @optoff: offset to where option begins
66  *
67  * Handles Home Address Option in IPv6 Destination Option header.
68  * Packet and offset to option are passed.  If HAO is used without
69  * binding, sends a Binding Error code 1.  When sending BE, notify bit
70  * is cleared to prevent IPv6 error handling from sending ICMP
71  * Parameter Problem.  Returns 1 on success, otherwise zero.
72  **/
73 int mipv6_handle_homeaddr(struct sk_buff *skb, int optoff)
74 {
75         struct in6_addr *saddr = &(skb->nh.ipv6h->saddr);
76         struct in6_addr coaddr;
77         struct inet6_skb_parm *opt = (struct inet6_skb_parm *) skb->cb;
78         struct mipv6_dstopt_homeaddr *haopt =
79             (struct mipv6_dstopt_homeaddr *) &skb->nh.raw[optoff];
80         struct mipv6_bce bc_entry;
81         u8 *dst1;
82
83         DEBUG_FUNC();
84
85         if (haopt->length != sizeof(*haopt) - 2) {
86                 DEBUG(DBG_WARNING, "HAO has invalid length");
87                 MIPV6_INC_STATS(n_ha_drop.invalid);
88                 return 0;
89         }
90         ipv6_addr_copy(&coaddr, saddr);
91         ipv6_addr_copy(saddr, &haopt->addr);
92         ipv6_addr_copy(&haopt->addr, &coaddr);
93         dst1 = (u8 *)skb->h.raw;
94         if (dst1[0] != IPPROTO_MOBILITY && 
95             mipv6_bcache_get(saddr, &skb->nh.ipv6h->daddr, &bc_entry) != 0) {
96                 DEBUG(DBG_INFO, "HAO without binding, sending BE code 1: "
97                       "home address %x:%x:%x:%x:%x:%x:%x:%x",
98                       NIPV6ADDR(saddr));
99                 haopt->type &= ~(0x80); /* clear notify bit */
100                 mipv6_send_be(&skb->nh.ipv6h->daddr, &coaddr, saddr,
101                               MIPV6_BE_HAO_WO_BINDING);
102                 MIPV6_INC_STATS(n_ha_drop.misc);
103                 return 0;
104         }
105         opt->hao = optoff + 2;
106         if (mip6_fn.mn_check_tunneled_packet != NULL)
107                 mip6_fn.mn_check_tunneled_packet(skb);
108
109         MIPV6_INC_STATS(n_ha_rcvd);
110         return 1;
111 }
112
113 /**
114  * mipv6_icmp_handle_homeaddr - Switch HAO and source for ICMP errors
115  * @skb: packet buffer
116  *
117  * Reset the source address and the Home Address option in skb before
118  * appending it to an ICMP error message, so original packet appears
119  * in the error message rather than mangled.
120  **/
121 void mipv6_icmp_handle_homeaddr(struct sk_buff *skb)
122 {
123         struct inet6_skb_parm *opt = (struct inet6_skb_parm *)skb->cb;
124
125         DEBUG_FUNC();
126
127         if (opt->hao) {
128                 struct in6_addr tmp;
129                 struct in6_addr *ha = 
130                         (struct in6_addr *)(skb->nh.raw + opt->hao);
131
132                 ipv6_addr_copy(&tmp, ha);
133                 ipv6_addr_copy(ha, &skb->nh.ipv6h->saddr);
134                 ipv6_addr_copy(&skb->nh.ipv6h->saddr, &tmp);
135         }
136 }
137
138 /**
139  * mipv6_append_rt2hdr - Add Type 2 Routing Header
140  * @rt: buffer for new routing header
141  * @addr: intermediate hop address
142  *
143  * Adds a Routing Header Type 2 in a packet.  Stores newly created
144  * routing header in buffer @rt.  Type 2 RT only carries one address,
145  * so there is no need to process old routing header.  @rt must have
146  * allocated space for 24 bytes.
147  **/
148 void mipv6_append_rt2hdr(struct ipv6_rt_hdr *rt, struct in6_addr *addr)
149 {
150         struct rt2_hdr *rt2 = (struct rt2_hdr *)rt;
151
152         DEBUG(DBG_DATADUMP, "RT2: %x:%x:%x:%x:%x:%x:%x:%x",
153               NIPV6ADDR(addr));
154
155         if (ipv6_addr_type(addr) == IPV6_ADDR_MULTICAST) {
156                 DEBUG(DBG_ERROR, "destination address not unicast");
157                 return;
158         }
159
160         memset(rt2, 0, sizeof(*rt2));
161         rt2->rt_hdr.type = 2;
162         rt2->rt_hdr.hdrlen = 2;
163         rt2->rt_hdr.segments_left = 1;
164         ipv6_addr_copy(&rt2->addr, addr);
165 }
166
167 /**
168  * mipv6_append_dst1opts - Add Destination Option (1) Headers
169  * @dst1opt: buffer for new destination options
170  * @saddr: address for Home Address Option
171  * @old_dst1opt: old destination options
172  * @len: length of options
173  *
174  * Adds Destination Option (1) Header to a packet.  New options are
175  * stored in @dst1opt.  If old destination options exist, they are
176  * copied from @old_dst1opt.  Only Home Address Option is destination
177  * option.  @dstopt must have allocated space for @len bytes.  @len
178  * includes Destination Option Header (2 bytes), Home Address Option
179  * (18 bytes) and possible HAO pad (8n+6).
180  **/
181 /*
182  * ISSUE: Home Address Destination Option should really be added to a
183  * new destination option header specified in Mobile IPv6 spec which
184  * should be placed after routing header(s), but before fragmentation
185  * header.  Putting HAO in DO1 works for now, but support for the new
186  * placement should be added to the IPv6 stack.
187  */
188 void 
189 mipv6_append_dst1opts(struct ipv6_opt_hdr *dst1opt, struct in6_addr *saddr,
190                       struct ipv6_opt_hdr *old_dst1opt, int len)
191 {
192         int offset;
193
194         if (old_dst1opt) {
195                 memcpy(dst1opt, old_dst1opt, ipv6_optlen(old_dst1opt));
196                 offset = ipv6_optlen(old_dst1opt);
197         } else {
198                 offset = sizeof (*dst1opt);
199         }
200         dst1opt->hdrlen = (len >> 3) - 1;
201         mipv6_append_home_addr((__u8 *) dst1opt, offset, saddr);
202 }
203
204 /**
205  * mipv6_modify_txoptions - Modify outgoing packets
206  * @sk: socket
207  * @skb: packet buffer for outgoing packet
208  * @old_opt: transmit options
209  * @fl: packet flow structure
210  * @dst: pointer to destination cache entry
211  *
212  * Adds Home Address Option (for MN packets, when not at home) and
213  * Routing Header Type 2 (for CN packets when sending to an MN) to
214  * data packets.  Old extension headers are copied from @old_opt (if
215  * any).  Extension headers are _explicitly_ added for packets with
216  * Mobility Header.  Returns the new header structure, or old if no
217  * changes.
218  **/
219 struct ipv6_txoptions *
220 mipv6_modify_txoptions(struct sock *sk, struct sk_buff *skb, 
221                        struct ipv6_txoptions *old_opt, struct flowi *fl, 
222                        struct dst_entry **dst)
223 {       
224         struct ipv6_opt_hdr *old_hopopt = NULL;
225         struct ipv6_opt_hdr *old_dst1opt = NULL;
226         struct ipv6_rt_hdr *old_srcrt = NULL;
227         struct ipv6_opt_hdr *old_dst0opt = NULL;
228         /* XXX: What about auth opt ? */
229
230         int optlen;
231         int srcrtlen = 0, dst1len = 0;
232         int tot_len, use_hao = 0;
233         struct ipv6_txoptions *opt;
234         struct mipv6_bce bc_entry;
235         struct in6_addr tmpaddr, *saddr, *daddr, coaddr;
236         __u8 *opt_ptr;
237
238         DEBUG_FUNC();
239
240         if (fl->proto == IPPROTO_MOBILITY) return old_opt;
241         /*
242          * we have to be prepared to the fact that saddr might not be present,
243          * if that is the case, we acquire saddr just as kernel does.
244          */
245         saddr = fl ? &fl->fl6_src : NULL;
246         daddr = fl ? &fl->fl6_dst : NULL;
247
248         if (daddr == NULL)
249                 return old_opt;
250         if (saddr == NULL) {
251                 int err = ipv6_get_saddr(NULL, daddr, &tmpaddr);
252                 if (err)
253                         return old_opt;
254                 else
255                         saddr = &tmpaddr;
256         }
257
258         DEBUG(DBG_DATADUMP,
259               "dest. address of packet: %x:%x:%x:%x:%x:%x:%x:%x",
260               NIPV6ADDR(daddr));
261         DEBUG(DBG_DATADUMP, " and src. address: %x:%x:%x:%x:%x:%x:%x:%x", 
262               NIPV6ADDR(saddr));
263
264         if (old_opt) {
265                 old_hopopt = old_opt->hopopt;
266                 old_dst1opt = old_opt->dst1opt;
267                 old_srcrt = old_opt->srcrt;
268                 old_dst0opt = old_opt->dst0opt;
269         } 
270
271         if (mip6_fn.mn_use_hao != NULL)
272                 use_hao = mip6_fn.mn_use_hao(daddr, saddr);
273
274         if (use_hao) {
275                 if (old_dst1opt)
276                         dst1len = ipv6_optlen(old_dst1opt);
277                 dst1len += sizeof(struct mipv6_dstopt_homeaddr) +
278                         ((6 - dst1len) & 7); /* padding */
279         }
280
281         if (mipv6_bcache_get(daddr, saddr, &bc_entry) == 0)
282                 srcrtlen = sizeof(struct rt2_hdr);
283
284         if (old_opt) {
285                 tot_len = old_opt->tot_len;
286                 if (use_hao && old_dst1opt) {
287                         /* already accounted for old_dst1opt in check above */
288                         tot_len -= ipv6_optlen(old_dst1opt);
289                 }
290         } else
291                 tot_len = sizeof(*opt);
292
293         if ((tot_len += (srcrtlen + dst1len)) == 0) { 
294                 return old_opt;
295         }
296
297         /* tot_len += sizeof(*opt); XXX : Already included in old_opt->len */
298
299         if (!(opt = kmalloc(tot_len, GFP_ATOMIC))) {
300                 return old_opt;
301         }
302         memset(opt, 0, tot_len);
303         opt->tot_len = tot_len;
304         opt_ptr = (__u8 *) (opt + 1);
305         
306         if (old_srcrt) {
307                 opt->srcrt = (struct ipv6_rt_hdr *) opt_ptr;
308                 opt_ptr += ipv6_optlen(old_srcrt);
309                 opt->opt_nflen += ipv6_optlen(old_srcrt);
310                 memcpy(opt->srcrt, old_srcrt, ipv6_optlen(old_srcrt));
311         }
312
313         if (srcrtlen) {
314                 DEBUG(DBG_DATADUMP, "Binding exists. Adding routing header");
315
316                 opt->srcrt2 = (struct ipv6_rt_hdr *) opt_ptr;
317                 opt->opt_nflen += srcrtlen;
318                 opt_ptr += srcrtlen;
319                 
320                 /*
321                  * Append care-of-address to routing header (original
322                  * destination address is home address, the first
323                  * source route segment gets put to the destination
324                  * address and the home address gets to the last
325                  * segment of source route (just as it should)) 
326                  */
327
328                 ipv6_addr_copy(&coaddr, &bc_entry.coa);
329
330                 mipv6_append_rt2hdr(opt->srcrt2, &coaddr);
331
332                 /*
333                  * reroute output (we have to do this in case of TCP
334                  * segment) unless a routing header of type 0 is also added
335                  */
336                 if (dst && !opt->srcrt) {
337                         struct in6_addr tmp;
338
339                         ipv6_addr_copy(&tmp, &fl->fl6_dst);
340                         ipv6_addr_copy(&fl->fl6_dst, &coaddr);
341
342                         dst_release(*dst);
343                         *dst = ip6_route_output(sk, fl);
344                         if (skb)
345                                 skb->dst = *dst;
346                         ipv6_addr_copy(&fl->fl6_dst, &tmp);
347
348                         DEBUG(DBG_DATADUMP, "Rerouted outgoing packet");
349                 }
350         }
351
352         /* Only home address option is inserted to first dst opt header */
353         if (dst1len) {
354                 opt->dst1opt = (struct ipv6_opt_hdr *) opt_ptr;
355                 opt_ptr += dst1len;
356                 opt->opt_flen += dst1len;
357                 mipv6_append_dst1opts(opt->dst1opt, saddr, old_dst1opt,
358                                         dst1len);
359                 opt->mipv6_flags = MIPV6_SND_HAO;
360         } else if (old_dst1opt) {
361                 optlen = ipv6_optlen(old_dst1opt);
362                 opt->dst1opt = (struct ipv6_opt_hdr *) opt_ptr;
363                 opt_ptr += optlen;
364                 opt->opt_flen += optlen;
365                 memcpy(opt->dst1opt, old_dst1opt, optlen);
366         }
367         if (old_hopopt) {
368                 optlen = ipv6_optlen(old_hopopt);
369                 opt->hopopt = (struct ipv6_opt_hdr *) opt_ptr;
370                 opt_ptr += optlen;
371                 opt->opt_nflen += optlen;
372                 memcpy(opt_ptr, old_hopopt, optlen);
373         }       
374         if (old_dst0opt) {
375                 optlen = ipv6_optlen(old_dst0opt);
376                 opt->dst0opt = (struct ipv6_opt_hdr *) opt_ptr;
377                 opt_ptr += optlen;
378                 opt->opt_nflen += optlen;
379                 memcpy(opt_ptr, old_dst0opt, optlen);
380         }       
381         return opt;
382 }