diff --git a/adapters/direct.go b/adapters/direct.go index 8185f8f1..3de2bea4 100644 --- a/adapters/direct.go +++ b/adapters/direct.go @@ -12,13 +12,8 @@ type DirectAdapter struct { conn net.Conn } -// Writer is used to output network traffic -func (d *DirectAdapter) Writer() io.Writer { - return d.conn -} - -// Reader is used to input network traffic -func (d *DirectAdapter) Reader() io.Reader { +// ReadWriter is used to handle network traffic +func (d *DirectAdapter) ReadWriter() io.ReadWriter { return d.conn } @@ -27,6 +22,11 @@ func (d *DirectAdapter) Close() { d.conn.Close() } +// Close is used to close connection +func (d *DirectAdapter) Conn() net.Conn { + return d.conn +} + type Direct struct { } diff --git a/adapters/reject.go b/adapters/reject.go index bfceb81f..b8fad1ff 100644 --- a/adapters/reject.go +++ b/adapters/reject.go @@ -2,6 +2,7 @@ package adapters import ( "io" + "net" C "github.com/Dreamacro/clash/constant" ) @@ -10,18 +11,17 @@ import ( type RejectAdapter struct { } -// Writer is used to output network traffic -func (r *RejectAdapter) Writer() io.Writer { - return &NopRW{} -} - -// Reader is used to input network traffic -func (r *RejectAdapter) Reader() io.Reader { +// ReadWriter is used to handle network traffic +func (r *RejectAdapter) ReadWriter() io.ReadWriter { return &NopRW{} } // Close is used to close connection -func (r *RejectAdapter) Close() { +func (r *RejectAdapter) Close() {} + +// Close is used to close connection +func (r *RejectAdapter) Conn() net.Conn { + return nil } type Reject struct { diff --git a/adapters/shadowsocks.go b/adapters/shadowsocks.go index 4159d7a2..555abef3 100644 --- a/adapters/shadowsocks.go +++ b/adapters/shadowsocks.go @@ -19,13 +19,8 @@ type ShadowsocksAdapter struct { conn net.Conn } -// Writer is used to output network traffic -func (ss *ShadowsocksAdapter) Writer() io.Writer { - return ss.conn -} - -// Reader is used to input network traffic -func (ss *ShadowsocksAdapter) Reader() io.Reader { +// ReadWriter is used to handle network traffic +func (ss *ShadowsocksAdapter) ReadWriter() io.ReadWriter { return ss.conn } @@ -34,6 +29,10 @@ func (ss *ShadowsocksAdapter) Close() { ss.conn.Close() } +func (ss *ShadowsocksAdapter) Conn() net.Conn { + return ss.conn +} + type ShadowSocks struct { server string cipher string diff --git a/constant/adapters.go b/constant/adapters.go index 63b49089..aab2db00 100644 --- a/constant/adapters.go +++ b/constant/adapters.go @@ -2,17 +2,19 @@ package constant import ( "io" + "net" ) type ProxyAdapter interface { - Writer() io.Writer - Reader() io.Reader + ReadWriter() io.ReadWriter + Conn() net.Conn Close() } type ServerAdapter interface { Addr() *Addr - ProxyAdapter + Connect(ProxyAdapter) + Close() } type Proxy interface { diff --git a/constant/addr.go b/constant/addr.go index e25fa0ac..6f498f98 100644 --- a/constant/addr.go +++ b/constant/addr.go @@ -9,10 +9,23 @@ const ( AtypIPv4 = 1 AtypDomainName = 3 AtypIPv6 = 4 + + TCP = iota + UDP ) +type NetWork int + +func (n *NetWork) String() string { + if *n == TCP { + return "tcp" + } + return "udp" +} + // Addr is used to store connection address type Addr struct { + NetWork NetWork AddrType int Host string IP *net.IP diff --git a/main.go b/main.go index 4bbaf6ad..f173b42b 100644 --- a/main.go +++ b/main.go @@ -6,7 +6,8 @@ import ( "syscall" C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/proxy" + "github.com/Dreamacro/clash/proxy/http" + "github.com/Dreamacro/clash/proxy/socks" "github.com/Dreamacro/clash/tunnel" log "github.com/sirupsen/logrus" @@ -33,8 +34,8 @@ func main() { log.Fatalf("Parse config error: %s", err.Error()) } - go proxy.NewHttpProxy(port) - go proxy.NewSocksProxy(socksPort) + go http.NewHttpProxy(port) + go socks.NewSocksProxy(socksPort) sigCh := make(chan os.Signal, 1) signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) diff --git a/proxy/http/http.go b/proxy/http/http.go new file mode 100644 index 00000000..f09e41b7 --- /dev/null +++ b/proxy/http/http.go @@ -0,0 +1,62 @@ +package http + +import ( + "io" + "net" + "net/http" + "time" + + C "github.com/Dreamacro/clash/constant" +) + +type HttpAdapter struct { + addr *C.Addr + r *http.Request + w http.ResponseWriter + done chan struct{} +} + +func (h *HttpAdapter) Close() { + h.done <- struct{}{} +} + +func (h *HttpAdapter) Addr() *C.Addr { + return h.addr +} + +func (h *HttpAdapter) Connect(proxy C.ProxyAdapter) { + req := http.Transport{ + Dial: func(string, string) (net.Conn, error) { + return proxy.Conn(), nil + }, + // from http.DefaultTransport + MaxIdleConns: 100, + IdleConnTimeout: 90 * time.Second, + TLSHandshakeTimeout: 10 * time.Second, + ExpectContinueTimeout: 1 * time.Second, + } + resp, err := req.RoundTrip(h.r) + if err != nil { + return + } + defer resp.Body.Close() + + header := h.w.Header() + for k, vv := range resp.Header { + for _, v := range vv { + header.Add(k, v) + } + } + h.w.WriteHeader(resp.StatusCode) + io.Copy(h.w, resp.Body) +} + +func NewHttp(host string, w http.ResponseWriter, r *http.Request) (*HttpAdapter, chan struct{}) { + done := make(chan struct{}) + return &HttpAdapter{ + addr: parseHttpAddr(host), + r: r, + w: w, + done: done, + }, done +} diff --git a/proxy/http/https.go b/proxy/http/https.go new file mode 100644 index 00000000..33eed177 --- /dev/null +++ b/proxy/http/https.go @@ -0,0 +1,35 @@ +package http + +import ( + "bufio" + "io" + "net" + + C "github.com/Dreamacro/clash/constant" +) + +type HttpsAdapter struct { + addr *C.Addr + conn net.Conn + rw *bufio.ReadWriter +} + +func (h *HttpsAdapter) Close() { + h.conn.Close() +} + +func (h *HttpsAdapter) Addr() *C.Addr { + return h.addr +} + +func (h *HttpsAdapter) Connect(proxy C.ProxyAdapter) { + go io.Copy(h.conn, proxy.ReadWriter()) + io.Copy(proxy.ReadWriter(), h.conn) +} + +func NewHttps(host string, conn net.Conn) *HttpsAdapter { + return &HttpsAdapter{ + addr: parseHttpAddr(host), + conn: conn, + } +} diff --git a/proxy/http.go b/proxy/http/server.go similarity index 55% rename from proxy/http.go rename to proxy/http/server.go index 62512234..c06a8f31 100644 --- a/proxy/http.go +++ b/proxy/http/server.go @@ -1,21 +1,22 @@ -package proxy +package http import ( - "bufio" - "bytes" "fmt" - "io" "net" "net/http" - "net/http/httputil" "strings" - "github.com/Dreamacro/clash/constant" + C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/tunnel" "github.com/riobard/go-shadowsocks2/socks" log "github.com/sirupsen/logrus" ) +var ( + tun = tunnel.GetInstance() +) + func NewHttpProxy(port string) { server := &http.Server{ Addr: fmt.Sprintf(":%s", port), @@ -32,21 +33,14 @@ func NewHttpProxy(port string) { } func handleHTTP(w http.ResponseWriter, r *http.Request) { - buf, _ := httputil.DumpRequestOut(r, true) - hijacker, ok := w.(http.Hijacker) - if !ok { - return - } - conn, rw, err := hijacker.Hijack() - if err != nil { - return - } addr := r.Host // padding default port if !strings.Contains(addr, ":") { addr += ":80" } - tun.Add(NewHttp(addr, conn, rw, buf)) + req, done := NewHttp(addr, w, r) + tun.Add(req) + <-done } func handleTunneling(w http.ResponseWriter, r *http.Request) { @@ -54,38 +48,16 @@ func handleTunneling(w http.ResponseWriter, r *http.Request) { if !ok { return } - conn, rw, err := hijacker.Hijack() + conn, _, err := hijacker.Hijack() if err != nil { return } // w.WriteHeader(http.StatusOK) doesn't works in Safari conn.Write([]byte("HTTP/1.1 200 OK\r\n\r\n")) - tun.Add(NewHttp(r.Host, conn, rw, []byte{})) + tun.Add(NewHttps(r.Host, conn)) } -type HttpAdapter struct { - addr *constant.Addr - conn net.Conn - r io.Reader -} - -func (h *HttpAdapter) Writer() io.Writer { - return h.conn -} - -func (h *HttpAdapter) Reader() io.Reader { - return h.r -} - -func (h *HttpAdapter) Close() { - h.conn.Close() -} - -func (h *HttpAdapter) Addr() *constant.Addr { - return h.addr -} - -func parseHttpAddr(target string) *constant.Addr { +func parseHttpAddr(target string) *C.Addr { host, port, _ := net.SplitHostPort(target) ipAddr, _ := net.ResolveIPAddr("ip", host) var addType int @@ -99,19 +71,11 @@ func parseHttpAddr(target string) *constant.Addr { addType = socks.AtypIPv4 } - return &constant.Addr{ + return &C.Addr{ + NetWork: C.TCP, AddrType: addType, Host: host, IP: &ipAddr.IP, Port: port, } } - -func NewHttp(host string, conn net.Conn, rw *bufio.ReadWriter, payload []byte) *HttpAdapter { - r := io.MultiReader(bytes.NewReader(payload), rw) - return &HttpAdapter{ - conn: conn, - addr: parseHttpAddr(host), - r: r, - } -} diff --git a/proxy/socks.go b/proxy/socks/tcp.go similarity index 83% rename from proxy/socks.go rename to proxy/socks/tcp.go index 1b5d3db8..163fdc55 100644 --- a/proxy/socks.go +++ b/proxy/socks/tcp.go @@ -1,4 +1,4 @@ -package proxy +package socks import ( "fmt" @@ -6,7 +6,7 @@ import ( "net" "strconv" - "github.com/Dreamacro/clash/constant" + C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/tunnel" "github.com/riobard/go-shadowsocks2/socks" @@ -45,26 +45,23 @@ func handleSocks(conn net.Conn) { type SocksAdapter struct { conn net.Conn - addr *constant.Addr -} - -func (s *SocksAdapter) Writer() io.Writer { - return s.conn -} - -func (s *SocksAdapter) Reader() io.Reader { - return s.conn + addr *C.Addr } func (s *SocksAdapter) Close() { s.conn.Close() } -func (s *SocksAdapter) Addr() *constant.Addr { +func (s *SocksAdapter) Addr() *C.Addr { return s.addr } -func parseSocksAddr(target socks.Addr) *constant.Addr { +func (s *SocksAdapter) Connect(proxy C.ProxyAdapter) { + go io.Copy(s.conn, proxy.ReadWriter()) + io.Copy(proxy.ReadWriter(), s.conn) +} + +func parseSocksAddr(target socks.Addr) *C.Addr { var host, port string var ip net.IP @@ -84,7 +81,8 @@ func parseSocksAddr(target socks.Addr) *constant.Addr { port = strconv.Itoa((int(target[1+net.IPv6len]) << 8) | int(target[1+net.IPv6len+1])) } - return &constant.Addr{ + return &C.Addr{ + NetWork: C.TCP, AddrType: int(target[0]), Host: host, IP: &ip, diff --git a/proxy/socks/udp.go b/proxy/socks/udp.go new file mode 100644 index 00000000..a266580e --- /dev/null +++ b/proxy/socks/udp.go @@ -0,0 +1 @@ +package socks diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 278b2559..c1edd9a2 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -2,7 +2,6 @@ package tunnel import ( "fmt" - "io" "strings" "sync" @@ -110,8 +109,7 @@ func (t *Tunnel) handleConn(localConn C.ServerAdapter) { } defer remoConn.Close() - go io.Copy(localConn.Writer(), remoConn.Reader()) - io.Copy(remoConn.Writer(), localConn.Reader()) + localConn.Connect(remoConn) } func (t *Tunnel) match(addr *C.Addr) C.Proxy {