diff --git a/client/proxy/udp.go b/client/proxy/udp.go index 1fca9904..5302f7b1 100644 --- a/client/proxy/udp.go +++ b/client/proxy/udp.go @@ -90,7 +90,7 @@ func (pxy *UDPProxy) Close() { func (pxy *UDPProxy) InWorkConn(conn net.Conn, _ *msg.StartWorkConn) { 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 pxy.Close() diff --git a/cmd/frpc/sub/root.go b/cmd/frpc/sub/root.go index a2532920..8ff9c438 100644 --- a/cmd/frpc/sub/root.go +++ b/cmd/frpc/sub/root.go @@ -48,7 +48,7 @@ var ( showVersion bool strictConfigMode bool allowUnsafe []string - authToken string + authTokens []string ) func init() { @@ -56,7 +56,7 @@ func init() { 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(&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{}, fmt.Sprintf("allowed unsafe features, one or more of: %s", strings.Join(security.ClientUnsafeFeatures, ", "))) } @@ -72,9 +72,9 @@ var rootCmd = &cobra.Command{ unsafeFeatures := security.NewUnsafeFeatures(allowUnsafe) - // If authToken is provided, fetch config from API - if authToken != "" { - err := runClientWithToken(authToken, unsafeFeatures) + // If authTokens is provided, fetch config from API + if len(authTokens) > 0 { + err := runClientWithTokens(authTokens, unsafeFeatures) if err != nil { fmt.Println(err) os.Exit(1) @@ -216,15 +216,58 @@ type APIResponse struct { } `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) + } + + // Verify all tokens use the same token value + if len(tokenToIDs) > 1 { + return fmt.Errorf("different tokens are not supported, all ids must use the same token") + } + + // 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 apiServer := os.Getenv("LOLIA_API") if apiServer == "" { apiServer = "https://api.lolia.link" } - // Fetch config from API - url := fmt.Sprintf("%s/api/v1/tunnel/frpc/config/%s", apiServer, token) + // Build URL with query parameters + 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) resp, err := http.Get(url) if err != nil {