1 /* 2 * Copyright (c) 1987, 1989 Regents of the University of California. 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms are permitted 6 * provided that the above copyright notice and this paragraph are 7 * duplicated in all such forms and that any documentation, 8 * advertising materials, and other materials related to such 9 * distribution and use acknowledge that the software was developed 10 * by the University of California, Berkeley. The name of the 11 * University may not be used to endorse or promote products derived 12 * from this software without specific prior written permission. 13 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR 14 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED 15 * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. 16 * 17 * @(#)if_sl.c 7.20 (Berkeley) 04/05/90 18 */ 19 20 /* 21 * Serial Line interface 22 * 23 * Rick Adams 24 * Center for Seismic Studies 25 * 1300 N 17th Street, Suite 1450 26 * Arlington, Virginia 22209 27 * (703)276-7900 28 * rick@seismo.ARPA 29 * seismo!rick 30 * 31 * Pounded on heavily by Chris Torek (chris@mimsy.umd.edu, umcp-cs!chris). 32 * N.B.: this belongs in netinet, not net, the way it stands now. 33 * Should have a link-layer type designation, but wouldn't be 34 * backwards-compatible. 35 * 36 * Converted to 4.3BSD Beta by Chris Torek. 37 * Other changes made at Berkeley, based in part on code by Kirk Smith. 38 * W. Jolitz added slip abort. 39 * 40 * Hacked almost beyond recognition by Van Jacobson (van@helios.ee.lbl.gov). 41 * Added priority queuing for "interactive" traffic; hooks for TCP 42 * header compression; ICMP filtering (at 2400 baud, some cretin 43 * pinging you can use up all your bandwidth). Made low clist behavior 44 * more robust and slightly less likely to hang serial line. 45 * Sped up a bunch of things. 46 * 47 * Note that splimp() is used throughout to block both (tty) input 48 * interrupts and network activity; thus, splimp must be >= spltty. 49 */ 50 51 /* $Header: if_sl.c,v 1.7 89/05/31 02:24:52 van Exp $ */ 52 /* from if_sl.c,v 1.11 84/10/04 12:54:47 rick Exp */ 53 54 #include "sl.h" 55 #if NSL > 0 56 57 #include "param.h" 58 #include "user.h" 59 #include "mbuf.h" 60 #include "buf.h" 61 #include "dkstat.h" 62 #include "socket.h" 63 #include "ioctl.h" 64 #include "file.h" 65 #include "tty.h" 66 #include "kernel.h" 67 #include "conf.h" 68 #include "errno.h" 69 70 #include "if.h" 71 #include "if_types.h" 72 #include "netisr.h" 73 #include "route.h" 74 #if INET 75 #include "../netinet/in.h" 76 #include "../netinet/in_systm.h" 77 #include "../netinet/in_var.h" 78 #include "../netinet/ip.h" 79 #endif 80 81 #include "machine/mtpr.h" 82 83 #include "slcompress.h" 84 #include "if_slvar.h" 85 86 /* 87 * SLMAX is a hard limit on input packet size. To simplify the code 88 * and improve performance, we require that packets fit in an mbuf 89 * cluster, and if we get a compressed packet, there's enough extra 90 * room to expand the header into a max length tcp/ip header (128 91 * bytes). So, SLMAX can be at most 92 * MCLBYTES - 128 93 * 94 * SLMTU is a hard limit on output packet size. To insure good 95 * interactive response, SLMTU wants to be the smallest size that 96 * amortizes the header cost. (Remember that even with 97 * type-of-service queuing, we have to wait for any in-progress 98 * packet to finish. I.e., we wait, on the average, 1/2 * mtu / 99 * cps, where cps is the line speed in characters per second. 100 * E.g., 533ms wait for a 1024 byte MTU on a 9600 baud line. The 101 * average compressed header size is 6-8 bytes so any MTU > 90 102 * bytes will give us 90% of the line bandwidth. A 100ms wait is 103 * tolerable (500ms is not), so want an MTU around 296. (Since TCP 104 * will send 256 byte segments (to allow for 40 byte headers), the 105 * typical packet size on the wire will be around 260 bytes). In 106 * 4.3tahoe+ systems, we can set an MTU in a route so we do that & 107 * leave the interface MTU relatively high (so we don't IP fragment 108 * when acting as a gateway to someone using a stupid MTU). 109 * 110 * Similar considerations apply to SLIP_HIWAT: It's the amount of 111 * data that will be queued 'downstream' of us (i.e., in clists 112 * waiting to be picked up by the tty output interrupt). If we 113 * queue a lot of data downstream, it's immune to our t.o.s. queuing. 114 * E.g., if SLIP_HIWAT is 1024, the interactive traffic in mixed 115 * telnet/ftp will see a 1 sec wait, independent of the mtu (the 116 * wait is dependent on the ftp window size but that's typically 117 * 1k - 4k). So, we want SLIP_HIWAT just big enough to amortize 118 * the cost (in idle time on the wire) of the tty driver running 119 * off the end of its clists & having to call back slstart for a 120 * new packet. For a tty interface with any buffering at all, this 121 * cost will be zero. Even with a totally brain dead interface (like 122 * the one on a typical workstation), the cost will be <= 1 character 123 * time. So, setting SLIP_HIWAT to ~100 guarantees that we'll lose 124 * at most 1% while maintaining good interactive response. 125 */ 126 #define BUFOFFSET 128 127 #define SLMAX (MCLBYTES - BUFOFFSET) 128 #define SLBUFSIZE (SLMAX + BUFOFFSET) 129 #define SLMTU 296 130 #define SLIP_HIWAT roundup(50,CBSIZE) 131 #define CLISTRESERVE 1024 /* Can't let clists get too low */ 132 133 /* 134 * SLIP ABORT ESCAPE MECHANISM: 135 * (inspired by HAYES modem escape arrangement) 136 * 1sec escape 1sec escape 1sec escape { 1sec escape 1sec escape } 137 * signals a "soft" exit from slip mode by usermode process 138 */ 139 140 #define ABT_ESC '\033' /* can't be t_intr - distant host must know it*/ 141 #define ABT_WAIT 1 /* in seconds - idle before an escape & after */ 142 #define ABT_RECYCLE (5*2+2) /* in seconds - time window processing abort */ 143 144 #define ABT_SOFT 3 /* count of escapes */ 145 146 /* 147 * The following disgusting hack gets around the problem that IP TOS 148 * can't be set yet. We want to put "interactive" traffic on a high 149 * priority queue. To decide if traffic is interactive, we check that 150 * a) it is TCP and b) one of its ports is telnet, rlogin or ftp control. 151 */ 152 static u_short interactive_ports[8] = { 153 0, 513, 0, 0, 154 0, 21, 0, 23, 155 }; 156 #define INTERACTIVE(p) (interactive_ports[(p) & 7] == (p)) 157 158 struct sl_softc sl_softc[NSL]; 159 160 #define FRAME_END 0xc0 /* Frame End */ 161 #define FRAME_ESCAPE 0xdb /* Frame Esc */ 162 #define TRANS_FRAME_END 0xdc /* transposed frame end */ 163 #define TRANS_FRAME_ESCAPE 0xdd /* transposed frame esc */ 164 165 #define t_sc T_LINEP 166 167 int sloutput(), slioctl(), ttrstrt(); 168 extern struct timeval time; 169 170 /* 171 * Called from boot code to establish sl interfaces. 172 */ 173 slattach() 174 { 175 register struct sl_softc *sc; 176 register int i = 0; 177 178 for (sc = sl_softc; i < NSL; sc++) { 179 sc->sc_if.if_name = "sl"; 180 sc->sc_if.if_unit = i++; 181 sc->sc_if.if_mtu = SLMTU; 182 sc->sc_if.if_flags = IFF_POINTOPOINT; 183 sc->sc_if.if_type = IFT_SLIP; 184 sc->sc_if.if_ioctl = slioctl; 185 sc->sc_if.if_output = sloutput; 186 sc->sc_if.if_snd.ifq_maxlen = 50; 187 sc->sc_fastq.ifq_maxlen = 32; 188 if_attach(&sc->sc_if); 189 } 190 } 191 192 static int 193 slinit(sc) 194 register struct sl_softc *sc; 195 { 196 register caddr_t p; 197 198 if (sc->sc_ep == (u_char *) 0) { 199 MCLALLOC(p, M_WAIT); 200 if (p) 201 sc->sc_ep = (u_char *)p + SLBUFSIZE; 202 else { 203 printf("sl%d: can't allocate buffer\n", sc - sl_softc); 204 sc->sc_if.if_flags &= ~IFF_UP; 205 return (0); 206 } 207 } 208 sc->sc_buf = sc->sc_ep - SLMAX; 209 sc->sc_mp = sc->sc_buf; 210 sl_compress_init(&sc->sc_comp); 211 return (1); 212 } 213 214 /* 215 * Line specific open routine. 216 * Attach the given tty to the first available sl unit. 217 */ 218 /* ARGSUSED */ 219 slopen(dev, tp) 220 dev_t dev; 221 register struct tty *tp; 222 { 223 register struct sl_softc *sc; 224 register int nsl; 225 int error; 226 227 if (error = suser(u.u_cred, &u.u_acflag)) 228 return (error); 229 230 if (tp->t_line == SLIPDISC) 231 return (0); 232 233 for (nsl = NSL, sc = sl_softc; --nsl >= 0; sc++) 234 if (sc->sc_ttyp == NULL) { 235 if (slinit(sc) == 0) 236 return (ENOBUFS); 237 tp->t_sc = (caddr_t)sc; 238 sc->sc_ttyp = tp; 239 sc->sc_if.if_baudrate = tp->t_ospeed; 240 ttyflush(tp, FREAD | FWRITE); 241 return (0); 242 } 243 return (ENXIO); 244 } 245 246 /* 247 * Line specific close routine. 248 * Detach the tty from the sl unit. 249 * Mimics part of ttyclose(). 250 */ 251 slclose(tp) 252 struct tty *tp; 253 { 254 register struct sl_softc *sc; 255 int s; 256 257 ttywflush(tp); 258 s = splimp(); /* actually, max(spltty, splnet) */ 259 tp->t_line = 0; 260 sc = (struct sl_softc *)tp->t_sc; 261 if (sc != NULL) { 262 if_down(&sc->sc_if); 263 sc->sc_ttyp = NULL; 264 tp->t_sc = NULL; 265 MCLFREE((caddr_t)(sc->sc_ep - SLBUFSIZE)); 266 sc->sc_ep = 0; 267 sc->sc_mp = 0; 268 sc->sc_buf = 0; 269 } 270 splx(s); 271 } 272 273 /* 274 * Line specific (tty) ioctl routine. 275 * Provide a way to get the sl unit number. 276 */ 277 /* ARGSUSED */ 278 sltioctl(tp, cmd, data, flag) 279 struct tty *tp; 280 caddr_t data; 281 { 282 struct sl_softc *sc = (struct sl_softc *)tp->t_sc; 283 int s; 284 285 switch (cmd) { 286 case TIOCGETD: /* XXX */ 287 case SLIOGUNIT: 288 *(int *)data = sc->sc_if.if_unit; 289 break; 290 291 case SLIOCGFLAGS: 292 *(int *)data = sc->sc_flags; 293 break; 294 295 case SLIOCSFLAGS: 296 #define SC_MASK 0xffff 297 s = splimp(); 298 sc->sc_flags = 299 (sc->sc_flags &~ SC_MASK) | ((*(int *)data) & SC_MASK); 300 splx(s); 301 break; 302 303 default: 304 return (-1); 305 } 306 return (0); 307 } 308 309 /* 310 * Queue a packet. Start transmission if not active. 311 */ 312 sloutput(ifp, m, dst) 313 struct ifnet *ifp; 314 register struct mbuf *m; 315 struct sockaddr *dst; 316 { 317 register struct sl_softc *sc = &sl_softc[ifp->if_unit]; 318 register struct ip *ip; 319 register struct ifqueue *ifq; 320 int s; 321 322 /* 323 * `Cannot happen' (see slioctl). Someday we will extend 324 * the line protocol to support other address families. 325 */ 326 if (dst->sa_family != AF_INET) { 327 printf("sl%d: af%d not supported\n", sc->sc_if.if_unit, 328 dst->sa_family); 329 m_freem(m); 330 return (EAFNOSUPPORT); 331 } 332 333 if (sc->sc_ttyp == NULL) { 334 m_freem(m); 335 return (ENETDOWN); /* sort of */ 336 } 337 if ((sc->sc_ttyp->t_state & TS_CARR_ON) == 0) { 338 m_freem(m); 339 return (EHOSTUNREACH); 340 } 341 ifq = &sc->sc_if.if_snd; 342 if ((ip = mtod(m, struct ip *))->ip_p == IPPROTO_TCP) { 343 register int p = ((int *)ip)[ip->ip_hl]; 344 345 if (INTERACTIVE(p & 0xffff) || INTERACTIVE(p >> 16)) { 346 ifq = &sc->sc_fastq; 347 p = 1; 348 } else 349 p = 0; 350 351 if (sc->sc_flags & SC_COMPRESS) { 352 /* 353 * The last parameter turns off connection id 354 * compression for background traffic: Since 355 * fastq traffic can jump ahead of the background 356 * traffic, we don't know what order packets will 357 * go on the line. 358 */ 359 p = sl_compress_tcp(m, ip, &sc->sc_comp, p); 360 *mtod(m, u_char *) |= p; 361 } 362 } else if (sc->sc_flags & SC_NOICMP && ip->ip_p == IPPROTO_ICMP) { 363 m_freem(m); 364 return (0); 365 } 366 s = splimp(); 367 if (IF_QFULL(ifq)) { 368 IF_DROP(ifq); 369 m_freem(m); 370 splx(s); 371 sc->sc_if.if_oerrors++; 372 return (ENOBUFS); 373 } 374 IF_ENQUEUE(ifq, m); 375 sc->sc_if.if_lastchange = time; 376 if (sc->sc_ttyp->t_outq.c_cc == 0) 377 slstart(sc->sc_ttyp); 378 splx(s); 379 return (0); 380 } 381 382 /* 383 * Start output on interface. Get another datagram 384 * to send from the interface queue and map it to 385 * the interface before starting output. 386 */ 387 slstart(tp) 388 register struct tty *tp; 389 { 390 register struct sl_softc *sc = (struct sl_softc *)tp->t_sc; 391 register struct mbuf *m; 392 register u_char *cp; 393 int s; 394 struct mbuf *m2; 395 extern int cfreecount; 396 397 for (;;) { 398 /* 399 * If there is more in the output queue, just send it now. 400 * We are being called in lieu of ttstart and must do what 401 * it would. 402 */ 403 if (tp->t_outq.c_cc != 0) { 404 (*tp->t_oproc)(tp); 405 if (tp->t_outq.c_cc > SLIP_HIWAT) 406 return; 407 } 408 /* 409 * This happens briefly when the line shuts down. 410 */ 411 if (sc == NULL) 412 return; 413 414 /* 415 * Get a packet and send it to the interface. 416 */ 417 s = splimp(); 418 IF_DEQUEUE(&sc->sc_fastq, m); 419 if (m == NULL) 420 IF_DEQUEUE(&sc->sc_if.if_snd, m); 421 splx(s); 422 if (m == NULL) 423 return; 424 sc->sc_if.if_lastchange = time; 425 /* 426 * If system is getting low on clists, just flush our 427 * output queue (if the stuff was important, it'll get 428 * retransmitted). 429 */ 430 if (cfreecount < CLISTRESERVE + SLMTU) { 431 m_freem(m); 432 sc->sc_if.if_collisions++; 433 continue; 434 } 435 436 /* 437 * The extra FRAME_END will start up a new packet, and thus 438 * will flush any accumulated garbage. We do this whenever 439 * the line may have been idle for some time. 440 */ 441 if (tp->t_outq.c_cc == 0) { 442 ++sc->sc_bytessent; 443 (void) putc(FRAME_END, &tp->t_outq); 444 } 445 446 while (m) { 447 register u_char *ep; 448 449 cp = mtod(m, u_char *); ep = cp + m->m_len; 450 while (cp < ep) { 451 /* 452 * Find out how many bytes in the string we can 453 * handle without doing something special. 454 */ 455 register u_char *bp = cp; 456 457 while (cp < ep) { 458 switch (*cp++) { 459 case FRAME_ESCAPE: 460 case FRAME_END: 461 --cp; 462 goto out; 463 } 464 } 465 out: 466 if (cp > bp) { 467 /* 468 * Put n characters at once 469 * into the tty output queue. 470 */ 471 if (b_to_q((char *)bp, cp - bp, &tp->t_outq)) 472 break; 473 sc->sc_bytessent += cp - bp; 474 } 475 /* 476 * If there are characters left in the mbuf, 477 * the first one must be special.. 478 * Put it out in a different form. 479 */ 480 if (cp < ep) { 481 if (putc(FRAME_ESCAPE, &tp->t_outq)) 482 break; 483 if (putc(*cp++ == FRAME_ESCAPE ? 484 TRANS_FRAME_ESCAPE : TRANS_FRAME_END, 485 &tp->t_outq)) { 486 (void) unputc(&tp->t_outq); 487 break; 488 } 489 sc->sc_bytessent += 2; 490 } 491 } 492 MFREE(m, m2); 493 m = m2; 494 } 495 496 if (putc(FRAME_END, &tp->t_outq)) { 497 /* 498 * Not enough room. Remove a char to make room 499 * and end the packet normally. 500 * If you get many collisions (more than one or two 501 * a day) you probably do not have enough clists 502 * and you should increase "nclist" in param.c. 503 */ 504 (void) unputc(&tp->t_outq); 505 (void) putc(FRAME_END, &tp->t_outq); 506 sc->sc_if.if_collisions++; 507 } else { 508 ++sc->sc_bytessent; 509 sc->sc_if.if_opackets++; 510 } 511 sc->sc_if.if_obytes = sc->sc_bytessent; 512 } 513 } 514 515 /* 516 * Copy data buffer to mbuf chain; add ifnet pointer. 517 */ 518 static struct mbuf * 519 sl_btom(sc, len) 520 register struct sl_softc *sc; 521 register int len; 522 { 523 register struct mbuf *m; 524 525 MGETHDR(m, M_DONTWAIT, MT_DATA); 526 if (m == NULL) 527 return (NULL); 528 529 /* 530 * If we have more than MHLEN bytes, it's cheaper to 531 * queue the cluster we just filled & allocate a new one 532 * for the input buffer. Otherwise, fill the mbuf we 533 * allocated above. Note that code in the input routine 534 * guarantees that packet will fit in a cluster. 535 */ 536 if (len >= MHLEN) { 537 MCLGET(m, M_DONTWAIT); 538 if ((m->m_flags & M_EXT) == 0) { 539 /* 540 * we couldn't get a cluster - if memory's this 541 * low, it's time to start dropping packets. 542 */ 543 (void) m_free(m); 544 return (NULL); 545 } 546 sc->sc_ep = mtod(m, u_char *) + SLBUFSIZE; 547 m->m_data = (caddr_t)sc->sc_buf; 548 m->m_ext.ext_buf = (caddr_t)((int)sc->sc_buf &~ MCLOFSET); 549 } else 550 bcopy((caddr_t)sc->sc_buf, mtod(m, caddr_t), len); 551 552 m->m_len = len; 553 m->m_pkthdr.len = len; 554 m->m_pkthdr.rcvif = &sc->sc_if; 555 return (m); 556 } 557 558 /* 559 * tty interface receiver interrupt. 560 */ 561 slinput(c, tp) 562 register int c; 563 register struct tty *tp; 564 { 565 register struct sl_softc *sc; 566 register struct mbuf *m; 567 register int len; 568 int s; 569 570 tk_nin++; 571 sc = (struct sl_softc *)tp->t_sc; 572 if (sc == NULL) 573 return; 574 if (!(tp->t_state&TS_CARR_ON)) /* XXX */ 575 return; 576 577 ++sc->sc_bytesrcvd; 578 ++sc->sc_if.if_ibytes; 579 c &= 0xff; /* XXX */ 580 581 #ifdef ABT_ESC 582 if (sc->sc_flags & SC_ABORT) { 583 /* if we see an abort after "idle" time, count it */ 584 if (c == ABT_ESC && time.tv_sec >= sc->sc_lasttime + ABT_WAIT) { 585 sc->sc_abortcount++; 586 /* record when the first abort escape arrived */ 587 if (sc->sc_abortcount == 1) 588 sc->sc_starttime = time.tv_sec; 589 } 590 /* 591 * if we have an abort, see that we have not run out of time, 592 * or that we have an "idle" time after the complete escape 593 * sequence 594 */ 595 if (sc->sc_abortcount) { 596 if (time.tv_sec >= sc->sc_starttime + ABT_RECYCLE) 597 sc->sc_abortcount = 0; 598 if (sc->sc_abortcount >= ABT_SOFT && 599 time.tv_sec >= sc->sc_lasttime + ABT_WAIT) { 600 slclose(tp); 601 return; 602 } 603 } 604 sc->sc_lasttime = time.tv_sec; 605 } 606 #endif 607 608 switch (c) { 609 610 case TRANS_FRAME_ESCAPE: 611 if (sc->sc_escape) 612 c = FRAME_ESCAPE; 613 break; 614 615 case TRANS_FRAME_END: 616 if (sc->sc_escape) 617 c = FRAME_END; 618 break; 619 620 case FRAME_ESCAPE: 621 sc->sc_escape = 1; 622 return; 623 624 case FRAME_END: 625 len = sc->sc_mp - sc->sc_buf; 626 if (len < 3) 627 /* less than min length packet - ignore */ 628 goto newpack; 629 630 if ((c = (*sc->sc_buf & 0xf0)) != (IPVERSION << 4)) { 631 if (c & 0x80) 632 c = TYPE_COMPRESSED_TCP; 633 else if (c == TYPE_UNCOMPRESSED_TCP) 634 *sc->sc_buf &= 0x4f; /* XXX */ 635 /* 636 * We've got something that's not an IP packet. 637 * If compression is enabled, try to decompress it. 638 * Otherwise, if `auto-enable' compression is on and 639 * it's a reasonable packet, decompress it and then 640 * enable compression. Otherwise, drop it. 641 */ 642 if (sc->sc_flags & SC_COMPRESS) { 643 len = sl_uncompress_tcp(&sc->sc_buf, len, 644 (u_int)c, &sc->sc_comp); 645 if (len <= 0) 646 goto error; 647 } else if ((sc->sc_flags & SC_AUTOCOMP) && 648 c == TYPE_UNCOMPRESSED_TCP && len >= 40) { 649 len = sl_uncompress_tcp(&sc->sc_buf, len, 650 (u_int)c, &sc->sc_comp); 651 if (len <= 0) 652 goto error; 653 sc->sc_flags |= SC_COMPRESS; 654 } else 655 goto error; 656 } 657 m = sl_btom(sc, len); 658 if (m == NULL) 659 goto error; 660 661 sc->sc_if.if_ipackets++; 662 sc->sc_if.if_lastchange = time; 663 s = splimp(); 664 if (IF_QFULL(&ipintrq)) { 665 IF_DROP(&ipintrq); 666 sc->sc_if.if_ierrors++; 667 sc->sc_if.if_iqdrops++; 668 m_freem(m); 669 } else { 670 IF_ENQUEUE(&ipintrq, m); 671 schednetisr(NETISR_IP); 672 } 673 splx(s); 674 goto newpack; 675 } 676 if (sc->sc_mp < sc->sc_ep) { 677 *sc->sc_mp++ = c; 678 sc->sc_escape = 0; 679 return; 680 } 681 error: 682 sc->sc_if.if_ierrors++; 683 newpack: 684 sc->sc_mp = sc->sc_buf = sc->sc_ep - SLMAX; 685 sc->sc_escape = 0; 686 } 687 688 /* 689 * Process an ioctl request. 690 */ 691 slioctl(ifp, cmd, data) 692 register struct ifnet *ifp; 693 int cmd; 694 caddr_t data; 695 { 696 register struct ifaddr *ifa = (struct ifaddr *)data; 697 int s = splimp(), error = 0; 698 699 switch (cmd) { 700 701 case SIOCSIFADDR: 702 if (ifa->ifa_addr->sa_family == AF_INET) 703 ifp->if_flags |= IFF_UP; 704 else 705 error = EAFNOSUPPORT; 706 break; 707 708 case SIOCSIFDSTADDR: 709 if (ifa->ifa_addr->sa_family != AF_INET) 710 error = EAFNOSUPPORT; 711 break; 712 713 default: 714 error = EINVAL; 715 } 716 splx(s); 717 return (error); 718 } 719 #endif 720