- 桌面应用
- 网络
【免费下载链接】mRemoteNG
mRemoteNG is the next generation of mRemote, open source, tabbed, multi-protocol, remote connections manager.
mRemoteNG 内置的Port Scan功能(位于菜单 Tools > Port Scan,当前版本由mRemoteNG.Plugins.PortScan插件承载)提供了一种类似 nmap 的轻量端口扫描能力:它按指定 IP 范围逐主机探测 mRemoteNG 所支持协议的端口是否开放,随后将命中的主机按所选协议批量导入为连接,是初始化一批服务器连接配置的高效手段。阅读本文后,你将掌握 Port Scan 的完整操作流程、每个输入参数的含义与默认值、扫描结果的解读方式,以及批量导入连接背后的实现原理与代码路径。
Port Scan 是什么
Port Scan 面向"我要管理一批新主机,但不想逐个手工创建连接"的场景。官方文档(port_scan.rst)将其描述为:
The Port Scan feature (under Tools > Port Scan) is similar to a nmap port scan. It will scan a range of IP addresses and to determine if specific mRemoteNG supported protocols are active. Hosts can then be bulk imported into mRemoteNG.
核心价值有两点:
- 协议探测:扫描一个 IP 地址区间,判断哪些主机开放了 mRemoteNG 支持的协议端口(SSH、Telnet、HTTP、HTTPS、Rlogin、RDP、VNC 等);
- 批量导入:把扫描命中的主机直接转换成 mRemoteNG 连接节点,无需手工录入。
从当前仓库的实现看,该功能分两条代码路径:
- 现代插件实现(
mRemoteNG.Plugins.PortScan项目):以IToolWindowPlugin形式注册,随插件体系分发,包含PortScanPlugin.cs、PortScanControl.cs、PortScanService.cs、PortScanHostResult.cs; - 主程序内的经典实现:
mRemoteNG/Tools/PortScanner.cs与ScanHost.cs,配合mRemoteNG/Config/Import/PortScanImporter.cs、mRemoteNG/Config/Serializers/MiscSerializers/PortScanDeserializer.cs完成扫描结果到连接树的序列化导入。
下文以当前仓库插件实现为主体展开,经典实现作为原理佐证。
启动 Port Scan
菜单入口与插件注册
按文档描述,通过菜单栏Tools > Port Scan启动。在插件化架构下,这个入口由 PortScanPlugin.cs 注册:
public sealed class PortScanPlugin : IToolWindowPlugin { public string Id => "mRp.PortScan"; public string DisplayName => Resources.GetString("PortScan", "Port Scan"); public Version Version => new(1, 0, 0); public Version MinimumHostVersion => new(1, 0, 0); public ToolWindowRegistration ToolWindow => new() { ContextMenuGroup = PluginContextMenuGroup.Import, ContextMenuText = Resources.GetString("ImportPortScan", "Import from Port Scan..."), Icon = Resources.GetImage("SearchAndApps_16x") as Image, MenuText = Resources.GetString("PortScan", "Port Scan"), PanelName = "General", ShowAsDocument = true, SortOrder = 100, WindowTitle = Resources.GetString("PortScan", "Port Scan"), }; ... }要点:
- 插件 ID 为
mRp.PortScan,版本 1.0.0; - 工具窗口以文档形式(
ShowAsDocument = true)显示,归属面板General; - 除了 "Port Scan" 菜单项,还在导入相关上下文菜单组(
PluginContextMenuGroup.Import)注册了 "Import from Port Scan..." 快捷入口; - 工具窗口的实际界面由 PortScanControl.cs 提供(
CreateControl()返回该控件)。
界面组成
打开后,界面自上而下由三块区域组成(对应 PortScanControl.cs 中的TableLayoutPanel布局):
- 扫描参数输入区:First IP / Last IP / First Port / Last Port / "Default protocol ports only" 复选框 / Timeout(秒);
- 结果表格区:逐行展示每个主机的探测结果;
- 导入操作区:协议下拉框(Protocol to import)+ Import 按钮。
扫描参数详解
IP 范围:First IP 与 Last IP
输入扫描区间的起始 IP 与结束 IP,两个地址都会被IPAddress.TryParse校验,解析失败会提示 "Cannot start port scan."(见ScanButtonOnClick)。需要注意:
- 仅支持 IPv4:地址展开逻辑在 PortScanService.cs 中,若地址族不是
InterNetwork会抛出NotSupportedException("Only IPv4 ranges are supported."); - 顺序无关:内部会对起止地址取
min/max后按数值顺序展开,无论你输入的顺序如何; - 区间大小决定扫描量:
ExpandAddresses会把起止 IP 转成 uint32 逐一遍历,例如192.168.1.1~192.168.1.254就是 254 个主机,每个主机还要依次做 Ping 与端口探测,区间越大耗时越长(文档原话:"Wait. Possibly a long time.")。
端口范围:First Port 与 Last Port
- 输入框为
NumericUpDown,取值范围1 ~ 65535; - 插件默认值为22(First Port)与 5900(Last Port),即默认覆盖 SSH 到 VNC 的常用端口区间;
- 代码对端口列表的构造逻辑(
BuildPortList):取min(first, last)到max(first, last)逐端口枚举;如果起始端口为 0,则退化为只扫描结束端口(start == 0时start = end)。
文档提示(tip):"If you leave this at the default of 0 & 0, the test will be for the default protocol ports that mRemoteNG supports."(如果保持默认的 0 & 0,将只测试 mRemoteNG 支持的默认协议端口。)经典版 PortScanner.cs 中的行为与此一致:
checkDefaultPortsOnly为真时只扫描 7 个默认协议端口。在插件版界面中,这一逻辑由"Default protocol ports only" 复选框显式承担——勾选后忽略端口输入框,只探测默认协议端口列表。
默认协议端口表
无论经典实现(PortScanner构造函数中内联的端口数组,端口取值来自 ScanHost.cs 中各协议默认端口)还是插件实现(PortScanService.cs 中的DefaultPorts),默认端口完全一致:
| 端口 | 协议 | 说明 |
|---|---|---|
| 22 | SSH | 来自ProtocolSSH1.Defaults.Port |
| 23 | Telnet | 来自ProtocolTelnet.Defaults.Port |
| 80 | HTTP | 来自ProtocolHTTP.Defaults.Port |
| 443 | HTTPS | 来自ProtocolHTTPS.Defaults.Port |
| 513 | Rlogin | 来自ProtocolRlogin.Defaults.Port |
| 3389 | RDP | 来自RdpProtocol.Defaults.Port |
| 5900 | VNC | 来自ProtocolVNC.Defaults.Port |
当你勾选 "Default protocol ports only" 时,扫描范围就固定为这 7 个端口,扫描速度会显著加快。
Timeout(秒)
- 取值范围1 ~ 60 秒,默认5 秒;
- 在插件实现中,界面值会被乘以 1000 转换为毫秒,同时用于Ping 超时与TCP 连接超时(
Task.WhenAny(connectTask, timeoutTask),见 PortScanService.cs); - 经典版 PortScanner.cs 的构造函数默认
timeoutInMilliseconds = 5000,同样默认 5 秒。
扫描流程:从点击 Scan 到结果就绪
完整执行路径如下(对应ScanButtonOnClick→PortScanService.ScanAsync):
- 校验参数:解析起止 IP,失败则通过
_pluginContext.Messages.Warning提示 "Cannot start port scan."; - 重置状态:清空结果绑定列表、进度条归零,扫描按钮文字变为"Stop"(再次点击即取消扫描);
- 展开地址与端口:
ExpandAddresses生成 IP 列表,BuildPortList生成端口列表; - 逐主机探测(
ScanHostAsync):- 先发送ICMP Ping(
Ping.SendPingAsync,超时即上文的 Timeout)判断主机是否存活; - Ping 失败 → 该主机所有端口记为关闭,协议标记全部为 false(经典实现中对应
SetAllProtocols(false)与ClosedPorts.AddRange(_ports)); - Ping 成功 → 先做DNS 反向解析(
Dns.GetHostEntryAsync)拿到主机名,再对每个端口做TCP 连接测试(TcpClient.ConnectAsync),连接成功即为开放端口;
- 先发送ICMP Ping(
- 实时刷新:每扫完一台主机,通过
AddScannedHost回调把结果追加进表格并推进进度条(进度条总数为主机数); - 完成通知:全部主机扫描完毕后,通过消息系统提示 "Port scan complete.";中途点击 Stop 则抛出
OperationCanceledException,提示 "Port Scan cancelled."。
单端口探测的判定核心(PortScanService.cs):
private static async Task<bool> IsPortOpenAsync(IPAddress address, int port, int timeoutInMilliseconds, CancellationToken cancellationToken) { try { using TcpClient client = new(); Task connectTask = client.ConnectAsync(address, port); Task timeoutTask = Task.Delay(timeoutInMilliseconds, cancellationToken); Task completedTask = await Task.WhenAny(connectTask, timeoutTask); if (completedTask != connectTask) return false; // 超时未连上 await connectTask; return client.Connected; // 成功建立 TCP 连接 } catch { return false; } }协议标记的端口判定
每个主机会根据端口探测结果打上对应协议标记(PortScanService.cs):
| 端口 | 置位属性 |
|---|---|
| 22 | Ssh |
| 23 | Telnet |
| 80 | Http |
| 443 | Https |
| 513 | Rlogin |
| 3389 | Rdp |
| 5900 | Vnc |
非默认端口即使开放,也只会出现在 Open Ports 列表里,不会影响协议标记位。
结果表格解读
扫描结果由 PortScanHostResult.cs 承载,表格列定义在ConfigureGridColumns中:
| 列 | 含义 | 取值 |
|---|---|---|
| Hostname / IP | 主机显示名 | 优先显示 DNS 反解出的主机名,解析失败则显示 IP |
| SSH / Telnet / HTTP / HTTPS / Rlogin / RDP / VNC | 各协议端口是否开放 | Yes / No |
| Open Ports | 开放的端口列表 | 逗号分隔,如22, 3389 |
| Closed Ports | 关闭的端口列表 | 逗号分隔 |
几个值得注意的细节:
- 结果表格只读(
ReadOnly = true)、支持整行多选(MultiSelect+FullRowSelect),方便一次选中多台主机导入; - "Open Ports" 与 "Closed Ports" 列宽 160px,协议列宽 60px;
- 经典实现 ScanHost.cs 中同样提供了
OpenPortsName、ClosedPortsName、HostIPorName等展示属性,还实现了ToString()输出各协议标记的汇总字符串。
批量导入扫描结果
操作方式
扫描完成后,按文档步骤操作:
- 在结果表格中选中需要导入的主机行(可多选);
- 在底部Protocol to import下拉框中选择目标协议(SSH、Telnet、HTTP、HTTPS、Rlogin、RDP、VNC、ARD);
- 点击Import。
导入时的核心逻辑(ImportButtonOnClick):
- 只导入勾选协议对应的端口确实开放的主机:
SupportsProtocol会把所选协议 ID 映射到主机的协议标记上,例如选择 SSH 只导入host.Ssh == true的主机;注意ARD 协议复用 VNC 标记(PluginProtocolIds.Ard => host.Vnc); - 生成
PluginConnectionRequest列表:Hostname取主机显示名(优先主机名),Name取HostNameWithoutDomain(主机名的第一段,如server1.domain.com→server1,若反解失败则直接用 IP); - 通过
_pluginContext.Connections.ImportConnections(requests)把请求交给插件上下文执行导入; - 未选择任何主机或所选主机均不支持该协议时,会给出相应警告("No scan results are selected for import." / "The selected hosts do not expose the chosen protocol.")。
底层:如何变成连接节点
主程序侧的导入链路由 PortScanImporter.cs 与 PortScanDeserializer.cs 构成:
PortScanImporter持有目标ProtocolType,调用PortScanDeserializer.Deserialize(hosts);- 反序列化器为每个符合条件的主机创建
ConnectionInfo:
ConnectionInfo newConnectionInfo = new() { Name = host.HostNameWithoutDomain, // 显示名:主机名第一段或 IP Hostname = host.HostName, // 主机名(含域)或 IP Protocol = finalProtocol }; newConnectionInfo.SetDefaultPort(); // 自动补齐该协议的默认端口- 生成的连接节点被添加到当前选中的目标容器(连接树文件夹)下,完成批量导入。
其中协议映射在PortScanDeserializer.ImportScannedHost的 switch 中:SSH2 ↔host.Ssh、Telnet ↔host.Telnet、HTTP ↔host.Http、HTTPS ↔host.Https、Rlogin ↔host.Rlogin、RDP ↔host.Rdp、VNC/ARD ↔host.Vnc,其余协议类型一律跳过(protocolValid = false)。
测试验证
仓库自带的单元测试 PortScanDeserializerTests.cs 验证了导入行为:给定ScanHost("10.20.30.40"),HostName = "server1.domain.com"、Ssh = true,以ProtocolType.SSH2反序列化后断言:
- 导入连接的
Name等于server1(DisplayNameImported); Hostname等于server1.domain.com(HostNameImported);Protocol等于SSH2(ProtocolImported)。
这三个断言精确对应了"批量导入"的产物形态:主机名精简为显示名、完整主机名作为连接地址、协议按探测结果落定。
使用建议与已知限制
综合文档与源码,实操中需要注意:
- 扫描耗时可观:扫描是逐主机串行执行的(Ping → DNS 反解 → 逐端口 TCP 探测),主机数与端口数越多耗时越长,文档也明确提示 "Wait. Possibly a long time."。若只需确认协议端口,建议勾选 "Default protocol ports only" 并保持合理超时;
- 随时可停止:扫描期间 Scan 按钮变为 Stop,点击即取消(插件实现通过
CancellationTokenSource取消,经典实现通过Ping.SendAsyncCancel取消),取消后已扫描的主机结果会保留在表格中; - 仅 IPv4:地址展开只支持
InterNetwork(IPv4)地址族,IPv6 范围会直接报错; - 依赖网络环境:Ping 与 TCP 探测都会受到防火墙策略影响——主机禁 Ping 会被判为"不可达",从而跳过端口探测并全部标记为关闭;扫描机与目标机之间的访问控制(ACL)会直接影响结果的准确性;
- 协议端口映射固定:只有探测到默认协议端口(22/23/80/443/513/3389/5900)才会点亮对应协议标记;自定义端口即使开放也只计入 Open Ports 列;
- 导入即建连接:导入会直接在当前连接树容器下创建连接节点并调用
SetDefaultPort()补齐端口,之后可按需修改各连接的详细配置。
相关资源
- 功能文档:port_scan.rst
- 导入导出相关说明:import_export.rst
- 插件实现:PortScanPlugin.cs、PortScanControl.cs、PortScanService.cs、PortScanHostResult.cs
- 主程序经典实现:PortScanner.cs、ScanHost.cs
- 导入链路:PortScanImporter.cs、PortScanDeserializer.cs
- 测试用例:PortScanDeserializerTests.cs
- 桌面应用
- 网络
【免费下载链接】mRemoteNG
mRemoteNG is the next generation of mRemote, open source, tabbed, multi-protocol, remote connections manager.
相关推荐
mRemoteNG 连接导入导出完全指南:文件、Active Directory 与端口扫描实战
mRemoteNG 连接导入导出完全指南:文件、Active Directory 与端口扫描实战 导读 本文以 mRemoteNG 官方文档的 Import/E
桌面应用网络mRemoteNG批量修改端口:快速更新多连接的服务端口号
mRemoteNG批量修改端口:快速更新多连接的服务端口号 痛点与解决方案概述 你是否还在逐个修改mRemoteNG中数百个服务器连接的端口号?当企业进行端口规
桌面应用网络osquery网络监控终极指南:连接追踪与端口扫描检测
osquery网络监控终极指南:连接追踪与端口扫描检测 想要实时监控网络连接、检测可疑端口活动吗?osquery作为一款强大的端点安全监控工具,能够通过SQL查
观测代理网络安全
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考