This commit is contained in:
aiden 2022-02-15 18:32:24 +00:00
commit b295d9cc64
31 changed files with 2505 additions and 0 deletions

4
.gitmodules vendored Normal file
View File

@ -0,0 +1,4 @@
[submodule "src/hashmap"]
path = src/hashmap
url = https://git.tcp.direct/aiden/hashmap
branch = main

15
LICENSE Normal file
View File

@ -0,0 +1,15 @@
ISC License
Copyright (c) 2021, interparse (aiden)
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

3
README.md Normal file
View File

@ -0,0 +1,3 @@
# bidirectiond-experimental
experimental modular "reverse" proxy written in c

51
build.py Normal file
View File

@ -0,0 +1,51 @@
#!/usr/bin/env python3
semver = "0.1.0"
import sys, json, os, subprocess
if sys.argv[0] != "./build.py":
print("shit")
exit(1)
gcc_args = ["gcc", "-o", "bidirectiond"]
services_c = "#include <bddc/api.h>\n"
services = "struct bdd_internal_service internal_services[] = {"
names = [
["bool %s(struct bdd_connections *connections, void *buf, size_t buf_size);", "serve"],
["bool %s(struct bdd_connections *connections, void *service_info, bdd_io_id client_id, struct sockaddr client_sockaddr);", "connections_init"],
["void %s(void *service_info);", "service_info_destructor"]]
np = 0
for dir, _, files in os.walk("src"):
for file in files:
if file[-2:] == ".c" or file[-2:] == ".o":
gcc_args.append(os.path.join(dir, file))
elif file == "service.json":
np += 1
path = os.path.join(dir, file)
fd = open(path)
p = json.loads(fd.read())
services += "{ NULL, "
for fn, member in names:
services += f".{member}="
if member in p:
name = p.get(member)
services_c += fn % name + "\n"
services += f"&({p.get(member)})"
else:
services += "NULL"
services += ","
services_c += f"""bool {p["service_init"]}(struct locked_hashmap *name_descriptions, struct bdd_internal_service *service, size_t n_arguments, char **arguments);\n"""
services += """
.service_init = &(%s),
.supported_arguments = (char *[]){ %s, NULL, },
.arguments_help = (char *)%s,
.n_max_io = %i,
}""" % (p["service_init"], json.dumps(p["supported_arguments"])[1:-1], json.dumps(p["arguments_help"]), p["n_max_io"])
fd.close()
services += "};"
services_c += services
fd = open("src/_services.c", "w")
fd.write(services_c)
fd.close()
gcc_args.append("src/_services.c")
sysname = os.uname().sysname
gcc_args.extend(["-lpthread", "-lssl", "-lcrypto", "-Iinc", "-DN_INTERNAL_SERVICES=" + str(np), "-DPROG_SEMVER=" + json.dumps(semver), "-funsigned-char"] + sys.argv[1:])
subprocess.call(gcc_args)
os.unlink("src/_services.c")

1
inc/bddc Symbolic link
View File

@ -0,0 +1 @@
../src/core

1
inc/hashmap Symbolic link
View File

@ -0,0 +1 @@
../src/hashmap/

177
src/core/accept.c Normal file
View File

@ -0,0 +1,177 @@
#include "internal.h"
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
int bdd_use_correct_ctx(SSL *client_ssl, int *_, struct bdd_accept_ctx *ctx) {
int r = SSL_TLSEXT_ERR_ALERT_FATAL;
char *name = (char *)SSL_get_servername(client_ssl, TLSEXT_NAMETYPE_host_name);
if (unlikely(name == NULL)) {
BDD_DEBUG_LOG("no dns name\n");
goto ucc__err;
}
// to-do: strlen is slow af, maybe openssl can give us the length instead
size_t name_len = strlen(name);
if (name_len == 0 || (name_len == 254 && name[253] != '.') || name_len > 254) {
goto ucc__err;
}
if (name[name_len - 1] == '.') {
if ((name_len -= 1) == 0) {
goto ucc__err;
}
}
// i'm doing great, okay?
// thabks
uint8_t found_req = 0;
for (size_t idx = 0;;) {
struct bdd_name_description *name_description = locked_hashmap_get_wl(ctx->locked_name_descriptions, &(name[idx]), name_len);
if (name_description != NULL) {
if (!(found_req & 0b01) && name_description->ssl_ctx != NULL) {
found_req |= 0b01;
SSL_set_SSL_CTX(client_ssl, name_description->ssl_ctx);
}
if (!(found_req & 0b10) && name_description->service_type != bdd_service_type_none) {
found_req |= 0b10;
ctx->service_name_description = name_description;
}
if (found_req == 0b11) {
r = SSL_TLSEXT_ERR_OK;
break;
}
}
bool fwc = name[idx] == '*';
do {
idx += 1;
name_len -= 1;
if (name_len == 0) {
if (fwc) {
goto ucc__err;
}
goto ucc__place_wc;
}
if (name[idx] == '.') {
if (!fwc) {
ucc__place_wc:;
name_len += 1;
name[--idx] = '*';
break;
}
fwc = false;
}
} while (true);
}
ucc__err:;
BDD_DEBUG_LOG("r: %i\n", r);
return r;
}
void *bdd_accept(struct bdd_instance *instance) {
struct bdd_accept_ctx *ctx = &(instance->accept.accept_ctx);
bdd_accept_thread__poll:;
while (poll(instance->accept.pollfds, 2, -1) < 0) {
if (errno != EINTR) {
bdd_stop(instance);
break;
}
}
if (unlikely(atomic_load(&(instance->exiting)))) {
bdd_thread_exit(instance);
}
struct bdd_connections *connections = NULL;
SSL *client_ssl = NULL;
instance->accept.accept_ctx.service_name_description = NULL;
int cl_socket = -1;
#ifdef BIDIRECTIOND_ACCEPT_OCBCNS
if ((connections = bdd_connections_obtain(instance)) == NULL) {
goto bdd_accept__err;
}
#endif
if ((client_ssl = SSL_new(instance->accept.ssl_ctx)) == NULL) {
goto bdd_accept__err;
}
// accept
BDD_DEBUG_LOG("accepting tcp connection\n");
struct sockaddr cl_sockaddr;
socklen_t sockaddr_sz = sizeof(struct sockaddr);
do {
cl_socket = accept(instance->sv_socket, &(cl_sockaddr), &(sockaddr_sz));
} while (cl_socket < 0 && errno == EINTR);
if (cl_socket < 0) {
BDD_DEBUG_LOG("rejected tcp connection\n");
goto bdd_accept__err;
}
BDD_DEBUG_LOG("accepted tcp connection\n");
fcntl(cl_socket, F_SETFL, fcntl(cl_socket, F_GETFL, 0) & ~(O_NONBLOCK));
setsockopt(cl_socket, SOL_SOCKET, SO_SNDTIMEO, &(instance->client_timeout), sizeof(instance->client_timeout));
setsockopt(cl_socket, SOL_SOCKET, SO_RCVTIMEO, &(instance->client_timeout), sizeof(instance->client_timeout));
if (!SSL_set_fd(client_ssl, cl_socket)) {
goto bdd_accept__err;
}
if ((ctx->locked_name_descriptions = hashmap_lock(instance->name_descriptions)) == NULL) {
BDD_DEBUG_LOG("failed to obtain name_descriptions\n");
goto bdd_accept__err;
}
if (SSL_accept(client_ssl) < 0) {
BDD_DEBUG_LOG("rejected tls setup\n");
goto bdd_accept__err;
}
switch (ctx->service_name_description->service_type) {
case (bdd_service_type_internal): {
#ifndef BIDIRECTIOND_ACCEPT_OCBCNS
if ((connections = bdd_connections_obtain(instance)) == NULL) {
goto bdd_accept__err;
}
#endif
switch (bdd_connections_init(connections, &(client_ssl), cl_sockaddr, ctx->service_name_description->service.internal.service, ctx->service_name_description->service.internal.service_info)) {
case (bdd_connections_init_failed): {
goto bdd_accept__err;
}
case (bdd_connections_init_success): {
bdd_connections_link(instance, &(connections));
break;
}
case (bdd_connections_init_failed_wants_deinit): {
bdd_connections_deinit(connections);
goto bdd_accept__err;
}
}
break;
}
default: {
assert(false);
}
}
locked_hashmap_unlock(&(ctx->locked_name_descriptions));
goto bdd_accept_thread__poll;
bdd_accept__err:;
BDD_DEBUG_LOG("failed to accept connection\n");
if (ctx->locked_name_descriptions != NULL) {
locked_hashmap_unlock(&(ctx->locked_name_descriptions));
}
if (client_ssl != NULL) {
SSL_free(client_ssl);
}
if (cl_socket >= 0) {
close(cl_socket);
}
if (connections != NULL) {
bdd_connections_release(instance, &(connections));
}
goto bdd_accept_thread__poll;
}

113
src/core/api.h Normal file
View File

@ -0,0 +1,113 @@
#ifndef bidirectiond_core__api__h
#define bidirectiond_core__api__h
#include <stddef.h>
#include <stdbool.h>
#include <stdatomic.h>
#include <openssl/ssl.h>
#include <pthread.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <hashmap/hashmap.h>
#include <poll.h>
#include <stdint.h>
#include <assert.h>
#ifndef POLLRDHUP
#define POLLRDHUP 0x400
#endif
enum bdd_name_description_service_type { bdd_name_description_service_type_none, bdd_name_description_service_type_internal, } __attribute__((packed));
typedef unsigned short int bdd_io_id;
struct bdd_instance;
struct bdd_io {
int fd;
SSL *ssl;
};
struct bdd_connections_associated {
void *data;
void (*destructor)(void *data);
};
struct bdd_connections {
struct bdd_connections *next;
bool working : 1, broken : 1;
pthread_mutex_t working_mutex;
const struct bdd_internal_service *service;
struct bdd_io *io;
struct bdd_connections_associated associated;
};
struct bdd_internal_service {
char *name;
bool (*serve)(struct bdd_connections *connections, void *buf, size_t buf_size);
bool (*connections_init)(struct bdd_connections *connections, void *service_info, bdd_io_id client_id, struct sockaddr client_sockaddr);
void (*service_info_destructor)(void *service_info);
bool (*service_init)(struct locked_hashmap *name_descriptions, struct bdd_internal_service *service, size_t n_arguments, char **arguments);
char **supported_arguments;
char *arguments_help;
bdd_io_id n_max_io;
};
struct bdd_settings {
int sv_socket;
struct hashmap *name_descriptions;
uint32_t client_timeout;
uint32_t buf_sz;
bool use_stack_buf : 1, use_work_queues : 1;
int n_connections;
int n_epoll_oevents;
unsigned short int n_worker_threads;
sigset_t sigmask;
};
enum bdd_service_type { bdd_service_type_none, bdd_service_type_internal, } __attribute__((packed));
struct bdd_name_description {
SSL_CTX *ssl_ctx;
enum bdd_service_type service_type;
union {
struct {
struct bdd_internal_service *service;
void *service_info;
} internal;
} service;
};
__attribute__((warn_unused_result)) int bdd_poll(struct bdd_connections *connections, bdd_io_id io_id);
__attribute__((warn_unused_result)) ssize_t bdd_read(struct bdd_connections *connections, bdd_io_id io_id, void *buf, ssize_t sz);
__attribute__((warn_unused_result)) ssize_t bdd_read_whole(struct bdd_connections *connections, bdd_io_id io_id, void *buf, ssize_t sz);
__attribute__((warn_unused_result)) ssize_t bdd_write(struct bdd_connections *connections, bdd_io_id io_id, void *buf, ssize_t sz);
__attribute__((warn_unused_result)) ssize_t bdd_write_whole(struct bdd_connections *connections, bdd_io_id io_id, void *buf, ssize_t sz);
bool bdd_create_io(struct bdd_connections *connections, bdd_io_id *io_id, int *fd, char *ssl_name);
void bdd_remove_io(struct bdd_connections *connections, bdd_io_id io_id);
void bdd_set_associated(struct bdd_connections *connections, void *data, void (*destructor)(void *));
void bdd_name_description_destroy(struct bdd_name_description *name_description);
#define bdd_get_associated(connections) (connections->associated.data)
struct bdd_instance *bdd_go(struct bdd_settings settings);
bool bdd_running(struct bdd_instance *instance);
void bdd_wait(struct bdd_instance *instance);
void bdd_stop(struct bdd_instance *instance);
void bdd_destroy(struct bdd_instance *instance);
bool bdd_name_descriptions_set_internal_service(struct locked_hashmap *name_descriptions, char *name, size_t name_len, struct bdd_internal_service *service, void *service_info);
bool bdd_name_descriptions_set_ssl_ctx(struct locked_hashmap *name_descriptions, char *name, size_t name_len, SSL_CTX *ssl_ctx);
#endif

