ip_options.c 15.0 KB
Newer Older
L
Linus Torvalds 已提交
1 2 3 4 5 6 7 8
/*
 * INET		An implementation of the TCP/IP protocol suite for the LINUX
 *		operating system.  INET is implemented using the  BSD Socket
 *		interface as the means of communication with the user level.
 *
 *		The options processing module for ip.c
 *
 * Authors:	A.N.Kuznetsov
9
 *
L
Linus Torvalds 已提交
10 11
 */

12 13
#define pr_fmt(fmt) "IPv4: " fmt

14
#include <linux/capability.h>
L
Linus Torvalds 已提交
15
#include <linux/module.h>
16
#include <linux/slab.h>
L
Linus Torvalds 已提交
17
#include <linux/types.h>
18
#include <linux/uaccess.h>
19
#include <asm/unaligned.h>
L
Linus Torvalds 已提交
20 21 22 23 24 25 26 27
#include <linux/skbuff.h>
#include <linux/ip.h>
#include <linux/icmp.h>
#include <linux/netdevice.h>
#include <linux/rtnetlink.h>
#include <net/sock.h>
#include <net/ip.h>
#include <net/icmp.h>
28
#include <net/route.h>
P
Paul Moore 已提交
29
#include <net/cipso_ipv4.h>
30
#include <net/ip_fib.h>
L
Linus Torvalds 已提交
31

32
/*
L
Linus Torvalds 已提交
33 34 35 36 37 38 39 40 41 42
 * Write options to IP header, record destination address to
 * source route option, address of outgoing interface
 * (we should already know it, so that this  function is allowed be
 * called only after routing decision) and timestamp,
 * if we originate this datagram.
 *
 * daddr is real destination address, next hop is recorded in IP header.
 * saddr is address of outgoing interface.
 */

43
void ip_options_build(struct sk_buff *skb, struct ip_options *opt,
44
		      __be32 daddr, struct rtable *rt, int is_frag)
L
Linus Torvalds 已提交
45
{
46
	unsigned char *iph = skb_network_header(skb);
L
Linus Torvalds 已提交
47 48 49 50 51 52 53 54 55 56

	memcpy(&(IPCB(skb)->opt), opt, sizeof(struct ip_options));
	memcpy(iph+sizeof(struct iphdr), opt->__data, opt->optlen);
	opt = &(IPCB(skb)->opt);

	if (opt->srr)
		memcpy(iph+opt->srr+iph[opt->srr+1]-4, &daddr, 4);

	if (!is_frag) {
		if (opt->rr_needaddr)
57
			ip_rt_get_source(iph+opt->rr+iph[opt->rr+2]-5, skb, rt);
L
Linus Torvalds 已提交
58
		if (opt->ts_needaddr)
59
			ip_rt_get_source(iph+opt->ts+iph[opt->ts+2]-9, skb, rt);
L
Linus Torvalds 已提交
60
		if (opt->ts_needtime) {
61
			__be32 midtime;
62 63

			midtime = inet_current_timestamp();
L
Linus Torvalds 已提交
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
			memcpy(iph+opt->ts+iph[opt->ts+2]-5, &midtime, 4);
		}
		return;
	}
	if (opt->rr) {
		memset(iph+opt->rr, IPOPT_NOP, iph[opt->rr+1]);
		opt->rr = 0;
		opt->rr_needaddr = 0;
	}
	if (opt->ts) {
		memset(iph+opt->ts, IPOPT_NOP, iph[opt->ts+1]);
		opt->ts = 0;
		opt->ts_needaddr = opt->ts_needtime = 0;
	}
}

80
/*
L
Linus Torvalds 已提交
81 82 83 84 85 86 87 88
 * Provided (sopt, skb) points to received options,
 * build in dopt compiled option set appropriate for answering.
 * i.e. invert SRR option, copy anothers,
 * and grab room in RR/TS options.
 *
 * NOTE: dopt cannot point to skb.
 */

89 90
int __ip_options_echo(struct net *net, struct ip_options *dopt,
		      struct sk_buff *skb, const struct ip_options *sopt)
