可以通过WebSockets或其他协议传输TCP连接吗?

huangapple go评论86阅读模式
英文:

Is it possible to transport a TCP connection over websockets or another protocol?

问题

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

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

例如,我想在一端创建一个位于NAT后面的socks5服务器(ServerN),在另一端创建一个ServerIE。互联网用户将通过socks5协议向ServerIE发出socks5请求,然后ServerIE将通过Websockets连接将socks5请求发送到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

英文:

I'm wondering, is it possible to transport/tunnel a TCP connection though the websockets protocol?

There is a websockets package and it seems I could io.copy the TCP connection into the websockets connection but I don't know how to reassemble it as TCPConn on the other end.

For example I would like to create a socks5
ServerN(behind NAT) on one end and a ServerIE on the other end. An internet user would make a socks5 request (though the socks5 protocol) to ServerIE and ServerIE would send the socks5 request through the websocket connection to ServerNAT. ServerNAT will then process the socks5 request(i.e. it's a socks5 server).

EDIT:
I've wrote some code but unfortunately it gets stuck on reading from the TCP connection into WS. To run it: go run main.go -logtostderr

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

To trigger the TCP listener you can issue a request on port 8080 (e.g. curl --socks5 localhost:8080 google.com -v or curl localhost:8080 -v)
1: https://godoc.org/golang.org/x/net/websocket

答案1

得分: 6

这应该不会有任何问题。我已经在许多协议上构建了TCP转发器,虽然没有使用过WebSockets,但我不认为它会有什么不同之处。

你有两个问题:

  • 正确的建立连接方式是使用net.Dial,而不仅仅是创建一个net.TCPConn
  • 你只是单向复制数据。你需要在两个方向上进行复制。

这是我今天早上为了不同的目的而构建的一个非常简单的代理(我的完整代码会不时地破坏一个数据包,以便我可以测试错误的连接)。它可以转换为许多其他用途:

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进行转发。

但可能最关键的是最后几行。你必须在两个方向上进行数据复制,人们常常忘记这一点。

英文:

This shouldn't be any problem. I've built TCP forwarders over a number of protocols. Never WebSockets, but I don't see a reason it would be different.

You have two problems:

  • The correct way to make a connection is with net.Dial, not by just creating a net.TCPConn.
  • You're only copying in one direction. You need to copy in both directions.

Here's a super simple proxy that I built this morning for a different purpose (my full code corrupts a packet every so often so I can test bad connectoins). It should be convertible to many other purposes:

func run(inPort int, dest string) {
l, err := net.Listen(&quot;tcp&quot;, fmt.Sprintf(&quot;:%d&quot;, inPort))
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
fmt.Printf(&quot;http://%v -&gt; %s\n&quot;, l.Addr(), dest)
for {
conn, err := l.Accept()
if err != nil {
log.Fatal(err)
}
fmt.Printf(&quot;Connection from %s\n&quot;, conn.RemoteAddr())
go proxy(conn, dest)
}
}
func proxy(in net.Conn, dest string) {
out, err := net.Dial(&quot;tcp&quot;, dest)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
go io.Copy(out, in)
io.Copy(in, out)
fmt.Printf(&quot;Disconnect from %s\n&quot;, in.RemoteAddr())
}

You just need to replace the net.Dial here with a WS call to your server, and on the server side use net.Dial to forward it along.

But probably the most key point is the last few lines. You have to copy in both directions and people forget that.

huangapple
  • 本文由 发表于 2015年8月21日 16:30:52
  • 转载请务必保留本文链接:https://go.coder-hub.com/32135763.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定