242
src/core/connections.c Normal file
View File

@ -0,0 +1,242 @@
#include "internal.h"
#include <string.h>
#include <errno.h>
#include <openssl/x509v3.h>
#include <unistd.h>
__attribute__((warn_unused_result)) int bdd_poll(struct bdd_connections *connections, bdd_io_id io_id) {
assert(connections != NULL && io_id >= 0 && io_id < bdd_connections_n_max_io(connections));
struct bdd_io *io = &(connections->io[io_id]);
assert(io->fd != -1);
struct pollfd pollfd = {
.fd = io->fd,
.events = POLLIN | POLLOUT | POLLRDHUP,
.revents = 0,
};
poll(&(pollfd), 1, 0);
if (io->ssl != NULL && SSL_has_pending(io->ssl)) {
pollfd.revents |= POLLIN;
}
return pollfd.revents;
}
__attribute__((warn_unused_result)) ssize_t bdd_read_internal(struct bdd_io *io, void *buf, ssize_t sz) {
if (sz <= 0) {
return 0;
}
ssize_t r = 0;
do {
if (io->ssl != NULL) {
r = SSL_read(io->ssl, buf, sz);
} else {
r = recv(io->fd, buf, sz, 0);
}
} while (r < 0 && errno == EINTR);
return r;
}
__attribute__((warn_unused_result)) ssize_t bdd_read(struct bdd_connections *connections, bdd_io_id io_id, void *buf, ssize_t sz) {
assert(connections != NULL && io_id >= 0 && io_id < bdd_connections_n_max_io(connections));
struct bdd_io *io = &(connections->io[io_id]);
assert(io->fd != -1);
return bdd_read_internal(io, buf, sz);
}
__attribute__((warn_unused_result)) ssize_t bdd_read_whole(struct bdd_connections *connections, bdd_io_id io_id, void *buf, ssize_t sz) {
assert(connections != NULL && io_id >= 0 && io_id < bdd_connections_n_max_io(connections));
struct bdd_io *io = &(connections->io[io_id]);
assert(io->fd != -1);
ssize_t n = 0;
while (n < sz) {
ssize_t r = bdd_read_internal(io, buf + n, sz - n);
if (r < 0) {
return r;
} else if (r == 0) {
return n;
}
n += r;
}
return n;
}
__attribute__((warn_unused_result)) ssize_t bdd_write_internal(struct bdd_io *io, void *buf, ssize_t sz) {
if (sz <= 0) {
return 0;
}
ssize_t r = 0;
do {
if (io->ssl != NULL) {
r = SSL_write(io->ssl, buf, sz);
} else {
r = send(io->fd, buf, sz, 0);
}
} while (r < 0 && errno == EINTR);
return r;
}
__attribute__((warn_unused_result)) ssize_t bdd_write(struct bdd_connections *connections, bdd_io_id io_id, void *buf, ssize_t sz) {
assert(connections != NULL && io_id >= 0 && io_id < bdd_connections_n_max_io(connections));
struct bdd_io *io = &(connections->io[io_id]);
assert(io->fd != -1);
return bdd_write_internal(io, buf, sz);
}
__attribute__((warn_unused_result)) ssize_t bdd_write_whole(struct bdd_connections *connections, bdd_io_id io_id, void *buf, ssize_t sz) {
assert(connections != NULL && io_id >= 0 && io_id < bdd_connections_n_max_io(connections));
struct bdd_io *io = &(connections->io[io_id]);
assert(io->fd != -1);
ssize_t n = 0;
while (n < sz) {
ssize_t r = bdd_write_internal(io, buf + n, sz - n);
if (r < 0) {
return r;
} else if (r == 0) {
return n;
}
n += r;
}
return n;
}
void bdd_set_associated(struct bdd_connections *connections, void *data, void (*destructor)(void *)) {
assert(connections != NULL);
if (connections->associated.destructor != NULL) {
connections->associated.destructor(connections->associated.data);
}
#ifndef NDEBUG
if (data != NULL || destructor != NULL) {
assert(data != NULL && destructor != NULL);
}
#endif
connections->associated.data = data;
connections->associated.destructor = destructor;
return;
}
bool bdd_create_io(struct bdd_connections *connections, bdd_io_id *io_id, int *fd, char *ssl_name) {
assert(connections != NULL && io_id != NULL);
struct bdd_io *io = NULL;
bdd_io_id idx = 0;
for (; idx < bdd_connections_n_max_io(connections); ++idx) {
if (connections->io[idx].fd == -1) {
io = &(connections->io[idx]);
break;
}
}
assert(io != NULL);
if (unlikely(io == NULL)) {
return false;
}
SSL *ssl = NULL;
if (ssl_name != NULL) {
// i think it's not finna write to the ctx, so a global mutex lock is not required here
// also, BDD_GLOBAL_CL_SSL_CTX is guaranteed to be valid here
if ((ssl = SSL_new(BDD_GLOBAL_CL_SSL_CTX)) == NULL) {
return false;
}
if (unlikely(!SSL_set_fd(ssl, (*fd)))) {
SSL_free(ssl);
return false;
}
SSL_set_hostflags(ssl, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS);
if (unlikely(!SSL_set_tlsext_host_name(ssl, ssl_name) || !SSL_set1_host(ssl, ssl_name))) {
SSL_free(ssl);
return false;
}
SSL_set_verify(ssl, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);
if (SSL_connect(ssl) != 1) {
SSL_free(ssl);
return false;
}
}
io->fd = (*fd);
io->ssl = ssl;
(*fd) = -1;
(*io_id) = idx;
return true;
}
void bdd_remove_io(struct bdd_connections *connections, bdd_io_id io_id) {
assert(connections != NULL && io_id >= 0 && io_id < bdd_connections_n_max_io(connections));
struct bdd_io *io = &(connections->io[io_id]);
assert(io->fd >= 0);
if (io->ssl != NULL) {
SSL_shutdown(io->ssl);
SSL_free(io->ssl);
} else {
shutdown(io->fd, SHUT_RDWR);
}
close(io->fd);
io->fd = -1;
return;
}
enum bdd_connections_init_status bdd_connections_init(struct bdd_connections *connections, SSL **client_ssl, struct sockaddr client_sockaddr, const struct bdd_internal_service *service, void *service_info) {
assert(service->n_max_io > 0);
if ((connections->io = malloc(sizeof(struct bdd_io) * service->n_max_io)) == NULL) {
return bdd_connections_init_failed;
}
connections->service = service;
connections->io[0].fd = SSL_get_fd((*client_ssl));
connections->io[0].ssl = (*client_ssl);
(*client_ssl) = NULL;
for (bdd_io_id idx = 1; idx < service->n_max_io; ++idx) {
connections->io[idx].fd = -1;
connections->io[idx].ssl = NULL;
}
if (service->connections_init != NULL && !service->connections_init(connections, service_info, 0, client_sockaddr)) {
return bdd_connections_init_failed_wants_deinit;
}
return bdd_connections_init_success;
}
void bdd_connections_deinit(struct bdd_connections *connections) {
if (connections->io != NULL) {
for (bdd_io_id io_id = 0; io_id < bdd_connections_n_max_io(connections); ++io_id) {
struct bdd_io *io = &(connections->io[io_id]);
if (io->fd < 0) {
continue;
}
bdd_remove_io(connections, io_id);
}
free(connections->io);
connections->io = NULL;
}
bdd_set_associated(connections, NULL, NULL);
connections->working = false;
connections->broken = false;
return;
}
void bdd_connections_link(struct bdd_instance *instance, struct bdd_connections **_connections) {
assert(_connections != NULL);
struct bdd_connections *connections = (*_connections);
assert(connections != NULL);
pthread_mutex_lock(&(instance->linked_connections.mutex));
connections->next = instance->linked_connections.head;
instance->linked_connections.head = connections;
bdd_signal(instance);
pthread_mutex_unlock(&(instance->linked_connections.mutex));
(*_connections) = NULL;
return;
}
struct bdd_connections *bdd_connections_obtain(struct bdd_instance *instance) {
if (instance->connections.n_connections <= 0) {
return NULL;
}
struct bdd_connections *connections = NULL;
pthread_mutex_lock(&(instance->connections.available_mutex));
while (!atomic_load(&(instance->exiting)) && instance->connections.available_idx == instance->connections.n_connections) {
pthread_cond_wait(&(instance->connections.available_cond), &(instance->connections.available_mutex));
}
if (!atomic_load(&(instance->exiting))) {
connections = &(instance->connections.connections[instance->connections.available[instance->connections.available_idx++]]);
}
pthread_mutex_unlock(&(instance->connections.available_mutex));
return connections;
}
void bdd_connections_release(struct bdd_instance *instance, struct bdd_connections **_connections) {
assert(_connections != NULL);
struct bdd_connections *connections = (*_connections);
assert(connections != NULL);
pthread_mutex_lock(&(instance->connections.available_mutex));
assert(instance->connections.available_idx != 0);
int id = bdd_connections_id(instance, connections);
assert(id >= 0 && id < instance->connections.n_connections);
instance->connections.available[--(instance->connections.available_idx)] = id;
pthread_cond_signal(&(instance->connections.available_cond));
pthread_mutex_unlock(&(instance->connections.available_mutex));
(*_connections) = NULL;
return;
}

