From 52a8626d4a23823909645ac9e731af249292e984 Mon Sep 17 00:00:00 2001 From: ginuerzh Date: Sat, 8 Dec 2018 18:00:08 +0800 Subject: [PATCH] add tests --- http_test.go | 227 +++++++++++++++++++++--------- server.go | 10 ++ sni.go | 2 +- sni_test.go | 105 ++++++++++++++ socks_test.go | 371 ++++++++++++++++++++++++++++++++++++++++++++++++++ ss_test.go | 314 ++++++++++++++++++++++++++++++++++++++++++ tls_test.go | 371 ++++++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 1333 insertions(+), 67 deletions(-) create mode 100644 sni_test.go create mode 100644 socks_test.go create mode 100644 ss_test.go create mode 100644 tls_test.go diff --git a/http_test.go b/http_test.go index e471178..eb91ad1 100644 --- a/http_test.go +++ b/http_test.go @@ -4,56 +4,56 @@ import ( "bufio" "bytes" "crypto/rand" + "crypto/tls" + "errors" + "fmt" "io" "io/ioutil" "net/http" "net/http/httptest" "net/url" "testing" + "time" ) var httpTestHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { io.Copy(w, r.Body) }) -func httpProxyRoundtrip(urlStr string, cliUser *url.Userinfo, srvUsers []*url.Userinfo, body io.Reader) (statusCode int, recv []byte, err error) { - ln, err := TCPListener("") - if err != nil { - return - } - h := HTTPHandler(UsersHandlerOption(srvUsers...)) - server := &Server{Listener: ln} - go server.Serve(h) - - exitChan := make(chan struct{}) - defer close(exitChan) - go func() { - defer server.Close() - <-exitChan - }() - - client := &Client{ - Connector: HTTPConnector(cliUser), - Transporter: TCPTransporter(), - } - conn, err := client.Dial(ln.Addr().String()) +func proxyRoundtrip(client *Client, server *Server, targetURL string, data []byte) (err error) { + conn, err := client.Dial(server.Addr().String()) if err != nil { return } defer conn.Close() + + conn.SetDeadline(time.Now().Add(1 * time.Second)) + conn, err = client.Handshake(conn) if err != nil { return } - url, err := url.Parse(urlStr) + u, err := url.Parse(targetURL) if err != nil { return } - conn, err = client.Connect(conn, url.Host) + conn, err = client.Connect(conn, u.Host) if err != nil { return } - req, err := http.NewRequest(http.MethodGet, urlStr, body) + + if u.Scheme == "https" { + conn = tls.Client(conn, + &tls.Config{ + InsecureSkipVerify: true, + }) + u.Scheme = "http" + } + req, err := http.NewRequest( + http.MethodGet, + u.String(), + bytes.NewReader(data), + ) if err != nil { return } @@ -65,63 +65,158 @@ func httpProxyRoundtrip(urlStr string, cliUser *url.Userinfo, srvUsers []*url.Us return } defer resp.Body.Close() - statusCode = resp.StatusCode - recv, err = ioutil.ReadAll(resp.Body) + + if resp.StatusCode != http.StatusOK { + return errors.New(resp.Status) + } + + recv, err := ioutil.ReadAll(resp.Body) + if err != nil { + return + } + + if !bytes.Equal(data, recv) { + return fmt.Errorf("data not equal") + } + return } var httpProxyTests = []struct { - url string cliUser *url.Userinfo srvUsers []*url.Userinfo errStr string }{ - {"", nil, nil, ""}, - {"", nil, []*url.Userinfo{url.User("admin")}, "407 Proxy Authentication Required"}, - {"", nil, []*url.Userinfo{url.UserPassword("", "123456")}, "407 Proxy Authentication Required"}, - {"", url.User("admin"), []*url.Userinfo{url.User("test")}, "407 Proxy Authentication Required"}, - {"", url.User("admin"), []*url.Userinfo{url.UserPassword("admin", "123456")}, "407 Proxy Authentication Required"}, - {"", url.User("admin"), []*url.Userinfo{url.User("admin")}, ""}, - {"", url.User("admin"), []*url.Userinfo{url.UserPassword("admin", "")}, ""}, - {"", url.UserPassword("admin", "123456"), nil, ""}, - {"", url.UserPassword("admin", "123456"), []*url.Userinfo{url.User("admin")}, ""}, - {"", url.UserPassword("admin", "123456"), []*url.Userinfo{url.UserPassword("", "123456")}, ""}, - {"", url.UserPassword("", "123456"), []*url.Userinfo{url.UserPassword("", "123456")}, ""}, - {"", url.UserPassword("admin", "123456"), []*url.Userinfo{url.UserPassword("admin", "123456")}, ""}, - {"", url.UserPassword("admin", "123456"), []*url.Userinfo{url.UserPassword("user", "pass"), url.UserPassword("admin", "123456")}, ""}, - {"http://:0", nil, nil, "503 Service Unavailable"}, + {nil, nil, ""}, + {nil, []*url.Userinfo{url.User("admin")}, "407 Proxy Authentication Required"}, + {nil, []*url.Userinfo{url.UserPassword("", "123456")}, "407 Proxy Authentication Required"}, + {url.User("admin"), []*url.Userinfo{url.User("test")}, "407 Proxy Authentication Required"}, + {url.User("admin"), []*url.Userinfo{url.UserPassword("admin", "123456")}, "407 Proxy Authentication Required"}, + {url.User("admin"), []*url.Userinfo{url.User("admin")}, ""}, + {url.User("admin"), []*url.Userinfo{url.UserPassword("admin", "")}, ""}, + {url.UserPassword("admin", "123456"), nil, ""}, + {url.UserPassword("admin", "123456"), []*url.Userinfo{url.User("admin")}, ""}, + {url.UserPassword("admin", "123456"), []*url.Userinfo{url.UserPassword("", "123456")}, ""}, + {url.UserPassword("", "123456"), []*url.Userinfo{url.UserPassword("", "123456")}, ""}, + {url.UserPassword("admin", "123456"), []*url.Userinfo{url.UserPassword("admin", "123456")}, ""}, + {url.UserPassword("admin", "123456"), []*url.Userinfo{url.UserPassword("user", "pass"), url.UserPassword("admin", "123456")}, ""}, +} + +func httpProxyRoundtrip(targetURL string, data []byte, clientInfo *url.Userinfo, serverInfo []*url.Userinfo) error { + ln, err := TCPListener("") + if err != nil { + return err + } + + client := &Client{ + Connector: HTTPConnector(clientInfo), + Transporter: TCPTransporter(), + } + + server := &Server{ + Listener: ln, + Handler: HTTPHandler( + UsersHandlerOption(serverInfo...), + ), + } + + go server.Run() + defer server.Close() + + return proxyRoundtrip(client, server, targetURL, data) } func TestHTTPProxy(t *testing.T) { - Debug = true httpSrv := httptest.NewServer(httpTestHandler) defer httpSrv.Close() - for _, test := range httpProxyTests { - send := make([]byte, 16) - rand.Read(send) - urlStr := test.url - if urlStr == "" { - urlStr = httpSrv.URL - } - _, recv, err := httpProxyRoundtrip(urlStr, test.cliUser, test.srvUsers, bytes.NewReader(send)) - if err == nil { - if test.errStr != "" { - t.Errorf("HTTP proxy response should failed with error %s", test.errStr) - continue + 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 := httpProxyRoundtrip(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) + } } - } else { - if test.errStr == "" { - t.Errorf("HTTP proxy got error %v", err) - } - if err.Error() != test.errStr { - t.Errorf("HTTP proxy got error %v, want %v", err, test.errStr) - } - continue - } - if !bytes.Equal(send, recv) { - t.Errorf("got %v, want %v", recv, send) - continue + }) + } +} + +func BenchmarkHTTPProxy(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: HTTPConnector(url.UserPassword("admin", "123456")), + Transporter: TCPTransporter(), + } + + 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 BenchmarkHTTPProxyParallel(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: HTTPConnector(url.UserPassword("admin", "123456")), + Transporter: TCPTransporter(), + } + + 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) + } + } + }) +} diff --git a/server.go b/server.go index d3804cf..9a07032 100644 --- a/server.go +++ b/server.go @@ -12,6 +12,7 @@ import ( // Server is a proxy server. type Server struct { Listener Listener + Handler Handler options *ServerOptions } @@ -46,6 +47,10 @@ func (s *Server) Serve(h Handler, opts ...ServerOption) error { } s.Listener = ln } + + if h == nil { + h = s.Handler + } if h == nil { h = HTTPHandler() } @@ -84,6 +89,11 @@ func (s *Server) Serve(h Handler, opts ...ServerOption) error { } } +// Run starts to serve. +func (s *Server) Run() error { + return s.Serve(s.Handler) +} + // ServerOptions holds the options for Server. type ServerOptions struct { } diff --git a/sni.go b/sni.go index 5e46abd..342a343 100644 --- a/sni.go +++ b/sni.go @@ -93,7 +93,7 @@ func (h *sniHandler) Handle(conn net.Conn) { host = net.JoinHostPort(host, "443") - log.Logf("[ss] %s -> %s -> %s", + log.Logf("[sni] %s -> %s -> %s", conn.RemoteAddr(), h.options.Node.String(), host) if !Can("tcp", host, h.options.Whitelist, h.options.Blacklist) { diff --git a/sni_test.go b/sni_test.go new file mode 100644 index 0000000..fe75201 --- /dev/null +++ b/sni_test.go @@ -0,0 +1,105 @@ +package gost + +import ( + "bufio" + "bytes" + "crypto/rand" + "crypto/tls" + "errors" + "net/http" + "net/http/httptest" + "net/url" + "testing" + "time" +) + +func sniRoundtrip(client *Client, server *Server, targetURL string, data []byte) (err error) { + conn, err := client.Dial(server.Addr().String()) + if err != nil { + return + } + defer conn.Close() + + conn.SetDeadline(time.Now().Add(3 * time.Second)) + + conn, err = client.Handshake(conn) + if err != nil { + return + } + u, err := url.Parse(targetURL) + if err != nil { + return + } + conn, err = client.Connect(conn, u.Host) + if err != nil { + return + } + + if u.Scheme == "https" { + conn = tls.Client(conn, + &tls.Config{ + InsecureSkipVerify: false, + ServerName: u.Hostname(), + }) + u.Scheme = "http" + } + req, err := http.NewRequest( + http.MethodGet, + u.String(), + bytes.NewReader(data), + ) + if err != nil { + return + } + if err = req.Write(conn); err != nil { + return + } + resp, err := http.ReadResponse(bufio.NewReader(conn), req) + if err != nil { + return + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return errors.New(resp.Status) + } + + return +} + +func sniProxyRoundtrip(targetURL string, data []byte, host string) error { + ln, err := TCPListener("") + if err != nil { + return err + } + + client := &Client{ + Connector: SNIConnector(host), + Transporter: TCPTransporter(), + } + + server := &Server{ + Listener: ln, + Handler: SNIHandler(), + } + + go server.Run() + defer server.Close() + + return sniRoundtrip(client, server, targetURL, data) +} + +func TestSNIProxy(t *testing.T) { + // SetLogger(&LogLogger{}) + // Debug = true + httpSrv := httptest.NewTLSServer(httpTestHandler) + defer httpSrv.Close() + + sendData := make([]byte, 128) + rand.Read(sendData) + + err := sniProxyRoundtrip("https://github.com", sendData, "google.com") + if err != nil { + t.Errorf("got error: %v", err) + } +} diff --git a/socks_test.go b/socks_test.go new file mode 100644 index 0000000..5ca20a4 --- /dev/null +++ b/socks_test.go @@ -0,0 +1,371 @@ +package gost + +import ( + "crypto/rand" + "crypto/tls" + "fmt" + "net/http/httptest" + "net/url" + "testing" +) + +var socks5ProxyTests = []struct { + cliUser *url.Userinfo + srvUsers []*url.Userinfo + pass bool +}{ + {nil, nil, true}, + {nil, []*url.Userinfo{url.User("admin")}, false}, + {nil, []*url.Userinfo{url.UserPassword("", "123456")}, false}, + {url.User("admin"), []*url.Userinfo{url.User("test")}, false}, + {url.User("admin"), []*url.Userinfo{url.UserPassword("admin", "123456")}, false}, + {url.User("admin"), []*url.Userinfo{url.User("admin")}, true}, + {url.User("admin"), []*url.Userinfo{url.UserPassword("admin", "")}, true}, + {url.UserPassword("admin", "123456"), nil, true}, + {url.UserPassword("admin", "123456"), []*url.Userinfo{url.User("admin")}, true}, + {url.UserPassword("admin", "123456"), []*url.Userinfo{url.UserPassword("", "123456")}, true}, + {url.UserPassword("", "123456"), []*url.Userinfo{url.UserPassword("", "123456")}, true}, + {url.UserPassword("admin", "123456"), []*url.Userinfo{url.UserPassword("admin", "123456")}, true}, + {url.UserPassword("admin", "123456"), []*url.Userinfo{url.UserPassword("user", "pass"), url.UserPassword("admin", "123456")}, true}, +} + +func socks5ProxyRoundtrip(targetURL string, data []byte, clientInfo *url.Userinfo, serverInfo []*url.Userinfo) error { + ln, err := TCPListener("") + if err != nil { + return err + } + + client := &Client{ + Connector: SOCKS5Connector(clientInfo), + Transporter: TCPTransporter(), + } + + server := &Server{ + Handler: SOCKS5Handler(UsersHandlerOption(serverInfo...)), + Listener: ln, + } + + go server.Run() + defer server.Close() + + return proxyRoundtrip(client, server, targetURL, data) +} + +func TestSOCKS5Proxy(t *testing.T) { + cert, err := GenCertificate() + if err != nil { + panic(err) + } + DefaultTLSConfig = &tls.Config{ + Certificates: []tls.Certificate{cert}, + } + + httpSrv := httptest.NewServer(httpTestHandler) + defer httpSrv.Close() + + sendData := make([]byte, 128) + rand.Read(sendData) + + for i, tc := range socks5ProxyTests { + tc := tc + t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) { + err := socks5ProxyRoundtrip(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 BenchmarkSOCKS5Proxy(b *testing.B) { + cert, err := GenCertificate() + if err != nil { + panic(err) + } + DefaultTLSConfig = &tls.Config{ + Certificates: []tls.Certificate{cert}, + } + + 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: SOCKS5Connector(url.UserPassword("admin", "123456")), + Transporter: TCPTransporter(), + } + + server := &Server{ + Handler: SOCKS5Handler(UsersHandlerOption(url.UserPassword("admin", "123456"))), + Listener: ln, + } + + 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 BenchmarkSOCKS5ProxyParallel(b *testing.B) { + cert, err := GenCertificate() + if err != nil { + panic(err) + } + DefaultTLSConfig = &tls.Config{ + Certificates: []tls.Certificate{cert}, + } + + 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: SOCKS5Connector(url.UserPassword("admin", "123456")), + Transporter: TCPTransporter(), + } + + server := &Server{ + Handler: SOCKS5Handler(UsersHandlerOption(url.UserPassword("admin", "123456"))), + Listener: ln, + } + + 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 socks4ProxyRoundtrip(targetURL string, data []byte) error { + ln, err := TCPListener("") + if err != nil { + return err + } + + client := &Client{ + Connector: SOCKS4Connector(), + Transporter: TCPTransporter(), + } + + server := &Server{ + Listener: ln, + Handler: SOCKS4Handler(), + } + go server.Run() + defer server.Close() + + return proxyRoundtrip(client, server, targetURL, data) +} + +func TestSOCKS4Proxy(t *testing.T) { + httpSrv := httptest.NewServer(httpTestHandler) + defer httpSrv.Close() + + sendData := make([]byte, 128) + rand.Read(sendData) + + err := socks4ProxyRoundtrip(httpSrv.URL, sendData) + // t.Logf("#%d %v", i, err) + if err != nil { + t.Errorf("got error: %v", err) + } +} + +func BenchmarkSOCKS4Proxy(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: SOCKS4Connector(), + Transporter: TCPTransporter(), + } + + server := &Server{ + Listener: ln, + Handler: SOCKS4Handler(), + } + 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 BenchmarkSOCKS4ProxyParallel(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: SOCKS4Connector(), + Transporter: TCPTransporter(), + } + + server := &Server{ + Listener: ln, + Handler: SOCKS4Handler(), + } + 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 socks4aProxyRoundtrip(targetURL string, data []byte) error { + ln, err := TCPListener("") + if err != nil { + return err + } + + client := &Client{ + Connector: SOCKS4AConnector(), + Transporter: TCPTransporter(), + } + + server := &Server{ + Listener: ln, + Handler: SOCKS4Handler(), + } + + go server.Run() + defer server.Close() + + return proxyRoundtrip(client, server, targetURL, data) +} + +func TestSOCKS4AProxy(t *testing.T) { + httpSrv := httptest.NewServer(httpTestHandler) + defer httpSrv.Close() + + sendData := make([]byte, 128) + rand.Read(sendData) + + err := socks4aProxyRoundtrip(httpSrv.URL, sendData) + // t.Logf("#%d %v", i, err) + if err != nil { + t.Errorf("got error: %v", err) + } +} + +func BenchmarkSOCKS4AProxy(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: SOCKS4AConnector(), + Transporter: TCPTransporter(), + } + + server := &Server{ + Listener: ln, + Handler: SOCKS4Handler(), + } + + 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 BenchmarkSOCKS4AProxyParallel(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: SOCKS4AConnector(), + Transporter: TCPTransporter(), + } + + server := &Server{ + Listener: ln, + Handler: SOCKS4Handler(), + } + + 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) + } + } + }) +} diff --git a/ss_test.go b/ss_test.go new file mode 100644 index 0000000..bb7a4dd --- /dev/null +++ b/ss_test.go @@ -0,0 +1,314 @@ +package gost + +import ( + "crypto/rand" + "fmt" + "net/http/httptest" + "net/url" + "testing" +) + +var ssProxyTests = []struct { + clientCipher *url.Userinfo + serverCipher *url.Userinfo + pass bool +}{ + {nil, nil, false}, + {&url.Userinfo{}, &url.Userinfo{}, false}, + {url.User("abc"), url.User("abc"), false}, + {url.UserPassword("abc", "def"), url.UserPassword("abc", "def"), false}, + + {url.User("aes-128-cfb"), url.User("aes-128-cfb"), false}, + {url.User("aes-128-cfb"), url.UserPassword("aes-128-cfb", "123456"), false}, + {url.UserPassword("aes-128-cfb", "123456"), url.User("aes-128-cfb"), false}, + {url.UserPassword("aes-128-cfb", "123456"), url.UserPassword("aes-128-cfb", "abc"), false}, + {url.UserPassword("aes-128-cfb", "123456"), url.UserPassword("aes-128-cfb", "123456"), true}, + {url.UserPassword("aes-128-cfb", "123456"), url.UserPassword("aes-192-cfb", "123456"), false}, + {url.UserPassword("aes-128-cfb", "123456"), url.UserPassword("aes-256-cfb", "123456"), false}, + {url.UserPassword("aes-128-cfb", "123456"), url.UserPassword("aes-128-ctr", "123456"), false}, + {url.UserPassword("aes-128-cfb", "123456"), url.UserPassword("aes-192-ctr", "123456"), false}, + {url.UserPassword("aes-128-cfb", "123456"), url.UserPassword("aes-256-ctr", "123456"), false}, + {url.UserPassword("aes-128-cfb", "123456"), url.UserPassword("des-cfb", "123456"), false}, + {url.UserPassword("aes-128-cfb", "123456"), url.UserPassword("bf-cfb", "123456"), false}, + {url.UserPassword("aes-128-cfb", "123456"), url.UserPassword("cast5-cfb", "123456"), false}, + {url.UserPassword("aes-128-cfb", "123456"), url.UserPassword("rc4-md5", "123456"), false}, + {url.UserPassword("aes-128-cfb", "123456"), url.UserPassword("chacha20", "123456"), false}, + {url.UserPassword("aes-128-cfb", "123456"), url.UserPassword("chacha20-ietf", "123456"), false}, + {url.UserPassword("aes-128-cfb", "123456"), url.UserPassword("salsa20", "123456"), false}, + + {url.User("aes-192-cfb"), url.User("aes-192-cfb"), false}, + {url.User("aes-192-cfb"), url.UserPassword("aes-192-cfb", "123456"), false}, + {url.UserPassword("aes-192-cfb", "123456"), url.User("aes-192-cfb"), false}, + {url.UserPassword("aes-192-cfb", "123456"), url.UserPassword("aes-192-cfb", "abc"), false}, + {url.UserPassword("aes-192-cfb", "123456"), url.UserPassword("aes-192-cfb", "123456"), true}, + {url.UserPassword("aes-192-cfb", "123456"), url.UserPassword("aes-128-cfb", "123456"), false}, + {url.UserPassword("aes-192-cfb", "123456"), url.UserPassword("aes-256-cfb", "123456"), false}, + {url.UserPassword("aes-192-cfb", "123456"), url.UserPassword("aes-128-ctr", "123456"), false}, + {url.UserPassword("aes-192-cfb", "123456"), url.UserPassword("aes-192-ctr", "123456"), false}, + {url.UserPassword("aes-192-cfb", "123456"), url.UserPassword("aes-256-ctr", "123456"), false}, + {url.UserPassword("aes-192-cfb", "123456"), url.UserPassword("des-cfb", "123456"), false}, + {url.UserPassword("aes-192-cfb", "123456"), url.UserPassword("bf-cfb", "123456"), false}, + {url.UserPassword("aes-192-cfb", "123456"), url.UserPassword("cast5-cfb", "123456"), false}, + {url.UserPassword("aes-192-cfb", "123456"), url.UserPassword("rc4-md5", "123456"), false}, + {url.UserPassword("aes-192-cfb", "123456"), url.UserPassword("chacha20", "123456"), false}, + {url.UserPassword("aes-192-cfb", "123456"), url.UserPassword("chacha20-ietf", "123456"), false}, + {url.UserPassword("aes-192-cfb", "123456"), url.UserPassword("salsa20", "123456"), false}, + + {url.User("aes-256-cfb"), url.User("aes-256-cfb"), false}, + {url.User("aes-256-cfb"), url.UserPassword("aes-256-cfb", "123456"), false}, + {url.UserPassword("aes-256-cfb", "123456"), url.User("aes-256-cfb"), false}, + {url.UserPassword("aes-256-cfb", "123456"), url.UserPassword("aes-256-cfb", "abc"), false}, + {url.UserPassword("aes-256-cfb", "123456"), url.UserPassword("aes-256-cfb", "123456"), true}, + {url.UserPassword("aes-256-cfb", "123456"), url.UserPassword("aes-128-cfb", "123456"), false}, + {url.UserPassword("aes-256-cfb", "123456"), url.UserPassword("aes-192-cfb", "123456"), false}, + {url.UserPassword("aes-256-cfb", "123456"), url.UserPassword("aes-128-ctr", "123456"), false}, + {url.UserPassword("aes-256-cfb", "123456"), url.UserPassword("aes-192-ctr", "123456"), false}, + {url.UserPassword("aes-256-cfb", "123456"), url.UserPassword("aes-256-ctr", "123456"), false}, + {url.UserPassword("aes-256-cfb", "123456"), url.UserPassword("des-cfb", "123456"), false}, + {url.UserPassword("aes-256-cfb", "123456"), url.UserPassword("bf-cfb", "123456"), false}, + {url.UserPassword("aes-256-cfb", "123456"), url.UserPassword("cast5-cfb", "123456"), false}, + {url.UserPassword("aes-256-cfb", "123456"), url.UserPassword("rc4-md5", "123456"), false}, + {url.UserPassword("aes-256-cfb", "123456"), url.UserPassword("chacha20", "123456"), false}, + {url.UserPassword("aes-256-cfb", "123456"), url.UserPassword("chacha20-ietf", "123456"), false}, + {url.UserPassword("aes-256-cfb", "123456"), url.UserPassword("salsa20", "123456"), false}, + + {url.User("aes-128-ctr"), url.User("aes-128-ctr"), false}, + {url.User("aes-128-ctr"), url.UserPassword("aes-128-ctr", "123456"), false}, + {url.UserPassword("aes-128-ctr", "123456"), url.User("aes-128-ctr"), false}, + {url.UserPassword("aes-128-ctr", "123456"), url.UserPassword("aes-128-ctr", "abc"), false}, + {url.UserPassword("aes-128-ctr", "123456"), url.UserPassword("aes-128-ctr", "123456"), true}, + + {url.User("aes-192-ctr"), url.User("aes-192-ctr"), false}, + {url.User("aes-192-ctr"), url.UserPassword("aes-192-ctr", "123456"), false}, + {url.UserPassword("aes-192-ctr", "123456"), url.User("aes-192-ctr"), false}, + {url.UserPassword("aes-192-ctr", "123456"), url.UserPassword("aes-192-ctr", "abc"), false}, + {url.UserPassword("aes-192-ctr", "123456"), url.UserPassword("aes-192-ctr", "123456"), true}, + + {url.User("aes-256-ctr"), url.User("aes-256-ctr"), false}, + {url.User("aes-256-ctr"), url.UserPassword("aes-256-ctr", "123456"), false}, + {url.UserPassword("aes-256-ctr", "123456"), url.User("aes-256-ctr"), false}, + {url.UserPassword("aes-256-ctr", "123456"), url.UserPassword("aes-256-ctr", "abc"), false}, + {url.UserPassword("aes-256-ctr", "123456"), url.UserPassword("aes-256-ctr", "123456"), true}, + + {url.User("des-cfb"), url.User("des-cfb"), false}, + {url.User("des-cfb"), url.UserPassword("des-cfb", "123456"), false}, + {url.UserPassword("des-cfb", "123456"), url.User("des-cfb"), false}, + {url.UserPassword("des-cfb", "123456"), url.UserPassword("des-cfb", "abc"), false}, + {url.UserPassword("des-cfb", "123456"), url.UserPassword("des-cfb", "123456"), true}, + + {url.User("bf-cfb"), url.User("bf-cfb"), false}, + {url.User("bf-cfb"), url.UserPassword("bf-cfb", "123456"), false}, + {url.UserPassword("bf-cfb", "123456"), url.User("bf-cfb"), false}, + {url.UserPassword("bf-cfb", "123456"), url.UserPassword("bf-cfb", "abc"), false}, + {url.UserPassword("bf-cfb", "123456"), url.UserPassword("bf-cfb", "123456"), true}, + + {url.User("cast5-cfb"), url.User("cast5-cfb"), false}, + {url.User("cast5-cfb"), url.UserPassword("cast5-cfb", "123456"), false}, + {url.UserPassword("cast5-cfb", "123456"), url.User("cast5-cfb"), false}, + {url.UserPassword("cast5-cfb", "123456"), url.UserPassword("cast5-cfb", "abc"), false}, + {url.UserPassword("cast5-cfb", "123456"), url.UserPassword("cast5-cfb", "123456"), true}, + + {url.User("rc4-md5"), url.User("rc4-md5"), false}, + {url.User("rc4-md5"), url.UserPassword("rc4-md5", "123456"), false}, + {url.UserPassword("rc4-md5", "123456"), url.User("rc4-md5"), false}, + {url.UserPassword("rc4-md5", "123456"), url.UserPassword("rc4-md5", "abc"), false}, + {url.UserPassword("rc4-md5", "123456"), url.UserPassword("rc4-md5", "123456"), true}, + + {url.User("chacha20"), url.User("chacha20"), false}, + {url.User("chacha20"), url.UserPassword("chacha20", "123456"), false}, + {url.UserPassword("chacha20", "123456"), url.User("chacha20"), false}, + {url.UserPassword("chacha20", "123456"), url.UserPassword("chacha20", "abc"), false}, + {url.UserPassword("chacha20", "123456"), url.UserPassword("chacha20", "123456"), true}, + + {url.User("chacha20-ietf"), url.User("chacha20-ietf"), false}, + {url.User("chacha20-ietf"), url.UserPassword("chacha20-ietf", "123456"), false}, + {url.UserPassword("chacha20-ietf", "123456"), url.User("chacha20-ietf"), false}, + {url.UserPassword("chacha20-ietf", "123456"), url.UserPassword("chacha20-ietf", "abc"), false}, + {url.UserPassword("chacha20-ietf", "123456"), url.UserPassword("chacha20-ietf", "123456"), true}, + + {url.User("salsa20"), url.User("salsa20"), false}, + {url.User("salsa20"), url.UserPassword("salsa20", "123456"), false}, + {url.UserPassword("salsa20", "123456"), url.User("salsa20"), false}, + {url.UserPassword("salsa20", "123456"), url.UserPassword("salsa20", "abc"), false}, + {url.UserPassword("salsa20", "123456"), url.UserPassword("salsa20", "123456"), true}, +} + +func ssProxyRoundtrip(targetURL string, data []byte, clientInfo *url.Userinfo, serverInfo *url.Userinfo) error { + ln, err := TCPListener("") + if err != nil { + return err + } + + client := &Client{ + Connector: ShadowConnector(clientInfo), + Transporter: TCPTransporter(), + } + + server := &Server{ + Handler: ShadowHandler(UsersHandlerOption(serverInfo)), + Listener: ln, + } + + go server.Run() + defer server.Close() + + return proxyRoundtrip(client, server, targetURL, data) +} + +func TestSSProxy(t *testing.T) { + httpSrv := httptest.NewServer(httpTestHandler) + defer httpSrv.Close() + + sendData := make([]byte, 128) + rand.Read(sendData) + + for i, tc := range ssProxyTests { + tc := tc + t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) { + err := ssProxyRoundtrip(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 BenchmarkSSProxy_AES256(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: ShadowConnector(url.UserPassword("aes-256-cfb", "123456")), + Transporter: TCPTransporter(), + } + + server := &Server{ + Handler: ShadowHandler(UsersHandlerOption(url.UserPassword("aes-256-cfb", "123456"))), + Listener: ln, + } + + 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 BenchmarkSSProxy_Chacha20(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: ShadowConnector(url.UserPassword("chacha20", "123456")), + Transporter: TCPTransporter(), + } + + server := &Server{ + Handler: ShadowHandler(UsersHandlerOption(url.UserPassword("chacha20", "123456"))), + Listener: ln, + } + + 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 BenchmarkSSProxy_Chacha20_ietf(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: ShadowConnector(url.UserPassword("chacha20-ietf", "123456")), + Transporter: TCPTransporter(), + } + + server := &Server{ + Handler: ShadowHandler(UsersHandlerOption(url.UserPassword("chacha20-ietf", "123456"))), + Listener: ln, + } + + 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 BenchmarkSSProxyParallel(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: ShadowConnector(url.UserPassword("chacha20-ietf", "123456")), + Transporter: TCPTransporter(), + } + + server := &Server{ + Handler: ShadowHandler(UsersHandlerOption(url.UserPassword("chacha20-ietf", "123456"))), + Listener: ln, + } + + 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) + } + } + }) +} diff --git a/tls_test.go b/tls_test.go new file mode 100644 index 0000000..bfa8ca7 --- /dev/null +++ b/tls_test.go @@ -0,0 +1,371 @@ +package gost + +import ( + "crypto/rand" + "crypto/tls" + "fmt" + "net/http/httptest" + "net/url" + "testing" +) + +func httpOverTLSRoundtrip(targetURL string, data []byte, tlsConfig *tls.Config, + clientInfo *url.Userinfo, serverInfo []*url.Userinfo) error { + + ln, err := TLSListener("", tlsConfig) + if err != nil { + return err + } + + client := &Client{ + Connector: HTTPConnector(clientInfo), + Transporter: TLSTransporter(), + } + + server := &Server{ + Listener: ln, + Handler: HTTPHandler( + UsersHandlerOption(serverInfo...), + ), + } + + go server.Run() + defer server.Close() + + return proxyRoundtrip(client, server, targetURL, data) +} + +func TestHTTPOverTLS(t *testing.T) { + cert, err := GenCertificate() + if err != nil { + t.Error(err) + } + tlsConfig := &tls.Config{ + Certificates: []tls.Certificate{cert}, + } + + 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 := httpOverTLSRoundtrip(httpSrv.URL, sendData, tlsConfig, 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 BenchmarkHTTPOverTLS(b *testing.B) { + httpSrv := httptest.NewServer(httpTestHandler) + defer httpSrv.Close() + + sendData := make([]byte, 128) + rand.Read(sendData) + + cert, err := GenCertificate() + if err != nil { + b.Error(err) + } + tlsConfig := &tls.Config{ + Certificates: []tls.Certificate{cert}, + } + + ln, err := TLSListener("", tlsConfig) + if err != nil { + b.Error(err) + } + + client := &Client{ + Connector: HTTPConnector(url.UserPassword("admin", "123456")), + Transporter: TLSTransporter(), + } + + 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 BenchmarkHTTPOverTLSParallel(b *testing.B) { + httpSrv := httptest.NewServer(httpTestHandler) + defer httpSrv.Close() + + sendData := make([]byte, 128) + rand.Read(sendData) + + cert, err := GenCertificate() + if err != nil { + b.Error(err) + } + tlsConfig := &tls.Config{ + Certificates: []tls.Certificate{cert}, + } + + ln, err := TLSListener("", tlsConfig) + if err != nil { + b.Error(err) + } + + client := &Client{ + Connector: HTTPConnector(url.UserPassword("admin", "123456")), + Transporter: TLSTransporter(), + } + + 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 socks5OverTLSRoundtrip(targetURL string, data []byte, tlsConfig *tls.Config, + clientInfo *url.Userinfo, serverInfo []*url.Userinfo) error { + + ln, err := TLSListener("", tlsConfig) + if err != nil { + return err + } + + client := &Client{ + Connector: SOCKS5Connector(clientInfo), + Transporter: TLSTransporter(), + } + + server := &Server{ + Listener: ln, + Handler: SOCKS5Handler( + UsersHandlerOption(serverInfo...), + ), + } + + go server.Run() + defer server.Close() + + return proxyRoundtrip(client, server, targetURL, data) +} + +func TestSOCKS5OverTLS(t *testing.T) { + cert, err := GenCertificate() + if err != nil { + t.Error(err) + } + tlsConfig := &tls.Config{ + Certificates: []tls.Certificate{cert}, + } + DefaultTLSConfig = tlsConfig + + httpSrv := httptest.NewServer(httpTestHandler) + defer httpSrv.Close() + + sendData := make([]byte, 128) + rand.Read(sendData) + + for i, tc := range socks5ProxyTests { + tc := tc + t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) { + err := socks5OverTLSRoundtrip(httpSrv.URL, sendData, + tlsConfig, + 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 socks4OverTLSRoundtrip(targetURL string, data []byte, tlsConfig *tls.Config) error { + ln, err := TLSListener("", tlsConfig) + if err != nil { + return err + } + + client := &Client{ + Connector: SOCKS4Connector(), + Transporter: TLSTransporter(), + } + + server := &Server{ + Listener: ln, + Handler: SOCKS4Handler(), + } + + go server.Run() + defer server.Close() + + return proxyRoundtrip(client, server, targetURL, data) +} + +func TestSOCKS4OverTLS(t *testing.T) { + cert, err := GenCertificate() + if err != nil { + t.Error(err) + } + tlsConfig := &tls.Config{ + Certificates: []tls.Certificate{cert}, + } + + httpSrv := httptest.NewServer(httpTestHandler) + defer httpSrv.Close() + + sendData := make([]byte, 128) + rand.Read(sendData) + + err = socks4OverTLSRoundtrip(httpSrv.URL, sendData, tlsConfig) + // t.Logf("#%d %v", i, err) + if err != nil { + t.Errorf("got error: %v", err) + } +} + +func socks4aOverTLSRoundtrip(targetURL string, data []byte, tlsConfig *tls.Config) error { + ln, err := TLSListener("", tlsConfig) + if err != nil { + return err + } + + client := &Client{ + Connector: SOCKS4AConnector(), + Transporter: TLSTransporter(), + } + + server := &Server{ + Listener: ln, + Handler: SOCKS4Handler(), + } + + go server.Run() + defer server.Close() + + return proxyRoundtrip(client, server, targetURL, data) +} + +func TestSOCKS4AOverTLS(t *testing.T) { + cert, err := GenCertificate() + if err != nil { + t.Error(err) + } + tlsConfig := &tls.Config{ + Certificates: []tls.Certificate{cert}, + } + + httpSrv := httptest.NewServer(httpTestHandler) + defer httpSrv.Close() + + sendData := make([]byte, 128) + rand.Read(sendData) + + err = socks4aOverTLSRoundtrip(httpSrv.URL, sendData, tlsConfig) + // t.Logf("#%d %v", i, err) + if err != nil { + t.Errorf("got error: %v", err) + } +} + +func ssOverTLSRoundtrip(targetURL string, data []byte, tlsConfig *tls.Config, + clientInfo, serverInfo *url.Userinfo) error { + + ln, err := TLSListener("", tlsConfig) + if err != nil { + return err + } + + client := &Client{ + Connector: ShadowConnector(clientInfo), + Transporter: TLSTransporter(), + } + + server := &Server{ + Listener: ln, + Handler: ShadowHandler( + UsersHandlerOption(serverInfo), + ), + } + + go server.Run() + defer server.Close() + + return proxyRoundtrip(client, server, targetURL, data) +} + +func TestSSOverTLS(t *testing.T) { + cert, err := GenCertificate() + if err != nil { + t.Error(err) + } + tlsConfig := &tls.Config{ + Certificates: []tls.Certificate{cert}, + } + DefaultTLSConfig = tlsConfig + + httpSrv := httptest.NewServer(httpTestHandler) + defer httpSrv.Close() + + sendData := make([]byte, 128) + rand.Read(sendData) + + for i, tc := range ssProxyTests { + tc := tc + t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) { + err := ssOverTLSRoundtrip(httpSrv.URL, sendData, + tlsConfig, + 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) + } + } + }) + } +}