L
Linus Torvalds 已提交
91 92 93 94 95 96 97
{
	unsigned char *sptr, *dptr;
	int soffset, doffset;
	int	optlen;

	memset(dopt, 0, sizeof(struct ip_options));

98
	if (sopt->optlen == 0)
L
Linus Torvalds 已提交
99 100
		return 0;

101
	sptr = skb_network_header(skb);
L
Linus Torvalds 已提交
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
	dptr = dopt->__data;

	if (sopt->rr) {
		optlen  = sptr[sopt->rr+1];
		soffset = sptr[sopt->rr+2];
		dopt->rr = dopt->optlen + sizeof(struct iphdr);
		memcpy(dptr, sptr+sopt->rr, optlen);
		if (sopt->rr_needaddr && soffset <= optlen) {
			if (soffset + 3 > optlen)
				return -EINVAL;
			dptr[2] = soffset + 4;
			dopt->rr_needaddr = 1;
		}
		dptr += optlen;
		dopt->optlen += optlen;
	}
	if (sopt->ts) {
		optlen = sptr[sopt->ts+1];
		soffset = sptr[sopt->ts+2];
		dopt->ts = dopt->optlen + sizeof(struct iphdr);
		memcpy(dptr, sptr+sopt->ts, optlen);
		if (soffset <= optlen) {
			if (sopt->ts_needaddr) {
				if (soffset + 3 > optlen)
					return -EINVAL;
				dopt->ts_needaddr = 1;
				soffset += 4;
			}
			if (sopt->ts_needtime) {
				if (soffset + 3 > optlen)
					return -EINVAL;
				if ((dptr[3]&0xF) != IPOPT_TS_PRESPEC) {
					dopt->ts_needtime = 1;
					soffset += 4;
				} else {
					dopt->ts_needtime = 0;

139
					if (soffset + 7 <= optlen) {
A
Al Viro 已提交
140
						__be32 addr;
L
Linus Torvalds 已提交
141

142
						memcpy(&addr, dptr+soffset-1, 4);
143
						if (inet_addr_type(net, addr) != RTN_UNICAST) {
L
Linus Torvalds 已提交
144 145 146 147 148 149 150 151 152 153 154 155
							dopt->ts_needtime = 1;
							soffset += 8;
						}
					}
				}
			}
			dptr[2] = soffset;
		}
		dptr += optlen;
		dopt->optlen += optlen;
	}
	if (sopt->srr) {
156
		unsigned char *start = sptr+sopt->srr;
A
Al Viro 已提交
157
		__be32 faddr;
L
Linus Torvalds 已提交
158 159 160 161 162 163 164 165 166

		optlen  = start[1];
		soffset = start[2];
		doffset = 0;
		if (soffset > optlen)
			soffset = optlen + 1;
		soffset -= 4;
		if (soffset > 3) {
			memcpy(&faddr, &start[soffset-1], 4);
W
Weilong Chen 已提交
167
			for (soffset -= 4, doffset = 4; soffset > 3; soffset -= 4, doffset += 4)
L
Linus Torvalds 已提交
168 169 170 171
				memcpy(&dptr[doffset-1], &start[soffset-1], 4);
			/*
			 * RFC1812 requires to fix illegal source routes.
			 */
172 173
			if (memcmp(&ip_hdr(skb)->saddr,
				   &start[soffset + 3], 4) == 0)
L
Linus Torvalds 已提交
174 175 176 177 178 179 180 181 182 183 184 185 186
				doffset -= 4;
		}
		if (doffset > 3) {
			dopt->faddr = faddr;
			dptr[0] = start[0];
			dptr[1] = doffset+3;
			dptr[2] = 4;
			dptr += doffset+3;
			dopt->srr = dopt->optlen + sizeof(struct iphdr);
			dopt->optlen += doffset+3;
			dopt->is_strictroute = sopt->is_strictroute;
		}
	}
P
Paul Moore 已提交
187 188 189 190 191 192 193
	if (sopt->cipso) {
		optlen  = sptr[sopt->cipso+1];
		dopt->cipso = dopt->optlen+sizeof(struct iphdr);
		memcpy(dptr, sptr+sopt->cipso, optlen);
		dptr += optlen;
		dopt->optlen += optlen;
	}
L
Linus Torvalds 已提交
194 195 196 197 198 199 200 201 202 203 204 205 206
	while (dopt->optlen & 3) {
		*dptr++ = IPOPT_END;
		dopt->optlen++;
	}
	return 0;
}

