diff --git a/CMakeLists.txt b/CMakeLists.txt index f7109f18..e5484f64 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.18.4) -project(candy LANGUAGES C CXX VERSION 5.9.3) +project(candy LANGUAGES C CXX VERSION 5.9.4) option(CANDY_NOEXE "Don't build executable") option(CANDY_DEVEL "Build development library") diff --git a/README.md b/README.md index ce82f03e..3e625d10 100644 --- a/README.md +++ b/README.md @@ -164,6 +164,14 @@ dhcp = "10.0.0.0/24" password = "123456" ``` +配置[多局域网组网](docs/muti-lan-interconn.md) + +```bash +# 进入设备网络 10.0.0.1/32 且目的网络为 192.168.2.0/24 的 IP 报文通过 10.0.0.2 转发, +# 通过分号分割的多条规则,配置中禁止出现空白符. +sdwan = "10.0.0.1/32,192.168.2.0/24,10.0.0.2;10.0.0.2/32,192.168.1.0/24,10.0.0.1"; +``` + #### 客户端 与上述服务端匹配的客户端配置 @@ -186,7 +194,7 @@ stun = "stun://stun.canets.org" port = 0 ``` -关于路由的配置 +配置客户端中继 ```bash # 路由功能在对等连接的基础上工作,对等连接是被动启动, @@ -201,7 +209,7 @@ discovery = 300 route = 5 ``` -关于局域网内建立对等的配置 +配置局域网内对等链接 ```bash # 用于建立对等连接的本机局域网 IP 地址,不配置此项时将尝试自动获取. diff --git a/candy.cfg b/candy.cfg index a047414c..24a4f1d5 100644 --- a/candy.cfg +++ b/candy.cfg @@ -25,6 +25,12 @@ websocket = "wss://canets.org/demo"; # configure a static address through tun. #dhcp = "172.16.0.0/16"; +# [Optional] software-defined wide area network +# IP packets entering 172.16.0.1/32 with the destination address 192.168.2.0/24 +# will be forwarded to 172.16.0.2. Multiple rules are separated by semicolons. +# Extraneous whitespace characters are prohibited. +#sdwan = "172.16.0.1/32,192.168.2.0/24,172.16.0.2;172.16.0.2/32,192.168.1.0/24,172.16.0.1"; + ################################# Client Only ################################# # [Optional] Network interface name # Used to differentiate networks when running multiple clients. diff --git a/docs/muti-lan-interconn.md b/docs/muti-lan-interconn.md index 83fcf917..8bfa0b8e 100644 --- a/docs/muti-lan-interconn.md +++ b/docs/muti-lan-interconn.md @@ -8,7 +8,7 @@ 此处假设你已经: -- 成功部署 Web 版本[服务端](https://hub.docker.com/r/lanthora/cucurbita) +- 成功部署服务端 - 在网关 (Gateway) 上部署 Candy 并分配了虚拟地址 以 LAN A 为例解释表格含义. diff --git a/src/core/server.cc b/src/core/server.cc index 5132eb59..16f40017 100644 --- a/src/core/server.cc +++ b/src/core/server.cc @@ -7,6 +7,7 @@ #include #include #include +#include namespace Candy { @@ -47,6 +48,34 @@ int Server::setDynamicAddressRange(const std::string &cidr) { return 0; } +int Server::setSdwan(const std::string &sdwan) { + if (sdwan.empty()) { + return 0; + } + std::string route; + std::stringstream stream(sdwan); + while (std::getline(stream, route, ';')) { + std::string addr; + SysRoute rt; + std::stringstream ss(route); + if (!std::getline(ss, addr, ',') || rt.dev.cidrUpdate(addr) || rt.dev.getIp() != rt.dev.getNet()) { + spdlog::critical("invalid route device: {}", route); + return -1; + } + if (!std::getline(ss, addr, ',') || rt.dst.cidrUpdate(addr) || rt.dst.getIp() != rt.dst.getNet()) { + spdlog::critical("invalid route dest: {}", route); + return -1; + } + if (!std::getline(ss, addr, ',') || rt.next.ipStrUpdate(addr)) { + spdlog::critical("invalid route nexthop: {}", route); + return -1; + } + spdlog::info("route: dev={} dst={} next={}", rt.dev.getCidr(), rt.dst.getCidr(), rt.next.getIpStr()); + this->routes.push_back(rt); + } + return 0; +} + int Server::run() { this->running = true; if (startWsThread()) { @@ -180,6 +209,7 @@ void Server::handleAuthMessage(WebSocketMessage &message) { this->ipWsMap[address.getIp()] = message.conn; this->wsIpMap[message.conn] = address.getIp(); + updateClientRoute(message, address.getIp()); } void Server::handleForwardMessage(WebSocketMessage &message) { @@ -468,4 +498,26 @@ void Server::handleCloseMessage(WebSocketMessage &message) { this->wsMacMap.erase(message.conn); } +void Server::updateClientRoute(WebSocketMessage &message, uint32_t client) { + message.buffer.resize(sizeof(SysRouteMessage)); + SysRouteMessage *header = (SysRouteMessage *)message.buffer.data(); + memset(header, 0, sizeof(SysRouteMessage)); + header->type = MessageType::ROUTE; + + for (auto rt : this->routes) { + if ((rt.dev.getMask() & client) == rt.dev.getIp()) { + SysRouteItem item; + item.dest = Address::hostToNet(rt.dst.getNet()); + item.mask = Address::hostToNet(rt.dst.getMask()); + item.nexthop = Address::hostToNet(rt.next.getIp()); + message.buffer.append((char *)(&item), sizeof(item)); + header->size += 1; + } + } + + if (header->size > 0) { + this->ws.write(message); + } +} + } // namespace Candy diff --git a/src/core/server.h b/src/core/server.h index 934a3a0c..6028f4fe 100644 --- a/src/core/server.h +++ b/src/core/server.h @@ -4,17 +4,25 @@ #include "utility/address.h" #include "websocket/server.h" +#include #include #include #include namespace Candy { +struct SysRoute { + Address dev; + Address dst; + Address next; +}; + class Server { public: int setWebSocketServer(const std::string &uri); int setPassword(const std::string &password); int setDynamicAddressRange(const std::string &cidr); + int setSdwan(const std::string &sdwan); int run(); int shutdown(); @@ -32,6 +40,8 @@ class Server { void handleGeneralMessage(WebSocketMessage &message); void handleCloseMessage(WebSocketMessage &message); + void updateClientRoute(WebSocketMessage &message, uint32_t client); + bool running = false; uint16_t port; std::string host; @@ -45,6 +55,7 @@ class Server { std::map ipWsMap; std::map wsIpMap; std::map wsMacMap; + std::list routes; }; } // namespace Candy diff --git a/src/main/main.cc b/src/main/main.cc index a95eb1da..c9402645 100644 --- a/src/main/main.cc +++ b/src/main/main.cc @@ -31,6 +31,7 @@ struct arguments { // 服务端配置 std::string dhcp; + std::string sdwan; // 客户端配置 std::string name; @@ -82,6 +83,7 @@ void parseConfig(std::string cfgFile, arguments &args) { {"debug", [&](const std::string &value) { args.debug = (value == "true"); }}, {"restart", [&](const std::string &value) { args.restart = std::stoi(value); }}, {"dhcp", [&](const std::string &value) { args.dhcp = value; }}, + {"sdwan", [&](const std::string &value) { args.sdwan = value; }}, {"tun", [&](const std::string &value) { args.tun = value; }}, {"stun", [&](const std::string &value) { args.stun = value; }}, {"name", [&](const std::string &value) { args.name = value; }}, @@ -256,6 +258,7 @@ int serve(const arguments &args) { server.setPassword(args.password); server.setWebSocketServer(args.websocket); server.setDynamicAddressRange(args.dhcp); + server.setSdwan(args.sdwan); server.run(); } @@ -299,6 +302,7 @@ int parseConfig(int argc, char *argv[], arguments &args) { program.add_argument("-p", "--password").help("authorization password").metavar("TEXT"); program.add_argument("--restart").help("restart interval").scan<'i', int>().metavar("SECONDS"); program.add_argument("-d", "--dhcp").help("dhcp address range").metavar("CIDR"); + program.add_argument("--sdwan").help("software-defined wide area network").metavar("ROUTES"); program.add_argument("-n", "--name").help("network interface name").metavar("TEXT"); program.add_argument("-t", "--tun").help("static address").metavar("CIDR"); program.add_argument("-s", "--stun").help("stun address").metavar("URI"); @@ -323,6 +327,7 @@ int parseConfig(int argc, char *argv[], arguments &args) { args.noTimestamp = program.is_used("--no-timestamp") ? program.get("--no-timestamp") : args.noTimestamp; args.debug = program.is_used("--debug") ? program.get("--debug") : args.debug; args.dhcp = program.is_used("--dhcp") ? program.get("--dhcp") : args.dhcp; + args.sdwan = program.is_used("--sdwan") ? program.get("--sdwan") : args.sdwan; args.name = program.is_used("--name") ? program.get("--name") : args.name; args.tun = program.is_used("--tun") ? program.get("--tun") : args.tun; args.stun = program.is_used("--stun") ? program.get("--stun") : args.stun;