Files
haiku-beta6/src/add-ons/kernel/network/protocols/tcp/EndpointManager.cpp
T
Axel Dörfler 42759f5626 * _BindToEphemeral() now remembers the last port it used, and tries a higher
one next. This should work around the problem Ingo observed with the OpenSSH
  tests (not tested, though).
* The only TCP compliant fix for this problem would be to not enter time wait
  for local connections at all (which I will do soon).
* Another work around other implementations use is to accept incoming
  connections for time wait endpoints (the connection will then be replaced).
  I'm not yet sure if I want that behaviour, though.
* Added tracing for unbinding endpoints (with stack trace) to investigate
  another bug (sometimes closed connections aren't actually closed).


git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@25218 a95241bf-73f2-0310-859d-f6bbb57e9c96
2008-04-27 18:14:03 +00:00

503 lines
11 KiB
C++

/*
* Copyright 2006-2008, Haiku, Inc. All Rights Reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Axel Dörfler, [email protected]
* Hugo Santos, [email protected]
*/
#include "EndpointManager.h"
#include <new>
#include <unistd.h>
#include <KernelExport.h>
#include <NetUtilities.h>
#include <util/AutoLock.h>
#include <tracing.h>
#include "TCPEndpoint.h"
//#define TRACE_ENDPOINT_MANAGER
#ifdef TRACE_ENDPOINT_MANAGER
# define TRACE(x) dprintf x
#else
# define TRACE(x)
#endif
//#define ENDPOINT_TRACING
#ifdef ENDPOINT_TRACING
namespace EndpointTracing {
class Unbind : public AbstractTraceEntry {
public:
Unbind(TCPEndpoint* endpoint)
:
fEndpoint(endpoint)
{
fStackTrace = capture_tracing_stack_trace(10, 0, false);
endpoint->LocalAddress().AsString(fLocal, sizeof(fLocal), true);
endpoint->PeerAddress().AsString(fPeer, sizeof(fPeer), true);
Initialized();
}
virtual void DumpStackTrace(TraceOutput& out)
{
out.PrintStackTrace(fStackTrace);
}
virtual void AddDump(TraceOutput& out)
{
out.Print("tcp:e:unbind: %p, local %s, peer %s"
"%lu", fEndpoint, fLocal, fPeer);
}
protected:
TCPEndpoint* fEndpoint;
tracing_stack_trace* fStackTrace;
char fLocal[32];
char fPeer[32];
};
} // namespace EndpointTracing
# define T(x) new(std::nothrow) EndpointTracing::x
#else
# define T(x)
#endif // ENDPOINT_TRACING
static const uint16 kLastReservedPort = 1023;
static const uint16 kFirstEphemeralPort = 40000;
ConnectionHashDefinition::ConnectionHashDefinition(EndpointManager *manager)
:
fManager(manager)
{
}
size_t
ConnectionHashDefinition::HashKey(const KeyType &key) const
{
return ConstSocketAddress(fManager->AddressModule(),
key.first).HashPair(key.second);
}
size_t
ConnectionHashDefinition::Hash(TCPEndpoint *endpoint) const
{
return endpoint->LocalAddress().HashPair(*endpoint->PeerAddress());
}
bool
ConnectionHashDefinition::Compare(const KeyType &key,
TCPEndpoint *endpoint) const
{
return endpoint->LocalAddress().EqualTo(key.first, true)
&& endpoint->PeerAddress().EqualTo(key.second, true);
}
HashTableLink<TCPEndpoint> *
ConnectionHashDefinition::GetLink(TCPEndpoint *endpoint) const
{
return &endpoint->fConnectionHashLink;
}
// #pragma mark -
size_t
EndpointHashDefinition::HashKey(uint16 port) const
{
return port;
}
size_t
EndpointHashDefinition::Hash(TCPEndpoint *endpoint) const
{
return endpoint->LocalAddress().Port();
}
bool
EndpointHashDefinition::Compare(uint16 port, TCPEndpoint *endpoint) const
{
return endpoint->LocalAddress().Port() == port;
}
bool
EndpointHashDefinition::CompareValues(TCPEndpoint *first,
TCPEndpoint *second) const
{
return first->LocalAddress().Port() == second->LocalAddress().Port();
}
HashTableLink<TCPEndpoint> *
EndpointHashDefinition::GetLink(TCPEndpoint *endpoint) const
{
return &endpoint->fEndpointHashLink;
}
// #pragma mark -
EndpointManager::EndpointManager(net_domain *domain)
:
fDomain(domain),
fConnectionHash(this),
fLastPort(kFirstEphemeralPort)
{
benaphore_init(&fLock, "endpoint manager");
}
EndpointManager::~EndpointManager()
{
benaphore_destroy(&fLock);
}
status_t
EndpointManager::InitCheck() const
{
if (fConnectionHash.InitCheck() < B_OK)
return fConnectionHash.InitCheck();
if (fEndpointHash.InitCheck() < B_OK)
return fEndpointHash.InitCheck();
if (fLock.sem < B_OK)
return fLock.sem;
return B_OK;
}
// #pragma mark - connections
/*! Returns the endpoint matching the connection.
You must hold the manager's lock when calling this method.
*/
TCPEndpoint *
EndpointManager::_LookupConnection(const sockaddr *local, const sockaddr *peer)
{
return fConnectionHash.Lookup(std::make_pair(local, peer));
}
status_t
EndpointManager::SetConnection(TCPEndpoint *endpoint,
const sockaddr *_local, const sockaddr *peer, const sockaddr *interfaceLocal)
{
TRACE(("EndpointManager::SetConnection(%p)\n", endpoint));
BenaphoreLocker _(fLock);
SocketAddressStorage local(AddressModule());
local.SetTo(_local);
if (local.IsEmpty(false)) {
uint16 port = local.Port();
local.SetTo(interfaceLocal);
local.SetPort(port);
}
if (_LookupConnection(*local, peer) != NULL)
return EADDRINUSE;
endpoint->LocalAddress().SetTo(*local);
endpoint->PeerAddress().SetTo(peer);
fConnectionHash.Insert(endpoint);
return B_OK;
}
status_t
EndpointManager::SetPassive(TCPEndpoint *endpoint)
{
BenaphoreLocker _(fLock);
if (!endpoint->IsBound()) {
// if the socket is unbound first bind it to ephemeral
SocketAddressStorage local(AddressModule());
local.SetToEmpty();
status_t status = _BindToEphemeral(endpoint, *local);
if (status < B_OK)
return status;
}
SocketAddressStorage passive(AddressModule());
passive.SetToEmpty();
if (_LookupConnection(*endpoint->LocalAddress(), *passive))
return EADDRINUSE;
endpoint->PeerAddress().SetTo(*passive);
fConnectionHash.Insert(endpoint);
return B_OK;
}
TCPEndpoint *
EndpointManager::FindConnection(sockaddr *local, sockaddr *peer)
{
BenaphoreLocker _(fLock);
TCPEndpoint *endpoint = _LookupConnection(local, peer);
if (endpoint != NULL) {
TRACE(("TCP: Received packet corresponds to explicit endpoint %p\n", endpoint));
return endpoint;
}
// no explicit endpoint exists, check for wildcard endpoints
SocketAddressStorage wildcard(AddressModule());
wildcard.SetToEmpty();
endpoint = _LookupConnection(local, *wildcard);
if (endpoint != NULL) {
TRACE(("TCP: Received packet corresponds to wildcard endpoint %p\n", endpoint));
return endpoint;
}
SocketAddressStorage localWildcard(AddressModule());
localWildcard.SetToEmpty();
localWildcard.SetPort(AddressModule()->get_port(local));
endpoint = _LookupConnection(*localWildcard, *wildcard);
if (endpoint != NULL) {
TRACE(("TCP: Received packet corresponds to local wildcard endpoint %p\n", endpoint));
return endpoint;
}
// no matching endpoint exists
TRACE(("TCP: no matching endpoint!\n"));
return NULL;
}
// #pragma mark - endpoints
status_t
EndpointManager::Bind(TCPEndpoint *endpoint, const sockaddr *address)
{
// TODO check the family:
//
// if (!AddressModule()->is_understandable(address))
// return EAFNOSUPPORT;
BenaphoreLocker _(fLock);
if (AddressModule()->get_port(address) == 0)
return _BindToEphemeral(endpoint, address);
return _BindToAddress(endpoint, address);
}
status_t
EndpointManager::BindChild(TCPEndpoint *endpoint)
{
BenaphoreLocker _(fLock);
return _Bind(endpoint, *endpoint->LocalAddress());
}
status_t
EndpointManager::_BindToAddress(TCPEndpoint *endpoint, const sockaddr *_address)
{
TRACE(("EndpointManager::BindToAddress(%p)\n", endpoint));
ConstSocketAddress address(AddressModule(), _address);
uint16 port = address.Port();
// TODO this check follows very typical UNIX semantics
// and generally should be improved.
if (ntohs(port) <= kLastReservedPort && geteuid() != 0)
return B_PERMISSION_DENIED;
EndpointTable::ValueIterator portUsers = fEndpointHash.Lookup(port);
while (portUsers.HasNext()) {
TCPEndpoint *user = portUsers.Next();
if (user->LocalAddress().IsEmpty(false)
|| address.EqualTo(*user->LocalAddress(), false)) {
if ((endpoint->socket->options & SO_REUSEADDR) == 0)
return EADDRINUSE;
// TODO lock endpoint before retriving state?
if (user->State() != TIME_WAIT && user->State() != CLOSED)
return EADDRINUSE;
}
}
return _Bind(endpoint, *address);
}
status_t
EndpointManager::_BindToEphemeral(TCPEndpoint *endpoint,
const sockaddr *address)
{
TRACE(("EndpointManager::BindToEphemeral(%p)\n", endpoint));
uint32 max = fLastPort + 65536;
for (int32 i = 1; i < 5; i++) {
// try to retrieve a more or less random port
uint32 step = i == 4 ? 1 : (system_time() & 0x1f) + 1;
uint32 counter = fLastPort + step;
while (counter < max) {
uint16 port = counter & 0xffff;
if (port <= kLastReservedPort)
port += kLastReservedPort;
fLastPort = port;
port = htons(port);
if (!fEndpointHash.Lookup(port).HasNext()) {
// found a port
SocketAddressStorage newAddress(AddressModule());
newAddress.SetTo(address);
newAddress.SetPort(port);
TRACE((" EndpointManager::BindToEphemeral(%p) -> %s\n",
endpoint, AddressString(Domain(), *newAddress,
true).Data()));
return _Bind(endpoint, *newAddress);
}
counter += step;
}
}
// could not find a port!
return EADDRINUSE;
}
status_t
EndpointManager::_Bind(TCPEndpoint *endpoint, const sockaddr *address)
{
// Thus far we have checked if the Bind() is allowed
status_t status = endpoint->next->module->bind(endpoint->next, address);
if (status < B_OK)
return status;
fEndpointHash.Insert(endpoint);
return B_OK;
}
status_t
EndpointManager::Unbind(TCPEndpoint *endpoint)
{
TRACE(("EndpointManager::Unbind(%p)\n", endpoint));
T(Unbind(endpoint));
if (endpoint == NULL || !endpoint->IsBound()) {
TRACE((" endpoint is unbound.\n"));
return B_BAD_VALUE;
}
BenaphoreLocker _(fLock);
if (!fEndpointHash.Remove(endpoint))
panic("bound endpoint %p not in hash!", endpoint);
fConnectionHash.Remove(endpoint);
(*endpoint->LocalAddress())->sa_len = 0;
return B_OK;
}
status_t
EndpointManager::ReplyWithReset(tcp_segment_header &segment,
net_buffer *buffer)
{
TRACE(("TCP: Sending RST...\n"));
net_buffer *reply = gBufferModule->create(512);
if (reply == NULL)
return B_NO_MEMORY;
AddressModule()->set_to(reply->source, buffer->destination);
AddressModule()->set_to(reply->destination, buffer->source);
tcp_segment_header outSegment(TCP_FLAG_RESET);
outSegment.sequence = 0;
outSegment.acknowledge = 0;
outSegment.advertised_window = 0;
outSegment.urgent_offset = 0;
if ((segment.flags & TCP_FLAG_ACKNOWLEDGE) == 0) {
outSegment.flags |= TCP_FLAG_ACKNOWLEDGE;
outSegment.acknowledge = segment.sequence + buffer->size;
// TODO: Confirm:
if ((segment.flags & (TCP_FLAG_SYNCHRONIZE | TCP_FLAG_FINISH)) != 0)
outSegment.acknowledge++;
} else
outSegment.sequence = segment.acknowledge;
status_t status = add_tcp_header(AddressModule(), outSegment, reply);
if (status == B_OK)
status = Domain()->module->send_data(NULL, reply);
if (status != B_OK)
gBufferModule->free(reply);
return status;
}
void
EndpointManager::DumpEndpoints() const
{
kprintf("-------- TCP Domain %p ---------\n", this);
kprintf("%10s %20s %20s %8s %8s %12s\n", "address", "local", "peer",
"recv-q", "send-q", "state");
ConnectionTable::Iterator iterator = fConnectionHash.GetIterator();
while (iterator.HasNext()) {
TCPEndpoint *endpoint = iterator.Next();
char localBuf[64], peerBuf[64];
endpoint->LocalAddress().AsString(localBuf, sizeof(localBuf), true);
endpoint->PeerAddress().AsString(peerBuf, sizeof(peerBuf), true);
kprintf("%p %20s %20s %8lu %8lu %12s\n", endpoint, localBuf, peerBuf,
endpoint->fReceiveQueue.Available(), endpoint->fSendQueue.Used(),
name_for_state(endpoint->State()));
}
}