1 /* 2 * Copyright (c) 2015 -2018 The DragonFly Project. All rights reserved. 3 * 4 * This code is derived from software contributed to The DragonFly Project 5 * by Bill Yuan <bycn82@dragonflybsd.org> 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in 15 * the documentation and/or other materials provided with the 16 * distribution. 17 * 3. Neither the name of The DragonFly Project nor the names of its 18 * contributors may be used to endorse or promote products derived 19 * from this software without specific, prior written permission. 20 * 21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 23 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 24 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 25 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 26 * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, 27 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 28 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 29 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 30 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 31 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 32 * SUCH DAMAGE. 33 * 34 */ 35 36 #include <sys/types.h> 37 #include <sys/param.h> 38 #include <sys/systm.h> 39 #include <sys/malloc.h> 40 #include <sys/mbuf.h> 41 #include <sys/kernel.h> 42 #include <sys/proc.h> 43 #include <sys/socket.h> 44 #include <sys/socketvar.h> 45 #include <sys/sysctl.h> 46 #include <sys/syslog.h> 47 #include <sys/ucred.h> 48 #include <sys/in_cksum.h> 49 #include <sys/lock.h> 50 51 #include <net/if.h> 52 #include <net/route.h> 53 #include <net/pfil.h> 54 #include <net/netmsg2.h> 55 #include <net/ethernet.h> 56 57 #include <netinet/in.h> 58 #include <netinet/in_systm.h> 59 #include <netinet/in_var.h> 60 #include <netinet/in_pcb.h> 61 #include <netinet/ip.h> 62 #include <netinet/ip_var.h> 63 #include <netinet/ip_icmp.h> 64 #include <netinet/tcp.h> 65 #include <netinet/tcp_timer.h> 66 #include <netinet/tcp_var.h> 67 #include <netinet/tcpip.h> 68 #include <netinet/udp.h> 69 #include <netinet/udp_var.h> 70 #include <netinet/ip_divert.h> 71 #include <netinet/if_ether.h> 72 73 #include <net/ipfw3/ip_fw.h> 74 #include <net/ipfw3_basic/ip_fw3_table.h> 75 76 MALLOC_DEFINE(M_IPFW3_TABLE, "IPFW3_TABLE", "mem for ip_fw3 table"); 77 78 extern struct ipfw3_context *fw3_ctx[MAXCPU]; 79 extern ip_fw_ctl_t *ip_fw3_ctl_table_ptr; 80 81 /* 82 * activate/create the table by setup the type and reset counts. 83 */ 84 void 85 table_create_dispatch(netmsg_t nmsg) 86 { 87 struct netmsg_table *tbmsg = (struct netmsg_table *)nmsg; 88 struct ipfw_ioc_table *ioc_table; 89 struct ipfw3_context *ctx = fw3_ctx[mycpuid]; 90 struct ipfw3_table_context *table_ctx; 91 ioc_table = tbmsg->ioc_table; 92 int id = ioc_table->id; 93 94 table_ctx = ctx->table_ctx; 95 table_ctx += id; 96 table_ctx->type = ioc_table->type; 97 table_ctx->count = 0; 98 strlcpy(table_ctx->name , ioc_table->name, IPFW_TABLE_NAME_LEN); 99 if (table_ctx->type == 1) { 100 rn_inithead(&table_ctx->mask, NULL, 0); 101 rn_inithead(&table_ctx->node, table_ctx->mask, 102 offsetof(struct sockaddr_in, sin_addr)); 103 } else if (table_ctx->type == 2) { 104 rn_inithead(&table_ctx->mask, NULL, 0); 105 rn_inithead(&table_ctx->node, table_ctx->mask, 106 offsetof(struct sockaddr, sa_data)); 107 } else { 108 goto done; 109 } 110 done: 111 netisr_forwardmsg_all(&nmsg->base, mycpuid + 1); 112 } 113 114 /* 115 * clean the table, especially the node 116 */ 117 void 118 table_delete_dispatch(netmsg_t nmsg) 119 { 120 struct netmsg_table *tbmsg = (struct netmsg_table *)nmsg; 121 struct ipfw_ioc_table *ioc_tbl; 122 struct ipfw3_context *ctx = fw3_ctx[mycpuid]; 123 struct ipfw3_table_context *table_ctx; 124 125 ioc_tbl = tbmsg->ioc_table; 126 table_ctx = ctx->table_ctx; 127 table_ctx += ioc_tbl->id; 128 table_ctx->count = 0; 129 130 rn_flush(table_ctx->node, flush_table_entry); 131 /* XXX: should free the tree: rn_freehead(table_ctx->node) */ 132 table_ctx->type = 0; 133 netisr_forwardmsg_all(&nmsg->base, mycpuid + 1); 134 } 135 136 void 137 table_append_dispatch(netmsg_t nmsg) 138 { 139 struct netmsg_table *tbmsg = (struct netmsg_table *)nmsg; 140 struct ipfw_ioc_table *ioc_tbl; 141 struct ipfw3_context *ctx = fw3_ctx[mycpuid]; 142 struct ipfw3_table_context *table_ctx; 143 struct radix_node_head *rnh; 144 145 uint8_t mlen; 146 147 ioc_tbl = tbmsg->ioc_table; 148 table_ctx = ctx->table_ctx; 149 table_ctx += ioc_tbl->id; 150 if (table_ctx->type != ioc_tbl->type) 151 goto done; 152 153 if (table_ctx->type == 1 ) { 154 struct table_ip_entry *ent; 155 156 rnh = table_ctx->node; 157 ent = kmalloc(sizeof(struct table_ip_entry), 158 M_IPFW3_TABLE, M_NOWAIT | M_ZERO); 159 160 if (ent == NULL) 161 return; 162 mlen = ioc_tbl->ip_ent->masklen; 163 ent->addr.sin_len = sizeof(ent->addr); 164 ent->mask.sin_len = sizeof(ent->mask); 165 ent->mask.sin_addr.s_addr = htonl(~((1 << (32 - mlen)) - 1)); 166 ent->addr.sin_addr.s_addr = ioc_tbl->ip_ent->addr & 167 ent->mask.sin_addr.s_addr; 168 169 if (rnh->rnh_addaddr((char *)&ent->addr, 170 (char *)&ent->mask, rnh, 171 (void *)ent->rn) != NULL) { 172 table_ctx->count++; 173 } 174 } else if (table_ctx->type == 2 ) { 175 struct table_mac_entry *ent; 176 177 rnh = table_ctx->node; 178 ent = kmalloc(sizeof(struct table_mac_entry), 179 M_IPFW3_TABLE, M_NOWAIT | M_ZERO); 180 if (ent == NULL) 181 return; 182 ent->addr.sa_len = offsetof(struct sockaddr, sa_data[6]); 183 strncpy(ent->addr.sa_data, ioc_tbl->mac_ent->addr.octet, 6); 184 185 if (rnh->rnh_addaddr((char *)&ent->addr, 186 NULL, rnh, (void *)ent->rn) != NULL) { 187 table_ctx->count++; 188 } 189 } 190 191 done: 192 netisr_forwardmsg_all(&nmsg->base, mycpuid + 1); 193 } 194 195 void 196 table_remove_dispatch(netmsg_t nmsg) 197 { 198 struct netmsg_table *tbmsg = (struct netmsg_table *)nmsg; 199 struct ipfw_ioc_table *ioc_tbl; 200 struct ipfw3_context *ctx = fw3_ctx[mycpuid]; 201 struct ipfw3_table_context *table_ctx; 202 struct radix_node_head *rnh; 203 struct table_entry *ent; 204 struct sockaddr_in sa, mask; 205 in_addr_t addr; 206 uint8_t mlen; 207 208 ioc_tbl = tbmsg->ioc_table; 209 table_ctx = ctx->table_ctx; 210 table_ctx += ioc_tbl->id; 211 if (table_ctx->type != ioc_tbl->type) 212 goto done; 213 214 rnh = table_ctx->node; 215 216 mlen = ioc_tbl->ip_ent->masklen; 217 addr = ioc_tbl->ip_ent->addr; 218 219 sa.sin_len = mask.sin_len = 8; 220 mask.sin_addr.s_addr = htonl(mlen ? ~((1 << (32 - mlen)) - 1) : 0); 221 sa.sin_addr.s_addr = addr & mask.sin_addr.s_addr; 222 223 ent = (struct table_entry *)rnh->rnh_deladdr((char *)&sa, (char *)&mask, rnh); 224 if (ent != NULL) { 225 table_ctx->count--; 226 kfree(ent, M_IPFW3_TABLE); 227 } 228 done: 229 netisr_forwardmsg_all(&nmsg->base, mycpuid + 1); 230 } 231 232 void 233 flush_table_entry(struct radix_node *rn) 234 { 235 kfree(rn, M_IPFW3_TABLE); 236 } 237 238 void 239 table_flush_dispatch(netmsg_t nmsg) 240 { 241 struct netmsg_table *tbmsg = (struct netmsg_table *)nmsg; 242 struct ipfw_ioc_table *ioc_tbl; 243 struct ipfw3_context *ctx = fw3_ctx[mycpuid]; 244 struct ipfw3_table_context *table_ctx; 245 struct radix_node_head *rnh; 246 247 ioc_tbl = tbmsg->ioc_table; 248 table_ctx = ctx->table_ctx; 249 table_ctx += ioc_tbl->id; 250 rnh = table_ctx->node; 251 table_ctx->count = 0; 252 253 rn_flush(rnh, flush_table_entry); 254 netisr_forwardmsg_all(&nmsg->base, mycpuid + 1); 255 } 256 257 /* 258 * rename the table 259 */ 260 void 261 table_rename_dispatch(netmsg_t nmsg) 262 { 263 struct netmsg_table *tbmsg = (struct netmsg_table *)nmsg; 264 struct ipfw_ioc_table *ioc_tbl; 265 struct ipfw3_context *ctx = fw3_ctx[mycpuid]; 266 struct ipfw3_table_context *table_ctx; 267 268 ioc_tbl = tbmsg->ioc_table; 269 table_ctx = ctx->table_ctx; 270 table_ctx += ioc_tbl->id; 271 strlcpy(table_ctx->name, ioc_tbl->name, IPFW_TABLE_NAME_LEN); 272 netisr_forwardmsg_all(&nmsg->base, mycpuid + 1); 273 } 274 275 /* 276 * list all the overview information about each table 277 */ 278 int 279 ip_fw3_ctl_table_list(struct sockopt *sopt) 280 { 281 struct ipfw3_context *ctx = fw3_ctx[mycpuid]; 282 struct ipfw3_table_context *table_ctx = ctx->table_ctx; 283 struct ipfw_ioc_table *ioc_table; 284 int i, error = 0, size; 285 286 size = IPFW_TABLES_MAX * sizeof(struct ipfw_ioc_table); 287 if (sopt->sopt_valsize < size) { 288 /* sopt_val is not big enough */ 289 bzero(sopt->sopt_val, sopt->sopt_valsize); 290 return 0; 291 } 292 ioc_table = (struct ipfw_ioc_table *)sopt->sopt_val; 293 for (i = 0; i < IPFW_TABLES_MAX; i++, ioc_table++, table_ctx++) { 294 ioc_table->id = i; 295 ioc_table->type = table_ctx->type; 296 ioc_table->count = table_ctx->count; 297 strlcpy(ioc_table->name, table_ctx->name, IPFW_TABLE_NAME_LEN); 298 } 299 sopt->sopt_valsize = size; 300 return error; 301 } 302 303 /* 304 * remove an item from the table 305 */ 306 int 307 ip_fw3_ctl_table_remove(struct sockopt *sopt) 308 { 309 struct netmsg_table tbmsg; 310 bzero(&tbmsg,sizeof(tbmsg)); 311 tbmsg.ioc_table = sopt->sopt_val; 312 netmsg_init(&tbmsg.base, NULL, &curthread->td_msgport, 313 0, table_remove_dispatch); 314 netisr_domsg(&tbmsg.base, 0); 315 return tbmsg.retval; 316 } 317 318 /* 319 * flush everything inside the table 320 */ 321 int 322 ip_fw3_ctl_table_flush(struct sockopt *sopt) 323 { 324 struct netmsg_table tbmsg; 325 bzero(&tbmsg,sizeof(tbmsg)); 326 tbmsg.ioc_table = sopt->sopt_val; 327 netmsg_init(&tbmsg.base, NULL, &curthread->td_msgport, 328 0, table_flush_dispatch); 329 netisr_domsg(&tbmsg.base, 0); 330 return tbmsg.retval; 331 } 332 333 /* 334 * dump the entries into the ioc_table 335 */ 336 int 337 dump_table_ip_entry(struct radix_node *rn, void *arg) 338 { 339 struct table_ip_entry *ent = (struct table_ip_entry *)rn; 340 struct ipfw_ioc_table_ip_entry *ioc_ent; 341 struct ipfw_ioc_table *tbl = arg; 342 struct sockaddr_in *addr, *mask; 343 344 addr = &ent->addr; 345 mask = &ent->mask; 346 347 ioc_ent = &tbl->ip_ent[tbl->count]; 348 if (in_nullhost(mask->sin_addr)) 349 ioc_ent->masklen = 0; 350 else 351 ioc_ent->masklen = 33 - ffs(ntohl(mask->sin_addr.s_addr)); 352 ioc_ent->addr = addr->sin_addr.s_addr; 353 tbl->count++; 354 return (0); 355 } 356 357 int 358 dump_table_mac_entry(struct radix_node *rn, void *arg) 359 { 360 struct table_mac_entry *ent = (struct table_mac_entry *)rn; 361 struct ipfw_ioc_table_mac_entry *ioc_ent; 362 struct ipfw_ioc_table *tbl = arg; 363 ioc_ent = &tbl->mac_ent[tbl->count]; 364 strncpy(ioc_ent->addr.octet, ent->addr.sa_data, 6); 365 tbl->count++; 366 return (0); 367 } 368 369 /* 370 * get and display all items in the table 371 */ 372 int 373 ip_fw3_ctl_table_show(struct sockopt *sopt) 374 { 375 struct ipfw3_context *ctx = fw3_ctx[mycpuid]; 376 struct ipfw3_table_context *table_ctx; 377 struct radix_node_head *rnh; 378 struct ipfw_ioc_table *tbl; 379 void *data; 380 int size; 381 382 int *id = (int *)sopt->sopt_val; 383 table_ctx = ctx->table_ctx; 384 table_ctx += *id; 385 if (table_ctx->type == 1) { 386 size = table_ctx->count * sizeof(struct ipfw_ioc_table_ip_entry) + 387 sizeof(struct ipfw_ioc_table); 388 if (sopt->sopt_valsize < size) { 389 /* sopt_val is not big enough */ 390 bzero(sopt->sopt_val, sopt->sopt_valsize); 391 return 0; 392 } 393 data = kmalloc(size, M_IPFW3_TABLE, M_NOWAIT | M_ZERO); 394 tbl = (struct ipfw_ioc_table *)data; 395 tbl->id = *id; 396 tbl->type = table_ctx->type; 397 strlcpy(tbl->name, table_ctx->name, IPFW_TABLE_NAME_LEN); 398 rnh = table_ctx->node; 399 rnh->rnh_walktree(rnh, dump_table_ip_entry, tbl); 400 bcopy(tbl, sopt->sopt_val, size); 401 sopt->sopt_valsize = size; 402 kfree(data, M_IPFW3_TABLE); 403 } else if (table_ctx->type == 2) { 404 size = table_ctx->count * sizeof(struct ipfw_ioc_table_mac_entry) + 405 sizeof(struct ipfw_ioc_table); 406 if (sopt->sopt_valsize < size) { 407 /* sopt_val is not big enough */ 408 bzero(sopt->sopt_val, sopt->sopt_valsize); 409 return 0; 410 } 411 data = kmalloc(size, M_IPFW3_TABLE, M_NOWAIT | M_ZERO); 412 tbl = (struct ipfw_ioc_table *)data; 413 tbl->id = *id; 414 tbl->type = table_ctx->type; 415 strlcpy(tbl->name, table_ctx->name, IPFW_TABLE_NAME_LEN); 416 rnh = table_ctx->node; 417 rnh->rnh_walktree(rnh, dump_table_mac_entry, tbl); 418 bcopy(tbl, sopt->sopt_val, size); 419 sopt->sopt_valsize = size; 420 kfree(data, M_IPFW3_TABLE); 421 } 422 return 0; 423 } 424 425 /* 426 * test whether the ip is in the table 427 */ 428 int 429 ip_fw3_ctl_table_test(struct sockopt *sopt) 430 { 431 struct ipfw3_context *ctx = fw3_ctx[mycpuid]; 432 struct ipfw3_table_context *table_ctx; 433 struct radix_node_head *rnh; 434 struct ipfw_ioc_table *tbl; 435 436 tbl = (struct ipfw_ioc_table *)sopt->sopt_val; 437 table_ctx = ctx->table_ctx; 438 table_ctx += tbl->id; 439 440 if (table_ctx->type != tbl->type) 441 goto done; 442 443 rnh = table_ctx->node; 444 if (tbl->type == 1) { 445 struct sockaddr_in sa; 446 sa.sin_len = 8; 447 sa.sin_addr.s_addr = tbl->ip_ent->addr; 448 449 if(rnh->rnh_lookup((char *)&sa, NULL, rnh) != NULL) 450 return 0; 451 } else if (tbl->type == 2) { 452 struct sockaddr sa; 453 sa.sa_len = 8; 454 strncpy(sa.sa_data, tbl->mac_ent->addr.octet, 6); 455 456 if(rnh->rnh_lookup((char *)&sa, NULL, rnh) != NULL) 457 return 0; 458 } else { 459 /* XXX TODO */ 460 } 461 done: 462 return 1; 463 } 464 465 /* 466 * activate the table 467 */ 468 int 469 ip_fw3_ctl_table_create(struct sockopt *sopt) 470 { 471 struct netmsg_table tbmsg; 472 bzero(&tbmsg,sizeof(tbmsg)); 473 tbmsg.ioc_table = sopt->sopt_val; 474 netmsg_init(&tbmsg.base, NULL, &curthread->td_msgport, 475 0, table_create_dispatch); 476 netisr_domsg(&tbmsg.base, 0); 477 return tbmsg.retval; 478 } 479 480 /* 481 * deactivate the table 482 */ 483 int 484 ip_fw3_ctl_table_delete(struct sockopt *sopt) 485 { 486 struct netmsg_table tbmsg; 487 bzero(&tbmsg,sizeof(tbmsg)); 488 tbmsg.ioc_table = sopt->sopt_val; 489 netmsg_init(&tbmsg.base, NULL, &curthread->td_msgport, 490 0, table_delete_dispatch); 491 netisr_domsg(&tbmsg.base, 0); 492 return tbmsg.retval; 493 } 494 495 /* 496 * append an item into the table 497 */ 498 int 499 ip_fw3_ctl_table_append(struct sockopt *sopt) 500 { 501 struct netmsg_table tbmsg; 502 bzero(&tbmsg,sizeof(tbmsg)); 503 tbmsg.ioc_table = sopt->sopt_val; 504 netmsg_init(&tbmsg.base, NULL, &curthread->td_msgport, 505 0, table_append_dispatch); 506 netisr_domsg(&tbmsg.base, 0); 507 return tbmsg.retval; 508 } 509 510 /* 511 * rename an table 512 */ 513 int 514 ip_fw3_ctl_table_rename(struct sockopt *sopt) 515 { 516 struct netmsg_table tbmsg; 517 bzero(&tbmsg,sizeof(tbmsg)); 518 tbmsg.ioc_table = sopt->sopt_val; 519 netmsg_init(&tbmsg.base, NULL, &curthread->td_msgport, 520 0, table_rename_dispatch); 521 netisr_domsg(&tbmsg.base, 0); 522 return tbmsg.retval; 523 } 524 525 /* 526 * sockopt handler 527 */ 528 int 529 ip_fw3_ctl_table_sockopt(struct sockopt *sopt) 530 { 531 int error = 0; 532 switch (sopt->sopt_name) { 533 case IP_FW_TABLE_CREATE: 534 error = ip_fw3_ctl_table_create(sopt); 535 break; 536 case IP_FW_TABLE_DELETE: 537 error = ip_fw3_ctl_table_delete(sopt); 538 break; 539 case IP_FW_TABLE_APPEND: 540 error = ip_fw3_ctl_table_append(sopt); 541 break; 542 case IP_FW_TABLE_REMOVE: 543 error = ip_fw3_ctl_table_remove(sopt); 544 break; 545 case IP_FW_TABLE_LIST: 546 error = ip_fw3_ctl_table_list(sopt); 547 break; 548 case IP_FW_TABLE_FLUSH: 549 error = ip_fw3_ctl_table_flush(sopt); 550 break; 551 case IP_FW_TABLE_SHOW: 552 error = ip_fw3_ctl_table_show(sopt); 553 break; 554 case IP_FW_TABLE_TEST: 555 error = ip_fw3_ctl_table_test(sopt); 556 break; 557 case IP_FW_TABLE_RENAME: 558 error = ip_fw3_ctl_table_rename(sopt); 559 break; 560 default: 561 kprintf("ipfw table invalid socket option %d\n", 562 sopt->sopt_name); 563 } 564 return error; 565 } 566 567 /* 568 * it will be invoked during init of ipfw3 569 * this function will prepare the tables 570 */ 571 void 572 ip_fw3_table_init_dispatch(netmsg_t nmsg) 573 { 574 struct ipfw3_context *ctx = fw3_ctx[mycpuid]; 575 ctx->table_ctx = kmalloc(sizeof(struct ipfw3_table_context) * IPFW_TABLES_MAX, 576 M_IPFW3_TABLE, M_WAITOK | M_ZERO); 577 netisr_forwardmsg_all(&nmsg->base, mycpuid + 1); 578 } 579 580 void 581 ip_fw3_table_fini_dispatch(netmsg_t nmsg) 582 { 583 struct ipfw3_table_context *table_ctx, *tmp_table; 584 int id; 585 table_ctx = fw3_ctx[mycpuid]->table_ctx; 586 tmp_table = table_ctx; 587 for (id = 0; id < IPFW_TABLES_MAX; id++, table_ctx++) { 588 rn_flush(table_ctx->node, flush_table_entry); 589 /* XXX: should free the tree: rn_freehead(table_ctx->node) */ 590 } 591 kfree(tmp_table, M_IPFW3_TABLE); 592 593 netisr_forwardmsg_all(&nmsg->base, mycpuid + 1); 594 } 595 596 597 void 598 ip_fw3_table_fini(void) 599 { 600 struct netmsg_base msg; 601 602 netmsg_init(&msg, NULL, &curthread->td_msgport, 603 0, ip_fw3_table_fini_dispatch); 604 605 netisr_domsg(&msg, 0); 606 } 607 608 void 609 ip_fw3_table_init(void) 610 { 611 struct netmsg_base msg; 612 613 ip_fw3_ctl_table_ptr = ip_fw3_ctl_table_sockopt; 614 netmsg_init(&msg, NULL, &curthread->td_msgport, 615 0, ip_fw3_table_init_dispatch); 616 netisr_domsg(&msg, 0); 617 } 618 619 620 void 621 ip_fw3_table_modevent(int type) 622 { 623 switch (type) { 624 case MOD_LOAD: 625 ip_fw3_table_init(); 626 break; 627 case MOD_UNLOAD: 628 ip_fw3_table_fini(); 629 break; 630 } 631 } 632