/*
 *	Options "fragmenting", just fill options not
 *	allowed in fragments with NOOPs.
 *	Simple and stupid 8), but the most efficient way.
 */

D
Daniel Baluta 已提交
207
void ip_options_fragment(struct sk_buff *skb)
L
Linus Torvalds 已提交
208
{
209
	unsigned char *optptr = skb_network_header(skb) + sizeof(struct iphdr);
D
Daniel Baluta 已提交
210
	struct ip_options *opt = &(IPCB(skb)->opt);
L
Linus Torvalds 已提交
211 212 213 214 215 216 217 218 219 220 221 222 223
	int  l = opt->optlen;
	int  optlen;

	while (l > 0) {
		switch (*optptr) {
		case IPOPT_END:
			return;
		case IPOPT_NOOP:
			l--;
			optptr++;
			continue;
		}
		optlen = optptr[1];
W
Weilong Chen 已提交
224
		if (optlen < 2 || optlen > l)
L
Linus Torvalds 已提交
225 226 227 228 229 230 231 232 233 234 235 236 237
		  return;
		if (!IPOPT_COPIED(*optptr))
			memset(optptr, IPOPT_NOOP, optlen);
		l -= optlen;
		optptr += optlen;
	}
	opt->ts = 0;
	opt->rr = 0;
	opt->rr_needaddr = 0;
	opt->ts_needaddr = 0;
	opt->ts_needtime = 0;
}

238 239 240 241 242 243 244 245 246
/* helper used by ip_options_compile() to call fib_compute_spec_dst()
 * at most one time.
 */
static void spec_dst_fill(__be32 *spec_dst, struct sk_buff *skb)
{
	if (*spec_dst == htonl(INADDR_ANY))
		*spec_dst = fib_compute_spec_dst(skb);
}

L
Linus Torvalds 已提交
247 248 249 250 251 252
/*
 * Verify options and fill pointers in struct options.
 * Caller should clear *opt, and set opt->data.
 * If opt == NULL, then skb->data should point to IP header.
 */

253
int ip_options_compile(struct net *net,
D
Daniel Baluta 已提交
254
		       struct ip_options *opt, struct sk_buff *skb)
