From d3664dc2b931576d456bdd9d45872bc659f1c5cd Mon Sep 17 00:00:00 2001 From: wweir Date: Sat, 30 Mar 2019 12:31:02 +0800 Subject: [PATCH] Add more protocols support --- proxy/client.go | 71 +++++++++++------ proxy/http_proxy.go | 18 ++++- proxy/parser/doc.go | 12 +++ proxy/parser/parse_addr.go | 152 +++++++++++++++++++++++++++++++------ proxy/server.go | 6 +- proxy/shadow/doc.go | 3 +- proxy/util.go | 20 ----- 7 files changed, 206 insertions(+), 76 deletions(-) create mode 100644 proxy/parser/doc.go diff --git a/proxy/client.go b/proxy/client.go index e01c84e..539b378 100644 --- a/proxy/client.go +++ b/proxy/client.go @@ -4,16 +4,26 @@ import ( "net" "github.com/golang/glog" + "github.com/wweir/sower/proxy/parser" "github.com/wweir/sower/proxy/shadow" + "github.com/wweir/sower/proxy/socks5" "github.com/wweir/sower/proxy/transport" ) func StartClient(tran transport.Transport, isSocks5 bool, server, cipher, password, listenIP string) { - connCh := listenLocal(listenIP, []string{"80", "443"}) + conn80 := listenLocal(listenIP, "80") + conn443 := listenLocal(listenIP, "443") + var isHttp bool + var conn net.Conn glog.Infoln("Client started.") for { - conn := <-connCh + select { + case conn = <-conn80: + isHttp = true + case conn = <-conn443: + isHttp = false + } resolveAddr(&server) glog.V(1).Infof("new conn from (%s) to (%s)", conn.RemoteAddr(), server) @@ -25,41 +35,56 @@ func StartClient(tran transport.Transport, isSocks5 bool, server, cipher, passwo continue } - if isSocks5 { - if rc, conn, err = buildSocks5Conn(rc, conn); err != nil { + if !isSocks5 { + rc = shadow.Shadow(rc, cipher, password) + + } else if isHttp { + conn, host, port, err := parser.ParseHttpAddr(conn) + if err != nil { + conn.Close() + rc.Close() glog.Errorln(err) continue } + + rc = socks5.ToSocks5(rc, host, port) + } else { - rc = shadow.Shadow(rc, cipher, password) + conn, host, err := parser.ParseHttpsHost(conn) + if err != nil { + conn.Close() + rc.Close() + glog.Errorln(err) + continue + } + + rc = socks5.ToSocks5(rc, host, "443") } go relay(conn, rc) } } -func listenLocal(listenIP string, ports []string) <-chan net.Conn { +func listenLocal(listenIP string, port string) <-chan net.Conn { connCh := make(chan net.Conn, 10) - for i := range ports { - go func(port string) { - ln, err := net.Listen("tcp", net.JoinHostPort(listenIP, port)) + go func() { + ln, err := net.Listen("tcp", net.JoinHostPort(listenIP, port)) + if err != nil { + glog.Fatalln(err) + } + + for { + conn, err := ln.Accept() if err != nil { - glog.Fatalln(err) + glog.Errorln("accept", listenIP+port, "fail:", err) + continue } - for { - conn, err := ln.Accept() - if err != nil { - glog.Errorln("accept", listenIP+port, "fail:", err) - continue - } + conn.(*net.TCPConn).SetKeepAlive(true) + connCh <- conn + } + }() - conn.(*net.TCPConn).SetKeepAlive(true) - connCh <- conn - } - }(ports[i]) - } - - glog.Infoln("listening ports:", ports) + glog.Infoln("listening port:", port) return connCh } diff --git a/proxy/http_proxy.go b/proxy/http_proxy.go index 9477972..f5ad8fc 100644 --- a/proxy/http_proxy.go +++ b/proxy/http_proxy.go @@ -10,7 +10,9 @@ import ( "time" "github.com/golang/glog" + "github.com/wweir/sower/proxy/parser" "github.com/wweir/sower/proxy/shadow" + "github.com/wweir/sower/proxy/socks5" "github.com/wweir/sower/proxy/transport" ) @@ -56,6 +58,7 @@ func httpProxy(w http.ResponseWriter, r *http.Request, return nil, err } + conn = parser.NewHttpProtocol(conn) return shadow.Shadow(conn, cipher, password), nil } } @@ -103,12 +106,19 @@ func httpsProxy(w http.ResponseWriter, r *http.Request, return } + host, port, err := net.SplitHostPort(r.Host) + if err != nil { + http.Error(w, err.Error(), http.StatusServiceUnavailable) + conn.Close() + glog.Errorln("serve https proxy, dial remote fail:", err) + return + } + if isSocks5 { - if rc, conn, err = buildSocks5Conn(rc, conn); err != nil { - glog.Errorln(err) - return - } + rc = socks5.ToSocks5(rc, host, port) + } else { + rc = parser.NewHttpsProtocol(rc, port) rc = shadow.Shadow(rc, cipher, password) } diff --git a/proxy/parser/doc.go b/proxy/parser/doc.go new file mode 100644 index 0000000..bf127df --- /dev/null +++ b/proxy/parser/doc.go @@ -0,0 +1,12 @@ +// Package parser transter conn to be a parser conn +// +// init request payload: +// (1) + (2))(+Overhead) + (size+Overhead) +// data definition: +// 0x00(any): [size](1) + [addr:port] + content +// 0x01(http): content +// 0x02(https): [port](2) + content +// +// init response payload: +// ([status code](2) + (2))(+Overhead) + (size+Overhead) +package parser diff --git a/proxy/parser/parse_addr.go b/proxy/parser/parse_addr.go index 5fff9c3..e32d920 100644 --- a/proxy/parser/parse_addr.go +++ b/proxy/parser/parse_addr.go @@ -2,45 +2,147 @@ package parser import ( "bufio" - "fmt" "io" "net" "net/http" + "strconv" "strings" + "github.com/pkg/errors" "github.com/wweir/sower/util" ) -func ParseAddr(conn net.Conn) (teeConn *util.TeeConn, addr string, err error) { - teeConn = &util.TeeConn{Conn: conn} +const ( + ANY byte = iota + HTTP + HTTPS +) + +// Write Addr +type conn struct { + typ byte + domain string + port string + init bool + net.Conn +} + +func NewAnyProtocol(c net.Conn, domain, port string) net.Conn { + return &conn{ + typ: ANY, + domain: domain, + port: port, + Conn: c, + } +} +func NewHttpProtocol(c net.Conn) net.Conn { + return &conn{ + typ: HTTP, + Conn: c, + } +} +func NewHttpsProtocol(c net.Conn, port string) net.Conn { + return &conn{ + typ: HTTPS, + port: port, + Conn: c, + } +} + +func (c *conn) Write(b []byte) (n int, err error) { + if !c.init { + var pkg []byte + switch c.typ { + case ANY: + pkg = make([]byte, 0, 1+len(c.domain)+1+len(c.port)+len(b)) + pkg = append(pkg, ANY) + pkg = append(pkg, byte(len(c.domain)+1+len(c.port))) + pkg = append(pkg, []byte(c.domain+":"+c.port)...) + + case HTTP: + pkg = make([]byte, 0, 1+len(b)) + pkg = append(pkg, HTTP) + + case HTTPS: + pkg = make([]byte, 0, 1+2+len(b)) + pkg = append(pkg, HTTPS) + port, _ := strconv.Atoi(c.port) + pkg = append(pkg, byte(port>>8), byte(port)) + } + + c.init = true + return c.Conn.Write(append(pkg, b...)) + } + + return c.Conn.Write(b) +} + +// Read Addr +func ParseAddr(conn net.Conn) (net.Conn, string, string, error) { + buf := make([]byte, 1) + if _, err := io.ReadFull(conn, buf); err != nil { + return conn, "", "", err + } + + switch buf[0] { + case ANY: + if _, err := io.ReadFull(conn, buf); err != nil { + return conn, "", "", err + } + buf = make([]byte, int(buf[0])) + if _, err := io.ReadFull(conn, buf); err != nil { + return conn, "", "", err + } + + addr := string(buf) + if idx := strings.LastIndex(addr, ":"); idx != -1 { + return conn, addr[:idx], addr[idx+1:], nil + } + return conn, "", "", errors.New("invalid payload") + + case HTTP: + return ParseHttpAddr(conn) + + case HTTPS: + buf = make([]byte, 2) + if _, err := io.ReadFull(conn, buf); err != nil { + return conn, "", "", err + } + + conn, domain, err := ParseHttpsHost(conn) + return conn, domain, strconv.Itoa(int(buf[0])<<8 + int(buf[1])), err + + default: + return conn, "", "", errors.Errorf("not supported type (%v)", buf[0]) + } +} + +func ParseHttpAddr(conn net.Conn) (net.Conn, string, string, error) { + teeConn := &util.TeeConn{Conn: conn} teeConn.StartOrReset() defer teeConn.Stop() - buf := make([]byte, 1) - if n, err := teeConn.Read(buf); err != nil { - return teeConn, "", fmt.Errorf("Read conn fail: %v, readed: %v", err, buf[:n]) - } - - teeConn.StartOrReset() - - // https - if buf[0] == 0x16 { // SSL handleshake - host, _, err := extractSNI(io.Reader(teeConn)) - if err != nil { - return teeConn, "", err - } - - return teeConn, host + ":443", nil - } - - // http b := bufio.NewReader(teeConn) resp, err := http.ReadRequest(b) + if err != nil { + return teeConn, "", "", err + } + + if idx := strings.LastIndex(resp.Host, ":"); idx != -1 { + return teeConn, resp.Host[:idx], resp.Host[idx+1:], nil + } + return teeConn, resp.Host, "80", nil +} + +func ParseHttpsHost(conn net.Conn) (net.Conn, string, error) { + teeConn := &util.TeeConn{Conn: conn} + teeConn.StartOrReset() + defer teeConn.Stop() + + domain, _, err := extractSNI(io.Reader(teeConn)) if err != nil { return teeConn, "", err } - if strings.Contains(resp.Host, ":") { - return teeConn, resp.Host, nil - } - return teeConn, resp.Host + ":80", nil + + return teeConn, domain, nil } diff --git a/proxy/server.go b/proxy/server.go index badd09e..b5a56b5 100644 --- a/proxy/server.go +++ b/proxy/server.go @@ -33,15 +33,15 @@ func StartServer(tran transport.Transport, port, cipher, password string) { } func handle(conn net.Conn) { - conn, addr, err := parser.ParseAddr(conn) + conn, host, port, err := parser.ParseAddr(conn) if err != nil { conn.Close() glog.Warningln(err) return } - glog.V(1).Infof("new conn from %s to %s", conn.RemoteAddr(), addr) + glog.V(1).Infof("new conn from %s to %s:%s", conn.RemoteAddr(), host, port) - rc, err := net.Dial("tcp", addr) + rc, err := net.Dial("tcp", net.JoinHostPort(host, port)) if err != nil { conn.Close() glog.Warningln(err) diff --git a/proxy/shadow/doc.go b/proxy/shadow/doc.go index d4d56e2..9bc17fe 100644 --- a/proxy/shadow/doc.go +++ b/proxy/shadow/doc.go @@ -1,4 +1,5 @@ // Package shadow transter conn to be a crypto conn // support aead mode only -// payload: [size](2+Overhead bytes) + [content](size+Overhead bytes) +// data payload: +// (2+Overhead) + (size+Overhead) package shadow diff --git a/proxy/util.go b/proxy/util.go index fc5116b..59fd500 100644 --- a/proxy/util.go +++ b/proxy/util.go @@ -8,8 +8,6 @@ import ( "time" "github.com/golang/glog" - "github.com/wweir/sower/proxy/parser" - "github.com/wweir/sower/proxy/socks5" ) // race safe @@ -26,24 +24,6 @@ func resolveAddr(server *string) { } } -func buildSocks5Conn(c, relay net.Conn) (net.Conn, net.Conn, error) { - conn, addr, err := parser.ParseAddr(relay) - if err != nil { - c.Close() - relay.Close() - return nil, nil, err - } - - host, port, err := net.SplitHostPort(addr) - if err != nil { - c.Close() - relay.Close() - return nil, nil, err - } - - return socks5.ToSocks5(c, host, port), conn, nil -} - func relay(conn1, conn2 net.Conn) { wg := &sync.WaitGroup{} exitFlag := new(int32)