diff --git a/client/proxy/proxy.go b/client/proxy/proxy.go index c9ef8dd0..f50fb208 100644 --- a/client/proxy/proxy.go +++ b/client/proxy/proxy.go @@ -72,6 +72,11 @@ func NewProxy(ctx context.Context, pxyConf config.ProxyConf, clientCfg config.Cl BaseProxy: &baseProxy, cfg: cfg, } + case *config.TcpHttpTunnelProxyConf: + pxy = &TcpHttpTunnelProxy{ + BaseProxy: &baseProxy, + cfg: cfg, + } case *config.UdpProxyConf: pxy = &UdpProxy{ BaseProxy: &baseProxy, @@ -141,6 +146,35 @@ func (pxy *TcpProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) { conn, []byte(pxy.clientCfg.Token), m) } +// TCP HTTP Tunnel +type TcpHttpTunnelProxy struct { + *BaseProxy + + cfg *config.TcpHttpTunnelProxyConf + proxyPlugin plugin.Plugin +} + +func (pxy *TcpHttpTunnelProxy) Run() (err error) { + if pxy.cfg.Plugin != "" { + pxy.proxyPlugin, err = plugin.Create(pxy.cfg.Plugin, pxy.cfg.PluginParams) + if err != nil { + return + } + } + return +} + +func (pxy *TcpHttpTunnelProxy) Close() { + if pxy.proxyPlugin != nil { + pxy.proxyPlugin.Close() + } +} + +func (pxy *TcpHttpTunnelProxy) 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) +} + // HTTP type HttpProxy struct { *BaseProxy diff --git a/cmd/frpc/sub/tcphttptunnel.go b/cmd/frpc/sub/tcphttptunnel.go new file mode 100644 index 00000000..1c14f448 --- /dev/null +++ b/cmd/frpc/sub/tcphttptunnel.go @@ -0,0 +1,89 @@ +// Copyright 2018 fatedier, fatedier@gmail.com +// +// 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/models/config/proxy.go b/models/config/proxy.go index 9bdd8341..20a1e028 100644 --- a/models/config/proxy.go +++ b/models/config/proxy.go @@ -34,6 +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.UdpProxy] = reflect.TypeOf(UdpProxyConf{}) proxyConfTypeMap[consts.HttpProxy] = reflect.TypeOf(HttpProxyConf{}) proxyConfTypeMap[consts.HttpsProxy] = reflect.TypeOf(HttpsProxyConf{}) @@ -530,7 +531,6 @@ func (cfg *HealthCheckConf) checkForCli() error { type TcpProxyConf struct { BaseProxyConf BindInfoConf - DomainConf } func (cfg *TcpProxyConf) Compare(cmp ProxyConf) bool { @@ -540,8 +540,7 @@ func (cfg *TcpProxyConf) Compare(cmp ProxyConf) bool { } if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) || - !cfg.BindInfoConf.compare(&cmpConf.BindInfoConf) || - !cfg.DomainConf.compare(&cmpConf.DomainConf) { + !cfg.BindInfoConf.compare(&cmpConf.BindInfoConf) { return false } return true @@ -550,7 +549,6 @@ func (cfg *TcpProxyConf) Compare(cmp ProxyConf) bool { func (cfg *TcpProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) { cfg.BaseProxyConf.UnmarshalFromMsg(pMsg) cfg.BindInfoConf.UnmarshalFromMsg(pMsg) - cfg.DomainConf.UnmarshalFromMsg(pMsg) } func (cfg *TcpProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) { @@ -560,16 +558,12 @@ func (cfg *TcpProxyConf) UnmarshalFromIni(prefix string, name string, section in if err = cfg.BindInfoConf.UnmarshalFromIni(prefix, name, section); err != nil { return } - if err = cfg.DomainConf.UnmarshalFromIni(prefix, name, section); err != nil { - return - } return } func (cfg *TcpProxyConf) MarshalToMsg(pMsg *msg.NewProxy) { cfg.BaseProxyConf.MarshalToMsg(pMsg) cfg.BindInfoConf.MarshalToMsg(pMsg) - cfg.DomainConf.MarshalToMsg(pMsg) } func (cfg *TcpProxyConf) CheckForCli() (err error) { @@ -579,9 +573,64 @@ func (cfg *TcpProxyConf) CheckForCli() (err error) { return } -func (cfg *TcpProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) { - if (len(cfg.CustomDomains) > 0 || cfg.SubDomain != "") && serverCfg.VhostTcpPort == 0 { - return fmt.Errorf("custom domain or subdomain can't be configured on type [tcp] without vhost_http_port") +func (cfg *TcpProxyConf) CheckForSvr(serverCfg ServerCommonConf) error { return nil } + +// TCP HTTP Tunnel +type TcpHttpTunnelProxyConf struct { + BaseProxyConf + DomainConf +} + +func (cfg *TcpHttpTunnelProxyConf) Compare(cmp ProxyConf) bool { + cmpConf, ok := cmp.(*TcpHttpTunnelProxyConf) + if !ok { + return false + } + + if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) || + !cfg.DomainConf.compare(&cmpConf.DomainConf) { + return false + } + return true +} + +func (cfg *TcpHttpTunnelProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) { + cfg.BaseProxyConf.UnmarshalFromMsg(pMsg) + cfg.DomainConf.UnmarshalFromMsg(pMsg) +} + +func (cfg *TcpHttpTunnelProxyConf) 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 + } + return +} + +func (cfg *TcpHttpTunnelProxyConf) MarshalToMsg(pMsg *msg.NewProxy) { + cfg.BaseProxyConf.MarshalToMsg(pMsg) + cfg.DomainConf.MarshalToMsg(pMsg) +} + +func (cfg *TcpHttpTunnelProxyConf) CheckForCli() (err error) { + if err = cfg.BaseProxyConf.checkForCli(); err != nil { + return err + } + if err = cfg.DomainConf.checkForCli(); err != nil { + return err + } + 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") + } + if err = cfg.DomainConf.checkForSvr(serverCfg); err != nil { + err = fmt.Errorf("proxy [%s] domain conf check error: %v", cfg.ProxyName, err) + return } return } diff --git a/models/config/server_common.go b/models/config/server_common.go index c225c544..54da7022 100644 --- a/models/config/server_common.go +++ b/models/config/server_common.go @@ -57,11 +57,11 @@ type ServerCommonConf struct { // requests. By default, this value is 0. VhostHttpsPort int `json:"vhost_https_port"` - // VhostTcpPort specifies the port that the server listens for TCP Vhost + // 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. - VhostTcpPort int `json:"vhost_tcp_port"` + TcpHttpTunnelPort int `json:"tcp_http_tunnel_port"` // VhostHttpTimeout specifies the response header timeout for the Vhost // HTTP server, in seconds. By default, this value is 60. @@ -162,7 +162,7 @@ func GetDefaultServerConf() ServerCommonConf { ProxyBindAddr: "0.0.0.0", VhostHttpPort: 0, VhostHttpsPort: 0, - VhostTcpPort: 0, + TcpHttpTunnelPort: 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", "vhost_tcp_port"); ok { + if tmpStr, ok = conf.Get("common", "tcp_http_tunnel_port"); ok { if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil { - err = fmt.Errorf("Parse conf error: invalid vhost_tcp_port") + err = fmt.Errorf("Parse conf error: invalid tcp_http_tunnel_port") return } else { - cfg.VhostTcpPort = int(v) + cfg.TcpHttpTunnelPort = int(v) } } else { - cfg.VhostTcpPort = 0 + cfg.TcpHttpTunnelPort = 0 } if tmpStr, ok = conf.Get("common", "vhost_http_timeout"); ok { diff --git a/models/consts/consts.go b/models/consts/consts.go index 9bf5880b..a4d2ad77 100644 --- a/models/consts/consts.go +++ b/models/consts/consts.go @@ -23,10 +23,11 @@ var ( Offline string = "offline" // proxy type - TcpProxy string = "tcp" - UdpProxy string = "udp" - HttpProxy string = "http" - HttpsProxy string = "https" - StcpProxy string = "stcp" - XtcpProxy string = "xtcp" + TcpProxy string = "tcp" + UdpProxy string = "udp" + TcpHttpTunnelProxy string = "tcphttptunnel" + HttpProxy string = "http" + HttpsProxy string = "https" + StcpProxy string = "stcp" + XtcpProxy string = "xtcp" ) diff --git a/server/controller/resource.go b/server/controller/resource.go index 9d9ed5c0..3b93ef11 100644 --- a/server/controller/resource.go +++ b/server/controller/resource.go @@ -45,7 +45,7 @@ type ResourceController struct { VhostHttpsMuxer *vhost.HttpsMuxer // For tcp proxies, route requests to different proxies based on HTTP CONNECT header - VhostTcpMuxer *vhost.TcpMuxer + VhostTcpMuxer *vhost.TcpHttpTunnelMuxer // Controller for nat hole connections NatHoleController *nathole.NatHoleController diff --git a/server/dashboard_api.go b/server/dashboard_api.go index 30ee9a68..3b2c99b9 100644 --- a/server/dashboard_api.go +++ b/server/dashboard_api.go @@ -95,6 +95,11 @@ type TcpOutConf struct { RemotePort int `json:"remote_port"` } +type TcpHttpTunnelOutConf struct { + BaseOutConf + config.DomainConf +} + type UdpOutConf struct { BaseOutConf RemotePort int `json:"remote_port"` @@ -124,6 +129,8 @@ func getConfByType(proxyType string) interface{} { switch proxyType { case consts.TcpProxy: return &TcpOutConf{} + case consts.TcpHttpTunnelProxy: + return &TcpHttpTunnelOutConf{} case consts.UdpProxy: return &UdpOutConf{} case consts.HttpProxy: diff --git a/server/proxy/proxy.go b/server/proxy/proxy.go index 84a04e61..c81c3dc1 100644 --- a/server/proxy/proxy.go +++ b/server/proxy/proxy.go @@ -176,6 +176,11 @@ func NewProxy(ctx context.Context, runId string, rc *controller.ResourceControll BaseProxy: &basePxy, cfg: cfg, } + case *config.TcpHttpTunnelProxyConf: + pxy = &TcpHttpTunnelProxy{ + BaseProxy: &basePxy, + cfg: cfg, + } case *config.HttpProxyConf: pxy = &HttpProxy{ BaseProxy: &basePxy, diff --git a/server/proxy/tcp.go b/server/proxy/tcp.go index b18ea4b6..0ecfe260 100644 --- a/server/proxy/tcp.go +++ b/server/proxy/tcp.go @@ -19,7 +19,6 @@ import ( "net" "github.com/fatedier/frp/models/config" - "github.com/fatedier/frp/utils/vhost" ) type TcpProxy struct { @@ -46,52 +45,22 @@ func (pxy *TcpProxy) Run() (remoteAddr string, err error) { pxy.listeners = append(pxy.listeners, l) xl.Info("tcp proxy listen port [%d] in group [%s]", pxy.cfg.RemotePort, pxy.cfg.Group) } else { - if pxy.serverCfg.VhostTcpPort > 0 && (len(pxy.cfg.CustomDomains) > 0 || pxy.cfg.SubDomain != "") { - pxy.realPort = pxy.serverCfg.VhostTcpPort - routeConfig := &vhost.VhostRouteConfig{} - 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("http tunnel server (tcp proxy) listen for host [%s]", routeConfig.Domain) - pxy.listeners = append(pxy.listeners, l) - } - - 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("http tunnel server (tcp proxy) listen for host [%s]", routeConfig.Domain) - pxy.listeners = append(pxy.listeners, l) - } - } else { - pxy.realPort, err = pxy.rc.TcpPortManager.Acquire(pxy.name, pxy.cfg.RemotePort) - if err != nil { - return - } - defer func() { - if err != nil { - pxy.rc.TcpPortManager.Release(pxy.realPort) - } - }() - listener, errRet := net.Listen("tcp", fmt.Sprintf("%s:%d", pxy.serverCfg.ProxyBindAddr, pxy.realPort)) - if errRet != nil { - err = errRet - return - } - pxy.listeners = append(pxy.listeners, listener) - xl.Info("tcp proxy listen port [%d]", pxy.cfg.RemotePort) + pxy.realPort, err = pxy.rc.TcpPortManager.Acquire(pxy.name, pxy.cfg.RemotePort) + if err != nil { + return } + defer func() { + if err != nil { + pxy.rc.TcpPortManager.Release(pxy.realPort) + } + }() + listener, errRet := net.Listen("tcp", fmt.Sprintf("%s:%d", pxy.serverCfg.ProxyBindAddr, pxy.realPort)) + if errRet != nil { + err = errRet + return + } + pxy.listeners = append(pxy.listeners, listener) + xl.Info("tcp proxy listen port [%d]", pxy.cfg.RemotePort) } pxy.cfg.RemotePort = pxy.realPort diff --git a/server/service.go b/server/service.go index 7f969201..31208108 100644 --- a/server/service.go +++ b/server/service.go @@ -215,21 +215,21 @@ func NewService(cfg config.ServerCommonConf) (svr *Service, err error) { log.Info("https service listen on %s:%d", cfg.ProxyBindAddr, cfg.VhostHttpsPort) } - // Create tcp vhost muxer. - if cfg.VhostTcpPort > 0 { + // Create tcp http tunnel muxer. + if cfg.TcpHttpTunnelPort > 0 { var l net.Listener - l, err = net.Listen("tcp", fmt.Sprintf("%s:%d", cfg.ProxyBindAddr, cfg.VhostTcpPort)) + l, err = net.Listen("tcp", fmt.Sprintf("%s:%d", cfg.ProxyBindAddr, cfg.TcpHttpTunnelPort)) if err != nil { err = fmt.Errorf("Create server listener error, %v", err) return } - svr.rc.VhostTcpMuxer, err = vhost.NewTcpMuxer(l, 30*time.Second) + svr.rc.VhostTcpMuxer, err = vhost.NewTcpHttpTunnelMuxer(l, 30*time.Second) if err != nil { err = fmt.Errorf("Create vhost tcpMuxer error, %v", err) return } - log.Info("tcp service listen on %s:%d", cfg.ProxyBindAddr, cfg.VhostTcpPort) + log.Info("tcp http tunnel service listen on %s:%d", cfg.ProxyBindAddr, cfg.TcpHttpTunnelPort) } // frp tls listener diff --git a/utils/vhost/tcp.go b/utils/vhost/tcphttptunnel.go similarity index 79% rename from utils/vhost/tcp.go rename to utils/vhost/tcphttptunnel.go index 1bde2cf0..aa0d9969 100644 --- a/utils/vhost/tcp.go +++ b/utils/vhost/tcphttptunnel.go @@ -23,13 +23,13 @@ import ( "time" ) -type TcpMuxer struct { +type TcpHttpTunnelMuxer struct { *VhostMuxer } -func NewTcpMuxer(listener net.Listener, timeout time.Duration) (*TcpMuxer, error) { - mux, err := NewVhostMuxer(listener, getTcpServiceName, nil, sendHttpOk, timeout) - return &TcpMuxer{mux}, err +func NewTcpHttpTunnelMuxer(listener net.Listener, timeout time.Duration) (*TcpHttpTunnelMuxer, error) { + mux, err := NewVhostMuxer(listener, getHostFromHttpConnect, nil, sendHttpOk, timeout) + return &TcpHttpTunnelMuxer{mux}, err } func readHttpConnectRequest(rd io.Reader) (host string, err error) { @@ -55,7 +55,7 @@ func sendHttpOk(c net.Conn, _ string) (_ net.Conn, err error) { return c, err } -func getTcpServiceName(c net.Conn) (_ net.Conn, _ map[string]string, err error) { +func getHostFromHttpConnect(c net.Conn) (_ net.Conn, _ map[string]string, err error) { reqInfoMap := make(map[string]string, 0) host, err := readHttpConnectRequest(c) if err != nil {