L
Linus Torvalds 已提交
255
{
256
	__be32 spec_dst = htonl(INADDR_ANY);
D
Daniel Baluta 已提交
257
	unsigned char *pp_ptr = NULL;
258
	struct rtable *rt = NULL;
259 260 261
	unsigned char *optptr;
	unsigned char *iph;
	int optlen, l;
L
Linus Torvalds 已提交
262

263
	if (skb) {
264
		rt = skb_rtable(skb);
265 266
		optptr = (unsigned char *)&(ip_hdr(skb)[1]);
	} else
267
		optptr = opt->__data;
268
	iph = optptr - sizeof(struct iphdr);
L
Linus Torvalds 已提交
269 270 271

	for (l = opt->optlen; l > 0; ) {
		switch (*optptr) {
272
		case IPOPT_END:
W
Weilong Chen 已提交
273
			for (optptr++, l--; l > 0; optptr++, l--) {
L
Linus Torvalds 已提交
274 275 276 277 278 279
				if (*optptr != IPOPT_END) {
					*optptr = IPOPT_END;
					opt->is_changed = 1;
				}
			}
			goto eol;
280
		case IPOPT_NOOP:
L
Linus Torvalds 已提交
281 282 283 284
			l--;
			optptr++;
			continue;
		}
285 286 287 288
		if (unlikely(l < 2)) {
			pp_ptr = optptr;
			goto error;
		}
L
Linus Torvalds 已提交
289
		optlen = optptr[1];
W
Weilong Chen 已提交
290
		if (optlen < 2 || optlen > l) {
L
Linus Torvalds 已提交
291 292 293 294
			pp_ptr = optptr;
			goto error;
		}
		switch (*optptr) {
295 296
		case IPOPT_SSRR:
		case IPOPT_LSRR:
L
Linus Torvalds 已提交
297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321
			if (optlen < 3) {
				pp_ptr = optptr + 1;
				goto error;
			}
			if (optptr[2] < 4) {
				pp_ptr = optptr + 2;
				goto error;
			}
			/* NB: cf RFC-1812 5.2.4.1 */
			if (opt->srr) {
				pp_ptr = optptr;
				goto error;
			}
			if (!skb) {
				if (optptr[2] != 4 || optlen < 7 || ((optlen-3) & 3)) {
					pp_ptr = optptr + 1;
					goto error;
				}
				memcpy(&opt->faddr, &optptr[3], 4);
				if (optlen > 7)
					memmove(&optptr[3], &optptr[7], optlen-7);
			}
			opt->is_strictroute = (optptr[0] == IPOPT_SSRR);
			opt->srr = optptr - iph;
			break;
322
		case IPOPT_RR:
L
Linus Torvalds 已提交
323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339
			if (opt->rr) {
				pp_ptr = optptr;
				goto error;
			}
			if (optlen < 3) {
				pp_ptr = optptr + 1;
				goto error;
			}
			if (optptr[2] < 4) {
				pp_ptr = optptr + 2;
				goto error;
			}
			if (optptr[2] <= optlen) {
				if (optptr[2]+3 > optlen) {
					pp_ptr = optptr + 2;
					goto error;
				}
340
				if (rt) {
341
					spec_dst_fill(&spec_dst, skb);
342
					memcpy(&optptr[optptr[2]-1], &spec_dst, 4);
L
Linus Torvalds 已提交
343 344 345 346 347 348 349
					opt->is_changed = 1;
				}
				optptr[2] += 4;
				opt->rr_needaddr = 1;
			}
			opt->rr = optptr - iph;
			break;
350
		case IPOPT_TIMESTAMP:
L
Linus Torvalds 已提交
351 352 353 354 355 356 357 358 359 360 361 362 363
			if (opt->ts) {
				pp_ptr = optptr;
				goto error;
			}
			if (optlen < 4) {
				pp_ptr = optptr + 1;
				goto error;
			}
			if (optptr[2] < 5) {
				pp_ptr = optptr + 2;
				goto error;
			}
			if (optptr[2] <= optlen) {
364
				unsigned char *timeptr = NULL;
365
				if (optptr[2]+3 > optlen) {
L
Linus Torvalds 已提交
366 367 368 369
					pp_ptr = optptr + 2;
					goto error;
				}
				switch (optptr[3]&0xF) {
370
				case IPOPT_TS_TSONLY:
371
					if (skb)
372
						timeptr = &optptr[optptr[2]-1];
L
Linus Torvalds 已提交
373 374 375
					opt->ts_needtime = 1;
					optptr[2] += 4;
					break;
376
				case IPOPT_TS_TSANDADDR:
377
					if (optptr[2]+7 > optlen) {
L
Linus Torvalds 已提交
378 379 380
						pp_ptr = optptr + 2;
						goto error;
					}
381
					if (rt)  {
382
						spec_dst_fill(&spec_dst, skb);
383
						memcpy(&optptr[optptr[2]-1], &spec_dst, 4);
384
						timeptr = &optptr[optptr[2]+3];
L
Linus Torvalds 已提交
385 386 387 388 389
					}
					opt->ts_needaddr = 1;
					opt->ts_needtime = 1;
					optptr[2] += 8;
					break;
390
				case IPOPT_TS_PRESPEC:
391
					if (optptr[2]+7 > optlen) {
L
Linus Torvalds 已提交
392 393 394 395
						pp_ptr = optptr + 2;
						goto error;
					}
					{
A
Al Viro 已提交
396
						__be32 addr;
L
Linus Torvalds 已提交
397
						memcpy(&addr, &optptr[optptr[2]-1], 4);
398
						if (inet_addr_type(net, addr) == RTN_UNICAST)
L
Linus Torvalds 已提交
399 400
							break;
						if (skb)
401
							timeptr = &optptr[optptr[2]+3];
L
Linus Torvalds 已提交
402 403 404 405
					}
					opt->ts_needtime = 1;
					optptr[2] += 8;
					break;
406
				default:
407
					if (!skb && !ns_capable(net->user_ns, CAP_NET_RAW)) {
L
Linus Torvalds 已提交
408 409 410 411 412 413
						pp_ptr = optptr + 3;
						goto error;
					}
					break;
				}
				if (timeptr) {
414 415 416 417
					__be32 midtime;

					midtime = inet_current_timestamp();
					memcpy(timeptr, &midtime, 4);
L
Linus Torvalds 已提交
418 419
					opt->is_changed = 1;
				}
420
			} else if ((optptr[3]&0xF) != IPOPT_TS_PRESPEC) {
421
				unsigned int overflow = optptr[3]>>4;
L
Linus Torvalds 已提交
422 423 424 425 426 427 428 429 430
				if (overflow == 15) {
					pp_ptr = optptr + 3;
					goto error;
				}
				if (skb) {
					optptr[3] = (optptr[3]&0xF)|((overflow+1)<<4);
					opt->is_changed = 1;
				}
			}
431
			opt->ts = optptr - iph;
L
Linus Torvalds 已提交
432
			break;
433
		case IPOPT_RA:
L
Linus Torvalds 已提交
434 435 436 437 438 439 440
			if (optlen < 4) {
				pp_ptr = optptr + 1;
				goto error;
			}
			if (optptr[2] == 0 && optptr[3] == 0)
				opt->router_alert = optptr - iph;
			break;
441
		case IPOPT_CIPSO:
442
			if ((!skb && !ns_capable(net->user_ns, CAP_NET_RAW)) || opt->cipso) {
P
Paul Moore 已提交
443 444 445 446
				pp_ptr = optptr;
				goto error;
			}
			opt->cipso = optptr - iph;
447
			if (cipso_v4_validate(skb, &optptr)) {
P
Paul Moore 已提交
448 449 450 451
				pp_ptr = optptr;
				goto error;
			}
			break;
452 453 454
		case IPOPT_SEC:
		case IPOPT_SID:
		default:
455
			if (!skb && !ns_capable(net->user_ns, CAP_NET_RAW)) {
L
Linus Torvalds 已提交
456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474
				pp_ptr = optptr;
				goto error;
			}
			break;
		}
		l -= optlen;
		optptr += optlen;
	}

eol:
	if (!pp_ptr)
		return 0;

error:
	if (skb) {
		icmp_send(skb, ICMP_PARAMETERPROB, 0, htonl((pp_ptr-iph)<<24));
	}
	return -EINVAL;
}
475
EXPORT_SYMBOL(ip_options_compile);
L
Linus Torvalds 已提交
476 477 478 479 480

