11da177e4SLinus Torvalds /* 21da177e4SLinus Torvalds * INET An implementation of the TCP/IP protocol suite for the LINUX 31da177e4SLinus Torvalds * operating system. INET is implemented using the BSD Socket 41da177e4SLinus Torvalds * interface as the means of communication with the user level. 51da177e4SLinus Torvalds * 61da177e4SLinus Torvalds * The options processing module for ip.c 71da177e4SLinus Torvalds * 81da177e4SLinus Torvalds * Version: $Id: ip_options.c,v 1.21 2001/09/01 00:31:50 davem Exp $ 91da177e4SLinus Torvalds * 101da177e4SLinus Torvalds * Authors: A.N.Kuznetsov 111da177e4SLinus Torvalds * 121da177e4SLinus Torvalds */ 131da177e4SLinus Torvalds 141da177e4SLinus Torvalds #include <linux/module.h> 151da177e4SLinus Torvalds #include <linux/types.h> 161da177e4SLinus Torvalds #include <asm/uaccess.h> 171da177e4SLinus Torvalds #include <linux/skbuff.h> 181da177e4SLinus Torvalds #include <linux/ip.h> 191da177e4SLinus Torvalds #include <linux/icmp.h> 201da177e4SLinus Torvalds #include <linux/netdevice.h> 211da177e4SLinus Torvalds #include <linux/rtnetlink.h> 221da177e4SLinus Torvalds #include <net/sock.h> 231da177e4SLinus Torvalds #include <net/ip.h> 241da177e4SLinus Torvalds #include <net/icmp.h> 25*14c85021SArnaldo Carvalho de Melo #include <net/route.h> 261da177e4SLinus Torvalds 271da177e4SLinus Torvalds /* 281da177e4SLinus Torvalds * Write options to IP header, record destination address to 291da177e4SLinus Torvalds * source route option, address of outgoing interface 301da177e4SLinus Torvalds * (we should already know it, so that this function is allowed be 311da177e4SLinus Torvalds * called only after routing decision) and timestamp, 321da177e4SLinus Torvalds * if we originate this datagram. 331da177e4SLinus Torvalds * 341da177e4SLinus Torvalds * daddr is real destination address, next hop is recorded in IP header. 351da177e4SLinus Torvalds * saddr is address of outgoing interface. 361da177e4SLinus Torvalds */ 371da177e4SLinus Torvalds 381da177e4SLinus Torvalds void ip_options_build(struct sk_buff * skb, struct ip_options * opt, 391da177e4SLinus Torvalds u32 daddr, struct rtable *rt, int is_frag) 401da177e4SLinus Torvalds { 411da177e4SLinus Torvalds unsigned char * iph = skb->nh.raw; 421da177e4SLinus Torvalds 431da177e4SLinus Torvalds memcpy(&(IPCB(skb)->opt), opt, sizeof(struct ip_options)); 441da177e4SLinus Torvalds memcpy(iph+sizeof(struct iphdr), opt->__data, opt->optlen); 451da177e4SLinus Torvalds opt = &(IPCB(skb)->opt); 461da177e4SLinus Torvalds opt->is_data = 0; 471da177e4SLinus Torvalds 481da177e4SLinus Torvalds if (opt->srr) 491da177e4SLinus Torvalds memcpy(iph+opt->srr+iph[opt->srr+1]-4, &daddr, 4); 501da177e4SLinus Torvalds 511da177e4SLinus Torvalds if (!is_frag) { 521da177e4SLinus Torvalds if (opt->rr_needaddr) 531da177e4SLinus Torvalds ip_rt_get_source(iph+opt->rr+iph[opt->rr+2]-5, rt); 541da177e4SLinus Torvalds if (opt->ts_needaddr) 551da177e4SLinus Torvalds ip_rt_get_source(iph+opt->ts+iph[opt->ts+2]-9, rt); 561da177e4SLinus Torvalds if (opt->ts_needtime) { 571da177e4SLinus Torvalds struct timeval tv; 581da177e4SLinus Torvalds __u32 midtime; 591da177e4SLinus Torvalds do_gettimeofday(&tv); 601da177e4SLinus Torvalds midtime = htonl((tv.tv_sec % 86400) * 1000 + tv.tv_usec / 1000); 611da177e4SLinus Torvalds memcpy(iph+opt->ts+iph[opt->ts+2]-5, &midtime, 4); 621da177e4SLinus Torvalds } 631da177e4SLinus Torvalds return; 641da177e4SLinus Torvalds } 651da177e4SLinus Torvalds if (opt->rr) { 661da177e4SLinus Torvalds memset(iph+opt->rr, IPOPT_NOP, iph[opt->rr+1]); 671da177e4SLinus Torvalds opt->rr = 0; 681da177e4SLinus Torvalds opt->rr_needaddr = 0; 691da177e4SLinus Torvalds } 701da177e4SLinus Torvalds if (opt->ts) { 711da177e4SLinus Torvalds memset(iph+opt->ts, IPOPT_NOP, iph[opt->ts+1]); 721da177e4SLinus Torvalds opt->ts = 0; 731da177e4SLinus Torvalds opt->ts_needaddr = opt->ts_needtime = 0; 741da177e4SLinus Torvalds } 751da177e4SLinus Torvalds } 761da177e4SLinus Torvalds 771da177e4SLinus Torvalds /* 781da177e4SLinus Torvalds * Provided (sopt, skb) points to received options, 791da177e4SLinus Torvalds * build in dopt compiled option set appropriate for answering. 801da177e4SLinus Torvalds * i.e. invert SRR option, copy anothers, 811da177e4SLinus Torvalds * and grab room in RR/TS options. 821da177e4SLinus Torvalds * 831da177e4SLinus Torvalds * NOTE: dopt cannot point to skb. 841da177e4SLinus Torvalds */ 851da177e4SLinus Torvalds 861da177e4SLinus Torvalds int ip_options_echo(struct ip_options * dopt, struct sk_buff * skb) 871da177e4SLinus Torvalds { 881da177e4SLinus Torvalds struct ip_options *sopt; 891da177e4SLinus Torvalds unsigned char *sptr, *dptr; 901da177e4SLinus Torvalds int soffset, doffset; 911da177e4SLinus Torvalds int optlen; 921da177e4SLinus Torvalds u32 daddr; 931da177e4SLinus Torvalds 941da177e4SLinus Torvalds memset(dopt, 0, sizeof(struct ip_options)); 951da177e4SLinus Torvalds 961da177e4SLinus Torvalds dopt->is_data = 1; 971da177e4SLinus Torvalds 981da177e4SLinus Torvalds sopt = &(IPCB(skb)->opt); 991da177e4SLinus Torvalds 1001da177e4SLinus Torvalds if (sopt->optlen == 0) { 1011da177e4SLinus Torvalds dopt->optlen = 0; 1021da177e4SLinus Torvalds return 0; 1031da177e4SLinus Torvalds } 1041da177e4SLinus Torvalds 1051da177e4SLinus Torvalds sptr = skb->nh.raw; 1061da177e4SLinus Torvalds dptr = dopt->__data; 1071da177e4SLinus Torvalds 1081da177e4SLinus Torvalds if (skb->dst) 1091da177e4SLinus Torvalds daddr = ((struct rtable*)skb->dst)->rt_spec_dst; 1101da177e4SLinus Torvalds else 1111da177e4SLinus Torvalds daddr = skb->nh.iph->daddr; 1121da177e4SLinus Torvalds 1131da177e4SLinus Torvalds if (sopt->rr) { 1141da177e4SLinus Torvalds optlen = sptr[sopt->rr+1]; 1151da177e4SLinus Torvalds soffset = sptr[sopt->rr+2]; 1161da177e4SLinus Torvalds dopt->rr = dopt->optlen + sizeof(struct iphdr); 1171da177e4SLinus Torvalds memcpy(dptr, sptr+sopt->rr, optlen); 1181da177e4SLinus Torvalds if (sopt->rr_needaddr && soffset <= optlen) { 1191da177e4SLinus Torvalds if (soffset + 3 > optlen) 1201da177e4SLinus Torvalds return -EINVAL; 1211da177e4SLinus Torvalds dptr[2] = soffset + 4; 1221da177e4SLinus Torvalds dopt->rr_needaddr = 1; 1231da177e4SLinus Torvalds } 1241da177e4SLinus Torvalds dptr += optlen; 1251da177e4SLinus Torvalds dopt->optlen += optlen; 1261da177e4SLinus Torvalds } 1271da177e4SLinus Torvalds if (sopt->ts) { 1281da177e4SLinus Torvalds optlen = sptr[sopt->ts+1]; 1291da177e4SLinus Torvalds soffset = sptr[sopt->ts+2]; 1301da177e4SLinus Torvalds dopt->ts = dopt->optlen + sizeof(struct iphdr); 1311da177e4SLinus Torvalds memcpy(dptr, sptr+sopt->ts, optlen); 1321da177e4SLinus Torvalds if (soffset <= optlen) { 1331da177e4SLinus Torvalds if (sopt->ts_needaddr) { 1341da177e4SLinus Torvalds if (soffset + 3 > optlen) 1351da177e4SLinus Torvalds return -EINVAL; 1361da177e4SLinus Torvalds dopt->ts_needaddr = 1; 1371da177e4SLinus Torvalds soffset += 4; 1381da177e4SLinus Torvalds } 1391da177e4SLinus Torvalds if (sopt->ts_needtime) { 1401da177e4SLinus Torvalds if (soffset + 3 > optlen) 1411da177e4SLinus Torvalds return -EINVAL; 1421da177e4SLinus Torvalds if ((dptr[3]&0xF) != IPOPT_TS_PRESPEC) { 1431da177e4SLinus Torvalds dopt->ts_needtime = 1; 1441da177e4SLinus Torvalds soffset += 4; 1451da177e4SLinus Torvalds } else { 1461da177e4SLinus Torvalds dopt->ts_needtime = 0; 1471da177e4SLinus Torvalds 1481da177e4SLinus Torvalds if (soffset + 8 <= optlen) { 1491da177e4SLinus Torvalds __u32 addr; 1501da177e4SLinus Torvalds 1511da177e4SLinus Torvalds memcpy(&addr, sptr+soffset-1, 4); 1521da177e4SLinus Torvalds if (inet_addr_type(addr) != RTN_LOCAL) { 1531da177e4SLinus Torvalds dopt->ts_needtime = 1; 1541da177e4SLinus Torvalds soffset += 8; 1551da177e4SLinus Torvalds } 1561da177e4SLinus Torvalds } 1571da177e4SLinus Torvalds } 1581da177e4SLinus Torvalds } 1591da177e4SLinus Torvalds dptr[2] = soffset; 1601da177e4SLinus Torvalds } 1611da177e4SLinus Torvalds dptr += optlen; 1621da177e4SLinus Torvalds dopt->optlen += optlen; 1631da177e4SLinus Torvalds } 1641da177e4SLinus Torvalds if (sopt->srr) { 1651da177e4SLinus Torvalds unsigned char * start = sptr+sopt->srr; 1661da177e4SLinus Torvalds u32 faddr; 1671da177e4SLinus Torvalds 1681da177e4SLinus Torvalds optlen = start[1]; 1691da177e4SLinus Torvalds soffset = start[2]; 1701da177e4SLinus Torvalds doffset = 0; 1711da177e4SLinus Torvalds if (soffset > optlen) 1721da177e4SLinus Torvalds soffset = optlen + 1; 1731da177e4SLinus Torvalds soffset -= 4; 1741da177e4SLinus Torvalds if (soffset > 3) { 1751da177e4SLinus Torvalds memcpy(&faddr, &start[soffset-1], 4); 1761da177e4SLinus Torvalds for (soffset-=4, doffset=4; soffset > 3; soffset-=4, doffset+=4) 1771da177e4SLinus Torvalds memcpy(&dptr[doffset-1], &start[soffset-1], 4); 1781da177e4SLinus Torvalds /* 1791da177e4SLinus Torvalds * RFC1812 requires to fix illegal source routes. 1801da177e4SLinus Torvalds */ 1811da177e4SLinus Torvalds if (memcmp(&skb->nh.iph->saddr, &start[soffset+3], 4) == 0) 1821da177e4SLinus Torvalds doffset -= 4; 1831da177e4SLinus Torvalds } 1841da177e4SLinus Torvalds if (doffset > 3) { 1851da177e4SLinus Torvalds memcpy(&start[doffset-1], &daddr, 4); 1861da177e4SLinus Torvalds dopt->faddr = faddr; 1871da177e4SLinus Torvalds dptr[0] = start[0]; 1881da177e4SLinus Torvalds dptr[1] = doffset+3; 1891da177e4SLinus Torvalds dptr[2] = 4; 1901da177e4SLinus Torvalds dptr += doffset+3; 1911da177e4SLinus Torvalds dopt->srr = dopt->optlen + sizeof(struct iphdr); 1921da177e4SLinus Torvalds dopt->optlen += doffset+3; 1931da177e4SLinus Torvalds dopt->is_strictroute = sopt->is_strictroute; 1941da177e4SLinus Torvalds } 1951da177e4SLinus Torvalds } 1961da177e4SLinus Torvalds while (dopt->optlen & 3) { 1971da177e4SLinus Torvalds *dptr++ = IPOPT_END; 1981da177e4SLinus Torvalds dopt->optlen++; 1991da177e4SLinus Torvalds } 2001da177e4SLinus Torvalds return 0; 2011da177e4SLinus Torvalds } 2021da177e4SLinus Torvalds 2031da177e4SLinus Torvalds /* 2041da177e4SLinus Torvalds * Options "fragmenting", just fill options not 2051da177e4SLinus Torvalds * allowed in fragments with NOOPs. 2061da177e4SLinus Torvalds * Simple and stupid 8), but the most efficient way. 2071da177e4SLinus Torvalds */ 2081da177e4SLinus Torvalds 2091da177e4SLinus Torvalds void ip_options_fragment(struct sk_buff * skb) 2101da177e4SLinus Torvalds { 2111da177e4SLinus Torvalds unsigned char * optptr = skb->nh.raw; 2121da177e4SLinus Torvalds struct ip_options * opt = &(IPCB(skb)->opt); 2131da177e4SLinus Torvalds int l = opt->optlen; 2141da177e4SLinus Torvalds int optlen; 2151da177e4SLinus Torvalds 2161da177e4SLinus Torvalds while (l > 0) { 2171da177e4SLinus Torvalds switch (*optptr) { 2181da177e4SLinus Torvalds case IPOPT_END: 2191da177e4SLinus Torvalds return; 2201da177e4SLinus Torvalds case IPOPT_NOOP: 2211da177e4SLinus Torvalds l--; 2221da177e4SLinus Torvalds optptr++; 2231da177e4SLinus Torvalds continue; 2241da177e4SLinus Torvalds } 2251da177e4SLinus Torvalds optlen = optptr[1]; 2261da177e4SLinus Torvalds if (optlen<2 || optlen>l) 2271da177e4SLinus Torvalds return; 2281da177e4SLinus Torvalds if (!IPOPT_COPIED(*optptr)) 2291da177e4SLinus Torvalds memset(optptr, IPOPT_NOOP, optlen); 2301da177e4SLinus Torvalds l -= optlen; 2311da177e4SLinus Torvalds optptr += optlen; 2321da177e4SLinus Torvalds } 2331da177e4SLinus Torvalds opt->ts = 0; 2341da177e4SLinus Torvalds opt->rr = 0; 2351da177e4SLinus Torvalds opt->rr_needaddr = 0; 2361da177e4SLinus Torvalds opt->ts_needaddr = 0; 2371da177e4SLinus Torvalds opt->ts_needtime = 0; 2381da177e4SLinus Torvalds return; 2391da177e4SLinus Torvalds } 2401da177e4SLinus Torvalds 2411da177e4SLinus Torvalds /* 2421da177e4SLinus Torvalds * Verify options and fill pointers in struct options. 2431da177e4SLinus Torvalds * Caller should clear *opt, and set opt->data. 2441da177e4SLinus Torvalds * If opt == NULL, then skb->data should point to IP header. 2451da177e4SLinus Torvalds */ 2461da177e4SLinus Torvalds 2471da177e4SLinus Torvalds int ip_options_compile(struct ip_options * opt, struct sk_buff * skb) 2481da177e4SLinus Torvalds { 2491da177e4SLinus Torvalds int l; 2501da177e4SLinus Torvalds unsigned char * iph; 2511da177e4SLinus Torvalds unsigned char * optptr; 2521da177e4SLinus Torvalds int optlen; 2531da177e4SLinus Torvalds unsigned char * pp_ptr = NULL; 2541da177e4SLinus Torvalds struct rtable *rt = skb ? (struct rtable*)skb->dst : NULL; 2551da177e4SLinus Torvalds 2561da177e4SLinus Torvalds if (!opt) { 2571da177e4SLinus Torvalds opt = &(IPCB(skb)->opt); 2581da177e4SLinus Torvalds memset(opt, 0, sizeof(struct ip_options)); 2591da177e4SLinus Torvalds iph = skb->nh.raw; 2601da177e4SLinus Torvalds opt->optlen = ((struct iphdr *)iph)->ihl*4 - sizeof(struct iphdr); 2611da177e4SLinus Torvalds optptr = iph + sizeof(struct iphdr); 2621da177e4SLinus Torvalds opt->is_data = 0; 2631da177e4SLinus Torvalds } else { 2641da177e4SLinus Torvalds optptr = opt->is_data ? opt->__data : (unsigned char*)&(skb->nh.iph[1]); 2651da177e4SLinus Torvalds iph = optptr - sizeof(struct iphdr); 2661da177e4SLinus Torvalds } 2671da177e4SLinus Torvalds 2681da177e4SLinus Torvalds for (l = opt->optlen; l > 0; ) { 2691da177e4SLinus Torvalds switch (*optptr) { 2701da177e4SLinus Torvalds case IPOPT_END: 2711da177e4SLinus Torvalds for (optptr++, l--; l>0; optptr++, l--) { 2721da177e4SLinus Torvalds if (*optptr != IPOPT_END) { 2731da177e4SLinus Torvalds *optptr = IPOPT_END; 2741da177e4SLinus Torvalds opt->is_changed = 1; 2751da177e4SLinus Torvalds } 2761da177e4SLinus Torvalds } 2771da177e4SLinus Torvalds goto eol; 2781da177e4SLinus Torvalds case IPOPT_NOOP: 2791da177e4SLinus Torvalds l--; 2801da177e4SLinus Torvalds optptr++; 2811da177e4SLinus Torvalds continue; 2821da177e4SLinus Torvalds } 2831da177e4SLinus Torvalds optlen = optptr[1]; 2841da177e4SLinus Torvalds if (optlen<2 || optlen>l) { 2851da177e4SLinus Torvalds pp_ptr = optptr; 2861da177e4SLinus Torvalds goto error; 2871da177e4SLinus Torvalds } 2881da177e4SLinus Torvalds switch (*optptr) { 2891da177e4SLinus Torvalds case IPOPT_SSRR: 2901da177e4SLinus Torvalds case IPOPT_LSRR: 2911da177e4SLinus Torvalds if (optlen < 3) { 2921da177e4SLinus Torvalds pp_ptr = optptr + 1; 2931da177e4SLinus Torvalds goto error; 2941da177e4SLinus Torvalds } 2951da177e4SLinus Torvalds if (optptr[2] < 4) { 2961da177e4SLinus Torvalds pp_ptr = optptr + 2; 2971da177e4SLinus Torvalds goto error; 2981da177e4SLinus Torvalds } 2991da177e4SLinus Torvalds /* NB: cf RFC-1812 5.2.4.1 */ 3001da177e4SLinus Torvalds if (opt->srr) { 3011da177e4SLinus Torvalds pp_ptr = optptr; 3021da177e4SLinus Torvalds goto error; 3031da177e4SLinus Torvalds } 3041da177e4SLinus Torvalds if (!skb) { 3051da177e4SLinus Torvalds if (optptr[2] != 4 || optlen < 7 || ((optlen-3) & 3)) { 3061da177e4SLinus Torvalds pp_ptr = optptr + 1; 3071da177e4SLinus Torvalds goto error; 3081da177e4SLinus Torvalds } 3091da177e4SLinus Torvalds memcpy(&opt->faddr, &optptr[3], 4); 3101da177e4SLinus Torvalds if (optlen > 7) 3111da177e4SLinus Torvalds memmove(&optptr[3], &optptr[7], optlen-7); 3121da177e4SLinus Torvalds } 3131da177e4SLinus Torvalds opt->is_strictroute = (optptr[0] == IPOPT_SSRR); 3141da177e4SLinus Torvalds opt->srr = optptr - iph; 3151da177e4SLinus Torvalds break; 3161da177e4SLinus Torvalds case IPOPT_RR: 3171da177e4SLinus Torvalds if (opt->rr) { 3181da177e4SLinus Torvalds pp_ptr = optptr; 3191da177e4SLinus Torvalds goto error; 3201da177e4SLinus Torvalds } 3211da177e4SLinus Torvalds if (optlen < 3) { 3221da177e4SLinus Torvalds pp_ptr = optptr + 1; 3231da177e4SLinus Torvalds goto error; 3241da177e4SLinus Torvalds } 3251da177e4SLinus Torvalds if (optptr[2] < 4) { 3261da177e4SLinus Torvalds pp_ptr = optptr + 2; 3271da177e4SLinus Torvalds goto error; 3281da177e4SLinus Torvalds } 3291da177e4SLinus Torvalds if (optptr[2] <= optlen) { 3301da177e4SLinus Torvalds if (optptr[2]+3 > optlen) { 3311da177e4SLinus Torvalds pp_ptr = optptr + 2; 3321da177e4SLinus Torvalds goto error; 3331da177e4SLinus Torvalds } 3341da177e4SLinus Torvalds if (skb) { 3351da177e4SLinus Torvalds memcpy(&optptr[optptr[2]-1], &rt->rt_spec_dst, 4); 3361da177e4SLinus Torvalds opt->is_changed = 1; 3371da177e4SLinus Torvalds } 3381da177e4SLinus Torvalds optptr[2] += 4; 3391da177e4SLinus Torvalds opt->rr_needaddr = 1; 3401da177e4SLinus Torvalds } 3411da177e4SLinus Torvalds opt->rr = optptr - iph; 3421da177e4SLinus Torvalds break; 3431da177e4SLinus Torvalds case IPOPT_TIMESTAMP: 3441da177e4SLinus Torvalds if (opt->ts) { 3451da177e4SLinus Torvalds pp_ptr = optptr; 3461da177e4SLinus Torvalds goto error; 3471da177e4SLinus Torvalds } 3481da177e4SLinus Torvalds if (optlen < 4) { 3491da177e4SLinus Torvalds pp_ptr = optptr + 1; 3501da177e4SLinus Torvalds goto error; 3511da177e4SLinus Torvalds } 3521da177e4SLinus Torvalds if (optptr[2] < 5) { 3531da177e4SLinus Torvalds pp_ptr = optptr + 2; 3541da177e4SLinus Torvalds goto error; 3551da177e4SLinus Torvalds } 3561da177e4SLinus Torvalds if (optptr[2] <= optlen) { 3571da177e4SLinus Torvalds __u32 * timeptr = NULL; 3581da177e4SLinus Torvalds if (optptr[2]+3 > optptr[1]) { 3591da177e4SLinus Torvalds pp_ptr = optptr + 2; 3601da177e4SLinus Torvalds goto error; 3611da177e4SLinus Torvalds } 3621da177e4SLinus Torvalds switch (optptr[3]&0xF) { 3631da177e4SLinus Torvalds case IPOPT_TS_TSONLY: 3641da177e4SLinus Torvalds opt->ts = optptr - iph; 3651da177e4SLinus Torvalds if (skb) 3661da177e4SLinus Torvalds timeptr = (__u32*)&optptr[optptr[2]-1]; 3671da177e4SLinus Torvalds opt->ts_needtime = 1; 3681da177e4SLinus Torvalds optptr[2] += 4; 3691da177e4SLinus Torvalds break; 3701da177e4SLinus Torvalds case IPOPT_TS_TSANDADDR: 3711da177e4SLinus Torvalds if (optptr[2]+7 > optptr[1]) { 3721da177e4SLinus Torvalds pp_ptr = optptr + 2; 3731da177e4SLinus Torvalds goto error; 3741da177e4SLinus Torvalds } 3751da177e4SLinus Torvalds opt->ts = optptr - iph; 3761da177e4SLinus Torvalds if (skb) { 3771da177e4SLinus Torvalds memcpy(&optptr[optptr[2]-1], &rt->rt_spec_dst, 4); 3781da177e4SLinus Torvalds timeptr = (__u32*)&optptr[optptr[2]+3]; 3791da177e4SLinus Torvalds } 3801da177e4SLinus Torvalds opt->ts_needaddr = 1; 3811da177e4SLinus Torvalds opt->ts_needtime = 1; 3821da177e4SLinus Torvalds optptr[2] += 8; 3831da177e4SLinus Torvalds break; 3841da177e4SLinus Torvalds case IPOPT_TS_PRESPEC: 3851da177e4SLinus Torvalds if (optptr[2]+7 > optptr[1]) { 3861da177e4SLinus Torvalds pp_ptr = optptr + 2; 3871da177e4SLinus Torvalds goto error; 3881da177e4SLinus Torvalds } 3891da177e4SLinus Torvalds opt->ts = optptr - iph; 3901da177e4SLinus Torvalds { 3911da177e4SLinus Torvalds u32 addr; 3921da177e4SLinus Torvalds memcpy(&addr, &optptr[optptr[2]-1], 4); 3931da177e4SLinus Torvalds if (inet_addr_type(addr) == RTN_UNICAST) 3941da177e4SLinus Torvalds break; 3951da177e4SLinus Torvalds if (skb) 3961da177e4SLinus Torvalds timeptr = (__u32*)&optptr[optptr[2]+3]; 3971da177e4SLinus Torvalds } 3981da177e4SLinus Torvalds opt->ts_needtime = 1; 3991da177e4SLinus Torvalds optptr[2] += 8; 4001da177e4SLinus Torvalds break; 4011da177e4SLinus Torvalds default: 4021da177e4SLinus Torvalds if (!skb && !capable(CAP_NET_RAW)) { 4031da177e4SLinus Torvalds pp_ptr = optptr + 3; 4041da177e4SLinus Torvalds goto error; 4051da177e4SLinus Torvalds } 4061da177e4SLinus Torvalds break; 4071da177e4SLinus Torvalds } 4081da177e4SLinus Torvalds if (timeptr) { 4091da177e4SLinus Torvalds struct timeval tv; 4101da177e4SLinus Torvalds __u32 midtime; 4111da177e4SLinus Torvalds do_gettimeofday(&tv); 4121da177e4SLinus Torvalds midtime = htonl((tv.tv_sec % 86400) * 1000 + tv.tv_usec / 1000); 4131da177e4SLinus Torvalds memcpy(timeptr, &midtime, sizeof(__u32)); 4141da177e4SLinus Torvalds opt->is_changed = 1; 4151da177e4SLinus Torvalds } 4161da177e4SLinus Torvalds } else { 4171da177e4SLinus Torvalds unsigned overflow = optptr[3]>>4; 4181da177e4SLinus Torvalds if (overflow == 15) { 4191da177e4SLinus Torvalds pp_ptr = optptr + 3; 4201da177e4SLinus Torvalds goto error; 4211da177e4SLinus Torvalds } 4221da177e4SLinus Torvalds opt->ts = optptr - iph; 4231da177e4SLinus Torvalds if (skb) { 4241da177e4SLinus Torvalds optptr[3] = (optptr[3]&0xF)|((overflow+1)<<4); 4251da177e4SLinus Torvalds opt->is_changed = 1; 4261da177e4SLinus Torvalds } 4271da177e4SLinus Torvalds } 4281da177e4SLinus Torvalds break; 4291da177e4SLinus Torvalds case IPOPT_RA: 4301da177e4SLinus Torvalds if (optlen < 4) { 4311da177e4SLinus Torvalds pp_ptr = optptr + 1; 4321da177e4SLinus Torvalds goto error; 4331da177e4SLinus Torvalds } 4341da177e4SLinus Torvalds if (optptr[2] == 0 && optptr[3] == 0) 4351da177e4SLinus Torvalds opt->router_alert = optptr - iph; 4361da177e4SLinus Torvalds break; 4371da177e4SLinus Torvalds case IPOPT_SEC: 4381da177e4SLinus Torvalds case IPOPT_SID: 4391da177e4SLinus Torvalds default: 4401da177e4SLinus Torvalds if (!skb && !capable(CAP_NET_RAW)) { 4411da177e4SLinus Torvalds pp_ptr = optptr; 4421da177e4SLinus Torvalds goto error; 4431da177e4SLinus Torvalds } 4441da177e4SLinus Torvalds break; 4451da177e4SLinus Torvalds } 4461da177e4SLinus Torvalds l -= optlen; 4471da177e4SLinus Torvalds optptr += optlen; 4481da177e4SLinus Torvalds } 4491da177e4SLinus Torvalds 4501da177e4SLinus Torvalds eol: 4511da177e4SLinus Torvalds if (!pp_ptr) 4521da177e4SLinus Torvalds return 0; 4531da177e4SLinus Torvalds 4541da177e4SLinus Torvalds error: 4551da177e4SLinus Torvalds if (skb) { 4561da177e4SLinus Torvalds icmp_send(skb, ICMP_PARAMETERPROB, 0, htonl((pp_ptr-iph)<<24)); 4571da177e4SLinus Torvalds } 4581da177e4SLinus Torvalds return -EINVAL; 4591da177e4SLinus Torvalds } 4601da177e4SLinus Torvalds 4611da177e4SLinus Torvalds 4621da177e4SLinus Torvalds /* 4631da177e4SLinus Torvalds * Undo all the changes done by ip_options_compile(). 4641da177e4SLinus Torvalds */ 4651da177e4SLinus Torvalds 4661da177e4SLinus Torvalds void ip_options_undo(struct ip_options * opt) 4671da177e4SLinus Torvalds { 4681da177e4SLinus Torvalds if (opt->srr) { 4691da177e4SLinus Torvalds unsigned char * optptr = opt->__data+opt->srr-sizeof(struct iphdr); 4701da177e4SLinus Torvalds memmove(optptr+7, optptr+3, optptr[1]-7); 4711da177e4SLinus Torvalds memcpy(optptr+3, &opt->faddr, 4); 4721da177e4SLinus Torvalds } 4731da177e4SLinus Torvalds if (opt->rr_needaddr) { 4741da177e4SLinus Torvalds unsigned char * optptr = opt->__data+opt->rr-sizeof(struct iphdr); 4751da177e4SLinus Torvalds optptr[2] -= 4; 4761da177e4SLinus Torvalds memset(&optptr[optptr[2]-1], 0, 4); 4771da177e4SLinus Torvalds } 4781da177e4SLinus Torvalds if (opt->ts) { 4791da177e4SLinus Torvalds unsigned char * optptr = opt->__data+opt->ts-sizeof(struct iphdr); 4801da177e4SLinus Torvalds if (opt->ts_needtime) { 4811da177e4SLinus Torvalds optptr[2] -= 4; 4821da177e4SLinus Torvalds memset(&optptr[optptr[2]-1], 0, 4); 4831da177e4SLinus Torvalds if ((optptr[3]&0xF) == IPOPT_TS_PRESPEC) 4841da177e4SLinus Torvalds optptr[2] -= 4; 4851da177e4SLinus Torvalds } 4861da177e4SLinus Torvalds if (opt->ts_needaddr) { 4871da177e4SLinus Torvalds optptr[2] -= 4; 4881da177e4SLinus Torvalds memset(&optptr[optptr[2]-1], 0, 4); 4891da177e4SLinus Torvalds } 4901da177e4SLinus Torvalds } 4911da177e4SLinus Torvalds } 4921da177e4SLinus Torvalds 4934c6ea29dSArnaldo Carvalho de Melo static struct ip_options *ip_options_get_alloc(const int optlen) 4941da177e4SLinus Torvalds { 4954c6ea29dSArnaldo Carvalho de Melo struct ip_options *opt = kmalloc(sizeof(*opt) + ((optlen + 3) & ~3), 4964c6ea29dSArnaldo Carvalho de Melo GFP_KERNEL); 4974c6ea29dSArnaldo Carvalho de Melo if (opt) 4984c6ea29dSArnaldo Carvalho de Melo memset(opt, 0, sizeof(*opt)); 4994c6ea29dSArnaldo Carvalho de Melo return opt; 5004c6ea29dSArnaldo Carvalho de Melo } 5011da177e4SLinus Torvalds 5024c6ea29dSArnaldo Carvalho de Melo static int ip_options_get_finish(struct ip_options **optp, 5034c6ea29dSArnaldo Carvalho de Melo struct ip_options *opt, int optlen) 5044c6ea29dSArnaldo Carvalho de Melo { 5051da177e4SLinus Torvalds while (optlen & 3) 5061da177e4SLinus Torvalds opt->__data[optlen++] = IPOPT_END; 5071da177e4SLinus Torvalds opt->optlen = optlen; 5081da177e4SLinus Torvalds opt->is_data = 1; 5091da177e4SLinus Torvalds opt->is_setbyuser = 1; 5101da177e4SLinus Torvalds if (optlen && ip_options_compile(opt, NULL)) { 5111da177e4SLinus Torvalds kfree(opt); 5121da177e4SLinus Torvalds return -EINVAL; 5131da177e4SLinus Torvalds } 5141da177e4SLinus Torvalds kfree(*optp); 5151da177e4SLinus Torvalds *optp = opt; 5161da177e4SLinus Torvalds return 0; 5171da177e4SLinus Torvalds } 5181da177e4SLinus Torvalds 5194c6ea29dSArnaldo Carvalho de Melo int ip_options_get_from_user(struct ip_options **optp, unsigned char __user *data, int optlen) 5204c6ea29dSArnaldo Carvalho de Melo { 5214c6ea29dSArnaldo Carvalho de Melo struct ip_options *opt = ip_options_get_alloc(optlen); 5224c6ea29dSArnaldo Carvalho de Melo 5234c6ea29dSArnaldo Carvalho de Melo if (!opt) 5244c6ea29dSArnaldo Carvalho de Melo return -ENOMEM; 5254c6ea29dSArnaldo Carvalho de Melo if (optlen && copy_from_user(opt->__data, data, optlen)) { 5264c6ea29dSArnaldo Carvalho de Melo kfree(opt); 5274c6ea29dSArnaldo Carvalho de Melo return -EFAULT; 5284c6ea29dSArnaldo Carvalho de Melo } 5294c6ea29dSArnaldo Carvalho de Melo return ip_options_get_finish(optp, opt, optlen); 5304c6ea29dSArnaldo Carvalho de Melo } 5314c6ea29dSArnaldo Carvalho de Melo 5324c6ea29dSArnaldo Carvalho de Melo int ip_options_get(struct ip_options **optp, unsigned char *data, int optlen) 5334c6ea29dSArnaldo Carvalho de Melo { 5344c6ea29dSArnaldo Carvalho de Melo struct ip_options *opt = ip_options_get_alloc(optlen); 5354c6ea29dSArnaldo Carvalho de Melo 5364c6ea29dSArnaldo Carvalho de Melo if (!opt) 5374c6ea29dSArnaldo Carvalho de Melo return -ENOMEM; 5384c6ea29dSArnaldo Carvalho de Melo if (optlen) 5394c6ea29dSArnaldo Carvalho de Melo memcpy(opt->__data, data, optlen); 5404c6ea29dSArnaldo Carvalho de Melo return ip_options_get_finish(optp, opt, optlen); 5414c6ea29dSArnaldo Carvalho de Melo } 5424c6ea29dSArnaldo Carvalho de Melo 5431da177e4SLinus Torvalds void ip_forward_options(struct sk_buff *skb) 5441da177e4SLinus Torvalds { 5451da177e4SLinus Torvalds struct ip_options * opt = &(IPCB(skb)->opt); 5461da177e4SLinus Torvalds unsigned char * optptr; 5471da177e4SLinus Torvalds struct rtable *rt = (struct rtable*)skb->dst; 5481da177e4SLinus Torvalds unsigned char *raw = skb->nh.raw; 5491da177e4SLinus Torvalds 5501da177e4SLinus Torvalds if (opt->rr_needaddr) { 5511da177e4SLinus Torvalds optptr = (unsigned char *)raw + opt->rr; 5521da177e4SLinus Torvalds ip_rt_get_source(&optptr[optptr[2]-5], rt); 5531da177e4SLinus Torvalds opt->is_changed = 1; 5541da177e4SLinus Torvalds } 5551da177e4SLinus Torvalds if (opt->srr_is_hit) { 5561da177e4SLinus Torvalds int srrptr, srrspace; 5571da177e4SLinus Torvalds 5581da177e4SLinus Torvalds optptr = raw + opt->srr; 5591da177e4SLinus Torvalds 5601da177e4SLinus Torvalds for ( srrptr=optptr[2], srrspace = optptr[1]; 5611da177e4SLinus Torvalds srrptr <= srrspace; 5621da177e4SLinus Torvalds srrptr += 4 5631da177e4SLinus Torvalds ) { 5641da177e4SLinus Torvalds if (srrptr + 3 > srrspace) 5651da177e4SLinus Torvalds break; 5661da177e4SLinus Torvalds if (memcmp(&rt->rt_dst, &optptr[srrptr-1], 4) == 0) 5671da177e4SLinus Torvalds break; 5681da177e4SLinus Torvalds } 5691da177e4SLinus Torvalds if (srrptr + 3 <= srrspace) { 5701da177e4SLinus Torvalds opt->is_changed = 1; 5711da177e4SLinus Torvalds ip_rt_get_source(&optptr[srrptr-1], rt); 5721da177e4SLinus Torvalds skb->nh.iph->daddr = rt->rt_dst; 5731da177e4SLinus Torvalds optptr[2] = srrptr+4; 5741da177e4SLinus Torvalds } else if (net_ratelimit()) 5751da177e4SLinus Torvalds printk(KERN_CRIT "ip_forward(): Argh! Destination lost!\n"); 5761da177e4SLinus Torvalds if (opt->ts_needaddr) { 5771da177e4SLinus Torvalds optptr = raw + opt->ts; 5781da177e4SLinus Torvalds ip_rt_get_source(&optptr[optptr[2]-9], rt); 5791da177e4SLinus Torvalds opt->is_changed = 1; 5801da177e4SLinus Torvalds } 5811da177e4SLinus Torvalds } 5821da177e4SLinus Torvalds if (opt->is_changed) { 5831da177e4SLinus Torvalds opt->is_changed = 0; 5841da177e4SLinus Torvalds ip_send_check(skb->nh.iph); 5851da177e4SLinus Torvalds } 5861da177e4SLinus Torvalds } 5871da177e4SLinus Torvalds 5881da177e4SLinus Torvalds int ip_options_rcv_srr(struct sk_buff *skb) 5891da177e4SLinus Torvalds { 5901da177e4SLinus Torvalds struct ip_options *opt = &(IPCB(skb)->opt); 5911da177e4SLinus Torvalds int srrspace, srrptr; 5921da177e4SLinus Torvalds u32 nexthop; 5931da177e4SLinus Torvalds struct iphdr *iph = skb->nh.iph; 5941da177e4SLinus Torvalds unsigned char * optptr = skb->nh.raw + opt->srr; 5951da177e4SLinus Torvalds struct rtable *rt = (struct rtable*)skb->dst; 5961da177e4SLinus Torvalds struct rtable *rt2; 5971da177e4SLinus Torvalds int err; 5981da177e4SLinus Torvalds 5991da177e4SLinus Torvalds if (!opt->srr) 6001da177e4SLinus Torvalds return 0; 6011da177e4SLinus Torvalds 6021da177e4SLinus Torvalds if (skb->pkt_type != PACKET_HOST) 6031da177e4SLinus Torvalds return -EINVAL; 6041da177e4SLinus Torvalds if (rt->rt_type == RTN_UNICAST) { 6051da177e4SLinus Torvalds if (!opt->is_strictroute) 6061da177e4SLinus Torvalds return 0; 6071da177e4SLinus Torvalds icmp_send(skb, ICMP_PARAMETERPROB, 0, htonl(16<<24)); 6081da177e4SLinus Torvalds return -EINVAL; 6091da177e4SLinus Torvalds } 6101da177e4SLinus Torvalds if (rt->rt_type != RTN_LOCAL) 6111da177e4SLinus Torvalds return -EINVAL; 6121da177e4SLinus Torvalds 6131da177e4SLinus Torvalds for (srrptr=optptr[2], srrspace = optptr[1]; srrptr <= srrspace; srrptr += 4) { 6141da177e4SLinus Torvalds if (srrptr + 3 > srrspace) { 6151da177e4SLinus Torvalds icmp_send(skb, ICMP_PARAMETERPROB, 0, htonl((opt->srr+2)<<24)); 6161da177e4SLinus Torvalds return -EINVAL; 6171da177e4SLinus Torvalds } 6181da177e4SLinus Torvalds memcpy(&nexthop, &optptr[srrptr-1], 4); 6191da177e4SLinus Torvalds 6201da177e4SLinus Torvalds rt = (struct rtable*)skb->dst; 6211da177e4SLinus Torvalds skb->dst = NULL; 6221da177e4SLinus Torvalds err = ip_route_input(skb, nexthop, iph->saddr, iph->tos, skb->dev); 6231da177e4SLinus Torvalds rt2 = (struct rtable*)skb->dst; 6241da177e4SLinus Torvalds if (err || (rt2->rt_type != RTN_UNICAST && rt2->rt_type != RTN_LOCAL)) { 6251da177e4SLinus Torvalds ip_rt_put(rt2); 6261da177e4SLinus Torvalds skb->dst = &rt->u.dst; 6271da177e4SLinus Torvalds return -EINVAL; 6281da177e4SLinus Torvalds } 6291da177e4SLinus Torvalds ip_rt_put(rt); 6301da177e4SLinus Torvalds if (rt2->rt_type != RTN_LOCAL) 6311da177e4SLinus Torvalds break; 6321da177e4SLinus Torvalds /* Superfast 8) loopback forward */ 6331da177e4SLinus Torvalds memcpy(&iph->daddr, &optptr[srrptr-1], 4); 6341da177e4SLinus Torvalds opt->is_changed = 1; 6351da177e4SLinus Torvalds } 6361da177e4SLinus Torvalds if (srrptr <= srrspace) { 6371da177e4SLinus Torvalds opt->srr_is_hit = 1; 6381da177e4SLinus Torvalds opt->is_changed = 1; 6391da177e4SLinus Torvalds } 6401da177e4SLinus Torvalds return 0; 6411da177e4SLinus Torvalds } 642