From 15f9e74e39b3df6a14c22a944b2d123a5366d744 Mon Sep 17 00:00:00 2001 From: nadoo <287492+nadoo@users.noreply.github.com> Date: Fri, 9 Jul 2021 19:17:16 +0800 Subject: [PATCH] wss: added a new scheme `wss` for convenience --- README.md | 36 +++++++++++---------- config.go | 7 +++-- go.mod | 2 +- go.sum | 4 +-- log/log.go | 5 ++- proxy/trojan/client.go | 8 ++--- proxy/trojan/server.go | 10 +++--- proxy/trojan/trojan.go | 6 +--- proxy/ws/client.go | 37 +++++++++++++++++++--- proxy/ws/server.go | 55 +++++++++++++++++++++++++++----- proxy/ws/ws.go | 71 ++++++++++++++++++++++++++++-------------- 11 files changed, 166 insertions(+), 75 deletions(-) diff --git a/README.md b/README.md index 16e804a..4c63ba1 100644 --- a/README.md +++ b/README.md @@ -64,10 +64,11 @@ we can set up local listeners as proxy servers, and forward requests to internet |KCP | |√|√| |transport client & server |Unix |√|√|√|√|transport client & server |Smux |√| |√| |transport client & server -|Websocket |√| |√| |transport client & server +|Websocket(WS)|√| |√| |transport client & server +|WS Secure |√| |√| |transport client & server |Simple-Obfs | | |√| |transport client only -|Redir |√| | | |linux only -|Redir6 |√| | | |linux only(ipv6) +|Redir |√| | | |linux redirect proxy +|Redir6 |√| | | |linux redirect proxy(ipv6) |Reject | | |√|√|reject all requests @@ -174,8 +175,8 @@ glider -verbose -listen :8443 -forward SCHEME://HOST:PORT ```bash Available schemes: - listen: mixed ss socks5 http vless trojan trojanc redir redir6 tcp udp tls ws unix smux kcp - forward: direct reject ss socks4 socks5 http ssr ssh vless vmess trojan trojanc tcp udp tls ws unix smux kcp simple-obfs + listen: mixed ss socks5 http vless trojan trojanc redir redir6 tcp udp tls ws wss unix smux kcp + forward: direct reject ss socks4 socks5 http ssr ssh vless vmess trojan trojanc tcp udp tls ws wss unix smux kcp simple-obfs Socks5 scheme: socks://[user:pass@]host:port @@ -235,15 +236,16 @@ Proxy over tls server: Websocket client scheme: ws://host:port[/path][?host=HOST][&origin=ORIGIN] + wss://host:port[/path][?serverName=SERVERNAME][&skipVerify=true][&host=HOST][&origin=ORIGIN] Websocket server scheme: ws://:port[/path][?host=HOST] + wss://:port[/path]?cert=PATH&key=PATH[?host=HOST] Websocket with a specified proxy protocol: ws://host:port[/path][?host=HOST],scheme:// ws://host:port[/path][?host=HOST],http://[user:pass@] ws://host:port[/path][?host=HOST],socks5://[user:pass@] - ws://host:port[/path][?host=HOST],vmess://[security:]uuid@?alterID=num TLS and Websocket with a specified proxy protocol: tls://host:port[?skipVerify=true][&serverName=SERVERNAME],ws://[@/path[?host=HOST]],scheme:// @@ -315,37 +317,37 @@ Config file format(see `./glider.conf.example` as an example): ```bash Examples: - ./glider -config glider.conf + glider -config glider.conf -run glider with specified config file. - ./glider -listen :8443 -verbose + glider -listen :8443 -verbose -listen on :8443, serve as http/socks5 proxy on the same port, in verbose mode. - ./glider -listen ss://AEAD_AES_128_GCM:pass@:8443 -verbose + glider -listen ss://AEAD_AES_128_GCM:pass@:8443 -verbose -listen on 0.0.0.0:8443 as a ss server. - ./glider -listen tls://:443?cert=crtFilePath&key=keyFilePath,http:// -verbose + glider -listen tls://:443?cert=crtFilePath&key=keyFilePath,http:// -verbose -listen on :443 as a https(http over tls) proxy server. - ./glider -listen http://:8080 -forward socks5://127.0.0.1:1080 + glider -listen http://:8080 -forward socks5://127.0.0.1:1080 -listen on :8080 as a http proxy server, forward all requests via socks5 server. - ./glider -listen socks5://:1080 -forward "tls://abc.com:443,vmess://security:uuid@?alterID=10" + glider -listen socks5://:1080 -forward "tls://abc.com:443,vmess://security:uuid@?alterID=10" -listen on :1080 as a socks5 server, forward all requests via remote tls+vmess server. - ./glider -listen socks5://:1080 -forward ss://method:pass@server1:port1 -forward ss://method:pass@server2:port2 -strategy rr + glider -listen socks5://:1080 -forward ss://method:pass@server1:port1 -forward ss://method:pass@server2:port2 -strategy rr -listen on :1080 as socks5 server, forward requests via server1 and server2 in round robin mode. - ./glider -listen tcp://:80 -forward tcp://2.2.2.2:80 + glider -listen tcp://:80 -forward tcp://2.2.2.2:80 -tcp tunnel: listen on :80 and forward all requests to 2.2.2.2:80. - ./glider -listen udp://:53 -forward ss://method:pass@1.1.1.1:8443,udp://8.8.8.8:53 + glider -listen udp://:53 -forward ss://method:pass@1.1.1.1:8443,udp://8.8.8.8:53 -listen on :53 and forward all udp requests to 8.8.8.8:53 via remote ss server. - ./glider -listen socks5://:1080 -listen http://:8080 -forward ss://method:pass@1.1.1.1:8443 + glider -listen socks5://:1080 -listen http://:8080 -forward ss://method:pass@1.1.1.1:8443 -listen on :1080 as socks5 server, :8080 as http proxy server, forward all requests via remote ss server. - ./glider -verbose -listen -dns=:53 -dnsserver=8.8.8.8:53 -forward ss://method:pass@server:port -dnsrecord=www.example.com/1.2.3.4 + glider -verbose -listen -dns=:53 -dnsserver=8.8.8.8:53 -forward ss://method:pass@server:port -dnsrecord=www.example.com/1.2.3.4 -listen on :53 as dns server, forward to 8.8.8.8:53 via ss server. ``` diff --git a/config.go b/config.go index 535d7cc..aa08fe3 100644 --- a/config.go +++ b/config.go @@ -132,8 +132,8 @@ func usage() { fmt.Fprintf(w, "\n") fmt.Fprintf(w, "Available schemes:\n") - fmt.Fprintf(w, " listen: mixed ss socks5 http vless trojan trojanc redir redir6 tcp udp tls ws unix smux kcp\n") - fmt.Fprintf(w, " forward: direct reject ss socks4 socks5 http ssr ssh vless vmess trojan trojanc tcp udp tls ws unix smux kcp simple-obfs\n") + fmt.Fprintf(w, " listen: mixed ss socks5 http vless trojan trojanc redir redir6 tcp udp tls ws wss unix smux kcp\n") + fmt.Fprintf(w, " forward: direct reject ss socks4 socks5 http ssr ssh vless vmess trojan trojanc tcp udp tls ws wss unix smux kcp simple-obfs\n") fmt.Fprintf(w, "\n") fmt.Fprintf(w, "Socks5 scheme:\n") @@ -208,17 +208,18 @@ func usage() { fmt.Fprintf(w, "Websocket client scheme:\n") fmt.Fprintf(w, " ws://host:port[/path][?host=HOST][&origin=ORIGIN]\n") + fmt.Fprintf(w, " wss://host:port[/path][?serverName=SERVERNAME][&skipVerify=true][&host=HOST][&origin=ORIGIN]\n") fmt.Fprintf(w, "\n") fmt.Fprintf(w, "Websocket server scheme:\n") fmt.Fprintf(w, " ws://:port[/path][?host=HOST]\n") + fmt.Fprintf(w, " wss://:port[/path]?cert=PATH&key=PATH[?host=HOST]\n") fmt.Fprintf(w, "\n") fmt.Fprintf(w, "Websocket with a specified proxy protocol:\n") fmt.Fprintf(w, " ws://host:port[/path][?host=HOST],scheme://\n") fmt.Fprintf(w, " ws://host:port[/path][?host=HOST],http://[user:pass@]\n") fmt.Fprintf(w, " ws://host:port[/path][?host=HOST],socks5://[user:pass@]\n") - fmt.Fprintf(w, " ws://host:port[/path][?host=HOST],vmess://[security:]uuid@?alterID=num\n") fmt.Fprintf(w, "\n") fmt.Fprintf(w, "TLS and Websocket with a specified proxy protocol:\n") diff --git a/go.mod b/go.mod index f86fec0..5818895 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/mdlayher/raw v0.0.0-20210412142147-51b895745faf // indirect github.com/nadoo/conflag v0.2.3 github.com/nadoo/ipset v0.3.0 - github.com/tjfoc/gmsm v1.4.0 // indirect + github.com/tjfoc/gmsm v1.4.1 // indirect github.com/u-root/uio v0.0.0-20210528151154-e40b768296a7 // indirect github.com/xtaci/kcp-go/v5 v5.6.1 golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e diff --git a/go.sum b/go.sum index deb185e..c00f2a2 100644 --- a/go.sum +++ b/go.sum @@ -86,8 +86,8 @@ github.com/templexxx/cpu v0.0.7/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6H github.com/templexxx/xorsimd v0.4.1 h1:iUZcywbOYDRAZUasAs2eSCUW8eobuZDy0I9FJiORkVg= github.com/templexxx/xorsimd v0.4.1/go.mod h1:W+ffZz8jJMH2SXwuKu9WhygqBMbFnp14G2fqEr8qaNo= github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w= -github.com/tjfoc/gmsm v1.4.0 h1:8nbaiZG+iVdh+fXVw0DZoZZa7a4TGm3Qab+xdrdzj8s= -github.com/tjfoc/gmsm v1.4.0/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= +github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= +github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= github.com/u-root/uio v0.0.0-20210528114334-82958018845c/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA= github.com/u-root/uio v0.0.0-20210528151154-e40b768296a7 h1:XMAtQHwKjWHIRwg+8Nj/rzUomQY1q6cM3ncA0wP8GU4= github.com/u-root/uio v0.0.0-20210528151154-e40b768296a7/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA= diff --git a/log/log.go b/log/log.go index 3c28a15..7b45f49 100644 --- a/log/log.go +++ b/log/log.go @@ -8,9 +8,12 @@ import ( // F is the main log function. var F = func(string, ...interface{}) {} +func init() { + stdlog.SetFlags(stdlog.Flags() | stdlog.Lshortfile) +} + // Debugf prints debug log. func Debugf(f string, v ...interface{}) { - stdlog.SetFlags(stdlog.LstdFlags | stdlog.Lshortfile) stdlog.Output(2, fmt.Sprintf(f, v...)) } diff --git a/proxy/trojan/client.go b/proxy/trojan/client.go index ce2495c..62fd597 100644 --- a/proxy/trojan/client.go +++ b/proxy/trojan/client.go @@ -2,6 +2,7 @@ package trojan import ( "crypto/tls" + "fmt" "net" "github.com/nadoo/glider/log" @@ -14,10 +15,8 @@ import ( func NewClearTextDialer(s string, d proxy.Dialer) (proxy.Dialer, error) { t, err := NewTrojan(s, d, nil) if err != nil { - log.F("[trojan] create instance error: %s", err) - return nil, err + return nil, fmt.Errorf("[trojanc] create instance error: %s", err) } - t.withTLS = false return t, err } @@ -26,8 +25,7 @@ func NewClearTextDialer(s string, d proxy.Dialer) (proxy.Dialer, error) { func NewTrojanDialer(s string, d proxy.Dialer) (proxy.Dialer, error) { t, err := NewTrojan(s, d, nil) if err != nil { - log.F("[trojan] create instance error: %s", err) - return nil, err + return nil, fmt.Errorf("[trojan] create instance error: %s", err) } t.tlsConfig = &tls.Config{ diff --git a/proxy/trojan/server.go b/proxy/trojan/server.go index 459ffbb..ac75604 100644 --- a/proxy/trojan/server.go +++ b/proxy/trojan/server.go @@ -19,8 +19,7 @@ import ( func NewClearTextServer(s string, p proxy.Proxy) (proxy.Server, error) { t, err := NewTrojan(s, nil, p) if err != nil { - log.F("[trojan] create instance error: %s", err) - return nil, err + return nil, fmt.Errorf("[trojanc] create instance error: %s", err) } t.withTLS = false @@ -31,8 +30,7 @@ func NewClearTextServer(s string, p proxy.Proxy) (proxy.Server, error) { func NewTrojanServer(s string, p proxy.Proxy) (proxy.Server, error) { t, err := NewTrojan(s, nil, p) if err != nil { - log.F("[trojan] create instance error: %s", err) - return nil, err + return nil, fmt.Errorf("[trojan] create instance error: %s", err) } if t.certFile == "" || t.keyFile == "" { @@ -41,8 +39,8 @@ func NewTrojanServer(s string, p proxy.Proxy) (proxy.Server, error) { cert, err := tls.LoadX509KeyPair(t.certFile, t.keyFile) if err != nil { - log.F("[trojan] unable to load cert: %s, key %s", t.certFile, t.keyFile) - return nil, err + return nil, fmt.Errorf("[trojan] unable to load cert: %s, key %s, error: %s", + t.certFile, t.keyFile, err) } t.tlsConfig = &tls.Config{ diff --git a/proxy/trojan/trojan.go b/proxy/trojan/trojan.go index f95e65a..a3cca8b 100644 --- a/proxy/trojan/trojan.go +++ b/proxy/trojan/trojan.go @@ -55,7 +55,7 @@ func NewTrojan(s string, d proxy.Dialer, p proxy.Proxy) (*Trojan, error) { serverName: query.Get("serverName"), certFile: query.Get("cert"), keyFile: query.Get("key"), - // fallback: "127.0.0.1:80", + fallback: query.Get("fallback"), } if _, port, _ := net.SplitHostPort(t.addr); port == "" { @@ -76,9 +76,5 @@ func NewTrojan(s string, d proxy.Dialer, p proxy.Proxy) (*Trojan, error) { hash.Write([]byte(pass)) hex.Encode(t.pass[:], hash.Sum(nil)) - if fb := u.Query().Get("fallback"); fb != "" { - t.fallback = fb - } - return t, nil } diff --git a/proxy/ws/client.go b/proxy/ws/client.go index ad4d9a3..84dae1d 100644 --- a/proxy/ws/client.go +++ b/proxy/ws/client.go @@ -1,7 +1,9 @@ package ws import ( + "crypto/tls" "errors" + "fmt" "io" "net" "net/textproto" @@ -10,13 +12,30 @@ import ( "github.com/nadoo/glider/proxy" ) -func init() { - proxy.RegisterDialer("ws", NewWSDialer) -} - // NewWSDialer returns a ws proxy dialer. func NewWSDialer(s string, d proxy.Dialer) (proxy.Dialer, error) { - return NewWS(s, d, nil) + w, err := NewWS(s, d, nil, false) + if err != nil { + return nil, fmt.Errorf("[ws] create instance error: %s", err) + } + return w, err +} + +// NewWSSDialer returns a wss proxy dialer. +func NewWSSDialer(s string, d proxy.Dialer) (proxy.Dialer, error) { + w, err := NewWS(s, d, nil, true) + if err != nil { + return nil, fmt.Errorf("[wss] create instance error: %s", err) + } + + w.tlsConfig = &tls.Config{ + ServerName: w.serverName, + InsecureSkipVerify: w.skipVerify, + NextProtos: []string{"http/1.1"}, + MinVersion: tls.VersionTLS12, + } + + return w, err } // Addr returns forwarder's address. @@ -34,6 +53,14 @@ func (s *WS) Dial(network, addr string) (net.Conn, error) { return nil, err } + if s.withTLS { + tlsConn := tls.Client(rc, s.tlsConfig) + if err := tlsConn.Handshake(); err != nil { + return nil, err + } + rc = tlsConn + } + return s.NewClientConn(rc) } diff --git a/proxy/ws/server.go b/proxy/ws/server.go index 0b8737e..924a405 100644 --- a/proxy/ws/server.go +++ b/proxy/ws/server.go @@ -1,7 +1,9 @@ package ws import ( + "crypto/tls" "errors" + "fmt" "io" "net" "net/textproto" @@ -12,16 +14,12 @@ import ( "github.com/nadoo/glider/proxy" ) -func init() { - proxy.RegisterServer("ws", NewWSServer) -} - // NewWSServer returns a ws transport server. func NewWSServer(s string, p proxy.Proxy) (proxy.Server, error) { schemes := strings.SplitN(s, ",", 2) - w, err := NewWS(schemes[0], nil, p) + w, err := NewWS(schemes[0], nil, p, false) if err != nil { - return nil, err + return nil, fmt.Errorf("[ws] create instance error: %s", err) } if len(schemes) > 1 { @@ -34,6 +32,39 @@ func NewWSServer(s string, p proxy.Proxy) (proxy.Server, error) { return w, nil } +// NewWSSServer returns a wss transport server. +func NewWSSServer(s string, p proxy.Proxy) (proxy.Server, error) { + schemes := strings.SplitN(s, ",", 2) + w, err := NewWS(schemes[0], nil, p, true) + if err != nil { + return nil, fmt.Errorf("[wss] create instance error: %s", err) + } + + if len(schemes) > 1 { + w.server, err = proxy.ServerFromURL(schemes[1], p) + if err != nil { + return nil, err + } + } + + if w.certFile == "" || w.keyFile == "" { + return nil, errors.New("[wss] cert and key file path must be spcified") + } + + cert, err := tls.LoadX509KeyPair(w.certFile, w.keyFile) + if err != nil { + return nil, fmt.Errorf("[ws] unable to load cert: %s, key %s, error: %s", + w.certFile, w.keyFile, err) + } + + w.tlsConfig = &tls.Config{ + Certificates: []tls.Certificate{cert}, + MinVersion: tls.VersionTLS12, + } + + return w, nil +} + // ListenAndServe listens on server's addr and serves connections. func (s *WS) ListenAndServe() { l, err := net.Listen("tcp", s.addr) @@ -43,7 +74,7 @@ func (s *WS) ListenAndServe() { } defer l.Close() - log.F("[ws] listening TCP on %s", s.addr) + log.F("[ws] listening TCP on %s, with TLS: %v", s.addr, s.withTLS) for { c, err := l.Accept() @@ -58,6 +89,16 @@ func (s *WS) ListenAndServe() { // Serve serves a connection. func (s *WS) Serve(cc net.Conn) { + if s.withTLS { + tlsConn := tls.Server(cc, s.tlsConfig) + err := tlsConn.Handshake() + if err != nil { + log.F("[ws] error in tls handshake: %s", err) + return + } + cc = tlsConn + } + c, err := s.NewServerConn(cc) if err != nil { log.F("[ws] handshake error: %s", err) diff --git a/proxy/ws/ws.go b/proxy/ws/ws.go index 97573f3..3063c82 100644 --- a/proxy/ws/ws.go +++ b/proxy/ws/ws.go @@ -3,65 +3,90 @@ package ws import ( "crypto/rand" "crypto/sha1" + "crypto/tls" "encoding/base64" + "fmt" "net" "net/url" "strings" - "github.com/nadoo/glider/log" "github.com/nadoo/glider/pool" "github.com/nadoo/glider/proxy" ) var keyGUID = []byte("258EAFA5-E914-47DA-95CA-C5AB0DC85B11") +func init() { + proxy.RegisterDialer("ws", NewWSDialer) + proxy.RegisterServer("ws", NewWSServer) + proxy.RegisterDialer("wss", NewWSSDialer) + proxy.RegisterServer("wss", NewWSSServer) +} + // WS is the base ws proxy struct. type WS struct { - dialer proxy.Dialer - proxy proxy.Proxy - addr string - host string - path string - origin string - - server proxy.Server + dialer proxy.Dialer + proxy proxy.Proxy + addr string + host string + path string + origin string + withTLS bool + tlsConfig *tls.Config + serverName string + skipVerify bool + certFile string + keyFile string + server proxy.Server } // NewWS returns a websocket proxy. -func NewWS(s string, d proxy.Dialer, p proxy.Proxy) (*WS, error) { +func NewWS(s string, d proxy.Dialer, p proxy.Proxy, withTLS bool) (*WS, error) { u, err := url.Parse(s) if err != nil { - log.F("[ws] parse url err: %s", err) - return nil, err + return nil, fmt.Errorf("parse url err: %s", err) } addr := u.Host - if addr != "" { - if _, port, _ := net.SplitHostPort(addr); port == "" { + if addr == "" && d != nil { + addr = d.Addr() + } + + if _, p, _ := net.SplitHostPort(addr); p == "" { + if withTLS { + addr = net.JoinHostPort(addr, "443") + } else { addr = net.JoinHostPort(addr, "80") } - } else if d != nil { - addr = d.Addr() } query := u.Query() w := &WS{ - dialer: d, - proxy: p, - addr: addr, - path: u.Path, - host: query.Get("host"), - origin: query.Get("origin"), + dialer: d, + proxy: p, + addr: addr, + path: u.Path, + host: query.Get("host"), + origin: query.Get("origin"), + withTLS: withTLS, + skipVerify: query.Get("skipVerify") == "true", + serverName: query.Get("serverName"), + certFile: query.Get("cert"), + keyFile: query.Get("key"), } if w.host == "" { - w.host = addr + w.host = w.addr } if w.path == "" { w.path = "/" } + if w.serverName == "" { + w.serverName = w.addr[:strings.LastIndex(w.addr, ":")] + } + return w, nil }