能否通过WebSockets或其他协议传输TCP连接?

9

我想知道是否可以通过Websockets协议传输/隧道化TCP连接?

有一个 Websockets包,似乎我可以将TCP连接复制到Websockets连接中,但我不知道如何在另一端重新组装它作为TCPConn。

例如,我想在一端创建一个socks5服务器N(位于NAT后面),并在另一端创建一个ServerIE。互联网用户会通过socks5协议向ServerIE发出请求,而ServerIE会将该请求通过websocket连接发送到ServerNAT。 ServerNAT然后将处理socks5请求(即它是一个socks5服务器)。

编辑: 我写了一些代码,但不幸的是它在从TCP连接读取到WS时被卡住了。 要运行它:go run main.go -logtostderr

package main

import (
    "flag"

    log "github.com/golang/glog"
    "golang.org/x/net/websocket"
    "bufio"
    "net"
    "net/http"
    "time"
//  "io"
)

func main() {
    flag.Parse()
    defer log.Flush()
    log.Infof("wsServer()")
    wsCh := wsServer()
    log.Infof("wsNAT()")
    natWS, err := wsNAT()
    if err != nil {
        log.Error(err)
        return
    }
    done := make(chan struct{}, 1)
        log.Infof("listenTCP()")
        conn := listenTCP()
        buf := bufio.NewReader(conn)
    go func() {
        log.Infof("Write TCP to WS")
        n, err := buf.WriteTo(natWS)
        if err != nil {
            log.Fatal(err)
            return
        }
        log.Infof("newConn.ws.ReadFrom(conn) %v", n)
        close(done)
    }()
    wsToTCP(<- wsCh)
    <-done
}
// wsNAT dials /ws endpoint and returns a websocket connection.
func wsNAT() (*websocket.Conn, error) {
    time.Sleep(1 * time.Second)
    origin := "http://localhost/"
    url := "ws://localhost:12345/ws"
    return websocket.Dial(url, "", origin)
}
 // wsServer returns the websocket
 // connections received on /ws endpoint
func wsServer() chan *websocket.Conn {
    wsCh := make(chan *websocket.Conn, 1)
    http.Handle("/ws", websocket.Handler(func(ws *websocket.Conn) {
        wsCh <- ws
        time.Sleep(1 *time.Hour)
    }))
    go func() {
        err := http.ListenAndServe(":12345", nil)
        if err != nil {
            panic("ListenAndServe: " + err.Error())
        }
    }()
    return wsCh
}
// wsToTCP reads a ws connection and converts its payload
// to a TCP connection.
func wsToTCP(ws *websocket.Conn) {
    conn := &net.TCPConn{}
    var msg []byte
    if _, err := ws.Read(msg); err != nil {
        log.Error(err)
        return
    }
    log.Infof("msg %v", msg)
    n, err := conn.Write(msg)
    if err != nil {
        log.Errorf("conn.Write %v, msg %v", err, msg)
        return
    }
    log.Infof("conn is %v, copied %v", conn, n)
    // END: do something with the connection
    // ..... 
}
//ListenTCP returns the first TCP connection received on port 8080
func listenTCP() *net.TCPConn {
    addr := &net.TCPAddr{IP: net.ParseIP("0.0.0.0"), Port: 8080}
    lr, err := net.ListenTCP("tcp", addr)
    if err != nil {
        panic(err)

    }
    defer lr.Close()
    ieConn, err := lr.AcceptTCP()
    if err != nil {
        panic(err)
    }
    log.Infof("connection accepted")
    return ieConn
}

为了触发TCP监听器,您可以在8080端口上发出请求(例如curl --socks5 localhost:8080 google.com -vcurl localhost:8080 -v)。

1
你是不是想要类似 wstunnel 或者 websockets-proxy 这样的东西? - Steffen Ullrich
1
是的,看起来wstunnel就是我需要的,尽管JavaScript不是我的强项。 - Anthony Hunt
1
websockets-proxy 似乎也可以做同样的事情... 是否有 Go 的等效工具呢? - Anthony Hunt
我不知道Go语言的等效替代。 - Steffen Ullrich
1个回答

6
这应该不是什么问题。我已经在许多协议上构建了TCP转发器,虽然从未使用过WebSockets,但我认为它不会有任何不同。
你有两个问题:
1. 正确的连接方式是使用net.Dial,而不是仅创建net.TCPConn。 2. 你只复制了一个方向。你需要在两个方向上都复制。
以下是我今天早上为其他目的构建的超级简单代理(我的完整代码会不时损坏数据包,以便测试不良连接)。它应该可以转换成许多其他用途:
func run(inPort int, dest string) {
    l, err := net.Listen("tcp", fmt.Sprintf(":%d", inPort))
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
    fmt.Printf("http://%v -> %s\n", l.Addr(), dest)

    for {
        conn, err := l.Accept()
        if err != nil {
            log.Fatal(err)
        }

        fmt.Printf("Connection from %s\n", conn.RemoteAddr())
        go proxy(conn, dest)
    }
}

func proxy(in net.Conn, dest string) {
    out, err := net.Dial("tcp", dest)
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }

    go io.Copy(out, in)
    io.Copy(in, out)
    fmt.Printf("Disconnect from %s\n", in.RemoteAddr())
}

您只需要将此处的net.Dial替换为对服务器的WS调用,并在服务器端使用net.Dial将其转发。

但最关键的可能是最后几行。您必须进行双向复制,人们经常会忘记这一点。


刚才我在考虑使用“假”监听器和拨号来创建TCP连接。我会尝试一下,然后告诉你结果如何。 - Anthony Hunt
我已经尝试过这个方法,但似乎没有进展。我已更新我的答案,并附上了我尝试过的代码。构造一个“虚假”的监听器和拨号器只是为了建立TCP连接,这种理想是错误的,也行不通。 - Anthony Hunt
值得注意的是,在您的示例中,您只有两个参与方,但我有三个(NAT、服务器和IE)。 - Anthony Hunt
我已经更新了我的答案,提供了最终的解决方案。实际上,我不得不拨打一个虚假的监听器,这让我感到非常尴尬(即在本地端口上保持侦听,并通过Websockets接收到的数据进行拨号)。我想知道是否可能为net包创建一个构造函数,以从Websockets(一个io.ReadWriter)中读取并返回*net.Conn,而无需使用虚假的拨号器/监听器。 - Anthony Hunt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接