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
This commit is contained in:
Axel Dörfler
2006-11-29 21:53:06 +00:00
parent 54c22262f6
commit 26417177e7
4 changed files with 136 additions and 39 deletions
@@ -193,6 +193,7 @@ BufferQueue::RemoveUntil(tcp_sequence sequence)
size_t size = sequence - buffer->sequence; size_t size = sequence - buffer->sequence;
gBufferModule->remove_header(buffer, size); gBufferModule->remove_header(buffer, size);
buffer->sequence += size;
fNumBytes -= size; fNumBytes -= size;
fContiguousBytes -= size; fContiguousBytes -= size;
} }
@@ -70,6 +70,7 @@ TCPConnection::TCPConnection(net_socket *socket)
fDuplicateAcknowledgeCount(0), fDuplicateAcknowledgeCount(0),
fRoute(NULL), fRoute(NULL),
fReceiveNext(0), fReceiveNext(0),
fReceiveMaxAdvertised(0),
fReceiveWindow(socket->receive.buffer_size), fReceiveWindow(socket->receive.buffer_size),
fReceiveMaxSegmentSize(TCP_DEFAULT_MAX_SEGMENT_SIZE), fReceiveMaxSegmentSize(TCP_DEFAULT_MAX_SEGMENT_SIZE),
fReceiveQueue(socket->receive.buffer_size), fReceiveQueue(socket->receive.buffer_size),
@@ -77,7 +78,7 @@ TCPConnection::TCPConnection(net_socket *socket)
fState(CLOSED), fState(CLOSED),
fError(B_OK) fError(B_OK)
{ {
gStackModule->init_timer(&fTimer, _TimeWait, this); //gStackModule->init_timer(&fTimer, _TimeWait, this);
recursive_lock_init(&fLock, "tcp lock"); recursive_lock_init(&fLock, "tcp lock");
// TODO: to be replaced with a real locking strategy! // TODO: to be replaced with a real locking strategy!
@@ -85,12 +86,17 @@ TCPConnection::TCPConnection(net_socket *socket)
//benaphore_init(&fSendLock, "tcp send"); //benaphore_init(&fSendLock, "tcp send");
fSendLock = create_sem(0, "tcp send"); fSendLock = create_sem(0, "tcp send");
fReceiveLock = create_sem(0, "tcp receive"); 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() TCPConnection::~TCPConnection()
{ {
gStackModule->set_timer(&fTimer, -1); //gStackModule->set_timer(&fTimer, -1);
recursive_lock_destroy(&fLock); recursive_lock_destroy(&fLock);
//benaphore_destroy(&fReceiveLock); //benaphore_destroy(&fReceiveLock);
@@ -115,6 +121,9 @@ TCPConnection::InitCheck() const
} }
// #pragma mark - protocol API
status_t status_t
TCPConnection::Open() TCPConnection::Open()
{ {
@@ -127,8 +136,9 @@ TCPConnection::Open()
status_t status_t
TCPConnection::Close() TCPConnection::Close()
{ {
//BenaphoreLocker lock(&fSendLock);
TRACE(("TCP:%p.Close()\n", this)); TRACE(("TCP:%p.Close()\n", this));
RecursiveLocker lock(fLock);
if (fState == SYNCHRONIZE_SENT || fState == LISTEN) { if (fState == SYNCHRONIZE_SENT || fState == LISTEN) {
fState = CLOSED; fState = CLOSED;
return B_OK; return B_OK;
@@ -161,7 +171,6 @@ TCPConnection::Free()
TRACE(("TCP:%p.Free()\n", this)); TRACE(("TCP:%p.Free()\n", this));
// TODO: if this connection is not in the hash, we don't have to call this one // TODO: if this connection is not in the hash, we don't have to call this one
remove_connection(this);
return B_OK; return B_OK;
} }
@@ -421,18 +430,40 @@ TCPConnection::ReadAvailable()
} }
// #pragma mark - misc
status_t status_t
TCPConnection::DelayedAcknowledge() TCPConnection::DelayedAcknowledge()
{ {
// TODO: use timer instead and/or piggyback on send // if the timer is already running, and there is still more than
return _SendQueued(); // 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 status_t
TCPConnection::SendAcknowledge() 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 int32
TCPConnection::ListenReceive(tcp_segment_header &segment, net_buffer *buffer) 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); fSendQueue.RemoveUntil(segment.acknowledge);
fSendUnacknowledged = 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 // notify threads waiting on the socket to become writable again
release_sem_etc(fSendLock, 1, B_DO_NOT_RESCHEDULE); 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); gSocketModule->notify(socket, B_SELECT_WRITE, fSendWindow);
// if there is data left to be send, send it now // if there is data left to be send, send it now
//return _SendQueuedData(TCP_FLAG_ACKNOWLEDGE, false); _SendQueued();
return DROP; return DROP;
} }
} else if (segment.acknowledge == fSendUnacknowledged } else if (segment.acknowledge == fSendUnacknowledged
@@ -621,7 +656,6 @@ TCPConnection::Receive(tcp_segment_header &segment, net_buffer *buffer)
// TODO: close connection depending on state // TODO: close connection depending on state
fError = ECONNREFUSED; fError = ECONNREFUSED;
fState = CLOSED; fState = CLOSED;
remove_connection(this);
} }
return DROP; return DROP;
@@ -692,15 +726,20 @@ TCPConnection::Receive(tcp_segment_header &segment, net_buffer *buffer)
if (fSendUnacknowledged >= segment.acknowledge) { if (fSendUnacknowledged >= segment.acknowledge) {
// this is a duplicate acknowledge // this is a duplicate acknowledge
// TODO: handle this! // TODO: handle this!
fDuplicateAcknowledgeCount++;
} else { } else {
// this segment acknowleges in flight data // this segment acknowleges in flight data
fDuplicateAcknowledgeCount = 0; fDuplicateAcknowledgeCount = 0;
if (fSendMax == segment.acknowledge) { if (fSendMax == segment.acknowledge) {
// there is no outstanding data to be acknowledged // 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 { } else {
// TODO: set retransmit timer // TODO: set retransmit timer correctly
if (!gStackModule->is_timer_active(&fRetransmitTimer))
gStackModule->set_timer(&fRetransmitTimer, 1000000LL);
} }
fSendUnacknowledged = segment.acknowledge; fSendUnacknowledged = segment.acknowledge;
@@ -774,7 +813,8 @@ TCPConnection::Receive(tcp_segment_header &segment, net_buffer *buffer)
// put it in the receive buffer // put it in the receive buffer
if (buffer->size > 0) { if (buffer->size > 0) {
fReceiveNext += buffer->size; if (fReceiveNext == segment.sequence)
fReceiveNext += buffer->size;
fReceiveQueue.Add(buffer, segment.sequence); fReceiveQueue.Add(buffer, segment.sequence);
} else } else
gBufferModule->free(buffer); gBufferModule->free(buffer);
@@ -783,19 +823,7 @@ TCPConnection::Receive(tcp_segment_header &segment, net_buffer *buffer)
} }
/*! // #pragma mark - send
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!
}
/*! /*!
@@ -838,11 +866,12 @@ inline bool
TCPConnection::_ShouldSendSegment(tcp_segment_header &segment, uint32 length, TCPConnection::_ShouldSendSegment(tcp_segment_header &segment, uint32 length,
bool outstandingAcknowledge) 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) { 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 if (length == fSendMaxSegmentSize
|| ((!outstandingAcknowledge || (fOptions & TCP_NODELAY) != 0) || ((!outstandingAcknowledge || (fOptions & TCP_NODELAY) != 0)
&& tcp_sequence(fSendNext + length) == fSendQueue.LastSequence()) && tcp_sequence(fSendNext + length) == fSendQueue.LastSequence())
@@ -851,11 +880,23 @@ TCPConnection::_ShouldSendSegment(tcp_segment_header &segment, uint32 length,
return true; 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; return true;
// there is no reason to send a segment just now
return false; return false;
} }
@@ -912,8 +953,14 @@ dprintf("fSendWindow = %lu, available = %lu, fSendNext = %lu, fSendUnacknowledge
while (true) { while (true) {
dprintf("length = %ld, segmentLength = %lu\n", length, segmentLength); dprintf("length = %ld, segmentLength = %lu\n", length, segmentLength);
// Determine if we should really send this segment // 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; return B_OK;
}
net_buffer *buffer = gBufferModule->create(256); net_buffer *buffer = gBufferModule->create(256);
if (buffer == NULL) if (buffer == NULL)
@@ -973,6 +1020,9 @@ dprintf("length = %ld, segmentLength = %lu\n", length, segmentLength);
fSendMax += size; fSendMax += size;
fSendNext += size; fSendNext += size;
fReceiveMaxAdvertised = fReceiveNext
+ ((uint32)segment.advertised_window << fReceiveWindowShift);
length -= segmentLength; length -= segmentLength;
if (length == 0) if (length == 0)
break; 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) TCPConnection::_TimeWait(struct net_timer *timer, void *data)
{ {
} }
// #pragma mark - hash functions
int int
TCPConnection::Compare(void *_connection, const void *_key) TCPConnection::Compare(void *_connection, const void *_key)
{ {
@@ -53,18 +53,21 @@ class TCPConnection : public net_protocol {
net_buffer *buffer); net_buffer *buffer);
int32 Receive(tcp_segment_header& segment, 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 int Compare(void *_packet, const void *_key);
static uint32 Hash(void *_packet, const void *_key, uint32 range); static uint32 Hash(void *_packet, const void *_key, uint32 range);
static int32 HashOffset() { return offsetof(TCPConnection, fHashNext); } static int32 HashOffset() { return offsetof(TCPConnection, fHashNext); }
private: private:
void _StartPersistTimer();
uint8 _CurrentFlags(); uint8 _CurrentFlags();
bool _ShouldSendSegment(tcp_segment_header &segment, uint32 length, bool _ShouldSendSegment(tcp_segment_header &segment, uint32 length,
bool outstandingAcknowledge); bool outstandingAcknowledge);
status_t _SendQueued(bool force = false); 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; TCPConnection *fHashNext;
@@ -91,6 +94,7 @@ class TCPConnection : public net_protocol {
// TODO: don't use a net_route, but a net_route_info!!! // TODO: don't use a net_route, but a net_route_info!!!
tcp_sequence fReceiveNext; tcp_sequence fReceiveNext;
tcp_sequence fReceiveMaxAdvertised;
uint32 fReceiveWindow; uint32 fReceiveWindow;
uint32 fReceiveMaxSegmentSize; uint32 fReceiveMaxSegmentSize;
BufferQueue fReceiveQueue; BufferQueue fReceiveQueue;
@@ -110,10 +114,11 @@ class TCPConnection : public net_protocol {
tcp_state fState; tcp_state fState;
status_t fError; status_t fError;
vint32 fDelayedAcknowledge;
// timer // timer
net_timer fTimer; net_timer fRetransmitTimer;
net_timer fPersistTimer;
net_timer fDelayedAcknowledgeTimer;
}; };
#endif // TCP_CONNECTION_H #endif // TCP_CONNECTION_H
@@ -101,7 +101,9 @@ class tcp_sequence {
#define TCP_FLAG_CONGESTION_WINDOW_REDUCED 0x80 #define TCP_FLAG_CONGESTION_WINDOW_REDUCED 0x80
#define TCP_CONNECTION_TIMEOUT 75000000 // 75 secs #define TCP_CONNECTION_TIMEOUT 75000000 // 75 secs
#define TCP_DELAYED_ACKNOWLEDGE_TIMEOUT 100000 // 100 msecs
#define TCP_DEFAULT_MAX_SEGMENT_SIZE 536 #define TCP_DEFAULT_MAX_SEGMENT_SIZE 536
#define TCP_MAX_WINDOW 65535
struct tcp_option { struct tcp_option {
uint8 kind; uint8 kind;