* 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:
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user