Skip to content
Draft

TEST #493

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
3b8ca65
lwip+socket+tcp work
Bepartofyou Jun 27, 2026
01e8369
fix memory leap
Bepartofyou Jun 27, 2026
a7b5f7d
lwip+socket+udp work
Bepartofyou Jun 27, 2026
600ca16
lwip+socket+icmp work
Bepartofyou Jun 27, 2026
2eb46f4
support Mac/Windows, no test
Bepartofyou Jun 28, 2026
47c9daa
tun2socks func
Bepartofyou Jun 28, 2026
da4dc57
tun2socks work
Bepartofyou Jun 28, 2026
48a7bc4
use libve, Linux/Mac OK
Bepartofyou Jun 30, 2026
73ff0eb
use libev lwip thirdparty
Bepartofyou Jul 1, 2026
1ca3043
remove ci.yaml
Bepartofyou Jul 1, 2026
09634c8
only userspace udp tcp
Bepartofyou Jul 1, 2026
cab8e37
remove unused
Bepartofyou Jul 1, 2026
e6b22c9
format
Bepartofyou Jul 1, 2026
0601c76
windows build fix
Bepartofyou Jul 2, 2026
ca9f9cf
windows build fix
Bepartofyou Jul 2, 2026
bd60826
refactor tun userspace
Bepartofyou Jul 2, 2026
e20c380
remove libev, use poco
Bepartofyou Jul 2, 2026
2f0b70d
optization, poco version
Bepartofyou Jul 2, 2026
798da53
optimization
Bepartofyou Jul 3, 2026
587e6f7
fix config
Bepartofyou Jul 3, 2026
32daa3a
remove
Bepartofyou Jul 6, 2026
e365ebe
update lwip
Bepartofyou Jul 9, 2026
6fb4267
fix us->us use 5-tuple
Bepartofyou Jul 10, 2026
afba6c4
per udp socket with src ip/port
Bepartofyou Jul 11, 2026
998ebec
netstack: migrate logging from spdlog to Poco
Bepartofyou Jul 11, 2026
a988c20
use only one socket for udp, switch config
Bepartofyou Jul 17, 2026
9f2ee2f
update config
Bepartofyou Jul 22, 2026
d4bfcbe
p2p only
Bepartofyou Jul 23, 2026
b519e4e
fix compile
Bepartofyou Jul 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -78,14 +78,23 @@ 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)
Fetch(poco "https://github.com/pocoproject/poco.git" "poco-1.13.3-release")
Fetch(poco "https://github.com/pocoproject/poco.git" "poco-1.14.2-release")
else()
find_package(Poco REQUIRED COMPONENTS Foundation XML JSON Net NetSSL Util)
endif()

set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)

# 用户态协议栈(netstack + lwIP)编译开关。默认关闭:不编译任何 netstack 代码、
# 不拉取 lwIP。线上发行版打包(部分要求构建时无网络)保持关闭即可零影响。
# GitHub Release 的纯静态二进制可用 -DCANDY_NETSTACK=ON 开启此功能。
option(CANDY_NETSTACK "Build the embedded userspace network stack (lwIP)" OFF)
if (${CANDY_NETSTACK})
add_compile_definitions(CANDY_NETSTACK)
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/lwip.cmake)
endif()

add_subdirectory(candy)
add_subdirectory(candy-cli)
add_subdirectory(candy-service)
26 changes: 26 additions & 0 deletions candy-cli/src/config.cc
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ Poco::JSON::Object arguments::json() {
config.set("port", this->port);
config.set("vmac", virtualMac(this->name));
config.set("expt", loadTunAddress(this->name));
config.set("p2p-only", this->p2pOnly);
config.set("userspace-stack", this->userspaceStack);
config.set("udp-port-convergence", this->udpPortConvergence);
}

