Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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 |
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

todo: document in security considerations how to limit to 127.0.0.1,::1

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

change localhost to 127.0.0.1 in demos

| `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);
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

127.0.0.1,::1 as actual e2e unit test?

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