/*
 *	Undo all the changes done by ip_options_compile().
 */

D
Daniel Baluta 已提交
481
void ip_options_undo(struct ip_options *opt)
L
Linus Torvalds 已提交
482 483
{
	if (opt->srr) {
D
Daniel Baluta 已提交
484
		unsigned  char *optptr = opt->__data+opt->srr-sizeof(struct  iphdr);
L
Linus Torvalds 已提交
485 486 487 488
		memmove(optptr+7, optptr+3, optptr[1]-7);
		memcpy(optptr+3, &opt->faddr, 4);
	}
	if (opt->rr_needaddr) {
D
Daniel Baluta 已提交
489
		unsigned  char *optptr = opt->__data+opt->rr-sizeof(struct  iphdr);
L
Linus Torvalds 已提交
490 491 492 493
		optptr[2] -= 4;
		memset(&optptr[optptr[2]-1], 0, 4);
	}
	if (opt->ts) {
D
Daniel Baluta 已提交
494
		unsigned  char *optptr = opt->__data+opt->ts-sizeof(struct  iphdr);
L
Linus Torvalds 已提交
495 496 497 498 499 500 501 502 503 504 505 506 507
		if (opt->ts_needtime) {
			optptr[2] -= 4;
			memset(&optptr[optptr[2]-1], 0, 4);
			if ((optptr[3]&0xF) == IPOPT_TS_PRESPEC)
				optptr[2] -= 4;
		}
		if (opt->ts_needaddr) {
			optptr[2] -= 4;
			memset(&optptr[optptr[2]-1], 0, 4);
		}
	}
}

