NPS 服务端核心解析:架构与流量管理

引言

在 NPS 项目概述中,我们对这款强大的内网穿透工具进行了宏观的介绍。本篇文章将深入 NPS 的服务端(Server)核心,从 nps/server/server.go 文件入手,详细剖析其架构设计、任务管理、流量处理以及系统监控等关键功能。理解 server.go 的运作机制,是掌握 NPS 核心原理的基石。

server.go:服务端的"大脑"

server.go 文件是 NPS 服务端的启动入口和主要协调者。它负责初始化各项服务、管理客户端连接、调度隧道任务以及收集系统运行状态。

核心组件概览

server.go 中,我们可以看到几个关键的全局变量和结构:

  • Bridge *bridge.Bridge:这是服务端与客户端之间通信的核心桥梁。它负责维护客户端连接、处理客户端发送的指令以及转发数据。
  • RunList sync.Map:一个并发安全的 Map,用于存储当前正在运行的所有隧道(proxy.Service 实例)。通过 sync.Map,NPS 能够高效地管理和访问多个并发的隧道服务。
  • once sync.Once:用于确保某些初始化操作(如 flowSession)只执行一次。

NPS 服务端核心组件架构可以用下图表示:

graph TD A[server.go] --> B[Bridge] A --> C[RunList] A --> D[once] B --> B1[客户端连接管理] B --> B2[指令处理] B --> B3[数据转发] C --> C1[TCP隧道服务] C --> C2[UDP隧道服务] C --> C3[HTTP代理服务] C --> C4[SOCKS5服务] C --> C5[Web管理服务] D --> D1[流量数据持久化]

初始化与任务加载

服务端的启动从初始化开始:

  • init() 函数:简单地初始化 RunList 为一个空的 sync.Map
  • InitFromCsv() 函数
    • 负责从持久化存储(通过 file.GetDb().JsonDb 访问,通常是 JSON 文件)中加载预设的隧道任务。
    • 如果配置了 public_vkey(公共验证密钥),会创建一个特殊的公共客户端,允许未注册的客户端通过此密钥连接。
    • 遍历所有已存储的隧道任务,如果任务状态为 true(启用),则调用 AddTask() 函数将其启动。

服务端核心循环:DealBridgeTask()

DealBridgeTask() 是服务端的一个关键 goroutine,它在一个无限循环中监听来自 Bridge 的各种事件通道,实现对隧道和客户端的动态管理:

  • Bridge.OpenTask:当有新的隧道任务需要启动时,从该通道接收任务并调用 AddTask()
  • Bridge.CloseTask:当有隧道任务需要停止时,从该通道接收任务 ID 并调用 StopServer()
  • Bridge.CloseClient:当客户端断开连接或需要被移除时,从该通道接收客户端 ID,并调用 DelTunnelAndHostByClientId() 删除该客户端关联的所有隧道和主机。
  • Bridge.SecretChan:处理特殊的"秘密连接"。如果连接的密码与某个启用状态的隧道匹配,则会启动一个 proxy.NewBaseServer 来处理该连接。

DealBridgeTask() 的处理流程可以用下图表示:

flowchart TD A[DealBridgeTask循环] --> B{事件类型?} B -->|OpenTask| C[接收新任务] C --> D[调用AddTask启动隧道] B -->|CloseTask| E[接收关闭任务ID] E --> F[调用StopServer停止隧道] B -->|CloseClient| G[接收客户端ID] G --> H[调用DelTunnelAndHostByClientId] B -->|SecretChan| I[接收秘密连接] I --> J{密码匹配?} J -->|是| K[启动proxy.NewBaseServer] J -->|否| L[忽略连接] D --> M[更新RunList] F --> M H --> M K --> M L --> M M --> A

服务端启动流程:StartNewServer()

StartNewServer() 是 NPS 服务端的主启动函数,它负责协调各项服务的启动:

  1. 初始化 Bridge:根据配置的桥接端口 (bridgePort)、类型 (bridgeType) 和断开连接策略 (bridgeDisconnect),创建一个 bridge.Bridge 实例。
  2. 启动 Bridge 监听:在一个独立的 goroutine 中启动 Bridge.StartTunnel(),使其开始监听客户端连接。
  3. 启动 P2P 服务:如果 p2p_port 在配置中指定,NPS 会启动多个 P2P 服务器实例,用于支持点对点连接。
  4. 启动任务处理协程:启动 DealBridgeTask() goroutine,开始处理来自 Bridge 的任务事件。
  5. 启动客户端流量处理:启动 dealClientFlow() goroutine,定期处理客户端的流量数据。
  6. 启动主代理服务:根据传入的隧道配置 (cnf.Mode),通过 NewMode() 函数实例化对应的 proxy.Service,并在独立的 goroutine 中启动它。这个 proxy.Service 实例会被存储在 RunList 中。

