mirror of
https://github.com/SagerNet/sing-box.git
synced 2025-06-08 11:44:13 +08:00
feat: add simple proxy provider support.
This commit is contained in:
parent
eaf1ace681
commit
cb026e63c1
@ -19,6 +19,7 @@ type Router interface {
|
|||||||
|
|
||||||
Outbounds() []Outbound
|
Outbounds() []Outbound
|
||||||
Outbound(tag string) (Outbound, bool)
|
Outbound(tag string) (Outbound, bool)
|
||||||
|
AddOutbound(string, Outbound)
|
||||||
DefaultOutbound(network string) Outbound
|
DefaultOutbound(network string) Outbound
|
||||||
|
|
||||||
RouteConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
RouteConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
||||||
|
2
box.go
2
box.go
@ -14,6 +14,7 @@ import (
|
|||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-box/outbound"
|
"github.com/sagernet/sing-box/outbound"
|
||||||
|
_ "github.com/sagernet/sing-box/outbound/provider"
|
||||||
"github.com/sagernet/sing-box/route"
|
"github.com/sagernet/sing-box/route"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
@ -120,6 +121,7 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
|
|||||||
}
|
}
|
||||||
inbounds = append(inbounds, in)
|
inbounds = append(inbounds, in)
|
||||||
}
|
}
|
||||||
|
outbounds = append(outbounds, outbound.InitCompatibleProxy(router, logFactory.NewLogger("outbound/provider"), option.Outbound{Type: "direct", Tag: "compatible"}.DirectOptions))
|
||||||
for i, outboundOptions := range options.Outbounds {
|
for i, outboundOptions := range options.Outbounds {
|
||||||
var out adapter.Outbound
|
var out adapter.Outbound
|
||||||
var tag string
|
var tag string
|
||||||
|
3
go.mod
3
go.mod
@ -7,6 +7,7 @@ require (
|
|||||||
github.com/cloudflare/circl v1.2.1-0.20220831060716-4cf0150356fc
|
github.com/cloudflare/circl v1.2.1-0.20220831060716-4cf0150356fc
|
||||||
github.com/cretz/bine v0.2.0
|
github.com/cretz/bine v0.2.0
|
||||||
github.com/database64128/tfo-go v1.1.2
|
github.com/database64128/tfo-go v1.1.2
|
||||||
|
github.com/dlclark/regexp2 v1.7.0
|
||||||
github.com/dustin/go-humanize v1.0.0
|
github.com/dustin/go-humanize v1.0.0
|
||||||
github.com/fsnotify/fsnotify v1.5.4
|
github.com/fsnotify/fsnotify v1.5.4
|
||||||
github.com/go-chi/chi/v5 v5.0.7
|
github.com/go-chi/chi/v5 v5.0.7
|
||||||
@ -40,6 +41,7 @@ require (
|
|||||||
golang.zx2c4.com/wireguard v0.0.0-20220904105730-b51010ba13f0
|
golang.zx2c4.com/wireguard v0.0.0-20220904105730-b51010ba13f0
|
||||||
google.golang.org/grpc v1.49.0
|
google.golang.org/grpc v1.49.0
|
||||||
google.golang.org/protobuf v1.28.1
|
google.golang.org/protobuf v1.28.1
|
||||||
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c
|
gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -78,6 +80,5 @@ require (
|
|||||||
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect
|
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20210722135532-667f2b7c528f // indirect
|
google.golang.org/genproto v0.0.0-20210722135532-667f2b7c528f // indirect
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
|
||||||
lukechampine.com/blake3 v1.1.7 // indirect
|
lukechampine.com/blake3 v1.1.7 // indirect
|
||||||
)
|
)
|
||||||
|
2
go.sum
2
go.sum
@ -26,6 +26,8 @@ github.com/database64128/tfo-go v1.1.2/go.mod h1:jgrSUPyOvTGQyn6irCOpk7L2W/q/0VL
|
|||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo=
|
||||||
|
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||||
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
||||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
|
@ -22,6 +22,7 @@ type _Outbound struct {
|
|||||||
SSHOptions SSHOutboundOptions `json:"-"`
|
SSHOptions SSHOutboundOptions `json:"-"`
|
||||||
ShadowTLSOptions ShadowTLSOutboundOptions `json:"-"`
|
ShadowTLSOptions ShadowTLSOutboundOptions `json:"-"`
|
||||||
SelectorOptions SelectorOutboundOptions `json:"-"`
|
SelectorOptions SelectorOutboundOptions `json:"-"`
|
||||||
|
ProviderOptions []ProviderOutboundOptions `json:"providers,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Outbound _Outbound
|
type Outbound _Outbound
|
||||||
|
7
option/provider.go
Normal file
7
option/provider.go
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package option
|
||||||
|
|
||||||
|
type ProviderOutboundOptions struct {
|
||||||
|
Url string `json:"url"`
|
||||||
|
Filter string `json:"filter"`
|
||||||
|
Interval int `json:"interval,omitempty"`
|
||||||
|
}
|
@ -42,7 +42,7 @@ func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, o
|
|||||||
case C.TypeShadowTLS:
|
case C.TypeShadowTLS:
|
||||||
return NewShadowTLS(ctx, router, logger, options.Tag, options.ShadowTLSOptions)
|
return NewShadowTLS(ctx, router, logger, options.Tag, options.ShadowTLSOptions)
|
||||||
case C.TypeSelector:
|
case C.TypeSelector:
|
||||||
return NewSelector(router, logger, options.Tag, options.SelectorOptions)
|
return NewSelector(ctx, router, logger, options.Tag, options.SelectorOptions, options.ProviderOptions)
|
||||||
default:
|
default:
|
||||||
return nil, E.New("unknown outbound type: ", options.Type)
|
return nil, E.New("unknown outbound type: ", options.Type)
|
||||||
}
|
}
|
||||||
|
180
outbound/provider.go
Normal file
180
outbound/provider.go
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
package outbound
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/dlclark/regexp2"
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Provider struct {
|
||||||
|
url string
|
||||||
|
filter *regexp2.Regexp
|
||||||
|
interval time.Duration
|
||||||
|
logger log.ContextLogger
|
||||||
|
ctx context.Context
|
||||||
|
router adapter.Router
|
||||||
|
}
|
||||||
|
|
||||||
|
type providerAdapter struct {
|
||||||
|
providers []Provider
|
||||||
|
}
|
||||||
|
|
||||||
|
type CachedProvider struct {
|
||||||
|
Outbounds []adapter.Outbound
|
||||||
|
LastUpdate time.Time
|
||||||
|
Lock *sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProviderResolver interface {
|
||||||
|
GetOutbounds([]byte, context.Context, adapter.Router, log.ContextLogger) []adapter.Outbound
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
providerResolvers = make(map[string]ProviderResolver)
|
||||||
|
cachedProviders = make(map[string]*CachedProvider, 0)
|
||||||
|
compatibleProxy adapter.Outbound
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewProvider(url, filterStr string, interval int, ctx context.Context, router adapter.Router,
|
||||||
|
logger log.ContextLogger) (Provider, error) {
|
||||||
|
if interval == 0 {
|
||||||
|
interval = 3600 * 24
|
||||||
|
}
|
||||||
|
filter, err := regexp2.Compile(filterStr, 0)
|
||||||
|
if err != nil {
|
||||||
|
return Provider{}, E.New("cannot parse provider regex filter")
|
||||||
|
}
|
||||||
|
return Provider{
|
||||||
|
url: url,
|
||||||
|
filter: filter,
|
||||||
|
interval: time.Duration(interval) * time.Second,
|
||||||
|
ctx: ctx,
|
||||||
|
router: router,
|
||||||
|
logger: logger},
|
||||||
|
nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) GetOutbounds() ([]string, map[string]adapter.Outbound) {
|
||||||
|
tags := make([]string, 0)
|
||||||
|
outbounds := make(map[string]adapter.Outbound, 0)
|
||||||
|
allOutbounds := p.getAllOutbounds()
|
||||||
|
for _, outbound := range allOutbounds {
|
||||||
|
if ok, _ := p.filter.MatchString(outbound.Tag()); ok {
|
||||||
|
p.router.AddOutbound(outbound.Tag(), outbound)
|
||||||
|
tags = append(tags, outbound.Tag())
|
||||||
|
outbounds[outbound.Tag()] = outbound
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tags, outbounds
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) getAllOutbounds() (res []adapter.Outbound) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
res = make([]adapter.Outbound, 0)
|
||||||
|
p.logger.Warn("failed to get provider outbounds: ", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if _, ok := cachedProviders[p.url]; !ok {
|
||||||
|
cachedProviders[p.url] = &CachedProvider{
|
||||||
|
Outbounds: make([]adapter.Outbound, 0),
|
||||||
|
LastUpdate: time.Time{},
|
||||||
|
Lock: &sync.Mutex{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cachedProviders[p.url].Lock.Lock()
|
||||||
|
defer cachedProviders[p.url].Lock.Unlock()
|
||||||
|
if (cachedProviders[p.url].LastUpdate.Add(p.interval)).Before(time.Now()) {
|
||||||
|
outbounds := make([]adapter.Outbound, 0)
|
||||||
|
resp, err := http.DefaultClient.Get(p.url)
|
||||||
|
if err == nil {
|
||||||
|
body := resp.Body
|
||||||
|
defer body.Close()
|
||||||
|
content, _ := io.ReadAll(body)
|
||||||
|
for _, resolver := range providerResolvers {
|
||||||
|
if len(outbounds) == 0 {
|
||||||
|
outbounds = resolver.GetOutbounds(content, p.ctx, p.router, p.logger)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cachedProviders[p.url].SetOutbounds(outbounds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cachedProviders[p.url].RefeshUpdateTime()
|
||||||
|
return cachedProviders[p.url].Outbounds
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *providerAdapter) NewUpdateFunc(tags *[]string, outbounds *map[string]adapter.Outbound, router adapter.Router, funcs []func()) func() {
|
||||||
|
res := func() {
|
||||||
|
for _, f := range funcs {
|
||||||
|
defer f()
|
||||||
|
}
|
||||||
|
for _, provider := range p.providers {
|
||||||
|
_, newOutbounds := provider.GetOutbounds()
|
||||||
|
for k, v := range newOutbounds {
|
||||||
|
if _, ok := (*outbounds)[k]; !ok {
|
||||||
|
*tags = append(*tags, k)
|
||||||
|
(*outbounds)[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(*tags) == 0 {
|
||||||
|
p.AddCompatibleProxy(tags, outbounds, router)
|
||||||
|
}
|
||||||
|
if len(*tags) > 2 {
|
||||||
|
if _, ok := (*outbounds)["compatible"]; ok {
|
||||||
|
delete(*outbounds, "compatible")
|
||||||
|
for i, tag := range *tags {
|
||||||
|
if tag == "compatible" {
|
||||||
|
*tags = append((*tags)[:i], (*tags)[i+1:]...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
ticker := time.NewTicker(time.Minute)
|
||||||
|
defer ticker.Stop()
|
||||||
|
for range ticker.C {
|
||||||
|
res()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *providerAdapter) AddCompatibleProxy(tags *[]string, outbounds *map[string]adapter.Outbound, router adapter.Router) {
|
||||||
|
if len(*tags) == 0 {
|
||||||
|
*tags = append(*tags, "compatible")
|
||||||
|
(*outbounds)["compatible"] = compatibleProxy
|
||||||
|
router.AddOutbound("compatible", compatibleProxy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *CachedProvider) SetOutbounds(outbounds []adapter.Outbound) {
|
||||||
|
p.Outbounds = outbounds
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *CachedProvider) RefeshUpdateTime() {
|
||||||
|
p.LastUpdate = time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
func InjectClashProviderResolver(name string, resolver ProviderResolver) {
|
||||||
|
providerResolvers[name] = resolver
|
||||||
|
}
|
||||||
|
|
||||||
|
func InitCompatibleProxy(router adapter.Router, logger log.ContextLogger, opts option.DirectOutboundOptions) adapter.Outbound {
|
||||||
|
var err error
|
||||||
|
compatibleProxy, err = NewDirect(router, logger, "compatible", opts)
|
||||||
|
if err != nil {
|
||||||
|
logger.Panic("cannot create compatible proxy")
|
||||||
|
}
|
||||||
|
return compatibleProxy
|
||||||
|
}
|
111
outbound/provider/clash.go
Normal file
111
outbound/provider/clash.go
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
package provider
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing-box/outbound"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
clashProxyParsers = make(map[string]func(context.Context, adapter.Router,
|
||||||
|
log.ContextLogger, map[string]interface{}) (adapter.Outbound, error))
|
||||||
|
)
|
||||||
|
|
||||||
|
type ClashProviderResolver struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ClashProviderResolver) GetOutbounds(rawData []byte, ctx context.Context,
|
||||||
|
router adapter.Router, logger log.ContextLogger) []adapter.Outbound {
|
||||||
|
data := make(map[string]interface{}, 0)
|
||||||
|
yaml.Unmarshal(rawData, &data)
|
||||||
|
outbounds := make([]adapter.Outbound, 0)
|
||||||
|
proxies, ok := data["proxies"]
|
||||||
|
if !ok {
|
||||||
|
return outbounds
|
||||||
|
}
|
||||||
|
proxyList, ok := proxies.([]interface{})
|
||||||
|
if !ok {
|
||||||
|
return outbounds
|
||||||
|
}
|
||||||
|
for _, proxy := range proxyList {
|
||||||
|
if proxyItem, ok := proxy.(map[string]interface{}); ok {
|
||||||
|
newOutbound, err := parseClashProxy(ctx, router, logger, proxyItem)
|
||||||
|
if err == nil {
|
||||||
|
outbounds = append(outbounds, newOutbound)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return outbounds
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseClashProxy(ctx context.Context, router adapter.Router,
|
||||||
|
logger log.ContextLogger, proxyItem map[string]interface{}) (res adapter.Outbound, err error) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
logger.Warn("cannot parse proxy: ", r)
|
||||||
|
res = nil
|
||||||
|
err = fmt.Errorf("cannot parse proxy: %v", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if proxyType, ok := proxyItem["type"].(string); ok {
|
||||||
|
if parser, ok := clashProxyParsers[proxyType]; ok {
|
||||||
|
return parser(ctx, router, logger, proxyItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("unkown proxy type")
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseClashSsProxy(ctx context.Context, router adapter.Router,
|
||||||
|
logger log.ContextLogger, proxyItem map[string]interface{}) (res adapter.Outbound, err error) {
|
||||||
|
options := option.ShadowsocksOutboundOptions{
|
||||||
|
DialerOptions: option.DialerOptions{},
|
||||||
|
ServerOptions: option.ServerOptions{
|
||||||
|
Server: proxyItem["server"].(string),
|
||||||
|
ServerPort: uint16(proxyItem["port"].(int))},
|
||||||
|
Password: proxyItem["password"].(string),
|
||||||
|
Method: proxyItem["cipher"].(string),
|
||||||
|
}
|
||||||
|
if udpEnabled, ok := proxyItem["udp"].(bool); ok && !udpEnabled {
|
||||||
|
options.Network = "tcp"
|
||||||
|
}
|
||||||
|
return outbound.NewShadowsocks(
|
||||||
|
ctx, router, logger,
|
||||||
|
proxyItem["name"].(string),
|
||||||
|
options,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseClashTrojanProxy(ctx context.Context, router adapter.Router,
|
||||||
|
logger log.ContextLogger, proxyItem map[string]interface{}) (res adapter.Outbound, err error) {
|
||||||
|
options := option.TrojanOutboundOptions{
|
||||||
|
DialerOptions: option.DialerOptions{},
|
||||||
|
ServerOptions: option.ServerOptions{
|
||||||
|
Server: proxyItem["server"].(string),
|
||||||
|
ServerPort: uint16(proxyItem["port"].(int))},
|
||||||
|
Password: proxyItem["password"].(string),
|
||||||
|
TLS: &option.OutboundTLSOptions{},
|
||||||
|
Multiplex: &option.MultiplexOptions{},
|
||||||
|
Transport: &option.V2RayTransportOptions{},
|
||||||
|
}
|
||||||
|
if udpEnabled, ok := proxyItem["udp"].(bool); ok && !udpEnabled {
|
||||||
|
options.Network = "tcp"
|
||||||
|
}
|
||||||
|
if sni, ok := proxyItem["sni"].(string); ok {
|
||||||
|
options.TLS.ServerName = sni
|
||||||
|
}
|
||||||
|
if skipCertVerity, ok := proxyItem["skip-cert-verify"].(bool); ok && skipCertVerity {
|
||||||
|
options.TLS.Insecure = true
|
||||||
|
}
|
||||||
|
return outbound.NewTrojan(ctx, router, logger, proxyItem["name"].(string), options)
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
clashProxyParsers["ss"] = parseClashSsProxy
|
||||||
|
clashProxyParsers["trojan"] = ParseClashTrojanProxy
|
||||||
|
outbound.InjectClashProviderResolver("clash", &ClashProviderResolver{})
|
||||||
|
}
|
@ -20,13 +20,14 @@ var (
|
|||||||
|
|
||||||
type Selector struct {
|
type Selector struct {
|
||||||
myOutboundAdapter
|
myOutboundAdapter
|
||||||
|
providerAdapter
|
||||||
tags []string
|
tags []string
|
||||||
defaultTag string
|
defaultTag string
|
||||||
outbounds map[string]adapter.Outbound
|
outbounds map[string]adapter.Outbound
|
||||||
selected adapter.Outbound
|
selected adapter.Outbound
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSelector(router adapter.Router, logger log.ContextLogger, tag string, options option.SelectorOutboundOptions) (*Selector, error) {
|
func NewSelector(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.SelectorOutboundOptions, providers []option.ProviderOutboundOptions) (*Selector, error) {
|
||||||
outbound := &Selector{
|
outbound := &Selector{
|
||||||
myOutboundAdapter: myOutboundAdapter{
|
myOutboundAdapter: myOutboundAdapter{
|
||||||
protocol: C.TypeSelector,
|
protocol: C.TypeSelector,
|
||||||
@ -38,6 +39,25 @@ func NewSelector(router adapter.Router, logger log.ContextLogger, tag string, op
|
|||||||
defaultTag: options.Default,
|
defaultTag: options.Default,
|
||||||
outbounds: make(map[string]adapter.Outbound),
|
outbounds: make(map[string]adapter.Outbound),
|
||||||
}
|
}
|
||||||
|
for _, providerOption := range providers {
|
||||||
|
provider, err := NewProvider(providerOption.Url, providerOption.Filter, providerOption.Interval, ctx, router, logger)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
outbound.providers = append(outbound.providers, provider)
|
||||||
|
go outbound.NewUpdateFunc(&outbound.tags, &outbound.outbounds, router, []func(){
|
||||||
|
func() {
|
||||||
|
if outbound.selected != nil {
|
||||||
|
if _, ok := outbound.outbounds[outbound.selected.Tag()]; ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
outbound.InitSelected()
|
||||||
|
}})()
|
||||||
|
}
|
||||||
|
if len(outbound.providers) > 0 {
|
||||||
|
outbound.AddCompatibleProxy(&outbound.tags, &outbound.outbounds, router)
|
||||||
|
}
|
||||||
if len(outbound.tags) == 0 {
|
if len(outbound.tags) == 0 {
|
||||||
return nil, E.New("missing tags")
|
return nil, E.New("missing tags")
|
||||||
}
|
}
|
||||||
@ -59,7 +79,10 @@ func (s *Selector) Start() error {
|
|||||||
}
|
}
|
||||||
s.outbounds[tag] = detour
|
s.outbounds[tag] = detour
|
||||||
}
|
}
|
||||||
|
return s.InitSelected()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Selector) InitSelected() error {
|
||||||
if s.tag != "" {
|
if s.tag != "" {
|
||||||
if clashServer := s.router.ClashServer(); clashServer != nil && clashServer.StoreSelected() {
|
if clashServer := s.router.ClashServer(); clashServer != nil && clashServer.StoreSelected() {
|
||||||
selected := clashServer.CacheFile().LoadSelected(s.tag)
|
selected := clashServer.CacheFile().LoadSelected(s.tag)
|
||||||
|
@ -507,6 +507,13 @@ func (r *Router) Outbound(tag string) (adapter.Outbound, bool) {
|
|||||||
return outbound, loaded
|
return outbound, loaded
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Router) AddOutbound(tag string, outbound adapter.Outbound) {
|
||||||
|
if _, loaded := r.outboundByTag[tag]; !loaded {
|
||||||
|
r.outbounds = append(r.outbounds, outbound)
|
||||||
|
}
|
||||||
|
r.outboundByTag[tag] = outbound
|
||||||
|
}
|
||||||
|
|
||||||
func (r *Router) DefaultOutbound(network string) adapter.Outbound {
|
func (r *Router) DefaultOutbound(network string) adapter.Outbound {
|
||||||
if network == N.NetworkTCP {
|
if network == N.NetworkTCP {
|
||||||
return r.defaultOutboundForConnection
|
return r.defaultOutboundForConnection
|
||||||
|
Loading…
x
Reference in New Issue
Block a user