tcp multiplexing over http connect tunnel
This commit is contained in:
parent
83d80857fd
commit
504f565d3f
@ -530,6 +530,7 @@ func (cfg *HealthCheckConf) checkForCli() error {
|
|||||||
type TcpProxyConf struct {
|
type TcpProxyConf struct {
|
||||||
BaseProxyConf
|
BaseProxyConf
|
||||||
BindInfoConf
|
BindInfoConf
|
||||||
|
DomainConf
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *TcpProxyConf) Compare(cmp ProxyConf) bool {
|
func (cfg *TcpProxyConf) Compare(cmp ProxyConf) bool {
|
||||||
@ -539,7 +540,8 @@ func (cfg *TcpProxyConf) Compare(cmp ProxyConf) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) ||
|
if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) ||
|
||||||
!cfg.BindInfoConf.compare(&cmpConf.BindInfoConf) {
|
!cfg.BindInfoConf.compare(&cmpConf.BindInfoConf) ||
|
||||||
|
!cfg.DomainConf.compare(&cmpConf.DomainConf) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
@ -548,6 +550,7 @@ func (cfg *TcpProxyConf) Compare(cmp ProxyConf) bool {
|
|||||||
func (cfg *TcpProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
|
func (cfg *TcpProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
|
||||||
cfg.BaseProxyConf.UnmarshalFromMsg(pMsg)
|
cfg.BaseProxyConf.UnmarshalFromMsg(pMsg)
|
||||||
cfg.BindInfoConf.UnmarshalFromMsg(pMsg)
|
cfg.BindInfoConf.UnmarshalFromMsg(pMsg)
|
||||||
|
cfg.DomainConf.UnmarshalFromMsg(pMsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *TcpProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
|
func (cfg *TcpProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
|
||||||
@ -557,12 +560,16 @@ func (cfg *TcpProxyConf) UnmarshalFromIni(prefix string, name string, section in
|
|||||||
if err = cfg.BindInfoConf.UnmarshalFromIni(prefix, name, section); err != nil {
|
if err = cfg.BindInfoConf.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if err = cfg.DomainConf.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *TcpProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
|
func (cfg *TcpProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
|
||||||
cfg.BaseProxyConf.MarshalToMsg(pMsg)
|
cfg.BaseProxyConf.MarshalToMsg(pMsg)
|
||||||
cfg.BindInfoConf.MarshalToMsg(pMsg)
|
cfg.BindInfoConf.MarshalToMsg(pMsg)
|
||||||
|
cfg.DomainConf.MarshalToMsg(pMsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *TcpProxyConf) CheckForCli() (err error) {
|
func (cfg *TcpProxyConf) CheckForCli() (err error) {
|
||||||
@ -572,7 +579,12 @@ func (cfg *TcpProxyConf) CheckForCli() (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *TcpProxyConf) CheckForSvr(serverCfg ServerCommonConf) error { return nil }
|
func (cfg *TcpProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) {
|
||||||
|
if len(cfg.CustomDomains) == 0 && cfg.SubDomain == "" && serverCfg.VhostTcpPort != 0 {
|
||||||
|
return fmt.Errorf("type [tcp] not support when vhost_http_port is on but no custom domain or subdomain configured")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// UDP
|
// UDP
|
||||||
type UdpProxyConf struct {
|
type UdpProxyConf struct {
|
||||||
|
@ -57,6 +57,12 @@ 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"`
|
||||||
|
|
||||||
|
// VhostTcpPort 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"`
|
||||||
|
|
||||||
// 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.
|
||||||
VhostHttpTimeout int64 `json:"vhost_http_timeout"`
|
VhostHttpTimeout int64 `json:"vhost_http_timeout"`
|
||||||
@ -156,6 +162,7 @@ func GetDefaultServerConf() ServerCommonConf {
|
|||||||
ProxyBindAddr: "0.0.0.0",
|
ProxyBindAddr: "0.0.0.0",
|
||||||
VhostHttpPort: 0,
|
VhostHttpPort: 0,
|
||||||
VhostHttpsPort: 0,
|
VhostHttpsPort: 0,
|
||||||
|
VhostTcpPort: 0,
|
||||||
VhostHttpTimeout: 60,
|
VhostHttpTimeout: 60,
|
||||||
DashboardAddr: "0.0.0.0",
|
DashboardAddr: "0.0.0.0",
|
||||||
DashboardPort: 0,
|
DashboardPort: 0,
|
||||||
@ -259,6 +266,17 @@ func UnmarshalServerConfFromIni(content string) (cfg ServerCommonConf, err error
|
|||||||
cfg.VhostHttpsPort = 0
|
cfg.VhostHttpsPort = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "vhost_tcp_port"); ok {
|
||||||
|
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
||||||
|
err = fmt.Errorf("Parse conf error: invalid vhost_tcp_port")
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
cfg.VhostTcpPort = int(v)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cfg.VhostTcpPort = 0
|
||||||
|
}
|
||||||
|
|
||||||
if tmpStr, ok = conf.Get("common", "vhost_http_timeout"); ok {
|
if tmpStr, ok = conf.Get("common", "vhost_http_timeout"); ok {
|
||||||
v, errRet := strconv.ParseInt(tmpStr, 10, 64)
|
v, errRet := strconv.ParseInt(tmpStr, 10, 64)
|
||||||
if errRet != nil || v < 0 {
|
if errRet != nil || v < 0 {
|
||||||
|
@ -44,6 +44,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.TcpMuxer
|
||||||
|
|
||||||
// Controller for nat hole connections
|
// Controller for nat hole connections
|
||||||
NatHoleController *nathole.NatHoleController
|
NatHoleController *nathole.NatHoleController
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/fatedier/frp/models/config"
|
"github.com/fatedier/frp/models/config"
|
||||||
|
"github.com/fatedier/frp/utils/vhost"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TcpProxy struct {
|
type TcpProxy struct {
|
||||||
@ -44,6 +45,35 @@ func (pxy *TcpProxy) Run() (remoteAddr string, err error) {
|
|||||||
pxy.realPort = realPort
|
pxy.realPort = realPort
|
||||||
pxy.listeners = append(pxy.listeners, l)
|
pxy.listeners = append(pxy.listeners, l)
|
||||||
xl.Info("tcp proxy listen port [%d] in group [%s]", pxy.cfg.RemotePort, pxy.cfg.Group)
|
xl.Info("tcp proxy listen port [%d] in group [%s]", pxy.cfg.RemotePort, pxy.cfg.Group)
|
||||||
|
} else {
|
||||||
|
if pxy.serverCfg.VhostTcpPort > 0 {
|
||||||
|
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 {
|
} else {
|
||||||
pxy.realPort, err = pxy.rc.TcpPortManager.Acquire(pxy.name, pxy.cfg.RemotePort)
|
pxy.realPort, err = pxy.rc.TcpPortManager.Acquire(pxy.name, pxy.cfg.RemotePort)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -62,6 +92,7 @@ func (pxy *TcpProxy) Run() (remoteAddr string, err error) {
|
|||||||
pxy.listeners = append(pxy.listeners, listener)
|
pxy.listeners = append(pxy.listeners, listener)
|
||||||
xl.Info("tcp proxy listen port [%d]", pxy.cfg.RemotePort)
|
xl.Info("tcp proxy listen port [%d]", pxy.cfg.RemotePort)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pxy.cfg.RemotePort = pxy.realPort
|
pxy.cfg.RemotePort = pxy.realPort
|
||||||
remoteAddr = fmt.Sprintf(":%d", pxy.realPort)
|
remoteAddr = fmt.Sprintf(":%d", pxy.realPort)
|
||||||
|
@ -215,6 +215,23 @@ 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 vhost muxer.
|
||||||
|
if cfg.VhostTcpPort > 0 {
|
||||||
|
var l net.Listener
|
||||||
|
l, err = net.Listen("tcp", fmt.Sprintf("%s:%d", cfg.ProxyBindAddr, cfg.VhostTcpPort))
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("Create server listener error, %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
svr.rc.VhostTcpMuxer, err = vhost.NewTcpMuxer(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)
|
||||||
|
}
|
||||||
|
|
||||||
// frp tls listener
|
// frp tls listener
|
||||||
svr.tlsListener = svr.muxer.Listen(1, 1, func(data []byte) bool {
|
svr.tlsListener = svr.muxer.Listen(1, 1, func(data []byte) bool {
|
||||||
return int(data[0]) == frpNet.FRP_TLS_HEAD_BYTE
|
return int(data[0]) == frpNet.FRP_TLS_HEAD_BYTE
|
||||||
|
@ -34,16 +34,6 @@ var (
|
|||||||
ErrNoDomain = errors.New("no such domain")
|
ErrNoDomain = errors.New("no such domain")
|
||||||
)
|
)
|
||||||
|
|
||||||
func getHostFromAddr(addr string) (host string) {
|
|
||||||
strs := strings.Split(addr, ":")
|
|
||||||
if len(strs) > 1 {
|
|
||||||
host = strs[0]
|
|
||||||
} else {
|
|
||||||
host = addr
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
type HttpReverseProxyOptions struct {
|
type HttpReverseProxyOptions struct {
|
||||||
ResponseHeaderTimeoutS int64
|
ResponseHeaderTimeoutS int64
|
||||||
}
|
}
|
||||||
|
@ -98,3 +98,17 @@ 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
|
||||||
|
}
|
||||||
|
67
utils/vhost/tcp.go
Normal file
67
utils/vhost/tcp.go
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
// Copyright 2019 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 vhost
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TcpMuxer 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 readHttpConnectRequest(rd io.Reader) (host string, err error) {
|
||||||
|
bufioReader := bufio.NewReader(rd)
|
||||||
|
|
||||||
|
req, err := http.ReadRequest(bufioReader)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Method != "CONNECT" {
|
||||||
|
err = fmt.Errorf("connections to tcp vhost must be of method CONNECT")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
host = getHostFromAddr(req.Host)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendHttpOk(c net.Conn, _ string) (_ net.Conn, err error) {
|
||||||
|
okResp := okResponse()
|
||||||
|
err = okResp.Write(c)
|
||||||
|
return c, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTcpServiceName(c net.Conn) (_ net.Conn, _ map[string]string, err error) {
|
||||||
|
reqInfoMap := make(map[string]string, 0)
|
||||||
|
host, err := readHttpConnectRequest(c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, reqInfoMap, err
|
||||||
|
}
|
||||||
|
reqInfoMap["Host"] = host
|
||||||
|
reqInfoMap["Scheme"] = "tcp"
|
||||||
|
return c, reqInfoMap, nil
|
||||||
|
}
|
@ -225,3 +225,13 @@ 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
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user