334 lines
10 KiB
C
334 lines
10 KiB
C
// This file (and its relative .c file) implements an asynchronous TCP/TLS
|
|
// server and client abstraction.
|
|
//
|
|
// It introduces the concept of a "socket manager", which is a pool of
|
|
// connection sockets and a listener socket. The listener is managed
|
|
// internally, which means the manager automatically accepts sockets
|
|
// from it and adds them to the pool.
|
|
//
|
|
// If the listener is configured using the function:
|
|
//
|
|
// socket_manager_listen_tcp
|
|
//
|
|
// the resulting connections will not use TLS. If instead the listener
|
|
// is configured using:
|
|
//
|
|
// socket_manager_listen_tls
|
|
//
|
|
// the listener will use TLS. Note that both functions can be used on
|
|
// the same manager to allow both plaintext and encrypted connections.
|
|
// Users may enable zero listeners, in which case only outgoing
|
|
// connections are allowed (more on this later).
|
|
//
|
|
// Once the manager is set up, one can wait for events by following
|
|
// this pattern:
|
|
//
|
|
// struct pollfd polled[...];
|
|
// int num_polled = socket_manager_register_events(sm, polled, max_polled);
|
|
// poll(polled, num_polled, -1);
|
|
//
|
|
// #define MAX_EVENTS ...
|
|
// SocketEvent events[MAX_EVENTS];
|
|
// int num_events = socket_manager_translate_events(sm, events, MAX_EVENTS, polled, num_polled);
|
|
// for (int i = 0; i < num_events; i++) {
|
|
// ... Here call socket_recv, socket_send, socket_close, ...
|
|
// }
|
|
//
|
|
// Note that from the user's perspective, there is no difference
|
|
// between connections that use plain TCP and those that use TCP/TLS.
|
|
//
|
|
// Users can also establish outgoing connections by calling the
|
|
// function:
|
|
//
|
|
// socket_connect
|
|
//
|
|
// Which allows the creation of a connection towards an host given
|
|
// its domain, IPv4, IPv6, or an array of them. This can be done both
|
|
// for TCP and TCP/TLS connection. Note that users that only intend
|
|
// to establish outgoing connection may omit the configuration of
|
|
// listeners entirely.
|
|
|
|
#ifdef _WIN32
|
|
#define NATIVE_SOCKET SOCKET
|
|
#define NATIVE_SOCKET_INVALID SOCKET_ERROR
|
|
#define CLOSE_NATIVE_SOCKET closesocket
|
|
#else
|
|
#define NATIVE_SOCKET int
|
|
#define NATIVE_SOCKET_INVALID -1
|
|
#define CLOSE_NATIVE_SOCKET close
|
|
#endif
|
|
|
|
typedef uint32_t SocketHandle;
|
|
#define SOCKET_HANDLE_INVALID ((SocketHandle) 0)
|
|
|
|
typedef uint16_t Port;
|
|
|
|
typedef enum {
|
|
SOCKET_EVENT_READY,
|
|
SOCKET_EVENT_DISCONNECT,
|
|
} SocketEventType;
|
|
|
|
typedef struct {
|
|
SocketEventType type;
|
|
SocketHandle handle;
|
|
void* user;
|
|
} SocketEvent;
|
|
|
|
// Internal use only
|
|
typedef enum {
|
|
|
|
// The Socket struct is unused
|
|
SOCKET_STATE_FREE,
|
|
|
|
// The state associated to a socket created
|
|
// by a connect operation that hasn't been
|
|
// processed yet.
|
|
SOCKET_STATE_PENDING,
|
|
|
|
// A connect() operation was started but is
|
|
// still pending.
|
|
SOCKET_STATE_CONNECTING,
|
|
|
|
// Outgoing connection was established, but
|
|
// a TLS handshake may need to be performed.
|
|
SOCKET_STATE_CONNECTED,
|
|
|
|
// Incoming connection was established, but
|
|
// a TLS handshake may need to be performed.
|
|
SOCKET_STATE_ACCEPTED,
|
|
|
|
// The connection was esablished, but the user
|
|
// wants to perform a read or write operation that
|
|
// would block.
|
|
SOCKET_STATE_ESTABLISHED_WAIT,
|
|
|
|
// The connection was established and it's possible
|
|
// to perform read or write operations on it without
|
|
// blocking.
|
|
SOCKET_STATE_ESTABLISHED_READY,
|
|
|
|
// The socket was marked to be closed.
|
|
SOCKET_STATE_SHUTDOWN,
|
|
|
|
// The current socket is was closed. The only
|
|
// valid thing to do here is free its resources.
|
|
SOCKET_STATE_DIED,
|
|
} SocketState;
|
|
|
|
typedef struct {
|
|
int refs;
|
|
char data[];
|
|
} RegisteredName;
|
|
|
|
// Internal use only
|
|
typedef struct {
|
|
union {
|
|
HTTP_IPv4 ipv4;
|
|
HTTP_IPv6 ipv6;
|
|
};
|
|
bool is_ipv4;
|
|
Port port;
|
|
|
|
#ifdef HTTPS_ENABLED
|
|
// When connecting to a peer using TLS, if the address
|
|
// was resolved from a registered name, that name is
|
|
// used to request the correct certificate once the TCP
|
|
// handshake is established, and therefore need to
|
|
// store it somewhere until that happens.
|
|
RegisteredName *name;
|
|
#endif
|
|
} AddressAndPort;
|
|
|
|
// Internal use only
|
|
typedef struct {
|
|
SocketState state;
|
|
|
|
// OS-specific socket type
|
|
NATIVE_SOCKET sock;
|
|
|
|
// Native socket events that need to be monitored
|
|
int events;
|
|
|
|
// Generation counter to invalidate any SocketHandle
|
|
// referring to this socket when it is freed.
|
|
// Note that this counter may wrap but always skips
|
|
// the 0 value to ensure the 0 SocketHandle is always
|
|
// invalid.
|
|
uint16_t gen;
|
|
|
|
// User-provided context pointer
|
|
void *user;
|
|
|
|
// A single connect operation may involve
|
|
// trying to establish a connection towards
|
|
// one of a set of addresses.
|
|
int num_addr;
|
|
int next_addr;
|
|
union {
|
|
AddressAndPort addr; // When num_addr=1
|
|
AddressAndPort *addrs; // Dynamically allocated when num_addr>1
|
|
};
|
|
|
|
#ifdef HTTPS_ENABLED
|
|
ClientSecureContext *client_secure_context;
|
|
ServerSecureContext *server_secure_context;
|
|
SSL *ssl;
|
|
#endif
|
|
|
|
} Socket;
|
|
|
|
// Glorified array of sockets. This structure
|
|
// is private to the .c file associated to this
|
|
// header.
|
|
typedef struct {
|
|
|
|
// This guards access to the main thread using
|
|
// the manager from other threads calling the
|
|
// wakeup function.
|
|
Mutex mutex;
|
|
|
|
// TCP listener sockets. The first is intended
|
|
// for plaintext, while the second is for TLS.
|
|
// The socket manager will accept and add new
|
|
// sockets to the pool automatically. Note that
|
|
// either may be unset. If both are unset, users
|
|
// can only create outgoing connections.
|
|
NATIVE_SOCKET plain_sock;
|
|
NATIVE_SOCKET secure_sock;
|
|
|
|
// Handles for the self-pipe trick necessary for
|
|
// other threads to wake up sockets blocked on
|
|
// poll().
|
|
NATIVE_SOCKET wait_sock;
|
|
NATIVE_SOCKET signal_sock;
|
|
|
|
// TLS contexts. One is used for outgoing connections
|
|
// (the client context) and one for incoming
|
|
// connections (server). If the secure_sock is
|
|
// set, the server context is initialized. If at
|
|
// least one connect was performed using TLS
|
|
// (and the flag is set), the client context is
|
|
// initialized.
|
|
bool at_least_one_secure_connect;
|
|
ClientSecureContext client_secure_context;
|
|
ServerSecureContext server_secure_context;
|
|
|
|
// Array of sockets. Structs with state FREE
|
|
// are unused.
|
|
int num_used;
|
|
int max_used;
|
|
Socket *sockets;
|
|
|
|
} SocketManager;
|
|
|
|
// Instanciate a socket manager. Returns 0 on
|
|
// success and -1 on error.
|
|
int socket_manager_init(SocketManager *sm, Socket *socks,
|
|
int num_socks);
|
|
|
|
// Deinitialize a socket manager
|
|
void socket_manager_free(SocketManager *sm);
|
|
|
|
// Configure the socket manager to listen on
|
|
// the specified interface for TCP connections.
|
|
// Incoming connections will be automatically
|
|
// added to the internal pool. This function
|
|
// can only be used once per manager.
|
|
// Returns 0 on success, -1 on error.
|
|
int socket_manager_listen_tcp(SocketManager *sm,
|
|
HTTP_String addr, Port port, int backlog,
|
|
bool reuse_addr);
|
|
|
|
// Same as the previous function, but incoming
|
|
// connections will be interpreted as TLS. You
|
|
// can only call this function once per manager,
|
|
// but you can call this and the plaintext variant
|
|
// on the same manager to accept both plaintext
|
|
// and secure connections.
|
|
// Returns 0 on success, -1 on error.
|
|
int socket_manager_listen_tls(SocketManager *sm,
|
|
HTTP_String addr, Port port, int backlog,
|
|
bool reuse_addr, HTTP_String cert_file,
|
|
HTTP_String key_file);
|
|
|
|
// If the socket manager was configures to accept
|
|
// TLS connections, this adds additional certificates
|
|
// the client can use to verify the server's
|
|
// authenticity.
|
|
// Returns 0 on success, -1 on error.
|
|
int socket_manager_add_certificate(SocketManager *sm,
|
|
HTTP_String domain, HTTP_String cert_file, HTTP_String key_file);
|
|
|
|
// When a thread is blocked on a poll() call for
|
|
// descriptors associated to this socket manager,
|
|
// other threads can call this function to wake
|
|
// up that blocked thread.
|
|
// Returns 0 on success, -1 on error.
|
|
int socket_manager_wakeup(SocketManager *sm);
|
|
|
|
typedef struct {
|
|
void **ptrs;
|
|
struct pollfd *polled;
|
|
int num_polled;
|
|
int max_polled;
|
|
} EventRegister;
|
|
|
|
// Resets the event register with the list of descriptors
|
|
// the socket manager wants monitored. Returns 0 on
|
|
// success, -1 if the event register's capacity isn't
|
|
// large enough.
|
|
int socket_manager_register_events(SocketManager *sm,
|
|
EventRegister *reg);
|
|
|
|
// After poll() is called on the previously registered
|
|
// pollfd array and the revents fields are set, this
|
|
// function processes those events to produce higher-level
|
|
// socket events. Returns the number of socket events
|
|
// written to the output array, or -1 on error.
|
|
//
|
|
// The maximum number of events this will write
|
|
// to the events array is equal to the numero of
|
|
// socket structs provided to the socket manager
|
|
// via the init function.
|
|
int socket_manager_translate_events(SocketManager *sm,
|
|
SocketEvent *events, EventRegister *reg);
|
|
|
|
typedef enum {
|
|
CONNECT_TARGET_NAME,
|
|
CONNECT_TARGET_IPV4,
|
|
CONNECT_TARGET_IPV6,
|
|
} ConnectTargetType;
|
|
|
|
typedef struct {
|
|
ConnectTargetType type;
|
|
Port port;
|
|
union {
|
|
HTTP_IPv4 ipv4;
|
|
HTTP_IPv6 ipv6;
|
|
HTTP_String name;
|
|
};
|
|
} ConnectTarget;
|
|
|
|
// Connect to one of the given targets. The socket
|
|
// manager will try to connecting to addresses until
|
|
// one succedes. If secure=true, the socket uses TLS.
|
|
// Returns 0 on success, -1 on error.
|
|
int socket_connect(SocketManager *sm, int num_targets,
|
|
ConnectTarget *targets, bool secure, void *user);
|
|
|
|
int socket_recv(SocketManager *sm, SocketHandle handle,
|
|
char *dst, int max);
|
|
|
|
int socket_send(SocketManager *sm, SocketHandle handle,
|
|
char *src, int len);
|
|
|
|
int socket_close(SocketManager *sm, SocketHandle handle);
|
|
|
|
// Returns -1 on error, 0 if the socket was accepted
|
|
// from the plaintext listener, or 1 if it was accepted
|
|
// by the secure listener.
|
|
int socket_is_secure(SocketManager *sm, SocketHandle handle);
|
|
|
|
// Set the user pointer of a socket
|
|
int socket_set_user(SocketManager *sm, SocketHandle handle, void *user);
|