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
ip_options_build(struct sk_buff * skb,struct ip_options * opt,__be32 daddr,struct rtable * rt)441da177e4SLinus Torvalds void ip_options_build(struct sk_buff *skb, struct ip_options *opt,
45*4f0e3040SJakub Kicinski __be32 daddr, struct rtable *rt)
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 (opt->rr_needaddr)
578e36360aSDavid S. Miller ip_rt_get_source(iph + opt->rr + iph[opt->rr + 2] - 5, skb, rt);
581da177e4SLinus Torvalds if (opt->ts_needaddr)
598e36360aSDavid S. Miller ip_rt_get_source(iph + opt->ts + iph[opt->ts + 2] - 9, skb, rt);
601da177e4SLinus Torvalds if (opt->ts_needtime) {
61e25d2ca6SAl Viro __be32 midtime;
62822c8685SDeepa Dinamani
63822c8685SDeepa Dinamani midtime = inet_current_timestamp();
641da177e4SLinus Torvalds memcpy(iph + opt->ts + iph[opt->ts + 2] - 5, &midtime, 4);
651da177e4SLinus Torvalds }
661da177e4SLinus Torvalds }
671da177e4SLinus Torvalds
681da177e4SLinus Torvalds /*
691da177e4SLinus Torvalds * Provided (sopt, skb) points to received options,
701da177e4SLinus Torvalds * build in dopt compiled option set appropriate for answering.
711da177e4SLinus Torvalds * i.e. invert SRR option, copy anothers,
721da177e4SLinus Torvalds * and grab room in RR/TS options.
731da177e4SLinus Torvalds *
741da177e4SLinus Torvalds * NOTE: dopt cannot point to skb.
751da177e4SLinus Torvalds */
761da177e4SLinus Torvalds
__ip_options_echo(struct net * net,struct ip_options * dopt,struct sk_buff * skb,const struct ip_options * sopt)7791ed1e66SPaolo Abeni int __ip_options_echo(struct net *net, struct ip_options *dopt,
7891ed1e66SPaolo Abeni struct sk_buff *skb, const struct ip_options *sopt)
791da177e4SLinus Torvalds {
801da177e4SLinus Torvalds unsigned char *sptr, *dptr;
811da177e4SLinus Torvalds int soffset, doffset;
821da177e4SLinus Torvalds int optlen;
831da177e4SLinus Torvalds
841da177e4SLinus Torvalds memset(dopt, 0, sizeof(struct ip_options));
851da177e4SLinus Torvalds
86f6d8bd05SEric Dumazet if (sopt->optlen == 0)
871da177e4SLinus Torvalds return 0;
881da177e4SLinus Torvalds
89d56f90a7SArnaldo Carvalho de Melo sptr = skb_network_header(skb);
901da177e4SLinus Torvalds dptr = dopt->__data;
911da177e4SLinus Torvalds
921da177e4SLinus Torvalds if (sopt->rr) {
931da177e4SLinus Torvalds optlen = sptr[sopt->rr+1];
941da177e4SLinus Torvalds soffset = sptr[sopt->rr+2];
951da177e4SLinus Torvalds dopt->rr = dopt->optlen + sizeof(struct iphdr);
961da177e4SLinus Torvalds memcpy(dptr, sptr+sopt->rr, optlen);
971da177e4SLinus Torvalds if (sopt->rr_needaddr && soffset <= optlen) {
981da177e4SLinus Torvalds if (soffset + 3 > optlen)
991da177e4SLinus Torvalds return -EINVAL;
1001da177e4SLinus Torvalds dptr[2] = soffset + 4;
1011da177e4SLinus Torvalds dopt->rr_needaddr = 1;
1021da177e4SLinus Torvalds }
1031da177e4SLinus Torvalds dptr += optlen;
1041da177e4SLinus Torvalds dopt->optlen += optlen;
1051da177e4SLinus Torvalds }
1061da177e4SLinus Torvalds if (sopt->ts) {
1071da177e4SLinus Torvalds optlen = sptr[sopt->ts+1];
1081da177e4SLinus Torvalds soffset = sptr[sopt->ts+2];
1091da177e4SLinus Torvalds dopt->ts = dopt->optlen + sizeof(struct iphdr);
1101da177e4SLinus Torvalds memcpy(dptr, sptr+sopt->ts, optlen);
1111da177e4SLinus Torvalds if (soffset <= optlen) {
1121da177e4SLinus Torvalds if (sopt->ts_needaddr) {
1131da177e4SLinus Torvalds if (soffset + 3 > optlen)
1141da177e4SLinus Torvalds return -EINVAL;
1151da177e4SLinus Torvalds dopt->ts_needaddr = 1;
1161da177e4SLinus Torvalds soffset += 4;
1171da177e4SLinus Torvalds }
1181da177e4SLinus Torvalds if (sopt->ts_needtime) {
1191da177e4SLinus Torvalds if (soffset + 3 > optlen)
1201da177e4SLinus Torvalds return -EINVAL;
1211da177e4SLinus Torvalds if ((dptr[3]&0xF) != IPOPT_TS_PRESPEC) {
1221da177e4SLinus Torvalds dopt->ts_needtime = 1;
1231da177e4SLinus Torvalds soffset += 4;
1241da177e4SLinus Torvalds } else {
1251da177e4SLinus Torvalds dopt->ts_needtime = 0;
1261da177e4SLinus Torvalds
1278628bd8aSJan Luebbe if (soffset + 7 <= optlen) {
128fd683222SAl Viro __be32 addr;
1291da177e4SLinus Torvalds
1308628bd8aSJan Luebbe memcpy(&addr, dptr+soffset-1, 4);
13191ed1e66SPaolo Abeni if (inet_addr_type(net, addr) != RTN_UNICAST) {
1321da177e4SLinus Torvalds dopt->ts_needtime = 1;
1331da177e4SLinus Torvalds soffset += 8;
1341da177e4SLinus Torvalds }
1351da177e4SLinus Torvalds }
1361da177e4SLinus Torvalds }
1371da177e4SLinus Torvalds }
1381da177e4SLinus Torvalds dptr[2] = soffset;
1391da177e4SLinus Torvalds }
1401da177e4SLinus Torvalds dptr += optlen;
1411da177e4SLinus Torvalds dopt->optlen += optlen;
1421da177e4SLinus Torvalds }
1431da177e4SLinus Torvalds if (sopt->srr) {
1441da177e4SLinus Torvalds unsigned char *start = sptr+sopt->srr;
1453ca3c68eSAl Viro __be32 faddr;
1461da177e4SLinus Torvalds
1471da177e4SLinus Torvalds optlen = start[1];
1481da177e4SLinus Torvalds soffset = start[2];
1491da177e4SLinus Torvalds doffset = 0;
1501da177e4SLinus Torvalds if (soffset > optlen)
1511da177e4SLinus Torvalds soffset = optlen + 1;
1521da177e4SLinus Torvalds soffset -= 4;
1531da177e4SLinus Torvalds if (soffset > 3) {
1541da177e4SLinus Torvalds memcpy(&faddr, &start[soffset-1], 4);
1551da177e4SLinus Torvalds for (soffset -= 4, doffset = 4; soffset > 3; soffset -= 4, doffset += 4)
1561da177e4SLinus Torvalds memcpy(&dptr[doffset-1], &start[soffset-1], 4);
1571da177e4SLinus Torvalds /*
1581da177e4SLinus Torvalds * RFC1812 requires to fix illegal source routes.
1591da177e4SLinus Torvalds */
160eddc9ec5SArnaldo Carvalho de Melo if (memcmp(&ip_hdr(skb)->saddr,
161eddc9ec5SArnaldo Carvalho de Melo &start[soffset + 3], 4) == 0)
1621da177e4SLinus Torvalds doffset -= 4;
1631da177e4SLinus Torvalds }
1641da177e4SLinus Torvalds if (doffset > 3) {
1651da177e4SLinus Torvalds dopt->faddr = faddr;
1661da177e4SLinus Torvalds dptr[0] = start[0];
1671da177e4SLinus Torvalds dptr[1] = doffset+3;
1681da177e4SLinus Torvalds dptr[2] = 4;
1691da177e4SLinus Torvalds dptr += doffset+3;
1701da177e4SLinus Torvalds dopt->srr = dopt->optlen + sizeof(struct iphdr);
1711da177e4SLinus Torvalds dopt->optlen += doffset+3;
1721da177e4SLinus Torvalds dopt->is_strictroute = sopt->is_strictroute;
1731da177e4SLinus Torvalds }
1741da177e4SLinus Torvalds }
17511a03f78SPaul Moore if (sopt->cipso) {
17611a03f78SPaul Moore optlen = sptr[sopt->cipso+1];
17711a03f78SPaul Moore dopt->cipso = dopt->optlen+sizeof(struct iphdr);
17811a03f78SPaul Moore memcpy(dptr, sptr+sopt->cipso, optlen);
17911a03f78SPaul Moore dptr += optlen;
18011a03f78SPaul Moore dopt->optlen += optlen;
18111a03f78SPaul Moore }
1821da177e4SLinus Torvalds while (dopt->optlen & 3) {
1831da177e4SLinus Torvalds *dptr++ = IPOPT_END;
1841da177e4SLinus Torvalds dopt->optlen++;
1851da177e4SLinus Torvalds }
1861da177e4SLinus Torvalds return 0;
1871da177e4SLinus Torvalds }
1881da177e4SLinus Torvalds
1891da177e4SLinus Torvalds /*
1901da177e4SLinus Torvalds * Options "fragmenting", just fill options not
1911da177e4SLinus Torvalds * allowed in fragments with NOOPs.
1921da177e4SLinus Torvalds * Simple and stupid 8), but the most efficient way.
1931da177e4SLinus Torvalds */
1941da177e4SLinus Torvalds
ip_options_fragment(struct sk_buff * skb)1951da177e4SLinus Torvalds void ip_options_fragment(struct sk_buff *skb)
1961da177e4SLinus Torvalds {
197d56f90a7SArnaldo Carvalho de Melo unsigned char *optptr = skb_network_header(skb) + sizeof(struct iphdr);
1981da177e4SLinus Torvalds struct ip_options *opt = &(IPCB(skb)->opt);
1991da177e4SLinus Torvalds int l = opt->optlen;
2001da177e4SLinus Torvalds int optlen;
2011da177e4SLinus Torvalds
2021da177e4SLinus Torvalds while (l > 0) {
2031da177e4SLinus Torvalds switch (*optptr) {
2041da177e4SLinus Torvalds case IPOPT_END:
2051da177e4SLinus Torvalds return;
2061da177e4SLinus Torvalds case IPOPT_NOOP:
2071da177e4SLinus Torvalds l--;
2081da177e4SLinus Torvalds optptr++;
2091da177e4SLinus Torvalds continue;
2101da177e4SLinus Torvalds }
2111da177e4SLinus Torvalds optlen = optptr[1];
2121da177e4SLinus Torvalds if (optlen < 2 || optlen > l)
2131da177e4SLinus Torvalds return;
2141da177e4SLinus Torvalds if (!IPOPT_COPIED(*optptr))
2151da177e4SLinus Torvalds memset(optptr, IPOPT_NOOP, optlen);
2161da177e4SLinus Torvalds l -= optlen;
2171da177e4SLinus Torvalds optptr += optlen;
2181da177e4SLinus Torvalds }
2191da177e4SLinus Torvalds opt->ts = 0;
2201da177e4SLinus Torvalds opt->rr = 0;
2211da177e4SLinus Torvalds opt->rr_needaddr = 0;
2221da177e4SLinus Torvalds opt->ts_needaddr = 0;
2231da177e4SLinus Torvalds opt->ts_needtime = 0;
2241da177e4SLinus Torvalds }
2251da177e4SLinus Torvalds
226bf5e53e3SEric Dumazet /* helper used by ip_options_compile() to call fib_compute_spec_dst()
227bf5e53e3SEric Dumazet * at most one time.
228bf5e53e3SEric Dumazet */
spec_dst_fill(__be32 * spec_dst,struct sk_buff * skb)229bf5e53e3SEric Dumazet static void spec_dst_fill(__be32 *spec_dst, struct sk_buff *skb)
230bf5e53e3SEric Dumazet {
231bf5e53e3SEric Dumazet if (*spec_dst == htonl(INADDR_ANY))
232bf5e53e3SEric Dumazet *spec_dst = fib_compute_spec_dst(skb);
233bf5e53e3SEric Dumazet }
234bf5e53e3SEric Dumazet
2351da177e4SLinus Torvalds /*
2361da177e4SLinus Torvalds * Verify options and fill pointers in struct options.
2371da177e4SLinus Torvalds * Caller should clear *opt, and set opt->data.
2381da177e4SLinus Torvalds * If opt == NULL, then skb->data should point to IP header.
2391da177e4SLinus Torvalds */
2401da177e4SLinus Torvalds
__ip_options_compile(struct net * net,struct ip_options * opt,struct sk_buff * skb,__be32 * info)2413da1ed7aSNazarov Sergey int __ip_options_compile(struct net *net,
2423da1ed7aSNazarov Sergey struct ip_options *opt, struct sk_buff *skb,
2433da1ed7aSNazarov Sergey __be32 *info)
2441da177e4SLinus Torvalds {
245bf5e53e3SEric Dumazet __be32 spec_dst = htonl(INADDR_ANY);
2461da177e4SLinus Torvalds unsigned char *pp_ptr = NULL;
24711604721SDavid S. Miller struct rtable *rt = NULL;
24835ebf65eSDavid S. Miller unsigned char *optptr;
24935ebf65eSDavid S. Miller unsigned char *iph;
25035ebf65eSDavid S. Miller int optlen, l;
2511da177e4SLinus Torvalds
25200db4124SIan Morris if (skb) {
25311604721SDavid S. Miller rt = skb_rtable(skb);
25422aba383SDenis V. Lunev optptr = (unsigned char *)&(ip_hdr(skb)[1]);
25522aba383SDenis V. Lunev } else
25610fe7d85SDenis V. Lunev optptr = opt->__data;
2571da177e4SLinus Torvalds iph = optptr - sizeof(struct iphdr);
2581da177e4SLinus Torvalds
2591da177e4SLinus Torvalds for (l = opt->optlen; l > 0; ) {
2601da177e4SLinus Torvalds switch (*optptr) {
2611da177e4SLinus Torvalds case IPOPT_END:
2621da177e4SLinus Torvalds for (optptr++, l--; l > 0; optptr++, l--) {
2631da177e4SLinus Torvalds if (*optptr != IPOPT_END) {
2641da177e4SLinus Torvalds *optptr = IPOPT_END;
2651da177e4SLinus Torvalds opt->is_changed = 1;
2661da177e4SLinus Torvalds }
2671da177e4SLinus Torvalds }
2681da177e4SLinus Torvalds goto eol;
2691da177e4SLinus Torvalds case IPOPT_NOOP:
2701da177e4SLinus Torvalds l--;
2711da177e4SLinus Torvalds optptr++;
2721da177e4SLinus Torvalds continue;
2731da177e4SLinus Torvalds }
27410ec9472SEric Dumazet if (unlikely(l < 2)) {
27510ec9472SEric Dumazet pp_ptr = optptr;
27610ec9472SEric Dumazet goto error;
27710ec9472SEric Dumazet }
2781da177e4SLinus Torvalds optlen = optptr[1];
2791da177e4SLinus Torvalds if (optlen < 2 || optlen > l) {
2801da177e4SLinus Torvalds pp_ptr = optptr;
2811da177e4SLinus Torvalds goto error;
2821da177e4SLinus Torvalds }
2831da177e4SLinus Torvalds switch (*optptr) {
2841da177e4SLinus Torvalds case IPOPT_SSRR:
2851da177e4SLinus Torvalds case IPOPT_LSRR:
2861da177e4SLinus Torvalds if (optlen < 3) {
2871da177e4SLinus Torvalds pp_ptr = optptr + 1;
2881da177e4SLinus Torvalds goto error;
2891da177e4SLinus Torvalds }
2901da177e4SLinus Torvalds if (optptr[2] < 4) {
2911da177e4SLinus Torvalds pp_ptr = optptr + 2;
2921da177e4SLinus Torvalds goto error;
2931da177e4SLinus Torvalds }
2941da177e4SLinus Torvalds /* NB: cf RFC-1812 5.2.4.1 */
2951da177e4SLinus Torvalds if (opt->srr) {
2961da177e4SLinus Torvalds pp_ptr = optptr;
2971da177e4SLinus Torvalds goto error;
2981da177e4SLinus Torvalds }
2991da177e4SLinus Torvalds if (!skb) {
3001da177e4SLinus Torvalds if (optptr[2] != 4 || optlen < 7 || ((optlen-3) & 3)) {
3011da177e4SLinus Torvalds pp_ptr = optptr + 1;
3021da177e4SLinus Torvalds goto error;
3031da177e4SLinus Torvalds }
3041da177e4SLinus Torvalds memcpy(&opt->faddr, &optptr[3], 4);
3051da177e4SLinus Torvalds if (optlen > 7)
3061da177e4SLinus Torvalds memmove(&optptr[3], &optptr[7], optlen-7);
3071da177e4SLinus Torvalds }
3081da177e4SLinus Torvalds opt->is_strictroute = (optptr[0] == IPOPT_SSRR);
3091da177e4SLinus Torvalds opt->srr = optptr - iph;
3101da177e4SLinus Torvalds break;
3111da177e4SLinus Torvalds case IPOPT_RR:
3121da177e4SLinus Torvalds if (opt->rr) {
3131da177e4SLinus Torvalds pp_ptr = optptr;
3141da177e4SLinus Torvalds goto error;
3151da177e4SLinus Torvalds }
3161da177e4SLinus Torvalds if (optlen < 3) {
3171da177e4SLinus Torvalds pp_ptr = optptr + 1;
3181da177e4SLinus Torvalds goto error;
3191da177e4SLinus Torvalds }
3201da177e4SLinus Torvalds if (optptr[2] < 4) {
3211da177e4SLinus Torvalds pp_ptr = optptr + 2;
3221da177e4SLinus Torvalds goto error;
3231da177e4SLinus Torvalds }
3241da177e4SLinus Torvalds if (optptr[2] <= optlen) {
3251da177e4SLinus Torvalds if (optptr[2]+3 > optlen) {
3261da177e4SLinus Torvalds pp_ptr = optptr + 2;
3271da177e4SLinus Torvalds goto error;
3281da177e4SLinus Torvalds }
32911604721SDavid S. Miller if (rt) {
330bf5e53e3SEric Dumazet spec_dst_fill(&spec_dst, skb);
33135ebf65eSDavid S. Miller memcpy(&optptr[optptr[2]-1], &spec_dst, 4);
3321da177e4SLinus Torvalds opt->is_changed = 1;
3331da177e4SLinus Torvalds }
3341da177e4SLinus Torvalds optptr[2] += 4;
3351da177e4SLinus Torvalds opt->rr_needaddr = 1;
3361da177e4SLinus Torvalds }
3371da177e4SLinus Torvalds opt->rr = optptr - iph;
3381da177e4SLinus Torvalds break;
3391da177e4SLinus Torvalds case IPOPT_TIMESTAMP:
3401da177e4SLinus Torvalds if (opt->ts) {
3411da177e4SLinus Torvalds pp_ptr = optptr;
3421da177e4SLinus Torvalds goto error;
3431da177e4SLinus Torvalds }
3441da177e4SLinus Torvalds if (optlen < 4) {
3451da177e4SLinus Torvalds pp_ptr = optptr + 1;
3461da177e4SLinus Torvalds goto error;
3471da177e4SLinus Torvalds }
3481da177e4SLinus Torvalds if (optptr[2] < 5) {
3491da177e4SLinus Torvalds pp_ptr = optptr + 2;
3501da177e4SLinus Torvalds goto error;
3511da177e4SLinus Torvalds }
3521da177e4SLinus Torvalds if (optptr[2] <= optlen) {
35348bdf072SChris Metcalf unsigned char *timeptr = NULL;
3545a2b646fSHisao Tanabe if (optptr[2]+3 > optlen) {
3551da177e4SLinus Torvalds pp_ptr = optptr + 2;
3561da177e4SLinus Torvalds goto error;
3571da177e4SLinus Torvalds }
3581da177e4SLinus Torvalds switch (optptr[3]&0xF) {
3591da177e4SLinus Torvalds case IPOPT_TS_TSONLY:
3601da177e4SLinus Torvalds if (skb)
36148bdf072SChris Metcalf timeptr = &optptr[optptr[2]-1];
3621da177e4SLinus Torvalds opt->ts_needtime = 1;
3631da177e4SLinus Torvalds optptr[2] += 4;
3641da177e4SLinus Torvalds break;
3651da177e4SLinus Torvalds case IPOPT_TS_TSANDADDR:
3665a2b646fSHisao Tanabe if (optptr[2]+7 > optlen) {
3671da177e4SLinus Torvalds pp_ptr = optptr + 2;
3681da177e4SLinus Torvalds goto error;
3691da177e4SLinus Torvalds }
37011604721SDavid S. Miller if (rt) {
371bf5e53e3SEric Dumazet spec_dst_fill(&spec_dst, skb);
37235ebf65eSDavid S. Miller memcpy(&optptr[optptr[2]-1], &spec_dst, 4);
37348bdf072SChris Metcalf timeptr = &optptr[optptr[2]+3];
3741da177e4SLinus Torvalds }
3751da177e4SLinus Torvalds opt->ts_needaddr = 1;
3761da177e4SLinus Torvalds opt->ts_needtime = 1;
3771da177e4SLinus Torvalds optptr[2] += 8;
3781da177e4SLinus Torvalds break;
3791da177e4SLinus Torvalds case IPOPT_TS_PRESPEC:
3805a2b646fSHisao Tanabe if (optptr[2]+7 > optlen) {
3811da177e4SLinus Torvalds pp_ptr = optptr + 2;
3821da177e4SLinus Torvalds goto error;
3831da177e4SLinus Torvalds }
3841da177e4SLinus Torvalds {
385fd683222SAl Viro __be32 addr;
3861da177e4SLinus Torvalds memcpy(&addr, &optptr[optptr[2]-1], 4);
3870e6bd4a1SDenis V. Lunev if (inet_addr_type(net, addr) == RTN_UNICAST)
3881da177e4SLinus Torvalds break;
3891da177e4SLinus Torvalds if (skb)
39048bdf072SChris Metcalf timeptr = &optptr[optptr[2]+3];
3911da177e4SLinus Torvalds }
3921da177e4SLinus Torvalds opt->ts_needtime = 1;
3931da177e4SLinus Torvalds optptr[2] += 8;
3941da177e4SLinus Torvalds break;
3951da177e4SLinus Torvalds default:
39652e804c6SEric W. Biederman if (!skb && !ns_capable(net->user_ns, CAP_NET_RAW)) {
3971da177e4SLinus Torvalds pp_ptr = optptr + 3;
3981da177e4SLinus Torvalds goto error;
3991da177e4SLinus Torvalds }
4001da177e4SLinus Torvalds break;
4011da177e4SLinus Torvalds }
4021da177e4SLinus Torvalds if (timeptr) {
403822c8685SDeepa Dinamani __be32 midtime;
404822c8685SDeepa Dinamani
405822c8685SDeepa Dinamani midtime = inet_current_timestamp();
406822c8685SDeepa Dinamani memcpy(timeptr, &midtime, 4);
4071da177e4SLinus Torvalds opt->is_changed = 1;
4081da177e4SLinus Torvalds }
409fa2b04f4SDavid Ward } else if ((optptr[3]&0xF) != IPOPT_TS_PRESPEC) {
41095c96174SEric Dumazet unsigned int overflow = optptr[3]>>4;
4111da177e4SLinus Torvalds if (overflow == 15) {
4121da177e4SLinus Torvalds pp_ptr = optptr + 3;
4131da177e4SLinus Torvalds goto error;
4141da177e4SLinus Torvalds }
4151da177e4SLinus Torvalds if (skb) {
4161da177e4SLinus Torvalds optptr[3] = (optptr[3]&0xF)|((overflow+1)<<4);
4171da177e4SLinus Torvalds opt->is_changed = 1;
4181da177e4SLinus Torvalds }
4191da177e4SLinus Torvalds }
4204660c7f4SDavid Ward opt->ts = optptr - iph;
4211da177e4SLinus Torvalds break;
4221da177e4SLinus Torvalds case IPOPT_RA:
4231da177e4SLinus Torvalds if (optlen < 4) {
4241da177e4SLinus Torvalds pp_ptr = optptr + 1;
4251da177e4SLinus Torvalds goto error;
4261da177e4SLinus Torvalds }
4271da177e4SLinus Torvalds if (optptr[2] == 0 && optptr[3] == 0)
4281da177e4SLinus Torvalds opt->router_alert = optptr - iph;
4291da177e4SLinus Torvalds break;
43011a03f78SPaul Moore case IPOPT_CIPSO:
43152e804c6SEric W. Biederman if ((!skb && !ns_capable(net->user_ns, CAP_NET_RAW)) || opt->cipso) {
43211a03f78SPaul Moore pp_ptr = optptr;
43311a03f78SPaul Moore goto error;
43411a03f78SPaul Moore }
43511a03f78SPaul Moore opt->cipso = optptr - iph;
43615c45f7bSPaul Moore if (cipso_v4_validate(skb, &optptr)) {
43711a03f78SPaul Moore pp_ptr = optptr;
43811a03f78SPaul Moore goto error;
43911a03f78SPaul Moore }
44011a03f78SPaul Moore break;
4411da177e4SLinus Torvalds case IPOPT_SEC:
4421da177e4SLinus Torvalds case IPOPT_SID:
4431da177e4SLinus Torvalds default:
44452e804c6SEric W. Biederman if (!skb && !ns_capable(net->user_ns, CAP_NET_RAW)) {
4451da177e4SLinus Torvalds pp_ptr = optptr;
4461da177e4SLinus Torvalds goto error;
4471da177e4SLinus Torvalds }
4481da177e4SLinus Torvalds break;
4491da177e4SLinus Torvalds }
4501da177e4SLinus Torvalds l -= optlen;
4511da177e4SLinus Torvalds optptr += optlen;
4521da177e4SLinus Torvalds }
4531da177e4SLinus Torvalds
4541da177e4SLinus Torvalds eol:
4551da177e4SLinus Torvalds if (!pp_ptr)
4561da177e4SLinus Torvalds return 0;
4571da177e4SLinus Torvalds
4581da177e4SLinus Torvalds error:
4593da1ed7aSNazarov Sergey if (info)
4603da1ed7aSNazarov Sergey *info = htonl((pp_ptr-iph)<<24);
4611da177e4SLinus Torvalds return -EINVAL;
4621da177e4SLinus Torvalds }
463dbb5281aSStephen Suryaputra EXPORT_SYMBOL(__ip_options_compile);
4643da1ed7aSNazarov Sergey
ip_options_compile(struct net * net,struct ip_options * opt,struct sk_buff * skb)4653da1ed7aSNazarov Sergey int ip_options_compile(struct net *net,
4663da1ed7aSNazarov Sergey struct ip_options *opt, struct sk_buff *skb)
4673da1ed7aSNazarov Sergey {
4683da1ed7aSNazarov Sergey int ret;
4693da1ed7aSNazarov Sergey __be32 info;
4703da1ed7aSNazarov Sergey
4713da1ed7aSNazarov Sergey ret = __ip_options_compile(net, opt, skb, &info);
4723da1ed7aSNazarov Sergey if (ret != 0 && skb)
4733da1ed7aSNazarov Sergey icmp_send(skb, ICMP_PARAMETERPROB, 0, info);
4743da1ed7aSNazarov Sergey return ret;
4753da1ed7aSNazarov Sergey }
476462fb2afSBandan Das EXPORT_SYMBOL(ip_options_compile);
4771da177e4SLinus Torvalds
4781da177e4SLinus Torvalds /*
4791da177e4SLinus Torvalds * Undo all the changes done by ip_options_compile().
4801da177e4SLinus Torvalds */
4811da177e4SLinus Torvalds
ip_options_undo(struct ip_options * opt)4821da177e4SLinus Torvalds void ip_options_undo(struct ip_options *opt)
4831da177e4SLinus Torvalds {
4841da177e4SLinus Torvalds if (opt->srr) {
4851da177e4SLinus Torvalds unsigned char *optptr = opt->__data + opt->srr - sizeof(struct iphdr);
486343d8c60SMiaohe Lin
4871da177e4SLinus Torvalds memmove(optptr + 7, optptr + 3, optptr[1] - 7);
4881da177e4SLinus Torvalds memcpy(optptr + 3, &opt->faddr, 4);
4891da177e4SLinus Torvalds }
4901da177e4SLinus Torvalds if (opt->rr_needaddr) {
4911da177e4SLinus Torvalds unsigned char *optptr = opt->__data + opt->rr - sizeof(struct iphdr);
492343d8c60SMiaohe Lin
4931da177e4SLinus Torvalds optptr[2] -= 4;
4941da177e4SLinus Torvalds memset(&optptr[optptr[2] - 1], 0, 4);
4951da177e4SLinus Torvalds }
4961da177e4SLinus Torvalds if (opt->ts) {
4971da177e4SLinus Torvalds unsigned char *optptr = opt->__data + opt->ts - sizeof(struct iphdr);
498343d8c60SMiaohe Lin
4991da177e4SLinus Torvalds if (opt->ts_needtime) {
5001da177e4SLinus Torvalds optptr[2] -= 4;
5011da177e4SLinus Torvalds memset(&optptr[optptr[2] - 1], 0, 4);
5021da177e4SLinus Torvalds if ((optptr[3] & 0xF) == IPOPT_TS_PRESPEC)
5031da177e4SLinus Torvalds optptr[2] -= 4;
5041da177e4SLinus Torvalds }
5051da177e4SLinus Torvalds if (opt->ts_needaddr) {
5061da177e4SLinus Torvalds optptr[2] -= 4;
5071da177e4SLinus Torvalds memset(&optptr[optptr[2] - 1], 0, 4);
5081da177e4SLinus Torvalds }
5091da177e4SLinus Torvalds }
5101da177e4SLinus Torvalds }
5111da177e4SLinus Torvalds
ip_options_get(struct net * net,struct ip_options_rcu ** optp,sockptr_t data,int optlen)512de40a3e8SChristoph Hellwig int ip_options_get(struct net *net, struct ip_options_rcu **optp,
513de40a3e8SChristoph Hellwig sockptr_t data, int optlen)
5141da177e4SLinus Torvalds {
515de40a3e8SChristoph Hellwig struct ip_options_rcu *opt;
516de40a3e8SChristoph Hellwig
517de40a3e8SChristoph Hellwig opt = kzalloc(sizeof(struct ip_options_rcu) + ((optlen + 3) & ~3),
5184c6ea29dSArnaldo Carvalho de Melo GFP_KERNEL);
519de40a3e8SChristoph Hellwig if (!opt)
520de40a3e8SChristoph Hellwig return -ENOMEM;
521de40a3e8SChristoph Hellwig if (optlen && copy_from_sockptr(opt->opt.__data, data, optlen)) {
522de40a3e8SChristoph Hellwig kfree(opt);
523de40a3e8SChristoph Hellwig return -EFAULT;
5244c6ea29dSArnaldo Carvalho de Melo }
5251da177e4SLinus Torvalds
5261da177e4SLinus Torvalds while (optlen & 3)
527f6d8bd05SEric Dumazet opt->opt.__data[optlen++] = IPOPT_END;
528f6d8bd05SEric Dumazet opt->opt.optlen = optlen;
529f6d8bd05SEric Dumazet if (optlen && ip_options_compile(net, &opt->opt, NULL)) {
5301da177e4SLinus Torvalds kfree(opt);
5311da177e4SLinus Torvalds return -EINVAL;
5321da177e4SLinus Torvalds }
5331da177e4SLinus Torvalds kfree(*optp);
5341da177e4SLinus Torvalds *optp = opt;
5351da177e4SLinus Torvalds return 0;
5361da177e4SLinus Torvalds }
5371da177e4SLinus Torvalds
ip_forward_options(struct sk_buff * skb)5381da177e4SLinus Torvalds void ip_forward_options(struct sk_buff *skb)
5391da177e4SLinus Torvalds {
5401da177e4SLinus Torvalds struct ip_options *opt = &(IPCB(skb)->opt);
5411da177e4SLinus Torvalds unsigned char *optptr;
542511c3f92SEric Dumazet struct rtable *rt = skb_rtable(skb);
543d56f90a7SArnaldo Carvalho de Melo unsigned char *raw = skb_network_header(skb);
5441da177e4SLinus Torvalds
5451da177e4SLinus Torvalds if (opt->rr_needaddr) {
5461da177e4SLinus Torvalds optptr = (unsigned char *)raw + opt->rr;
5478e36360aSDavid S. Miller ip_rt_get_source(&optptr[optptr[2]-5], skb, rt);
5481da177e4SLinus Torvalds opt->is_changed = 1;
5491da177e4SLinus Torvalds }
5501da177e4SLinus Torvalds if (opt->srr_is_hit) {
5511da177e4SLinus Torvalds int srrptr, srrspace;
5521da177e4SLinus Torvalds
5531da177e4SLinus Torvalds optptr = raw + opt->srr;
5541da177e4SLinus Torvalds
5551da177e4SLinus Torvalds for ( srrptr = optptr[2], srrspace = optptr[1];
5561da177e4SLinus Torvalds srrptr <= srrspace;
5571da177e4SLinus Torvalds srrptr += 4
5581da177e4SLinus Torvalds ) {
5591da177e4SLinus Torvalds if (srrptr + 3 > srrspace)
5601da177e4SLinus Torvalds break;
561ac8a4810SLi Wei if (memcmp(&opt->nexthop, &optptr[srrptr-1], 4) == 0)
5621da177e4SLinus Torvalds break;
5631da177e4SLinus Torvalds }
5641da177e4SLinus Torvalds if (srrptr + 3 <= srrspace) {
5651da177e4SLinus Torvalds opt->is_changed = 1;
566ac8a4810SLi Wei ip_hdr(skb)->daddr = opt->nexthop;
5675dc7883fSLi Wei ip_rt_get_source(&optptr[srrptr-1], skb, rt);
5681da177e4SLinus Torvalds optptr[2] = srrptr+4;
569e87cc472SJoe Perches } else {
570e87cc472SJoe Perches net_crit_ratelimited("%s(): Argh! Destination lost!\n",
571e87cc472SJoe Perches __func__);
572e87cc472SJoe Perches }
5731da177e4SLinus Torvalds if (opt->ts_needaddr) {
5741da177e4SLinus Torvalds optptr = raw + opt->ts;
5758e36360aSDavid S. Miller ip_rt_get_source(&optptr[optptr[2]-9], skb, rt);
5761da177e4SLinus Torvalds opt->is_changed = 1;
5771da177e4SLinus Torvalds }
5781da177e4SLinus Torvalds }
5791da177e4SLinus Torvalds if (opt->is_changed) {
5801da177e4SLinus Torvalds opt->is_changed = 0;
581eddc9ec5SArnaldo Carvalho de Melo ip_send_check(ip_hdr(skb));
5821da177e4SLinus Torvalds }
5831da177e4SLinus Torvalds }
5841da177e4SLinus Torvalds
ip_options_rcv_srr(struct sk_buff * skb,struct net_device * dev)5858c83f2dfSStephen Suryaputra int ip_options_rcv_srr(struct sk_buff *skb, struct net_device *dev)
5861da177e4SLinus Torvalds {
5871da177e4SLinus Torvalds struct ip_options *opt = &(IPCB(skb)->opt);
5881da177e4SLinus Torvalds int srrspace, srrptr;
5899e12bb22SAl Viro __be32 nexthop;
590eddc9ec5SArnaldo Carvalho de Melo struct iphdr *iph = ip_hdr(skb);
591d56f90a7SArnaldo Carvalho de Melo unsigned char *optptr = skb_network_header(skb) + opt->srr;
592511c3f92SEric Dumazet struct rtable *rt = skb_rtable(skb);
5931da177e4SLinus Torvalds struct rtable *rt2;
5947fee226aSEric Dumazet unsigned long orefdst;
5951da177e4SLinus Torvalds int err;
5961da177e4SLinus Torvalds
59710949550SDavid S. Miller if (!rt)
5981da177e4SLinus Torvalds return 0;
5991da177e4SLinus Torvalds
6001da177e4SLinus Torvalds if (skb->pkt_type != PACKET_HOST)
6011da177e4SLinus Torvalds return -EINVAL;
6021da177e4SLinus Torvalds if (rt->rt_type == RTN_UNICAST) {
6031da177e4SLinus Torvalds if (!opt->is_strictroute)
6041da177e4SLinus Torvalds return 0;
6051da177e4SLinus Torvalds icmp_send(skb, ICMP_PARAMETERPROB, 0, htonl(16<<24));
6061da177e4SLinus Torvalds return -EINVAL;
6071da177e4SLinus Torvalds }
6081da177e4SLinus Torvalds if (rt->rt_type != RTN_LOCAL)
6091da177e4SLinus Torvalds return -EINVAL;
6101da177e4SLinus Torvalds
6111da177e4SLinus Torvalds for (srrptr = optptr[2], srrspace = optptr[1]; srrptr <= srrspace; srrptr += 4) {
6121da177e4SLinus Torvalds if (srrptr + 3 > srrspace) {
6131da177e4SLinus Torvalds icmp_send(skb, ICMP_PARAMETERPROB, 0, htonl((opt->srr+2)<<24));
6141da177e4SLinus Torvalds return -EINVAL;
6151da177e4SLinus Torvalds }
6161da177e4SLinus Torvalds memcpy(&nexthop, &optptr[srrptr-1], 4);
6171da177e4SLinus Torvalds
6187fee226aSEric Dumazet orefdst = skb->_skb_refdst;
619adf30907SEric Dumazet skb_dst_set(skb, NULL);
6208c83f2dfSStephen Suryaputra err = ip_route_input(skb, nexthop, iph->saddr, iph->tos, dev);
621511c3f92SEric Dumazet rt2 = skb_rtable(skb);
6221da177e4SLinus Torvalds if (err || (rt2->rt_type != RTN_UNICAST && rt2->rt_type != RTN_LOCAL)) {
6237fee226aSEric Dumazet skb_dst_drop(skb);
6247fee226aSEric Dumazet skb->_skb_refdst = orefdst;
6251da177e4SLinus Torvalds return -EINVAL;
6261da177e4SLinus Torvalds }
6277fee226aSEric Dumazet refdst_drop(orefdst);
6281da177e4SLinus Torvalds if (rt2->rt_type != RTN_LOCAL)
6291da177e4SLinus Torvalds break;
6301da177e4SLinus Torvalds /* Superfast 8) loopback forward */
631c30883bdSDavid S. Miller iph->daddr = nexthop;
6321da177e4SLinus Torvalds opt->is_changed = 1;
6331da177e4SLinus Torvalds }
6341da177e4SLinus Torvalds if (srrptr <= srrspace) {
6351da177e4SLinus Torvalds opt->srr_is_hit = 1;
636ac8a4810SLi Wei opt->nexthop = nexthop;
6371da177e4SLinus Torvalds opt->is_changed = 1;
6381da177e4SLinus Torvalds }
6391da177e4SLinus Torvalds return 0;
6401da177e4SLinus Torvalds }
641462fb2afSBandan Das EXPORT_SYMBOL(ip_options_rcv_srr);
642