diff --git a/src/add-ons/kernel/network/protocols/tcp/TCPEndpoint.cpp b/src/add-ons/kernel/network/protocols/tcp/TCPEndpoint.cpp index c07592d002..8a4a13ad19 100644 --- a/src/add-ons/kernel/network/protocols/tcp/TCPEndpoint.cpp +++ b/src/add-ons/kernel/network/protocols/tcp/TCPEndpoint.cpp @@ -1061,7 +1061,7 @@ TCPEndpoint::_SendQueued(bool force) if ((fOptions & TCP_NOOPT) == 0) { if (fFlags & FLAG_OPTION_TIMESTAMP) { - segment.has_timestamps = true; + segment.options |= TCP_HAS_TIMESTAMPS; segment.TSecr = fReceivedTSval; segment.TSval = htonl(tcp_now()); } @@ -1071,7 +1071,7 @@ TCPEndpoint::_SendQueued(bool force) // add connection establishment options segment.max_segment_size = fReceiveMaxSegmentSize; if (fFlags & FLAG_OPTION_WINDOW_SCALE) { - segment.has_window_shift = true; + segment.options |= TCP_HAS_WINDOW_SCALE; segment.window_shift = fReceiveWindowShift; } } @@ -1185,7 +1185,7 @@ TCPEndpoint::_SendQueued(bool force) // for local connections as the answer is directly handled if (segment.flags & TCP_FLAG_SYNCHRONIZE) { - segment.has_window_shift = false; + segment.options &= ~TCP_HAS_WINDOW_SCALE; segment.max_segment_size = 0; size++; } @@ -1583,7 +1583,7 @@ TCPEndpoint::_PrepareReceivePath(tcp_segment_header &segment) if (segment.max_segment_size > 0) fSendMaxSegmentSize = segment.max_segment_size; - if (segment.has_window_shift) { + if (segment.options & TCP_HAS_WINDOW_SCALE) { fFlags |= FLAG_OPTION_WINDOW_SCALE; fSendWindowShift = segment.window_shift; } else { @@ -1591,7 +1591,7 @@ TCPEndpoint::_PrepareReceivePath(tcp_segment_header &segment) fReceiveWindowShift = 0; } - if (segment.has_timestamps) { + if (segment.options & TCP_HAS_TIMESTAMPS) { fFlags |= FLAG_OPTION_TIMESTAMP; fReceivedTSval = segment.TSval; } else @@ -1654,7 +1654,7 @@ TCPEndpoint::_Acknowledged(tcp_segment_header &segment) if (fSendUnacknowledged == fSendMax) gStackModule->cancel_timer(&fRetransmitTimer); - if (segment.has_timestamps) + if (segment.options & TCP_HAS_TIMESTAMPS) _UpdateSRTT(tcp_diff_timestamp(ntohl(segment.TSecr))); else { // TODO Fallback to RFC 793 type estimation diff --git a/src/add-ons/kernel/network/protocols/tcp/tcp.cpp b/src/add-ons/kernel/network/protocols/tcp/tcp.cpp index 82af07f35c..688c6d426e 100644 --- a/src/add-ons/kernel/network/protocols/tcp/tcp.cpp +++ b/src/add-ons/kernel/network/protocols/tcp/tcp.cpp @@ -57,6 +57,10 @@ static recursive_lock sEndpointManagersLock; static EndpointManagerList sEndpointManagers; +// The TCP header length is at most 64 bytes. +static const int kMaxOptionSize = 64 - sizeof(tcp_header); + + static EndpointManager * endpoint_manager_for(net_domain *domain) { @@ -114,14 +118,15 @@ 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) { + 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.has_timestamps && length + 12 < bufferSize) { + if ((segment.options & TCP_HAS_TIMESTAMPS) + && length + 12 <= bufferSize) { // two NOPs so the timestamps get aligned to a 4 byte boundary option->kind = TCP_OPTION_NOP; bump_option(option, length); @@ -135,7 +140,8 @@ add_options(tcp_segment_header &segment, uint8 *buffer, size_t bufferSize) bump_option(option, length); } - if (segment.has_window_shift && length + 4 < bufferSize) { + if ((segment.options & TCP_HAS_WINDOW_SCALE) + && length + 4 <= bufferSize) { // insert one NOP so that the subsequent data is aligned on a 4 byte boundary option->kind = TCP_OPTION_NOP; bump_option(option, length); @@ -146,6 +152,30 @@ add_options(tcp_segment_header &segment, uint8 *buffer, size_t bufferSize) bump_option(option, length); } + if ((segment.options & TCP_SACK_PERMITTED) + && length + 2 <= bufferSize) { + option->kind = TCP_OPTION_SACK_PERMITTED; + option->length = 2; + bump_option(option, length); + } + + if (segment.sack_count > 0) { + int sackCount = ((int)(bufferSize - length) - 4) / sizeof(tcp_sack); + if (sackCount > segment.sack_count) + sackCount = segment.sack_count; + + if (sackCount > 0) { + option->kind = TCP_OPTION_NOP; + bump_option(option, length); + option->kind = TCP_OPTION_NOP; + bump_option(option, length); + option->kind = TCP_OPTION_SACK; + option->length = 2 + sackCount * sizeof(tcp_sack); + memcpy(option->sack, segment.sacks, sackCount * sizeof(tcp_sack)); + bump_option(option, length); + } + } + if ((length & 3) == 0) { // options completely fill out the option space return length; @@ -167,7 +197,7 @@ add_tcp_header(net_address_module_info *addressModule, { buffer->protocol = IPPROTO_TCP; - uint8 optionsBuffer[32]; + uint8 optionsBuffer[kMaxOptionSize]; uint32 optionsLength = add_options(segment, optionsBuffer, sizeof(optionsBuffer)); NetBufferPrepend bufferHeader(buffer, sizeof(tcp_header) + optionsLength); @@ -213,12 +243,22 @@ tcp_options_length(tcp_segment_header &segment) if (segment.max_segment_size > 0) length += 4; - if (segment.has_timestamps) + if (segment.options & TCP_HAS_TIMESTAMPS) length += 12; - if (segment.has_window_shift) + if (segment.options & TCP_HAS_WINDOW_SCALE) length += 4; + if (segment.options & TCP_SACK_PERMITTED) + length += 2; + + if (segment.sack_count > 0) { + int sackCount = min_c((int)((kMaxOptionSize - length - 4) + / sizeof(tcp_sack)), segment.sack_count); + if (sackCount > 0) + length += 4 + sackCount * sizeof(tcp_sack); + } + if ((length & 3) == 0) return length; @@ -227,17 +267,20 @@ tcp_options_length(tcp_segment_header &segment) void -process_options(tcp_segment_header &segment, net_buffer *buffer, int32 size) +process_options(tcp_segment_header &segment, net_buffer *buffer, size_t size) { if (size == 0) return; tcp_option *option; - uint8 optionsBuffer[32]; + + uint8 optionsBuffer[kMaxOptionSize]; if (gBufferModule->direct_access(buffer, sizeof(tcp_header), size, (void **)&option) != B_OK) { - if (size > 32) - panic("UNIMPLEMENTED: TCP option processing across data nodes"); + if (size > sizeof(optionsBuffer)) { + dprintf("Ignoring TCP options larger than expected.\n"); + return; + } gBufferModule->read(buffer, sizeof(tcp_header), optionsBuffer, size); option = (tcp_option *)optionsBuffer; @@ -257,17 +300,20 @@ process_options(tcp_segment_header &segment, net_buffer *buffer, int32 size) break; case TCP_OPTION_WINDOW_SHIFT: if (option->length == 3 && (size - 3) >= 0) { - segment.has_window_shift = true; + segment.options |= TCP_HAS_WINDOW_SCALE; segment.window_shift = option->window_shift; } break; case TCP_OPTION_TIMESTAMP: if (option->length == 10 && (size - 10) >= 0) { - segment.has_timestamps = true; + segment.options |= TCP_HAS_TIMESTAMPS; segment.TSval = option->timestamp.TSval; segment.TSecr = ntohl(option->timestamp.TSecr); } break; + case TCP_OPTION_SACK_PERMITTED: + if (option->length == 2 && (size - 2) >= 0) + segment.options |= TCP_SACK_PERMITTED; } if (length < 0) { diff --git a/src/add-ons/kernel/network/protocols/tcp/tcp.h b/src/add-ons/kernel/network/protocols/tcp/tcp.h index e0690fe51b..7ceeda03a7 100644 --- a/src/add-ons/kernel/network/protocols/tcp/tcp.h +++ b/src/add-ons/kernel/network/protocols/tcp/tcp.h @@ -106,6 +106,11 @@ class tcp_sequence { #define TCP_MAX_WINDOW 65535 #define TCP_MAX_SEGMENT_LIFETIME 60000000 // 60 secs +struct tcp_sack { + uint32 left_edge; + uint32 right_edge; +} _PACKED; + struct tcp_option { uint8 kind; uint8 length; @@ -116,6 +121,7 @@ struct tcp_option { uint32 TSval; uint32 TSecr; } timestamp; + tcp_sack sack[0]; }; } _PACKED; @@ -124,19 +130,27 @@ enum tcp_option_kind { TCP_OPTION_NOP = 1, TCP_OPTION_MAX_SEGMENT_SIZE = 2, TCP_OPTION_WINDOW_SHIFT = 3, + TCP_OPTION_SACK_PERMITTED = 4, + TCP_OPTION_SACK = 5, TCP_OPTION_TIMESTAMP = 8, }; #define TCP_MAX_WINDOW_SHIFT 14 +enum { + TCP_HAS_WINDOW_SCALE = 1 << 0, + TCP_HAS_TIMESTAMPS = 1 << 1, + TCP_SACK_PERMITTED = 1 << 2, +}; + struct tcp_segment_header { tcp_segment_header(uint8 _flags) : flags(_flags), window_shift(0), max_segment_size(0), - has_window_shift(false), - has_timestamps(false) + sack_count(0), + options(0) {} uint32 sequence; @@ -150,8 +164,10 @@ struct tcp_segment_header { uint32 TSval; uint32 TSecr; - bool has_window_shift : 1; - bool has_timestamps : 1; + tcp_sack *sacks; + int sack_count; + + uint32 options; bool AcknowledgeOnly() const {