From 8ec025b56a91a3aebf6d79df8bcd5c7123900f90 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Mon, 27 Aug 2018 00:06:40 +0800 Subject: [PATCH] Improve: HTTP proxy server handler --- README.md | 2 - adapters/local/http.go | 80 ++++++++++---------------- adapters/local/https.go | 14 +++++ adapters/local/{socks.go => socket.go} | 16 +++--- adapters/local/util.go | 10 +++- proxy/http/server.go | 19 +++--- proxy/redir/tcp.go | 2 +- proxy/socks/tcp.go | 2 +- tunnel/connection.go | 47 ++++++++++++--- tunnel/tunnel.go | 2 +- 10 files changed, 109 insertions(+), 85 deletions(-) create mode 100644 adapters/local/https.go rename adapters/local/{socks.go => socket.go} (51%) diff --git a/README.md b/README.md index 48690e4d3..fc1600e4d 100644 --- a/README.md +++ b/README.md @@ -108,8 +108,6 @@ FINAL,,Proxy # note: there is two "," [riobard/go-shadowsocks2](https://github.com/riobard/go-shadowsocks2) -[google/tcpproxy](https://github.com/google/tcpproxy) - ## License [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2FDreamacro%2Fclash.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2FDreamacro%2Fclash?ref=badge_large) diff --git a/adapters/local/http.go b/adapters/local/http.go index e9d3f6228..ddd846118 100644 --- a/adapters/local/http.go +++ b/adapters/local/http.go @@ -1,56 +1,18 @@ package adapters import ( - "bufio" - "bytes" - "io" "net" + "net/http" "strings" C "github.com/Dreamacro/clash/constant" ) -// PeekedConn handle http connection and buffed HTTP data -type PeekedConn struct { - net.Conn - Peeked []byte - host string - isHTTP bool -} - -func (c *PeekedConn) Read(p []byte) (n int, err error) { - if len(c.Peeked) > 0 { - n = copy(p, c.Peeked) - c.Peeked = c.Peeked[n:] - if len(c.Peeked) == 0 { - c.Peeked = nil - } - 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) -} - // HTTPAdapter is a adapter for HTTP connection type HTTPAdapter struct { addr *C.Addr - conn *PeekedConn + conn net.Conn + R *http.Request } // Close HTTP connection @@ -69,14 +31,34 @@ func (h *HTTPAdapter) Conn() net.Conn { } // NewHTTP is HTTPAdapter generator -func NewHTTP(host string, peeked []byte, isHTTP bool, conn net.Conn) *HTTPAdapter { +func NewHTTP(request *http.Request, conn net.Conn) *HTTPAdapter { return &HTTPAdapter{ - addr: parseHTTPAddr(host), - conn: &PeekedConn{ - Peeked: peeked, - Conn: conn, - host: host, - isHTTP: isHTTP, - }, + addr: parseHTTPAddr(request), + R: request, + conn: conn, + } +} + +// RemoveHopByHopHeaders remove hop-by-hop header +func RemoveHopByHopHeaders(header http.Header) { + // Strip hop-by-hop header based on RFC: + // http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.5.1 + // https://www.mnot.net/blog/2011/07/11/what_proxies_must_do + + header.Del("Proxy-Connection") + header.Del("Proxy-Authenticate") + header.Del("Proxy-Authorization") + header.Del("TE") + header.Del("Trailers") + header.Del("Transfer-Encoding") + header.Del("Upgrade") + + connections := header.Get("Connection") + header.Del("Connection") + if len(connections) == 0 { + return + } + for _, h := range strings.Split(connections, ",") { + header.Del(strings.TrimSpace(h)) } } diff --git a/adapters/local/https.go b/adapters/local/https.go new file mode 100644 index 000000000..61d312558 --- /dev/null +++ b/adapters/local/https.go @@ -0,0 +1,14 @@ +package adapters + +import ( + "net" + "net/http" +) + +// NewHTTPS is HTTPAdapter generator +func NewHTTPS(request *http.Request, conn net.Conn) *SocketAdapter { + return &SocketAdapter{ + addr: parseHTTPAddr(request), + conn: conn, + } +} diff --git a/adapters/local/socks.go b/adapters/local/socket.go similarity index 51% rename from adapters/local/socks.go rename to adapters/local/socket.go index 2f31cdc59..4143e7464 100644 --- a/adapters/local/socks.go +++ b/adapters/local/socket.go @@ -7,30 +7,30 @@ import ( "github.com/riobard/go-shadowsocks2/socks" ) -// SocksAdapter is a adapter for socks and redir connection -type SocksAdapter struct { +// SocketAdapter is a adapter for socks and redir connection +type SocketAdapter struct { conn net.Conn addr *C.Addr } // Close socks and redir connection -func (s *SocksAdapter) Close() { +func (s *SocketAdapter) Close() { s.conn.Close() } // Addr return destination address -func (s *SocksAdapter) Addr() *C.Addr { +func (s *SocketAdapter) Addr() *C.Addr { return s.addr } // Conn return raw net.Conn -func (s *SocksAdapter) Conn() net.Conn { +func (s *SocketAdapter) Conn() net.Conn { return s.conn } -// NewSocks is SocksAdapter generator -func NewSocks(target socks.Addr, conn net.Conn) *SocksAdapter { - return &SocksAdapter{ +// NewSocket is SocketAdapter generator +func NewSocket(target socks.Addr, conn net.Conn) *SocketAdapter { + return &SocketAdapter{ conn: conn, addr: parseSocksAddr(target), } diff --git a/adapters/local/util.go b/adapters/local/util.go index 90e9c472d..37e47db45 100644 --- a/adapters/local/util.go +++ b/adapters/local/util.go @@ -40,8 +40,12 @@ func parseSocksAddr(target socks.Addr) *C.Addr { } } -func parseHTTPAddr(target string) *C.Addr { - host, port, _ := net.SplitHostPort(target) +func parseHTTPAddr(request *http.Request) *C.Addr { + host := request.URL.Hostname() + port := request.URL.Port() + if port == "" { + port = "80" + } ipAddr, err := net.ResolveIPAddr("ip", host) var resolveIP *net.IP if err == nil { @@ -92,6 +96,7 @@ func ParserHTTPHostHeader(br *bufio.Reader) (method, host string) { return } if bytes.Index(b, crlfcrlf) != -1 || bytes.Index(b, lflf) != -1 { + println(string(b)) req, err := http.ReadRequest(bufio.NewReader(bytes.NewReader(b))) if err != nil { return @@ -102,6 +107,7 @@ func ParserHTTPHostHeader(br *bufio.Reader) (method, host string) { // multiple Host headers? return } + println(req.Host) return req.Method, req.Host } } diff --git a/proxy/http/server.go b/proxy/http/server.go index 5dae39607..eb11c648a 100644 --- a/proxy/http/server.go +++ b/proxy/http/server.go @@ -4,7 +4,6 @@ import ( "bufio" "net" "net/http" - "strings" "github.com/Dreamacro/clash/adapters/local" C "github.com/Dreamacro/clash/constant" @@ -56,24 +55,20 @@ func NewHttpProxy(addr string) (*C.ProxySignal, error) { func handleConn(conn net.Conn) { br := bufio.NewReader(conn) - method, hostName := adapters.ParserHTTPHostHeader(br) - if hostName == "" { + request, err := http.ReadRequest(br) + if err != nil { + conn.Close() return } - if !strings.Contains(hostName, ":") { - hostName += ":80" - } - - var peeked []byte - if method == http.MethodConnect { + if request.Method == http.MethodConnect { _, err := conn.Write([]byte("HTTP/1.1 200 Connection established\r\n\r\n")) if err != nil { return } - } else if n := br.Buffered(); n > 0 { - peeked, _ = br.Peek(br.Buffered()) + tun.Add(adapters.NewHTTPS(request, conn)) + return } - tun.Add(adapters.NewHTTP(hostName, peeked, method != http.MethodConnect, conn)) + tun.Add(adapters.NewHTTP(request, conn)) } diff --git a/proxy/redir/tcp.go b/proxy/redir/tcp.go index 16ccb248a..3f653dc92 100644 --- a/proxy/redir/tcp.go +++ b/proxy/redir/tcp.go @@ -58,5 +58,5 @@ func handleRedir(conn net.Conn) { return } conn.(*net.TCPConn).SetKeepAlive(true) - tun.Add(adapters.NewSocks(target, conn)) + tun.Add(adapters.NewSocket(target, conn)) } diff --git a/proxy/socks/tcp.go b/proxy/socks/tcp.go index 0fe78e29a..5aa8ccc1a 100644 --- a/proxy/socks/tcp.go +++ b/proxy/socks/tcp.go @@ -59,5 +59,5 @@ func handleSocks(conn net.Conn) { return } conn.(*net.TCPConn).SetKeepAlive(true) - tun.Add(adapters.NewSocks(target, conn)) + tun.Add(adapters.NewSocket(target, conn)) } diff --git a/tunnel/connection.go b/tunnel/connection.go index ea3d42fd2..581ca1ace 100644 --- a/tunnel/connection.go +++ b/tunnel/connection.go @@ -1,7 +1,9 @@ package tunnel import ( + "bufio" "io" + "net/http" "github.com/Dreamacro/clash/adapters/local" C "github.com/Dreamacro/clash/constant" @@ -9,20 +11,47 @@ import ( func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, proxy C.ProxyAdapter) { conn := newTrafficTrack(proxy.Conn(), t.traffic) + req := request.R + host := req.Host - // Before we unwrap src and/or dst, copy any buffered data. - if wc, ok := request.Conn().(*adapters.PeekedConn); ok && len(wc.Peeked) > 0 { - if _, err := conn.Write(wc.Peeked); err != nil { - return + for { + req.Header.Set("Connection", "close") + req.RequestURI = "" + adapters.RemoveHopByHopHeaders(req.Header) + err := req.Write(conn) + if err != nil { + break } - wc.Peeked = nil - } + br := bufio.NewReader(conn) + resp, err := http.ReadResponse(br, req) + if err != nil { + break + } + adapters.RemoveHopByHopHeaders(resp.Header) + if resp.ContentLength >= 0 { + resp.Header.Set("Proxy-Connection", "keep-alive") + resp.Header.Set("Connection", "keep-alive") + resp.Header.Set("Keep-Alive", "timeout=4") + resp.Close = false + } else { + resp.Close = true + } + resp.Write(request.Conn()) - go io.Copy(request.Conn(), conn) - io.Copy(conn, request.Conn()) + req, err = http.ReadRequest(bufio.NewReader(request.Conn())) + if err != nil { + break + } + + // Sometimes firefox just open a socket to process multiple domains in HTTP + // The temporary solution is close connection when encountering different HOST + if req.Host != host { + break + } + } } -func (t *Tunnel) handleSOCKS(request *adapters.SocksAdapter, proxy C.ProxyAdapter) { +func (t *Tunnel) handleSOCKS(request *adapters.SocketAdapter, proxy C.ProxyAdapter) { conn := newTrafficTrack(proxy.Conn(), t.traffic) go io.Copy(request.Conn(), conn) io.Copy(conn, request.Conn()) diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 8d3e57736..a3d29f688 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -106,7 +106,7 @@ func (t *Tunnel) handleConn(localConn C.ServerAdapter) { switch adapter := localConn.(type) { case *LocalAdapter.HTTPAdapter: t.handleHTTP(adapter, remoConn) - case *LocalAdapter.SocksAdapter: + case *LocalAdapter.SocketAdapter: t.handleSOCKS(adapter, remoConn) } }