63
src/core/debug_log.c Normal file
View File

@ -0,0 +1,63 @@
#if !defined(NDEBUG) && defined(BIDIRECTIOND_VERBOSE_DEBUG_LOG)
#include <stdio.h>
#include <openssl/ssl.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/socket.h>
int bdd_vdl_SSL_write(void *x, char *data, size_t len) {
if (len == 0) {
puts("SSL_write len is 0?");
}
for (size_t idx = 0; idx < len; ++idx) {
if (data[idx] >= 0x20 && data[idx] <= 0x7e) {
putc(data[idx], stdout);
} else {
printf("\\x%02x", data[idx]);
}
}
putchar('\n');
return SSL_write(x, data, len);
}
int bdd_vdl_send(int a, char *b, size_t c, int _) {
if (c == 0) {
puts("send len is 0?");
}
for (size_t idx = 0; idx < c; ++idx) {
if (b[idx] >= 0x20 && b[idx] <= 0x7e) {
putc(b[idx], stdout);
} else {
printf("\\x%02x", b[idx]);
}
}
putchar('\n');
return send(a, b, c, _);
}
int bdd_vdl_pthread_mutex_lock(void *_, char *name, int ln) {
printf("%p (%s) lock attempt @ %i!\n", _, name, ln);
int x = pthread_mutex_lock(_);
printf("%p (%s) lock @ %i!\n", _, name, ln);
return x;
}
int bdd_vdl_pthread_mutex_trylock(void *_, char *name, int ln) {
printf("%p (%s) lock attempt @ %i!\n", _, name, ln);
int x = pthread_mutex_trylock(_);
if (x == 0) {
printf("%p (%s) lock @ %i!\n", _, name, ln);
} else {
printf("%p (%s) trylock failed @ %i!\n", _, name, ln);
}
return x;
}
int bdd_vdl_pthread_mutex_unlock(void *_, char *name, int ln) {
int x = pthread_mutex_unlock(_);
printf("%p (%s) unlock @ %i!\n", _, name, ln);
return x;
}
int bdd_vdl_pthread_cond_wait(void *_, void *__, char *name, int ln) {
printf("%p (%s) unlock @ %i!\n", _, name, ln);
int x = pthread_cond_wait(_, __);
printf("%p (%s) lock @ %i!\n", _, name, ln);
return x;
}
#endif

351
src/core/instance.c Normal file
View File

@ -0,0 +1,351 @@
#include "internal.h"
#include <sys/epoll.h>
#include <sys/eventfd.h>
#include <unistd.h>
// fuck this whole file
struct bdd_instance *bdd_instance_alloc(void) {
struct bdd_instance *instance = malloc(sizeof(struct bdd_instance));
if (instance == NULL) {
return NULL;
}
// exiting
atomic_store(&(instance->exiting), false);
// running threads
instance->n_running_threads = 0;
bdd_mutex_preinit(&(instance->n_running_threads_mutex));
bdd_cond_preinit(&(instance->n_running_threads_cond));
// epoll
instance->epoll_fd = -1;
instance->n_epoll_oevents = 0;
instance->epoll_oevents = NULL;
// name_descriptions
instance->name_descriptions = NULL;
// client timeout
instance->client_timeout.tv_sec = 0;
instance->client_timeout.tv_usec = 0;
// server socket
instance->sv_socket = -1;
// connections
instance->connections.n_connections = 0;
instance->connections.connections = NULL;
instance->connections.connections_idx = 0;
instance->connections.available = NULL;
instance->connections.available_idx = 0;
bdd_mutex_preinit(&(instance->connections.available_mutex));
bdd_cond_preinit(&(instance->connections.available_cond));
// linked connections
bdd_mutex_preinit(&(instance->linked_connections.mutex));
// accept thread stuff
instance->accept.eventfd = -1;
for (uint8_t idx = 0; idx < 2; ++idx) {
instance->accept.pollfds[idx].fd = -1;
instance->accept.pollfds[idx].events = 0;
instance->accept.pollfds[idx].revents = 0;
}
instance->accept.ssl_ctx = NULL;
instance->accept.accept_ctx.service_name_description = NULL;
instance->accept.accept_ctx.locked_name_descriptions = NULL;
instance->linked_connections.head = NULL;
// serve_eventfd
instance->serve_eventfd = -1;
// workers
bdd_mutex_preinit(&(instance->workers.available_stack.mutex));
bdd_cond_preinit(&(instance->workers.available_stack.cond));
instance->workers.available_stack.ids = NULL;
instance->workers.available_stack.idx = 0;
instance->workers.n_workers = 0;
instance->workers.info = NULL;
instance->workers.info_idx = 0;
instance->workers.buf = NULL;
instance->workers.buf_sz_per_worker = 0;
return instance;
}
struct bdd_instance *bdd_go(struct bdd_settings settings) {
if (settings.sv_socket < 0 || settings.buf_sz == 0 || settings.n_connections < 0 || settings.n_epoll_oevents < 0 || settings.name_descriptions == NULL ||
(
(settings.n_connections == 0 || settings.n_worker_threads == 0 || settings.n_epoll_oevents == 0) &&
(settings.n_connections != 0 || settings.n_worker_threads != 0 || settings.n_epoll_oevents != 0)
)) {
return NULL;
}
bool uses_internal_services = settings.n_connections != 0;
struct bdd_instance *instance = bdd_instance_alloc();
if (instance == NULL) {
return NULL;
}
if (pthread_mutex_init(&(instance->n_running_threads_mutex), NULL) != 0) {
free(instance);
return NULL;
}
if (pthread_cond_init(&(instance->n_running_threads_cond), NULL) != 0) {
pthread_mutex_destroy(&(instance->n_running_threads_mutex));
free(instance);
return NULL;
}
struct bdd_instance *ret = (struct bdd_instance *)instance;
{
while (atomic_flag_test_and_set(&(BDD_GLOBAL_MUTEX)));
if (BDD_GLOBAL_RC == 0) {
if ((BDD_GLOBAL_CL_SSL_CTX = SSL_CTX_new(TLS_client_method())) == NULL) {
atomic_flag_clear(&(BDD_GLOBAL_MUTEX));
free(instance);
return NULL;
}
SSL_CTX_set_mode(BDD_GLOBAL_CL_SSL_CTX, SSL_MODE_RELEASE_BUFFERS);
SSL_CTX_set_options(BDD_GLOBAL_CL_SSL_CTX, SSL_OP_NO_COMPRESSION);
}
if ((BDD_GLOBAL_RC += 1) <= 0) {
atomic_flag_clear(&(BDD_GLOBAL_MUTEX));
goto bdd_go__err;
}
atomic_flag_clear(&(BDD_GLOBAL_MUTEX));
}
// sigmask
instance->sigmask = settings.sigmask;
if (uses_internal_services) {
// epoll
if ((instance->epoll_fd = epoll_create1(0)) < 0) {
goto bdd_go__err;
}
instance->n_epoll_oevents = settings.n_epoll_oevents;
if ((instance->epoll_oevents = malloc(sizeof(struct epoll_event) * settings.n_epoll_oevents)) == NULL) {
goto bdd_go__err;
}
}
// name_descriptions
instance->name_descriptions = settings.name_descriptions;
// client timeout
instance->client_timeout.tv_sec = (settings.client_timeout / 1000);
instance->client_timeout.tv_usec = (settings.client_timeout % 1000) * 1000;
// server socket
instance->sv_socket = settings.sv_socket;
if (uses_internal_services) {
// connections
instance->connections.n_connections = settings.n_connections;
if ((instance->connections.connections = malloc(
(settings.n_connections * sizeof(struct bdd_connections)) +
(settings.n_connections * sizeof(int))
)) == NULL) {
goto bdd_go__err;
}
// available stack
instance->connections.available = (void *)&(instance->connections.connections[settings.n_connections]);
instance->connections.available_idx = 0;
if (pthread_mutex_init(&(instance->connections.available_mutex), NULL) != 0 || pthread_cond_init(&(instance->connections.available_cond), NULL) != 0) {
goto bdd_go__err;
}
// init connections, and the available stack
for (int *idx = &(instance->connections.connections_idx); (*idx) < settings.n_connections; ++(*idx)) {
if (pthread_mutex_init(&(instance->connections.connections[(*idx)].working_mutex), NULL) != 0) {
goto bdd_go__err;
}
instance->connections.connections[(*idx)].associated.data = NULL;
instance->connections.connections[(*idx)].associated.destructor = NULL;
instance->connections.connections[(*idx)].io = NULL;
instance->connections.connections[(*idx)].working = false;
instance->connections.connections[(*idx)].broken = false;
instance->connections.available[(*idx)] = (*idx);
}
// linked connections
if (pthread_mutex_init(&(instance->linked_connections.mutex), NULL) != 0) {
goto bdd_go__err;
}
instance->linked_connections.head = NULL;
}
// accept
if ((instance->accept.eventfd = eventfd(0, EFD_NONBLOCK)) < 0) {
goto bdd_go__err;
}
instance->accept.pollfds[0].fd = settings.sv_socket;
instance->accept.pollfds[0].events = POLLIN;
instance->accept.pollfds[1].fd = instance->accept.eventfd;
instance->accept.pollfds[1].events = POLLIN;
if ((instance->accept.ssl_ctx = SSL_CTX_new(TLS_server_method())) == NULL || SSL_CTX_set_cipher_list(instance->accept.ssl_ctx, "AES256-SHA256") == 0) {
goto bdd_go__err;
}
SSL_CTX_set_tlsext_servername_callback(instance->accept.ssl_ctx, &(bdd_use_correct_ctx));
SSL_CTX_set_tlsext_servername_arg(instance->accept.ssl_ctx, &(instance->accept.accept_ctx));
if (uses_internal_services) {
// serve
if ((instance->serve_eventfd = eventfd(0, EFD_NONBLOCK)) < 0) {
goto bdd_go__err;
}
struct epoll_event event = {
.events = EPOLLIN,
.data = {
.ptr = NULL,
},
};
if (epoll_ctl(instance->epoll_fd, EPOLL_CTL_ADD, instance->serve_eventfd, &(event)) != 0) {
goto bdd_go__err;
}
// workers
if (!settings.use_work_queues) {
if ((instance->workers.available_stack.ids = malloc(settings.n_worker_threads * sizeof(unsigned short int))) == NULL) {
goto bdd_go__err;
}
instance->workers.available_stack.idx = settings.n_worker_threads;
}
if ((instance->workers.buf = malloc(settings.buf_sz * settings.n_worker_threads)) == NULL) {
goto bdd_go__err;
}
instance->workers.buf_sz_per_worker = settings.buf_sz;
}
{
pthread_mutex_lock(&(instance->n_running_threads_mutex));
bool e = false;
pthread_t pthid;
if (pthread_create(&(pthid), NULL, (void *(*)(void *))(&(bdd_accept)), instance) != 0) {
e = true;
} else {
instance->n_running_threads += 1;
}
if (uses_internal_services) {
if (!e) {
if (pthread_create(&(pthid), NULL, (void *(*)(void *))(&(bdd_serve)), instance) != 0) {
e = true;
} else {
instance->n_running_threads += 1;
}
}
struct bdd_worker *workers;
if (!e && (workers = malloc(sizeof(struct bdd_worker) * settings.n_worker_threads)) == NULL) {
e = true;
}
instance->workers.info = workers;
for (unsigned short int *idx = &(instance->workers.n_workers); !e && (*idx) < settings.n_worker_threads; ++(*idx)) {
(*((struct bdd_instance **)&(workers[(*idx)].instance))) = instance;
if (pthread_mutex_init(&(workers[(*idx)].work_mutex), NULL) != 0) {
e = true;
}
if (pthread_cond_init(&(workers[(*idx)].work_cond), NULL) != 0) {
pthread_mutex_destroy(&(workers[(*idx)].work_mutex));
e = true;
}
workers[(*idx)].id = (*idx);
workers[(*idx)].connections = NULL;
workers[(*idx)].connections_appender = NULL;
if (pthread_create(&(pthid), NULL, (void *(*)(void *))(&(bdd_worker)), &(workers[(*idx)])) != 0) {
e = true;
} else {
instance->n_running_threads += 1;
}
}
if (e) {
pthread_mutex_unlock(&(instance->n_running_threads_mutex));
goto bdd_go__err;
}
}
pthread_mutex_unlock(&(instance->n_running_threads_mutex));
}
return ret;
bdd_go__err:;
bdd_stop(ret);
bdd_wait(ret);
bdd_destroy(ret);
return NULL;
}
void bdd_stop(struct bdd_instance *instance) {
atomic_store(&(instance->exiting), true);
if (instance->accept.eventfd != -1) {
bdd_stop_accept(instance);
}
if (instance->serve_eventfd != -1) {
bdd_signal(instance);
}
for (unsigned short int idx = 0; idx < instance->workers.n_workers; ++idx) {
pthread_mutex_lock(&(instance->workers.info[idx].work_mutex));
pthread_cond_signal(&(instance->workers.info[idx].work_cond));
pthread_mutex_unlock(&(instance->workers.info[idx].work_mutex));
}
return;
}
bool bdd_running(struct bdd_instance *instance) {
pthread_mutex_lock(&(instance->n_running_threads_mutex));
bool running = instance->n_running_threads != 0;
pthread_mutex_unlock(&(instance->n_running_threads_mutex));
return running;
}
void bdd_wait(struct bdd_instance *instance) {
if (pthread_mutex_lock(&(instance->n_running_threads_mutex)) != 0) {
return;
}
while (instance->n_running_threads != 0) {
pthread_cond_wait(&(instance->n_running_threads_cond), &(instance->n_running_threads_mutex));
}
pthread_mutex_unlock(&(instance->n_running_threads_mutex));
return;
}
void bdd_destroy(struct bdd_instance *instance) {
pthread_mutex_destroy(&(instance->n_running_threads_mutex));
pthread_cond_destroy(&(instance->n_running_threads_cond));
if (instance->epoll_fd >= 0) {
close(instance->epoll_fd);
}
if (instance->epoll_oevents != NULL) {
free(instance->epoll_oevents);
}
pthread_mutex_destroy(&(instance->connections.available_mutex));
pthread_cond_destroy(&(instance->connections.available_cond));
for (int idx = 0; idx < instance->connections.n_connections; ++idx) {
bdd_connections_deinit(&(instance->connections.connections[idx]));
pthread_mutex_destroy(&(instance->connections.connections[idx].working_mutex));
}
if (instance->connections.connections != NULL) {
free(instance->connections.connections);
}
pthread_mutex_destroy(&(instance->linked_connections.mutex));
if (instance->accept.eventfd >= 0) {
close(instance->accept.eventfd);
}
if (instance->accept.ssl_ctx != NULL) {
SSL_CTX_free(instance->accept.ssl_ctx);
}
if (instance->serve_eventfd >= 0) {
close(instance->serve_eventfd);
}
pthread_mutex_destroy(&(instance->workers.available_stack.mutex));
pthread_cond_destroy(&(instance->workers.available_stack.cond));
if (instance->workers.available_stack.ids != NULL) {
free(instance->workers.available_stack.ids);
}
for (unsigned short int idx = 0; idx < instance->workers.n_workers; ++idx) {
pthread_mutex_destroy(&(instance->workers.info[idx].work_mutex));
pthread_cond_destroy(&(instance->workers.info[idx].work_cond));
}
if (instance->workers.buf != NULL) {
free(instance->workers.buf);
}
if (instance->workers.info != NULL) {
free(instance->workers.info);
}
free(instance);
while (atomic_flag_test_and_set(&(BDD_GLOBAL_MUTEX)));
if (--BDD_GLOBAL_RC == 0) {
SSL_CTX_free(BDD_GLOBAL_CL_SSL_CTX);
}
atomic_flag_clear(&(BDD_GLOBAL_MUTEX));
return;
}

