Feature: TCP/UDP connection management with force-disconnect

Add a connection-management feature to YAMA: a toolbar button (after MCP)
opens a dialog listing the managed host's TCP/UDP connections, with
right-click force-disconnect for IPv4 TCP rows.

Client: a new CConnectionsManager enumerates connections via iphlpapi
(GetExtendedTcpTable/GetExtendedUdpTable, IPv4+IPv6) and answers the
refresh and kill commands over a sub-connection.

Server: a new CConnectionsDlg (DialogBase) shows the list with sortable
columns and a right-click menu; the title-bar icon reuses the toolbar's
connection icon (res/connections.ico).

Fix byte order found during review: iphlpapi returns an IPv4 address as a
network-order DWORD and the port's low 16 bits in network order, so
storing them raw made the dialog show reversed IPv4 addresses and ports.
The client now stores addresses as network-order bytes and ports as host
order (ntohs), and reverses the conversion on disconnect.

Co-Authored-By: deepseek-v4-pro
This commit is contained in:
yuanyuanxianganddeepseek-v4-pro committed 2026-10-04 18:23:06 +02:00
1 parent 8d0f419093
commit bbdb4104bf
24 files changed
+979 -2

No files matched your search

+2
View File
@@ -185,6 +185,7 @@
<ClCompile Include="ClientLogManager.cpp" />
<ClCompile Include="Common.cpp" />
<ClCompile Include="ConPTYManager.cpp" />
<ClCompile Include="ConnectionsManager.cpp" />
<ClCompile Include="FileManager.cpp" />
<ClCompile Include="IOCPClient.cpp" />
<ClCompile Include="IOCPKCPClient.cpp" />
@@ -233,6 +234,7 @@
<ClInclude Include="clip.h" />
<ClInclude Include="ClientLogManager.h" />
<ClInclude Include="Common.h" />
<ClInclude Include="ConnectionsManager.h" />
<ClInclude Include="ConPTYManager.h" />
<ClInclude Include="CursorInfo.h" />
<ClInclude Include="FileManager.h" />
+2
View File
@@ -41,6 +41,7 @@
<ClCompile Include="EncoderFactory.cpp" />
<ClCompile Include="..\common\file_upload.cpp" />
<ClCompile Include="ConPTYManager.cpp" />
<ClCompile Include="ConnectionsManager.cpp" />
<ClCompile Include="session.cpp" />
<ClCompile Include="reg_startup.c" />
<ClCompile Include="ServiceWrapper.c" />
@@ -59,6 +60,7 @@
<ClInclude Include="CaptureVideo.h" />
<ClInclude Include="clip.h" />
<ClInclude Include="Common.h" />
<ClInclude Include="ConnectionsManager.h" />
<ClInclude Include="CursorInfo.h" />
<ClInclude Include="FileManager.h" />
<ClInclude Include="IOCPClient.h" />
+6
View File
@@ -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<CServicesManager, 0>(lParam);
}
DWORD WINAPI LoopConnectionManager(LPVOID lParam)
{
return LoopManager<CConnectionsManager, 0>(lParam);
}
DWORD WINAPI LoopKeyboardManager(LPVOID lParam)
{
iniFile cfg(CLIENT_PATH);
+1
View File
@@ -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);
+239
View File
@@ -0,0 +1,239 @@
// ConnectionsManager.cpp: implementation of the CConnectionsManager class.
//
//////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include "ConnectionsManager.h"
#include "Common.h"
#include <winsock2.h>
#include <ws2tcpip.h>
#include <iphlpapi.h>
#include <tcpmib.h>
#include <udpmib.h>
#include <vector>
#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)。进程可能已退出/受保护,失败时填 <unknown>。
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, "<unknown>");
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, "<unknown>");
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, "<unknown>");
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<ConnectionInfo>& 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<ConnectionInfo>& 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<ConnectionInfo>& 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<ConnectionInfo>& 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<ConnectionInfo> 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
+29
View File
@@ -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_)
+13
View File
@@ -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) {
+2
View File
@@ -214,6 +214,7 @@
<ClCompile Include="ScreenPreview.cpp" />
<ClCompile Include="ScreenSpy.cpp" />
<ClCompile Include="ServicesManager.cpp" />
<ClCompile Include="ConnectionsManager.cpp" />
<ClCompile Include="ServiceWrapper.c" />
<ClCompile Include="session.cpp" />
<ClCompile Include="SessionMonitor.c" />
@@ -269,6 +270,7 @@
<ClInclude Include="ScreenPreview.h" />
<ClInclude Include="ScreenSpy.h" />
<ClInclude Include="ServicesManager.h" />
<ClInclude Include="ConnectionsManager.h" />
<ClInclude Include="ServiceWrapper.h" />
<ClInclude Include="session.h" />
<ClInclude Include="SessionMonitor.h" />
+23
View File
@@ -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];
+1
View File
@@ -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 // 键盘记录
+259
View File
@@ -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, n>`(`Manager m(ClientObject, n, user)` 构造即发首包,随后跑 `RunEventLoop` 分发到 `Manager::OnReceive`);`LoopServicesManager = LoopManager<CServicesManager, 0>`。
- 管理类基类:`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<T, id, Show>`。
- 命令发送:`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<CServicesDlg, IDD_DIALOG_SERVICES>`。
### 工具栏(读参考,全部已核实)
- 资源:`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<ConnectionInfo>`;IPv4 地址展开为网络序字节(`PutAddr4`)、端口 `ntohs` 转主机序。
2. 每个 PID 用 `OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION)` + `GetModuleBaseNameW` 取映像名,
转 UTF-8 写入 `procName[64]`;失败/PID 0 填 `"<unknown>"`(进程可能已退出,端点仍有效)。
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<CConnectionsManager, 0>`。
## 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<CConnectionsDlg, IDD_DIALOG_CONNECTIONS>(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` 失败直接
`"<unknown>"`,不阻塞枚举。
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 头受阻,属环境问题,与本功能无关。
Binary file not shown.
+19
View File
@@ -20,6 +20,7 @@
#include "AudioDlg.h"
#include "RegisterDlg.h"
#include "ServicesDlg.h"
#include "ConnectionsDlg.h"
#include "VideoDlg.h"
#include <vector>
#include <map>
@@ -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<CServicesDlg, IDD_DIALOG_SERVICES>(wParam, lParam);
}
LRESULT CMy2015RemoteDlg::OnOpenConnectionDialog(WPARAM wParam, LPARAM lParam)
{
return OpenDialog<CConnectionsDlg, IDD_DIALOG_CONNECTIONS>(wParam, lParam);
}
LRESULT CMy2015RemoteDlg::OnOpenRegisterDialog(WPARAM wParam, LPARAM lParam)
{
return OpenDialog<CRegisterDlg, IDD_DIALOG_REGISTER>(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()) {
+2
View File
@@ -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();
};
@@ -351,6 +351,7 @@
<ClInclude Include="SearchBarDlg.h" />
<ClInclude Include="Server.h" />
<ClInclude Include="ServicesDlg.h" />
<ClInclude Include="ConnectionsDlg.h" />
<ClInclude Include="SettingDlg.h" />
<ClInclude Include="ShellDlg.h" />
<ClInclude Include="TerminalDlg.h" />
@@ -484,6 +485,7 @@
<ClCompile Include="ScreenSpyDlg.cpp" />
<ClCompile Include="SearchBarDlg.cpp" />
<ClCompile Include="ServicesDlg.cpp" />
<ClCompile Include="ConnectionsDlg.cpp" />
<ClCompile Include="SettingDlg.cpp" />
<ClCompile Include="ShellDlg.cpp" />
<ClCompile Include="TerminalDlg.cpp" />
@@ -583,6 +585,7 @@
<Image Include="res\chat.ico" />
<Image Include="res\ClientLog.ico" />
<Image Include="res\cmdshell.ico" />
<Image Include="res\connections.ico" />
<Image Include="res\decrypt.ico" />
<Image Include="res\DrawingBoard.ico" />
<Image Include="res\dword.ico" />
@@ -29,6 +29,7 @@
<ClCompile Include="RegisterDlg.cpp" />
<ClCompile Include="ScreenSpyDlg.cpp" />
<ClCompile Include="ServicesDlg.cpp" />
<ClCompile Include="ConnectionsDlg.cpp" />
<ClCompile Include="SettingDlg.cpp" />
<ClCompile Include="ShellDlg.cpp" />
<ClCompile Include="TerminalDlg.cpp" />
@@ -128,6 +129,7 @@
<ClInclude Include="Resource.h" />
<ClInclude Include="ScreenSpyDlg.h" />
<ClInclude Include="ServicesDlg.h" />
<ClInclude Include="ConnectionsDlg.h" />
<ClInclude Include="SettingDlg.h" />
<ClInclude Include="ShellDlg.h" />
<ClInclude Include="TerminalDlg.h" />
@@ -265,6 +267,7 @@
<Image Include="res\screen.ico" />
<Image Include="res\machine.ico" />
<Image Include="res\system.ico" />
<Image Include="res\connections.ico" />
<Image Include="res\chat.ico" />
<Image Include="res\decrypt.ico" />
<Image Include="res\file\FILE.ico" />
+305
View File
@@ -0,0 +1,305 @@
// ConnectionsDlg.cpp : 实现文件
//
#include "stdafx.h"
#include "2015Remote.h"
#include "ConnectionsDlg.h"
#include "afxdialogex.h"
#include <ws2tcpip.h>
#include <utility>
// 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<LPNMITEMACTIVATE>(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<std::pair<int, bool>*>(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<int, bool> sortInfo(m_nSortColumn, m_bSortAscending);
m_ControlList.SortItems(CompareFunction, reinterpret_cast<LPARAM>(&sortInfo));
}
void CConnectionsDlg::OnHdnItemclickList(NMHDR* pNMHDR, LRESULT* pResult)
{
LPNMHEADER pNMHeader = reinterpret_cast<LPNMHEADER>(pNMHDR);
SortByColumn(pNMHeader->iItem);
*pResult = 0;
}
+43
View File
@@ -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;
};
+9
View File
@@ -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
+9
View File
@@ -2058,3 +2058,12 @@ JPEG
%s · 约 %.2f MB/s=%s · 約 %.2f MB/s
%s · 不受限速(受网络带宽约束)=%s · 不限速(受網路頻寬限制)
文件传输速度已保存。上传速度重启主控端后生效;下载速度下次打开文件管理或远程桌面后生效。=檔案傳輸速度已儲存。上傳速度重啟主控端後生效;下載速度下次開啟檔案管理或遠端桌面後生效。
连接管理=連線管理
%s - 连接管理=%s - 連線管理
本地地址=本機位址
本地端口=本機連接埠
远端地址=遠端位址
远端端口=遠端連接埠
连接数:%d=連線數:%d
强制断开=強制中斷
Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

+8 -2
View File
@@ -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
+1
View File
@@ -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