Можно ли передать TCP-соединение через веб-сокеты или другой протокол?

Мне интересно, возможно ли транспортировать/туннелировать TCP-соединение через протокол веб-сокетов?

Существует пакет веб-сокетов, и кажется, что я могу io.copy TCP-соединение в соединение с веб-сокетами, но я не знаю, как собрать его как TCPConn на другом конце.

Например, я хотел бы создать socks5 ServerN (за NAT) на одном конце и ServerIE на другом конце. Интернет-пользователь сделает запрос socks5 (через протокол socks5) к ServerIE, а ServerIE отправит запрос socks5 через соединение через веб-сокет к ServerNAT. Затем ServerNAT обработает запрос socks5 (т. е. это сервер socks5).

EDIT: я написал некоторый код, но, к сожалению, он зависает при чтении из 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 -v или curl localhost:8080 -v).


person Anthony Hunt    schedule 21.08.2015    source источник
comment
Вы имели в виду что-то вроде wstunnel или websockets-proxy ?   -  person Steffen Ullrich    schedule 21.08.2015
comment
да, кажется, я имею в виду wstunnel, хотя javascript не для меня.   -  person Anthony Hunt    schedule 21.08.2015
comment
websockets-proxy, кажется, тоже делает то же самое... есть ли эквивалент Go?   -  person Anthony Hunt    schedule 21.08.2015
comment
Я не знаю эквивалента Go.   -  person Steffen Ullrich    schedule 21.08.2015


Ответы (1)


Это не должно быть проблемой. Я создал серверы пересылки 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 для его пересылки.

Но, пожалуй, самый ключевой момент — это последние несколько строк. Вы должны копировать в обоих направлениях, и люди забывают об этом.

person Rob Napier    schedule 21.08.2015
comment
только сейчас я подумал использовать поддельный прослушиватель и Dial для создания TCP-соединения. Я попробую это и дам вам знать, как это происходит. - person Anthony Hunt; 21.08.2015
comment
Я пробовал это, и это, кажется, тупик. Я обновил свой ответ кодом, который пробовал. Идеал иметь поддельный прослушиватель и номеронабиратель только для создания TCP-соединения кажется неправильным и не работает. - person Anthony Hunt; 22.08.2015
comment
Стоит отметить, что в вашем примере у вас только две стороны, а у меня 3 (NAT, Сервер и IE). - person Anthony Hunt; 22.08.2015
comment
Я обновил свой ответ окончательным решением. Действительно, мне пришлось набрать фальшивого слушателя, который я нахожу очень неудобным (т.е. продолжая прослушивать локальный порт и набирая его с данными, полученными через веб-сокеты). Мне интересно, можно ли создать конструктор для сетевого пакета для чтения из веб-сокетов (io.ReadWriter) и возврата *net.Conn без фальшивого номеронабирателя/слушателя. - person Anthony Hunt; 22.08.2015