TCP: added SACK definitions and option processing.

git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@20775 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
Hugo Santos 2007-04-20 20:50:24 +00:00
parent 530017c6c9
commit 739e994278
3 changed files with 84 additions and 22 deletions

View File

@ -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

View File

@ -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<tcp_header> 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) {

View File

@ -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
{