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