135
src/core/internal.h Normal file
View File

@ -0,0 +1,135 @@
#ifndef bidirectiond_core__internal__h
#define bidirectiond_core__internal__h
#include "api.h"
extern atomic_flag BDD_GLOBAL_MUTEX;
extern size_t BDD_GLOBAL_RC;
extern SSL_CTX *BDD_GLOBAL_CL_SSL_CTX;
void bdd_mutex_preinit(pthread_mutex_t *dest);
void bdd_cond_preinit(pthread_cond_t *dest);
// my justification for the following shit is: my ram usage is already kinda pushing it, so i'd like to save some heap space in exchange for a few extra instructions
#define bdd_connections_n_max_io(c) (c->service->n_max_io)
#define bdd_connections_id(instance, c) (((char *)c - (char *)(instance->connections.connections)) / sizeof(struct bdd_connections))
struct bdd_worker {
struct bdd_instance *const instance;
unsigned short int id;
pthread_mutex_t work_mutex;
pthread_cond_t work_cond;
struct bdd_connections *connections;
struct bdd_connections **connections_appender;
};
struct bdd_workers {
struct {
pthread_mutex_t mutex;
pthread_cond_t cond;
unsigned short int *ids;
unsigned short int idx;
} available_stack;
unsigned short int n_workers;
struct bdd_worker *info;
unsigned short int info_idx;
void *buf;
size_t buf_sz_per_worker;
};
struct bdd_accept_ctx {
struct bdd_name_description *service_name_description;
struct locked_hashmap *locked_name_descriptions;
};
struct bdd_instance {
sigset_t sigmask;
atomic_bool exiting;
int n_running_threads;
pthread_mutex_t n_running_threads_mutex;
pthread_cond_t n_running_threads_cond;
int epoll_fd;
int n_epoll_oevents;
struct epoll_event *epoll_oevents;
void *name_descriptions;
struct timeval client_timeout;
int sv_socket;
struct {
int n_connections;
struct bdd_connections *connections;
int connections_idx;
// stack
int *available;
int available_idx;
pthread_mutex_t available_mutex;
pthread_cond_t available_cond;
} connections;
struct {
pthread_mutex_t mutex;
struct bdd_connections *head;
} linked_connections;
struct {
int eventfd;
struct pollfd pollfds[2];
SSL_CTX *ssl_ctx;
struct bdd_accept_ctx accept_ctx;
} accept;
int serve_eventfd;
struct bdd_workers workers;
};
#ifdef NDEBUG
#define BDD_DEBUG_LOG(x...) (0)
#else
#include <stdio.h>
#ifdef BIDIRECTIOND_VERBOSE_DEBUG_LOG
int bdd_vdl_SSL_write(void *x, char *data, size_t len);
int bdd_vdl_send(int a, char *b, size_t c, int _);
#define SSL_write bdd_vdl_SSL_write
#define send bdd_vdl_send
int bdd_vdl_pthread_mutex_lock(void *_, char *name, int ln);
int bdd_vdl_pthread_mutex_unlock(void *_, char *name, int ln);
int bdd_vdl_pthread_cond_wait(void *_, void *__, char *name, int ln);
int bdd_vdl_pthread_mutex_trylock(void *_, char *name, int ln);
#define pthread_mutex_lock(x) bdd_vdl_pthread_mutex_lock(x, #x, __LINE__)
#define pthread_mutex_unlock(x) bdd_vdl_pthread_mutex_unlock(x, #x, __LINE__)
#define pthread_cond_wait(_, __) bdd_vdl_pthread_cond_wait(_, __, #__, __LINE__)
#define pthread_mutex_trylock(x) bdd_vdl_pthread_mutex_trylock(x, #x, __LINE__)
#endif
#define BDD_DEBUG_LOG(string, args...) (printf("[DEBUG (%p)] "string, (void *)pthread_self(), ##args), fflush(stdout))
#endif
enum bdd_connections_init_status { bdd_connections_init_success, bdd_connections_init_failed_wants_deinit, bdd_connections_init_failed, } __attribute__((packed));
enum bdd_connections_init_status bdd_connections_init(struct bdd_connections *connections, SSL **client_ssl, struct sockaddr client_sockaddr, const struct bdd_internal_service *service, void *service_info);
struct bdd_connections *bdd_connections_obtain(struct bdd_instance *instance);
void bdd_connections_release(struct bdd_instance *instance, struct bdd_connections **connections);
void bdd_connections_deinit(struct bdd_connections *connections);
void bdd_connections_link(struct bdd_instance *instance, struct bdd_connections **connections);
void bdd_signal(struct bdd_instance *instance);
int bdd_use_correct_ctx(SSL *client_ssl, int *_, struct bdd_accept_ctx *ctx);
void *bdd_serve(struct bdd_instance *instance);
void *bdd_accept(struct bdd_instance *instance);
void *bdd_worker(struct bdd_worker *worker);
void bdd_stop_accept(struct bdd_instance *instance);
void bdd_thread_exit(struct bdd_instance *instance);
#ifndef unlikely
#define unlikely(x) __builtin_expect((x), 0)
#endif
#endif

View File

@ -0,0 +1,6 @@
#include <stdatomic.h>
#include <stddef.h>
#include <openssl/ssl.h>
atomic_flag BDD_GLOBAL_MUTEX = ATOMIC_FLAG_INIT;
size_t BDD_GLOBAL_RC = 0;
SSL_CTX *BDD_GLOBAL_CL_SSL_CTX = NULL;

View File

