From e262ac6abd448c043963963b8925a07d661b080f Mon Sep 17 00:00:00 2001 From: fatedier Date: Mon, 15 Aug 2016 00:55:22 +0800 Subject: [PATCH] frpc: add support for connecting server through http proxies, see #67 --- conf/frpc.ini | 2 ++ src/cmd/frpc/control.go | 6 ++++- src/models/client/client.go | 8 ++++-- src/models/client/config.go | 10 ++++++++ src/utils/conn/conn.go | 50 +++++++++++++++++++++++++++++++++++-- test/func_test.go | 2 +- 6 files changed, 72 insertions(+), 6 deletions(-) diff --git a/conf/frpc.ini b/conf/frpc.ini index 9e2030ac..98517708 100644 --- a/conf/frpc.ini +++ b/conf/frpc.ini @@ -4,6 +4,8 @@ # in square brackets, as in "[::1]:80", "[ipv6-host]:http" or "[ipv6-host%zone]:80" server_addr = 0.0.0.0 server_port = 7000 +# if you want to connect frps by http proxy, you can set http_proxy here or in global environment variables +# http_proxy = http://user:pwd@192.168.1.128:8080 # console or real logFile path like ./frpc.log log_file = ./frpc.log # debug, info, warn, error diff --git a/src/cmd/frpc/control.go b/src/cmd/frpc/control.go index 3fdb611c..bde6906f 100644 --- a/src/cmd/frpc/control.go +++ b/src/cmd/frpc/control.go @@ -130,7 +130,11 @@ func msgSender(cli *client.ProxyClient, c *conn.Conn, msgSendChan chan interface } func loginToServer(cli *client.ProxyClient) (c *conn.Conn, err error) { - c, err = conn.ConnectServer(client.ServerAddr, client.ServerPort) + if client.HttpProxy == "" { + c, err = conn.ConnectServer(fmt.Sprintf("%s:%d", client.ServerAddr, client.ServerPort)) + } else { + c, err = conn.ConnectServerByHttpProxy(client.HttpProxy, fmt.Sprintf("%s:%d", client.ServerAddr, client.ServerPort)) + } if err != nil { log.Error("ProxyName [%s], connect to server [%s:%d] error, %v", cli.Name, client.ServerAddr, client.ServerPort, err) return diff --git a/src/models/client/client.go b/src/models/client/client.go index 5c82fbb4..2548b237 100644 --- a/src/models/client/client.go +++ b/src/models/client/client.go @@ -37,7 +37,7 @@ type ProxyClient struct { } func (p *ProxyClient) GetLocalConn() (c *conn.Conn, err error) { - c, err = conn.ConnectServer(p.LocalIp, p.LocalPort) + c, err = conn.ConnectServer(fmt.Sprintf("%s:%d", p.LocalIp, p.LocalPort)) if err != nil { log.Error("ProxyName [%s], connect to local port error, %v", p.Name, err) } @@ -51,7 +51,11 @@ func (p *ProxyClient) GetRemoteConn(addr string, port int64) (c *conn.Conn, err } }() - c, err = conn.ConnectServer(addr, port) + if HttpProxy == "" { + c, err = conn.ConnectServer(fmt.Sprintf("%s:%d", addr, port)) + } else { + c, err = conn.ConnectServerByHttpProxy(HttpProxy, fmt.Sprintf("%s:%d", addr, port)) + } if err != nil { log.Error("ProxyName [%s], connect to server [%s:%d] error, %v", p.Name, addr, port, err) return diff --git a/src/models/client/config.go b/src/models/client/config.go index 3068ef17..ddc5d1d4 100644 --- a/src/models/client/config.go +++ b/src/models/client/config.go @@ -16,6 +16,7 @@ package client import ( "fmt" + "os" "strconv" "strings" @@ -26,6 +27,7 @@ import ( var ( ServerAddr string = "0.0.0.0" ServerPort int64 = 7000 + HttpProxy string = "" LogFile string = "console" LogWay string = "console" LogLevel string = "info" @@ -57,6 +59,14 @@ func LoadConf(confFile string) (err error) { ServerPort, _ = strconv.ParseInt(tmpStr, 10, 64) } + tmpStr, ok = conf.Get("common", "http_proxy") + if ok { + HttpProxy = tmpStr + } else { + // get http_proxy from env + HttpProxy = os.Getenv("http_proxy") + } + tmpStr, ok = conf.Get("common", "log_file") if ok { LogFile = tmpStr diff --git a/src/utils/conn/conn.go b/src/utils/conn/conn.go index 9922758c..fb61707f 100644 --- a/src/utils/conn/conn.go +++ b/src/utils/conn/conn.go @@ -16,9 +16,12 @@ package conn import ( "bufio" + "encoding/base64" "fmt" "io" "net" + "net/http" + "net/url" "strings" "sync" "time" @@ -104,9 +107,9 @@ func NewConn(conn net.Conn) (c *Conn) { return c } -func ConnectServer(host string, port int64) (c *Conn, err error) { +func ConnectServer(addr string) (c *Conn, err error) { c = &Conn{} - servertAddr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:%d", host, port)) + servertAddr, err := net.ResolveTCPAddr("tcp", addr) if err != nil { return } @@ -120,6 +123,49 @@ func ConnectServer(host string, port int64) (c *Conn, err error) { return c, nil } +func ConnectServerByHttpProxy(httpProxy string, serverAddr string) (c *Conn, err error) { + var proxyUrl *url.URL + if proxyUrl, err = url.Parse(httpProxy); err != nil { + return + } + + var proxyAuth string + if proxyUrl.User != nil { + proxyAuth = "Basic " + base64.StdEncoding.EncodeToString([]byte(proxyUrl.User.String())) + } + + if proxyUrl.Scheme != "http" { + err = fmt.Errorf("Proxy URL scheme must be http, not [%s]", proxyUrl.Scheme) + return + } + + if c, err = ConnectServer(proxyUrl.Host); err != nil { + return + } + + req, err := http.NewRequest("CONNECT", "https://"+serverAddr, nil) + if err != nil { + return + } + if proxyAuth != "" { + req.Header.Set("Proxy-Authorization", proxyAuth) + } + req.Header.Set("User-Agent", "Mozilla/5.0") + req.Write(c.TcpConn) + + resp, err := http.ReadResponse(bufio.NewReader(c), req) + if err != nil { + return + } + resp.Body.Close() + if resp.StatusCode != 200 { + err = fmt.Errorf("ConnectServer using proxy error, StatusCode [%d]", resp.StatusCode) + return + } + + return +} + // if the tcpConn is different with c.TcpConn // you should call c.Close() first func (c *Conn) SetTcpConn(tcpConn net.Conn) { diff --git a/test/func_test.go b/test/func_test.go index 901ae7d2..3683c516 100644 --- a/test/func_test.go +++ b/test/func_test.go @@ -19,7 +19,7 @@ var ( ) func TestEchoServer(t *testing.T) { - c, err := conn.ConnectServer("0.0.0.0", ECHO_PORT) + c, err := conn.ConnectServer(fmt.Sprintf("0.0.0.0:%d", ECHO_PORT)) if err != nil { t.Fatalf("connect to echo server error: %v", err) }