* Implemented adding/processing TCP options; so far only "max segment size" (MSS)

is really supported.
* Added TCP option fields to the tcp_segment_header structure - they are filled
  in on receive, and add_tcp_header() now gets a tcp_segment_header as well, and
  will add any options automatically.
* Fixed some minor bugs (connection with passive open had wrong state after connect).


git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@19333 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
Axel Dörfler
2006-11-20 17:35:51 +00:00
parent fc82227a6e
commit 75743fcd65
4 changed files with 190 additions and 41 deletions
@@ -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;
@@ -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;
+130 -33
View File
@@ -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<tcp_header> bufferHeader(buffer);
uint8 optionsBuffer[32];
uint32 optionsLength = add_options(segment, optionsBuffer, sizeof(optionsBuffer));
NetBufferPrepend<tcp_header> 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
+26 -3
View File
@@ -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);