1 /* 2 * Copyright 2006, Haiku, Inc. All Rights Reserved. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Axel Dörfler, axeld@pinc-software.de 7 * Andrew Galante, haiku.galante@gmail.com 8 */ 9 10 11 #include "EndpointManager.h" 12 #include "TCPEndpoint.h" 13 14 #include <net_protocol.h> 15 #include <net_stat.h> 16 17 #include <KernelExport.h> 18 #include <util/list.h> 19 20 #include <netinet/in.h> 21 #include <netinet/ip.h> 22 #include <new> 23 #include <stdlib.h> 24 #include <string.h> 25 26 #include <lock.h> 27 #include <util/AutoLock.h> 28 29 #include <NetBufferUtilities.h> 30 #include <NetUtilities.h> 31 32 //#define TRACE_TCP 33 #ifdef TRACE_TCP 34 # define TRACE(x) dprintf x 35 # define TRACE_BLOCK(x) dump_block x 36 #else 37 # define TRACE(x) 38 # define TRACE_BLOCK(x) 39 #endif 40 41 42 typedef NetBufferField<uint16, offsetof(tcp_header, checksum)> TCPChecksumField; 43 44 45 net_buffer_module_info *gBufferModule; 46 net_datalink_module_info *gDatalinkModule; 47 net_socket_module_info *gSocketModule; 48 net_stack_module_info *gStackModule; 49 50 51 // TODO we need to think of a better way to do this. It would be 52 // nice if we registered a per EndpointManager receiving 53 // protocol cookie, so we don't have to go through the list 54 // for each segment. 55 typedef DoublyLinkedList<EndpointManager> EndpointManagerList; 56 static mutex sEndpointManagersLock; 57 static EndpointManagerList sEndpointManagers; 58 59 60 // The TCP header length is at most 64 bytes. 61 static const int kMaxOptionSize = 64 - sizeof(tcp_header); 62 63 64 static EndpointManager * 65 endpoint_manager_for(net_domain *domain) 66 { 67 EndpointManagerList::Iterator iterator = sEndpointManagers.GetIterator(); 68 while (iterator.HasNext()) { 69 EndpointManager *endpointManager = iterator.Next(); 70 if (endpointManager->Domain() == domain) 71 return endpointManager; 72 } 73 74 return NULL; 75 } 76 77 78 EndpointManager * 79 create_endpoint_manager(net_domain *domain) 80 { 81 EndpointManager *endpointManager = endpoint_manager_for(domain); 82 if (endpointManager) 83 return endpointManager; 84 85 endpointManager = new (std::nothrow) EndpointManager(domain); 86 if (endpointManager) 87 sEndpointManagers.Add(endpointManager); 88 89 return endpointManager; 90 } 91 92 93 void 94 return_endpoint_manager(EndpointManager *endpointManager) 95 { 96 // TODO when the connection and endpoint count reach zero 97 // we should remove the endpoint manager from the endpoints 98 // list and delete it. 99 } 100 101 102 static inline void 103 bump_option(tcp_option *&option, size_t &length) 104 { 105 if (option->kind <= TCP_OPTION_NOP) { 106 length++; 107 option = (tcp_option *)((uint8 *)option + 1); 108 } else { 109 length += option->length; 110 option = (tcp_option *)((uint8 *)option + option->length); 111 } 112 } 113 114 115 static inline size_t 116 add_options(tcp_segment_header &segment, uint8 *buffer, size_t bufferSize) 117 { 118 tcp_option *option = (tcp_option *)buffer; 119 size_t length = 0; 120 121 if (segment.max_segment_size > 0 && length + 8 <= bufferSize) { 122 option->kind = TCP_OPTION_MAX_SEGMENT_SIZE; 123 option->length = 4; 124 option->max_segment_size = htons(segment.max_segment_size); 125 bump_option(option, length); 126 } 127 128 if ((segment.options & TCP_HAS_TIMESTAMPS) 129 && length + 12 <= bufferSize) { 130 // two NOPs so the timestamps get aligned to a 4 byte boundary 131 option->kind = TCP_OPTION_NOP; 132 bump_option(option, length); 133 option->kind = TCP_OPTION_NOP; 134 bump_option(option, length); 135 option->kind = TCP_OPTION_TIMESTAMP; 136 option->length = 10; 137 option->timestamp.timestamp_value = htonl(segment.timestamp_value); 138 // TSecr is opaque to us, we send it as we received it. 139 option->timestamp.timestamp_reply = segment.timestamp_reply; 140 bump_option(option, length); 141 } 142 143 if ((segment.options & TCP_HAS_WINDOW_SCALE) 144 && length + 4 <= bufferSize) { 145 // insert one NOP so that the subsequent data is aligned on a 4 byte boundary 146 option->kind = TCP_OPTION_NOP; 147 bump_option(option, length); 148 149 option->kind = TCP_OPTION_WINDOW_SHIFT; 150 option->length = 3; 151 option->window_shift = segment.window_shift; 152 bump_option(option, length); 153 } 154 155 if ((segment.options & TCP_SACK_PERMITTED) 156 && length + 2 <= bufferSize) { 157 option->kind = TCP_OPTION_SACK_PERMITTED; 158 option->length = 2; 159 bump_option(option, length); 160 } 161 162 if (segment.sack_count > 0) { 163 int sackCount = ((int)(bufferSize - length) - 4) / sizeof(tcp_sack); 164 if (sackCount > segment.sack_count) 165 sackCount = segment.sack_count; 166 167 if (sackCount > 0) { 168 option->kind = TCP_OPTION_NOP; 169 bump_option(option, length); 170 option->kind = TCP_OPTION_NOP; 171 bump_option(option, length); 172 option->kind = TCP_OPTION_SACK; 173 option->length = 2 + sackCount * sizeof(tcp_sack); 174 memcpy(option->sack, segment.sacks, sackCount * sizeof(tcp_sack)); 175 bump_option(option, length); 176 } 177 } 178 179 if ((length & 3) == 0) { 180 // options completely fill out the option space 181 return length; 182 } 183 184 option->kind = TCP_OPTION_END; 185 return (length + 3) & ~3; 186 // bump to a multiple of 4 length 187 } 188 189 190 /*! 191 Constructs a TCP header on \a buffer with the specified values 192 for \a flags, \a seq \a ack and \a advertisedWindow. 193 */ 194 status_t 195 add_tcp_header(net_address_module_info *addressModule, 196 tcp_segment_header &segment, net_buffer *buffer) 197 { 198 buffer->protocol = IPPROTO_TCP; 199 200 uint8 optionsBuffer[kMaxOptionSize]; 201 uint32 optionsLength = add_options(segment, optionsBuffer, sizeof(optionsBuffer)); 202 203 NetBufferPrepend<tcp_header> bufferHeader(buffer, sizeof(tcp_header) + optionsLength); 204 if (bufferHeader.Status() != B_OK) 205 return bufferHeader.Status(); 206 207 tcp_header &header = bufferHeader.Data(); 208 209 header.source_port = addressModule->get_port(buffer->source); 210 header.destination_port = addressModule->get_port(buffer->destination); 211 header.sequence = htonl(segment.sequence); 212 header.acknowledge = (segment.flags & TCP_FLAG_ACKNOWLEDGE) 213 ? htonl(segment.acknowledge) : 0; 214 header.reserved = 0; 215 header.header_length = (sizeof(tcp_header) + optionsLength) >> 2; 216 header.flags = segment.flags; 217 header.advertised_window = htons(segment.advertised_window); 218 header.checksum = 0; 219 header.urgent_offset = segment.urgent_offset; 220 221 // we must detach before calculating the checksum as we may 222 // not have a contiguous buffer. 223 bufferHeader.Sync(); 224 225 if (optionsLength > 0) 226 gBufferModule->write(buffer, sizeof(tcp_header), optionsBuffer, optionsLength); 227 228 TRACE(("add_tcp_header(): buffer %p, flags 0x%x, seq %lu, ack %lu, win %u\n", buffer, 229 segment.flags, segment.sequence, segment.acknowledge, segment.advertised_window)); 230 231 *TCPChecksumField(buffer) = Checksum::PseudoHeader(addressModule, 232 gBufferModule, buffer, IPPROTO_TCP); 233 234 return B_OK; 235 } 236 237 238 size_t 239 tcp_options_length(tcp_segment_header &segment) 240 { 241 size_t length = 0; 242 243 if (segment.max_segment_size > 0) 244 length += 4; 245 246 if (segment.options & TCP_HAS_TIMESTAMPS) 247 length += 12; 248 249 if (segment.options & TCP_HAS_WINDOW_SCALE) 250 length += 4; 251 252 if (segment.options & TCP_SACK_PERMITTED) 253 length += 2; 254 255 if (segment.sack_count > 0) { 256 int sackCount = min_c((int)((kMaxOptionSize - length - 4) 257 / sizeof(tcp_sack)), segment.sack_count); 258 if (sackCount > 0) 259 length += 4 + sackCount * sizeof(tcp_sack); 260 } 261 262 if ((length & 3) == 0) 263 return length; 264 265 return (length + 3) & ~3; 266 } 267 268 269 void 270 process_options(tcp_segment_header &segment, net_buffer *buffer, size_t size) 271 { 272 if (size == 0) 273 return; 274 275 tcp_option *option; 276 277 uint8 optionsBuffer[kMaxOptionSize]; 278 if (gBufferModule->direct_access(buffer, sizeof(tcp_header), size, 279 (void **)&option) != B_OK) { 280 if (size > sizeof(optionsBuffer)) { 281 dprintf("Ignoring TCP options larger than expected.\n"); 282 return; 283 } 284 285 gBufferModule->read(buffer, sizeof(tcp_header), optionsBuffer, size); 286 option = (tcp_option *)optionsBuffer; 287 } 288 289 while (size > 0) { 290 int32 length = -1; 291 292 switch (option->kind) { 293 case TCP_OPTION_END: 294 case TCP_OPTION_NOP: 295 length = 1; 296 break; 297 case TCP_OPTION_MAX_SEGMENT_SIZE: 298 if (option->length == 4 && (size - 4) >= 0) 299 segment.max_segment_size = ntohs(option->max_segment_size); 300 break; 301 case TCP_OPTION_WINDOW_SHIFT: 302 if (option->length == 3 && (size - 3) >= 0) { 303 segment.options |= TCP_HAS_WINDOW_SCALE; 304 segment.window_shift = option->window_shift; 305 } 306 break; 307 case TCP_OPTION_TIMESTAMP: 308 if (option->length == 10 && (size - 10) >= 0) { 309 segment.options |= TCP_HAS_TIMESTAMPS; 310 segment.timestamp_value = option->timestamp.timestamp_value; 311 segment.timestamp_reply = 312 ntohl(option->timestamp.timestamp_reply); 313 } 314 break; 315 case TCP_OPTION_SACK_PERMITTED: 316 if (option->length == 2 && (size - 2) >= 0) 317 segment.options |= TCP_SACK_PERMITTED; 318 } 319 320 if (length < 0) { 321 length = option->length; 322 if (length == 0) 323 break; 324 } 325 326 size -= length; 327 option = (tcp_option *)((uint8 *)option + length); 328 } 329 } 330 331 332 const char * 333 name_for_state(tcp_state state) 334 { 335 switch (state) { 336 case CLOSED: 337 return "closed"; 338 case LISTEN: 339 return "listen"; 340 case SYNCHRONIZE_SENT: 341 return "syn-sent"; 342 case SYNCHRONIZE_RECEIVED: 343 return "syn-received"; 344 case ESTABLISHED: 345 return "established"; 346 347 // peer closes the connection 348 case FINISH_RECEIVED: 349 return "close-wait"; 350 case WAIT_FOR_FINISH_ACKNOWLEDGE: 351 return "last-ack"; 352 353 // we close the connection 354 case FINISH_SENT: 355 return "fin-wait1"; 356 case FINISH_ACKNOWLEDGED: 357 return "fin-wait2"; 358 case CLOSING: 359 return "closing"; 360 361 case TIME_WAIT: 362 return "time-wait"; 363 } 364 365 return "-"; 366 } 367 368 369 #if 0 370 static void 371 dump_tcp_header(tcp_header &header) 372 { 373 dprintf(" source port: %u\n", ntohs(header.source_port)); 374 dprintf(" dest port: %u\n", ntohs(header.destination_port)); 375 dprintf(" sequence: %lu\n", header.Sequence()); 376 dprintf(" ack: %lu\n", header.Acknowledge()); 377 dprintf(" flags: %s%s%s%s%s%s\n", (header.flags & TCP_FLAG_FINISH) ? "FIN " : "", 378 (header.flags & TCP_FLAG_SYNCHRONIZE) ? "SYN " : "", 379 (header.flags & TCP_FLAG_RESET) ? "RST " : "", 380 (header.flags & TCP_FLAG_PUSH) ? "PUSH " : "", 381 (header.flags & TCP_FLAG_ACKNOWLEDGE) ? "ACK " : "", 382 (header.flags & TCP_FLAG_URGENT) ? "URG " : ""); 383 dprintf(" window: %u\n", header.AdvertisedWindow()); 384 dprintf(" urgent offset: %u\n", header.UrgentOffset()); 385 } 386 #endif 387 388 389 // #pragma mark - protocol API 390 391 392 net_protocol * 393 tcp_init_protocol(net_socket *socket) 394 { 395 TCPEndpoint *protocol = new (std::nothrow) TCPEndpoint(socket); 396 if (protocol == NULL) 397 return NULL; 398 399 if (protocol->InitCheck() != B_OK) { 400 delete protocol; 401 return NULL; 402 } 403 404 TRACE(("Creating new TCPEndpoint: %p\n", protocol)); 405 socket->protocol = IPPROTO_TCP; 406 return protocol; 407 } 408 409 410 status_t 411 tcp_uninit_protocol(net_protocol *protocol) 412 { 413 TRACE(("Deleting TCPEndpoint: %p\n", protocol)); 414 delete (TCPEndpoint *)protocol; 415 return B_OK; 416 } 417 418 419 status_t 420 tcp_open(net_protocol *protocol) 421 { 422 return ((TCPEndpoint *)protocol)->Open(); 423 } 424 425 426 status_t 427 tcp_close(net_protocol *protocol) 428 { 429 return ((TCPEndpoint *)protocol)->Close(); 430 } 431 432 433 status_t 434 tcp_free(net_protocol *protocol) 435 { 436 return ((TCPEndpoint *)protocol)->Free(); 437 } 438 439 440 status_t 441 tcp_connect(net_protocol *protocol, const struct sockaddr *address) 442 { 443 return ((TCPEndpoint *)protocol)->Connect(address); 444 } 445 446 447 status_t 448 tcp_accept(net_protocol *protocol, struct net_socket **_acceptedSocket) 449 { 450 return ((TCPEndpoint *)protocol)->Accept(_acceptedSocket); 451 } 452 453 454 status_t 455 tcp_control(net_protocol *_protocol, int level, int option, void *value, 456 size_t *_length) 457 { 458 TCPEndpoint *protocol = (TCPEndpoint *)_protocol; 459 460 if ((level & LEVEL_MASK) == IPPROTO_TCP) { 461 if (option == NET_STAT_SOCKET) 462 return protocol->FillStat((net_stat *)value); 463 } 464 465 return protocol->next->module->control(protocol->next, level, option, 466 value, _length); 467 } 468 469 470 status_t 471 tcp_getsockopt(net_protocol *_protocol, int level, int option, void *value, 472 int *_length) 473 { 474 TCPEndpoint *protocol = (TCPEndpoint *)_protocol; 475 476 /* TODO getting IPPROTO_TCP options is missing */ 477 return protocol->next->module->getsockopt(protocol->next, level, option, 478 value, _length); 479 } 480 481 482 status_t 483 tcp_setsockopt(net_protocol *_protocol, int level, int option, 484 const void *_value, int length) 485 { 486 TCPEndpoint *protocol = (TCPEndpoint *)_protocol; 487 488 if (level == SOL_SOCKET) { 489 if (option == SO_SNDBUF || option == SO_RCVBUF) { 490 if (length != sizeof(int)) 491 return B_BAD_VALUE; 492 493 status_t status; 494 const int *value = (const int *)_value; 495 496 if (option == SO_SNDBUF) 497 status = protocol->SetSendBufferSize(*value); 498 else 499 status = protocol->SetReceiveBufferSize(*value); 500 501 if (status < B_OK) 502 return status; 503 } 504 } else if (level == IPPROTO_TCP) 505 return protocol->SetOption(option, _value, length); 506 507 return protocol->next->module->setsockopt(protocol->next, level, option, 508 _value, length); 509 } 510 511 512 status_t 513 tcp_bind(net_protocol *protocol, const struct sockaddr *address) 514 { 515 return ((TCPEndpoint *)protocol)->Bind(address); 516 } 517 518 519 status_t 520 tcp_unbind(net_protocol *protocol, struct sockaddr *address) 521 { 522 return ((TCPEndpoint *)protocol)->Unbind(address); 523 } 524 525 526 status_t 527 tcp_listen(net_protocol *protocol, int count) 528 { 529 return ((TCPEndpoint *)protocol)->Listen(count); 530 } 531 532 533 status_t 534 tcp_shutdown(net_protocol *protocol, int direction) 535 { 536 return ((TCPEndpoint *)protocol)->Shutdown(direction); 537 } 538 539 540 status_t 541 tcp_send_data(net_protocol *protocol, net_buffer *buffer) 542 { 543 return ((TCPEndpoint *)protocol)->SendData(buffer); 544 } 545 546 547 status_t 548 tcp_send_routed_data(net_protocol *protocol, struct net_route *route, 549 net_buffer *buffer) 550 { 551 // TCP never sends routed data 552 return B_ERROR; 553 } 554 555 556 ssize_t 557 tcp_send_avail(net_protocol *protocol) 558 { 559 return ((TCPEndpoint *)protocol)->SendAvailable(); 560 } 561 562 563 status_t 564 tcp_read_data(net_protocol *protocol, size_t numBytes, uint32 flags, 565 net_buffer **_buffer) 566 { 567 return ((TCPEndpoint *)protocol)->ReadData(numBytes, flags, _buffer); 568 } 569 570 571 ssize_t 572 tcp_read_avail(net_protocol *protocol) 573 { 574 return ((TCPEndpoint *)protocol)->ReadAvailable(); 575 } 576 577 578 struct net_domain * 579 tcp_get_domain(net_protocol *protocol) 580 { 581 return protocol->next->module->get_domain(protocol->next); 582 } 583 584 585 size_t 586 tcp_get_mtu(net_protocol *protocol, const struct sockaddr *address) 587 { 588 return protocol->next->module->get_mtu(protocol->next, address); 589 } 590 591 592 status_t 593 tcp_receive_data(net_buffer *buffer) 594 { 595 TRACE(("TCP: Received buffer %p\n", buffer)); 596 597 if (buffer->interface == NULL || buffer->interface->domain == NULL) 598 return B_ERROR; 599 600 net_domain *domain = buffer->interface->domain; 601 net_address_module_info *addressModule = domain->address_module; 602 603 NetBufferHeaderReader<tcp_header> bufferHeader(buffer); 604 if (bufferHeader.Status() < B_OK) 605 return bufferHeader.Status(); 606 607 tcp_header &header = bufferHeader.Data(); 608 609 uint16 headerLength = header.HeaderLength(); 610 if (headerLength < sizeof(tcp_header)) 611 return B_BAD_DATA; 612 613 if (Checksum::PseudoHeader(addressModule, gBufferModule, buffer, 614 IPPROTO_TCP) != 0) 615 return B_BAD_DATA; 616 617 addressModule->set_port(buffer->source, header.source_port); 618 addressModule->set_port(buffer->destination, header.destination_port); 619 620 TRACE((" Looking for: peer %s, local %s\n", 621 AddressString(domain, buffer->source, true).Data(), 622 AddressString(domain, buffer->destination, true).Data())); 623 //dump_tcp_header(header); 624 //gBufferModule->dump(buffer); 625 626 tcp_segment_header segment(header.flags); 627 segment.sequence = header.Sequence(); 628 segment.acknowledge = header.Acknowledge(); 629 segment.advertised_window = header.AdvertisedWindow(); 630 segment.urgent_offset = header.UrgentOffset(); 631 process_options(segment, buffer, headerLength - sizeof(tcp_header)); 632 633 bufferHeader.Remove(headerLength); 634 // we no longer need to keep the header around 635 636 MutexLocker _(sEndpointManagersLock); 637 638 EndpointManager *endpointManager = endpoint_manager_for(domain); 639 if (endpointManager == NULL) 640 return B_ERROR; 641 642 int32 segmentAction = DROP; 643 644 TCPEndpoint *endpoint = endpointManager->FindConnection( 645 buffer->destination, buffer->source); 646 if (endpoint != NULL) 647 segmentAction = endpoint->SegmentReceived(segment, buffer); 648 else if ((segment.flags & TCP_FLAG_RESET) == 0) 649 segmentAction = DROP | RESET; 650 651 if (segmentAction & RESET) { 652 // send reset 653 endpointManager->ReplyWithReset(segment, buffer); 654 } 655 if (segmentAction & DROP) 656 gBufferModule->free(buffer); 657 658 return B_OK; 659 } 660 661 662 status_t 663 tcp_error(uint32 code, net_buffer *data) 664 { 665 return B_ERROR; 666 } 667 668 669 status_t 670 tcp_error_reply(net_protocol *protocol, net_buffer *causedError, uint32 code, 671 void *errorData) 672 { 673 return B_ERROR; 674 } 675 676 677 static int 678 dump_endpoints(int argc, char *argv[]) 679 { 680 EndpointManagerList::Iterator it = sEndpointManagers.GetIterator(); 681 682 while (it.HasNext()) 683 it.Next()->DumpEndpoints(); 684 685 return 0; 686 } 687 688 689 static int 690 dump_endpoint(int argc, char *argv[]) 691 { 692 if (argc < 2) { 693 kprintf("usage: tcp_endpoint [address]\n"); 694 return 0; 695 } 696 697 TCPEndpoint *endpoint = (TCPEndpoint *)strtoul(argv[1], NULL, 16); 698 endpoint->DumpInternalState(); 699 700 return 0; 701 } 702 703 704 // #pragma mark - 705 706 707 static status_t 708 tcp_init() 709 { 710 status_t status = mutex_init(&sEndpointManagersLock, 711 "endpoint managers lock"); 712 713 if (status < B_OK) 714 return status; 715 716 status = gStackModule->register_domain_protocols(AF_INET, SOCK_STREAM, 0, 717 "network/protocols/tcp/v1", 718 "network/protocols/ipv4/v1", 719 NULL); 720 if (status < B_OK) 721 return status; 722 723 status = gStackModule->register_domain_protocols(AF_INET, SOCK_STREAM, IPPROTO_TCP, 724 "network/protocols/tcp/v1", 725 "network/protocols/ipv4/v1", 726 NULL); 727 if (status < B_OK) 728 return status; 729 730 status = gStackModule->register_domain_receiving_protocol(AF_INET, IPPROTO_TCP, 731 "network/protocols/tcp/v1"); 732 if (status < B_OK) 733 return status; 734 735 add_debugger_command("tcp_endpoints", dump_endpoints, 736 "lists all open TCP endpoints"); 737 add_debugger_command("tcp_endpoint", dump_endpoint, 738 "dumps a TCP endpoint internal state"); 739 740 return B_OK; 741 } 742 743 744 static status_t 745 tcp_uninit() 746 { 747 remove_debugger_command("tcp_endpoint", dump_endpoint); 748 remove_debugger_command("tcp_endpoints", dump_endpoints); 749 mutex_destroy(&sEndpointManagersLock); 750 return B_OK; 751 } 752 753 754 static status_t 755 tcp_std_ops(int32 op, ...) 756 { 757 switch (op) { 758 case B_MODULE_INIT: 759 return tcp_init(); 760 761 case B_MODULE_UNINIT: 762 return tcp_uninit(); 763 764 default: 765 return B_ERROR; 766 } 767 } 768 769 770 net_protocol_module_info sTCPModule = { 771 { 772 "network/protocols/tcp/v1", 773 0, 774 tcp_std_ops 775 }, 776 tcp_init_protocol, 777 tcp_uninit_protocol, 778 tcp_open, 779 tcp_close, 780 tcp_free, 781 tcp_connect, 782 tcp_accept, 783 tcp_control, 784 tcp_getsockopt, 785 tcp_setsockopt, 786 tcp_bind, 787 tcp_unbind, 788 tcp_listen, 789 tcp_shutdown, 790 tcp_send_data, 791 tcp_send_routed_data, 792 tcp_send_avail, 793 tcp_read_data, 794 tcp_read_avail, 795 tcp_get_domain, 796 tcp_get_mtu, 797 tcp_receive_data, 798 NULL, 799 tcp_error, 800 tcp_error_reply, 801 }; 802 803 module_dependency module_dependencies[] = { 804 {NET_STACK_MODULE_NAME, (module_info **)&gStackModule}, 805 {NET_BUFFER_MODULE_NAME, (module_info **)&gBufferModule}, 806 {NET_DATALINK_MODULE_NAME, (module_info **)&gDatalinkModule}, 807 {NET_SOCKET_MODULE_NAME, (module_info **)&gSocketModule}, 808 {} 809 }; 810 811 module_info *modules[] = { 812 (module_info *)&sTCPModule, 813 NULL 814 }; 815