mirror of
https://github.com/fatedier/frp.git
synced 2025-07-26 23:29:58 +00:00
support yaml/json/toml configuration format, make ini deprecated (#3599)
This commit is contained in:
19
pkg/config/v1/api.go
Normal file
19
pkg/config/v1/api.go
Normal file
@@ -0,0 +1,19 @@
|
||||
// Copyright 2023 The frp Authors
|
||||
//
|
||||
// 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 v1
|
||||
|
||||
type APIMetadata struct {
|
||||
Version string `json:"version"`
|
||||
}
|
199
pkg/config/v1/client.go
Normal file
199
pkg/config/v1/client.go
Normal file
@@ -0,0 +1,199 @@
|
||||
// Copyright 2023 The frp Authors
|
||||
//
|
||||
// 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 v1
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/samber/lo"
|
||||
|
||||
"github.com/fatedier/frp/pkg/util/util"
|
||||
)
|
||||
|
||||
type ClientConfig struct {
|
||||
ClientCommonConfig
|
||||
|
||||
Proxies []TypedProxyConfig `json:"proxies,omitempty"`
|
||||
Visitors []TypedVisitorConfig `json:"visitors,omitempty"`
|
||||
}
|
||||
|
||||
type ClientCommonConfig struct {
|
||||
APIMetadata
|
||||
|
||||
Auth AuthClientConfig `json:"auth,omitempty"`
|
||||
// User specifies a prefix for proxy names to distinguish them from other
|
||||
// clients. If this value is not "", proxy names will automatically be
|
||||
// changed to "{user}.{proxy_name}".
|
||||
User string `json:"user,omitempty"`
|
||||
|
||||
// ServerAddr specifies the address of the server to connect to. By
|
||||
// default, this value is "0.0.0.0".
|
||||
ServerAddr string `json:"serverAddr,omitempty"`
|
||||
// ServerPort specifies the port to connect to the server on. By default,
|
||||
// this value is 7000.
|
||||
ServerPort int `json:"serverPort,omitempty"`
|
||||
// STUN server to help penetrate NAT hole.
|
||||
NatHoleSTUNServer string `json:"natHoleStunServer,omitempty"`
|
||||
// DNSServer specifies a DNS server address for FRPC to use. If this value
|
||||
// is "", the default DNS will be used.
|
||||
DNSServer string `json:"dnsServer,omitempty"`
|
||||
// LoginFailExit controls whether or not the client should exit after a
|
||||
// failed login attempt. If false, the client will retry until a login
|
||||
// attempt succeeds. By default, this value is true.
|
||||
LoginFailExit *bool `json:"loginFailExit,omitempty"`
|
||||
// Start specifies a set of enabled proxies by name. If this set is empty,
|
||||
// all supplied proxies are enabled. By default, this value is an empty
|
||||
// set.
|
||||
Start []string `json:"start,omitempty"`
|
||||
|
||||
Log LogConfig `json:"log,omitempty"`
|
||||
WebServer WebServerConfig `json:"webServer,omitempty"`
|
||||
Transport ClientTransportConfig `json:"transport,omitempty"`
|
||||
|
||||
// UDPPacketSize specifies the udp packet size
|
||||
// By default, this value is 1500
|
||||
UDPPacketSize int64 `json:"udpPacketSize,omitempty"`
|
||||
// Client metadata info
|
||||
Metadatas map[string]string `json:"metadatas,omitempty"`
|
||||
|
||||
// Include other config files for proxies.
|
||||
IncludeConfigFiles []string `json:"includes,omitempty"`
|
||||
}
|
||||
|
||||
func (c *ClientCommonConfig) Complete() {
|
||||
c.ServerAddr = util.EmptyOr(c.ServerAddr, "0.0.0.0")
|
||||
c.ServerPort = util.EmptyOr(c.ServerPort, 7000)
|
||||
c.LoginFailExit = util.EmptyOr(c.LoginFailExit, lo.ToPtr(true))
|
||||
|
||||
c.Auth.Complete()
|
||||
c.Log.Complete()
|
||||
c.Transport.Complete()
|
||||
c.WebServer.Complete()
|
||||
|
||||
c.UDPPacketSize = util.EmptyOr(c.UDPPacketSize, 1500)
|
||||
}
|
||||
|
||||
type ClientTransportConfig struct {
|
||||
// Protocol specifies the protocol to use when interacting with the server.
|
||||
// Valid values are "tcp", "kcp", "quic", "websocket" and "wss". By default, this value
|
||||
// is "tcp".
|
||||
Protocol string `json:"protocol,omitempty"`
|
||||
// The maximum amount of time a dial to server will wait for a connect to complete.
|
||||
DialServerTimeout int64 `json:"dialServerTimeout,omitempty"`
|
||||
// DialServerKeepAlive specifies the interval between keep-alive probes for an active network connection between frpc and frps.
|
||||
// If negative, keep-alive probes are disabled.
|
||||
DialServerKeepAlive int64 `json:"dialServerKeepalive,omitempty"`
|
||||
// ConnectServerLocalIP specifies the address of the client bind when it connect to server.
|
||||
// Note: This value only use in TCP/Websocket protocol. Not support in KCP protocol.
|
||||
ConnectServerLocalIP string `json:"connectServerLocalIP,omitempty"`
|
||||
// ProxyURL specifies a proxy address to connect to the server through. If
|
||||
// this value is "", the server will be connected to directly. By default,
|
||||
// this value is read from the "http_proxy" environment variable.
|
||||
ProxyURL string `json:"proxyURL,omitempty"`
|
||||
// PoolCount specifies the number of connections the client will make to
|
||||
// the server in advance.
|
||||
PoolCount int `json:"poolCount,omitempty"`
|
||||
// TCPMux toggles TCP stream multiplexing. This allows multiple requests
|
||||
// from a client to share a single TCP connection. If this value is true,
|
||||
// the server must have TCP multiplexing enabled as well. By default, this
|
||||
// value is true.
|
||||
TCPMux *bool `json:"tcpMux,omitempty"`
|
||||
// TCPMuxKeepaliveInterval specifies the keep alive interval for TCP stream multipler.
|
||||
// If TCPMux is true, heartbeat of application layer is unnecessary because it can only rely on heartbeat in TCPMux.
|
||||
TCPMuxKeepaliveInterval int64 `json:"tcpMuxKeepaliveInterval,omitempty"`
|
||||
// QUIC protocol options.
|
||||
QUIC QUICOptions `json:"quic,omitempty"`
|
||||
// HeartBeatInterval specifies at what interval heartbeats are sent to the
|
||||
// server, in seconds. It is not recommended to change this value. By
|
||||
// default, this value is 30. Set negative value to disable it.
|
||||
HeartbeatInterval int64 `json:"heartbeatInterval,omitempty"`
|
||||
// HeartBeatTimeout specifies the maximum allowed heartbeat response delay
|
||||
// before the connection is terminated, in seconds. It is not recommended
|
||||
// to change this value. By default, this value is 90. Set negative value to disable it.
|
||||
HeartbeatTimeout int64 `json:"heartbeatTimeout,omitempty"`
|
||||
// TLS specifies TLS settings for the connection to the server.
|
||||
TLS TLSClientConfig `json:"tls,omitempty"`
|
||||
}
|
||||
|
||||
func (c *ClientTransportConfig) Complete() {
|
||||
c.Protocol = util.EmptyOr(c.Protocol, "tcp")
|
||||
c.DialServerTimeout = util.EmptyOr(c.DialServerTimeout, 10)
|
||||
c.DialServerKeepAlive = util.EmptyOr(c.DialServerKeepAlive, 7200)
|
||||
c.ProxyURL = util.EmptyOr(c.ProxyURL, os.Getenv("http_proxy"))
|
||||
c.PoolCount = util.EmptyOr(c.PoolCount, 1)
|
||||
c.TCPMux = util.EmptyOr(c.TCPMux, lo.ToPtr(true))
|
||||
c.TCPMuxKeepaliveInterval = util.EmptyOr(c.TCPMuxKeepaliveInterval, 60)
|
||||
c.HeartbeatInterval = util.EmptyOr(c.HeartbeatInterval, 30)
|
||||
c.HeartbeatTimeout = util.EmptyOr(c.HeartbeatTimeout, 90)
|
||||
c.QUIC.Complete()
|
||||
c.TLS.Complete()
|
||||
}
|
||||
|
||||
type TLSClientConfig struct {
|
||||
// TLSEnable specifies whether or not TLS should be used when communicating
|
||||
// with the server. If "tls.certFile" and "tls.keyFile" are valid,
|
||||
// client will load the supplied tls configuration.
|
||||
// Since v0.50.0, the default value has been changed to true, and tls is enabled by default.
|
||||
Enable *bool `json:"enable,omitempty"`
|
||||
// If DisableCustomTLSFirstByte is set to false, frpc will establish a connection with frps using the
|
||||
// first custom byte when tls is enabled.
|
||||
// Since v0.50.0, the default value has been changed to true, and the first custom byte is disabled by default.
|
||||
DisableCustomTLSFirstByte *bool `json:"disableCustomTLSFirstByte,omitempty"`
|
||||
|
||||
TLSConfig
|
||||
}
|
||||
|
||||
func (c *TLSClientConfig) Complete() {
|
||||
c.Enable = util.EmptyOr(c.Enable, lo.ToPtr(true))
|
||||
c.DisableCustomTLSFirstByte = util.EmptyOr(c.DisableCustomTLSFirstByte, lo.ToPtr(true))
|
||||
}
|
||||
|
||||
type AuthClientConfig struct {
|
||||
// Method specifies what authentication method to use to
|
||||
// authenticate frpc with frps. If "token" is specified - token will be
|
||||
// read into login message. If "oidc" is specified - OIDC (Open ID Connect)
|
||||
// token will be issued using OIDC settings. By default, this value is "token".
|
||||
Method string `json:"method,omitempty"`
|
||||
// Specify whether to include auth info in additional scope.
|
||||
// Current supported scopes are: "HeartBeats", "NewWorkConns".
|
||||
AdditionalAuthScopes []AuthScope `json:"additionalAuthScopes,omitempty"`
|
||||
// Token specifies the authorization token used to create keys to be sent
|
||||
// to the server. The server must have a matching token for authorization
|
||||
// to succeed. By default, this value is "".
|
||||
Token string `json:"token,omitempty"`
|
||||
OIDC AuthOIDCClientConfig `json:"oidc,omitempty"`
|
||||
}
|
||||
|
||||
func (c *AuthClientConfig) Complete() {
|
||||
c.Method = util.EmptyOr(c.Method, "token")
|
||||
}
|
||||
|
||||
type AuthOIDCClientConfig struct {
|
||||
// ClientID specifies the client ID to use to get a token in OIDC authentication.
|
||||
ClientID string `json:"clientID,omitempty"`
|
||||
// ClientSecret specifies the client secret to use to get a token in OIDC
|
||||
// authentication.
|
||||
ClientSecret string `json:"clientSecret,omitempty"`
|
||||
// Audience specifies the audience of the token in OIDC authentication.
|
||||
Audience string `json:"audience,omitempty"`
|
||||
// Scope specifies the scope of the token in OIDC authentication.
|
||||
Scope string `json:"scope,omitempty"`
|
||||
// TokenEndpointURL specifies the URL which implements OIDC Token Endpoint.
|
||||
// It will be used to get an OIDC token.
|
||||
TokenEndpointURL string `json:"tokenEndpointURL,omitempty"`
|
||||
// AdditionalEndpointParams specifies additional parameters to be sent
|
||||
// this field will be transfer to map[string][]string in OIDC token generator.
|
||||
AdditionalEndpointParams map[string]string `json:"additionalEndpointParams,omitempty"`
|
||||
}
|
34
pkg/config/v1/client_test.go
Normal file
34
pkg/config/v1/client_test.go
Normal file
@@ -0,0 +1,34 @@
|
||||
// Copyright 2023 The frp Authors
|
||||
//
|
||||
// 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 v1
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/samber/lo"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestClientConfigComplete(t *testing.T) {
|
||||
require := require.New(t)
|
||||
c := &ClientConfig{}
|
||||
c.Complete()
|
||||
|
||||
require.Equal("token", c.Auth.Method)
|
||||
require.Equal(true, lo.FromPtr(c.Transport.TCPMux))
|
||||
require.Equal(true, lo.FromPtr(c.LoginFailExit))
|
||||
require.Equal(true, lo.FromPtr(c.Transport.TLS.Enable))
|
||||
require.Equal(true, lo.FromPtr(c.Transport.TLS.DisableCustomTLSFirstByte))
|
||||
}
|
110
pkg/config/v1/common.go
Normal file
110
pkg/config/v1/common.go
Normal file
@@ -0,0 +1,110 @@
|
||||
// Copyright 2023 The frp Authors
|
||||
//
|
||||
// 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 v1
|
||||
|
||||
import (
|
||||
"github.com/fatedier/frp/pkg/util/util"
|
||||
)
|
||||
|
||||
type AuthScope string
|
||||
|
||||
const (
|
||||
AuthScopeHeartBeats AuthScope = "HeartBeats"
|
||||
AuthScopeNewWorkConns AuthScope = "NewWorkConns"
|
||||
)
|
||||
|
||||
// QUIC protocol options
|
||||
type QUICOptions struct {
|
||||
KeepalivePeriod int `json:"quicKeepalivePeriod,omitempty" validate:"gte=0"`
|
||||
MaxIdleTimeout int `json:"quicMaxIdleTimeout,omitempty" validate:"gte=0"`
|
||||
MaxIncomingStreams int `json:"quicMaxIncomingStreams,omitempty" validate:"gte=0"`
|
||||
}
|
||||
|
||||
func (c *QUICOptions) Complete() {
|
||||
c.KeepalivePeriod = util.EmptyOr(c.KeepalivePeriod, 10)
|
||||
c.MaxIdleTimeout = util.EmptyOr(c.MaxIdleTimeout, 30)
|
||||
c.MaxIncomingStreams = util.EmptyOr(c.MaxIncomingStreams, 100000)
|
||||
}
|
||||
|
||||
type WebServerConfig struct {
|
||||
// This is the network address to bind on for serving the web interface and API.
|
||||
// By default, this value is "127.0.0.1".
|
||||
Addr string `json:"addr,omitempty"`
|
||||
// Port specifies the port for the web server to listen on. If this
|
||||
// value is 0, the admin server will not be started.
|
||||
Port int `json:"port,omitempty"`
|
||||
// User specifies the username that the web server will use for login.
|
||||
User string `json:"user,omitempty"`
|
||||
// Password specifies the password that the admin server will use for login.
|
||||
Password string `json:"password,omitempty"`
|
||||
// AssetsDir specifies the local directory that the admin server will load
|
||||
// resources from. If this value is "", assets will be loaded from the
|
||||
// bundled executable using embed package.
|
||||
AssetsDir string `json:"assetsDir,omitempty"`
|
||||
// Enable golang pprof handlers.
|
||||
PprofEnable bool `json:"pprofEnable,omitempty"`
|
||||
// Enable TLS if TLSConfig is not nil.
|
||||
TLS *TLSConfig `json:"tls,omitempty"`
|
||||
}
|
||||
|
||||
func (c *WebServerConfig) Complete() {
|
||||
c.Addr = util.EmptyOr(c.Addr, "127.0.0.1")
|
||||
}
|
||||
|
||||
type TLSConfig struct {
|
||||
// CertPath specifies the path of the cert file that client will load.
|
||||
CertFile string `json:"certFile,omitempty"`
|
||||
// KeyPath specifies the path of the secret key file that client will load.
|
||||
KeyFile string `json:"keyFile,omitempty"`
|
||||
// TrustedCaFile specifies the path of the trusted ca file that will load.
|
||||
TrustedCaFile string `json:"trustedCaFile,omitempty"`
|
||||
// ServerName specifies the custom server name of tls certificate. By
|
||||
// default, server name if same to ServerAddr.
|
||||
ServerName string `json:"serverName,omitempty"`
|
||||
}
|
||||
|
||||
type LogConfig struct {
|
||||
// This is destination where frp should wirte the logs.
|
||||
// If "console" is used, logs will be printed to stdout, otherwise,
|
||||
// logs will be written to the specified file.
|
||||
// By default, this value is "console".
|
||||
To string `json:"to,omitempty"`
|
||||
// Level specifies the minimum log level. Valid values are "trace",
|
||||
// "debug", "info", "warn", and "error". By default, this value is "info".
|
||||
Level string `json:"level,omitempty"`
|
||||
// MaxDays specifies the maximum number of days to store log information
|
||||
// before deletion.
|
||||
MaxDays int64 `json:"maxDays"`
|
||||
// DisablePrintColor disables log colors when log.to is "console".
|
||||
DisablePrintColor bool `json:"disablePrintColor,omitempty"`
|
||||
}
|
||||
|
||||
func (c *LogConfig) Complete() {
|
||||
c.To = util.EmptyOr(c.To, "console")
|
||||
c.Level = util.EmptyOr(c.Level, "info")
|
||||
c.MaxDays = util.EmptyOr(c.MaxDays, 3)
|
||||
}
|
||||
|
||||
type HTTPPluginOptions struct {
|
||||
Name string `json:"name"`
|
||||
Addr string `json:"addr"`
|
||||
Path string `json:"path"`
|
||||
Ops []string `json:"ops"`
|
||||
TLSVerify bool `json:"tls_verify,omitempty"`
|
||||
}
|
||||
|
||||
type HeaderOperations struct {
|
||||
Set map[string]string `json:"set,omitempty"`
|
||||
}
|
117
pkg/config/v1/plugin.go
Normal file
117
pkg/config/v1/plugin.go
Normal file
@@ -0,0 +1,117 @@
|
||||
// Copyright 2023 The frp Authors
|
||||
//
|
||||
// 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 v1
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
type ClientPluginOptions interface{}
|
||||
|
||||
type TypedClientPluginOptions struct {
|
||||
Type string `json:"type"`
|
||||
ClientPluginOptions
|
||||
}
|
||||
|
||||
func (c *TypedClientPluginOptions) UnmarshalJSON(b []byte) error {
|
||||
if len(b) == 4 && string(b) == "null" {
|
||||
return errors.New("type is required")
|
||||
}
|
||||
|
||||
typeStruct := struct {
|
||||
Type string `json:"type"`
|
||||
}{}
|
||||
if err := json.Unmarshal(b, &typeStruct); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.Type = typeStruct.Type
|
||||
|
||||
v, ok := clientPluginOptionsTypeMap[typeStruct.Type]
|
||||
if !ok {
|
||||
return fmt.Errorf("unknown plugin type: %s", typeStruct.Type)
|
||||
}
|
||||
if err := json.Unmarshal(b, v); err != nil {
|
||||
return err
|
||||
}
|
||||
c.ClientPluginOptions = v
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
PluginHTTP2HTTPS = "http2https"
|
||||
PluginHTTPProxy = "http_proxy"
|
||||
PluginHTTPS2HTTP = "https2http"
|
||||
PluginHTTPS2HTTPS = "https2https"
|
||||
PluginSocks5 = "socks5"
|
||||
PluginStaticFile = "static_file"
|
||||
PluginUnixDomainSocket = "unix_domain_socket"
|
||||
)
|
||||
|
||||
var clientPluginOptionsTypeMap = map[string]reflect.Type{
|
||||
PluginHTTP2HTTPS: reflect.TypeOf(HTTP2HTTPSPluginOptions{}),
|
||||
PluginHTTPProxy: reflect.TypeOf(HTTPProxyPluginOptions{}),
|
||||
PluginHTTPS2HTTP: reflect.TypeOf(HTTPS2HTTPPluginOptions{}),
|
||||
PluginHTTPS2HTTPS: reflect.TypeOf(HTTPS2HTTPSPluginOptions{}),
|
||||
PluginSocks5: reflect.TypeOf(Socks5PluginOptions{}),
|
||||
PluginStaticFile: reflect.TypeOf(StaticFilePluginOptions{}),
|
||||
PluginUnixDomainSocket: reflect.TypeOf(UnixDomainSocketPluginOptions{}),
|
||||
}
|
||||
|
||||
type HTTP2HTTPSPluginOptions struct {
|
||||
LocalAddr string `json:"localAddr,omitempty"`
|
||||
HostHeaderRewrite string `json:"hostHeaderRewrite,omitempty"`
|
||||
RequestHeaders HeaderOperations `json:"requestHeaders,omitempty"`
|
||||
}
|
||||
|
||||
type HTTPProxyPluginOptions struct {
|
||||
HTTPUser string `json:"httpUser,omitempty"`
|
||||
HTTPPassword string `json:"httpPassword,omitempty"`
|
||||
}
|
||||
|
||||
type HTTPS2HTTPPluginOptions struct {
|
||||
LocalAddr string `json:"localAddr,omitempty"`
|
||||
HostHeaderRewrite string `json:"hostHeaderRewrite,omitempty"`
|
||||
RequestHeaders HeaderOperations `json:"requestHeaders,omitempty"`
|
||||
CrtPath string `json:"crtPath,omitempty"`
|
||||
KeyPath string `json:"keyPath,omitempty"`
|
||||
}
|
||||
|
||||
type HTTPS2HTTPSPluginOptions struct {
|
||||
LocalAddr string `json:"localAddr,omitempty"`
|
||||
HostHeaderRewrite string `json:"hostHeaderRewrite,omitempty"`
|
||||
RequestHeaders HeaderOperations `json:"requestHeaders,omitempty"`
|
||||
CrtPath string `json:"crtPath,omitempty"`
|
||||
KeyPath string `json:"keyPath,omitempty"`
|
||||
}
|
||||
|
||||
type Socks5PluginOptions struct {
|
||||
Username string `json:"username,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
}
|
||||
|
||||
type StaticFilePluginOptions struct {
|
||||
LocalPath string `json:"localPath,omitempty"`
|
||||
StripPrefix string `json:"stripPrefix,omitempty"`
|
||||
HTTPUser string `json:"httpUser,omitempty"`
|
||||
HTTPPassword string `json:"httpPassword,omitempty"`
|
||||
}
|
||||
|
||||
type UnixDomainSocketPluginOptions struct {
|
||||
UnixPath string `json:"unixPath,omitempty"`
|
||||
}
|
420
pkg/config/v1/proxy.go
Normal file
420
pkg/config/v1/proxy.go
Normal file
@@ -0,0 +1,420 @@
|
||||
// Copyright 2023 The frp Authors
|
||||
//
|
||||
// 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 v1
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/samber/lo"
|
||||
|
||||
"github.com/fatedier/frp/pkg/config/types"
|
||||
"github.com/fatedier/frp/pkg/consts"
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
"github.com/fatedier/frp/pkg/util/util"
|
||||
)
|
||||
|
||||
type ProxyTransport struct {
|
||||
// UseEncryption controls whether or not communication with the server will
|
||||
// be encrypted. Encryption is done using the tokens supplied in the server
|
||||
// and client configuration.
|
||||
UseEncryption bool `json:"useEncryption,omitempty"`
|
||||
// UseCompression controls whether or not communication with the server
|
||||
// will be compressed.
|
||||
UseCompression bool `json:"useCompression,omitempty"`
|
||||
// BandwidthLimit limit the bandwidth
|
||||
// 0 means no limit
|
||||
BandwidthLimit types.BandwidthQuantity `json:"bandwidthLimit,omitempty"`
|
||||
// BandwidthLimitMode specifies whether to limit the bandwidth on the
|
||||
// client or server side. Valid values include "client" and "server".
|
||||
// By default, this value is "client".
|
||||
BandwidthLimitMode string `json:"bandwidthLimitMode,omitempty"`
|
||||
// ProxyProtocolVersion specifies which protocol version to use. Valid
|
||||
// values include "v1", "v2", and "". If the value is "", a protocol
|
||||
// version will be automatically selected. By default, this value is "".
|
||||
ProxyProtocolVersion string `json:"proxyProtocolVersion,omitempty"`
|
||||
}
|
||||
|
||||
type LoadBalancerConfig struct {
|
||||
// Group specifies which group the is a part of. The server will use
|
||||
// this information to load balance proxies in the same group. If the value
|
||||
// is "", this will not be in a group.
|
||||
Group string `json:"group"`
|
||||
// GroupKey specifies a group key, which should be the same among proxies
|
||||
// of the same group.
|
||||
GroupKey string `json:"groupKey,omitempty"`
|
||||
}
|
||||
|
||||
type ProxyBackend struct {
|
||||
// LocalIP specifies the IP address or host name of the backend.
|
||||
LocalIP string `json:"localIP,omitempty"`
|
||||
// LocalPort specifies the port of the backend.
|
||||
LocalPort int `json:"localPort,omitempty"`
|
||||
|
||||
// Plugin specifies what plugin should be used for handling connections. If this value
|
||||
// is set, the LocalIP and LocalPort values will be ignored.
|
||||
Plugin TypedClientPluginOptions `json:"plugin,omitempty"`
|
||||
}
|
||||
|
||||
// HealthCheckConfig configures health checking. This can be useful for load
|
||||
// balancing purposes to detect and remove proxies to failing services.
|
||||
type HealthCheckConfig struct {
|
||||
// Type specifies what protocol to use for health checking.
|
||||
// Valid values include "tcp", "http", and "". If this value is "", health
|
||||
// checking will not be performed.
|
||||
//
|
||||
// If the type is "tcp", a connection will be attempted to the target
|
||||
// server. If a connection cannot be established, the health check fails.
|
||||
//
|
||||
// If the type is "http", a GET request will be made to the endpoint
|
||||
// specified by HealthCheckURL. If the response is not a 200, the health
|
||||
// check fails.
|
||||
Type string `json:"type"` // tcp | http
|
||||
// TimeoutSeconds specifies the number of seconds to wait for a health
|
||||
// check attempt to connect. If the timeout is reached, this counts as a
|
||||
// health check failure. By default, this value is 3.
|
||||
TimeoutSeconds int `json:"timeoutSeconds,omitempty"`
|
||||
// MaxFailed specifies the number of allowed failures before the
|
||||
// is stopped. By default, this value is 1.
|
||||
MaxFailed int `json:"maxFailed,omitempty"`
|
||||
// IntervalSeconds specifies the time in seconds between health
|
||||
// checks. By default, this value is 10.
|
||||
IntervalSeconds int `json:"intervalSeconds"`
|
||||
// Path specifies the path to send health checks to if the
|
||||
// health check type is "http".
|
||||
Path string `json:"path,omitempty"`
|
||||
}
|
||||
|
||||
type DomainConfig struct {
|
||||
CustomDomains []string `json:"customDomains,omitempty"`
|
||||
SubDomain string `json:"subdomain,omitempty"`
|
||||
}
|
||||
|
||||
type ProxyBaseConfig struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Transport ProxyTransport `json:"transport,omitempty"`
|
||||
// metadata info for each proxy
|
||||
Metadatas map[string]string `json:"metadatas,omitempty"`
|
||||
LoadBalancer LoadBalancerConfig `json:"loadBalancer,omitempty"`
|
||||
HealthCheck HealthCheckConfig `json:"healthCheck,omitempty"`
|
||||
ProxyBackend
|
||||
}
|
||||
|
||||
func (c *ProxyBaseConfig) GetBaseConfig() *ProxyBaseConfig {
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *ProxyBaseConfig) Complete(namePrefix string) {
|
||||
c.Name = lo.Ternary(namePrefix == "", "", namePrefix+".") + c.Name
|
||||
c.LocalIP = util.EmptyOr(c.LocalIP, "127.0.0.1")
|
||||
c.Transport.BandwidthLimitMode = util.EmptyOr(c.Transport.BandwidthLimitMode, types.BandwidthLimitModeClient)
|
||||
}
|
||||
|
||||
func (c *ProxyBaseConfig) MarshalToMsg(m *msg.NewProxy) {
|
||||
m.ProxyName = c.Name
|
||||
m.ProxyType = c.Type
|
||||
m.UseEncryption = c.Transport.UseEncryption
|
||||
m.UseCompression = c.Transport.UseCompression
|
||||
m.BandwidthLimit = c.Transport.BandwidthLimit.String()
|
||||
// leave it empty for default value to reduce traffic
|
||||
if c.Transport.BandwidthLimitMode != "client" {
|
||||
m.BandwidthLimitMode = c.Transport.BandwidthLimitMode
|
||||
}
|
||||
m.Group = c.LoadBalancer.Group
|
||||
m.GroupKey = c.LoadBalancer.GroupKey
|
||||
m.Metas = c.Metadatas
|
||||
}
|
||||
|
||||
func (c *ProxyBaseConfig) UnmarshalFromMsg(m *msg.NewProxy) {
|
||||
c.Name = m.ProxyName
|
||||
c.Type = m.ProxyType
|
||||
c.Transport.UseEncryption = m.UseEncryption
|
||||
c.Transport.UseCompression = m.UseCompression
|
||||
if m.BandwidthLimit != "" {
|
||||
c.Transport.BandwidthLimit, _ = types.NewBandwidthQuantity(m.BandwidthLimit)
|
||||
}
|
||||
if m.BandwidthLimitMode != "" {
|
||||
c.Transport.BandwidthLimitMode = m.BandwidthLimitMode
|
||||
}
|
||||
c.LoadBalancer.Group = m.Group
|
||||
c.LoadBalancer.GroupKey = m.GroupKey
|
||||
c.Metadatas = m.Metas
|
||||
}
|
||||
|
||||
type TypedProxyConfig struct {
|
||||
Type string `json:"type"`
|
||||
ProxyConfigurer
|
||||
}
|
||||
|
||||
func (c *TypedProxyConfig) UnmarshalJSON(b []byte) error {
|
||||
if len(b) == 4 && string(b) == "null" {
|
||||
return errors.New("type is required")
|
||||
}
|
||||
|
||||
typeStruct := struct {
|
||||
Type string `json:"type"`
|
||||
}{}
|
||||
if err := json.Unmarshal(b, &typeStruct); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.Type = typeStruct.Type
|
||||
configurer := NewProxyConfigurerByType(typeStruct.Type)
|
||||
if configurer == nil {
|
||||
return fmt.Errorf("unknown proxy type: %s", typeStruct.Type)
|
||||
}
|
||||
if err := json.Unmarshal(b, configurer); err != nil {
|
||||
return err
|
||||
}
|
||||
c.ProxyConfigurer = configurer
|
||||
return nil
|
||||
}
|
||||
|
||||
type ProxyConfigurer interface {
|
||||
Complete(namePrefix string)
|
||||
GetBaseConfig() *ProxyBaseConfig
|
||||
// MarshalToMsg marshals this config into a msg.NewProxy message. This
|
||||
// function will be called on the frpc side.
|
||||
MarshalToMsg(*msg.NewProxy)
|
||||
// UnmarshalFromMsg unmarshals a msg.NewProxy message into this config.
|
||||
// This function will be called on the frps side.
|
||||
UnmarshalFromMsg(*msg.NewProxy)
|
||||
}
|
||||
|
||||
var proxyConfigTypeMap = map[string]reflect.Type{
|
||||
consts.TCPProxy: reflect.TypeOf(TCPProxyConfig{}),
|
||||
consts.UDPProxy: reflect.TypeOf(UDPProxyConfig{}),
|
||||
consts.HTTPProxy: reflect.TypeOf(HTTPProxyConfig{}),
|
||||
consts.HTTPSProxy: reflect.TypeOf(HTTPSProxyConfig{}),
|
||||
consts.TCPMuxProxy: reflect.TypeOf(TCPMuxProxyConfig{}),
|
||||
consts.STCPProxy: reflect.TypeOf(STCPProxyConfig{}),
|
||||
consts.XTCPProxy: reflect.TypeOf(XTCPProxyConfig{}),
|
||||
consts.SUDPProxy: reflect.TypeOf(SUDPProxyConfig{}),
|
||||
}
|
||||
|
||||
func NewProxyConfigurerByType(proxyType string) ProxyConfigurer {
|
||||
v, ok := proxyConfigTypeMap[proxyType]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return reflect.New(v).Interface().(ProxyConfigurer)
|
||||
}
|
||||
|
||||
var _ ProxyConfigurer = &TCPProxyConfig{}
|
||||
|
||||
type TCPProxyConfig struct {
|
||||
ProxyBaseConfig
|
||||
|
||||
RemotePort int `json:"remotePort,omitempty"`
|
||||
}
|
||||
|
||||
func (c *TCPProxyConfig) MarshalToMsg(m *msg.NewProxy) {
|
||||
c.ProxyBaseConfig.MarshalToMsg(m)
|
||||
|
||||
m.RemotePort = c.RemotePort
|
||||
}
|
||||
|
||||
func (c *TCPProxyConfig) UnmarshalFromMsg(m *msg.NewProxy) {
|
||||
c.ProxyBaseConfig.UnmarshalFromMsg(m)
|
||||
|
||||
c.RemotePort = m.RemotePort
|
||||
}
|
||||
|
||||
var _ ProxyConfigurer = &UDPProxyConfig{}
|
||||
|
||||
type UDPProxyConfig struct {
|
||||
ProxyBaseConfig
|
||||
|
||||
RemotePort int `json:"remotePort,omitempty"`
|
||||
}
|
||||
|
||||
func (c *UDPProxyConfig) MarshalToMsg(m *msg.NewProxy) {
|
||||
c.ProxyBaseConfig.MarshalToMsg(m)
|
||||
|
||||
m.RemotePort = c.RemotePort
|
||||
}
|
||||
|
||||
func (c *UDPProxyConfig) UnmarshalFromMsg(m *msg.NewProxy) {
|
||||
c.ProxyBaseConfig.UnmarshalFromMsg(m)
|
||||
|
||||
c.RemotePort = m.RemotePort
|
||||
}
|
||||
|
||||
var _ ProxyConfigurer = &HTTPProxyConfig{}
|
||||
|
||||
type HTTPProxyConfig struct {
|
||||
ProxyBaseConfig
|
||||
DomainConfig
|
||||
|
||||
Locations []string `json:"locations,omitempty"`
|
||||
HTTPUser string `json:"httpUser,omitempty"`
|
||||
HTTPPassword string `json:"httpPassword,omitempty"`
|
||||
HostHeaderRewrite string `json:"hostHeaderRewrite,omitempty"`
|
||||
RequestHeaders HeaderOperations `json:"requestHeaders,omitempty"`
|
||||
RouteByHTTPUser string `json:"routeByHttpUser,omitempty"`
|
||||
}
|
||||
|
||||
func (c *HTTPProxyConfig) MarshalToMsg(m *msg.NewProxy) {
|
||||
c.ProxyBaseConfig.MarshalToMsg(m)
|
||||
|
||||
m.CustomDomains = c.CustomDomains
|
||||
m.SubDomain = c.SubDomain
|
||||
m.Locations = c.Locations
|
||||
m.HostHeaderRewrite = c.HostHeaderRewrite
|
||||
m.HTTPUser = c.HTTPUser
|
||||
m.HTTPPwd = c.HTTPPassword
|
||||
m.Headers = c.RequestHeaders.Set
|
||||
m.RouteByHTTPUser = c.RouteByHTTPUser
|
||||
}
|
||||
|
||||
func (c *HTTPProxyConfig) UnmarshalFromMsg(m *msg.NewProxy) {
|
||||
c.ProxyBaseConfig.UnmarshalFromMsg(m)
|
||||
|
||||
c.CustomDomains = m.CustomDomains
|
||||
c.SubDomain = m.SubDomain
|
||||
c.Locations = m.Locations
|
||||
c.HostHeaderRewrite = m.HostHeaderRewrite
|
||||
c.HTTPUser = m.HTTPUser
|
||||
c.HTTPPassword = m.HTTPPwd
|
||||
c.RequestHeaders.Set = m.Headers
|
||||
c.RouteByHTTPUser = m.RouteByHTTPUser
|
||||
}
|
||||
|
||||
var _ ProxyConfigurer = &HTTPSProxyConfig{}
|
||||
|
||||
type HTTPSProxyConfig struct {
|
||||
ProxyBaseConfig
|
||||
DomainConfig
|
||||
}
|
||||
|
||||
func (c *HTTPSProxyConfig) MarshalToMsg(m *msg.NewProxy) {
|
||||
c.ProxyBaseConfig.MarshalToMsg(m)
|
||||
|
||||
m.CustomDomains = c.CustomDomains
|
||||
m.SubDomain = c.SubDomain
|
||||
}
|
||||
|
||||
func (c *HTTPSProxyConfig) UnmarshalFromMsg(m *msg.NewProxy) {
|
||||
c.ProxyBaseConfig.UnmarshalFromMsg(m)
|
||||
|
||||
c.CustomDomains = m.CustomDomains
|
||||
c.SubDomain = m.SubDomain
|
||||
}
|
||||
|
||||
var _ ProxyConfigurer = &TCPMuxProxyConfig{}
|
||||
|
||||
type TCPMuxProxyConfig struct {
|
||||
ProxyBaseConfig
|
||||
DomainConfig
|
||||
|
||||
HTTPUser string `json:"httpUser,omitempty"`
|
||||
HTTPPassword string `json:"httpPassword,omitempty"`
|
||||
RouteByHTTPUser string `json:"routeByHttpUser,omitempty"`
|
||||
Multiplexer string `json:"multiplexer,omitempty"`
|
||||
}
|
||||
|
||||
func (c *TCPMuxProxyConfig) MarshalToMsg(m *msg.NewProxy) {
|
||||
c.ProxyBaseConfig.MarshalToMsg(m)
|
||||
|
||||
m.CustomDomains = c.CustomDomains
|
||||
m.SubDomain = c.SubDomain
|
||||
m.Multiplexer = c.Multiplexer
|
||||
m.HTTPUser = c.HTTPUser
|
||||
m.HTTPPwd = c.HTTPPassword
|
||||
m.RouteByHTTPUser = c.RouteByHTTPUser
|
||||
}
|
||||
|
||||
func (c *TCPMuxProxyConfig) UnmarshalFromMsg(m *msg.NewProxy) {
|
||||
c.ProxyBaseConfig.UnmarshalFromMsg(m)
|
||||
|
||||
c.CustomDomains = m.CustomDomains
|
||||
c.SubDomain = m.SubDomain
|
||||
c.Multiplexer = m.Multiplexer
|
||||
c.HTTPUser = m.HTTPUser
|
||||
c.HTTPPassword = m.HTTPPwd
|
||||
c.RouteByHTTPUser = m.RouteByHTTPUser
|
||||
}
|
||||
|
||||
var _ ProxyConfigurer = &STCPProxyConfig{}
|
||||
|
||||
type STCPProxyConfig struct {
|
||||
ProxyBaseConfig
|
||||
|
||||
Secretkey string `json:"secretKey,omitempty"`
|
||||
AllowUsers []string `json:"allowUsers,omitempty"`
|
||||
}
|
||||
|
||||
func (c *STCPProxyConfig) MarshalToMsg(m *msg.NewProxy) {
|
||||
c.ProxyBaseConfig.MarshalToMsg(m)
|
||||
|
||||
m.Sk = c.Secretkey
|
||||
m.AllowUsers = c.AllowUsers
|
||||
}
|
||||
|
||||
func (c *STCPProxyConfig) UnmarshalFromMsg(m *msg.NewProxy) {
|
||||
c.ProxyBaseConfig.UnmarshalFromMsg(m)
|
||||
|
||||
c.Secretkey = m.Sk
|
||||
c.AllowUsers = m.AllowUsers
|
||||
}
|
||||
|
||||
var _ ProxyConfigurer = &XTCPProxyConfig{}
|
||||
|
||||
type XTCPProxyConfig struct {
|
||||
ProxyBaseConfig
|
||||
|
||||
Secretkey string `json:"secretKey,omitempty"`
|
||||
AllowUsers []string `json:"allowUsers,omitempty"`
|
||||
}
|
||||
|
||||
func (c *XTCPProxyConfig) MarshalToMsg(m *msg.NewProxy) {
|
||||
c.ProxyBaseConfig.MarshalToMsg(m)
|
||||
|
||||
m.Sk = c.Secretkey
|
||||
m.AllowUsers = c.AllowUsers
|
||||
}
|
||||
|
||||
func (c *XTCPProxyConfig) UnmarshalFromMsg(m *msg.NewProxy) {
|
||||
c.ProxyBaseConfig.UnmarshalFromMsg(m)
|
||||
|
||||
c.Secretkey = m.Sk
|
||||
c.AllowUsers = m.AllowUsers
|
||||
}
|
||||
|
||||
var _ ProxyConfigurer = &SUDPProxyConfig{}
|
||||
|
||||
type SUDPProxyConfig struct {
|
||||
ProxyBaseConfig
|
||||
|
||||
Secretkey string `json:"secretKey,omitempty"`
|
||||
AllowUsers []string `json:"allowUsers,omitempty"`
|
||||
}
|
||||
|
||||
func (c *SUDPProxyConfig) MarshalToMsg(m *msg.NewProxy) {
|
||||
c.ProxyBaseConfig.MarshalToMsg(m)
|
||||
|
||||
m.Sk = c.Secretkey
|
||||
m.AllowUsers = c.AllowUsers
|
||||
}
|
||||
|
||||
func (c *SUDPProxyConfig) UnmarshalFromMsg(m *msg.NewProxy) {
|
||||
c.ProxyBaseConfig.UnmarshalFromMsg(m)
|
||||
|
||||
c.Secretkey = m.Sk
|
||||
c.AllowUsers = m.AllowUsers
|
||||
}
|
49
pkg/config/v1/proxy_test.go
Normal file
49
pkg/config/v1/proxy_test.go
Normal file
@@ -0,0 +1,49 @@
|
||||
// Copyright 2023 The frp Authors
|
||||
//
|
||||
// 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 v1
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestUnmarshalTypedProxyConfig(t *testing.T) {
|
||||
require := require.New(t)
|
||||
proxyConfigs := struct {
|
||||
Proxies []TypedProxyConfig `json:"proxies,omitempty"`
|
||||
}{}
|
||||
|
||||
strs := `{
|
||||
"proxies": [
|
||||
{
|
||||
"type": "tcp",
|
||||
"localPort": 22,
|
||||
"remotePort": 6000
|
||||
},
|
||||
{
|
||||
"type": "http",
|
||||
"localPort": 80,
|
||||
"customDomains": ["www.example.com"]
|
||||
}
|
||||
]
|
||||
}`
|
||||
err := json.Unmarshal([]byte(strs), &proxyConfigs)
|
||||
require.NoError(err)
|
||||
|
||||
require.IsType(&TCPProxyConfig{}, proxyConfigs.Proxies[0].ProxyConfigurer)
|
||||
require.IsType(&HTTPProxyConfig{}, proxyConfigs.Proxies[1].ProxyConfigurer)
|
||||
}
|
190
pkg/config/v1/server.go
Normal file
190
pkg/config/v1/server.go
Normal file
@@ -0,0 +1,190 @@
|
||||
// Copyright 2023 The frp Authors
|
||||
//
|
||||
// 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 v1
|
||||
|
||||
import (
|
||||
"github.com/samber/lo"
|
||||
|
||||
"github.com/fatedier/frp/pkg/config/types"
|
||||
"github.com/fatedier/frp/pkg/util/util"
|
||||
)
|
||||
|
||||
type ServerConfig struct {
|
||||
APIMetadata
|
||||
|
||||
Auth AuthServerConfig `json:"auth,omitempty"`
|
||||
// BindAddr specifies the address that the server binds to. By default,
|
||||
// this value is "0.0.0.0".
|
||||
BindAddr string `json:"bindAddr,omitempty"`
|
||||
// BindPort specifies the port that the server listens on. By default, this
|
||||
// value is 7000.
|
||||
BindPort int `json:"bindPort,omitempty" validate:"gte=0,lte=65535"`
|
||||
// KCPBindPort specifies the KCP port that the server listens on. If this
|
||||
// value is 0, the server will not listen for KCP connections.
|
||||
KCPBindPort int `json:"kcpBindPort,omitempty" validate:"gte=0,lte=65535"`
|
||||
// QUICBindPort specifies the QUIC port that the server listens on.
|
||||
// Set this value to 0 will disable this feature.
|
||||
QUICBindPort int `json:"quicBindPort,omitempty" validate:"gte=0,lte=65535"`
|
||||
// ProxyBindAddr specifies the address that the proxy binds to. This value
|
||||
// may be the same as BindAddr.
|
||||
ProxyBindAddr string `json:"proxyBindAddr,omitempty"`
|
||||
// VhostHTTPPort specifies the port that the server listens for HTTP Vhost
|
||||
// requests. If this value is 0, the server will not listen for HTTP
|
||||
// requests.
|
||||
VhostHTTPPort int `json:"vhostHTTPPort,omitempty" validate:"gte=0,lte=65535"`
|
||||
// VhostHTTPTimeout specifies the response header timeout for the Vhost
|
||||
// HTTP server, in seconds. By default, this value is 60.
|
||||
VhostHTTPTimeout int64 `json:"vhostHTTPTimeout,omitempty"`
|
||||
// VhostHTTPSPort specifies the port that the server listens for HTTPS
|
||||
// Vhost requests. If this value is 0, the server will not listen for HTTPS
|
||||
// requests.
|
||||
VhostHTTPSPort int `json:"vhostHTTPSPort,omitempty" validate:"gte=0,lte=65535"`
|
||||
// TCPMuxHTTPConnectPort specifies the port that the server listens for TCP
|
||||
// HTTP CONNECT 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.
|
||||
TCPMuxHTTPConnectPort int `json:"tcpmuxHTTPConnectPort,omitempty" validate:"gte=0,lte=65535"`
|
||||
// If TCPMuxPassthrough is true, frps won't do any update on traffic.
|
||||
TCPMuxPassthrough bool `json:"tcpmuxPassthrough,omitempty"`
|
||||
// SubDomainHost specifies the domain that will be attached to sub-domains
|
||||
// requested by the client when using Vhost proxying. For example, if this
|
||||
// value is set to "frps.com" and the client requested the subdomain
|
||||
// "test", the resulting URL would be "test.frps.com".
|
||||
SubDomainHost string `json:"subdomainHost,omitempty"`
|
||||
// Custom404Page specifies a path to a custom 404 page to display. If this
|
||||
// value is "", a default page will be displayed.
|
||||
Custom404Page string `json:"custom404Page,omitempty"`
|
||||
|
||||
WebServer WebServerConfig `json:"webServer,omitempty"`
|
||||
// EnablePrometheus will export prometheus metrics on webserver address
|
||||
// in /metrics api.
|
||||
EnablePrometheus bool `json:"enablePrometheus,omitempty"`
|
||||
|
||||
Log LogConfig `json:"log,omitempty"`
|
||||
|
||||
Transport ServerTransportConfig `json:"transport,omitempty"`
|
||||
|
||||
TLS TLSServerConfig `json:"tls,omitempty"`
|
||||
|
||||
// DetailedErrorsToClient defines whether to send the specific error (with
|
||||
// debug info) to frpc. By default, this value is true.
|
||||
DetailedErrorsToClient *bool `json:"detailedErrorsToClient,omitempty"`
|
||||
// MaxPortsPerClient specifies the maximum number of ports a single client
|
||||
// may proxy to. If this value is 0, no limit will be applied.
|
||||
MaxPortsPerClient int64 `json:"maxPortsPerClient,omitempty"`
|
||||
// UserConnTimeout specifies the maximum time to wait for a work
|
||||
// connection. By default, this value is 10.
|
||||
UserConnTimeout int64 `json:"userConnTimeout,omitempty"`
|
||||
// UDPPacketSize specifies the UDP packet size
|
||||
// By default, this value is 1500
|
||||
UDPPacketSize int64 `json:"udpPacketSize,omitempty"`
|
||||
// NatHoleAnalysisDataReserveHours specifies the hours to reserve nat hole analysis data.
|
||||
NatHoleAnalysisDataReserveHours int64 `json:"natholeAnalysisDataReserveHours,omitempty"`
|
||||
|
||||
AllowPorts []types.PortsRange `json:"allowPorts,omitempty"`
|
||||
|
||||
HTTPPlugins []HTTPPluginOptions `json:"httpPlugins,omitempty"`
|
||||
}
|
||||
|
||||
func (c *ServerConfig) Complete() {
|
||||
c.Auth.Complete()
|
||||
c.Log.Complete()
|
||||
c.Transport.Complete()
|
||||
c.WebServer.Complete()
|
||||
|
||||
c.BindAddr = util.EmptyOr(c.BindAddr, "0.0.0.0")
|
||||
c.BindPort = util.EmptyOr(c.BindPort, 7000)
|
||||
if c.ProxyBindAddr == "" {
|
||||
c.ProxyBindAddr = c.BindAddr
|
||||
}
|
||||
if c.TLS.TrustedCaFile != "" {
|
||||
c.TLS.Force = true
|
||||
}
|
||||
|
||||
if c.WebServer.Port > 0 {
|
||||
c.WebServer.Addr = util.EmptyOr(c.WebServer.Addr, "0.0.0.0")
|
||||
}
|
||||
|
||||
c.VhostHTTPTimeout = util.EmptyOr(c.VhostHTTPTimeout, 60)
|
||||
c.DetailedErrorsToClient = util.EmptyOr(c.DetailedErrorsToClient, lo.ToPtr(true))
|
||||
c.UserConnTimeout = util.EmptyOr(c.UserConnTimeout, 10)
|
||||
c.UDPPacketSize = util.EmptyOr(c.UDPPacketSize, 1500)
|
||||
c.NatHoleAnalysisDataReserveHours = util.EmptyOr(c.NatHoleAnalysisDataReserveHours, 7*24)
|
||||
}
|
||||
|
||||
type AuthServerConfig struct {
|
||||
Method string `json:"method,omitempty"`
|
||||
AdditionalAuthScopes []AuthScope `json:"additionalAuthScopes,omitempty"`
|
||||
Token string `json:"token,omitempty"`
|
||||
OIDC AuthOIDCServerConfig `json:"oidc,omitempty"`
|
||||
}
|
||||
|
||||
func (c *AuthServerConfig) Complete() {
|
||||
c.Method = util.EmptyOr(c.Method, "token")
|
||||
}
|
||||
|
||||
type AuthOIDCServerConfig struct {
|
||||
// Issuer specifies the issuer to verify OIDC tokens with. This issuer
|
||||
// will be used to load public keys to verify signature and will be compared
|
||||
// with the issuer claim in the OIDC token.
|
||||
Issuer string `json:"issuer,omitempty"`
|
||||
// Audience specifies the audience OIDC tokens should contain when validated.
|
||||
// If this value is empty, audience ("client ID") verification will be skipped.
|
||||
Audience string `json:"audience,omitempty"`
|
||||
// SkipExpiryCheck specifies whether to skip checking if the OIDC token is
|
||||
// expired.
|
||||
SkipExpiryCheck bool `json:"skipExpiryCheck,omitempty"`
|
||||
// SkipIssuerCheck specifies whether to skip checking if the OIDC token's
|
||||
// issuer claim matches the issuer specified in OidcIssuer.
|
||||
SkipIssuerCheck bool `json:"skipIssuerCheck,omitempty"`
|
||||
}
|
||||
|
||||
type ServerTransportConfig struct {
|
||||
// TCPMux toggles TCP stream multiplexing. This allows multiple requests
|
||||
// from a client to share a single TCP connection. By default, this value
|
||||
// is true.
|
||||
TCPMux *bool `json:"tcpMux,omitempty"`
|
||||
// TCPMuxKeepaliveInterval specifies the keep alive interval for TCP stream multipler.
|
||||
// If TCPMux is true, heartbeat of application layer is unnecessary because it can only rely on heartbeat in TCPMux.
|
||||
TCPMuxKeepaliveInterval int64 `json:"tcpMuxKeepaliveInterval,omitempty"`
|
||||
// TCPKeepAlive specifies the interval between keep-alive probes for an active network connection between frpc and frps.
|
||||
// If negative, keep-alive probes are disabled.
|
||||
TCPKeepAlive int64 `json:"tcpKeepalive,omitempty"`
|
||||
// MaxPoolCount specifies the maximum pool size for each proxy. By default,
|
||||
// this value is 5.
|
||||
MaxPoolCount int64 `json:"maxPoolCount,omitempty"`
|
||||
// HeartBeatTimeout specifies the maximum time to wait for a heartbeat
|
||||
// before terminating the connection. It is not recommended to change this
|
||||
// value. By default, this value is 90. Set negative value to disable it.
|
||||
HeartbeatTimeout int64 `json:"heartbeatTimeout,omitempty"`
|
||||
// QUIC options.
|
||||
QUIC QUICOptions `json:"quic,omitempty"`
|
||||
}
|
||||
|
||||
func (c *ServerTransportConfig) Complete() {
|
||||
c.TCPMux = util.EmptyOr(c.TCPMux, lo.ToPtr(true))
|
||||
c.TCPMuxKeepaliveInterval = util.EmptyOr(c.TCPMuxKeepaliveInterval, 60)
|
||||
c.TCPKeepAlive = util.EmptyOr(c.TCPKeepAlive, 7200)
|
||||
c.MaxPoolCount = util.EmptyOr(c.MaxPoolCount, 5)
|
||||
c.HeartbeatTimeout = util.EmptyOr(c.HeartbeatTimeout, 90)
|
||||
c.QUIC.Complete()
|
||||
}
|
||||
|
||||
type TLSServerConfig struct {
|
||||
// Force specifies whether to only accept TLS-encrypted connections.
|
||||
Force bool `json:"force,omitempty"`
|
||||
|
||||
TLSConfig
|
||||
}
|
32
pkg/config/v1/server_test.go
Normal file
32
pkg/config/v1/server_test.go
Normal file
@@ -0,0 +1,32 @@
|
||||
// Copyright 2023 The frp Authors
|
||||
//
|
||||
// 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 v1
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/samber/lo"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestServerConfigComplete(t *testing.T) {
|
||||
require := require.New(t)
|
||||
c := &ServerConfig{}
|
||||
c.Complete()
|
||||
|
||||
require.Equal("token", c.Auth.Method)
|
||||
require.Equal(true, lo.FromPtr(c.Transport.TCPMux))
|
||||
require.Equal(true, lo.FromPtr(c.DetailedErrorsToClient))
|
||||
}
|
90
pkg/config/v1/validation/client.go
Normal file
90
pkg/config/v1/validation/client.go
Normal file
@@ -0,0 +1,90 @@
|
||||
// Copyright 2023 The frp Authors
|
||||
//
|
||||
// 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 validation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/samber/lo"
|
||||
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
)
|
||||
|
||||
func ValidateClientCommonConfig(c *v1.ClientCommonConfig) (Warning, error) {
|
||||
var (
|
||||
warnings Warning
|
||||
errs error
|
||||
)
|
||||
if c.Transport.HeartbeatTimeout > 0 && c.Transport.HeartbeatInterval > 0 {
|
||||
if c.Transport.HeartbeatTimeout < c.Transport.HeartbeatInterval {
|
||||
errs = AppendError(errs, fmt.Errorf("invalid transport.heartbeatTimeout, heartbeat timeout should not less than heartbeat interval"))
|
||||
}
|
||||
}
|
||||
|
||||
if !lo.FromPtr(c.Transport.TLS.Enable) {
|
||||
checkTLSConfig := func(name string, value string) Warning {
|
||||
if value != "" {
|
||||
return fmt.Errorf("%s is invalid when transport.tls.enable is false", name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
warnings = AppendError(warnings, checkTLSConfig("transport.tls.certFile", c.Transport.TLS.CertFile))
|
||||
warnings = AppendError(warnings, checkTLSConfig("transport.tls.keyFile", c.Transport.TLS.KeyFile))
|
||||
warnings = AppendError(warnings, checkTLSConfig("transport.tls.trustedCaFile", c.Transport.TLS.TrustedCaFile))
|
||||
}
|
||||
|
||||
if !lo.Contains([]string{"tcp", "kcp", "quic", "websocket", "wss"}, c.Transport.Protocol) {
|
||||
errs = AppendError(errs, fmt.Errorf("invalid transport.protocol, only support tcp, kcp, quic, websocket, wss"))
|
||||
}
|
||||
|
||||
for _, f := range c.IncludeConfigFiles {
|
||||
absDir, err := filepath.Abs(filepath.Dir(f))
|
||||
if err != nil {
|
||||
errs = AppendError(errs, fmt.Errorf("include: parse directory of %s failed: %v", f, err))
|
||||
continue
|
||||
}
|
||||
if _, err := os.Stat(absDir); os.IsNotExist(err) {
|
||||
errs = AppendError(errs, fmt.Errorf("include: directory of %s not exist", f))
|
||||
}
|
||||
}
|
||||
return warnings, errs
|
||||
}
|
||||
|
||||
func ValidateAllClientConfig(c *v1.ClientCommonConfig, pxyCfgs []v1.ProxyConfigurer, visitorCfgs []v1.VisitorConfigurer) (Warning, error) {
|
||||
var warnings Warning
|
||||
if c != nil {
|
||||
warning, err := ValidateClientCommonConfig(c)
|
||||
warnings = AppendError(warnings, warning)
|
||||
if err != nil {
|
||||
return err, warnings
|
||||
}
|
||||
}
|
||||
|
||||
for _, c := range pxyCfgs {
|
||||
if err := ValidateProxyConfigurerForClient(c); err != nil {
|
||||
return warnings, fmt.Errorf("proxy %s: %v", c.GetBaseConfig().Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, c := range visitorCfgs {
|
||||
if err := ValidateVisitorConfigurer(c); err != nil {
|
||||
return warnings, fmt.Errorf("visitor %s: %v", c.GetBaseConfig().Name, err)
|
||||
}
|
||||
}
|
||||
return warnings, nil
|
||||
}
|
41
pkg/config/v1/validation/common.go
Normal file
41
pkg/config/v1/validation/common.go
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright 2023 The frp Authors
|
||||
//
|
||||
// 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 validation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
)
|
||||
|
||||
func validateWebServerConfig(c *v1.WebServerConfig) error {
|
||||
if c.TLS != nil {
|
||||
if c.TLS.CertFile == "" {
|
||||
return fmt.Errorf("tls.certFile must be specified when tls is enabled")
|
||||
}
|
||||
if c.TLS.KeyFile == "" {
|
||||
return fmt.Errorf("tls.keyFile must be specified when tls is enabled")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidatePort checks that the network port is in range
|
||||
func ValidatePort(port int) error {
|
||||
if 0 <= port && port <= 65535 {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("port number %d must be in the range 0..65535", port)
|
||||
}
|
72
pkg/config/v1/validation/plugin.go
Normal file
72
pkg/config/v1/validation/plugin.go
Normal file
@@ -0,0 +1,72 @@
|
||||
// Copyright 2023 The frp Authors
|
||||
//
|
||||
// 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 validation
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
)
|
||||
|
||||
func ValidateClientPluginOptions(c v1.ClientPluginOptions) error {
|
||||
switch v := c.(type) {
|
||||
case *v1.HTTP2HTTPSPluginOptions:
|
||||
return validateHTTP2HTTPSPluginOptions(v)
|
||||
case *v1.HTTPS2HTTPPluginOptions:
|
||||
return validateHTTPS2HTTPPluginOptions(v)
|
||||
case *v1.HTTPS2HTTPSPluginOptions:
|
||||
return validateHTTPS2HTTPSPluginOptions(v)
|
||||
case *v1.StaticFilePluginOptions:
|
||||
return validateStaticFilePluginOptions(v)
|
||||
case *v1.UnixDomainSocketPluginOptions:
|
||||
return validateUnixDomainSocketPluginOptions(v)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateHTTP2HTTPSPluginOptions(c *v1.HTTP2HTTPSPluginOptions) error {
|
||||
if c.LocalAddr == "" {
|
||||
return errors.New("localAddr is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateHTTPS2HTTPPluginOptions(c *v1.HTTPS2HTTPPluginOptions) error {
|
||||
if c.LocalAddr == "" {
|
||||
return errors.New("localAddr is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateHTTPS2HTTPSPluginOptions(c *v1.HTTPS2HTTPSPluginOptions) error {
|
||||
if c.LocalAddr == "" {
|
||||
return errors.New("localAddr is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateStaticFilePluginOptions(c *v1.StaticFilePluginOptions) error {
|
||||
if c.LocalPath == "" {
|
||||
return errors.New("localPath is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateUnixDomainSocketPluginOptions(c *v1.UnixDomainSocketPluginOptions) error {
|
||||
if c.UnixPath == "" {
|
||||
return errors.New("unixPath is required")
|
||||
}
|
||||
return nil
|
||||
}
|
234
pkg/config/v1/validation/proxy.go
Normal file
234
pkg/config/v1/validation/proxy.go
Normal file
@@ -0,0 +1,234 @@
|
||||
// Copyright 2023 The frp Authors
|
||||
//
|
||||
// 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 validation
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/samber/lo"
|
||||
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
"github.com/fatedier/frp/pkg/consts"
|
||||
)
|
||||
|
||||
func validateProxyBaseConfigForClient(c *v1.ProxyBaseConfig) error {
|
||||
if c.Name == "" {
|
||||
return errors.New("name should not be empty")
|
||||
}
|
||||
|
||||
if !lo.Contains([]string{"", "v1", "v2"}, c.Transport.ProxyProtocolVersion) {
|
||||
return fmt.Errorf("not support proxy protocol version: %s", c.Transport.ProxyProtocolVersion)
|
||||
}
|
||||
|
||||
if !lo.Contains([]string{"client", "server"}, c.Transport.BandwidthLimitMode) {
|
||||
return fmt.Errorf("bandwidth limit mode should be client or server")
|
||||
}
|
||||
|
||||
if c.Plugin.Type == "" {
|
||||
if err := ValidatePort(c.LocalPort); err != nil {
|
||||
return fmt.Errorf("localPort: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if !lo.Contains([]string{"", "tcp", "http"}, c.HealthCheck.Type) {
|
||||
return fmt.Errorf("not support health check type: %s", c.HealthCheck.Type)
|
||||
}
|
||||
if c.HealthCheck.Type != "" {
|
||||
if c.HealthCheck.Type == "http" &&
|
||||
c.HealthCheck.Path == "" {
|
||||
return fmt.Errorf("health check path should not be empty")
|
||||
}
|
||||
}
|
||||
|
||||
if c.Plugin.Type != "" {
|
||||
if err := ValidateClientPluginOptions(c.Plugin.ClientPluginOptions); err != nil {
|
||||
return fmt.Errorf("plugin %s: %v", c.Plugin.Type, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateProxyBaseConfigForServer(c *v1.ProxyBaseConfig, s *v1.ServerConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateDomainConfigForClient(c *v1.DomainConfig) error {
|
||||
if c.SubDomain == "" && len(c.CustomDomains) == 0 {
|
||||
return errors.New("subdomain and custom domains should not be both empty")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateDomainConfigForServer(c *v1.DomainConfig, s *v1.ServerConfig) error {
|
||||
for _, domain := range c.CustomDomains {
|
||||
if s.SubDomainHost != "" && len(strings.Split(s.SubDomainHost, ".")) < len(strings.Split(domain, ".")) {
|
||||
if strings.Contains(domain, s.SubDomainHost) {
|
||||
return fmt.Errorf("custom domain [%s] should not belong to subdomain host [%s]", domain, s.SubDomainHost)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if c.SubDomain != "" {
|
||||
if s.SubDomainHost == "" {
|
||||
return errors.New("subdomain is not supported because this feature is not enabled in server")
|
||||
}
|
||||
|
||||
if strings.Contains(c.SubDomain, ".") || strings.Contains(c.SubDomain, "*") {
|
||||
return errors.New("'.' and '*' are not supported in subdomain")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValidateProxyConfigurerForClient(c v1.ProxyConfigurer) error {
|
||||
base := c.GetBaseConfig()
|
||||
if err := validateProxyBaseConfigForClient(base); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch v := c.(type) {
|
||||
case *v1.TCPProxyConfig:
|
||||
return validateTCPProxyConfigForClient(v)
|
||||
case *v1.UDPProxyConfig:
|
||||
return validateUDPProxyConfigForClient(v)
|
||||
case *v1.TCPMuxProxyConfig:
|
||||
return validateTCPMuxProxyConfigForClient(v)
|
||||
case *v1.HTTPProxyConfig:
|
||||
return validateHTTPProxyConfigForClient(v)
|
||||
case *v1.HTTPSProxyConfig:
|
||||
return validateHTTPSProxyConfigForClient(v)
|
||||
case *v1.STCPProxyConfig:
|
||||
return validateSTCPProxyConfigForClient(v)
|
||||
case *v1.XTCPProxyConfig:
|
||||
return validateXTCPProxyConfigForClient(v)
|
||||
case *v1.SUDPProxyConfig:
|
||||
return validateSUDPProxyConfigForClient(v)
|
||||
}
|
||||
return errors.New("unknown proxy config type")
|
||||
}
|
||||
|
||||
func validateTCPProxyConfigForClient(c *v1.TCPProxyConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateUDPProxyConfigForClient(c *v1.UDPProxyConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateTCPMuxProxyConfigForClient(c *v1.TCPMuxProxyConfig) error {
|
||||
if err := validateDomainConfigForClient(&c.DomainConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !lo.Contains([]string{consts.HTTPConnectTCPMultiplexer}, c.Multiplexer) {
|
||||
return fmt.Errorf("not support multiplexer: %s", c.Multiplexer)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateHTTPProxyConfigForClient(c *v1.HTTPProxyConfig) error {
|
||||
return validateDomainConfigForClient(&c.DomainConfig)
|
||||
}
|
||||
|
||||
func validateHTTPSProxyConfigForClient(c *v1.HTTPSProxyConfig) error {
|
||||
return validateDomainConfigForClient(&c.DomainConfig)
|
||||
}
|
||||
|
||||
func validateSTCPProxyConfigForClient(c *v1.STCPProxyConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateXTCPProxyConfigForClient(c *v1.XTCPProxyConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateSUDPProxyConfigForClient(c *v1.SUDPProxyConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValidateProxyConfigurerForServer(c v1.ProxyConfigurer, s *v1.ServerConfig) error {
|
||||
base := c.GetBaseConfig()
|
||||
if err := validateProxyBaseConfigForServer(base, s); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch v := c.(type) {
|
||||
case *v1.TCPProxyConfig:
|
||||
return validateTCPProxyConfigForServer(v, s)
|
||||
case *v1.UDPProxyConfig:
|
||||
return validateUDPProxyConfigForServer(v, s)
|
||||
case *v1.TCPMuxProxyConfig:
|
||||
return validateTCPMuxProxyConfigForServer(v, s)
|
||||
case *v1.HTTPProxyConfig:
|
||||
return validateHTTPProxyConfigForServer(v, s)
|
||||
case *v1.HTTPSProxyConfig:
|
||||
return validateHTTPSProxyConfigForServer(v, s)
|
||||
case *v1.STCPProxyConfig:
|
||||
return validateSTCPProxyConfigForServer(v, s)
|
||||
case *v1.XTCPProxyConfig:
|
||||
return validateXTCPProxyConfigForServer(v, s)
|
||||
case *v1.SUDPProxyConfig:
|
||||
return validateSUDPProxyConfigForServer(v, s)
|
||||
default:
|
||||
return errors.New("unknown proxy config type")
|
||||
}
|
||||
}
|
||||
|
||||
func validateTCPProxyConfigForServer(c *v1.TCPProxyConfig, s *v1.ServerConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateUDPProxyConfigForServer(c *v1.UDPProxyConfig, s *v1.ServerConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateTCPMuxProxyConfigForServer(c *v1.TCPMuxProxyConfig, s *v1.ServerConfig) error {
|
||||
if c.Multiplexer == consts.HTTPConnectTCPMultiplexer &&
|
||||
s.TCPMuxHTTPConnectPort == 0 {
|
||||
return fmt.Errorf("tcpmux with multiplexer httpconnect not supported because this feature is not enabled in server")
|
||||
}
|
||||
|
||||
return validateDomainConfigForServer(&c.DomainConfig, s)
|
||||
}
|
||||
|
||||
func validateHTTPProxyConfigForServer(c *v1.HTTPProxyConfig, s *v1.ServerConfig) error {
|
||||
if s.VhostHTTPPort == 0 {
|
||||
return fmt.Errorf("type [http] not supported when vhost http port is not set")
|
||||
}
|
||||
|
||||
return validateDomainConfigForServer(&c.DomainConfig, s)
|
||||
}
|
||||
|
||||
func validateHTTPSProxyConfigForServer(c *v1.HTTPSProxyConfig, s *v1.ServerConfig) error {
|
||||
if s.VhostHTTPSPort == 0 {
|
||||
return fmt.Errorf("type [https] not supported when vhost https port is not set")
|
||||
}
|
||||
|
||||
return validateDomainConfigForServer(&c.DomainConfig, s)
|
||||
}
|
||||
|
||||
func validateSTCPProxyConfigForServer(c *v1.STCPProxyConfig, s *v1.ServerConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateXTCPProxyConfigForServer(c *v1.XTCPProxyConfig, s *v1.ServerConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateSUDPProxyConfigForServer(c *v1.SUDPProxyConfig, s *v1.ServerConfig) error {
|
||||
return nil
|
||||
}
|
37
pkg/config/v1/validation/server.go
Normal file
37
pkg/config/v1/validation/server.go
Normal file
@@ -0,0 +1,37 @@
|
||||
// Copyright 2023 The frp Authors
|
||||
//
|
||||
// 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 validation
|
||||
|
||||
import (
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
)
|
||||
|
||||
func ValidateServerConfig(c *v1.ServerConfig) (Warning, error) {
|
||||
var (
|
||||
warnings Warning
|
||||
errs error
|
||||
)
|
||||
if err := validateWebServerConfig(&c.WebServer); err != nil {
|
||||
errs = AppendError(errs, err)
|
||||
}
|
||||
|
||||
errs = AppendError(errs, ValidatePort(c.BindPort))
|
||||
errs = AppendError(errs, ValidatePort(c.KCPBindPort))
|
||||
errs = AppendError(errs, ValidatePort(c.QUICBindPort))
|
||||
errs = AppendError(errs, ValidatePort(c.VhostHTTPPort))
|
||||
errs = AppendError(errs, ValidatePort(c.VhostHTTPSPort))
|
||||
errs = AppendError(errs, ValidatePort(c.TCPMuxHTTPConnectPort))
|
||||
return warnings, errs
|
||||
}
|
28
pkg/config/v1/validation/validation.go
Normal file
28
pkg/config/v1/validation/validation.go
Normal file
@@ -0,0 +1,28 @@
|
||||
// Copyright 2023 The frp Authors
|
||||
//
|
||||
// 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 validation
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
type Warning error
|
||||
|
||||
func AppendError(err error, errs ...error) error {
|
||||
if len(errs) == 0 {
|
||||
return err
|
||||
}
|
||||
return errors.Join(append([]error{err}, errs...)...)
|
||||
}
|
59
pkg/config/v1/validation/visitor.go
Normal file
59
pkg/config/v1/validation/visitor.go
Normal file
@@ -0,0 +1,59 @@
|
||||
// Copyright 2023 The frp Authors
|
||||
//
|
||||
// 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 validation
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/samber/lo"
|
||||
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
)
|
||||
|
||||
func ValidateVisitorConfigurer(c v1.VisitorConfigurer) error {
|
||||
base := c.GetBaseConfig()
|
||||
if err := validateVisitorBaseConfig(base); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch v := c.(type) {
|
||||
case *v1.STCPVisitorConfig:
|
||||
case *v1.SUDPVisitorConfig:
|
||||
case *v1.XTCPVisitorConfig:
|
||||
return validateXTCPVisitorConfig(v)
|
||||
default:
|
||||
return errors.New("unknown visitor config type")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateVisitorBaseConfig(c *v1.VisitorBaseConfig) error {
|
||||
if c.Name == "" {
|
||||
return errors.New("name should not be empty")
|
||||
}
|
||||
|
||||
if c.BindPort == 0 {
|
||||
return errors.New("bind port is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateXTCPVisitorConfig(c *v1.XTCPVisitorConfig) error {
|
||||
if !lo.Contains([]string{"kcp", "quic"}, c.Protocol) {
|
||||
return fmt.Errorf("protocol should be kcp or quic")
|
||||
}
|
||||
return nil
|
||||
}
|
155
pkg/config/v1/visitor.go
Normal file
155
pkg/config/v1/visitor.go
Normal file
@@ -0,0 +1,155 @@
|
||||
// Copyright 2023 The frp Authors
|
||||
//
|
||||
// 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 v1
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/samber/lo"
|
||||
|
||||
"github.com/fatedier/frp/pkg/consts"
|
||||
"github.com/fatedier/frp/pkg/util/util"
|
||||
)
|
||||
|
||||
type VisitorTransport struct {
|
||||
UseEncryption bool `json:"useEncryption,omitempty"`
|
||||
UseCompression bool `json:"useCompression,omitempty"`
|
||||
}
|
||||
|
||||
type VisitorBaseConfig struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Transport VisitorTransport `json:"transport,omitempty"`
|
||||
SecretKey string `json:"sk,omitempty"`
|
||||
// if the server user is not set, it defaults to the current user
|
||||
ServerUser string `json:"serverUser,omitempty"`
|
||||
ServerName string `json:"serverName,omitempty"`
|
||||
BindAddr string `json:"bindAddr,omitempty"`
|
||||
// BindPort is the port that visitor listens on.
|
||||
// It can be less than 0, it means don't bind to the port and only receive connections redirected from
|
||||
// other visitors. (This is not supported for SUDP now)
|
||||
BindPort int `json:"bindPort,omitempty"`
|
||||
}
|
||||
|
||||
func (c *VisitorBaseConfig) GetBaseConfig() *VisitorBaseConfig {
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *VisitorBaseConfig) Complete(g *ClientCommonConfig) {
|
||||
if c.BindAddr == "" {
|
||||
c.BindAddr = "127.0.0.1"
|
||||
}
|
||||
|
||||
namePrefix := ""
|
||||
if g.User != "" {
|
||||
namePrefix = g.User + "."
|
||||
}
|
||||
c.Name = namePrefix + c.Name
|
||||
|
||||
if c.ServerUser != "" {
|
||||
c.ServerName = c.ServerUser + "." + c.ServerName
|
||||
} else {
|
||||
c.ServerName = namePrefix + c.ServerName
|
||||
}
|
||||
}
|
||||
|
||||
type VisitorConfigurer interface {
|
||||
Complete(*ClientCommonConfig)
|
||||
GetBaseConfig() *VisitorBaseConfig
|
||||
}
|
||||
|
||||
var visitorConfigTypeMap = map[string]reflect.Type{
|
||||
consts.STCPProxy: reflect.TypeOf(STCPVisitorConfig{}),
|
||||
consts.XTCPProxy: reflect.TypeOf(XTCPVisitorConfig{}),
|
||||
consts.SUDPProxy: reflect.TypeOf(SUDPVisitorConfig{}),
|
||||
}
|
||||
|
||||
type TypedVisitorConfig struct {
|
||||
Type string `json:"type"`
|
||||
VisitorConfigurer
|
||||
}
|
||||
|
||||
func (c *TypedVisitorConfig) UnmarshalJSON(b []byte) error {
|
||||
if len(b) == 4 && string(b) == "null" {
|
||||
return errors.New("type is required")
|
||||
}
|
||||
|
||||
typeStruct := struct {
|
||||
Type string `json:"type"`
|
||||
}{}
|
||||
if err := json.Unmarshal(b, &typeStruct); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.Type = typeStruct.Type
|
||||
configurer := NewVisitorConfigurerByType(typeStruct.Type)
|
||||
if configurer == nil {
|
||||
return fmt.Errorf("unknown visitor type: %s" + typeStruct.Type)
|
||||
}
|
||||
if err := json.Unmarshal(b, configurer); err != nil {
|
||||
return err
|
||||
}
|
||||
c.VisitorConfigurer = configurer
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewVisitorConfigurerByType(t string) VisitorConfigurer {
|
||||
v, ok := visitorConfigTypeMap[t]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return reflect.New(v).Interface().(VisitorConfigurer)
|
||||
}
|
||||
|
||||
var _ VisitorConfigurer = &STCPVisitorConfig{}
|
||||
|
||||
type STCPVisitorConfig struct {
|
||||
VisitorBaseConfig
|
||||
}
|
||||
|
||||
var _ VisitorConfigurer = &SUDPVisitorConfig{}
|
||||
|
||||
type SUDPVisitorConfig struct {
|
||||
VisitorBaseConfig
|
||||
}
|
||||
|
||||
var _ VisitorConfigurer = &XTCPVisitorConfig{}
|
||||
|
||||
type XTCPVisitorConfig struct {
|
||||
VisitorBaseConfig
|
||||
|
||||
Protocol string `json:"protocol,omitempty"`
|
||||
KeepTunnelOpen bool `json:"keepTunnelOpen,omitempty"`
|
||||
MaxRetriesAnHour int `json:"maxRetriesAnHour,omitempty"`
|
||||
MinRetryInterval int `json:"minRetryInterval,omitempty"`
|
||||
FallbackTo string `json:"fallbackTo,omitempty"`
|
||||
FallbackTimeoutMs int `json:"fallbackTimeoutMs,omitempty"`
|
||||
}
|
||||
|
||||
func (c *XTCPVisitorConfig) Complete(g *ClientCommonConfig) {
|
||||
c.VisitorBaseConfig.Complete(g)
|
||||
|
||||
c.Protocol = util.EmptyOr(c.Protocol, "quic")
|
||||
c.MaxRetriesAnHour = util.EmptyOr(c.MaxRetriesAnHour, 8)
|
||||
c.MinRetryInterval = util.EmptyOr(c.MinRetryInterval, 90)
|
||||
c.FallbackTimeoutMs = util.EmptyOr(c.FallbackTimeoutMs, 1000)
|
||||
|
||||
if c.FallbackTo != "" {
|
||||
c.FallbackTo = lo.Ternary(g.User == "", "", g.User+".") + c.FallbackTo
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user