diff --git a/Dockerfile.mod b/Dockerfile.mod new file mode 100644 index 0000000..ec749fa --- /dev/null +++ b/Dockerfile.mod @@ -0,0 +1,21 @@ +FROM golang:1-alpine as builder + +RUN apk add --no-cache musl-dev git gcc + +ADD . /data + +WORKDIR /data + +ENV GO111MODULE=on + +RUN cd cmd/gost && go build + +FROM alpine:latest + +WORKDIR /bin/ + +COPY --from=builder /data/cmd/gost/gost . + +RUN /bin/gost -V + +ENTRYPOINT ["/bin/gost"] \ No newline at end of file diff --git a/cmd/gost/route.go b/cmd/gost/route.go index 4858729..c9cdb6b 100644 --- a/cmd/gost/route.go +++ b/cmd/gost/route.go @@ -110,6 +110,8 @@ func parseChainNode(ns string) (nodes []gost.Node, err error) { wsOpts.WriteBufferSize = node.GetInt("wbuf") wsOpts.UserAgent = node.Get("agent") + var host string + var tr gost.Transporter switch node.Transport { case "tls": @@ -160,6 +162,7 @@ func parseChainNode(ns string) (nodes []gost.Node, err error) { case "obfs4": tr = gost.Obfs4Transporter() case "ohttp": + host = node.Get("host") tr = gost.ObfsHTTPTransporter() default: tr = gost.TCPTransporter() @@ -197,9 +200,12 @@ func parseChainNode(ns string) (nodes []gost.Node, err error) { gost.TimeoutDialOption(time.Duration(timeout)*time.Second), ) + if host == "" { + host = node.Host + } handshakeOptions := []gost.HandshakeOption{ gost.AddrHandshakeOption(node.Addr), - gost.HostHandshakeOption(node.Host), + gost.HostHandshakeOption(host), gost.UserHandshakeOption(node.User), gost.TLSConfigHandshakeOption(tlsCfg), gost.IntervalHandshakeOption(time.Duration(node.GetInt("ping")) * time.Second), diff --git a/http2.go b/http2.go index 337afeb..2ab87c7 100644 --- a/http2.go +++ b/http2.go @@ -34,6 +34,11 @@ func HTTP2Connector(user *url.Userinfo) Connector { } func (c *http2Connector) Connect(conn net.Conn, addr string, options ...ConnectOption) (net.Conn, error) { + opts := &ConnectOptions{} + for _, option := range options { + option(opts) + } + cc, ok := conn.(*http2ClientConn) if !ok { return nil, errors.New("wrong connection type") @@ -142,7 +147,7 @@ func (tr *http2Transporter) Dial(addr string, options ...DialOption) (net.Conn, } client = &http.Client{ Transport: &transport, - Timeout: timeout, + // Timeout: timeout, } tr.clients[addr] = client } @@ -214,7 +219,7 @@ func (tr *h2Transporter) Dial(addr string, options ...DialOption) (net.Conn, err } client = &http.Client{ Transport: &transport, - Timeout: timeout, + // Timeout: timeout, } tr.clients[addr] = client } diff --git a/obfs.go b/obfs.go index b0cea1a..e403927 100644 --- a/obfs.go +++ b/obfs.go @@ -67,10 +67,7 @@ func (l *obfsHTTPListener) Accept() (net.Conn, error) { type obfsHTTPConn struct { net.Conn host string - request *http.Request - response *http.Response - rbuf []byte - wbuf []byte + buf []byte isServer bool handshaked bool handshakeMutex sync.Mutex @@ -99,19 +96,19 @@ func (c *obfsHTTPConn) Handshake() (err error) { func (c *obfsHTTPConn) serverHandshake() (err error) { br := bufio.NewReader(c.Conn) - c.request, err = http.ReadRequest(br) + r, err := http.ReadRequest(br) if err != nil { return } if Debug { - dump, _ := httputil.DumpRequest(c.request, false) + dump, _ := httputil.DumpRequest(r, false) log.Logf("[ohttp] %s -> %s\n%s", c.Conn.RemoteAddr(), c.Conn.LocalAddr(), string(dump)) } - if br.Buffered() > 0 { - c.rbuf, err = br.Peek(br.Buffered()) + if r.ContentLength > 0 { + c.buf, err = ioutil.ReadAll(r.Body) } else { - c.rbuf, err = ioutil.ReadAll(c.request.Body) + c.buf, err = br.Peek(br.Buffered()) } if err != nil { @@ -120,13 +117,13 @@ func (c *obfsHTTPConn) serverHandshake() (err error) { } b := bytes.Buffer{} - if c.request.Header.Get("Upgrade") == "websocket" { + if r.Header.Get("Upgrade") == "websocket" { b.WriteString("HTTP/1.1 101 Switching Protocols\r\n") b.WriteString("Server: nginx/1.10.0\r\n") b.WriteString("Date: " + time.Now().Format(time.RFC1123) + "\r\n") b.WriteString("Connection: Upgrade\r\n") b.WriteString("Upgrade: websocket\r\n") - b.WriteString(fmt.Sprintf("Sec-WebSocket-Accept: %s\r\n", computeAcceptKey(c.request.Header.Get("Sec-WebSocket-Key")))) + b.WriteString(fmt.Sprintf("Sec-WebSocket-Accept: %s\r\n", computeAcceptKey(r.Header.Get("Sec-WebSocket-Key")))) b.WriteString("\r\n") } else { b.WriteString("HTTP/1.1 200 OK\r\n") @@ -146,24 +143,19 @@ func (c *obfsHTTPConn) serverHandshake() (err error) { } func (c *obfsHTTPConn) clientHandshake() (err error) { - r := c.request - if r == nil { - r = &http.Request{ - Method: http.MethodGet, - ProtoMajor: 1, - ProtoMinor: 1, - URL: &url.URL{Scheme: "http", Host: c.host}, - Header: make(http.Header), - } - r.Header.Set("Connection", "keep-alive") - r.Header.Set("Upgrade", "websocket") - r.Header.Set("User-Agent", DefaultUserAgent) - if len(c.wbuf) > 0 { - log.Log("write buf", len(c.wbuf)) - r.Body = ioutil.NopCloser(bytes.NewReader(c.wbuf)) - r.ContentLength = int64(len(c.wbuf)) - } + r := &http.Request{ + Method: http.MethodGet, + ProtoMajor: 1, + ProtoMinor: 1, + URL: &url.URL{Scheme: "http", Host: c.host}, + Header: make(http.Header), } + r.Header.Set("User-Agent", "curl/7.49.1") + r.Header.Set("Connection", "Upgrade") + r.Header.Set("Upgrade", "websocket") + key, _ := generateChallengeKey() + r.Header.Set("Sec-WebSocket-Key", key) + if err = r.Write(c.Conn); err != nil { return } @@ -182,6 +174,7 @@ func (c *obfsHTTPConn) clientHandshake() (err error) { dump, _ := httputil.DumpResponse(resp, false) log.Logf("[ohttp] %s <- %s\n%s", c.Conn.LocalAddr(), c.Conn.RemoteAddr(), string(dump)) } + return nil } @@ -189,24 +182,18 @@ func (c *obfsHTTPConn) Read(b []byte) (n int, err error) { if err = c.Handshake(); err != nil { return } - if len(c.rbuf) > 0 { - n = copy(b, c.rbuf) - c.rbuf = c.rbuf[n:] + if len(c.buf) > 0 { + n = copy(b, c.buf) + c.buf = c.buf[n:] return } return c.Conn.Read(b) } func (c *obfsHTTPConn) Write(b []byte) (n int, err error) { - handshaked := c.handshaked - c.wbuf = b if err = c.Handshake(); err != nil { return } - if !handshaked { - n = len(c.wbuf) - return - } return c.Conn.Write(b) } diff --git a/obfs_test.go b/obfs_test.go new file mode 100644 index 0000000..8999cda --- /dev/null +++ b/obfs_test.go @@ -0,0 +1,371 @@ +package gost + +import ( + "crypto/rand" + "fmt" + "net/http/httptest" + "net/url" + "testing" +) + +func httpOverObfsHTTPRoundtrip(targetURL string, data []byte, + clientInfo *url.Userinfo, serverInfo []*url.Userinfo) error { + + ln, err := ObfsHTTPListener("") + if err != nil { + return err + } + + client := &Client{ + Connector: HTTPConnector(clientInfo), + Transporter: ObfsHTTPTransporter(), + } + + server := &Server{ + Listener: ln, + Handler: HTTPHandler( + UsersHandlerOption(serverInfo...), + ), + } + + go server.Run() + defer server.Close() + + return proxyRoundtrip(client, server, targetURL, data) +} + +func TestHTTPOverObfsHTTP(t *testing.T) { + httpSrv := httptest.NewServer(httpTestHandler) + defer httpSrv.Close() + + sendData := make([]byte, 128) + rand.Read(sendData) + + for i, tc := range httpProxyTests { + tc := tc + t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) { + err := httpOverObfsHTTPRoundtrip(httpSrv.URL, sendData, tc.cliUser, tc.srvUsers) + if err == nil { + if tc.errStr != "" { + t.Errorf("#%d should failed with error %s", i, tc.errStr) + } + } else { + if tc.errStr == "" { + t.Errorf("#%d got error %v", i, err) + } + if err.Error() != tc.errStr { + t.Errorf("#%d got error %v, want %v", i, err, tc.errStr) + } + } + }) + } +} + +func BenchmarkHTTPOverObfsHTTP(b *testing.B) { + httpSrv := httptest.NewServer(httpTestHandler) + defer httpSrv.Close() + + sendData := make([]byte, 128) + rand.Read(sendData) + + ln, err := ObfsHTTPListener("") + if err != nil { + b.Error(err) + } + // b.Log(ln.Addr()) + + client := &Client{ + Connector: HTTPConnector(url.UserPassword("admin", "123456")), + Transporter: ObfsHTTPTransporter(), + } + + server := &Server{ + Listener: ln, + Handler: HTTPHandler( + UsersHandlerOption(url.UserPassword("admin", "123456")), + ), + } + go server.Run() + defer server.Close() + + for i := 0; i < b.N; i++ { + if err := proxyRoundtrip(client, server, httpSrv.URL, sendData); err != nil { + b.Error(err) + } + } +} + +func BenchmarkHTTPOverObfsHTTPParallel(b *testing.B) { + httpSrv := httptest.NewServer(httpTestHandler) + defer httpSrv.Close() + + sendData := make([]byte, 128) + rand.Read(sendData) + + ln, err := ObfsHTTPListener("") + if err != nil { + b.Error(err) + } + + client := &Client{ + Connector: HTTPConnector(url.UserPassword("admin", "123456")), + Transporter: ObfsHTTPTransporter(), + } + + server := &Server{ + Listener: ln, + Handler: HTTPHandler( + UsersHandlerOption(url.UserPassword("admin", "123456")), + ), + } + go server.Run() + defer server.Close() + + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + if err := proxyRoundtrip(client, server, httpSrv.URL, sendData); err != nil { + b.Error(err) + } + } + }) +} + +func socks5OverObfsHTTPRoundtrip(targetURL string, data []byte, + clientInfo *url.Userinfo, serverInfo []*url.Userinfo) error { + + ln, err := ObfsHTTPListener("") + if err != nil { + return err + } + + client := &Client{ + Connector: SOCKS5Connector(clientInfo), + Transporter: ObfsHTTPTransporter(), + } + + server := &Server{ + Listener: ln, + Handler: SOCKS5Handler( + UsersHandlerOption(serverInfo...), + ), + } + + go server.Run() + defer server.Close() + + return proxyRoundtrip(client, server, targetURL, data) +} + +func TestSOCKS5OverObfsHTTP(t *testing.T) { + httpSrv := httptest.NewServer(httpTestHandler) + defer httpSrv.Close() + + sendData := make([]byte, 128) + rand.Read(sendData) + + for i, tc := range socks5ProxyTests { + err := socks5OverObfsHTTPRoundtrip(httpSrv.URL, sendData, + tc.cliUser, + tc.srvUsers, + ) + if err == nil { + if !tc.pass { + t.Errorf("#%d should failed", i) + } + } else { + // t.Logf("#%d %v", i, err) + if tc.pass { + t.Errorf("#%d got error: %v", i, err) + } + } + } +} + +func socks4OverObfsHTTPRoundtrip(targetURL string, data []byte) error { + ln, err := ObfsHTTPListener("") + if err != nil { + return err + } + + client := &Client{ + Connector: SOCKS4Connector(), + Transporter: ObfsHTTPTransporter(), + } + + server := &Server{ + Listener: ln, + Handler: SOCKS4Handler(), + } + + go server.Run() + defer server.Close() + + return proxyRoundtrip(client, server, targetURL, data) +} + +func TestSOCKS4OverObfsHTTP(t *testing.T) { + httpSrv := httptest.NewServer(httpTestHandler) + defer httpSrv.Close() + + sendData := make([]byte, 128) + rand.Read(sendData) + + err := socks4OverObfsHTTPRoundtrip(httpSrv.URL, sendData) + // t.Logf("#%d %v", i, err) + if err != nil { + t.Errorf("got error: %v", err) + } +} + +func socks4aOverObfsHTTPRoundtrip(targetURL string, data []byte) error { + ln, err := ObfsHTTPListener("") + if err != nil { + return err + } + + client := &Client{ + Connector: SOCKS4AConnector(), + Transporter: ObfsHTTPTransporter(), + } + + server := &Server{ + Listener: ln, + Handler: SOCKS4Handler(), + } + + go server.Run() + defer server.Close() + + return proxyRoundtrip(client, server, targetURL, data) +} + +func TestSOCKS4AOverObfsHTTP(t *testing.T) { + + httpSrv := httptest.NewServer(httpTestHandler) + defer httpSrv.Close() + + sendData := make([]byte, 128) + rand.Read(sendData) + + err := socks4aOverObfsHTTPRoundtrip(httpSrv.URL, sendData) + // t.Logf("#%d %v", i, err) + if err != nil { + t.Errorf("got error: %v", err) + } +} + +func ssOverObfsHTTPRoundtrip(targetURL string, data []byte, + clientInfo, serverInfo *url.Userinfo) error { + + ln, err := ObfsHTTPListener("") + if err != nil { + return err + } + + client := &Client{ + Connector: ShadowConnector(clientInfo), + Transporter: ObfsHTTPTransporter(), + } + + server := &Server{ + Listener: ln, + Handler: ShadowHandler( + UsersHandlerOption(serverInfo), + ), + } + + go server.Run() + defer server.Close() + + return proxyRoundtrip(client, server, targetURL, data) +} + +func TestSSOverObfsHTTP(t *testing.T) { + httpSrv := httptest.NewServer(httpTestHandler) + defer httpSrv.Close() + + sendData := make([]byte, 128) + rand.Read(sendData) + + for i, tc := range ssProxyTests { + err := ssOverObfsHTTPRoundtrip(httpSrv.URL, sendData, + tc.clientCipher, + tc.serverCipher, + ) + if err == nil { + if !tc.pass { + t.Errorf("#%d should failed", i) + } + } else { + // t.Logf("#%d %v", i, err) + if tc.pass { + t.Errorf("#%d got error: %v", i, err) + } + } + } +} + +func sniOverObfsHTTPRoundtrip(targetURL string, data []byte, host string) error { + ln, err := ObfsHTTPListener("") + if err != nil { + return err + } + + u, err := url.Parse(targetURL) + if err != nil { + return err + } + + client := &Client{ + Connector: SNIConnector(host), + Transporter: ObfsHTTPTransporter(), + } + + server := &Server{ + Listener: ln, + Handler: SNIHandler(HostHandlerOption(u.Host)), + } + + go server.Run() + defer server.Close() + + return sniRoundtrip(client, server, targetURL, data) +} + +func TestSNIOverObfsHTTP(t *testing.T) { + httpSrv := httptest.NewServer(httpTestHandler) + defer httpSrv.Close() + httpsSrv := httptest.NewTLSServer(httpTestHandler) + defer httpsSrv.Close() + + sendData := make([]byte, 128) + rand.Read(sendData) + + var sniProxyTests = []struct { + targetURL string + host string + pass bool + }{ + {httpSrv.URL, "", true}, + {httpSrv.URL, "example.com", true}, + {httpsSrv.URL, "", true}, + {httpsSrv.URL, "example.com", true}, + } + + for i, tc := range sniProxyTests { + tc := tc + t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) { + err := sniOverObfsHTTPRoundtrip(tc.targetURL, sendData, tc.host) + if err == nil { + if !tc.pass { + t.Errorf("#%d should failed", i) + } + } else { + // t.Logf("#%d %v", i, err) + if tc.pass { + t.Errorf("#%d got error: %v", i, err) + } + } + }) + } +} diff --git a/ssh.go b/ssh.go index 6361804..9195394 100644 --- a/ssh.go +++ b/ssh.go @@ -95,13 +95,15 @@ func (c *sshRemoteForwardConnector) Connect(conn net.Conn, addr string, options if err != nil { return } + log.Log("[ssh-rtcp] listening on", ln.Addr()) + for { rc, err := ln.Accept() if err != nil { log.Logf("[ssh-rtcp] %s <-> %s accpet : %s", ln.Addr(), addr, err) return } - + // log.Log("[ssh-rtcp] accept", rc.LocalAddr(), rc.RemoteAddr()) select { case cc.session.connChan <- rc: default: @@ -593,7 +595,6 @@ func (h *sshForwardHandler) tcpipForwardRequest(sshConn ssh.Conn, req *ssh.Reque return } - log.Log("[ssh-rtcp] listening on tcp", addr) ln, err := net.Listen("tcp", addr) //tie to the client connection if err != nil { log.Log("[ssh-rtcp]", err) @@ -602,6 +603,8 @@ func (h *sshForwardHandler) tcpipForwardRequest(sshConn ssh.Conn, req *ssh.Reque } defer ln.Close() + log.Log("[ssh-rtcp] listening on tcp", ln.Addr()) + replyFunc := func() error { if t.Port == 0 && req.WantReply { // Client sent port 0. let them know which port is actually being used _, port, err := getHostPortFromAddr(ln.Addr()) @@ -850,15 +853,15 @@ func (c *sshNopConn) RemoteAddr() net.Addr { } func (c *sshNopConn) SetDeadline(t time.Time) error { - return &net.OpError{Op: "set", Net: "http2", Source: nil, Addr: nil, Err: errors.New("deadline not supported")} + return &net.OpError{Op: "set", Net: "ssh", Source: nil, Addr: nil, Err: errors.New("deadline not supported")} } func (c *sshNopConn) SetReadDeadline(t time.Time) error { - return &net.OpError{Op: "set", Net: "http2", Source: nil, Addr: nil, Err: errors.New("deadline not supported")} + return &net.OpError{Op: "set", Net: "ssh", Source: nil, Addr: nil, Err: errors.New("deadline not supported")} } func (c *sshNopConn) SetWriteDeadline(t time.Time) error { - return &net.OpError{Op: "set", Net: "http2", Source: nil, Addr: nil, Err: errors.New("deadline not supported")} + return &net.OpError{Op: "set", Net: "ssh", Source: nil, Addr: nil, Err: errors.New("deadline not supported")} } type sshConn struct { diff --git a/ssh_test.go b/ssh_test.go index 0395260..6c11f2b 100644 --- a/ssh_test.go +++ b/ssh_test.go @@ -4,11 +4,181 @@ import ( "crypto/rand" "crypto/tls" "fmt" + "net" "net/http/httptest" "net/url" "testing" ) +func sshDirectForwardRoundtrip(targetURL string, data []byte) error { + ln, err := TCPListener("") + if err != nil { + return err + } + + client := &Client{ + Connector: SSHDirectForwardConnector(), + Transporter: SSHForwardTransporter(), + } + + server := &Server{ + Listener: ln, + Handler: SSHForwardHandler(), + } + + go server.Run() + defer server.Close() + + return proxyRoundtrip(client, server, targetURL, data) +} + +func TestSSHDirectForward(t *testing.T) { + httpSrv := httptest.NewServer(httpTestHandler) + defer httpSrv.Close() + + sendData := make([]byte, 128) + rand.Read(sendData) + + err := sshDirectForwardRoundtrip(httpSrv.URL, sendData) + if err != nil { + t.Error(err) + } +} + +func BenchmarkSSHDirectForward(b *testing.B) { + httpSrv := httptest.NewServer(httpTestHandler) + defer httpSrv.Close() + + sendData := make([]byte, 128) + rand.Read(sendData) + + ln, err := TCPListener("") + if err != nil { + b.Error(err) + } + + client := &Client{ + Connector: SSHDirectForwardConnector(), + Transporter: SSHForwardTransporter(), + } + + server := &Server{ + Listener: ln, + Handler: SSHForwardHandler(), + } + + go server.Run() + defer server.Close() + + for i := 0; i < b.N; i++ { + if err := proxyRoundtrip(client, server, httpSrv.URL, sendData); err != nil { + b.Error(err) + } + } +} + +func BenchmarkSSHDirectForwardParallel(b *testing.B) { + httpSrv := httptest.NewServer(httpTestHandler) + defer httpSrv.Close() + + sendData := make([]byte, 128) + rand.Read(sendData) + + ln, err := TCPListener("") + if err != nil { + b.Error(err) + } + + client := &Client{ + Connector: SSHDirectForwardConnector(), + Transporter: SSHForwardTransporter(), + } + + server := &Server{ + Listener: ln, + Handler: SSHForwardHandler(), + } + + go server.Run() + defer server.Close() + + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + if err := proxyRoundtrip(client, server, httpSrv.URL, sendData); err != nil { + b.Error(err) + } + } + }) +} + +func sshRemoteForwardRoundtrip(t *testing.T, targetURL string, data []byte) (err error) { + ln, err := TCPListener("") + if err != nil { + return + } + + client := &Client{ + Connector: SSHRemoteForwardConnector(), + Transporter: SSHForwardTransporter(), + } + + server := &Server{ + Listener: ln, + Handler: SSHForwardHandler(), + } + + go server.Run() + defer server.Close() + + conn, err := proxyConn(client, server) + if err != nil { + return + } + defer conn.Close() + + go func() { + conn, err = client.Connect(conn, ":0") + if err != nil { + return + } + }() + + c, err := net.Dial("tcp", conn.LocalAddr().String()) + if err != nil { + return + } + defer c.Close() + + u, err := url.Parse(targetURL) + if err != nil { + return + } + + cc, err := net.Dial("tcp", u.Host) + if err != nil { + return + } + defer cc.Close() + + go transport(conn, cc) + + t.Log("httpRoundtrip") + return httpRoundtrip(c, targetURL, data) +} + +func _TestSSHRemoteForward(t *testing.T) { + httpSrv := httptest.NewServer(httpTestHandler) + defer httpSrv.Close() + + sendData := make([]byte, 128) + rand.Read(sendData) + + err := sshRemoteForwardRoundtrip(t, httpSrv.URL, sendData) + if err != nil { + t.Error(err) + } +} + func httpOverSSHTunnelRoundtrip(targetURL string, data []byte, tlsConfig *tls.Config, clientInfo *url.Userinfo, serverInfo []*url.Userinfo) error {