508
static struct ip_options_rcu *ip_options_get_alloc(const int optlen)
L
Linus Torvalds 已提交
509
{
510
	return kzalloc(sizeof(struct ip_options_rcu) + ((optlen + 3) & ~3),
511
		       GFP_KERNEL);
512
}
L
Linus Torvalds 已提交
513

514 515
static int ip_options_get_finish(struct net *net, struct ip_options_rcu **optp,
				 struct ip_options_rcu *opt, int optlen)
516
{
L
Linus Torvalds 已提交
517
	while (optlen & 3)
518 519 520
		opt->opt.__data[optlen++] = IPOPT_END;
	opt->opt.optlen = optlen;
	if (optlen && ip_options_compile(net, &opt->opt, NULL)) {
L
Linus Torvalds 已提交
521 522 523
		kfree(opt);
		return -EINVAL;
	}
J
Jesper Juhl 已提交
524
	kfree(*optp);
L
Linus Torvalds 已提交
525 526 527 528
	*optp = opt;
	return 0;
}

529
int ip_options_get_from_user(struct net *net, struct ip_options_rcu **optp,
530
			     unsigned char __user *data, int optlen)
531
{
532
	struct ip_options_rcu *opt = ip_options_get_alloc(optlen);
533 534 535

	if (!opt)
		return -ENOMEM;
536
	if (optlen && copy_from_user(opt->opt.__data, data, optlen)) {
537 538 539
		kfree(opt);
		return -EFAULT;
	}
540
	return ip_options_get_finish(net, optp, opt, optlen);
541 542
}

543
int ip_options_get(struct net *net, struct ip_options_rcu **optp,
544
		   unsigned char *data, int optlen)
545
{
546
	struct ip_options_rcu *opt = ip_options_get_alloc(optlen);
547 548 549 550

	if (!opt)
		return -ENOMEM;
	if (optlen)
551
		memcpy(opt->opt.__data, data, optlen);
552
	return ip_options_get_finish(net, optp, opt, optlen);
553 554
}

L
Linus Torvalds 已提交
555 556
void ip_forward_options(struct sk_buff *skb)
{
D
Daniel Baluta 已提交
557 558
	struct   ip_options *opt	= &(IPCB(skb)->opt);
	unsigned char *optptr;
E
Eric Dumazet 已提交
559
	struct rtable *rt = skb_rtable(skb);
560
	unsigned char *raw = skb_network_header(skb);
L
Linus Torvalds 已提交
561 562 563

	if (opt->rr_needaddr) {
		optptr = (unsigned char *)raw + opt->rr;
564
		ip_rt_get_source(&optptr[optptr[2]-5], skb, rt);
L
Linus Torvalds 已提交
565 566 567 568 569 570 571
		opt->is_changed = 1;
	}
	if (opt->srr_is_hit) {
		int srrptr, srrspace;

		optptr = raw + opt->srr;

W
Weilong Chen 已提交
572
		for ( srrptr = optptr[2], srrspace = optptr[1];
L
Linus Torvalds 已提交
573 574 575 576 577
		     srrptr <= srrspace;
		     srrptr += 4
		     ) {
			if (srrptr + 3 > srrspace)
				break;
578
			if (memcmp(&opt->nexthop, &optptr[srrptr-1], 4) == 0)
L
Linus Torvalds 已提交
579 580 581 582
				break;
		}
		if (srrptr + 3 <= srrspace) {
			opt->is_changed = 1;
583
			ip_hdr(skb)->daddr = opt->nexthop;
584
			ip_rt_get_source(&optptr[srrptr-1], skb, rt);
L
Linus Torvalds 已提交
585
			optptr[2] = srrptr+4;
586 587 588 589
		} else {
			net_crit_ratelimited("%s(): Argh! Destination lost!\n",
					     __func__);
		}
L
Linus Torvalds 已提交
590 591
		if (opt->ts_needaddr) {
			optptr = raw + opt->ts;
592
			ip_rt_get_source(&optptr[optptr[2]-9], skb, rt);
L
Linus Torvalds 已提交
593 594 595 596 597
			opt->is_changed = 1;
		}
	}
	if (opt->is_changed) {
		opt->is_changed = 0;
598
		ip_send_check(ip_hdr(skb));
L
Linus Torvalds 已提交
599 600 601 602 603 604 605
	}
}

