Skip to content

Commit

Permalink
type http/tcpmux proxy support route_by_http_user, tcpmux support pas…
Browse files Browse the repository at this point in the history
…sthourgh mode (fatedier#2932)
  • Loading branch information
fatedier authored May 26, 2022
1 parent bd89eab commit 4af85da
Show file tree
Hide file tree
Showing 22 changed files with 605 additions and 282 deletions.
6 changes: 0 additions & 6 deletions Release.md
Original file line number Diff line number Diff line change
@@ -1,7 +1 @@
### New

* Added new parameter `config_dir` in frpc to run multiple client instances in one process.

### Fix

* Equal sign in environment variables causes parsing error.
3 changes: 3 additions & 0 deletions conf/frpc_full.ini
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,8 @@ subdomain = web01
custom_domains = web01.yourdomain.com
# locations is only available for http type
locations = /,/pic
# route requests to this service if http basic auto user is abc
# route_by_http_user = abc
host_header_rewrite = example.com
# params with prefix "header_" will be used to update http request headers
header_X-From-Where = frp
Expand Down Expand Up @@ -348,3 +350,4 @@ multiplexer = httpconnect
local_ip = 127.0.0.1
local_port = 10701
custom_domains = tunnel1
# route_by_http_user = user1
3 changes: 3 additions & 0 deletions conf/frps_full.ini
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ vhost_https_port = 443
# HTTP CONNECT requests. By default, this value is 0.
# tcpmux_httpconnect_port = 1337

# If tcpmux_passthrough is true, frps won't do any update on traffic.
# tcpmux_passthrough = false

# set dashboard_addr and dashboard_port to view dashboard of frps
# dashboard_addr's default value is same with bind_addr
# dashboard is available only if dashboard_port is set
Expand Down
13 changes: 10 additions & 3 deletions pkg/config/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ type HTTPProxyConf struct {
HTTPPwd string `ini:"http_pwd" json:"http_pwd"`
HostHeaderRewrite string `ini:"host_header_rewrite" json:"host_header_rewrite"`
Headers map[string]string `ini:"-" json:"headers"`
RouteByHTTPUser string `ini:"route_by_http_user" json:"route_by_http_user"`
}

// HTTPS
Expand All @@ -178,8 +179,9 @@ type TCPProxyConf struct {

// TCPMux
type TCPMuxProxyConf struct {
BaseProxyConf `ini:",extends"`
DomainConf `ini:",extends"`
BaseProxyConf `ini:",extends"`
DomainConf `ini:",extends"`
RouteByHTTPUser string `ini:"route_by_http_user" json:"route_by_http_user"`

Multiplexer string `ini:"multiplexer"`
}
Expand Down Expand Up @@ -576,7 +578,7 @@ func (cfg *TCPMuxProxyConf) Compare(cmp ProxyConf) bool {
return false
}

if cfg.Multiplexer != cmpConf.Multiplexer {
if cfg.Multiplexer != cmpConf.Multiplexer || cfg.RouteByHTTPUser != cmpConf.RouteByHTTPUser {
return false
}

Expand All @@ -601,6 +603,7 @@ func (cfg *TCPMuxProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
cfg.CustomDomains = pMsg.CustomDomains
cfg.SubDomain = pMsg.SubDomain
cfg.Multiplexer = pMsg.Multiplexer
cfg.RouteByHTTPUser = pMsg.RouteByHTTPUser
}

func (cfg *TCPMuxProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
Expand All @@ -610,6 +613,7 @@ func (cfg *TCPMuxProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
pMsg.CustomDomains = cfg.CustomDomains
pMsg.SubDomain = cfg.SubDomain
pMsg.Multiplexer = cfg.Multiplexer
pMsg.RouteByHTTPUser = cfg.RouteByHTTPUser
}

func (cfg *TCPMuxProxyConf) CheckForCli() (err error) {
Expand Down Expand Up @@ -724,6 +728,7 @@ func (cfg *HTTPProxyConf) Compare(cmp ProxyConf) bool {
cfg.HTTPUser != cmpConf.HTTPUser ||
cfg.HTTPPwd != cmpConf.HTTPPwd ||
cfg.HostHeaderRewrite != cmpConf.HostHeaderRewrite ||
cfg.RouteByHTTPUser != cmpConf.RouteByHTTPUser ||
!reflect.DeepEqual(cfg.Headers, cmpConf.Headers) {
return false
}
Expand Down Expand Up @@ -754,6 +759,7 @@ func (cfg *HTTPProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
cfg.HTTPUser = pMsg.HTTPUser
cfg.HTTPPwd = pMsg.HTTPPwd
cfg.Headers = pMsg.Headers
cfg.RouteByHTTPUser = pMsg.RouteByHTTPUser
}

func (cfg *HTTPProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
Expand All @@ -767,6 +773,7 @@ func (cfg *HTTPProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
pMsg.HTTPUser = cfg.HTTPUser
pMsg.HTTPPwd = cfg.HTTPPwd
pMsg.Headers = cfg.Headers
pMsg.RouteByHTTPUser = cfg.RouteByHTTPUser
}

func (cfg *HTTPProxyConf) CheckForCli() (err error) {
Expand Down
3 changes: 3 additions & 0 deletions pkg/config/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ type ServerCommonConf struct {
// 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 `ini:"tcpmux_httpconnect_port" json:"tcpmux_httpconnect_port" validate:"gte=0,lte=65535"`
// If TCPMuxPassthrough is true, frps won't do any update on traffic.
TCPMuxPassthrough bool `ini:"tcpmux_passthrough" json:"tcpmux_passthrough"`
// VhostHTTPTimeout specifies the response header timeout for the Vhost
// HTTP server, in seconds. By default, this value is 60.
VhostHTTPTimeout int64 `ini:"vhost_http_timeout" json:"vhost_http_timeout"`
Expand Down Expand Up @@ -188,6 +190,7 @@ func GetDefaultServerConf() ServerCommonConf {
VhostHTTPPort: 0,
VhostHTTPSPort: 0,
TCPMuxHTTPConnectPort: 0,
TCPMuxPassthrough: false,
VhostHTTPTimeout: 60,
DashboardAddr: "0.0.0.0",
DashboardPort: 0,
Expand Down
135 changes: 68 additions & 67 deletions pkg/msg/msg.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,133 +62,134 @@ var (

// When frpc start, client send this message to login to server.
type Login struct {
Version string `json:"version"`
Hostname string `json:"hostname"`
Os string `json:"os"`
Arch string `json:"arch"`
User string `json:"user"`
PrivilegeKey string `json:"privilege_key"`
Timestamp int64 `json:"timestamp"`
RunID string `json:"run_id"`
Metas map[string]string `json:"metas"`
Version string `json:"version,omitempty"`
Hostname string `json:"hostname,omitempty"`
Os string `json:"os,omitempty"`
Arch string `json:"arch,omitempty"`
User string `json:"user,omitempty"`
PrivilegeKey string `json:"privilege_key,omitempty"`
Timestamp int64 `json:"timestamp,omitempty"`
RunID string `json:"run_id,omitempty"`
Metas map[string]string `json:"metas,omitempty"`

// Some global configures.
PoolCount int `json:"pool_count"`
PoolCount int `json:"pool_count,omitempty"`
}

type LoginResp struct {
Version string `json:"version"`
RunID string `json:"run_id"`
ServerUDPPort int `json:"server_udp_port"`
Error string `json:"error"`
Version string `json:"version,omitempty"`
RunID string `json:"run_id,omitempty"`
ServerUDPPort int `json:"server_udp_port,omitempty"`
Error string `json:"error,omitempty"`
}

// When frpc login success, send this message to frps for running a new proxy.
type NewProxy struct {
ProxyName string `json:"proxy_name"`
ProxyType string `json:"proxy_type"`
UseEncryption bool `json:"use_encryption"`
UseCompression bool `json:"use_compression"`
Group string `json:"group"`
GroupKey string `json:"group_key"`
Metas map[string]string `json:"metas"`
ProxyName string `json:"proxy_name,omitempty"`
ProxyType string `json:"proxy_type,omitempty"`
UseEncryption bool `json:"use_encryption,omitempty"`
UseCompression bool `json:"use_compression,omitempty"`
Group string `json:"group,omitempty"`
GroupKey string `json:"group_key,omitempty"`
Metas map[string]string `json:"metas,omitempty"`

// tcp and udp only
RemotePort int `json:"remote_port"`
RemotePort int `json:"remote_port,omitempty"`

// http and https only
CustomDomains []string `json:"custom_domains"`
SubDomain string `json:"subdomain"`
Locations []string `json:"locations"`
HTTPUser string `json:"http_user"`
HTTPPwd string `json:"http_pwd"`
HostHeaderRewrite string `json:"host_header_rewrite"`
Headers map[string]string `json:"headers"`
CustomDomains []string `json:"custom_domains,omitempty"`
SubDomain string `json:"subdomain,omitempty"`
Locations []string `json:"locations,omitempty"`
HTTPUser string `json:"http_user,omitempty"`
HTTPPwd string `json:"http_pwd,omitempty"`
HostHeaderRewrite string `json:"host_header_rewrite,omitempty"`
Headers map[string]string `json:"headers,omitempty"`
RouteByHTTPUser string `json:"route_by_http_user,omitempty"`

// stcp
Sk string `json:"sk"`
Sk string `json:"sk,omitempty"`

// tcpmux
Multiplexer string `json:"multiplexer"`
Multiplexer string `json:"multiplexer,omitempty"`
}

type NewProxyResp struct {
ProxyName string `json:"proxy_name"`
RemoteAddr string `json:"remote_addr"`
Error string `json:"error"`
ProxyName string `json:"proxy_name,omitempty"`
RemoteAddr string `json:"remote_addr,omitempty"`
Error string `json:"error,omitempty"`
}

type CloseProxy struct {
ProxyName string `json:"proxy_name"`
ProxyName string `json:"proxy_name,omitempty"`
}

type NewWorkConn struct {
RunID string `json:"run_id"`
PrivilegeKey string `json:"privilege_key"`
Timestamp int64 `json:"timestamp"`
RunID string `json:"run_id,omitempty"`
PrivilegeKey string `json:"privilege_key,omitempty"`
Timestamp int64 `json:"timestamp,omitempty"`
}

type ReqWorkConn struct {
}

type StartWorkConn struct {
ProxyName string `json:"proxy_name"`
SrcAddr string `json:"src_addr"`
DstAddr string `json:"dst_addr"`
SrcPort uint16 `json:"src_port"`
DstPort uint16 `json:"dst_port"`
Error string `json:"error"`
ProxyName string `json:"proxy_name,omitempty"`
SrcAddr string `json:"src_addr,omitempty"`
DstAddr string `json:"dst_addr,omitempty"`
SrcPort uint16 `json:"src_port,omitempty"`
DstPort uint16 `json:"dst_port,omitempty"`
Error string `json:"error,omitempty"`
}

type NewVisitorConn struct {
ProxyName string `json:"proxy_name"`
SignKey string `json:"sign_key"`
Timestamp int64 `json:"timestamp"`
UseEncryption bool `json:"use_encryption"`
UseCompression bool `json:"use_compression"`
ProxyName string `json:"proxy_name,omitempty"`
SignKey string `json:"sign_key,omitempty"`
Timestamp int64 `json:"timestamp,omitempty"`
UseEncryption bool `json:"use_encryption,omitempty"`
UseCompression bool `json:"use_compression,omitempty"`
}

type NewVisitorConnResp struct {
ProxyName string `json:"proxy_name"`
Error string `json:"error"`
ProxyName string `json:"proxy_name,omitempty"`
Error string `json:"error,omitempty"`
}

type Ping struct {
PrivilegeKey string `json:"privilege_key"`
Timestamp int64 `json:"timestamp"`
PrivilegeKey string `json:"privilege_key,omitempty"`
Timestamp int64 `json:"timestamp,omitempty"`
}

type Pong struct {
Error string `json:"error"`
Error string `json:"error,omitempty"`
}

type UDPPacket struct {
Content string `json:"c"`
LocalAddr *net.UDPAddr `json:"l"`
RemoteAddr *net.UDPAddr `json:"r"`
Content string `json:"c,omitempty"`
LocalAddr *net.UDPAddr `json:"l,omitempty"`
RemoteAddr *net.UDPAddr `json:"r,omitempty"`
}

type NatHoleVisitor struct {
ProxyName string `json:"proxy_name"`
SignKey string `json:"sign_key"`
Timestamp int64 `json:"timestamp"`
ProxyName string `json:"proxy_name,omitempty"`
SignKey string `json:"sign_key,omitempty"`
Timestamp int64 `json:"timestamp,omitempty"`
}

type NatHoleClient struct {
ProxyName string `json:"proxy_name"`
Sid string `json:"sid"`
ProxyName string `json:"proxy_name,omitempty"`
Sid string `json:"sid,omitempty"`
}

type NatHoleResp struct {
Sid string `json:"sid"`
VisitorAddr string `json:"visitor_addr"`
ClientAddr string `json:"client_addr"`
Error string `json:"error"`
Sid string `json:"sid,omitempty"`
VisitorAddr string `json:"visitor_addr,omitempty"`
ClientAddr string `json:"client_addr,omitempty"`
Error string `json:"error,omitempty"`
}

type NatHoleClientDetectOK struct {
}

type NatHoleSid struct {
Sid string `json:"sid"`
Sid string `json:"sid,omitempty"`
}
42 changes: 34 additions & 8 deletions pkg/util/tcpmux/httpconnect.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,24 @@ import (

"github.com/fatedier/frp/pkg/util/util"
"github.com/fatedier/frp/pkg/util/vhost"
gnet "github.com/fatedier/golib/net"
)

type HTTPConnectTCPMuxer struct {
*vhost.Muxer

passthrough bool
authRequired bool // Not supported until we really need this.
}

func NewHTTPConnectTCPMuxer(listener net.Listener, timeout time.Duration) (*HTTPConnectTCPMuxer, error) {
mux, err := vhost.NewMuxer(listener, getHostFromHTTPConnect, nil, sendHTTPOk, nil, timeout)
return &HTTPConnectTCPMuxer{mux}, err
func NewHTTPConnectTCPMuxer(listener net.Listener, passthrough bool, timeout time.Duration) (*HTTPConnectTCPMuxer, error) {
ret := &HTTPConnectTCPMuxer{passthrough: passthrough, authRequired: false}
mux, err := vhost.NewMuxer(listener, ret.getHostFromHTTPConnect, nil, ret.sendConnectResponse, nil, timeout)
ret.Muxer = mux
return ret, err
}

func readHTTPConnectRequest(rd io.Reader) (host string, err error) {
func (muxer *HTTPConnectTCPMuxer) readHTTPConnectRequest(rd io.Reader) (host string, httpUser string, err error) {
bufioReader := bufio.NewReader(rd)

req, err := http.ReadRequest(bufioReader)
Expand All @@ -49,20 +55,40 @@ func readHTTPConnectRequest(rd io.Reader) (host string, err error) {
}

host, _ = util.CanonicalHost(req.Host)
proxyAuth := req.Header.Get("Proxy-Authorization")
if proxyAuth != "" {
httpUser, _, _ = util.ParseBasicAuth(proxyAuth)
}
return
}

func sendHTTPOk(c net.Conn) error {
func (muxer *HTTPConnectTCPMuxer) sendConnectResponse(c net.Conn, reqInfo map[string]string) error {
if muxer.passthrough {
return nil
}
return util.OkResponse().Write(c)
}

func getHostFromHTTPConnect(c net.Conn) (_ net.Conn, _ map[string]string, err error) {
func (muxer *HTTPConnectTCPMuxer) getHostFromHTTPConnect(c net.Conn) (net.Conn, map[string]string, error) {
reqInfoMap := make(map[string]string, 0)
host, err := readHTTPConnectRequest(c)
sc, rd := gnet.NewSharedConn(c)

host, httpUser, err := muxer.readHTTPConnectRequest(rd)
if err != nil {
return nil, reqInfoMap, err
}

reqInfoMap["Host"] = host
reqInfoMap["Scheme"] = "tcp"
return c, reqInfoMap, nil
reqInfoMap["HTTPUser"] = httpUser

var outConn net.Conn = c
if muxer.passthrough {
outConn = sc
if muxer.authRequired && httpUser == "" {
util.ProxyUnauthorizedResponse().Write(c)
outConn = c
}
}
return outConn, reqInfoMap, nil
}
Loading

0 comments on commit 4af85da

Please sign in to comment.