Golang实现TCP代理服务器的首次检测

Golang实现TCP代理服务器的首次检测 我见过一些代码片段,也尝试过很多其他方法,但至今仍未掌握其中的概念。

我有一个TCP代理,我希望它能够验证哪一方先发送数据。

我见过下面这个总是客户端先发送的示例: https://forum.golangbridge.org/t/help-on-a-golang-tcp-forward-proxy/7462

但我想要的是能够知道哪个连接先发送数据,即:

clientConn , err := server.Accept() serverconn, err:= net.Dial(“tcp”, “someserver:25”)

确保上述哪个连接先准备好读取。 我永远无法知道远程服务器是会先向我发送"ehlo",还是仍在测试我,所以如果客户端先发送数据,我想先查看它。 然后如果服务器先发送数据,我想先查看它,然后根据情况做出正确的下一步决定,比如将连接拼接/管道给对方,或者做其他操作。

问题是我并不想深入到FD级别,希望有现成的示例可用。 我考虑过使用通道,但对我来说很难想出如何实现这样的解决方案。


更多关于Golang实现TCP代理服务器的首次检测的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于Golang实现TCP代理服务器的首次检测的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


在Go中实现TCP代理服务器时检测哪一方先发送数据,可以使用net.ConnRead方法结合goroutine和通道来监控两个连接的读取状态。以下是一个实现示例:

package main

import (
    "io"
    "log"
    "net"
    "sync"
)

type firstSender int

const (
    clientFirst firstSender = iota
    serverFirst
)

func proxy(clientConn, serverConn net.Conn) {
    // 创建通道来通知哪一方先发送数据
    firstChan := make(chan firstSender, 1)
    
    var once sync.Once
    
    // 监控客户端连接
    go func() {
        buf := make([]byte, 1)
        _, err := clientConn.Read(buf)
        if err != nil {
            return
        }
        
        // 将读取的数据放回连接中
        clientConn = &prependConn{
            Conn: clientConn,
            data: buf,
        }
        
        once.Do(func() {
            firstChan <- clientFirst
        })
    }()
    
    // 监控服务端连接
    go func() {
        buf := make([]byte, 1)
        _, err := serverConn.Read(buf)
        if err != nil {
            return
        }
        
        // 将读取的数据放回连接中
        serverConn = &prependConn{
            Conn: serverConn,
            data: buf,
        }
        
        once.Do(func() {
            firstChan <- serverFirst
        })
    }()
    
    // 等待检测结果
    first := <-firstChan
    close(firstChan)
    
    switch first {
    case clientFirst:
        log.Println("客户端先发送数据")
        // 处理客户端先发送的逻辑
    case serverFirst:
        log.Println("服务端先发送数据")
        // 处理服务端先发送的逻辑
    }
    
    // 继续代理连接
    var wg sync.WaitGroup
    wg.Add(2)
    
    go func() {
        defer wg.Done()
        io.Copy(serverConn, clientConn)
        serverConn.Close()
    }()
    
    go func() {
        defer wg.Done()
        io.Copy(clientConn, serverConn)
        clientConn.Close()
    }()
    
    wg.Wait()
}

// prependConn 用于将已读取的数据放回连接中
type prependConn struct {
    net.Conn
    data []byte
    read bool
}

func (p *prependConn) Read(b []byte) (n int, err error) {
    if !p.read && len(p.data) > 0 {
        n = copy(b, p.data)
        if n < len(p.data) {
            p.data = p.data[n:]
        } else {
            p.data = nil
            p.read = true
        }
        return n, nil
    }
    return p.Conn.Read(b)
}

func main() {
    listener, err := net.Listen("tcp", ":8080")
    if err != nil {
        log.Fatal(err)
    }
    defer listener.Close()
    
    for {
        clientConn, err := listener.Accept()
        if err != nil {
            log.Println("Accept error:", err)
            continue
        }
        
        serverConn, err := net.Dial("tcp", "someserver:25")
        if err != nil {
            clientConn.Close()
            log.Println("Dial server error:", err)
            continue
        }
        
        go proxy(clientConn, serverConn)
    }
}

这个实现的关键点:

  1. 使用两个goroutine分别监控客户端和服务端连接的读取操作
  2. 使用sync.Once确保只有第一个发送数据的连接会被记录
  3. 使用prependConn结构体将已读取的字节放回连接中,确保数据不会丢失
  4. 通过通道firstChan传递检测结果

当检测到哪一方先发送数据后,你可以根据业务需求在相应的case分支中添加处理逻辑。检测完成后,连接会继续正常的双向代理通信。

回到顶部