From ef0b21c3d5aa9ee355182dec825742c7a23b2450 Mon Sep 17 00:00:00 2001 From: Guy Lewin Date: Tue, 3 Mar 2020 16:54:45 -0500 Subject: [PATCH] feat: extend tcpmux with multiple multiplexers --- client/proxy/proxy.go | 14 +-- cmd/frpc/sub/root.go | 1 + cmd/frpc/sub/tcphttptunnel.go | 89 ------------------ cmd/frpc/sub/tcpmux.go | 91 +++++++++++++++++++ models/config/proxy.go | 44 ++++++--- models/config/server_common.go | 20 ++-- models/consts/consts.go | 17 ++-- models/msg/msg.go | 3 + server/controller/resource.go | 7 +- server/dashboard_api.go | 7 +- server/proxy/proxy.go | 4 +- server/proxy/tcphttptunnel.go | 83 ----------------- server/proxy/tcpmux.go | 91 +++++++++++++++++++ server/service.go | 11 ++- tests/ci/auto_test_frpc.ini | 5 +- tests/ci/auto_test_frps.ini | 2 +- tests/ci/normal_test.go | 4 +- tests/consts/consts.go | 2 +- .../httpconnect.go} | 19 ++-- utils/util/http.go | 44 +++++++++ utils/vhost/http.go | 7 +- utils/vhost/resource.go | 14 --- utils/vhost/vhost.go | 10 -- 23 files changed, 326 insertions(+), 263 deletions(-) delete mode 100644 cmd/frpc/sub/tcphttptunnel.go create mode 100644 cmd/frpc/sub/tcpmux.go delete mode 100644 server/proxy/tcphttptunnel.go create mode 100644 server/proxy/tcpmux.go rename utils/{vhost/tcphttptunnel.go => tcpmux/httpconnect.go} (75%) create mode 100644 utils/util/http.go diff --git a/client/proxy/proxy.go b/client/proxy/proxy.go index f50fb208..c2366bc7 100644 --- a/client/proxy/proxy.go +++ b/client/proxy/proxy.go @@ -72,8 +72,8 @@ func NewProxy(ctx context.Context, pxyConf config.ProxyConf, clientCfg config.Cl BaseProxy: &baseProxy, cfg: cfg, } - case *config.TcpHttpTunnelProxyConf: - pxy = &TcpHttpTunnelProxy{ + case *config.TcpMuxProxyConf: + pxy = &TcpMuxProxy{ BaseProxy: &baseProxy, cfg: cfg, } @@ -147,14 +147,14 @@ func (pxy *TcpProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) { } // TCP HTTP Tunnel -type TcpHttpTunnelProxy struct { +type TcpMuxProxy struct { *BaseProxy - cfg *config.TcpHttpTunnelProxyConf + cfg *config.TcpMuxProxyConf proxyPlugin plugin.Plugin } -func (pxy *TcpHttpTunnelProxy) Run() (err error) { +func (pxy *TcpMuxProxy) Run() (err error) { if pxy.cfg.Plugin != "" { pxy.proxyPlugin, err = plugin.Create(pxy.cfg.Plugin, pxy.cfg.PluginParams) if err != nil { @@ -164,13 +164,13 @@ func (pxy *TcpHttpTunnelProxy) Run() (err error) { return } -func (pxy *TcpHttpTunnelProxy) Close() { +func (pxy *TcpMuxProxy) Close() { if pxy.proxyPlugin != nil { pxy.proxyPlugin.Close() } } -func (pxy *TcpHttpTunnelProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) { +func (pxy *TcpMuxProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) { HandleTcpWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, pxy.limiter, conn, []byte(pxy.clientCfg.Token), m) } diff --git a/cmd/frpc/sub/root.go b/cmd/frpc/sub/root.go index 8ef25e48..8f47986a 100644 --- a/cmd/frpc/sub/root.go +++ b/cmd/frpc/sub/root.go @@ -66,6 +66,7 @@ var ( hostHeaderRewrite string role string sk string + multiplexer string serverName string bindAddr string bindPort int diff --git a/cmd/frpc/sub/tcphttptunnel.go b/cmd/frpc/sub/tcphttptunnel.go deleted file mode 100644 index 0311cc07..00000000 --- a/cmd/frpc/sub/tcphttptunnel.go +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright 2020 guylewin, guy@lewin.co.il -// -// 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 sub - -import ( - "fmt" - "os" - "strings" - - "github.com/spf13/cobra" - - "github.com/fatedier/frp/models/config" - "github.com/fatedier/frp/models/consts" -) - -func init() { - tcpHttpTunnelCmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address") - tcpHttpTunnelCmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user") - tcpHttpTunnelCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp or websocket") - tcpHttpTunnelCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token") - tcpHttpTunnelCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level") - tcpHttpTunnelCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path") - tcpHttpTunnelCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days") - tcpHttpTunnelCmd.PersistentFlags().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console") - - tcpHttpTunnelCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name") - tcpHttpTunnelCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip") - tcpHttpTunnelCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port") - tcpHttpTunnelCmd.PersistentFlags().StringVarP(&customDomains, "custom_domain", "d", "", "custom domain") - tcpHttpTunnelCmd.PersistentFlags().StringVarP(&subDomain, "sd", "", "", "sub domain") - tcpHttpTunnelCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption") - tcpHttpTunnelCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression") - - rootCmd.AddCommand(tcpHttpTunnelCmd) -} - -var tcpHttpTunnelCmd = &cobra.Command{ - Use: "tcphttptunnel", - Short: "Run frpc with a single tcphttptunnel proxy", - RunE: func(cmd *cobra.Command, args []string) error { - clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "") - if err != nil { - fmt.Println(err) - os.Exit(1) - } - - cfg := &config.TcpHttpTunnelProxyConf{} - var prefix string - if user != "" { - prefix = user + "." - } - cfg.ProxyName = prefix + proxyName - cfg.ProxyType = consts.TcpHttpTunnelProxy - cfg.LocalIp = localIp - cfg.LocalPort = localPort - cfg.CustomDomains = strings.Split(customDomains, ",") - cfg.SubDomain = subDomain - cfg.UseEncryption = useEncryption - cfg.UseCompression = useCompression - - err = cfg.CheckForCli() - if err != nil { - fmt.Println(err) - os.Exit(1) - } - - proxyConfs := map[string]config.ProxyConf{ - cfg.ProxyName: cfg, - } - err = startService(clientCfg, proxyConfs, nil, "") - if err != nil { - fmt.Println(err) - os.Exit(1) - } - return nil - }, -} diff --git a/cmd/frpc/sub/tcpmux.go b/cmd/frpc/sub/tcpmux.go new file mode 100644 index 00000000..d67d4981 --- /dev/null +++ b/cmd/frpc/sub/tcpmux.go @@ -0,0 +1,91 @@ +// Copyright 2020 guylewin, guy@lewin.co.il +// +// 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 sub + +import ( + "fmt" + "os" + "strings" + + "github.com/spf13/cobra" + + "github.com/fatedier/frp/models/config" + "github.com/fatedier/frp/models/consts" +) + +func init() { + tcpMuxCmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address") + tcpMuxCmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user") + tcpMuxCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp or websocket") + tcpMuxCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token") + tcpMuxCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level") + tcpMuxCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path") + tcpMuxCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days") + tcpMuxCmd.PersistentFlags().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console") + + tcpMuxCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name") + tcpMuxCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip") + tcpMuxCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port") + tcpMuxCmd.PersistentFlags().StringVarP(&customDomains, "custom_domain", "d", "", "custom domain") + tcpMuxCmd.PersistentFlags().StringVarP(&subDomain, "sd", "", "", "sub domain") + tcpMuxCmd.PersistentFlags().StringVarP(&multiplexer, "mux", "", "", "multiplexer") + tcpMuxCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption") + tcpMuxCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression") + + rootCmd.AddCommand(tcpMuxCmd) +} + +var tcpMuxCmd = &cobra.Command{ + Use: "tcpmux", + Short: "Run frpc with a single tcpmux proxy", + RunE: func(cmd *cobra.Command, args []string) error { + clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "") + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + cfg := &config.TcpMuxProxyConf{} + var prefix string + if user != "" { + prefix = user + "." + } + cfg.ProxyName = prefix + proxyName + cfg.ProxyType = consts.TcpMuxProxy + cfg.LocalIp = localIp + cfg.LocalPort = localPort + cfg.CustomDomains = strings.Split(customDomains, ",") + cfg.SubDomain = subDomain + cfg.Multiplexer = multiplexer + cfg.UseEncryption = useEncryption + cfg.UseCompression = useCompression + + err = cfg.CheckForCli() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + proxyConfs := map[string]config.ProxyConf{ + cfg.ProxyName: cfg, + } + err = startService(clientCfg, proxyConfs, nil, "") + if err != nil { + fmt.Println(err) + os.Exit(1) + } + return nil + }, +} diff --git a/models/config/proxy.go b/models/config/proxy.go index 2d53802f..efc8c7b4 100644 --- a/models/config/proxy.go +++ b/models/config/proxy.go @@ -34,7 +34,7 @@ var ( func init() { proxyConfTypeMap = make(map[string]reflect.Type) proxyConfTypeMap[consts.TcpProxy] = reflect.TypeOf(TcpProxyConf{}) - proxyConfTypeMap[consts.TcpHttpTunnelProxy] = reflect.TypeOf(TcpHttpTunnelProxyConf{}) + proxyConfTypeMap[consts.TcpMuxProxy] = reflect.TypeOf(TcpMuxProxyConf{}) proxyConfTypeMap[consts.UdpProxy] = reflect.TypeOf(UdpProxyConf{}) proxyConfTypeMap[consts.HttpProxy] = reflect.TypeOf(HttpProxyConf{}) proxyConfTypeMap[consts.HttpsProxy] = reflect.TypeOf(HttpsProxyConf{}) @@ -575,59 +575,77 @@ func (cfg *TcpProxyConf) CheckForCli() (err error) { func (cfg *TcpProxyConf) CheckForSvr(serverCfg ServerCommonConf) error { return nil } -// TCP HTTP Tunnel -type TcpHttpTunnelProxyConf struct { +// TCP Multiplexer +type TcpMuxProxyConf struct { BaseProxyConf DomainConf + + Multiplexer string `json:"multiplexer"` } -func (cfg *TcpHttpTunnelProxyConf) Compare(cmp ProxyConf) bool { - cmpConf, ok := cmp.(*TcpHttpTunnelProxyConf) +func (cfg *TcpMuxProxyConf) Compare(cmp ProxyConf) bool { + cmpConf, ok := cmp.(*TcpMuxProxyConf) if !ok { return false } if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) || - !cfg.DomainConf.compare(&cmpConf.DomainConf) { + !cfg.DomainConf.compare(&cmpConf.DomainConf) || + cfg.Multiplexer != cmpConf.Multiplexer { return false } return true } -func (cfg *TcpHttpTunnelProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) { +func (cfg *TcpMuxProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) { cfg.BaseProxyConf.UnmarshalFromMsg(pMsg) cfg.DomainConf.UnmarshalFromMsg(pMsg) + cfg.Multiplexer = pMsg.Multiplexer } -func (cfg *TcpHttpTunnelProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) { +func (cfg *TcpMuxProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) { if err = cfg.BaseProxyConf.UnmarshalFromIni(prefix, name, section); err != nil { return } if err = cfg.DomainConf.UnmarshalFromIni(prefix, name, section); err != nil { return } + + cfg.Multiplexer = section["multiplexer"] + if cfg.Multiplexer != consts.HttpConnectTcpMultiplexer { + return fmt.Errorf("parse conf error: proxy [%s] incorrect multiplexer [%s]", name, cfg.Multiplexer) + } return } -func (cfg *TcpHttpTunnelProxyConf) MarshalToMsg(pMsg *msg.NewProxy) { +func (cfg *TcpMuxProxyConf) MarshalToMsg(pMsg *msg.NewProxy) { cfg.BaseProxyConf.MarshalToMsg(pMsg) cfg.DomainConf.MarshalToMsg(pMsg) + pMsg.Multiplexer = cfg.Multiplexer } -func (cfg *TcpHttpTunnelProxyConf) CheckForCli() (err error) { +func (cfg *TcpMuxProxyConf) CheckForCli() (err error) { if err = cfg.BaseProxyConf.checkForCli(); err != nil { return err } if err = cfg.DomainConf.checkForCli(); err != nil { return err } + if cfg.Multiplexer != consts.HttpConnectTcpMultiplexer { + return fmt.Errorf("parse conf error: incorrect multiplexer [%s]", cfg.Multiplexer) + } return } -func (cfg *TcpHttpTunnelProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) { - if serverCfg.TcpHttpTunnelPort == 0 { - return fmt.Errorf("type [tcphttptunnel] not support when tcp_http_tunnel_port is not set") +func (cfg *TcpMuxProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) { + if cfg.Multiplexer != consts.HttpConnectTcpMultiplexer { + return fmt.Errorf("proxy [%s] incorrect multiplexer [%s]", cfg.ProxyName, cfg.Multiplexer) } + + if cfg.Multiplexer == consts.HttpConnectTcpMultiplexer && serverCfg.TCPMuxHTTPConnectPort == 0 { + return fmt.Errorf("proxy [%s] type [tcpmux] with multiplexer [httpconnect] requires tcpmux_httpconnect_port configuration", cfg.ProxyName) + } + if err = cfg.DomainConf.checkForSvr(serverCfg); err != nil { err = fmt.Errorf("proxy [%s] domain conf check error: %v", cfg.ProxyName, err) return diff --git a/models/config/server_common.go b/models/config/server_common.go index d6d03de2..e8d4098c 100644 --- a/models/config/server_common.go +++ b/models/config/server_common.go @@ -59,11 +59,11 @@ type ServerCommonConf struct { // requests. By default, this value is 0. VhostHttpsPort int `json:"vhost_https_port"` - // TcpHttpTunnelPort specifies the port that the server listens for TCP Vhost - // requests. If the value is 0, the server will not multiplex TCP requests - // on one single port. If it's not - it will listen on this value for HTTP - // CONNECT requests. By default, this value is 0. - TcpHttpTunnelPort int `json:"tcp_http_tunnel_port"` + // TCPMuxHTTPConnectPort specifies the port that the server listens for TCP + // HTTP CONNECT requests. If the value is 0, the server will not multiplex TCP + // requests on one single port. If it's not - it will listen on this value for + // HTTP CONNECT requests. By default, this value is 0. + TCPMuxHTTPConnectPort int `json:"tcpmux_httpconnect_port"` // VhostHttpTimeout specifies the response header timeout for the Vhost // HTTP server, in seconds. By default, this value is 60. @@ -161,7 +161,7 @@ func GetDefaultServerConf() ServerCommonConf { ProxyBindAddr: "0.0.0.0", VhostHttpPort: 0, VhostHttpsPort: 0, - TcpHttpTunnelPort: 0, + TCPMuxHTTPConnectPort: 0, VhostHttpTimeout: 60, DashboardAddr: "0.0.0.0", DashboardPort: 0, @@ -266,15 +266,15 @@ func UnmarshalServerConfFromIni(content string) (cfg ServerCommonConf, err error cfg.VhostHttpsPort = 0 } - if tmpStr, ok = conf.Get("common", "tcp_http_tunnel_port"); ok { + if tmpStr, ok = conf.Get("common", "tcpmux_httpconnect_port"); ok { if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil { - err = fmt.Errorf("Parse conf error: invalid tcp_http_tunnel_port") + err = fmt.Errorf("Parse conf error: invalid tcpmux_httpconnect_port") return } else { - cfg.TcpHttpTunnelPort = int(v) + cfg.TCPMuxHTTPConnectPort = int(v) } } else { - cfg.TcpHttpTunnelPort = 0 + cfg.TCPMuxHTTPConnectPort = 0 } if tmpStr, ok = conf.Get("common", "vhost_http_timeout"); ok { diff --git a/models/consts/consts.go b/models/consts/consts.go index 8da906b0..4c1ca4c7 100644 --- a/models/consts/consts.go +++ b/models/consts/consts.go @@ -23,15 +23,18 @@ var ( Offline string = "offline" // proxy type - TcpProxy string = "tcp" - UdpProxy string = "udp" - TcpHttpTunnelProxy string = "tcphttptunnel" - HttpProxy string = "http" - HttpsProxy string = "https" - StcpProxy string = "stcp" - XtcpProxy string = "xtcp" + TcpProxy string = "tcp" + UdpProxy string = "udp" + TcpMuxProxy string = "tcpmux" + HttpProxy string = "http" + HttpsProxy string = "https" + StcpProxy string = "stcp" + XtcpProxy string = "xtcp" // authentication method TokenAuthMethod string = "token" OidcAuthMethod string = "oidc" + + // tcp multiplexer + HttpConnectTcpMultiplexer string = "httpconnect" ) diff --git a/models/msg/msg.go b/models/msg/msg.go index 0acce5b1..b08f1542 100644 --- a/models/msg/msg.go +++ b/models/msg/msg.go @@ -107,6 +107,9 @@ type NewProxy struct { // stcp Sk string `json:"sk"` + + // tcpmux + Multiplexer string `json:"multiplexer"` } type NewProxyResp struct { diff --git a/server/controller/resource.go b/server/controller/resource.go index 3b93ef11..5098dfbf 100644 --- a/server/controller/resource.go +++ b/server/controller/resource.go @@ -18,6 +18,7 @@ import ( "github.com/fatedier/frp/models/nathole" "github.com/fatedier/frp/server/group" "github.com/fatedier/frp/server/ports" + "github.com/fatedier/frp/utils/tcpmux" "github.com/fatedier/frp/utils/vhost" ) @@ -44,9 +45,9 @@ type ResourceController struct { // For https proxies, route requests to different clients by hostname and other information VhostHttpsMuxer *vhost.HttpsMuxer - // For tcp proxies, route requests to different proxies based on HTTP CONNECT header - VhostTcpMuxer *vhost.TcpHttpTunnelMuxer - // Controller for nat hole connections NatHoleController *nathole.NatHoleController + + // TcpMux HTTP CONNECT multiplexer + TcpMuxHttpConnectMuxer *tcpmux.HttpConnectTcpMuxer } diff --git a/server/dashboard_api.go b/server/dashboard_api.go index 3b2c99b9..9746e94a 100644 --- a/server/dashboard_api.go +++ b/server/dashboard_api.go @@ -95,9 +95,10 @@ type TcpOutConf struct { RemotePort int `json:"remote_port"` } -type TcpHttpTunnelOutConf struct { +type TcpMuxTunnelOutConf struct { BaseOutConf config.DomainConf + Multiplexer string `json:"multiplexer"` } type UdpOutConf struct { @@ -129,8 +130,8 @@ func getConfByType(proxyType string) interface{} { switch proxyType { case consts.TcpProxy: return &TcpOutConf{} - case consts.TcpHttpTunnelProxy: - return &TcpHttpTunnelOutConf{} + case consts.TcpMuxProxy: + return &TcpMuxTunnelOutConf{} case consts.UdpProxy: return &UdpOutConf{} case consts.HttpProxy: diff --git a/server/proxy/proxy.go b/server/proxy/proxy.go index 583b4bfc..ecd9621b 100644 --- a/server/proxy/proxy.go +++ b/server/proxy/proxy.go @@ -177,8 +177,8 @@ func NewProxy(ctx context.Context, runId string, rc *controller.ResourceControll BaseProxy: &basePxy, cfg: cfg, } - case *config.TcpHttpTunnelProxyConf: - pxy = &TcpHttpTunnelProxy{ + case *config.TcpMuxProxyConf: + pxy = &TcpMuxProxy{ BaseProxy: &basePxy, cfg: cfg, } diff --git a/server/proxy/tcphttptunnel.go b/server/proxy/tcphttptunnel.go deleted file mode 100644 index 743efb1e..00000000 --- a/server/proxy/tcphttptunnel.go +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright 2020 guylewin, guy@lewin.co.il -// -// 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 proxy - -import ( - "github.com/fatedier/frp/models/config" - "github.com/fatedier/frp/utils/util" - "github.com/fatedier/frp/utils/vhost" - "strings" -) - -type TcpHttpTunnelProxy struct { - *BaseProxy - cfg *config.TcpHttpTunnelProxyConf - - realPort int -} - -func (pxy *TcpHttpTunnelProxy) Run() (remoteAddr string, err error) { - xl := pxy.xl - routeConfig := &vhost.VhostRouteConfig{} - - defer func() { - if err != nil { - pxy.Close() - } - }() - addrs := make([]string, 0) - for _, domain := range pxy.cfg.CustomDomains { - if domain == "" { - continue - } - - routeConfig.Domain = domain - l, errRet := pxy.rc.VhostTcpMuxer.Listen(pxy.ctx, routeConfig) - if errRet != nil { - err = errRet - return - } - xl.Info("tcp http tunnel server listen for host [%s]", routeConfig.Domain) - pxy.listeners = append(pxy.listeners, l) - addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, pxy.serverCfg.TcpHttpTunnelPort)) - } - - if pxy.cfg.SubDomain != "" { - routeConfig.Domain = pxy.cfg.SubDomain + "." + pxy.serverCfg.SubDomainHost - l, errRet := pxy.rc.VhostTcpMuxer.Listen(pxy.ctx, routeConfig) - if errRet != nil { - err = errRet - return - } - xl.Info("tcp http tunnel server listen for host [%s]", routeConfig.Domain) - pxy.listeners = append(pxy.listeners, l) - addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, pxy.serverCfg.TcpHttpTunnelPort)) - } - - pxy.startListenHandler(pxy, HandleUserTcpConnection) - remoteAddr = strings.Join(addrs, ",") - return -} - -func (pxy *TcpHttpTunnelProxy) GetConf() config.ProxyConf { - return pxy.cfg -} - -func (pxy *TcpHttpTunnelProxy) Close() { - pxy.BaseProxy.Close() - if pxy.cfg.Group == "" { - pxy.rc.TcpPortManager.Release(pxy.realPort) - } -} diff --git a/server/proxy/tcpmux.go b/server/proxy/tcpmux.go new file mode 100644 index 00000000..64fcab6c --- /dev/null +++ b/server/proxy/tcpmux.go @@ -0,0 +1,91 @@ +// Copyright 2020 guylewin, guy@lewin.co.il +// +// 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 proxy + +import ( + "fmt" + "strings" + + "github.com/fatedier/frp/models/config" + "github.com/fatedier/frp/models/consts" + "github.com/fatedier/frp/utils/util" + "github.com/fatedier/frp/utils/vhost" +) + +type TcpMuxProxy struct { + *BaseProxy + cfg *config.TcpMuxProxyConf + + realPort int +} + +func (pxy *TcpMuxProxy) Run() (remoteAddr string, err error) { + xl := pxy.xl + routeConfig := &vhost.VhostRouteConfig{} + + switch pxy.cfg.Multiplexer { + case consts.HttpConnectTcpMultiplexer: + defer func() { + if err != nil { + pxy.Close() + } + }() + addrs := make([]string, 0) + for _, domain := range pxy.cfg.CustomDomains { + if domain == "" { + continue + } + + routeConfig.Domain = domain + l, errRet := pxy.rc.TcpMuxHttpConnectMuxer.Listen(pxy.ctx, routeConfig) + if errRet != nil { + err = errRet + return + } + xl.Info("tcpmux httpconnect multiplexer listens for host [%s]", routeConfig.Domain) + pxy.listeners = append(pxy.listeners, l) + addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, pxy.serverCfg.TCPMuxHTTPConnectPort)) + } + + if pxy.cfg.SubDomain != "" { + routeConfig.Domain = pxy.cfg.SubDomain + "." + pxy.serverCfg.SubDomainHost + l, errRet := pxy.rc.TcpMuxHttpConnectMuxer.Listen(pxy.ctx, routeConfig) + if errRet != nil { + err = errRet + return + } + xl.Info("tcpmux httpconnect multiplexer listens for host [%s]", routeConfig.Domain) + pxy.listeners = append(pxy.listeners, l) + addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, pxy.serverCfg.TCPMuxHTTPConnectPort)) + } + + pxy.startListenHandler(pxy, HandleUserTcpConnection) + remoteAddr = strings.Join(addrs, ",") + return + default: + } + return "", fmt.Errorf("unknown multiplexer [%s]", pxy.cfg.Multiplexer) +} + +func (pxy *TcpMuxProxy) GetConf() config.ProxyConf { + return pxy.cfg +} + +func (pxy *TcpMuxProxy) Close() { + pxy.BaseProxy.Close() + if pxy.cfg.Group == "" { + pxy.rc.TcpPortManager.Release(pxy.realPort) + } +} diff --git a/server/service.go b/server/service.go index 9e6bc650..b8c28af4 100644 --- a/server/service.go +++ b/server/service.go @@ -42,6 +42,7 @@ import ( "github.com/fatedier/frp/server/stats" "github.com/fatedier/frp/utils/log" frpNet "github.com/fatedier/frp/utils/net" + "github.com/fatedier/frp/utils/tcpmux" "github.com/fatedier/frp/utils/util" "github.com/fatedier/frp/utils/version" "github.com/fatedier/frp/utils/vhost" @@ -220,21 +221,21 @@ func NewService(cfg config.ServerCommonConf) (svr *Service, err error) { log.Info("https service listen on %s:%d", cfg.ProxyBindAddr, cfg.VhostHttpsPort) } - // Create tcp http tunnel muxer. - if cfg.TcpHttpTunnelPort > 0 { + // Create tcpmux httpconnect multiplexer. + if cfg.TCPMuxHTTPConnectPort > 0 { var l net.Listener - l, err = net.Listen("tcp", fmt.Sprintf("%s:%d", cfg.ProxyBindAddr, cfg.TcpHttpTunnelPort)) + l, err = net.Listen("tcp", fmt.Sprintf("%s:%d", cfg.ProxyBindAddr, cfg.TCPMuxHTTPConnectPort)) if err != nil { err = fmt.Errorf("Create server listener error, %v", err) return } - svr.rc.VhostTcpMuxer, err = vhost.NewTcpHttpTunnelMuxer(l, 30*time.Second) + svr.rc.TcpMuxHttpConnectMuxer, err = tcpmux.NewHttpConnectTcpMuxer(l, 30*time.Second) if err != nil { err = fmt.Errorf("Create vhost tcpMuxer error, %v", err) return } - log.Info("tcp http tunnel service listen on %s:%d", cfg.ProxyBindAddr, cfg.TcpHttpTunnelPort) + log.Info("tcpmux httpconnect multiplexer listen on %s:%d", cfg.ProxyBindAddr, cfg.TCPMuxHTTPConnectPort) } // frp tls listener diff --git a/tests/ci/auto_test_frpc.ini b/tests/ci/auto_test_frpc.ini index 9213384f..fbcf971a 100644 --- a/tests/ci/auto_test_frpc.ini +++ b/tests/ci/auto_test_frpc.ini @@ -126,8 +126,9 @@ custom_domains = test6.frp.com host_header_rewrite = test6.frp.com header_X-From-Where = frp -[tcphttptunnel1] -type = tcphttptunnel +[tcpmuxhttpconnect] +type = tcpmux +multiplexer = httpconnect local_ip = 127.0.0.1 local_port = 10701 custom_domains = tunnel1 diff --git a/tests/ci/auto_test_frps.ini b/tests/ci/auto_test_frps.ini index 98584e1a..25f7d13d 100644 --- a/tests/ci/auto_test_frps.ini +++ b/tests/ci/auto_test_frps.ini @@ -2,7 +2,7 @@ bind_addr = 0.0.0.0 bind_port = 10700 vhost_http_port = 10804 -tcp_http_tunnel_port = 10806 +tcpmux_httpconnect_port = 10806 log_level = trace token = 123456 allow_ports = 10000-20000,20002,30000-50000 diff --git a/tests/ci/normal_test.go b/tests/ci/normal_test.go index f4a18fe1..572fec09 100644 --- a/tests/ci/normal_test.go +++ b/tests/ci/normal_test.go @@ -212,10 +212,10 @@ func TestHttp(t *testing.T) { } } -func TestTcpHttpTunnel(t *testing.T) { +func TestTcpMux(t *testing.T) { assert := assert.New(t) - conn, err := gnet.DialTcpByProxy(fmt.Sprintf("http://%s:%d", "127.0.0.1", consts.TEST_TCP_HTTP_TUNNEL_FRP_PORT), "tunnel1") + conn, err := gnet.DialTcpByProxy(fmt.Sprintf("http://%s:%d", "127.0.0.1", consts.TEST_TCP_MUX_FRP_PORT), "tunnel1") if assert.NoError(err) { res, err := util.SendTcpMsgByConn(conn, consts.TEST_TCP_ECHO_STR) assert.NoError(err) diff --git a/tests/consts/consts.go b/tests/consts/consts.go index d62ac38c..5fc8857d 100644 --- a/tests/consts/consts.go +++ b/tests/consts/consts.go @@ -40,7 +40,7 @@ var ( TEST_HTTP_FOO_STR string = "http foo string: " + TEST_STR TEST_HTTP_BAR_STR string = "http bar string: " + TEST_STR - TEST_TCP_HTTP_TUNNEL_FRP_PORT int = 10806 + TEST_TCP_MUX_FRP_PORT int = 10806 TEST_STCP_FRP_PORT int = 10805 TEST_STCP_EC_FRP_PORT int = 10905 diff --git a/utils/vhost/tcphttptunnel.go b/utils/tcpmux/httpconnect.go similarity index 75% rename from utils/vhost/tcphttptunnel.go rename to utils/tcpmux/httpconnect.go index e69d7df1..92d3eb3e 100644 --- a/utils/vhost/tcphttptunnel.go +++ b/utils/tcpmux/httpconnect.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package vhost +package tcpmux import ( "bufio" @@ -21,15 +21,18 @@ import ( "net" "net/http" "time" + + "github.com/fatedier/frp/utils/util" + "github.com/fatedier/frp/utils/vhost" ) -type TcpHttpTunnelMuxer struct { - *VhostMuxer +type HttpConnectTcpMuxer struct { + *vhost.VhostMuxer } -func NewTcpHttpTunnelMuxer(listener net.Listener, timeout time.Duration) (*TcpHttpTunnelMuxer, error) { - mux, err := NewVhostMuxer(listener, getHostFromHttpConnect, nil, sendHttpOk, timeout) - return &TcpHttpTunnelMuxer{mux}, err +func NewHttpConnectTcpMuxer(listener net.Listener, timeout time.Duration) (*HttpConnectTcpMuxer, error) { + mux, err := vhost.NewVhostMuxer(listener, getHostFromHttpConnect, nil, sendHttpOk, timeout) + return &HttpConnectTcpMuxer{mux}, err } func readHttpConnectRequest(rd io.Reader) (host string, err error) { @@ -45,12 +48,12 @@ func readHttpConnectRequest(rd io.Reader) (host string, err error) { return } - host = getHostFromAddr(req.Host) + host = util.GetHostFromAddr(req.Host) return } func sendHttpOk(c net.Conn, _ string) (_ net.Conn, err error) { - okResp := okResponse() + okResp := util.OkResponse() err = okResp.Write(c) return c, err } diff --git a/utils/util/http.go b/utils/util/http.go new file mode 100644 index 00000000..bbd3f879 --- /dev/null +++ b/utils/util/http.go @@ -0,0 +1,44 @@ +// Copyright 2020 guylewin, guy@lewin.co.il +// +// 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 util + +import ( + "net/http" + "strings" +) + +func OkResponse() *http.Response { + header := make(http.Header) + + res := &http.Response{ + Status: "OK", + StatusCode: 200, + Proto: "HTTP/1.1", + ProtoMajor: 1, + ProtoMinor: 1, + Header: header, + } + return res +} + +func GetHostFromAddr(addr string) (host string) { + strs := strings.Split(addr, ":") + if len(strs) > 1 { + host = strs[0] + } else { + host = addr + } + return +} diff --git a/utils/vhost/http.go b/utils/vhost/http.go index c651f806..f63920b3 100644 --- a/utils/vhost/http.go +++ b/utils/vhost/http.go @@ -26,6 +26,7 @@ import ( "time" frpLog "github.com/fatedier/frp/utils/log" + "github.com/fatedier/frp/utils/util" "github.com/fatedier/golib/pool" ) @@ -57,7 +58,7 @@ func NewHttpReverseProxy(option HttpReverseProxyOptions, vhostRouter *VhostRoute Director: func(req *http.Request) { req.URL.Scheme = "http" url := req.Context().Value("url").(string) - oldHost := getHostFromAddr(req.Context().Value("host").(string)) + oldHost := util.GetHostFromAddr(req.Context().Value("host").(string)) host := rp.GetRealHost(oldHost, url) if host != "" { req.Host = host @@ -74,7 +75,7 @@ func NewHttpReverseProxy(option HttpReverseProxyOptions, vhostRouter *VhostRoute DisableKeepAlives: true, DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { url := ctx.Value("url").(string) - host := getHostFromAddr(ctx.Value("host").(string)) + host := util.GetHostFromAddr(ctx.Value("host").(string)) remote := ctx.Value("remote").(string) return rp.CreateConnection(host, url, remote) }, @@ -177,7 +178,7 @@ func (rp *HttpReverseProxy) getVhost(domain string, location string) (vr *VhostR } func (rp *HttpReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) { - domain := getHostFromAddr(req.Host) + domain := util.GetHostFromAddr(req.Host) location := req.URL.Path user, passwd, _ := req.BasicAuth() if !rp.CheckAuth(domain, location, user, passwd) { diff --git a/utils/vhost/resource.go b/utils/vhost/resource.go index 1ee77ce1..5c084306 100644 --- a/utils/vhost/resource.go +++ b/utils/vhost/resource.go @@ -98,17 +98,3 @@ func noAuthResponse() *http.Response { } return res } - -func okResponse() *http.Response { - header := make(http.Header) - - res := &http.Response{ - Status: "OK", - StatusCode: 200, - Proto: "HTTP/1.1", - ProtoMajor: 1, - ProtoMinor: 1, - Header: header, - } - return res -} diff --git a/utils/vhost/vhost.go b/utils/vhost/vhost.go index 9650204c..57f82394 100644 --- a/utils/vhost/vhost.go +++ b/utils/vhost/vhost.go @@ -225,13 +225,3 @@ func (l *Listener) Name() string { func (l *Listener) Addr() net.Addr { return (*net.TCPAddr)(nil) } - -func getHostFromAddr(addr string) (host string) { - strs := strings.Split(addr, ":") - if len(strs) > 1 { - host = strs[0] - } else { - host = addr - } - return -}