forked from Mxmilu666/frp
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
803e548f42
|
|||
|
2dac44ac2e
|
|||
|
655dc3cb2a
|
|||
|
9894342f46
|
|||
|
e7cc706c86
|
@@ -90,7 +90,7 @@ func (pxy *UDPProxy) Close() {
|
|||||||
|
|
||||||
func (pxy *UDPProxy) InWorkConn(conn net.Conn, _ *msg.StartWorkConn) {
|
func (pxy *UDPProxy) InWorkConn(conn net.Conn, _ *msg.StartWorkConn) {
|
||||||
xl := pxy.xl
|
xl := pxy.xl
|
||||||
xl.Infof("incoming a new work connection for udp proxy, %s", conn.RemoteAddr().String())
|
xl.Infof("收到一条新的 UDP 代理工作连接, %s", conn.RemoteAddr().String())
|
||||||
// close resources related with old workConn
|
// close resources related with old workConn
|
||||||
pxy.Close()
|
pxy.Close()
|
||||||
|
|
||||||
|
|||||||
@@ -54,7 +54,11 @@ func NewAdminCommand(name, short string, handler func(*v1.ClientCommonConfig) er
|
|||||||
Use: name,
|
Use: name,
|
||||||
Short: short,
|
Short: short,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
cfg, _, _, _, err := config.LoadClientConfig(cfgFile, strictConfigMode)
|
if len(cfgFiles) == 0 || cfgFiles[0] == "" {
|
||||||
|
fmt.Println("frpc: the configuration file is not specified")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
cfg, _, _, _, err := config.LoadClientConfig(cfgFiles[0], strictConfigMode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
|||||||
@@ -48,8 +48,17 @@ var natholeDiscoveryCmd = &cobra.Command{
|
|||||||
Short: "Discover nathole information from stun server",
|
Short: "Discover nathole information from stun server",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
// ignore error here, because we can use command line pameters
|
// ignore error here, because we can use command line pameters
|
||||||
cfg, _, _, _, err := config.LoadClientConfig(cfgFile, strictConfigMode)
|
var cfg *v1.ClientCommonConfig
|
||||||
if err != nil {
|
if len(cfgFiles) > 0 && cfgFiles[0] != "" {
|
||||||
|
_, _, _, _, err := config.LoadClientConfig(cfgFiles[0], strictConfigMode)
|
||||||
|
if err != nil {
|
||||||
|
cfg = &v1.ClientCommonConfig{}
|
||||||
|
if err := cfg.Complete(); err != nil {
|
||||||
|
fmt.Printf("failed to complete config: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
cfg = &v1.ClientCommonConfig{}
|
cfg = &v1.ClientCommonConfig{}
|
||||||
if err := cfg.Complete(); err != nil {
|
if err := cfg.Complete(); err != nil {
|
||||||
fmt.Printf("failed to complete config: %v\n", err)
|
fmt.Printf("failed to complete config: %v\n", err)
|
||||||
|
|||||||
@@ -43,20 +43,22 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
cfgFile string
|
cfgFiles []string
|
||||||
cfgDir string
|
cfgDir string
|
||||||
showVersion bool
|
showVersion bool
|
||||||
strictConfigMode bool
|
strictConfigMode bool
|
||||||
allowUnsafe []string
|
allowUnsafe []string
|
||||||
authToken string
|
authTokens []string
|
||||||
|
|
||||||
|
bannerDisplayed bool
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "./frpc.ini", "config file of frpc")
|
rootCmd.PersistentFlags().StringSliceVarP(&cfgFiles, "config", "c", []string{"./frpc.ini"}, "config files of frpc (support multiple files)")
|
||||||
rootCmd.PersistentFlags().StringVarP(&cfgDir, "config_dir", "", "", "config directory, run one frpc service for each file in config directory")
|
rootCmd.PersistentFlags().StringVarP(&cfgDir, "config_dir", "", "", "config directory, run one frpc service for each file in config directory")
|
||||||
rootCmd.PersistentFlags().BoolVarP(&showVersion, "version", "v", false, "version of frpc")
|
rootCmd.PersistentFlags().BoolVarP(&showVersion, "version", "v", false, "version of frpc")
|
||||||
rootCmd.PersistentFlags().BoolVarP(&strictConfigMode, "strict_config", "", true, "strict config parsing mode, unknown fields will cause an errors")
|
rootCmd.PersistentFlags().BoolVarP(&strictConfigMode, "strict_config", "", true, "strict config parsing mode, unknown fields will cause an errors")
|
||||||
rootCmd.PersistentFlags().StringVarP(&authToken, "token", "t", "", "authentication token of frpc (LoliaFRP only)")
|
rootCmd.PersistentFlags().StringSliceVarP(&authTokens, "token", "t", []string{}, "authentication tokens in format 'id:token' (LoliaFRP only)")
|
||||||
rootCmd.PersistentFlags().StringSliceVarP(&allowUnsafe, "allow-unsafe", "", []string{},
|
rootCmd.PersistentFlags().StringSliceVarP(&allowUnsafe, "allow-unsafe", "", []string{},
|
||||||
fmt.Sprintf("allowed unsafe features, one or more of: %s", strings.Join(security.ClientUnsafeFeatures, ", ")))
|
fmt.Sprintf("allowed unsafe features, one or more of: %s", strings.Join(security.ClientUnsafeFeatures, ", ")))
|
||||||
}
|
}
|
||||||
@@ -72,9 +74,9 @@ var rootCmd = &cobra.Command{
|
|||||||
|
|
||||||
unsafeFeatures := security.NewUnsafeFeatures(allowUnsafe)
|
unsafeFeatures := security.NewUnsafeFeatures(allowUnsafe)
|
||||||
|
|
||||||
// If authToken is provided, fetch config from API
|
// If authTokens is provided, fetch config from API
|
||||||
if authToken != "" {
|
if len(authTokens) > 0 {
|
||||||
err := runClientWithToken(authToken, unsafeFeatures)
|
err := runClientWithTokens(authTokens, unsafeFeatures)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@@ -83,14 +85,19 @@ var rootCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If cfgDir is not empty, run multiple frpc service for each config file in cfgDir.
|
// If cfgDir is not empty, run multiple frpc service for each config file in cfgDir.
|
||||||
// Note that it's only designed for testing. It's not guaranteed to be stable.
|
|
||||||
if cfgDir != "" {
|
if cfgDir != "" {
|
||||||
_ = runMultipleClients(cfgDir, unsafeFeatures)
|
_ = runMultipleClients(cfgDir, unsafeFeatures)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If multiple config files are specified, run one frpc service for each file
|
||||||
|
if len(cfgFiles) > 1 {
|
||||||
|
_ = runMultipleClientsFromFiles(cfgFiles, unsafeFeatures)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Do not show command usage here.
|
// Do not show command usage here.
|
||||||
err := runClient(cfgFile, unsafeFeatures)
|
err := runClient(cfgFiles[0], unsafeFeatures)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@@ -120,6 +127,30 @@ func runMultipleClients(cfgDir string, unsafeFeatures *security.UnsafeFeatures)
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func runMultipleClientsFromFiles(cfgFiles []string, unsafeFeatures *security.UnsafeFeatures) error {
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
// Display banner first
|
||||||
|
banner.DisplayBanner()
|
||||||
|
bannerDisplayed = true
|
||||||
|
log.Infof("检测到 %d 个配置文件,将启动多个 frpc 服务实例", len(cfgFiles))
|
||||||
|
|
||||||
|
for i, cfgFile := range cfgFiles {
|
||||||
|
wg.Add(1)
|
||||||
|
// Add a small delay to avoid log output mixing
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
go func(index int, path string) {
|
||||||
|
defer wg.Done()
|
||||||
|
err := runClient(path, unsafeFeatures)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("\n配置文件 [%s] 启动失败: %v\n", path, err)
|
||||||
|
}
|
||||||
|
}(i, cfgFile)
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func Execute() {
|
func Execute() {
|
||||||
rootCmd.SetGlobalNormalizationFunc(config.WordSepNormalizeFunc)
|
rootCmd.SetGlobalNormalizationFunc(config.WordSepNormalizeFunc)
|
||||||
if err := rootCmd.Execute(); err != nil {
|
if err := rootCmd.Execute(); err != nil {
|
||||||
@@ -172,10 +203,11 @@ func startService(
|
|||||||
) error {
|
) error {
|
||||||
log.InitLogger(cfg.Log.To, cfg.Log.Level, int(cfg.Log.MaxDays), cfg.Log.DisablePrintColor)
|
log.InitLogger(cfg.Log.To, cfg.Log.Level, int(cfg.Log.MaxDays), cfg.Log.DisablePrintColor)
|
||||||
|
|
||||||
// Display banner before starting the service
|
// Display banner only once before starting the first service
|
||||||
banner.DisplayBanner()
|
if !bannerDisplayed {
|
||||||
|
banner.DisplayBanner()
|
||||||
log.Infof("Nya! %s 启动中", version.Full())
|
bannerDisplayed = true
|
||||||
|
}
|
||||||
|
|
||||||
// Display node information if available
|
// Display node information if available
|
||||||
if nodeName != "" {
|
if nodeName != "" {
|
||||||
@@ -216,15 +248,58 @@ type APIResponse struct {
|
|||||||
} `json:"data"`
|
} `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func runClientWithToken(token string, unsafeFeatures *security.UnsafeFeatures) error {
|
// TokenInfo stores parsed id and token from the -t parameter
|
||||||
|
type TokenInfo struct {
|
||||||
|
ID string
|
||||||
|
Token string
|
||||||
|
}
|
||||||
|
|
||||||
|
func runClientWithTokens(tokens []string, unsafeFeatures *security.UnsafeFeatures) error {
|
||||||
|
// Parse all tokens (format: id:token)
|
||||||
|
tokenInfos := make([]TokenInfo, 0, len(tokens))
|
||||||
|
for _, t := range tokens {
|
||||||
|
parts := strings.SplitN(t, ":", 2)
|
||||||
|
if len(parts) != 2 {
|
||||||
|
return fmt.Errorf("invalid token format '%s', expected 'id:token'", t)
|
||||||
|
}
|
||||||
|
tokenInfos = append(tokenInfos, TokenInfo{
|
||||||
|
ID: strings.TrimSpace(parts[0]),
|
||||||
|
Token: strings.TrimSpace(parts[1]),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Group tokens by token value (same token can have multiple IDs)
|
||||||
|
tokenToIDs := make(map[string][]string)
|
||||||
|
for _, ti := range tokenInfos {
|
||||||
|
tokenToIDs[ti.Token] = append(tokenToIDs[ti.Token], ti.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have multiple different tokens, start one service for each token group
|
||||||
|
if len(tokenToIDs) > 1 {
|
||||||
|
return runMultipleClientsWithTokens(tokenToIDs, unsafeFeatures)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the single token and all its IDs
|
||||||
|
var token string
|
||||||
|
var ids []string
|
||||||
|
for t, idList := range tokenToIDs {
|
||||||
|
token = t
|
||||||
|
ids = idList
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return runClientWithTokenAndIDs(token, ids, unsafeFeatures)
|
||||||
|
}
|
||||||
|
|
||||||
|
func runClientWithTokenAndIDs(token string, ids []string, unsafeFeatures *security.UnsafeFeatures) error {
|
||||||
// Get API server address from environment variable
|
// Get API server address from environment variable
|
||||||
apiServer := os.Getenv("LOLIA_API")
|
apiServer := os.Getenv("LOLIA_API")
|
||||||
if apiServer == "" {
|
if apiServer == "" {
|
||||||
apiServer = "https://api.lolia.link"
|
apiServer = "https://api.lolia.link"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch config from API
|
// Build URL with query parameters
|
||||||
url := fmt.Sprintf("%s/api/v1/tunnel/frpc/config/%s", apiServer, token)
|
url := fmt.Sprintf("%s/api/v1/tunnel/frpc/config?token=%s&id=%s", apiServer, token, strings.Join(ids, ","))
|
||||||
// #nosec G107 -- URL is constructed from trusted source (environment variable or hardcoded)
|
// #nosec G107 -- URL is constructed from trusted source (environment variable or hardcoded)
|
||||||
resp, err := http.Get(url)
|
resp, err := http.Get(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -255,6 +330,45 @@ func runClientWithToken(token string, unsafeFeatures *security.UnsafeFeatures) e
|
|||||||
return runClientWithConfig(configBytes, unsafeFeatures, apiResp.Data.NodeName, apiResp.Data.TunnelRemark)
|
return runClientWithConfig(configBytes, unsafeFeatures, apiResp.Data.NodeName, apiResp.Data.TunnelRemark)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func runMultipleClientsWithTokens(tokenToIDs map[string][]string, unsafeFeatures *security.UnsafeFeatures) error {
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
// Display banner first
|
||||||
|
banner.DisplayBanner()
|
||||||
|
bannerDisplayed = true
|
||||||
|
log.Infof("检测到 %d 个不同的 token,将并行启动多个 frpc 服务实例", len(tokenToIDs))
|
||||||
|
|
||||||
|
index := 0
|
||||||
|
for token, ids := range tokenToIDs {
|
||||||
|
wg.Add(1)
|
||||||
|
currentIndex := index
|
||||||
|
currentToken := token
|
||||||
|
currentIDs := ids
|
||||||
|
totalCount := len(tokenToIDs)
|
||||||
|
|
||||||
|
// Add a small delay to avoid log output mixing
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
maskedToken := currentToken
|
||||||
|
if len(maskedToken) > 6 {
|
||||||
|
maskedToken = maskedToken[:3] + "***" + maskedToken[len(maskedToken)-3:]
|
||||||
|
} else {
|
||||||
|
maskedToken = "***"
|
||||||
|
}
|
||||||
|
log.Infof("[%d/%d] 启动 token: %s (IDs: %v)", currentIndex+1, totalCount, maskedToken, currentIDs)
|
||||||
|
err := runClientWithTokenAndIDs(currentToken, currentIDs, unsafeFeatures)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("\nToken [%s] 启动失败: %v\n", maskedToken, err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
index++
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func runClientWithConfig(configBytes []byte, unsafeFeatures *security.UnsafeFeatures, nodeName, tunnelRemark string) error {
|
func runClientWithConfig(configBytes []byte, unsafeFeatures *security.UnsafeFeatures, nodeName, tunnelRemark string) error {
|
||||||
// Render template first
|
// Render template first
|
||||||
renderedBytes, err := config.RenderWithTemplate(configBytes, config.GetValues())
|
renderedBytes, err := config.RenderWithTemplate(configBytes, config.GetValues())
|
||||||
|
|||||||
@@ -33,11 +33,12 @@ var verifyCmd = &cobra.Command{
|
|||||||
Use: "verify",
|
Use: "verify",
|
||||||
Short: "Verify that the configures is valid",
|
Short: "Verify that the configures is valid",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
if cfgFile == "" {
|
if len(cfgFiles) == 0 || cfgFiles[0] == "" {
|
||||||
fmt.Println("frpc: the configuration file is not specified")
|
fmt.Println("frpc: the configuration file is not specified")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cfgFile := cfgFiles[0]
|
||||||
cliCfg, proxyCfgs, visitorCfgs, _, err := config.LoadClientConfig(cfgFile, strictConfigMode)
|
cliCfg, proxyCfgs, visitorCfgs, _, err := config.LoadClientConfig(cfgFile, strictConfigMode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
package banner
|
package banner
|
||||||
|
|
||||||
import "fmt"
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/pkg/util/log"
|
||||||
|
"github.com/fatedier/frp/pkg/util/version"
|
||||||
|
)
|
||||||
|
|
||||||
func DisplayBanner() {
|
func DisplayBanner() {
|
||||||
fmt.Println(" __ ___ __________ ____ ________ ____")
|
fmt.Println(" __ ___ __________ ____ ________ ____")
|
||||||
@@ -9,4 +14,5 @@ func DisplayBanner() {
|
|||||||
fmt.Println(" / /___/ /_/ / / / /_/ / __/ / _, _/ ____/_____/ /___/ /____/ / ")
|
fmt.Println(" / /___/ /_/ / / / /_/ / __/ / _, _/ ____/_____/ /___/ /____/ / ")
|
||||||
fmt.Println("/_____/\\____/_/_/\\__,_/_/ /_/ |_/_/ \\____/_____/___/ ")
|
fmt.Println("/_____/\\____/_/_/\\__,_/_/ /_/ |_/_/ \\____/_____/___/ ")
|
||||||
fmt.Println(" ")
|
fmt.Println(" ")
|
||||||
|
log.Infof("Nya! %s 启动中", version.Full())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
package version
|
package version
|
||||||
|
|
||||||
var version = "LoliaFRP-CLI 0.66.1"
|
var version = "LoliaFRP-CLI 0.66.3"
|
||||||
|
|
||||||
func Full() string {
|
func Full() string {
|
||||||
return version
|
return version
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ const (
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h1>域名未绑定绑定</h1>
|
<h1>域名未绑定</h1>
|
||||||
<p>这个域名还没有绑定到任何隧道哦 (;д;)</p>
|
<p>这个域名还没有绑定到任何隧道哦 (;д;)</p>
|
||||||
<p><strong>可能是这些原因:</strong></p>
|
<p><strong>可能是这些原因:</strong></p>
|
||||||
<ul>
|
<ul>
|
||||||
|
|||||||
Reference in New Issue
Block a user