diff --git a/src/.clang-format b/.clang-format similarity index 100% rename from src/.clang-format rename to .clang-format diff --git a/.github/workflows/check.yaml b/.github/workflows/check.yaml index bcc96ee7..bf2f3dbf 100644 --- a/.github/workflows/check.yaml +++ b/.github/workflows/check.yaml @@ -14,7 +14,7 @@ jobs: uses: jidicula/clang-format-action@v4.11.0 with: check-path: 'src' - exclude-regex: 'src/utils/argparse.h' + exclude-regex: 'argparse.h' linux: runs-on: ubuntu-latest @@ -28,7 +28,7 @@ jobs: runs-on: macos-latest steps: - name: depends - run: brew update && brew install fmt poco spdlog + run: brew update && brew install fmt poco spdlog nlohmann-json - name: checkout uses: actions/checkout@v4 - name: build @@ -57,6 +57,7 @@ jobs: mingw-w64-x86_64-gcc mingw-w64-x86_64-spdlog mingw-w64-x86_64-poco + mingw-w64-x86_64-nlohmann-json - name: checkout uses: actions/checkout@v4 - name: cache diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index a7434ec2..da9846c5 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -61,6 +61,7 @@ jobs: mingw-w64-x86_64-gcc mingw-w64-x86_64-spdlog mingw-w64-x86_64-poco + mingw-w64-x86_64-nlohmann-json - name: checkout uses: actions/checkout@v4 - name: cache @@ -75,7 +76,7 @@ jobs: mkdir artifact cp candy.cfg artifact cp build/src/tun/wintun/bin/amd64/wintun.dll artifact - scripts/search-deps.sh build/src/main/candy.exe artifact + scripts/search-deps.sh build/candy-cli/candy.exe artifact - name: set release package name shell: bash if: github.event_name == 'release' diff --git a/CMakeLists.txt b/CMakeLists.txt index 2840dae0..7c117135 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,29 +1,30 @@ cmake_minimum_required(VERSION 3.16) +project(Candy VERSION 6.1.0) -project(candy LANGUAGES C CXX VERSION 6.0.5) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) -option(CANDY_NOEXE "Don't build executable") -option(CANDY_DEVEL "Build development library") -option(CANDY_STATIC "Static linking dependent libraries") +add_compile_definitions(CANDY_VERSION="${PROJECT_VERSION}") set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) +set(CMAKE_SKIP_BUILD_RPATH TRUE) if (${CANDY_STATIC}) - set(CMAKE_SKIP_BUILD_RPATH TRUE) set(CANDY_STATIC_OPENSSL 1) - set(CANDY_STATIC_FMT 1) set(CANDY_STATIC_SPDLOG 1) + set(CANDY_STATIC_NLOHMANN_JSON 1) set(CANDY_STATIC_POCO 1) endif() find_package(PkgConfig REQUIRED) +include(${CMAKE_SOURCE_DIR}/cmake/Fetch.cmake) if (${CANDY_STATIC_OPENSSL}) execute_process( COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/openssl ) execute_process( - COMMAND ${CMAKE_COMMAND} -DTARGET_OPENSSL=${TARGET_OPENSSL} ${CMAKE_CURRENT_SOURCE_DIR}/cmake/openssl + COMMAND ${CMAKE_COMMAND} -DTARGET_OPENSSL=${TARGET_OPENSSL} ${CMAKE_SOURCE_DIR}/cmake/openssl WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/openssl RESULT_VARIABLE result ) @@ -63,18 +64,10 @@ else() find_package(OpenSSL REQUIRED) endif() -if (${CANDY_STATIC_FMT}) - include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/Fetch.cmake) - Fetch(fmt "https://github.com/fmtlib/fmt.git" "10.2.1") -endif() - if (${CANDY_STATIC_SPDLOG}) - include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/Fetch.cmake) - Fetch(spdlog "https://github.com/gabime/spdlog.git" "v1.14.1") + Fetch(spdlog "https://github.com/gabime/spdlog.git" "v1.15.3") else() - pkg_check_modules(SPDLOG REQUIRED spdlog) - add_definitions(${SPDLOG_CFLAGS}) - include_directories(${SPDLOG_INCLUDEDIR}) + find_package(spdlog REQUIRED) endif() if (${CANDY_STATIC_POCO}) @@ -93,24 +86,16 @@ if (${CANDY_STATIC_POCO}) set(ENABLE_ACTIVERECORD_COMPILER OFF CACHE BOOL "" FORCE) set(ENABLE_ZIP OFF CACHE BOOL "" FORCE) set(ENABLE_JWT OFF CACHE BOOL "" FORCE) - include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/Fetch.cmake) Fetch(poco "https://github.com/pocoproject/poco.git" "poco-1.13.3-release") else() find_package(Poco REQUIRED COMPONENTS Foundation XML JSON Net NetSSL) endif() -set(CANDY_EXECUTE_NAME "candy") -set(CANDY_LIBRARY_NAME "libcandy") - -add_compile_definitions(CANDY_VERSION="${PROJECT_VERSION}") -add_compile_options("-Wfatal-errors") - -include_directories(${PROJECT_SOURCE_DIR}/src) +if (${CANDY_STATIC_NLOHMANN_JSON}) + Fetch(nlohmann_json "https://github.com/nlohmann/json.git" "v3.11.3") +else() + find_package(nlohmann_json REQUIRED) +endif() -add_subdirectory(${PROJECT_SOURCE_DIR}/src/ffi) -add_subdirectory(${PROJECT_SOURCE_DIR}/src/main) -add_subdirectory(${PROJECT_SOURCE_DIR}/src/core) -add_subdirectory(${PROJECT_SOURCE_DIR}/src/websocket) -add_subdirectory(${PROJECT_SOURCE_DIR}/src/tun) -add_subdirectory(${PROJECT_SOURCE_DIR}/src/peer) -add_subdirectory(${PROJECT_SOURCE_DIR}/src/utils) +add_subdirectory(candy) +add_subdirectory(candy-cli) diff --git a/candy-cli/CMakeLists.txt b/candy-cli/CMakeLists.txt new file mode 100644 index 00000000..2b518f12 --- /dev/null +++ b/candy-cli/CMakeLists.txt @@ -0,0 +1,20 @@ +file(GLOB_RECURSE SOURCES "src/*.cc") +add_executable(candy_cli ${SOURCES}) + +target_include_directories(candy_library PUBLIC + $ + $ +) + +set_target_properties(candy_cli PROPERTIES + OUTPUT_NAME "candy" +) + +target_link_libraries(candy_cli PRIVATE spdlog::spdlog) +target_link_libraries(candy_cli PRIVATE Poco::Foundation) +target_link_libraries(candy_cli PRIVATE nlohmann_json::nlohmann_json) +target_link_libraries(candy_cli PRIVATE Candy::Library) + +install(TARGETS candy_cli) + +add_executable(Candy::CLI ALIAS candy_cli) diff --git a/src/utils/argparse.h b/candy-cli/src/argparse.h similarity index 100% rename from src/utils/argparse.h rename to candy-cli/src/argparse.h diff --git a/src/main/config.cc b/candy-cli/src/config.cc similarity index 79% rename from src/main/config.cc rename to candy-cli/src/config.cc index e885f6f0..1c93025e 100644 --- a/src/main/config.cc +++ b/candy-cli/src/config.cc @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT -#include "main/config.h" -#include "core/version.h" -#include "utils/argparse.h" -#include "utils/random.h" +#include "config.h" +#include "argparse.h" +#include "candy/candy.h" #include #include #include @@ -10,51 +9,39 @@ #include #include #include +#include #include #include #include -void arguments::dump(const std::string &key, const std::string &value) { - if (!value.empty()) { - spdlog::debug("--{}={}", key, value); - } -} - -void arguments::dump(const std::string &key, int value) { - if (value) { - spdlog::debug("--{}={}", key, value); - } -} - -void arguments::dump() { - spdlog::debug("================================"); - dump("mode", this->mode); - dump("websocket", this->websocket); - dump("password", this->password); - dump("ntp", this->ntp); - dump("restart", this->restart); - dump("dhcp", this->dhcp); - dump("sdwan", this->sdwan); - dump("name", this->name); - dump("tun", this->tun); - dump("stun", this->stun); - dump("localhost", this->localhost); - dump("discovery", this->discovery); - dump("route", this->routeCost); - dump("mtu", this->mtu); - dump("port", this->port); - spdlog::debug("================================"); +nlohmann::json arguments::json() { + nlohmann::json config = { + {"mode", this->mode}, + {"websocket", this->websocket}, + {"password", this->password}, + {"dhcp", this->dhcp}, + {"sdwan", this->sdwan}, + {"name", this->name}, + {"tun", this->tun}, + {"stun", this->stun}, + {"localhost", this->localhost}, + {"discovery", this->discovery}, + {"route", this->routeCost}, + {"mtu", this->mtu}, + {"port", this->port}, + {"vmac", virtualMac(this->name)}, + {"expt", loadTunAddress(this->name)}, + }; + return config; } int arguments::parse(int argc, char *argv[]) { - argparse::ArgumentParser program("candy", CANDY_VERSION); + argparse::ArgumentParser program("candy", candy::version()); program.add_argument("-c", "--config").help("config file path"); program.add_argument("-m", "--mode").help("working mode"); program.add_argument("-w", "--websocket").help("websocket address"); program.add_argument("-p", "--password").help("authorization password"); - program.add_argument("--ntp").help("ntp server"); - program.add_argument("-r", "--restart").help("restart interval").scan<'i', int>(); program.add_argument("-d", "--dhcp").help("dhcp address range"); program.add_argument("--sdwan").help("software-defined wide area network"); program.add_argument("-n", "--name").help("network interface name"); @@ -63,7 +50,7 @@ int arguments::parse(int argc, char *argv[]) { program.add_argument("--port").help("p2p listen port").scan<'i', int>(); program.add_argument("--mtu").help("maximum transmission unit").scan<'i', int>(); program.add_argument("-r", "--route").help("routing cost").scan<'i', int>(); - program.add_argument("-d", "--discovery").help("discovery interval").scan<'i', int>(); + program.add_argument("--discovery").help("discovery interval").scan<'i', int>(); program.add_argument("--localhost").help("local ip"); program.add_argument("--no-timestamp").implicit_value(true); @@ -82,8 +69,6 @@ int arguments::parse(int argc, char *argv[]) { program.set_if_used("--mode", this->mode); program.set_if_used("--websocket", this->websocket); program.set_if_used("--password", this->password); - program.set_if_used("--ntp", this->ntp); - program.set_if_used("--restart", this->restart); program.set_if_used("--no-timestamp", this->noTimestamp); program.set_if_used("--debug", this->debug); program.set_if_used("--dhcp", this->dhcp); @@ -116,7 +101,6 @@ int arguments::parse(int argc, char *argv[]) { } if (this->debug) { spdlog::set_level(spdlog::level::debug); - this->dump(); } return 0; } catch (const std::exception &e) { @@ -131,9 +115,7 @@ void arguments::parseFile(std::string cfgFile) { {"mode", [&](const std::string &value) { this->mode = value; }}, {"websocket", [&](const std::string &value) { this->websocket = value; }}, {"password", [&](const std::string &value) { this->password = value; }}, - {"ntp", [&](const std::string &value) { this->ntp = value; }}, {"debug", [&](const std::string &value) { this->debug = (value == "true"); }}, - {"restart", [&](const std::string &value) { this->restart = std::stoi(value); }}, {"dhcp", [&](const std::string &value) { this->dhcp = value; }}, {"sdwan", [&](const std::string &value) { this->sdwan = value; }}, {"tun", [&](const std::string &value) { this->tun = value; }}, @@ -216,13 +198,11 @@ std::string loadTunAddress(const std::string &name) { return "0.0.0.0/0"; } -static const int VMAC_SIZE = 16; - std::string virtualMacHelper(std::string name = "") { try { std::string path = storageDirectory("vmac/"); path += name.empty() ? "__noname__" : name; - char buffer[VMAC_SIZE]; + char buffer[candy::VMAC_SIZE]; std::stringstream ss; std::ifstream ifs(path); if (ifs.is_open()) { @@ -245,14 +225,13 @@ std::string initVirtualMac() { try { std::string path = storageDirectory("vmac/__noname__"); std::filesystem::create_directories(std::filesystem::path(path).parent_path()); - std::stringstream ss; - ss << Candy::randomHexString(VMAC_SIZE); + std::string vmac = candy::create_vmac(); std::ofstream ofs(path); if (ofs.is_open()) { - ofs << ss.str(); + ofs << vmac; ofs.close(); } - return ss.str(); + return vmac; } catch (std::exception &e) { spdlog::critical("init vmac failed: {}", e.what()); return ""; @@ -279,22 +258,6 @@ bool starts_with(const std::string &str, const std::string &prefix) { return str.size() >= prefix.size() && std::equal(prefix.begin(), prefix.end(), str.begin()); } -bool hasContainerVolume(const arguments &args) { - if (args.mode != "client") { - return true; - } - if (!std::filesystem::exists(storageDirectory("lost"))) { - return true; - } - if (starts_with(args.websocket, "wss://canets.org")) { - return false; - } - if (!args.tun.empty()) { - return true; - } - return false; -} - #if POCO_OS == POCO_OS_WINDOWS_NT std::string storageDirectory(std::string subdir) { return "C:/ProgramData/Candy/" + subdir; diff --git a/src/main/config.h b/candy-cli/src/config.h similarity index 53% rename from src/main/config.h rename to candy-cli/src/config.h index dc3b9042..a023b45b 100644 --- a/src/main/config.h +++ b/candy-cli/src/config.h @@ -1,26 +1,28 @@ // SPDX-License-Identifier: MIT -#ifndef CANDY_MAIN_CONFIG_H -#define CANDY_MAIN_CONFIG_H +#ifndef CANDY_CLI_CONFIG_H +#define CANDY_CLI_CONFIG_H #include -#include +#include #include struct arguments { - // 通用配置 + int parse(int argc, char *argv[]); + nlohmann::json json(); + +private: + void parseFile(std::string cfgFile); + std::map fileToKvMap(const std::string &filename); + std::string mode; std::string websocket; std::string password; - std::string ntp; - int restart = 0; bool noTimestamp = false; bool debug = false; - // 服务端配置 std::string dhcp; std::string sdwan; - // 客户端配置 std::string name; std::string tun; std::string stun; @@ -29,32 +31,11 @@ struct arguments { int discovery = 0; int routeCost = 0; int mtu = 1400; - - int parse(int argc, char *argv[]); - -private: - void dump(const std::string &key, const std::string &value); - void dump(const std::string &key, int value); - void dump(); - void parseFile(std::string cfgFile); - std::map fileToKvMap(const std::string &filename); }; -// 保存虚拟地址 int saveTunAddress(const std::string &name, const std::string &cidr); - -// 获取虚拟地址 std::string loadTunAddress(const std::string &name); - -// 获取或生成虚拟硬件地址 std::string virtualMac(const std::string &name); - -// 检查是否能成功保存虚拟硬件地址,虚拟硬件地址不能持久化会导致 -// 1. 址申请动态 IP 时会重复获取地址资源 -// 2. 使用静态地址时可能会冲突 -bool hasContainerVolume(const arguments &args); - -// 获取数据存储目录,默认参数非空是追加为子目录或目录下的文件 std::string storageDirectory(std::string subdir = ""); #endif diff --git a/candy-cli/src/main.cc b/candy-cli/src/main.cc new file mode 100644 index 00000000..12a57474 --- /dev/null +++ b/candy-cli/src/main.cc @@ -0,0 +1,52 @@ +#include "candy/candy.h" +#include "config.h" +#include +#include + +int main(int argc, char *argv[]) { + arguments args; + args.parse(argc, argv); + + auto config = args.json(); + + if (config["mode"] == "client") { + static const std::string id = "cli"; + + auto handler = [](int) -> void { candy::client::shutdown(id); }; + + signal(SIGINT, handler); + signal(SIGTERM, handler); + + std::thread([&]() { + while (true) { + std::this_thread::sleep_for(std::chrono::seconds(1)); + auto status = candy::client::status(id); + if (status) { + auto it = (*status).find("address"); + if (it != (*status).end() && it->is_string()) { + auto address = it->get(); + if (!address.empty()) { + saveTunAddress(config["name"], address); + break; + } + } + } + } + }).detach(); + + candy::client::run(id, config); + return 0; + } + + if (config["mode"] == "server") { + auto handler = [](int) -> void { candy::server::shutdown(); }; + + signal(SIGINT, handler); + signal(SIGTERM, handler); + + candy::server::run(config); + return 0; + } + + return -1; +} diff --git a/candy.cfg b/candy.cfg index cf3c291f..10c161b2 100644 --- a/candy.cfg +++ b/candy.cfg @@ -15,18 +15,6 @@ websocket = "wss://canets.org" # [Optional] Show debug log #debug = false -# [Optional] Restart interval -# When an internal exception is detected, the service will be automatically -# restarted. When configured to 0, the process will exit without restarting. -# Otherwise, it will restart after an interval of n seconds. -#restart = 3 - -# [Optional] Set NTP Server -# The time difference between the client and the server should not exceed 30 -# seconds. If the local time is inaccurate, the current real time can be -# obtained from the NTP server. -ntp = "pool.ntp.org" - ################################# Server Only ################################# # [Optional] The range of addresses automatically assigned by the server # Server address allocation is not enabled by default, and the client needs to diff --git a/candy/.vscode/c_cpp_properties.json b/candy/.vscode/c_cpp_properties.json new file mode 100644 index 00000000..ef1d543a --- /dev/null +++ b/candy/.vscode/c_cpp_properties.json @@ -0,0 +1,15 @@ +{ + "configurations": [ + { + "name": "Linux", + "includePath": [ + "${workspaceFolder}/**" + ], + "defines": [], + "compilerPath": "/usr/bin/clang", + "cStandard": "c17", + "intelliSenseMode": "linux-clang-x64" + } + ], + "version": 4 +} diff --git a/candy/.vscode/settings.json b/candy/.vscode/settings.json new file mode 100644 index 00000000..163bc9ec --- /dev/null +++ b/candy/.vscode/settings.json @@ -0,0 +1,12 @@ +{ + "editor.detectIndentation": false, + "editor.tabSize": 4, + "editor.formatOnSave": true, + "editor.insertSpaces": true, + "editor.formatOnSaveMode": "modifications", + "files.insertFinalNewline": true, + "json.format.enable": true, + "C_Cpp.default.cppStandard": "c++17", + "C_Cpp.autoAddFileAssociations": false, + "C_Cpp.errorSquiggles": "disabled" +} diff --git a/candy/CMakeLists.txt b/candy/CMakeLists.txt new file mode 100644 index 00000000..add2c45b --- /dev/null +++ b/candy/CMakeLists.txt @@ -0,0 +1,61 @@ +add_library(candy_library) + +file(GLOB_RECURSE SOURCES "src/*.cc") +target_sources(candy_library PRIVATE ${SOURCES}) + +target_include_directories(candy_library PUBLIC + $ + $ + $ +) + +if (${CANDY_STATIC_OPENSSL}) + target_link_libraries(candy_library PRIVATE ${OPENSSL_LIB_CRYPTO} ${OPENSSL_LIB_SSL}) +else() + target_link_libraries(candy_library PRIVATE OpenSSL::SSL OpenSSL::Crypto) +endif() + +target_link_libraries(candy_library PRIVATE spdlog::spdlog) +target_link_libraries(candy_library PRIVATE Poco::Foundation Poco::Net Poco::NetSSL) +target_link_libraries(candy_library PRIVATE nlohmann_json::nlohmann_json) + +set(THREADS_PREFER_PTHREAD_FLAG ON) +find_package(Threads REQUIRED) +target_link_libraries(candy_library PRIVATE Threads::Threads) + +if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") + target_link_libraries(candy_library PRIVATE ws2_32) +endif() + +if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") + target_link_libraries(candy_library PRIVATE iphlpapi) + target_link_libraries(candy_library PRIVATE ws2_32) + + set(WINTUN_VERSION 0.14.1) + set(WINTUN_ZIP wintun-${WINTUN_VERSION}.zip) + set(WINTUN_URL https://www.wintun.net/builds/${WINTUN_ZIP}) + + if (NOT EXISTS ${CMAKE_CURRENT_BINARY_DIR}/${WINTUN_ZIP}) + file(DOWNLOAD ${WINTUN_URL} ${CMAKE_CURRENT_BINARY_DIR}/${WINTUN_ZIP} STATUS DOWNLOAD_STATUS) + list(GET DOWNLOAD_STATUS 0 STATUS_CODE) + list(GET DOWNLOAD_STATUS 1 ERROR_MESSAGE) + + if(${STATUS_CODE} EQUAL 0) + message(STATUS "wintun download success") + else() + message(FATAL_ERROR "wintun download failed: ${ERROR_MESSAGE}") + endif() + else() + message(STATUS "use wintun cache") + endif() + + file(ARCHIVE_EXTRACT INPUT ${CMAKE_CURRENT_BINARY_DIR}/${WINTUN_ZIP}) + + include_directories(${CMAKE_CURRENT_BINARY_DIR}/wintun/include) +endif() + +set_target_properties(candy_library PROPERTIES + OUTPUT_NAME "candy" +) + +add_library(Candy::Library ALIAS candy_library) diff --git a/candy/include/candy/candy.h b/candy/include/candy/candy.h new file mode 100644 index 00000000..70f60e13 --- /dev/null +++ b/candy/include/candy/candy.h @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: MIT +#ifndef CANDY_CANDY_H +#define CANDY_CANDY_H + +#include "client.h" +#include "common.h" +#include "server.h" + +#endif diff --git a/candy/include/candy/client.h b/candy/include/candy/client.h new file mode 100644 index 00000000..41718924 --- /dev/null +++ b/candy/include/candy/client.h @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT +#ifndef CANDY_CLIENT_H +#define CANDY_CLIENT_H + +#include +#include +#include + +namespace candy { +namespace client { + +bool run(const std::string &id, const nlohmann::json &config); +bool shutdown(const std::string &id); +std::optional status(const std::string &id); + +} // namespace client +} // namespace candy + +#endif diff --git a/candy/include/candy/common.h b/candy/include/candy/common.h new file mode 100644 index 00000000..478da519 --- /dev/null +++ b/candy/include/candy/common.h @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT +#ifndef CANDY_COMMON_H +#define CANDY_COMMON_H + +#include + +namespace candy { +static const int VMAC_SIZE = 16; + +std::string version(); +std::string create_vmac(); +} // namespace candy + +#endif diff --git a/candy/include/candy/server.h b/candy/include/candy/server.h new file mode 100644 index 00000000..f7c36948 --- /dev/null +++ b/candy/include/candy/server.h @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +#ifndef CANDY_SERVER_H +#define CANDY_SERVER_H + +#include +#include + +namespace candy { +namespace server { + +bool run(const nlohmann::json &config); +bool shutdown(); + +} // namespace server +} // namespace candy + +#endif diff --git a/candy/src/candy/client.cc b/candy/src/candy/client.cc new file mode 100644 index 00000000..8111ebd8 --- /dev/null +++ b/candy/src/candy/client.cc @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: MIT +#include "candy/client.h" +#include "core/client.h" +#include "utils/atomic.h" +#include +#include +#include +#include +#include +#include +#include + +namespace candy { +namespace client { + +namespace { +using Utils::Atomic; + +class Instance { +public: + bool is_running() { + return this->running.load(); + } + + void exit() { + this->running.store(false); + if (auto client = this->client.lock()) { + client->shutdown(); + } + } + + nlohmann::json status() { + nlohmann::json data; + if (auto client = this->client.lock()) { + data["address"] = client->getTunCidr(); + } + return data; + } + + std::shared_ptr create_client() { + auto client = std::make_shared(); + this->client = client; + return client; + } + +private: + Atomic running = Atomic(true); + std::weak_ptr client; +}; + +std::map> instance_map; +std::shared_mutex instance_mutex; + +std::optional> try_create_instance(const std::string &id) { + std::unique_lock lock(instance_mutex); + auto it = instance_map.find(id); + if (it != instance_map.end()) { + spdlog::warn("instance already exists: id={}", id); + return std::nullopt; + } + auto manager = std::make_shared(); + instance_map.emplace(id, manager); + return manager; +} + +bool try_erase_instance(const std::string &id) { + std::unique_lock lock(instance_mutex); + return instance_map.erase(id) > 0; +} + +} // namespace + +bool run(const std::string &id, const nlohmann::json &config) { + auto instance = try_create_instance(id); + if (!instance) { + return false; + } + + spdlog::info("run enter: id={} config={}", id, config.dump(4)); + while ((*instance)->is_running()) { + std::this_thread::sleep_for(std::chrono::seconds(1)); + auto client = (*instance)->create_client(); + client->setName(config["name"]); + client->setPassword(config["password"]); + client->setWebSocket(config["websocket"]); + client->setTunAddress(config["tun"]); + client->setVirtualMac(config["vmac"]); + client->setExptTunAddress(config["expt"]); + client->setStun(config["stun"]); + client->setDiscoveryInterval(config["discovery"]); + client->setRouteCost(config["route"]), client->setMtu(config["mtu"]); + client->setPort(config["port"]); + client->setLocalhost(config["localhost"]); + client->run(); + } + spdlog::info("run exit: id={} ", id); + + return try_erase_instance(id); +} + +bool shutdown(const std::string &id) { + std::shared_lock lock(instance_mutex); + auto it = instance_map.find(id); + if (it == instance_map.end()) { + spdlog::warn("instance not found: id={}", id); + return false; + } + if (auto instance = it->second) { + instance->exit(); + } + return true; +} + +std::optional status(const std::string &id) { + std::shared_lock lock(instance_mutex); + auto it = instance_map.find(id); + if (it != instance_map.end()) { + if (auto instance = it->second) { + return instance->status(); + } + } + return std::nullopt; +} + +} // namespace client +} // namespace candy diff --git a/candy/src/candy/server.cc b/candy/src/candy/server.cc new file mode 100644 index 00000000..eacb5eb7 --- /dev/null +++ b/candy/src/candy/server.cc @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +#include "candy/server.h" +#include "core/server.h" +#include "utils/atomic.h" + +namespace candy { +namespace server { + +namespace { +Utils::Atomic running(true); +std::shared_ptr server; +} // namespace + +bool run(const nlohmann::json &config) { + while (running.load()) { + std::this_thread::sleep_for(std::chrono::seconds(1)); + server = std::make_shared(); + server->setWebSocket(config["websocket"]); + server->setPassword(config["password"]); + server->setDHCP(config["dhcp"]); + server->setSdwan(config["sdwan"]); + server->run(); + } + return true; +} + +bool shutdown() { + running.store(false); + server->shutdown(); + return true; +} + +} // namespace server +} // namespace candy diff --git a/src/core/client.cc b/candy/src/core/client.cc similarity index 77% rename from src/core/client.cc rename to candy/src/core/client.cc index f3e18008..7e5847d9 100644 --- a/src/core/client.cc +++ b/candy/src/core/client.cc @@ -4,7 +4,7 @@ #include #include -namespace Candy { +namespace candy { Msg MsgQueue::read() { std::unique_lock lock(msgMutex); @@ -42,6 +42,10 @@ std::string Client::getName() const { return this->tunName; } +std::string Client::getTunCidr() const { + return ws.getTunCidr(); +} + IP4 Client::address() { return this->tun.getIP(); } @@ -103,32 +107,34 @@ void Client::setMtu(int mtu) { tun.setMTU(mtu); } -void Client::setTunUpdateCallback(std::function callback) { - this->ws.setTunUpdateCallback(callback); -} - void Client::run() { - std::lock_guard lock(this->runningMutex); - if (this->running == false) { - this->running = true; - ws.run(this); - tun.run(this); - peerManager.run(this); + this->running.store(true); + + if (ws.run(this)) { + return; + } + if (tun.run(this)) { + return; } + if (peerManager.run(this)) { + return; + } + + ws.wait(); + tun.wait(); + peerManager.wait(); + + wsMsgQueue.clear(); + tunMsgQueue.clear(); + peerMsgQueue.clear(); +} + +bool Client::isRunning() { + return this->running.load(); } void Client::shutdown() { - std::lock_guard lock(this->runningMutex); - if (this->running == true) { - this->running = false; - ws.shutdown(); - tun.shutdown(); - peerManager.shutdown(); - - wsMsgQueue.clear(); - tunMsgQueue.clear(); - peerMsgQueue.clear(); - } + this->running.store(false); } -} // namespace Candy +} // namespace candy diff --git a/src/core/client.h b/candy/src/core/client.h similarity index 86% rename from src/core/client.h rename to candy/src/core/client.h index f0f73a30..61fd6795 100644 --- a/src/core/client.h +++ b/candy/src/core/client.h @@ -5,15 +5,14 @@ #include "core/message.h" #include "peer/manager.h" #include "tun/tun.h" +#include "utils/atomic.h" #include "websocket/client.h" #include #include #include #include -namespace Candy { - -void shutdown(Client *client); +namespace candy { class MsgQueue { public: @@ -39,20 +38,21 @@ class Client { void setPort(int port); void setLocalhost(std::string ip); void setMtu(int mtu); - void setTunUpdateCallback(std::function callback); void setExptTunAddress(const std::string &cidr); void setVirtualMac(const std::string &vmac); void run(); + bool isRunning(); void shutdown(); - bool running = false; - std::mutex runningMutex; - std::string getName() const; + std::string getTunCidr() const; IP4 address(); +private: + Utils::Atomic running; + public: MsgQueue &getTunMsgQueue(); MsgQueue &getPeerMsgQueue(); @@ -69,6 +69,6 @@ class Client { std::string tunName; }; -} // namespace Candy +} // namespace candy #endif diff --git a/candy/src/core/common.cc b/candy/src/core/common.cc new file mode 100644 index 00000000..6c5de773 --- /dev/null +++ b/candy/src/core/common.cc @@ -0,0 +1,16 @@ +#include "candy/common.h" +#include "core/version.h" +#include "utils/random.h" +#include + +namespace candy { + +std::string version() { + return CANDY_VERSION; +} + +std::string create_vmac() { + return randomHexString(VMAC_SIZE); +} + +} // namespace candy \ No newline at end of file diff --git a/src/core/message.cc b/candy/src/core/message.cc similarity index 90% rename from src/core/message.cc rename to candy/src/core/message.cc index 57030a88..7bf17404 100644 --- a/src/core/message.cc +++ b/candy/src/core/message.cc @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT #include "core/message.h" -namespace Candy { +namespace candy { Msg::Msg(MsgKind kind, std::string data) { this->kind = kind; @@ -19,4 +19,4 @@ Msg &Msg::operator=(Msg &&packet) { return *this; } -} // namespace Candy +} // namespace candy diff --git a/src/core/message.h b/candy/src/core/message.h similarity index 94% rename from src/core/message.h rename to candy/src/core/message.h index 9c5cf97e..0d3f6fa8 100644 --- a/src/core/message.h +++ b/candy/src/core/message.h @@ -6,7 +6,7 @@ #include #include -namespace Candy { +namespace candy { enum class MsgKind { TIMEOUT, @@ -42,6 +42,6 @@ struct PubInfo { } // namespace CoreMsg -} // namespace Candy +} // namespace candy #endif diff --git a/src/core/net.cc b/candy/src/core/net.cc similarity index 98% rename from src/core/net.cc rename to candy/src/core/net.cc index c68afa5a..8513818e 100644 --- a/src/core/net.cc +++ b/candy/src/core/net.cc @@ -4,7 +4,7 @@ #include #include -namespace Candy { +namespace candy { IP4::IP4(const std::string &ip) { fromString(ip); @@ -159,4 +159,4 @@ std::string Address::toCidr() { return host.toString() + "/" + std::to_string(mask.toPrefix()); } -} // namespace Candy +} // namespace candy diff --git a/src/core/net.h b/candy/src/core/net.h similarity index 95% rename from src/core/net.h rename to candy/src/core/net.h index 926d28ec..3e7a2ec7 100644 --- a/src/core/net.h +++ b/candy/src/core/net.h @@ -8,7 +8,7 @@ #include #include -namespace Candy { +namespace candy { template typename std::enable_if::value, T>::type byteswap(T value) { static_assert(std::is_integral::value, "byteswap requires integral type"); @@ -118,11 +118,12 @@ class Address { IP4 mask; }; -} // namespace Candy +} // namespace candy namespace std { -template <> struct hash { - size_t operator()(const Candy::IP4 &ip) const noexcept { +using candy::IP4; +template <> struct hash { + size_t operator()(const IP4 &ip) const noexcept { return hash{}(ip); } }; diff --git a/src/core/server.cc b/candy/src/core/server.cc similarity index 80% rename from src/core/server.cc rename to candy/src/core/server.cc index 3b15317b..9d454c09 100644 --- a/src/core/server.cc +++ b/candy/src/core/server.cc @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT #include "core/server.h" -namespace Candy { +namespace candy { void Server::setWebSocket(const std::string &uri) { ws.setWebSocket(uri); @@ -20,11 +20,14 @@ void Server::setSdwan(const std::string &sdwan) { } void Server::run() { + running.store(true); ws.run(); + running.wait(true); + ws.shutdown(); } void Server::shutdown() { - ws.shutdown(); + running.store(false); } -} // namespace Candy +} // namespace candy diff --git a/src/core/server.h b/candy/src/core/server.h similarity index 86% rename from src/core/server.h rename to candy/src/core/server.h index 416a3972..92187bdc 100644 --- a/src/core/server.h +++ b/candy/src/core/server.h @@ -2,10 +2,11 @@ #ifndef CANDY_CORE_SERVER_H #define CANDY_CORE_SERVER_H +#include "utils/atomic.h" #include "websocket/server.h" #include -namespace Candy { +namespace candy { class Server { public: @@ -23,8 +24,9 @@ class Server { private: // 目前只有一个 WebSocket 服务端的子模块 WebSocketServer ws; + Utils::Atomic running; }; -} // namespace Candy +} // namespace candy #endif diff --git a/src/core/version.h b/candy/src/core/version.h similarity index 100% rename from src/core/version.h rename to candy/src/core/version.h diff --git a/src/peer/manager.cc b/candy/src/peer/manager.cc similarity index 93% rename from src/peer/manager.cc rename to candy/src/peer/manager.cc index db0732cc..eafbccc0 100644 --- a/src/peer/manager.cc +++ b/candy/src/peer/manager.cc @@ -13,7 +13,7 @@ #include #include -namespace Candy { +namespace candy { int PeerManager::setPassword(const std::string &password) { this->password = password; @@ -58,21 +58,25 @@ int PeerManager::run(Client *client) { this->localP2PDisabled = false; if (this->stun.update()) { + spdlog::critical("update stun failed"); return -1; } this->msgThread = std::thread([&] { - spdlog::info("start thread: peer manager msg"); - while (getClient().running) { - handlePeerQueue(); + spdlog::debug("start thread: peer manager msg"); + while (getClient().isRunning()) { + if (handlePeerQueue()) { + break; + } } - spdlog::info("stop thread: peer manager msg"); + getClient().shutdown(); + spdlog::debug("stop thread: peer manager msg"); }); return 0; } -int PeerManager::shutdown() { +int PeerManager::wait() { if (this->msgThread.joinable()) { this->msgThread.join(); } @@ -102,7 +106,7 @@ std::string PeerManager::getPassword() { return this->password; } -void PeerManager::handlePeerQueue() { +int PeerManager::handlePeerQueue() { Msg msg = getClient().getPeerMsgQueue().read(); switch (msg.kind) { case MsgKind::TIMEOUT: @@ -111,8 +115,12 @@ void PeerManager::handlePeerQueue() { handlePacket(std::move(msg)); break; case MsgKind::TUNADDR: - startTickThread(); - handleTunAddr(std::move(msg)); + if (startTickThread()) { + return -1; + } + if (handleTunAddr(std::move(msg))) { + return -1; + } break; case MsgKind::SYSRT: this->localP2PDisabled = true; @@ -127,6 +135,7 @@ void PeerManager::handlePeerQueue() { spdlog::warn("unexcepted peer message type: {}", static_cast(msg.kind)); break; } + return 0; } int PeerManager::sendPacket(IP4 dst, const Msg &msg) { @@ -180,18 +189,19 @@ IP4 PeerManager::getTunIp() { return this->tunAddr.Host(); } -void PeerManager::handlePacket(Msg msg) { +int PeerManager::handlePacket(Msg msg) { auto header = (IP4Header *)msg.data.data(); if (!sendPacket(header->daddr, msg)) { - return; + return 0; } getClient().getWsMsgQueue().write(std::move(msg)); + return 0; } -void PeerManager::handleTunAddr(Msg msg) { +int PeerManager::handleTunAddr(Msg msg) { if (this->tunAddr.fromCidr(msg.data)) { spdlog::error("set tun addr failed: {}", msg.data); - return; + return -1; } std::string data; @@ -201,9 +211,10 @@ void PeerManager::handleTunAddr(Msg msg) { this->key.resize(SHA256_DIGEST_LENGTH); SHA256((unsigned char *)data.data(), data.size(), (unsigned char *)this->key.data()); + return 0; } -void PeerManager::handleTryP2P(Msg msg) { +int PeerManager::handleTryP2P(Msg msg) { IP4 src(msg.data); { @@ -211,7 +222,7 @@ void PeerManager::handleTryP2P(Msg msg) { auto it = this->ipPeerMap.find(src); if (it != this->ipPeerMap.end()) { it->second.tryConnecct(); - return; + return 0; } } @@ -220,19 +231,20 @@ void PeerManager::handleTryP2P(Msg msg) { auto it = this->ipPeerMap.emplace(std::piecewise_construct, std::forward_as_tuple(src), std::forward_as_tuple(src, this)); if (it.second) { it.first->second.tryConnecct(); - return; + return 0; } } spdlog::warn("can not find peer: {}", src.toString()); + return 0; } -void PeerManager::handlePubInfo(Msg msg) { +int PeerManager::handlePubInfo(Msg msg) { auto info = (CoreMsg::PubInfo *)(msg.data.data()); if (info->src == getClient().address() || info->dst != getClient().address()) { spdlog::warn("invalid public info: src=[{}] dst=[{}]", info->src.toString(), info->dst.toString()); - return; + return 0; } { @@ -250,9 +262,11 @@ void PeerManager::handlePubInfo(Msg msg) { std::forward_as_tuple(info->src, this)); if (it.second) { it.first->second.handlePubInfo(info->ip, info->port, info->local); - return; + return 0; } } + + return 0; } int PeerManager::startTickThread() { @@ -273,23 +287,24 @@ int PeerManager::startTickThread() { } if (this->initSocket()) { - Candy::shutdown(this->client); return -1; } this->tickThread = std::thread([&] { - spdlog::info("start thread: peer manager tick"); - while (getClient().running) { + spdlog::debug("start thread: peer manager tick"); + while (getClient().isRunning()) { auto wake_time = std::chrono::system_clock::now() + std::chrono::seconds(1); - tick(); + if (tick()) { + break; + } std::this_thread::sleep_until(wake_time); } - - spdlog::info("stop thread: peer manager tick"); + getClient().shutdown(); + spdlog::debug("stop thread: peer manager tick"); }); return 0; } -void PeerManager::tick() { +int PeerManager::tick() { if (this->discoveryInterval && this->stun.enabled()) { if ((++tickTick % discoveryInterval == 0)) { getClient().getWsMsgQueue().write(Msg(MsgKind::DISCOVERY)); @@ -306,6 +321,8 @@ void PeerManager::tick() { sendStunRequest(); this->stun.needed = false; } + + return 0; } int PeerManager::initSocket() { @@ -323,11 +340,12 @@ int PeerManager::initSocket() { this->decryptCtx = std::shared_ptr(EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free); this->pollThread = std::thread([&]() { - spdlog::info("start thread: peer manager poll"); - while (getClient().running) { + spdlog::debug("start thread: peer manager poll"); + while (getClient().isRunning()) { poll(); } - spdlog::info("stop thread: peer manager poll"); + getClient().shutdown(); + spdlog::debug("stop thread: peer manager poll"); }); return 0; } @@ -681,4 +699,4 @@ int PeerManager::updateRtTable(PeerRouteEntry entry) { return 0; } -} // namespace Candy +} // namespace candy diff --git a/src/peer/manager.h b/candy/src/peer/manager.h similarity index 94% rename from src/peer/manager.h rename to candy/src/peer/manager.h index 585dd82b..94a7d68d 100644 --- a/src/peer/manager.h +++ b/candy/src/peer/manager.h @@ -16,7 +16,7 @@ #include #include -namespace Candy { +namespace candy { using Poco::Net::SocketAddress; @@ -68,7 +68,7 @@ class PeerManager { int setLocalhost(const std::string &ip); int run(Client *client); - int shutdown(); + int wait(); std::string getPassword(); @@ -83,11 +83,11 @@ class PeerManager { private: // 处理来自消息队列的数据 - void handlePeerQueue(); - void handlePacket(Msg msg); - void handleTunAddr(Msg msg); - void handleTryP2P(Msg msg); - void handlePubInfo(Msg msg); + int handlePeerQueue(); + int handlePacket(Msg msg); + int handleTunAddr(Msg msg); + int handleTryP2P(Msg msg); + int handlePubInfo(Msg msg); std::thread msgThread; @@ -98,7 +98,7 @@ class PeerManager { Address tunAddr; int startTickThread(); - void tick(); + int tick(); std::thread tickThread; uint64_t tickTick = randomUint32(); @@ -151,6 +151,6 @@ class PeerManager { Client *client; }; -} // namespace Candy +} // namespace candy #endif diff --git a/src/peer/message.cc b/candy/src/peer/message.cc similarity index 86% rename from src/peer/message.cc rename to candy/src/peer/message.cc index c6b2fd40..ba0cdda1 100644 --- a/src/peer/message.cc +++ b/candy/src/peer/message.cc @@ -1,7 +1,7 @@ #include "peer/message.h" #include -namespace Candy { +namespace candy { namespace PeerMsg { std::string Forward::create(const std::string &packet) { @@ -12,4 +12,4 @@ std::string Forward::create(const std::string &packet) { } } // namespace PeerMsg -} // namespace Candy +} // namespace candy diff --git a/src/peer/message.h b/candy/src/peer/message.h similarity index 97% rename from src/peer/message.h rename to candy/src/peer/message.h index c5f30d05..41c32f6a 100644 --- a/src/peer/message.h +++ b/candy/src/peer/message.h @@ -6,7 +6,7 @@ #include "utils/random.h" #include -namespace Candy { +namespace candy { namespace PeerMsgKind { @@ -76,6 +76,6 @@ struct __attribute__((packed)) Route { } // namespace PeerMsg -} // namespace Candy +} // namespace candy #endif diff --git a/src/peer/peer.cc b/candy/src/peer/peer.cc similarity index 99% rename from src/peer/peer.cc rename to candy/src/peer/peer.cc index f1f3436a..be50bf0b 100644 --- a/src/peer/peer.cc +++ b/candy/src/peer/peer.cc @@ -30,7 +30,7 @@ bool isLocalNetwork(const SocketAddress &addr) { } // namespace -namespace Candy { +namespace candy { Peer::Peer(const IP4 &addr, PeerManager *peerManager) : peerManager(peerManager), addr(addr) { std::string data; @@ -355,4 +355,4 @@ void Peer::resetState() { this->rtt = RTT_LIMIT; } -} // namespace Candy +} // namespace candy diff --git a/src/peer/peer.h b/candy/src/peer/peer.h similarity index 98% rename from src/peer/peer.h rename to candy/src/peer/peer.h index 6abd5d26..81431d35 100644 --- a/src/peer/peer.h +++ b/candy/src/peer/peer.h @@ -14,7 +14,7 @@ #include #include -namespace Candy { +namespace candy { class PeerManager; @@ -80,6 +80,6 @@ class Peer { IP4 addr; }; -} // namespace Candy +} // namespace candy #endif diff --git a/src/tun/linux.cc b/candy/src/tun/linux.cc similarity index 96% rename from src/tun/linux.cc rename to candy/src/tun/linux.cc index 170e6cfc..dc21a16e 100644 --- a/src/tun/linux.cc +++ b/candy/src/tun/linux.cc @@ -15,7 +15,7 @@ #include #include -namespace { +namespace candy { class LinuxTun { public: @@ -24,16 +24,16 @@ class LinuxTun { return 0; } - int setIP(Candy::IP4 ip) { + int setIP(IP4 ip) { this->ip = ip; return 0; } - Candy::IP4 getIP() { + IP4 getIP() { return this->ip; } - int setMask(Candy::IP4 mask) { + int setMask(IP4 mask) { this->mask = mask; return 0; } @@ -154,7 +154,7 @@ class LinuxTun { return ::write(this->tunFd, buffer.c_str(), buffer.size()); } - int setSysRtTable(Candy::IP4 dst, Candy::IP4 mask, Candy::IP4 nexthop) { + int setSysRtTable(IP4 dst, IP4 mask, IP4 nexthop) { int sockfd = socket(AF_INET, SOCK_DGRAM, 0); if (sockfd == -1) { spdlog::error("set route failed: create socket failed"); @@ -190,16 +190,16 @@ class LinuxTun { private: std::string name; - Candy::IP4 ip; - Candy::IP4 mask; + IP4 ip; + IP4 mask; int mtu; int timeout; int tunFd; }; -} // namespace +} // namespace candy -namespace Candy { +namespace candy { Tun::Tun() { this->impl = std::make_shared(); @@ -281,6 +281,6 @@ int Tun::setSysRtTable(IP4 dst, IP4 mask, IP4 nexthop) { return tun->setSysRtTable(dst, mask, nexthop); } -} // namespace Candy +} // namespace candy #endif diff --git a/src/tun/macos.cc b/candy/src/tun/macos.cc similarity index 97% rename from src/tun/macos.cc rename to candy/src/tun/macos.cc index ad2b7ea1..90a6c3f8 100644 --- a/src/tun/macos.cc +++ b/candy/src/tun/macos.cc @@ -24,7 +24,7 @@ #include #include -namespace { +namespace candy { class MacTun { public: @@ -33,16 +33,16 @@ class MacTun { return 0; } - int setIP(Candy::IP4 ip) { + int setIP(IP4 ip) { this->ip = ip; return 0; } - Candy::IP4 getIP() { + IP4 getIP() { return this->ip; } - int setMask(Candy::IP4 mask) { + int setMask(IP4 mask) { this->mask = mask; return 0; } @@ -207,7 +207,7 @@ class MacTun { return ::writev(this->tunFd, iov, sizeof(iov) / sizeof(iov[0])) - sizeof(sizeof(this->packetinfo)); } - int setSysRtTable(Candy::IP4 dst, Candy::IP4 mask, Candy::IP4 nexthop) { + int setSysRtTable(IP4 dst, IP4 mask, IP4 nexthop) { struct { struct rt_msghdr msghdr; struct sockaddr_in addr[3]; @@ -244,8 +244,8 @@ class MacTun { private: std::string name; char ifname[IFNAMSIZ] = {0}; - Candy::IP4 ip; - Candy::IP4 mask; + IP4 ip; + IP4 mask; int mtu; int timeout; int tunFd; @@ -253,9 +253,9 @@ class MacTun { uint8_t packetinfo[4] = {0x00, 0x00, 0x00, 0x02}; }; -} // namespace +} // namespace candy -namespace Candy { +namespace candy { Tun::Tun() { this->impl = std::make_shared(); @@ -336,6 +336,6 @@ int Tun::setSysRtTable(IP4 dst, IP4 mask, IP4 nexthop) { return tun->setSysRtTable(dst, mask, nexthop); } -} // namespace Candy +} // namespace candy #endif diff --git a/src/tun/tun.cc b/candy/src/tun/tun.cc similarity index 72% rename from src/tun/tun.cc rename to candy/src/tun/tun.cc index e5f42355..472e045d 100644 --- a/src/tun/tun.cc +++ b/candy/src/tun/tun.cc @@ -7,21 +7,24 @@ #include #include -namespace Candy { +namespace candy { int Tun::run(Client *client) { this->client = client; this->msgThread = std::thread([&] { - spdlog::info("start thread: tun msg"); - while (this->client->running) { - handleTunQueue(); + spdlog::debug("start thread: tun msg"); + while (getClient().isRunning()) { + if (handleTunQueue()) { + break; + } } - spdlog::info("stop thread: tun msg"); + getClient().shutdown(); + spdlog::debug("stop thread: tun msg"); }); return 0; } -int Tun::shutdown() { +int Tun::wait() { if (this->tunThread.joinable()) { this->tunThread.join(); } @@ -35,18 +38,18 @@ int Tun::shutdown() { return 0; } -void Tun::handleTunDevice() { +int Tun::handleTunDevice() { std::string buffer; int error = read(buffer); if (error <= 0) { - return; + return 0; } if (buffer.length() < sizeof(IP4Header)) { - return; + return 0; } IP4Header *header = (IP4Header *)buffer.data(); if (!header->isIPv4()) { - return; + return 0; } IP4 nextHop = [&]() { @@ -68,13 +71,14 @@ void Tun::handleTunDevice() { if (header->daddr == getIP()) { write(buffer); - return; + return 0; } this->client->getPeerMsgQueue().write(Msg(MsgKind::PACKET, std::move(buffer))); + return 0; } -void Tun::handleTunQueue() { +int Tun::handleTunQueue() { Msg msg = this->client->getTunMsgQueue().read(); switch (msg.kind) { case MsgKind::TIMEOUT: @@ -83,7 +87,9 @@ void Tun::handleTunQueue() { handlePacket(std::move(msg)); break; case MsgKind::TUNADDR: - handleTunAddr(std::move(msg)); + if (handleTunAddr(std::move(msg))) { + return -1; + } break; case MsgKind::SYSRT: handleSysRt(std::move(msg)); @@ -92,12 +98,13 @@ void Tun::handleTunQueue() { spdlog::warn("unexcepted tun message type: {}", static_cast(msg.kind)); break; } + return 0; } -void Tun::handlePacket(Msg msg) { +int Tun::handlePacket(Msg msg) { if (msg.data.size() < sizeof(IP4Header)) { spdlog::warn("invalid IPv4 packet: {:n}", spdlog::to_hex(msg.data)); - return; + return 0; } IP4Header *header = (IP4Header *)msg.data.data(); if (header->isIPIP()) { @@ -105,41 +112,47 @@ void Tun::handlePacket(Msg msg) { header = (IP4Header *)msg.data.data(); } write(msg.data); + return 0; } -void Tun::handleTunAddr(Msg msg) { +int Tun::handleTunAddr(Msg msg) { if (setAddress(msg.data)) { - Candy::shutdown(this->client); - return; + return -1; } if (up()) { spdlog::critical("tun up failed"); - Candy::shutdown(this->client); - return; + return -1; } this->tunThread = std::thread([&] { - spdlog::info("start thread: tun"); - while (this->client->running) { - handleTunDevice(); + spdlog::debug("start thread: tun"); + while (getClient().isRunning()) { + if (handleTunDevice()) { + break; + } } - spdlog::info("stop thread: tun"); + getClient().shutdown(); + spdlog::debug("stop thread: tun"); if (down()) { spdlog::critical("tun down failed"); - Candy::shutdown(this->client); return; } }); + + return 0; } -void Tun::handleSysRt(Msg msg) { +int Tun::handleSysRt(Msg msg) { SysRouteEntry *rt = (SysRouteEntry *)msg.data.data(); if (rt->nexthop != getIP()) { spdlog::info("route: {}/{} via {}", rt->dst.toString(), rt->mask.toPrefix(), rt->nexthop.toString()); - setSysRtTable(*rt); + if (setSysRtTable(*rt)) { + return -1; + } } + return 0; } int Tun::setSysRtTable(const SysRouteEntry &entry) { @@ -148,4 +161,8 @@ int Tun::setSysRtTable(const SysRouteEntry &entry) { return setSysRtTable(entry.dst, entry.mask, entry.nexthop); } -} // namespace Candy +Client &Tun::getClient() { + return *this->client; +} + +} // namespace candy diff --git a/src/tun/tun.h b/candy/src/tun/tun.h similarity index 81% rename from src/tun/tun.h rename to candy/src/tun/tun.h index b5a682a6..81738c16 100644 --- a/src/tun/tun.h +++ b/candy/src/tun/tun.h @@ -10,7 +10,7 @@ #include #include -namespace Candy { +namespace candy { class Client; @@ -23,7 +23,7 @@ class Tun { int setMTU(int mtu); int run(Client *client); - int shutdown(); + int wait(); IP4 getIP(); @@ -31,13 +31,13 @@ class Tun { int setAddress(const std::string &cidr); // 处理来自 TUN 设备的数据 - void handleTunDevice(); + int handleTunDevice(); // 处理来自消息队列的数据 - void handleTunQueue(); - void handlePacket(Msg msg); - void handleTunAddr(Msg msg); - void handleSysRt(Msg msg); + int handleTunQueue(); + int handlePacket(Msg msg); + int handleTunAddr(Msg msg); + int handleSysRt(Msg msg); std::string tunAddress; std::thread tunThread; @@ -60,9 +60,10 @@ class Tun { std::any impl; private: + Client &getClient(); Client *client; }; -} // namespace Candy +} // namespace candy #endif diff --git a/src/tun/unknown.cc b/candy/src/tun/unknown.cc similarity index 94% rename from src/tun/unknown.cc rename to candy/src/tun/unknown.cc index b493e65a..c04ea6a7 100644 --- a/src/tun/unknown.cc +++ b/candy/src/tun/unknown.cc @@ -5,7 +5,7 @@ #include "tun/tun.h" -namespace Candy { +namespace candy { Tun::Tun() {} @@ -43,6 +43,6 @@ int Tun::setSysRtTable(IP4 dst, IP4 mask, IP4 nexthop) { return -1; } -} // namespace Candy +} // namespace candy #endif diff --git a/src/tun/windows.cc b/candy/src/tun/windows.cc similarity index 96% rename from src/tun/windows.cc rename to candy/src/tun/windows.cc index 74864792..ecde72ab 100644 --- a/src/tun/windows.cc +++ b/candy/src/tun/windows.cc @@ -29,7 +29,7 @@ #include #pragma GCC diagnostic pop -namespace { +namespace candy { WINTUN_CREATE_ADAPTER_FUNC *WintunCreateAdapter; WINTUN_CLOSE_ADAPTER_FUNC *WintunCloseAdapter; @@ -95,12 +95,12 @@ class WindowsTun { return 0; } - int setIP(Candy::IP4 ip) { + int setIP(IP4 ip) { this->ip = ip; return 0; } - Candy::IP4 getIP() { + IP4 getIP() { return this->ip; } @@ -125,7 +125,7 @@ class WindowsTun { unsigned char hash[SHA256_DIGEST_LENGTH]; SHA256((unsigned char *)data.c_str(), data.size(), hash); memcpy(&Guid, hash, sizeof(Guid)); - this->adapter = WintunCreateAdapter(Candy::UTF8ToUTF16(this->name).c_str(), L"Candy", &Guid); + this->adapter = WintunCreateAdapter(UTF8ToUTF16(this->name).c_str(), L"Candy", &Guid); if (!this->adapter) { spdlog::critical("create wintun adapter failed: {}", GetLastError()); return -1; @@ -220,7 +220,7 @@ class WindowsTun { return -1; } - int setSysRtTable(Candy::IP4 dst, Candy::IP4 mask, Candy::IP4 nexthop) { + int setSysRtTable(IP4 dst, IP4 mask, IP4 nexthop) { MIB_IPFORWARDROW route; route.dwForwardDest = dst; @@ -250,7 +250,7 @@ class WindowsTun { private: std::string name; - Candy::IP4 ip; + IP4 ip; uint32_t prefix; int mtu; int timeout; @@ -261,9 +261,9 @@ class WindowsTun { WINTUN_SESSION_HANDLE session = NULL; }; -} // namespace +} // namespace candy -namespace Candy { +namespace candy { Tun::Tun() { this->impl = std::make_shared(); @@ -344,6 +344,6 @@ int Tun::setSysRtTable(IP4 dst, IP4 mask, IP4 nexthop) { return tun->setSysRtTable(dst, mask, nexthop); } -} // namespace Candy +} // namespace candy #endif diff --git a/candy/src/utils/atomic.h b/candy/src/utils/atomic.h new file mode 100644 index 00000000..2ffed2b6 --- /dev/null +++ b/candy/src/utils/atomic.h @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: MIT +#ifndef CANDY_UTILS_ATOMIC_H +#define CANDY_UTILS_ATOMIC_H + +#include + +namespace candy { +namespace Utils { + +template class Atomic { +public: + explicit Atomic(T initial = T()) : value(initial) {} + + T load() const { + std::lock_guard lock(mutex); + return value; + } + + void store(T new_value) { + std::lock_guard lock(mutex); + value = new_value; + cv.notify_all(); + } + + void wait(const T &expected) { + std::unique_lock lock(mutex); + cv.wait(lock, [this, &expected] { return value != expected; }); + } + + template void wait_until(Predicate pred) { + std::unique_lock lock(mutex); + cv.wait(lock, pred); + } + + void notify_one() { + std::lock_guard lock(mutex); + cv.notify_one(); + } + + void notify_all() { + std::lock_guard lock(mutex); + cv.notify_all(); + } + +private: + T value; + mutable std::mutex mutex; + std::condition_variable cv; +}; + +} // namespace Utils +} // namespace candy + +#endif diff --git a/src/utils/codecvt.cc b/candy/src/utils/codecvt.cc similarity index 96% rename from src/utils/codecvt.cc rename to candy/src/utils/codecvt.cc index 77213ca0..27a6b592 100644 --- a/src/utils/codecvt.cc +++ b/candy/src/utils/codecvt.cc @@ -3,7 +3,7 @@ #include "utils/codecvt.h" #include -namespace Candy { +namespace candy { std::string UTF16ToUTF8(const std::wstring &utf16Str) { if (utf16Str.empty()) @@ -39,6 +39,6 @@ std::wstring UTF8ToUTF16(const std::string &utf8Str) { return utf16Str; } -} // namespace Candy +} // namespace candy #endif diff --git a/src/utils/codecvt.h b/candy/src/utils/codecvt.h similarity index 85% rename from src/utils/codecvt.h rename to candy/src/utils/codecvt.h index 64002f72..35d542f3 100644 --- a/src/utils/codecvt.h +++ b/candy/src/utils/codecvt.h @@ -4,11 +4,11 @@ #include -namespace Candy { +namespace candy { std::string UTF16ToUTF8(const std::wstring &utf16Str); std::wstring UTF8ToUTF16(const std::string &utf8Str); -} // namespace Candy +} // namespace candy #endif diff --git a/src/utils/random.cc b/candy/src/utils/random.cc similarity index 94% rename from src/utils/random.cc rename to candy/src/utils/random.cc index 841fc59f..9998a39f 100644 --- a/src/utils/random.cc +++ b/candy/src/utils/random.cc @@ -14,7 +14,7 @@ int randomHex() { } } // namespace -namespace Candy { +namespace candy { uint32_t randomUint32() { std::random_device device; @@ -31,4 +31,4 @@ std::string randomHexString(int length) { return ss.str(); } -} // namespace Candy +} // namespace candy diff --git a/src/utils/random.h b/candy/src/utils/random.h similarity index 84% rename from src/utils/random.h rename to candy/src/utils/random.h index 8625b5fd..053f99f5 100644 --- a/src/utils/random.h +++ b/candy/src/utils/random.h @@ -5,11 +5,11 @@ #include #include -namespace Candy { +namespace candy { uint32_t randomUint32(); std::string randomHexString(int length); -} // namespace Candy +} // namespace candy #endif diff --git a/candy/src/utils/time.cc b/candy/src/utils/time.cc new file mode 100644 index 00000000..e2915d4e --- /dev/null +++ b/candy/src/utils/time.cc @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: MIT +#include "utils/time.h" +#include "core/net.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace candy { + +int64_t unixTime() { + using namespace std::chrono; + return duration_cast(system_clock::now().time_since_epoch()).count(); +} + +int64_t bootTime() { + using namespace std::chrono; + auto now = steady_clock::now(); + return duration_cast(now.time_since_epoch()).count(); +} + +std::string getCurrentTimeWithMillis() { + auto now = std::chrono::system_clock::now(); + + auto ms_tp = std::chrono::time_point_cast(now); + auto epoch = ms_tp.time_since_epoch(); + auto value = std::chrono::duration_cast(epoch).count(); + + std::time_t now_time_t = std::chrono::system_clock::to_time_t(now); + std::tm *ptm = std::localtime(&now_time_t); + + std::ostringstream oss; + oss << std::put_time(ptm, "%Y-%m-%d %H:%M:%S"); + oss << '.' << std::setfill('0') << std::setw(3) << (value % 1000); + + return oss.str(); +} + +} // namespace candy diff --git a/src/utils/time.h b/candy/src/utils/time.h similarity index 69% rename from src/utils/time.h rename to candy/src/utils/time.h index c1f342f5..d13e945c 100644 --- a/src/utils/time.h +++ b/candy/src/utils/time.h @@ -5,16 +5,13 @@ #include #include -namespace Candy { - -extern bool useSystemTime; -extern std::string ntpServer; +namespace candy { int64_t unixTime(); int64_t bootTime(); std::string getCurrentTimeWithMillis(); -} // namespace Candy +} // namespace candy #endif diff --git a/src/websocket/client.cc b/candy/src/websocket/client.cc similarity index 94% rename from src/websocket/client.cc rename to candy/src/websocket/client.cc index 97cf3133..3a7b2c04 100644 --- a/src/websocket/client.cc +++ b/candy/src/websocket/client.cc @@ -16,7 +16,7 @@ #include #include -namespace Candy { +namespace candy { int WebSocketClient::setName(const std::string &name) { this->name = name; @@ -48,9 +48,8 @@ int WebSocketClient::setVirtualMac(const std::string &vmac) { return 0; } -int WebSocketClient::setTunUpdateCallback(std::function callback) { - this->addressUpdateCallback = callback; - return 0; +std::string WebSocketClient::getTunCidr() const { + return this->tunCidr; } int WebSocketClient::run(Client *client) { @@ -58,7 +57,6 @@ int WebSocketClient::run(Client *client) { if (connect()) { spdlog::critical("websocket client connect failed"); - Candy::shutdown(this->client); return -1; } @@ -70,35 +68,35 @@ int WebSocketClient::run(Client *client) { } this->msgThread = std::thread([&] { - spdlog::info("start thread: websocket client msg"); - while (this->client->running) { + spdlog::debug("start thread: websocket client msg"); + while (getClient().isRunning()) { handleWsQueue(); } - spdlog::info("stop thread: websocket client msg"); + getClient().shutdown(); + spdlog::debug("stop thread: websocket client msg"); }); this->wsThread = std::thread([&] { - spdlog::info("start thread: websocket client ws"); - while (this->client->running) { + spdlog::debug("start thread: websocket client ws"); + while (getClient().isRunning()) { if (handleWsConn()) { - Candy::shutdown(this->client); break; } } - spdlog::info("stop thread: websocket client ws"); + getClient().shutdown(); + spdlog::debug("stop thread: websocket client ws"); }); return 0; } -int WebSocketClient::shutdown() { +int WebSocketClient::wait() { if (this->msgThread.joinable()) { this->msgThread.join(); } if (this->wsThread.joinable()) { this->wsThread.join(); } - return 0; } @@ -340,9 +338,6 @@ void WebSocketClient::sendAuthMsg() { sendFrame(&buffer, sizeof(buffer)); this->client->getTunMsgQueue().write(Msg(MsgKind::TUNADDR, address.toCidr())); this->client->getPeerMsgQueue().write(Msg(MsgKind::TUNADDR, address.toCidr())); - if (addressUpdateCallback) { - addressUpdateCallback(address.toCidr()); - } sendPingMessage(); } @@ -417,4 +412,8 @@ int WebSocketClient::disconnect() { return 0; } -} // namespace Candy +Client &WebSocketClient::getClient() { + return *this->client; +} + +} // namespace candy diff --git a/src/websocket/client.h b/candy/src/websocket/client.h similarity index 94% rename from src/websocket/client.h rename to candy/src/websocket/client.h index fdefa38e..8c0c4afc 100644 --- a/src/websocket/client.h +++ b/candy/src/websocket/client.h @@ -10,7 +10,7 @@ #include #include -namespace Candy { +namespace candy { class Client; @@ -24,8 +24,10 @@ class WebSocketClient { int setVirtualMac(const std::string &vmac); int setTunUpdateCallback(std::function callback); + std::string getTunCidr() const; + int run(Client *client); - int shutdown(); + int wait(); private: void handleWsQueue(); @@ -74,9 +76,11 @@ class WebSocketClient { std::string vmac; std::string name; std::string password; + + Client &getClient(); Client *client; }; -} // namespace Candy +} // namespace candy #endif diff --git a/src/websocket/message.cc b/candy/src/websocket/message.cc similarity index 90% rename from src/websocket/message.cc rename to candy/src/websocket/message.cc index cebf7dc4..7093d5a6 100644 --- a/src/websocket/message.cc +++ b/candy/src/websocket/message.cc @@ -2,7 +2,7 @@ #include "websocket/message.h" #include "utils/time.h" -namespace Candy { +namespace candy { namespace WsMsg { Auth::Auth(IP4 ip) { @@ -20,21 +20,17 @@ void Auth::updateHash(const std::string &password) { } bool Auth::check(const std::string &password) { - // 检查时间 int64_t localTime = unixTime(); int64_t remoteTime = ntoh(this->timestamp); - if (std::abs(localTime - remoteTime) > 30) { + if (std::abs(localTime - remoteTime) > 300) { spdlog::warn("auth header timestamp check failed: server {} client {}", localTime, remoteTime); } - // 备份上报的数据 uint8_t reported[SHA256_DIGEST_LENGTH]; std::memcpy(reported, this->hash, SHA256_DIGEST_LENGTH); - // 用口令计算正确的哈希并填充 updateHash(password); - // 检查上报的哈希和填充的哈希是否相等 if (std::memcmp(reported, this->hash, SHA256_DIGEST_LENGTH)) { spdlog::warn("auth header hash check failed"); return false; @@ -62,9 +58,8 @@ void ExptTun::updateHash(const std::string &password) { bool ExptTun::check(const std::string &password) { int64_t localTime = unixTime(); int64_t remoteTime = ntoh(this->timestamp); - if (std::abs(localTime - remoteTime) > 30) { + if (std::abs(localTime - remoteTime) > 300) { spdlog::warn("expected address header timestamp check failed: server {} client {}", localTime, remoteTime); - return false; } uint8_t reported[SHA256_DIGEST_LENGTH]; @@ -104,9 +99,8 @@ void VMac::updateHash(const std::string &password) { bool VMac::check(const std::string &password) { int64_t localTime = unixTime(); int64_t remoteTime = ntoh(this->timestamp); - if (std::abs(localTime - remoteTime) > 30) { + if (std::abs(localTime - remoteTime) > 300) { spdlog::warn("vmac message timestamp check failed: server {} client {}", localTime, remoteTime); - return false; } uint8_t reported[SHA256_DIGEST_LENGTH]; @@ -134,4 +128,4 @@ ConnLocal::ConnLocal() { this->ge.extra = 0; } } // namespace WsMsg -} // namespace Candy +} // namespace candy diff --git a/src/websocket/message.h b/candy/src/websocket/message.h similarity index 98% rename from src/websocket/message.h rename to candy/src/websocket/message.h index 73a2a62b..f1a6992b 100644 --- a/src/websocket/message.h +++ b/candy/src/websocket/message.h @@ -5,7 +5,7 @@ #include "core/net.h" #include -namespace Candy { +namespace candy { namespace WsMsgKind { constexpr uint8_t AUTH = 0; @@ -108,6 +108,6 @@ struct __attribute__((packed)) ConnLocal { }; } // namespace WsMsg -} // namespace Candy +} // namespace candy #endif diff --git a/src/websocket/server.cc b/candy/src/websocket/server.cc similarity index 98% rename from src/websocket/server.cc rename to candy/src/websocket/server.cc index 16ed7ead..459a20ec 100644 --- a/src/websocket/server.cc +++ b/candy/src/websocket/server.cc @@ -62,7 +62,7 @@ class HTTPRequestHandlerFactory : public Poco::Net::HTTPRequestHandlerFactory { }; // namespace -namespace Candy { +namespace candy { void WsCtx::sendFrame(const std::string &frame, int flags) { this->ws->sendFrame(frame.data(), frame.size(), flags); @@ -465,18 +465,16 @@ void WebSocketServer::updateSysRoute(WsCtx &ctx) { int WebSocketServer::listen() { try { - // 设置监听的地址和端口 Poco::Net::ServerSocket socket(Poco::Net::SocketAddress(host, port)); - // 设置最多同时可以处理的客户端数为局域网最大主机数 Poco::Net::HTTPServerParams *params = new Poco::Net::HTTPServerParams(); params->setMaxThreads(0x00FFFFFF); - // 创建 HTTP 服务端并启动 this->running = true; WebSocketHandler wsHandler = [this](Poco::Net::WebSocket &ws) { handleWebsocket(ws); }; this->httpServer = std::make_shared(new HTTPRequestHandlerFactory(wsHandler), socket, params); this->httpServer->start(); + spdlog::info("listen on: {}:{}", host, port); return 0; } catch (std::exception &e) { spdlog::critical("listen failed: {}", e.what()); @@ -540,4 +538,4 @@ void WebSocketServer::handleWebsocket(Poco::Net::WebSocket &ws) { } } -} // namespace Candy +} // namespace candy diff --git a/src/websocket/server.h b/candy/src/websocket/server.h similarity index 98% rename from src/websocket/server.h rename to candy/src/websocket/server.h index cbbf4bee..945b2767 100644 --- a/src/websocket/server.h +++ b/candy/src/websocket/server.h @@ -10,7 +10,7 @@ #include #include -namespace Candy { +namespace candy { struct WsCtx { Poco::Net::WebSocket *ws; @@ -77,6 +77,6 @@ class WebSocketServer { std::shared_ptr httpServer; }; -} // namespace Candy +} // namespace candy #endif diff --git a/dockerfile b/dockerfile index aea4ddd9..2c6a3e05 100644 --- a/dockerfile +++ b/dockerfile @@ -3,7 +3,7 @@ RUN apk update RUN apk add spdlog openssl poco FROM base AS build -RUN apk add git cmake ninja pkgconf g++ spdlog-dev openssl-dev poco-dev linux-headers +RUN apk add git cmake ninja pkgconf g++ spdlog-dev openssl-dev poco-dev nlohmann-json linux-headers COPY . candy RUN cd candy && cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release && cmake --build build && cmake --install build diff --git a/docs/CNAME b/docs/CNAME deleted file mode 100644 index 10997796..00000000 --- a/docs/CNAME +++ /dev/null @@ -1 +0,0 @@ -docs.canets.org diff --git a/docs/_config.yml b/docs/_config.yml deleted file mode 100644 index 22adff1e..00000000 --- a/docs/_config.yml +++ /dev/null @@ -1 +0,0 @@ -title: Candy diff --git a/docs/deploy-cli-server.md b/docs/deploy-cli-server.md deleted file mode 100644 index ab50149b..00000000 --- a/docs/deploy-cli-server.md +++ /dev/null @@ -1,5 +0,0 @@ -# 部署 CLI 服务端 - -根据帮助信息 `candy --help` 和配置文件描述部署. - -非专业用户请[部署 Web 服务端](https://docs.canets.org/deploy-web-server). diff --git a/docs/deploy-web-server.md b/docs/deploy-web-server.md deleted file mode 100644 index ffd8828a..00000000 --- a/docs/deploy-web-server.md +++ /dev/null @@ -1,54 +0,0 @@ -# 部署 Web 服务端 - -## 前置条件 - -知道如何部署 Web 服务,并能够申请证书后对外提供 HTTPS 服务. - -否则使用明文传输将导致数据泄漏,存在安全隐患.此时建议使用社区服务器构建私有网络. - -## 一键部署服务端 - -```bash -docker run --name=cacao --detach --volume /var/lib/cacao:/var/lib/cacao --publish 8080:80 docker.io/lanthora/cacao:latest -``` - -## 使用 - -假设你的域名为 `example.com`, 此时通过 `https://example.com` 应该能够正常访问服务.如果不是 `https` 请回到最开始解决前置条件. - -服务器启动后的第一个注册用户默认被设置为管理员.管理员无法创建网络,且无权查看其他用户的网络. - -管理员配置页面,能够配置是否允许注册,以及允许注册时的注册间隔(避免脚本小子刷注册用户).同时可以配置自动清理不活跃用户. - -![](images/cacao-admin-setting.png) - -### 单网络模式 - -在不允许注册时,管理员可以手动添加用户.其中名为 @ 的用户是一个特殊用户,这个用户只能创建一个名为 @ 的网络.用户名和网络名的作用在后面说明.先创建这个用户. - -![](images/cacao-admin-user.png) - -退出管理员,并以 @ 用户登录.此时已经默认添加了 @ 网络.默认网络生成了随机密码 `ZrhaUcz1` - -![](images/cacao-network.png) - -此时连接这个网络的客户端仅需要修改以下配置: - -```cfg -websocket = "wss://example.com" -password = "ZrhaUcz1" -``` - -除非你知道自己在做什么,否则请不要修改任何其他配置项. - -### 多用户多网络模式 - -如果只是创建一个网络,单网络模式已经足够了.如果要允许多个用户使用,且每个用户可以创建多个网络.则可以使用多用户多网络模式. - -假设由管理员创建或自行注册的普通用户名为 `${username}`, 这个用户拥有的一个网络名是 `${netname}`,那么客户端对应的配置仅需要修改为: - -```cfg -websocket = "wss://example.com/${username}/${netname}" -``` - -当用户名或者网络名为 @ 时,在客户端的配置中需要留空.当用户名和网络名都为空时,就是所谓的单网络模式 diff --git a/docs/images/alipay.png b/docs/images/alipay.png deleted file mode 100644 index 1a553917..00000000 Binary files a/docs/images/alipay.png and /dev/null differ diff --git a/docs/images/cacao-admin-setting.png b/docs/images/cacao-admin-setting.png deleted file mode 100644 index d5e9b50a..00000000 Binary files a/docs/images/cacao-admin-setting.png and /dev/null differ diff --git a/docs/images/cacao-admin-user.png b/docs/images/cacao-admin-user.png deleted file mode 100644 index e26a54e3..00000000 Binary files a/docs/images/cacao-admin-user.png and /dev/null differ diff --git a/docs/images/cacao-network-another.png b/docs/images/cacao-network-another.png deleted file mode 100644 index 28c579c6..00000000 Binary files a/docs/images/cacao-network-another.png and /dev/null differ diff --git a/docs/images/cacao-network.png b/docs/images/cacao-network.png deleted file mode 100644 index 75b6ddf3..00000000 Binary files a/docs/images/cacao-network.png and /dev/null differ diff --git a/docs/images/cacao-register.png b/docs/images/cacao-register.png deleted file mode 100644 index a45bc353..00000000 Binary files a/docs/images/cacao-register.png and /dev/null differ diff --git a/docs/images/cake.png b/docs/images/cake.png deleted file mode 100644 index ad256605..00000000 Binary files a/docs/images/cake.png and /dev/null differ diff --git a/docs/images/sdwan.png b/docs/images/sdwan.png deleted file mode 100644 index 8b932ca1..00000000 Binary files a/docs/images/sdwan.png and /dev/null differ diff --git a/docs/images/wechat.png b/docs/images/wechat.png deleted file mode 100644 index e5df8486..00000000 Binary files a/docs/images/wechat.png and /dev/null differ diff --git a/docs/index.md b/docs/index.md deleted file mode 100644 index 5c0fc159..00000000 --- a/docs/index.md +++ /dev/null @@ -1,32 +0,0 @@ -# Candy - -

