xref: /openbmc/linux/net/ipv4/ip_options.c (revision dbb5281a)
1b2441318SGreg Kroah-Hartman // SPDX-License-Identifier: GPL-2.0
21da177e4SLinus Torvalds /*
31da177e4SLinus Torvalds  * INET		An implementation of the TCP/IP protocol suite for the LINUX
41da177e4SLinus Torvalds  *		operating system.  INET is implemented using the  BSD Socket
51da177e4SLinus Torvalds  *		interface as the means of communication with the user level.
61da177e4SLinus Torvalds  *
71da177e4SLinus Torvalds  *		The options processing module for ip.c
81da177e4SLinus Torvalds  *
91da177e4SLinus Torvalds  * Authors:	A.N.Kuznetsov
101da177e4SLinus Torvalds  *
111da177e4SLinus Torvalds  */
121da177e4SLinus Torvalds 
13afd46503SJoe Perches #define pr_fmt(fmt) "IPv4: " fmt
14afd46503SJoe Perches 
154fc268d2SRandy Dunlap #include <linux/capability.h>
161da177e4SLinus Torvalds #include <linux/module.h>
175a0e3ad6STejun Heo #include <linux/slab.h>
181da177e4SLinus Torvalds #include <linux/types.h>
197c0f6ba6SLinus Torvalds #include <linux/uaccess.h>
2048bdf072SChris Metcalf #include <asm/unaligned.h>
211da177e4SLinus Torvalds #include <linux/skbuff.h>
221da177e4SLinus Torvalds #include <linux/ip.h>
231da177e4SLinus Torvalds #include <linux/icmp.h>
241da177e4SLinus Torvalds #include <linux/netdevice.h>
251da177e4SLinus Torvalds #include <linux/rtnetlink.h>
261da177e4SLinus Torvalds #include <net/sock.h>
271da177e4SLinus Torvalds #include <net/ip.h>
281da177e4SLinus Torvalds #include <net/icmp.h>
2914c85021SArnaldo Carvalho de Melo #include <net/route.h>
3011a03f78SPaul Moore #include <net/cipso_ipv4.h>
3135ebf65eSDavid S. Miller #include <net/ip_fib.h>
321da177e4SLinus Torvalds 
331da177e4SLinus Torvalds /*
341da177e4SLinus Torvalds  * Write options to IP header, record destination address to
351da177e4SLinus Torvalds  * source route option, address of outgoing interface
361da177e4SLinus Torvalds  * (we should already know it, so that this  function is allowed be
371da177e4SLinus Torvalds  * called only after routing decision) and timestamp,
381da177e4SLinus Torvalds  * if we originate this datagram.
391da177e4SLinus Torvalds  *
401da177e4SLinus Torvalds  * daddr is real destination address, next hop is recorded in IP header.
411da177e4SLinus Torvalds  * saddr is address of outgoing interface.
421da177e4SLinus Torvalds  */
431da177e4SLinus Torvalds 
441da177e4SLinus Torvalds void ip_options_build(struct sk_buff *skb, struct ip_options *opt,
458712f774SAl Viro 		      __be32 daddr, struct rtable *rt, int is_frag)
461da177e4SLinus Torvalds {
47d56f90a7SArnaldo Carvalho de Melo 	unsigned char *iph = skb_network_header(skb);
481da177e4SLinus Torvalds 
491da177e4SLinus Torvalds 	memcpy(&(IPCB(skb)->opt), opt, sizeof(struct ip_options));
501da177e4SLinus Torvalds 	memcpy(iph+sizeof(struct iphdr), opt->__data, opt->optlen);
511da177e4SLinus Torvalds 	opt = &(IPCB(skb)->opt);
521da177e4SLinus Torvalds 
531da177e4SLinus Torvalds 	if (opt->srr)
541da177e4SLinus Torvalds 		memcpy(iph+opt->srr+iph[opt->srr+1]-4, &daddr, 4);
551da177e4SLinus Torvalds 
561da177e4SLinus Torvalds 	if (!is_frag) {
571da177e4SLinus Torvalds 		if (opt->rr_needaddr)
588e36360aSDavid S. Miller 			ip_rt_get_source(iph+opt->rr+iph[opt->rr+2]-5, skb, rt);
591da177e4SLinus Torvalds 		if (opt->ts_needaddr)
608e36360aSDavid S. Miller 			ip_rt_get_source(iph+opt->ts+iph[opt->ts+2]-9, skb, rt);
611da177e4SLinus Torvalds 		if (opt->ts_needtime) {
62e25d2ca6SAl Viro 			__be32 midtime;
63822c8685SDeepa Dinamani 
64822c8685SDeepa Dinamani 			midtime = inet_current_timestamp();
651da177e4SLinus Torvalds 			memcpy(iph+opt->ts+iph[opt->ts+2]-5, &midtime, 4);
661da177e4SLinus Torvalds 		}
671da177e4SLinus Torvalds 		return;
681da177e4SLinus Torvalds 	}
691da177e4SLinus Torvalds 	if (opt->rr) {
701da177e4SLinus Torvalds 		memset(iph+opt->rr, IPOPT_NOP, iph[opt->rr+1]);
711da177e4SLinus Torvalds 		opt->rr = 0;
721da177e4SLinus Torvalds 		opt->rr_needaddr = 0;
731da177e4SLinus Torvalds 	}
741da177e4SLinus Torvalds 	if (opt->ts) {
751da177e4SLinus Torvalds 		memset(iph+opt->ts, IPOPT_NOP, iph[opt->ts+1]);
761da177e4SLinus Torvalds 		opt->ts = 0;
771da177e4SLinus Torvalds 		opt->ts_needaddr = opt->ts_needtime = 0;
781da177e4SLinus Torvalds 	}
791da177e4SLinus Torvalds }
801da177e4SLinus Torvalds 
811da177e4SLinus Torvalds /*
821da177e4SLinus Torvalds  * Provided (sopt, skb) points to received options,
831da177e4SLinus Torvalds  * build in dopt compiled option set appropriate for answering.
841da177e4SLinus Torvalds  * i.e. invert SRR option, copy anothers,
851da177e4SLinus Torvalds  * and grab room in RR/TS options.
861da177e4SLinus Torvalds  *
871da177e4SLinus Torvalds  * NOTE: dopt cannot point to skb.
881da177e4SLinus Torvalds  */
891da177e4SLinus Torvalds 
9091ed1e66SPaolo Abeni int __ip_options_echo(struct net *net, struct ip_options *dopt,
9191ed1e66SPaolo Abeni 		      struct sk_buff *skb, const struct ip_options *sopt)
921da177e4SLinus Torvalds {
931da177e4SLinus Torvalds 	unsigned char *sptr, *dptr;
941da177e4SLinus Torvalds 	int soffset, doffset;
951da177e4SLinus Torvalds 	int	optlen;
961da177e4SLinus Torvalds 
971da177e4SLinus Torvalds 	memset(dopt, 0, sizeof(struct ip_options));
981da177e4SLinus Torvalds 
99f6d8bd05SEric Dumazet 	if (sopt->optlen == 0)
1001da177e4SLinus Torvalds 		return 0;
1011da177e4SLinus Torvalds 
102d56f90a7SArnaldo Carvalho de Melo 	sptr = skb_network_header(skb);
1031da177e4SLinus Torvalds 	dptr = dopt->__data;
1041da177e4SLinus Torvalds 
1051da177e4SLinus Torvalds 	if (sopt->rr) {
1061da177e4SLinus Torvalds 		optlen  = sptr[sopt->rr+1];
1071da177e4SLinus Torvalds 		soffset = sptr[sopt->rr+2];
1081da177e4SLinus Torvalds 		dopt->rr = dopt->optlen + sizeof(struct iphdr);
1091da177e4SLinus Torvalds 		memcpy(dptr, sptr+sopt->rr, optlen);
1101da177e4SLinus Torvalds 		if (sopt->rr_needaddr && soffset <= optlen) {
1111da177e4SLinus Torvalds 			if (soffset + 3 > optlen)
1121da177e4SLinus Torvalds 				return -EINVAL;
1131da177e4SLinus Torvalds 			dptr[2] = soffset + 4;
1141da177e4SLinus Torvalds 			dopt->rr_needaddr = 1;
1151da177e4SLinus Torvalds 		}
1161da177e4SLinus Torvalds 		dptr += optlen;
1171da177e4SLinus Torvalds 		dopt->optlen += optlen;
1181da177e4SLinus Torvalds 	}
1191da177e4SLinus Torvalds 	if (sopt->ts) {
1201da177e4SLinus Torvalds 		optlen = sptr[sopt->ts+1];
1211da177e4SLinus Torvalds 		soffset = sptr[sopt->ts+2];
1221da177e4SLinus Torvalds 		dopt->ts = dopt->optlen + sizeof(struct iphdr);
1231da177e4SLinus Torvalds 		memcpy(dptr, sptr+sopt->ts, optlen);
1241da177e4SLinus Torvalds 		if (soffset <= optlen) {
1251da177e4SLinus Torvalds 			if (sopt->ts_needaddr) {
1261da177e4SLinus Torvalds 				if (soffset + 3 > optlen)
1271da177e4SLinus Torvalds 					return -EINVAL;
1281da177e4SLinus Torvalds 				dopt->ts_needaddr = 1;
1291da177e4SLinus Torvalds 				soffset += 4;
1301da177e4SLinus Torvalds 			}
1311da177e4SLinus Torvalds 			if (sopt->ts_needtime) {
1321da177e4SLinus Torvalds 				if (soffset + 3 > optlen)
1331da177e4SLinus Torvalds 					return -EINVAL;
1341da177e4SLinus Torvalds 				if ((dptr[3]&0xF) != IPOPT_TS_PRESPEC) {
1351da177e4SLinus Torvalds 					dopt->ts_needtime = 1;
1361da177e4SLinus Torvalds 					soffset += 4;
1371da177e4SLinus Torvalds 				} else {
1381da177e4SLinus Torvalds 					dopt->ts_needtime = 0;
1391da177e4SLinus Torvalds 
1408628bd8aSJan Luebbe 					if (soffset + 7 <= optlen) {
141fd683222SAl Viro 						__be32 addr;
1421da177e4SLinus Torvalds 
1438628bd8aSJan Luebbe 						memcpy(&addr, dptr+soffset-1, 4);
14491ed1e66SPaolo Abeni 						if (inet_addr_type(net, addr) != RTN_UNICAST) {
1451da177e4SLinus Torvalds 							dopt->ts_needtime = 1;
1461da177e4SLinus Torvalds 							soffset += 8;
1471da177e4SLinus Torvalds 						}
1481da177e4SLinus Torvalds 					}
1491da177e4SLinus Torvalds 				}
1501da177e4SLinus Torvalds 			}
1511da177e4SLinus Torvalds 			dptr[2] = soffset;
1521da177e4SLinus Torvalds 		}
1531da177e4SLinus Torvalds 		dptr += optlen;
1541da177e4SLinus Torvalds 		dopt->optlen += optlen;
1551da177e4SLinus Torvalds 	}
1561da177e4SLinus Torvalds 	if (sopt->srr) {
1571da177e4SLinus Torvalds 		unsigned char *start = sptr+sopt->srr;
1583ca3c68eSAl Viro 		__be32 faddr;
1591da177e4SLinus Torvalds 
1601da177e4SLinus Torvalds 		optlen  = start[1];
1611da177e4SLinus Torvalds 		soffset = start[2];
1621da177e4SLinus Torvalds 		doffset = 0;
1631da177e4SLinus Torvalds 		if (soffset > optlen)
1641da177e4SLinus Torvalds 			soffset = optlen + 1;
1651da177e4SLinus Torvalds 		soffset -= 4;
1661da177e4SLinus Torvalds 		if (soffset > 3) {
1671da177e4SLinus Torvalds 			memcpy(&faddr, &start[soffset-1], 4);
1681da177e4SLinus Torvalds 			for (soffset -= 4, doffset = 4; soffset > 3; soffset -= 4, doffset += 4)
1691da177e4SLinus Torvalds 				memcpy(&dptr[doffset-1], &start[soffset-1], 4);
1701da177e4SLinus Torvalds 			/*
1711da177e4SLinus Torvalds 			 * RFC1812 requires to fix illegal source routes.
1721da177e4SLinus Torvalds 			 */
173eddc9ec5SArnaldo Carvalho de Melo 			if (memcmp(&ip_hdr(skb)->saddr,
174eddc9ec5SArnaldo Carvalho de Melo 				   &start[soffset + 3], 4) == 0)
1751da177e4SLinus Torvalds 				doffset -= 4;
1761da177e4SLinus Torvalds 		}
1771da177e4SLinus Torvalds 		if (doffset > 3) {
1781da177e4SLinus Torvalds 			dopt->faddr = faddr;
1791da177e4SLinus Torvalds 			dptr[0] = start[0];
1801da177e4SLinus Torvalds 			dptr[1] = doffset+3;
1811da177e4SLinus Torvalds 			dptr[2] = 4;
1821da177e4SLinus Torvalds 			dptr += doffset+3;
1831da177e4SLinus Torvalds 			dopt->srr = dopt->optlen + sizeof(struct iphdr);
1841da177e4SLinus Torvalds 			dopt->optlen += doffset+3;
1851da177e4SLinus Torvalds 			dopt->is_strictroute = sopt->is_strictroute;
1861da177e4SLinus Torvalds 		}
1871da177e4SLinus Torvalds 	}
18811a03f78SPaul Moore 	if (sopt->cipso) {
18911a03f78SPaul Moore 		optlen  = sptr[sopt->cipso+1];
19011a03f78SPaul Moore 		dopt->cipso = dopt->optlen+sizeof(struct iphdr);
19111a03f78SPaul Moore 		memcpy(dptr, sptr+sopt->cipso, optlen);
19211a03f78SPaul Moore 		dptr += optlen;
19311a03f78SPaul Moore 		dopt->optlen += optlen;
19411a03f78SPaul Moore 	}
1951da177e4SLinus Torvalds 	while (dopt->optlen & 3) {
1961da177e4SLinus Torvalds 		*dptr++ = IPOPT_END;
1971da177e4SLinus Torvalds 		dopt->optlen++;
1981da177e4SLinus Torvalds 	}
1991da177e4SLinus Torvalds 	return 0;
2001da177e4SLinus Torvalds }
2011da177e4SLinus Torvalds 
2021da177e4SLinus Torvalds /*
2031da177e4SLinus Torvalds  *	Options "fragmenting", just fill options not
2041da177e4SLinus Torvalds  *	allowed in fragments with NOOPs.
2051da177e4SLinus Torvalds  *	Simple and stupid 8), but the most efficient way.
2061da177e4SLinus Torvalds  */
2071da177e4SLinus Torvalds 
2081da177e4SLinus Torvalds void ip_options_fragment(struct sk_buff *skb)
2091da177e4SLinus Torvalds {
210d56f90a7SArnaldo Carvalho de Melo 	unsigned char *optptr = skb_network_header(skb) + sizeof(struct iphdr);
2111da177e4SLinus Torvalds 	struct ip_options *opt = &(IPCB(skb)->opt);
2121da177e4SLinus Torvalds 	int  l = opt->optlen;
2131da177e4SLinus Torvalds 	int  optlen;
2141da177e4SLinus Torvalds 
2151da177e4SLinus Torvalds 	while (l > 0) {
2161da177e4SLinus Torvalds 		switch (*optptr) {
2171da177e4SLinus Torvalds 		case IPOPT_END:
2181da177e4SLinus Torvalds 			return;
2191da177e4SLinus Torvalds 		case IPOPT_NOOP:
2201da177e4SLinus Torvalds 			l--;
2211da177e4SLinus Torvalds 			optptr++;
2221da177e4SLinus Torvalds 			continue;
2231da177e4SLinus Torvalds 		}
2241da177e4SLinus Torvalds 		optlen = optptr[1];
2251da177e4SLinus Torvalds 		if (optlen < 2 || optlen > l)
2261da177e4SLinus Torvalds 		  return;
2271da177e4SLinus Torvalds 		if (!IPOPT_COPIED(*optptr))
2281da177e4SLinus Torvalds 			memset(optptr, IPOPT_NOOP, optlen);
2291da177e4SLinus Torvalds 		l -= optlen;
2301da177e4SLinus Torvalds 		optptr += optlen;
2311da177e4SLinus Torvalds 	}
2321da177e4SLinus Torvalds 	opt->ts = 0;
2331da177e4SLinus Torvalds 	opt->rr = 0;
2341da177e4SLinus Torvalds 	opt->rr_needaddr = 0;
2351da177e4SLinus Torvalds 	opt->ts_needaddr = 0;
2361da177e4SLinus Torvalds 	opt->ts_needtime = 0;
2371da177e4SLinus Torvalds }
2381da177e4SLinus Torvalds 
239bf5e53e3SEric Dumazet /* helper used by ip_options_compile() to call fib_compute_spec_dst()
240bf5e53e3SEric Dumazet  * at most one time.
241bf5e53e3SEric Dumazet  */
242bf5e53e3SEric Dumazet static void spec_dst_fill(__be32 *spec_dst, struct sk_buff *skb)
243bf5e53e3SEric Dumazet {
244bf5e53e3SEric Dumazet 	if (*spec_dst == htonl(INADDR_ANY))
245bf5e53e3SEric Dumazet 		*spec_dst = fib_compute_spec_dst(skb);
246bf5e53e3SEric Dumazet }
247bf5e53e3SEric Dumazet 
2481da177e4SLinus Torvalds /*
2491da177e4SLinus Torvalds  * Verify options and fill pointers in struct options.
2501da177e4SLinus Torvalds  * Caller should clear *opt, and set opt->data.
2511da177e4SLinus Torvalds  * If opt == NULL, then skb->data should point to IP header.
2521da177e4SLinus Torvalds  */
2531da177e4SLinus Torvalds 
2543da1ed7aSNazarov Sergey int __ip_options_compile(struct net *net,
2553da1ed7aSNazarov Sergey 			 struct ip_options *opt, struct sk_buff *skb,
2563da1ed7aSNazarov Sergey 			 __be32 *info)
2571da177e4SLinus Torvalds {
258bf5e53e3SEric Dumazet 	__be32 spec_dst = htonl(INADDR_ANY);
2591da177e4SLinus Torvalds 	unsigned char *pp_ptr = NULL;
26011604721SDavid S. Miller 	struct rtable *rt = NULL;
26135ebf65eSDavid S. Miller 	unsigned char *optptr;
26235ebf65eSDavid S. Miller 	unsigned char *iph;
26335ebf65eSDavid S. Miller 	int optlen, l;
2641da177e4SLinus Torvalds 
26500db4124SIan Morris 	if (skb) {
26611604721SDavid S. Miller 		rt = skb_rtable(skb);
26722aba383SDenis V. Lunev 		optptr = (unsigned char *)&(ip_hdr(skb)[1]);
26822aba383SDenis V. Lunev 	} else
26910fe7d85SDenis V. Lunev 		optptr = opt->__data;
2701da177e4SLinus Torvalds 	iph = optptr - sizeof(struct iphdr);
2711da177e4SLinus Torvalds 
2721da177e4SLinus Torvalds 	for (l = opt->optlen; l > 0; ) {
2731da177e4SLinus Torvalds 		switch (*optptr) {
2741da177e4SLinus Torvalds 		case IPOPT_END:
2751da177e4SLinus Torvalds 			for (optptr++, l--; l > 0; optptr++, l--) {
2761da177e4SLinus Torvalds 				if (*optptr != IPOPT_END) {
2771da177e4SLinus Torvalds 					*optptr = IPOPT_END;
2781da177e4SLinus Torvalds 					opt->is_changed = 1;
2791da177e4SLinus Torvalds 				}
2801da177e4SLinus Torvalds 			}
2811da177e4SLinus Torvalds 			goto eol;
2821da177e4SLinus Torvalds 		case IPOPT_NOOP:
2831da177e4SLinus Torvalds 			l--;
2841da177e4SLinus Torvalds 			optptr++;
2851da177e4SLinus Torvalds 			continue;
2861da177e4SLinus Torvalds 		}
28710ec9472SEric Dumazet 		if (unlikely(l < 2)) {
28810ec9472SEric Dumazet 			pp_ptr = optptr;
28910ec9472SEric Dumazet 			goto error;
29010ec9472SEric Dumazet 		}
2911da177e4SLinus Torvalds 		optlen = optptr[1];
2921da177e4SLinus Torvalds 		if (optlen < 2 || optlen > l) {
2931da177e4SLinus Torvalds 			pp_ptr = optptr;
2941da177e4SLinus Torvalds 			goto error;
2951da177e4SLinus Torvalds 		}
2961da177e4SLinus Torvalds 		switch (*optptr) {
2971da177e4SLinus Torvalds 		case IPOPT_SSRR:
2981da177e4SLinus Torvalds 		case IPOPT_LSRR:
2991da177e4SLinus Torvalds 			if (optlen < 3) {
3001da177e4SLinus Torvalds 				pp_ptr = optptr + 1;
3011da177e4SLinus Torvalds 				goto error;
3021da177e4SLinus Torvalds 			}
3031da177e4SLinus Torvalds 			if (optptr[2] < 4) {
3041da177e4SLinus Torvalds 				pp_ptr = optptr + 2;
3051da177e4SLinus Torvalds 				goto error;
3061da177e4SLinus Torvalds 			}
3071da177e4SLinus Torvalds 			/* NB: cf RFC-1812 5.2.4.1 */
3081da177e4SLinus Torvalds 			if (opt->srr) {
3091da177e4SLinus Torvalds 				pp_ptr = optptr;
3101da177e4SLinus Torvalds 				goto error;
3111da177e4SLinus Torvalds 			}
3121da177e4SLinus Torvalds 			if (!skb) {
3131da177e4SLinus Torvalds 				if (optptr[2] != 4 || optlen < 7 || ((optlen-3) & 3)) {
3141da177e4SLinus Torvalds 					pp_ptr = optptr + 1;
3151da177e4SLinus Torvalds 					goto error;
3161da177e4SLinus Torvalds 				}
3171da177e4SLinus Torvalds 				memcpy(&opt->faddr, &optptr[3], 4);
3181da177e4SLinus Torvalds 				if (optlen > 7)
3191da177e4SLinus Torvalds 					memmove(&optptr[3], &optptr[7], optlen-7);
3201da177e4SLinus Torvalds 			}
3211da177e4SLinus Torvalds 			opt->is_strictroute = (optptr[0] == IPOPT_SSRR);
3221da177e4SLinus Torvalds 			opt->srr = optptr - iph;
3231da177e4SLinus Torvalds 			break;
3241da177e4SLinus Torvalds 		case IPOPT_RR:
3251da177e4SLinus Torvalds 			if (opt->rr) {
3261da177e4SLinus Torvalds 				pp_ptr = optptr;
3271da177e4SLinus Torvalds 				goto error;
3281da177e4SLinus Torvalds 			}
3291da177e4SLinus Torvalds 			if (optlen < 3) {
3301da177e4SLinus Torvalds 				pp_ptr = optptr + 1;
3311da177e4SLinus Torvalds 				goto error;
3321da177e4SLinus Torvalds 			}
3331da177e4SLinus Torvalds 			if (optptr[2] < 4) {
3341da177e4SLinus Torvalds 				pp_ptr = optptr + 2;
3351da177e4SLinus Torvalds 				goto error;
3361da177e4SLinus Torvalds 			}
3371da177e4SLinus Torvalds 			if (optptr[2] <= optlen) {
3381da177e4SLinus Torvalds 				if (optptr[2]+3 > optlen) {
3391da177e4SLinus Torvalds 					pp_ptr = optptr + 2;
3401da177e4SLinus Torvalds 					goto error;
3411da177e4SLinus Torvalds 				}
34211604721SDavid S. Miller 				if (rt) {
343bf5e53e3SEric Dumazet 					spec_dst_fill(&spec_dst, skb);
34435ebf65eSDavid S. Miller 					memcpy(&optptr[optptr[2]-1], &spec_dst, 4);
3451da177e4SLinus Torvalds 					opt->is_changed = 1;
3461da177e4SLinus Torvalds 				}
3471da177e4SLinus Torvalds 				optptr[2] += 4;
3481da177e4SLinus Torvalds 				opt->rr_needaddr = 1;
3491da177e4SLinus Torvalds 			}
3501da177e4SLinus Torvalds 			opt->rr = optptr - iph;
3511da177e4SLinus Torvalds 			break;
3521da177e4SLinus Torvalds 		case IPOPT_TIMESTAMP:
3531da177e4SLinus Torvalds 			if (opt->ts) {
3541da177e4SLinus Torvalds 				pp_ptr = optptr;
3551da177e4SLinus Torvalds 				goto error;
3561da177e4SLinus Torvalds 			}
3571da177e4SLinus Torvalds 			if (optlen < 4) {
3581da177e4SLinus Torvalds 				pp_ptr = optptr + 1;
3591da177e4SLinus Torvalds 				goto error;
3601da177e4SLinus Torvalds 			}
3611da177e4SLinus Torvalds 			if (optptr[2] < 5) {
3621da177e4SLinus Torvalds 				pp_ptr = optptr + 2;
3631da177e4SLinus Torvalds 				goto error;
3641da177e4SLinus Torvalds 			}
3651da177e4SLinus Torvalds 			if (optptr[2] <= optlen) {
36648bdf072SChris Metcalf 				unsigned char *timeptr = NULL;
3675a2b646fSHisao Tanabe 				if (optptr[2]+3 > optlen) {
3681da177e4SLinus Torvalds 					pp_ptr = optptr + 2;
3691da177e4SLinus Torvalds 					goto error;
3701da177e4SLinus Torvalds 				}
3711da177e4SLinus Torvalds 				switch (optptr[3]&0xF) {
3721da177e4SLinus Torvalds 				case IPOPT_TS_TSONLY:
3731da177e4SLinus Torvalds 					if (skb)
37448bdf072SChris Metcalf 						timeptr = &optptr[optptr[2]-1];
3751da177e4SLinus Torvalds 					opt->ts_needtime = 1;
3761da177e4SLinus Torvalds 					optptr[2] += 4;
3771da177e4SLinus Torvalds 					break;
3781da177e4SLinus Torvalds 				case IPOPT_TS_TSANDADDR:
3795a2b646fSHisao Tanabe 					if (optptr[2]+7 > optlen) {
3801da177e4SLinus Torvalds 						pp_ptr = optptr + 2;
3811da177e4SLinus Torvalds 						goto error;
3821da177e4SLinus Torvalds 					}
38311604721SDavid S. Miller 					if (rt)  {
384bf5e53e3SEric Dumazet 						spec_dst_fill(&spec_dst, skb);
38535ebf65eSDavid S. Miller 						memcpy(&optptr[optptr[2]-1], &spec_dst, 4);
38648bdf072SChris Metcalf 						timeptr = &optptr[optptr[2]+3];
3871da177e4SLinus Torvalds 					}
3881da177e4SLinus Torvalds 					opt->ts_needaddr = 1;
3891da177e4SLinus Torvalds 					opt->ts_needtime = 1;
3901da177e4SLinus Torvalds 					optptr[2] += 8;
3911da177e4SLinus Torvalds 					break;
3921da177e4SLinus Torvalds 				case IPOPT_TS_PRESPEC:
3935a2b646fSHisao Tanabe 					if (optptr[2]+7 > optlen) {
3941da177e4SLinus Torvalds 						pp_ptr = optptr + 2;
3951da177e4SLinus Torvalds 						goto error;
3961da177e4SLinus Torvalds 					}
3971da177e4SLinus Torvalds 					{
398fd683222SAl Viro 						__be32 addr;
3991da177e4SLinus Torvalds 						memcpy(&addr, &optptr[optptr[2]-1], 4);
4000e6bd4a1SDenis V. Lunev 						if (inet_addr_type(net, addr) == RTN_UNICAST)
4011da177e4SLinus Torvalds 							break;
4021da177e4SLinus Torvalds 						if (skb)
40348bdf072SChris Metcalf 							timeptr = &optptr[optptr[2]+3];
4041da177e4SLinus Torvalds 					}
4051da177e4SLinus Torvalds 					opt->ts_needtime = 1;
4061da177e4SLinus Torvalds 					optptr[2] += 8;
4071da177e4SLinus Torvalds 					break;
4081da177e4SLinus Torvalds 				default:
40952e804c6SEric W. Biederman 					if (!skb && !ns_capable(net->user_ns, CAP_NET_RAW)) {
4101da177e4SLinus Torvalds 						pp_ptr = optptr + 3;
4111da177e4SLinus Torvalds 						goto error;
4121da177e4SLinus Torvalds 					}
4131da177e4SLinus Torvalds 					break;
4141da177e4SLinus Torvalds 				}
4151da177e4SLinus Torvalds 				if (timeptr) {
416822c8685SDeepa Dinamani 					__be32 midtime;
417822c8685SDeepa Dinamani 
418822c8685SDeepa Dinamani 					midtime = inet_current_timestamp();
419822c8685SDeepa Dinamani 					memcpy(timeptr, &midtime, 4);
4201da177e4SLinus Torvalds 					opt->is_changed = 1;
4211da177e4SLinus Torvalds 				}
422fa2b04f4SDavid Ward 			} else if ((optptr[3]&0xF) != IPOPT_TS_PRESPEC) {
42395c96174SEric Dumazet 				unsigned int overflow = optptr[3]>>4;
4241da177e4SLinus Torvalds 				if (overflow == 15) {
4251da177e4SLinus Torvalds 					pp_ptr = optptr + 3;
4261da177e4SLinus Torvalds 					goto error;
4271da177e4SLinus Torvalds 				}
4281da177e4SLinus Torvalds 				if (skb) {
4291da177e4SLinus Torvalds 					optptr[3] = (optptr[3]&0xF)|((overflow+1)<<4);
4301da177e4SLinus Torvalds 					opt->is_changed = 1;
4311da177e4SLinus Torvalds 				}
4321da177e4SLinus Torvalds 			}
4334660c7f4SDavid Ward 			opt->ts = optptr - iph;
4341da177e4SLinus Torvalds 			break;
4351da177e4SLinus Torvalds 		case IPOPT_RA:
4361da177e4SLinus Torvalds 			if (optlen < 4) {
4371da177e4SLinus Torvalds 				pp_ptr = optptr + 1;
4381da177e4SLinus Torvalds 				goto error;
4391da177e4SLinus Torvalds 			}
4401da177e4SLinus Torvalds 			if (optptr[2] == 0 && optptr[3] == 0)
4411da177e4SLinus Torvalds 				opt->router_alert = optptr - iph;
4421da177e4SLinus Torvalds 			break;
44311a03f78SPaul Moore 		case IPOPT_CIPSO:
44452e804c6SEric W. Biederman 			if ((!skb && !ns_capable(net->user_ns, CAP_NET_RAW)) || opt->cipso) {
44511a03f78SPaul Moore 				pp_ptr = optptr;
44611a03f78SPaul Moore 				goto error;
44711a03f78SPaul Moore 			}
44811a03f78SPaul Moore 			opt->cipso = optptr - iph;
44915c45f7bSPaul Moore 			if (cipso_v4_validate(skb, &optptr)) {
45011a03f78SPaul Moore 				pp_ptr = optptr;
45111a03f78SPaul Moore 				goto error;
45211a03f78SPaul Moore 			}
45311a03f78SPaul Moore 			break;
4541da177e4SLinus Torvalds 		case IPOPT_SEC:
4551da177e4SLinus Torvalds 		case IPOPT_SID:
4561da177e4SLinus Torvalds 		default:
45752e804c6SEric W. Biederman 			if (!skb && !ns_capable(net->user_ns, CAP_NET_RAW)) {
4581da177e4SLinus Torvalds 				pp_ptr = optptr;
4591da177e4SLinus Torvalds 				goto error;
4601da177e4SLinus Torvalds 			}
4611da177e4SLinus Torvalds 			break;
4621da177e4SLinus Torvalds 		}
4631da177e4SLinus Torvalds 		l -= optlen;
4641da177e4SLinus Torvalds 		optptr += optlen;
4651da177e4SLinus Torvalds 	}
4661da177e4SLinus Torvalds 
4671da177e4SLinus Torvalds eol:
4681da177e4SLinus Torvalds 	if (!pp_ptr)
4691da177e4SLinus Torvalds 		return 0;
4701da177e4SLinus Torvalds 
4711da177e4SLinus Torvalds error:
4723da1ed7aSNazarov Sergey 	if (info)
4733da1ed7aSNazarov Sergey 		*info = htonl((pp_ptr-iph)<<24);
4741da177e4SLinus Torvalds 	return -EINVAL;
4751da177e4SLinus Torvalds }
476dbb5281aSStephen Suryaputra EXPORT_SYMBOL(__ip_options_compile);
4773da1ed7aSNazarov Sergey 
4783da1ed7aSNazarov Sergey int ip_options_compile(struct net *net,
4793da1ed7aSNazarov Sergey 		       struct ip_options *opt, struct sk_buff *skb)
4803da1ed7aSNazarov Sergey {
4813da1ed7aSNazarov Sergey 	int ret;
4823da1ed7aSNazarov Sergey 	__be32 info;
4833da1ed7aSNazarov Sergey 
4843da1ed7aSNazarov Sergey 	ret = __ip_options_compile(net, opt, skb, &info);
4853da1ed7aSNazarov Sergey 	if (ret != 0 && skb)
4863da1ed7aSNazarov Sergey 		icmp_send(skb, ICMP_PARAMETERPROB, 0, info);
4873da1ed7aSNazarov Sergey 	return ret;
4883da1ed7aSNazarov Sergey }
489462fb2afSBandan Das EXPORT_SYMBOL(ip_options_compile);
4901da177e4SLinus Torvalds 
4911da177e4SLinus Torvalds /*
4921da177e4SLinus Torvalds  *	Undo all the changes done by ip_options_compile().
4931da177e4SLinus Torvalds  */
4941da177e4SLinus Torvalds 
4951da177e4SLinus Torvalds void ip_options_undo(struct ip_options *opt)
4961da177e4SLinus Torvalds {
4971da177e4SLinus Torvalds 	if (opt->srr) {
4981da177e4SLinus Torvalds 		unsigned  char *optptr = opt->__data+opt->srr-sizeof(struct  iphdr);
4991da177e4SLinus Torvalds 		memmove(optptr+7, optptr+3, optptr[1]-7);
5001da177e4SLinus Torvalds 		memcpy(optptr+3, &opt->faddr, 4);
5011da177e4SLinus Torvalds 	}
5021da177e4SLinus Torvalds 	if (opt->rr_needaddr) {
5031da177e4SLinus Torvalds 		unsigned  char *optptr = opt->__data+opt->rr-sizeof(struct  iphdr);
5041da177e4SLinus Torvalds 		optptr[2] -= 4;
5051da177e4SLinus Torvalds 		memset(&optptr[optptr[2]-1], 0, 4);
5061da177e4SLinus Torvalds 	}
5071da177e4SLinus Torvalds 	if (opt->ts) {
5081da177e4SLinus Torvalds 		unsigned  char *optptr = opt->__data+opt->ts-sizeof(struct  iphdr);
5091da177e4SLinus Torvalds 		if (opt->ts_needtime) {
5101da177e4SLinus Torvalds 			optptr[2] -= 4;
5111da177e4SLinus Torvalds 			memset(&optptr[optptr[2]-1], 0, 4);
5121da177e4SLinus Torvalds 			if ((optptr[3]&0xF) == IPOPT_TS_PRESPEC)
5131da177e4SLinus Torvalds 				optptr[2] -= 4;
5141da177e4SLinus Torvalds 		}
5151da177e4SLinus Torvalds 		if (opt->ts_needaddr) {
5161da177e4SLinus Torvalds 			optptr[2] -= 4;
5171da177e4SLinus Torvalds 			memset(&optptr[optptr[2]-1], 0, 4);
5181da177e4SLinus Torvalds 		}
5191da177e4SLinus Torvalds 	}
5201da177e4SLinus Torvalds }
5211da177e4SLinus Torvalds 
522f6d8bd05SEric Dumazet static struct ip_options_rcu *ip_options_get_alloc(const int optlen)
5231da177e4SLinus Torvalds {
524f6d8bd05SEric Dumazet 	return kzalloc(sizeof(struct ip_options_rcu) + ((optlen + 3) & ~3),
5254c6ea29dSArnaldo Carvalho de Melo 		       GFP_KERNEL);
5264c6ea29dSArnaldo Carvalho de Melo }
5271da177e4SLinus Torvalds 
528f6d8bd05SEric Dumazet static int ip_options_get_finish(struct net *net, struct ip_options_rcu **optp,
529f6d8bd05SEric Dumazet 				 struct ip_options_rcu *opt, int optlen)
5304c6ea29dSArnaldo Carvalho de Melo {
5311da177e4SLinus Torvalds 	while (optlen & 3)
532f6d8bd05SEric Dumazet 		opt->opt.__data[optlen++] = IPOPT_END;
533f6d8bd05SEric Dumazet 	opt->opt.optlen = optlen;
534f6d8bd05SEric Dumazet 	if (optlen && ip_options_compile(net, &opt->opt, NULL)) {
5351da177e4SLinus Torvalds 		kfree(opt);
5361da177e4SLinus Torvalds 		return -EINVAL;
5371da177e4SLinus Torvalds 	}
5381da177e4SLinus Torvalds 	kfree(*optp);
5391da177e4SLinus Torvalds 	*optp = opt;
5401da177e4SLinus Torvalds 	return 0;
5411da177e4SLinus Torvalds }
5421da177e4SLinus Torvalds 
543f6d8bd05SEric Dumazet int ip_options_get_from_user(struct net *net, struct ip_options_rcu **optp,
544f2c4802bSDenis V. Lunev 			     unsigned char __user *data, int optlen)
5454c6ea29dSArnaldo Carvalho de Melo {
546f6d8bd05SEric Dumazet 	struct ip_options_rcu *opt = ip_options_get_alloc(optlen);
5474c6ea29dSArnaldo Carvalho de Melo 
5484c6ea29dSArnaldo Carvalho de Melo 	if (!opt)
5494c6ea29dSArnaldo Carvalho de Melo 		return -ENOMEM;
550f6d8bd05SEric Dumazet 	if (optlen && copy_from_user(opt->opt.__data, data, optlen)) {
5514c6ea29dSArnaldo Carvalho de Melo 		kfree(opt);
5524c6ea29dSArnaldo Carvalho de Melo 		return -EFAULT;
5534c6ea29dSArnaldo Carvalho de Melo 	}
554f2c4802bSDenis V. Lunev 	return ip_options_get_finish(net, optp, opt, optlen);
5554c6ea29dSArnaldo Carvalho de Melo }
5564c6ea29dSArnaldo Carvalho de Melo 
557f6d8bd05SEric Dumazet int ip_options_get(struct net *net, struct ip_options_rcu **optp,
558f2c4802bSDenis V. Lunev 		   unsigned char *data, int optlen)
5594c6ea29dSArnaldo Carvalho de Melo {
560f6d8bd05SEric Dumazet 	struct ip_options_rcu *opt = ip_options_get_alloc(optlen);
5614c6ea29dSArnaldo Carvalho de Melo 
5624c6ea29dSArnaldo Carvalho de Melo 	if (!opt)
5634c6ea29dSArnaldo Carvalho de Melo 		return -ENOMEM;
5644c6ea29dSArnaldo Carvalho de Melo 	if (optlen)
565f6d8bd05SEric Dumazet 		memcpy(opt->opt.__data, data, optlen);
566f2c4802bSDenis V. Lunev 	return ip_options_get_finish(net, optp, opt, optlen);
5674c6ea29dSArnaldo Carvalho de Melo }
5684c6ea29dSArnaldo Carvalho de Melo 
5691da177e4SLinus Torvalds void ip_forward_options(struct sk_buff *skb)
5701da177e4SLinus Torvalds {
5711da177e4SLinus Torvalds 	struct   ip_options *opt	= &(IPCB(skb)->opt);
5721da177e4SLinus Torvalds 	unsigned char *optptr;
573511c3f92SEric Dumazet 	struct rtable *rt = skb_rtable(skb);
574d56f90a7SArnaldo Carvalho de Melo 	unsigned char *raw = skb_network_header(skb);
5751da177e4SLinus Torvalds 
5761da177e4SLinus Torvalds 	if (opt->rr_needaddr) {
5771da177e4SLinus Torvalds 		optptr = (unsigned char *)raw + opt->rr;
5788e36360aSDavid S. Miller 		ip_rt_get_source(&optptr[optptr[2]-5], skb, rt);
5791da177e4SLinus Torvalds 		opt->is_changed = 1;
5801da177e4SLinus Torvalds 	}
5811da177e4SLinus Torvalds 	if (opt->srr_is_hit) {
5821da177e4SLinus Torvalds 		int srrptr, srrspace;
5831da177e4SLinus Torvalds 
5841da177e4SLinus Torvalds 		optptr = raw + opt->srr;
5851da177e4SLinus Torvalds 
5861da177e4SLinus Torvalds 		for ( srrptr = optptr[2], srrspace = optptr[1];
5871da177e4SLinus Torvalds 		     srrptr <= srrspace;
5881da177e4SLinus Torvalds 		     srrptr += 4
5891da177e4SLinus Torvalds 		     ) {
5901da177e4SLinus Torvalds 			if (srrptr + 3 > srrspace)
5911da177e4SLinus Torvalds 				break;
592ac8a4810SLi Wei 			if (memcmp(&opt->nexthop, &optptr[srrptr-1], 4) == 0)
5931da177e4SLinus Torvalds 				break;
5941da177e4SLinus Torvalds 		}
5951da177e4SLinus Torvalds 		if (srrptr + 3 <= srrspace) {
5961da177e4SLinus Torvalds 			opt->is_changed = 1;
597ac8a4810SLi Wei 			ip_hdr(skb)->daddr = opt->nexthop;
5985dc7883fSLi Wei 			ip_rt_get_source(&optptr[srrptr-1], skb, rt);
5991da177e4SLinus Torvalds 			optptr[2] = srrptr+4;
600e87cc472SJoe Perches 		} else {
601e87cc472SJoe Perches 			net_crit_ratelimited("%s(): Argh! Destination lost!\n",
602e87cc472SJoe Perches 					     __func__);
603e87cc472SJoe Perches 		}
6041da177e4SLinus Torvalds 		if (opt->ts_needaddr) {
6051da177e4SLinus Torvalds 			optptr = raw + opt->ts;
6068e36360aSDavid S. Miller 			ip_rt_get_source(&optptr[optptr[2]-9], skb, rt);
6071da177e4SLinus Torvalds 			opt->is_changed = 1;
6081da177e4SLinus Torvalds 		}
6091da177e4SLinus Torvalds 	}
6101da177e4SLinus Torvalds 	if (opt->is_changed) {
6111da177e4SLinus Torvalds 		opt->is_changed = 0;
612eddc9ec5SArnaldo Carvalho de Melo 		ip_send_check(ip_hdr(skb));
6131da177e4SLinus Torvalds 	}
6141da177e4SLinus Torvalds }
6151da177e4SLinus Torvalds 
6168c83f2dfSStephen Suryaputra int ip_options_rcv_srr(struct sk_buff *skb, struct net_device *dev)
6171da177e4SLinus Torvalds {
6181da177e4SLinus Torvalds 	struct ip_options *opt = &(IPCB(skb)->opt);
6191da177e4SLinus Torvalds 	int srrspace, srrptr;
6209e12bb22SAl Viro 	__be32 nexthop;
621eddc9ec5SArnaldo Carvalho de Melo 	struct iphdr *iph = ip_hdr(skb);
622d56f90a7SArnaldo Carvalho de Melo 	unsigned char *optptr = skb_network_header(skb) + opt->srr;
623511c3f92SEric Dumazet 	struct rtable *rt = skb_rtable(skb);
6241da177e4SLinus Torvalds 	struct rtable *rt2;
6257fee226aSEric Dumazet 	unsigned long orefdst;
6261da177e4SLinus Torvalds 	int err;
6271da177e4SLinus Torvalds 
62810949550SDavid S. Miller 	if (!rt)
6291da177e4SLinus Torvalds 		return 0;
6301da177e4SLinus Torvalds 
6311da177e4SLinus Torvalds 	if (skb->pkt_type != PACKET_HOST)
6321da177e4SLinus Torvalds 		return -EINVAL;
6331da177e4SLinus Torvalds 	if (rt->rt_type == RTN_UNICAST) {
6341da177e4SLinus Torvalds 		if (!opt->is_strictroute)
6351da177e4SLinus Torvalds 			return 0;
6361da177e4SLinus Torvalds 		icmp_send(skb, ICMP_PARAMETERPROB, 0, htonl(16<<24));
6371da177e4SLinus Torvalds 		return -EINVAL;
6381da177e4SLinus Torvalds 	}
6391da177e4SLinus Torvalds 	if (rt->rt_type != RTN_LOCAL)
6401da177e4SLinus Torvalds 		return -EINVAL;
6411da177e4SLinus Torvalds 
6421da177e4SLinus Torvalds 	for (srrptr = optptr[2], srrspace = optptr[1]; srrptr <= srrspace; srrptr += 4) {
6431da177e4SLinus Torvalds 		if (srrptr + 3 > srrspace) {
6441da177e4SLinus Torvalds 			icmp_send(skb, ICMP_PARAMETERPROB, 0, htonl((opt->srr+2)<<24));
6451da177e4SLinus Torvalds 			return -EINVAL;
6461da177e4SLinus Torvalds 		}
6471da177e4SLinus Torvalds 		memcpy(&nexthop, &optptr[srrptr-1], 4);
6481da177e4SLinus Torvalds 
6497fee226aSEric Dumazet 		orefdst = skb->_skb_refdst;
650adf30907SEric Dumazet 		skb_dst_set(skb, NULL);
6518c83f2dfSStephen Suryaputra 		err = ip_route_input(skb, nexthop, iph->saddr, iph->tos, dev);
652511c3f92SEric Dumazet 		rt2 = skb_rtable(skb);
6531da177e4SLinus Torvalds 		if (err || (rt2->rt_type != RTN_UNICAST && rt2->rt_type != RTN_LOCAL)) {
6547fee226aSEric Dumazet 			skb_dst_drop(skb);
6557fee226aSEric Dumazet 			skb->_skb_refdst = orefdst;
6561da177e4SLinus Torvalds 			return -EINVAL;
6571da177e4SLinus Torvalds 		}
6587fee226aSEric Dumazet 		refdst_drop(orefdst);
6591da177e4SLinus Torvalds 		if (rt2->rt_type != RTN_LOCAL)
6601da177e4SLinus Torvalds 			break;
6611da177e4SLinus Torvalds 		/* Superfast 8) loopback forward */
662c30883bdSDavid S. Miller 		iph->daddr = nexthop;
6631da177e4SLinus Torvalds 		opt->is_changed = 1;
6641da177e4SLinus Torvalds 	}
6651da177e4SLinus Torvalds 	if (srrptr <= srrspace) {
6661da177e4SLinus Torvalds 		opt->srr_is_hit = 1;
667ac8a4810SLi Wei 		opt->nexthop = nexthop;
6681da177e4SLinus Torvalds 		opt->is_changed = 1;
6691da177e4SLinus Torvalds 	}
6701da177e4SLinus Torvalds 	return 0;
6711da177e4SLinus Torvalds }
672462fb2afSBandan Das EXPORT_SYMBOL(ip_options_rcv_srr);
673