From bc1ea195d8fdd77215d9b7d49588a2fc9bcec4a3 Mon Sep 17 00:00:00 2001 From: edolphin-ydf Date: Sun, 26 Sep 2021 22:59:28 +0800 Subject: [PATCH 1/2] socks5 support --- go.mod | 1 + go.sum | 8 ++++++++ pac/otto.go | 13 ++++++++++++- pac/pac.go | 9 ++++++++- pac/parse.go | 15 ++++++++++++--- proxyhttphandler.go | 41 +++++++++++++++++++++++++++++++++++------ 6 files changed, 76 insertions(+), 11 deletions(-) diff --git a/go.mod b/go.mod index 66a827e..f029e0d 100644 --- a/go.mod +++ b/go.mod @@ -4,5 +4,6 @@ go 1.13 require ( github.com/robertkrimen/otto v0.0.0-20180617131154-15f95af6e78d + github.com/txthinking/socks5 v0.0.0-20210716140126-fa1f52a8f2da gopkg.in/sourcemap.v1 v1.0.5 // indirect ) diff --git a/go.sum b/go.sum index 019fff0..ce4d2fe 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,12 @@ +github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/robertkrimen/otto v0.0.0-20180617131154-15f95af6e78d h1:1VUlQbCfkoSGv7qP7Y+ro3ap1P1pPZxgdGVqiTVy5C4= github.com/robertkrimen/otto v0.0.0-20180617131154-15f95af6e78d/go.mod h1:xvqspoSXJTIpemEonrMDFq6XzwHYYgToXWj5eRX1OtY= +github.com/txthinking/runnergroup v0.0.0-20210608031112-152c7c4432bf h1:7PflaKRtU4np/epFxRXlFhlzLXZzKFrH5/I4so5Ove0= +github.com/txthinking/runnergroup v0.0.0-20210608031112-152c7c4432bf/go.mod h1:CLUSJbazqETbaR+i0YAhXBICV9TrKH93pziccMhmhpM= +github.com/txthinking/socks5 v0.0.0-20210716140126-fa1f52a8f2da h1:7x8pJcBTdKTBpQbRjZZc9o6CDquXBbvm9UIrR6ZSRJ4= +github.com/txthinking/socks5 v0.0.0-20210716140126-fa1f52a8f2da/go.mod h1:7NloQcrxaZYKURWph5HLxVDlIwMHJXCPkeWPtpftsIg= +github.com/txthinking/x v0.0.0-20210326105829-476fab902fbe h1:gMWxZxBFRAXqoGkwkYlPX2zvyyKNWJpxOxCrjqJkm5A= +github.com/txthinking/x v0.0.0-20210326105829-476fab902fbe/go.mod h1:WgqbSEmUYSjEV3B1qmee/PpP2NYEz4bL9/+mF1ma+s4= gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI= gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78= diff --git a/pac/otto.go b/pac/otto.go index 6d49562..10587e0 100644 --- a/pac/otto.go +++ b/pac/otto.go @@ -34,6 +34,7 @@ func NewOttoEngine(opts ...OttoEngineOpt) *OttoEngine { loader: func() (string, error) { return "", errors.New("pac loader has not been configured") }, + cache: make(map[string]Proxies), } for _, opt := range opts { opt(otto) @@ -47,6 +48,7 @@ type OttoEngine struct { loader Loader isStarted bool vm *otto.Otto + cache map[string]Proxies } func (o *OttoEngine) Start() error { @@ -358,6 +360,10 @@ func (o *OttoEngine) FindProxyForURL(in *url.URL) (Proxies, error) { o.mutex.Lock() defer o.mutex.Unlock() + if cached, ok := o.cache[in.Hostname()]; ok { + return cached, nil + } + value, err := o.vm.Call("FindProxyForURL", nil, in.String(), in.Hostname()) if err != nil { return Proxies{}, err @@ -368,5 +374,10 @@ func (o *OttoEngine) FindProxyForURL(in *url.URL) (Proxies, error) { return Proxies{}, err } - return ParseFindProxyString(findProxyString) + r, err := ParseFindProxyString(findProxyString) + if err != nil { + return Proxies{}, nil + } + o.cache[in.Hostname()] = r + return r, nil } diff --git a/pac/pac.go b/pac/pac.go index 21c9cfd..8ba0a8b 100644 --- a/pac/pac.go +++ b/pac/pac.go @@ -54,8 +54,15 @@ type ProxyChecker interface { } */ +const ( + ProxySchemeHttp = "http" + ProxySchemeHttps = "https" + ProxySchemeSocks5 = "socks5" +) + // Proxy information struct type Proxy struct { + Scheme string Hostname string Port int } @@ -64,7 +71,7 @@ func (p Proxy) String() string { if p == DirectProxy { return "DIRECT" } - return fmt.Sprintf("PROXY %s:%d", p.Hostname, p.Port) + return fmt.Sprintf("%s %s:%d", p.Scheme, p.Hostname, p.Port) } // DirectProxy is used to represent a "DIRECT" value diff --git a/pac/parse.go b/pac/parse.go index 76398f4..9d7f8a9 100644 --- a/pac/parse.go +++ b/pac/parse.go @@ -29,8 +29,10 @@ func ParseFindProxyString(s string) (Proxies, error) { portStr string portInt int portErr error + scheme string ) for _, statement := range pacStatementSplit.Split(s, 50) { + scheme = "" if statement == "" { continue } @@ -39,10 +41,18 @@ func ParseFindProxyString(s string) (Proxies, error) { case "DIRECT": proxies = append(proxies, DirectProxy) case "PROXY": + scheme = ProxySchemeHttp + case "SOCKS5": + scheme = ProxySchemeSocks5 + default: + return Proxies{}, fmt.Errorf("unsupported PAC command %q", part[0]) + } + + if scheme != "" { if len(part) != 2 { return Proxies{}, fmt.Errorf("unable to parse proxy details from %q", statement) } - url, urlErr = url.Parse("http://" + part[1]) + url, urlErr = url.Parse(scheme + "://" + part[1]) if urlErr != nil { return Proxies{}, urlErr } @@ -56,11 +66,10 @@ func ParseFindProxyString(s string) (Proxies, error) { return Proxies{}, portErr } proxies = append(proxies, Proxy{ + Scheme: scheme, Hostname: url.Hostname(), Port: portInt, }) - default: - return Proxies{}, fmt.Errorf("unsupported PAC command %q", part[0]) } } return proxies, nil diff --git a/proxyhttphandler.go b/proxyhttphandler.go index fb9cfec..c61f6b7 100644 --- a/proxyhttphandler.go +++ b/proxyhttphandler.go @@ -13,6 +13,7 @@ import ( "sync" "time" + "github.com/txthinking/socks5" "github.com/williambailey/pacproxy/pac" ) @@ -86,7 +87,8 @@ func (h *proxyHTTPHandler) lookupProxy(r *http.Request) (*url.URL, error) { return nil, nil } proxyURL := &url.URL{ - Host: fmt.Sprintf("%s:%d", proxy.Hostname, proxy.Port), + Scheme: proxy.Scheme, + Host: fmt.Sprintf("%s:%d", proxy.Hostname, proxy.Port), } if proxyAuth := r.Header.Get("Proxy-Authorization"); proxyAuth != "" { if u, p, ok := parseBasicAuth(proxyAuth); ok { @@ -118,7 +120,7 @@ func (h *proxyHTTPHandler) doConnectProxy(w http.ResponseWriter, r *http.Request return } defer serverConn.Close() - } else { + } else if proxyURL.Scheme == pac.ProxySchemeHttp { serverConn, err = h.dialer.Dial("tcp", proxyURL.Hostname()+":"+proxyURL.Port()) if err != nil { log.Printf("HTTP Connect Proxy %q: %d %s", r.URL, http.StatusBadGateway, err) @@ -129,6 +131,14 @@ func (h *proxyHTTPHandler) doConnectProxy(w http.ResponseWriter, r *http.Request removeProxyHeaders(r) //r.WriteProxy(serverConn) r.Write(serverConn) // instead of WriteProxy as this will *hopefully* deal with CONNECT correctly. + } else if proxyURL.Scheme == pac.ProxySchemeSocks5 { + c, _ := socks5.NewClient(proxyURL.Hostname()+":"+proxyURL.Port(), "", "", 0, 0) + serverConn, err = c.Dial("tcp", r.URL.Host) + if err != nil { + log.Printf("socks5 dial fail:%s", err.Error()) + http.Error(w, err.Error(), http.StatusBadGateway) + return + } } hj, ok := w.(http.Hijacker) if !ok { @@ -144,20 +154,39 @@ func (h *proxyHTTPHandler) doConnectProxy(w http.ResponseWriter, r *http.Request return } defer clientConn.Close() - if proxyURL == nil { + if proxyURL == nil || proxyURL.Scheme != pac.ProxySchemeHttp { clientConn.Write([]byte("HTTP/1.0 200 OK\r\n\r\n")) } var wg sync.WaitGroup + var closeOnce sync.Once wg.Add(1) go func() { defer wg.Done() - io.Copy(clientConn, serverConn) - clientConn.SetDeadline(time.Now().Add(10 * time.Millisecond)) + n, err := io.Copy(clientConn, serverConn) + if err != nil { + log.Printf("%s iocopy from server to client fail:%s", r.URL.Hostname(), err.Error()) + } else { + log.Printf("%s iocopy from server to client done: %d", r.URL.Hostname(), n) + } + closeOnce.Do(func() { + clientConn.Close() + serverConn.Close() + }) + clientConn.SetDeadline(time.Now().Add(100 * time.Microsecond)) }() wg.Add(1) go func() { defer wg.Done() - io.Copy(serverConn, clientConn) + n, err := io.Copy(serverConn, clientConn) + if err != nil { + log.Printf("%s iocopy from client to server fail: %d %s", r.URL.Hostname(), n, err.Error()) + } else { + log.Printf("%s iocopy from client to server done: %d", r.URL.Hostname(), n) + } + closeOnce.Do(func() { + clientConn.Close() + serverConn.Close() + }) }() wg.Wait() } From 15e3be33e16185e97f3afedf5afff79fa2d4b136 Mon Sep 17 00:00:00 2001 From: edolphin-ydf Date: Mon, 27 Sep 2021 11:05:05 +0800 Subject: [PATCH 2/2] make cache reloadable --- pac/otto.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pac/otto.go b/pac/otto.go index 10587e0..59e772e 100644 --- a/pac/otto.go +++ b/pac/otto.go @@ -34,7 +34,6 @@ func NewOttoEngine(opts ...OttoEngineOpt) *OttoEngine { loader: func() (string, error) { return "", errors.New("pac loader has not been configured") }, - cache: make(map[string]Proxies), } for _, opt := range opts { opt(otto) @@ -72,6 +71,8 @@ func (o *OttoEngine) Start() error { if o.isStarted { return nil } + // Init the cache when start, so the cache will be invalided when reload. + o.cache = make(map[string]Proxies) log.Print("initialising OttoEngine") vm := otto.New() @@ -376,7 +377,7 @@ func (o *OttoEngine) FindProxyForURL(in *url.URL) (Proxies, error) { r, err := ParseFindProxyString(findProxyString) if err != nil { - return Proxies{}, nil + return Proxies{}, err } o.cache[in.Hostname()] = r return r, nil