From 03d55201b2d3f3e468db5442a78ae211b8fba78e Mon Sep 17 00:00:00 2001 From: fatedier Date: Tue, 30 May 2017 14:37:51 +0800 Subject: [PATCH] plugin: add http_proxy --- .travis.yml | 1 - client/proxy.go | 3 - conf/frpc_full.ini | 11 +- models/plugin/http-proxy/config/config.json | 3 - models/plugin/http-proxy/proxy/config.go | 32 --- models/plugin/http-proxy/proxy/proxy.go | 203 ------------------ models/plugin/http_proxy.go | 226 ++++++++++++++++++++ utils/net/conn.go | 4 +- 8 files changed, 237 insertions(+), 246 deletions(-) delete mode 100644 models/plugin/http-proxy/config/config.json delete mode 100644 models/plugin/http-proxy/proxy/config.go delete mode 100644 models/plugin/http-proxy/proxy/proxy.go create mode 100644 models/plugin/http_proxy.go diff --git a/.travis.yml b/.travis.yml index b49aa7c..8494825 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,6 @@ sudo: false language: go go: - - 1.7.x - 1.8.x install: diff --git a/client/proxy.go b/client/proxy.go index 4e1d020..3dbedf8 100644 --- a/client/proxy.go +++ b/client/proxy.go @@ -103,7 +103,6 @@ func (pxy *TcpProxy) Close() { } func (pxy *TcpProxy) InWorkConn(conn frpNet.Conn) { - defer conn.Close() HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn) } @@ -132,7 +131,6 @@ func (pxy *HttpProxy) Close() { } func (pxy *HttpProxy) InWorkConn(conn frpNet.Conn) { - defer conn.Close() HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn) } @@ -161,7 +159,6 @@ func (pxy *HttpsProxy) Close() { } func (pxy *HttpsProxy) InWorkConn(conn frpNet.Conn) { - defer conn.Close() HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn) } diff --git a/conf/frpc_full.ini b/conf/frpc_full.ini index f6b6b8a..93fe53d 100644 --- a/conf/frpc_full.ini +++ b/conf/frpc_full.ini @@ -90,11 +90,18 @@ use_compression = false subdomain = web01 custom_domains = web02.yourdomain.com -[unix_domain_socket] +[plugin_unix_domain_socket] type = tcp -remote_port = 6001 +remote_port = 6003 # if plugin is defined, local_ip and local_port is useless # plugin will handle connections got from frps plugin = unix_domain_socket # params set with prefix "plugin_" that plugin needed plugin_unix_path = /var/run/docker.sock + +[plugin_http_proxy] +type = tcp +remote_port = 6004 +plugin = http_proxy +plugin_http_user = abc +plugin_http_passwd = abc diff --git a/models/plugin/http-proxy/config/config.json b/models/plugin/http-proxy/config/config.json deleted file mode 100644 index 3574ab2..0000000 --- a/models/plugin/http-proxy/config/config.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "port":":8080" -} diff --git a/models/plugin/http-proxy/proxy/config.go b/models/plugin/http-proxy/proxy/config.go deleted file mode 100644 index 4e8d0c1..0000000 --- a/models/plugin/http-proxy/proxy/config.go +++ /dev/null @@ -1,32 +0,0 @@ -package proxy - -import ( - "bufio" - "encoding/json" - "os" -) - -// Config 保存代理服务器的配置 -type Config struct { - Port string `json:"port"` - Auth bool `json:"auth"` - - User map[string]string `json:"user"` -} - -// 从指定json文件读取config配置 -func (c *Config) GetConfig(filename string) error { - - configFile, err := os.Open(filename) - if err != nil { - return err - } - defer configFile.Close() - - br := bufio.NewReader(configFile) - err = json.NewDecoder(br).Decode(c) - if err != nil { - return err - } - return nil -} diff --git a/models/plugin/http-proxy/proxy/proxy.go b/models/plugin/http-proxy/proxy/proxy.go deleted file mode 100644 index 062b99d..0000000 --- a/models/plugin/http-proxy/proxy/proxy.go +++ /dev/null @@ -1,203 +0,0 @@ -package proxy - -import ( - "fmt" - "io" - "net" - "net/http" - "os" - "time" - - "github.com/fatedier/frp/models/plugin" - "github.com/fatedier/frp/utils/log" - wrap "github.com/fatedier/frp/utils/net" -) - -var ( - HTTP_200 = []byte("HTTP/1.1 200 Connection Established\r\n\r\n") - ProxyName = "default-proxy" - cnfg Config -) - -func init() { - // 加载配置文件 - err := cnfg.GetConfig("../config/config.json") - if err != nil { - log.Error("can not load config file:%v\n", err) - os.Exit(-1) - } - plugin.Register(ProxyName, NewProxyPlugin) -} - -type ProxyServer struct { - Tr *http.Transport -} - -type Proxy struct { - Server *http.Server - Ln net.Listener -} - -func NewProxyPlugin(params map[string]string) (p plugin.Plugin, err error) { - - listen, err := net.Listen("tcp", cnfg.Port) - if err != nil { - log.Error("can not listen %v port", cnfg.Port) - return - } - - proxy := &Proxy{ - Server: NewProxyServer(), - Ln: listen, - } - go proxy.Server.Serve(proxy.Ln) - - return proxy, nil -} - -func (proxy *Proxy) Name() string { - return ProxyName -} - -// right?? -func (proxy *Proxy) Handle(conn io.ReadWriteCloser) { - wrapConn := wrap.WrapReadWriteCloserToConn(conn) - - remote, err := net.Dial("tcp", cnfg.Port) - if err != nil { - log.Error("dial tcp error:%v", err) - return - } - - // or tcp.Join(remote,wrapConn) - _, err = io.Copy(remote, wrapConn) - if err != nil && err != io.EOF { - log.Error("io copy data error:%v", err) - return - } - return -} - -func (proxy *Proxy) Close() error { - return proxy.Server.Close() -} - -func NewProxyServer() *http.Server { - - return &http.Server{ - Addr: cnfg.Port, - Handler: &ProxyServer{Tr: http.DefaultTransport.(*http.Transport)}, - ReadTimeout: 10 * time.Second, - WriteTimeout: 10 * time.Second, - MaxHeaderBytes: 1 << 20, - } -} - -func (proxy *ProxyServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) { - defer func() { - if err := recover(); err != nil { - rw.WriteHeader(http.StatusInternalServerError) - log.Error("Panic: %v", err) - fmt.Fprintf(rw, fmt.Sprintln(err)) - } - }() - - if req.Method == "CONNECT" { // 是connect连接 - proxy.HttpsHandler(rw, req) - } else { - proxy.HttpHandler(rw, req) - } -} - -// 处理普通的http请求 -func (proxy *ProxyServer) HttpHandler(rw http.ResponseWriter, req *http.Request) { - log.Info("is sending request %v %v ", req.Method, req.URL.Host) - removeProxyHeaders(req) // 去除不必要的头 - - resp, err := proxy.Tr.RoundTrip(req) - if err != nil { - log.Error("transport RoundTrip error: %v", err) - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - defer resp.Body.Close() - - clearHeaders(rw.Header()) // 得到一个空的Header - copyHeaders(rw.Header(), resp.Header) - rw.WriteHeader(resp.StatusCode) - - nr, err := io.Copy(rw, resp.Body) - if err != nil && err != io.EOF { - log.Error("got an error when copy remote response to client.%v", err) - return - } - log.Info("copied %v bytes from remote host %v.", nr, req.URL.Host) -} - -// 处理https连接,主要用于CONNECT方法 -func (proxy *ProxyServer) HttpsHandler(rw http.ResponseWriter, req *http.Request) { - log.Info("[CONNECT] tried to connect to remote host %v", req.URL.Host) - - hj, _ := rw.(http.Hijacker) - client, _, err := hj.Hijack() //获取客户端与代理服务器的tcp连接 - if err != nil { - log.Error("failed to get Tcp connection of", req.RequestURI) - http.Error(rw, "Failed", http.StatusBadRequest) - return - } - - remote, err := net.Dial("tcp", req.URL.Host) //建立服务端和代理服务器的tcp连接 - if err != nil { - log.Error("failed to connect %v", req.RequestURI) - http.Error(rw, "Failed", http.StatusBadRequest) - client.Close() - return - } - - client.Write(HTTP_200) - - go copyRemoteToClient(remote, client) - go copyRemoteToClient(client, remote) -} - -// data copy between two socket -func copyRemoteToClient(remote, client net.Conn) { - defer func() { - remote.Close() - client.Close() - }() - - nr, err := io.Copy(remote, client) - if err != nil && err != io.EOF { - log.Error("got an error when handles CONNECT %v", err) - return - } - log.Info("[CONNECT] transported %v bytes betwwen %v and %v", nr, remote.RemoteAddr(), client.RemoteAddr()) -} - -func copyHeaders(dst, src http.Header) { - for key, values := range src { - for _, value := range values { - dst.Add(key, value) - } - } -} - -func clearHeaders(headers http.Header) { - for key, _ := range headers { - headers.Del(key) - } -} - -func removeProxyHeaders(req *http.Request) { - req.RequestURI = "" - req.Header.Del("Proxy-Connection") - req.Header.Del("Connection") - req.Header.Del("Keep-Alive") - req.Header.Del("Proxy-Authenticate") - req.Header.Del("Proxy-Authorization") - req.Header.Del("TE") - req.Header.Del("Trailers") - req.Header.Del("Transfer-Encoding") - req.Header.Del("Upgrade") -} diff --git a/models/plugin/http_proxy.go b/models/plugin/http_proxy.go new file mode 100644 index 0000000..1e03f21 --- /dev/null +++ b/models/plugin/http_proxy.go @@ -0,0 +1,226 @@ +// Copyright 2017 frp team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package plugin + +import ( + "encoding/base64" + "fmt" + "io" + "net" + "net/http" + "strings" + "sync" + + "github.com/fatedier/frp/models/proto/tcp" + "github.com/fatedier/frp/utils/errors" + frpNet "github.com/fatedier/frp/utils/net" +) + +const PluginHttpProxy = "http_proxy" + +func init() { + Register(PluginHttpProxy, NewHttpProxyPlugin) +} + +type Listener struct { + conns chan net.Conn + closed bool + mu sync.Mutex +} + +func NewProxyListener() *Listener { + return &Listener{ + conns: make(chan net.Conn, 64), + } +} + +func (l *Listener) Accept() (net.Conn, error) { + conn, ok := <-l.conns + if !ok { + return nil, fmt.Errorf("listener closed") + } + return conn, nil +} + +func (l *Listener) PutConn(conn net.Conn) error { + err := errors.PanicToError(func() { + l.conns <- conn + }) + return err +} + +func (l *Listener) Close() error { + l.mu.Lock() + defer l.mu.Unlock() + if !l.closed { + close(l.conns) + } + return nil +} + +func (l *Listener) Addr() net.Addr { + return (*net.TCPAddr)(nil) +} + +type HttpProxy struct { + l *Listener + s *http.Server + AuthUser string + AuthPasswd string +} + +func NewHttpProxyPlugin(params map[string]string) (Plugin, error) { + user := params["plugin_http_user"] + passwd := params["plugin_http_passwd"] + listener := NewProxyListener() + + hp := &HttpProxy{ + l: listener, + AuthUser: user, + AuthPasswd: passwd, + } + + hp.s = &http.Server{ + Handler: hp, + } + + go hp.s.Serve(listener) + return hp, nil +} + +func (hp *HttpProxy) Name() string { + return PluginHttpProxy +} + +func (hp *HttpProxy) Handle(conn io.ReadWriteCloser) { + var wrapConn net.Conn + if realConn, ok := conn.(net.Conn); ok { + wrapConn = realConn + } else { + wrapConn = frpNet.WrapReadWriteCloserToConn(conn) + } + + hp.l.PutConn(wrapConn) + return +} + +func (hp *HttpProxy) Close() error { + hp.s.Close() + hp.l.Close() + return nil +} + +func (hp *HttpProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) { + if ok := hp.Auth(rw, req); !ok { + rw.Header().Set("Proxy-Authenticate", "Basic") + rw.WriteHeader(http.StatusProxyAuthRequired) + return + } + + if req.Method == "CONNECT" { + hp.ConnectHandler(rw, req) + } else { + hp.HttpHandler(rw, req) + } +} + +func (hp *HttpProxy) HttpHandler(rw http.ResponseWriter, req *http.Request) { + removeProxyHeaders(req) + + resp, err := http.DefaultTransport.RoundTrip(req) + if err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + defer resp.Body.Close() + + copyHeaders(rw.Header(), resp.Header) + rw.WriteHeader(resp.StatusCode) + + _, err = io.Copy(rw, resp.Body) + if err != nil && err != io.EOF { + return + } +} + +func (hp *HttpProxy) ConnectHandler(rw http.ResponseWriter, req *http.Request) { + hj, ok := rw.(http.Hijacker) + if !ok { + rw.WriteHeader(http.StatusInternalServerError) + return + } + + client, _, err := hj.Hijack() + if err != nil { + rw.WriteHeader(http.StatusInternalServerError) + return + } + + remote, err := net.Dial("tcp", req.URL.Host) + if err != nil { + http.Error(rw, "Failed", http.StatusBadRequest) + client.Close() + return + } + client.Write([]byte("HTTP/1.0 200 OK\r\n\r\n")) + + go tcp.Join(remote, client) +} + +func (hp *HttpProxy) Auth(rw http.ResponseWriter, req *http.Request) bool { + if hp.AuthUser == "" && hp.AuthPasswd == "" { + return true + } + + s := strings.SplitN(req.Header.Get("Proxy-Authorization"), " ", 2) + if len(s) != 2 { + return false + } + + b, err := base64.StdEncoding.DecodeString(s[1]) + if err != nil { + return false + } + + pair := strings.SplitN(string(b), ":", 2) + if len(pair) != 2 { + return false + } + + if pair[0] != hp.AuthUser || pair[1] != hp.AuthPasswd { + return false + } + return true +} + +func copyHeaders(dst, src http.Header) { + for key, values := range src { + for _, value := range values { + dst.Add(key, value) + } + } +} + +func removeProxyHeaders(req *http.Request) { + req.RequestURI = "" + req.Header.Del("Proxy-Connection") + req.Header.Del("Connection") + req.Header.Del("Proxy-Authenticate") + req.Header.Del("Proxy-Authorization") + req.Header.Del("TE") + req.Header.Del("Trailers") + req.Header.Del("Transfer-Encoding") + req.Header.Del("Upgrade") +} diff --git a/utils/net/conn.go b/utils/net/conn.go index c085fe7..5f172bc 100644 --- a/utils/net/conn.go +++ b/utils/net/conn.go @@ -46,11 +46,11 @@ type WrapReadWriteCloserConn struct { } func (conn *WrapReadWriteCloserConn) LocalAddr() net.Addr { - return nil + return (*net.TCPAddr)(nil) } func (conn *WrapReadWriteCloserConn) RemoteAddr() net.Addr { - return nil + return (*net.TCPAddr)(nil) } func (conn *WrapReadWriteCloserConn) SetDeadline(t time.Time) error {