@ -0,0 +1,79 @@
#include "internal.h"
// struct bdd_name_description
struct bdd_name_description *bdd_name_description_alloc(void) {
struct bdd_name_description *name_description = malloc(sizeof(struct bdd_name_description));
if (name_description == NULL) {
return NULL;
}
name_description->ssl_ctx = NULL;
name_description->service_type = bdd_service_type_none;
return name_description;
}
void bdd_name_description_clean_ssl_ctx(struct bdd_name_description *name_description) {
if (name_description->ssl_ctx != NULL) {
SSL_CTX_free(name_description->ssl_ctx); // misleading function name; it actually decs the ref count and frees the ssl_ctx if the rc hits 0
name_description->ssl_ctx = NULL;
}
return;
}
void bdd_name_description_clean_service(struct bdd_name_description *name_description) {
if (name_description->service_type == bdd_service_type_internal && name_description->service.internal.service->service_info_destructor != NULL) {
name_description->service.internal.service->service_info_destructor(name_description->service.internal.service_info);
}
name_description->service_type = bdd_service_type_none;
return;
}
void bdd_name_description_set_internal_service(struct bdd_name_description *name_description, struct bdd_internal_service *service, void *service_info) {
bdd_name_description_clean_service(name_description);
name_description->service_type = bdd_service_type_internal;
name_description->service.internal.service = service;
name_description->service.internal.service_info = service_info;
return;
}
void bdd_name_description_set_ssl_ctx(struct bdd_name_description *name_description, SSL_CTX *ssl_ctx) {
bdd_name_description_clean_ssl_ctx(name_description);
name_description->ssl_ctx = ssl_ctx;
return;
}
void bdd_name_description_destroy(struct bdd_name_description *name_description) {
bdd_name_description_clean_service(name_description);
bdd_name_description_clean_ssl_ctx(name_description);
free(name_description);
return;
}
// name_descriptions hashmap
#define bdd_name_descriptions() \
if (name_len == 0 || name_len > 254 || (name_len == 254 && name[253] != '.')) { \
return false; \
} \
if (name[name_len - 1] == '.') { \
if ((name_len -= 1) == 0) { \
return false; \
} \
} \
struct bdd_name_description *name_description = locked_hashmap_get_wl(name_descriptions, name, name_len); \
if (name_description == NULL) { \
if ((name_description = bdd_name_description_alloc()) == NULL) { \
return false; \
} \
if (!locked_hashmap_set_wl(name_descriptions, name, name_len, name_description, 1)) { \
bdd_name_description_destroy(name_description); \
return false; \
} \
}
bool bdd_name_descriptions_set_internal_service(struct locked_hashmap *name_descriptions, char *name, size_t name_len, struct bdd_internal_service *service, void *service_info) {
bdd_name_descriptions();
bdd_name_description_set_internal_service(name_description, service, service_info);
return true;
}
bool bdd_name_descriptions_set_ssl_ctx(struct locked_hashmap *name_descriptions, char *name, size_t name_len, SSL_CTX *ssl_ctx) {
bdd_name_descriptions();
bdd_name_description_set_ssl_ctx(name_description, ssl_ctx);
return true;
}

14
src/core/preinit.c Normal file
View File

@ -0,0 +1,14 @@
#include <stdatomic.h>
#include <stddef.h>
#include <openssl/ssl.h>
#include <string.h>
void bdd_mutex_preinit(pthread_mutex_t *dest) {
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
memcpy(dest, &(mutex), sizeof(pthread_mutex_t));
}
void bdd_cond_preinit(pthread_cond_t *dest) {
static pthread_cond_t mutex = PTHREAD_COND_INITIALIZER;
memcpy(dest, &(mutex), sizeof(pthread_cond_t));
}

143
src/core/serve.c Normal file
View File

@ -0,0 +1,143 @@
#include "internal.h"
#include <errno.h>
#include <unistd.h>
#include <sys/epoll.h>
#include <signal.h>
void *bdd_serve(struct bdd_instance *instance) {
pthread_sigmask(SIG_BLOCK, &(instance->sigmask), NULL);
unsigned short int next_worker_id = 0;
struct bdd_workers *workers = &(instance->workers);
bdd_serve__find_connections:;
BDD_DEBUG_LOG("linked_connections.head is %p\n", instance->linked_connections.head);
BDD_DEBUG_LOG("polling\n");
int n_events;
do {
n_events = epoll_wait(instance->epoll_fd, instance->epoll_oevents, instance->n_epoll_oevents, -1);
} while (n_events < 0 && errno == EINTR);
if (unlikely(n_events < 0)) {
fprintf(stderr, "bidirectiond epoll error: %i - try increasing your rlimits for open files\n", errno);
bdd_stop(instance);
bdd_thread_exit(instance);
}
pthread_mutex_lock(&(instance->linked_connections.mutex));
{
char g[8];
int r = read(instance->serve_eventfd, &(g), 8);
assert(r == 8 || r < 0);
}
for (struct bdd_connections **connections = &(instance->linked_connections.head); (*connections) != NULL;) {
struct bdd_connections *next = (*connections)->next;
(*connections)->working = false;
bool broken = true;
if (!(*connections)->broken) for (bdd_io_id idx = 0; idx < bdd_connections_n_max_io((*connections)); ++idx) {
int fd = (*connections)->io[idx].fd;
if (fd < 0) {
continue;
}
struct epoll_event event = {
.events = EPOLLIN,
.data = {
.ptr = (*connections),
},
};
if (epoll_ctl(instance->epoll_fd, EPOLL_CTL_ADD, fd, &(event)) != 0) {
for (bdd_io_id idx2 = 0; idx2 < idx; ++idx2) {
fd = (*connections)->io[idx2].fd;
if (fd >= 0) {
int r = epoll_ctl(instance->epoll_fd, EPOLL_CTL_DEL, fd, NULL);
assert(r == 0);
}
}
broken = true;
break;
}
broken = false;
}
if (broken) {
bdd_connections_deinit((*connections));
bdd_connections_release(instance, connections);
}
(*connections) = next;
}
pthread_mutex_unlock(&(instance->linked_connections.mutex));
if (unlikely(atomic_load(&(instance->exiting)))) {
bdd_thread_exit(instance);
}
for (int idx = 0; idx < n_events; ++idx) {
struct epoll_event *event = &(instance->epoll_oevents[idx]);
struct bdd_connections *connections = event->data.ptr;
if (connections == NULL) {
continue;
}
if (pthread_mutex_trylock(&(connections->working_mutex)) != 0) {
continue;
}
bool already_working = connections->working;
connections->working = true;
pthread_mutex_unlock(&(connections->working_mutex));
if (already_working) {
continue;
}
bool broken = false;
for (bdd_io_id io_id = 0; io_id < bdd_connections_n_max_io(connections); ++io_id) {
if (connections->io[io_id].fd < 0) {
continue;
}
if (!broken) {
short revents = bdd_poll(connections, io_id);
if ((revents & (POLLERR | POLLHUP | POLLRDHUP)) && !(revents & POLLIN)) {
broken = true;
}
assert(!(revents & POLLNVAL));
}
int r = epoll_ctl(instance->epoll_fd, EPOLL_CTL_DEL, connections->io[io_id].fd, NULL);
assert(r == 0);
}
if (broken) {
BDD_DEBUG_LOG("found broken connections struct\n");
bdd_connections_deinit(connections);
bdd_connections_release(instance, &(connections));
} else {
BDD_DEBUG_LOG("found working connections struct\n");
struct bdd_worker *worker;
if (workers->available_stack.ids == NULL) {
worker = &(workers->info[next_worker_id]);
next_worker_id = (next_worker_id + 1) % workers->n_workers;
} else {
pthread_mutex_lock(&(workers->available_stack.mutex));
if (workers->available_stack.idx == workers->n_workers) {
BDD_DEBUG_LOG("no available worker threads; waiting...\n");
do {
pthread_cond_wait(&(workers->available_stack.cond), &(workers->available_stack.mutex));
} while (workers->available_stack.idx == workers->n_workers);
}
worker = &(workers->info[workers->available_stack.ids[(workers->available_stack.idx)++]]);
pthread_mutex_unlock(&(workers->available_stack.mutex));
}
BDD_DEBUG_LOG("worker thread %i chosen!\n", (int)worker->id);
pthread_mutex_lock(&(worker->work_mutex));
if (worker->connections == NULL) {
worker->connections_appender = &(worker->connections);
}
connections->next = NULL;
(*(worker->connections_appender)) = connections;
worker->connections_appender = &(connections->next);
pthread_cond_signal(&(worker->work_cond));
pthread_mutex_unlock(&(worker->work_mutex));
}
}
goto bdd_serve__find_connections;
}

25
src/core/signal.c Normal file
View File

@ -0,0 +1,25 @@
#include "internal.h"
#include <unistd.h>
void bdd_signal(struct bdd_instance *instance) {
char buf[8] = { ~0, 0, 0, 0, 0, 0, 0, ~0, };
int r = write(instance->serve_eventfd, (void *)buf, 8);
assert(r == 8 || r < 0);
return;
}
void bdd_stop_accept(struct bdd_instance *instance) {
char buf[8] = { ~0, 0, 0, 0, 0, 0, 0, ~0, };
int r = write(instance->accept.eventfd, (void *)buf, 8);
assert(r == 8 || r < 0);
return;
}
void bdd_thread_exit(struct bdd_instance *instance) {
pthread_mutex_lock(&(instance->n_running_threads_mutex));
if ((instance->n_running_threads -= 1) == 0) {
pthread_cond_signal(&(instance->n_running_threads_cond));
}
pthread_mutex_unlock(&(instance->n_running_threads_mutex));
pthread_exit(NULL);
}

51
src/core/workers.c Normal file
View File

@ -0,0 +1,51 @@
#include "internal.h"
#include <signal.h>
void *bdd_worker(struct bdd_worker *worker) {
struct bdd_instance *instance = worker->instance;
struct bdd_workers *workers = &(instance->workers);
pthread_sigmask(SIG_BLOCK, &(instance->sigmask), NULL);
unsigned char *buf;
size_t buf_sz = workers->buf_sz_per_worker;
if (workers->buf == NULL) {
buf = alloca(buf_sz);
} else {
buf = workers->buf + (worker->id * buf_sz);
}
bdd_worker__work:;
if (workers->available_stack.ids != NULL) {
pthread_mutex_lock(&(workers->available_stack.mutex));
workers->available_stack.ids[--(workers->available_stack.idx)] = worker->id;
pthread_cond_signal(&(workers->available_stack.cond));
pthread_mutex_unlock(&(workers->available_stack.mutex));
}
BDD_DEBUG_LOG("thread accepting work\n");
// await work
pthread_mutex_lock(&(worker->work_mutex));
while (worker->connections == NULL && !atomic_load(&(instance->exiting))) {
pthread_cond_wait(&(worker->work_cond), &(worker->work_mutex));
}
BDD_DEBUG_LOG("thread received work!\n");
if (unlikely(atomic_load(&(instance->exiting)))) {
bdd_thread_exit(instance);
}
struct bdd_connections *connections = worker->connections;
worker->connections = connections->next;
pthread_mutex_unlock(&(worker->work_mutex));
assert(connections->service->serve != NULL);
if (!connections->service->serve(connections, buf, buf_sz)) {
connections->broken = true;
}
bdd_connections_link(instance, &(connections));
goto bdd_worker__work;
}

6
src/core_settings.h Normal file
View File

@ -0,0 +1,6 @@
#ifndef bidirectiond__core_settings__h
#define bidirectiond__core_settings__h
#include <bddc/api.h>
extern struct bdd_internal_service internal_services[];
extern struct bdd_settings settings;
#endif

20
src/cp_pwd.c Normal file
View File