if (this->mode == "server") {
Expand Down Expand Up @@ -125,6 +128,23 @@ int arguments::parse(int argc, char *argv[]) {
"If omitted, auto-detected from the first physical network interface. (client only)")
.metavar("<ip>");

program.add_argument("--p2p-only")
.help("allow business packets only over direct P2P links.\n"
"WebSocket remains available for signaling, but relay forwarding is disabled.\n"
"Default is false (fall back to WebSocket relay when P2P is unavailable). (client only)")
.implicit_value(true);

program.add_argument("--userspace-stack")
.help("terminate landing traffic with the built-in userspace network stack (lwIP)\n"
"instead of the kernel stack. Default is false (kernel). (client only)")
.implicit_value(true);

program.add_argument("--udp-port-convergence")
.help("converge all inner UDP sources onto a single landing socket/port (single-port mode)\n"
"instead of one socket per source. Requires --userspace-stack.\n"
"Default is false (per-source full-cone). (client only)")
.implicit_value(true);

program.add_group("Server options");

program.add_argument("-d", "--dhcp")
Expand Down Expand Up @@ -182,6 +202,9 @@ int arguments::parse(int argc, char *argv[]) {
program.set_if_used("--mtu", this->mtu);
program.set_if_used("--discovery", this->discovery);
program.set_if_used("--route", this->routeCost);
program.set_if_used("--p2p-only", this->p2pOnly);
program.set_if_used("--userspace-stack", this->userspaceStack);
program.set_if_used("--udp-port-convergence", this->udpPortConvergence);

bool needShowUsage = [&]() {
if (this->mode != "client" && this->mode != "server")
Expand Down Expand Up @@ -239,6 +262,9 @@ void arguments::parseFile(std::string cfgFile) {
{"port", [&](const std::string &value) { this->port = std::stoi(value); }},
{"mtu", [&](const std::string &value) { this->mtu = std::stoi(value); }},
{"localhost", [&](const std::string &value) { this->localhost = value; }},
{"p2p-only", [&](const std::string &value) { this->p2pOnly = (value == "true"); }},
{"userspace-stack", [&](const std::string &value) { this->userspaceStack = (value == "true"); }},
{"udp-port-convergence", [&](const std::string &value) { this->udpPortConvergence = (value == "true"); }},
};
auto trim = [](std::string str) {
if (str.length() >= 2 && str.front() == '\"' && str.back() == '\"') {
Expand Down
6 changes: 6 additions & 0 deletions candy-cli/src/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ struct arguments {
int discovery = 0;
int routeCost = 0;
int mtu = 1400;
// 仅允许 P2P 承载业务数据;WebSocket 只保留信令(默认关闭=允许服务端中转)。
bool p2pOnly = false;
// userspace-stack:内嵌 lwIP 落地(默认关闭=继续走内核 tun)。
bool userspaceStack = false;
// UDP 单端口收敛:多个内部源共用一个出口 fd/端口(默认关闭=保留每源全锥形)。
bool udpPortConvergence = false;
};

int saveTunAddress(const std::string &name, const std::string &cidr);
Expand Down
25 changes: 25 additions & 0 deletions candy.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -63,5 +63,30 @@ route = 5
# results may not be the best. You can specify it manually.
#localhost = "127.0.0.1"

# [Optional] Allow business traffic only over direct P2P links.
# false (default): prefer P2P and fall back to WebSocket relay through the
# server when no P2P link is available.
# true: WebSocket is used only for authentication, routing and P2P signaling;
# business packets are dropped until a direct P2P link is established.
# Public-network use requires a reachable STUN server; discovery > 0 is
# recommended for periodic P2P discovery.
p2p-only = false

# [Optional] Maximum Transmission Unit
#mtu=1400

# [Optional] Enable the embedded userspace network stack (lwIP) for landing
# gateway.
# false (default): write decapsulated packets to the kernel tun device, relying
# on external iptables MASQUERADE / ip_forward for cross-LAN access.
# true: terminate the inner packet with an embedded userspace network stack
# (lwIP) and dial the target with a local kernel socket, replacing iptables
# MASQUERADE. Requires a build with CANDY_NETSTACK enabled. Phase 1 supports
# TCP/UDP only.
userspace-stack = false

# [Optional] UDP single-port convergence for netstack landing mode.
# false (default): per-source UDP sockets/full-cone behavior.
# true: all inner UDP sources share one landing UDP socket/port. Requires
# userspace-stack = true.
udp-port-convergence = false
7 changes: 7 additions & 0 deletions candy/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
add_library(candy-library)

file(GLOB_RECURSE SOURCES "src/*.cc")
# 未开启 CANDY_NETSTACK 时,netstack 目录整体不参与编译(默认关闭,零代码编译)。
if (NOT CANDY_NETSTACK)
list(FILTER SOURCES EXCLUDE REGEX "/src/netstack/")
endif()
target_sources(candy-library PRIVATE ${SOURCES})

target_include_directories(candy-library PUBLIC
Expand All @@ -17,6 +21,9 @@ endif()

target_link_libraries(candy-library PRIVATE Poco::Foundation Poco::JSON Poco::Net Poco::NetSSL)
target_link_libraries(candy-library PRIVATE Threads::Threads)
if (${CANDY_NETSTACK})
target_link_libraries(candy-library PRIVATE lwip)
endif()

if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
target_link_libraries(candy-library PRIVATE ws2_32)
Expand Down
3 changes: 3 additions & 0 deletions candy/src/candy/client.cc
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ bool run(const std::string &id, const Poco::JSON::Object &config) {
client->setRouteCost(config.getValue<int>("route")), client->setMtu(config.getValue<int>("mtu"));
client->setPort(config.getValue<int>("port"));
client->setLocalhost(config.getValue<std::string>("localhost"));
client->setP2POnly(config.optValue<bool>("p2p-only", false));
client->setUserspaceStack(config.optValue<bool>("userspace-stack", false));
client->setUdpPortConvergence(config.optValue<bool>("udp-port-convergence", false));
client->run();
}
candy::logger().information(Poco::format("run exit: id=%s ", id));
Expand Down
2 changes: 2 additions & 0 deletions candy/src/candy/server.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
#include "candy/server.h"
#include "core/server.h"
#include "utils/atomic.h"
#include <chrono>
#include <thread>

namespace candy {
namespace server {
Expand Down
41 changes: 41 additions & 0 deletions candy/src/core/client.cc
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,26 @@ MsgQueue &Client::getWsMsgQueue() {
return this->wsMsgQueue;
}

MsgQueue &Client::getNetStackMsgQueue() {
return this->netstackMsgQueue;
}

bool Client::getUserspaceStack() const {
return this->userspaceStack;
}

bool Client::getP2POnly() const {
return this->p2pOnly;
}

bool Client::getUdpPortConvergence() const {
return this->udpPortConvergence;
}

int Client::getMtu() const {
return this->tun.getMTU();
}

void Client::setPassword(const std::string &password) {
ws.setPassword(password);
peerManager.setPassword(password);
Expand Down Expand Up @@ -107,9 +127,26 @@ void Client::setMtu(int mtu) {
tun.setMTU(mtu);
}

void Client::setP2POnly(bool enable) {
this->p2pOnly = enable;
}

void Client::setUserspaceStack(bool enable) {
this->userspaceStack = enable;
}

void Client::setUdpPortConvergence(bool enable) {
this->udpPortConvergence = enable;
}

void Client::run() {
this->running.store(true);

#ifdef CANDY_NETSTACK
if (netstack.run(this)) {
return;
}
#endif
if (ws.run(this)) {
return;
}
Expand All @@ -123,10 +160,14 @@ void Client::run() {
ws.wait();
tun.wait();
peerManager.wait();
#ifdef CANDY_NETSTACK
netstack.wait();
#endif

wsMsgQueue.clear();
tunMsgQueue.clear();
peerMsgQueue.clear();
netstackMsgQueue.clear();
}

bool Client::isRunning() {
Expand Down
22 changes: 21 additions & 1 deletion candy/src/core/client.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
#include "tun/tun.h"
#include "utils/atomic.h"
#include "websocket/client.h"
#ifdef CANDY_NETSTACK
#include "netstack/netstack.h"
#endif
#include <condition_variable>
#include <mutex>
#include <queue>
Expand Down Expand Up @@ -42,6 +45,10 @@ class Client {
void setExptTunAddress(const std::string &cidr);
void setVirtualMac(const std::string &vmac);

void setP2POnly(bool enable);
void setUserspaceStack(bool enable);
void setUdpPortConvergence(bool enable);

void run();
bool isRunning();
void shutdown();
Expand All @@ -57,16 +64,29 @@ class Client {
MsgQueue &getTunMsgQueue();
MsgQueue &getPeerMsgQueue();
MsgQueue &getWsMsgQueue();
MsgQueue &getNetStackMsgQueue();

bool getP2POnly() const;
bool getUserspaceStack() const;
bool getUdpPortConvergence() const;
int getMtu() const;

private:
MsgQueue tunMsgQueue, peerMsgQueue, wsMsgQueue;
MsgQueue tunMsgQueue, peerMsgQueue, wsMsgQueue, netstackMsgQueue;

Tun tun;
PeerManager peerManager;
WebSocketClient ws;
#ifdef CANDY_NETSTACK
NetStack netstack;
#endif

private:
std::string tunName;
bool p2pOnly = false;
bool userspaceStack = false;
// UDP 单端口收敛开关(默认关闭=每源全锥形)。仅在 userspaceStack=true 时生效。
bool udpPortConvergence = false;
};

} // namespace candy
Expand Down
1 change: 1 addition & 0 deletions candy/src/core/message.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ enum class MsgKind {
TRYP2P,
PUBINFO,
DISCOVERY,
NETSTACK,
};

struct Msg {
Expand Down
Loading