Allow listening on multiple interfaces
This commit is contained in:
@@ -1,8 +1,10 @@
|
|||||||
# WeaselDB Configuration File
|
# WeaselDB Configuration File
|
||||||
|
|
||||||
[server]
|
[server]
|
||||||
bind_address = "127.0.0.1"
|
# Network interfaces to listen on - production config with just TCP
|
||||||
port = 8080
|
interfaces = [
|
||||||
|
{ type = "tcp", address = "127.0.0.1", port = 8080 }
|
||||||
|
]
|
||||||
# Maximum request size in bytes (for 413 Content Too Large responses)
|
# Maximum request size in bytes (for 413 Content Too Large responses)
|
||||||
max_request_size_bytes = 1048576 # 1MB
|
max_request_size_bytes = 1048576 # 1MB
|
||||||
# Number of I/O threads for handling connections and network events
|
# Number of I/O threads for handling connections and network events
|
||||||
|
|||||||
@@ -79,9 +79,31 @@ void ConfigParser::parse_section(const auto &toml_data,
|
|||||||
void ConfigParser::parse_server_config(const auto &toml_data,
|
void ConfigParser::parse_server_config(const auto &toml_data,
|
||||||
ServerConfig &config) {
|
ServerConfig &config) {
|
||||||
parse_section(toml_data, "server", [&](const auto &srv) {
|
parse_section(toml_data, "server", [&](const auto &srv) {
|
||||||
parse_field(srv, "bind_address", config.bind_address);
|
// Parse interfaces array
|
||||||
parse_field(srv, "port", config.port);
|
if (srv.contains("interfaces")) {
|
||||||
parse_field(srv, "unix_socket_path", config.unix_socket_path);
|
auto interfaces = srv.at("interfaces");
|
||||||
|
if (interfaces.is_array()) {
|
||||||
|
for (const auto &iface : interfaces.as_array()) {
|
||||||
|
if (iface.contains("type")) {
|
||||||
|
std::string type = iface.at("type").as_string();
|
||||||
|
if (type == "tcp") {
|
||||||
|
std::string address = iface.at("address").as_string();
|
||||||
|
int port = iface.at("port").as_integer();
|
||||||
|
config.interfaces.push_back(ListenInterface::tcp(address, port));
|
||||||
|
} else if (type == "unix") {
|
||||||
|
std::string path = iface.at("path").as_string();
|
||||||
|
config.interfaces.push_back(ListenInterface::unix_socket(path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no interfaces configured, use default TCP interface
|
||||||
|
if (config.interfaces.empty()) {
|
||||||
|
config.interfaces.push_back(ListenInterface::tcp("127.0.0.1", 8080));
|
||||||
|
}
|
||||||
|
|
||||||
parse_field(srv, "max_request_size_bytes", config.max_request_size_bytes);
|
parse_field(srv, "max_request_size_bytes", config.max_request_size_bytes);
|
||||||
parse_field(srv, "io_threads", config.io_threads);
|
parse_field(srv, "io_threads", config.io_threads);
|
||||||
|
|
||||||
@@ -127,25 +149,38 @@ void ConfigParser::parse_subscription_config(const auto &toml_data,
|
|||||||
bool ConfigParser::validate_config(const Config &config) {
|
bool ConfigParser::validate_config(const Config &config) {
|
||||||
bool valid = true;
|
bool valid = true;
|
||||||
|
|
||||||
// Validate server configuration
|
// Validate server interfaces
|
||||||
if (config.server.unix_socket_path.empty()) {
|
if (config.server.interfaces.empty()) {
|
||||||
// TCP mode validation
|
std::cerr << "Configuration error: no interfaces configured" << std::endl;
|
||||||
if (config.server.port <= 0 || config.server.port > 65535) {
|
|
||||||
std::cerr << "Configuration error: server.port must be between 1 and "
|
|
||||||
"65535, got "
|
|
||||||
<< config.server.port << std::endl;
|
|
||||||
valid = false;
|
valid = false;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// Unix socket mode validation
|
for (const auto &iface : config.server.interfaces) {
|
||||||
if (config.server.unix_socket_path.length() >
|
if (iface.type == ListenInterface::Type::TCP) {
|
||||||
107) { // UNIX_PATH_MAX is typically 108
|
if (iface.port <= 0 || iface.port > 65535) {
|
||||||
std::cerr << "Configuration error: unix_socket_path too long (max 107 "
|
std::cerr << "Configuration error: TCP port must be between 1 and "
|
||||||
"chars), got "
|
"65535, got "
|
||||||
<< config.server.unix_socket_path.length() << " chars"
|
<< iface.port << std::endl;
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
|
if (iface.address.empty()) {
|
||||||
|
std::cerr << "Configuration error: TCP address cannot be empty"
|
||||||
<< std::endl;
|
<< std::endl;
|
||||||
valid = false;
|
valid = false;
|
||||||
}
|
}
|
||||||
|
} else { // Unix socket
|
||||||
|
if (iface.path.empty()) {
|
||||||
|
std::cerr << "Configuration error: Unix socket path cannot be empty"
|
||||||
|
<< std::endl;
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
|
if (iface.path.length() > 107) { // UNIX_PATH_MAX is typically 108
|
||||||
|
std::cerr << "Configuration error: Unix socket path too long (max 107 "
|
||||||
|
"chars), got "
|
||||||
|
<< iface.path.length() << " chars" << std::endl;
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.server.max_request_size_bytes == 0) {
|
if (config.server.max_request_size_bytes == 0) {
|
||||||
|
|||||||
@@ -3,19 +3,40 @@
|
|||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace weaseldb {
|
namespace weaseldb {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Configuration for a single network interface to listen on.
|
||||||
|
*/
|
||||||
|
struct ListenInterface {
|
||||||
|
enum class Type { TCP, Unix };
|
||||||
|
|
||||||
|
Type type;
|
||||||
|
/// For TCP: IP address to bind to (e.g., "127.0.0.1", "0.0.0.0")
|
||||||
|
std::string address;
|
||||||
|
/// For TCP: port number
|
||||||
|
int port = 0;
|
||||||
|
/// For Unix: socket file path
|
||||||
|
std::string path;
|
||||||
|
|
||||||
|
// Factory methods for cleaner config creation
|
||||||
|
static ListenInterface tcp(const std::string &addr, int port_num) {
|
||||||
|
return {Type::TCP, addr, port_num, ""};
|
||||||
|
}
|
||||||
|
|
||||||
|
static ListenInterface unix_socket(const std::string &socket_path) {
|
||||||
|
return {Type::Unix, "", 0, socket_path};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Configuration settings for the WeaselDB server component.
|
* @brief Configuration settings for the WeaselDB server component.
|
||||||
*/
|
*/
|
||||||
struct ServerConfig {
|
struct ServerConfig {
|
||||||
/// IP address to bind the server to (default: localhost)
|
/// Network interfaces to listen on (TCP and/or Unix sockets)
|
||||||
std::string bind_address = "127.0.0.1";
|
std::vector<ListenInterface> interfaces;
|
||||||
/// TCP port number for the server to listen on
|
|
||||||
int port = 8080;
|
|
||||||
/// Unix socket path (if specified, takes precedence over TCP)
|
|
||||||
std::string unix_socket_path;
|
|
||||||
/// Maximum size in bytes for incoming HTTP requests (default: 1MB)
|
/// Maximum size in bytes for incoming HTTP requests (default: 1MB)
|
||||||
int64_t max_request_size_bytes = 1024 * 1024;
|
int64_t max_request_size_bytes = 1024 * 1024;
|
||||||
/// Number of I/O threads for handling connections and network events
|
/// Number of I/O threads for handling connections and network events
|
||||||
|
|||||||
92
src/main.cpp
92
src/main.cpp
@@ -29,12 +29,7 @@ void signal_handler(int sig) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<int> create_listen_sockets(const weaseldb::Config &config) {
|
int create_unix_socket(const std::string &path) {
|
||||||
std::vector<int> listen_fds;
|
|
||||||
|
|
||||||
// Check if unix socket path is specified
|
|
||||||
if (!config.server.unix_socket_path.empty()) {
|
|
||||||
// Create unix socket
|
|
||||||
int sfd = socket(AF_UNIX, SOCK_STREAM, 0);
|
int sfd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||||
if (sfd == -1) {
|
if (sfd == -1) {
|
||||||
perror("socket");
|
perror("socket");
|
||||||
@@ -42,19 +37,18 @@ std::vector<int> create_listen_sockets(const weaseldb::Config &config) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Remove existing socket file if it exists
|
// Remove existing socket file if it exists
|
||||||
unlink(config.server.unix_socket_path.c_str());
|
unlink(path.c_str());
|
||||||
|
|
||||||
struct sockaddr_un addr;
|
struct sockaddr_un addr;
|
||||||
std::memset(&addr, 0, sizeof(addr));
|
std::memset(&addr, 0, sizeof(addr));
|
||||||
addr.sun_family = AF_UNIX;
|
addr.sun_family = AF_UNIX;
|
||||||
|
|
||||||
if (config.server.unix_socket_path.length() >= sizeof(addr.sun_path)) {
|
if (path.length() >= sizeof(addr.sun_path)) {
|
||||||
std::fprintf(stderr, "Unix socket path too long\n");
|
std::fprintf(stderr, "Unix socket path too long: %s\n", path.c_str());
|
||||||
std::abort();
|
std::abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::strncpy(addr.sun_path, config.server.unix_socket_path.c_str(),
|
std::strncpy(addr.sun_path, path.c_str(), sizeof(addr.sun_path) - 1);
|
||||||
sizeof(addr.sun_path) - 1);
|
|
||||||
|
|
||||||
if (bind(sfd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
|
if (bind(sfd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
|
||||||
perror("bind");
|
perror("bind");
|
||||||
@@ -66,11 +60,10 @@ std::vector<int> create_listen_sockets(const weaseldb::Config &config) {
|
|||||||
std::abort();
|
std::abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
listen_fds.push_back(sfd);
|
return sfd;
|
||||||
return listen_fds;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TCP socket creation
|
int create_tcp_socket(const std::string &address, int port) {
|
||||||
struct addrinfo hints;
|
struct addrinfo hints;
|
||||||
struct addrinfo *result, *rp;
|
struct addrinfo *result, *rp;
|
||||||
int s;
|
int s;
|
||||||
@@ -84,8 +77,8 @@ std::vector<int> create_listen_sockets(const weaseldb::Config &config) {
|
|||||||
hints.ai_addr = nullptr;
|
hints.ai_addr = nullptr;
|
||||||
hints.ai_next = nullptr;
|
hints.ai_next = nullptr;
|
||||||
|
|
||||||
s = getaddrinfo(config.server.bind_address.c_str(),
|
s = getaddrinfo(address.c_str(), std::to_string(port).c_str(), &hints,
|
||||||
std::to_string(config.server.port).c_str(), &hints, &result);
|
&result);
|
||||||
if (s != 0) {
|
if (s != 0) {
|
||||||
std::fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
|
std::fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
|
||||||
std::abort();
|
std::abort();
|
||||||
@@ -94,18 +87,13 @@ std::vector<int> create_listen_sockets(const weaseldb::Config &config) {
|
|||||||
int sfd = -1;
|
int sfd = -1;
|
||||||
for (rp = result; rp != nullptr; rp = rp->ai_next) {
|
for (rp = result; rp != nullptr; rp = rp->ai_next) {
|
||||||
sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
|
sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
|
||||||
if (sfd == -1) {
|
if (sfd == -1)
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
|
|
||||||
int val = 1;
|
int val = 1;
|
||||||
if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) == -1) {
|
if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) == -1) {
|
||||||
perror("setsockopt SO_REUSEADDR");
|
perror("setsockopt SO_REUSEADDR");
|
||||||
int e = close(sfd);
|
close(sfd);
|
||||||
if (e == -1 && errno != EINTR) {
|
|
||||||
perror("close sfd (SO_REUSEADDR failed)");
|
|
||||||
std::abort();
|
|
||||||
}
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,40 +101,56 @@ std::vector<int> create_listen_sockets(const weaseldb::Config &config) {
|
|||||||
if (rp->ai_family == AF_INET || rp->ai_family == AF_INET6) {
|
if (rp->ai_family == AF_INET || rp->ai_family == AF_INET6) {
|
||||||
if (setsockopt(sfd, IPPROTO_TCP, TCP_NODELAY, &val, sizeof(val)) == -1) {
|
if (setsockopt(sfd, IPPROTO_TCP, TCP_NODELAY, &val, sizeof(val)) == -1) {
|
||||||
perror("setsockopt TCP_NODELAY");
|
perror("setsockopt TCP_NODELAY");
|
||||||
int e = close(sfd);
|
close(sfd);
|
||||||
if (e == -1 && errno != EINTR) {
|
|
||||||
perror("close sfd (TCP_NODELAY failed)");
|
|
||||||
std::abort();
|
|
||||||
}
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bind(sfd, rp->ai_addr, rp->ai_addrlen) == 0) {
|
if (bind(sfd, rp->ai_addr, rp->ai_addrlen) == 0) {
|
||||||
|
if (listen(sfd, SOMAXCONN) == -1) {
|
||||||
|
perror("listen");
|
||||||
|
close(sfd);
|
||||||
|
freeaddrinfo(result);
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
break; /* Success */
|
break; /* Success */
|
||||||
}
|
}
|
||||||
|
|
||||||
int e = close(sfd);
|
close(sfd);
|
||||||
if (e == -1 && errno != EINTR) {
|
|
||||||
perror("close sfd (bind failed)");
|
|
||||||
std::abort();
|
|
||||||
}
|
|
||||||
sfd = -1;
|
sfd = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
freeaddrinfo(result);
|
freeaddrinfo(result);
|
||||||
|
|
||||||
if (rp == nullptr || sfd == -1) {
|
if (sfd == -1) {
|
||||||
std::fprintf(stderr, "Could not bind to any address\n");
|
std::fprintf(stderr, "Could not bind to %s:%d\n", address.c_str(), port);
|
||||||
std::abort();
|
std::abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (listen(sfd, SOMAXCONN) == -1) {
|
return sfd;
|
||||||
perror("listen");
|
}
|
||||||
|
|
||||||
|
std::vector<int> create_listen_sockets(const weaseldb::Config &config) {
|
||||||
|
std::vector<int> listen_fds;
|
||||||
|
|
||||||
|
for (const auto &iface : config.server.interfaces) {
|
||||||
|
int fd;
|
||||||
|
if (iface.type == weaseldb::ListenInterface::Type::TCP) {
|
||||||
|
fd = create_tcp_socket(iface.address, iface.port);
|
||||||
|
std::cout << "Listening on TCP " << iface.address << ":" << iface.port
|
||||||
|
<< std::endl;
|
||||||
|
} else {
|
||||||
|
fd = create_unix_socket(iface.path);
|
||||||
|
std::cout << "Listening on Unix socket " << iface.path << std::endl;
|
||||||
|
}
|
||||||
|
listen_fds.push_back(fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (listen_fds.empty()) {
|
||||||
|
std::fprintf(stderr, "No interfaces configured\n");
|
||||||
std::abort();
|
std::abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
listen_fds.push_back(sfd);
|
|
||||||
return listen_fds;
|
return listen_fds;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,13 +222,13 @@ int main(int argc, char *argv[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::cout << "Configuration loaded successfully:" << std::endl;
|
std::cout << "Configuration loaded successfully:" << std::endl;
|
||||||
if (!config->server.unix_socket_path.empty()) {
|
std::cout << "Interfaces: " << config->server.interfaces.size() << std::endl;
|
||||||
std::cout << "Unix socket path: " << config->server.unix_socket_path
|
for (const auto &iface : config->server.interfaces) {
|
||||||
<< std::endl;
|
if (iface.type == weaseldb::ListenInterface::Type::TCP) {
|
||||||
|
std::cout << " TCP: " << iface.address << ":" << iface.port << std::endl;
|
||||||
} else {
|
} else {
|
||||||
std::cout << "Server bind address: " << config->server.bind_address
|
std::cout << " Unix socket: " << iface.path << std::endl;
|
||||||
<< std::endl;
|
}
|
||||||
std::cout << "Server port: " << config->server.port << std::endl;
|
|
||||||
}
|
}
|
||||||
std::cout << "Max request size: " << config->server.max_request_size_bytes
|
std::cout << "Max request size: " << config->server.max_request_size_bytes
|
||||||
<< " bytes" << std::endl;
|
<< " bytes" << std::endl;
|
||||||
|
|||||||
@@ -98,9 +98,11 @@ Server::~Server() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean up unix socket file if it exists
|
// Clean up unix socket files if they exist
|
||||||
if (!config_.server.unix_socket_path.empty()) {
|
for (const auto &iface : config_.server.interfaces) {
|
||||||
unlink(config_.server.unix_socket_path.c_str());
|
if (iface.type == weaseldb::ListenInterface::Type::Unix) {
|
||||||
|
unlink(iface.path.c_str());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
# WeaselDB Configuration File
|
# WeaselDB Configuration File
|
||||||
|
|
||||||
[server]
|
[server]
|
||||||
unix_socket_path = "weaseldb.sock"
|
# Network interfaces to listen on - both TCP for external access and Unix socket for high-performance local testing
|
||||||
bind_address = "127.0.0.1"
|
interfaces = [
|
||||||
port = 8080
|
{ type = "tcp", address = "127.0.0.1", port = 8080 },
|
||||||
|
{ type = "unix", path = "weaseldb.sock" }
|
||||||
|
]
|
||||||
# Maximum request size in bytes (for 413 Content Too Large responses)
|
# Maximum request size in bytes (for 413 Content Too Large responses)
|
||||||
max_request_size_bytes = 1048576 # 1MB
|
max_request_size_bytes = 1048576 # 1MB
|
||||||
# Number of I/O threads for handling connections and network events
|
# Number of I/O threads for handling connections and network events
|
||||||
|
|||||||
Reference in New Issue
Block a user