引言
在上一篇文章中,我们深入探讨了 nps/server/proxy/base.go
中定义的通用代理基础和流量控制机制。本篇文章将聚焦于 NPS 最常用也是最基础的两种代理模式:TCP 隧道和 HTTP 代理。我们将通过分析 nps/server/proxy/tcp.go
文件,揭示这两种模式的具体实现细节。
tcp.go
:TCP 隧道与 HTTP 代理的实现
tcp.go
文件主要定义了 TunnelModeServer
结构体,它是实现 TCP 隧道和 HTTP 代理的核心。此外,该文件还包含了 WebServer
结构体,用于启动 NPS 的 Web 管理界面。
TunnelModeServer
:通用隧道模式服务器
TunnelModeServer
结构体继承了 BaseServer
,并增加了两个特定于隧道模式的字段:
type TunnelModeServer struct {
BaseServer
process process
listener net.Listener
}
BaseServer
:继承了base.go
中定义的通用功能,如流量统计、安全检查等。process process
:这是一个函数类型type process func(c *conn.Conn, s *TunnelModeServer) error
,它定义了如何处理传入的客户端连接。不同的代理模式会传入不同的process
函数。listener net.Listener
:用于监听传入连接的网络监听器。
NewTunnelModeServer()
函数用于创建并初始化一个 TunnelModeServer
实例。
Start()
方法:启动监听
TunnelModeServer
的 Start()
方法负责启动 TCP 监听,并为每个传入连接调用 process
函数进行处理:
func (s *TunnelModeServer) Start() error {
return conn.NewTcpListenerAndProcess(s.task.ServerIp+":"+strconv.Itoa(s.task.Port), func(c net.Conn) {
// ... 流量和连接数检查 ...
logs.Trace("new tcp connection,local port %d,client %d,remote address %s", s.task.Port, s.task.Client.Id, c.RemoteAddr())
s.process(conn.NewConn(c), s) // 调用具体的处理函数
s.task.Client.AddConn()
}, &s.listener)
}
conn.NewTcpListenerAndProcess()
:这是一个辅助函数,用于创建一个 TCP 监听器,并在有新连接到来时,在一个新的 goroutine 中调用传入的处理函数。- 流量和连接数检查:在处理新连接之前,会调用
s.CheckFlowAndConnNum()
对客户端的流量和连接数进行检查,如果超出限制则直接关闭连接。 - 调用
s.process()
:这是关键一步,它将传入的net.Conn
封装为conn.Conn
,然后调用TunnelModeServer
实例中存储的process
函数来处理具体的代理逻辑。
Close()
方法:关闭监听
TunnelModeServer
的 Close()
方法非常简单,它仅仅关闭了底层的 net.Listener
,从而停止接收新的连接:
func (s *TunnelModeServer) Close() error {
return s.listener.Close()
}
ProcessTunnel()
:实现 TCP 隧道
ProcessTunnel()
函数是 process
类型的一个具体实现,它负责处理标准的 TCP 隧道连接。当 TunnelModeServer
以 tcp
或 file
模式启动时,会使用这个函数。
func ProcessTunnel(c *conn.Conn, s *TunnelModeServer) error {
targetAddr, err := s.task.Target.GetRandomTarget() // 获取目标地址
if err != nil {
c.Close()
logs.Warn("tcp port %d ,client id %d,task id %d connect error %s", s.task.Port, s.task.Client.Id, s.task.Id, err.Error())
return err
}
// 调用 BaseServer 的 DealClient 方法进行数据转发
return s.DealClient(c, s.task.Client, targetAddr, nil, common.CONN_TCP, nil, s.task.Client.Flow, s.task.Target.LocalProxy, s.task)
}
- 获取目标地址:
s.task.Target.GetRandomTarget()
从隧道配置中获取一个随机的目标地址。NPS 支持配置多个目标地址,实现负载均衡。 - 调用
s.DealClient()
:这是核心的数据转发逻辑。ProcessTunnel
将客户端连接 (c
)、客户端信息 (s.task.Client
)、目标地址 (targetAddr
) 以及其他相关参数传递给BaseServer
的DealClient()
方法。DealClient()
会负责与目标建立连接,并在客户端和目标之间进行双向数据拷贝,同时处理加密、压缩和流量统计等通用逻辑。
ProcessHttp()
:实现 HTTP 代理
ProcessHttp()
函数是 process
类型的一个具体实现,它负责处理 HTTP 代理连接。当 TunnelModeServer
以 httpProxy
模式启动时,会使用这个函数。
func ProcessHttp(c *conn.Conn, s *TunnelModeServer) error {
_, addr, rb, err, r := c.GetHost() // 从 HTTP 请求中解析出目标地址
if err != nil {
c.Close()
logs.Info(err)
return err
}
if r.Method == "CONNECT" { // 处理 HTTPS CONNECT 请求
c.Write([]byte("HTTP/1.1 200 Connection established\r\n\r\n"))
rb = nil
}
if err := s.auth(r, c, s.task.Client.Cnf.U, s.task.Client.Cnf.P); err != nil { // 认证检查
return err
}
// 调用 BaseServer 的 DealClient 方法进行数据转发
return s.DealClient(c, s.task.Client, addr, rb, common.CONN_TCP, nil, s.task.Client.Flow, s.task.Target.LocalProxy, nil)
}
- 解析 HTTP 请求:
c.GetHost()
用于从传入的 TCP 连接中解析出 HTTP 请求的 Host 头,从而获取目标地址 (addr
) 和请求的原始字节 (rb
)。 - 处理 HTTPS CONNECT 请求:如果 HTTP 方法是
CONNECT
(通常用于 HTTPS 代理),NPS 会向客户端发送HTTP/1.1 200 Connection established
响应,表示连接已建立,然后后续的数据将直接在客户端和目标之间转发。 - 认证检查:调用
s.auth()
对 HTTP 请求进行认证,如果配置了用户名和密码,则会进行验证。 - 调用
s.DealClient()
:与ProcessTunnel
类似,ProcessHttp
也将解析出的目标地址和请求数据传递给BaseServer
的DealClient()
方法进行数据转发。
WebServer
:Web 管理界面的启动
tcp.go
文件中还包含了 WebServer
结构体和相关方法,用于启动 NPS 的 Web 管理界面。虽然它与 TCP 隧道和 HTTP 代理的逻辑不同,但由于其在 server.go
中通过 NewMode()
被 webServer
模式调用,所以被放在了同一个文件中。
WebServer
结构体:继承BaseServer
,但其Start()
方法主要负责配置 Beego Web 框架的静态文件路径、视图路径,并启动 HTTP 或 HTTPS 服务来提供 Web 管理界面。NewWebServer()
:用于创建WebServer
实例。
总结
nps/server/proxy/tcp.go
文件是 NPS 实现 TCP 隧道和 HTTP 代理的关键。通过 TunnelModeServer
结构体和 process
函数的抽象,NPS 能够以统一的方式处理这两种代理模式。ProcessTunnel()
和 ProcessHttp()
分别实现了各自的代理逻辑,并最终都依赖于 BaseServer
的 DealClient()
方法进行实际的数据转发和通用功能处理。WebServer
的存在则确保了 NPS 拥有一个功能完善的 Web 管理界面。
在下一篇文章中,我们将继续探索 NPS 的其他代理模式,例如 SOCKS5 代理。