@ -0,0 +1,20 @@
#include <string.h>
#include <assert.h>
#include "cp_pwd.h"
int bdd_cp_pwd(char *dest, int dest_sz, int rwflag, void *_ctx) {
assert(rwflag == 0);
struct bdd_cp_ctx *ctx = _ctx;
ctx->success = false;
char *src = ctx->password;
if (src == NULL) {
return 0;
}
int src_len = (int)strlen(src);
if (dest_sz <= 0 || src_len <= 0 || src_len >= dest_sz) {
return 0;
}
memcpy(dest, src, dest_sz);
ctx->success = true;
return src_len;
}

9
src/cp_pwd.h Normal file
View File

@ -0,0 +1,9 @@
#ifndef bidirectiond__cp_pwd__h
#define bidirectiond__cp_pwd__h
#include <stdbool.h>
struct bdd_cp_ctx {
bool success;
char *password;
};
int bdd_cp_pwd(char *dest, int dest_sz, int rwflag, void *_ctx);
#endif

1
src/hashmap Submodule

@ -0,0 +1 @@
Subproject commit f610c4618f586e59abb4c46895c6287f1848707b

232
src/input_processor.c Normal file
View File

@ -0,0 +1,232 @@
#include "input_processor.h"
#include "cp_pwd.h"
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <openssl/ssl.h>
#include <pthread.h>
#include <errno.h>
#include "tls_put.h"
#include "core_settings.h"
#ifndef BIDIRECTIOND_BUFFERED_READ_BUF_SIZE
#define BIDIRECTIOND_BUFFERED_READ_BUF_SIZE 0x200
#endif
struct buffered_read_ctx {
unsigned int idx;
unsigned int len;
char byte;
};
#define BUFFERED_READ_CTX_INITALISER { .idx = 0, .len = 0, .byte = 0, }
static bool buffered_read(int fd, char *buf, int buf_sz, struct buffered_read_ctx *ctx) {
if (buf_sz < 0) {
return false;
}
if (ctx->idx == ctx->len) {
for (;;) {
int r = read(fd, buf, buf_sz);
if (r < 0 && errno == EINTR) {
continue;
}
if (r <= 0) {
return false;
}
ctx->len = r;
ctx->idx = 0;
break;
}
}
ctx->byte = buf[ctx->idx++];
return true;
}
#define buffered_read() buffered_read(fd, br_buf, br_buf_sz, &(br_ctx))
void input_processor(int sfd, char *br_buf, int br_buf_sz) {
int fd = accept(sfd, NULL, 0);
struct buffered_read_ctx br_ctx = BUFFERED_READ_CTX_INITALISER;
static struct {
char *str;
uint8_t str_sz;
} match_list[] = {
{
.str = "TLS_PEM_LOAD",
.str_sz = 12,
},
{
.str = "PING",
.str_sz = 4,
},
};
input_processor__process:;
uint8_t n_matches = sizeof(match_list) / sizeof(match_list[0]);
uint8_t match;
bool matches[sizeof(match_list) / sizeof(match_list[0])];
for (typeof(match) idx = 0; idx < n_matches; ++idx) {
matches[idx] = true;
}
for (uint8_t it = 0;; ++it) {
match = 0;
if (it == 0xFF) {
goto input_processor__process;
}
if (!buffered_read()) {
goto input_processor__err;
}
if (br_ctx.byte == 1) {
goto input_processor__process;
}
if (br_ctx.byte == 0) {
if (it == 0) {
goto input_processor__wait;
}
for (typeof(match) rmatch = 0; rmatch < n_matches; ++rmatch) {
match = n_matches - 1 - rmatch;
if (matches[match]) {
goto input_processor__matched;
}
}
goto input_processor__wait;
}
for (; match < n_matches; ++match) {
if (it >= match_list[match].str_sz) {
if ((n_matches = match) == 0) {
goto input_processor__process;
}
break;
}
if (match_list[match].str[it] != br_ctx.byte) {
matches[match] = false;
}
}
}
input_processor__matched:;
if (match == 0 /* TLS_PEM_LOAD */) {
SSL_CTX *ctx = SSL_CTX_new(TLS_server_method());
BIO *bio = NULL;
X509 *cert = NULL;
EVP_PKEY *key = NULL;
uint8_t e = 0;
if (ctx == NULL) {
goto input_processor__tls_pem_load_err;
}
if (SSL_CTX_set_cipher_list(ctx, "AES256-SHA256") == 0) {
goto input_processor__tls_pem_load_err;
}
if ((bio = BIO_new(BIO_s_mem())) == NULL) {
goto input_processor__tls_pem_load_err;
}
for (;;) {
if (!buffered_read()) {
e |= 0b1;
goto input_processor__tls_pem_load_err;
}
if (br_ctx.byte == 0) {
break;
}
if (br_ctx.byte == 1 || BIO_write(bio, &(br_ctx.byte), 1) != 1) {
goto input_processor__tls_pem_load_err;
}
}
if ((cert = PEM_read_bio_X509(bio, NULL, NULL, "")) == NULL) {
goto input_processor__tls_pem_load_err;
}
BIO_free(bio);
if ((bio = BIO_new(BIO_s_mem())) == NULL) {
goto input_processor__tls_pem_load_err;
}
for (;;) {
if (!buffered_read()) {
e |= 0b1;
goto input_processor__tls_pem_load_err;
}
if (br_ctx.byte == 0) {
break;
}
if (br_ctx.byte == 1 || BIO_write(bio, &(br_ctx.byte), 1) != 1) {
goto input_processor__tls_pem_load_err;
}
}
if (!buffered_read()) {
br_ctx.byte = 1;
}
struct bdd_cp_ctx cp_ctx = {
.success = false,
.password = NULL,
};
if (br_ctx.byte != 1) {
char env_variable_name[0x100];
for (size_t idx = 0;; ++idx) {
if (idx == sizeof(env_variable_name)) {
goto input_processor__tls_pem_load_err;
}
env_variable_name[idx] = br_ctx.byte;
if (br_ctx.byte == 0) {
cp_ctx.password = getenv(env_variable_name);
break;
}
if (br_ctx.byte == 1) {
goto input_processor__tls_pem_load_err;
}
if (!buffered_read()) {
e |= 0b1;
goto input_processor__tls_pem_load_err;
}
}
}
if ((key = PEM_read_bio_PrivateKey(bio, NULL, bdd_cp_pwd, &(cp_ctx))) == NULL) {
goto input_processor__tls_pem_load_err;
}
if (SSL_CTX_use_certificate(ctx, cert) == 1 &&
SSL_CTX_use_PrivateKey(ctx, key) == 1) {
struct locked_hashmap *lh = hashmap_lock(settings.name_descriptions);
if (tls_put(lh, &(ctx))) {
e |= 0b10;
}
locked_hashmap_unlock(&(lh));
}
input_processor__tls_pem_load_err:;
if (ctx != NULL) {
SSL_CTX_free(ctx);
}
if (bio != NULL) {
BIO_free(bio);
}
if (cert != NULL) {
X509_free(cert);
}
if (key != NULL) {
EVP_PKEY_free(key);
}
if (e & 0b10) {
puts("added SSL_CTX");
} else {
puts("failed to add SSL_CTX");
}
if (e & 0b1) {
goto input_processor__err;
}
} else if (match == 1) {
puts("input_processor got PING!");
}
input_processor__wait:;
while (br_ctx.byte != 1) {
if (!buffered_read()) {
goto input_processor__err;
}
if (br_ctx.byte == 1) {
break;
}
puts("extraneous byte sent to input_processor");
}
goto input_processor__process;
input_processor__err:;
shutdown(fd, SHUT_RDWR);
close(fd);
return;
}

10
src/input_processor.h Normal file
View File

@ -0,0 +1,10 @@
#ifndef bidirectiond__fifo_processor__h
#define bidirectiond__fifo_processor__h
#include <stddef.h>
#include <sys/types.h>
#include <stdbool.h>
void input_processor(int fd, char *br_buf, int br_buf_sz);
#endif

459
src/main.c Normal file
View File

