引言
在 NPS 服务端核心解析中,我们了解了 server.go 如何作为服务端的“大脑”,协调各项任务。本篇文章将深入 NPS 的代理实现细节,从 nps/server/proxy/base.go 文件入手,剖析所有代理服务共用的基础结构、接口定义以及流量控制、安全检查等通用逻辑。理解这些通用组件,有助于我们更好地把握 NPS 多样化代理模式的实现原理。
base.go:代理服务的基石
base.go 文件定义了 NPS 中所有代理服务的基础抽象和通用功能。它确保了不同代理模式(如 TCP、UDP、SOCKS5、HTTP 等)能够遵循统一的接口规范,并共享一些核心的辅助功能。
核心接口定义
base.go 中定义了两个重要的接口:
-
Service interface:type Service interface { Start() error Close() error }这是所有 NPS 代理服务必须实现的接口。任何代理服务,无论是 TCP 隧道、SOCKS5 代理还是 HTTP 代理,都必须提供
Start()方法来启动服务,以及Close()方法来优雅地关闭服务。这体现了面向接口编程的思想,使得 NPS 能够以统一的方式管理和操作各种代理服务。 -
NetBridge interface:type NetBridge interface { SendLinkInfo(clientId int, link *conn.Link, t *file.Tunnel) (target net.Conn, err error) }这个接口定义了与
bridge模块(服务端与客户端通信的桥梁)交互的方法。SendLinkInfo方法负责将客户端的连接信息 (link) 和隧道信息 (t) 发送给bridge,并从bridge获取到与目标服务建立的连接 (target net.Conn)。这表明proxy模块本身并不直接与客户端通信,而是通过bridge模块进行抽象和解耦。
NPS 代理服务的核心接口可以用下图表示:
BaseServer:通用代理服务结构体
BaseServer 是一个嵌入在具体代理服务结构体中的基础结构体,它包含了所有代理服务通用的属性和方法:
type BaseServer struct {
id int
bridge NetBridge
task *file.Tunnel
errorContent []byte
sync.Mutex
}
id int:代理服务的唯一标识符。bridge NetBridge:实现了NetBridge接口的实例,用于与客户端通信。task *file.Tunnel:当前代理服务所对应的隧道配置信息。errorContent []byte:当连接失败时,发送给客户端的错误内容。sync.Mutex:用于保护BaseServer内部状态的并发访问,特别是流量统计相关的操作。
NewBaseServer() 函数用于创建并初始化一个 BaseServer 实例。
流量统计与控制
BaseServer 提供了核心的流量统计和控制功能,确保 NPS 能够对每个隧道和客户端的流量进行精确管理:
FlowAdd(in, out int64):- 功能:累加当前隧道 (
s.task) 的流入 (in) 和流出 (out) 流量。 - 实现:通过
s.Lock()和s.Unlock()确保并发安全,避免流量统计数据出现竞态条件。
- 功能:累加当前隧道 (
FlowAddHost(host *file.Host, in, out int64):- 功能:累加指定主机 (
host) 的流入 (in) 和流出 (out) 流量。 - 实现:同样通过互斥锁保证并发安全。
- 功能:累加指定主机 (
CheckFlowAndConnNum(client *file.Client):- 功能:检查客户端的流量是否超出限制,以及当前连接数是否超出客户端允许的最大连接数。
- 流量限制:如果客户端配置了
FlowLimit(流量限制),并且当前已用流量 (ExportFlow + InletFlow) 超过限制,则返回错误。 - 连接数限制:通过
client.GetConn()方法检查客户端是否还有可用的连接数。 - 重要性:这是 NPS 实现资源管理和防止滥用的关键机制。
流量统计与控制的流程可以用下图表示:
安全与认证
base.go 也包含了基本的安全检查和认证逻辑:
auth(r *http.Request, c *conn.Conn, u, p string):- 功能:对 HTTP 请求进行基本认证。
- 实现:如果配置了用户名 (
u) 和密码 (p),则调用common.CheckAuth()检查请求头中的认证信息。如果认证失败,则发送 401 Unauthorized 响应并关闭连接。
IsGlobalBlackIp(ipPort string):- 功能:判断传入的 IP 地址是否在全局黑名单中。
- 实现:从全局配置中获取黑名单列表,并使用
in()辅助函数进行查找。
common.IsBlackIp(ipPort string, client.VerifyKey, client.BlackIpList):- 功能:判断传入的 IP 地址是否在客户端特定的黑名单中。
- 重要性:这些黑名单机制可以有效防止恶意扫描和攻击。
连接处理核心:DealClient()
DealClient() 是 BaseServer 中处理客户端连接的核心方法。它负责建立客户端与目标服务之间的连接,并进行数据转发:
- 黑名单检查:首先检查客户端的远程 IP 地址是否在全局黑名单或客户端特定的黑名单中。如果在黑名单中,则直接关闭连接。
- 构建
conn.Link:根据连接类型 (tp)、目标地址 (addr)、加密 (Crypt)、压缩 (Compress)、客户端远程地址 (c.Conn.RemoteAddr().String())、本地代理 (localProxy) 和协议版本 (protoVersion) 等信息,构建一个conn.Link结构体。conn.Link封装了连接的元数据和传输特性。 - 通过
bridge获取目标连接:调用s.bridge.SendLinkInfo(client.Id, link, s.task)将link信息发送给bridge模块。bridge模块会负责与客户端建立实际的隧道连接,并返回一个与目标服务建立的net.Conn。 - 数据拷贝:如果成功获取到目标连接,则调用
conn.CopyWaitGroup(target, c.Conn, ...)开始在客户端连接 (c.Conn) 和目标连接 (target) 之间进行双向数据拷贝。CopyWaitGroup会处理加密、压缩、流量限制等逻辑。
DealClient() 的处理流程可以用下图表示:
总结
nps/server/proxy/base.go 为 NPS 的所有代理服务提供了坚实的基础。它通过定义统一的接口、提供通用的 BaseServer 结构体,并封装了流量统计、安全检查和连接处理等核心功能,极大地简化了不同代理模式的实现。理解 base.go 的设计,有助于我们更好地理解 NPS 模块化和可扩展的架构。
在下一篇文章中,我们将开始深入具体的代理模式实现,首先从最基础的 TCP 代理 (tcp.go) 开始。