1 /* 2 * Copyright (c) 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997 3 * The Regents of the University of California. All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that: (1) source code distributions 7 * retain the above copyright notice and this paragraph in its entirety, (2) 8 * distributions including binary code include the above copyright notice and 9 * this paragraph in its entirety in the documentation or other materials 10 * provided with the distribution, and (3) all advertising materials mentioning 11 * features or use of this software display the following acknowledgement: 12 * ``This product includes software developed by the University of California, 13 * Lawrence Berkeley Laboratory and its contributors.'' Neither the name of 14 * the University nor the names of its contributors may be used to endorse 15 * or promote products derived from this software without specific prior 16 * written permission. 17 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED 18 * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF 19 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. 20 * 21 * $FreeBSD$ 22 */ 23 24 #define NETDISSECT_REWORKED 25 #ifdef HAVE_CONFIG_H 26 #include "config.h" 27 #endif 28 29 #include <tcpdump-stdinc.h> 30 31 #include <string.h> 32 33 #include "interface.h" 34 #include "addrtoname.h" 35 #include "extract.h" /* must come after interface.h */ 36 37 #include "ip.h" 38 #include "ipproto.h" 39 40 static const char tstr[] = "[|ip]"; 41 42 static const struct tok ip_option_values[] = { 43 { IPOPT_EOL, "EOL" }, 44 { IPOPT_NOP, "NOP" }, 45 { IPOPT_TS, "timestamp" }, 46 { IPOPT_SECURITY, "security" }, 47 { IPOPT_RR, "RR" }, 48 { IPOPT_SSRR, "SSRR" }, 49 { IPOPT_LSRR, "LSRR" }, 50 { IPOPT_RA, "RA" }, 51 { IPOPT_RFC1393, "traceroute" }, 52 { 0, NULL } 53 }; 54 55 /* 56 * print the recorded route in an IP RR, LSRR or SSRR option. 57 */ 58 static void 59 ip_printroute(netdissect_options *ndo, 60 register const u_char *cp, u_int length) 61 { 62 register u_int ptr; 63 register u_int len; 64 65 if (length < 3) { 66 ND_PRINT((ndo, " [bad length %u]", length)); 67 return; 68 } 69 if ((length + 1) & 3) 70 ND_PRINT((ndo, " [bad length %u]", length)); 71 ptr = cp[2] - 1; 72 if (ptr < 3 || ((ptr + 1) & 3) || ptr > length + 1) 73 ND_PRINT((ndo, " [bad ptr %u]", cp[2])); 74 75 for (len = 3; len < length; len += 4) { 76 ND_PRINT((ndo, " %s", ipaddr_string(ndo, &cp[len]))); 77 if (ptr > len) 78 ND_PRINT((ndo, ",")); 79 } 80 } 81 82 /* 83 * If source-routing is present and valid, return the final destination. 84 * Otherwise, return IP destination. 85 * 86 * This is used for UDP and TCP pseudo-header in the checksum 87 * calculation. 88 */ 89 static uint32_t 90 ip_finddst(netdissect_options *ndo, 91 const struct ip *ip) 92 { 93 int length; 94 int len; 95 const u_char *cp; 96 uint32_t retval; 97 98 cp = (const u_char *)(ip + 1); 99 length = (IP_HL(ip) << 2) - sizeof(struct ip); 100 101 for (; length > 0; cp += len, length -= len) { 102 int tt; 103 104 ND_TCHECK(*cp); 105 tt = *cp; 106 if (tt == IPOPT_EOL) 107 break; 108 else if (tt == IPOPT_NOP) 109 len = 1; 110 else { 111 ND_TCHECK(cp[1]); 112 len = cp[1]; 113 if (len < 2) 114 break; 115 } 116 ND_TCHECK2(*cp, len); 117 switch (tt) { 118 119 case IPOPT_SSRR: 120 case IPOPT_LSRR: 121 if (len < 7) 122 break; 123 UNALIGNED_MEMCPY(&retval, cp + len - 4, 4); 124 return retval; 125 } 126 } 127 trunc: 128 UNALIGNED_MEMCPY(&retval, &ip->ip_dst.s_addr, sizeof(uint32_t)); 129 return retval; 130 } 131 132 /* 133 * Compute a V4-style checksum by building a pseudoheader. 134 */ 135 int 136 nextproto4_cksum(netdissect_options *ndo, 137 const struct ip *ip, const uint8_t *data, 138 u_int len, u_int covlen, u_int next_proto) 139 { 140 struct phdr { 141 uint32_t src; 142 uint32_t dst; 143 u_char mbz; 144 u_char proto; 145 uint16_t len; 146 } ph; 147 struct cksum_vec vec[2]; 148 149 /* pseudo-header.. */ 150 ph.len = htons((uint16_t)len); 151 ph.mbz = 0; 152 ph.proto = next_proto; 153 UNALIGNED_MEMCPY(&ph.src, &ip->ip_src.s_addr, sizeof(uint32_t)); 154 if (IP_HL(ip) == 5) 155 UNALIGNED_MEMCPY(&ph.dst, &ip->ip_dst.s_addr, sizeof(uint32_t)); 156 else 157 ph.dst = ip_finddst(ndo, ip); 158 159 vec[0].ptr = (const uint8_t *)(void *)&ph; 160 vec[0].len = sizeof(ph); 161 vec[1].ptr = data; 162 vec[1].len = covlen; 163 return (in_cksum(vec, 2)); 164 } 165 166 static void 167 ip_printts(netdissect_options *ndo, 168 register const u_char *cp, u_int length) 169 { 170 register u_int ptr; 171 register u_int len; 172 int hoplen; 173 const char *type; 174 175 if (length < 4) { 176 ND_PRINT((ndo, "[bad length %u]", length)); 177 return; 178 } 179 ND_PRINT((ndo, " TS{")); 180 hoplen = ((cp[3]&0xF) != IPOPT_TS_TSONLY) ? 8 : 4; 181 if ((length - 4) & (hoplen-1)) 182 ND_PRINT((ndo, "[bad length %u]", length)); 183 ptr = cp[2] - 1; 184 len = 0; 185 if (ptr < 4 || ((ptr - 4) & (hoplen-1)) || ptr > length + 1) 186 ND_PRINT((ndo, "[bad ptr %u]", cp[2])); 187 switch (cp[3]&0xF) { 188 case IPOPT_TS_TSONLY: 189 ND_PRINT((ndo, "TSONLY")); 190 break; 191 case IPOPT_TS_TSANDADDR: 192 ND_PRINT((ndo, "TS+ADDR")); 193 break; 194 /* 195 * prespecified should really be 3, but some ones might send 2 196 * instead, and the IPOPT_TS_PRESPEC constant can apparently 197 * have both values, so we have to hard-code it here. 198 */ 199 200 case 2: 201 ND_PRINT((ndo, "PRESPEC2.0")); 202 break; 203 case 3: /* IPOPT_TS_PRESPEC */ 204 ND_PRINT((ndo, "PRESPEC")); 205 break; 206 default: 207 ND_PRINT((ndo, "[bad ts type %d]", cp[3]&0xF)); 208 goto done; 209 } 210 211 type = " "; 212 for (len = 4; len < length; len += hoplen) { 213 if (ptr == len) 214 type = " ^ "; 215 ND_PRINT((ndo, "%s%d@%s", type, EXTRACT_32BITS(&cp[len+hoplen-4]), 216 hoplen!=8 ? "" : ipaddr_string(ndo, &cp[len]))); 217 type = " "; 218 } 219 220 done: 221 ND_PRINT((ndo, "%s", ptr == len ? " ^ " : "")); 222 223 if (cp[3]>>4) 224 ND_PRINT((ndo, " [%d hops not recorded]} ", cp[3]>>4)); 225 else 226 ND_PRINT((ndo, "}")); 227 } 228 229 /* 230 * print IP options. 231 */ 232 static void 233 ip_optprint(netdissect_options *ndo, 234 register const u_char *cp, u_int length) 235 { 236 register u_int option_len; 237 const char *sep = ""; 238 239 for (; length > 0; cp += option_len, length -= option_len) { 240 u_int option_code; 241 242 ND_PRINT((ndo, "%s", sep)); 243 sep = ","; 244 245 ND_TCHECK(*cp); 246 option_code = *cp; 247 248 ND_PRINT((ndo, "%s", 249 tok2str(ip_option_values,"unknown %u",option_code))); 250 251 if (option_code == IPOPT_NOP || 252 option_code == IPOPT_EOL) 253 option_len = 1; 254 255 else { 256 ND_TCHECK(cp[1]); 257 option_len = cp[1]; 258 if (option_len < 2) { 259 ND_PRINT((ndo, " [bad length %u]", option_len)); 260 return; 261 } 262 } 263 264 if (option_len > length) { 265 ND_PRINT((ndo, " [bad length %u]", option_len)); 266 return; 267 } 268 269 ND_TCHECK2(*cp, option_len); 270 271 switch (option_code) { 272 case IPOPT_EOL: 273 return; 274 275 case IPOPT_TS: 276 ip_printts(ndo, cp, option_len); 277 break; 278 279 case IPOPT_RR: /* fall through */ 280 case IPOPT_SSRR: 281 case IPOPT_LSRR: 282 ip_printroute(ndo, cp, option_len); 283 break; 284 285 case IPOPT_RA: 286 if (option_len < 4) { 287 ND_PRINT((ndo, " [bad length %u]", option_len)); 288 break; 289 } 290 ND_TCHECK(cp[3]); 291 if (EXTRACT_16BITS(&cp[2]) != 0) 292 ND_PRINT((ndo, " value %u", EXTRACT_16BITS(&cp[2]))); 293 break; 294 295 case IPOPT_NOP: /* nothing to print - fall through */ 296 case IPOPT_SECURITY: 297 default: 298 break; 299 } 300 } 301 return; 302 303 trunc: 304 ND_PRINT((ndo, "%s", tstr)); 305 } 306 307 #define IP_RES 0x8000 308 309 static const struct tok ip_frag_values[] = { 310 { IP_MF, "+" }, 311 { IP_DF, "DF" }, 312 { IP_RES, "rsvd" }, /* The RFC3514 evil ;-) bit */ 313 { 0, NULL } 314 }; 315 316 struct ip_print_demux_state { 317 const struct ip *ip; 318 const u_char *cp; 319 u_int len, off; 320 u_char nh; 321 int advance; 322 }; 323 324 static void 325 ip_print_demux(netdissect_options *ndo, 326 struct ip_print_demux_state *ipds) 327 { 328 struct protoent *proto; 329 struct cksum_vec vec[1]; 330 331 again: 332 switch (ipds->nh) { 333 334 case IPPROTO_AH: 335 ipds->nh = *ipds->cp; 336 ipds->advance = ah_print(ndo, ipds->cp); 337 if (ipds->advance <= 0) 338 break; 339 ipds->cp += ipds->advance; 340 ipds->len -= ipds->advance; 341 goto again; 342 343 case IPPROTO_ESP: 344 { 345 int enh, padlen; 346 ipds->advance = esp_print(ndo, ipds->cp, ipds->len, 347 (const u_char *)ipds->ip, 348 &enh, &padlen); 349 if (ipds->advance <= 0) 350 break; 351 ipds->cp += ipds->advance; 352 ipds->len -= ipds->advance + padlen; 353 ipds->nh = enh & 0xff; 354 goto again; 355 } 356 357 case IPPROTO_IPCOMP: 358 { 359 int enh; 360 ipds->advance = ipcomp_print(ndo, ipds->cp, &enh); 361 if (ipds->advance <= 0) 362 break; 363 ipds->cp += ipds->advance; 364 ipds->len -= ipds->advance; 365 ipds->nh = enh & 0xff; 366 goto again; 367 } 368 369 case IPPROTO_SCTP: 370 sctp_print(ndo, ipds->cp, (const u_char *)ipds->ip, ipds->len); 371 break; 372 373 case IPPROTO_DCCP: 374 dccp_print(ndo, ipds->cp, (const u_char *)ipds->ip, ipds->len); 375 break; 376 377 case IPPROTO_TCP: 378 /* pass on the MF bit plus the offset to detect fragments */ 379 tcp_print(ndo, ipds->cp, ipds->len, (const u_char *)ipds->ip, 380 ipds->off & (IP_MF|IP_OFFMASK)); 381 break; 382 383 case IPPROTO_UDP: 384 /* pass on the MF bit plus the offset to detect fragments */ 385 udp_print(ndo, ipds->cp, ipds->len, (const u_char *)ipds->ip, 386 ipds->off & (IP_MF|IP_OFFMASK)); 387 break; 388 389 case IPPROTO_ICMP: 390 /* pass on the MF bit plus the offset to detect fragments */ 391 icmp_print(ndo, ipds->cp, ipds->len, (const u_char *)ipds->ip, 392 ipds->off & (IP_MF|IP_OFFMASK)); 393 break; 394 395 case IPPROTO_PIGP: 396 /* 397 * XXX - the current IANA protocol number assignments 398 * page lists 9 as "any private interior gateway 399 * (used by Cisco for their IGRP)" and 88 as 400 * "EIGRP" from Cisco. 401 * 402 * Recent BSD <netinet/in.h> headers define 403 * IP_PROTO_PIGP as 9 and IP_PROTO_IGRP as 88. 404 * We define IP_PROTO_PIGP as 9 and 405 * IP_PROTO_EIGRP as 88; those names better 406 * match was the current protocol number 407 * assignments say. 408 */ 409 igrp_print(ndo, ipds->cp, ipds->len); 410 break; 411 412 case IPPROTO_EIGRP: 413 eigrp_print(ndo, ipds->cp, ipds->len); 414 break; 415 416 case IPPROTO_ND: 417 ND_PRINT((ndo, " nd %d", ipds->len)); 418 break; 419 420 case IPPROTO_EGP: 421 egp_print(ndo, ipds->cp, ipds->len); 422 break; 423 424 case IPPROTO_OSPF: 425 ospf_print(ndo, ipds->cp, ipds->len, (const u_char *)ipds->ip); 426 break; 427 428 case IPPROTO_IGMP: 429 igmp_print(ndo, ipds->cp, ipds->len); 430 break; 431 432 case IPPROTO_IPV4: 433 /* DVMRP multicast tunnel (ip-in-ip encapsulation) */ 434 ip_print(ndo, ipds->cp, ipds->len); 435 if (! ndo->ndo_vflag) { 436 ND_PRINT((ndo, " (ipip-proto-4)")); 437 return; 438 } 439 break; 440 441 #ifdef INET6 442 case IPPROTO_IPV6: 443 /* ip6-in-ip encapsulation */ 444 ip6_print(ndo, ipds->cp, ipds->len); 445 break; 446 #endif /*INET6*/ 447 448 case IPPROTO_RSVP: 449 rsvp_print(ndo, ipds->cp, ipds->len); 450 break; 451 452 case IPPROTO_GRE: 453 /* do it */ 454 gre_print(ndo, ipds->cp, ipds->len); 455 break; 456 457 case IPPROTO_MOBILE: 458 mobile_print(ndo, ipds->cp, ipds->len); 459 break; 460 461 case IPPROTO_PIM: 462 vec[0].ptr = ipds->cp; 463 vec[0].len = ipds->len; 464 pim_print(ndo, ipds->cp, ipds->len, in_cksum(vec, 1)); 465 break; 466 467 case IPPROTO_VRRP: 468 if (ndo->ndo_packettype == PT_CARP) { 469 if (ndo->ndo_vflag) 470 ND_PRINT((ndo, "carp %s > %s: ", 471 ipaddr_string(ndo, &ipds->ip->ip_src), 472 ipaddr_string(ndo, &ipds->ip->ip_dst))); 473 carp_print(ndo, ipds->cp, ipds->len, ipds->ip->ip_ttl); 474 } else { 475 if (ndo->ndo_vflag) 476 ND_PRINT((ndo, "vrrp %s > %s: ", 477 ipaddr_string(ndo, &ipds->ip->ip_src), 478 ipaddr_string(ndo, &ipds->ip->ip_dst))); 479 vrrp_print(ndo, ipds->cp, ipds->len, 480 (const u_char *)ipds->ip, ipds->ip->ip_ttl); 481 } 482 break; 483 484 case IPPROTO_PGM: 485 pgm_print(ndo, ipds->cp, ipds->len, (const u_char *)ipds->ip); 486 break; 487 488 #if defined(HAVE_NET_PFVAR_H) 489 case IPPROTO_PFSYNC: 490 pfsync_ip_print(ipds->cp, ipds->len); 491 break; 492 #endif 493 494 default: 495 if (ndo->ndo_nflag==0 && (proto = getprotobynumber(ipds->nh)) != NULL) 496 ND_PRINT((ndo, " %s", proto->p_name)); 497 else 498 ND_PRINT((ndo, " ip-proto-%d", ipds->nh)); 499 ND_PRINT((ndo, " %d", ipds->len)); 500 break; 501 } 502 } 503 504 void 505 ip_print_inner(netdissect_options *ndo, 506 const u_char *bp, 507 u_int length, u_int nh, 508 const u_char *bp2) 509 { 510 struct ip_print_demux_state ipd; 511 512 ipd.ip = (const struct ip *)bp2; 513 ipd.cp = bp; 514 ipd.len = length; 515 ipd.off = 0; 516 ipd.nh = nh; 517 ipd.advance = 0; 518 519 ip_print_demux(ndo, &ipd); 520 } 521 522 523 /* 524 * print an IP datagram. 525 */ 526 void 527 ip_print(netdissect_options *ndo, 528 const u_char *bp, 529 u_int length) 530 { 531 struct ip_print_demux_state ipd; 532 struct ip_print_demux_state *ipds=&ipd; 533 const u_char *ipend; 534 u_int hlen; 535 struct cksum_vec vec[1]; 536 uint16_t sum, ip_sum; 537 struct protoent *proto; 538 539 ipds->ip = (const struct ip *)bp; 540 if (IP_V(ipds->ip) != 4) { /* print version if != 4 */ 541 ND_PRINT((ndo, "IP%u ", IP_V(ipds->ip))); 542 if (IP_V(ipds->ip) == 6) 543 ND_PRINT((ndo, ", wrong link-layer encapsulation")); 544 } 545 else if (!ndo->ndo_eflag) 546 ND_PRINT((ndo, "IP ")); 547 548 if ((u_char *)(ipds->ip + 1) > ndo->ndo_snapend) { 549 ND_PRINT((ndo, "%s", tstr)); 550 return; 551 } 552 if (length < sizeof (struct ip)) { 553 ND_PRINT((ndo, "truncated-ip %u", length)); 554 return; 555 } 556 hlen = IP_HL(ipds->ip) * 4; 557 if (hlen < sizeof (struct ip)) { 558 ND_PRINT((ndo, "bad-hlen %u", hlen)); 559 return; 560 } 561 562 ipds->len = EXTRACT_16BITS(&ipds->ip->ip_len); 563 if (length < ipds->len) 564 ND_PRINT((ndo, "truncated-ip - %u bytes missing! ", 565 ipds->len - length)); 566 if (ipds->len < hlen) { 567 #ifdef GUESS_TSO 568 if (ipds->len) { 569 ND_PRINT((ndo, "bad-len %u", ipds->len)); 570 return; 571 } 572 else { 573 /* we guess that it is a TSO send */ 574 ipds->len = length; 575 } 576 #else 577 ND_PRINT((ndo, "bad-len %u", ipds->len)); 578 return; 579 #endif /* GUESS_TSO */ 580 } 581 582 /* 583 * Cut off the snapshot length to the end of the IP payload. 584 */ 585 ipend = bp + ipds->len; 586 if (ipend < ndo->ndo_snapend) 587 ndo->ndo_snapend = ipend; 588 589 ipds->len -= hlen; 590 591 ipds->off = EXTRACT_16BITS(&ipds->ip->ip_off); 592 593 if (ndo->ndo_vflag) { 594 ND_PRINT((ndo, "(tos 0x%x", (int)ipds->ip->ip_tos)); 595 /* ECN bits */ 596 if (ipds->ip->ip_tos & 0x03) { 597 switch (ipds->ip->ip_tos & 0x03) { 598 case 1: 599 ND_PRINT((ndo, ",ECT(1)")); 600 break; 601 case 2: 602 ND_PRINT((ndo, ",ECT(0)")); 603 break; 604 case 3: 605 ND_PRINT((ndo, ",CE")); 606 } 607 } 608 609 if (ipds->ip->ip_ttl >= 1) 610 ND_PRINT((ndo, ", ttl %u", ipds->ip->ip_ttl)); 611 612 /* 613 * for the firewall guys, print id, offset. 614 * On all but the last stick a "+" in the flags portion. 615 * For unfragmented datagrams, note the don't fragment flag. 616 */ 617 618 ND_PRINT((ndo, ", id %u, offset %u, flags [%s], proto %s (%u)", 619 EXTRACT_16BITS(&ipds->ip->ip_id), 620 (ipds->off & 0x1fff) * 8, 621 bittok2str(ip_frag_values, "none", ipds->off&0xe000), 622 tok2str(ipproto_values,"unknown",ipds->ip->ip_p), 623 ipds->ip->ip_p)); 624 625 ND_PRINT((ndo, ", length %u", EXTRACT_16BITS(&ipds->ip->ip_len))); 626 627 if ((hlen - sizeof(struct ip)) > 0) { 628 ND_PRINT((ndo, ", options (")); 629 ip_optprint(ndo, (u_char *)(ipds->ip + 1), hlen - sizeof(struct ip)); 630 ND_PRINT((ndo, ")")); 631 } 632 633 if (!ndo->ndo_Kflag && (u_char *)ipds->ip + hlen <= ndo->ndo_snapend) { 634 vec[0].ptr = (const uint8_t *)(void *)ipds->ip; 635 vec[0].len = hlen; 636 sum = in_cksum(vec, 1); 637 if (sum != 0) { 638 ip_sum = EXTRACT_16BITS(&ipds->ip->ip_sum); 639 ND_PRINT((ndo, ", bad cksum %x (->%x)!", ip_sum, 640 in_cksum_shouldbe(ip_sum, sum))); 641 } 642 } 643 644 ND_PRINT((ndo, ")\n ")); 645 } 646 647 /* 648 * If this is fragment zero, hand it to the next higher 649 * level protocol. 650 */ 651 if ((ipds->off & 0x1fff) == 0) { 652 ipds->cp = (const u_char *)ipds->ip + hlen; 653 ipds->nh = ipds->ip->ip_p; 654 655 if (ipds->nh != IPPROTO_TCP && ipds->nh != IPPROTO_UDP && 656 ipds->nh != IPPROTO_SCTP && ipds->nh != IPPROTO_DCCP) { 657 ND_PRINT((ndo, "%s > %s: ", 658 ipaddr_string(ndo, &ipds->ip->ip_src), 659 ipaddr_string(ndo, &ipds->ip->ip_dst))); 660 } 661 ip_print_demux(ndo, ipds); 662 } else { 663 /* Ultra quiet now means that all this stuff should be suppressed */ 664 if (ndo->ndo_qflag > 1) return; 665 666 /* 667 * if this isn't the first frag, we're missing the 668 * next level protocol header. print the ip addr 669 * and the protocol. 670 */ 671 if (ipds->off & 0x1fff) { 672 ND_PRINT((ndo, "%s > %s:", ipaddr_string(ndo, &ipds->ip->ip_src), 673 ipaddr_string(ndo, &ipds->ip->ip_dst))); 674 if (!ndo->ndo_nflag && (proto = getprotobynumber(ipds->ip->ip_p)) != NULL) 675 ND_PRINT((ndo, " %s", proto->p_name)); 676 else 677 ND_PRINT((ndo, " ip-proto-%d", ipds->ip->ip_p)); 678 } 679 } 680 } 681 682 void 683 ipN_print(netdissect_options *ndo, register const u_char *bp, register u_int length) 684 { 685 struct ip hdr; 686 687 if (length < 4) { 688 ND_PRINT((ndo, "truncated-ip %d", length)); 689 return; 690 } 691 memcpy (&hdr, bp, 4); 692 switch (IP_V(&hdr)) { 693 case 4: 694 ip_print (ndo, bp, length); 695 return; 696 #ifdef INET6 697 case 6: 698 ip6_print (ndo, bp, length); 699 return; 700 #endif 701 default: 702 ND_PRINT((ndo, "unknown ip %d", IP_V(&hdr))); 703 return; 704 } 705 } 706 707 /* 708 * Local Variables: 709 * c-style: whitesmith 710 * c-basic-offset: 8 711 * End: 712 */ 713 714 715