Files
haiku-beta6/src/add-ons/kernel/network/protocols/tcp/EndpointManager.cpp
T

440 lines
9.9 KiB
C++
Raw Normal View History

2006-12-04 13:44:35 +00:00
/*
* Copyright 2006-2007, Haiku, Inc. All Rights Reserved.
2006-12-04 13:44:35 +00:00
* Distributed under the terms of the MIT License.
*
* Authors:
* Axel Dörfler, [email protected]
*/
#include "EndpointManager.h"
2006-12-04 14:49:58 +00:00
#include "TCPEndpoint.h"
2006-12-04 13:44:35 +00:00
#include <NetUtilities.h>
#include <util/AutoLock.h>
#include <KernelExport.h>
//#define TRACE_ENDPOINT_MANAGER
2006-12-04 13:44:35 +00:00
#ifdef TRACE_ENDPOINT_MANAGER
# define TRACE(x) dprintf x
#else
# define TRACE(x)
#endif
static const uint16 kLastReservedPort = 1023;
static const uint16 kFirstEphemeralPort = 40000;
2006-12-04 13:44:35 +00:00
ConnectionHashDefinition::ConnectionHashDefinition(EndpointManager *manager)
: fManager(manager) {}
size_t
ConnectionHashDefinition::HashKey(const KeyType &key) const
{
return ConstSocketAddress(fManager->AddressModule(),
key.first).HashPair(key.second);
}
2006-12-04 13:44:35 +00:00
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);
}
2007-04-25 18:55:05 +00:00
HashTableLink<TCPEndpoint> *
ConnectionHashDefinition::GetLink(TCPEndpoint *endpoint) const
2007-04-25 18:55:05 +00:00
{
return &endpoint->fConnectionHashLink;
}
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;
}
2006-12-04 13:44:35 +00:00
2007-04-25 18:55:05 +00:00
HashTableLink<TCPEndpoint> *
EndpointHashDefinition::GetLink(TCPEndpoint *endpoint) const
2007-04-25 18:55:05 +00:00
{
return &endpoint->fEndpointHashLink;
}
2007-04-16 00:27:39 +00:00
EndpointManager::EndpointManager(net_domain *domain)
: fDomain(domain), fConnectionHash(this)
2006-12-04 13:44:35 +00:00
{
benaphore_init(&fLock, "endpoint manager");
2006-12-04 13:44:35 +00:00
}
EndpointManager::~EndpointManager()
{
benaphore_destroy(&fLock);
2006-12-04 13:44:35 +00:00
}
status_t
EndpointManager::InitCheck() const
{
if (fConnectionHash.InitCheck() < B_OK)
return fConnectionHash.InitCheck();
if (fEndpointHash.InitCheck() < B_OK)
return fEndpointHash.InitCheck();
2006-12-04 13:44:35 +00:00
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.
*/
2006-12-04 14:49:58 +00:00
TCPEndpoint *
EndpointManager::_LookupConnection(const sockaddr *local, const sockaddr *peer)
2006-12-04 13:44:35 +00:00
{
return fConnectionHash.Lookup(std::make_pair(local, peer));
2006-12-04 13:44:35 +00:00
}
status_t
EndpointManager::SetConnection(TCPEndpoint *endpoint,
const sockaddr *_local, const sockaddr *peer, const sockaddr *interfaceLocal)
2006-12-04 13:44:35 +00:00
{
TRACE(("EndpointManager::SetConnection(%p)\n", endpoint));
2006-12-04 13:44:35 +00:00
BenaphoreLocker _(fLock);
2006-12-04 13:44:35 +00:00
SocketAddressStorage local(AddressModule());
local.SetTo(_local);
if (local.IsEmpty(false)) {
uint16 port = local.Port();
local.SetTo(interfaceLocal);
local.SetPort(port);
}
2006-12-04 13:44:35 +00:00
if (_LookupConnection(*local, peer) != NULL)
return EADDRINUSE;
2006-12-04 13:44:35 +00:00
endpoint->LocalAddress().SetTo(*local);
endpoint->PeerAddress().SetTo(peer);
2006-12-04 13:44:35 +00:00
2007-04-25 18:55:05 +00:00
fConnectionHash.Insert(endpoint);
return B_OK;
2006-12-04 13:44:35 +00:00
}
status_t
EndpointManager::SetPassive(TCPEndpoint *endpoint)
2006-12-04 13:44:35 +00:00
{
BenaphoreLocker _(fLock);
2007-04-09 09:34:47 +00:00
if (!endpoint->IsBound()) {
// if the socket is unbound first bind it to ephemeral
SocketAddressStorage local(AddressModule());
local.SetToEmpty();
2006-12-04 13:44:35 +00:00
status_t status = _BindToEphemeral(endpoint, *local);
if (status < B_OK)
return status;
2006-12-04 13:44:35 +00:00
}
SocketAddressStorage passive(AddressModule());
passive.SetToEmpty();
2006-12-04 13:44:35 +00:00
if (_LookupConnection(*endpoint->LocalAddress(), *passive))
2006-12-04 13:44:35 +00:00
return EADDRINUSE;
endpoint->PeerAddress().SetTo(*passive);
2007-04-25 18:55:05 +00:00
fConnectionHash.Insert(endpoint);
return B_OK;
2006-12-04 13:44:35 +00:00
}
2006-12-04 14:49:58 +00:00
TCPEndpoint *
2006-12-04 13:44:35 +00:00
EndpointManager::FindConnection(sockaddr *local, sockaddr *peer)
{
BenaphoreLocker _(fLock);
2006-12-04 14:49:58 +00:00
TCPEndpoint *endpoint = _LookupConnection(local, peer);
2006-12-04 13:44:35 +00:00
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();
2006-12-04 13:44:35 +00:00
endpoint = _LookupConnection(local, *wildcard);
2006-12-04 13:44:35 +00:00
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));
2006-12-04 13:44:35 +00:00
endpoint = _LookupConnection(*localWildcard, *wildcard);
2006-12-04 13:44:35 +00:00
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)
2006-12-04 13:44:35 +00:00
{
// TODO check the family:
//
// if (!AddressModule()->is_understandable(address))
// return EAFNOSUPPORT;
BenaphoreLocker _(fLock);
2006-12-04 13:44:35 +00:00
if (AddressModule()->get_port(address) == 0)
return _BindToEphemeral(endpoint, address);
return _BindToAddress(endpoint, address);
2006-12-04 13:44:35 +00:00
}
status_t
EndpointManager::_BindToAddress(TCPEndpoint *endpoint, const sockaddr *address)
2006-12-04 13:44:35 +00:00
{
TRACE(("EndpointManager::BindToAddress(%p)\n", endpoint));
2006-12-04 13:44:35 +00:00
2007-04-16 00:27:39 +00:00
uint16 port = AddressModule()->get_port(address);
2006-12-04 13:44:35 +00:00
// TODO this check follows very typical UNIX semantics
// and generally should be improved.
2006-12-04 13:44:35 +00:00
if (ntohs(port) <= kLastReservedPort && geteuid() != 0)
return B_PERMISSION_DENIED;
return _Bind(endpoint, address);
2006-12-04 13:44:35 +00:00
}
status_t
EndpointManager::_BindToEphemeral(TCPEndpoint *endpoint,
const sockaddr *address)
2006-12-04 13:44:35 +00:00
{
2007-04-09 09:34:47 +00:00
TRACE(("EndpointManager::BindToEphemeral(%p)\n", endpoint));
2006-12-04 13:44:35 +00:00
uint32 max = kFirstEphemeralPort + 65536;
for (int32 i = 1; i < 5; i++) {
// try to retrieve a more or less random port
uint32 counter = kFirstEphemeralPort;
uint32 step = i == 4 ? 1 : (system_time() & 0x1f) + 1;
2006-12-04 13:44:35 +00:00
while (counter < max) {
uint16 port = counter & 0xffff;
if (port <= kLastReservedPort)
port += kLastReservedPort;
port = htons(port);
TCPEndpoint *other = fEndpointHash.Lookup(port);
2006-12-04 13:44:35 +00:00
if (other == NULL) {
SocketAddressStorage newAddress(AddressModule());
newAddress.SetTo(address);
newAddress.SetPort(port);
2006-12-04 13:44:35 +00:00
// found a port
2007-04-09 09:34:47 +00:00
TRACE((" EndpointManager::BindToEphemeral(%p) -> %s\n", endpoint,
AddressString(Domain(), *newAddress, true).Data()));
return _Bind(endpoint, *newAddress);
2006-12-04 13:44:35 +00:00
}
counter += step;
}
}
// could not find a port!
return EADDRINUSE;
}
status_t
EndpointManager::_Bind(TCPEndpoint *endpoint, const sockaddr *address)
2006-12-04 13:44:35 +00:00
{
uint16 port = AddressModule()->get_port(address);
TCPEndpoint *first = fEndpointHash.Lookup(port);
// If there is already an endpoint bound to that port, SO_REUSEADDR has to be
// specified by the new endpoint to be allowed to bind to that same port.
// Alternatively, all endpoints must have the SO_REUSEPORT option set.
if (first != NULL
&& (endpoint->socket->options & SO_REUSEADDR) == 0
&& ((endpoint->socket->options & SO_REUSEPORT) == 0
|| (first->socket->options & SO_REUSEPORT) == 0))
return EADDRINUSE;
2006-12-04 13:44:35 +00:00
TCPEndpoint *insertionPoint = NULL;
2006-12-04 13:44:35 +00:00
if (first != NULL) {
while (true) {
// check if this endpoint binds to a wildcard address
if (first->LocalAddress().IsEmpty(false)) {
// you cannot specialize a wildcard endpoint - you have to open
// the wildcard endpoint last
return B_PERMISSION_DENIED;
2007-04-10 16:47:47 +00:00
}
2006-12-04 13:44:35 +00:00
if (first->fEndpointNextWithSamePort == NULL)
break;
first = first->fEndpointNextWithSamePort;
2007-04-10 16:47:47 +00:00
}
insertionPoint = first;
2006-12-04 13:44:35 +00:00
}
// 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;
2006-12-04 13:44:35 +00:00
endpoint->fEndpointNextWithSamePort = NULL;
if (insertionPoint)
insertionPoint->fEndpointNextWithSamePort = endpoint;
else
fEndpointHash.Insert(endpoint);
return B_OK;
}
status_t
EndpointManager::Unbind(TCPEndpoint *endpoint)
{
TRACE(("EndpointManager::Unbind(%p)\n", endpoint));
if (endpoint == NULL || !endpoint->IsBound()) {
TRACE((" endpoint is unbound.\n"));
return B_BAD_VALUE;
}
BenaphoreLocker _(fLock);
TCPEndpoint *other =
fEndpointHash.Lookup(endpoint->LocalAddress().Port());
if (other != endpoint) {
// remove endpoint from the list of endpoints with the same port
while (other != NULL && other->fEndpointNextWithSamePort != endpoint)
other = other->fEndpointNextWithSamePort;
if (other != NULL)
other->fEndpointNextWithSamePort = endpoint->fEndpointNextWithSamePort;
else if (!endpoint->fSpawned)
panic("bound endpoint %p not in hash!", endpoint);
} else {
// we need to replace the first endpoint in the list
fEndpointHash.Remove(endpoint);
other = endpoint->fEndpointNextWithSamePort;
if (other != NULL)
fEndpointHash.Insert(other);
}
endpoint->fEndpointNextWithSamePort = NULL;
fConnectionHash.Remove(endpoint);
2006-12-04 13:44:35 +00:00
(*endpoint->LocalAddress())->sa_len = 0;
2006-12-04 13:44:35 +00:00
return B_OK;
}
2007-04-16 00:27:39 +00:00
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((sockaddr *)&reply->source,
(sockaddr *)&buffer->destination);
AddressModule()->set_to((sockaddr *)&reply->destination,
(sockaddr *)&buffer->source);
2007-04-19 00:54:31 +00:00
tcp_segment_header outSegment(TCP_FLAG_RESET);
2007-04-16 00:27:39 +00:00
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;
} 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;
}