服务端启动流程可以用下图表示:

graph TD A[StartNewServer] --> B[初始化Bridge] B --> C[启动Bridge监听] C --> D[启动P2P服务] D --> E[启动任务处理协程] E --> F[启动客户端流量处理] F --> G[启动主代理服务] G --> H[存储到RunList] H --> I[服务端启动完成] C --> C1[goroutine: Bridge.StartTunnel] E --> E1[goroutine: DealBridgeTask] F --> F1[goroutine: dealClientFlow] G --> G1[goroutine: proxy.Service]

代理模式工厂:NewMode()

NewMode() 函数是 NPS 服务端实现多协议支持的关键。它根据隧道配置中的 Mode 字段,返回不同的 proxy.Service 实现,每种实现对应一种代理模式:

  • tcp / file:通过 proxy.NewTunnelModeServer(proxy.ProcessTunnel, ...) 创建,用于标准的 TCP 流量转发。
  • socks5:通过 proxy.NewSock5ModeServer(...) 创建,提供 SOCKS5 代理服务。
  • httpProxy:通过 proxy.NewTunnelModeServer(proxy.ProcessHttp, ...) 创建,用于 HTTP 代理。
  • tcpTrans:通过 proxy.NewTunnelModeServer(proxy.HandleTrans, ...) 创建,用于 TCP 透明传输。
  • udp:通过 proxy.NewUdpModeServer(...) 创建,用于 UDP 流量转发。
  • webServer:这是一个特殊的模式,它会初始化数据库中的任务,并启动一个 httpHostServer 类型的隧道,然后通过 proxy.NewWebServer(...) 创建 Web 管理界面服务。
  • httpHostServer:通过 proxy.NewHttp(...) 创建,用于处理 HTTP/HTTPS 域名解析和代理。

隧道任务生命周期管理

server.go 提供了一系列函数来管理隧道任务的生命周期:

  • AddTask(t *file.Tunnel):添加并启动一个新的隧道任务。它会根据任务模式进行一些预处理(如端口测试),然后通过 NewMode() 实例化并启动对应的代理服务。
  • StopServer(id int):停止指定 ID 的隧道服务。它会从 RunList 中查找对应的服务实例,调用其 Close() 方法,并更新数据库中任务的状态。
  • StartTask(id int):启动一个已存在的、但当前处于停止状态的隧道任务。
  • DelTask(id int):删除指定 ID 的隧道任务,如果任务正在运行,会先停止它。

客户端与流量数据处理

  • dealClientFlow():一个定时器(每分钟触发一次)协程,用于定期调用 dealClientData()
  • dealClientData():遍历所有客户端,更新它们的在线状态、最后在线时间、版本信息,并计算总的流入/流出流量。这些数据用于 Web 管理界面的展示。
  • DelTunnelAndHostByClientId(clientId int, justDelNoStore bool):删除指定客户端 ID 关联的所有隧道和主机配置。
  • DelClientConnect(clientId int):断开指定客户端 ID 的连接。

仪表盘数据获取:GetDashboardData()

GetDashboardData() 函数负责收集并返回用于 Web 管理界面仪表盘的数据,包括:

  • NPS 版本、主机数量、客户端数量(在线/总数)。
  • 总流入/流出流量。
  • 各种隧道类型(TCP、UDP、SOCKS5、HTTP 代理、秘密代理、P2P)的数量。
  • 桥接类型、HTTP/HTTPS 代理端口、IP 限制、流量存储间隔、P2P IP/端口、日志级别等配置信息。
  • 系统资源使用情况:CPU 使用率、系统负载、内存使用率(Swap 和 Virtual Memory)、网络 I/O 和 TCP 连接数。

数据持久化:flowSession()

flowSession() 是一个独立的 goroutine,它会根据配置的间隔(默认为 1 分钟)定时将主机、任务、客户端和全局流量数据持久化到 JSON 文件中,确保数据在服务重启后不会丢失。

总结

nps/server/server.go 作为 NPS 服务端的“大脑”,通过精巧的并发设计和模块化管理,实现了强大的内网穿透功能。它不仅负责启动和协调各种代理服务,还提供了完善的任务生命周期管理、客户端连接管理、流量统计以及系统监控能力。对 server.go 的深入理解,为我们后续剖析 NPS 的具体代理实现和客户端通信奠定了基础。

在下一篇文章中,我们将继续深入 nps/server/proxy 目录,探索不同代理模式的具体实现细节。