1 /*- 2 * Copyright (c) 2012 Gleb Smirnoff <glebius@FreeBSD.org> 3 * Copyright (c) 1980, 1986, 1993 4 * The Regents of the University of California. All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 4. Neither the name of the University nor the names of its contributors 15 * may be used to endorse or promote products derived from this software 16 * without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28 * SUCH DAMAGE. 29 * 30 * @(#)if.c 8.5 (Berkeley) 1/9/95 31 * $FreeBSD$ 32 */ 33 34 #include <sys/param.h> 35 #include <sys/eventhandler.h> 36 #include <sys/malloc.h> 37 #include <sys/limits.h> 38 #include <sys/lock.h> 39 #include <sys/mutex.h> 40 #include <sys/kernel.h> 41 #include <sys/systm.h> 42 #include <sys/types.h> 43 #include <sys/socket.h> 44 45 #include <net/if.h> 46 #include <net/if_var.h> 47 #include <net/if_clone.h> 48 #include <net/radix.h> 49 #include <net/route.h> 50 #include <net/vnet.h> 51 52 /* Current IF_MAXUNIT expands maximum to 5 characters. */ 53 #define IFCLOSIZ (IFNAMSIZ - 5) 54 55 /* 56 * Structure describing a `cloning' interface. 57 * 58 * List of locks 59 * (c) const until freeing 60 * (d) driver specific data, may need external protection. 61 * (e) locked by if_cloners_mtx 62 * (i) locked by ifc_mtx mtx 63 */ 64 struct if_clone { 65 char ifc_name[IFCLOSIZ]; /* (c) Name of device, e.g. `gif' */ 66 struct unrhdr *ifc_unrhdr; /* (c) alloc_unr(9) header */ 67 int ifc_maxunit; /* (c) maximum unit number */ 68 long ifc_refcnt; /* (i) Reference count. */ 69 LIST_HEAD(, ifnet) ifc_iflist; /* (i) List of cloned interfaces */ 70 struct mtx ifc_mtx; /* Mutex to protect members. */ 71 72 enum { SIMPLE, ADVANCED } ifc_type; /* (c) */ 73 74 /* (c) Driver specific cloning functions. Called with no locks held. */ 75 union { 76 struct { /* advanced cloner */ 77 ifc_match_t *_ifc_match; 78 ifc_create_t *_ifc_create; 79 ifc_destroy_t *_ifc_destroy; 80 } A; 81 struct { /* simple cloner */ 82 ifcs_create_t *_ifcs_create; 83 ifcs_destroy_t *_ifcs_destroy; 84 int _ifcs_minifs; /* minimum ifs */ 85 86 } S; 87 } U; 88 #define ifc_match U.A._ifc_match 89 #define ifc_create U.A._ifc_create 90 #define ifc_destroy U.A._ifc_destroy 91 #define ifcs_create U.S._ifcs_create 92 #define ifcs_destroy U.S._ifcs_destroy 93 #define ifcs_minifs U.S._ifcs_minifs 94 95 LIST_ENTRY(if_clone) ifc_list; /* (e) On list of cloners */ 96 }; 97 98 static void if_clone_free(struct if_clone *ifc); 99 static int if_clone_createif(struct if_clone *ifc, char *name, size_t len, 100 caddr_t params); 101 102 static int ifc_simple_match(struct if_clone *, const char *); 103 static int ifc_simple_create(struct if_clone *, char *, size_t, caddr_t); 104 static int ifc_simple_destroy(struct if_clone *, struct ifnet *); 105 106 static struct mtx if_cloners_mtx; 107 static VNET_DEFINE(int, if_cloners_count); 108 VNET_DEFINE(LIST_HEAD(, if_clone), if_cloners); 109 110 #define V_if_cloners_count VNET(if_cloners_count) 111 #define V_if_cloners VNET(if_cloners) 112 113 #define IF_CLONERS_LOCK_INIT() \ 114 mtx_init(&if_cloners_mtx, "if_cloners lock", NULL, MTX_DEF) 115 #define IF_CLONERS_LOCK_ASSERT() mtx_assert(&if_cloners_mtx, MA_OWNED) 116 #define IF_CLONERS_LOCK() mtx_lock(&if_cloners_mtx) 117 #define IF_CLONERS_UNLOCK() mtx_unlock(&if_cloners_mtx) 118 119 #define IF_CLONE_LOCK_INIT(ifc) \ 120 mtx_init(&(ifc)->ifc_mtx, "if_clone lock", NULL, MTX_DEF) 121 #define IF_CLONE_LOCK_DESTROY(ifc) mtx_destroy(&(ifc)->ifc_mtx) 122 #define IF_CLONE_LOCK_ASSERT(ifc) mtx_assert(&(ifc)->ifc_mtx, MA_OWNED) 123 #define IF_CLONE_LOCK(ifc) mtx_lock(&(ifc)->ifc_mtx) 124 #define IF_CLONE_UNLOCK(ifc) mtx_unlock(&(ifc)->ifc_mtx) 125 126 #define IF_CLONE_ADDREF(ifc) \ 127 do { \ 128 IF_CLONE_LOCK(ifc); \ 129 IF_CLONE_ADDREF_LOCKED(ifc); \ 130 IF_CLONE_UNLOCK(ifc); \ 131 } while (0) 132 #define IF_CLONE_ADDREF_LOCKED(ifc) \ 133 do { \ 134 IF_CLONE_LOCK_ASSERT(ifc); \ 135 KASSERT((ifc)->ifc_refcnt >= 0, \ 136 ("negative refcnt %ld", (ifc)->ifc_refcnt)); \ 137 (ifc)->ifc_refcnt++; \ 138 } while (0) 139 #define IF_CLONE_REMREF(ifc) \ 140 do { \ 141 IF_CLONE_LOCK(ifc); \ 142 IF_CLONE_REMREF_LOCKED(ifc); \ 143 } while (0) 144 #define IF_CLONE_REMREF_LOCKED(ifc) \ 145 do { \ 146 IF_CLONE_LOCK_ASSERT(ifc); \ 147 KASSERT((ifc)->ifc_refcnt > 0, \ 148 ("bogus refcnt %ld", (ifc)->ifc_refcnt)); \ 149 if (--(ifc)->ifc_refcnt == 0) { \ 150 IF_CLONE_UNLOCK(ifc); \ 151 if_clone_free(ifc); \ 152 } else { \ 153 /* silently free the lock */ \ 154 IF_CLONE_UNLOCK(ifc); \ 155 } \ 156 } while (0) 157 158 #define IFC_IFLIST_INSERT(_ifc, _ifp) \ 159 LIST_INSERT_HEAD(&_ifc->ifc_iflist, _ifp, if_clones) 160 #define IFC_IFLIST_REMOVE(_ifc, _ifp) \ 161 LIST_REMOVE(_ifp, if_clones) 162 163 static MALLOC_DEFINE(M_CLONE, "clone", "interface cloning framework"); 164 165 void 166 vnet_if_clone_init(void) 167 { 168 169 LIST_INIT(&V_if_cloners); 170 } 171 172 void 173 if_clone_init(void) 174 { 175 176 IF_CLONERS_LOCK_INIT(); 177 } 178 179 /* 180 * Lookup and create a clone network interface. 181 */ 182 int 183 if_clone_create(char *name, size_t len, caddr_t params) 184 { 185 struct if_clone *ifc; 186 187 /* Try to find an applicable cloner for this request */ 188 IF_CLONERS_LOCK(); 189 LIST_FOREACH(ifc, &V_if_cloners, ifc_list) 190 if (ifc->ifc_type == SIMPLE) { 191 if (ifc_simple_match(ifc, name)) 192 break; 193 } else { 194 if (ifc->ifc_match(ifc, name)) 195 break; 196 } 197 #ifdef VIMAGE 198 if (ifc == NULL && !IS_DEFAULT_VNET(curvnet)) { 199 CURVNET_SET_QUIET(vnet0); 200 LIST_FOREACH(ifc, &V_if_cloners, ifc_list) 201 if (ifc->ifc_type == SIMPLE) { 202 if (ifc_simple_match(ifc, name)) 203 break; 204 } else { 205 if (ifc->ifc_match(ifc, name)) 206 break; 207 } 208 CURVNET_RESTORE(); 209 } 210 #endif 211 IF_CLONERS_UNLOCK(); 212 213 if (ifc == NULL) 214 return (EINVAL); 215 216 return (if_clone_createif(ifc, name, len, params)); 217 } 218 219 /* 220 * Create a clone network interface. 221 */ 222 static int 223 if_clone_createif(struct if_clone *ifc, char *name, size_t len, caddr_t params) 224 { 225 int err; 226 struct ifnet *ifp; 227 228 if (ifunit(name) != NULL) 229 return (EEXIST); 230 231 if (ifc->ifc_type == SIMPLE) 232 err = ifc_simple_create(ifc, name, len, params); 233 else 234 err = (*ifc->ifc_create)(ifc, name, len, params); 235 236 if (!err) { 237 ifp = ifunit(name); 238 if (ifp == NULL) 239 panic("%s: lookup failed for %s", __func__, name); 240 241 if_addgroup(ifp, ifc->ifc_name); 242 243 IF_CLONE_LOCK(ifc); 244 IFC_IFLIST_INSERT(ifc, ifp); 245 IF_CLONE_UNLOCK(ifc); 246 } 247 248 return (err); 249 } 250 251 /* 252 * Lookup and destroy a clone network interface. 253 */ 254 int 255 if_clone_destroy(const char *name) 256 { 257 int err; 258 struct if_clone *ifc; 259 struct ifnet *ifp; 260 261 ifp = ifunit_ref(name); 262 if (ifp == NULL) 263 return (ENXIO); 264 265 /* Find the cloner for this interface */ 266 IF_CLONERS_LOCK(); 267 LIST_FOREACH(ifc, &V_if_cloners, ifc_list) { 268 if (strcmp(ifc->ifc_name, ifp->if_dname) == 0) { 269 break; 270 } 271 } 272 #ifdef VIMAGE 273 if (ifc == NULL && !IS_DEFAULT_VNET(curvnet)) { 274 CURVNET_SET_QUIET(vnet0); 275 LIST_FOREACH(ifc, &V_if_cloners, ifc_list) 276 if (ifc->ifc_type == SIMPLE) { 277 if (ifc_simple_match(ifc, name)) 278 break; 279 } else { 280 if (ifc->ifc_match(ifc, name)) 281 break; 282 } 283 CURVNET_RESTORE(); 284 } 285 #endif 286 IF_CLONERS_UNLOCK(); 287 if (ifc == NULL) { 288 if_rele(ifp); 289 return (EINVAL); 290 } 291 292 err = if_clone_destroyif(ifc, ifp); 293 if_rele(ifp); 294 return err; 295 } 296 297 /* 298 * Destroy a clone network interface. 299 */ 300 int 301 if_clone_destroyif(struct if_clone *ifc, struct ifnet *ifp) 302 { 303 int err; 304 struct ifnet *ifcifp; 305 306 if (ifc->ifc_type == ADVANCED && ifc->ifc_destroy == NULL) 307 return(EOPNOTSUPP); 308 309 /* 310 * Given that the cloned ifnet might be attached to a different 311 * vnet from where its cloner was registered, we have to 312 * switch to the vnet context of the target vnet. 313 */ 314 CURVNET_SET_QUIET(ifp->if_vnet); 315 316 IF_CLONE_LOCK(ifc); 317 LIST_FOREACH(ifcifp, &ifc->ifc_iflist, if_clones) { 318 if (ifcifp == ifp) { 319 IFC_IFLIST_REMOVE(ifc, ifp); 320 break; 321 } 322 } 323 IF_CLONE_UNLOCK(ifc); 324 if (ifcifp == NULL) { 325 CURVNET_RESTORE(); 326 return (ENXIO); /* ifp is not on the list. */ 327 } 328 329 if_delgroup(ifp, ifc->ifc_name); 330 331 if (ifc->ifc_type == SIMPLE) 332 err = ifc_simple_destroy(ifc, ifp); 333 else 334 err = (*ifc->ifc_destroy)(ifc, ifp); 335 336 if (err != 0) { 337 if_addgroup(ifp, ifc->ifc_name); 338 339 IF_CLONE_LOCK(ifc); 340 IFC_IFLIST_INSERT(ifc, ifp); 341 IF_CLONE_UNLOCK(ifc); 342 } 343 CURVNET_RESTORE(); 344 return (err); 345 } 346 347 static struct if_clone * 348 if_clone_alloc(const char *name, int maxunit) 349 { 350 struct if_clone *ifc; 351 352 KASSERT(name != NULL, ("%s: no name\n", __func__)); 353 354 ifc = malloc(sizeof(struct if_clone), M_CLONE, M_WAITOK | M_ZERO); 355 strncpy(ifc->ifc_name, name, IFCLOSIZ-1); 356 IF_CLONE_LOCK_INIT(ifc); 357 IF_CLONE_ADDREF(ifc); 358 ifc->ifc_maxunit = maxunit ? maxunit : IF_MAXUNIT; 359 ifc->ifc_unrhdr = new_unrhdr(0, ifc->ifc_maxunit, &ifc->ifc_mtx); 360 LIST_INIT(&ifc->ifc_iflist); 361 362 return (ifc); 363 } 364 365 static int 366 if_clone_attach(struct if_clone *ifc) 367 { 368 struct if_clone *ifc1; 369 370 IF_CLONERS_LOCK(); 371 LIST_FOREACH(ifc1, &V_if_cloners, ifc_list) 372 if (strcmp(ifc->ifc_name, ifc1->ifc_name) == 0) { 373 IF_CLONERS_UNLOCK(); 374 IF_CLONE_REMREF(ifc); 375 return (EEXIST); 376 } 377 LIST_INSERT_HEAD(&V_if_cloners, ifc, ifc_list); 378 V_if_cloners_count++; 379 IF_CLONERS_UNLOCK(); 380 381 return (0); 382 } 383 384 struct if_clone * 385 if_clone_advanced(const char *name, u_int maxunit, ifc_match_t match, 386 ifc_create_t create, ifc_destroy_t destroy) 387 { 388 struct if_clone *ifc; 389 390 ifc = if_clone_alloc(name, maxunit); 391 ifc->ifc_type = ADVANCED; 392 ifc->ifc_match = match; 393 ifc->ifc_create = create; 394 ifc->ifc_destroy = destroy; 395 396 if (if_clone_attach(ifc) != 0) { 397 if_clone_free(ifc); 398 return (NULL); 399 } 400 401 EVENTHANDLER_INVOKE(if_clone_event, ifc); 402 403 return (ifc); 404 } 405 406 struct if_clone * 407 if_clone_simple(const char *name, ifcs_create_t create, ifcs_destroy_t destroy, 408 u_int minifs) 409 { 410 struct if_clone *ifc; 411 u_int unit; 412 413 ifc = if_clone_alloc(name, 0); 414 ifc->ifc_type = SIMPLE; 415 ifc->ifcs_create = create; 416 ifc->ifcs_destroy = destroy; 417 ifc->ifcs_minifs = minifs; 418 419 if (if_clone_attach(ifc) != 0) { 420 if_clone_free(ifc); 421 return (NULL); 422 } 423 424 for (unit = 0; unit < minifs; unit++) { 425 char name[IFNAMSIZ]; 426 int error; 427 428 snprintf(name, IFNAMSIZ, "%s%d", ifc->ifc_name, unit); 429 error = if_clone_createif(ifc, name, IFNAMSIZ, NULL); 430 KASSERT(error == 0, 431 ("%s: failed to create required interface %s", 432 __func__, name)); 433 } 434 435 EVENTHANDLER_INVOKE(if_clone_event, ifc); 436 437 return (ifc); 438 } 439 440 /* 441 * Unregister a network interface cloner. 442 */ 443 void 444 if_clone_detach(struct if_clone *ifc) 445 { 446 447 IF_CLONERS_LOCK(); 448 LIST_REMOVE(ifc, ifc_list); 449 V_if_cloners_count--; 450 IF_CLONERS_UNLOCK(); 451 452 /* Allow all simples to be destroyed */ 453 if (ifc->ifc_type == SIMPLE) 454 ifc->ifcs_minifs = 0; 455 456 /* destroy all interfaces for this cloner */ 457 while (!LIST_EMPTY(&ifc->ifc_iflist)) 458 if_clone_destroyif(ifc, LIST_FIRST(&ifc->ifc_iflist)); 459 460 IF_CLONE_REMREF(ifc); 461 } 462 463 static void 464 if_clone_free(struct if_clone *ifc) 465 { 466 467 KASSERT(LIST_EMPTY(&ifc->ifc_iflist), 468 ("%s: ifc_iflist not empty", __func__)); 469 470 IF_CLONE_LOCK_DESTROY(ifc); 471 delete_unrhdr(ifc->ifc_unrhdr); 472 free(ifc, M_CLONE); 473 } 474 475 /* 476 * Provide list of interface cloners to userspace. 477 */ 478 int 479 if_clone_list(struct if_clonereq *ifcr) 480 { 481 char *buf, *dst, *outbuf = NULL; 482 struct if_clone *ifc; 483 int buf_count, count, err = 0; 484 485 if (ifcr->ifcr_count < 0) 486 return (EINVAL); 487 488 IF_CLONERS_LOCK(); 489 /* 490 * Set our internal output buffer size. We could end up not 491 * reporting a cloner that is added between the unlock and lock 492 * below, but that's not a major problem. Not caping our 493 * allocation to the number of cloners actually in the system 494 * could be because that would let arbitrary users cause us to 495 * allocate abritrary amounts of kernel memory. 496 */ 497 buf_count = (V_if_cloners_count < ifcr->ifcr_count) ? 498 V_if_cloners_count : ifcr->ifcr_count; 499 IF_CLONERS_UNLOCK(); 500 501 outbuf = malloc(IFNAMSIZ*buf_count, M_CLONE, M_WAITOK | M_ZERO); 502 503 IF_CLONERS_LOCK(); 504 505 ifcr->ifcr_total = V_if_cloners_count; 506 if ((dst = ifcr->ifcr_buffer) == NULL) { 507 /* Just asking how many there are. */ 508 goto done; 509 } 510 count = (V_if_cloners_count < buf_count) ? 511 V_if_cloners_count : buf_count; 512 513 for (ifc = LIST_FIRST(&V_if_cloners), buf = outbuf; 514 ifc != NULL && count != 0; 515 ifc = LIST_NEXT(ifc, ifc_list), count--, buf += IFNAMSIZ) { 516 strlcpy(buf, ifc->ifc_name, IFNAMSIZ); 517 } 518 519 done: 520 IF_CLONERS_UNLOCK(); 521 if (err == 0) 522 err = copyout(outbuf, dst, buf_count*IFNAMSIZ); 523 if (outbuf != NULL) 524 free(outbuf, M_CLONE); 525 return (err); 526 } 527 528 /* 529 * A utility function to extract unit numbers from interface names of 530 * the form name###. 531 * 532 * Returns 0 on success and an error on failure. 533 */ 534 int 535 ifc_name2unit(const char *name, int *unit) 536 { 537 const char *cp; 538 int cutoff = INT_MAX / 10; 539 int cutlim = INT_MAX % 10; 540 541 for (cp = name; *cp != '\0' && (*cp < '0' || *cp > '9'); cp++); 542 if (*cp == '\0') { 543 *unit = -1; 544 } else if (cp[0] == '0' && cp[1] != '\0') { 545 /* Disallow leading zeroes. */ 546 return (EINVAL); 547 } else { 548 for (*unit = 0; *cp != '\0'; cp++) { 549 if (*cp < '0' || *cp > '9') { 550 /* Bogus unit number. */ 551 return (EINVAL); 552 } 553 if (*unit > cutoff || 554 (*unit == cutoff && *cp - '0' > cutlim)) 555 return (EINVAL); 556 *unit = (*unit * 10) + (*cp - '0'); 557 } 558 } 559 560 return (0); 561 } 562 563 int 564 ifc_alloc_unit(struct if_clone *ifc, int *unit) 565 { 566 char name[IFNAMSIZ]; 567 int wildcard; 568 569 wildcard = (*unit < 0); 570 retry: 571 if (*unit > ifc->ifc_maxunit) 572 return (ENOSPC); 573 if (*unit < 0) { 574 *unit = alloc_unr(ifc->ifc_unrhdr); 575 if (*unit == -1) 576 return (ENOSPC); 577 } else { 578 *unit = alloc_unr_specific(ifc->ifc_unrhdr, *unit); 579 if (*unit == -1) { 580 if (wildcard) { 581 (*unit)++; 582 goto retry; 583 } else 584 return (EEXIST); 585 } 586 } 587 588 snprintf(name, IFNAMSIZ, "%s%d", ifc->ifc_name, *unit); 589 if (ifunit(name) != NULL) { 590 free_unr(ifc->ifc_unrhdr, *unit); 591 if (wildcard) { 592 (*unit)++; 593 goto retry; 594 } else 595 return (EEXIST); 596 } 597 598 IF_CLONE_ADDREF(ifc); 599 600 return (0); 601 } 602 603 void 604 ifc_free_unit(struct if_clone *ifc, int unit) 605 { 606 607 free_unr(ifc->ifc_unrhdr, unit); 608 IF_CLONE_REMREF(ifc); 609 } 610 611 static int 612 ifc_simple_match(struct if_clone *ifc, const char *name) 613 { 614 const char *cp; 615 int i; 616 617 /* Match the name */ 618 for (cp = name, i = 0; i < strlen(ifc->ifc_name); i++, cp++) { 619 if (ifc->ifc_name[i] != *cp) 620 return (0); 621 } 622 623 /* Make sure there's a unit number or nothing after the name */ 624 for (; *cp != '\0'; cp++) { 625 if (*cp < '0' || *cp > '9') 626 return (0); 627 } 628 629 return (1); 630 } 631 632 static int 633 ifc_simple_create(struct if_clone *ifc, char *name, size_t len, caddr_t params) 634 { 635 char *dp; 636 int wildcard; 637 int unit; 638 int err; 639 640 err = ifc_name2unit(name, &unit); 641 if (err != 0) 642 return (err); 643 644 wildcard = (unit < 0); 645 646 err = ifc_alloc_unit(ifc, &unit); 647 if (err != 0) 648 return (err); 649 650 err = ifc->ifcs_create(ifc, unit, params); 651 if (err != 0) { 652 ifc_free_unit(ifc, unit); 653 return (err); 654 } 655 656 /* In the wildcard case, we need to update the name. */ 657 if (wildcard) { 658 for (dp = name; *dp != '\0'; dp++); 659 if (snprintf(dp, len - (dp-name), "%d", unit) > 660 len - (dp-name) - 1) { 661 /* 662 * This can only be a programmer error and 663 * there's no straightforward way to recover if 664 * it happens. 665 */ 666 panic("if_clone_create(): interface name too long"); 667 } 668 669 } 670 671 return (0); 672 } 673 674 static int 675 ifc_simple_destroy(struct if_clone *ifc, struct ifnet *ifp) 676 { 677 int unit; 678 679 unit = ifp->if_dunit; 680 681 if (unit < ifc->ifcs_minifs) 682 return (EINVAL); 683 684 ifc->ifcs_destroy(ifp); 685 686 ifc_free_unit(ifc, unit); 687 688 return (0); 689 } 690