From e19ff132f4dca00ea4534191f389a48f868e3b84 Mon Sep 17 00:00:00 2001 From: "rui.zheng" Date: Sat, 10 Oct 2015 18:08:58 +0800 Subject: [PATCH] fix http proxy --- cert2.pem => cert.pem | 0 conn.go | 179 +++++++++++++++++++++++++++++++++++++++++- http.go | 52 +++++++++++- main.go | 20 ++--- socks.go | 7 +- util.go | 61 ++++++-------- 6 files changed, 266 insertions(+), 53 deletions(-) rename cert2.pem => cert.pem (100%) diff --git a/cert2.pem b/cert.pem similarity index 100% rename from cert2.pem rename to cert.pem diff --git a/conn.go b/conn.go index 43620b8..5ee0c5f 100644 --- a/conn.go +++ b/conn.go @@ -2,18 +2,47 @@ package main import ( "bufio" + "bytes" "crypto/tls" + "encoding/base64" + "errors" "github.com/ginuerzh/gosocks5" "github.com/golang/glog" "io" "net" "net/http" + "net/http/httputil" + "net/url" + "strconv" + "strings" +) + +const ( + ConnHttp = "http" + ConnHttpConnect = "http-connect" + ConnSocks5 = "socks5" ) func listenAndServe(arg Args) error { var ln net.Listener var err error + if glog.V(3) { + b := bytes.Buffer{} + b.WriteString("listen on %s, use %s tunnel and %s protocol for data transport. ") + if arg.EncMeth == "tls" { + b.WriteString("for socks5, tls encrypt method is supported.") + } else { + b.WriteString("for socks5, tls encrypt method is NOT supported.") + } + protocol := arg.Protocol + if protocol == "" { + protocol = "http/socks5" + } + glog.Infof(b.String(), arg.Addr, arg.Transport, protocol) + + } + switch arg.Transport { case "ws": // websocket connection err = NewWs(arg).ListenAndServe() @@ -26,6 +55,8 @@ func listenAndServe(arg Args) error { case "tls": // tls connection ln, err = tls.Listen("tcp", arg.Addr, &tls.Config{Certificates: []tls.Certificate{arg.Cert}}) + case "tcp": + fallthrough default: ln, err = net.Listen("tcp", arg.Addr) } @@ -36,6 +67,7 @@ func listenAndServe(arg Args) error { } return err } + defer ln.Close() for { @@ -61,11 +93,14 @@ func handleConn(conn net.Conn, arg Args) { selector := &serverSelector{ methods: []uint8{ gosocks5.MethodNoAuth, gosocks5.MethodUserPass, - MethodTLS, MethodTLSAuth, }, arg: arg, } + if arg.EncMeth == "tls" { + selector.methods = append(selector.methods, MethodTLS, MethodTLSAuth) + } + switch arg.Protocol { case "ss": // shadowsocks return @@ -174,3 +209,145 @@ func (r *reqReader) Read(p []byte) (n int, err error) { return } + +func connect(connType, addr string) (conn net.Conn, err error) { + if !strings.Contains(addr, ":") { + addr += ":80" + } + + if len(forwardArgs) > 0 { + // TODO: multi-foward + forward := forwardArgs[0] + return connectForward(addr, forward) + } + + if len(proxyArgs) > 0 { + proxy := proxyArgs[0] + return connectProxy(connType, addr, proxy) + } + + return net.Dial("tcp", addr) +} + +func connectProxy(connType, addr string, proxy Args) (conn net.Conn, err error) { + if glog.V(LINFO) { + glog.Infoln("connect proxy:", proxy.Addr) + } + conn, err = net.Dial("tcp", proxy.Addr) + if err != nil { + return + } + + switch proxy.Transport { + case "ws": // websocket connection + c, err := wsClient(conn, proxy.Addr) + if err != nil { + conn.Close() + return nil, err + } + conn = c + case "tls": // tls connection + conn = tls.Client(conn, &tls.Config{InsecureSkipVerify: true}) + case "tcp": + fallthrough + default: + } + + switch proxy.Protocol { + case "ss": // shadowsocks + conn.Close() + return nil, errors.New("Not implemented") + case "socks", "socks5": + selector := &clientSelector{ + methods: []uint8{gosocks5.MethodNoAuth, gosocks5.MethodUserPass}, + arg: proxy, + } + if proxy.EncMeth == "tls" { + selector.methods = []uint8{MethodTLS, MethodTLSAuth} + } + c := gosocks5.ClientConn(conn, selector) + if err := c.Handleshake(); err != nil { + c.Close() + return nil, err + } + conn = c + + if connType == ConnHttp || connType == ConnHttpConnect { + host, port, _ := net.SplitHostPort(addr) + p, _ := strconv.ParseUint(port, 10, 16) + r := gosocks5.NewRequest(gosocks5.CmdConnect, &gosocks5.Addr{ + Type: gosocks5.AddrDomain, + Host: host, + Port: uint16(p), + }) + if glog.V(LDEBUG) { + glog.Infoln(r.String()) + } + if err := r.Write(conn); err != nil { + conn.Close() + return nil, err + } + + rep, err := gosocks5.ReadReply(conn) + if err != nil { + conn.Close() + return nil, err + } + if glog.V(LDEBUG) { + glog.Infoln(rep.String()) + } + + if rep.Rep != gosocks5.Succeeded { + conn.Close() + return nil, errors.New("Service unavailable") + } + } + case "http": + fallthrough + default: + if connType == ConnHttpConnect { + req := &http.Request{ + Method: "CONNECT", + URL: &url.URL{Host: addr}, + Host: addr, + ProtoMajor: 1, + ProtoMinor: 1, + Header: make(http.Header), + } + req.Header.Set("Proxy-Connection", "keep-alive") + if proxy.User != nil { + req.Header.Set("Proxy-Authorization", + "Basic "+base64.StdEncoding.EncodeToString([]byte(proxy.User.String()))) + } + if err = req.Write(conn); err != nil { + conn.Close() + return nil, err + } + if glog.V(LDEBUG) { + dump, _ := httputil.DumpRequest(req, false) + glog.Infoln(string(dump)) + } + + resp, err := http.ReadResponse(bufio.NewReader(conn), req) + if err != nil { + conn.Close() + return nil, err + } + if glog.V(LDEBUG) { + dump, _ := httputil.DumpResponse(resp, false) + glog.Infoln(string(dump)) + } + if resp.StatusCode != http.StatusOK { + conn.Close() + //log.Println(resp.Status) + return nil, errors.New(resp.Status) + } + } + } + + return +} + +func connectForward(addr string, forward Args) (net.Conn, error) { + return nil, errors.New("Not implemented") +} diff --git a/http.go b/http.go index 4a09290..aa960fa 100644 --- a/http.go +++ b/http.go @@ -19,13 +19,18 @@ func handleHttpRequest(req *http.Request, conn net.Conn, arg Args) { } } + connType := ConnHttp + if req.Method == "CONNECT" { + connType = ConnHttpConnect + } + var username, password string if arg.User != nil { username = arg.User.Username() password, _ = arg.User.Password() } - u, p, _ := proxyBasicAuth(req.Header.Get("Proxy-Authorization")) + u, p, _ := basicAuth(req.Header.Get("Proxy-Authorization")) req.Header.Del("Proxy-Authorization") if (username != "" && u != username) || (password != "" && p != password) { @@ -46,9 +51,52 @@ func handleHttpRequest(req *http.Request, conn net.Conn, arg Args) { } return } + + c, err := connect(connType, req.Host) + if err != nil { + if glog.V(LWARNING) { + glog.Warningln(err) + } + b := []byte("HTTP/1.1 503 Service unavailable\r\n" + + "Proxy-Agent: gost/" + Version + "\r\n\r\n") + if glog.V(LDEBUG) { + glog.Infoln(string(b)) + } + conn.Write(b) + return + } + defer c.Close() + + if connType == ConnHttpConnect { + b := []byte("HTTP/1.1 200 Connection established\r\n" + + "Proxy-Agent: gost/" + Version + "\r\n\r\n") + if glog.V(LDEBUG) { + glog.Infoln(string(b)) + } + if _, err := conn.Write(b); err != nil { + if glog.V(LWARNING) { + glog.Warningln(err) + } + return + } + } else { + if len(proxyArgs) > 0 { + err = req.WriteProxy(c) + } else { + err = req.Write(c) + } + if err != nil { + if glog.V(LWARNING) { + glog.Warningln(err) + } + return + } + } + + Transport(conn, c) } -func proxyBasicAuth(authInfo string) (username, password string, ok bool) { +func basicAuth(authInfo string) (username, password string, ok bool) { if authInfo == "" { return } diff --git a/main.go b/main.go index 9bfa51f..16b8755 100644 --- a/main.go +++ b/main.go @@ -17,6 +17,7 @@ const ( var ( listenUrl, proxyUrl, forwardUrl string + pv bool // print version listenArgs []Args proxyArgs []Args @@ -27,6 +28,7 @@ func init() { flag.StringVar(&listenUrl, "L", ":http", "local address") flag.StringVar(&forwardUrl, "S", "", "remote address") flag.StringVar(&proxyUrl, "P", "", "proxy address") + flag.BoolVar(&pv, "V", false, "print version") flag.Parse() @@ -38,22 +40,22 @@ func init() { func main() { defer glog.Flush() + if pv { + printVersion() + return + } + if len(listenArgs) == 0 { glog.Fatalln("no listen addr") } var wg sync.WaitGroup - - for _, arg := range listenArgs { + for _, args := range listenArgs { wg.Add(1) - go func() { + go func(arg Args) { defer wg.Done() - if err := listenAndServe(arg); err != nil { - if glog.V(LFATAL) { - glog.Errorln(err) - } - } - }() + listenAndServe(arg) + }(args) } wg.Wait() } diff --git a/socks.go b/socks.go index ef7094c..899ded4 100644 --- a/socks.go +++ b/socks.go @@ -14,11 +14,12 @@ const ( ) type clientSelector struct { - arg Args + methods []uint8 + arg Args } func (selector *clientSelector) Methods() []uint8 { - return nil + return selector.methods } func (selector *clientSelector) Select(methods ...uint8) (method uint8) { @@ -187,7 +188,7 @@ func handleSocks5Request(req *gosocks5.Request, conn net.Conn, arg Args) { if glog.V(LINFO) { glog.Infoln("socks5 connect:", req.Addr.String()) } - tconn, err := connect(req.Addr.String()) + tconn, err := connect(ConnSocks5, req.Addr.String()) if err != nil { if glog.V(LWARNING) { glog.Warningln("socks5 connect:", err) diff --git a/util.go b/util.go index c388ed6..a1f674c 100644 --- a/util.go +++ b/util.go @@ -2,7 +2,6 @@ package main import ( "crypto/tls" - "errors" "fmt" "github.com/golang/glog" "io" @@ -11,10 +10,10 @@ import ( "strings" ) -// socks://admin:123456@localhost:8080 +// socks://admin:123456@localhost:8080/tls type Args struct { Addr string // host:port - Protocol string // protocol: hs/http/socks/socks5/ss, default is hs(http+socks5) + Protocol string // protocol: http&socks5/http/socks/socks5/ss, default is http&socks5 Transport string // transport: tcp/ws/tls, default is tcp(raw tcp) User *url.Userinfo EncMeth string // data encryption method @@ -41,7 +40,7 @@ func parseArgs(rawurl string) (args []Args) { for _, s := range ss { if !strings.Contains(s, "://") { - s = "hs://" + s + s = "tcp://" + s } u, err := url.Parse(s) if err != nil { @@ -58,23 +57,23 @@ func parseArgs(rawurl string) (args []Args) { schemes := strings.Split(u.Scheme, "+") if len(schemes) == 1 { - switch schemes[0] { - case "http", "socks", "socks5", "ss": - arg.Protocol = schemes[0] - case "ws", "tls", "tcp": - arg.Transport = schemes[0] - } + arg.Protocol = schemes[0] + arg.Transport = schemes[0] } if len(schemes) == 2 { arg.Protocol = schemes[0] arg.Transport = schemes[1] } - arg.Cert, err = tls.LoadX509KeyPair("cert.pem", "key.pem") - if err != nil { - if glog.V(LFATAL) { - glog.Errorln(err, ", tls will not be supported") - } + switch arg.Protocol { + case "http", "socks", "socks5", "ss": + default: + arg.Protocol = "" + } + switch arg.Transport { + case "ws", "tls", "tcp": + default: + arg.Transport = "tcp" } mp := strings.Split(strings.Trim(u.Path, "/"), ":") @@ -85,8 +84,15 @@ func parseArgs(rawurl string) (args []Args) { arg.EncMeth = mp[0] arg.EncPass = mp[1] } - if glog.V(LINFO) { - glog.Infoln(arg) + + if arg.Transport == "tls" || arg.EncMeth == "tls" { + arg.Cert, err = tls.LoadX509KeyPair("cert.pem", "key.pem") + if err != nil { + if glog.V(LFATAL) { + glog.Errorln(err) + } + continue + } } args = append(args, arg) } @@ -94,27 +100,6 @@ func parseArgs(rawurl string) (args []Args) { return } -func connect(addr string) (net.Conn, error) { - if !strings.Contains(addr, ":") { - addr += ":80" - } - /* - if proxyURL == nil { - return dial(addr) - } - - switch proxyURL.Scheme { - case "socks": // socks5 proxy - return connectSocks5Proxy(addr) - case "http": // http proxy - fallthrough - default: - return connectHTTPProxy(addr) - } - */ - return nil, errors.New("not implemented") -} - // based on io.Copy func Copy(dst io.Writer, src io.Reader) (written int64, err error) { buf := make([]byte, 32*1024)