Skip to content
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
5c6b239
save
dkalinowski Oct 28, 2025
9dd21c7
disable test
dkalinowski Oct 28, 2025
6b25b8b
save
dkalinowski Oct 28, 2025
80ec6ff
Merge remote-tracking branch 'origin/main' into multi-listener-poc
dkalinowski Oct 28, 2025
3bd6c77
save
dkalinowski Oct 29, 2025
939d942
save
dkalinowski Oct 29, 2025
333a261
Merge remote-tracking branch 'origin/main' into multi-listener-poc
dkalinowski Oct 29, 2025
eaa0564
save
dkalinowski Oct 29, 2025
324577c
save2
dkalinowski Oct 29, 2025
618e894
Merge remote-tracking branch 'origin/main' into multi-listener-poc
dkalinowski Oct 29, 2025
ca4cbba
save
dkalinowski Oct 29, 2025
9042f75
save
dkalinowski Oct 29, 2025
85e9ec7
save
dkalinowski Oct 29, 2025
5426390
bump
dkalinowski Oct 30, 2025
da4166e
Merge remote-tracking branch 'origin/main' into multi-listener
dkalinowski Nov 5, 2025
bce6fb7
Merge remote-tracking branch 'origin/main' into multi-listener
dkalinowski Nov 5, 2025
fc0380b
revert default value
dkalinowski Nov 5, 2025
b10dc43
Merge remote-tracking branch 'origin/main' into multi-listener
dkalinowski Nov 5, 2025
1b889f6
Merge remote-tracking branch 'origin/main' into multi-listener
dkalinowski Nov 7, 2025
4ef60de
localhost -> 127.0.0.1 wherever requests is involved
dkalinowski Nov 7, 2025
408722d
unit test
dkalinowski Nov 7, 2025
29e2c71
Revert "unit test"
dkalinowski Nov 7, 2025
ba1d4cc
security considerations
dkalinowski Nov 7, 2025
d436295
perf tuning paragraph
dkalinowski Nov 7, 2025
b973b78
Merge remote-tracking branch 'origin/main' into multi-listener
dkalinowski Nov 12, 2025
c558b13
Darek comment
dkalinowski Nov 12, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/parameters.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ Configuration options for the server are defined only via command-line options a
|---|---|---|
| `port` | `integer` | Number of the port used by gRPC sever. |
| `rest_port` | `integer` | Number of the port used by HTTP server (if not provided or set to 0, HTTP server will not be launched). |
| `grpc_bind_address` | `string` | Network interface address or a hostname, to which gRPC server will bind to. Default: all interfaces: 0.0.0.0 |
| `rest_bind_address` | `string` | Network interface address or a hostname, to which REST server will bind to. Default: all interfaces: 0.0.0.0 |
| `grpc_bind_address` | `string` | Network interface address or a hostname, to which gRPC server will bind to. Default: all interfaces (both ipv4 and ipv6): 0.0.0.0 |
| `rest_bind_address` | `string` | Network interface address or a hostname, to which REST server will bind to. Default: all interfaces (both ipv4 and ipv6): 0.0.0.0 |
| `grpc_workers` | `integer` | Number of the gRPC server instances (must be from 1 to CPU core count). Default value is 1 and it's optimal for most use cases. Consider setting higher value while expecting heavy load. |
| `rest_workers` | `integer` | Number of HTTP server threads. Effective when `rest_port` > 0. Default value is set based on the number of CPUs. |
| `file_system_poll_wait_seconds` | `integer` | Time interval between config and model versions changes detection in seconds. Default value is 1. Zero value disables changes monitoring. |
Expand Down
3 changes: 2 additions & 1 deletion src/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -2278,7 +2278,8 @@ ovms_cc_library(
"libovmslogging",
"libovmsstatus",
"libhttp_status_code",
"libovms_config"
"libovms_config",
"libovmsstring_utils",
],
visibility = ["//visibility:public",],
)
Expand Down
32 changes: 29 additions & 3 deletions src/config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@
#include <thread>
#include <vector>

