mirror of
https://github.com/basiliscos/syncspirit.git
synced 2026-05-02 17:42:26 +00:00
initial commit
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
---
|
||||
Language: Cpp
|
||||
# BasedOnStyle: LLVM
|
||||
AccessModifierOffset: -2
|
||||
AlignAfterOpenBracket: true
|
||||
AlignConsecutiveAssignments: false
|
||||
AlignEscapedNewlinesLeft: false
|
||||
AlignOperands: true
|
||||
AlignTrailingComments: true
|
||||
AllowAllParametersOfDeclarationOnNextLine: true
|
||||
AllowShortBlocksOnASingleLine: false
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: All
|
||||
AllowShortIfStatementsOnASingleLine: false
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AlwaysBreakAfterDefinitionReturnType: None
|
||||
AlwaysBreakBeforeMultilineStrings: false
|
||||
AlwaysBreakTemplateDeclarations: false
|
||||
BinPackArguments: true
|
||||
BinPackParameters: true
|
||||
BreakBeforeBinaryOperators: None
|
||||
BreakBeforeBraces: Attach
|
||||
BreakBeforeTernaryOperators: true
|
||||
BreakConstructorInitializersBeforeComma: false
|
||||
ColumnLimit: 120
|
||||
CommentPragmas: '^ IWYU pragma:'
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: false
|
||||
ConstructorInitializerIndentWidth: 4
|
||||
ContinuationIndentWidth: 4
|
||||
Cpp11BracedListStyle: true
|
||||
DerivePointerAlignment: false
|
||||
DisableFormat: false
|
||||
ExperimentalAutoDetectBinPacking: false
|
||||
ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ]
|
||||
IndentCaseLabels: false
|
||||
IndentWidth: 4
|
||||
IndentWrappedFunctionNames: false
|
||||
KeepEmptyLinesAtTheStartOfBlocks: true
|
||||
MacroBlockBegin: ''
|
||||
MacroBlockEnd: ''
|
||||
MaxEmptyLinesToKeep: 1
|
||||
NamespaceIndentation: None
|
||||
ObjCBlockIndentWidth: 4
|
||||
ObjCSpaceAfterProperty: false
|
||||
ObjCSpaceBeforeProtocolList: true
|
||||
PenaltyBreakBeforeFirstCallParameter: 19
|
||||
PenaltyBreakComment: 300
|
||||
PenaltyBreakFirstLessLess: 120
|
||||
PenaltyBreakString: 1000
|
||||
PenaltyExcessCharacter: 1000000
|
||||
PenaltyReturnTypeOnItsOwnLine: 60
|
||||
PointerAlignment: Right
|
||||
SortIncludes: false
|
||||
SpaceAfterCStyleCast: false
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeParens: ControlStatements
|
||||
SpaceInEmptyParentheses: false
|
||||
SpacesBeforeTrailingComments: 1
|
||||
SpacesInAngles: false
|
||||
SpacesInContainerLiterals: true
|
||||
SpacesInCStyleCastParentheses: false
|
||||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
Standard: Cpp11
|
||||
TabWidth: 8
|
||||
UseTab: Never
|
||||
...
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
[submodule "lib/rotor"]
|
||||
path = lib/rotor
|
||||
url = git@github.com:basiliscos/cpp-rotor.git
|
||||
[submodule "lib/spdlog"]
|
||||
path = lib/spdlog
|
||||
url = git@github.com:gabime/spdlog.git
|
||||
[submodule "lib/fmt"]
|
||||
path = lib/fmt
|
||||
url = https://github.com/fmtlib/fmt.git
|
||||
[submodule "lib/json"]
|
||||
path = lib/json
|
||||
url = https://github.com/nlohmann/json.git
|
||||
@@ -0,0 +1,44 @@
|
||||
cmake_minimum_required (VERSION 3.2)
|
||||
project (syncspirit)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
find_package(
|
||||
Boost
|
||||
COMPONENTS
|
||||
filesystem
|
||||
program_options
|
||||
system
|
||||
REQUIRED
|
||||
)
|
||||
find_package(OpenSSL REQUIRED)
|
||||
|
||||
add_executable(syncspirit
|
||||
src/configuration.cpp
|
||||
src/main.cpp
|
||||
src/net/global_discovery_actor.cpp
|
||||
src/net/net_supervisor.cpp
|
||||
src/net/upnp_actor.cpp
|
||||
src/utils/error_code.cpp
|
||||
src/utils/uri.cpp
|
||||
src/utils/upnp_support.cpp
|
||||
)
|
||||
|
||||
add_subdirectory("lib/rotor")
|
||||
add_subdirectory("lib/fmt")
|
||||
|
||||
target_include_directories(syncspirit PUBLIC
|
||||
${syncspirit_SOURCE_DIR}/lib/rotor/include
|
||||
${syncspirit_SOURCE_DIR}/lib/spdlog/include
|
||||
${syncspirit_SOURCE_DIR}/lib/fmt/include
|
||||
${syncspirit_SOURCE_DIR}/lib/json/include
|
||||
${syncspirit_SOURCE_DIR}/lib/outcome/include
|
||||
)
|
||||
|
||||
target_link_libraries(syncspirit
|
||||
rotor_asio
|
||||
fmt::fmt
|
||||
${Boost_LIBRARIES}
|
||||
OpenSSL::SSL
|
||||
)
|
||||
Submodule
+1
Submodule lib/fmt added at 9e554999ce
Submodule
+1
Submodule lib/json added at a015b78e81
File diff suppressed because it is too large
Load Diff
Submodule
+1
Submodule lib/rotor added at 1b1723fdcc
Submodule
+1
Submodule lib/spdlog added at a7148b718e
@@ -0,0 +1,117 @@
|
||||
#include "configuration.h"
|
||||
#include <boost/program_options.hpp>
|
||||
#include <boost/tokenizer.hpp>
|
||||
|
||||
namespace po = boost::program_options;
|
||||
|
||||
namespace syncspirit::config {
|
||||
|
||||
spdlog::level::level_enum get_log_level(const std::string &log_level) {
|
||||
using namespace spdlog::level;
|
||||
level_enum value = info;
|
||||
if (log_level == "trace")
|
||||
value = trace;
|
||||
if (log_level == "debug")
|
||||
value = debug;
|
||||
if (log_level == "info")
|
||||
value = info;
|
||||
if (log_level == "warn")
|
||||
value = warn;
|
||||
if (log_level == "error")
|
||||
value = err;
|
||||
if (log_level == "crit")
|
||||
value = critical;
|
||||
if (log_level == "off")
|
||||
value = off;
|
||||
return value;
|
||||
}
|
||||
|
||||
static void fill_logging_config(po::variables_map &vm, configuration_t &cfg) {
|
||||
// sinks
|
||||
boost::optional<console_sink_t> console_sink;
|
||||
if (vm.count("sink_console.level")) {
|
||||
auto level = get_log_level(vm["sink_console.level"].as<std::string>());
|
||||
console_sink = console_sink_t{level};
|
||||
}
|
||||
|
||||
boost::optional<file_sink_t> file_sink;
|
||||
if (vm.count("sink_file.file")) {
|
||||
auto file = vm["sink_file.file"].as<std::string>();
|
||||
auto level = get_log_level(vm["sink_file.level"].as<std::string>());
|
||||
file_sink = file_sink_t{level, file};
|
||||
}
|
||||
|
||||
if (vm.count("logging.sinks")) {
|
||||
using tokenizer = boost::tokenizer<boost::char_separator<char>>;
|
||||
auto sinks = vm["logging.sinks"].as<std::string>();
|
||||
boost::char_separator<char> separator(",");
|
||||
tokenizer tokens(sinks, separator);
|
||||
for (auto &sink_name : tokens) {
|
||||
if (sink_name == "console" && console_sink) {
|
||||
cfg.logging_config.sinks.push_back(*console_sink);
|
||||
}
|
||||
if (sink_name == "file" && file_sink) {
|
||||
cfg.logging_config.sinks.push_back(*file_sink);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boost::optional<configuration_t> get_config(std::ifstream &config) {
|
||||
using result_t = boost::optional<configuration_t>;
|
||||
result_t result{};
|
||||
configuration_t cfg;
|
||||
|
||||
// clang-format off
|
||||
po::options_description descr("Allowed options");
|
||||
descr.add_options()
|
||||
("sink_console.level", po::value<std::string>()->default_value("info"),"log level (default: info)")
|
||||
("sink_file.level", po::value<std::string>()->default_value("info"), "log level (default: info)")
|
||||
("sink_file.file", po::value<std::string>(), "log file")
|
||||
("logging.sinks", po::value<std::string>()->default_value("console"), "comma-separated list of sinks, mandatory")
|
||||
("local_announce.enabled", po::value<bool>()->default_value(true), "enable LAN-announcements")
|
||||
("local_announce.port", po::value<std::uint16_t>()->default_value(21027), "LAN-announcement port")
|
||||
("global_discovery.server", po::value<std::string>()->default_value("https://discovery.syncthing.net/v2/?noannounce&id=LYXKCHX-VI3NYZR-ALCJBHF-WMZYSPK-QG6QJA3-MPFYMSO-U56GTUK-NA2MIAW"), "Global announce server")
|
||||
("global_discovery.cert_file", po::value<std::string>()->default_value("~/.config/syncthing/cert.pem"), "certificate file path")
|
||||
("global_discovery.key_file", po::value<std::string>()->default_value("~/.config/syncthing/key.pem"), "key file path")
|
||||
("global_discovery.timeout", po::value<std::uint32_t>()->default_value(1), "discovery timeout (seconds)")
|
||||
("upnp.timeout", po::value<std::uint32_t>()->default_value(5), "max wait discovery timeout")
|
||||
;
|
||||
// clang-format on
|
||||
|
||||
po::variables_map vm;
|
||||
po::store(po::parse_config_file(config, descr, false), vm);
|
||||
po::notify(vm);
|
||||
|
||||
fill_logging_config(vm, cfg);
|
||||
|
||||
cfg.local_announce_config.enabled = vm["local_announce.enabled"].as<bool>();
|
||||
cfg.local_announce_config.port = vm["local_announce.port"].as<std::uint16_t>();
|
||||
|
||||
auto server_url_raw = vm["global_discovery.server"].as<std::string>();
|
||||
auto server_url = utils::parse(server_url_raw.c_str());
|
||||
if (!server_url) {
|
||||
std::cout << "wrong server_url: " << server_url_raw << "\n";
|
||||
std::cout << descr << "\n";
|
||||
return result;
|
||||
}
|
||||
cfg.global_announce_config.server_url = *server_url;
|
||||
cfg.global_announce_config.cert_file = vm["global_discovery.cert_file"].as<std::string>();
|
||||
cfg.global_announce_config.key_file = vm["global_discovery.key_file"].as<std::string>();
|
||||
cfg.global_announce_config.timeout = vm["global_discovery.timeout"].as<std::uint32_t>();
|
||||
|
||||
cfg.upnp_config.timeout = vm["upnp.timeout"].as<std::uint32_t>();
|
||||
|
||||
// checks
|
||||
if (cfg.logging_config.sinks.empty()) {
|
||||
std::cout << descr << "\n";
|
||||
return result;
|
||||
}
|
||||
|
||||
// all OK
|
||||
result = std::move(cfg);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace syncspirit::config
|
||||
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
|
||||
#include "utils/uri.h"
|
||||
#include <boost/optional.hpp>
|
||||
#include <boost/variant.hpp>
|
||||
#include <cstdint>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
namespace syncspirit::config {
|
||||
|
||||
struct console_sink_t {
|
||||
spdlog::level::level_enum level;
|
||||
};
|
||||
|
||||
struct file_sink_t {
|
||||
spdlog::level::level_enum level;
|
||||
std::string file;
|
||||
};
|
||||
|
||||
using sink_config_t = boost::variant<console_sink_t, file_sink_t>;
|
||||
|
||||
struct logging_config_t {
|
||||
std::vector<sink_config_t> sinks;
|
||||
};
|
||||
|
||||
struct local_announce_config_t {
|
||||
std::uint16_t port;
|
||||
bool enabled;
|
||||
};
|
||||
|
||||
struct global_announce_config_t {
|
||||
syncspirit::utils::URI server_url;
|
||||
std::string cert_file;
|
||||
std::string key_file;
|
||||
std::uint32_t timeout;
|
||||
};
|
||||
|
||||
struct upnp_config_t {
|
||||
std::uint32_t timeout;
|
||||
};
|
||||
|
||||
struct configuration_t {
|
||||
logging_config_t logging_config;
|
||||
local_announce_config_t local_announce_config;
|
||||
upnp_config_t upnp_config;
|
||||
global_announce_config_t global_announce_config;
|
||||
};
|
||||
|
||||
spdlog::level::level_enum get_log_level(const std::string &log_level);
|
||||
|
||||
boost::optional<configuration_t> get_config(std::ifstream &config);
|
||||
|
||||
} // namespace syncspirit::config
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
#include <chrono>
|
||||
#include <csignal>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include "configuration.h"
|
||||
#include "net/net_supervisor.h"
|
||||
#include <boost/program_options.hpp>
|
||||
#include <rotor/asio.hpp>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
namespace po = boost::program_options;
|
||||
namespace pt = boost::posix_time;
|
||||
namespace ra = rotor::asio;
|
||||
namespace asio = boost::asio;
|
||||
|
||||
using namespace syncspirit;
|
||||
|
||||
static std::atomic<bool> signal_shutdown_flag{false};
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
using guard_t = asio::executor_work_guard<asio::io_context::executor_type>;
|
||||
try {
|
||||
// clang-format off
|
||||
/* parse command-line & config options */
|
||||
po::options_description cmdline_descr("Allowed options");
|
||||
cmdline_descr.add_options()
|
||||
("help", "show this help message")
|
||||
("log_level", po::value<std::string>()->default_value("info"), "initial log level")
|
||||
( "config_file", po::value<std::string>()->default_value("syncspirit.conf"), "configuration file path");
|
||||
// clang-format on
|
||||
|
||||
po::variables_map vm;
|
||||
po::store(po::parse_command_line(argc, argv, cmdline_descr), vm);
|
||||
po::notify(vm);
|
||||
|
||||
bool show_help = vm.count("help");
|
||||
if (show_help) {
|
||||
std::cout << cmdline_descr << "\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
auto log_level_str = vm["log_level"].as<std::string>();
|
||||
auto log_level = config::get_log_level(log_level_str);
|
||||
spdlog::set_level(log_level);
|
||||
|
||||
std::string config_file_path = vm["config_file"].as<std::string>();
|
||||
std::ifstream config_file(config_file_path.c_str());
|
||||
if (!config_file) {
|
||||
spdlog::error("Cannot open config file {}", config_file_path);
|
||||
return 1;
|
||||
}
|
||||
|
||||
auto cfg_option = config::get_config(config_file);
|
||||
if (!cfg_option) {
|
||||
spdlog::error("Config file {} is incorrect", config_file_path);
|
||||
return 1;
|
||||
}
|
||||
spdlog::trace("configuration seems OK");
|
||||
spdlog::info("starting");
|
||||
|
||||
/* pre-init actors */
|
||||
asio::io_context io_context;
|
||||
ra::system_context_ptr_t sys_context{new ra::system_context_asio_t{io_context}};
|
||||
ra::supervisor_config_t sup_conf{pt::milliseconds{500}};
|
||||
auto sup_net = sys_context->create_supervisor<net::net_supervisor_t>(sup_conf, *cfg_option);
|
||||
sup_net->start();
|
||||
|
||||
/* launch actors */
|
||||
guard_t net_guard{asio::make_work_guard(io_context)};
|
||||
auto net_thread = std::thread([&io_context]() {
|
||||
io_context.run();
|
||||
spdlog::trace("net thread has been terminated");
|
||||
signal_shutdown_flag = true;
|
||||
});
|
||||
|
||||
struct sigaction act;
|
||||
act.sa_handler = [](int) { signal_shutdown_flag = true; };
|
||||
if (sigaction(SIGINT, &act, nullptr) != 0) {
|
||||
spdlog::critical("cannot set signal handler");
|
||||
}
|
||||
auto console_thread = std::thread([] {
|
||||
while (!signal_shutdown_flag) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
}
|
||||
spdlog::trace("console thread has been terminated");
|
||||
});
|
||||
|
||||
spdlog::trace("waiting actors terminations");
|
||||
console_thread.join();
|
||||
net_guard.reset();
|
||||
|
||||
net_thread.join();
|
||||
|
||||
spdlog::trace("everything has been terminated");
|
||||
} catch (const std::exception &ex) {
|
||||
spdlog::critical("Starting failure : {}", ex.what());
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* exit */
|
||||
spdlog::info("normal exit");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
#include "global_discovery_actor.h"
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
using namespace syncspirit::net;
|
||||
namespace ssl = boost::asio::ssl;
|
||||
namespace pt = boost::posix_time;
|
||||
|
||||
global_discovery_actor_t::global_discovery_actor_t(ra::supervisor_asio_t &sup,
|
||||
const config::global_announce_config_t &cfg_)
|
||||
: r::actor_base_t{sup}, cfg{cfg_}, strand{static_cast<ra::supervisor_asio_t &>(supervisor).get_strand()},
|
||||
io_context{strand.get_io_context()}, ssl_context{ssl::context::tls}, resolver{io_context},
|
||||
stream{io_context, ssl_context}, timer{io_context}, activities_flag{0} {}
|
||||
|
||||
void global_discovery_actor_t::on_initialize(r::message_t<r::payload::initialize_actor_t> &msg) noexcept {
|
||||
spdlog::trace("global_discovery_actor_t::on_initialize");
|
||||
|
||||
ssl_context.set_options(ssl::context::default_workarounds | ssl::context::no_sslv2);
|
||||
ssl_context.use_certificate_chain_file(cfg.cert_file);
|
||||
ssl_context.use_private_key_file(cfg.key_file, ssl::context::pem);
|
||||
// Set SNI Hostname (many hosts need this to handshake successfully)
|
||||
if (!SSL_set_tlsext_host_name(stream.native_handle(), cfg.server_url.host.c_str())) {
|
||||
sys::error_code ec{static_cast<int>(::ERR_get_error()), asio::error::get_ssl_category()};
|
||||
spdlog::error("global_discovery_actor:: Set SNI Hostname : {}", ec.message());
|
||||
trigger_shutdown();
|
||||
return;
|
||||
}
|
||||
spdlog::trace("ssl socket has been initialized");
|
||||
|
||||
r::actor_base_t::on_initialize(msg);
|
||||
}
|
||||
|
||||
void global_discovery_actor_t::trigger_shutdown() noexcept {
|
||||
if (!(activities_flag & SHUTDOWN_ACTIVE)) {
|
||||
activities_flag |= SHUTDOWN_ACTIVE;
|
||||
do_shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
void global_discovery_actor_t::on_shutdown(r::message_t<r::payload::shutdown_request_t> &msg) noexcept {
|
||||
spdlog::trace("global_discovery_actor::on_shutdown");
|
||||
|
||||
if (activities_flag & TIMER_ACTIVE) {
|
||||
sys::error_code ec;
|
||||
timer.cancel(ec);
|
||||
if (ec) {
|
||||
spdlog::error("global_discovery_actor:: timer cancellaction : {}", ec.message());
|
||||
}
|
||||
activities_flag &= ~TIMER_ACTIVE;
|
||||
}
|
||||
|
||||
if (activities_flag & RESOLVER_ACTIVE) {
|
||||
resolver.cancel();
|
||||
activities_flag &= ~RESOLVER_ACTIVE;
|
||||
}
|
||||
|
||||
if (activities_flag & SOCKET_ACTIVE) {
|
||||
sys::error_code ec;
|
||||
stream.next_layer().cancel(ec);
|
||||
if (ec) {
|
||||
spdlog::error("global_discovery_actor:: socket cancellaction : {}", ec.message());
|
||||
}
|
||||
activities_flag &= ~SOCKET_ACTIVE;
|
||||
}
|
||||
|
||||
r::actor_base_t::on_shutdown(msg);
|
||||
}
|
||||
|
||||
void global_discovery_actor_t::on_timeout_trigger() noexcept {
|
||||
spdlog::error("global_discovery_actor:: timeout");
|
||||
activities_flag &= ~TIMER_ACTIVE;
|
||||
trigger_shutdown();
|
||||
}
|
||||
|
||||
void global_discovery_actor_t::on_timeout_error(const sys::error_code &ec) noexcept {
|
||||
activities_flag &= ~TIMER_ACTIVE;
|
||||
if (ec != asio::error::operation_aborted) {
|
||||
spdlog::warn("global_discovery_actor::on_timer_error :: {}", ec.message());
|
||||
trigger_shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
void global_discovery_actor_t::on_start(r::message_t<r::payload::start_actor_t> &msg) noexcept {
|
||||
spdlog::trace("global_discovery_actor_t::on_start");
|
||||
|
||||
/* start timer */
|
||||
timer.expires_from_now(pt::seconds{cfg.timeout});
|
||||
auto fwd_timeout = ra::forwarder_t(*this, &global_discovery_actor_t::on_timeout_trigger,
|
||||
&global_discovery_actor_t::on_timeout_error);
|
||||
timer.async_wait(std::move(fwd_timeout));
|
||||
activities_flag |= TIMER_ACTIVE;
|
||||
|
||||
/* start resolver */
|
||||
auto &url = cfg.server_url;
|
||||
spdlog::trace("global_discovery_actor resolving {}:{}", url.host, url.port);
|
||||
|
||||
auto fwd_resolver =
|
||||
ra::forwarder_t(*this, &global_discovery_actor_t::on_resolve, &global_discovery_actor_t::on_resolve_error);
|
||||
resolver.async_resolve(cfg.server_url.host, url.proto, std::move(fwd_resolver));
|
||||
activities_flag |= RESOLVER_ACTIVE;
|
||||
|
||||
r::actor_base_t::on_start(msg);
|
||||
}
|
||||
|
||||
void global_discovery_actor_t::on_resolve_error(const sys::error_code &ec) noexcept {
|
||||
activities_flag &= ~RESOLVER_ACTIVE;
|
||||
if (ec != asio::error::operation_aborted) {
|
||||
spdlog::warn("global_discovery_actor_t::on_resolve_error :: {}", ec.message());
|
||||
trigger_shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
void global_discovery_actor_t::on_resolve(resolve_results_t results) noexcept {
|
||||
spdlog::trace("global_discovery_actor_t::on_resolve");
|
||||
activities_flag &= ~RESOLVER_ACTIVE;
|
||||
activities_flag |= SOCKET_ACTIVE;
|
||||
auto fwd =
|
||||
ra::forwarder_t(*this, &global_discovery_actor_t::on_connect, &global_discovery_actor_t::on_connect_error);
|
||||
asio::async_connect(stream.next_layer(), results.begin(), results.end(), std::move(fwd));
|
||||
}
|
||||
|
||||
void global_discovery_actor_t::on_connect_error(const sys::error_code &ec) noexcept {
|
||||
activities_flag &= ~SOCKET_ACTIVE;
|
||||
if (ec != asio::error::operation_aborted) {
|
||||
spdlog::warn("gvcp_actor::on_connect_error :: {}", ec.message());
|
||||
trigger_shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
void global_discovery_actor_t::on_connect(resolve_it_t it) noexcept {
|
||||
std::stringstream buff;
|
||||
buff << it->endpoint();
|
||||
spdlog::trace("global_discovery_actor::on_connect success ({})", buff.str());
|
||||
auto fwd =
|
||||
ra::forwarder_t(*this, &global_discovery_actor_t::on_handshake, &global_discovery_actor_t::on_handshake_error);
|
||||
stream.async_handshake(ssl::stream_base::client, std::move(fwd));
|
||||
}
|
||||
|
||||
void global_discovery_actor_t::on_handshake_error(const sys::error_code &ec) noexcept {
|
||||
if (ec != asio::error::operation_aborted) {
|
||||
spdlog::warn("gvcp_actor::on_handshake_error :: {}", ec.message());
|
||||
trigger_shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
void global_discovery_actor_t::on_handshake() noexcept {
|
||||
spdlog::trace("global_discovery_actor::on_handshake success");
|
||||
trigger_shutdown();
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
#pragma once
|
||||
|
||||
#include "../configuration.h"
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/asio/ssl.hpp>
|
||||
#include <rotor/asio/supervisor_asio.h>
|
||||
|
||||
namespace syncspirit {
|
||||
namespace net {
|
||||
|
||||
namespace r = rotor;
|
||||
namespace ra = rotor::asio;
|
||||
namespace asio = boost::asio;
|
||||
namespace sys = boost::system;
|
||||
|
||||
struct global_discovery_actor_t : public r::actor_base_t {
|
||||
using resolve_results_t = asio::ip::tcp::resolver::results_type;
|
||||
using resolve_it_t = asio::ip::tcp::resolver::results_type::iterator;
|
||||
|
||||
global_discovery_actor_t(ra::supervisor_asio_t &sup, const config::global_announce_config_t &cfg);
|
||||
|
||||
virtual void on_initialize(r::message_t<r::payload::initialize_actor_t> &) noexcept override;
|
||||
virtual void on_start(r::message_t<r::payload::start_actor_t> &) noexcept override;
|
||||
virtual void on_shutdown(r::message_t<r::payload::shutdown_request_t> &) noexcept override;
|
||||
|
||||
void trigger_shutdown() noexcept;
|
||||
void on_timeout_trigger() noexcept;
|
||||
void on_timeout_error(const sys::error_code &ec) noexcept;
|
||||
void on_resolve_error(const sys::error_code &ec) noexcept;
|
||||
void on_resolve(resolve_results_t results) noexcept;
|
||||
void on_connect_error(const sys::error_code &ec) noexcept;
|
||||
void on_connect(resolve_it_t endpoint) noexcept;
|
||||
void on_handshake_error(const sys::error_code &ec) noexcept;
|
||||
void on_handshake() noexcept;
|
||||
|
||||
private:
|
||||
using socket_t = asio::ip::tcp::socket;
|
||||
using endpoint_t = asio::ip::tcp::endpoint;
|
||||
using stream_t = asio::ssl::stream<socket_t>;
|
||||
using timer_t = asio::deadline_timer;
|
||||
|
||||
const static constexpr std::uint32_t SHUTDOWN_ACTIVE = 0b0000'0001;
|
||||
const static constexpr std::uint32_t TIMER_ACTIVE = 0b0000'0010;
|
||||
const static constexpr std::uint32_t RESOLVER_ACTIVE = 0b0000'0100;
|
||||
const static constexpr std::uint32_t SOCKET_ACTIVE = 0b0000'1000;
|
||||
|
||||
config::global_announce_config_t cfg;
|
||||
asio::io_context::strand &strand;
|
||||
asio::io_context &io_context;
|
||||
asio::ssl::context ssl_context;
|
||||
asio::ip::tcp::resolver resolver;
|
||||
stream_t stream;
|
||||
timer_t timer;
|
||||
std::uint32_t activities_flag;
|
||||
};
|
||||
|
||||
} // namespace net
|
||||
} // namespace syncspirit
|
||||
@@ -0,0 +1,22 @@
|
||||
#include "net_supervisor.h"
|
||||
#include "global_discovery_actor.h"
|
||||
#include "upnp_actor.h"
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
using namespace syncspirit::net;
|
||||
|
||||
net_supervisor_t::net_supervisor_t(ra::supervisor_asio_t *sup, ra::system_context_ptr_t ctx,
|
||||
const ra::supervisor_config_t &sup_cfg, const config::configuration_t &cfg)
|
||||
: ra::supervisor_asio_t(sup, ctx, sup_cfg), cfg{cfg} {}
|
||||
|
||||
void net_supervisor_t::on_start(r::message_t<r::payload::start_actor_t> &msg) noexcept {
|
||||
spdlog::trace("net_supervisor_t::on_start");
|
||||
ra::supervisor_asio_t::on_start(msg);
|
||||
launch_upnp();
|
||||
}
|
||||
|
||||
void net_supervisor_t::launch_discovery() noexcept {
|
||||
create_actor<global_discovery_actor_t>(cfg.global_announce_config);
|
||||
}
|
||||
|
||||
void net_supervisor_t::launch_upnp() noexcept { create_actor<upnp_actor_t>(cfg.upnp_config); }
|
||||
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include "../configuration.h"
|
||||
#include <boost/asio.hpp>
|
||||
#include <rotor/asio/supervisor_asio.h>
|
||||
|
||||
namespace syncspirit {
|
||||
namespace net {
|
||||
|
||||
namespace r = rotor;
|
||||
namespace ra = rotor::asio;
|
||||
namespace asio = boost::asio;
|
||||
|
||||
struct net_supervisor_t : public ra::supervisor_asio_t {
|
||||
net_supervisor_t(ra::supervisor_asio_t *sup, ra::system_context_ptr_t ctx, const ra::supervisor_config_t &sup_cfg,
|
||||
const config::configuration_t &cfg);
|
||||
|
||||
virtual void on_start(r::message_t<r::payload::start_actor_t> &) noexcept override;
|
||||
|
||||
private:
|
||||
void launch_discovery() noexcept;
|
||||
void launch_upnp() noexcept;
|
||||
|
||||
config::configuration_t cfg;
|
||||
};
|
||||
|
||||
} // namespace net
|
||||
} // namespace syncspirit
|
||||
@@ -0,0 +1,202 @@
|
||||
#include "upnp_actor.h"
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
using namespace syncspirit::net;
|
||||
using namespace syncspirit::utils;
|
||||
|
||||
using udp = boost::asio::ip::udp;
|
||||
using tcp = boost::asio::ip::tcp;
|
||||
using v4 = asio::ip::address_v4;
|
||||
|
||||
constexpr std::size_t rx_buff_sz = 1500;
|
||||
|
||||
upnp_actor_t::upnp_actor_t(ra::supervisor_asio_t &sup, const config::upnp_config_t &cfg_)
|
||||
: r::actor_base_t{sup}, cfg{cfg_}, strand{static_cast<ra::supervisor_asio_t &>(supervisor).get_strand()},
|
||||
io_context{strand.get_io_context()}, resolver{io_context}, udp_socket{io_context, udp::endpoint(udp::v4(), 0)},
|
||||
tcp_socket{io_context}, timer{io_context}, activities_flag{0} {}
|
||||
|
||||
void upnp_actor_t::trigger_shutdown() noexcept {
|
||||
if (!(activities_flag & SHUTDOWN_ACTIVE)) {
|
||||
activities_flag |= SHUTDOWN_ACTIVE;
|
||||
do_shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
void upnp_actor_t::on_timeout_trigger() noexcept {
|
||||
spdlog::error("upnp_actor_t:: timeout");
|
||||
activities_flag &= ~TIMER_ACTIVE;
|
||||
trigger_shutdown();
|
||||
}
|
||||
|
||||
void upnp_actor_t::on_timeout_error(const sys::error_code &ec) noexcept {
|
||||
activities_flag &= ~TIMER_ACTIVE;
|
||||
if (ec != asio::error::operation_aborted) {
|
||||
spdlog::warn("upnp_actor_t::on_timer_error :: {}", ec.message());
|
||||
trigger_shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
void upnp_actor_t::on_shutdown(r::message_t<r::payload::shutdown_request_t> &msg) noexcept {
|
||||
spdlog::trace("upnp_actor_t::on_shutdown");
|
||||
|
||||
if (activities_flag & TIMER_ACTIVE) {
|
||||
sys::error_code ec;
|
||||
timer.cancel(ec);
|
||||
if (ec) {
|
||||
spdlog::error("upnp_actor_t:: timer cancellation : {}", ec.message());
|
||||
}
|
||||
activities_flag &= ~TIMER_ACTIVE;
|
||||
}
|
||||
|
||||
if (activities_flag & UDP_ACTIVE) {
|
||||
sys::error_code ec;
|
||||
udp_socket.cancel(ec);
|
||||
if (ec) {
|
||||
spdlog::error("upnp_actor_t:: udp socket cancellation : {}", ec.message());
|
||||
}
|
||||
activities_flag &= ~UDP_ACTIVE;
|
||||
}
|
||||
|
||||
if (activities_flag & RESOLVER_ACTIVE) {
|
||||
resolver.cancel();
|
||||
activities_flag &= ~RESOLVER_ACTIVE;
|
||||
}
|
||||
|
||||
if (activities_flag & TCP_ACTIVE) {
|
||||
sys::error_code ec;
|
||||
tcp_socket.cancel(ec);
|
||||
if (ec) {
|
||||
spdlog::error("upnp_actor_t:: tcp socket cancellation : {}", ec.message());
|
||||
}
|
||||
activities_flag &= ~TCP_ACTIVE;
|
||||
}
|
||||
|
||||
r::actor_base_t::on_shutdown(msg);
|
||||
}
|
||||
|
||||
void upnp_actor_t::on_start(r::message_t<r::payload::start_actor_t> &msg) noexcept {
|
||||
spdlog::trace("upnp_actor::on_start");
|
||||
|
||||
/* start timer */
|
||||
timer.expires_from_now(pt::seconds{cfg.timeout});
|
||||
auto fwd_timeout = ra::forwarder_t(*this, &upnp_actor_t::on_timeout_trigger, &upnp_actor_t::on_timeout_error);
|
||||
timer.async_wait(std::move(fwd_timeout));
|
||||
activities_flag |= TIMER_ACTIVE;
|
||||
|
||||
auto destination = udp::endpoint(v4::from_string(upnp_addr), upnp_port);
|
||||
auto request_result = make_discovery_request(tx_buff, cfg.timeout);
|
||||
if (!request_result) {
|
||||
spdlog::error("upnp_actor:: can't serialize upnp discovery request: {}", request_result.error().message());
|
||||
return trigger_shutdown();
|
||||
}
|
||||
|
||||
spdlog::trace("upnp_actor:: sending multicast request to {}:{} ({} bytes)", upnp_addr, upnp_port, tx_buff.size());
|
||||
|
||||
auto fwd_discovery = ra::forwarder_t(*this, &upnp_actor_t::on_discovery_sent, &upnp_actor_t::on_udp_error);
|
||||
auto buff = asio::buffer(tx_buff.data(), tx_buff.size());
|
||||
udp_socket.async_send_to(buff, destination, std::move(fwd_discovery));
|
||||
activities_flag |= UDP_ACTIVE;
|
||||
r::actor_base_t::on_start(msg);
|
||||
}
|
||||
|
||||
void upnp_actor_t::on_udp_error(const sys::error_code &ec) noexcept {
|
||||
activities_flag &= ~UDP_ACTIVE;
|
||||
if (ec != asio::error::operation_aborted) {
|
||||
spdlog::warn("upnp_actor_t::on_udp_error :: {}", ec.message());
|
||||
return trigger_shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
void upnp_actor_t::on_discovery_sent(std::size_t bytes) noexcept {
|
||||
spdlog::trace("upnp_actor_t::on_discovery_sent");
|
||||
activities_flag &= ~UDP_ACTIVE;
|
||||
if (bytes != tx_buff.size()) {
|
||||
spdlog::warn("upnp_actor_t::on_discovery_sent :: tx buff size mismatch {} vs {}", bytes, tx_buff.size());
|
||||
return trigger_shutdown();
|
||||
}
|
||||
|
||||
spdlog::trace("upnp_actor::will wait discovery reply");
|
||||
auto fwd = ra::forwarder_t(*this, &upnp_actor_t::on_discovery_received, &upnp_actor_t::on_udp_error);
|
||||
auto buff = rx_buff.prepare(rx_buff_sz);
|
||||
udp_socket.async_receive(buff, std::move(fwd));
|
||||
activities_flag |= UDP_ACTIVE;
|
||||
}
|
||||
|
||||
void upnp_actor_t::on_discovery_received(std::size_t bytes) noexcept {
|
||||
spdlog::trace("upnp_actor_t::on_discovery_received ({} bytes)", bytes);
|
||||
activities_flag &= ~UDP_ACTIVE;
|
||||
|
||||
const std::uint8_t *buff = reinterpret_cast<const std::uint8_t *>(rx_buff.data().data());
|
||||
auto discovery_result = parse(buff, bytes);
|
||||
if (!discovery_result) {
|
||||
spdlog::warn("upnp_actor:: can't get discovery result: {}", discovery_result.error().message());
|
||||
return trigger_shutdown();
|
||||
}
|
||||
rx_buff.consume(bytes);
|
||||
discovery_option = discovery_result.value();
|
||||
|
||||
auto &location = discovery_option->location;
|
||||
if (location.proto != "http") {
|
||||
spdlog::warn("upnp_actor:: unsupported protocol ({}) for location url {}", location.proto, location.full);
|
||||
return trigger_shutdown();
|
||||
}
|
||||
|
||||
spdlog::trace("upnp_actor going to discover IGN at {}", location.full);
|
||||
auto fwd = ra::forwarder_t(*this, &upnp_actor_t::on_resolve, &upnp_actor_t::on_resolve_error);
|
||||
resolver.async_resolve(location.host, location.service, std::move(fwd));
|
||||
activities_flag |= RESOLVER_ACTIVE;
|
||||
}
|
||||
|
||||
void upnp_actor_t::on_resolve_error(const sys::error_code &ec) noexcept {
|
||||
activities_flag &= ~RESOLVER_ACTIVE;
|
||||
if (ec != asio::error::operation_aborted) {
|
||||
spdlog::warn("upnp_actor_t::on_resolve_error :: {}", ec.message());
|
||||
return trigger_shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
void upnp_actor_t::on_resolve(resolve_results_t results) noexcept {
|
||||
spdlog::trace("upnp_actor_t::on_resolve");
|
||||
activities_flag &= ~RESOLVER_ACTIVE;
|
||||
|
||||
activities_flag |= TCP_ACTIVE;
|
||||
auto fwd = ra::forwarder_t(*this, &upnp_actor_t::on_connect, &upnp_actor_t::on_tcp_error);
|
||||
asio::async_connect(tcp_socket, results.begin(), results.end(), std::move(fwd));
|
||||
}
|
||||
|
||||
void upnp_actor_t::on_tcp_error(const sys::error_code &ec) noexcept {
|
||||
activities_flag &= ~TCP_ACTIVE;
|
||||
if (ec != asio::error::operation_aborted) {
|
||||
spdlog::warn("upnp_actor_t::on_tcp_error :: {}", ec.message());
|
||||
return trigger_shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
void upnp_actor_t::on_connect(resolve_it_t it) noexcept {
|
||||
std::stringstream str_buff;
|
||||
str_buff << it->endpoint();
|
||||
spdlog::trace("upnp_actor_t::on_connect ({})", str_buff.str());
|
||||
|
||||
auto request_result = make_description_request(tx_buff, *discovery_option);
|
||||
if (!request_result) {
|
||||
spdlog::warn("upnp_actor:: can't serialize description request: {}", request_result.error().message());
|
||||
return trigger_shutdown();
|
||||
}
|
||||
spdlog::trace("upnp_actor:: making description request ({} bytes) to {} ", tx_buff.size(),
|
||||
discovery_option->location.path);
|
||||
auto fwd = ra::forwarder_t(*this, &upnp_actor_t::on_description_requested, &upnp_actor_t::on_tcp_error);
|
||||
auto buff = asio::buffer(tx_buff.data(), tx_buff.size());
|
||||
asio::async_write(tcp_socket, buff, std::move(fwd));
|
||||
}
|
||||
|
||||
void upnp_actor_t::on_description_requested(std::size_t) noexcept {
|
||||
spdlog::trace("upnp_actor_t::on_description_requested");
|
||||
|
||||
auto fwd = ra::forwarder_t(*this, &upnp_actor_t::on_description_received, &upnp_actor_t::on_tcp_error);
|
||||
http::async_read(tcp_socket, rx_buff, responce, std::move(fwd));
|
||||
}
|
||||
|
||||
void upnp_actor_t::on_description_received(std::size_t bytes) noexcept {
|
||||
spdlog::trace("upnp_actor_t::on_description_received ({} bytes)", bytes);
|
||||
trigger_shutdown();
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
#pragma once
|
||||
|
||||
#include "../configuration.h"
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/asio/ssl.hpp>
|
||||
#include <boost/beast/core.hpp>
|
||||
#include <boost/beast/http.hpp>
|
||||
#include <boost/smart_ptr/local_shared_ptr.hpp>
|
||||
#include <boost/optional.hpp>
|
||||
#include <rotor/asio/supervisor_asio.h>
|
||||
#include "../utils/upnp_support.h"
|
||||
|
||||
namespace syncspirit {
|
||||
namespace net {
|
||||
|
||||
namespace r = rotor;
|
||||
namespace ra = rotor::asio;
|
||||
namespace asio = boost::asio;
|
||||
namespace sys = boost::system;
|
||||
namespace pt = boost::posix_time;
|
||||
namespace http = boost::beast::http;
|
||||
using udp = asio::ip::udp;
|
||||
using tcp = asio::ip::tcp;
|
||||
|
||||
struct upnp_actor_t : public r::actor_base_t {
|
||||
using udp_socket_t = asio::ip::udp::socket;
|
||||
using tcp_socket_t = asio::ip::tcp::socket;
|
||||
using timer_t = asio::deadline_timer;
|
||||
using discovery_option_t = boost::optional<utils::discovery_result>;
|
||||
using resolve_results_t = asio::ip::tcp::resolver::results_type;
|
||||
using resolve_it_t = asio::ip::tcp::resolver::results_type::iterator;
|
||||
using http_response_t = boost::beast::http::response<boost::beast::http::string_body>;
|
||||
|
||||
upnp_actor_t(ra::supervisor_asio_t &sup, const config::upnp_config_t &cfg);
|
||||
|
||||
virtual void on_start(r::message_t<r::payload::start_actor_t> &) noexcept override;
|
||||
virtual void on_shutdown(r::message_t<r::payload::shutdown_request_t> &) noexcept override;
|
||||
|
||||
void trigger_shutdown() noexcept;
|
||||
void on_timeout_trigger() noexcept;
|
||||
void on_timeout_error(const sys::error_code &ec) noexcept;
|
||||
void on_discovery_sent(std::size_t bytes) noexcept;
|
||||
void on_udp_error(const sys::error_code &ec) noexcept;
|
||||
void on_discovery_received(std::size_t bytes) noexcept;
|
||||
void on_resolve_error(const sys::error_code &ec) noexcept;
|
||||
void on_resolve(resolve_results_t results) noexcept;
|
||||
void on_tcp_error(const sys::error_code &ec) noexcept;
|
||||
void on_connect(resolve_it_t endpoint) noexcept;
|
||||
void on_description_requested(std::size_t bytes) noexcept;
|
||||
void on_description_received(std::size_t bytes) noexcept;
|
||||
|
||||
private:
|
||||
const static constexpr std::uint32_t SHUTDOWN_ACTIVE = 1 << 1;
|
||||
const static constexpr std::uint32_t TIMER_ACTIVE = 1 << 2;
|
||||
const static constexpr std::uint32_t UDP_ACTIVE = 1 << 3;
|
||||
const static constexpr std::uint32_t TCP_ACTIVE = 1 << 4;
|
||||
const static constexpr std::uint32_t RESOLVER_ACTIVE = 1 << 5;
|
||||
|
||||
config::upnp_config_t cfg;
|
||||
asio::io_context::strand &strand;
|
||||
asio::io_context &io_context;
|
||||
asio::ip::tcp::resolver resolver;
|
||||
udp_socket_t udp_socket;
|
||||
tcp_socket_t tcp_socket;
|
||||
timer_t timer;
|
||||
std::uint32_t activities_flag;
|
||||
|
||||
fmt::memory_buffer tx_buff;
|
||||
boost::beast::flat_buffer rx_buff;
|
||||
discovery_option_t discovery_option;
|
||||
http_response_t responce;
|
||||
};
|
||||
|
||||
} // namespace net
|
||||
} // namespace syncspirit
|
||||
@@ -0,0 +1,33 @@
|
||||
#include "error_code.h"
|
||||
|
||||
namespace syncspirit::utils::detail {
|
||||
|
||||
const char *error_code_category::name() const noexcept { return "syncspirit_error"; }
|
||||
|
||||
std::string error_code_category::message(int c) const {
|
||||
switch (static_cast<error_code>(c)) {
|
||||
case error_code::success:
|
||||
return "success";
|
||||
case error_code::no_location:
|
||||
return "no location";
|
||||
case error_code::incomplete_discovery_reply:
|
||||
return "incomplete discovery reply";
|
||||
case error_code::no_st:
|
||||
return "no st (search target)";
|
||||
case error_code::no_usn:
|
||||
return "no usn";
|
||||
case error_code::igd_mismatch:
|
||||
return "IGD (InternetGatewayDevice) mismatch";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace syncspirit::utils::detail
|
||||
|
||||
namespace syncspirit::utils {
|
||||
|
||||
const static detail::error_code_category category;
|
||||
const detail::error_code_category &error_code_category() { return category; }
|
||||
|
||||
} // namespace syncspirit::utils
|
||||
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <system_error>
|
||||
|
||||
namespace syncspirit::utils {
|
||||
|
||||
enum class error_code {
|
||||
success = 0,
|
||||
incomplete_discovery_reply,
|
||||
no_location,
|
||||
no_st,
|
||||
no_usn,
|
||||
igd_mismatch,
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
|
||||
class error_code_category : public std::error_category {
|
||||
virtual const char *name() const noexcept override;
|
||||
virtual std::string message(int c) const override;
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
const detail::error_code_category &error_code_category();
|
||||
|
||||
inline std::error_code make_error_code(error_code e) { return {static_cast<int>(e), error_code_category()}; }
|
||||
|
||||
} // namespace syncspirit::utils
|
||||
|
||||
namespace std {
|
||||
template <> struct is_error_code_enum<syncspirit::utils::error_code> : std::true_type {};
|
||||
} // namespace std
|
||||
@@ -0,0 +1,130 @@
|
||||
#include <boost/beast/http.hpp>
|
||||
#include <boost/beast/version.hpp>
|
||||
#include <string_view>
|
||||
|
||||
#include "error_code.h"
|
||||
#include "upnp_support.h"
|
||||
|
||||
namespace syncspirit::utils {
|
||||
|
||||
const char *upnp_fields::st = "ST";
|
||||
const char *upnp_fields::man = "MAN";
|
||||
const char *upnp_fields::mx = "MX";
|
||||
const char *upnp_fields::usn = "USN";
|
||||
|
||||
const char *upnp_addr = "239.255.255.250";
|
||||
|
||||
static const char *igd_v1_st_v = "urn:schemas-upnp-org:device:InternetGatewayDevice:1";
|
||||
static const char *igd_man_v = "\"ssdp:discover\"";
|
||||
|
||||
constexpr unsigned http_version = 11;
|
||||
|
||||
namespace http = boost::beast::http;
|
||||
namespace asio = boost::asio;
|
||||
namespace sys = boost::system;
|
||||
|
||||
outcome::result<void> make_discovery_request(fmt::memory_buffer &buff, std::uint32_t max_wait) noexcept {
|
||||
std::string upnp_host = fmt::format("{}:{}", upnp_addr, upnp_port);
|
||||
std::string upnp_max_wait = fmt::format("{}", max_wait);
|
||||
|
||||
auto req = http::request<http::empty_body>();
|
||||
req.version(http_version);
|
||||
req.method(http::verb::msearch);
|
||||
req.target("*");
|
||||
req.set(http::field::host, upnp_host.data());
|
||||
req.set(upnp_fields::st, igd_v1_st_v);
|
||||
req.set(upnp_fields::man, igd_man_v);
|
||||
req.set(upnp_fields::mx, upnp_max_wait.data());
|
||||
req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
|
||||
|
||||
auto serializer = http::serializer<true, http::empty_body>(req);
|
||||
serializer.split(false);
|
||||
|
||||
sys::error_code ec;
|
||||
serializer.next(ec, [&](auto ec, const auto &buff_seq) {
|
||||
if (!ec) {
|
||||
auto sz = buffer_size(buff_seq);
|
||||
buff.resize(sz);
|
||||
buffer_copy(asio::mutable_buffer(buff.data(), sz), buff_seq);
|
||||
}
|
||||
});
|
||||
if (ec) {
|
||||
return ec;
|
||||
};
|
||||
return outcome::success();
|
||||
}
|
||||
|
||||
outcome::result<discovery_result> parse(const std::uint8_t *data, std::size_t bytes) noexcept {
|
||||
http::parser<false, http::empty_body> parser;
|
||||
auto buff = asio::const_buffers_1(data, bytes);
|
||||
sys::error_code ec;
|
||||
parser.put(buff, ec);
|
||||
if (ec) {
|
||||
return ec;
|
||||
}
|
||||
|
||||
parser.put_eof(ec);
|
||||
if (ec) {
|
||||
return ec;
|
||||
}
|
||||
|
||||
if (!parser.is_done()) {
|
||||
return error_code::incomplete_discovery_reply;
|
||||
}
|
||||
|
||||
auto &message = parser.get();
|
||||
auto it_location = message.find(http::field::location);
|
||||
if (it_location == message.end()) {
|
||||
return error_code::no_location;
|
||||
}
|
||||
// std::string_view location_str = it_location->value();
|
||||
auto location_option = parse(it_location->value());
|
||||
|
||||
auto it_st = message.find(upnp_fields::st);
|
||||
if (it_st == message.end()) {
|
||||
return error_code::no_st;
|
||||
}
|
||||
|
||||
auto it_usn = message.find(upnp_fields::usn);
|
||||
if (it_usn == message.end()) {
|
||||
return error_code::no_usn;
|
||||
}
|
||||
|
||||
auto st = it_st->value();
|
||||
if (st != igd_v1_st_v) {
|
||||
return error_code::igd_mismatch;
|
||||
}
|
||||
|
||||
auto usn = it_usn->value();
|
||||
return discovery_result{
|
||||
*location_option,
|
||||
std::string(st.begin(), st.end()),
|
||||
std::string(usn.begin(), usn.end()),
|
||||
};
|
||||
}
|
||||
|
||||
outcome::result<void> make_description_request(fmt::memory_buffer &buff, const discovery_result &dr) noexcept {
|
||||
auto &location = dr.location;
|
||||
http::request<http::empty_body> req;
|
||||
req.method(http::verb::get);
|
||||
req.version(http_version);
|
||||
req.target(location.path);
|
||||
req.set(http::field::host, location.host);
|
||||
req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
|
||||
|
||||
sys::error_code ec;
|
||||
auto serializer = http::serializer<true, http::empty_body>(req);
|
||||
serializer.next(ec, [&](auto ec, const auto &buff_seq) {
|
||||
if (!ec) {
|
||||
auto sz = buffer_size(buff_seq);
|
||||
buff.resize(sz);
|
||||
buffer_copy(asio::mutable_buffer(buff.data(), sz), buff_seq);
|
||||
}
|
||||
});
|
||||
if (ec) {
|
||||
return ec;
|
||||
};
|
||||
return outcome::success();
|
||||
}
|
||||
|
||||
} // namespace syncspirit::utils
|
||||
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
#include "uri.h"
|
||||
#include <fmt/format.h>
|
||||
#include <outcome.hpp>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace syncspirit::utils {
|
||||
|
||||
namespace outcome = OUTCOME_V2_NAMESPACE;
|
||||
|
||||
struct upnp_fields {
|
||||
static const char *st;
|
||||
static const char *man;
|
||||
static const char *mx;
|
||||
static const char *usn;
|
||||
};
|
||||
|
||||
extern const char *upnp_addr;
|
||||
constexpr std::uint16_t upnp_port = 1900;
|
||||
|
||||
struct discovery_result {
|
||||
URI location;
|
||||
std::string search_target;
|
||||
std::string usn;
|
||||
};
|
||||
|
||||
outcome::result<discovery_result> parse(const std::uint8_t *data, std::size_t bytes) noexcept;
|
||||
|
||||
outcome::result<void> make_discovery_request(fmt::memory_buffer &buff, std::uint32_t max_wait) noexcept;
|
||||
|
||||
outcome::result<void> make_description_request(fmt::memory_buffer &buff, const discovery_result &dr) noexcept;
|
||||
|
||||
} // namespace syncspirit::utils
|
||||
@@ -0,0 +1,38 @@
|
||||
#include "uri.h"
|
||||
#include <charconv>
|
||||
#include <regex>
|
||||
|
||||
namespace syncspirit::utils {
|
||||
|
||||
boost::optional<URI> parse(const char *uri) { return parse(boost::string_view(uri, strlen(uri))); }
|
||||
|
||||
boost::optional<URI> parse(const boost::string_view &uri) {
|
||||
using result_t = boost::optional<URI>;
|
||||
std::regex re("(\\w+)://([^/ :]+):?([^/ ]*)(/?[^ #?]*)\\x3f?([^ #]*)#?([^ ]*)");
|
||||
std::cmatch what;
|
||||
if (regex_match(uri.begin(), uri.end(), what, re)) {
|
||||
auto proto = std::string(what[1].first, what[1].length());
|
||||
int port = 0;
|
||||
std::from_chars(what[3].first, what[3].second, port);
|
||||
if (!port) {
|
||||
if (proto == "http") {
|
||||
port = 80;
|
||||
} else if (proto == "https") {
|
||||
port = 443;
|
||||
}
|
||||
}
|
||||
|
||||
return result_t{URI{
|
||||
std::string(uri.begin(), uri.end()), std::string(what[2].first, what[2].length()), // host
|
||||
static_cast<std::uint16_t>(port), // port
|
||||
proto, // proto
|
||||
std::string(what[3].first, what[3].length()), // service
|
||||
std::string(what[4].first, what[4].length()), // path
|
||||
std::string(what[5].first, what[5].length()), // query
|
||||
std::string(what[6].first, what[6].length()), // fragment
|
||||
}};
|
||||
}
|
||||
return result_t{};
|
||||
}
|
||||
|
||||
}; // namespace syncspirit::utils
|
||||
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
#include <boost/optional.hpp>
|
||||
#include <boost/utility/string_view.hpp>
|
||||
#include <string>
|
||||
|
||||
namespace syncspirit::utils {
|
||||
|
||||
struct URI {
|
||||
std::string full;
|
||||
std::string host;
|
||||
std::uint16_t port;
|
||||
std::string proto;
|
||||
std::string service;
|
||||
std::string path;
|
||||
std::string query;
|
||||
std::string fragment;
|
||||
};
|
||||
|
||||
boost::optional<URI> parse(const char *uri);
|
||||
boost::optional<URI> parse(const boost::string_view &uri);
|
||||
|
||||
} // namespace syncspirit::utils
|
||||
@@ -0,0 +1,24 @@
|
||||
[sink_console]
|
||||
level = trace
|
||||
# pattern = [%^%l%$] %v
|
||||
|
||||
[sink_file]
|
||||
file = syncspirit.log
|
||||
level = info
|
||||
|
||||
[logging]
|
||||
sinks = console,file
|
||||
|
||||
[local_announce]
|
||||
enabled = 1
|
||||
port = 21027
|
||||
|
||||
[global_discovery]
|
||||
server = https://discovery.syncthing.net/v2/?noannounce&id=LYXKCHX-VI3NYZR-ALCJBHF-WMZYSPK-QG6QJA3-MPFYMSO-U56GTUK-NA2MIAW
|
||||
cert_file = /home/b/.config/syncthing/https-cert.pem
|
||||
key_file = /home/b/.config/syncthing/https-key.pem
|
||||
timeout = 5
|
||||
|
||||
|
||||
[upnp]
|
||||
timeout = 10
|
||||
Reference in New Issue
Block a user