mirror of
https://github.com/fatedier/frp.git
synced 2025-07-27 07:35:07 +00:00
support yaml/json/toml configuration format, make ini deprecated (#3599)
This commit is contained in:
@@ -17,76 +17,26 @@ package auth
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
"github.com/fatedier/frp/pkg/consts"
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
)
|
||||
|
||||
type BaseConfig struct {
|
||||
// AuthenticationMethod 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".
|
||||
AuthenticationMethod string `ini:"authentication_method" json:"authentication_method"`
|
||||
// AuthenticateHeartBeats specifies whether to include authentication token in
|
||||
// heartbeats sent to frps. By default, this value is false.
|
||||
AuthenticateHeartBeats bool `ini:"authenticate_heartbeats" json:"authenticate_heartbeats"`
|
||||
// AuthenticateNewWorkConns specifies whether to include authentication token in
|
||||
// new work connections sent to frps. By default, this value is false.
|
||||
AuthenticateNewWorkConns bool `ini:"authenticate_new_work_conns" json:"authenticate_new_work_conns"`
|
||||
}
|
||||
|
||||
func getDefaultBaseConf() BaseConfig {
|
||||
return BaseConfig{
|
||||
AuthenticationMethod: "token",
|
||||
AuthenticateHeartBeats: false,
|
||||
AuthenticateNewWorkConns: false,
|
||||
}
|
||||
}
|
||||
|
||||
type ClientConfig struct {
|
||||
BaseConfig `ini:",extends"`
|
||||
OidcClientConfig `ini:",extends"`
|
||||
TokenConfig `ini:",extends"`
|
||||
}
|
||||
|
||||
func GetDefaultClientConf() ClientConfig {
|
||||
return ClientConfig{
|
||||
BaseConfig: getDefaultBaseConf(),
|
||||
OidcClientConfig: getDefaultOidcClientConf(),
|
||||
TokenConfig: getDefaultTokenConf(),
|
||||
}
|
||||
}
|
||||
|
||||
type ServerConfig struct {
|
||||
BaseConfig `ini:",extends"`
|
||||
OidcServerConfig `ini:",extends"`
|
||||
TokenConfig `ini:",extends"`
|
||||
}
|
||||
|
||||
func GetDefaultServerConf() ServerConfig {
|
||||
return ServerConfig{
|
||||
BaseConfig: getDefaultBaseConf(),
|
||||
OidcServerConfig: getDefaultOidcServerConf(),
|
||||
TokenConfig: getDefaultTokenConf(),
|
||||
}
|
||||
}
|
||||
|
||||
type Setter interface {
|
||||
SetLogin(*msg.Login) error
|
||||
SetPing(*msg.Ping) error
|
||||
SetNewWorkConn(*msg.NewWorkConn) error
|
||||
}
|
||||
|
||||
func NewAuthSetter(cfg ClientConfig) (authProvider Setter) {
|
||||
switch cfg.AuthenticationMethod {
|
||||
func NewAuthSetter(cfg v1.AuthClientConfig) (authProvider Setter) {
|
||||
switch cfg.Method {
|
||||
case consts.TokenAuthMethod:
|
||||
authProvider = NewTokenAuth(cfg.BaseConfig, cfg.TokenConfig)
|
||||
authProvider = NewTokenAuth(cfg.AdditionalAuthScopes, cfg.Token)
|
||||
case consts.OidcAuthMethod:
|
||||
authProvider = NewOidcAuthSetter(cfg.BaseConfig, cfg.OidcClientConfig)
|
||||
authProvider = NewOidcAuthSetter(cfg.AdditionalAuthScopes, cfg.OIDC)
|
||||
default:
|
||||
panic(fmt.Sprintf("wrong authentication method: '%s'", cfg.AuthenticationMethod))
|
||||
panic(fmt.Sprintf("wrong method: '%s'", cfg.Method))
|
||||
}
|
||||
|
||||
return authProvider
|
||||
}
|
||||
|
||||
@@ -96,13 +46,12 @@ type Verifier interface {
|
||||
VerifyNewWorkConn(*msg.NewWorkConn) error
|
||||
}
|
||||
|
||||
func NewAuthVerifier(cfg ServerConfig) (authVerifier Verifier) {
|
||||
switch cfg.AuthenticationMethod {
|
||||
func NewAuthVerifier(cfg v1.AuthServerConfig) (authVerifier Verifier) {
|
||||
switch cfg.Method {
|
||||
case consts.TokenAuthMethod:
|
||||
authVerifier = NewTokenAuth(cfg.BaseConfig, cfg.TokenConfig)
|
||||
authVerifier = NewTokenAuth(cfg.AdditionalAuthScopes, cfg.Token)
|
||||
case consts.OidcAuthMethod:
|
||||
authVerifier = NewOidcAuthVerifier(cfg.BaseConfig, cfg.OidcServerConfig)
|
||||
authVerifier = NewOidcAuthVerifier(cfg.AdditionalAuthScopes, cfg.OIDC)
|
||||
}
|
||||
|
||||
return authVerifier
|
||||
}
|
||||
|
145
pkg/auth/legacy/legacy.go
Normal file
145
pkg/auth/legacy/legacy.go
Normal file
@@ -0,0 +1,145 @@
|
||||
// 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 legacy
|
||||
|
||||
type BaseConfig struct {
|
||||
// AuthenticationMethod 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".
|
||||
AuthenticationMethod string `ini:"authentication_method" json:"authentication_method"`
|
||||
// AuthenticateHeartBeats specifies whether to include authentication token in
|
||||
// heartbeats sent to frps. By default, this value is false.
|
||||
AuthenticateHeartBeats bool `ini:"authenticate_heartbeats" json:"authenticate_heartbeats"`
|
||||
// AuthenticateNewWorkConns specifies whether to include authentication token in
|
||||
// new work connections sent to frps. By default, this value is false.
|
||||
AuthenticateNewWorkConns bool `ini:"authenticate_new_work_conns" json:"authenticate_new_work_conns"`
|
||||
}
|
||||
|
||||
func getDefaultBaseConf() BaseConfig {
|
||||
return BaseConfig{
|
||||
AuthenticationMethod: "token",
|
||||
AuthenticateHeartBeats: false,
|
||||
AuthenticateNewWorkConns: false,
|
||||
}
|
||||
}
|
||||
|
||||
type ClientConfig struct {
|
||||
BaseConfig `ini:",extends"`
|
||||
OidcClientConfig `ini:",extends"`
|
||||
TokenConfig `ini:",extends"`
|
||||
}
|
||||
|
||||
func GetDefaultClientConf() ClientConfig {
|
||||
return ClientConfig{
|
||||
BaseConfig: getDefaultBaseConf(),
|
||||
OidcClientConfig: getDefaultOidcClientConf(),
|
||||
TokenConfig: getDefaultTokenConf(),
|
||||
}
|
||||
}
|
||||
|
||||
type ServerConfig struct {
|
||||
BaseConfig `ini:",extends"`
|
||||
OidcServerConfig `ini:",extends"`
|
||||
TokenConfig `ini:",extends"`
|
||||
}
|
||||
|
||||
func GetDefaultServerConf() ServerConfig {
|
||||
return ServerConfig{
|
||||
BaseConfig: getDefaultBaseConf(),
|
||||
OidcServerConfig: getDefaultOidcServerConf(),
|
||||
TokenConfig: getDefaultTokenConf(),
|
||||
}
|
||||
}
|
||||
|
||||
type OidcClientConfig struct {
|
||||
// OidcClientID specifies the client ID to use to get a token in OIDC
|
||||
// authentication if AuthenticationMethod == "oidc". By default, this value
|
||||
// is "".
|
||||
OidcClientID string `ini:"oidc_client_id" json:"oidc_client_id"`
|
||||
// OidcClientSecret specifies the client secret to use to get a token in OIDC
|
||||
// authentication if AuthenticationMethod == "oidc". By default, this value
|
||||
// is "".
|
||||
OidcClientSecret string `ini:"oidc_client_secret" json:"oidc_client_secret"`
|
||||
// OidcAudience specifies the audience of the token in OIDC authentication
|
||||
// if AuthenticationMethod == "oidc". By default, this value is "".
|
||||
OidcAudience string `ini:"oidc_audience" json:"oidc_audience"`
|
||||
// OidcScope specifies the scope of the token in OIDC authentication
|
||||
// if AuthenticationMethod == "oidc". By default, this value is "".
|
||||
OidcScope string `ini:"oidc_scope" json:"oidc_scope"`
|
||||
// OidcTokenEndpointURL specifies the URL which implements OIDC Token Endpoint.
|
||||
// It will be used to get an OIDC token if AuthenticationMethod == "oidc".
|
||||
// By default, this value is "".
|
||||
OidcTokenEndpointURL string `ini:"oidc_token_endpoint_url" json:"oidc_token_endpoint_url"`
|
||||
|
||||
// OidcAdditionalEndpointParams specifies additional parameters to be sent
|
||||
// this field will be transfer to map[string][]string in OIDC token generator
|
||||
// The field will be set by prefix "oidc_additional_"
|
||||
OidcAdditionalEndpointParams map[string]string `ini:"-" json:"oidc_additional_endpoint_params"`
|
||||
}
|
||||
|
||||
func getDefaultOidcClientConf() OidcClientConfig {
|
||||
return OidcClientConfig{
|
||||
OidcClientID: "",
|
||||
OidcClientSecret: "",
|
||||
OidcAudience: "",
|
||||
OidcScope: "",
|
||||
OidcTokenEndpointURL: "",
|
||||
OidcAdditionalEndpointParams: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
type OidcServerConfig struct {
|
||||
// OidcIssuer 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. It will be used if
|
||||
// AuthenticationMethod == "oidc". By default, this value is "".
|
||||
OidcIssuer string `ini:"oidc_issuer" json:"oidc_issuer"`
|
||||
// OidcAudience specifies the audience OIDC tokens should contain when validated.
|
||||
// If this value is empty, audience ("client ID") verification will be skipped.
|
||||
// It will be used when AuthenticationMethod == "oidc". By default, this
|
||||
// value is "".
|
||||
OidcAudience string `ini:"oidc_audience" json:"oidc_audience"`
|
||||
// OidcSkipExpiryCheck specifies whether to skip checking if the OIDC token is
|
||||
// expired. It will be used when AuthenticationMethod == "oidc". By default, this
|
||||
// value is false.
|
||||
OidcSkipExpiryCheck bool `ini:"oidc_skip_expiry_check" json:"oidc_skip_expiry_check"`
|
||||
// OidcSkipIssuerCheck specifies whether to skip checking if the OIDC token's
|
||||
// issuer claim matches the issuer specified in OidcIssuer. It will be used when
|
||||
// AuthenticationMethod == "oidc". By default, this value is false.
|
||||
OidcSkipIssuerCheck bool `ini:"oidc_skip_issuer_check" json:"oidc_skip_issuer_check"`
|
||||
}
|
||||
|
||||
func getDefaultOidcServerConf() OidcServerConfig {
|
||||
return OidcServerConfig{
|
||||
OidcIssuer: "",
|
||||
OidcAudience: "",
|
||||
OidcSkipExpiryCheck: false,
|
||||
OidcSkipIssuerCheck: false,
|
||||
}
|
||||
}
|
||||
|
||||
type TokenConfig struct {
|
||||
// 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 `ini:"token" json:"token"`
|
||||
}
|
||||
|
||||
func getDefaultTokenConf() TokenConfig {
|
||||
return TokenConfig{
|
||||
Token: "",
|
||||
}
|
||||
}
|
117
pkg/auth/oidc.go
117
pkg/auth/oidc.go
@@ -19,105 +19,40 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/coreos/go-oidc/v3/oidc"
|
||||
"github.com/samber/lo"
|
||||
"golang.org/x/oauth2/clientcredentials"
|
||||
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
)
|
||||
|
||||
type OidcClientConfig struct {
|
||||
// OidcClientID specifies the client ID to use to get a token in OIDC
|
||||
// authentication if AuthenticationMethod == "oidc". By default, this value
|
||||
// is "".
|
||||
OidcClientID string `ini:"oidc_client_id" json:"oidc_client_id"`
|
||||
// OidcClientSecret specifies the client secret to use to get a token in OIDC
|
||||
// authentication if AuthenticationMethod == "oidc". By default, this value
|
||||
// is "".
|
||||
OidcClientSecret string `ini:"oidc_client_secret" json:"oidc_client_secret"`
|
||||
// OidcAudience specifies the audience of the token in OIDC authentication
|
||||
// if AuthenticationMethod == "oidc". By default, this value is "".
|
||||
OidcAudience string `ini:"oidc_audience" json:"oidc_audience"`
|
||||
// OidcScope specifies the scope of the token in OIDC authentication
|
||||
// if AuthenticationMethod == "oidc". By default, this value is "".
|
||||
OidcScope string `ini:"oidc_scope" json:"oidc_scope"`
|
||||
// OidcTokenEndpointURL specifies the URL which implements OIDC Token Endpoint.
|
||||
// It will be used to get an OIDC token if AuthenticationMethod == "oidc".
|
||||
// By default, this value is "".
|
||||
OidcTokenEndpointURL string `ini:"oidc_token_endpoint_url" json:"oidc_token_endpoint_url"`
|
||||
|
||||
// OidcAdditionalEndpointParams specifies additional parameters to be sent
|
||||
// this field will be transfer to map[string][]string in OIDC token generator
|
||||
// The field will be set by prefix "oidc_additional_"
|
||||
OidcAdditionalEndpointParams map[string]string `ini:"-" json:"oidc_additional_endpoint_params"`
|
||||
}
|
||||
|
||||
func getDefaultOidcClientConf() OidcClientConfig {
|
||||
return OidcClientConfig{
|
||||
OidcClientID: "",
|
||||
OidcClientSecret: "",
|
||||
OidcAudience: "",
|
||||
OidcScope: "",
|
||||
OidcTokenEndpointURL: "",
|
||||
OidcAdditionalEndpointParams: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
type OidcServerConfig struct {
|
||||
// OidcIssuer 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. It will be used if
|
||||
// AuthenticationMethod == "oidc". By default, this value is "".
|
||||
OidcIssuer string `ini:"oidc_issuer" json:"oidc_issuer"`
|
||||
// OidcAudience specifies the audience OIDC tokens should contain when validated.
|
||||
// If this value is empty, audience ("client ID") verification will be skipped.
|
||||
// It will be used when AuthenticationMethod == "oidc". By default, this
|
||||
// value is "".
|
||||
OidcAudience string `ini:"oidc_audience" json:"oidc_audience"`
|
||||
// OidcSkipExpiryCheck specifies whether to skip checking if the OIDC token is
|
||||
// expired. It will be used when AuthenticationMethod == "oidc". By default, this
|
||||
// value is false.
|
||||
OidcSkipExpiryCheck bool `ini:"oidc_skip_expiry_check" json:"oidc_skip_expiry_check"`
|
||||
// OidcSkipIssuerCheck specifies whether to skip checking if the OIDC token's
|
||||
// issuer claim matches the issuer specified in OidcIssuer. It will be used when
|
||||
// AuthenticationMethod == "oidc". By default, this value is false.
|
||||
OidcSkipIssuerCheck bool `ini:"oidc_skip_issuer_check" json:"oidc_skip_issuer_check"`
|
||||
}
|
||||
|
||||
func getDefaultOidcServerConf() OidcServerConfig {
|
||||
return OidcServerConfig{
|
||||
OidcIssuer: "",
|
||||
OidcAudience: "",
|
||||
OidcSkipExpiryCheck: false,
|
||||
OidcSkipIssuerCheck: false,
|
||||
}
|
||||
}
|
||||
|
||||
type OidcAuthProvider struct {
|
||||
BaseConfig
|
||||
additionalAuthScopes []v1.AuthScope
|
||||
|
||||
tokenGenerator *clientcredentials.Config
|
||||
}
|
||||
|
||||
func NewOidcAuthSetter(baseCfg BaseConfig, cfg OidcClientConfig) *OidcAuthProvider {
|
||||
func NewOidcAuthSetter(additionalAuthScopes []v1.AuthScope, cfg v1.AuthOIDCClientConfig) *OidcAuthProvider {
|
||||
eps := make(map[string][]string)
|
||||
for k, v := range cfg.OidcAdditionalEndpointParams {
|
||||
for k, v := range cfg.AdditionalEndpointParams {
|
||||
eps[k] = []string{v}
|
||||
}
|
||||
|
||||
if cfg.OidcAudience != "" {
|
||||
eps["audience"] = []string{cfg.OidcAudience}
|
||||
if cfg.Audience != "" {
|
||||
eps["audience"] = []string{cfg.Audience}
|
||||
}
|
||||
|
||||
tokenGenerator := &clientcredentials.Config{
|
||||
ClientID: cfg.OidcClientID,
|
||||
ClientSecret: cfg.OidcClientSecret,
|
||||
Scopes: []string{cfg.OidcScope},
|
||||
TokenURL: cfg.OidcTokenEndpointURL,
|
||||
ClientID: cfg.ClientID,
|
||||
ClientSecret: cfg.ClientSecret,
|
||||
Scopes: []string{cfg.Scope},
|
||||
TokenURL: cfg.TokenEndpointURL,
|
||||
EndpointParams: eps,
|
||||
}
|
||||
|
||||
return &OidcAuthProvider{
|
||||
BaseConfig: baseCfg,
|
||||
tokenGenerator: tokenGenerator,
|
||||
additionalAuthScopes: additionalAuthScopes,
|
||||
tokenGenerator: tokenGenerator,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,7 +70,7 @@ func (auth *OidcAuthProvider) SetLogin(loginMsg *msg.Login) (err error) {
|
||||
}
|
||||
|
||||
func (auth *OidcAuthProvider) SetPing(pingMsg *msg.Ping) (err error) {
|
||||
if !auth.AuthenticateHeartBeats {
|
||||
if !lo.Contains(auth.additionalAuthScopes, v1.AuthScopeHeartBeats) {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -144,7 +79,7 @@ func (auth *OidcAuthProvider) SetPing(pingMsg *msg.Ping) (err error) {
|
||||
}
|
||||
|
||||
func (auth *OidcAuthProvider) SetNewWorkConn(newWorkConnMsg *msg.NewWorkConn) (err error) {
|
||||
if !auth.AuthenticateNewWorkConns {
|
||||
if !lo.Contains(auth.additionalAuthScopes, v1.AuthScopeNewWorkConns) {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -153,26 +88,26 @@ func (auth *OidcAuthProvider) SetNewWorkConn(newWorkConnMsg *msg.NewWorkConn) (e
|
||||
}
|
||||
|
||||
type OidcAuthConsumer struct {
|
||||
BaseConfig
|
||||
additionalAuthScopes []v1.AuthScope
|
||||
|
||||
verifier *oidc.IDTokenVerifier
|
||||
subjectFromLogin string
|
||||
}
|
||||
|
||||
func NewOidcAuthVerifier(baseCfg BaseConfig, cfg OidcServerConfig) *OidcAuthConsumer {
|
||||
provider, err := oidc.NewProvider(context.Background(), cfg.OidcIssuer)
|
||||
func NewOidcAuthVerifier(additionalAuthScopes []v1.AuthScope, cfg v1.AuthOIDCServerConfig) *OidcAuthConsumer {
|
||||
provider, err := oidc.NewProvider(context.Background(), cfg.Issuer)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
verifierConf := oidc.Config{
|
||||
ClientID: cfg.OidcAudience,
|
||||
SkipClientIDCheck: cfg.OidcAudience == "",
|
||||
SkipExpiryCheck: cfg.OidcSkipExpiryCheck,
|
||||
SkipIssuerCheck: cfg.OidcSkipIssuerCheck,
|
||||
ClientID: cfg.Audience,
|
||||
SkipClientIDCheck: cfg.Audience == "",
|
||||
SkipExpiryCheck: cfg.SkipExpiryCheck,
|
||||
SkipIssuerCheck: cfg.SkipIssuerCheck,
|
||||
}
|
||||
return &OidcAuthConsumer{
|
||||
BaseConfig: baseCfg,
|
||||
verifier: provider.Verifier(&verifierConf),
|
||||
additionalAuthScopes: additionalAuthScopes,
|
||||
verifier: provider.Verifier(&verifierConf),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,7 +135,7 @@ func (auth *OidcAuthConsumer) verifyPostLoginToken(privilegeKey string) (err err
|
||||
}
|
||||
|
||||
func (auth *OidcAuthConsumer) VerifyPing(pingMsg *msg.Ping) (err error) {
|
||||
if !auth.AuthenticateHeartBeats {
|
||||
if !lo.Contains(auth.additionalAuthScopes, v1.AuthScopeHeartBeats) {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -208,7 +143,7 @@ func (auth *OidcAuthConsumer) VerifyPing(pingMsg *msg.Ping) (err error) {
|
||||
}
|
||||
|
||||
func (auth *OidcAuthConsumer) VerifyNewWorkConn(newWorkConnMsg *msg.NewWorkConn) (err error) {
|
||||
if !auth.AuthenticateNewWorkConns {
|
||||
if !lo.Contains(auth.additionalAuthScopes, v1.AuthScopeNewWorkConns) {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@@ -18,43 +18,32 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/samber/lo"
|
||||
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
"github.com/fatedier/frp/pkg/util/util"
|
||||
)
|
||||
|
||||
type TokenConfig struct {
|
||||
// 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 `ini:"token" json:"token"`
|
||||
}
|
||||
|
||||
func getDefaultTokenConf() TokenConfig {
|
||||
return TokenConfig{
|
||||
Token: "",
|
||||
}
|
||||
}
|
||||
|
||||
type TokenAuthSetterVerifier struct {
|
||||
BaseConfig
|
||||
|
||||
token string
|
||||
additionalAuthScopes []v1.AuthScope
|
||||
token string
|
||||
}
|
||||
|
||||
func NewTokenAuth(baseCfg BaseConfig, cfg TokenConfig) *TokenAuthSetterVerifier {
|
||||
func NewTokenAuth(additionalAuthScopes []v1.AuthScope, token string) *TokenAuthSetterVerifier {
|
||||
return &TokenAuthSetterVerifier{
|
||||
BaseConfig: baseCfg,
|
||||
token: cfg.Token,
|
||||
additionalAuthScopes: additionalAuthScopes,
|
||||
token: token,
|
||||
}
|
||||
}
|
||||
|
||||
func (auth *TokenAuthSetterVerifier) SetLogin(loginMsg *msg.Login) (err error) {
|
||||
func (auth *TokenAuthSetterVerifier) SetLogin(loginMsg *msg.Login) error {
|
||||
loginMsg.PrivilegeKey = util.GetAuthKey(auth.token, loginMsg.Timestamp)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (auth *TokenAuthSetterVerifier) SetPing(pingMsg *msg.Ping) error {
|
||||
if !auth.AuthenticateHeartBeats {
|
||||
if !lo.Contains(auth.additionalAuthScopes, v1.AuthScopeHeartBeats) {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -64,7 +53,7 @@ func (auth *TokenAuthSetterVerifier) SetPing(pingMsg *msg.Ping) error {
|
||||
}
|
||||
|
||||
func (auth *TokenAuthSetterVerifier) SetNewWorkConn(newWorkConnMsg *msg.NewWorkConn) error {
|
||||
if !auth.AuthenticateNewWorkConns {
|
||||
if !lo.Contains(auth.additionalAuthScopes, v1.AuthScopeNewWorkConns) {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -81,7 +70,7 @@ func (auth *TokenAuthSetterVerifier) VerifyLogin(m *msg.Login) error {
|
||||
}
|
||||
|
||||
func (auth *TokenAuthSetterVerifier) VerifyPing(m *msg.Ping) error {
|
||||
if !auth.AuthenticateHeartBeats {
|
||||
if !lo.Contains(auth.additionalAuthScopes, v1.AuthScopeHeartBeats) {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -92,7 +81,7 @@ func (auth *TokenAuthSetterVerifier) VerifyPing(m *msg.Ping) error {
|
||||
}
|
||||
|
||||
func (auth *TokenAuthSetterVerifier) VerifyNewWorkConn(m *msg.NewWorkConn) error {
|
||||
if !auth.AuthenticateNewWorkConns {
|
||||
if !lo.Contains(auth.additionalAuthScopes, v1.AuthScopeNewWorkConns) {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@@ -1,680 +0,0 @@
|
||||
// Copyright 2020 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 config
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/fatedier/frp/pkg/auth"
|
||||
"github.com/fatedier/frp/pkg/consts"
|
||||
)
|
||||
|
||||
const (
|
||||
testUser = "test"
|
||||
)
|
||||
|
||||
var testClientBytesWithFull = []byte(`
|
||||
# [common] is integral section
|
||||
[common]
|
||||
server_addr = 0.0.0.9
|
||||
server_port = 7009
|
||||
http_proxy = http://user:passwd@192.168.1.128:8080
|
||||
log_file = ./frpc.log9
|
||||
log_way = file
|
||||
log_level = info9
|
||||
log_max_days = 39
|
||||
disable_log_color = false
|
||||
authenticate_heartbeats = false
|
||||
authenticate_new_work_conns = false
|
||||
token = 12345678
|
||||
oidc_client_id = client-id
|
||||
oidc_client_secret = client-secret
|
||||
oidc_audience = audience
|
||||
oidc_token_endpoint_url = endpoint_url
|
||||
admin_addr = 127.0.0.9
|
||||
admin_port = 7409
|
||||
admin_user = admin9
|
||||
admin_pwd = admin9
|
||||
assets_dir = ./static9
|
||||
pool_count = 59
|
||||
tcp_mux
|
||||
user = your_name
|
||||
login_fail_exit
|
||||
protocol = tcp
|
||||
tls_enable = true
|
||||
tls_cert_file = client.crt
|
||||
tls_key_file = client.key
|
||||
tls_trusted_ca_file = ca.crt
|
||||
tls_server_name = example.com
|
||||
dns_server = 8.8.8.9
|
||||
start = ssh,dns
|
||||
heartbeat_interval = 39
|
||||
heartbeat_timeout = 99
|
||||
meta_var1 = 123
|
||||
meta_var2 = 234
|
||||
udp_packet_size = 1509
|
||||
|
||||
# all proxy
|
||||
[ssh]
|
||||
type = tcp
|
||||
local_ip = 127.0.0.9
|
||||
local_port = 29
|
||||
bandwidth_limit = 19MB
|
||||
bandwidth_limit_mode = server
|
||||
use_encryption
|
||||
use_compression
|
||||
remote_port = 6009
|
||||
group = test_group
|
||||
group_key = 123456
|
||||
health_check_type = tcp
|
||||
health_check_timeout_s = 3
|
||||
health_check_max_failed = 3
|
||||
health_check_interval_s = 19
|
||||
meta_var1 = 123
|
||||
meta_var2 = 234
|
||||
|
||||
[ssh_random]
|
||||
type = tcp
|
||||
local_ip = 127.0.0.9
|
||||
local_port = 29
|
||||
remote_port = 9
|
||||
|
||||
[range:tcp_port]
|
||||
type = tcp
|
||||
local_ip = 127.0.0.9
|
||||
local_port = 6010-6011,6019
|
||||
remote_port = 6010-6011,6019
|
||||
use_encryption = false
|
||||
use_compression = false
|
||||
|
||||
[dns]
|
||||
type = udp
|
||||
local_ip = 114.114.114.114
|
||||
local_port = 59
|
||||
remote_port = 6009
|
||||
use_encryption
|
||||
use_compression
|
||||
|
||||
[range:udp_port]
|
||||
type = udp
|
||||
local_ip = 114.114.114.114
|
||||
local_port = 6000,6010-6011
|
||||
remote_port = 6000,6010-6011
|
||||
use_encryption
|
||||
use_compression
|
||||
|
||||
[web01]
|
||||
type = http
|
||||
local_ip = 127.0.0.9
|
||||
local_port = 89
|
||||
use_encryption
|
||||
use_compression
|
||||
http_user = admin
|
||||
http_pwd = admin
|
||||
subdomain = web01
|
||||
custom_domains = web02.yourdomain.com
|
||||
locations = /,/pic
|
||||
host_header_rewrite = example.com
|
||||
header_X-From-Where = frp
|
||||
health_check_type = http
|
||||
health_check_url = /status
|
||||
health_check_interval_s = 19
|
||||
health_check_max_failed = 3
|
||||
health_check_timeout_s = 3
|
||||
|
||||
[web02]
|
||||
type = https
|
||||
local_ip = 127.0.0.9
|
||||
local_port = 8009
|
||||
use_encryption
|
||||
use_compression
|
||||
subdomain = web01
|
||||
custom_domains = web02.yourdomain.com
|
||||
proxy_protocol_version = v2
|
||||
|
||||
[secret_tcp]
|
||||
type = stcp
|
||||
sk = abcdefg
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 22
|
||||
use_encryption = false
|
||||
use_compression = false
|
||||
|
||||
[p2p_tcp]
|
||||
type = xtcp
|
||||
sk = abcdefg
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 22
|
||||
use_encryption = false
|
||||
use_compression = false
|
||||
|
||||
[tcpmuxhttpconnect]
|
||||
type = tcpmux
|
||||
multiplexer = httpconnect
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 10701
|
||||
custom_domains = tunnel1
|
||||
|
||||
[plugin_unix_domain_socket]
|
||||
type = tcp
|
||||
remote_port = 6003
|
||||
plugin = unix_domain_socket
|
||||
plugin_unix_path = /var/run/docker.sock
|
||||
|
||||
[plugin_http_proxy]
|
||||
type = tcp
|
||||
remote_port = 6004
|
||||
plugin = http_proxy
|
||||
plugin_http_user = abc
|
||||
plugin_http_passwd = abc
|
||||
|
||||
[plugin_socks5]
|
||||
type = tcp
|
||||
remote_port = 6005
|
||||
plugin = socks5
|
||||
plugin_user = abc
|
||||
plugin_passwd = abc
|
||||
|
||||
[plugin_static_file]
|
||||
type = tcp
|
||||
remote_port = 6006
|
||||
plugin = static_file
|
||||
plugin_local_path = /var/www/blog
|
||||
plugin_strip_prefix = static
|
||||
plugin_http_user = abc
|
||||
plugin_http_passwd = abc
|
||||
|
||||
[plugin_https2http]
|
||||
type = https
|
||||
custom_domains = test.yourdomain.com
|
||||
plugin = https2http
|
||||
plugin_local_addr = 127.0.0.1:80
|
||||
plugin_crt_path = ./server.crt
|
||||
plugin_key_path = ./server.key
|
||||
plugin_host_header_rewrite = 127.0.0.1
|
||||
plugin_header_X-From-Where = frp
|
||||
|
||||
[plugin_http2https]
|
||||
type = http
|
||||
custom_domains = test.yourdomain.com
|
||||
plugin = http2https
|
||||
plugin_local_addr = 127.0.0.1:443
|
||||
plugin_host_header_rewrite = 127.0.0.1
|
||||
plugin_header_X-From-Where = frp
|
||||
|
||||
# visitor
|
||||
[secret_tcp_visitor]
|
||||
role = visitor
|
||||
type = stcp
|
||||
server_name = secret_tcp
|
||||
sk = abcdefg
|
||||
bind_addr = 127.0.0.1
|
||||
bind_port = 9000
|
||||
use_encryption = false
|
||||
use_compression = false
|
||||
|
||||
[p2p_tcp_visitor]
|
||||
role = visitor
|
||||
type = xtcp
|
||||
server_name = p2p_tcp
|
||||
sk = abcdefg
|
||||
bind_addr = 127.0.0.1
|
||||
bind_port = 9001
|
||||
use_encryption = false
|
||||
use_compression = false
|
||||
`)
|
||||
|
||||
func Test_LoadClientCommonConf(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
expected := ClientCommonConf{
|
||||
ClientConfig: auth.ClientConfig{
|
||||
BaseConfig: auth.BaseConfig{
|
||||
AuthenticationMethod: "token",
|
||||
AuthenticateHeartBeats: false,
|
||||
AuthenticateNewWorkConns: false,
|
||||
},
|
||||
TokenConfig: auth.TokenConfig{
|
||||
Token: "12345678",
|
||||
},
|
||||
OidcClientConfig: auth.OidcClientConfig{
|
||||
OidcClientID: "client-id",
|
||||
OidcClientSecret: "client-secret",
|
||||
OidcAudience: "audience",
|
||||
OidcTokenEndpointURL: "endpoint_url",
|
||||
},
|
||||
},
|
||||
ServerAddr: "0.0.0.9",
|
||||
ServerPort: 7009,
|
||||
NatHoleSTUNServer: "stun.easyvoip.com:3478",
|
||||
DialServerTimeout: 10,
|
||||
DialServerKeepAlive: 7200,
|
||||
HTTPProxy: "http://user:passwd@192.168.1.128:8080",
|
||||
LogFile: "./frpc.log9",
|
||||
LogWay: "file",
|
||||
LogLevel: "info9",
|
||||
LogMaxDays: 39,
|
||||
DisableLogColor: false,
|
||||
AdminAddr: "127.0.0.9",
|
||||
AdminPort: 7409,
|
||||
AdminUser: "admin9",
|
||||
AdminPwd: "admin9",
|
||||
AssetsDir: "./static9",
|
||||
PoolCount: 59,
|
||||
TCPMux: true,
|
||||
TCPMuxKeepaliveInterval: 60,
|
||||
User: "your_name",
|
||||
LoginFailExit: true,
|
||||
Protocol: "tcp",
|
||||
QUICKeepalivePeriod: 10,
|
||||
QUICMaxIdleTimeout: 30,
|
||||
QUICMaxIncomingStreams: 100000,
|
||||
TLSEnable: true,
|
||||
TLSCertFile: "client.crt",
|
||||
TLSKeyFile: "client.key",
|
||||
TLSTrustedCaFile: "ca.crt",
|
||||
TLSServerName: "example.com",
|
||||
DisableCustomTLSFirstByte: true,
|
||||
DNSServer: "8.8.8.9",
|
||||
Start: []string{"ssh", "dns"},
|
||||
HeartbeatInterval: 39,
|
||||
HeartbeatTimeout: 99,
|
||||
Metas: map[string]string{
|
||||
"var1": "123",
|
||||
"var2": "234",
|
||||
},
|
||||
UDPPacketSize: 1509,
|
||||
IncludeConfigFiles: []string{},
|
||||
}
|
||||
|
||||
common, err := UnmarshalClientConfFromIni(testClientBytesWithFull)
|
||||
assert.NoError(err)
|
||||
assert.EqualValues(expected, common)
|
||||
}
|
||||
|
||||
func Test_LoadClientBasicConf(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
proxyExpected := map[string]ProxyConf{
|
||||
testUser + ".ssh": &TCPProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testUser + ".ssh",
|
||||
ProxyType: consts.TCPProxy,
|
||||
UseCompression: true,
|
||||
UseEncryption: true,
|
||||
Group: "test_group",
|
||||
GroupKey: "123456",
|
||||
BandwidthLimit: MustBandwidthQuantity("19MB"),
|
||||
BandwidthLimitMode: BandwidthLimitModeServer,
|
||||
Metas: map[string]string{
|
||||
"var1": "123",
|
||||
"var2": "234",
|
||||
},
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "127.0.0.9",
|
||||
LocalPort: 29,
|
||||
},
|
||||
HealthCheckConf: HealthCheckConf{
|
||||
HealthCheckType: consts.TCPProxy,
|
||||
HealthCheckTimeoutS: 3,
|
||||
HealthCheckMaxFailed: 3,
|
||||
HealthCheckIntervalS: 19,
|
||||
HealthCheckAddr: "127.0.0.9:29",
|
||||
},
|
||||
},
|
||||
RemotePort: 6009,
|
||||
},
|
||||
testUser + ".ssh_random": &TCPProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testUser + ".ssh_random",
|
||||
ProxyType: consts.TCPProxy,
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "127.0.0.9",
|
||||
LocalPort: 29,
|
||||
},
|
||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||
},
|
||||
RemotePort: 9,
|
||||
},
|
||||
testUser + ".tcp_port_0": &TCPProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testUser + ".tcp_port_0",
|
||||
ProxyType: consts.TCPProxy,
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "127.0.0.9",
|
||||
LocalPort: 6010,
|
||||
},
|
||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||
},
|
||||
RemotePort: 6010,
|
||||
},
|
||||
testUser + ".tcp_port_1": &TCPProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testUser + ".tcp_port_1",
|
||||
ProxyType: consts.TCPProxy,
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "127.0.0.9",
|
||||
LocalPort: 6011,
|
||||
},
|
||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||
},
|
||||
RemotePort: 6011,
|
||||
},
|
||||
testUser + ".tcp_port_2": &TCPProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testUser + ".tcp_port_2",
|
||||
ProxyType: consts.TCPProxy,
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "127.0.0.9",
|
||||
LocalPort: 6019,
|
||||
},
|
||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||
},
|
||||
RemotePort: 6019,
|
||||
},
|
||||
testUser + ".dns": &UDPProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testUser + ".dns",
|
||||
ProxyType: consts.UDPProxy,
|
||||
UseEncryption: true,
|
||||
UseCompression: true,
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "114.114.114.114",
|
||||
LocalPort: 59,
|
||||
},
|
||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||
},
|
||||
RemotePort: 6009,
|
||||
},
|
||||
testUser + ".udp_port_0": &UDPProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testUser + ".udp_port_0",
|
||||
ProxyType: consts.UDPProxy,
|
||||
UseEncryption: true,
|
||||
UseCompression: true,
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "114.114.114.114",
|
||||
LocalPort: 6000,
|
||||
},
|
||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||
},
|
||||
RemotePort: 6000,
|
||||
},
|
||||
testUser + ".udp_port_1": &UDPProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testUser + ".udp_port_1",
|
||||
ProxyType: consts.UDPProxy,
|
||||
UseEncryption: true,
|
||||
UseCompression: true,
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "114.114.114.114",
|
||||
LocalPort: 6010,
|
||||
},
|
||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||
},
|
||||
RemotePort: 6010,
|
||||
},
|
||||
testUser + ".udp_port_2": &UDPProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testUser + ".udp_port_2",
|
||||
ProxyType: consts.UDPProxy,
|
||||
UseEncryption: true,
|
||||
UseCompression: true,
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "114.114.114.114",
|
||||
LocalPort: 6011,
|
||||
},
|
||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||
},
|
||||
RemotePort: 6011,
|
||||
},
|
||||
testUser + ".web01": &HTTPProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testUser + ".web01",
|
||||
ProxyType: consts.HTTPProxy,
|
||||
UseCompression: true,
|
||||
UseEncryption: true,
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "127.0.0.9",
|
||||
LocalPort: 89,
|
||||
},
|
||||
HealthCheckConf: HealthCheckConf{
|
||||
HealthCheckType: consts.HTTPProxy,
|
||||
HealthCheckTimeoutS: 3,
|
||||
HealthCheckMaxFailed: 3,
|
||||
HealthCheckIntervalS: 19,
|
||||
HealthCheckURL: "http://127.0.0.9:89/status",
|
||||
},
|
||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||
},
|
||||
DomainConf: DomainConf{
|
||||
CustomDomains: []string{"web02.yourdomain.com"},
|
||||
SubDomain: "web01",
|
||||
},
|
||||
Locations: []string{"/", "/pic"},
|
||||
HTTPUser: "admin",
|
||||
HTTPPwd: "admin",
|
||||
HostHeaderRewrite: "example.com",
|
||||
Headers: map[string]string{
|
||||
"X-From-Where": "frp",
|
||||
},
|
||||
},
|
||||
testUser + ".web02": &HTTPSProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testUser + ".web02",
|
||||
ProxyType: consts.HTTPSProxy,
|
||||
UseCompression: true,
|
||||
UseEncryption: true,
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "127.0.0.9",
|
||||
LocalPort: 8009,
|
||||
},
|
||||
ProxyProtocolVersion: "v2",
|
||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||
},
|
||||
DomainConf: DomainConf{
|
||||
CustomDomains: []string{"web02.yourdomain.com"},
|
||||
SubDomain: "web01",
|
||||
},
|
||||
},
|
||||
testUser + ".secret_tcp": &STCPProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testUser + ".secret_tcp",
|
||||
ProxyType: consts.STCPProxy,
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "127.0.0.1",
|
||||
LocalPort: 22,
|
||||
},
|
||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||
},
|
||||
RoleServerCommonConf: RoleServerCommonConf{
|
||||
Role: "server",
|
||||
Sk: "abcdefg",
|
||||
},
|
||||
},
|
||||
testUser + ".p2p_tcp": &XTCPProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testUser + ".p2p_tcp",
|
||||
ProxyType: consts.XTCPProxy,
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "127.0.0.1",
|
||||
LocalPort: 22,
|
||||
},
|
||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||
},
|
||||
RoleServerCommonConf: RoleServerCommonConf{
|
||||
Role: "server",
|
||||
Sk: "abcdefg",
|
||||
},
|
||||
},
|
||||
testUser + ".tcpmuxhttpconnect": &TCPMuxProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testUser + ".tcpmuxhttpconnect",
|
||||
ProxyType: consts.TCPMuxProxy,
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "127.0.0.1",
|
||||
LocalPort: 10701,
|
||||
},
|
||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||
},
|
||||
DomainConf: DomainConf{
|
||||
CustomDomains: []string{"tunnel1"},
|
||||
SubDomain: "",
|
||||
},
|
||||
Multiplexer: "httpconnect",
|
||||
},
|
||||
testUser + ".plugin_unix_domain_socket": &TCPProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testUser + ".plugin_unix_domain_socket",
|
||||
ProxyType: consts.TCPProxy,
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "127.0.0.1",
|
||||
Plugin: "unix_domain_socket",
|
||||
PluginParams: map[string]string{
|
||||
"plugin_unix_path": "/var/run/docker.sock",
|
||||
},
|
||||
},
|
||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||
},
|
||||
RemotePort: 6003,
|
||||
},
|
||||
testUser + ".plugin_http_proxy": &TCPProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testUser + ".plugin_http_proxy",
|
||||
ProxyType: consts.TCPProxy,
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "127.0.0.1",
|
||||
Plugin: "http_proxy",
|
||||
PluginParams: map[string]string{
|
||||
"plugin_http_user": "abc",
|
||||
"plugin_http_passwd": "abc",
|
||||
},
|
||||
},
|
||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||
},
|
||||
RemotePort: 6004,
|
||||
},
|
||||
testUser + ".plugin_socks5": &TCPProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testUser + ".plugin_socks5",
|
||||
ProxyType: consts.TCPProxy,
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "127.0.0.1",
|
||||
Plugin: "socks5",
|
||||
PluginParams: map[string]string{
|
||||
"plugin_user": "abc",
|
||||
"plugin_passwd": "abc",
|
||||
},
|
||||
},
|
||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||
},
|
||||
RemotePort: 6005,
|
||||
},
|
||||
testUser + ".plugin_static_file": &TCPProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testUser + ".plugin_static_file",
|
||||
ProxyType: consts.TCPProxy,
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "127.0.0.1",
|
||||
Plugin: "static_file",
|
||||
PluginParams: map[string]string{
|
||||
"plugin_local_path": "/var/www/blog",
|
||||
"plugin_strip_prefix": "static",
|
||||
"plugin_http_user": "abc",
|
||||
"plugin_http_passwd": "abc",
|
||||
},
|
||||
},
|
||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||
},
|
||||
RemotePort: 6006,
|
||||
},
|
||||
testUser + ".plugin_https2http": &HTTPSProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testUser + ".plugin_https2http",
|
||||
ProxyType: consts.HTTPSProxy,
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "127.0.0.1",
|
||||
Plugin: "https2http",
|
||||
PluginParams: map[string]string{
|
||||
"plugin_local_addr": "127.0.0.1:80",
|
||||
"plugin_crt_path": "./server.crt",
|
||||
"plugin_key_path": "./server.key",
|
||||
"plugin_host_header_rewrite": "127.0.0.1",
|
||||
"plugin_header_X-From-Where": "frp",
|
||||
},
|
||||
},
|
||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||
},
|
||||
DomainConf: DomainConf{
|
||||
CustomDomains: []string{"test.yourdomain.com"},
|
||||
},
|
||||
},
|
||||
testUser + ".plugin_http2https": &HTTPProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testUser + ".plugin_http2https",
|
||||
ProxyType: consts.HTTPProxy,
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "127.0.0.1",
|
||||
Plugin: "http2https",
|
||||
PluginParams: map[string]string{
|
||||
"plugin_local_addr": "127.0.0.1:443",
|
||||
"plugin_host_header_rewrite": "127.0.0.1",
|
||||
"plugin_header_X-From-Where": "frp",
|
||||
},
|
||||
},
|
||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||
},
|
||||
DomainConf: DomainConf{
|
||||
CustomDomains: []string{"test.yourdomain.com"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
visitorExpected := map[string]VisitorConf{
|
||||
testUser + ".secret_tcp_visitor": &STCPVisitorConf{
|
||||
BaseVisitorConf: BaseVisitorConf{
|
||||
ProxyName: testUser + ".secret_tcp_visitor",
|
||||
ProxyType: consts.STCPProxy,
|
||||
Role: "visitor",
|
||||
Sk: "abcdefg",
|
||||
ServerName: testVisitorPrefix + "secret_tcp",
|
||||
BindAddr: "127.0.0.1",
|
||||
BindPort: 9000,
|
||||
},
|
||||
},
|
||||
testUser + ".p2p_tcp_visitor": &XTCPVisitorConf{
|
||||
BaseVisitorConf: BaseVisitorConf{
|
||||
ProxyName: testUser + ".p2p_tcp_visitor",
|
||||
ProxyType: consts.XTCPProxy,
|
||||
Role: "visitor",
|
||||
Sk: "abcdefg",
|
||||
ServerName: testProxyPrefix + "p2p_tcp",
|
||||
BindAddr: "127.0.0.1",
|
||||
BindPort: 9001,
|
||||
},
|
||||
Protocol: "quic",
|
||||
MaxRetriesAnHour: 8,
|
||||
MinRetryInterval: 90,
|
||||
FallbackTimeoutMs: 1000,
|
||||
},
|
||||
}
|
||||
|
||||
proxyActual, visitorActual, err := LoadAllProxyConfsFromIni(testUser, testClientBytesWithFull, nil)
|
||||
assert.NoError(err)
|
||||
assert.Equal(proxyExpected, proxyActual)
|
||||
assert.Equal(visitorExpected, visitorActual)
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 The frp Authors
|
||||
// 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.
|
||||
@@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package config
|
||||
package legacy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -23,15 +23,16 @@ import (
|
||||
"github.com/samber/lo"
|
||||
"gopkg.in/ini.v1"
|
||||
|
||||
"github.com/fatedier/frp/pkg/auth"
|
||||
legacyauth "github.com/fatedier/frp/pkg/auth/legacy"
|
||||
"github.com/fatedier/frp/pkg/util/util"
|
||||
)
|
||||
|
||||
// ClientCommonConf contains information for a client service. It is
|
||||
// ClientCommonConf is the configuration parsed from ini.
|
||||
// It contains information for a client service. It is
|
||||
// recommended to use GetDefaultClientConf instead of creating this object
|
||||
// directly, so that all unspecified fields have reasonable default values.
|
||||
type ClientCommonConf struct {
|
||||
auth.ClientConfig `ini:",extends"`
|
||||
legacyauth.ClientConfig `ini:",extends"`
|
||||
|
||||
// ServerAddr specifies the address of the server to connect to. By
|
||||
// default, this value is "0.0.0.0".
|
||||
@@ -168,85 +169,6 @@ type ClientCommonConf struct {
|
||||
PprofEnable bool `ini:"pprof_enable" json:"pprof_enable"`
|
||||
}
|
||||
|
||||
// GetDefaultClientConf returns a client configuration with default values.
|
||||
func GetDefaultClientConf() ClientCommonConf {
|
||||
return ClientCommonConf{
|
||||
ClientConfig: auth.GetDefaultClientConf(),
|
||||
ServerAddr: "0.0.0.0",
|
||||
ServerPort: 7000,
|
||||
NatHoleSTUNServer: "stun.easyvoip.com:3478",
|
||||
DialServerTimeout: 10,
|
||||
DialServerKeepAlive: 7200,
|
||||
HTTPProxy: os.Getenv("http_proxy"),
|
||||
LogFile: "console",
|
||||
LogWay: "console",
|
||||
LogLevel: "info",
|
||||
LogMaxDays: 3,
|
||||
AdminAddr: "127.0.0.1",
|
||||
PoolCount: 1,
|
||||
TCPMux: true,
|
||||
TCPMuxKeepaliveInterval: 60,
|
||||
LoginFailExit: true,
|
||||
Start: make([]string, 0),
|
||||
Protocol: "tcp",
|
||||
QUICKeepalivePeriod: 10,
|
||||
QUICMaxIdleTimeout: 30,
|
||||
QUICMaxIncomingStreams: 100000,
|
||||
TLSEnable: true,
|
||||
DisableCustomTLSFirstByte: true,
|
||||
HeartbeatInterval: 30,
|
||||
HeartbeatTimeout: 90,
|
||||
Metas: make(map[string]string),
|
||||
UDPPacketSize: 1500,
|
||||
IncludeConfigFiles: make([]string, 0),
|
||||
}
|
||||
}
|
||||
|
||||
func (cfg *ClientCommonConf) Complete() {
|
||||
if cfg.LogFile == "console" {
|
||||
cfg.LogWay = "console"
|
||||
} else {
|
||||
cfg.LogWay = "file"
|
||||
}
|
||||
}
|
||||
|
||||
func (cfg *ClientCommonConf) Validate() error {
|
||||
if cfg.HeartbeatTimeout > 0 && cfg.HeartbeatInterval > 0 {
|
||||
if cfg.HeartbeatTimeout < cfg.HeartbeatInterval {
|
||||
return fmt.Errorf("invalid heartbeat_timeout, heartbeat_timeout is less than heartbeat_interval")
|
||||
}
|
||||
}
|
||||
|
||||
if !cfg.TLSEnable {
|
||||
if cfg.TLSCertFile != "" {
|
||||
fmt.Println("WARNING! tls_cert_file is invalid when tls_enable is false")
|
||||
}
|
||||
|
||||
if cfg.TLSKeyFile != "" {
|
||||
fmt.Println("WARNING! tls_key_file is invalid when tls_enable is false")
|
||||
}
|
||||
|
||||
if cfg.TLSTrustedCaFile != "" {
|
||||
fmt.Println("WARNING! tls_trusted_ca_file is invalid when tls_enable is false")
|
||||
}
|
||||
}
|
||||
|
||||
if !lo.Contains([]string{"tcp", "kcp", "quic", "websocket", "wss"}, cfg.Protocol) {
|
||||
return fmt.Errorf("invalid protocol")
|
||||
}
|
||||
|
||||
for _, f := range cfg.IncludeConfigFiles {
|
||||
absDir, err := filepath.Abs(filepath.Dir(f))
|
||||
if err != nil {
|
||||
return fmt.Errorf("include: parse directory of %s failed: %v", f, absDir)
|
||||
}
|
||||
if _, err := os.Stat(absDir); os.IsNotExist(err) {
|
||||
return fmt.Errorf("include: directory of %s not exist", f)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Supported sources including: string(file path), []byte, Reader interface.
|
||||
func UnmarshalClientConfFromIni(source interface{}) (ClientCommonConf, error) {
|
||||
f, err := ini.LoadSources(ini.LoadOptions{
|
||||
@@ -421,3 +343,74 @@ func copySection(source, target *ini.Section) {
|
||||
_, _ = target.NewKey(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
// GetDefaultClientConf returns a client configuration with default values.
|
||||
func GetDefaultClientConf() ClientCommonConf {
|
||||
return ClientCommonConf{
|
||||
ClientConfig: legacyauth.GetDefaultClientConf(),
|
||||
ServerAddr: "0.0.0.0",
|
||||
ServerPort: 7000,
|
||||
NatHoleSTUNServer: "stun.easyvoip.com:3478",
|
||||
DialServerTimeout: 10,
|
||||
DialServerKeepAlive: 7200,
|
||||
HTTPProxy: os.Getenv("http_proxy"),
|
||||
LogFile: "console",
|
||||
LogWay: "console",
|
||||
LogLevel: "info",
|
||||
LogMaxDays: 3,
|
||||
AdminAddr: "127.0.0.1",
|
||||
PoolCount: 1,
|
||||
TCPMux: true,
|
||||
TCPMuxKeepaliveInterval: 60,
|
||||
LoginFailExit: true,
|
||||
Start: make([]string, 0),
|
||||
Protocol: "tcp",
|
||||
QUICKeepalivePeriod: 10,
|
||||
QUICMaxIdleTimeout: 30,
|
||||
QUICMaxIncomingStreams: 100000,
|
||||
TLSEnable: true,
|
||||
DisableCustomTLSFirstByte: true,
|
||||
HeartbeatInterval: 30,
|
||||
HeartbeatTimeout: 90,
|
||||
Metas: make(map[string]string),
|
||||
UDPPacketSize: 1500,
|
||||
IncludeConfigFiles: make([]string, 0),
|
||||
}
|
||||
}
|
||||
|
||||
func (cfg *ClientCommonConf) Validate() error {
|
||||
if cfg.HeartbeatTimeout > 0 && cfg.HeartbeatInterval > 0 {
|
||||
if cfg.HeartbeatTimeout < cfg.HeartbeatInterval {
|
||||
return fmt.Errorf("invalid heartbeat_timeout, heartbeat_timeout is less than heartbeat_interval")
|
||||
}
|
||||
}
|
||||
|
||||
if !cfg.TLSEnable {
|
||||
if cfg.TLSCertFile != "" {
|
||||
fmt.Println("WARNING! tls_cert_file is invalid when tls_enable is false")
|
||||
}
|
||||
|
||||
if cfg.TLSKeyFile != "" {
|
||||
fmt.Println("WARNING! tls_key_file is invalid when tls_enable is false")
|
||||
}
|
||||
|
||||
if cfg.TLSTrustedCaFile != "" {
|
||||
fmt.Println("WARNING! tls_trusted_ca_file is invalid when tls_enable is false")
|
||||
}
|
||||
}
|
||||
|
||||
if !lo.Contains([]string{"tcp", "kcp", "quic", "websocket", "wss"}, cfg.Protocol) {
|
||||
return fmt.Errorf("invalid protocol")
|
||||
}
|
||||
|
||||
for _, f := range cfg.IncludeConfigFiles {
|
||||
absDir, err := filepath.Abs(filepath.Dir(f))
|
||||
if err != nil {
|
||||
return fmt.Errorf("include: parse directory of %s failed: %v", f, err)
|
||||
}
|
||||
if _, err := os.Stat(absDir); os.IsNotExist(err) {
|
||||
return fmt.Errorf("include: directory of %s not exist", f)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
350
pkg/config/legacy/conversion.go
Normal file
350
pkg/config/legacy/conversion.go
Normal file
@@ -0,0 +1,350 @@
|
||||
// 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 legacy
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/samber/lo"
|
||||
|
||||
"github.com/fatedier/frp/pkg/config/types"
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
)
|
||||
|
||||
func Convert_ClientCommonConf_To_v1(conf *ClientCommonConf) *v1.ClientCommonConfig {
|
||||
out := &v1.ClientCommonConfig{}
|
||||
out.User = conf.User
|
||||
out.Auth.Method = conf.ClientConfig.AuthenticationMethod
|
||||
out.Auth.Token = conf.ClientConfig.Token
|
||||
if conf.ClientConfig.AuthenticateHeartBeats {
|
||||
out.Auth.AdditionalAuthScopes = append(out.Auth.AdditionalAuthScopes, v1.AuthScopeHeartBeats)
|
||||
}
|
||||
if conf.ClientConfig.AuthenticateNewWorkConns {
|
||||
out.Auth.AdditionalAuthScopes = append(out.Auth.AdditionalAuthScopes, v1.AuthScopeNewWorkConns)
|
||||
}
|
||||
out.Auth.OIDC.ClientID = conf.ClientConfig.OidcClientID
|
||||
out.Auth.OIDC.ClientSecret = conf.ClientConfig.OidcClientSecret
|
||||
out.Auth.OIDC.Audience = conf.ClientConfig.OidcAudience
|
||||
out.Auth.OIDC.Scope = conf.ClientConfig.OidcScope
|
||||
out.Auth.OIDC.TokenEndpointURL = conf.ClientConfig.OidcTokenEndpointURL
|
||||
out.Auth.OIDC.AdditionalEndpointParams = conf.ClientConfig.OidcAdditionalEndpointParams
|
||||
|
||||
out.ServerAddr = conf.ServerAddr
|
||||
out.ServerPort = conf.ServerPort
|
||||
out.NatHoleSTUNServer = conf.NatHoleSTUNServer
|
||||
out.Transport.DialServerTimeout = conf.DialServerTimeout
|
||||
out.Transport.DialServerKeepAlive = conf.DialServerKeepAlive
|
||||
out.Transport.ConnectServerLocalIP = conf.ConnectServerLocalIP
|
||||
out.Transport.ProxyURL = conf.HTTPProxy
|
||||
out.Transport.PoolCount = conf.PoolCount
|
||||
out.Transport.TCPMux = lo.ToPtr(conf.TCPMux)
|
||||
out.Transport.TCPMuxKeepaliveInterval = conf.TCPMuxKeepaliveInterval
|
||||
out.Transport.Protocol = conf.Protocol
|
||||
out.Transport.HeartbeatInterval = conf.HeartbeatInterval
|
||||
out.Transport.HeartbeatTimeout = conf.HeartbeatTimeout
|
||||
out.Transport.QUIC.KeepalivePeriod = conf.QUICKeepalivePeriod
|
||||
out.Transport.QUIC.MaxIdleTimeout = conf.QUICMaxIdleTimeout
|
||||
out.Transport.QUIC.MaxIncomingStreams = conf.QUICMaxIncomingStreams
|
||||
out.Transport.TLS.Enable = lo.ToPtr(conf.TLSEnable)
|
||||
out.Transport.TLS.DisableCustomTLSFirstByte = lo.ToPtr(conf.DisableCustomTLSFirstByte)
|
||||
out.Transport.TLS.TLSConfig.CertFile = conf.TLSCertFile
|
||||
out.Transport.TLS.TLSConfig.KeyFile = conf.TLSKeyFile
|
||||
out.Transport.TLS.TLSConfig.TrustedCaFile = conf.TLSTrustedCaFile
|
||||
out.Transport.TLS.TLSConfig.ServerName = conf.TLSServerName
|
||||
|
||||
out.Log.To = conf.LogFile
|
||||
out.Log.Level = conf.LogLevel
|
||||
out.Log.MaxDays = conf.LogMaxDays
|
||||
out.Log.DisablePrintColor = conf.DisableLogColor
|
||||
|
||||
out.WebServer.Addr = conf.AdminAddr
|
||||
out.WebServer.Port = conf.AdminPort
|
||||
out.WebServer.Password = conf.AdminPwd
|
||||
out.WebServer.AssetsDir = conf.AssetsDir
|
||||
out.WebServer.PprofEnable = conf.PprofEnable
|
||||
|
||||
out.DNSServer = conf.DNSServer
|
||||
out.LoginFailExit = lo.ToPtr(conf.LoginFailExit)
|
||||
out.Start = conf.Start
|
||||
out.UDPPacketSize = conf.UDPPacketSize
|
||||
out.Metadatas = conf.Metas
|
||||
out.IncludeConfigFiles = conf.IncludeConfigFiles
|
||||
return out
|
||||
}
|
||||
|
||||
func Convert_ServerCommonConf_To_v1(conf *ServerCommonConf) *v1.ServerConfig {
|
||||
out := &v1.ServerConfig{}
|
||||
out.Auth.Method = conf.ServerConfig.AuthenticationMethod
|
||||
out.Auth.Token = conf.ServerConfig.Token
|
||||
if conf.ServerConfig.AuthenticateHeartBeats {
|
||||
out.Auth.AdditionalAuthScopes = append(out.Auth.AdditionalAuthScopes, v1.AuthScopeHeartBeats)
|
||||
}
|
||||
if conf.ServerConfig.AuthenticateNewWorkConns {
|
||||
out.Auth.AdditionalAuthScopes = append(out.Auth.AdditionalAuthScopes, v1.AuthScopeNewWorkConns)
|
||||
}
|
||||
out.Auth.OIDC.Audience = conf.ServerConfig.OidcAudience
|
||||
out.Auth.OIDC.Issuer = conf.ServerConfig.OidcIssuer
|
||||
out.Auth.OIDC.SkipExpiryCheck = conf.ServerConfig.OidcSkipExpiryCheck
|
||||
out.Auth.OIDC.SkipIssuerCheck = conf.ServerConfig.OidcSkipIssuerCheck
|
||||
|
||||
out.BindAddr = conf.BindAddr
|
||||
out.BindPort = conf.BindPort
|
||||
out.KCPBindPort = conf.KCPBindPort
|
||||
out.QUICBindPort = conf.QUICBindPort
|
||||
out.Transport.QUIC.KeepalivePeriod = conf.QUICKeepalivePeriod
|
||||
out.Transport.QUIC.MaxIdleTimeout = conf.QUICMaxIdleTimeout
|
||||
out.Transport.QUIC.MaxIncomingStreams = conf.QUICMaxIncomingStreams
|
||||
|
||||
out.ProxyBindAddr = conf.ProxyBindAddr
|
||||
out.VhostHTTPPort = conf.VhostHTTPPort
|
||||
out.VhostHTTPSPort = conf.VhostHTTPSPort
|
||||
out.TCPMuxHTTPConnectPort = conf.TCPMuxHTTPConnectPort
|
||||
out.TCPMuxPassthrough = conf.TCPMuxPassthrough
|
||||
out.VhostHTTPTimeout = conf.VhostHTTPTimeout
|
||||
|
||||
out.WebServer.Addr = conf.DashboardAddr
|
||||
out.WebServer.Port = conf.DashboardPort
|
||||
out.WebServer.User = conf.DashboardUser
|
||||
out.WebServer.Password = conf.DashboardPwd
|
||||
out.WebServer.AssetsDir = conf.AssetsDir
|
||||
if conf.DashboardTLSMode {
|
||||
out.WebServer.TLS = &v1.TLSConfig{}
|
||||
out.WebServer.TLS.CertFile = conf.DashboardTLSCertFile
|
||||
out.WebServer.TLS.KeyFile = conf.DashboardTLSKeyFile
|
||||
out.WebServer.PprofEnable = conf.PprofEnable
|
||||
}
|
||||
|
||||
out.EnablePrometheus = conf.EnablePrometheus
|
||||
|
||||
out.Log.To = conf.LogFile
|
||||
out.Log.Level = conf.LogLevel
|
||||
out.Log.MaxDays = conf.LogMaxDays
|
||||
out.Log.DisablePrintColor = conf.DisableLogColor
|
||||
|
||||
out.DetailedErrorsToClient = lo.ToPtr(conf.DetailedErrorsToClient)
|
||||
out.SubDomainHost = conf.SubDomainHost
|
||||
out.Custom404Page = conf.Custom404Page
|
||||
out.UserConnTimeout = conf.UserConnTimeout
|
||||
out.UDPPacketSize = conf.UDPPacketSize
|
||||
out.NatHoleAnalysisDataReserveHours = conf.NatHoleAnalysisDataReserveHours
|
||||
|
||||
out.Transport.TCPMux = lo.ToPtr(conf.TCPMux)
|
||||
out.Transport.TCPMuxKeepaliveInterval = conf.TCPMuxKeepaliveInterval
|
||||
out.Transport.TCPKeepAlive = conf.TCPKeepAlive
|
||||
out.Transport.MaxPoolCount = conf.MaxPoolCount
|
||||
out.Transport.HeartbeatTimeout = conf.HeartbeatTimeout
|
||||
|
||||
out.MaxPortsPerClient = conf.MaxPortsPerClient
|
||||
|
||||
out.TLS.Force = conf.TLSOnly
|
||||
out.TLS.CertFile = conf.TLSCertFile
|
||||
out.TLS.KeyFile = conf.TLSKeyFile
|
||||
out.TLS.TrustedCaFile = conf.TLSTrustedCaFile
|
||||
|
||||
for _, v := range conf.HTTPPlugins {
|
||||
out.HTTPPlugins = append(out.HTTPPlugins, v1.HTTPPluginOptions{
|
||||
Name: v.Name,
|
||||
Addr: v.Addr,
|
||||
Path: v.Path,
|
||||
Ops: v.Ops,
|
||||
TLSVerify: v.TLSVerify,
|
||||
})
|
||||
}
|
||||
|
||||
out.AllowPorts, _ = types.NewPortsRangeSliceFromString(conf.AllowPortsStr)
|
||||
return out
|
||||
}
|
||||
|
||||
func transformHeadersFromPluginParams(params map[string]string) v1.HeaderOperations {
|
||||
out := v1.HeaderOperations{}
|
||||
for k, v := range params {
|
||||
if !strings.HasPrefix(k, "plugin_header_") {
|
||||
continue
|
||||
}
|
||||
if k = strings.TrimPrefix(k, "plugin_header_"); k != "" {
|
||||
out.Set[k] = v
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func Convert_ProxyConf_To_v1_Base(conf ProxyConf) *v1.ProxyBaseConfig {
|
||||
out := &v1.ProxyBaseConfig{}
|
||||
base := conf.GetBaseConfig()
|
||||
|
||||
out.Name = base.ProxyName
|
||||
out.Type = base.ProxyType
|
||||
out.Metadatas = base.Metas
|
||||
|
||||
out.Transport.UseEncryption = base.UseEncryption
|
||||
out.Transport.UseCompression = base.UseCompression
|
||||
out.Transport.BandwidthLimit = base.BandwidthLimit
|
||||
out.Transport.BandwidthLimitMode = base.BandwidthLimitMode
|
||||
out.Transport.ProxyProtocolVersion = base.ProxyProtocolVersion
|
||||
|
||||
out.LoadBalancer.Group = base.Group
|
||||
out.LoadBalancer.GroupKey = base.GroupKey
|
||||
|
||||
out.HealthCheck.Type = base.HealthCheckType
|
||||
out.HealthCheck.TimeoutSeconds = base.HealthCheckTimeoutS
|
||||
out.HealthCheck.MaxFailed = base.HealthCheckMaxFailed
|
||||
out.HealthCheck.IntervalSeconds = base.HealthCheckIntervalS
|
||||
out.HealthCheck.Path = base.HealthCheckURL
|
||||
|
||||
out.LocalIP = base.LocalIP
|
||||
out.LocalPort = base.LocalPort
|
||||
|
||||
switch base.Plugin {
|
||||
case "http2https":
|
||||
out.Plugin.ClientPluginOptions = &v1.HTTP2HTTPSPluginOptions{
|
||||
LocalAddr: base.PluginParams["plugin_local_addr"],
|
||||
HostHeaderRewrite: base.PluginParams["plugin_host_header_rewrite"],
|
||||
RequestHeaders: transformHeadersFromPluginParams(base.PluginParams),
|
||||
}
|
||||
case "http_proxy":
|
||||
out.Plugin.ClientPluginOptions = &v1.HTTPProxyPluginOptions{
|
||||
HTTPUser: base.PluginParams["plugin_http_user"],
|
||||
HTTPPassword: base.PluginParams["plugin_http_passwd"],
|
||||
}
|
||||
case "https2http":
|
||||
out.Plugin.ClientPluginOptions = &v1.HTTPS2HTTPPluginOptions{
|
||||
LocalAddr: base.PluginParams["plugin_local_addr"],
|
||||
HostHeaderRewrite: base.PluginParams["plugin_host_header_rewrite"],
|
||||
RequestHeaders: transformHeadersFromPluginParams(base.PluginParams),
|
||||
CrtPath: base.PluginParams["plugin_crt_path"],
|
||||
KeyPath: base.PluginParams["plugin_key_path"],
|
||||
}
|
||||
case "https2https":
|
||||
out.Plugin.ClientPluginOptions = &v1.HTTPS2HTTPSPluginOptions{
|
||||
LocalAddr: base.PluginParams["plugin_local_addr"],
|
||||
HostHeaderRewrite: base.PluginParams["plugin_host_header_rewrite"],
|
||||
RequestHeaders: transformHeadersFromPluginParams(base.PluginParams),
|
||||
CrtPath: base.PluginParams["plugin_crt_path"],
|
||||
KeyPath: base.PluginParams["plugin_key_path"],
|
||||
}
|
||||
case "socks5":
|
||||
out.Plugin.ClientPluginOptions = &v1.Socks5PluginOptions{
|
||||
Username: base.PluginParams["plugin_user"],
|
||||
Password: base.PluginParams["plugin_passwd"],
|
||||
}
|
||||
case "static_file":
|
||||
out.Plugin.ClientPluginOptions = &v1.StaticFilePluginOptions{
|
||||
LocalPath: base.PluginParams["plugin_local_path"],
|
||||
StripPrefix: base.PluginParams["plugin_strip_prefix"],
|
||||
HTTPUser: base.PluginParams["plugin_http_user"],
|
||||
HTTPPassword: base.PluginParams["plugin_http_passwd"],
|
||||
}
|
||||
case "unix_domain_socket":
|
||||
out.Plugin.ClientPluginOptions = &v1.UnixDomainSocketPluginOptions{
|
||||
UnixPath: base.PluginParams["plugin_unix_path"],
|
||||
}
|
||||
}
|
||||
out.Plugin.Type = base.Plugin
|
||||
return out
|
||||
}
|
||||
|
||||
func Convert_ProxyConf_To_v1(conf ProxyConf) v1.ProxyConfigurer {
|
||||
outBase := Convert_ProxyConf_To_v1_Base(conf)
|
||||
var out v1.ProxyConfigurer
|
||||
switch v := conf.(type) {
|
||||
case *TCPProxyConf:
|
||||
c := &v1.TCPProxyConfig{ProxyBaseConfig: *outBase}
|
||||
c.RemotePort = v.RemotePort
|
||||
out = c
|
||||
case *UDPProxyConf:
|
||||
c := &v1.UDPProxyConfig{ProxyBaseConfig: *outBase}
|
||||
c.RemotePort = v.RemotePort
|
||||
out = c
|
||||
case *HTTPProxyConf:
|
||||
c := &v1.HTTPProxyConfig{ProxyBaseConfig: *outBase}
|
||||
c.CustomDomains = v.CustomDomains
|
||||
c.SubDomain = v.SubDomain
|
||||
c.Locations = v.Locations
|
||||
c.HTTPUser = v.HTTPUser
|
||||
c.HTTPPassword = v.HTTPPwd
|
||||
c.HostHeaderRewrite = v.HostHeaderRewrite
|
||||
c.RequestHeaders.Set = v.Headers
|
||||
c.RouteByHTTPUser = v.RouteByHTTPUser
|
||||
out = c
|
||||
case *HTTPSProxyConf:
|
||||
c := &v1.HTTPSProxyConfig{ProxyBaseConfig: *outBase}
|
||||
c.CustomDomains = v.CustomDomains
|
||||
c.SubDomain = v.SubDomain
|
||||
out = c
|
||||
case *TCPMuxProxyConf:
|
||||
c := &v1.TCPMuxProxyConfig{ProxyBaseConfig: *outBase}
|
||||
c.CustomDomains = v.CustomDomains
|
||||
c.SubDomain = v.SubDomain
|
||||
c.HTTPUser = v.HTTPUser
|
||||
c.HTTPPassword = v.HTTPPwd
|
||||
c.RouteByHTTPUser = v.RouteByHTTPUser
|
||||
c.Multiplexer = v.Multiplexer
|
||||
out = c
|
||||
case *STCPProxyConf:
|
||||
c := &v1.STCPProxyConfig{ProxyBaseConfig: *outBase}
|
||||
c.Secretkey = v.Sk
|
||||
c.AllowUsers = v.AllowUsers
|
||||
out = c
|
||||
case *SUDPProxyConf:
|
||||
c := &v1.SUDPProxyConfig{ProxyBaseConfig: *outBase}
|
||||
c.Secretkey = v.Sk
|
||||
c.AllowUsers = v.AllowUsers
|
||||
out = c
|
||||
case *XTCPProxyConf:
|
||||
c := &v1.XTCPProxyConfig{ProxyBaseConfig: *outBase}
|
||||
c.Secretkey = v.Sk
|
||||
c.AllowUsers = v.AllowUsers
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func Convert_VisitorConf_To_v1_Base(conf VisitorConf) *v1.VisitorBaseConfig {
|
||||
out := &v1.VisitorBaseConfig{}
|
||||
base := conf.GetBaseConfig()
|
||||
|
||||
out.Name = base.ProxyName
|
||||
out.Type = base.ProxyType
|
||||
out.Transport.UseEncryption = base.UseEncryption
|
||||
out.Transport.UseCompression = base.UseCompression
|
||||
out.SecretKey = base.Sk
|
||||
out.ServerUser = base.ServerUser
|
||||
out.ServerName = base.ServerName
|
||||
out.BindAddr = base.BindAddr
|
||||
out.BindPort = base.BindPort
|
||||
return out
|
||||
}
|
||||
|
||||
func Convert_VisitorConf_To_v1(conf VisitorConf) v1.VisitorConfigurer {
|
||||
outBase := Convert_VisitorConf_To_v1_Base(conf)
|
||||
var out v1.VisitorConfigurer
|
||||
switch v := conf.(type) {
|
||||
case *STCPVisitorConf:
|
||||
c := &v1.STCPVisitorConfig{VisitorBaseConfig: *outBase}
|
||||
out = c
|
||||
case *SUDPVisitorConf:
|
||||
c := &v1.SUDPVisitorConfig{VisitorBaseConfig: *outBase}
|
||||
out = c
|
||||
case *XTCPVisitorConf:
|
||||
c := &v1.XTCPVisitorConfig{VisitorBaseConfig: *outBase}
|
||||
c.Protocol = v.Protocol
|
||||
c.KeepTunnelOpen = v.KeepTunnelOpen
|
||||
c.MaxRetriesAnHour = v.MaxRetriesAnHour
|
||||
c.MinRetryInterval = v.MinRetryInterval
|
||||
c.FallbackTo = v.FallbackTo
|
||||
c.FallbackTimeoutMs = v.FallbackTimeoutMs
|
||||
out = c
|
||||
}
|
||||
return out
|
||||
}
|
@@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package config
|
||||
package legacy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -40,7 +40,6 @@ func ParseClientConfig(filePath string) (
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
cfg.Complete()
|
||||
if err = cfg.Validate(); err != nil {
|
||||
err = fmt.Errorf("parse config error: %v", err)
|
||||
return
|
375
pkg/config/legacy/proxy.go
Normal file
375
pkg/config/legacy/proxy.go
Normal file
@@ -0,0 +1,375 @@
|
||||
// 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 legacy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"gopkg.in/ini.v1"
|
||||
|
||||
"github.com/fatedier/frp/pkg/config/types"
|
||||
"github.com/fatedier/frp/pkg/consts"
|
||||
)
|
||||
|
||||
// Proxy
|
||||
var (
|
||||
proxyConfTypeMap = map[string]reflect.Type{
|
||||
consts.TCPProxy: reflect.TypeOf(TCPProxyConf{}),
|
||||
consts.TCPMuxProxy: reflect.TypeOf(TCPMuxProxyConf{}),
|
||||
consts.UDPProxy: reflect.TypeOf(UDPProxyConf{}),
|
||||
consts.HTTPProxy: reflect.TypeOf(HTTPProxyConf{}),
|
||||
consts.HTTPSProxy: reflect.TypeOf(HTTPSProxyConf{}),
|
||||
consts.STCPProxy: reflect.TypeOf(STCPProxyConf{}),
|
||||
consts.XTCPProxy: reflect.TypeOf(XTCPProxyConf{}),
|
||||
consts.SUDPProxy: reflect.TypeOf(SUDPProxyConf{}),
|
||||
}
|
||||
)
|
||||
|
||||
type ProxyConf interface {
|
||||
// GetBaseConfig returns the BaseProxyConf for this config.
|
||||
GetBaseConfig() *BaseProxyConf
|
||||
// UnmarshalFromIni unmarshals a ini.Section into this config. This function
|
||||
// will be called on the frpc side.
|
||||
UnmarshalFromIni(string, string, *ini.Section) error
|
||||
}
|
||||
|
||||
func NewConfByType(proxyType string) ProxyConf {
|
||||
v, ok := proxyConfTypeMap[proxyType]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
cfg := reflect.New(v).Interface().(ProxyConf)
|
||||
return cfg
|
||||
}
|
||||
|
||||
// Proxy Conf Loader
|
||||
// DefaultProxyConf creates a empty ProxyConf object by proxyType.
|
||||
// If proxyType doesn't exist, return nil.
|
||||
func DefaultProxyConf(proxyType string) ProxyConf {
|
||||
return NewConfByType(proxyType)
|
||||
}
|
||||
|
||||
// Proxy loaded from ini
|
||||
func NewProxyConfFromIni(prefix, name string, section *ini.Section) (ProxyConf, error) {
|
||||
// section.Key: if key not exists, section will set it with default value.
|
||||
proxyType := section.Key("type").String()
|
||||
if proxyType == "" {
|
||||
proxyType = consts.TCPProxy
|
||||
}
|
||||
|
||||
conf := DefaultProxyConf(proxyType)
|
||||
if conf == nil {
|
||||
return nil, fmt.Errorf("invalid type [%s]", proxyType)
|
||||
}
|
||||
|
||||
if err := conf.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return conf, nil
|
||||
}
|
||||
|
||||
// LocalSvrConf configures what location the client will to, or what
|
||||
// plugin will be used.
|
||||
type LocalSvrConf struct {
|
||||
// LocalIP specifies the IP address or host name to to.
|
||||
LocalIP string `ini:"local_ip" json:"local_ip"`
|
||||
// LocalPort specifies the port to to.
|
||||
LocalPort int `ini:"local_port" json:"local_port"`
|
||||
|
||||
// Plugin specifies what plugin should be used for ng. If this value
|
||||
// is set, the LocalIp and LocalPort values will be ignored. By default,
|
||||
// this value is "".
|
||||
Plugin string `ini:"plugin" json:"plugin"`
|
||||
// PluginParams specify parameters to be passed to the plugin, if one is
|
||||
// being used. By default, this value is an empty map.
|
||||
PluginParams map[string]string `ini:"-"`
|
||||
}
|
||||
|
||||
// HealthCheckConf configures health checking. This can be useful for load
|
||||
// balancing purposes to detect and remove proxies to failing services.
|
||||
type HealthCheckConf struct {
|
||||
// HealthCheckType specifies what protocol to use for health checking.
|
||||
// Valid values include "tcp", "http", and "". If this value is "", health
|
||||
// checking will not be performed. By default, this value is "".
|
||||
//
|
||||
// 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.
|
||||
HealthCheckType string `ini:"health_check_type" json:"health_check_type"` // tcp | http
|
||||
// HealthCheckTimeoutS 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.
|
||||
HealthCheckTimeoutS int `ini:"health_check_timeout_s" json:"health_check_timeout_s"`
|
||||
// HealthCheckMaxFailed specifies the number of allowed failures before the
|
||||
// is stopped. By default, this value is 1.
|
||||
HealthCheckMaxFailed int `ini:"health_check_max_failed" json:"health_check_max_failed"`
|
||||
// HealthCheckIntervalS specifies the time in seconds between health
|
||||
// checks. By default, this value is 10.
|
||||
HealthCheckIntervalS int `ini:"health_check_interval_s" json:"health_check_interval_s"`
|
||||
// HealthCheckURL specifies the address to send health checks to if the
|
||||
// health check type is "http".
|
||||
HealthCheckURL string `ini:"health_check_url" json:"health_check_url"`
|
||||
// HealthCheckAddr specifies the address to connect to if the health check
|
||||
// type is "tcp".
|
||||
HealthCheckAddr string `ini:"-"`
|
||||
}
|
||||
|
||||
// BaseProxyConf provides configuration info that is common to all types.
|
||||
type BaseProxyConf struct {
|
||||
// ProxyName is the name of this
|
||||
ProxyName string `ini:"name" json:"name"`
|
||||
// ProxyType specifies the type of this Valid values include "tcp",
|
||||
// "udp", "http", "https", "stcp", and "xtcp". By default, this value is
|
||||
// "tcp".
|
||||
ProxyType string `ini:"type" json:"type"`
|
||||
|
||||
// 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. By default, this value is false.
|
||||
UseEncryption bool `ini:"use_encryption" json:"use_encryption"`
|
||||
// UseCompression controls whether or not communication with the server
|
||||
// will be compressed. By default, this value is false.
|
||||
UseCompression bool `ini:"use_compression" json:"use_compression"`
|
||||
// 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. By default, this value is "".
|
||||
Group string `ini:"group" json:"group"`
|
||||
// GroupKey specifies a group key, which should be the same among proxies
|
||||
// of the same group. By default, this value is "".
|
||||
GroupKey string `ini:"group_key" json:"group_key"`
|
||||
|
||||
// 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 `ini:"proxy_protocol_version" json:"proxy_protocol_version"`
|
||||
|
||||
// BandwidthLimit limit the bandwidth
|
||||
// 0 means no limit
|
||||
BandwidthLimit types.BandwidthQuantity `ini:"bandwidth_limit" json:"bandwidth_limit"`
|
||||
// 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 `ini:"bandwidth_limit_mode" json:"bandwidth_limit_mode"`
|
||||
|
||||
// meta info for each proxy
|
||||
Metas map[string]string `ini:"-" json:"metas"`
|
||||
|
||||
LocalSvrConf `ini:",extends"`
|
||||
HealthCheckConf `ini:",extends"`
|
||||
}
|
||||
|
||||
// Base
|
||||
func (cfg *BaseProxyConf) GetBaseConfig() *BaseProxyConf {
|
||||
return cfg
|
||||
}
|
||||
|
||||
// BaseProxyConf apply custom logic changes.
|
||||
func (cfg *BaseProxyConf) decorate(_ string, name string, section *ini.Section) error {
|
||||
cfg.ProxyName = name
|
||||
// metas_xxx
|
||||
cfg.Metas = GetMapWithoutPrefix(section.KeysHash(), "meta_")
|
||||
|
||||
// bandwidth_limit
|
||||
if bandwidth, err := section.GetKey("bandwidth_limit"); err == nil {
|
||||
cfg.BandwidthLimit, err = types.NewBandwidthQuantity(bandwidth.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// plugin_xxx
|
||||
cfg.LocalSvrConf.PluginParams = GetMapByPrefix(section.KeysHash(), "plugin_")
|
||||
return nil
|
||||
}
|
||||
|
||||
type DomainConf struct {
|
||||
CustomDomains []string `ini:"custom_domains" json:"custom_domains"`
|
||||
SubDomain string `ini:"subdomain" json:"subdomain"`
|
||||
}
|
||||
|
||||
type RoleServerCommonConf struct {
|
||||
Role string `ini:"role" json:"role"`
|
||||
Sk string `ini:"sk" json:"sk"`
|
||||
AllowUsers []string `ini:"allow_users" json:"allow_users"`
|
||||
}
|
||||
|
||||
// HTTP
|
||||
type HTTPProxyConf struct {
|
||||
BaseProxyConf `ini:",extends"`
|
||||
DomainConf `ini:",extends"`
|
||||
|
||||
Locations []string `ini:"locations" json:"locations"`
|
||||
HTTPUser string `ini:"http_user" json:"http_user"`
|
||||
HTTPPwd string `ini:"http_pwd" json:"http_pwd"`
|
||||
HostHeaderRewrite string `ini:"host_header_rewrite" json:"host_header_rewrite"`
|
||||
Headers map[string]string `ini:"-" json:"headers"`
|
||||
RouteByHTTPUser string `ini:"route_by_http_user" json:"route_by_http_user"`
|
||||
}
|
||||
|
||||
func (cfg *HTTPProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error {
|
||||
err := preUnmarshalFromIni(cfg, prefix, name, section)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add custom logic unmarshal if exists
|
||||
cfg.Headers = GetMapWithoutPrefix(section.KeysHash(), "header_")
|
||||
return nil
|
||||
}
|
||||
|
||||
// HTTPS
|
||||
type HTTPSProxyConf struct {
|
||||
BaseProxyConf `ini:",extends"`
|
||||
DomainConf `ini:",extends"`
|
||||
}
|
||||
|
||||
func (cfg *HTTPSProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error {
|
||||
err := preUnmarshalFromIni(cfg, prefix, name, section)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add custom logic unmarshal if exists
|
||||
return nil
|
||||
}
|
||||
|
||||
// TCP
|
||||
type TCPProxyConf struct {
|
||||
BaseProxyConf `ini:",extends"`
|
||||
RemotePort int `ini:"remote_port" json:"remote_port"`
|
||||
}
|
||||
|
||||
func (cfg *TCPProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error {
|
||||
err := preUnmarshalFromIni(cfg, prefix, name, section)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add custom logic unmarshal if exists
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UDP
|
||||
type UDPProxyConf struct {
|
||||
BaseProxyConf `ini:",extends"`
|
||||
|
||||
RemotePort int `ini:"remote_port" json:"remote_port"`
|
||||
}
|
||||
|
||||
func (cfg *UDPProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error {
|
||||
err := preUnmarshalFromIni(cfg, prefix, name, section)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add custom logic unmarshal if exists
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TCPMux
|
||||
type TCPMuxProxyConf struct {
|
||||
BaseProxyConf `ini:",extends"`
|
||||
DomainConf `ini:",extends"`
|
||||
HTTPUser string `ini:"http_user" json:"http_user,omitempty"`
|
||||
HTTPPwd string `ini:"http_pwd" json:"http_pwd,omitempty"`
|
||||
RouteByHTTPUser string `ini:"route_by_http_user" json:"route_by_http_user"`
|
||||
|
||||
Multiplexer string `ini:"multiplexer"`
|
||||
}
|
||||
|
||||
func (cfg *TCPMuxProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error {
|
||||
err := preUnmarshalFromIni(cfg, prefix, name, section)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add custom logic unmarshal if exists
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// STCP
|
||||
type STCPProxyConf struct {
|
||||
BaseProxyConf `ini:",extends"`
|
||||
RoleServerCommonConf `ini:",extends"`
|
||||
}
|
||||
|
||||
func (cfg *STCPProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error {
|
||||
err := preUnmarshalFromIni(cfg, prefix, name, section)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add custom logic unmarshal if exists
|
||||
if cfg.Role == "" {
|
||||
cfg.Role = "server"
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// XTCP
|
||||
type XTCPProxyConf struct {
|
||||
BaseProxyConf `ini:",extends"`
|
||||
RoleServerCommonConf `ini:",extends"`
|
||||
}
|
||||
|
||||
func (cfg *XTCPProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error {
|
||||
err := preUnmarshalFromIni(cfg, prefix, name, section)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add custom logic unmarshal if exists
|
||||
if cfg.Role == "" {
|
||||
cfg.Role = "server"
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SUDP
|
||||
type SUDPProxyConf struct {
|
||||
BaseProxyConf `ini:",extends"`
|
||||
RoleServerCommonConf `ini:",extends"`
|
||||
}
|
||||
|
||||
func (cfg *SUDPProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error {
|
||||
err := preUnmarshalFromIni(cfg, prefix, name, section)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add custom logic unmarshal if exists
|
||||
return nil
|
||||
}
|
||||
|
||||
func preUnmarshalFromIni(cfg ProxyConf, prefix string, name string, section *ini.Section) error {
|
||||
err := section.MapTo(cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = cfg.GetBaseConfig().decorate(prefix, name, section)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 The frp Authors
|
||||
// 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.
|
||||
@@ -12,25 +12,29 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package config
|
||||
package legacy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
"gopkg.in/ini.v1"
|
||||
|
||||
"github.com/fatedier/frp/pkg/auth"
|
||||
plugin "github.com/fatedier/frp/pkg/plugin/server"
|
||||
"github.com/fatedier/frp/pkg/util/util"
|
||||
legacyauth "github.com/fatedier/frp/pkg/auth/legacy"
|
||||
)
|
||||
|
||||
type HTTPPluginOptions struct {
|
||||
Name string `ini:"name"`
|
||||
Addr string `ini:"addr"`
|
||||
Path string `ini:"path"`
|
||||
Ops []string `ini:"ops"`
|
||||
TLSVerify bool `ini:"tls_verify"`
|
||||
}
|
||||
|
||||
// ServerCommonConf contains information for a server service. It is
|
||||
// recommended to use GetDefaultServerConf instead of creating this object
|
||||
// directly, so that all unspecified fields have reasonable default values.
|
||||
type ServerCommonConf struct {
|
||||
auth.ServerConfig `ini:",extends"`
|
||||
legacyauth.ServerConfig `ini:",extends"`
|
||||
|
||||
// BindAddr specifies the address that the server binds to. By default,
|
||||
// this value is "0.0.0.0".
|
||||
@@ -185,7 +189,7 @@ type ServerCommonConf struct {
|
||||
// connection. By default, this value is 10.
|
||||
UserConnTimeout int64 `ini:"user_conn_timeout" json:"user_conn_timeout"`
|
||||
// HTTPPlugins specify the server plugins support HTTP protocol.
|
||||
HTTPPlugins map[string]plugin.HTTPPluginOptions `ini:"-" json:"http_plugins"`
|
||||
HTTPPlugins map[string]HTTPPluginOptions `ini:"-" json:"http_plugins"`
|
||||
// UDPPacketSize specifies the UDP packet size
|
||||
// By default, this value is 1500
|
||||
UDPPacketSize int64 `ini:"udp_packet_size" json:"udp_packet_size"`
|
||||
@@ -200,7 +204,7 @@ type ServerCommonConf struct {
|
||||
// defaults.
|
||||
func GetDefaultServerConf() ServerCommonConf {
|
||||
return ServerCommonConf{
|
||||
ServerConfig: auth.GetDefaultServerConf(),
|
||||
ServerConfig: legacyauth.GetDefaultServerConf(),
|
||||
BindAddr: "0.0.0.0",
|
||||
BindPort: 7000,
|
||||
QUICKeepalivePeriod: 10,
|
||||
@@ -221,7 +225,7 @@ func GetDefaultServerConf() ServerCommonConf {
|
||||
MaxPortsPerClient: 0,
|
||||
HeartbeatTimeout: 90,
|
||||
UserConnTimeout: 10,
|
||||
HTTPPlugins: make(map[string]plugin.HTTPPluginOptions),
|
||||
HTTPPlugins: make(map[string]HTTPPluginOptions),
|
||||
UDPPacketSize: 1500,
|
||||
NatHoleAnalysisDataReserveHours: 7 * 24,
|
||||
}
|
||||
@@ -253,18 +257,11 @@ func UnmarshalServerConfFromIni(source interface{}) (ServerCommonConf, error) {
|
||||
// allow_ports
|
||||
allowPortStr := s.Key("allow_ports").String()
|
||||
if allowPortStr != "" {
|
||||
allowPorts, err := util.ParseRangeNumbers(allowPortStr)
|
||||
if err != nil {
|
||||
return ServerCommonConf{}, fmt.Errorf("invalid allow_ports: %v", err)
|
||||
}
|
||||
for _, port := range allowPorts {
|
||||
common.AllowPorts[int(port)] = struct{}{}
|
||||
}
|
||||
common.AllowPortsStr = allowPortStr
|
||||
}
|
||||
|
||||
// plugin.xxx
|
||||
pluginOpts := make(map[string]plugin.HTTPPluginOptions)
|
||||
pluginOpts := make(map[string]HTTPPluginOptions)
|
||||
for _, section := range f.Sections() {
|
||||
name := section.Name()
|
||||
if !strings.HasPrefix(name, "plugin.") {
|
||||
@@ -283,47 +280,10 @@ func UnmarshalServerConfFromIni(source interface{}) (ServerCommonConf, error) {
|
||||
return common, nil
|
||||
}
|
||||
|
||||
func (cfg *ServerCommonConf) Complete() {
|
||||
if cfg.LogFile == "console" {
|
||||
cfg.LogWay = "console"
|
||||
} else {
|
||||
cfg.LogWay = "file"
|
||||
}
|
||||
|
||||
if cfg.ProxyBindAddr == "" {
|
||||
cfg.ProxyBindAddr = cfg.BindAddr
|
||||
}
|
||||
|
||||
if cfg.TLSTrustedCaFile != "" {
|
||||
cfg.TLSOnly = true
|
||||
}
|
||||
}
|
||||
|
||||
func (cfg *ServerCommonConf) Validate() error {
|
||||
if !cfg.DashboardTLSMode {
|
||||
if cfg.DashboardTLSCertFile != "" {
|
||||
fmt.Println("WARNING! dashboard_tls_cert_file is invalid when dashboard_tls_mode is false")
|
||||
}
|
||||
|
||||
if cfg.DashboardTLSKeyFile != "" {
|
||||
fmt.Println("WARNING! dashboard_tls_key_file is invalid when dashboard_tls_mode is false")
|
||||
}
|
||||
} else {
|
||||
if cfg.DashboardTLSCertFile == "" {
|
||||
return fmt.Errorf("ERROR! dashboard_tls_cert_file must be specified when dashboard_tls_mode is true")
|
||||
}
|
||||
|
||||
if cfg.DashboardTLSKeyFile == "" {
|
||||
return fmt.Errorf("ERROR! dashboard_tls_cert_file must be specified when dashboard_tls_mode is true")
|
||||
}
|
||||
}
|
||||
return validator.New().Struct(cfg)
|
||||
}
|
||||
|
||||
func loadHTTPPluginOpt(section *ini.Section) (*plugin.HTTPPluginOptions, error) {
|
||||
func loadHTTPPluginOpt(section *ini.Section) (*HTTPPluginOptions, error) {
|
||||
name := strings.TrimSpace(strings.TrimPrefix(section.Name(), "plugin."))
|
||||
|
||||
opt := new(plugin.HTTPPluginOptions)
|
||||
opt := &HTTPPluginOptions{}
|
||||
err := section.MapTo(opt)
|
||||
if err != nil {
|
||||
return nil, err
|
@@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package config
|
||||
package legacy
|
||||
|
||||
import (
|
||||
"strings"
|
@@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package config
|
||||
package legacy
|
||||
|
||||
import (
|
||||
"bytes"
|
@@ -1,4 +1,4 @@
|
||||
// Copyright 2018 fatedier, fatedier@gmail.com
|
||||
// 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.
|
||||
@@ -12,13 +12,12 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package config
|
||||
package legacy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/samber/lo"
|
||||
"gopkg.in/ini.v1"
|
||||
|
||||
"github.com/fatedier/frp/pkg/consts"
|
||||
@@ -38,8 +37,16 @@ type VisitorConf interface {
|
||||
GetBaseConfig() *BaseVisitorConf
|
||||
// UnmarshalFromIni unmarshals config from ini.
|
||||
UnmarshalFromIni(prefix string, name string, section *ini.Section) error
|
||||
// Validate validates config.
|
||||
Validate() error
|
||||
}
|
||||
|
||||
// DefaultVisitorConf creates a empty VisitorConf object by visitorType.
|
||||
// If visitorType doesn't exist, return nil.
|
||||
func DefaultVisitorConf(visitorType string) VisitorConf {
|
||||
v, ok := visitorConfTypeMap[visitorType]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return reflect.New(v).Interface().(VisitorConf)
|
||||
}
|
||||
|
||||
type BaseVisitorConf struct {
|
||||
@@ -59,96 +66,14 @@ type BaseVisitorConf struct {
|
||||
BindPort int `ini:"bind_port" json:"bind_port"`
|
||||
}
|
||||
|
||||
type SUDPVisitorConf struct {
|
||||
BaseVisitorConf `ini:",extends"`
|
||||
}
|
||||
|
||||
type STCPVisitorConf struct {
|
||||
BaseVisitorConf `ini:",extends"`
|
||||
}
|
||||
|
||||
type XTCPVisitorConf struct {
|
||||
BaseVisitorConf `ini:",extends"`
|
||||
|
||||
Protocol string `ini:"protocol" json:"protocol,omitempty"`
|
||||
KeepTunnelOpen bool `ini:"keep_tunnel_open" json:"keep_tunnel_open,omitempty"`
|
||||
MaxRetriesAnHour int `ini:"max_retries_an_hour" json:"max_retries_an_hour,omitempty"`
|
||||
MinRetryInterval int `ini:"min_retry_interval" json:"min_retry_interval,omitempty"`
|
||||
FallbackTo string `ini:"fallback_to" json:"fallback_to,omitempty"`
|
||||
FallbackTimeoutMs int `ini:"fallback_timeout_ms" json:"fallback_timeout_ms,omitempty"`
|
||||
}
|
||||
|
||||
// DefaultVisitorConf creates a empty VisitorConf object by visitorType.
|
||||
// If visitorType doesn't exist, return nil.
|
||||
func DefaultVisitorConf(visitorType string) VisitorConf {
|
||||
v, ok := visitorConfTypeMap[visitorType]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return reflect.New(v).Interface().(VisitorConf)
|
||||
}
|
||||
|
||||
// Visitor loaded from ini
|
||||
func NewVisitorConfFromIni(prefix string, name string, section *ini.Section) (VisitorConf, error) {
|
||||
// section.Key: if key not exists, section will set it with default value.
|
||||
visitorType := section.Key("type").String()
|
||||
|
||||
if visitorType == "" {
|
||||
return nil, fmt.Errorf("type shouldn't be empty")
|
||||
}
|
||||
|
||||
conf := DefaultVisitorConf(visitorType)
|
||||
if conf == nil {
|
||||
return nil, fmt.Errorf("type [%s] error", visitorType)
|
||||
}
|
||||
|
||||
if err := conf.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||
return nil, fmt.Errorf("type [%s] error", visitorType)
|
||||
}
|
||||
|
||||
if err := conf.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return conf, nil
|
||||
}
|
||||
|
||||
// Base
|
||||
func (cfg *BaseVisitorConf) GetBaseConfig() *BaseVisitorConf {
|
||||
return cfg
|
||||
}
|
||||
|
||||
func (cfg *BaseVisitorConf) validate() (err error) {
|
||||
if cfg.Role != "visitor" {
|
||||
err = fmt.Errorf("invalid role")
|
||||
return
|
||||
}
|
||||
if cfg.BindAddr == "" {
|
||||
err = fmt.Errorf("bind_addr shouldn't be empty")
|
||||
return
|
||||
}
|
||||
// BindPort can be less than 0, it means don't bind to the port and only receive connections redirected from
|
||||
// other visitors
|
||||
if cfg.BindPort == 0 {
|
||||
err = fmt.Errorf("bind_port is required")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (cfg *BaseVisitorConf) unmarshalFromIni(prefix string, name string, section *ini.Section) error {
|
||||
_ = section
|
||||
|
||||
func (cfg *BaseVisitorConf) unmarshalFromIni(_ string, name string, _ *ini.Section) error {
|
||||
// Custom decoration after basic unmarshal:
|
||||
// proxy name
|
||||
cfg.ProxyName = prefix + name
|
||||
|
||||
// server_name
|
||||
if cfg.ServerUser == "" {
|
||||
cfg.ServerName = prefix + cfg.ServerName
|
||||
} else {
|
||||
cfg.ServerName = cfg.ServerUser + "." + cfg.ServerName
|
||||
}
|
||||
cfg.ProxyName = name
|
||||
|
||||
// bind_addr
|
||||
if cfg.BindAddr == "" {
|
||||
@@ -170,8 +95,9 @@ func preVisitorUnmarshalFromIni(cfg VisitorConf, prefix string, name string, sec
|
||||
return nil
|
||||
}
|
||||
|
||||
// SUDP
|
||||
var _ VisitorConf = &SUDPVisitorConf{}
|
||||
type SUDPVisitorConf struct {
|
||||
BaseVisitorConf `ini:",extends"`
|
||||
}
|
||||
|
||||
func (cfg *SUDPVisitorConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) (err error) {
|
||||
err = preVisitorUnmarshalFromIni(cfg, prefix, name, section)
|
||||
@@ -184,19 +110,10 @@ func (cfg *SUDPVisitorConf) UnmarshalFromIni(prefix string, name string, section
|
||||
return
|
||||
}
|
||||
|
||||
func (cfg *SUDPVisitorConf) Validate() (err error) {
|
||||
if err = cfg.BaseVisitorConf.validate(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Add custom logic validate, if exists
|
||||
|
||||
return
|
||||
type STCPVisitorConf struct {
|
||||
BaseVisitorConf `ini:",extends"`
|
||||
}
|
||||
|
||||
// STCP
|
||||
var _ VisitorConf = &STCPVisitorConf{}
|
||||
|
||||
func (cfg *STCPVisitorConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) (err error) {
|
||||
err = preVisitorUnmarshalFromIni(cfg, prefix, name, section)
|
||||
if err != nil {
|
||||
@@ -208,19 +125,17 @@ func (cfg *STCPVisitorConf) UnmarshalFromIni(prefix string, name string, section
|
||||
return
|
||||
}
|
||||
|
||||
func (cfg *STCPVisitorConf) Validate() (err error) {
|
||||
if err = cfg.BaseVisitorConf.validate(); err != nil {
|
||||
return
|
||||
}
|
||||
type XTCPVisitorConf struct {
|
||||
BaseVisitorConf `ini:",extends"`
|
||||
|
||||
// Add custom logic validate, if exists
|
||||
|
||||
return
|
||||
Protocol string `ini:"protocol" json:"protocol,omitempty"`
|
||||
KeepTunnelOpen bool `ini:"keep_tunnel_open" json:"keep_tunnel_open,omitempty"`
|
||||
MaxRetriesAnHour int `ini:"max_retries_an_hour" json:"max_retries_an_hour,omitempty"`
|
||||
MinRetryInterval int `ini:"min_retry_interval" json:"min_retry_interval,omitempty"`
|
||||
FallbackTo string `ini:"fallback_to" json:"fallback_to,omitempty"`
|
||||
FallbackTimeoutMs int `ini:"fallback_timeout_ms" json:"fallback_timeout_ms,omitempty"`
|
||||
}
|
||||
|
||||
// XTCP
|
||||
var _ VisitorConf = &XTCPVisitorConf{}
|
||||
|
||||
func (cfg *XTCPVisitorConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) (err error) {
|
||||
err = preVisitorUnmarshalFromIni(cfg, prefix, name, section)
|
||||
if err != nil {
|
||||
@@ -243,14 +158,22 @@ func (cfg *XTCPVisitorConf) UnmarshalFromIni(prefix string, name string, section
|
||||
return
|
||||
}
|
||||
|
||||
func (cfg *XTCPVisitorConf) Validate() (err error) {
|
||||
if err = cfg.BaseVisitorConf.validate(); err != nil {
|
||||
return
|
||||
// Visitor loaded from ini
|
||||
func NewVisitorConfFromIni(prefix string, name string, section *ini.Section) (VisitorConf, error) {
|
||||
// section.Key: if key not exists, section will set it with default value.
|
||||
visitorType := section.Key("type").String()
|
||||
|
||||
if visitorType == "" {
|
||||
return nil, fmt.Errorf("type shouldn't be empty")
|
||||
}
|
||||
|
||||
// Add custom logic validate, if exists
|
||||
if !lo.Contains([]string{"", "kcp", "quic"}, cfg.Protocol) {
|
||||
return fmt.Errorf("protocol should be 'kcp' or 'quic'")
|
||||
conf := DefaultVisitorConf(visitorType)
|
||||
if conf == nil {
|
||||
return nil, fmt.Errorf("type [%s] error", visitorType)
|
||||
}
|
||||
return
|
||||
|
||||
if err := conf.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||
return nil, fmt.Errorf("type [%s] error", visitorType)
|
||||
}
|
||||
return conf, nil
|
||||
}
|
283
pkg/config/load.go
Normal file
283
pkg/config/load.go
Normal file
@@ -0,0 +1,283 @@
|
||||
// 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 config
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/samber/lo"
|
||||
"gopkg.in/ini.v1"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apimachinery/pkg/util/yaml"
|
||||
|
||||
"github.com/fatedier/frp/pkg/config/legacy"
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
"github.com/fatedier/frp/pkg/config/v1/validation"
|
||||
"github.com/fatedier/frp/pkg/consts"
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
"github.com/fatedier/frp/pkg/util/util"
|
||||
)
|
||||
|
||||
var glbEnvs map[string]string
|
||||
|
||||
func init() {
|
||||
glbEnvs = make(map[string]string)
|
||||
envs := os.Environ()
|
||||
for _, env := range envs {
|
||||
pair := strings.SplitN(env, "=", 2)
|
||||
if len(pair) != 2 {
|
||||
continue
|
||||
}
|
||||
glbEnvs[pair[0]] = pair[1]
|
||||
}
|
||||
}
|
||||
|
||||
type Values struct {
|
||||
Envs map[string]string // environment vars
|
||||
}
|
||||
|
||||
func GetValues() *Values {
|
||||
return &Values{
|
||||
Envs: glbEnvs,
|
||||
}
|
||||
}
|
||||
|
||||
func DetectLegacyINIFormat(content []byte) bool {
|
||||
f, err := ini.Load(content)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if _, err := f.GetSection("common"); err == nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func DetectLegacyINIFormatFromFile(path string) bool {
|
||||
b, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return DetectLegacyINIFormat(b)
|
||||
}
|
||||
|
||||
func RenderWithTemplate(in []byte, values *Values) ([]byte, error) {
|
||||
tmpl, err := template.New("frp").Parse(string(in))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buffer := bytes.NewBufferString("")
|
||||
if err := tmpl.Execute(buffer, values); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buffer.Bytes(), nil
|
||||
}
|
||||
|
||||
func LoadFileContentWithTemplate(path string, values *Values) ([]byte, error) {
|
||||
b, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return RenderWithTemplate(b, values)
|
||||
}
|
||||
|
||||
func LoadConfigureFromFile(path string, c any) error {
|
||||
content, err := LoadFileContentWithTemplate(path, GetValues())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return LoadConfigure(content, c)
|
||||
}
|
||||
|
||||
// LoadConfigure loads configuration from bytes and unmarshal into c.
|
||||
// Now it supports json, yaml and toml format.
|
||||
func LoadConfigure(b []byte, c any) error {
|
||||
var tomlObj interface{}
|
||||
if err := toml.Unmarshal(b, &tomlObj); err == nil {
|
||||
b, err = json.Marshal(&tomlObj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
decoder := yaml.NewYAMLOrJSONDecoder(bytes.NewBuffer(b), 4096)
|
||||
return decoder.Decode(c)
|
||||
}
|
||||
|
||||
func NewProxyConfigurerFromMsg(m *msg.NewProxy, serverCfg *v1.ServerConfig) (v1.ProxyConfigurer, error) {
|
||||
m.ProxyType = util.EmptyOr(m.ProxyType, consts.TCPProxy)
|
||||
|
||||
configurer := v1.NewProxyConfigurerByType(m.ProxyType)
|
||||
if configurer == nil {
|
||||
return nil, fmt.Errorf("unknown proxy type: %s", m.ProxyType)
|
||||
}
|
||||
|
||||
configurer.UnmarshalFromMsg(m)
|
||||
configurer.Complete("")
|
||||
|
||||
if err := validation.ValidateProxyConfigurerForServer(configurer, serverCfg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return configurer, nil
|
||||
}
|
||||
|
||||
func LoadServerConfig(path string) (*v1.ServerConfig, bool, error) {
|
||||
var (
|
||||
svrCfg *v1.ServerConfig
|
||||
isLegacyFormat bool
|
||||
)
|
||||
// detect legacy ini format
|
||||
if DetectLegacyINIFormatFromFile(path) {
|
||||
content, err := legacy.GetRenderedConfFromFile(path)
|
||||
if err != nil {
|
||||
return nil, true, err
|
||||
}
|
||||
legacyCfg, err := legacy.UnmarshalServerConfFromIni(content)
|
||||
if err != nil {
|
||||
return nil, true, err
|
||||
}
|
||||
svrCfg = legacy.Convert_ServerCommonConf_To_v1(&legacyCfg)
|
||||
isLegacyFormat = true
|
||||
} else {
|
||||
svrCfg = &v1.ServerConfig{}
|
||||
if err := LoadConfigureFromFile(path, svrCfg); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
}
|
||||
if svrCfg != nil {
|
||||
svrCfg.Complete()
|
||||
}
|
||||
return svrCfg, isLegacyFormat, nil
|
||||
}
|
||||
|
||||
func LoadClientConfig(path string) (
|
||||
*v1.ClientCommonConfig,
|
||||
[]v1.ProxyConfigurer,
|
||||
[]v1.VisitorConfigurer,
|
||||
bool, error,
|
||||
) {
|
||||
var (
|
||||
cliCfg *v1.ClientCommonConfig
|
||||
pxyCfgs = make([]v1.ProxyConfigurer, 0)
|
||||
visitorCfgs = make([]v1.VisitorConfigurer, 0)
|
||||
isLegacyFormat bool
|
||||
)
|
||||
|
||||
if DetectLegacyINIFormatFromFile(path) {
|
||||
legacyCommon, legacyPxyCfgs, legacyVisitorCfgs, err := legacy.ParseClientConfig(path)
|
||||
if err != nil {
|
||||
return nil, nil, nil, true, err
|
||||
}
|
||||
cliCfg = legacy.Convert_ClientCommonConf_To_v1(&legacyCommon)
|
||||
for _, c := range legacyPxyCfgs {
|
||||
pxyCfgs = append(pxyCfgs, legacy.Convert_ProxyConf_To_v1(c))
|
||||
}
|
||||
for _, c := range legacyVisitorCfgs {
|
||||
visitorCfgs = append(visitorCfgs, legacy.Convert_VisitorConf_To_v1(c))
|
||||
}
|
||||
isLegacyFormat = true
|
||||
} else {
|
||||
allCfg := v1.ClientConfig{}
|
||||
if err := LoadConfigureFromFile(path, &allCfg); err != nil {
|
||||
return nil, nil, nil, false, err
|
||||
}
|
||||
cliCfg = &allCfg.ClientCommonConfig
|
||||
for _, c := range allCfg.Proxies {
|
||||
pxyCfgs = append(pxyCfgs, c.ProxyConfigurer)
|
||||
}
|
||||
for _, c := range allCfg.Visitors {
|
||||
visitorCfgs = append(visitorCfgs, c.VisitorConfigurer)
|
||||
}
|
||||
}
|
||||
|
||||
// Load additional config from includes.
|
||||
// legacy ini format alredy handle this in ParseClientConfig.
|
||||
if len(cliCfg.IncludeConfigFiles) > 0 && !isLegacyFormat {
|
||||
extPxyCfgs, extVisitorCfgs, err := LoadAdditionalClientConfigs(cliCfg.IncludeConfigFiles, isLegacyFormat)
|
||||
if err != nil {
|
||||
return nil, nil, nil, isLegacyFormat, err
|
||||
}
|
||||
pxyCfgs = append(pxyCfgs, extPxyCfgs...)
|
||||
visitorCfgs = append(visitorCfgs, extVisitorCfgs...)
|
||||
}
|
||||
|
||||
// Filter by start
|
||||
if len(cliCfg.Start) > 0 {
|
||||
startSet := sets.New(cliCfg.Start...)
|
||||
pxyCfgs = lo.Filter(pxyCfgs, func(c v1.ProxyConfigurer, _ int) bool {
|
||||
return startSet.Has(c.GetBaseConfig().Name)
|
||||
})
|
||||
visitorCfgs = lo.Filter(visitorCfgs, func(c v1.VisitorConfigurer, _ int) bool {
|
||||
return startSet.Has(c.GetBaseConfig().Name)
|
||||
})
|
||||
}
|
||||
|
||||
if cliCfg != nil {
|
||||
cliCfg.Complete()
|
||||
}
|
||||
for _, c := range pxyCfgs {
|
||||
c.Complete(cliCfg.User)
|
||||
}
|
||||
for _, c := range visitorCfgs {
|
||||
c.Complete(cliCfg)
|
||||
}
|
||||
return cliCfg, pxyCfgs, visitorCfgs, isLegacyFormat, nil
|
||||
}
|
||||
|
||||
func LoadAdditionalClientConfigs(paths []string, isLegacyFormat bool) ([]v1.ProxyConfigurer, []v1.VisitorConfigurer, error) {
|
||||
pxyCfgs := make([]v1.ProxyConfigurer, 0)
|
||||
visitorCfgs := make([]v1.VisitorConfigurer, 0)
|
||||
for _, path := range paths {
|
||||
absDir, err := filepath.Abs(filepath.Dir(path))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if _, err := os.Stat(absDir); os.IsNotExist(err) {
|
||||
return nil, nil, err
|
||||
}
|
||||
files, err := os.ReadDir(absDir)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
for _, fi := range files {
|
||||
if fi.IsDir() {
|
||||
continue
|
||||
}
|
||||
absFile := filepath.Join(absDir, fi.Name())
|
||||
if matched, _ := filepath.Match(filepath.Join(absDir, filepath.Base(path)), absFile); matched {
|
||||
// support yaml/json/toml
|
||||
cfg := v1.ClientConfig{}
|
||||
if err := LoadConfigureFromFile(absFile, &cfg); err != nil {
|
||||
return nil, nil, fmt.Errorf("load additional config from %s error: %v", absFile, err)
|
||||
}
|
||||
for _, c := range cfg.Proxies {
|
||||
pxyCfgs = append(pxyCfgs, c.ProxyConfigurer)
|
||||
}
|
||||
for _, c := range cfg.Visitors {
|
||||
visitorCfgs = append(visitorCfgs, c.VisitorConfigurer)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return pxyCfgs, visitorCfgs, nil
|
||||
}
|
@@ -1,921 +0,0 @@
|
||||
// Copyright 2016 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// 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 config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/ini.v1"
|
||||
|
||||
"github.com/fatedier/frp/pkg/consts"
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
)
|
||||
|
||||
// Proxy
|
||||
var (
|
||||
proxyConfTypeMap = map[string]reflect.Type{
|
||||
consts.TCPProxy: reflect.TypeOf(TCPProxyConf{}),
|
||||
consts.TCPMuxProxy: reflect.TypeOf(TCPMuxProxyConf{}),
|
||||
consts.UDPProxy: reflect.TypeOf(UDPProxyConf{}),
|
||||
consts.HTTPProxy: reflect.TypeOf(HTTPProxyConf{}),
|
||||
consts.HTTPSProxy: reflect.TypeOf(HTTPSProxyConf{}),
|
||||
consts.STCPProxy: reflect.TypeOf(STCPProxyConf{}),
|
||||
consts.XTCPProxy: reflect.TypeOf(XTCPProxyConf{}),
|
||||
consts.SUDPProxy: reflect.TypeOf(SUDPProxyConf{}),
|
||||
}
|
||||
)
|
||||
|
||||
func NewConfByType(proxyType string) ProxyConf {
|
||||
v, ok := proxyConfTypeMap[proxyType]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
cfg := reflect.New(v).Interface().(ProxyConf)
|
||||
return cfg
|
||||
}
|
||||
|
||||
type ProxyConf interface {
|
||||
// GetBaseConfig returns the BaseProxyConf for this config.
|
||||
GetBaseConfig() *BaseProxyConf
|
||||
// SetDefaultValues sets the default values for this config.
|
||||
SetDefaultValues()
|
||||
// UnmarshalFromMsg unmarshals a msg.NewProxy message into this config.
|
||||
// This function will be called on the frps side.
|
||||
UnmarshalFromMsg(*msg.NewProxy)
|
||||
// UnmarshalFromIni unmarshals a ini.Section into this config. This function
|
||||
// will be called on the frpc side.
|
||||
UnmarshalFromIni(string, string, *ini.Section) error
|
||||
// MarshalToMsg marshals this config into a msg.NewProxy message. This
|
||||
// function will be called on the frpc side.
|
||||
MarshalToMsg(*msg.NewProxy)
|
||||
// ValidateForClient checks that the config is valid for the frpc side.
|
||||
ValidateForClient() error
|
||||
// ValidateForServer checks that the config is valid for the frps side.
|
||||
ValidateForServer(ServerCommonConf) error
|
||||
}
|
||||
|
||||
// LocalSvrConf configures what location the client will to, or what
|
||||
// plugin will be used.
|
||||
type LocalSvrConf struct {
|
||||
// LocalIP specifies the IP address or host name to to.
|
||||
LocalIP string `ini:"local_ip" json:"local_ip"`
|
||||
// LocalPort specifies the port to to.
|
||||
LocalPort int `ini:"local_port" json:"local_port"`
|
||||
|
||||
// Plugin specifies what plugin should be used for ng. If this value
|
||||
// is set, the LocalIp and LocalPort values will be ignored. By default,
|
||||
// this value is "".
|
||||
Plugin string `ini:"plugin" json:"plugin"`
|
||||
// PluginParams specify parameters to be passed to the plugin, if one is
|
||||
// being used. By default, this value is an empty map.
|
||||
PluginParams map[string]string `ini:"-"`
|
||||
}
|
||||
|
||||
// HealthCheckConf configures health checking. This can be useful for load
|
||||
// balancing purposes to detect and remove proxies to failing services.
|
||||
type HealthCheckConf struct {
|
||||
// HealthCheckType specifies what protocol to use for health checking.
|
||||
// Valid values include "tcp", "http", and "". If this value is "", health
|
||||
// checking will not be performed. By default, this value is "".
|
||||
//
|
||||
// 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.
|
||||
HealthCheckType string `ini:"health_check_type" json:"health_check_type"` // tcp | http
|
||||
// HealthCheckTimeoutS 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.
|
||||
HealthCheckTimeoutS int `ini:"health_check_timeout_s" json:"health_check_timeout_s"`
|
||||
// HealthCheckMaxFailed specifies the number of allowed failures before the
|
||||
// is stopped. By default, this value is 1.
|
||||
HealthCheckMaxFailed int `ini:"health_check_max_failed" json:"health_check_max_failed"`
|
||||
// HealthCheckIntervalS specifies the time in seconds between health
|
||||
// checks. By default, this value is 10.
|
||||
HealthCheckIntervalS int `ini:"health_check_interval_s" json:"health_check_interval_s"`
|
||||
// HealthCheckURL specifies the address to send health checks to if the
|
||||
// health check type is "http".
|
||||
HealthCheckURL string `ini:"health_check_url" json:"health_check_url"`
|
||||
// HealthCheckAddr specifies the address to connect to if the health check
|
||||
// type is "tcp".
|
||||
HealthCheckAddr string `ini:"-"`
|
||||
}
|
||||
|
||||
// BaseProxyConf provides configuration info that is common to all types.
|
||||
type BaseProxyConf struct {
|
||||
// ProxyName is the name of this
|
||||
ProxyName string `ini:"name" json:"name"`
|
||||
// ProxyType specifies the type of this Valid values include "tcp",
|
||||
// "udp", "http", "https", "stcp", and "xtcp". By default, this value is
|
||||
// "tcp".
|
||||
ProxyType string `ini:"type" json:"type"`
|
||||
|
||||
// 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. By default, this value is false.
|
||||
UseEncryption bool `ini:"use_encryption" json:"use_encryption"`
|
||||
// UseCompression controls whether or not communication with the server
|
||||
// will be compressed. By default, this value is false.
|
||||
UseCompression bool `ini:"use_compression" json:"use_compression"`
|
||||
// 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. By default, this value is "".
|
||||
Group string `ini:"group" json:"group"`
|
||||
// GroupKey specifies a group key, which should be the same among proxies
|
||||
// of the same group. By default, this value is "".
|
||||
GroupKey string `ini:"group_key" json:"group_key"`
|
||||
|
||||
// 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 `ini:"proxy_protocol_version" json:"proxy_protocol_version"`
|
||||
|
||||
// BandwidthLimit limit the bandwidth
|
||||
// 0 means no limit
|
||||
BandwidthLimit BandwidthQuantity `ini:"bandwidth_limit" json:"bandwidth_limit"`
|
||||
// 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 `ini:"bandwidth_limit_mode" json:"bandwidth_limit_mode"`
|
||||
|
||||
// meta info for each proxy
|
||||
Metas map[string]string `ini:"-" json:"metas"`
|
||||
|
||||
LocalSvrConf `ini:",extends"`
|
||||
HealthCheckConf `ini:",extends"`
|
||||
}
|
||||
|
||||
type DomainConf struct {
|
||||
CustomDomains []string `ini:"custom_domains" json:"custom_domains"`
|
||||
SubDomain string `ini:"subdomain" json:"subdomain"`
|
||||
}
|
||||
|
||||
type RoleServerCommonConf struct {
|
||||
Role string `ini:"role" json:"role"`
|
||||
Sk string `ini:"sk" json:"sk"`
|
||||
AllowUsers []string `ini:"allow_users" json:"allow_users"`
|
||||
}
|
||||
|
||||
func (cfg *RoleServerCommonConf) setDefaultValues() {
|
||||
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
|
||||
type HTTPProxyConf struct {
|
||||
BaseProxyConf `ini:",extends"`
|
||||
DomainConf `ini:",extends"`
|
||||
|
||||
Locations []string `ini:"locations" json:"locations"`
|
||||
HTTPUser string `ini:"http_user" json:"http_user"`
|
||||
HTTPPwd string `ini:"http_pwd" json:"http_pwd"`
|
||||
HostHeaderRewrite string `ini:"host_header_rewrite" json:"host_header_rewrite"`
|
||||
Headers map[string]string `ini:"-" json:"headers"`
|
||||
RouteByHTTPUser string `ini:"route_by_http_user" json:"route_by_http_user"`
|
||||
}
|
||||
|
||||
// HTTPS
|
||||
type HTTPSProxyConf struct {
|
||||
BaseProxyConf `ini:",extends"`
|
||||
DomainConf `ini:",extends"`
|
||||
}
|
||||
|
||||
// TCP
|
||||
type TCPProxyConf struct {
|
||||
BaseProxyConf `ini:",extends"`
|
||||
RemotePort int `ini:"remote_port" json:"remote_port"`
|
||||
}
|
||||
|
||||
// UDP
|
||||
type UDPProxyConf struct {
|
||||
BaseProxyConf `ini:",extends"`
|
||||
|
||||
RemotePort int `ini:"remote_port" json:"remote_port"`
|
||||
}
|
||||
|
||||
// TCPMux
|
||||
type TCPMuxProxyConf struct {
|
||||
BaseProxyConf `ini:",extends"`
|
||||
DomainConf `ini:",extends"`
|
||||
HTTPUser string `ini:"http_user" json:"http_user,omitempty"`
|
||||
HTTPPwd string `ini:"http_pwd" json:"http_pwd,omitempty"`
|
||||
RouteByHTTPUser string `ini:"route_by_http_user" json:"route_by_http_user"`
|
||||
|
||||
Multiplexer string `ini:"multiplexer"`
|
||||
}
|
||||
|
||||
// STCP
|
||||
type STCPProxyConf struct {
|
||||
BaseProxyConf `ini:",extends"`
|
||||
RoleServerCommonConf `ini:",extends"`
|
||||
}
|
||||
|
||||
// XTCP
|
||||
type XTCPProxyConf struct {
|
||||
BaseProxyConf `ini:",extends"`
|
||||
RoleServerCommonConf `ini:",extends"`
|
||||
}
|
||||
|
||||
// SUDP
|
||||
type SUDPProxyConf struct {
|
||||
BaseProxyConf `ini:",extends"`
|
||||
RoleServerCommonConf `ini:",extends"`
|
||||
}
|
||||
|
||||
// Proxy Conf Loader
|
||||
// DefaultProxyConf creates a empty ProxyConf object by proxyType.
|
||||
// If proxyType doesn't exist, return nil.
|
||||
func DefaultProxyConf(proxyType string) ProxyConf {
|
||||
conf := NewConfByType(proxyType)
|
||||
if conf != nil {
|
||||
conf.SetDefaultValues()
|
||||
}
|
||||
return conf
|
||||
}
|
||||
|
||||
// Proxy loaded from ini
|
||||
func NewProxyConfFromIni(prefix, name string, section *ini.Section) (ProxyConf, error) {
|
||||
// section.Key: if key not exists, section will set it with default value.
|
||||
proxyType := section.Key("type").String()
|
||||
if proxyType == "" {
|
||||
proxyType = consts.TCPProxy
|
||||
}
|
||||
|
||||
conf := DefaultProxyConf(proxyType)
|
||||
if conf == nil {
|
||||
return nil, fmt.Errorf("invalid type [%s]", proxyType)
|
||||
}
|
||||
|
||||
if err := conf.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := conf.ValidateForClient(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return conf, nil
|
||||
}
|
||||
|
||||
// Proxy loaded from msg
|
||||
func NewProxyConfFromMsg(m *msg.NewProxy, serverCfg ServerCommonConf) (ProxyConf, error) {
|
||||
if m.ProxyType == "" {
|
||||
m.ProxyType = consts.TCPProxy
|
||||
}
|
||||
|
||||
conf := DefaultProxyConf(m.ProxyType)
|
||||
if conf == nil {
|
||||
return nil, fmt.Errorf("proxy [%s] type [%s] error", m.ProxyName, m.ProxyType)
|
||||
}
|
||||
|
||||
conf.UnmarshalFromMsg(m)
|
||||
|
||||
err := conf.ValidateForServer(serverCfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return conf, nil
|
||||
}
|
||||
|
||||
// Base
|
||||
func (cfg *BaseProxyConf) GetBaseConfig() *BaseProxyConf {
|
||||
return cfg
|
||||
}
|
||||
|
||||
func (cfg *BaseProxyConf) SetDefaultValues() {
|
||||
cfg.LocalSvrConf = LocalSvrConf{
|
||||
LocalIP: "127.0.0.1",
|
||||
}
|
||||
cfg.BandwidthLimitMode = BandwidthLimitModeClient
|
||||
}
|
||||
|
||||
// BaseProxyConf apply custom logic changes.
|
||||
func (cfg *BaseProxyConf) decorate(prefix string, name string, section *ini.Section) error {
|
||||
// proxy_name
|
||||
cfg.ProxyName = prefix + name
|
||||
|
||||
// metas_xxx
|
||||
cfg.Metas = GetMapWithoutPrefix(section.KeysHash(), "meta_")
|
||||
|
||||
// bandwidth_limit
|
||||
if bandwidth, err := section.GetKey("bandwidth_limit"); err == nil {
|
||||
cfg.BandwidthLimit, err = NewBandwidthQuantity(bandwidth.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// plugin_xxx
|
||||
cfg.LocalSvrConf.PluginParams = GetMapByPrefix(section.KeysHash(), "plugin_")
|
||||
|
||||
// custom logic code
|
||||
if cfg.HealthCheckType == "tcp" && cfg.Plugin == "" {
|
||||
cfg.HealthCheckAddr = cfg.LocalIP + fmt.Sprintf(":%d", cfg.LocalPort)
|
||||
}
|
||||
|
||||
if cfg.HealthCheckType == "http" && cfg.Plugin == "" && cfg.HealthCheckURL != "" {
|
||||
s := "http://" + net.JoinHostPort(cfg.LocalIP, strconv.Itoa(cfg.LocalPort))
|
||||
if !strings.HasPrefix(cfg.HealthCheckURL, "/") {
|
||||
s += "/"
|
||||
}
|
||||
cfg.HealthCheckURL = s + cfg.HealthCheckURL
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cfg *BaseProxyConf) marshalToMsg(m *msg.NewProxy) {
|
||||
m.ProxyName = cfg.ProxyName
|
||||
m.ProxyType = cfg.ProxyType
|
||||
m.UseEncryption = cfg.UseEncryption
|
||||
m.UseCompression = cfg.UseCompression
|
||||
m.BandwidthLimit = cfg.BandwidthLimit.String()
|
||||
// leave it empty for default value to reduce traffic
|
||||
if cfg.BandwidthLimitMode != "client" {
|
||||
m.BandwidthLimitMode = cfg.BandwidthLimitMode
|
||||
}
|
||||
m.Group = cfg.Group
|
||||
m.GroupKey = cfg.GroupKey
|
||||
m.Metas = cfg.Metas
|
||||
}
|
||||
|
||||
func (cfg *BaseProxyConf) unmarshalFromMsg(m *msg.NewProxy) {
|
||||
cfg.ProxyName = m.ProxyName
|
||||
cfg.ProxyType = m.ProxyType
|
||||
cfg.UseEncryption = m.UseEncryption
|
||||
cfg.UseCompression = m.UseCompression
|
||||
if m.BandwidthLimit != "" {
|
||||
cfg.BandwidthLimit, _ = NewBandwidthQuantity(m.BandwidthLimit)
|
||||
}
|
||||
if m.BandwidthLimitMode != "" {
|
||||
cfg.BandwidthLimitMode = m.BandwidthLimitMode
|
||||
}
|
||||
cfg.Group = m.Group
|
||||
cfg.GroupKey = m.GroupKey
|
||||
cfg.Metas = m.Metas
|
||||
}
|
||||
|
||||
func (cfg *BaseProxyConf) validateForClient() (err error) {
|
||||
if cfg.ProxyProtocolVersion != "" {
|
||||
if cfg.ProxyProtocolVersion != "v1" && cfg.ProxyProtocolVersion != "v2" {
|
||||
return fmt.Errorf("no support proxy protocol version: %s", cfg.ProxyProtocolVersion)
|
||||
}
|
||||
}
|
||||
|
||||
if cfg.BandwidthLimitMode != "client" && cfg.BandwidthLimitMode != "server" {
|
||||
return fmt.Errorf("bandwidth_limit_mode should be client or server")
|
||||
}
|
||||
|
||||
if err = cfg.LocalSvrConf.validateForClient(); err != nil {
|
||||
return
|
||||
}
|
||||
if err = cfg.HealthCheckConf.validateForClient(); err != nil {
|
||||
return
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cfg *BaseProxyConf) validateForServer() (err error) {
|
||||
if cfg.BandwidthLimitMode != "client" && cfg.BandwidthLimitMode != "server" {
|
||||
return fmt.Errorf("bandwidth_limit_mode should be client or server")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DomainConf
|
||||
func (cfg *DomainConf) check() (err error) {
|
||||
if len(cfg.CustomDomains) == 0 && cfg.SubDomain == "" {
|
||||
err = fmt.Errorf("custom_domains and subdomain should set at least one of them")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (cfg *DomainConf) validateForClient() (err error) {
|
||||
if err = cfg.check(); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (cfg *DomainConf) validateForServer(serverCfg ServerCommonConf) (err error) {
|
||||
if err = cfg.check(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, domain := range cfg.CustomDomains {
|
||||
if serverCfg.SubDomainHost != "" && len(strings.Split(serverCfg.SubDomainHost, ".")) < len(strings.Split(domain, ".")) {
|
||||
if strings.Contains(domain, serverCfg.SubDomainHost) {
|
||||
return fmt.Errorf("custom domain [%s] should not belong to subdomain_host [%s]", domain, serverCfg.SubDomainHost)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if cfg.SubDomain != "" {
|
||||
if serverCfg.SubDomainHost == "" {
|
||||
return fmt.Errorf("subdomain is not supported because this feature is not enabled in remote frps")
|
||||
}
|
||||
if strings.Contains(cfg.SubDomain, ".") || strings.Contains(cfg.SubDomain, "*") {
|
||||
return fmt.Errorf("'.' and '*' is not supported in subdomain")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LocalSvrConf
|
||||
func (cfg *LocalSvrConf) validateForClient() (err error) {
|
||||
if cfg.Plugin == "" {
|
||||
if cfg.LocalIP == "" {
|
||||
err = fmt.Errorf("local ip or plugin is required")
|
||||
return
|
||||
}
|
||||
if cfg.LocalPort <= 0 {
|
||||
err = fmt.Errorf("error local_port")
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// HealthCheckConf
|
||||
func (cfg *HealthCheckConf) validateForClient() error {
|
||||
if cfg.HealthCheckType != "" && cfg.HealthCheckType != "tcp" && cfg.HealthCheckType != "http" {
|
||||
return fmt.Errorf("unsupport health check type")
|
||||
}
|
||||
if cfg.HealthCheckType != "" {
|
||||
if cfg.HealthCheckType == "http" && cfg.HealthCheckURL == "" {
|
||||
return fmt.Errorf("health_check_url is required for health check type 'http'")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func preUnmarshalFromIni(cfg ProxyConf, prefix string, name string, section *ini.Section) error {
|
||||
err := section.MapTo(cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = cfg.GetBaseConfig().decorate(prefix, name, section)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TCP
|
||||
func (cfg *TCPProxyConf) UnmarshalFromMsg(m *msg.NewProxy) {
|
||||
cfg.BaseProxyConf.unmarshalFromMsg(m)
|
||||
|
||||
// Add custom logic unmarshal if exists
|
||||
cfg.RemotePort = m.RemotePort
|
||||
}
|
||||
|
||||
func (cfg *TCPProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error {
|
||||
err := preUnmarshalFromIni(cfg, prefix, name, section)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add custom logic unmarshal if exists
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cfg *TCPProxyConf) MarshalToMsg(m *msg.NewProxy) {
|
||||
cfg.BaseProxyConf.marshalToMsg(m)
|
||||
|
||||
// Add custom logic marshal if exists
|
||||
m.RemotePort = cfg.RemotePort
|
||||
}
|
||||
|
||||
func (cfg *TCPProxyConf) ValidateForClient() (err error) {
|
||||
if err = cfg.BaseProxyConf.validateForClient(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Add custom logic check if exists
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (cfg *TCPProxyConf) ValidateForServer(_ ServerCommonConf) error {
|
||||
return cfg.BaseProxyConf.validateForServer()
|
||||
}
|
||||
|
||||
// TCPMux
|
||||
func (cfg *TCPMuxProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error {
|
||||
err := preUnmarshalFromIni(cfg, prefix, name, section)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add custom logic unmarshal if exists
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cfg *TCPMuxProxyConf) UnmarshalFromMsg(m *msg.NewProxy) {
|
||||
cfg.BaseProxyConf.unmarshalFromMsg(m)
|
||||
|
||||
// Add custom logic unmarshal if exists
|
||||
cfg.CustomDomains = m.CustomDomains
|
||||
cfg.SubDomain = m.SubDomain
|
||||
cfg.Multiplexer = m.Multiplexer
|
||||
cfg.HTTPUser = m.HTTPUser
|
||||
cfg.HTTPPwd = m.HTTPPwd
|
||||
cfg.RouteByHTTPUser = m.RouteByHTTPUser
|
||||
}
|
||||
|
||||
func (cfg *TCPMuxProxyConf) MarshalToMsg(m *msg.NewProxy) {
|
||||
cfg.BaseProxyConf.marshalToMsg(m)
|
||||
|
||||
// Add custom logic marshal if exists
|
||||
m.CustomDomains = cfg.CustomDomains
|
||||
m.SubDomain = cfg.SubDomain
|
||||
m.Multiplexer = cfg.Multiplexer
|
||||
m.HTTPUser = cfg.HTTPUser
|
||||
m.HTTPPwd = cfg.HTTPPwd
|
||||
m.RouteByHTTPUser = cfg.RouteByHTTPUser
|
||||
}
|
||||
|
||||
func (cfg *TCPMuxProxyConf) ValidateForClient() (err error) {
|
||||
if err = cfg.BaseProxyConf.validateForClient(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Add custom logic check if exists
|
||||
if err = cfg.DomainConf.validateForClient(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if cfg.Multiplexer != consts.HTTPConnectTCPMultiplexer {
|
||||
return fmt.Errorf("parse conf error: incorrect multiplexer [%s]", cfg.Multiplexer)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (cfg *TCPMuxProxyConf) ValidateForServer(serverCfg ServerCommonConf) (err error) {
|
||||
if err := cfg.BaseProxyConf.validateForServer(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cfg.Multiplexer != consts.HTTPConnectTCPMultiplexer {
|
||||
return fmt.Errorf("proxy [%s] incorrect multiplexer [%s]", cfg.ProxyName, cfg.Multiplexer)
|
||||
}
|
||||
|
||||
if cfg.Multiplexer == consts.HTTPConnectTCPMultiplexer && serverCfg.TCPMuxHTTPConnectPort == 0 {
|
||||
return fmt.Errorf("proxy [%s] type [tcpmux] with multiplexer [httpconnect] requires tcpmux_httpconnect_port configuration", cfg.ProxyName)
|
||||
}
|
||||
|
||||
if err = cfg.DomainConf.validateForServer(serverCfg); err != nil {
|
||||
err = fmt.Errorf("proxy [%s] domain conf check error: %v", cfg.ProxyName, err)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// UDP
|
||||
func (cfg *UDPProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error {
|
||||
err := preUnmarshalFromIni(cfg, prefix, name, section)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add custom logic unmarshal if exists
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cfg *UDPProxyConf) UnmarshalFromMsg(m *msg.NewProxy) {
|
||||
cfg.BaseProxyConf.unmarshalFromMsg(m)
|
||||
|
||||
// Add custom logic unmarshal if exists
|
||||
cfg.RemotePort = m.RemotePort
|
||||
}
|
||||
|
||||
func (cfg *UDPProxyConf) MarshalToMsg(m *msg.NewProxy) {
|
||||
cfg.BaseProxyConf.marshalToMsg(m)
|
||||
|
||||
// Add custom logic marshal if exists
|
||||
m.RemotePort = cfg.RemotePort
|
||||
}
|
||||
|
||||
func (cfg *UDPProxyConf) ValidateForClient() (err error) {
|
||||
if err = cfg.BaseProxyConf.validateForClient(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Add custom logic check if exists
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (cfg *UDPProxyConf) ValidateForServer(_ ServerCommonConf) error {
|
||||
return cfg.BaseProxyConf.validateForServer()
|
||||
}
|
||||
|
||||
// HTTP
|
||||
func (cfg *HTTPProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error {
|
||||
err := preUnmarshalFromIni(cfg, prefix, name, section)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add custom logic unmarshal if exists
|
||||
cfg.Headers = GetMapWithoutPrefix(section.KeysHash(), "header_")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cfg *HTTPProxyConf) UnmarshalFromMsg(m *msg.NewProxy) {
|
||||
cfg.BaseProxyConf.unmarshalFromMsg(m)
|
||||
|
||||
// Add custom logic unmarshal if exists
|
||||
cfg.CustomDomains = m.CustomDomains
|
||||
cfg.SubDomain = m.SubDomain
|
||||
cfg.Locations = m.Locations
|
||||
cfg.HostHeaderRewrite = m.HostHeaderRewrite
|
||||
cfg.HTTPUser = m.HTTPUser
|
||||
cfg.HTTPPwd = m.HTTPPwd
|
||||
cfg.Headers = m.Headers
|
||||
cfg.RouteByHTTPUser = m.RouteByHTTPUser
|
||||
}
|
||||
|
||||
func (cfg *HTTPProxyConf) MarshalToMsg(m *msg.NewProxy) {
|
||||
cfg.BaseProxyConf.marshalToMsg(m)
|
||||
|
||||
// Add custom logic marshal if exists
|
||||
m.CustomDomains = cfg.CustomDomains
|
||||
m.SubDomain = cfg.SubDomain
|
||||
m.Locations = cfg.Locations
|
||||
m.HostHeaderRewrite = cfg.HostHeaderRewrite
|
||||
m.HTTPUser = cfg.HTTPUser
|
||||
m.HTTPPwd = cfg.HTTPPwd
|
||||
m.Headers = cfg.Headers
|
||||
m.RouteByHTTPUser = cfg.RouteByHTTPUser
|
||||
}
|
||||
|
||||
func (cfg *HTTPProxyConf) ValidateForClient() (err error) {
|
||||
if err = cfg.BaseProxyConf.validateForClient(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Add custom logic check if exists
|
||||
if err = cfg.DomainConf.validateForClient(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (cfg *HTTPProxyConf) ValidateForServer(serverCfg ServerCommonConf) (err error) {
|
||||
if err := cfg.BaseProxyConf.validateForServer(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if serverCfg.VhostHTTPPort == 0 {
|
||||
return fmt.Errorf("type [http] not support when vhost_http_port is not set")
|
||||
}
|
||||
|
||||
if err = cfg.DomainConf.validateForServer(serverCfg); err != nil {
|
||||
err = fmt.Errorf("proxy [%s] domain conf check error: %v", cfg.ProxyName, err)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// HTTPS
|
||||
func (cfg *HTTPSProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error {
|
||||
err := preUnmarshalFromIni(cfg, prefix, name, section)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add custom logic unmarshal if exists
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cfg *HTTPSProxyConf) UnmarshalFromMsg(m *msg.NewProxy) {
|
||||
cfg.BaseProxyConf.unmarshalFromMsg(m)
|
||||
|
||||
// Add custom logic unmarshal if exists
|
||||
cfg.CustomDomains = m.CustomDomains
|
||||
cfg.SubDomain = m.SubDomain
|
||||
}
|
||||
|
||||
func (cfg *HTTPSProxyConf) MarshalToMsg(m *msg.NewProxy) {
|
||||
cfg.BaseProxyConf.marshalToMsg(m)
|
||||
|
||||
// Add custom logic marshal if exists
|
||||
m.CustomDomains = cfg.CustomDomains
|
||||
m.SubDomain = cfg.SubDomain
|
||||
}
|
||||
|
||||
func (cfg *HTTPSProxyConf) ValidateForClient() (err error) {
|
||||
if err = cfg.BaseProxyConf.validateForClient(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Add custom logic check if exists
|
||||
if err = cfg.DomainConf.validateForClient(); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (cfg *HTTPSProxyConf) ValidateForServer(serverCfg ServerCommonConf) (err error) {
|
||||
if err := cfg.BaseProxyConf.validateForServer(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if serverCfg.VhostHTTPSPort == 0 {
|
||||
return fmt.Errorf("type [https] not support when vhost_https_port is not set")
|
||||
}
|
||||
|
||||
if err = cfg.DomainConf.validateForServer(serverCfg); err != nil {
|
||||
err = fmt.Errorf("proxy [%s] domain conf check error: %v", cfg.ProxyName, err)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// SUDP
|
||||
func (cfg *SUDPProxyConf) SetDefaultValues() {
|
||||
cfg.BaseProxyConf.SetDefaultValues()
|
||||
cfg.RoleServerCommonConf.setDefaultValues()
|
||||
}
|
||||
|
||||
func (cfg *SUDPProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error {
|
||||
err := preUnmarshalFromIni(cfg, prefix, name, section)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add custom logic unmarshal if exists
|
||||
return nil
|
||||
}
|
||||
|
||||
// Only for role server.
|
||||
func (cfg *SUDPProxyConf) UnmarshalFromMsg(m *msg.NewProxy) {
|
||||
cfg.BaseProxyConf.unmarshalFromMsg(m)
|
||||
|
||||
// Add custom logic unmarshal if exists
|
||||
cfg.RoleServerCommonConf.unmarshalFromMsg(m)
|
||||
}
|
||||
|
||||
func (cfg *SUDPProxyConf) MarshalToMsg(m *msg.NewProxy) {
|
||||
cfg.BaseProxyConf.marshalToMsg(m)
|
||||
|
||||
// Add custom logic marshal if exists
|
||||
cfg.RoleServerCommonConf.marshalToMsg(m)
|
||||
}
|
||||
|
||||
func (cfg *SUDPProxyConf) ValidateForClient() (err error) {
|
||||
if err := cfg.BaseProxyConf.validateForClient(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add custom logic check if exists
|
||||
if cfg.Role != "server" {
|
||||
return fmt.Errorf("role should be 'server'")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cfg *SUDPProxyConf) ValidateForServer(_ ServerCommonConf) error {
|
||||
return cfg.BaseProxyConf.validateForServer()
|
||||
}
|
||||
|
||||
// STCP
|
||||
func (cfg *STCPProxyConf) SetDefaultValues() {
|
||||
cfg.BaseProxyConf.SetDefaultValues()
|
||||
cfg.RoleServerCommonConf.setDefaultValues()
|
||||
}
|
||||
|
||||
func (cfg *STCPProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error {
|
||||
err := preUnmarshalFromIni(cfg, prefix, name, section)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add custom logic unmarshal if exists
|
||||
if cfg.Role == "" {
|
||||
cfg.Role = "server"
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Only for role server.
|
||||
func (cfg *STCPProxyConf) UnmarshalFromMsg(m *msg.NewProxy) {
|
||||
cfg.BaseProxyConf.unmarshalFromMsg(m)
|
||||
|
||||
// Add custom logic unmarshal if exists
|
||||
cfg.RoleServerCommonConf.unmarshalFromMsg(m)
|
||||
}
|
||||
|
||||
func (cfg *STCPProxyConf) MarshalToMsg(m *msg.NewProxy) {
|
||||
cfg.BaseProxyConf.marshalToMsg(m)
|
||||
|
||||
// Add custom logic marshal if exists
|
||||
cfg.RoleServerCommonConf.marshalToMsg(m)
|
||||
}
|
||||
|
||||
func (cfg *STCPProxyConf) ValidateForClient() (err error) {
|
||||
if err = cfg.BaseProxyConf.validateForClient(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Add custom logic check if exists
|
||||
if cfg.Role != "server" {
|
||||
return fmt.Errorf("role should be 'server'")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (cfg *STCPProxyConf) ValidateForServer(_ ServerCommonConf) error {
|
||||
return cfg.BaseProxyConf.validateForServer()
|
||||
}
|
||||
|
||||
// XTCP
|
||||
func (cfg *XTCPProxyConf) SetDefaultValues() {
|
||||
cfg.BaseProxyConf.SetDefaultValues()
|
||||
cfg.RoleServerCommonConf.setDefaultValues()
|
||||
}
|
||||
|
||||
func (cfg *XTCPProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error {
|
||||
err := preUnmarshalFromIni(cfg, prefix, name, section)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add custom logic unmarshal if exists
|
||||
if cfg.Role == "" {
|
||||
cfg.Role = "server"
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Only for role server.
|
||||
func (cfg *XTCPProxyConf) UnmarshalFromMsg(m *msg.NewProxy) {
|
||||
cfg.BaseProxyConf.unmarshalFromMsg(m)
|
||||
|
||||
// Add custom logic unmarshal if exists
|
||||
cfg.RoleServerCommonConf.unmarshalFromMsg(m)
|
||||
}
|
||||
|
||||
func (cfg *XTCPProxyConf) MarshalToMsg(m *msg.NewProxy) {
|
||||
cfg.BaseProxyConf.marshalToMsg(m)
|
||||
|
||||
// Add custom logic marshal if exists
|
||||
cfg.RoleServerCommonConf.marshalToMsg(m)
|
||||
}
|
||||
|
||||
func (cfg *XTCPProxyConf) ValidateForClient() (err error) {
|
||||
if err = cfg.BaseProxyConf.validateForClient(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Add custom logic check if exists
|
||||
if cfg.Role != "server" {
|
||||
return fmt.Errorf("role should be 'server'")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (cfg *XTCPProxyConf) ValidateForServer(_ ServerCommonConf) error {
|
||||
return cfg.BaseProxyConf.validateForServer()
|
||||
}
|
@@ -1,478 +0,0 @@
|
||||
// Copyright 2020 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 config
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gopkg.in/ini.v1"
|
||||
|
||||
"github.com/fatedier/frp/pkg/consts"
|
||||
)
|
||||
|
||||
var (
|
||||
testLoadOptions = ini.LoadOptions{
|
||||
Insensitive: false,
|
||||
InsensitiveSections: false,
|
||||
InsensitiveKeys: false,
|
||||
IgnoreInlineComment: true,
|
||||
AllowBooleanKeys: true,
|
||||
}
|
||||
|
||||
testProxyPrefix = "test."
|
||||
)
|
||||
|
||||
func Test_Proxy_Interface(_ *testing.T) {
|
||||
for name := range proxyConfTypeMap {
|
||||
NewConfByType(name)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Proxy_UnmarshalFromIni(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
testcases := []struct {
|
||||
sname string
|
||||
source []byte
|
||||
expected ProxyConf
|
||||
}{
|
||||
{
|
||||
sname: "ssh",
|
||||
source: []byte(`
|
||||
[ssh]
|
||||
# tcp | udp | http | https | stcp | xtcp, default is tcp
|
||||
type = tcp
|
||||
local_ip = 127.0.0.9
|
||||
local_port = 29
|
||||
bandwidth_limit = 19MB
|
||||
bandwidth_limit_mode = server
|
||||
use_encryption
|
||||
use_compression
|
||||
remote_port = 6009
|
||||
group = test_group
|
||||
group_key = 123456
|
||||
health_check_type = tcp
|
||||
health_check_timeout_s = 3
|
||||
health_check_max_failed = 3
|
||||
health_check_interval_s = 19
|
||||
meta_var1 = 123
|
||||
meta_var2 = 234`),
|
||||
expected: &TCPProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testProxyPrefix + "ssh",
|
||||
ProxyType: consts.TCPProxy,
|
||||
UseCompression: true,
|
||||
UseEncryption: true,
|
||||
Group: "test_group",
|
||||
GroupKey: "123456",
|
||||
BandwidthLimit: MustBandwidthQuantity("19MB"),
|
||||
BandwidthLimitMode: BandwidthLimitModeServer,
|
||||
Metas: map[string]string{
|
||||
"var1": "123",
|
||||
"var2": "234",
|
||||
},
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "127.0.0.9",
|
||||
LocalPort: 29,
|
||||
},
|
||||
HealthCheckConf: HealthCheckConf{
|
||||
HealthCheckType: consts.TCPProxy,
|
||||
HealthCheckTimeoutS: 3,
|
||||
HealthCheckMaxFailed: 3,
|
||||
HealthCheckIntervalS: 19,
|
||||
HealthCheckAddr: "127.0.0.9:29",
|
||||
},
|
||||
},
|
||||
RemotePort: 6009,
|
||||
},
|
||||
},
|
||||
{
|
||||
sname: "ssh_random",
|
||||
source: []byte(`
|
||||
[ssh_random]
|
||||
type = tcp
|
||||
local_ip = 127.0.0.9
|
||||
local_port = 29
|
||||
remote_port = 9
|
||||
`),
|
||||
expected: &TCPProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testProxyPrefix + "ssh_random",
|
||||
ProxyType: consts.TCPProxy,
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "127.0.0.9",
|
||||
LocalPort: 29,
|
||||
},
|
||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||
},
|
||||
RemotePort: 9,
|
||||
},
|
||||
},
|
||||
{
|
||||
sname: "dns",
|
||||
source: []byte(`
|
||||
[dns]
|
||||
type = udp
|
||||
local_ip = 114.114.114.114
|
||||
local_port = 59
|
||||
remote_port = 6009
|
||||
use_encryption
|
||||
use_compression
|
||||
`),
|
||||
expected: &UDPProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testProxyPrefix + "dns",
|
||||
ProxyType: consts.UDPProxy,
|
||||
UseEncryption: true,
|
||||
UseCompression: true,
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "114.114.114.114",
|
||||
LocalPort: 59,
|
||||
},
|
||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||
},
|
||||
RemotePort: 6009,
|
||||
},
|
||||
},
|
||||
{
|
||||
sname: "web01",
|
||||
source: []byte(`
|
||||
[web01]
|
||||
type = http
|
||||
local_ip = 127.0.0.9
|
||||
local_port = 89
|
||||
use_encryption
|
||||
use_compression
|
||||
http_user = admin
|
||||
http_pwd = admin
|
||||
subdomain = web01
|
||||
custom_domains = web02.yourdomain.com
|
||||
locations = /,/pic
|
||||
host_header_rewrite = example.com
|
||||
header_X-From-Where = frp
|
||||
health_check_type = http
|
||||
health_check_url = /status
|
||||
health_check_interval_s = 19
|
||||
health_check_max_failed = 3
|
||||
health_check_timeout_s = 3
|
||||
`),
|
||||
expected: &HTTPProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testProxyPrefix + "web01",
|
||||
ProxyType: consts.HTTPProxy,
|
||||
UseCompression: true,
|
||||
UseEncryption: true,
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "127.0.0.9",
|
||||
LocalPort: 89,
|
||||
},
|
||||
HealthCheckConf: HealthCheckConf{
|
||||
HealthCheckType: consts.HTTPProxy,
|
||||
HealthCheckTimeoutS: 3,
|
||||
HealthCheckMaxFailed: 3,
|
||||
HealthCheckIntervalS: 19,
|
||||
HealthCheckURL: "http://127.0.0.9:89/status",
|
||||
},
|
||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||
},
|
||||
DomainConf: DomainConf{
|
||||
CustomDomains: []string{"web02.yourdomain.com"},
|
||||
SubDomain: "web01",
|
||||
},
|
||||
Locations: []string{"/", "/pic"},
|
||||
HTTPUser: "admin",
|
||||
HTTPPwd: "admin",
|
||||
HostHeaderRewrite: "example.com",
|
||||
Headers: map[string]string{
|
||||
"X-From-Where": "frp",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
sname: "web02",
|
||||
source: []byte(`
|
||||
[web02]
|
||||
type = https
|
||||
local_ip = 127.0.0.9
|
||||
local_port = 8009
|
||||
use_encryption
|
||||
use_compression
|
||||
subdomain = web01
|
||||
custom_domains = web02.yourdomain.com
|
||||
proxy_protocol_version = v2
|
||||
`),
|
||||
expected: &HTTPSProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testProxyPrefix + "web02",
|
||||
ProxyType: consts.HTTPSProxy,
|
||||
UseCompression: true,
|
||||
UseEncryption: true,
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "127.0.0.9",
|
||||
LocalPort: 8009,
|
||||
},
|
||||
ProxyProtocolVersion: "v2",
|
||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||
},
|
||||
DomainConf: DomainConf{
|
||||
CustomDomains: []string{"web02.yourdomain.com"},
|
||||
SubDomain: "web01",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
sname: "secret_tcp",
|
||||
source: []byte(`
|
||||
[secret_tcp]
|
||||
type = stcp
|
||||
sk = abcdefg
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 22
|
||||
use_encryption = false
|
||||
use_compression = false
|
||||
`),
|
||||
expected: &STCPProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testProxyPrefix + "secret_tcp",
|
||||
ProxyType: consts.STCPProxy,
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "127.0.0.1",
|
||||
LocalPort: 22,
|
||||
},
|
||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||
},
|
||||
RoleServerCommonConf: RoleServerCommonConf{
|
||||
Role: "server",
|
||||
Sk: "abcdefg",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
sname: "p2p_tcp",
|
||||
source: []byte(`
|
||||
[p2p_tcp]
|
||||
type = xtcp
|
||||
sk = abcdefg
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 22
|
||||
use_encryption = false
|
||||
use_compression = false
|
||||
`),
|
||||
expected: &XTCPProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testProxyPrefix + "p2p_tcp",
|
||||
ProxyType: consts.XTCPProxy,
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "127.0.0.1",
|
||||
LocalPort: 22,
|
||||
},
|
||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||
},
|
||||
RoleServerCommonConf: RoleServerCommonConf{
|
||||
Role: "server",
|
||||
Sk: "abcdefg",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
sname: "tcpmuxhttpconnect",
|
||||
source: []byte(`
|
||||
[tcpmuxhttpconnect]
|
||||
type = tcpmux
|
||||
multiplexer = httpconnect
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 10701
|
||||
custom_domains = tunnel1
|
||||
`),
|
||||
expected: &TCPMuxProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testProxyPrefix + "tcpmuxhttpconnect",
|
||||
ProxyType: consts.TCPMuxProxy,
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "127.0.0.1",
|
||||
LocalPort: 10701,
|
||||
},
|
||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||
},
|
||||
DomainConf: DomainConf{
|
||||
CustomDomains: []string{"tunnel1"},
|
||||
SubDomain: "",
|
||||
},
|
||||
Multiplexer: "httpconnect",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range testcases {
|
||||
f, err := ini.LoadSources(testLoadOptions, c.source)
|
||||
assert.NoError(err)
|
||||
|
||||
proxyType := f.Section(c.sname).Key("type").String()
|
||||
assert.NotEmpty(proxyType)
|
||||
|
||||
actual := DefaultProxyConf(proxyType)
|
||||
assert.NotNil(actual)
|
||||
|
||||
err = actual.UnmarshalFromIni(testProxyPrefix, c.sname, f.Section(c.sname))
|
||||
assert.NoError(err)
|
||||
assert.Equal(c.expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_RangeProxy_UnmarshalFromIni(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
testcases := []struct {
|
||||
sname string
|
||||
source []byte
|
||||
expected map[string]ProxyConf
|
||||
}{
|
||||
{
|
||||
sname: "range:tcp_port",
|
||||
source: []byte(`
|
||||
[range:tcp_port]
|
||||
type = tcp
|
||||
local_ip = 127.0.0.9
|
||||
local_port = 6010-6011,6019
|
||||
remote_port = 6010-6011,6019
|
||||
use_encryption = false
|
||||
use_compression = false
|
||||
`),
|
||||
expected: map[string]ProxyConf{
|
||||
"tcp_port_0": &TCPProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testProxyPrefix + "tcp_port_0",
|
||||
ProxyType: consts.TCPProxy,
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "127.0.0.9",
|
||||
LocalPort: 6010,
|
||||
},
|
||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||
},
|
||||
RemotePort: 6010,
|
||||
},
|
||||
"tcp_port_1": &TCPProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testProxyPrefix + "tcp_port_1",
|
||||
ProxyType: consts.TCPProxy,
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "127.0.0.9",
|
||||
LocalPort: 6011,
|
||||
},
|
||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||
},
|
||||
RemotePort: 6011,
|
||||
},
|
||||
"tcp_port_2": &TCPProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testProxyPrefix + "tcp_port_2",
|
||||
ProxyType: consts.TCPProxy,
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "127.0.0.9",
|
||||
LocalPort: 6019,
|
||||
},
|
||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||
},
|
||||
RemotePort: 6019,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
sname: "range:udp_port",
|
||||
source: []byte(`
|
||||
[range:udp_port]
|
||||
type = udp
|
||||
local_ip = 114.114.114.114
|
||||
local_port = 6000,6010-6011
|
||||
remote_port = 6000,6010-6011
|
||||
use_encryption
|
||||
use_compression
|
||||
`),
|
||||
expected: map[string]ProxyConf{
|
||||
"udp_port_0": &UDPProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testProxyPrefix + "udp_port_0",
|
||||
ProxyType: consts.UDPProxy,
|
||||
UseEncryption: true,
|
||||
UseCompression: true,
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "114.114.114.114",
|
||||
LocalPort: 6000,
|
||||
},
|
||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||
},
|
||||
RemotePort: 6000,
|
||||
},
|
||||
"udp_port_1": &UDPProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testProxyPrefix + "udp_port_1",
|
||||
ProxyType: consts.UDPProxy,
|
||||
UseEncryption: true,
|
||||
UseCompression: true,
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "114.114.114.114",
|
||||
LocalPort: 6010,
|
||||
},
|
||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||
},
|
||||
RemotePort: 6010,
|
||||
},
|
||||
"udp_port_2": &UDPProxyConf{
|
||||
BaseProxyConf: BaseProxyConf{
|
||||
ProxyName: testProxyPrefix + "udp_port_2",
|
||||
ProxyType: consts.UDPProxy,
|
||||
UseEncryption: true,
|
||||
UseCompression: true,
|
||||
LocalSvrConf: LocalSvrConf{
|
||||
LocalIP: "114.114.114.114",
|
||||
LocalPort: 6011,
|
||||
},
|
||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||
},
|
||||
RemotePort: 6011,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range testcases {
|
||||
|
||||
f, err := ini.LoadSources(testLoadOptions, c.source)
|
||||
assert.NoError(err)
|
||||
|
||||
actual := make(map[string]ProxyConf)
|
||||
s := f.Section(c.sname)
|
||||
|
||||
err = renderRangeProxyTemplates(f, s)
|
||||
assert.NoError(err)
|
||||
|
||||
f.DeleteSection(ini.DefaultSection)
|
||||
f.DeleteSection(c.sname)
|
||||
|
||||
for _, section := range f.Sections() {
|
||||
proxyType := section.Key("type").String()
|
||||
newsname := section.Name()
|
||||
|
||||
tmp := DefaultProxyConf(proxyType)
|
||||
err = tmp.UnmarshalFromIni(testProxyPrefix, newsname, section)
|
||||
assert.NoError(err)
|
||||
|
||||
actual[newsname] = tmp
|
||||
}
|
||||
|
||||
assert.Equal(c.expected, actual)
|
||||
}
|
||||
}
|
@@ -1,217 +0,0 @@
|
||||
// Copyright 2020 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 config
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/fatedier/frp/pkg/auth"
|
||||
plugin "github.com/fatedier/frp/pkg/plugin/server"
|
||||
)
|
||||
|
||||
func Test_LoadServerCommonConf(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
testcases := []struct {
|
||||
source []byte
|
||||
expected ServerCommonConf
|
||||
}{
|
||||
{
|
||||
source: []byte(`
|
||||
# [common] is integral section
|
||||
[common]
|
||||
bind_addr = 0.0.0.9
|
||||
bind_port = 7009
|
||||
kcp_bind_port = 7007
|
||||
proxy_bind_addr = 127.0.0.9
|
||||
vhost_http_port = 89
|
||||
vhost_https_port = 449
|
||||
vhost_http_timeout = 69
|
||||
tcpmux_httpconnect_port = 1339
|
||||
dashboard_addr = 0.0.0.9
|
||||
dashboard_port = 7509
|
||||
dashboard_user = admin9
|
||||
dashboard_pwd = admin9
|
||||
enable_prometheus
|
||||
assets_dir = ./static9
|
||||
log_file = ./frps.log9
|
||||
log_way = file
|
||||
log_level = info9
|
||||
log_max_days = 39
|
||||
disable_log_color = false
|
||||
detailed_errors_to_client
|
||||
authentication_method = token
|
||||
authenticate_heartbeats = false
|
||||
authenticate_new_work_conns = false
|
||||
token = 123456789
|
||||
oidc_issuer = test9
|
||||
oidc_audience = test9
|
||||
oidc_skip_expiry_check
|
||||
oidc_skip_issuer_check
|
||||
heartbeat_timeout = 99
|
||||
user_conn_timeout = 9
|
||||
allow_ports = 10-12,99
|
||||
max_pool_count = 59
|
||||
max_ports_per_client = 9
|
||||
tls_only = false
|
||||
tls_cert_file = server.crt
|
||||
tls_key_file = server.key
|
||||
tls_trusted_ca_file = ca.crt
|
||||
subdomain_host = frps.com
|
||||
tcp_mux
|
||||
udp_packet_size = 1509
|
||||
[plugin.user-manager]
|
||||
addr = 127.0.0.1:9009
|
||||
path = /handler
|
||||
ops = Login
|
||||
[plugin.port-manager]
|
||||
addr = 127.0.0.1:9009
|
||||
path = /handler
|
||||
ops = NewProxy
|
||||
tls_verify
|
||||
`),
|
||||
expected: ServerCommonConf{
|
||||
ServerConfig: auth.ServerConfig{
|
||||
BaseConfig: auth.BaseConfig{
|
||||
AuthenticationMethod: "token",
|
||||
AuthenticateHeartBeats: false,
|
||||
AuthenticateNewWorkConns: false,
|
||||
},
|
||||
TokenConfig: auth.TokenConfig{
|
||||
Token: "123456789",
|
||||
},
|
||||
OidcServerConfig: auth.OidcServerConfig{
|
||||
OidcIssuer: "test9",
|
||||
OidcAudience: "test9",
|
||||
OidcSkipExpiryCheck: true,
|
||||
OidcSkipIssuerCheck: true,
|
||||
},
|
||||
},
|
||||
BindAddr: "0.0.0.9",
|
||||
BindPort: 7009,
|
||||
KCPBindPort: 7007,
|
||||
QUICKeepalivePeriod: 10,
|
||||
QUICMaxIdleTimeout: 30,
|
||||
QUICMaxIncomingStreams: 100000,
|
||||
ProxyBindAddr: "127.0.0.9",
|
||||
VhostHTTPPort: 89,
|
||||
VhostHTTPSPort: 449,
|
||||
VhostHTTPTimeout: 69,
|
||||
TCPMuxHTTPConnectPort: 1339,
|
||||
DashboardAddr: "0.0.0.9",
|
||||
DashboardPort: 7509,
|
||||
DashboardUser: "admin9",
|
||||
DashboardPwd: "admin9",
|
||||
EnablePrometheus: true,
|
||||
AssetsDir: "./static9",
|
||||
LogFile: "./frps.log9",
|
||||
LogWay: "file",
|
||||
LogLevel: "info9",
|
||||
LogMaxDays: 39,
|
||||
DisableLogColor: false,
|
||||
DetailedErrorsToClient: true,
|
||||
HeartbeatTimeout: 99,
|
||||
UserConnTimeout: 9,
|
||||
AllowPorts: map[int]struct{}{
|
||||
10: {},
|
||||
11: {},
|
||||
12: {},
|
||||
99: {},
|
||||
},
|
||||
AllowPortsStr: "10-12,99",
|
||||
MaxPoolCount: 59,
|
||||
MaxPortsPerClient: 9,
|
||||
TLSOnly: true,
|
||||
TLSCertFile: "server.crt",
|
||||
TLSKeyFile: "server.key",
|
||||
TLSTrustedCaFile: "ca.crt",
|
||||
SubDomainHost: "frps.com",
|
||||
TCPMux: true,
|
||||
TCPMuxKeepaliveInterval: 60,
|
||||
TCPKeepAlive: 7200,
|
||||
UDPPacketSize: 1509,
|
||||
NatHoleAnalysisDataReserveHours: 7 * 24,
|
||||
|
||||
HTTPPlugins: map[string]plugin.HTTPPluginOptions{
|
||||
"user-manager": {
|
||||
Name: "user-manager",
|
||||
Addr: "127.0.0.1:9009",
|
||||
Path: "/handler",
|
||||
Ops: []string{"Login"},
|
||||
},
|
||||
"port-manager": {
|
||||
Name: "port-manager",
|
||||
Addr: "127.0.0.1:9009",
|
||||
Path: "/handler",
|
||||
Ops: []string{"NewProxy"},
|
||||
TLSVerify: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
source: []byte(`
|
||||
# [common] is integral section
|
||||
[common]
|
||||
bind_addr = 0.0.0.9
|
||||
bind_port = 7009
|
||||
`),
|
||||
expected: ServerCommonConf{
|
||||
ServerConfig: auth.ServerConfig{
|
||||
BaseConfig: auth.BaseConfig{
|
||||
AuthenticationMethod: "token",
|
||||
AuthenticateHeartBeats: false,
|
||||
AuthenticateNewWorkConns: false,
|
||||
},
|
||||
},
|
||||
BindAddr: "0.0.0.9",
|
||||
BindPort: 7009,
|
||||
QUICKeepalivePeriod: 10,
|
||||
QUICMaxIdleTimeout: 30,
|
||||
QUICMaxIncomingStreams: 100000,
|
||||
ProxyBindAddr: "0.0.0.9",
|
||||
VhostHTTPTimeout: 60,
|
||||
DashboardAddr: "0.0.0.0",
|
||||
DashboardUser: "",
|
||||
DashboardPwd: "",
|
||||
EnablePrometheus: false,
|
||||
LogFile: "console",
|
||||
LogWay: "console",
|
||||
LogLevel: "info",
|
||||
LogMaxDays: 3,
|
||||
DetailedErrorsToClient: true,
|
||||
TCPMux: true,
|
||||
TCPMuxKeepaliveInterval: 60,
|
||||
TCPKeepAlive: 7200,
|
||||
AllowPorts: make(map[int]struct{}),
|
||||
MaxPoolCount: 5,
|
||||
HeartbeatTimeout: 90,
|
||||
UserConnTimeout: 10,
|
||||
HTTPPlugins: make(map[string]plugin.HTTPPluginOptions),
|
||||
UDPPacketSize: 1500,
|
||||
NatHoleAnalysisDataReserveHours: 7 * 24,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range testcases {
|
||||
actual, err := UnmarshalServerConfFromIni(c.source)
|
||||
assert.NoError(err)
|
||||
actual.Complete()
|
||||
assert.Equal(c.expected, actual)
|
||||
}
|
||||
}
|
@@ -12,11 +12,12 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package config
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
@@ -123,3 +124,62 @@ func (q *BandwidthQuantity) MarshalJSON() ([]byte, error) {
|
||||
func (q *BandwidthQuantity) Bytes() int64 {
|
||||
return q.i
|
||||
}
|
||||
|
||||
type PortsRange struct {
|
||||
Start int `json:"start,omitempty"`
|
||||
End int `json:"end,omitempty"`
|
||||
Single int `json:"single,omitempty"`
|
||||
}
|
||||
|
||||
type PortsRangeSlice []PortsRange
|
||||
|
||||
func (p PortsRangeSlice) String() string {
|
||||
strs := []string{}
|
||||
for _, v := range p {
|
||||
if v.Single > 0 {
|
||||
strs = append(strs, strconv.Itoa(v.Single))
|
||||
} else {
|
||||
strs = append(strs, strconv.Itoa(v.Start)+"-"+strconv.Itoa(v.End))
|
||||
}
|
||||
}
|
||||
return strings.Join(strs, ",")
|
||||
}
|
||||
|
||||
// the format of str is like "1000-2000,3000,4000-5000"
|
||||
func NewPortsRangeSliceFromString(str string) ([]PortsRange, error) {
|
||||
str = strings.TrimSpace(str)
|
||||
out := []PortsRange{}
|
||||
numRanges := strings.Split(str, ",")
|
||||
for _, numRangeStr := range numRanges {
|
||||
// 1000-2000 or 2001
|
||||
numArray := strings.Split(numRangeStr, "-")
|
||||
// length: only 1 or 2 is correct
|
||||
rangeType := len(numArray)
|
||||
switch rangeType {
|
||||
case 1:
|
||||
// single number
|
||||
singleNum, err := strconv.ParseInt(strings.TrimSpace(numArray[0]), 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("range number is invalid, %v", err)
|
||||
}
|
||||
out = append(out, PortsRange{Single: int(singleNum)})
|
||||
case 2:
|
||||
// range numbers
|
||||
min, err := strconv.ParseInt(strings.TrimSpace(numArray[0]), 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("range number is invalid, %v", err)
|
||||
}
|
||||
max, err := strconv.ParseInt(strings.TrimSpace(numArray[1]), 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("range number is invalid, %v", err)
|
||||
}
|
||||
if max < min {
|
||||
return nil, fmt.Errorf("range number is invalid")
|
||||
}
|
||||
out = append(out, PortsRange{Start: int(min), End: int(max)})
|
||||
default:
|
||||
return nil, fmt.Errorf("range number is invalid")
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
@@ -12,13 +12,13 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package config
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type Wrap struct {
|
||||
@@ -27,14 +27,46 @@ type Wrap struct {
|
||||
}
|
||||
|
||||
func TestBandwidthQuantity(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
var w Wrap
|
||||
err := json.Unmarshal([]byte(`{"b":"1KB","int":5}`), &w)
|
||||
assert.NoError(err)
|
||||
assert.EqualValues(1*KB, w.B.Bytes())
|
||||
require.NoError(err)
|
||||
require.EqualValues(1*KB, w.B.Bytes())
|
||||
|
||||
buf, err := json.Marshal(&w)
|
||||
assert.NoError(err)
|
||||
assert.Equal(`{"b":"1KB","int":5}`, string(buf))
|
||||
require.NoError(err)
|
||||
require.Equal(`{"b":"1KB","int":5}`, string(buf))
|
||||
}
|
||||
|
||||
func TestPortsRangeSlice2String(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
ports := []PortsRange{
|
||||
{
|
||||
Start: 1000,
|
||||
End: 2000,
|
||||
},
|
||||
{
|
||||
Single: 3000,
|
||||
},
|
||||
}
|
||||
str := PortsRangeSlice(ports).String()
|
||||
require.Equal("1000-2000,3000", str)
|
||||
}
|
||||
|
||||
func TestNewPortsRangeSliceFromString(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
ports, err := NewPortsRangeSliceFromString("1000-2000,3000")
|
||||
require.NoError(err)
|
||||
require.Equal([]PortsRange{
|
||||
{
|
||||
Start: 1000,
|
||||
End: 2000,
|
||||
},
|
||||
{
|
||||
Single: 3000,
|
||||
},
|
||||
}, ports)
|
||||
}
|
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
|
||||
}
|
||||
}
|
@@ -1,112 +0,0 @@
|
||||
// Copyright 2020 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 config
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gopkg.in/ini.v1"
|
||||
|
||||
"github.com/fatedier/frp/pkg/consts"
|
||||
)
|
||||
|
||||
const testVisitorPrefix = "test."
|
||||
|
||||
func Test_Visitor_Interface(_ *testing.T) {
|
||||
for name := range visitorConfTypeMap {
|
||||
DefaultVisitorConf(name)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Visitor_UnmarshalFromIni(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
testcases := []struct {
|
||||
sname string
|
||||
source []byte
|
||||
expected VisitorConf
|
||||
}{
|
||||
{
|
||||
sname: "secret_tcp_visitor",
|
||||
source: []byte(`
|
||||
[secret_tcp_visitor]
|
||||
role = visitor
|
||||
type = stcp
|
||||
server_name = secret_tcp
|
||||
sk = abcdefg
|
||||
bind_addr = 127.0.0.1
|
||||
bind_port = 9000
|
||||
use_encryption = false
|
||||
use_compression = false
|
||||
`),
|
||||
expected: &STCPVisitorConf{
|
||||
BaseVisitorConf: BaseVisitorConf{
|
||||
ProxyName: testVisitorPrefix + "secret_tcp_visitor",
|
||||
ProxyType: consts.STCPProxy,
|
||||
Role: "visitor",
|
||||
Sk: "abcdefg",
|
||||
ServerName: testVisitorPrefix + "secret_tcp",
|
||||
BindAddr: "127.0.0.1",
|
||||
BindPort: 9000,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
sname: "p2p_tcp_visitor",
|
||||
source: []byte(`
|
||||
[p2p_tcp_visitor]
|
||||
role = visitor
|
||||
type = xtcp
|
||||
server_name = p2p_tcp
|
||||
sk = abcdefg
|
||||
bind_addr = 127.0.0.1
|
||||
bind_port = 9001
|
||||
use_encryption = false
|
||||
use_compression = false
|
||||
`),
|
||||
expected: &XTCPVisitorConf{
|
||||
BaseVisitorConf: BaseVisitorConf{
|
||||
ProxyName: testVisitorPrefix + "p2p_tcp_visitor",
|
||||
ProxyType: consts.XTCPProxy,
|
||||
Role: "visitor",
|
||||
Sk: "abcdefg",
|
||||
ServerName: testProxyPrefix + "p2p_tcp",
|
||||
BindAddr: "127.0.0.1",
|
||||
BindPort: 9001,
|
||||
},
|
||||
Protocol: "quic",
|
||||
MaxRetriesAnHour: 8,
|
||||
MinRetryInterval: 90,
|
||||
FallbackTimeoutMs: 1000,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range testcases {
|
||||
f, err := ini.LoadSources(testLoadOptions, c.source)
|
||||
assert.NoError(err)
|
||||
|
||||
visitorType := f.Section(c.sname).Key("type").String()
|
||||
assert.NotEmpty(visitorType)
|
||||
|
||||
actual := DefaultVisitorConf(visitorType)
|
||||
assert.NotNil(actual)
|
||||
|
||||
err = actual.UnmarshalFromIni(testVisitorPrefix, c.sname, f.Section(c.sname))
|
||||
assert.NoError(err)
|
||||
assert.Equal(c.expected, actual)
|
||||
}
|
||||
}
|
@@ -16,55 +16,34 @@ package plugin
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"strings"
|
||||
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
utilnet "github.com/fatedier/frp/pkg/util/net"
|
||||
)
|
||||
|
||||
const PluginHTTP2HTTPS = "http2https"
|
||||
|
||||
func init() {
|
||||
Register(PluginHTTP2HTTPS, NewHTTP2HTTPSPlugin)
|
||||
Register(v1.PluginHTTP2HTTPS, NewHTTP2HTTPSPlugin)
|
||||
}
|
||||
|
||||
type HTTP2HTTPSPlugin struct {
|
||||
hostHeaderRewrite string
|
||||
localAddr string
|
||||
headers map[string]string
|
||||
opts *v1.HTTP2HTTPSPluginOptions
|
||||
|
||||
l *Listener
|
||||
s *http.Server
|
||||
}
|
||||
|
||||
func NewHTTP2HTTPSPlugin(params map[string]string) (Plugin, error) {
|
||||
localAddr := params["plugin_local_addr"]
|
||||
hostHeaderRewrite := params["plugin_host_header_rewrite"]
|
||||
headers := make(map[string]string)
|
||||
for k, v := range params {
|
||||
if !strings.HasPrefix(k, "plugin_header_") {
|
||||
continue
|
||||
}
|
||||
if k = strings.TrimPrefix(k, "plugin_header_"); k != "" {
|
||||
headers[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
if localAddr == "" {
|
||||
return nil, fmt.Errorf("plugin_local_addr is required")
|
||||
}
|
||||
func NewHTTP2HTTPSPlugin(options v1.ClientPluginOptions) (Plugin, error) {
|
||||
opts := options.(*v1.HTTP2HTTPSPluginOptions)
|
||||
|
||||
listener := NewProxyListener()
|
||||
|
||||
p := &HTTP2HTTPSPlugin{
|
||||
localAddr: localAddr,
|
||||
hostHeaderRewrite: hostHeaderRewrite,
|
||||
headers: headers,
|
||||
l: listener,
|
||||
opts: opts,
|
||||
l: listener,
|
||||
}
|
||||
|
||||
tr := &http.Transport{
|
||||
@@ -74,11 +53,11 @@ func NewHTTP2HTTPSPlugin(params map[string]string) (Plugin, error) {
|
||||
rp := &httputil.ReverseProxy{
|
||||
Director: func(req *http.Request) {
|
||||
req.URL.Scheme = "https"
|
||||
req.URL.Host = p.localAddr
|
||||
if p.hostHeaderRewrite != "" {
|
||||
req.Host = p.hostHeaderRewrite
|
||||
req.URL.Host = p.opts.LocalAddr
|
||||
if p.opts.HostHeaderRewrite != "" {
|
||||
req.Host = p.opts.HostHeaderRewrite
|
||||
}
|
||||
for k, v := range p.headers {
|
||||
for k, v := range p.opts.RequestHeaders.Set {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
},
|
||||
@@ -103,7 +82,7 @@ func (p *HTTP2HTTPSPlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, _
|
||||
}
|
||||
|
||||
func (p *HTTP2HTTPSPlugin) Name() string {
|
||||
return PluginHTTP2HTTPS
|
||||
return v1.PluginHTTP2HTTPS
|
||||
}
|
||||
|
||||
func (p *HTTP2HTTPSPlugin) Close() error {
|
||||
|
@@ -26,32 +26,29 @@ import (
|
||||
libio "github.com/fatedier/golib/io"
|
||||
libnet "github.com/fatedier/golib/net"
|
||||
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
utilnet "github.com/fatedier/frp/pkg/util/net"
|
||||
"github.com/fatedier/frp/pkg/util/util"
|
||||
)
|
||||
|
||||
const PluginHTTPProxy = "http_proxy"
|
||||
|
||||
func init() {
|
||||
Register(PluginHTTPProxy, NewHTTPProxyPlugin)
|
||||
Register(v1.PluginHTTPProxy, NewHTTPProxyPlugin)
|
||||
}
|
||||
|
||||
type HTTPProxy struct {
|
||||
l *Listener
|
||||
s *http.Server
|
||||
AuthUser string
|
||||
AuthPasswd string
|
||||
opts *v1.HTTPProxyPluginOptions
|
||||
|
||||
l *Listener
|
||||
s *http.Server
|
||||
}
|
||||
|
||||
func NewHTTPProxyPlugin(params map[string]string) (Plugin, error) {
|
||||
user := params["plugin_http_user"]
|
||||
passwd := params["plugin_http_passwd"]
|
||||
func NewHTTPProxyPlugin(options v1.ClientPluginOptions) (Plugin, error) {
|
||||
opts := options.(*v1.HTTPProxyPluginOptions)
|
||||
listener := NewProxyListener()
|
||||
|
||||
hp := &HTTPProxy{
|
||||
l: listener,
|
||||
AuthUser: user,
|
||||
AuthPasswd: passwd,
|
||||
l: listener,
|
||||
opts: opts,
|
||||
}
|
||||
|
||||
hp.s = &http.Server{
|
||||
@@ -65,7 +62,7 @@ func NewHTTPProxyPlugin(params map[string]string) (Plugin, error) {
|
||||
}
|
||||
|
||||
func (hp *HTTPProxy) Name() string {
|
||||
return PluginHTTPProxy
|
||||
return v1.PluginHTTPProxy
|
||||
}
|
||||
|
||||
func (hp *HTTPProxy) Handle(conn io.ReadWriteCloser, realConn net.Conn, _ []byte) {
|
||||
@@ -162,7 +159,7 @@ func (hp *HTTPProxy) ConnectHandler(rw http.ResponseWriter, req *http.Request) {
|
||||
}
|
||||
|
||||
func (hp *HTTPProxy) Auth(req *http.Request) bool {
|
||||
if hp.AuthUser == "" && hp.AuthPasswd == "" {
|
||||
if hp.opts.HTTPUser == "" && hp.opts.HTTPPassword == "" {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -181,8 +178,8 @@ func (hp *HTTPProxy) Auth(req *http.Request) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
if !util.ConstantTimeEqString(pair[0], hp.AuthUser) ||
|
||||
!util.ConstantTimeEqString(pair[1], hp.AuthPasswd) {
|
||||
if !util.ConstantTimeEqString(pair[0], hp.opts.HTTPUser) ||
|
||||
!util.ConstantTimeEqString(pair[1], hp.opts.HTTPPassword) {
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
return false
|
||||
}
|
||||
|
@@ -21,67 +21,40 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"strings"
|
||||
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
"github.com/fatedier/frp/pkg/transport"
|
||||
utilnet "github.com/fatedier/frp/pkg/util/net"
|
||||
)
|
||||
|
||||
const PluginHTTPS2HTTP = "https2http"
|
||||
|
||||
func init() {
|
||||
Register(PluginHTTPS2HTTP, NewHTTPS2HTTPPlugin)
|
||||
Register(v1.PluginHTTPS2HTTP, NewHTTPS2HTTPPlugin)
|
||||
}
|
||||
|
||||
type HTTPS2HTTPPlugin struct {
|
||||
crtPath string
|
||||
keyPath string
|
||||
hostHeaderRewrite string
|
||||
localAddr string
|
||||
headers map[string]string
|
||||
opts *v1.HTTPS2HTTPPluginOptions
|
||||
|
||||
l *Listener
|
||||
s *http.Server
|
||||
}
|
||||
|
||||
func NewHTTPS2HTTPPlugin(params map[string]string) (Plugin, error) {
|
||||
crtPath := params["plugin_crt_path"]
|
||||
keyPath := params["plugin_key_path"]
|
||||
localAddr := params["plugin_local_addr"]
|
||||
hostHeaderRewrite := params["plugin_host_header_rewrite"]
|
||||
headers := make(map[string]string)
|
||||
for k, v := range params {
|
||||
if !strings.HasPrefix(k, "plugin_header_") {
|
||||
continue
|
||||
}
|
||||
if k = strings.TrimPrefix(k, "plugin_header_"); k != "" {
|
||||
headers[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
if localAddr == "" {
|
||||
return nil, fmt.Errorf("plugin_local_addr is required")
|
||||
}
|
||||
|
||||
func NewHTTPS2HTTPPlugin(options v1.ClientPluginOptions) (Plugin, error) {
|
||||
opts := options.(*v1.HTTPS2HTTPPluginOptions)
|
||||
listener := NewProxyListener()
|
||||
|
||||
p := &HTTPS2HTTPPlugin{
|
||||
crtPath: crtPath,
|
||||
keyPath: keyPath,
|
||||
localAddr: localAddr,
|
||||
hostHeaderRewrite: hostHeaderRewrite,
|
||||
headers: headers,
|
||||
l: listener,
|
||||
opts: opts,
|
||||
l: listener,
|
||||
}
|
||||
|
||||
rp := &httputil.ReverseProxy{
|
||||
Director: func(req *http.Request) {
|
||||
req.URL.Scheme = "http"
|
||||
req.URL.Host = p.localAddr
|
||||
if p.hostHeaderRewrite != "" {
|
||||
req.Host = p.hostHeaderRewrite
|
||||
req.URL.Host = p.opts.LocalAddr
|
||||
if p.opts.HostHeaderRewrite != "" {
|
||||
req.Host = p.opts.HostHeaderRewrite
|
||||
}
|
||||
for k, v := range p.headers {
|
||||
for k, v := range p.opts.RequestHeaders.Set {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
},
|
||||
@@ -95,7 +68,7 @@ func NewHTTPS2HTTPPlugin(params map[string]string) (Plugin, error) {
|
||||
tlsConfig *tls.Config
|
||||
err error
|
||||
)
|
||||
if crtPath != "" || keyPath != "" {
|
||||
if opts.CrtPath != "" || opts.KeyPath != "" {
|
||||
tlsConfig, err = p.genTLSConfig()
|
||||
} else {
|
||||
tlsConfig, err = transport.NewServerTLSConfig("", "", "")
|
||||
@@ -113,7 +86,7 @@ func NewHTTPS2HTTPPlugin(params map[string]string) (Plugin, error) {
|
||||
}
|
||||
|
||||
func (p *HTTPS2HTTPPlugin) genTLSConfig() (*tls.Config, error) {
|
||||
cert, err := tls.LoadX509KeyPair(p.crtPath, p.keyPath)
|
||||
cert, err := tls.LoadX509KeyPair(p.opts.CrtPath, p.opts.KeyPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -128,7 +101,7 @@ func (p *HTTPS2HTTPPlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, _
|
||||
}
|
||||
|
||||
func (p *HTTPS2HTTPPlugin) Name() string {
|
||||
return PluginHTTPS2HTTP
|
||||
return v1.PluginHTTPS2HTTP
|
||||
}
|
||||
|
||||
func (p *HTTPS2HTTPPlugin) Close() error {
|
||||
|
@@ -21,57 +21,31 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"strings"
|
||||
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
"github.com/fatedier/frp/pkg/transport"
|
||||
utilnet "github.com/fatedier/frp/pkg/util/net"
|
||||
)
|
||||
|
||||
const PluginHTTPS2HTTPS = "https2https"
|
||||
|
||||
func init() {
|
||||
Register(PluginHTTPS2HTTPS, NewHTTPS2HTTPSPlugin)
|
||||
Register(v1.PluginHTTPS2HTTPS, NewHTTPS2HTTPSPlugin)
|
||||
}
|
||||
|
||||
type HTTPS2HTTPSPlugin struct {
|
||||
crtPath string
|
||||
keyPath string
|
||||
hostHeaderRewrite string
|
||||
localAddr string
|
||||
headers map[string]string
|
||||
opts *v1.HTTPS2HTTPSPluginOptions
|
||||
|
||||
l *Listener
|
||||
s *http.Server
|
||||
}
|
||||
|
||||
func NewHTTPS2HTTPSPlugin(params map[string]string) (Plugin, error) {
|
||||
crtPath := params["plugin_crt_path"]
|
||||
keyPath := params["plugin_key_path"]
|
||||
localAddr := params["plugin_local_addr"]
|
||||
hostHeaderRewrite := params["plugin_host_header_rewrite"]
|
||||
headers := make(map[string]string)
|
||||
for k, v := range params {
|
||||
if !strings.HasPrefix(k, "plugin_header_") {
|
||||
continue
|
||||
}
|
||||
if k = strings.TrimPrefix(k, "plugin_header_"); k != "" {
|
||||
headers[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
if localAddr == "" {
|
||||
return nil, fmt.Errorf("plugin_local_addr is required")
|
||||
}
|
||||
func NewHTTPS2HTTPSPlugin(options v1.ClientPluginOptions) (Plugin, error) {
|
||||
opts := options.(*v1.HTTPS2HTTPSPluginOptions)
|
||||
|
||||
listener := NewProxyListener()
|
||||
|
||||
p := &HTTPS2HTTPSPlugin{
|
||||
crtPath: crtPath,
|
||||
keyPath: keyPath,
|
||||
localAddr: localAddr,
|
||||
hostHeaderRewrite: hostHeaderRewrite,
|
||||
headers: headers,
|
||||
l: listener,
|
||||
opts: opts,
|
||||
l: listener,
|
||||
}
|
||||
|
||||
tr := &http.Transport{
|
||||
@@ -81,11 +55,11 @@ func NewHTTPS2HTTPSPlugin(params map[string]string) (Plugin, error) {
|
||||
rp := &httputil.ReverseProxy{
|
||||
Director: func(req *http.Request) {
|
||||
req.URL.Scheme = "https"
|
||||
req.URL.Host = p.localAddr
|
||||
if p.hostHeaderRewrite != "" {
|
||||
req.Host = p.hostHeaderRewrite
|
||||
req.URL.Host = p.opts.LocalAddr
|
||||
if p.opts.HostHeaderRewrite != "" {
|
||||
req.Host = p.opts.HostHeaderRewrite
|
||||
}
|
||||
for k, v := range p.headers {
|
||||
for k, v := range p.opts.RequestHeaders.Set {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
},
|
||||
@@ -100,7 +74,7 @@ func NewHTTPS2HTTPSPlugin(params map[string]string) (Plugin, error) {
|
||||
tlsConfig *tls.Config
|
||||
err error
|
||||
)
|
||||
if crtPath != "" || keyPath != "" {
|
||||
if opts.CrtPath != "" || opts.KeyPath != "" {
|
||||
tlsConfig, err = p.genTLSConfig()
|
||||
} else {
|
||||
tlsConfig, err = transport.NewServerTLSConfig("", "", "")
|
||||
@@ -118,7 +92,7 @@ func NewHTTPS2HTTPSPlugin(params map[string]string) (Plugin, error) {
|
||||
}
|
||||
|
||||
func (p *HTTPS2HTTPSPlugin) genTLSConfig() (*tls.Config, error) {
|
||||
cert, err := tls.LoadX509KeyPair(p.crtPath, p.keyPath)
|
||||
cert, err := tls.LoadX509KeyPair(p.opts.CrtPath, p.opts.KeyPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -133,7 +107,7 @@ func (p *HTTPS2HTTPSPlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, _
|
||||
}
|
||||
|
||||
func (p *HTTPS2HTTPSPlugin) Name() string {
|
||||
return PluginHTTPS2HTTPS
|
||||
return v1.PluginHTTPS2HTTPS
|
||||
}
|
||||
|
||||
func (p *HTTPS2HTTPSPlugin) Close() error {
|
||||
|
@@ -21,21 +21,23 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/fatedier/golib/errors"
|
||||
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
)
|
||||
|
||||
// Creators is used for create plugins to handle connections.
|
||||
var creators = make(map[string]CreatorFn)
|
||||
|
||||
// params has prefix "plugin_"
|
||||
type CreatorFn func(params map[string]string) (Plugin, error)
|
||||
type CreatorFn func(options v1.ClientPluginOptions) (Plugin, error)
|
||||
|
||||
func Register(name string, fn CreatorFn) {
|
||||
creators[name] = fn
|
||||
}
|
||||
|
||||
func Create(name string, params map[string]string) (p Plugin, err error) {
|
||||
func Create(name string, options v1.ClientPluginOptions) (p Plugin, err error) {
|
||||
if fn, ok := creators[name]; ok {
|
||||
p, err = fn(params)
|
||||
p, err = fn(options)
|
||||
} else {
|
||||
err = fmt.Errorf("plugin [%s] is not registered", name)
|
||||
}
|
||||
|
@@ -21,28 +21,26 @@ import (
|
||||
|
||||
gosocks5 "github.com/armon/go-socks5"
|
||||
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
utilnet "github.com/fatedier/frp/pkg/util/net"
|
||||
)
|
||||
|
||||
const PluginSocks5 = "socks5"
|
||||
|
||||
func init() {
|
||||
Register(PluginSocks5, NewSocks5Plugin)
|
||||
Register(v1.PluginSocks5, NewSocks5Plugin)
|
||||
}
|
||||
|
||||
type Socks5Plugin struct {
|
||||
Server *gosocks5.Server
|
||||
}
|
||||
|
||||
func NewSocks5Plugin(params map[string]string) (p Plugin, err error) {
|
||||
user := params["plugin_user"]
|
||||
passwd := params["plugin_passwd"]
|
||||
func NewSocks5Plugin(options v1.ClientPluginOptions) (p Plugin, err error) {
|
||||
opts := options.(*v1.Socks5PluginOptions)
|
||||
|
||||
cfg := &gosocks5.Config{
|
||||
Logger: log.New(io.Discard, "", log.LstdFlags),
|
||||
}
|
||||
if user != "" || passwd != "" {
|
||||
cfg.Credentials = gosocks5.StaticCredentials(map[string]string{user: passwd})
|
||||
if opts.Username != "" || opts.Password != "" {
|
||||
cfg.Credentials = gosocks5.StaticCredentials(map[string]string{opts.Username: opts.Password})
|
||||
}
|
||||
sp := &Socks5Plugin{}
|
||||
sp.Server, err = gosocks5.New(cfg)
|
||||
@@ -57,7 +55,7 @@ func (sp *Socks5Plugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, _ []b
|
||||
}
|
||||
|
||||
func (sp *Socks5Plugin) Name() string {
|
||||
return PluginSocks5
|
||||
return v1.PluginSocks5
|
||||
}
|
||||
|
||||
func (sp *Socks5Plugin) Close() error {
|
||||
|
@@ -22,51 +22,41 @@ import (
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
utilnet "github.com/fatedier/frp/pkg/util/net"
|
||||
)
|
||||
|
||||
const PluginStaticFile = "static_file"
|
||||
|
||||
func init() {
|
||||
Register(PluginStaticFile, NewStaticFilePlugin)
|
||||
Register(v1.PluginStaticFile, NewStaticFilePlugin)
|
||||
}
|
||||
|
||||
type StaticFilePlugin struct {
|
||||
localPath string
|
||||
stripPrefix string
|
||||
httpUser string
|
||||
httpPasswd string
|
||||
opts *v1.StaticFilePluginOptions
|
||||
|
||||
l *Listener
|
||||
s *http.Server
|
||||
}
|
||||
|
||||
func NewStaticFilePlugin(params map[string]string) (Plugin, error) {
|
||||
localPath := params["plugin_local_path"]
|
||||
stripPrefix := params["plugin_strip_prefix"]
|
||||
httpUser := params["plugin_http_user"]
|
||||
httpPasswd := params["plugin_http_passwd"]
|
||||
func NewStaticFilePlugin(options v1.ClientPluginOptions) (Plugin, error) {
|
||||
opts := options.(*v1.StaticFilePluginOptions)
|
||||
|
||||
listener := NewProxyListener()
|
||||
|
||||
sp := &StaticFilePlugin{
|
||||
localPath: localPath,
|
||||
stripPrefix: stripPrefix,
|
||||
httpUser: httpUser,
|
||||
httpPasswd: httpPasswd,
|
||||
opts: opts,
|
||||
|
||||
l: listener,
|
||||
}
|
||||
var prefix string
|
||||
if stripPrefix != "" {
|
||||
prefix = "/" + stripPrefix + "/"
|
||||
if opts.StripPrefix != "" {
|
||||
prefix = "/" + opts.StripPrefix + "/"
|
||||
} else {
|
||||
prefix = "/"
|
||||
}
|
||||
|
||||
router := mux.NewRouter()
|
||||
router.Use(utilnet.NewHTTPAuthMiddleware(httpUser, httpPasswd).SetAuthFailDelay(200 * time.Millisecond).Middleware)
|
||||
router.PathPrefix(prefix).Handler(utilnet.MakeHTTPGzipHandler(http.StripPrefix(prefix, http.FileServer(http.Dir(localPath))))).Methods("GET")
|
||||
router.Use(utilnet.NewHTTPAuthMiddleware(opts.HTTPUser, opts.HTTPPassword).SetAuthFailDelay(200 * time.Millisecond).Middleware)
|
||||
router.PathPrefix(prefix).Handler(utilnet.MakeHTTPGzipHandler(http.StripPrefix(prefix, http.FileServer(http.Dir(opts.LocalPath))))).Methods("GET")
|
||||
sp.s = &http.Server{
|
||||
Handler: router,
|
||||
}
|
||||
@@ -82,7 +72,7 @@ func (sp *StaticFilePlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, _
|
||||
}
|
||||
|
||||
func (sp *StaticFilePlugin) Name() string {
|
||||
return PluginStaticFile
|
||||
return v1.PluginStaticFile
|
||||
}
|
||||
|
||||
func (sp *StaticFilePlugin) Close() error {
|
||||
|
@@ -15,31 +15,26 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
|
||||
libio "github.com/fatedier/golib/io"
|
||||
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
)
|
||||
|
||||
const PluginUnixDomainSocket = "unix_domain_socket"
|
||||
|
||||
func init() {
|
||||
Register(PluginUnixDomainSocket, NewUnixDomainSocketPlugin)
|
||||
Register(v1.PluginUnixDomainSocket, NewUnixDomainSocketPlugin)
|
||||
}
|
||||
|
||||
type UnixDomainSocketPlugin struct {
|
||||
UnixAddr *net.UnixAddr
|
||||
}
|
||||
|
||||
func NewUnixDomainSocketPlugin(params map[string]string) (p Plugin, err error) {
|
||||
unixPath, ok := params["plugin_unix_path"]
|
||||
if !ok {
|
||||
err = fmt.Errorf("plugin_unix_path not found")
|
||||
return
|
||||
}
|
||||
func NewUnixDomainSocketPlugin(options v1.ClientPluginOptions) (p Plugin, err error) {
|
||||
opts := options.(*v1.UnixDomainSocketPluginOptions)
|
||||
|
||||
unixAddr, errRet := net.ResolveUnixAddr("unix", unixPath)
|
||||
unixAddr, errRet := net.ResolveUnixAddr("unix", opts.UnixPath)
|
||||
if errRet != nil {
|
||||
err = errRet
|
||||
return
|
||||
@@ -66,7 +61,7 @@ func (uds *UnixDomainSocketPlugin) Handle(conn io.ReadWriteCloser, _ net.Conn, e
|
||||
}
|
||||
|
||||
func (uds *UnixDomainSocketPlugin) Name() string {
|
||||
return PluginUnixDomainSocket
|
||||
return v1.PluginUnixDomainSocket
|
||||
}
|
||||
|
||||
func (uds *UnixDomainSocketPlugin) Close() error {
|
||||
|
@@ -25,24 +25,18 @@ import (
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
)
|
||||
|
||||
type HTTPPluginOptions struct {
|
||||
Name string `ini:"name"`
|
||||
Addr string `ini:"addr"`
|
||||
Path string `ini:"path"`
|
||||
Ops []string `ini:"ops"`
|
||||
TLSVerify bool `ini:"tls_verify"`
|
||||
}
|
||||
|
||||
type httpPlugin struct {
|
||||
options HTTPPluginOptions
|
||||
options v1.HTTPPluginOptions
|
||||
|
||||
url string
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
func NewHTTPPluginOptions(options HTTPPluginOptions) Plugin {
|
||||
func NewHTTPPluginOptions(options v1.HTTPPluginOptions) Plugin {
|
||||
url := fmt.Sprintf("%s%s", options.Addr, options.Path)
|
||||
|
||||
var client *http.Client
|
||||
|
@@ -29,15 +29,14 @@ func init() {
|
||||
Log.SetLogFuncCallDepth(Log.GetLogFuncCallDepth() + 1)
|
||||
}
|
||||
|
||||
func InitLog(logWay string, logFile string, logLevel string, maxdays int64, disableLogColor bool) {
|
||||
SetLogFile(logWay, logFile, maxdays, disableLogColor)
|
||||
func InitLog(logFile string, logLevel string, maxdays int64, disableLogColor bool) {
|
||||
SetLogFile(logFile, maxdays, disableLogColor)
|
||||
SetLogLevel(logLevel)
|
||||
}
|
||||
|
||||
// SetLogFile to configure log params
|
||||
// logWay: file or console
|
||||
func SetLogFile(logWay string, logFile string, maxdays int64, disableLogColor bool) {
|
||||
if logWay == "console" {
|
||||
func SetLogFile(logFile string, maxdays int64, disableLogColor bool) {
|
||||
if logFile == "console" {
|
||||
params := ""
|
||||
if disableLogColor {
|
||||
params = `{"color": false}`
|
||||
|
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 guylewin, guy@lewin.co.il
|
||||
// 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.
|
||||
|
23
pkg/util/util/types.go
Normal file
23
pkg/util/util/types.go
Normal file
@@ -0,0 +1,23 @@
|
||||
// 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 util
|
||||
|
||||
func EmptyOr[T comparable](v T, fallback T) T {
|
||||
var zero T
|
||||
if zero == v {
|
||||
return fallback
|
||||
}
|
||||
return v
|
||||
}
|
Reference in New Issue
Block a user