From 1a0098cf83b9e0581c5f9668794b67c679730644 Mon Sep 17 00:00:00 2001 From: ginuerzh Date: Sat, 17 Nov 2018 15:17:54 +0800 Subject: [PATCH] add probing resistance support for HTTP proxy --- cmd/gost/main.go | 1 + forward.go | 6 +++ handler.go | 32 +++++++----- http.go | 125 ++++++++++++++++++++++++++++++++++++----------- sni.go | 6 +-- 5 files changed, 126 insertions(+), 44 deletions(-) diff --git a/cmd/gost/main.go b/cmd/gost/main.go index 8a8b12c..529f80c 100644 --- a/cmd/gost/main.go +++ b/cmd/gost/main.go @@ -469,6 +469,7 @@ func (r *route) serve() error { gost.HostsHandlerOption(hosts), gost.RetryHandlerOption(node.GetInt("retry")), gost.TimeoutHandlerOption(time.Duration(node.GetInt("timeout"))*time.Second), + gost.ProbeResistHandlerOption(node.Get("probe_resist")), ) srv := &gost.Server{Listener: ln} diff --git a/forward.go b/forward.go index ab67c2b..c901c60 100644 --- a/forward.go +++ b/forward.go @@ -41,6 +41,9 @@ func TCPDirectForwardHandler(raddr string, opts ...HandlerOption) Handler { group: NewNodeGroup(), } + if raddr == "" { + raddr = ":0" // dummy address + } for i, addr := range strings.Split(raddr, ",") { if addr == "" { continue @@ -136,6 +139,9 @@ func UDPDirectForwardHandler(raddr string, opts ...HandlerOption) Handler { group: NewNodeGroup(), } + if raddr == "" { + raddr = ":0" // dummy address + } for i, addr := range strings.Split(raddr, ",") { if addr == "" { continue diff --git a/handler.go b/handler.go index 705ac62..cc9a6c1 100644 --- a/handler.go +++ b/handler.go @@ -20,18 +20,19 @@ type Handler interface { // HandlerOptions describes the options for Handler. type HandlerOptions struct { - Addr string - Chain *Chain - Users []*url.Userinfo - TLSConfig *tls.Config - Whitelist *Permissions - Blacklist *Permissions - Strategy Strategy - Bypass *Bypass - Retries int - Timeout time.Duration - Resolver Resolver - Hosts *Hosts + Addr string + Chain *Chain + Users []*url.Userinfo + TLSConfig *tls.Config + Whitelist *Permissions + Blacklist *Permissions + Strategy Strategy + Bypass *Bypass + Retries int + Timeout time.Duration + Resolver Resolver + Hosts *Hosts + ProbeResist string } // HandlerOption allows a common way to set handler options. @@ -121,6 +122,13 @@ func HostsHandlerOption(hosts *Hosts) HandlerOption { } } +// ProbeResistHandlerOption adds the probe resistance for HTTP proxy. +func ProbeResistHandlerOption(pr string) HandlerOption { + return func(opts *HandlerOptions) { + opts.ProbeResist = pr + } +} + type autoHandler struct { options *HandlerOptions } diff --git a/http.go b/http.go index 8ea090d..5f349ce 100644 --- a/http.go +++ b/http.go @@ -8,6 +8,8 @@ import ( "net/http" "net/http/httputil" "net/url" + "os" + "strconv" "strings" "time" @@ -111,16 +113,6 @@ func (h *httpHandler) handleRequest(conn net.Conn, req *http.Request) { log.Logf("[http] %s -> %s\n%s", conn.RemoteAddr(), req.Host, string(dump)) } - if req.Method == "PRI" || (req.Method != http.MethodConnect && req.URL.Scheme != "http") { - resp := "HTTP/1.1 400 Bad Request\r\n" + - "Proxy-Agent: gost/" + Version + "\r\n\r\n" - conn.Write([]byte(resp)) - if Debug { - log.Logf("[http] %s <- %s\n%s", conn.RemoteAddr(), req.Host, resp) - } - return - } - // try to get the actual host. if v := req.Header.Get("Gost-Target"); v != "" { if host, err := decodeServerName(v); err == nil { @@ -128,25 +120,37 @@ func (h *httpHandler) handleRequest(conn net.Conn, req *http.Request) { } } + resp := &http.Response{ + ProtoMajor: 1, + ProtoMinor: 1, + Header: http.Header{}, + } + resp.Header.Add("Proxy-Agent", "gost/"+Version) + if !Can("tcp", req.Host, h.options.Whitelist, h.options.Blacklist) { - log.Logf("[http] Unauthorized to tcp connect to %s", req.Host) - b := []byte("HTTP/1.1 403 Forbidden\r\n" + - "Proxy-Agent: gost/" + Version + "\r\n\r\n") - conn.Write(b) + log.Logf("[http] %s - %s : Unauthorized to tcp connect to %s", + conn.RemoteAddr(), req.Host, req.Host) + resp.StatusCode = http.StatusForbidden + if Debug { - log.Logf("[http] %s <- %s\n%s", conn.RemoteAddr(), req.Host, string(b)) + dump, _ := httputil.DumpResponse(resp, false) + log.Logf("[http] %s <- %s\n%s", conn.RemoteAddr(), req.Host, string(dump)) } + + resp.Write(conn) return } if h.options.Bypass.Contains(req.Host) { log.Logf("[http] [bypass] %s", req.Host) - b := []byte("HTTP/1.1 403 Forbidden\r\n" + - "Proxy-Agent: gost/" + Version + "\r\n\r\n") - conn.Write(b) + resp.StatusCode = http.StatusForbidden + if Debug { - log.Logf("[http] %s <- %s\n%s", conn.RemoteAddr(), req.Host, string(b)) + dump, _ := httputil.DumpResponse(resp, false) + log.Logf("[http] %s <- %s\n%s", conn.RemoteAddr(), req.Host, string(dump)) } + + resp.Write(conn) return } @@ -155,16 +159,78 @@ func (h *httpHandler) handleRequest(conn net.Conn, req *http.Request) { log.Logf("[http] %s - %s : Authorization: '%s' '%s'", conn.RemoteAddr(), req.Host, u, p) } if !authenticate(u, p, h.options.Users...) { - log.Logf("[http] %s <- %s : proxy authentication required", conn.RemoteAddr(), req.Host) - resp := "HTTP/1.1 407 Proxy Authentication Required\r\n" + - "Proxy-Authenticate: Basic realm=\"gost\"\r\n" + - "Proxy-Agent: gost/" + Version + "\r\n\r\n" - conn.Write([]byte(resp)) + // probing resistance is enabled + if ss := strings.SplitN(h.options.ProbeResist, ":", 2); len(ss) == 2 { + switch ss[0] { + case "error": + resp.StatusCode, _ = strconv.Atoi(ss[1]) + case "web": + url := ss[1] + if !strings.HasPrefix(url, "http") { + url = "http://" + url + } + if r, err := http.Get(url); err == nil { + resp = r + } + case "host": + cc, err := net.Dial("tcp", ss[1]) + if err == nil { + req.Write(cc) + log.Logf("[http] %s <-> %s", conn.LocalAddr(), ss[1]) + transport(conn, cc) + log.Logf("[http] %s >-< %s", conn.LocalAddr(), ss[1]) + return + } + case "file": + f, _ := os.Open(ss[1]) + if f != nil { + resp.StatusCode = http.StatusOK + if finfo, _ := f.Stat(); finfo != nil { + resp.ContentLength = finfo.Size() + } + resp.Body = f + } + } + } + + if resp.StatusCode == 0 { + log.Logf("[http] %s <- %s : proxy authentication required", conn.RemoteAddr(), req.Host) + resp.StatusCode = http.StatusProxyAuthRequired + resp.Header.Add("Proxy-Authenticate", "Basic realm=\"gost\"") + } else { + resp.Header = http.Header{} + resp.Header.Set("Server", "nginx/1.14.1") + resp.Header.Set("Date", time.Now().Format(http.TimeFormat)) + if resp.ContentLength > 0 { + resp.Header.Set("Content-Type", "text/html") + } + if resp.StatusCode == http.StatusOK { + resp.Header.Set("Connection", "keep-alive") + } + } + + if Debug { + dump, _ := httputil.DumpResponse(resp, false) + log.Logf("[http] %s <- %s\n%s", conn.RemoteAddr(), req.Host, string(dump)) + } + + resp.Write(conn) + return + } + + if req.Method == "PRI" || (req.Method != http.MethodConnect && req.URL.Scheme != "http") { + resp.StatusCode = http.StatusBadRequest + + if Debug { + dump, _ := httputil.DumpResponse(resp, false) + log.Logf("[http] %s <- %s\n%s", conn.RemoteAddr(), req.Host, string(dump)) + } + + resp.Write(conn) return } req.Header.Del("Proxy-Authorization") - // req.Header.Del("Proxy-Connection") host := req.Host if _, port, _ := net.SplitHostPort(host); port == "" { @@ -212,13 +278,14 @@ func (h *httpHandler) handleRequest(conn net.Conn, req *http.Request) { if err != nil { log.Logf("[http] %s -> %s : %s", conn.RemoteAddr(), host, err) + resp.StatusCode = http.StatusServiceUnavailable - b := []byte("HTTP/1.1 503 Service unavailable\r\n" + - "Proxy-Agent: gost/" + Version + "\r\n\r\n") if Debug { - log.Logf("[http] %s <- %s\n%s", conn.RemoteAddr(), host, string(b)) + dump, _ := httputil.DumpResponse(resp, false) + log.Logf("[http] %s <- %s\n%s", conn.RemoteAddr(), host, string(dump)) } - conn.Write(b) + + resp.Write(conn) return } defer cc.Close() diff --git a/sni.go b/sni.go index e9b3615..ac7062a 100644 --- a/sni.go +++ b/sni.go @@ -112,12 +112,12 @@ func (h *sniHandler) Handle(conn net.Conn) { defer cc.Close() if _, err := cc.Write(b); err != nil { - log.Logf("[sni] %s -> %s : %s", conn.RemoteAddr(), host, err) + log.Logf("[sni] %s -> %s : %s", conn.RemoteAddr(), addr, err) } - log.Logf("[sni] %s <-> %s", cc.LocalAddr(), host) + log.Logf("[sni] %s <-> %s", cc.LocalAddr(), addr) transport(conn, cc) - log.Logf("[sni] %s >-< %s", cc.LocalAddr(), host) + log.Logf("[sni] %s >-< %s", cc.LocalAddr(), addr) } // sniSniffConn is a net.Conn that reads from r, fails on Writes,