Browse Source

stcp, xtcp, sudp: support allow_users and specified server user (#3472)

pull/3474/head
fatedier 1 year ago committed by GitHub
parent
commit
de85c9455a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      .gitignore
  2. 15
      Makefile
  3. 63
      hack/download.sh
  4. 26
      hack/run-e2e.sh
  5. 2
      pkg/config/client.go
  6. 216
      pkg/config/proxy.go
  7. 6
      pkg/config/visitor.go
  8. 5
      pkg/msg/msg.go
  9. 28
      pkg/nathole/controller.go
  10. 8
      server/control.go
  11. 7
      server/proxy/stcp.go
  12. 8
      server/proxy/sudp.go
  13. 8
      server/proxy/xtcp.go
  14. 11
      server/service.go
  15. 43
      server/visitor/visitor.go
  16. 82
      test/e2e/basic/basic.go
  17. 2
      test/e2e/framework/process.go

1
.gitignore vendored

@ -29,6 +29,7 @@ packages/
release/ release/
test/bin/ test/bin/
vendor/ vendor/
lastversion/
dist/ dist/
.idea/ .idea/
.vscode/ .vscode/

15
Makefile

@ -46,8 +46,23 @@ e2e:
e2e-trace: e2e-trace:
DEBUG=true LOG_LEVEL=trace ./hack/run-e2e.sh DEBUG=true LOG_LEVEL=trace ./hack/run-e2e.sh
e2e-compatibility-last-frpc:
if [ ! -d "./lastversion" ]; then \
TARGET_DIRNAME=lastversion ./hack/download.sh; \
fi
FRPC_PATH="`pwd`/lastversion/frpc" ./hack/run-e2e.sh
rm -r ./lastversion
e2e-compatibility-last-frps:
if [ ! -d "./lastversion" ]; then \
TARGET_DIRNAME=lastversion ./hack/download.sh; \
fi
FRPS_PATH="`pwd`/lastversion/frps" ./hack/run-e2e.sh
rm -r ./lastversion
alltest: vet gotest e2e alltest: vet gotest e2e
clean: clean:
rm -f ./bin/frpc rm -f ./bin/frpc
rm -f ./bin/frps rm -f ./bin/frps
rm -rf ./lastversion

63
hack/download.sh

@ -0,0 +1,63 @@
#!/bin/sh
OS="$(go env GOOS)"
ARCH="$(go env GOARCH)"
if [ "${TARGET_OS}" ]; then
OS="${TARGET_OS}"
fi
if [ "${TARGET_ARCH}" ]; then
ARCH="${TARGET_ARCH}"
fi
# Determine the latest version by version number ignoring alpha, beta, and rc versions.
if [ "${FRP_VERSION}" = "" ] ; then
FRP_VERSION="$(curl -sL https://github.com/fatedier/frp/releases | \
grep -o 'releases/tag/v[0-9]*.[0-9]*.[0-9]*"' | sort -V | \
tail -1 | awk -F'/' '{ print $3}')"
FRP_VERSION="${FRP_VERSION%?}"
FRP_VERSION="${FRP_VERSION#?}"
fi
if [ "${FRP_VERSION}" = "" ] ; then
printf "Unable to get latest frp version. Set FRP_VERSION env var and re-run. For example: export FRP_VERSION=1.0.0"
exit 1;
fi
SUFFIX=".tar.gz"
if [ "${OS}" = "windows" ] ; then
SUFFIX=".zip"
fi
NAME="frp_${FRP_VERSION}_${OS}_${ARCH}${SUFFIX}"
DIR_NAME="frp_${FRP_VERSION}_${OS}_${ARCH}"
URL="https://github.com/fatedier/frp/releases/download/v${FRP_VERSION}/${NAME}"
download_and_extract() {
printf "Downloading %s from %s ...\n" "$NAME" "${URL}"
if ! curl -o /dev/null -sIf "${URL}"; then
printf "\n%s is not found, please specify a valid FRP_VERSION\n" "${URL}"
exit 1
fi
curl -fsLO "${URL}"
filename=$NAME
if [ "${OS}" = "windows" ]; then
unzip "${filename}"
else
tar -xzf "${filename}"
fi
rm "${filename}"
if [ "${TARGET_DIRNAME}" ]; then
mv "${DIR_NAME}" "${TARGET_DIRNAME}"
DIR_NAME="${TARGET_DIRNAME}"
fi
}
download_and_extract
printf ""
printf "\nfrp %s Download Complete!\n" "$FRP_VERSION"
printf "\n"
printf "frp has been successfully downloaded into the %s folder on your system.\n" "$DIR_NAME"
printf "\n"

26
hack/run-e2e.sh

@ -1,20 +1,30 @@
#!/usr/bin/env bash #!/bin/sh
ROOT=$(unset CDPATH && cd $(dirname "${BASH_SOURCE[0]}")/.. && pwd) SCRIPT=$(readlink -f "$0")
ROOT=$(unset CDPATH && cd "$(dirname "$SCRIPT")/.." && pwd)
which ginkgo &> /dev/null ginkgo_command=$(which ginkgo 2>/dev/null)
if [ $? -ne 0 ]; then if [ -z "$ginkgo_command" ]; then
echo "ginkgo not found, try to install..." echo "ginkgo not found, try to install..."
go install github.com/onsi/ginkgo/v2/ginkgo@v2.8.3 go install github.com/onsi/ginkgo/v2/ginkgo@v2.8.3
fi fi
debug=false debug=false
if [ x${DEBUG} == x"true" ]; then if [ "x${DEBUG}" = "xtrue" ]; then
debug=true debug=true
fi fi
logLevel=debug logLevel=debug
if [ x${LOG_LEVEL} != x"" ]; then if [ "${LOG_LEVEL}" ]; then
logLevel=${LOG_LEVEL} logLevel="${LOG_LEVEL}"
fi fi
ginkgo -nodes=8 --poll-progress-after=30s ${ROOT}/test/e2e -- -frpc-path=${ROOT}/bin/frpc -frps-path=${ROOT}/bin/frps -log-level=${logLevel} -debug=${debug} frpcPath=${ROOT}/bin/frpc
if [ "${FRPC_PATH}" ]; then
frpcPath="${FRPC_PATH}"
fi
frpsPath=${ROOT}/bin/frps
if [ "${FRPS_PATH}" ]; then
frpsPath="${FRPS_PATH}"
fi
ginkgo -nodes=8 --poll-progress-after=60s ${ROOT}/test/e2e -- -frpc-path=${frpcPath} -frps-path=${frpsPath} -log-level=${logLevel} -debug=${debug}

2
pkg/config/client.go

@ -352,7 +352,7 @@ func LoadAllProxyConfsFromIni(
case "visitor": case "visitor":
newConf, newErr := NewVisitorConfFromIni(prefix, name, section) newConf, newErr := NewVisitorConfFromIni(prefix, name, section)
if newErr != nil { if newErr != nil {
return nil, nil, newErr return nil, nil, fmt.Errorf("failed to parse visitor %s, err: %v", name, newErr)
} }
visitorConfs[prefix+name] = newConf visitorConfs[prefix+name] = newConf
default: default:

216
pkg/config/proxy.go

@ -178,6 +178,16 @@ func (cfg *RoleServerCommonConf) setDefaultValues() {
cfg.Role = "server" cfg.Role = "server"
} }
func (cfg *RoleServerCommonConf) marshalToMsg(m *msg.NewProxy) {
m.Sk = cfg.Sk
m.AllowUsers = cfg.AllowUsers
}
func (cfg *RoleServerCommonConf) unmarshalFromMsg(m *msg.NewProxy) {
cfg.Sk = m.Sk
cfg.AllowUsers = m.AllowUsers
}
// HTTP // HTTP
type HTTPProxyConf struct { type HTTPProxyConf struct {
BaseProxyConf `ini:",extends"` BaseProxyConf `ini:",extends"`
@ -260,7 +270,7 @@ func NewProxyConfFromIni(prefix, name string, section *ini.Section) (ProxyConf,
conf := DefaultProxyConf(proxyType) conf := DefaultProxyConf(proxyType)
if conf == nil { if conf == nil {
return nil, fmt.Errorf("proxy %s has invalid type [%s]", name, proxyType) return nil, fmt.Errorf("invalid type [%s]", proxyType)
} }
if err := conf.UnmarshalFromIni(prefix, name, section); err != nil { if err := conf.UnmarshalFromIni(prefix, name, section); err != nil {
@ -274,17 +284,17 @@ func NewProxyConfFromIni(prefix, name string, section *ini.Section) (ProxyConf,
} }
// Proxy loaded from msg // Proxy loaded from msg
func NewProxyConfFromMsg(pMsg *msg.NewProxy, serverCfg ServerCommonConf) (ProxyConf, error) { func NewProxyConfFromMsg(m *msg.NewProxy, serverCfg ServerCommonConf) (ProxyConf, error) {
if pMsg.ProxyType == "" { if m.ProxyType == "" {
pMsg.ProxyType = consts.TCPProxy m.ProxyType = consts.TCPProxy
} }
conf := DefaultProxyConf(pMsg.ProxyType) conf := DefaultProxyConf(m.ProxyType)
if conf == nil { if conf == nil {
return nil, fmt.Errorf("proxy [%s] type [%s] error", pMsg.ProxyName, pMsg.ProxyType) return nil, fmt.Errorf("proxy [%s] type [%s] error", m.ProxyName, m.ProxyType)
} }
conf.UnmarshalFromMsg(pMsg) conf.UnmarshalFromMsg(m)
err := conf.ValidateForServer(serverCfg) err := conf.ValidateForServer(serverCfg)
if err != nil { if err != nil {
@ -341,35 +351,35 @@ func (cfg *BaseProxyConf) decorate(prefix string, name string, section *ini.Sect
return nil return nil
} }
func (cfg *BaseProxyConf) marshalToMsg(pMsg *msg.NewProxy) { func (cfg *BaseProxyConf) marshalToMsg(m *msg.NewProxy) {
pMsg.ProxyName = cfg.ProxyName m.ProxyName = cfg.ProxyName
pMsg.ProxyType = cfg.ProxyType m.ProxyType = cfg.ProxyType
pMsg.UseEncryption = cfg.UseEncryption m.UseEncryption = cfg.UseEncryption
pMsg.UseCompression = cfg.UseCompression m.UseCompression = cfg.UseCompression
pMsg.BandwidthLimit = cfg.BandwidthLimit.String() m.BandwidthLimit = cfg.BandwidthLimit.String()
// leave it empty for default value to reduce traffic // leave it empty for default value to reduce traffic
if cfg.BandwidthLimitMode != "client" { if cfg.BandwidthLimitMode != "client" {
pMsg.BandwidthLimitMode = cfg.BandwidthLimitMode m.BandwidthLimitMode = cfg.BandwidthLimitMode
} }
pMsg.Group = cfg.Group m.Group = cfg.Group
pMsg.GroupKey = cfg.GroupKey m.GroupKey = cfg.GroupKey
pMsg.Metas = cfg.Metas m.Metas = cfg.Metas
} }
func (cfg *BaseProxyConf) unmarshalFromMsg(pMsg *msg.NewProxy) { func (cfg *BaseProxyConf) unmarshalFromMsg(m *msg.NewProxy) {
cfg.ProxyName = pMsg.ProxyName cfg.ProxyName = m.ProxyName
cfg.ProxyType = pMsg.ProxyType cfg.ProxyType = m.ProxyType
cfg.UseEncryption = pMsg.UseEncryption cfg.UseEncryption = m.UseEncryption
cfg.UseCompression = pMsg.UseCompression cfg.UseCompression = m.UseCompression
if pMsg.BandwidthLimit != "" { if m.BandwidthLimit != "" {
cfg.BandwidthLimit, _ = NewBandwidthQuantity(pMsg.BandwidthLimit) cfg.BandwidthLimit, _ = NewBandwidthQuantity(m.BandwidthLimit)
} }
if pMsg.BandwidthLimitMode != "" { if m.BandwidthLimitMode != "" {
cfg.BandwidthLimitMode = pMsg.BandwidthLimitMode cfg.BandwidthLimitMode = m.BandwidthLimitMode
} }
cfg.Group = pMsg.Group cfg.Group = m.Group
cfg.GroupKey = pMsg.GroupKey cfg.GroupKey = m.GroupKey
cfg.Metas = pMsg.Metas cfg.Metas = m.Metas
} }
func (cfg *BaseProxyConf) validateForClient() (err error) { func (cfg *BaseProxyConf) validateForClient() (err error) {
@ -482,11 +492,11 @@ func preUnmarshalFromIni(cfg ProxyConf, prefix string, name string, section *ini
} }
// TCP // TCP
func (cfg *TCPProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) { func (cfg *TCPProxyConf) UnmarshalFromMsg(m *msg.NewProxy) {
cfg.BaseProxyConf.unmarshalFromMsg(pMsg) cfg.BaseProxyConf.unmarshalFromMsg(m)
// Add custom logic unmarshal if exists // Add custom logic unmarshal if exists
cfg.RemotePort = pMsg.RemotePort cfg.RemotePort = m.RemotePort
} }
func (cfg *TCPProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error { func (cfg *TCPProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error {
@ -500,11 +510,11 @@ func (cfg *TCPProxyConf) UnmarshalFromIni(prefix string, name string, section *i
return nil return nil
} }
func (cfg *TCPProxyConf) MarshalToMsg(pMsg *msg.NewProxy) { func (cfg *TCPProxyConf) MarshalToMsg(m *msg.NewProxy) {
cfg.BaseProxyConf.marshalToMsg(pMsg) cfg.BaseProxyConf.marshalToMsg(m)
// Add custom logic marshal if exists // Add custom logic marshal if exists
pMsg.RemotePort = cfg.RemotePort m.RemotePort = cfg.RemotePort
} }
func (cfg *TCPProxyConf) ValidateForClient() (err error) { func (cfg *TCPProxyConf) ValidateForClient() (err error) {
@ -536,28 +546,28 @@ func (cfg *TCPMuxProxyConf) UnmarshalFromIni(prefix string, name string, section
return nil return nil
} }
func (cfg *TCPMuxProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) { func (cfg *TCPMuxProxyConf) UnmarshalFromMsg(m *msg.NewProxy) {
cfg.BaseProxyConf.unmarshalFromMsg(pMsg) cfg.BaseProxyConf.unmarshalFromMsg(m)
// Add custom logic unmarshal if exists // Add custom logic unmarshal if exists
cfg.CustomDomains = pMsg.CustomDomains cfg.CustomDomains = m.CustomDomains
cfg.SubDomain = pMsg.SubDomain cfg.SubDomain = m.SubDomain
cfg.Multiplexer = pMsg.Multiplexer cfg.Multiplexer = m.Multiplexer
cfg.HTTPUser = pMsg.HTTPUser cfg.HTTPUser = m.HTTPUser
cfg.HTTPPwd = pMsg.HTTPPwd cfg.HTTPPwd = m.HTTPPwd
cfg.RouteByHTTPUser = pMsg.RouteByHTTPUser cfg.RouteByHTTPUser = m.RouteByHTTPUser
} }
func (cfg *TCPMuxProxyConf) MarshalToMsg(pMsg *msg.NewProxy) { func (cfg *TCPMuxProxyConf) MarshalToMsg(m *msg.NewProxy) {
cfg.BaseProxyConf.marshalToMsg(pMsg) cfg.BaseProxyConf.marshalToMsg(m)
// Add custom logic marshal if exists // Add custom logic marshal if exists
pMsg.CustomDomains = cfg.CustomDomains m.CustomDomains = cfg.CustomDomains
pMsg.SubDomain = cfg.SubDomain m.SubDomain = cfg.SubDomain
pMsg.Multiplexer = cfg.Multiplexer m.Multiplexer = cfg.Multiplexer
pMsg.HTTPUser = cfg.HTTPUser m.HTTPUser = cfg.HTTPUser
pMsg.HTTPPwd = cfg.HTTPPwd m.HTTPPwd = cfg.HTTPPwd
pMsg.RouteByHTTPUser = cfg.RouteByHTTPUser m.RouteByHTTPUser = cfg.RouteByHTTPUser
} }
func (cfg *TCPMuxProxyConf) ValidateForClient() (err error) { func (cfg *TCPMuxProxyConf) ValidateForClient() (err error) {
@ -610,18 +620,18 @@ func (cfg *UDPProxyConf) UnmarshalFromIni(prefix string, name string, section *i
return nil return nil
} }
func (cfg *UDPProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) { func (cfg *UDPProxyConf) UnmarshalFromMsg(m *msg.NewProxy) {
cfg.BaseProxyConf.unmarshalFromMsg(pMsg) cfg.BaseProxyConf.unmarshalFromMsg(m)
// Add custom logic unmarshal if exists // Add custom logic unmarshal if exists
cfg.RemotePort = pMsg.RemotePort cfg.RemotePort = m.RemotePort
} }
func (cfg *UDPProxyConf) MarshalToMsg(pMsg *msg.NewProxy) { func (cfg *UDPProxyConf) MarshalToMsg(m *msg.NewProxy) {
cfg.BaseProxyConf.marshalToMsg(pMsg) cfg.BaseProxyConf.marshalToMsg(m)
// Add custom logic marshal if exists // Add custom logic marshal if exists
pMsg.RemotePort = cfg.RemotePort m.RemotePort = cfg.RemotePort
} }
func (cfg *UDPProxyConf) ValidateForClient() (err error) { func (cfg *UDPProxyConf) ValidateForClient() (err error) {
@ -653,32 +663,32 @@ func (cfg *HTTPProxyConf) UnmarshalFromIni(prefix string, name string, section *
return nil return nil
} }
func (cfg *HTTPProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) { func (cfg *HTTPProxyConf) UnmarshalFromMsg(m *msg.NewProxy) {
cfg.BaseProxyConf.unmarshalFromMsg(pMsg) cfg.BaseProxyConf.unmarshalFromMsg(m)
// Add custom logic unmarshal if exists // Add custom logic unmarshal if exists
cfg.CustomDomains = pMsg.CustomDomains cfg.CustomDomains = m.CustomDomains
cfg.SubDomain = pMsg.SubDomain cfg.SubDomain = m.SubDomain
cfg.Locations = pMsg.Locations cfg.Locations = m.Locations
cfg.HostHeaderRewrite = pMsg.HostHeaderRewrite cfg.HostHeaderRewrite = m.HostHeaderRewrite
cfg.HTTPUser = pMsg.HTTPUser cfg.HTTPUser = m.HTTPUser
cfg.HTTPPwd = pMsg.HTTPPwd cfg.HTTPPwd = m.HTTPPwd
cfg.Headers = pMsg.Headers cfg.Headers = m.Headers
cfg.RouteByHTTPUser = pMsg.RouteByHTTPUser cfg.RouteByHTTPUser = m.RouteByHTTPUser
} }
func (cfg *HTTPProxyConf) MarshalToMsg(pMsg *msg.NewProxy) { func (cfg *HTTPProxyConf) MarshalToMsg(m *msg.NewProxy) {
cfg.BaseProxyConf.marshalToMsg(pMsg) cfg.BaseProxyConf.marshalToMsg(m)
// Add custom logic marshal if exists // Add custom logic marshal if exists
pMsg.CustomDomains = cfg.CustomDomains m.CustomDomains = cfg.CustomDomains
pMsg.SubDomain = cfg.SubDomain m.SubDomain = cfg.SubDomain
pMsg.Locations = cfg.Locations m.Locations = cfg.Locations
pMsg.HostHeaderRewrite = cfg.HostHeaderRewrite m.HostHeaderRewrite = cfg.HostHeaderRewrite
pMsg.HTTPUser = cfg.HTTPUser m.HTTPUser = cfg.HTTPUser
pMsg.HTTPPwd = cfg.HTTPPwd m.HTTPPwd = cfg.HTTPPwd
pMsg.Headers = cfg.Headers m.Headers = cfg.Headers
pMsg.RouteByHTTPUser = cfg.RouteByHTTPUser m.RouteByHTTPUser = cfg.RouteByHTTPUser
} }
func (cfg *HTTPProxyConf) ValidateForClient() (err error) { func (cfg *HTTPProxyConf) ValidateForClient() (err error) {
@ -722,20 +732,20 @@ func (cfg *HTTPSProxyConf) UnmarshalFromIni(prefix string, name string, section
return nil return nil
} }
func (cfg *HTTPSProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) { func (cfg *HTTPSProxyConf) UnmarshalFromMsg(m *msg.NewProxy) {
cfg.BaseProxyConf.unmarshalFromMsg(pMsg) cfg.BaseProxyConf.unmarshalFromMsg(m)
// Add custom logic unmarshal if exists // Add custom logic unmarshal if exists
cfg.CustomDomains = pMsg.CustomDomains cfg.CustomDomains = m.CustomDomains
cfg.SubDomain = pMsg.SubDomain cfg.SubDomain = m.SubDomain
} }
func (cfg *HTTPSProxyConf) MarshalToMsg(pMsg *msg.NewProxy) { func (cfg *HTTPSProxyConf) MarshalToMsg(m *msg.NewProxy) {
cfg.BaseProxyConf.marshalToMsg(pMsg) cfg.BaseProxyConf.marshalToMsg(m)
// Add custom logic marshal if exists // Add custom logic marshal if exists
pMsg.CustomDomains = cfg.CustomDomains m.CustomDomains = cfg.CustomDomains
pMsg.SubDomain = cfg.SubDomain m.SubDomain = cfg.SubDomain
} }
func (cfg *HTTPSProxyConf) ValidateForClient() (err error) { func (cfg *HTTPSProxyConf) ValidateForClient() (err error) {
@ -784,18 +794,18 @@ func (cfg *SUDPProxyConf) UnmarshalFromIni(prefix string, name string, section *
} }
// Only for role server. // Only for role server.
func (cfg *SUDPProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) { func (cfg *SUDPProxyConf) UnmarshalFromMsg(m *msg.NewProxy) {
cfg.BaseProxyConf.unmarshalFromMsg(pMsg) cfg.BaseProxyConf.unmarshalFromMsg(m)
// Add custom logic unmarshal if exists // Add custom logic unmarshal if exists
cfg.Sk = pMsg.Sk cfg.RoleServerCommonConf.unmarshalFromMsg(m)
} }
func (cfg *SUDPProxyConf) MarshalToMsg(pMsg *msg.NewProxy) { func (cfg *SUDPProxyConf) MarshalToMsg(m *msg.NewProxy) {
cfg.BaseProxyConf.marshalToMsg(pMsg) cfg.BaseProxyConf.marshalToMsg(m)
// Add custom logic marshal if exists // Add custom logic marshal if exists
pMsg.Sk = cfg.Sk cfg.RoleServerCommonConf.marshalToMsg(m)
} }
func (cfg *SUDPProxyConf) ValidateForClient() (err error) { func (cfg *SUDPProxyConf) ValidateForClient() (err error) {
@ -838,18 +848,18 @@ func (cfg *STCPProxyConf) UnmarshalFromIni(prefix string, name string, section *
} }
// Only for role server. // Only for role server.
func (cfg *STCPProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) { func (cfg *STCPProxyConf) UnmarshalFromMsg(m *msg.NewProxy) {
cfg.BaseProxyConf.unmarshalFromMsg(pMsg) cfg.BaseProxyConf.unmarshalFromMsg(m)
// Add custom logic unmarshal if exists // Add custom logic unmarshal if exists
cfg.Sk = pMsg.Sk cfg.RoleServerCommonConf.unmarshalFromMsg(m)
} }
func (cfg *STCPProxyConf) MarshalToMsg(pMsg *msg.NewProxy) { func (cfg *STCPProxyConf) MarshalToMsg(m *msg.NewProxy) {
cfg.BaseProxyConf.marshalToMsg(pMsg) cfg.BaseProxyConf.marshalToMsg(m)
// Add custom logic marshal if exists // Add custom logic marshal if exists
pMsg.Sk = cfg.Sk cfg.RoleServerCommonConf.marshalToMsg(m)
} }
func (cfg *STCPProxyConf) ValidateForClient() (err error) { func (cfg *STCPProxyConf) ValidateForClient() (err error) {
@ -892,18 +902,18 @@ func (cfg *XTCPProxyConf) UnmarshalFromIni(prefix string, name string, section *
} }
// Only for role server. // Only for role server.
func (cfg *XTCPProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) { func (cfg *XTCPProxyConf) UnmarshalFromMsg(m *msg.NewProxy) {
cfg.BaseProxyConf.unmarshalFromMsg(pMsg) cfg.BaseProxyConf.unmarshalFromMsg(m)
// Add custom logic unmarshal if exists // Add custom logic unmarshal if exists
cfg.Sk = pMsg.Sk cfg.RoleServerCommonConf.unmarshalFromMsg(m)
} }
func (cfg *XTCPProxyConf) MarshalToMsg(pMsg *msg.NewProxy) { func (cfg *XTCPProxyConf) MarshalToMsg(m *msg.NewProxy) {
cfg.BaseProxyConf.marshalToMsg(pMsg) cfg.BaseProxyConf.marshalToMsg(m)
// Add custom logic marshal if exists // Add custom logic marshal if exists
pMsg.Sk = cfg.Sk cfg.RoleServerCommonConf.marshalToMsg(m)
} }
func (cfg *XTCPProxyConf) ValidateForClient() (err error) { func (cfg *XTCPProxyConf) ValidateForClient() (err error) {

6
pkg/config/visitor.go

@ -94,16 +94,16 @@ func NewVisitorConfFromIni(prefix string, name string, section *ini.Section) (Vi
visitorType := section.Key("type").String() visitorType := section.Key("type").String()
if visitorType == "" { if visitorType == "" {
return nil, fmt.Errorf("visitor [%s] type shouldn't be empty", name) return nil, fmt.Errorf("type shouldn't be empty")
} }
conf := DefaultVisitorConf(visitorType) conf := DefaultVisitorConf(visitorType)
if conf == nil { if conf == nil {
return nil, fmt.Errorf("visitor [%s] type [%s] error", name, visitorType) return nil, fmt.Errorf("type [%s] error", visitorType)
} }
if err := conf.UnmarshalFromIni(prefix, name, section); err != nil { if err := conf.UnmarshalFromIni(prefix, name, section); err != nil {
return nil, fmt.Errorf("visitor [%s] type [%s] error", name, visitorType) return nil, fmt.Errorf("type [%s] error", visitorType)
} }
if err := conf.Validate(); err != nil { if err := conf.Validate(); err != nil {

5
pkg/msg/msg.go

@ -110,8 +110,9 @@ type NewProxy struct {
Headers map[string]string `json:"headers,omitempty"` Headers map[string]string `json:"headers,omitempty"`
RouteByHTTPUser string `json:"route_by_http_user,omitempty"` RouteByHTTPUser string `json:"route_by_http_user,omitempty"`
// stcp // stcp, sudp, xtcp
Sk string `json:"sk,omitempty"` Sk string `json:"sk,omitempty"`
AllowUsers []string `json:"allow_users,omitempty"`
// tcpmux // tcpmux
Multiplexer string `json:"multiplexer,omitempty"` Multiplexer string `json:"multiplexer,omitempty"`

28
pkg/nathole/controller.go

@ -43,9 +43,10 @@ func NewTransactionID() string {
} }
type ClientCfg struct { type ClientCfg struct {
name string name string
sk string sk string
sidCh chan string allowUsers []string
sidCh chan string
} }
type Session struct { type Session struct {
@ -120,11 +121,12 @@ func (c *Controller) CleanWorker(ctx context.Context) {
} }
} }
func (c *Controller) ListenClient(name string, sk string) chan string { func (c *Controller) ListenClient(name string, sk string, allowUsers []string) chan string {
cfg := &ClientCfg{ cfg := &ClientCfg{
name: name, name: name,
sk: sk, sk: sk,
sidCh: make(chan string), allowUsers: allowUsers,
sidCh: make(chan string),
} }
c.mu.Lock() c.mu.Lock()
defer c.mu.Unlock() defer c.mu.Unlock()
@ -144,14 +146,18 @@ func (c *Controller) GenSid() string {
return fmt.Sprintf("%d%s", t, id) return fmt.Sprintf("%d%s", t, id)
} }
func (c *Controller) HandleVisitor(m *msg.NatHoleVisitor, transporter transport.MessageTransporter) { func (c *Controller) HandleVisitor(m *msg.NatHoleVisitor, transporter transport.MessageTransporter, visitorUser string) {
if m.PreCheck { if m.PreCheck {
_, ok := c.clientCfgs[m.ProxyName] cfg, ok := c.clientCfgs[m.ProxyName]
if !ok { if !ok {
_ = transporter.Send(c.GenNatHoleResponse(m.TransactionID, nil, fmt.Sprintf("xtcp server for [%s] doesn't exist", m.ProxyName))) _ = transporter.Send(c.GenNatHoleResponse(m.TransactionID, nil, fmt.Sprintf("xtcp server for [%s] doesn't exist", m.ProxyName)))
} else { return
_ = transporter.Send(c.GenNatHoleResponse(m.TransactionID, nil, "")) }
if !lo.Contains(cfg.allowUsers, visitorUser) && !lo.Contains(cfg.allowUsers, "*") {
_ = transporter.Send(c.GenNatHoleResponse(m.TransactionID, nil, fmt.Sprintf("xtcp visitor user [%s] not allowed for [%s]", visitorUser, m.ProxyName)))
return
} }
_ = transporter.Send(c.GenNatHoleResponse(m.TransactionID, nil, ""))
return return
} }

8
server/control.go

@ -524,7 +524,7 @@ func (ctl *Control) manager() {
} }
func (ctl *Control) HandleNatHoleVisitor(m *msg.NatHoleVisitor) { func (ctl *Control) HandleNatHoleVisitor(m *msg.NatHoleVisitor) {
ctl.rc.NatHoleController.HandleVisitor(m, ctl.msgTransporter) ctl.rc.NatHoleController.HandleVisitor(m, ctl.msgTransporter, ctl.loginMsg.User)
} }
func (ctl *Control) HandleNatHoleClient(m *msg.NatHoleClient) { func (ctl *Control) HandleNatHoleClient(m *msg.NatHoleClient) {
@ -537,7 +537,7 @@ func (ctl *Control) HandleNatHoleReport(m *msg.NatHoleReport) {
func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (remoteAddr string, err error) { func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (remoteAddr string, err error) {
var pxyConf config.ProxyConf var pxyConf config.ProxyConf
// Load configures from NewProxy message and check. // Load configures from NewProxy message and validate.
pxyConf, err = config.NewProxyConfFromMsg(pxyMsg, ctl.serverCfg) pxyConf, err = config.NewProxyConfFromMsg(pxyMsg, ctl.serverCfg)
if err != nil { if err != nil {
return return
@ -550,8 +550,8 @@ func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (remoteAddr string, err
RunID: ctl.runID, RunID: ctl.runID,
} }
// NewProxy will return a interface Proxy. // NewProxy will return an interface Proxy.
// In fact it create different proxies by different proxy type, we just call run() here. // In fact, it creates different proxies based on the proxy type. We just call run() here.
pxy, err := proxy.NewProxy(ctl.ctx, userInfo, ctl.rc, ctl.poolCount, ctl.GetWorkConn, pxyConf, ctl.serverCfg, ctl.loginMsg) pxy, err := proxy.NewProxy(ctl.ctx, userInfo, ctl.rc, ctl.poolCount, ctl.GetWorkConn, pxyConf, ctl.serverCfg, ctl.loginMsg)
if err != nil { if err != nil {
return remoteAddr, err return remoteAddr, err

7
server/proxy/stcp.go

@ -27,7 +27,12 @@ type STCPProxy struct {
func (pxy *STCPProxy) Run() (remoteAddr string, err error) { func (pxy *STCPProxy) Run() (remoteAddr string, err error) {
xl := pxy.xl xl := pxy.xl
listener, errRet := pxy.rc.VisitorManager.Listen(pxy.GetName(), pxy.cfg.Sk) allowUsers := pxy.cfg.AllowUsers
// if allowUsers is empty, only allow same user from proxy
if len(allowUsers) == 0 {
allowUsers = []string{pxy.GetUserInfo().User}
}
listener, errRet := pxy.rc.VisitorManager.Listen(pxy.GetName(), pxy.cfg.Sk, allowUsers)
if errRet != nil { if errRet != nil {
err = errRet err = errRet
return return

8
server/proxy/sudp.go

@ -27,8 +27,12 @@ type SUDPProxy struct {
func (pxy *SUDPProxy) Run() (remoteAddr string, err error) { func (pxy *SUDPProxy) Run() (remoteAddr string, err error) {
xl := pxy.xl xl := pxy.xl
allowUsers := pxy.cfg.AllowUsers
listener, errRet := pxy.rc.VisitorManager.Listen(pxy.GetName(), pxy.cfg.Sk) // if allowUsers is empty, only allow same user from proxy
if len(allowUsers) == 0 {
allowUsers = []string{pxy.GetUserInfo().User}
}
listener, errRet := pxy.rc.VisitorManager.Listen(pxy.GetName(), pxy.cfg.Sk, allowUsers)
if errRet != nil { if errRet != nil {
err = errRet err = errRet
return return

8
server/proxy/xtcp.go

@ -35,11 +35,15 @@ func (pxy *XTCPProxy) Run() (remoteAddr string, err error) {
xl := pxy.xl xl := pxy.xl
if pxy.rc.NatHoleController == nil { if pxy.rc.NatHoleController == nil {
xl.Error("udp port for xtcp is not specified.")
err = fmt.Errorf("xtcp is not supported in frps") err = fmt.Errorf("xtcp is not supported in frps")
return return
} }
sidCh := pxy.rc.NatHoleController.ListenClient(pxy.GetName(), pxy.cfg.Sk) allowUsers := pxy.cfg.AllowUsers
// if allowUsers is empty, only allow same user from proxy
if len(allowUsers) == 0 {
allowUsers = []string{pxy.GetUserInfo().User}
}
sidCh := pxy.rc.NatHoleController.ListenClient(pxy.GetName(), pxy.cfg.Sk, allowUsers)
go func() { go func() {
for { for {
select { select {

11
server/service.go

@ -587,6 +587,15 @@ func (svr *Service) RegisterWorkConn(workConn net.Conn, newMsg *msg.NewWorkConn)
} }
func (svr *Service) RegisterVisitorConn(visitorConn net.Conn, newMsg *msg.NewVisitorConn) error { func (svr *Service) RegisterVisitorConn(visitorConn net.Conn, newMsg *msg.NewVisitorConn) error {
visitorUser := ""
// TODO: Compatible with old versions, can be without runID, user is empty. In later versions, it will be mandatory to include runID.
if newMsg.RunID != "" {
ctl, exist := svr.ctlManager.GetByID(newMsg.RunID)
if !exist {
return fmt.Errorf("no client control found for run id [%s]", newMsg.RunID)
}
visitorUser = ctl.loginMsg.User
}
return svr.rc.VisitorManager.NewConn(newMsg.ProxyName, visitorConn, newMsg.Timestamp, newMsg.SignKey, return svr.rc.VisitorManager.NewConn(newMsg.ProxyName, visitorConn, newMsg.Timestamp, newMsg.SignKey,
newMsg.UseEncryption, newMsg.UseCompression) newMsg.UseEncryption, newMsg.UseCompression, visitorUser)
} }

43
server/visitor/visitor.go

@ -21,57 +21,69 @@ import (
"sync" "sync"
libio "github.com/fatedier/golib/io" libio "github.com/fatedier/golib/io"
"github.com/samber/lo"
utilnet "github.com/fatedier/frp/pkg/util/net" utilnet "github.com/fatedier/frp/pkg/util/net"
"github.com/fatedier/frp/pkg/util/util" "github.com/fatedier/frp/pkg/util/util"
) )
type listenerBundle struct {
l *utilnet.InternalListener
sk string
allowUsers []string
}
// Manager for visitor listeners. // Manager for visitor listeners.
type Manager struct { type Manager struct {
visitorListeners map[string]*utilnet.InternalListener listeners map[string]*listenerBundle
skMap map[string]string
mu sync.RWMutex mu sync.RWMutex
} }
func NewManager() *Manager { func NewManager() *Manager {
return &Manager{ return &Manager{
visitorListeners: make(map[string]*utilnet.InternalListener), listeners: make(map[string]*listenerBundle),
skMap: make(map[string]string),
} }
} }
func (vm *Manager) Listen(name string, sk string) (l *utilnet.InternalListener, err error) { func (vm *Manager) Listen(name string, sk string, allowUsers []string) (l *utilnet.InternalListener, err error) {
vm.mu.Lock() vm.mu.Lock()
defer vm.mu.Unlock() defer vm.mu.Unlock()
if _, ok := vm.visitorListeners[name]; ok { if _, ok := vm.listeners[name]; ok {
err = fmt.Errorf("custom listener for [%s] is repeated", name) err = fmt.Errorf("custom listener for [%s] is repeated", name)
return return
} }
l = utilnet.NewInternalListener() l = utilnet.NewInternalListener()
vm.visitorListeners[name] = l vm.listeners[name] = &listenerBundle{
vm.skMap[name] = sk l: l,
sk: sk,
allowUsers: allowUsers,
}
return return
} }
func (vm *Manager) NewConn(name string, conn net.Conn, timestamp int64, signKey string, func (vm *Manager) NewConn(name string, conn net.Conn, timestamp int64, signKey string,
useEncryption bool, useCompression bool, useEncryption bool, useCompression bool, visitorUser string,
) (err error) { ) (err error) {
vm.mu.RLock() vm.mu.RLock()
defer vm.mu.RUnlock() defer vm.mu.RUnlock()
if l, ok := vm.visitorListeners[name]; ok { if l, ok := vm.listeners[name]; ok {
var sk string if util.GetAuthKey(l.sk, timestamp) != signKey {
if sk = vm.skMap[name]; util.GetAuthKey(sk, timestamp) != signKey {
err = fmt.Errorf("visitor connection of [%s] auth failed", name) err = fmt.Errorf("visitor connection of [%s] auth failed", name)
return return
} }
if !lo.Contains(l.allowUsers, visitorUser) && !lo.Contains(l.allowUsers, "*") {
err = fmt.Errorf("visitor connection of [%s] user [%s] not allowed", name, visitorUser)
return
}
var rwc io.ReadWriteCloser = conn var rwc io.ReadWriteCloser = conn
if useEncryption { if useEncryption {
if rwc, err = libio.WithEncryption(rwc, []byte(sk)); err != nil { if rwc, err = libio.WithEncryption(rwc, []byte(l.sk)); err != nil {
err = fmt.Errorf("create encryption connection failed: %v", err) err = fmt.Errorf("create encryption connection failed: %v", err)
return return
} }
@ -79,7 +91,7 @@ func (vm *Manager) NewConn(name string, conn net.Conn, timestamp int64, signKey
if useCompression { if useCompression {
rwc = libio.WithCompression(rwc) rwc = libio.WithCompression(rwc)
} }
err = l.PutConn(utilnet.WrapReadWriteCloserToConn(rwc, conn)) err = l.l.PutConn(utilnet.WrapReadWriteCloserToConn(rwc, conn))
} else { } else {
err = fmt.Errorf("custom listener for [%s] doesn't exist", name) err = fmt.Errorf("custom listener for [%s] doesn't exist", name)
return return
@ -91,6 +103,5 @@ func (vm *Manager) CloseListener(name string) {
vm.mu.Lock() vm.mu.Lock()
defer vm.mu.Unlock() defer vm.mu.Unlock()
delete(vm.visitorListeners, name) delete(vm.listeners, name)
delete(vm.skMap, name)
} }

82
test/e2e/basic/basic.go

@ -282,8 +282,9 @@ var _ = ginkgo.Describe("[Feature: Basic]", func() {
proxyType := t proxyType := t
ginkgo.It(fmt.Sprintf("Expose echo server with %s", strings.ToUpper(proxyType)), func() { ginkgo.It(fmt.Sprintf("Expose echo server with %s", strings.ToUpper(proxyType)), func() {
serverConf := consts.DefaultServerConfig serverConf := consts.DefaultServerConfig
clientServerConf := consts.DefaultClientConfig clientServerConf := consts.DefaultClientConfig + "\nuser = user1"
clientVisitorConf := consts.DefaultClientConfig clientVisitorConf := consts.DefaultClientConfig + "\nuser = user1"
clientUser2VisitorConf := consts.DefaultClientConfig + "\nuser = user2"
localPortName := "" localPortName := ""
protocol := "tcp" protocol := "tcp"
@ -312,7 +313,7 @@ var _ = ginkgo.Describe("[Feature: Basic]", func() {
`+extra, proxyName, proxyType, correctSK, localPortName) `+extra, proxyName, proxyType, correctSK, localPortName)
} }
getProxyVisitorConf := func(proxyName string, portName, visitorSK, extra string) string { getProxyVisitorConf := func(proxyName string, portName, visitorSK, extra string) string {
return fmt.Sprintf(` out := fmt.Sprintf(`
[%s] [%s]
type = %s type = %s
role = visitor role = visitor
@ -320,14 +321,22 @@ var _ = ginkgo.Describe("[Feature: Basic]", func() {
sk = %s sk = %s
bind_port = {{ .%s }} bind_port = {{ .%s }}
`+extra, proxyName, proxyType, proxyName, visitorSK, portName) `+extra, proxyName, proxyType, proxyName, visitorSK, portName)
if proxyType == "xtcp" {
// Set keep_tunnel_open to reduce testing time.
out += "\nkeep_tunnel_open = true"
}
return out
} }
tests := []struct { tests := []struct {
proxyName string proxyName string
bindPortName string bindPortName string
visitorSK string visitorSK string
extraConfig string commonExtraConfig string
expectError bool proxyExtraConfig string
visitorExtraConfig string
expectError bool
user2 bool
}{ }{
{ {
proxyName: "normal", proxyName: "normal",
@ -335,22 +344,22 @@ var _ = ginkgo.Describe("[Feature: Basic]", func() {
visitorSK: correctSK, visitorSK: correctSK,
}, },
{ {
proxyName: "with-encryption", proxyName: "with-encryption",
bindPortName: port.GenName("WithEncryption"), bindPortName: port.GenName("WithEncryption"),
visitorSK: correctSK, visitorSK: correctSK,
extraConfig: "use_encryption = true", commonExtraConfig: "use_encryption = true",
}, },
{ {
proxyName: "with-compression", proxyName: "with-compression",
bindPortName: port.GenName("WithCompression"), bindPortName: port.GenName("WithCompression"),
visitorSK: correctSK, visitorSK: correctSK,
extraConfig: "use_compression = true", commonExtraConfig: "use_compression = true",
}, },
{ {
proxyName: "with-encryption-and-compression", proxyName: "with-encryption-and-compression",
bindPortName: port.GenName("WithEncryptionAndCompression"), bindPortName: port.GenName("WithEncryptionAndCompression"),
visitorSK: correctSK, visitorSK: correctSK,
extraConfig: ` commonExtraConfig: `
use_encryption = true use_encryption = true
use_compression = true use_compression = true
`, `,
@ -361,22 +370,53 @@ var _ = ginkgo.Describe("[Feature: Basic]", func() {
visitorSK: wrongSK, visitorSK: wrongSK,
expectError: true, expectError: true,
}, },
{
proxyName: "allowed-user",
bindPortName: port.GenName("AllowedUser"),
visitorSK: correctSK,
proxyExtraConfig: "allow_users = another, user2",
visitorExtraConfig: "server_user = user1",
user2: true,
},
{
proxyName: "not-allowed-user",
bindPortName: port.GenName("NotAllowedUser"),
visitorSK: correctSK,
proxyExtraConfig: "allow_users = invalid",
visitorExtraConfig: "server_user = user1",
expectError: true,
},
{
proxyName: "allow-all",
bindPortName: port.GenName("AllowAll"),
visitorSK: correctSK,
proxyExtraConfig: "allow_users = *",
visitorExtraConfig: "server_user = user1",
user2: true,
},
} }
// build all client config // build all client config
for _, test := range tests { for _, test := range tests {
clientServerConf += getProxyServerConf(test.proxyName, test.extraConfig) + "\n" clientServerConf += getProxyServerConf(test.proxyName, test.commonExtraConfig+"\n"+test.proxyExtraConfig) + "\n"
} }
for _, test := range tests { for _, test := range tests {
clientVisitorConf += getProxyVisitorConf(test.proxyName, test.bindPortName, test.visitorSK, test.extraConfig) + "\n" config := getProxyVisitorConf(
test.proxyName, test.bindPortName, test.visitorSK, test.commonExtraConfig+"\n"+test.visitorExtraConfig,
) + "\n"
if test.user2 {
clientUser2VisitorConf += config
} else {
clientVisitorConf += config
}
} }
// run frps and frpc // run frps and frpc
f.RunProcesses([]string{serverConf}, []string{clientServerConf, clientVisitorConf}) f.RunProcesses([]string{serverConf}, []string{clientServerConf, clientVisitorConf, clientUser2VisitorConf})
for _, test := range tests { for _, test := range tests {
framework.NewRequestExpect(f). framework.NewRequestExpect(f).
RequestModify(func(r *request.Request) { RequestModify(func(r *request.Request) {
r.Timeout(10 * time.Second) r.Timeout(3 * time.Second)
}). }).
Protocol(protocol). Protocol(protocol).
PortName(test.bindPortName). PortName(test.bindPortName).

2
test/e2e/framework/process.go

@ -56,7 +56,7 @@ func (f *Framework) RunProcesses(serverTemplates []string, clientTemplates []str
ExpectNoError(err) ExpectNoError(err)
time.Sleep(500 * time.Millisecond) time.Sleep(500 * time.Millisecond)
} }
time.Sleep(2 * time.Second) time.Sleep(3 * time.Second)
return currentServerProcesses, currentClientProcesses return currentServerProcesses, currentClientProcesses
} }

Loading…
Cancel
Save