feat: extend tcpmux with multiple multiplexers

This commit is contained in:
Guy Lewin 2020-03-03 16:54:45 -05:00
parent e5edfb10f7
commit ef0b21c3d5
23 changed files with 326 additions and 263 deletions

View File

@ -72,8 +72,8 @@ func NewProxy(ctx context.Context, pxyConf config.ProxyConf, clientCfg config.Cl
BaseProxy: &baseProxy, BaseProxy: &baseProxy,
cfg: cfg, cfg: cfg,
} }
case *config.TcpHttpTunnelProxyConf: case *config.TcpMuxProxyConf:
pxy = &TcpHttpTunnelProxy{ pxy = &TcpMuxProxy{
BaseProxy: &baseProxy, BaseProxy: &baseProxy,
cfg: cfg, cfg: cfg,
} }
@ -147,14 +147,14 @@ func (pxy *TcpProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
} }
// TCP HTTP Tunnel // TCP HTTP Tunnel
type TcpHttpTunnelProxy struct { type TcpMuxProxy struct {
*BaseProxy *BaseProxy
cfg *config.TcpHttpTunnelProxyConf cfg *config.TcpMuxProxyConf
proxyPlugin plugin.Plugin proxyPlugin plugin.Plugin
} }
func (pxy *TcpHttpTunnelProxy) Run() (err error) { func (pxy *TcpMuxProxy) Run() (err error) {
if pxy.cfg.Plugin != "" { if pxy.cfg.Plugin != "" {
pxy.proxyPlugin, err = plugin.Create(pxy.cfg.Plugin, pxy.cfg.PluginParams) pxy.proxyPlugin, err = plugin.Create(pxy.cfg.Plugin, pxy.cfg.PluginParams)
if err != nil { if err != nil {
@ -164,13 +164,13 @@ func (pxy *TcpHttpTunnelProxy) Run() (err error) {
return return
} }
func (pxy *TcpHttpTunnelProxy) Close() { func (pxy *TcpMuxProxy) Close() {
if pxy.proxyPlugin != nil { if pxy.proxyPlugin != nil {
pxy.proxyPlugin.Close() 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, HandleTcpWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, pxy.limiter,
conn, []byte(pxy.clientCfg.Token), m) conn, []byte(pxy.clientCfg.Token), m)
} }

View File

@ -66,6 +66,7 @@ var (
hostHeaderRewrite string hostHeaderRewrite string
role string role string
sk string sk string
multiplexer string
serverName string serverName string
bindAddr string bindAddr string
bindPort int bindPort int

View File

@ -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
},
}

91
cmd/frpc/sub/tcpmux.go Normal file
View File

@ -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
},
}

View File

@ -34,7 +34,7 @@ var (
func init() { func init() {
proxyConfTypeMap = make(map[string]reflect.Type) proxyConfTypeMap = make(map[string]reflect.Type)
proxyConfTypeMap[consts.TcpProxy] = reflect.TypeOf(TcpProxyConf{}) 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.UdpProxy] = reflect.TypeOf(UdpProxyConf{})
proxyConfTypeMap[consts.HttpProxy] = reflect.TypeOf(HttpProxyConf{}) proxyConfTypeMap[consts.HttpProxy] = reflect.TypeOf(HttpProxyConf{})
proxyConfTypeMap[consts.HttpsProxy] = reflect.TypeOf(HttpsProxyConf{}) 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 } func (cfg *TcpProxyConf) CheckForSvr(serverCfg ServerCommonConf) error { return nil }
// TCP HTTP Tunnel // TCP Multiplexer
type TcpHttpTunnelProxyConf struct { type TcpMuxProxyConf struct {
BaseProxyConf BaseProxyConf
DomainConf DomainConf
Multiplexer string `json:"multiplexer"`
} }
func (cfg *TcpHttpTunnelProxyConf) Compare(cmp ProxyConf) bool { func (cfg *TcpMuxProxyConf) Compare(cmp ProxyConf) bool {
cmpConf, ok := cmp.(*TcpHttpTunnelProxyConf) cmpConf, ok := cmp.(*TcpMuxProxyConf)
if !ok { if !ok {
return false return false
} }
if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) || if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) ||
!cfg.DomainConf.compare(&cmpConf.DomainConf) { !cfg.DomainConf.compare(&cmpConf.DomainConf) ||
cfg.Multiplexer != cmpConf.Multiplexer {
return false return false
} }
return true return true
} }
func (cfg *TcpHttpTunnelProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) { func (cfg *TcpMuxProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.UnmarshalFromMsg(pMsg) cfg.BaseProxyConf.UnmarshalFromMsg(pMsg)
cfg.DomainConf.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 { if err = cfg.BaseProxyConf.UnmarshalFromIni(prefix, name, section); err != nil {
return return
} }
if err = cfg.DomainConf.UnmarshalFromIni(prefix, name, section); err != nil { if err = cfg.DomainConf.UnmarshalFromIni(prefix, name, section); err != nil {
return 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 return
} }
func (cfg *TcpHttpTunnelProxyConf) MarshalToMsg(pMsg *msg.NewProxy) { func (cfg *TcpMuxProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.MarshalToMsg(pMsg) cfg.BaseProxyConf.MarshalToMsg(pMsg)
cfg.DomainConf.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 { if err = cfg.BaseProxyConf.checkForCli(); err != nil {
return err return err
} }
if err = cfg.DomainConf.checkForCli(); err != nil { if err = cfg.DomainConf.checkForCli(); err != nil {
return err return err
} }
if cfg.Multiplexer != consts.HttpConnectTcpMultiplexer {
return fmt.Errorf("parse conf error: incorrect multiplexer [%s]", cfg.Multiplexer)
}
return return
} }
func (cfg *TcpHttpTunnelProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) { func (cfg *TcpMuxProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) {
if serverCfg.TcpHttpTunnelPort == 0 { if cfg.Multiplexer != consts.HttpConnectTcpMultiplexer {
return fmt.Errorf("type [tcphttptunnel] not support when tcp_http_tunnel_port is not set") 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 { if err = cfg.DomainConf.checkForSvr(serverCfg); err != nil {
err = fmt.Errorf("proxy [%s] domain conf check error: %v", cfg.ProxyName, err) err = fmt.Errorf("proxy [%s] domain conf check error: %v", cfg.ProxyName, err)
return return

View File

@ -59,11 +59,11 @@ type ServerCommonConf struct {
// requests. By default, this value is 0. // requests. By default, this value is 0.
VhostHttpsPort int `json:"vhost_https_port"` VhostHttpsPort int `json:"vhost_https_port"`
// TcpHttpTunnelPort specifies the port that the server listens for TCP Vhost // TCPMuxHTTPConnectPort specifies the port that the server listens for TCP
// requests. If the value is 0, the server will not multiplex TCP requests // HTTP CONNECT requests. If the value is 0, the server will not multiplex TCP
// on one single port. If it's not - it will listen on this value for HTTP // requests on one single port. If it's not - it will listen on this value for
// CONNECT requests. By default, this value is 0. // HTTP CONNECT requests. By default, this value is 0.
TcpHttpTunnelPort int `json:"tcp_http_tunnel_port"` TCPMuxHTTPConnectPort int `json:"tcpmux_httpconnect_port"`
// VhostHttpTimeout specifies the response header timeout for the Vhost // VhostHttpTimeout specifies the response header timeout for the Vhost
// HTTP server, in seconds. By default, this value is 60. // HTTP server, in seconds. By default, this value is 60.
@ -161,7 +161,7 @@ func GetDefaultServerConf() ServerCommonConf {
ProxyBindAddr: "0.0.0.0", ProxyBindAddr: "0.0.0.0",
VhostHttpPort: 0, VhostHttpPort: 0,
VhostHttpsPort: 0, VhostHttpsPort: 0,
TcpHttpTunnelPort: 0, TCPMuxHTTPConnectPort: 0,
VhostHttpTimeout: 60, VhostHttpTimeout: 60,
DashboardAddr: "0.0.0.0", DashboardAddr: "0.0.0.0",
DashboardPort: 0, DashboardPort: 0,
@ -266,15 +266,15 @@ func UnmarshalServerConfFromIni(content string) (cfg ServerCommonConf, err error
cfg.VhostHttpsPort = 0 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 { 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 return
} else { } else {
cfg.TcpHttpTunnelPort = int(v) cfg.TCPMuxHTTPConnectPort = int(v)
} }
} else { } else {
cfg.TcpHttpTunnelPort = 0 cfg.TCPMuxHTTPConnectPort = 0
} }
if tmpStr, ok = conf.Get("common", "vhost_http_timeout"); ok { if tmpStr, ok = conf.Get("common", "vhost_http_timeout"); ok {

View File

@ -23,15 +23,18 @@ var (
Offline string = "offline" Offline string = "offline"
// proxy type // proxy type
TcpProxy string = "tcp" TcpProxy string = "tcp"
UdpProxy string = "udp" UdpProxy string = "udp"
TcpHttpTunnelProxy string = "tcphttptunnel" TcpMuxProxy string = "tcpmux"
HttpProxy string = "http" HttpProxy string = "http"
HttpsProxy string = "https" HttpsProxy string = "https"
StcpProxy string = "stcp" StcpProxy string = "stcp"
XtcpProxy string = "xtcp" XtcpProxy string = "xtcp"
// authentication method // authentication method
TokenAuthMethod string = "token" TokenAuthMethod string = "token"
OidcAuthMethod string = "oidc" OidcAuthMethod string = "oidc"
// tcp multiplexer
HttpConnectTcpMultiplexer string = "httpconnect"
) )

View File

@ -107,6 +107,9 @@ type NewProxy struct {
// stcp // stcp
Sk string `json:"sk"` Sk string `json:"sk"`
// tcpmux
Multiplexer string `json:"multiplexer"`
} }
type NewProxyResp struct { type NewProxyResp struct {

View File

@ -18,6 +18,7 @@ import (
"github.com/fatedier/frp/models/nathole" "github.com/fatedier/frp/models/nathole"
"github.com/fatedier/frp/server/group" "github.com/fatedier/frp/server/group"
"github.com/fatedier/frp/server/ports" "github.com/fatedier/frp/server/ports"
"github.com/fatedier/frp/utils/tcpmux"
"github.com/fatedier/frp/utils/vhost" "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 // For https proxies, route requests to different clients by hostname and other information
VhostHttpsMuxer *vhost.HttpsMuxer VhostHttpsMuxer *vhost.HttpsMuxer
// For tcp proxies, route requests to different proxies based on HTTP CONNECT header
VhostTcpMuxer *vhost.TcpHttpTunnelMuxer
// Controller for nat hole connections // Controller for nat hole connections
NatHoleController *nathole.NatHoleController NatHoleController *nathole.NatHoleController
// TcpMux HTTP CONNECT multiplexer
TcpMuxHttpConnectMuxer *tcpmux.HttpConnectTcpMuxer
} }

View File

@ -95,9 +95,10 @@ type TcpOutConf struct {
RemotePort int `json:"remote_port"` RemotePort int `json:"remote_port"`
} }
type TcpHttpTunnelOutConf struct { type TcpMuxTunnelOutConf struct {
BaseOutConf BaseOutConf
config.DomainConf config.DomainConf
Multiplexer string `json:"multiplexer"`
} }
type UdpOutConf struct { type UdpOutConf struct {
@ -129,8 +130,8 @@ func getConfByType(proxyType string) interface{} {
switch proxyType { switch proxyType {
case consts.TcpProxy: case consts.TcpProxy:
return &TcpOutConf{} return &TcpOutConf{}
case consts.TcpHttpTunnelProxy: case consts.TcpMuxProxy:
return &TcpHttpTunnelOutConf{} return &TcpMuxTunnelOutConf{}
case consts.UdpProxy: case consts.UdpProxy:
return &UdpOutConf{} return &UdpOutConf{}
case consts.HttpProxy: case consts.HttpProxy:

View File

@ -177,8 +177,8 @@ func NewProxy(ctx context.Context, runId string, rc *controller.ResourceControll
BaseProxy: &basePxy, BaseProxy: &basePxy,
cfg: cfg, cfg: cfg,
} }
case *config.TcpHttpTunnelProxyConf: case *config.TcpMuxProxyConf:
pxy = &TcpHttpTunnelProxy{ pxy = &TcpMuxProxy{
BaseProxy: &basePxy, BaseProxy: &basePxy,
cfg: cfg, cfg: cfg,
} }

View File

@ -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)
}
}

91
server/proxy/tcpmux.go Normal file
View File

@ -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)
}
}

View File

@ -42,6 +42,7 @@ import (
"github.com/fatedier/frp/server/stats" "github.com/fatedier/frp/server/stats"
"github.com/fatedier/frp/utils/log" "github.com/fatedier/frp/utils/log"
frpNet "github.com/fatedier/frp/utils/net" frpNet "github.com/fatedier/frp/utils/net"
"github.com/fatedier/frp/utils/tcpmux"
"github.com/fatedier/frp/utils/util" "github.com/fatedier/frp/utils/util"
"github.com/fatedier/frp/utils/version" "github.com/fatedier/frp/utils/version"
"github.com/fatedier/frp/utils/vhost" "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) log.Info("https service listen on %s:%d", cfg.ProxyBindAddr, cfg.VhostHttpsPort)
} }
// Create tcp http tunnel muxer. // Create tcpmux httpconnect multiplexer.
if cfg.TcpHttpTunnelPort > 0 { if cfg.TCPMuxHTTPConnectPort > 0 {
var l net.Listener 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 { if err != nil {
err = fmt.Errorf("Create server listener error, %v", err) err = fmt.Errorf("Create server listener error, %v", err)
return return
} }
svr.rc.VhostTcpMuxer, err = vhost.NewTcpHttpTunnelMuxer(l, 30*time.Second) svr.rc.TcpMuxHttpConnectMuxer, err = tcpmux.NewHttpConnectTcpMuxer(l, 30*time.Second)
if err != nil { if err != nil {
err = fmt.Errorf("Create vhost tcpMuxer error, %v", err) err = fmt.Errorf("Create vhost tcpMuxer error, %v", err)
return 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 // frp tls listener

View File

@ -126,8 +126,9 @@ custom_domains = test6.frp.com
host_header_rewrite = test6.frp.com host_header_rewrite = test6.frp.com
header_X-From-Where = frp header_X-From-Where = frp
[tcphttptunnel1] [tcpmuxhttpconnect]
type = tcphttptunnel type = tcpmux
multiplexer = httpconnect
local_ip = 127.0.0.1 local_ip = 127.0.0.1
local_port = 10701 local_port = 10701
custom_domains = tunnel1 custom_domains = tunnel1

View File

@ -2,7 +2,7 @@
bind_addr = 0.0.0.0 bind_addr = 0.0.0.0
bind_port = 10700 bind_port = 10700
vhost_http_port = 10804 vhost_http_port = 10804
tcp_http_tunnel_port = 10806 tcpmux_httpconnect_port = 10806
log_level = trace log_level = trace
token = 123456 token = 123456
allow_ports = 10000-20000,20002,30000-50000 allow_ports = 10000-20000,20002,30000-50000

View File

@ -212,10 +212,10 @@ func TestHttp(t *testing.T) {
} }
} }
func TestTcpHttpTunnel(t *testing.T) { func TestTcpMux(t *testing.T) {
assert := assert.New(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) { if assert.NoError(err) {
res, err := util.SendTcpMsgByConn(conn, consts.TEST_TCP_ECHO_STR) res, err := util.SendTcpMsgByConn(conn, consts.TEST_TCP_ECHO_STR)
assert.NoError(err) assert.NoError(err)

View File

@ -40,7 +40,7 @@ var (
TEST_HTTP_FOO_STR string = "http foo string: " + TEST_STR TEST_HTTP_FOO_STR string = "http foo string: " + TEST_STR
TEST_HTTP_BAR_STR string = "http bar 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_FRP_PORT int = 10805
TEST_STCP_EC_FRP_PORT int = 10905 TEST_STCP_EC_FRP_PORT int = 10905

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package vhost package tcpmux
import ( import (
"bufio" "bufio"
@ -21,15 +21,18 @@ import (
"net" "net"
"net/http" "net/http"
"time" "time"
"github.com/fatedier/frp/utils/util"
"github.com/fatedier/frp/utils/vhost"
) )
type TcpHttpTunnelMuxer struct { type HttpConnectTcpMuxer struct {
*VhostMuxer *vhost.VhostMuxer
} }
func NewTcpHttpTunnelMuxer(listener net.Listener, timeout time.Duration) (*TcpHttpTunnelMuxer, error) { func NewHttpConnectTcpMuxer(listener net.Listener, timeout time.Duration) (*HttpConnectTcpMuxer, error) {
mux, err := NewVhostMuxer(listener, getHostFromHttpConnect, nil, sendHttpOk, timeout) mux, err := vhost.NewVhostMuxer(listener, getHostFromHttpConnect, nil, sendHttpOk, timeout)
return &TcpHttpTunnelMuxer{mux}, err return &HttpConnectTcpMuxer{mux}, err
} }
func readHttpConnectRequest(rd io.Reader) (host string, err error) { func readHttpConnectRequest(rd io.Reader) (host string, err error) {
@ -45,12 +48,12 @@ func readHttpConnectRequest(rd io.Reader) (host string, err error) {
return return
} }
host = getHostFromAddr(req.Host) host = util.GetHostFromAddr(req.Host)
return return
} }
func sendHttpOk(c net.Conn, _ string) (_ net.Conn, err error) { func sendHttpOk(c net.Conn, _ string) (_ net.Conn, err error) {
okResp := okResponse() okResp := util.OkResponse()
err = okResp.Write(c) err = okResp.Write(c)
return c, err return c, err
} }

44
utils/util/http.go Normal file
View File

@ -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
}

View File

@ -26,6 +26,7 @@ import (
"time" "time"
frpLog "github.com/fatedier/frp/utils/log" frpLog "github.com/fatedier/frp/utils/log"
"github.com/fatedier/frp/utils/util"
"github.com/fatedier/golib/pool" "github.com/fatedier/golib/pool"
) )
@ -57,7 +58,7 @@ func NewHttpReverseProxy(option HttpReverseProxyOptions, vhostRouter *VhostRoute
Director: func(req *http.Request) { Director: func(req *http.Request) {
req.URL.Scheme = "http" req.URL.Scheme = "http"
url := req.Context().Value("url").(string) 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) host := rp.GetRealHost(oldHost, url)
if host != "" { if host != "" {
req.Host = host req.Host = host
@ -74,7 +75,7 @@ func NewHttpReverseProxy(option HttpReverseProxyOptions, vhostRouter *VhostRoute
DisableKeepAlives: true, DisableKeepAlives: true,
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
url := ctx.Value("url").(string) url := ctx.Value("url").(string)
host := getHostFromAddr(ctx.Value("host").(string)) host := util.GetHostFromAddr(ctx.Value("host").(string))
remote := ctx.Value("remote").(string) remote := ctx.Value("remote").(string)
return rp.CreateConnection(host, url, remote) 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) { func (rp *HttpReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
domain := getHostFromAddr(req.Host) domain := util.GetHostFromAddr(req.Host)
location := req.URL.Path location := req.URL.Path
user, passwd, _ := req.BasicAuth() user, passwd, _ := req.BasicAuth()
if !rp.CheckAuth(domain, location, user, passwd) { if !rp.CheckAuth(domain, location, user, passwd) {

View File

@ -98,17 +98,3 @@ func noAuthResponse() *http.Response {
} }
return res 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
}

View File

@ -225,13 +225,3 @@ func (l *Listener) Name() string {
func (l *Listener) Addr() net.Addr { func (l *Listener) Addr() net.Addr {
return (*net.TCPAddr)(nil) 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
}