tcp: Don't skip TIME_WAIT state for loopback sockets
This fixes a SEGFAULT in the tcp add-on reported in issue #15952. See that issue for some analysis. The short version is that, when closing a session over the loopback interface, there is a special branch which skips the TIME_WAIT state and instead just releases the socket while handling a RST/ACK segment. If the timing is right this can lead to the reference counts becoming imbalanced, leading to the code in tcp_receive_data segfaulting when it tries to release the reference it acquired from EndpointManager::FindConnection. I can't find any other systems which skip the TIME_WAIT state with loopback sessions, and I'm not entirely certain that it's a totally safe thing to do anyway. This patch instead just treats local sessions the same way it does a remote session and uses the TIME_WAIT state. Any workload which creates and discards lots of ephemeral sockets can just use SO_REUSEADDR to handle this situation like any other system. To add a final bit of safety, the only place where a net_socket can be used after calling gSocketModule->release_socket(net_socket*) is in tcp_receive_data(). release_socket() returns true if the reference count falls to zero, deleting the socket. There was an unused segment action flag DELETE_ENDPOINT that I renamed to DELETED_ENDPOINT, which is used by tcp_receive_data to know whether its safe to release its reference to the socket after calling TCPEndpoint::SegmentReceived(). Change-Id: I2652fb225c3c8419234cfd627f74ff2de8402003 Reviewed-on: https://review.haiku-os.org/c/haiku/+/2793 Reviewed-by: Axel Dörfler <[email protected]> Reviewed-by: waddlesplash <[email protected]>
This commit is contained in:
committed by
waddlesplash
parent
8a9a366fef
commit
51dd385e3e
@@ -1151,12 +1151,6 @@ TCPEndpoint::_EnterTimeWait()
|
||||
|
||||
if (fState == TIME_WAIT) {
|
||||
_CancelConnectionTimers();
|
||||
|
||||
if (IsLocal()) {
|
||||
// we do not use TIME_WAIT state for local connections
|
||||
fFlags |= FLAG_DELETE_ON_CLOSE;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_UpdateTimeWait();
|
||||
@@ -1905,8 +1899,10 @@ TCPEndpoint::SegmentReceived(tcp_segment_header& segment, net_buffer* buffer)
|
||||
|
||||
if ((fFlags & (FLAG_CLOSED | FLAG_DELETE_ON_CLOSE))
|
||||
== (FLAG_CLOSED | FLAG_DELETE_ON_CLOSE)) {
|
||||
|
||||
locker.Unlock();
|
||||
gSocketModule->release_socket(socket);
|
||||
if (gSocketModule->release_socket(socket))
|
||||
segmentAction |= DELETED_ENDPOINT;
|
||||
}
|
||||
|
||||
return segmentAction;
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
#include "EndpointManager.h"
|
||||
#include "TCPEndpoint.h"
|
||||
#include "tcp.h"
|
||||
|
||||
#include <net_protocol.h>
|
||||
#include <net_stat.h>
|
||||
@@ -710,7 +711,14 @@ tcp_receive_data(net_buffer* buffer)
|
||||
buffer->destination, buffer->source);
|
||||
if (endpoint != NULL) {
|
||||
segmentAction = endpoint->SegmentReceived(segment, buffer);
|
||||
gSocketModule->release_socket(endpoint->socket);
|
||||
|
||||
// There are some states in which the socket could have been deleted
|
||||
// while handling a segment. If this flag is set in segmentAction
|
||||
// then we know the socket has been freed and can skip releasing
|
||||
// the reference acquired in EndpointManager::FindConnection()
|
||||
// above.
|
||||
if ((segmentAction & DELETED_ENDPOINT) == 0)
|
||||
gSocketModule->release_socket(endpoint->socket);
|
||||
} else if ((segment.flags & TCP_FLAG_RESET) == 0)
|
||||
segmentAction = DROP | RESET;
|
||||
|
||||
|
||||
@@ -267,12 +267,12 @@ struct tcp_segment_header {
|
||||
};
|
||||
|
||||
enum tcp_segment_action {
|
||||
KEEP = 0x00,
|
||||
DROP = 0x01,
|
||||
RESET = 0x02,
|
||||
ACKNOWLEDGE = 0x04,
|
||||
IMMEDIATE_ACKNOWLEDGE = 0x08,
|
||||
DELETE_ENDPOINT = 0x10,
|
||||
KEEP = 0x00,
|
||||
DROP = 0x01,
|
||||
RESET = 0x02,
|
||||
ACKNOWLEDGE = 0x04,
|
||||
IMMEDIATE_ACKNOWLEDGE = 0x08,
|
||||
DELETED_ENDPOINT = 0x10,
|
||||
};
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user