diff --git a/src/add-ons/kernel/network/protocols/tcp/TCPConnection.cpp b/src/add-ons/kernel/network/protocols/tcp/TCPConnection.cpp index c711d7127a..f9ddbbcb95 100644 --- a/src/add-ons/kernel/network/protocols/tcp/TCPConnection.cpp +++ b/src/add-ons/kernel/network/protocols/tcp/TCPConnection.cpp @@ -92,7 +92,7 @@ TCPConnection::TCPConnection(net_socket *socket) fSendBuffer(NULL), fRoute(NULL), fReceiveNext(0), - fReceiveWindow(0), + fReceiveWindow(32768), fAvgRTT(TCP_INITIAL_RTT), fReceiveBuffer(NULL), fState(CLOSED), @@ -263,6 +263,7 @@ TCPConnection::Connect(const struct sockaddr *address) TRACE((" TCP: Connect(): starting 3-way handshake...\n")); fState = SYNCHRONIZE_SENT; + fMaxReceiveSize = fRoute->mtu - 40; // send SYN status = _SendQueuedData(TCP_FLAG_SYNCHRONIZE, false); @@ -581,7 +582,15 @@ TCPConnection::ListenReceive(tcp_segment_header& segment, net_buffer *buffer) if (insert_connection(connection) < B_OK) return DROP; - connection->fState = SYNCHRONIZE_SENT; + connection->fState = SYNCHRONIZE_RECEIVED; + connection->fMaxReceiveSize = connection->fRoute->mtu - 40; + // 40 bytes for IP and TCP header without any options + // TODO: make this depending on the RTF_LOCAL flag? + connection->fReceiveNext = segment.sequence + 1; + // account for the extra sequence number for the synchronization + + if (segment.max_segment_size > 0) + connection->fMaxSegmentSize = segment.max_segment_size; benaphore_lock(&connection->fSendLock); status_t status = connection->_SendQueuedData( @@ -663,14 +672,21 @@ TCPConnection::Receive(tcp_segment_header& segment, net_buffer *buffer) return DROP; } else if (fState == FINISH_SENT) fState = FINISH_ACKNOWLEDGED; + else if (fState == SYNCHRONIZE_RECEIVED) + fState = ESTABLISHED; } + + fSendWindow = segment.advertised_window; + if (segment.flags & TCP_FLAG_FINISH) { + dprintf("peer is finishing connection!"); fReceiveNext++; // other side is closing connection; change states switch (fState) { case ESTABLISHED: fState = FINISH_RECEIVED; + action |= IMMEDIATE_ACKNOWLEDGE; break; case FINISH_ACKNOWLEDGED: fState = TIME_WAIT; @@ -691,7 +707,7 @@ TCPConnection::Receive(tcp_segment_header& segment, net_buffer *buffer) break; } } - if (fState != FINISH_ACKNOWLEDGED) + if (buffer->size > 0 || (segment.flags & (TCP_FLAG_SYNCHRONIZE | TCP_FLAG_FINISH)) != 0) action |= ACKNOWLEDGE; } else { // out-of-order packet received, remind the other side of where we are @@ -779,8 +795,19 @@ TCPConnection::_SendQueuedData(uint16 flags, bool empty) uint32 size = buffer->size; - status_t status = add_tcp_header(buffer, flags, fSendNext, - fReceiveNext, fReceiveWindow); + tcp_segment_header segment; + segment.flags = (uint8)flags; + segment.sequence = fSendNext; + segment.acknowledge = fReceiveNext; + segment.advertised_window = fReceiveWindow; + segment.urgent_offset = 0; + + if ((flags & TCP_FLAG_SYNCHRONIZE) != 0) { + // add connection establishment options + segment.max_segment_size = fMaxReceiveSize; + } + + status_t status = add_tcp_header(segment, buffer); if (status != B_OK) { gBufferModule->free(buffer); return status; diff --git a/src/add-ons/kernel/network/protocols/tcp/TCPConnection.h b/src/add-ons/kernel/network/protocols/tcp/TCPConnection.h index 18a761a04a..c11b2b9f3c 100644 --- a/src/add-ons/kernel/network/protocols/tcp/TCPConnection.h +++ b/src/add-ons/kernel/network/protocols/tcp/TCPConnection.h @@ -73,6 +73,7 @@ class TCPConnection : public net_protocol { uint32 fLastAcknowledged; uint32 fSendNext; uint32 fSendWindow; + uint32 fMaxSegmentSize; net_buffer *fSendBuffer; net_route *fRoute; @@ -80,6 +81,7 @@ class TCPConnection : public net_protocol { uint32 fReceiveNext; uint32 fReceiveWindow; + uint32 fMaxReceiveSize; bigtime_t fAvgRTT; net_buffer *fReceiveBuffer; diff --git a/src/add-ons/kernel/network/protocols/tcp/tcp.cpp b/src/add-ons/kernel/network/protocols/tcp/tcp.cpp index 2713813e3e..e3df23489d 100644 --- a/src/add-ons/kernel/network/protocols/tcp/tcp.cpp +++ b/src/add-ons/kernel/network/protocols/tcp/tcp.cpp @@ -74,7 +74,7 @@ tcp_dump_hash() hash_close(gConnectionHash, &iterator, false); } #else -# define DUMP_TCP_HASH 0 +# define DUMP_TCP_HASH ; #endif @@ -100,17 +100,63 @@ set_domain(net_interface *interface = NULL) } +static inline void +bump_option(tcp_option *&option, size_t &length) +{ + length = option->length; + option = (tcp_option *)((uint8 *)option + option->length); +} + + +static inline size_t +add_options(tcp_segment_header &segment, uint8 *buffer, size_t bufferSize) +{ + tcp_option *option = (tcp_option *)buffer; + size_t length = 0; + + if (segment.max_segment_size > 0 && length + 8 < bufferSize) { + option->kind = TCP_OPTION_MAX_SEGMENT_SIZE; + option->length = 4; + option->max_segment_size = htons(segment.max_segment_size); + bump_option(option, length); + } + if (segment.window_shift > 0 && length + 4 < bufferSize) { + option->kind = TCP_OPTION_WINDOW_SHIFT; + option->length = 3; + option->window_shift = segment.window_shift; + bump_option(option, length); + } + + if (length == 0) { + // no option defined + return 0; + } + + while ((length + 1) & 0x3) { + // bump to a multiple of 4 length + option->kind = TCP_OPTION_NOP; + option = (tcp_option *)((uint8 *)option + 1); + length++; + } + + option->kind = TCP_OPTION_END; + return length + 1; +} + + /*! Constructs a TCP header on \a buffer with the specified values for \a flags, \a seq \a ack and \a advertisedWindow. */ status_t -add_tcp_header(net_buffer *buffer, uint16 flags, uint32 sequence, - uint32 acknowledge, uint16 advertisedWindow) +add_tcp_header(tcp_segment_header &segment, net_buffer *buffer) { buffer->protocol = IPPROTO_TCP; - NetBufferPrepend bufferHeader(buffer); + uint8 optionsBuffer[32]; + uint32 optionsLength = add_options(segment, optionsBuffer, sizeof(optionsBuffer)); + + NetBufferPrepend bufferHeader(buffer, sizeof(tcp_header) + optionsLength); if (bufferHeader.Status() != B_OK) return bufferHeader.Status(); @@ -118,19 +164,22 @@ add_tcp_header(net_buffer *buffer, uint16 flags, uint32 sequence, header.source_port = gAddressModule->get_port((sockaddr *)&buffer->source); header.destination_port = gAddressModule->get_port((sockaddr *)&buffer->destination); - header.sequence = htonl(sequence); - header.acknowledge = (flags & TCP_FLAG_ACKNOWLEDGE) ? htonl(acknowledge) : 0; + header.sequence = htonl(segment.sequence); + header.acknowledge = (segment.flags & TCP_FLAG_ACKNOWLEDGE) + ? htonl(segment.acknowledge) : 0; header.reserved = 0; - header.header_length = sizeof(tcp_header) >> 2; - // currently no options supported - header.flags = (uint8)flags; - header.advertised_window = htons(advertisedWindow); + header.header_length = (sizeof(tcp_header) + optionsLength) >> 2; + header.flags = segment.flags; + header.advertised_window = htons(segment.advertised_window); header.checksum = 0; header.urgent_offset = 0; // TODO: urgent pointer not yet supported - TRACE(("add_tcp_header(): buffer %p, flags 0x%x, seq %lu, ack %lu\n", buffer, - flags, sequence, acknowledge)); + if (optionsLength > 0) + gBufferModule->write(buffer, sizeof(tcp_header), optionsBuffer, optionsLength); + + TRACE(("add_tcp_header(): buffer %p, flags 0x%x, seq %lu, ack %lu, win %u\n", buffer, + segment.flags, segment.sequence, segment.acknowledge, segment.advertised_window)); // compute and store checksum Checksum checksum; @@ -146,8 +195,64 @@ add_tcp_header(net_buffer *buffer, uint16 flags, uint32 sequence, } +void +process_options(tcp_segment_header &segment, net_buffer *buffer, int32 size) +{ + segment.window_shift = 0; + segment.max_segment_size = 0; + + if (size == 0) + return; + + tcp_option *option; + uint8 optionsBuffer[32]; + if (gBufferModule->direct_access(buffer, sizeof(tcp_header), size, + (void **)&option) != B_OK) { + if (size > 32) { + dprintf("options too large to take into account (%ld bytes)\n", size); + return; + } + + gBufferModule->read(buffer, sizeof(tcp_header), optionsBuffer, size); + option = (tcp_option *)optionsBuffer; + } + + while (size > 0) { + uint32 length = 1; + switch (option->kind) { + case TCP_OPTION_END: + case TCP_OPTION_NOP: + break; + case TCP_OPTION_MAX_SEGMENT_SIZE: + segment.max_segment_size = ntohs(option->max_segment_size); + length = 4; + break; + case TCP_OPTION_WINDOW_SHIFT: + segment.window_shift = option->window_shift; + length = 3; + break; + case TCP_OPTION_TIMESTAMP: + // TODO: support timestamp! + length = 10; + break; + + default: + length = option->length; + // make sure we don't end up in an endless loop + if (length == 0) + return; + break; + } + + size -= length; + option = (tcp_option *)((uint8 *)option + length); + } + // TODO: check if options are valid! +} + + status_t -reply_with_reset(tcp_segment_header& segment, net_buffer *buffer) +reply_with_reset(tcp_segment_header &segment, net_buffer *buffer) { TRACE(("TCP: Sending RST...\n")); @@ -160,21 +265,22 @@ reply_with_reset(tcp_segment_header& segment, net_buffer *buffer) gAddressModule->set_to((sockaddr *)&reply->destination, (sockaddr *)&buffer->source); - uint8 flags = TCP_FLAG_RESET; - uint32 acknowledge = 0; - uint32 sequence = 0; + tcp_segment_header outSegment; + outSegment.flags = TCP_FLAG_RESET; + outSegment.sequence = 0; + outSegment.acknowledge = 0; + outSegment.advertised_window = 0; + outSegment.urgent_offset = 0; if ((segment.flags & TCP_FLAG_ACKNOWLEDGE) == 0) { - flags |= TCP_FLAG_ACKNOWLEDGE; - acknowledge = segment.sequence + buffer->size; + outSegment.flags |= TCP_FLAG_ACKNOWLEDGE; + outSegment.acknowledge = segment.sequence + buffer->size; } else - sequence = segment.acknowledge; + outSegment.sequence = segment.acknowledge; - status_t status = add_tcp_header(reply, flags, - sequence, acknowledge, 0); - if (status == B_OK) { + status_t status = add_tcp_header(segment, reply); + if (status == B_OK) status = gDomain->module->send_data(NULL, reply); - } if (status != B_OK) gBufferModule->free(reply); @@ -277,7 +383,6 @@ tcp_init_protocol(net_socket *socket) status_t tcp_uninit_protocol(net_protocol *protocol) { - DUMP_TCP_HASH; TRACE(("Deleting TCPConnection: %p\n", protocol)); delete (TCPConnection *)protocol; return B_OK; @@ -290,8 +395,6 @@ tcp_open(net_protocol *protocol) if (gDomain == NULL && set_domain() != B_OK) return B_ERROR; - DUMP_TCP_HASH; - return ((TCPConnection *)protocol)->Open(); } @@ -299,7 +402,6 @@ tcp_open(net_protocol *protocol) status_t tcp_close(net_protocol *protocol) { - DUMP_TCP_HASH; return ((TCPConnection *)protocol)->Close(); } @@ -307,7 +409,6 @@ tcp_close(net_protocol *protocol) status_t tcp_free(net_protocol *protocol) { - DUMP_TCP_HASH; return ((TCPConnection *)protocol)->Free(); } @@ -339,7 +440,6 @@ tcp_control(net_protocol *protocol, int level, int option, void *value, status_t tcp_bind(net_protocol *protocol, struct sockaddr *address) { - DUMP_TCP_HASH; return ((TCPConnection *)protocol)->Bind(address); } @@ -347,7 +447,6 @@ tcp_bind(net_protocol *protocol, struct sockaddr *address) status_t tcp_unbind(net_protocol *protocol, struct sockaddr *address) { - DUMP_TCP_HASH; return ((TCPConnection *)protocol)->Unbind(address); } @@ -454,15 +553,13 @@ tcp_receive_data(net_buffer *buffer) AddressString(gDomain, (sockaddr *)&buffer->source, true).Data(), AddressString(gDomain, (sockaddr *)&buffer->destination, true).Data())); - DUMP_TCP_HASH; - - // TODO: process options! tcp_segment_header segment; segment.sequence = header.Sequence(); segment.acknowledge = header.Acknowledge(); segment.advertised_window = header.AdvertisedWindow(); segment.urgent_offset = header.UrgentOffset(); segment.flags = header.flags; + process_options(segment, buffer, headerLength - sizeof(tcp_header)); bufferHeader.Remove(headerLength); // we no longer need to keep the header around diff --git a/src/add-ons/kernel/network/protocols/tcp/tcp.h b/src/add-ons/kernel/network/protocols/tcp/tcp.h index 7cad032f69..ba702df98d 100644 --- a/src/add-ons/kernel/network/protocols/tcp/tcp.h +++ b/src/add-ons/kernel/network/protocols/tcp/tcp.h @@ -67,7 +67,7 @@ struct tcp_header { uint16 AdvertisedWindow() const { return ntohs(advertised_window); } uint16 Checksum() const { return ntohs(checksum); } uint16 UrgentOffset() const { return ntohs(urgent_offset); } -}; +} _PACKED; // TCP flag constants #define TCP_FLAG_FINISH 0x01 @@ -79,12 +79,36 @@ struct tcp_header { #define TCP_FLAG_ECN 0x40 // Explicit Congestion Notification echo #define TCP_FLAG_CWR 0x80 // Congestion Window Reduced +struct tcp_option { + uint8 kind; + uint8 length; + union { + uint8 window_shift; + uint16 max_segment_size; + uint32 timestamp; + }; + uint32 timestamp_reply; +} _PACKED; + +enum tcp_option_kind { + TCP_OPTION_END = 0, + TCP_OPTION_NOP = 1, + TCP_OPTION_MAX_SEGMENT_SIZE = 2, + TCP_OPTION_WINDOW_SHIFT = 3, + TCP_OPTION_TIMESTAMP = 8, +}; + struct tcp_segment_header { + tcp_segment_header() : window_shift(0), max_segment_size(0) {} + // constructor zeros options + uint32 sequence; uint32 acknowledge; uint16 advertised_window; uint16 urgent_offset; uint8 flags; + uint8 window_shift; + uint16 max_segment_size; }; enum tcp_segment_action { @@ -111,8 +135,7 @@ extern net_stack_module_info *gStackModule; //extern benaphore gConnectionLock; -status_t add_tcp_header(net_buffer *buffer, uint16 flags, uint32 sequence, - uint32 ack, uint16 advertisedWindow); +status_t add_tcp_header(tcp_segment_header &segment, net_buffer *buffer); status_t remove_connection(TCPConnection *connection); status_t insert_connection(TCPConnection *connection);