From 26417177e74510783b4a6f48670c62e96cde0ee2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Axel=20D=C3=B6rfler?= Date: Wed, 29 Nov 2006 21:53:06 +0000 Subject: [PATCH] More work-in-progress: * Added some timer methods * Fixed removing the connection too early (and thus replying with a RST instead of nothing when the peer acknowledged our FIN). * BufferQueue::RemoveUntil() did not bump the buffer's sequence * Delayed acknowledge, and retransmitting should work now (the latter is *very* basic right now) * Completed TCPConnection::_ShouldSendSegment() * fReceiveNext was always updated on a received buffer, even if there was a buffer missing. git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@19396 a95241bf-73f2-0310-859d-f6bbb57e9c96 --- .../network/protocols/tcp/BufferQueue.cpp | 1 + .../network/protocols/tcp/TCPConnection.cpp | 159 ++++++++++++++---- .../network/protocols/tcp/TCPConnection.h | 13 +- .../kernel/network/protocols/tcp/tcp.h | 2 + 4 files changed, 136 insertions(+), 39 deletions(-) diff --git a/src/add-ons/kernel/network/protocols/tcp/BufferQueue.cpp b/src/add-ons/kernel/network/protocols/tcp/BufferQueue.cpp index dea456d356..07995ffc2c 100644 --- a/src/add-ons/kernel/network/protocols/tcp/BufferQueue.cpp +++ b/src/add-ons/kernel/network/protocols/tcp/BufferQueue.cpp @@ -193,6 +193,7 @@ BufferQueue::RemoveUntil(tcp_sequence sequence) size_t size = sequence - buffer->sequence; gBufferModule->remove_header(buffer, size); + buffer->sequence += size; fNumBytes -= size; fContiguousBytes -= size; } diff --git a/src/add-ons/kernel/network/protocols/tcp/TCPConnection.cpp b/src/add-ons/kernel/network/protocols/tcp/TCPConnection.cpp index 20279ce13c..fbae2cf449 100644 --- a/src/add-ons/kernel/network/protocols/tcp/TCPConnection.cpp +++ b/src/add-ons/kernel/network/protocols/tcp/TCPConnection.cpp @@ -70,6 +70,7 @@ TCPConnection::TCPConnection(net_socket *socket) fDuplicateAcknowledgeCount(0), fRoute(NULL), fReceiveNext(0), + fReceiveMaxAdvertised(0), fReceiveWindow(socket->receive.buffer_size), fReceiveMaxSegmentSize(TCP_DEFAULT_MAX_SEGMENT_SIZE), fReceiveQueue(socket->receive.buffer_size), @@ -77,7 +78,7 @@ TCPConnection::TCPConnection(net_socket *socket) fState(CLOSED), fError(B_OK) { - gStackModule->init_timer(&fTimer, _TimeWait, this); + //gStackModule->init_timer(&fTimer, _TimeWait, this); recursive_lock_init(&fLock, "tcp lock"); // TODO: to be replaced with a real locking strategy! @@ -85,12 +86,17 @@ TCPConnection::TCPConnection(net_socket *socket) //benaphore_init(&fSendLock, "tcp send"); fSendLock = create_sem(0, "tcp send"); fReceiveLock = create_sem(0, "tcp receive"); + + gStackModule->init_timer(&fPersistTimer, TCPConnection::_PersistTimer, this); + gStackModule->init_timer(&fRetransmitTimer, TCPConnection::_RetransmitTimer, this); + gStackModule->init_timer(&fDelayedAcknowledgeTimer, + TCPConnection::_DelayedAcknowledgeTimer, this); } TCPConnection::~TCPConnection() { - gStackModule->set_timer(&fTimer, -1); + //gStackModule->set_timer(&fTimer, -1); recursive_lock_destroy(&fLock); //benaphore_destroy(&fReceiveLock); @@ -115,6 +121,9 @@ TCPConnection::InitCheck() const } +// #pragma mark - protocol API + + status_t TCPConnection::Open() { @@ -127,8 +136,9 @@ TCPConnection::Open() status_t TCPConnection::Close() { - //BenaphoreLocker lock(&fSendLock); TRACE(("TCP:%p.Close()\n", this)); + RecursiveLocker lock(fLock); + if (fState == SYNCHRONIZE_SENT || fState == LISTEN) { fState = CLOSED; return B_OK; @@ -161,7 +171,6 @@ TCPConnection::Free() TRACE(("TCP:%p.Free()\n", this)); // TODO: if this connection is not in the hash, we don't have to call this one - remove_connection(this); return B_OK; } @@ -421,18 +430,40 @@ TCPConnection::ReadAvailable() } +// #pragma mark - misc + + status_t TCPConnection::DelayedAcknowledge() { - // TODO: use timer instead and/or piggyback on send - return _SendQueued(); + // if the timer is already running, and there is still more than + // half of the receive window free, just wait for the timer to expire + if (gStackModule->is_timer_active(&fDelayedAcknowledgeTimer) + && (fReceiveMaxAdvertised - fReceiveNext) > (socket->receive.buffer_size >> 1)) + return B_OK; + + if (gStackModule->cancel_timer(&fDelayedAcknowledgeTimer)) { + // timer was active, send an ACK now (with the exception above, + // we send every other ACK) + return _SendQueued(); + } + + gStackModule->set_timer(&fDelayedAcknowledgeTimer, TCP_DELAYED_ACKNOWLEDGE_TIMEOUT); + return B_OK; } status_t TCPConnection::SendAcknowledge() { - return _SendQueued(); + return _SendQueued(true); +} + + +void +TCPConnection::_StartPersistTimer() +{ + gStackModule->set_timer(&fPersistTimer, 1000000LL); } @@ -443,6 +474,9 @@ TCPConnection::UpdateTimeWait() } +// #pragma mark - receive + + int32 TCPConnection::ListenReceive(tcp_segment_header &segment, net_buffer *buffer) { @@ -582,7 +616,8 @@ TCPConnection::Receive(tcp_segment_header &segment, net_buffer *buffer) fSendQueue.RemoveUntil(segment.acknowledge); fSendUnacknowledged = segment.acknowledge; - // TODO: stop retransmit timer + // stop retransmit timer + gStackModule->cancel_timer(&fRetransmitTimer); // notify threads waiting on the socket to become writable again release_sem_etc(fSendLock, 1, B_DO_NOT_RESCHEDULE); @@ -590,7 +625,7 @@ TCPConnection::Receive(tcp_segment_header &segment, net_buffer *buffer) gSocketModule->notify(socket, B_SELECT_WRITE, fSendWindow); // if there is data left to be send, send it now - //return _SendQueuedData(TCP_FLAG_ACKNOWLEDGE, false); + _SendQueued(); return DROP; } } else if (segment.acknowledge == fSendUnacknowledged @@ -621,7 +656,6 @@ TCPConnection::Receive(tcp_segment_header &segment, net_buffer *buffer) // TODO: close connection depending on state fError = ECONNREFUSED; fState = CLOSED; - remove_connection(this); } return DROP; @@ -692,15 +726,20 @@ TCPConnection::Receive(tcp_segment_header &segment, net_buffer *buffer) if (fSendUnacknowledged >= segment.acknowledge) { // this is a duplicate acknowledge // TODO: handle this! + fDuplicateAcknowledgeCount++; } else { // this segment acknowleges in flight data fDuplicateAcknowledgeCount = 0; - + if (fSendMax == segment.acknowledge) { // there is no outstanding data to be acknowledged - // TODO: stop retransmit timer + // TODO: if the transmit timer function is already waiting + // to acquire this connection's lock, we should stop it anyway + gStackModule->cancel_timer(&fRetransmitTimer); } else { - // TODO: set retransmit timer + // TODO: set retransmit timer correctly + if (!gStackModule->is_timer_active(&fRetransmitTimer)) + gStackModule->set_timer(&fRetransmitTimer, 1000000LL); } fSendUnacknowledged = segment.acknowledge; @@ -774,7 +813,8 @@ TCPConnection::Receive(tcp_segment_header &segment, net_buffer *buffer) // put it in the receive buffer if (buffer->size > 0) { - fReceiveNext += buffer->size; + if (fReceiveNext == segment.sequence) + fReceiveNext += buffer->size; fReceiveQueue.Add(buffer, segment.sequence); } else gBufferModule->free(buffer); @@ -783,19 +823,7 @@ TCPConnection::Receive(tcp_segment_header &segment, net_buffer *buffer) } -/*! - Resends a sent segment (\a data) if the segment's ACK wasn't received - before the timeout (eg \a timer expired) -*/ -void -TCPConnection::ResendSegment(struct net_timer *timer, void *data) -{ - TRACE(("TCP:ResendSegment(%p)\n", data)); - if (data == NULL) - return; - - // TODO: implement me! -} +// #pragma mark - send /*! @@ -838,11 +866,12 @@ inline bool TCPConnection::_ShouldSendSegment(tcp_segment_header &segment, uint32 length, bool outstandingAcknowledge) { - // Avoid the silly window syndrome - we only send a segment in case: - // - we have a full segment to send, or - // - we're at the end of our buffer queue (and we're - // - we're retransmitting data if (length > 0) { + // Avoid the silly window syndrome - we only send a segment in case: + // - we have a full segment to send, or + // - we're at the end of our buffer queue, or + // - the buffer is at least larger than half of the maximum send window, or + // - we're retransmitting data if (length == fSendMaxSegmentSize || ((!outstandingAcknowledge || (fOptions & TCP_NODELAY) != 0) && tcp_sequence(fSendNext + length) == fSendQueue.LastSequence()) @@ -851,11 +880,23 @@ TCPConnection::_ShouldSendSegment(tcp_segment_header &segment, uint32 length, return true; } - // TODO: incomplete! + // check if we need to send a window update to the peer + if (segment.advertised_window > 0) { + // correct the window to take into account what already has been advertised + uint32 window = (min_c(segment.advertised_window, TCP_MAX_WINDOW) + << fReceiveWindowShift) - (fReceiveMaxAdvertised - fReceiveNext); - if ((segment.flags & (TCP_FLAG_SYNCHRONIZE | TCP_FLAG_FINISH | TCP_FLAG_RESET | TCP_FLAG_ACKNOWLEDGE)) != 0) + // if we can advertise a window larger than twice the maximum segment + // size, or half the maximum buffer size we send a window update + if (window >= (fReceiveMaxSegmentSize << 1) + || window >= (socket->receive.buffer_size >> 1)) + return true; + } + + if ((segment.flags & (TCP_FLAG_SYNCHRONIZE | TCP_FLAG_FINISH | TCP_FLAG_RESET)) != 0) return true; + // there is no reason to send a segment just now return false; } @@ -912,8 +953,14 @@ dprintf("fSendWindow = %lu, available = %lu, fSendNext = %lu, fSendUnacknowledge while (true) { dprintf("length = %ld, segmentLength = %lu\n", length, segmentLength); // Determine if we should really send this segment - if (!force && !_ShouldSendSegment(segment, segmentLength, outstandingAcknowledge)) + if (!force && !_ShouldSendSegment(segment, segmentLength, outstandingAcknowledge)) { + if (fSendQueue.Available() + && !gStackModule->is_timer_active(&fPersistTimer) + && !gStackModule->is_timer_active(&fRetransmitTimer)) + _StartPersistTimer(); + return B_OK; + } net_buffer *buffer = gBufferModule->create(256); if (buffer == NULL) @@ -973,6 +1020,9 @@ dprintf("length = %ld, segmentLength = %lu\n", length, segmentLength); fSendMax += size; fSendNext += size; + fReceiveMaxAdvertised = fReceiveNext + + ((uint32)segment.advertised_window << fReceiveWindowShift); + length -= segmentLength; if (length == 0) break; @@ -985,12 +1035,51 @@ dprintf("length = %ld, segmentLength = %lu\n", length, segmentLength); } -void +// #pragma mark - timer + + +/*static*/ void +TCPConnection::_RetransmitTimer(net_timer *timer, void *data) +{ + TCPConnection *connection = (TCPConnection *)data; + + RecursiveLocker locker(connection->Lock()); + + connection->fSendNext = connection->fSendUnacknowledged; + connection->_SendQueued(); + connection->fSendNext = connection->fSendMax; +} + + +/*static*/ void +TCPConnection::_PersistTimer(net_timer *timer, void *data) +{ + TCPConnection *connection = (TCPConnection *)data; + + RecursiveLocker locker(connection->Lock()); + connection->_SendQueued(true); +} + + +/*static*/ void +TCPConnection::_DelayedAcknowledgeTimer(struct net_timer *timer, void *data) +{ + TCPConnection *connection = (TCPConnection *)data; + + RecursiveLocker locker(connection->Lock()); + connection->_SendQueued(true); +} + + +/*static*/ void TCPConnection::_TimeWait(struct net_timer *timer, void *data) { } +// #pragma mark - hash functions + + int TCPConnection::Compare(void *_connection, const void *_key) { diff --git a/src/add-ons/kernel/network/protocols/tcp/TCPConnection.h b/src/add-ons/kernel/network/protocols/tcp/TCPConnection.h index 6f8cf3eeee..6b5dfcc654 100644 --- a/src/add-ons/kernel/network/protocols/tcp/TCPConnection.h +++ b/src/add-ons/kernel/network/protocols/tcp/TCPConnection.h @@ -53,18 +53,21 @@ class TCPConnection : public net_protocol { net_buffer *buffer); int32 Receive(tcp_segment_header& segment, net_buffer *buffer); - static void ResendSegment(struct net_timer *timer, void *data); static int Compare(void *_packet, const void *_key); static uint32 Hash(void *_packet, const void *_key, uint32 range); static int32 HashOffset() { return offsetof(TCPConnection, fHashNext); } private: + void _StartPersistTimer(); uint8 _CurrentFlags(); bool _ShouldSendSegment(tcp_segment_header &segment, uint32 length, bool outstandingAcknowledge); status_t _SendQueued(bool force = false); - static void _TimeWait(struct net_timer *timer, void *data); + static void _TimeWait(net_timer *timer, void *data); + static void _RetransmitTimer(net_timer *timer, void *data); + static void _PersistTimer(net_timer *timer, void *data); + static void _DelayedAcknowledgeTimer(net_timer *timer, void *data); TCPConnection *fHashNext; @@ -91,6 +94,7 @@ class TCPConnection : public net_protocol { // TODO: don't use a net_route, but a net_route_info!!! tcp_sequence fReceiveNext; + tcp_sequence fReceiveMaxAdvertised; uint32 fReceiveWindow; uint32 fReceiveMaxSegmentSize; BufferQueue fReceiveQueue; @@ -110,10 +114,11 @@ class TCPConnection : public net_protocol { tcp_state fState; status_t fError; - vint32 fDelayedAcknowledge; // timer - net_timer fTimer; + net_timer fRetransmitTimer; + net_timer fPersistTimer; + net_timer fDelayedAcknowledgeTimer; }; #endif // TCP_CONNECTION_H diff --git a/src/add-ons/kernel/network/protocols/tcp/tcp.h b/src/add-ons/kernel/network/protocols/tcp/tcp.h index c17a2795d4..32a3471d74 100644 --- a/src/add-ons/kernel/network/protocols/tcp/tcp.h +++ b/src/add-ons/kernel/network/protocols/tcp/tcp.h @@ -101,7 +101,9 @@ class tcp_sequence { #define TCP_FLAG_CONGESTION_WINDOW_REDUCED 0x80 #define TCP_CONNECTION_TIMEOUT 75000000 // 75 secs +#define TCP_DELAYED_ACKNOWLEDGE_TIMEOUT 100000 // 100 msecs #define TCP_DEFAULT_MAX_SEGMENT_SIZE 536 +#define TCP_MAX_WINDOW 65535 struct tcp_option { uint8 kind;