mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-14 12:48:28 +10:00
164 lines
6.0 KiB
Go
164 lines
6.0 KiB
Go
package ccm
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
C "github.com/sagernet/sing-box/constant"
|
|
"github.com/sagernet/sing-box/log"
|
|
"github.com/sagernet/sing-box/option"
|
|
E "github.com/sagernet/sing/common/exceptions"
|
|
)
|
|
|
|
func buildCredentialProviders(
|
|
ctx context.Context,
|
|
options option.CCMServiceOptions,
|
|
logger log.ContextLogger,
|
|
) (map[string]credentialProvider, []Credential, error) {
|
|
allCredentialMap := make(map[string]Credential)
|
|
var allCredentials []Credential
|
|
providers := make(map[string]credentialProvider)
|
|
|
|
// Pass 1: create default and external credentials
|
|
for _, credentialOption := range options.Credentials {
|
|
switch credentialOption.Type {
|
|
case "default":
|
|
credential, err := newDefaultCredential(ctx, credentialOption.Tag, credentialOption.DefaultOptions, logger)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
allCredentialMap[credentialOption.Tag] = credential
|
|
allCredentials = append(allCredentials, credential)
|
|
providers[credentialOption.Tag] = &singleCredentialProvider{credential: credential}
|
|
case "external":
|
|
credential, err := newExternalCredential(ctx, credentialOption.Tag, credentialOption.ExternalOptions, logger)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
allCredentialMap[credentialOption.Tag] = credential
|
|
allCredentials = append(allCredentials, credential)
|
|
providers[credentialOption.Tag] = &singleCredentialProvider{credential: credential}
|
|
}
|
|
}
|
|
|
|
// Pass 2: create balancer providers
|
|
for _, credentialOption := range options.Credentials {
|
|
if credentialOption.Type == "balancer" {
|
|
subCredentials, err := resolveCredentialTags(credentialOption.BalancerOptions.Credentials, allCredentialMap, credentialOption.Tag)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
providers[credentialOption.Tag] = newBalancerProvider(subCredentials, credentialOption.BalancerOptions.Strategy, time.Duration(credentialOption.BalancerOptions.PollInterval), credentialOption.BalancerOptions.RebalanceThreshold, logger)
|
|
}
|
|
}
|
|
|
|
return providers, allCredentials, nil
|
|
}
|
|
|
|
func resolveCredentialTags(tags []string, allCredentials map[string]Credential, parentTag string) ([]Credential, error) {
|
|
credentials := make([]Credential, 0, len(tags))
|
|
for _, tag := range tags {
|
|
credential, exists := allCredentials[tag]
|
|
if !exists {
|
|
return nil, E.New("credential ", parentTag, " references unknown credential: ", tag)
|
|
}
|
|
credentials = append(credentials, credential)
|
|
}
|
|
if len(credentials) == 0 {
|
|
return nil, E.New("credential ", parentTag, " has no sub-credentials")
|
|
}
|
|
return credentials, nil
|
|
}
|
|
|
|
func validateCCMOptions(options option.CCMServiceOptions) error {
|
|
tags := make(map[string]bool)
|
|
credentialTypes := make(map[string]string)
|
|
for _, credential := range options.Credentials {
|
|
if tags[credential.Tag] {
|
|
return E.New("duplicate credential tag: ", credential.Tag)
|
|
}
|
|
tags[credential.Tag] = true
|
|
credentialTypes[credential.Tag] = credential.Type
|
|
if credential.Type == "default" || credential.Type == "" {
|
|
if credential.DefaultOptions.Reserve5h > 99 {
|
|
return E.New("credential ", credential.Tag, ": reserve_5h must be at most 99")
|
|
}
|
|
if credential.DefaultOptions.ReserveWeekly > 99 {
|
|
return E.New("credential ", credential.Tag, ": reserve_weekly must be at most 99")
|
|
}
|
|
if credential.DefaultOptions.Limit5h > 100 {
|
|
return E.New("credential ", credential.Tag, ": limit_5h must be at most 100")
|
|
}
|
|
if credential.DefaultOptions.LimitWeekly > 100 {
|
|
return E.New("credential ", credential.Tag, ": limit_weekly must be at most 100")
|
|
}
|
|
if credential.DefaultOptions.Reserve5h > 0 && credential.DefaultOptions.Limit5h > 0 {
|
|
return E.New("credential ", credential.Tag, ": reserve_5h and limit_5h are mutually exclusive")
|
|
}
|
|
if credential.DefaultOptions.ReserveWeekly > 0 && credential.DefaultOptions.LimitWeekly > 0 {
|
|
return E.New("credential ", credential.Tag, ": reserve_weekly and limit_weekly are mutually exclusive")
|
|
}
|
|
}
|
|
if credential.Type == "external" {
|
|
if credential.ExternalOptions.Token == "" {
|
|
return E.New("credential ", credential.Tag, ": external credential requires token")
|
|
}
|
|
if credential.ExternalOptions.Reverse && credential.ExternalOptions.URL == "" {
|
|
return E.New("credential ", credential.Tag, ": reverse external credential requires url")
|
|
}
|
|
}
|
|
if credential.Type == "balancer" {
|
|
switch credential.BalancerOptions.Strategy {
|
|
case "", C.BalancerStrategyLeastUsed, C.BalancerStrategyRoundRobin, C.BalancerStrategyRandom, C.BalancerStrategyFallback:
|
|
default:
|
|
return E.New("credential ", credential.Tag, ": unknown balancer strategy: ", credential.BalancerOptions.Strategy)
|
|
}
|
|
if credential.BalancerOptions.RebalanceThreshold < 0 {
|
|
return E.New("credential ", credential.Tag, ": rebalance_threshold must not be negative")
|
|
}
|
|
}
|
|
}
|
|
|
|
singleCredential := len(options.Credentials) == 1
|
|
for _, user := range options.Users {
|
|
if user.Credential == "" && !singleCredential {
|
|
return E.New("user ", user.Name, " must specify credential in multi-credential mode")
|
|
}
|
|
if user.Credential != "" && !tags[user.Credential] {
|
|
return E.New("user ", user.Name, " references unknown credential: ", user.Credential)
|
|
}
|
|
if user.ExternalCredential != "" {
|
|
if !tags[user.ExternalCredential] {
|
|
return E.New("user ", user.Name, " references unknown external_credential: ", user.ExternalCredential)
|
|
}
|
|
if credentialTypes[user.ExternalCredential] != "external" {
|
|
return E.New("user ", user.Name, ": external_credential must reference an external type credential")
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func credentialForUser(
|
|
userConfigMap map[string]*option.CCMUser,
|
|
providers map[string]credentialProvider,
|
|
username string,
|
|
) (credentialProvider, error) {
|
|
userConfig, exists := userConfigMap[username]
|
|
if !exists {
|
|
return nil, E.New("no credential mapping for user: ", username)
|
|
}
|
|
if userConfig.Credential == "" {
|
|
for _, provider := range providers {
|
|
return provider, nil
|
|
}
|
|
return nil, E.New("no credential available")
|
|
}
|
|
provider, exists := providers[userConfig.Credential]
|
|
if !exists {
|
|
return nil, E.New("unknown credential: ", userConfig.Credential)
|
|
}
|
|
return provider, nil
|
|
}
|