2 * Extension Header handling and adding code
5 * Sami Kivisaari <skivisaa@cc.hut.fi>
7 * $Id: s.exthdrs.c 1.73 03/10/02 14:20:42+03:00 henkku@mart10.hut.mediapoli.com $
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.
15 #include <linux/types.h>
16 #include <linux/slab.h>
19 #include <net/ip6_route.h>
20 #include <net/addrconf.h>
21 #include <net/mipv6.h>
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
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.
43 int mipv6_append_home_addr(__u8 *opt, int offset, struct in6_addr *addr)
46 struct mipv6_dstopt_homeaddr *ho;
48 DEBUG(DBG_DATADUMP, "HAO: %x:%x:%x:%x:%x:%x:%x:%x",
51 pad = (6 - offset) & 7;
52 mipv6_add_pad(opt + offset, pad);
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);
59 return offset + pad + sizeof(*ho);
63 * mipv6_handle_homeaddr - Home Address Destination Option handler
65 * @optoff: offset to where option begins
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.
73 int mipv6_handle_homeaddr(struct sk_buff *skb, int optoff)
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;
85 if (haopt->length != sizeof(*haopt) - 2) {
86 DEBUG(DBG_WARNING, "HAO has invalid length");
87 MIPV6_INC_STATS(n_ha_drop.invalid);
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",
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);
105 opt->hao = optoff + 2;
106 if (mip6_fn.mn_check_tunneled_packet != NULL)
107 mip6_fn.mn_check_tunneled_packet(skb);
109 MIPV6_INC_STATS(n_ha_rcvd);
114 * mipv6_icmp_handle_homeaddr - Switch HAO and source for ICMP errors
115 * @skb: packet buffer
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.
121 void mipv6_icmp_handle_homeaddr(struct sk_buff *skb)
123 struct inet6_skb_parm *opt = (struct inet6_skb_parm *)skb->cb;
129 struct in6_addr *ha =
130 (struct in6_addr *)(skb->nh.raw + opt->hao);
132 ipv6_addr_copy(&tmp, ha);
133 ipv6_addr_copy(ha, &skb->nh.ipv6h->saddr);
134 ipv6_addr_copy(&skb->nh.ipv6h->saddr, &tmp);
139 * mipv6_append_rt2hdr - Add Type 2 Routing Header
140 * @rt: buffer for new routing header
141 * @addr: intermediate hop address
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.
148 void mipv6_append_rt2hdr(struct ipv6_rt_hdr *rt, struct in6_addr *addr)
150 struct rt2_hdr *rt2 = (struct rt2_hdr *)rt;
152 DEBUG(DBG_DATADUMP, "RT2: %x:%x:%x:%x:%x:%x:%x:%x",
155 if (ipv6_addr_type(addr) == IPV6_ADDR_MULTICAST) {
156 DEBUG(DBG_ERROR, "destination address not unicast");
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);
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
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).
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.
189 mipv6_append_dst1opts(struct ipv6_opt_hdr *dst1opt, struct in6_addr *saddr,
190 struct ipv6_opt_hdr *old_dst1opt, int len)
195 memcpy(dst1opt, old_dst1opt, ipv6_optlen(old_dst1opt));
196 offset = ipv6_optlen(old_dst1opt);
198 offset = sizeof (*dst1opt);
200 dst1opt->hdrlen = (len >> 3) - 1;
201 mipv6_append_home_addr((__u8 *) dst1opt, offset, saddr);
205 * mipv6_modify_txoptions - Modify outgoing packets
207 * @skb: packet buffer for outgoing packet
208 * @old_opt: transmit options
209 * @fl: packet flow structure
210 * @dst: pointer to destination cache entry
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
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)
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 ? */
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;
240 if (fl->proto == IPPROTO_MOBILITY) return old_opt;
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.
245 saddr = fl ? &fl->fl6_src : NULL;
246 daddr = fl ? &fl->fl6_dst : NULL;
251 int err = ipv6_get_saddr(NULL, daddr, &tmpaddr);
259 "dest. address of packet: %x:%x:%x:%x:%x:%x:%x:%x",
261 DEBUG(DBG_DATADUMP, " and src. address: %x:%x:%x:%x:%x:%x:%x:%x",
265 old_hopopt = old_opt->hopopt;
266 old_dst1opt = old_opt->dst1opt;
267 old_srcrt = old_opt->srcrt;
268 old_dst0opt = old_opt->dst0opt;
271 if (mip6_fn.mn_use_hao != NULL)
272 use_hao = mip6_fn.mn_use_hao(daddr, saddr);
276 dst1len = ipv6_optlen(old_dst1opt);
277 dst1len += sizeof(struct mipv6_dstopt_homeaddr) +
278 ((6 - dst1len) & 7); /* padding */
281 if (mipv6_bcache_get(daddr, saddr, &bc_entry) == 0)
282 srcrtlen = sizeof(struct rt2_hdr);
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);
291 tot_len = sizeof(*opt);
293 if ((tot_len += (srcrtlen + dst1len)) == 0) {
297 /* tot_len += sizeof(*opt); XXX : Already included in old_opt->len */
299 if (!(opt = kmalloc(tot_len, GFP_ATOMIC))) {
302 memset(opt, 0, tot_len);
303 opt->tot_len = tot_len;
304 opt_ptr = (__u8 *) (opt + 1);
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));
314 DEBUG(DBG_DATADUMP, "Binding exists. Adding routing header");
316 opt->srcrt2 = (struct ipv6_rt_hdr *) opt_ptr;
317 opt->opt_nflen += srcrtlen;
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))
328 ipv6_addr_copy(&coaddr, &bc_entry.coa);
330 mipv6_append_rt2hdr(opt->srcrt2, &coaddr);
333 * reroute output (we have to do this in case of TCP
334 * segment) unless a routing header of type 0 is also added
336 if (dst && !opt->srcrt) {
339 ipv6_addr_copy(&tmp, &fl->fl6_dst);
340 ipv6_addr_copy(&fl->fl6_dst, &coaddr);
343 *dst = ip6_route_output(sk, fl);
346 ipv6_addr_copy(&fl->fl6_dst, &tmp);
348 DEBUG(DBG_DATADUMP, "Rerouted outgoing packet");
352 /* Only home address option is inserted to first dst opt header */
354 opt->dst1opt = (struct ipv6_opt_hdr *) opt_ptr;
356 opt->opt_flen += dst1len;
357 mipv6_append_dst1opts(opt->dst1opt, saddr, old_dst1opt,
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;
364 opt->opt_flen += optlen;
365 memcpy(opt->dst1opt, old_dst1opt, optlen);
368 optlen = ipv6_optlen(old_hopopt);
369 opt->hopopt = (struct ipv6_opt_hdr *) opt_ptr;
371 opt->opt_nflen += optlen;
372 memcpy(opt_ptr, old_hopopt, optlen);
375 optlen = ipv6_optlen(old_dst0opt);
376 opt->dst0opt = (struct ipv6_opt_hdr *) opt_ptr;
378 opt->opt_nflen += optlen;
379 memcpy(opt_ptr, old_dst0opt, optlen);