Skip to content

Commit

Permalink
Implement the new network stack server API.
Browse files Browse the repository at this point in the history
The new server API allows application-level code to create listening TCP
sockets bound to given server ports, and accept connections on a
listening socket.

The permission to open a given server port is encoded in bind
capabilities: capabilities sealed with a NetAPI-owned key that permit
the opening of a TCP server port on either IPv4 or IPv6.

Important implementation point here: we use socket callbacks to handle
the situation where a three-way TCP handshake initiated by a peer on a
listening socket fails. In this case the firewall hole will be closed by
the socket disconnection callback and not by a call to
`FreeRTOS_closesocket`. See comment in `network_wrapper.cc` for more
details. This behavior can easily be tested by sending a lone TCP SYN,
e.g., through:

   hping3 -c 1 -S -p $port $ip

Signed-off-by: Hugo Lefeuvre <[email protected]>
  • Loading branch information
hlef committed Oct 9, 2024
1 parent 71e1019 commit 78bc84f
Show file tree
Hide file tree
Showing 5 changed files with 420 additions and 8 deletions.
70 changes: 70 additions & 0 deletions include/NetAPI.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,43 @@ SObj __cheri_compartment("NetAPI")
SObj mallocCapability,
SObj hostCapability);

/**
* Create a listening TCP socket bound to a given port.
*
* The `mallocCapability` argument is used to allocate memory for the socket
* and must have sufficient quota remaining for the socket.
*
* The `bindCapability` argument is a capability authorising the bind to a
* specific server port.
*
* This returns a valid sealed capability to a socket on success, or a null on
* failure.
*/
SObj __cheri_compartment("NetAPI")
network_socket_listen_tcp(Timeout *timeout,
SObj mallocCapability,
SObj bindCapability);

/**
* Accept a connection on a listening socket.
*
* This function will block until a connection is established or the timeout is
* reached.
*
* The `address` and `port` arguments are used to return the address and port
* of the connected client. These can be null if the caller is not interested
* in the client's address or port.
*
* This returns a valid sealed capability to a connected socket on success, or
* a null on failure.
*/
SObj __cheri_compartment("TCPIP")
network_socket_accept_tcp(Timeout *timeout,
SObj mallocCapability,
SObj listeningSocket,
NetworkAddress *address,
uint16_t *port);

/**
* Create a bound UDP socket, allocated from the quota associated with
* `mallocCapability`. This will use IPv4 if `isIPv6` is false, or IPv6 if it
Expand Down Expand Up @@ -288,6 +325,23 @@ struct ConnectionCapability
char hostname[];
};

/**
* Bind capability contents. Instances of this sealed with the NetworkBindKey
* sealing capability authorise binding to a specific server port.
*/
struct BindCapability
{
/**
* Allow to bind on an IPv6 or IPv4 interface.
*/
bool isIPv6;
/**
* The server port this bind capability allows to bind to. This is
* provided in host byte order.
*/
uint16_t port;
};

/**
* Define a capability that authorises connecting to a specific host and port
* with UDP or TCP.
Expand All @@ -308,3 +362,19 @@ struct ConnectionCapability
portNumber, \
sizeof(authorisedHost), \
authorisedHost)

/**
* Define a capability that authorises binding to a specific server port with
* TCP. Binding to a server port with UDP is not supported.
*/
#define DECLARE_AND_DEFINE_BIND_CAPABILITY(name, isIPv6Binding, portNumber) \
DECLARE_AND_DEFINE_STATIC_SEALED_VALUE( \
struct { \
bool isIPv6; \
uint16_t port; \
}, \
NetAPI, \
NetworkBindKey, \
name, \
isIPv6Binding, \
portNumber)
64 changes: 64 additions & 0 deletions lib/netapi/NetAPI.cc
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ namespace
{
return STATIC_SEALING_TYPE(NetworkConnectionKey);
}

/**
* Returns the sealing key used for a server port.
*/
__always_inline SKey bind_capability_key()
{
return STATIC_SEALING_TYPE(NetworkBindKey);
}
} // namespace

SObj network_socket_connect_tcp(Timeout *timeout,
Expand Down Expand Up @@ -131,6 +139,62 @@ SObj network_socket_connect_tcp(Timeout *timeout,
return sealedSocket;
}

SObj network_socket_listen_tcp(Timeout *timeout,
SObj mallocCapability,
SObj bindCapability)
{
if (!check_timeout_pointer(timeout))
{
return nullptr;
}
Sealed<BindCapability> sealedBindCapability{bindCapability};
auto *serverPort =
token_unseal(bind_capability_key(), sealedBindCapability);
if (serverPort == nullptr)
{
Debug::log("Failed to unseal bind capability");
return nullptr;
}

if constexpr (!UseIPv6)
{
if (serverPort->isIPv6)
{
Debug::log("IPv6 is not supported");
return nullptr;
}
}

// Create a standard TCP socket in listening mode.
CHERI::Capability sealedSocket =
network_socket_create_and_bind(timeout,
mallocCapability,
serverPort->isIPv6,
ConnectionTypeTCP,
serverPort->port,
true /* listening mode */);
if (!sealedSocket.is_valid())
{
Debug::log("Failed to create socket");
return nullptr;
}

// Register the server port.
if (serverPort->isIPv6)
{
if constexpr (UseIPv6)
{
firewall_add_tcpipv6_server_port(ntohs(serverPort->port));
}
}
else
{
firewall_add_tcpipv4_server_port(ntohs(serverPort->port));
}

return sealedSocket;
}

NetworkAddress network_socket_udp_authorise_host(Timeout *timeout,
SObj socket,
SObj hostCapability)
Expand Down
6 changes: 6 additions & 0 deletions lib/tcpip/FreeRTOSIPConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@
*/
#include <FreeRTOS_errno.h>

/**
* Enable socket callbacks. We use FREERTOS_SO_TCP_CONN_HANDLER, which notifies
* us when a TCP connection is terminated (see `network_wrapper.cc`).
*/
#define ipconfigUSE_CALLBACKS 1

/**
* `INCLUDE_*` macros have no effect here since we do not use the FreeRTOS
* core, however FreeRTOS+TCP refuses to compile without defining these.
Expand Down
10 changes: 9 additions & 1 deletion lib/tcpip/network-internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ __cheri_compartment("TCPIP") int network_host_resolve(
* Create a socket and bind it to the given address. The socket will be
* allocated with the malloc capability.
*
* The socket will be bound to any passed non-zero `localPort`. Otherwise, a
* random local port will be selected.
*
* If `isListening` is set, the socket will be marked as a passive socket which
* can be used to accept incoming connections (see
* `network_socket_accept_tcp`).
*
* This returns a sealed capability to a socket on success, or null on failure.
*
* This should be called only from the NetAPI compartment.
Expand All @@ -40,7 +47,8 @@ SObj __cheri_compartment("TCPIP")
SObj mallocCapability,
bool isIPv6,
ConnectionType type,
uint16_t localPort = 0);
uint16_t localPort = 0,
bool isListening = false);

/**
* Connect a TCP socket to the given address.
Expand Down
Loading

0 comments on commit 78bc84f

Please sign in to comment.