- - - - - -

- -一个高可用,低时延,反审查的组网工具. - -## 如何使用 - -- [安装 Windows 客户端](install-client-for-windows) -- [安装 macOS 客户端](install-client-for-macos) -- [安装 Linux 客户端](install-client-for-linux) -- [部署 Web 服务端](deploy-web-server) -- [部署 CLI 服务端](deploy-cli-server) -- [使用社区服务器](use-the-community-server) -- [多局域网组网](software-defined-wide-area-network) - -## 相关项目 - -- [Cacao](https://github.com/lanthora/cacao): WebUI 版的 Candy 服务器 -- [Cake](https://github.com/lanthora/cake): Qt 实现的 Candy GUI 桌面应用程序 -- [EasyTier](https://github.com/EasyTier/EasyTier): 一个简单、安全、去中心化的内网穿透 VPN 组网方案,使用 Rust 语言和 Tokio 框架实现 - -## 交流群 - -- QQ: 768305206 -- TG: [Click to Join](https://t.me/CandyUserGroup) diff --git a/docs/install-client-for-linux.md b/docs/install-client-for-linux.md deleted file mode 100644 index b1003526..00000000 --- a/docs/install-client-for-linux.md +++ /dev/null @@ -1,89 +0,0 @@ -# 安装 Linux 客户端 - -我们针对不同 Linux 发行版提供了多种格式的安装包.对于暂未支持的发行版,可以选择容器部署或者静态链接的可执行文件. -我们致力于支持所有架构的 Linux 系统. - -## Docker - -镜像已上传 [Docker Hub](https://hub.docker.com/r/lanthora/candy) 和 [Github Packages](https://github.com/lanthora/candy/pkgs/container/candy). - -获取最新镜像 - -```bash -docker pull docker.io/lanthora/candy:latest -``` - -容器需要管理员权限读取设备创建虚拟网卡并设置路由,同时需要 Host 网络命名空间共享虚拟网卡. - -以默认配置文件启动将加入社区网络.指定的参数为 `--rm` 当进程结束时会自动销毁容器,且日志会在控制台输出,这有利于初次运行调试. - -```bash -docker run --rm --privileged=true --net=host --volume /var/lib/candy:/var/lib/candy docker.io/lanthora/candy:latest -``` - -以自定义配置文件启动.请在[默认配置](https://raw.githubusercontent.com/lanthora/candy/refs/heads/master/candy.cfg)基础上自定义配置文件. - -```bash -docker run --rm --privileged=true --net=host --volume /var/lib/candy:/var/lib/candy --volume /path/to/candy.cfg:/etc/candy.cfg docker.io/lanthora/candy:latest -``` - -一切正常后,以守护进程的形式启动. - -```bash -docker run --detach --restart=always --privileged=true --net=host --volume /var/lib/candy:/var/lib/candy --volume /path/to/candy.cfg:/etc/candy.cfg docker.io/lanthora/candy:latest -``` - -## Arch Linux - -使用 [AUR](https://aur.archlinux.org/packages/candy) 或者 [archlinuxcn](https://github.com/archlinuxcn/repo/tree/master/archlinuxcn/candy) 仓库 - -```bash -# AUR -paru candy -# archlinuxcn -pacman -S candy -``` - -## Gentoo - -```bash -emerge --sync gentoo && emerge -av candy -``` - -## 单文件可执行程序 - -当上述所有方式都不适用时,尝试[单文件可执行程序](https://github.com/lanthora/candy/releases/latest). - -该程序由[交叉编译脚本](https://github.com/lanthora/candy/tree/master/scripts/build-standalone.sh)构建. - -如果你的系统在使用 Systemd 管理进程.请复制以下文件到指定目录. - -```bash -cp candy.service /usr/lib/systemd/system/candy.service -cp candy@.service /usr/lib/systemd/system/candy@.service -cp candy.cfg /etc/candy.cfg -``` - -然后按照后续进程管理的方式管理进程. - -判断 Systemd 的方法: 检查 `ps -p 1 -o comm=` 输出的内容里是否为 systemd - -## 进程管理 - -各发行版安装后自带 Service 文件,强烈建议使用 Systemd 管理进程,不要使用自己编写的脚本. - -对于自定义配置的用户,可以通过以下方式启动进程,不要修改默认配置. - -```bash -mkdir /etc/candy.d -# 复制一份默认配置,并修改.文件名为 one.cfg -cp /etc/candy.cfg /etc/candy.d/one.cfg -# 以 one.cfg 为配置启动进程 -systemctl start candy@one - -# 复制一份默认配置,并修改.文件名为 two.cfg -# 需要注意不同配置文件中的 name 字段不能重复 -cp /etc/candy.cfg /etc/candy.d/two.cfg -# 以 two.cfg 为配置启动进程 -systemctl start candy@two -``` diff --git a/docs/install-client-for-macos.md b/docs/install-client-for-macos.md deleted file mode 100644 index 8d019d7f..00000000 --- a/docs/install-client-for-macos.md +++ /dev/null @@ -1,39 +0,0 @@ -# 安装 macOS 客户端 - -macOS 客户端通过 [Homebrew](https://brew.sh) 安装并提供服务. - -## 安装 Homebrew - -```bash -/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" -``` - -## 添加第三方仓库 - -```bash -brew tap lanthora/repo -``` - -## 安装 Candy - -```bash -brew install candy -``` - -## 修改配置 - -对于 M 系列处理器,配置文件在 `/opt/homebrew/etc/candy.cfg`, Intel 系列处理器,配置文件在 `/usr/local/etc/candy.cfg` - -通过以下命令进行测试: - -```bash -sudo candy -c /path/to/candy.cfg -``` - -## 启动服务 - -测试成功后以服务的形式运行. - -```bash -sudo brew services start lanthora/repo/candy -``` diff --git a/docs/install-client-for-windows.md b/docs/install-client-for-windows.md deleted file mode 100644 index 41cce81a..00000000 --- a/docs/install-client-for-windows.md +++ /dev/null @@ -1,19 +0,0 @@ -# 安装 Windows 客户端 - -## 图形用户界面 - -对于 Windows 10 及以上的用户,请使用[图形界面版本](https://github.com/lanthora/cake/releases/latest).此版本支持同时配置多个网络. - -在没有任何配置时,点击 "文件" => "新建" 将填充默认配置,点击 "保存" 后配置生效,客户端此时才开始连接服务端. - -图形界面的配置与[默认配置](https://raw.githubusercontent.com/lanthora/candy/refs/heads/master/candy.cfg)对应. - -日志保存在 `C:/ProgramData/Cake/logs`, 反馈 Windows 相关问题请带着日志和配置截图. - -![cake](images/cake.png) - -## 命令行 - -使用命令行版本请自行解决遇到的任何问题,我们不对 Windows 命令行提供任何技术支持. - -Windows 7 用户只能使用[命令行版本](https://github.com/lanthora/candy/releases/latest) diff --git a/docs/software-defined-wide-area-network.md b/docs/software-defined-wide-area-network.md deleted file mode 100644 index 86d26da0..00000000 --- a/docs/software-defined-wide-area-network.md +++ /dev/null @@ -1,122 +0,0 @@ -# 多局域网组网 - -## 需求 - -在多地有多个局域网时,希望能够让本局域网内的设备通过其他局域网的地址直接访问对方的设备,并且无需在所有设备上部署 Candy 客户端. - -## 示例 - -首先你需要: - -- 有一个独立的网络.可以自建服务端或者使用社区服务器 -- 在网关 (Gateway) 上部署 Candy 并成功加入自己创建的网络 - -以 LAN A 为例解释表格含义. - -- 局域网 (Network) 地址为 `172.16.1.0/24`, 这个地址不能与 B,C 冲突 -- 网关 (Gateway) 可以是路由器,也可以是局域网中任意一台 Linux 系统,但需要能够部署 Candy 客户端,假设它在局域网中的地址是 `172.16.1.1`. 通过给局域网中的设备配置路由,确保流量能够进入网关 -- Candy 客户端部署在网关上,它在虚拟网络中的地址是 `192.168.202.1` - -| LAN | A | B | C | -| :------ | :------------ | :------------ | :------------ | -| Network | 172.16.1.0/24 | 172.16.2.0/24 | 172.16.3.0/24 | -| Gateway | 172.16.1.1 | 172.16.2.1 | 172.16.3.1 | -| Candy | 192.168.202.1 | 192.168.202.2 | 192.168.202.3 | - -当 `172.16.1.0/24` 的设备访问 `172.16.2.0/24` 的设备时,希望流量可以通过以下方式送达: - -```txt -172.16.1.0/24 <=> 172.16.1.1 <=> 192.168.202.1 <=> 192.168.202.2 <=> 172.16.2.1 <=> 172.16.2.0/24 -``` - -### 流量转发到网关 (172.16.1.0/24 => 172.16.1.1) - -如果网关是路由器,不需要任何操作,流量就应该能够进入网关.否则需要在非网关设备上配置流量转发到网关的路由. - -给 172.16.1.0/24 的设备配置路由: - -- dst: 172.16.2.0/24; gw: 172.16.1.1 -- dst: 172.16.3.0/24; gw: 172.16.1.1 - -需要用同样的方式给另外两个局域网做配置. - -### 允许网关转发流量 (172.16.1.1 <=> 192.168.202.1) - -#### Linux - -如果你的网关是路由器,应该能够轻易的配置出允许转发.否则需要手动添加转发相关的配置. - -开启内核流量转发功能 - -```bash -sysctl -w net.ipv4.ip_forward=1 -``` - -开启动态伪装并接受转发报文. - -```bash -iptables -t nat -A POSTROUTING -j MASQUERADE -iptables -A FORWARD -j ACCEPT -``` - -#### Windows - -查看的网卡名,应该与配置文件中写的名称相同,对于 GUI 版本客户端的默认配置网卡名应该为 `candy` - -```ps -Get-NetAdapter -``` - -允许转发,注意要把网卡名替换成上一步查出来的网卡名 - -```ps -Set-NetIPInterface -ifAlias 'candy' -Forwarding Enabled -``` - -#### macOS - -应该不会有人拿 macOS 做网关吧, Windows 应该都没有多少人,有需要再补充这部分文档 - -### 创建虚拟链路 (172.16.1.0/24 <=> 172.16.2.0/24) - -所有 Candy 客户端 `192.168.202.0/24` 收到发往 `172.16.1.0/24` 的 IP 报文时,将其转发到 `192.168.202.1`; -所有 Candy 客户端 `192.168.202.0/24` 收到发往 `172.16.2.0/24` 的 IP 报文时,将其转发到 `192.168.202.2`; -所有 Candy 客户端 `192.168.202.0/24` 收到发往 `172.16.3.0/24` 的 IP 报文时,将其转发到 `192.168.202.3`; - -策略会发下给属于 `192.168.202.0/24` 网络的客户端,上面的配置下发给了虚拟网络中的所有设备,能够满足大部分用户场景. - -此外支持更细粒度的控制供用户选择,例如 `192.168.202.1/32` 就表示仅把路由策略下发给 `192.168.202.1` 这台设备. - -#### Cacao 配置 - -如果你在使用 Cacao 服务端(例如社区服务端),配置如下. - -![sdwan](images/sdwan.png) - -#### Candy 配置 - -如果你在使用命令行版本的 Candy 服务端,等效配置如下. - -```ini -sdwan = "192.168.202.0/24,172.16.1.0/24,192.168.202.1;192.168.202.0/24,172.16.2.0/24,192.168.202.2;192.168.202.0/24,172.16.3.0/24,192.168.202.3;" -``` - -### 测试 - -此时局域网设备之间应当可以相互 ping 通. - -## 常见问题 - -### 能 ping 通网关,但 ping 不通网关下的目标设备 - -- 检查 iptables 配置的动态伪装是否生效.如果生效,抓包可以看到发往目标设备的源地址已经改成了网关地址 -- 检查目标设备防火墙.例如 Windows 系统防火墙默认禁止 ping, 此时直接尝试访问 Windows 提供出的服务,例如远程桌面, SSH, Web 服务等 - -### 能 ping 通目标设备,但不能访问服务 - -- 检查 iptables 配置的动态伪装是否生效.动态伪装不生效的情况下,某种路由配置规则也可以实现 ping 通目标设备,但是防火墙会拦截对应报文. - -### 关于源进源出 - -通过合理的路由配置和对防火墙策略的调整,在不使用动态伪装的情况下,可以做到在目标设备看到请求的真实源地址.想要达成这个效果需要有足够的计算机网络知识储备,请自行探索. - diff --git a/docs/use-the-community-server.md b/docs/use-the-community-server.md deleted file mode 100644 index 462acb5c..00000000 --- a/docs/use-the-community-server.md +++ /dev/null @@ -1,45 +0,0 @@ -# 使用社区服务器 - -社区服务器支持用户级别的隔离,同时支持一个用户创建多个网络. - -__服务器将定期清理不活跃用户,请确保短期内至少有一台设备连接过服务器,或手动登录过服务器管理页面.__ - -## 注册 - -在社区服务器[注册](https://canets.org/register),示例中的用户名为 `username`. - -![](images/cacao-register.png) - -## 使用默认网络 - -查看网络,可以注意到已经有一个名称为 @ 的默认网络,密码是 `ZrhaUcz1` - -![](images/cacao-network.png) - -连接到这个网络的客户端仅需要修改以下配置,关于配置文件的位置请参考客户端安装的相关文档: - -```cfg -websocket = "wss://canets.org/username" -password = "ZrhaUcz1" -``` - -## 多个网络 - -点击左上角 `Add` 可以创建多个网络,例如: - -![](images/cacao-network-another.png) - -这个新网络,网络名为 `netname`, 这会体现到 `websocket` 参数中; 密码为空; 网络范围是 `10.0.0.0/24`; 不允许广播; 且租期为 3 天, 即超过 3 天不活跃的客户端将被自动从网络中移除, 配置为 0 时表示不自动移除. - -客户端的配置应该为: - -```cfg -websocket = "wss://canets.org/username/netname" -password = "" -``` - -如果要给某个客户端指定静态地址 `10.0.0.1/24`, 只需要修改配置中的: - -```cfg -tun = "10.0.0.1/24" -``` diff --git a/package/debian/README b/package/debian/README deleted file mode 100644 index f44c1419..00000000 --- a/package/debian/README +++ /dev/null @@ -1,6 +0,0 @@ -The Debian Package candy ----------------------------- - -common cmake package - - -- YangMame Tue, 12 Mar 2024 10:06:09 +0800 diff --git a/package/debian/candy.install b/package/debian/candy.install deleted file mode 100644 index 5a41fed9..00000000 --- a/package/debian/candy.install +++ /dev/null @@ -1,2 +0,0 @@ -candy.cfg etc -candy*.service usr/lib/systemd/system/ diff --git a/package/debian/control b/package/debian/control deleted file mode 100644 index 43a63c02..00000000 --- a/package/debian/control +++ /dev/null @@ -1,24 +0,0 @@ -Source: candy -Section: net -Priority: optional -Maintainer: YangMame -Rules-Requires-Root: no -Build-Depends: - debhelper-compat (= 13), - cmake, - pkg-config, - libssl-dev, - libpoco-dev, - libpcre2-dev, - libfmt-dev, - libspdlog-dev, -Standards-Version: 4.6.2 -Homepage: https://github.com/lanthora/candy -Vcs-Git: https://github.com/lanthora/candy.git - -Package: candy -Architecture: any -Depends: - ${shlibs:Depends}, - ${misc:Depends}, -Description: A simple networking tool diff --git a/package/debian/rules b/package/debian/rules deleted file mode 100755 index f1d1d256..00000000 --- a/package/debian/rules +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/make -f - -# See debhelper(7) (uncomment to enable). -# Output every command that modifies files on the build system. -#export DH_VERBOSE = 1 - - -# See FEATURE AREAS in dpkg-buildflags(1). -#export DEB_BUILD_MAINT_OPTIONS = hardening=+all - -# See ENVIRONMENT in dpkg-buildflags(1). -# Package maintainers to append CFLAGS. -#export DEB_CFLAGS_MAINT_APPEND = -Wall -pedantic -# Package maintainers to append LDFLAGS. -#export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed - - -%: - dh $@ - - -# dh_make generated override targets. -# This is an example for Cmake (see ). -#override_dh_auto_configure: -# dh_auto_configure -- \ -# -DCMAKE_LIBRARY_PATH=$(DEB_HOST_MULTIARCH) diff --git a/package/debian/source/format b/package/debian/source/format deleted file mode 100644 index 89ae9db8..00000000 --- a/package/debian/source/format +++ /dev/null @@ -1 +0,0 @@ -3.0 (native) diff --git a/package/openSUSE/candy.spec b/package/openSUSE/candy.spec deleted file mode 100644 index 507be4be..00000000 --- a/package/openSUSE/candy.spec +++ /dev/null @@ -1,72 +0,0 @@ -# -# spec file for package candy -# -# Copyright (c) 2024 SUSE LLC -# -# All modifications and additions to the file contributed by third parties -# remain the property of their copyright owners, unless otherwise agreed -# upon. The license for this file, and modifications and additions to the -# file, is the same license as for the pristine package itself (unless the -# license for the pristine package is not an Open Source License, in which -# case the license is the MIT License). An "Open Source License" is a -# license that conforms to the Open Source Definition (Version 1.9) -# published by the Open Source Initiative. - -# Please submit bugfixes or comments via https://bugs.opensuse.org/ -# - -Name: candy -Version: 9999 -Release: 0 -Summary: A simple networking tool -License: MIT -Group: Productivity/Networking/Security -URL: https://github.com/lanthora/candy -Source: candy.tar.gz -BuildRequires: cmake -BuildRequires: gcc14-c++ -BuildRequires: libopenssl-devel -BuildRequires: poco-devel -BuildRequires: pcre2-devel -BuildRequires: fmt-devel -BuildRequires: spdlog-devel -BuildRequires: systemd-rpm-macros -Requires: openssl - -%description -A simple networking tool - -%prep -%setup -q -n candy - -%build -%cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=g++-14 -%make_build - -%install -%cmake_install - -install -D -m 644 candy.cfg %{buildroot}/etc/candy.cfg -install -D -m 644 candy.service %{buildroot}%{_unitdir}/candy.service -install -D -m 644 candy@.service %{buildroot}%{_unitdir}/candy@.service - -%pre -%service_add_pre candy.service candy@.service - -%post -%service_add_post candy.service candy@.service - -%preun -%service_del_preun candy.service candy@.service - -%postun -%service_del_postun candy.service candy@.service - -%files -%doc README.md -%{_bindir}/candy -%config(noreplace) /etc/candy.cfg -%{_unitdir}/candy.service -%{_unitdir}/candy@.service - -%changelog diff --git a/scripts/build-standalone.sh b/scripts/build-standalone.sh index f19c0600..d655fdc0 100755 --- a/scripts/build-standalone.sh +++ b/scripts/build-standalone.sh @@ -64,7 +64,7 @@ if which ninja >/dev/null 2>&1;then GENERATOR="Ninja";else GENERATOR="Unix Makef SOURCE_DIR="$(dirname $(readlink -f "$0"))/../" cmake -G "$GENERATOR" -B "$BUILD_DIR" -DCMAKE_BUILD_TYPE=Release -DCANDY_STATIC=1 -DTARGET_OPENSSL=$TARGET_OPENSSL $SOURCE_DIR cmake --build $BUILD_DIR --parallel $(nproc) -mkdir -p $OUTPUT_DIR && cp $BUILD_DIR/src/main/candy $OUTPUT_DIR/candy +mkdir -p $OUTPUT_DIR && cp $BUILD_DIR/candy-cli/candy $OUTPUT_DIR/candy if [[ $CANDY_STRIP && $CANDY_STRIP -eq 1 ]];then $STRIP $OUTPUT_DIR/candy diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt deleted file mode 100644 index c71b3cf1..00000000 --- a/src/core/CMakeLists.txt +++ /dev/null @@ -1,29 +0,0 @@ -aux_source_directory(. DIR_LIB_SRCS) -add_library(core STATIC ${DIR_LIB_SRCS}) - -set_target_properties(core PROPERTIES CXX_STANDARD 17) - -if (${CANDY_STATIC_FMT}) - target_link_libraries(core PRIVATE fmt::fmt) -else() - pkg_check_modules(FMT REQUIRED fmt) - add_definitions(${FMT_CFLAGS}) - include_directories(${FMT_INCLUDEDIR}) - target_link_libraries(core PRIVATE ${FMT_LIBRARIES}) -endif() - -if (${CANDY_STATIC_SPDLOG}) - target_link_libraries(core PRIVATE spdlog::spdlog) -else() - target_link_libraries(core PRIVATE ${SPDLOG_LIBRARIES}) -endif() - -if (${CANDY_STATIC_OPENSSL}) - target_link_libraries(core PRIVATE ${OPENSSL_LIB_CRYPTO} ${OPENSSL_LIB_SSL}) -endif() - -target_link_libraries(core PRIVATE Poco::Foundation Poco::Net Poco::NetSSL) - -target_link_libraries(core PRIVATE websocket) -target_link_libraries(core PRIVATE tun) -target_link_libraries(core PRIVATE peer) diff --git a/src/ffi/CMakeLists.txt b/src/ffi/CMakeLists.txt deleted file mode 100644 index 310e6fb2..00000000 --- a/src/ffi/CMakeLists.txt +++ /dev/null @@ -1,28 +0,0 @@ -aux_source_directory(. DIR_SRCS) -add_library(${CANDY_LIBRARY_NAME} STATIC ${DIR_SRCS}) - -set_target_properties(${CANDY_LIBRARY_NAME} PROPERTIES OUTPUT_NAME ${CANDY_EXECUTE_NAME}) -set_target_properties(${CANDY_LIBRARY_NAME} PROPERTIES CXX_STANDARD 17) - -target_link_libraries(${CANDY_LIBRARY_NAME} PRIVATE core) -target_link_libraries(${CANDY_LIBRARY_NAME} PRIVATE websocket) -target_link_libraries(${CANDY_LIBRARY_NAME} PRIVATE tun) -target_link_libraries(${CANDY_LIBRARY_NAME} PRIVATE peer) -target_link_libraries(${CANDY_LIBRARY_NAME} PRIVATE utils) - -if (${CANDY_STATIC_SPDLOG}) - target_link_libraries(${CANDY_LIBRARY_NAME} PRIVATE spdlog::spdlog) -else() - target_link_libraries(${CANDY_LIBRARY_NAME} PRIVATE ${SPDLOG_LIBRARIES}) -endif() - -if (${CANDY_STATIC_OPENSSL}) - target_link_libraries(${CANDY_LIBRARY_NAME} PRIVATE ${OPENSSL_LIB_CRYPTO} ${OPENSSL_LIB_SSL}) -endif() - -target_link_libraries(${CANDY_LIBRARY_NAME} PRIVATE Poco::Foundation Poco::Net Poco::NetSSL) - -if (${CANDY_DEVEL}) - install(FILES candy.h DESTINATION include) - install(TARGETS ${CANDY_LIBRARY_NAME} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) -endif() diff --git a/src/ffi/candy.cc b/src/ffi/candy.cc deleted file mode 100644 index 8ca6421d..00000000 --- a/src/ffi/candy.cc +++ /dev/null @@ -1,136 +0,0 @@ -// SPDX-License-Identifier: MIT -#include "ffi/candy.h" -#include "core/client.h" -#include "utils/time.h" -#include -#include -#include - -void candy_init() {} - -void *candy_client_create() { - Candy::Client *c = new Candy::Client(); - return c; -} - -void candy_client_release(void *candy) { - Candy::Client *c = static_cast(candy); - delete c; -} - -void candy_client_set_name(void *candy, const char *name) { - Candy::Client *c = static_cast(candy); - c->setName(name); -} - -void candy_client_set_password(void *candy, const char *password) { - Candy::Client *c = static_cast(candy); - c->setPassword(password); -} - -void candy_client_set_websocket(void *candy, const char *server) { - Candy::Client *c = static_cast(candy); - c->setWebSocket(server); -} - -void candy_client_set_tun_address(void *candy, const char *cidr) { - Candy::Client *c = static_cast(candy); - c->setTunAddress(cidr); -} - -void candy_client_set_expt_tun_address(void *candy, const char *cidr) { - Candy::Client *c = static_cast(candy); - c->setExptTunAddress(cidr); -} - -void candy_client_set_virtual_mac(void *candy, const char *vmac) { - Candy::Client *c = static_cast(candy); - c->setVirtualMac(vmac); -} - -void candy_client_set_stun(void *candy, const char *stun) { - Candy::Client *c = static_cast(candy); - c->setStun(stun); -} - -void candy_client_set_discovery_interval(void *candy, int interval) { - Candy::Client *c = static_cast(candy); - c->setDiscoveryInterval(interval); -} - -void candy_client_set_route_cost(void *candy, int cost) { - Candy::Client *c = static_cast(candy); - c->setRouteCost(cost); -} - -void candy_client_set_mtu(void *candy, int mtu) { - Candy::Client *c = static_cast(candy); - c->setMtu(mtu); -} - -void candy_client_set_tun_update_callback(void *candy, void (*callback)(const char *, const char *)) { - Candy::Client *c = static_cast(candy); - return c->setTunUpdateCallback([=](const std::string &address) { - callback(c->getName().c_str(), address.c_str()); - return 0; - }); -} - -void candy_client_set_port(void *candy, int port) { - Candy::Client *c = static_cast(candy); - c->setPort(port); -} - -void candy_client_set_localhost(void *candy, const char *ip) { - Candy::Client *c = static_cast(candy); - c->setLocalhost(ip); -} - -void candy_client_run(void *candy) { - Candy::Client *c = static_cast(candy); - c->run(); -} - -void candy_client_shutdown(void *candy) { - Candy::Client *c = static_cast(candy); - c->shutdown(); -} - -namespace { -void (*client_error_cb)(void *) = NULL; -} - -namespace Candy { -void shutdown(Client *c) { - if (client_error_cb) { - client_error_cb(c); - } else { - exit(1); - } -} -} // namespace Candy - -void candy_client_set_error_cb(void (*callback)(void *)) { - client_error_cb = callback; -} - -void candy_use_system_time() { - Candy::useSystemTime = true; -} - -void candy_set_log_path(const char *path) { - auto max_size = 1048576 * 5; - auto max_files = 3; - auto logger = spdlog::rotating_logger_mt("candy", path, max_size, max_files, true); - spdlog::set_default_logger(logger); - spdlog::flush_every(std::chrono::seconds(1)); -} - -void candy_enable_debug() { - spdlog::set_level(spdlog::level::debug); -} - -void candy_release() { - spdlog::drop_all(); - spdlog::shutdown(); -} diff --git a/src/ffi/candy.h b/src/ffi/candy.h deleted file mode 100644 index c73ee787..00000000 --- a/src/ffi/candy.h +++ /dev/null @@ -1,37 +0,0 @@ -// SPDX-License-Identifier: MIT -#ifndef CANDY_FFI_CANDY_H -#define CANDY_FFI_CANDY_H - -#ifdef __cplusplus -extern "C" { -#endif - -void candy_init(); -void *candy_client_create(); -void candy_client_set_name(void *candy, const char *name); -void candy_client_set_password(void *candy, const char *password); -void candy_client_set_websocket(void *candy, const char *server); -void candy_client_set_tun_address(void *candy, const char *cidr); -void candy_client_set_expt_tun_address(void *candy, const char *cidr); -void candy_client_set_virtual_mac(void *candy, const char *vmac); -void candy_client_set_stun(void *candy, const char *stun); -void candy_client_set_discovery_interval(void *candy, int interval); -void candy_client_set_route_cost(void *candy, int cost); -void candy_client_set_mtu(void *candy, int mtu); -void candy_client_set_tun_update_callback(void *candy, void (*callback)(const char *, const char *)); -void candy_client_set_port(void *candy, int port); -void candy_client_set_localhost(void *candy, const char *ip); -void candy_client_set_error_cb(void (*callback)(void *)); -void candy_client_run(void *candy); -void candy_client_shutdown(void *candy); -void candy_client_release(void *candy); -void candy_use_system_time(); -void candy_set_log_path(const char *path); -void candy_enable_debug(); -void candy_release(); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/src/main/CMakeLists.txt b/src/main/CMakeLists.txt deleted file mode 100644 index b82f5d7e..00000000 --- a/src/main/CMakeLists.txt +++ /dev/null @@ -1,27 +0,0 @@ -aux_source_directory(. DIR_SRCS) -add_executable(${CANDY_EXECUTE_NAME} ${DIR_SRCS}) - -set_target_properties(${CANDY_EXECUTE_NAME} PROPERTIES CXX_STANDARD 17) - -if (${CANDY_STATIC_SPDLOG}) - target_link_libraries(${CANDY_EXECUTE_NAME} PRIVATE spdlog::spdlog) -else() - target_link_libraries(${CANDY_EXECUTE_NAME} PRIVATE ${SPDLOG_LIBRARIES}) -endif() - -if (${CANDY_STATIC_OPENSSL}) - target_link_libraries(${CANDY_EXECUTE_NAME} PRIVATE ${OPENSSL_LIB_CRYPTO} ${OPENSSL_LIB_SSL}) -endif() - -target_link_libraries(${CANDY_EXECUTE_NAME} PRIVATE Poco::Foundation Poco::Net Poco::NetSSL) - -set(THREADS_PREFER_PTHREAD_FLAG ON) -find_package(Threads REQUIRED) -target_link_libraries(${CANDY_EXECUTE_NAME} PRIVATE Threads::Threads) - -target_link_libraries(${CANDY_EXECUTE_NAME} PRIVATE core) -target_link_libraries(${CANDY_EXECUTE_NAME} PRIVATE utils) - -if (NOT ${CANDY_NOEXE}) - install(TARGETS ${CANDY_EXECUTE_NAME}) -endif() diff --git a/src/main/main.cc b/src/main/main.cc deleted file mode 100644 index 3acb9fb2..00000000 --- a/src/main/main.cc +++ /dev/null @@ -1,145 +0,0 @@ -// SPDX-License-Identifier: MIT -#include "core/client.h" -#include "core/server.h" -#include "main/config.h" -#include "utils/time.h" -#include -#include -#include -#include -#include -#include -#include - -template class Atomic { -public: - explicit Atomic(T initial = T()) : value_(initial) {} - - T load() const { - std::lock_guard lock(mutex_); - return value_; - } - - void store(T new_value) { - std::lock_guard lock(mutex_); - value_ = new_value; - cv_.notify_all(); - } - - void wait(const T &expected) { - std::unique_lock lock(mutex_); - cv_.wait(lock, [this, &expected] { return value_ != expected; }); - } - - template void wait_until(Predicate pred) { - std::unique_lock lock(mutex_); - cv_.wait(lock, pred); - } - - void notify_one() { - std::lock_guard lock(mutex_); - cv_.notify_one(); - } - - void notify_all() { - std::lock_guard lock(mutex_); - cv_.notify_all(); - } - -private: - T value_; - mutable std::mutex mutex_; - std::condition_variable cv_; -}; - -Atomic running(true); - -namespace Candy { - -void shutdown(Client *client) { - running.store(false); -} - -void shutdown(Server *server) { - running.store(false); -} - -} // namespace Candy - -int exitCode = 1; - -int serve(const arguments &args) { - - Poco::Net::initializeNetwork(); - - if (args.mode == "server") { - Candy::Server server; - server.setPassword(args.password); - server.setWebSocket(args.websocket); - server.setDHCP(args.dhcp); - server.setSdwan(args.sdwan); - server.run(); - running.wait(true); - server.shutdown(); - } - - if (args.mode == "client") { - Candy::Client client; - client.setDiscoveryInterval(args.discovery); - client.setRouteCost(args.routeCost); - client.setPort(args.port); - client.setLocalhost(args.localhost); - client.setPassword(args.password); - client.setWebSocket(args.websocket); - client.setStun(args.stun); - client.setTunAddress(args.tun); - client.setExptTunAddress(loadTunAddress(args.name)); - client.setVirtualMac(virtualMac(args.name)); - client.setMtu(args.mtu); - client.setName(args.name); - client.setTunUpdateCallback([&](auto tunCidr) { return saveTunAddress(args.name, tunCidr); }); - client.run(); - running.wait(true); - client.shutdown(); - } - - if (exitCode == 0) { - spdlog::info("service exit: normal"); - } else { - spdlog::info("service exit: internal exception"); - } - - Poco::Net::uninitializeNetwork(); - return exitCode; -} - -void signalHandler(int signal) { - exitCode = 0; - running.store(false); -} - -int main(int argc, char *argv[]) { - arguments args; - args.parse(argc, argv); - - signal(SIGINT, signalHandler); - signal(SIGTERM, signalHandler); - - if (!hasContainerVolume(args)) { - spdlog::critical("the container needs to add a storage volume: {}", storageDirectory()); - running.store(false); - } - - Candy::ntpServer = args.ntp; - - while (running.load() && serve(args) && args.restart) { - running.store(true); - Candy::useSystemTime = false; - spdlog::info("service will restart in {} seconds", args.restart); - std::this_thread::sleep_for(std::chrono::seconds(args.restart)); - } - - spdlog::drop_all(); - spdlog::shutdown(); - return exitCode; -} diff --git a/src/peer/CMakeLists.txt b/src/peer/CMakeLists.txt deleted file mode 100644 index f607ccd7..00000000 --- a/src/peer/CMakeLists.txt +++ /dev/null @@ -1,23 +0,0 @@ -aux_source_directory(. DIR_LIB_SRCS) -add_library(peer STATIC ${DIR_LIB_SRCS}) - -set_target_properties(peer PROPERTIES CXX_STANDARD 17) - -if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") - target_link_libraries(peer PRIVATE ws2_32) -endif() - -if (${CANDY_STATIC_SPDLOG}) - target_link_libraries(peer PRIVATE spdlog::spdlog) -else() - target_link_libraries(peer PRIVATE ${SPDLOG_LIBRARIES}) -endif() - -if (${CANDY_STATIC_OPENSSL}) - target_link_libraries(peer PRIVATE ${OPENSSL_LIB_CRYPTO} ${OPENSSL_LIB_SSL}) -else() - target_link_libraries(peer PRIVATE OpenSSL::SSL OpenSSL::Crypto) -endif() - -target_link_libraries(peer PRIVATE Poco::Foundation Poco::Net Poco::NetSSL) - diff --git a/src/tun/CMakeLists.txt b/src/tun/CMakeLists.txt deleted file mode 100644 index 1cb9be91..00000000 --- a/src/tun/CMakeLists.txt +++ /dev/null @@ -1,45 +0,0 @@ -aux_source_directory(. DIR_LIB_SRCS) -add_library(tun STATIC ${DIR_LIB_SRCS}) - -set_target_properties(tun PROPERTIES CXX_STANDARD 17) - -if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") - target_link_libraries(tun PRIVATE iphlpapi) - target_link_libraries(tun PRIVATE ws2_32) - - set(WINTUN_VERSION 0.14.1) - set(WINTUN_ZIP wintun-${WINTUN_VERSION}.zip) - set(WINTUN_URL https://www.wintun.net/builds/${WINTUN_ZIP}) - - if (NOT EXISTS ${CMAKE_CURRENT_BINARY_DIR}/${WINTUN_ZIP}) - file(DOWNLOAD ${WINTUN_URL} ${CMAKE_CURRENT_BINARY_DIR}/${WINTUN_ZIP} STATUS DOWNLOAD_STATUS) - list(GET DOWNLOAD_STATUS 0 STATUS_CODE) - list(GET DOWNLOAD_STATUS 1 ERROR_MESSAGE) - - if(${STATUS_CODE} EQUAL 0) - message(STATUS "wintun download success") - else() - message(FATAL_ERROR "wintun download failed: ${ERROR_MESSAGE}") - endif() - else() - message(STATUS "use wintun cache") - endif() - - file(ARCHIVE_EXTRACT INPUT ${CMAKE_CURRENT_BINARY_DIR}/${WINTUN_ZIP}) - - include_directories(${CMAKE_CURRENT_BINARY_DIR}/wintun/include) -endif() - -if (${CANDY_STATIC_SPDLOG}) - target_link_libraries(tun PRIVATE spdlog::spdlog) -else() - target_link_libraries(tun PRIVATE ${SPDLOG_LIBRARIES}) -endif() - -if (${CANDY_STATIC_OPENSSL}) - target_link_libraries(tun PRIVATE ${OPENSSL_LIB_CRYPTO} ${OPENSSL_LIB_SSL}) -endif() - -target_link_libraries(tun PRIVATE Poco::Foundation Poco::Net Poco::NetSSL) - -target_link_libraries(tun PRIVATE utils) diff --git a/src/utils/CMakeLists.txt b/src/utils/CMakeLists.txt deleted file mode 100644 index 2fbcad18..00000000 --- a/src/utils/CMakeLists.txt +++ /dev/null @@ -1,16 +0,0 @@ -aux_source_directory(. DIR_LIB_SRCS) -add_library(utils STATIC ${DIR_LIB_SRCS}) - -set_target_properties(utils PROPERTIES CXX_STANDARD 17) - -if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") - target_link_libraries(utils PRIVATE ws2_32) -endif() - -if (${CANDY_STATIC_SPDLOG}) - target_link_libraries(utils PRIVATE spdlog::spdlog) -else() - target_link_libraries(utils PRIVATE ${SPDLOG_LIBRARIES}) -endif() - -target_link_libraries(utils PRIVATE Poco::Foundation Poco::Net) diff --git a/src/utils/time.cc b/src/utils/time.cc deleted file mode 100644 index a5fa2801..00000000 --- a/src/utils/time.cc +++ /dev/null @@ -1,138 +0,0 @@ -// SPDX-License-Identifier: MIT -#include "utils/time.h" -#include "core/net.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace Candy { - -bool useSystemTime = false; -std::string ntpServer; - -struct ntp_packet { - uint8_t li_vn_mode = 0x23; - - uint8_t stratum; - uint8_t poll; - uint8_t precision; - - uint32_t rootDelay; - uint32_t rootDispersion; - uint32_t refId; - - uint32_t refTm_s; - uint32_t refTm_f; - - uint32_t origTm_s; - uint32_t origTm_f; - - uint32_t rxTm_s; - uint32_t rxTm_f; - - uint32_t txTm_s; - uint32_t txTm_f; -}; - -int64_t ntpTime() { - try { - Poco::Net::DatagramSocket socket; - socket.connect(Poco::Net::SocketAddress(ntpServer, 123)); - - struct ntp_packet packet = {}; - socket.sendBytes(&packet, sizeof(packet)); - - socket.setReceiveTimeout(Poco::Timespan(1, 0)); - int len = socket.receiveBytes(&packet, sizeof(packet)); - - if (len != sizeof(packet) || (packet.li_vn_mode & 0x07) != 4) { - spdlog::warn("invalid ntp response"); - return 0; - } - - int64_t retval = (int64_t)(ntoh(packet.rxTm_s)); - if (retval == 0) { - spdlog::warn("invalid ntp response buffer: {:n}", spdlog::to_hex(std::string((char *)(&packet), sizeof(packet)))); - return 0; - } - - // Fix ntp 2036 problem - if (!(retval & 0x80000000)) { - retval += UINT32_MAX; - } - - retval -= 2208988800U; - return retval; - } catch (std::exception &e) { - spdlog::debug("ntp time failed: {}", e.what()); - return 0; - } -} - -int64_t unixTime() { - using namespace std::chrono; - - int64_t sysTime; - int64_t netTime; - - if (useSystemTime || ntpServer.empty()) { - sysTime = duration_cast(system_clock::now().time_since_epoch()).count(); - return sysTime; - } - - netTime = 0; - for (int i = 0; i < 3 && netTime == 0; ++i) { - if (i > 0) { - spdlog::debug("get time from ntp server failed: retry {}", i); - } - netTime = ntpTime(); - } - - sysTime = duration_cast(system_clock::now().time_since_epoch()).count(); - if (std::abs(netTime - sysTime) < 3) { - useSystemTime = true; - spdlog::debug("use system time"); - } - if (netTime) { - return netTime; - } - - spdlog::warn("request network time failed"); - return sysTime; -} - -int64_t bootTime() { - using namespace std::chrono; - auto now = steady_clock::now(); - return duration_cast(now.time_since_epoch()).count(); -} - -std::string getCurrentTimeWithMillis() { - // 获取当前时间点(精确到纳秒) - auto now = std::chrono::system_clock::now(); - - // 将时间点转换为time_point - auto ms_tp = std::chrono::time_point_cast(now); - auto epoch = ms_tp.time_since_epoch(); - auto value = std::chrono::duration_cast(epoch).count(); - - // 分离秒和毫秒部分 - std::time_t now_time_t = std::chrono::system_clock::to_time_t(now); - std::tm *ptm = std::localtime(&now_time_t); - - // 格式化输出时间和毫秒 - std::ostringstream oss; - oss << std::put_time(ptm, "%Y-%m-%d %H:%M:%S"); - oss << '.' << std::setfill('0') << std::setw(3) << (value % 1000); - - return oss.str(); -} - -} // namespace Candy diff --git a/src/websocket/CMakeLists.txt b/src/websocket/CMakeLists.txt deleted file mode 100644 index b880cf89..00000000 --- a/src/websocket/CMakeLists.txt +++ /dev/null @@ -1,16 +0,0 @@ -aux_source_directory(. DIR_LIB_SRCS) -add_library(websocket STATIC ${DIR_LIB_SRCS}) - -set_target_properties(websocket PROPERTIES CXX_STANDARD 17) - -if (${CANDY_STATIC_SPDLOG}) - target_link_libraries(websocket PRIVATE spdlog::spdlog) -else() - target_link_libraries(websocket PRIVATE ${SPDLOG_LIBRARIES}) -endif() - -if (${CANDY_STATIC_OPENSSL}) - target_link_libraries(websocket PRIVATE ${OPENSSL_LIB_CRYPTO} ${OPENSSL_LIB_SSL}) -endif() - -target_link_libraries(websocket PRIVATE Poco::Foundation Poco::Net Poco::NetSSL)