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;
gBufferModule->remove_header(buffer, size);
buffer->sequence += size;
fNumBytes -= size;
fContiguousBytes -= size;
}
@@ -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)
{
@@ -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
@@ -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;