add probing resistance support for HTTP proxy

This commit is contained in:
ginuerzh 2018-11-17 15:17:54 +08:00
parent 8a1bfdb1cc
commit 1a0098cf83
5 changed files with 126 additions and 44 deletions

View File

@ -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}

View File

@ -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

View File

@ -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
}

125
http.go
View File

@ -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()

6
sni.go
View File

@ -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,