@ -0,0 +1,459 @@
#include <bddc/api.h>
#include <sys/resource.h>
#include <arpa/inet.h>
#include "strtoint.h"
#include "core_settings.h"
#include "input_processor.h"
#include "tls_put.h"
#include "cp_pwd.h"
#include <unistd.h>
#include <pwd.h>
#include <fcntl.h>
#include <openssl/err.h>
#include <openssl/x509v3.h>
#include <signal.h>
#include <sys/signalfd.h>
#include <openssl/engine.h>
#include <sys/stat.h>
#include <sys/un.h>
#ifndef BIDIRECTIOND_USERNAME
#define BIDIRECTIOND_USERNAME "nobody"
#endif
#if CHAR_BIT != 8
#error The target architecture is unsupported
#endif
struct bdd_settings settings = {
.name_descriptions = NULL,
.n_connections = 0x100,
.n_epoll_oevents = 0x100,
.buf_sz = 0x800,
.n_worker_threads = 16,
.client_timeout = 12000,
.use_stack_buf = false,
.sv_socket = -1,
.use_work_queues = false,
// .sigmask = x,
};
#define PASTE(x, y) x##y
#define sto(w, t) \
void PASTE(sto, w)(t *dest, char *str) { \
signed long long int v; \
if (!bdd_strtosll(str, strlen(str), &(v))) { \
return; \
} \
if (v == (t)v) { \
*dest = (t)v; \
} \
return; \
}
sto(i, int);
sto(ui, unsigned int);
sto(usi, unsigned short int);
sto(uid, uid_t);
sto(gid, gid_t);
sto(sz, size_t);
void storlim(rlim_t *dest, char *str) {
unsigned long long int v;
if (!bdd_strtoull(str, strlen(str), &(v))) {
return;
}
if (v == (rlim_t)v) {
*dest = (rlim_t)v;
}
return;
}
// main
#ifndef HASHMAP_MAIN
int main(int argc, char *argv[], char *env[]) {
puts("bidirectiond version "PROG_SEMVER);
// set up tls
if (!OPENSSL_init_ssl(0, NULL)) {
return 1; // nothing to clean up
}
struct bdd_instance *bdd_instance = NULL;
int input_fd = -1;
struct sockaddr_un input_addr = { 0, .sun_family = AF_UNIX, };
int sig_fd = -1;
// name_descriptions
if ((settings.name_descriptions = hashmap_create((void (*)(void *))&(bdd_name_description_destroy))) == NULL) {
fputs("failed to allocate settings.name_descriptions\n", stderr);
goto main__clean_up;
}
// args
char **arg = &(argv[1]);
if (argc < 1 /* linux */) {
arg = argv;
}
bool use_ipv4 = false;
int backlog = 0;
unsigned short int port = 443;
// the uid of the current user
/* for example:
nuid will contain the uid that the program was started by,
even if bidirectiond is setuid.
nuid will be used later to restore a safe uid, if the process
becomes (or already is) root. */
uid_t nuid = getuid();
gid_t ngid = getgid();
setpwent();
for (struct passwd *pw = getpwent(); pw != NULL; pw = getpwent()) {
if (strcmp(pw->pw_name, BIDIRECTIOND_USERNAME) == 0) {
#ifdef BIDIRECTIOND_SU
nuid = pw->pw_uid;
ngid = pw->pw_gid;
#else
if (nuid == 0) {
nuid = pw->pw_uid;
}
if (ngid == 0) {
ngid = pw->pw_gid;
}
#endif
break;
}
}
endpwent();
struct locked_hashmap *lh = hashmap_lock(settings.name_descriptions);
size_t big_alloc_sz = 0;
#define EXPECT_ARGS(n) for (size_t idx = 1; idx <= n; ++idx) { \
if (arg[idx] == NULL || arg[idx][0] == '-') { \
goto main__arg_fuck; \
} \
}
main__arg_iter:;
while ((*arg) != NULL) {
if (strcmp((*arg), "--n-connection-threads") == 0 || strcmp((*arg), "-t") == 0) {
EXPECT_ARGS(1);
stousi(&(settings.n_worker_threads), arg[1]);
arg += 2;
} else if (strcmp((*arg), "--client-timeout") == 0) {
EXPECT_ARGS(1);
stoui(&(settings.client_timeout), arg[1]);
arg += 2;
} else if (strcmp((*arg), "-l") == 0) {
EXPECT_ARGS(2);
struct rlimit rlimit;
storlim(&(rlimit.rlim_cur), arg[1]);
storlim(&(rlimit.rlim_max), arg[2]);
if (setrlimit(RLIMIT_NOFILE, &(rlimit)) != 0) {
fputs("setrlimit failed\n", stderr);
}
arg += 3;
} else if (strcmp((*arg), "--buffer-size") == 0 || strcmp((*arg), "-b") == 0) {
EXPECT_ARGS(1);
stoui(&(settings.buf_sz), arg[1]);
arg += 2;
} else if (strcmp((*arg), "--backlog") == 0) {
EXPECT_ARGS(1);
stoi(&(backlog), arg[1]);
arg += 2;
} else if (strcmp((*arg), "--server-tcp-port") == 0 || strcmp((*arg), "-p") == 0) {
EXPECT_ARGS(1);
stousi(&(port), arg[1]);
arg += 2;
} else if (strcmp((*arg), "--max-connections") == 0) {
EXPECT_ARGS(1);
stoi(&(settings.n_connections), arg[1]);
arg += 2;
} else if (strcmp((*arg), "--listen-ipv4") == 0) {
use_ipv4 = true;
arg += 1;
} else if (strcmp((*arg), "--use-work-queue") == 0) {
settings.use_work_queues = true;
arg += 1;
} else if (strcmp((*arg), "--nohup") == 0) {
struct sigaction action = {
.sa_handler = SIG_IGN,
.sa_flags = SA_RESTART,
};
sigaction(SIGHUP, &(action), 0);
arg += 1;
} else if (strcmp((*arg), "--tls-credentials") == 0 || strcmp((*arg), "-c") == 0) {
EXPECT_ARGS(3);
SSL_CTX *ctx = SSL_CTX_new(TLS_server_method());
if (SSL_CTX_set_cipher_list(ctx, "AES256-SHA256") == 0) {
goto main__arg_creds_err;
}
if (SSL_CTX_use_certificate_file(ctx, arg[1], SSL_FILETYPE_PEM) != 1) {
fputs("invalid certificate file\n", stderr);
goto main__arg_creds_err;
}
struct bdd_cp_ctx cp_ctx = {
.success = false,
.password = getenv(arg[3]),
};
SSL_CTX_set_default_passwd_cb(ctx, bdd_cp_pwd);
SSL_CTX_set_default_passwd_cb_userdata(ctx, &(cp_ctx));
if (SSL_CTX_use_PrivateKey_file(ctx, arg[2], SSL_FILETYPE_PEM) != 1) {
fputs("invalid private key file\n", stderr);
goto main__arg_creds_err;
}
if (!cp_ctx.success) {
fputs("the private key file must be encrypted\n", stderr);
goto main__arg_creds_err;
}
if (!tls_put(lh, &(ctx))) {
fputs("seemingly invalid certificate file\n", stderr);
goto main__arg_creds_err;
}
main__arg_creds_err:;
if (ctx != NULL) {
SSL_CTX_free(ctx);
}
arg += 4;
} else if (strcmp((*arg), "--UNSAFE allocate buffer on stack") == 0) {
fputs("'--UNSAFE allocate buffer on stack' is unsafe and shouldn't be used\n", stderr);
settings.use_stack_buf = true;
arg += 1;
} else if (getuid() == 0 && strcmp((*arg), "--uid") == 0) {
EXPECT_ARGS(1);
stouid(&(nuid), arg[1]);
arg += 2;
} else if (getgid() == 0 && strcmp((*arg), "--gid") == 0) {
EXPECT_ARGS(1);
stogid(&(ngid), arg[1]);
arg += 2;
} else if (strcmp((*arg), "--n-epoll-oevents") == 0) {
EXPECT_ARGS(1);
stoi(&(settings.n_epoll_oevents), arg[1]);
arg += 2;
} else if (strcmp((*arg), "--input") == 0) {
EXPECT_ARGS(1);
size_t str_len = strlen(arg[1]);
if (str_len == 0 || str_len >= sizeof(input_addr.sun_path)) {
fputs("--input path is of invalid length\n", stderr);
} else if (input_addr.sun_path[0] != 0) {
fputs("cannot specify --input twice\n", stderr);
} else {
strcpy(input_addr.sun_path, arg[1]);
}
arg += 2;
} else if (strcmp((*arg), "--big-alloc") == 0) {
EXPECT_ARGS(1);
stosz(&(big_alloc_sz), arg[1]);
arg += 2;
} else {
for (size_t idx = 0; idx < N_INTERNAL_SERVICES; ++idx) {
if (internal_services[idx].supported_arguments != NULL) for (size_t pidx = 0; internal_services[idx].supported_arguments[pidx]; ++pidx) {
if (strcmp((*arg), internal_services[idx].supported_arguments[pidx]) == 0) {
size_t n = 1;
while (arg[n] != NULL && (arg[n][0] != '-' || (arg[n][1] >= '0' && arg[n][1] <= '9'))) {
n += 1;
}
if (!internal_services[idx].service_init(lh, &(internal_services[idx]), n, arg)) {
goto main__arg_fuck;
}
arg = &(arg[n]);
goto main__arg_iter;
}
}
}
main__arg_fuck:;
puts(
"argument parsing failed\n"
"-t: set the amount of worker threads\n"
"--client-timeout: set the timeout (in ms) for client socket i/o\n"
"-l: set the rlimits for open files (soft limit, hard limit)\n"
"-b: set the size of the large worker buffers\n"
"--backlog: set the tcp backlog for sv_socket\n"
"-p: set the tcp port to bind sv_socket to\n"
"--max-connections: the max amount of bdd_connections structs\n"
"--listen-ipv4: sv_socket should not use ipv6\n"
"--use-work-queue: do not wait for worker threads before giving them work\n"
"--nohup: SIG_IGN SIGHUP\n"
"-c: load pem-encoded tls credentials (e.g. `-c cert.pem encrypted-key.pem name-of-password-environment-variable`)\n"
"--input: set the path for a udp unix socket, so that some bidirectiond settings can be modified without restarting\n"
"--n-epoll-oevents: epoll_wait maxevents\n"
"--big-alloc: reserve some ram"
);
for (size_t idx = 0; idx < N_INTERNAL_SERVICES; ++idx) {
if (internal_services[idx].arguments_help != NULL) {
fputs(internal_services[idx].arguments_help, stdout);
}
}
locked_hashmap_unlock(&(lh));
goto main__clean_up;
}
}
locked_hashmap_unlock(&(lh));
// potentially a setgid program
if (getgid() != 0 && getegid() == 0) {
setgid(0);
}
// potentially a setuid program
if (getuid() != 0 && geteuid() == 0) {
setuid(0);
}
if (port < 1024 && getuid() != 0) {
// root required to obtain port 443
fputs("must be run as root\n", stderr);
goto main__clean_up;
}
// set up socket
union {
struct sockaddr_in inet4;
struct sockaddr_in6 inet6;
} sv_addr = { 0, };
size_t sv_addr_sz = 0;
if (use_ipv4) {
settings.sv_socket = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
sv_addr_sz = sizeof(struct sockaddr_in);
sv_addr.inet4.sin_family = AF_INET;
sv_addr.inet4.sin_addr.s_addr = INADDR_ANY;
sv_addr.inet4.sin_port = htons(port);
} else {
settings.sv_socket = socket(AF_INET6, SOCK_STREAM | SOCK_NONBLOCK, 0);
sv_addr_sz = sizeof(struct sockaddr_in6);
sv_addr.inet6.sin6_family = AF_INET6;
sv_addr.inet6.sin6_addr = in6addr_any;
sv_addr.inet6.sin6_port = htons(port);
}
// try to bind to port
int opt = 1;
setsockopt(settings.sv_socket, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &(opt), sizeof(opt));
if (bind(settings.sv_socket, (struct sockaddr *)&(sv_addr), sv_addr_sz) < 0 ||
listen(settings.sv_socket, backlog) < 0) {
fputs("failed to bind sv_socket! is the port already use? is the internet protocol enabled?\n", stderr);
goto main__clean_up;
}
if (getgid() == 0) {
setgid(ngid);
}
if (getuid() == 0) {
setuid(nuid);
}
//#ifndef NDEBUG
// check should never pass
if (getuid() == 0 || getgid() == 0 || geteuid() == 0 || getegid() == 0) {
fputs("unsafe\n", stderr);
assert(false);
return 1;
}
//#endif
if (input_addr.sun_path[0] != 0 && (input_fd = socket(AF_UNIX, SOCK_STREAM, 0)) >= 0) {
if (bind(input_fd, (struct sockaddr *)&(input_addr), sizeof(struct sockaddr_un)) != 0) {
fputs("failed to bind input socket\n", stderr);
close(input_fd);
input_fd = -1;
} else if (listen(input_fd, 0) != 0) {
close(input_fd);
input_fd = -1;
}
}
// signals
signal(SIGPIPE, SIG_IGN);
sigset_t sigset;
sigemptyset(&(sigset));
sigaddset(&(sigset), SIGINT);
sigaddset(&(sigset), SIGTERM);
settings.sigmask = sigset;
pthread_sigmask(SIG_BLOCK, &(sigset), NULL);
if ((sig_fd = signalfd(-1, &(sigset), 0)) < 0) {
goto main__clean_up;
}
struct pollfd pollfds[2] = {
{
.fd = sig_fd,
.events = POLLIN,
},
{
.fd = input_fd,
.events = POLLIN,
.revents = 0,
},
};
// serve
bdd_instance = bdd_go(settings);
if (bdd_instance == NULL) {
goto main__clean_up;
}
if (big_alloc_sz > 0) {
void *big_alloc = malloc(big_alloc_sz);
if (big_alloc == NULL) {
goto main__clean_up;
}
free(big_alloc);
}
struct signalfd_siginfo sig;
for (;;) {
while (poll((struct pollfd *)&(pollfds), input_fd < 0 ? 1 : 2, -1) < 0) {
if (errno != EINTR /* e.g., SIGUSR1 could be sent to bidirectiond, and then handled by this thread */) {
goto main__clean_up;
}
}
if (pollfds[0].revents & POLLIN) {
if (read(sig_fd, &(sig), sizeof(struct signalfd_siginfo)) != sizeof(struct signalfd_siginfo)) {
goto main__clean_up;
}
switch (sig.ssi_signo) {
case (SIGINT): case (SIGTERM): {
goto main__clean_up;
}
default: {
assert(false);
}
}
}
if (pollfds[1].revents & POLLIN) {
char buf[0x100];
input_processor(input_fd, (char *)&(buf), sizeof(buf));
}
}
main__clean_up:;
if (bdd_instance != NULL) {
bdd_stop(bdd_instance);
bdd_wait(bdd_instance);
bdd_destroy(bdd_instance);
}
if (sig_fd != -1) {
close(sig_fd);
}
if (input_fd != -1) {
unlink(input_addr.sun_path);
close(input_fd);
}
if (settings.sv_socket != -1) {
shutdown(settings.sv_socket, SHUT_RDWR);
close(settings.sv_socket);
}
// aight
if (settings.name_descriptions != NULL) {
hashmap_destroy(settings.name_descriptions);
}
// https://stackoverflow.com/questions/29845527/how-to-properly-uninitialize-openssl
//FIPS_mode_set(0);
CRYPTO_set_locking_callback(NULL);
CRYPTO_set_id_callback(NULL);
//ERR_remove_state(0);
SSL_COMP_free_compression_methods();
ENGINE_cleanup();
CONF_modules_free();
CONF_modules_unload(1);
COMP_zlib_cleanup();
ERR_free_strings(); // TODO: is that needed?
EVP_cleanup();
CRYPTO_cleanup_all_ex_data();
return 0;
}
#endif