#ifdef _WIN32
#include <ws2tcpip.h>
#else
#include <netdb.h>
#endif

#include "logging.hpp"
#include "ovms_exit_codes.hpp"

Expand Down Expand Up @@ -59,7 +65,29 @@ bool Config::parse(ServerSettingsImpl* serverSettings, ModelsSettingsImpl* model
return validate();
}

bool Config::is_ipv6(const std::string& s) {
addrinfo hints{};
hints.ai_family = AF_INET6;
hints.ai_flags = AI_NUMERICHOST;
addrinfo* res = nullptr;
const int rc = getaddrinfo(s.c_str(), nullptr, &hints, &res);
if (res) {
freeaddrinfo(res);
}
return rc == 0;
}

bool Config::check_hostname_or_ip(const std::string& input) {
auto split = ovms::tokenize(input, ',');
if (split.size() > 1) {
for (const auto& part : split) {
if (!check_hostname_or_ip(part)) {
return false;
}
}
return true;
}

if (input.size() > 255) {
return false;
}
Expand All @@ -74,9 +102,7 @@ bool Config::check_hostname_or_ip(const std::string& input) {
}
if (all_numeric) {
static const std::regex valid_ipv4_regex("^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$");
static const std::regex valid_ipv6_regex(R"(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))");
return std::regex_match(input, valid_ipv4_regex) ||
std::regex_match(input, valid_ipv6_regex);
return std::regex_match(input, valid_ipv4_regex) || is_ipv6(input);
} else {
std::regex valid_hostname_regex("^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]*[a-zA-Z0-9])\\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\\-]*[A-Za-z0-9])$");
return std::regex_match(input, valid_hostname_regex);
Expand Down
1 change: 1 addition & 0 deletions src/config.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ class Config {
* @return bool
*/
static bool check_hostname_or_ip(const std::string& input);
static bool is_ipv6(const std::string& input);

/**
* @brief Get the config path
Expand Down
12 changes: 9 additions & 3 deletions src/drogon_http_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include "logging.hpp"
#include "mediapipe/framework/port/threadpool.h"
#include "timer.hpp"
#include "stringutils.hpp"

namespace ovms {

Expand Down Expand Up @@ -129,9 +130,14 @@ Status DrogonHttpServer::startAcceptingRequests() {
if (allowedHeaders.size()) {
resp->addHeader("Access-Control-Allow-Headers", allowedHeaders);
}
})
.addListener(this->address, this->port)
.run();
});

auto ips = ovms::tokenize(this->address, ',');
for (const auto& ip : ips) {
SPDLOG_INFO("Binding REST server to address: {}:{}", ip, this->port);
drogon::app().addListener(ip, this->port);
}
drogon::app().run();
} catch (...) {
SPDLOG_ERROR("Exception occurred during drogon::run()");
}
Expand Down
15 changes: 14 additions & 1 deletion src/grpcservermodule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,14 @@ GRPCServerModule::GRPCServerModule(Server& server) :
tfsModelService(this->server),
kfsGrpcInferenceService(this->server) {}

static std::string host_with_port(const std::string& host, int port) {
if (Config::is_ipv6(host)) {
return "[" + host + "]:" + std::to_string(port);
} else {
return host + ":" + std::to_string(port);
}
}

Status GRPCServerModule::start(const ovms::Config& config) {
state = ModuleState::STARTED_INITIALIZE;
SPDLOG_INFO("{} starting", GRPC_SERVER_MODULE_NAME);
Expand All @@ -123,7 +131,12 @@ Status GRPCServerModule::start(const ovms::Config& config) {
ServerBuilder builder;
builder.SetMaxReceiveMessageSize(GIGABYTE);
builder.SetMaxSendMessageSize(GIGABYTE);
builder.AddListeningPort(config.grpcBindAddress() + ":" + std::to_string(config.port()), grpc::InsecureServerCredentials());
auto ips = ovms::tokenize(config.grpcBindAddress(), ',');
for (const auto& ip : ips) {
auto hostWithPort = host_with_port(ip, config.port());
SPDLOG_INFO("Binding gRPC server to address: {}", hostWithPort);
builder.AddListeningPort(hostWithPort, grpc::InsecureServerCredentials());
}
builder.RegisterService(&tfsPredictService);
builder.RegisterService(&tfsModelService);
builder.RegisterService(&kfsGrpcInferenceService);
Expand Down
25 changes: 25 additions & 0 deletions src/test/ovmsconfig_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1983,6 +1983,7 @@ TEST_F(OvmsParamsTest, hostname_ip_regex) {
EXPECT_EQ(ovms::Config::check_hostname_or_ip(
"2001:db8:85a3::8a2e:370:7334"),
true);
EXPECT_EQ(ovms::Config::check_hostname_or_ip("0:0:0:0:0:0:0:0"), true);
EXPECT_EQ(ovms::Config::check_hostname_or_ip("::1"), true);
EXPECT_EQ(ovms::Config::check_hostname_or_ip("::"), true);
// Link-local IPv6 with zone index (RFC 4007 § 11) - unsupported
Expand All @@ -1997,6 +1998,30 @@ TEST_F(OvmsParamsTest, hostname_ip_regex) {
EXPECT_EQ(ovms::Config::check_hostname_or_ip("::ffff:192.0.2.128"), true);
// IPv4-translated IPv6 addresses
EXPECT_EQ(ovms::Config::check_hostname_or_ip("::ffff:0:192.0.2.128"), true);

// Multiple selections
EXPECT_EQ(ovms::Config::check_hostname_or_ip("0.0.0.0"), true);
EXPECT_EQ(ovms::Config::check_hostname_or_ip("0.0.0.0,0:0:0:0:0:0:0:0"), true);
EXPECT_EQ(ovms::Config::check_hostname_or_ip("127.0.0.1,::1"), true);
EXPECT_EQ(ovms::Config::check_hostname_or_ip("127.0.0.1,0:0:0:0:0:0:0:1"), true);
EXPECT_EQ(ovms::Config::check_hostname_or_ip("192.0.2.33,fe80::1234"), true);
EXPECT_EQ(ovms::Config::check_hostname_or_ip("192.0.2.33,fe80::1234,192.0.2.34,192.0.2.35,fe80::1235,fe80::1236"), true);
}

TEST_F(OvmsParamsTest, check_is_ipv6_address) {
EXPECT_EQ(ovms::Config::is_ipv6("fe80:0000:0000:0000:0202:b3ff:fe1e:8329"), true);
EXPECT_EQ(ovms::Config::is_ipv6("2001:db8:85a3::8a2e:370:7334"), true);
EXPECT_EQ(ovms::Config::is_ipv6("0:0:0:0:0:0:0:0"), true);
EXPECT_EQ(ovms::Config::is_ipv6("::1"), true);
EXPECT_EQ(ovms::Config::is_ipv6("::"), true);
EXPECT_EQ(ovms::Config::is_ipv6("64:ff9b::192.0.2.33"), true);
EXPECT_EQ(ovms::Config::is_ipv6("2001:db8:122:344::192.0.2.33"), true);
EXPECT_EQ(ovms::Config::is_ipv6("::ffff:192.0.2.128"), true);
EXPECT_EQ(ovms::Config::is_ipv6("::ffff:0:192.0.2.128"), true);

EXPECT_EQ(ovms::Config::is_ipv6("127.0.0.1"), false);
EXPECT_EQ(ovms::Config::is_ipv6("192.0.2.33"), false);
EXPECT_EQ(ovms::Config::is_ipv6("10.0.0.255"), false);
}

TEST(OvmsConfigTest, positiveMulti) {
Expand Down