int ip_options_rcv_srr(struct sk_buff *skb)
{
	struct ip_options *opt = &(IPCB(skb)->opt);
	int srrspace, srrptr;
A
Al Viro 已提交
606
	__be32 nexthop;
607
	struct iphdr *iph = ip_hdr(skb);
608
	unsigned char *optptr = skb_network_header(skb) + opt->srr;
E
Eric Dumazet 已提交
609
	struct rtable *rt = skb_rtable(skb);
L
Linus Torvalds 已提交
610
	struct rtable *rt2;
E
Eric Dumazet 已提交
611
	unsigned long orefdst;
L
Linus Torvalds 已提交
612 613
	int err;

614
	if (!rt)
L
Linus Torvalds 已提交
615 616 617 618 619 620 621 622 623 624 625 626 627
		return 0;

	if (skb->pkt_type != PACKET_HOST)
		return -EINVAL;
	if (rt->rt_type == RTN_UNICAST) {
		if (!opt->is_strictroute)
			return 0;
		icmp_send(skb, ICMP_PARAMETERPROB, 0, htonl(16<<24));
		return -EINVAL;
	}
	if (rt->rt_type != RTN_LOCAL)
		return -EINVAL;

W
Weilong Chen 已提交
628
	for (srrptr = optptr[2], srrspace = optptr[1]; srrptr <= srrspace; srrptr += 4) {
L
Linus Torvalds 已提交
629 630 631 632 633 634
		if (srrptr + 3 > srrspace) {
			icmp_send(skb, ICMP_PARAMETERPROB, 0, htonl((opt->srr+2)<<24));
			return -EINVAL;
		}
		memcpy(&nexthop, &optptr[srrptr-1], 4);

E
Eric Dumazet 已提交
635
		orefdst = skb->_skb_refdst;
E
Eric Dumazet 已提交
636
		skb_dst_set(skb, NULL);
L
Linus Torvalds 已提交
637
		err = ip_route_input(skb, nexthop, iph->saddr, iph->tos, skb->dev);
E
Eric Dumazet 已提交
638
		rt2 = skb_rtable(skb);
L
Linus Torvalds 已提交
639
		if (err || (rt2->rt_type != RTN_UNICAST && rt2->rt_type != RTN_LOCAL)) {
E
Eric Dumazet 已提交
640 641
			skb_dst_drop(skb);
			skb->_skb_refdst = orefdst;
L
Linus Torvalds 已提交
642 643
			return -EINVAL;
		}
E
Eric Dumazet 已提交
644
		refdst_drop(orefdst);
L
Linus Torvalds 已提交
645 646 647
		if (rt2->rt_type != RTN_LOCAL)
			break;
		/* Superfast 8) loopback forward */
648
		iph->daddr = nexthop;
L
Linus Torvalds 已提交
649 650 651 652
		opt->is_changed = 1;
	}
	if (srrptr <= srrspace) {
		opt->srr_is_hit = 1;
653
		opt->nexthop = nexthop;
L
Linus Torvalds 已提交
654 655 656 657
		opt->is_changed = 1;
	}
	return 0;
}
658
EXPORT_SYMBOL(ip_options_rcv_srr);