125
src/services/gs/gs.c Normal file
View File

@ -0,0 +1,125 @@
#include <bddc/api.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
struct general_service__associated {
bdd_io_id client;
bdd_io_id service;
};
static bool serve(struct bdd_connections *connections, void *buf, size_t buf_size, bdd_io_id from, bdd_io_id to) {
do {
int n;
if ((n = bdd_read(connections, from, buf, buf_size)) <= 0) {
return false;
}
if (bdd_write(connections, to, buf, n) <= 0) {
return false;
}
} while ((bdd_poll(connections, from) & POLLIN));
return true;
}
bool general_service__serve(struct bdd_connections *connections, void *buf, size_t buf_size) {
struct general_service__associated *associated = bdd_get_associated(connections);
if ((bdd_poll(connections, associated->client) & POLLIN)) {
if (!serve(connections, buf, buf_size, associated->client, associated->service)) {
return false;
}
}
if ((bdd_poll(connections, associated->service)) & POLLIN) {
if (!serve(connections, buf, buf_size, associated->service, associated->client)) {
return false;
}
}
return true;
}
struct general_service__info {
struct addrinfo *addrinfo;
char *tls_name;
};
bool general_service__connections_init(struct bdd_connections *connections, void *service_info, bdd_io_id client_id, struct sockaddr client_sockaddr) {
struct general_service__info *info = service_info;
struct addrinfo *addrinfo = info->addrinfo;
int sock = -1;
for (; addrinfo != NULL; addrinfo = addrinfo->ai_next) {
if ((sock = socket(addrinfo->ai_family, addrinfo->ai_socktype, addrinfo->ai_protocol)) < 0) {
continue;
}
if (connect(sock, addrinfo->ai_addr, addrinfo->ai_addrlen) >= 0) {
break;
}
close(sock);
sock = -1;
}
if (sock < 0) {
return false;
}
bdd_io_id service;
struct general_service__associated *associated;
if (!bdd_create_io(connections, &(service), &(sock), info->tls_name)) {
close(sock);
return false;
}
if ((associated = malloc(sizeof(struct general_service__associated))) == NULL) {
// bdd-core will destroy the io
return false;
}
associated->client = client_id;
associated->service = service;
bdd_set_associated(connections, associated, free);
return true;
}
void general_service__service_info_destructor(void *hint) {
struct general_service__info *info = hint;
freeaddrinfo(info->addrinfo);
free(info);
}
static bool handle_s(struct locked_hashmap *name_descriptions, struct bdd_internal_service *service, char *scope, char *addr, char *port, bool use_tls) {
struct general_service__info *info = malloc(sizeof(struct general_service__info));
struct addrinfo hints = { 0, .ai_family = AF_UNSPEC, .ai_socktype = SOCK_STREAM, };
struct addrinfo *res = NULL;
if (info == NULL) {
goto handle_s__err;
}
if (use_tls) {
info->tls_name = addr;
} else {
info->tls_name = NULL;
}
if (getaddrinfo(addr, port, &(hints), &(res)) != 0) {
goto handle_s__err;
}
info->addrinfo = res;
if (!bdd_name_descriptions_set_internal_service(name_descriptions, scope, strlen(scope), service, info)) {
goto handle_s__err;
}
return true;
handle_s__err:;
if (info != NULL) {
free(info);
}
if (res != NULL) {
freeaddrinfo(res);
}
return false;
}
bool general_service__service_init(struct locked_hashmap *name_descriptions, struct bdd_internal_service *service, size_t argc, char **argv) {
if (strcmp(argv[0], "-s") == 0) {
if (argc != 5) {
return false;
}
bool use_tls;
if (strcmp(argv[4], "true") == 0) {
use_tls = true;
} else if (strcmp(argv[4], "false") == 0) {
use_tls = false;
} else {
return false;
}
return handle_s(name_descriptions, service, argv[1], argv[2], argv[3], use_tls);
}
return false;
}

View File

@ -0,0 +1,10 @@
{
"name": "general_service",
"serve": "general_service__serve",
"service_init": "general_service__service_init",
"connections_init": "general_service__connections_init",
"service_info_destructor": "general_service__service_info_destructor",
"supported_arguments": [ "-s" ],
"arguments_help": "-s: creates a barebones proxy (e.g. `-s [service name] [target address/name] [target tcp port] [use ssl (true/false)]`)\n",
"n_max_io": 2
}

68
src/strtoint.c Normal file
View File

@ -0,0 +1,68 @@
#include "strtoint.h"
bool bdd_strtosll(char *str, size_t len, signed long long int *sll) {
if (len == 0) {
return false;
}
bool negative = false;
size_t idx = 0;
if (str[idx] == '-') {
negative = true;
if ((idx += 1) <= len) {
return false;
}
}
long long int r = 0;
long long int t;
char d;
for (; idx < len; ++idx) {
t = r;
r *= 10;
d = str[idx];
if (d < '0' || d > '9') {
return false;
}
d -= '0';
if (negative) {
if ((r -= d) > t) {
return false;
}
} else {
if ((r += d) < t) {
return false;
}
}
}
*sll = r;
return true;
}
bool bdd_strtoull(char *str, size_t len, unsigned long long int *llu) {
if (len == 0) {
return false;
}
bool negative = false;
long long int r = 0;
long long int t;
char d;
for (size_t idx = 0; idx < len; ++idx) {
t = r;
r *= 10;
d = str[idx];
if (d < '0' || d > '9') {
return false;
}
d -= '0';
if ((r += d) < t) {
return false;
}
}
*llu = r;
return true;
}
size_t bdd_strlene(char *str, char e) {
size_t len = 0;
while (str[len] != e) {
len += 1;
}
return len;
}

11
src/strtoint.h Normal file
View File

@ -0,0 +1,11 @@
#ifndef bidirectiond__strtolli__h
#define bidirectiond__strtolli__h
#include <stddef.h>
#include <stdbool.h>
bool bdd_strtosll(char *str, size_t len, signed long long int *sll);
bool bdd_strtoull(char *str, size_t len, unsigned long long int *ull);
size_t bdd_strlene(char *str, char e);
#endif

70
src/tls_put.c Normal file
View File

@ -0,0 +1,70 @@
#include <string.h>
#include <openssl/x509v3.h>
#include "tls_put.h"
#include "core_settings.h"
// hashmap_lock call makes it thread-safe
bool tls_put(struct locked_hashmap *ns, SSL_CTX **ctx_ref) {
// take the ref
SSL_CTX *ctx = *ctx_ref;
*ctx_ref = NULL;
bool should_up_rc = false;
GENERAL_NAMES *dns_alt_names = X509_get_ext_d2i(SSL_CTX_get0_certificate(ctx), NID_subject_alt_name, 0, 0);
if (dns_alt_names != NULL) {
int n_dns_alt_names = sk_GENERAL_NAME_num(dns_alt_names);
if (n_dns_alt_names < 0) {
n_dns_alt_names = 0;
}
for (int idx = 0; idx < n_dns_alt_names; ++idx) {
GENERAL_NAME *entry = sk_GENERAL_NAME_value(dns_alt_names, idx);
if (entry->type != GEN_DNS) {
continue;
}
ASN1_IA5STRING *asn1_str = entry->d.dNSName;
int data_length = asn1_str->length;
if (bdd_name_descriptions_set_ssl_ctx(ns, (char *)asn1_str->data, data_length, ctx)) {
if (should_up_rc) {
SSL_CTX_up_ref(ctx);
} else {
should_up_rc = true;
}
}
}
GENERAL_NAMES_free(dns_alt_names);
} else { // rfc6125
X509_NAME *dns_subject_names = X509_get_subject_name(SSL_CTX_get0_certificate(ctx));
if (dns_subject_names != NULL) {
int n_dns_subject_names = X509_NAME_entry_count(dns_subject_names);
if (n_dns_subject_names < 0) {
n_dns_subject_names = 0;
}
for (int idx = 0; idx < n_dns_subject_names; ++idx) {
X509_NAME_ENTRY *entry = X509_NAME_get_entry(dns_subject_names, idx);
ASN1_OBJECT *asn1_obj = X509_NAME_ENTRY_get_object(entry);
ASN1_STRING *asn1_str = X509_NAME_ENTRY_get_data(entry);
if (asn1_obj == NULL || asn1_str == NULL) {
continue;
}
if (OBJ_obj2nid(asn1_obj) != NID_commonName) {
continue;
}
int data_length = asn1_str->length;
if (bdd_name_descriptions_set_ssl_ctx(ns, (char *)asn1_str->data, data_length, ctx)) {
if (should_up_rc) {
SSL_CTX_up_ref(ctx);
} else {
should_up_rc = true;
}
}
}
}
}
if (!should_up_rc) /* if `ctx` is not referenced by any hashmap values, then free `ctx` */ {
SSL_CTX_free(ctx);
return false;
}
return true;
}

10
src/tls_put.h Normal file
View File

@ -0,0 +1,10 @@
#ifndef bidirectiond__tls_put__h
#define bidirectiond__tls_put__h
#include <stdbool.h>
#include <openssl/ssl.h>
#include <hashmap/hashmap.h>
bool tls_put(struct locked_hashmap *ns, SSL_CTX **ctx);
#endif