跳到主要内容

SimpleTCPServer 主动代理

SimpleTCPServer 监听一个本地端口,接受客户端连接后向上游 dial,再双向复制流量并按链路号序记录到任意 io.Writer / WriteFunc。这是 gogetway 主动代理模式的核心结构。

源码:getwayServer/simpleTCPserver.go


1. 结构体

type SimpleTCPServer struct {
Forward string // 上游转发地址,如 "127.0.0.1:8080"
Port string // 本地监听地址,如 ":9000"
ListenType Types.ClientType // 监听协议类型:TCPType=1 / HTTPType=2
WriteType string // 预留:写入类型标识(File / Other)
ClientRespParse ClientRespParse // 客户端方向流量分析钩子
ForwardRespParse ClientRespParse // 转发方向流量分析钩子
Writer *os.File // 默认 Writer(已由 ResourceGroup 接管,推荐用 WithWriterAndFunc 注入)

// 私有字段省略
}
字段类型必填说明
Forwardstring上游目标,net.Dial("tcp", ...) 使用
Portstring本地监听地址,需带冒号(":9000"
ListenTypeTypes.ClientType通过 getwayServer.TCPType / HTTPType 选择,影响包格式标记
ClientRespParseClientRespParse客户端→上游 方向的流量分析钩子,需手动开启 startAnalyze
ForwardRespParseClientRespParse上游→客户端 方向的流量分析钩子
Writer*os.File已弱化,建议通过构造函数传入

2. 构造函数

2.1 NewSimpleTCPServer

func NewSimpleTCPServer(ForwardAdd, LocalAdd string, ListenType Types.ClientType) *SimpleTCPServer

最简单的构造,会在当前目录创建 log1.txt 作为录制文件。生产不推荐,会污染当前目录。

2.2 NewSimpleTCPServerWithWriterAndFunc(推荐)

func NewSimpleTCPServerWithWriterAndFunc(
ForwardAdd, LocalAdd string,
ListenType Types.ClientType,
writer io.Writer,
writeFunc WriteFunc,
) *SimpleTCPServer
参数类型说明
ForwardAddstring上游地址
LocalAddstring本地监听地址
ListenTypeTypes.ClientType监听类型
writerio.Writer默认写入器,可传 io.Discard 表示不落盘
writeFuncWriteFunc自定义写入函数,优先级高于 writer(非 nil 时只调用它)

2.3 NewSimpleTCPServerWithLockGroup

func NewSimpleTCPServerWithLockGroup(
ForwardAdd, LocalAdd string,
ListenType Types.ClientType,
lockGroup lockMap.LockGroup,
) *SimpleTCPServer

需要自定义锁分配(如 per-tenant 隔离/限流)时使用。

2.4 NewSimpleTcpServerWithResourceGroup

func NewSimpleTcpServerWithResourceGroup(
ForwardAdd, LocalAdd string,
ListenType Types.ClientType,
group ResourceGroup,
) *SimpleTCPServer

完全自定义资源池(连接池接入、按租户分文件等)用这个。


3. 实例方法

3.1 StartListen()

func (s *SimpleTCPServer) StartListen()
  • 入参:无
  • 返回:无(阻塞,直到收到 SIGINT/SIGTERM 调用 os.Exit(0)
  • 副作用:监听 s.Port,每条连接 spawn 一个 goroutine

注意StartListen 内部会安装信号处理器并直接 os.Exit。如果你需要在自己的进程里集成,应当自行重新实现这一段,改成上层 context 控制。

3.2 SetConnectTargetFunc(connectFunc ConnectTarget)

func (s *SimpleTCPServer) SetConnectTargetFunc(connectFunc ConnectTarget)

注入自定义的"上游 dial"逻辑,详见下面的 ConnectTarget

3.3 UpdateResourceGroup(resourceGroup ResourceGroup)

func (s *SimpleTCPServer) UpdateResourceGroup(resourceGroup ResourceGroup)
  • 入参:resourceGroup 不能为 nil(否则 panic)
  • 用途:启动前替换默认的 DefaultResourceGroup

4. Hook 类型

4.1 WriteFunc — 自定义写入行为

type WriteFunc func(data []byte, ctx context.Context) (offset int, err error)
参数类型说明
data[]byte已经过 proto.WriteProto 封装的字节流
ctxcontext.Context可读取 ListenType / FromIP / ToIP / FromTogetwayServer/consts.go
返回值类型说明
offsetint已写入字节数(可不返回准确值,只用作日志)
errerror非 nil 时仅记日志,不会中断流量

典型用途:写数据库、Kafka、自定义日志格式、按链路分文件。

writeFunc := func(data []byte, ctx context.Context) (int, error) {
fromTo, _ := ctx.Value(getwayServer.FromTo).(string)
log.Printf("link=%s bytes=%d", fromTo, len(data))
return kafkaProducer.Send(data)
}

4.2 ClientRespParse — 流量分析钩子

type ClientRespParse func(ctx context.Context, DataPaket *proto.Packet) (isContinue bool, err error)
参数类型说明
ctxcontext.ContextFromIP/ToIP/FromTo/ListenType
DataPaket*proto.Packet当前流的快照
返回值类型说明
isContinueboolfalse 表示丢弃当前包,不继续记录
errerror非 nil 时立刻终止流(从 NetTeeReader.Read 返回错误)

仅在 s.startAnalyze.Get() == true 时才生效,目前没有公开 setter,需要自己向上层提后续 PR 暴露。

4.3 ConnectTarget

type ConnectTarget func(ctx context.Context, client net.Conn) (net.Conn, error)
参数类型说明
ctxcontext.Context来自 contextPool 的复用 ctx
clientnet.Conn客户端入站连接,可读取 RemoteAddr 做路由

典型用途:基于 client 信息动态选择上游、TLS 卸载、连接池接入。

server.SetConnectTargetFunc(func(ctx context.Context, client net.Conn) (net.Conn, error) {
if strings.HasPrefix(client.RemoteAddr().String(), "10.") {
return net.Dial("tcp", "internal-upstream:8080")
}
return tls.Dial("tcp", "external-upstream:443", tlsConfig)
})

5. 完整示例

recordFile, _ := os.Create("./traffic.log")
defer recordFile.Close()

writeFunc := func(data []byte, ctx context.Context) (int, error) {
fromTo, _ := ctx.Value(getwayServer.FromTo).(string)
log.Printf("recorded %d bytes from %s", len(data), fromTo)
return len(data), nil
}

server := getwayServer.NewSimpleTCPServerWithWriterAndFunc(
"127.0.0.1:8080", // 上游
":9000", // 本地监听
getwayServer.TCPType,
recordFile,
writeFunc,
)

server.SetConnectTargetFunc(func(ctx context.Context, client net.Conn) (net.Conn, error) {
return net.Dial("tcp", "127.0.0.1:8080")
})

server.StartListen() // 阻塞

6. 常见坑

  1. NewSimpleTCPServer(无参版本) 会在当前目录创建 log1.txt,生产环境请用 NewSimpleTCPServerWithWriterAndFunc 显式传 writer。
  2. writeFunc 优先于 writer:两者都传时只调用 writeFunc。需要双写时请自己在 writeFunc 里同时写 writer
  3. StartListen 直接 os.Exit:嵌入到现有进程时要重写信号处理。
  4. WriteQueue 保证写入顺序:每条 client 连接独立的 WriteQueue,跨连接顺序不保证。

参考:ConnectResource / ResourceGroup · GopacketTrafficMirror