From 9a1de2a8159a6e1ec0d02613360fd69943bb653c Mon Sep 17 00:00:00 2001 From: gVisor bot Date: Sun, 12 Aug 2018 12:14:59 +0800 Subject: [PATCH] Fix: firefox one socket to process multiple domains --- adapters/local/http.go | 99 +++++++++++++++++++++++++++++++++++++++++- proxy/http/server.go | 4 +- proxy/http/util.go | 77 -------------------------------- 3 files changed, 100 insertions(+), 80 deletions(-) delete mode 100644 proxy/http/util.go diff --git a/adapters/local/http.go b/adapters/local/http.go index cd335f92..574f02bc 100644 --- a/adapters/local/http.go +++ b/adapters/local/http.go @@ -1,7 +1,12 @@ package adapters import ( + "bufio" + "bytes" + "io" "net" + "net/http" + "strings" C "github.com/Dreamacro/clash/constant" ) @@ -9,6 +14,8 @@ import ( type PeekedConn struct { net.Conn Peeked []byte + host string + isHTTP bool } func (c *PeekedConn) Read(p []byte) (n int, err error) { @@ -20,6 +27,23 @@ func (c *PeekedConn) Read(p []byte) (n int, err error) { } return n, nil } + + // Sometimes firefox just open a socket to process multiple domains in HTTP + // The temporary solution is to return io.EOF when encountering different HOST + if c.isHTTP { + br := bufio.NewReader(bytes.NewReader(p)) + _, hostName := ParserHTTPHostHeader(br) + if hostName != "" { + if !strings.Contains(hostName, ":") { + hostName += ":80" + } + + if hostName != c.host { + return 0, io.EOF + } + } + } + return c.Conn.Read(p) } @@ -40,12 +64,85 @@ func (h *HttpAdapter) Conn() net.Conn { return h.conn } -func NewHttp(host string, peeked []byte, conn net.Conn) *HttpAdapter { +func NewHttp(host string, peeked []byte, isHTTP bool, conn net.Conn) *HttpAdapter { return &HttpAdapter{ addr: parseHttpAddr(host), conn: &PeekedConn{ Peeked: peeked, Conn: conn, + host: host, + isHTTP: isHTTP, }, } } + +// ParserHTTPHostHeader returns the HTTP Host header from br without +// consuming any of its bytes. It returns "" if it can't find one. +func ParserHTTPHostHeader(br *bufio.Reader) (method, host string) { + // br := bufio.NewReader(bytes.NewReader(data)) + const maxPeek = 4 << 10 + peekSize := 0 + for { + peekSize++ + if peekSize > maxPeek { + b, _ := br.Peek(br.Buffered()) + return method, httpHostHeaderFromBytes(b) + } + b, err := br.Peek(peekSize) + if n := br.Buffered(); n > peekSize { + b, _ = br.Peek(n) + peekSize = n + } + if len(b) > 0 { + if b[0] < 'A' || b[0] > 'Z' { + // Doesn't look like an HTTP verb + // (GET, POST, etc). + return + } + if bytes.Index(b, crlfcrlf) != -1 || bytes.Index(b, lflf) != -1 { + req, err := http.ReadRequest(bufio.NewReader(bytes.NewReader(b))) + if err != nil { + return + } + if len(req.Header["Host"]) > 1 { + // TODO(bradfitz): what does + // ReadRequest do if there are + // multiple Host headers? + return + } + return req.Method, req.Host + } + } + if err != nil { + return method, httpHostHeaderFromBytes(b) + } + } +} + +var ( + lfHostColon = []byte("\nHost:") + lfhostColon = []byte("\nhost:") + crlf = []byte("\r\n") + lf = []byte("\n") + crlfcrlf = []byte("\r\n\r\n") + lflf = []byte("\n\n") +) + +func httpHostHeaderFromBytes(b []byte) string { + if i := bytes.Index(b, lfHostColon); i != -1 { + return string(bytes.TrimSpace(untilEOL(b[i+len(lfHostColon):]))) + } + if i := bytes.Index(b, lfhostColon); i != -1 { + return string(bytes.TrimSpace(untilEOL(b[i+len(lfhostColon):]))) + } + return "" +} + +// untilEOL returns v, truncated before the first '\n' byte, if any. +// The returned slice may include a '\r' at the end. +func untilEOL(v []byte) []byte { + if i := bytes.IndexByte(v, '\n'); i != -1 { + return v[:i] + } + return v +} diff --git a/proxy/http/server.go b/proxy/http/server.go index 31c0856f..d015a085 100644 --- a/proxy/http/server.go +++ b/proxy/http/server.go @@ -56,7 +56,7 @@ func NewHttpProxy(addr string) (*C.ProxySignal, error) { func handleConn(conn net.Conn) { br := bufio.NewReader(conn) - method, hostName := httpHostHeader(br) + method, hostName := adapters.ParserHTTPHostHeader(br) if hostName == "" { return } @@ -75,5 +75,5 @@ func handleConn(conn net.Conn) { peeked, _ = br.Peek(br.Buffered()) } - tun.Add(adapters.NewHttp(hostName, peeked, conn)) + tun.Add(adapters.NewHttp(hostName, peeked, method != http.MethodConnect, conn)) } diff --git a/proxy/http/util.go b/proxy/http/util.go deleted file mode 100644 index 090ff7e6..00000000 --- a/proxy/http/util.go +++ /dev/null @@ -1,77 +0,0 @@ -package http - -import ( - "bufio" - "bytes" - "net/http" -) - -// httpHostHeader returns the HTTP Host header from br without -// consuming any of its bytes. It returns ""if it can't find one. -func httpHostHeader(br *bufio.Reader) (method, host string) { - const maxPeek = 4 << 10 - peekSize := 0 - for { - peekSize++ - if peekSize > maxPeek { - b, _ := br.Peek(br.Buffered()) - return method, httpHostHeaderFromBytes(b) - } - b, err := br.Peek(peekSize) - if n := br.Buffered(); n > peekSize { - b, _ = br.Peek(n) - peekSize = n - } - if len(b) > 0 { - if b[0] < 'A' || b[0] > 'Z' { - // Doesn't look like an HTTP verb - // (GET, POST, etc). - return - } - if bytes.Index(b, crlfcrlf) != -1 || bytes.Index(b, lflf) != -1 { - req, err := http.ReadRequest(bufio.NewReader(bytes.NewReader(b))) - if err != nil { - return - } - if len(req.Header["Host"]) > 1 { - // TODO(bradfitz): what does - // ReadRequest do if there are - // multiple Host headers? - return - } - return req.Method, req.Host - } - } - if err != nil { - return method, httpHostHeaderFromBytes(b) - } - } -} - -var ( - lfHostColon = []byte("\nHost:") - lfhostColon = []byte("\nhost:") - crlf = []byte("\r\n") - lf = []byte("\n") - crlfcrlf = []byte("\r\n\r\n") - lflf = []byte("\n\n") -) - -func httpHostHeaderFromBytes(b []byte) string { - if i := bytes.Index(b, lfHostColon); i != -1 { - return string(bytes.TrimSpace(untilEOL(b[i+len(lfHostColon):]))) - } - if i := bytes.Index(b, lfhostColon); i != -1 { - return string(bytes.TrimSpace(untilEOL(b[i+len(lfhostColon):]))) - } - return "" -} - -// untilEOL returns v, truncated before the first '\n' byte, if any. -// The returned slice may include a '\r' at the end. -func untilEOL(v []byte) []byte { - if i := bytes.IndexByte(v, '\n'); i != -1 { - return v[:i] - } - return v -}