/*
       * 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
       *
       * Version:	$Id: ip_options.c,v 1.20 2000/08/09 09:17:00 davem Exp $
       *
       * Authors:	A.N.Kuznetsov
       *		
       */
      
      #include <linux/types.h>
      #include <asm/uaccess.h>
      #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>
      
      /* 
       * 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.
       */
      
  36  void ip_options_build(struct sk_buff * skb, struct ip_options * opt,
      			    u32 daddr, struct rtable *rt, int is_frag) 
      {
      	unsigned char * iph = skb->nh.raw;
      
      	memcpy(&(IPCB(skb)->opt), opt, sizeof(struct ip_options));
      	memcpy(iph+sizeof(struct iphdr), opt->__data, opt->optlen);
      	opt = &(IPCB(skb)->opt);
      	opt->is_data = 0;
      
  46  	if (opt->srr)
      		memcpy(iph+opt->srr+iph[opt->srr+1]-4, &daddr, 4);
      
  49  	if (!is_frag) {
  50  		if (opt->rr_needaddr)
      			ip_rt_get_source(iph+opt->rr+iph[opt->rr+2]-5, rt);
  52  		if (opt->ts_needaddr)
      			ip_rt_get_source(iph+opt->ts+iph[opt->ts+2]-9, rt);
  54  		if (opt->ts_needtime) {
      			struct timeval tv;
      			__u32 midtime;
      			do_gettimeofday(&tv);
      			midtime = htonl((tv.tv_sec % 86400) * 1000 + tv.tv_usec / 1000);
      			memcpy(iph+opt->ts+iph[opt->ts+2]-5, &midtime, 4);
      		}
  61  		return;
      	}
  63  	if (opt->rr) {
      		memset(iph+opt->rr, IPOPT_NOP, iph[opt->rr+1]);
      		opt->rr = 0;
      		opt->rr_needaddr = 0;
      	}
  68  	if (opt->ts) {
      		memset(iph+opt->ts, IPOPT_NOP, iph[opt->ts+1]);
      		opt->ts = 0;
      		opt->ts_needaddr = opt->ts_needtime = 0;
      	}
      }
      
      /* 
       * 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.
       */
      
  84  int ip_options_echo(struct ip_options * dopt, struct sk_buff * skb) 
      {
      	struct ip_options *sopt;
      	unsigned char *sptr, *dptr;
      	int soffset, doffset;
      	int	optlen;
      	u32	daddr;
      
      	memset(dopt, 0, sizeof(struct ip_options));
      
      	dopt->is_data = 1;
      
      	sopt = &(IPCB(skb)->opt);
      
  98  	if (sopt->optlen == 0) {
      		dopt->optlen = 0;
 100  		return 0;
      	}
      
      	sptr = skb->nh.raw;
      	dptr = dopt->__data;
      
 106  	if (skb->dst)
      		daddr = ((struct rtable*)skb->dst)->rt_spec_dst;
 108  	else
      		daddr = skb->nh.iph->daddr;
      
 111  	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);
 116  		if (sopt->rr_needaddr && soffset <= optlen) {
 117  			if (soffset + 3 > optlen)
 118  				return -EINVAL;
      			dptr[2] = soffset + 4;
      			dopt->rr_needaddr = 1;
      		}
      		dptr += optlen;
      		dopt->optlen += optlen;
      	}
 125  	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);
 130  		if (soffset <= optlen) {
 131  			if (sopt->ts_needaddr) {
 132  				if (soffset + 3 > optlen)
 133  					return -EINVAL;
      				dopt->ts_needaddr = 1;
      				soffset += 4;
      			}
 137  			if (sopt->ts_needtime) {
 138  				if (soffset + 3 > optlen)
 139  					return -EINVAL;
 140  				if ((dptr[3]&0xF) != IPOPT_TS_PRESPEC) {
      					dopt->ts_needtime = 1;
      					soffset += 4;
 143  				} else {
      					dopt->ts_needtime = 0;
      
 146  					if (soffset + 8 <= optlen) {
      						__u32 addr;
      
      						memcpy(&addr, sptr+soffset-1, 4);
 150  						if (inet_addr_type(addr) != RTN_LOCAL) {
      							dopt->ts_needtime = 1;
      							soffset += 8;
      						}
      					}
      				}
      			}
      			dptr[2] = soffset;
      		}
      		dptr += optlen;
      		dopt->optlen += optlen;
      	}
 162  	if (sopt->srr) {
      		unsigned char * start = sptr+sopt->srr;
      		u32 faddr;
      
      		optlen  = start[1];
      		soffset = start[2];
      		doffset = 0;
 169  		if (soffset > optlen)
      			soffset = optlen + 1;
      		soffset -= 4;
 172  		if (soffset > 3) {
      			memcpy(&faddr, &start[soffset-1], 4);
 174  			for (soffset-=4, doffset=4; soffset > 3; soffset-=4, doffset+=4)
      				memcpy(&dptr[doffset-1], &start[soffset-1], 4);
      			/*
      			 * RFC1812 requires to fix illegal source routes.
      			 */
 179  			if (memcmp(&skb->nh.iph->saddr, &start[soffset+3], 4) == 0)
      				doffset -= 4;
      		}
 182  		if (doffset > 3) {
      			memcpy(&start[doffset-1], &daddr, 4);
      			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;
      		}
      	}
 194  	while (dopt->optlen & 3) {
      		*dptr++ = IPOPT_END;
      		dopt->optlen++;
      	}
 198  	return 0;
      }
      
      /*
       *	Options "fragmenting", just fill options not
       *	allowed in fragments with NOOPs.
       *	Simple and stupid 8), but the most efficient way.
       */
      
 207  void ip_options_fragment(struct sk_buff * skb) 
      {
      	unsigned char * optptr = skb->nh.raw;
      	struct ip_options * opt = &(IPCB(skb)->opt);
      	int  l = opt->optlen;
      	int  optlen;
      
 214  	while (l > 0) {
 215  		switch (*optptr) {
 216  		case IPOPT_END:
 217  			return;
 218  		case IPOPT_NOOP:
      			l--;
      			optptr++;
 221  			continue;
      		}
      		optlen = optptr[1];
 224  		if (optlen<2 || optlen>l)
 225  		  return;
 226  		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;
 236  	return;
      }
      
      /*
       * 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.
       */
      
 245  int ip_options_compile(struct ip_options * opt, struct sk_buff * skb)
      {
      	int l;
      	unsigned char * iph;
      	unsigned char * optptr;
      	int optlen;
      	unsigned char * pp_ptr = NULL;
      	struct rtable *rt = skb ? (struct rtable*)skb->dst : NULL;
      
 254  	if (!opt) {
      		opt = &(IPCB(skb)->opt);
      		memset(opt, 0, sizeof(struct ip_options));
      		iph = skb->nh.raw;
      		opt->optlen = ((struct iphdr *)iph)->ihl*4 - sizeof(struct iphdr);
      		optptr = iph + sizeof(struct iphdr);
      		opt->is_data = 0;
 261  	} else {
      		optptr = opt->is_data ? opt->__data : (unsigned char*)&(skb->nh.iph[1]);
      		iph = optptr - sizeof(struct iphdr);
      	}
      
 266  	for (l = opt->optlen; l > 0; ) {
 267  		switch (*optptr) {
 268  		      case IPOPT_END:
 269  			for (optptr++, l--; l>0; l--) {
 270  				if (*optptr != IPOPT_END) {
      					*optptr = IPOPT_END;
      					opt->is_changed = 1;
      				}
      			}
 275  			goto eol;
 276  		      case IPOPT_NOOP:
      			l--;
      			optptr++;
 279  			continue;
      		}
      		optlen = optptr[1];
 282  		if (optlen<2 || optlen>l) {
      			pp_ptr = optptr;
 284  			goto error;
      		}
 286  		switch (*optptr) {
 287  		      case IPOPT_SSRR:
 288  		      case IPOPT_LSRR:
 289  			if (optlen < 3) {
      				pp_ptr = optptr + 1;
 291  				goto error;
      			}
 293  			if (optptr[2] < 4) {
      				pp_ptr = optptr + 2;
 295  				goto error;
      			}
      			/* NB: cf RFC-1812 5.2.4.1 */
 298  			if (opt->srr) {
      				pp_ptr = optptr;
 300  				goto error;
      			}
 302  			if (!skb) {
 303  				if (optptr[2] != 4 || optlen < 7 || ((optlen-3) & 3)) {
      					pp_ptr = optptr + 1;
 305  					goto error;
      				}
      				memcpy(&opt->faddr, &optptr[3], 4);
 308  				if (optlen > 7)
      					memmove(&optptr[3], &optptr[7], optlen-7);
      			}
      			opt->is_strictroute = (optptr[0] == IPOPT_SSRR);
      			opt->srr = optptr - iph;
 313  			break;
 314  		      case IPOPT_RR:
 315  			if (opt->rr) {
      				pp_ptr = optptr;
 317  				goto error;
      			}
 319  			if (optlen < 3) {
      				pp_ptr = optptr + 1;
 321  				goto error;
      			}
 323  			if (optptr[2] < 4) {
      				pp_ptr = optptr + 2;
 325  				goto error;
      			}
 327  			if (optptr[2] <= optlen) {
 328  				if (optptr[2]+3 > optlen) {
      					pp_ptr = optptr + 2;
 330  					goto error;
      				}
 332  				if (skb) {
      					memcpy(&optptr[optptr[2]-1], &rt->rt_spec_dst, 4);
      					opt->is_changed = 1;
      				}
      				optptr[2] += 4;
      				opt->rr_needaddr = 1;
      			}
      			opt->rr = optptr - iph;
 340  			break;
 341  		      case IPOPT_TIMESTAMP:
 342  			if (opt->ts) {
      				pp_ptr = optptr;
 344  				goto error;
      			}
 346  			if (optlen < 4) {
      				pp_ptr = optptr + 1;
 348  				goto error;
      			}
 350  			if (optptr[2] < 5) {
      				pp_ptr = optptr + 2;
 352  				goto error;
      			}
 354  			if (optptr[2] <= optlen) {
      				__u32 * timeptr = NULL;
 356  				if (optptr[2]+3 > optptr[1]) {
      					pp_ptr = optptr + 2;
 358  					goto error;
      				}
 360  				switch (optptr[3]&0xF) {
 361  				      case IPOPT_TS_TSONLY:
      					opt->ts = optptr - iph;
 363  					if (skb) 
      						timeptr = (__u32*)&optptr[optptr[2]-1];
      					opt->ts_needtime = 1;
      					optptr[2] += 4;
 367  					break;
 368  				      case IPOPT_TS_TSANDADDR:
 369  					if (optptr[2]+7 > optptr[1]) {
      						pp_ptr = optptr + 2;
 371  						goto error;
      					}
      					opt->ts = optptr - iph;
 374  					if (skb) {
      						memcpy(&optptr[optptr[2]-1], &rt->rt_spec_dst, 4);
      						timeptr = (__u32*)&optptr[optptr[2]+3];
      					}
      					opt->ts_needaddr = 1;
      					opt->ts_needtime = 1;
      					optptr[2] += 8;
 381  					break;
 382  				      case IPOPT_TS_PRESPEC:
 383  					if (optptr[2]+7 > optptr[1]) {
      						pp_ptr = optptr + 2;
 385  						goto error;
      					}
      					opt->ts = optptr - iph;
      					{
      						u32 addr;
      						memcpy(&addr, &optptr[optptr[2]-1], 4);
 391  						if (inet_addr_type(addr) == RTN_UNICAST)
 392  							break;
 393  						if (skb)
      							timeptr = (__u32*)&optptr[optptr[2]+3];
      					}
      					opt->ts_needtime = 1;
      					optptr[2] += 8;
 398  					break;
 399  				      default:
 400  					if (!skb && !capable(CAP_NET_RAW)) {
      						pp_ptr = optptr + 3;
 402  						goto error;
      					}
 404  					break;
      				}
 406  				if (timeptr) {
      					struct timeval tv;
      					__u32  midtime;
      					do_gettimeofday(&tv);
      					midtime = htonl((tv.tv_sec % 86400) * 1000 + tv.tv_usec / 1000);
      					memcpy(timeptr, &midtime, sizeof(__u32));
      					opt->is_changed = 1;
      				}
 414  			} else {
      				unsigned overflow = optptr[3]>>4;
 416  				if (overflow == 15) {
      					pp_ptr = optptr + 3;
 418  					goto error;
      				}
      				opt->ts = optptr - iph;
 421  				if (skb) {
      					optptr[3] = (optptr[3]&0xF)|((overflow+1)<<4);
      					opt->is_changed = 1;
      				}
      			}
 426  			break;
 427  		      case IPOPT_RA:
 428  			if (optlen < 4) {
      				pp_ptr = optptr + 1;
 430  				goto error;
      			}
 432  			if (optptr[2] == 0 && optptr[3] == 0)
      				opt->router_alert = optptr - iph;
 434  			break;
 435  		      case IPOPT_SEC:
 436  		      case IPOPT_SID:
 437  		      default:
 438  			if (!skb && !capable(CAP_NET_RAW)) {
      				pp_ptr = optptr;
 440  				goto error;
      			}
 442  			break;
      		}
      		l -= optlen;
      		optptr += optlen;
      	}
      
      eol:
 449  	if (!pp_ptr)
 450  		return 0;
      
      error:
 453  	if (skb) {
      		icmp_send(skb, ICMP_PARAMETERPROB, 0, htonl((pp_ptr-iph)<<24));
      	}
 456  	return -EINVAL;
      }
      
      
      /*
       *	Undo all the changes done by ip_options_compile().
       */
      
 464  void ip_options_undo(struct ip_options * opt)
      {
 466  	if (opt->srr) {
      		unsigned  char * optptr = opt->__data+opt->srr-sizeof(struct  iphdr);
      		memmove(optptr+7, optptr+3, optptr[1]-7);
      		memcpy(optptr+3, &opt->faddr, 4);
      	}
 471  	if (opt->rr_needaddr) {
      		unsigned  char * optptr = opt->__data+opt->rr-sizeof(struct  iphdr);
      		optptr[2] -= 4;
      		memset(&optptr[optptr[2]-1], 0, 4);
      	}
 476  	if (opt->ts) {
      		unsigned  char * optptr = opt->__data+opt->ts-sizeof(struct  iphdr);
 478  		if (opt->ts_needtime) {
      			optptr[2] -= 4;
      			memset(&optptr[optptr[2]-1], 0, 4);
 481  			if ((optptr[3]&0xF) == IPOPT_TS_PRESPEC)
      				optptr[2] -= 4;
      		}
 484  		if (opt->ts_needaddr) {
      			optptr[2] -= 4;
      			memset(&optptr[optptr[2]-1], 0, 4);
      		}
      	}
      }
      
 491  int ip_options_get(struct ip_options **optp, unsigned char *data, int optlen, int user)
      {
      	struct ip_options *opt;
      
      	opt = kmalloc(sizeof(struct ip_options)+((optlen+3)&~3), GFP_KERNEL);
 496  	if (!opt)
 497  		return -ENOMEM;
      	memset(opt, 0, sizeof(struct ip_options));
 499  	if (optlen) {
 500  		if (user) {
 501  			if (copy_from_user(opt->__data, data, optlen)) {
      				kfree(opt);
 503  				return -EFAULT;
      			}
 505  		} else
      			memcpy(opt->__data, data, optlen);
      	}
 508  	while (optlen & 3)
      		opt->__data[optlen++] = IPOPT_END;
      	opt->optlen = optlen;
      	opt->is_data = 1;
      	opt->is_setbyuser = 1;
 513  	if (optlen && ip_options_compile(opt, NULL)) {
      		kfree(opt);
 515  		return -EINVAL;
      	}
      	*optp = opt;
 518  	return 0;
      }
      
 521  void ip_forward_options(struct sk_buff *skb)
      {
      	struct   ip_options * opt	= &(IPCB(skb)->opt);
      	unsigned char * optptr;
      	struct rtable *rt = (struct rtable*)skb->dst;
      	unsigned char *raw = skb->nh.raw;
      
 528  	if (opt->rr_needaddr) {
      		optptr = (unsigned char *)raw + opt->rr;
      		ip_rt_get_source(&optptr[optptr[2]-5], rt);
      		opt->is_changed = 1;
      	}
 533  	if (opt->srr_is_hit) {
      		int srrptr, srrspace;
      
      		optptr = raw + opt->srr;
      
      		for ( srrptr=optptr[2], srrspace = optptr[1];
 539  		     srrptr <= srrspace;
      		     srrptr += 4
      		     ) {
 542  			if (srrptr + 3 > srrspace)
 543  				break;
 544  			if (memcmp(&rt->rt_dst, &optptr[srrptr-1], 4) == 0)
 545  				break;
      		}
 547  		if (srrptr + 3 <= srrspace) {
      			opt->is_changed = 1;
      			ip_rt_get_source(&optptr[srrptr-1], rt);
      			skb->nh.iph->daddr = rt->rt_dst;
      			optptr[2] = srrptr+4;
 552  		} else
      			printk(KERN_CRIT "ip_forward(): Argh! Destination lost!\n");
 554  		if (opt->ts_needaddr) {
      			optptr = raw + opt->ts;
      			ip_rt_get_source(&optptr[optptr[2]-9], rt);
      			opt->is_changed = 1;
      		}
      	}
 560  	if (opt->is_changed) {
      		opt->is_changed = 0;
      		ip_send_check(skb->nh.iph);
      	}
      }
      
 566  int ip_options_rcv_srr(struct sk_buff *skb)
      {
      	struct ip_options *opt = &(IPCB(skb)->opt);
      	int srrspace, srrptr;
      	u32 nexthop;
      	struct iphdr *iph = skb->nh.iph;
      	unsigned char * optptr = skb->nh.raw + opt->srr;
      	struct rtable *rt = (struct rtable*)skb->dst;
      	struct rtable *rt2;
      	int err;
      
 577  	if (!opt->srr)
 578  		return 0;
      
 580  	if (skb->pkt_type != PACKET_HOST)
 581  		return -EINVAL;
 582  	if (rt->rt_type == RTN_UNICAST) {
 583  		if (!opt->is_strictroute)
 584  			return 0;
      		icmp_send(skb, ICMP_PARAMETERPROB, 0, htonl(16<<24));
 586  		return -EINVAL;
      	}
 588  	if (rt->rt_type != RTN_LOCAL)
 589  		return -EINVAL;
      
 591  	for (srrptr=optptr[2], srrspace = optptr[1]; srrptr <= srrspace; srrptr += 4) {
 592  		if (srrptr + 3 > srrspace) {
      			icmp_send(skb, ICMP_PARAMETERPROB, 0, htonl((opt->srr+2)<<24));
 594  			return -EINVAL;
      		}
      		memcpy(&nexthop, &optptr[srrptr-1], 4);
      
      		rt = (struct rtable*)skb->dst;
      		skb->dst = NULL;
      		err = ip_route_input(skb, nexthop, iph->saddr, iph->tos, skb->dev);
      		rt2 = (struct rtable*)skb->dst;
 602  		if (err || (rt2->rt_type != RTN_UNICAST && rt2->rt_type != RTN_LOCAL)) {
      			ip_rt_put(rt2);
      			skb->dst = &rt->u.dst;
 605  			return -EINVAL;
      		}
      		ip_rt_put(rt);
 608  		if (rt2->rt_type != RTN_LOCAL)
 609  			break;
      		/* Superfast 8) loopback forward */
      		memcpy(&iph->daddr, &optptr[srrptr-1], 4);
      		opt->is_changed = 1;
      	}
 614  	if (srrptr <= srrspace) {
      		opt->srr_is_hit = 1;
      		opt->is_changed = 1;
      	}
 618  	return 0;
      }