mirror of
https://github.com/SagerNet/sing-box.git
synced 2024-11-22 08:31:30 +00:00
Remove urltest outbound
This commit is contained in:
parent
c240f1b359
commit
c0a2f77258
|
@ -25,3 +25,10 @@ type OutboundGroup interface {
|
|||
Now() string
|
||||
All() []string
|
||||
}
|
||||
|
||||
func OutboundTag(detour Outbound) string {
|
||||
if group, isGroup := detour.(OutboundGroup); isGroup {
|
||||
return group.Now()
|
||||
}
|
||||
return detour.Tag()
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@ import (
|
|||
"net/netip"
|
||||
|
||||
"github.com/sagernet/sing-box/common/geoip"
|
||||
"github.com/sagernet/sing-box/common/urltest"
|
||||
"github.com/sagernet/sing-dns"
|
||||
"github.com/sagernet/sing/common/control"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
|
@ -40,7 +39,6 @@ type Router interface {
|
|||
|
||||
Rules() []Rule
|
||||
SetTrafficController(controller TrafficController)
|
||||
URLTestHistoryStorage(create bool) *urltest.HistoryStorage
|
||||
}
|
||||
|
||||
type Rule interface {
|
||||
|
|
|
@ -16,5 +16,4 @@ const (
|
|||
|
||||
const (
|
||||
TypeSelector = "selector"
|
||||
TypeURLTest = "urltest"
|
||||
)
|
||||
|
|
|
@ -22,7 +22,6 @@
|
|||
| `shadowsocks` | [Shadowsocks](./shadowsocks) |
|
||||
| `dns` | [DNS](./dns) |
|
||||
| `selector` | [Selector](./selector) |
|
||||
| `urltest` | [URLTest](./urltest) |
|
||||
|
||||
#### tag
|
||||
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
### Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"outbounds": [
|
||||
{
|
||||
"type": "urltest",
|
||||
"tag": "auto",
|
||||
|
||||
"outbounds": [
|
||||
"proxy-a",
|
||||
"proxy-b",
|
||||
"proxy-c"
|
||||
],
|
||||
"url": "http://www.gstatic.com/generate_204",
|
||||
"interval": "1m",
|
||||
"tolerance": 50
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Fields
|
||||
|
||||
#### outbounds
|
||||
|
||||
==Required==
|
||||
|
||||
List of outbound tags to test.
|
||||
|
||||
#### url
|
||||
|
||||
The URL to test. `http://www.gstatic.com/generate_204` will be used if empty.
|
||||
|
||||
#### interval
|
||||
|
||||
The test interval. `1m` will be used if empty.
|
||||
|
||||
#### tolerance
|
||||
|
||||
The test tolerance in milliseconds. `50` will be used if empty.
|
|
@ -77,16 +77,13 @@ func proxyInfo(server *Server, detour adapter.Outbound) *badjson.JSONObject {
|
|||
case C.TypeSelector:
|
||||
clashType = "Selector"
|
||||
isGroup = true
|
||||
case C.TypeURLTest:
|
||||
clashType = "URLTest"
|
||||
isGroup = true
|
||||
default:
|
||||
clashType = "Unknown"
|
||||
}
|
||||
info.Put("type", clashType)
|
||||
info.Put("name", detour.Tag())
|
||||
info.Put("udp", common.Contains(detour.Network(), C.NetworkUDP))
|
||||
delayHistory := server.router.URLTestHistoryStorage(false).LoadURLTestHistory(outbound.RealTag(detour))
|
||||
delayHistory := server.urlTestHistory.LoadURLTestHistory(adapter.OutboundTag(detour))
|
||||
if delayHistory != nil {
|
||||
info.Put("history", []*urltest.History{delayHistory})
|
||||
} else {
|
||||
|
@ -218,9 +215,9 @@ func getProxyDelay(server *Server) func(w http.ResponseWriter, r *http.Request)
|
|||
defer func() {
|
||||
realTag := outbound.RealTag(proxy)
|
||||
if err != nil {
|
||||
server.router.URLTestHistoryStorage(true).DeleteURLTestHistory(realTag)
|
||||
server.urlTestHistory.DeleteURLTestHistory(realTag)
|
||||
} else {
|
||||
server.router.URLTestHistoryStorage(true).StoreURLTestHistory(realTag, &urltest.History{
|
||||
server.urlTestHistory.StoreURLTestHistory(realTag, &urltest.History{
|
||||
Time: time.Now(),
|
||||
Delay: delay,
|
||||
})
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/json"
|
||||
"github.com/sagernet/sing-box/common/urltest"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/experimental/clashapi/trafficontrol"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
|
@ -33,6 +34,7 @@ type Server struct {
|
|||
logger log.Logger
|
||||
httpServer *http.Server
|
||||
trafficManager *trafficontrol.Manager
|
||||
urlTestHistory *urltest.HistoryStorage
|
||||
}
|
||||
|
||||
func NewServer(router adapter.Router, logFactory log.ObservableFactory, options option.ClashAPIOptions) *Server {
|
||||
|
@ -46,6 +48,7 @@ func NewServer(router adapter.Router, logFactory log.ObservableFactory, options
|
|||
Handler: chiRouter,
|
||||
},
|
||||
trafficManager,
|
||||
urltest.NewHistoryStorage(),
|
||||
}
|
||||
cors := cors.New(cors.Options{
|
||||
AllowedOrigins: []string{"*"},
|
||||
|
|
|
@ -57,7 +57,6 @@ nav:
|
|||
- Shadowsocks: configuration/outbound/shadowsocks.md
|
||||
- DNS: configuration/outbound/dns.md
|
||||
- Selector: configuration/outbound/selector.md
|
||||
- URLTest: configuration/outbound/urltest.md
|
||||
- Route:
|
||||
- configuration/route/index.md
|
||||
- GeoIP: configuration/route/geoip.md
|
||||
|
|
|
@ -10,10 +10,3 @@ type SelectorOutboundOptions struct {
|
|||
Outbounds []string `json:"outbounds"`
|
||||
Default string `json:"default,omitempty"`
|
||||
}
|
||||
|
||||
type URLTestOutboundOptions struct {
|
||||
Outbounds []string `json:"outbounds"`
|
||||
URL string `json:"url,omitempty"`
|
||||
Interval Duration `json:"interval,omitempty"`
|
||||
Tolerance uint16 `json:"tolerance,omitempty"`
|
||||
}
|
||||
|
|
|
@ -16,7 +16,6 @@ type _Outbound struct {
|
|||
ShadowsocksOptions ShadowsocksOutboundOptions `json:"-"`
|
||||
VMessOptions VMessOutboundOptions `json:"-"`
|
||||
SelectorOptions SelectorOutboundOptions `json:"-"`
|
||||
URLTestOptions URLTestOutboundOptions `json:"-"`
|
||||
}
|
||||
|
||||
type Outbound _Outbound
|
||||
|
@ -38,8 +37,6 @@ func (h Outbound) MarshalJSON() ([]byte, error) {
|
|||
v = h.VMessOptions
|
||||
case C.TypeSelector:
|
||||
v = h.SelectorOptions
|
||||
case C.TypeURLTest:
|
||||
v = h.URLTestOptions
|
||||
default:
|
||||
return nil, E.New("unknown outbound type: ", h.Type)
|
||||
}
|
||||
|
@ -67,8 +64,6 @@ func (h *Outbound) UnmarshalJSON(bytes []byte) error {
|
|||
v = &h.VMessOptions
|
||||
case C.TypeSelector:
|
||||
v = &h.SelectorOptions
|
||||
case C.TypeURLTest:
|
||||
v = &h.URLTestOptions
|
||||
default:
|
||||
return E.New("unknown outbound type: ", h.Type)
|
||||
}
|
||||
|
|
|
@ -29,8 +29,6 @@ func New(router adapter.Router, logger log.ContextLogger, options option.Outboun
|
|||
return NewVMess(router, logger, options.Tag, options.VMessOptions)
|
||||
case C.TypeSelector:
|
||||
return NewSelector(router, logger, options.Tag, options.SelectorOptions)
|
||||
case C.TypeURLTest:
|
||||
return NewURLTest(router, logger, options.Tag, options.URLTestOptions)
|
||||
default:
|
||||
return nil, E.New("unknown outbound type: ", options.Type)
|
||||
}
|
||||
|
|
|
@ -103,3 +103,10 @@ func (s *Selector) NewConnection(ctx context.Context, conn net.Conn, metadata ad
|
|||
func (s *Selector) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
|
||||
return s.selected.NewPacketConnection(ctx, conn, metadata)
|
||||
}
|
||||
|
||||
func RealTag(detour adapter.Outbound) string {
|
||||
if group, isGroup := detour.(adapter.OutboundGroup); isGroup {
|
||||
return group.Now()
|
||||
}
|
||||
return detour.Tag()
|
||||
}
|
||||
|
|
|
@ -1,249 +0,0 @@
|
|||
package outbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/urltest"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/batch"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
var (
|
||||
_ adapter.Outbound = (*URLTest)(nil)
|
||||
_ adapter.OutboundGroup = (*URLTest)(nil)
|
||||
)
|
||||
|
||||
type URLTest struct {
|
||||
myOutboundAdapter
|
||||
tags []string
|
||||
link string
|
||||
interval time.Duration
|
||||
tolerance uint16
|
||||
group *URLTestGroup
|
||||
}
|
||||
|
||||
func NewURLTest(router adapter.Router, logger log.ContextLogger, tag string, options option.URLTestOutboundOptions) (*URLTest, error) {
|
||||
outbound := &URLTest{
|
||||
myOutboundAdapter: myOutboundAdapter{
|
||||
protocol: C.TypeURLTest,
|
||||
router: router,
|
||||
logger: logger,
|
||||
tag: tag,
|
||||
},
|
||||
tags: options.Outbounds,
|
||||
link: options.URL,
|
||||
interval: time.Duration(options.Interval),
|
||||
tolerance: options.Tolerance,
|
||||
}
|
||||
if len(outbound.tags) == 0 {
|
||||
return nil, E.New("missing tags")
|
||||
}
|
||||
return outbound, nil
|
||||
}
|
||||
|
||||
func (s *URLTest) Network() []string {
|
||||
if s.group == nil {
|
||||
return []string{C.NetworkTCP, C.NetworkUDP}
|
||||
}
|
||||
return s.group.Select().Network()
|
||||
}
|
||||
|
||||
func (s *URLTest) Start() error {
|
||||
outbounds := make([]adapter.Outbound, 0, len(s.tags))
|
||||
for i, tag := range s.tags {
|
||||
detour, loaded := s.router.Outbound(tag)
|
||||
if !loaded {
|
||||
return E.New("outbound ", i, " not found: ", tag)
|
||||
}
|
||||
outbounds = append(outbounds, detour)
|
||||
}
|
||||
s.group = NewURLTestGroup(s.router, s.logger, outbounds, s.link, s.interval, s.tolerance)
|
||||
return s.group.Start()
|
||||
}
|
||||
|
||||
func (s URLTest) Close() error {
|
||||
return common.Close(
|
||||
common.PtrOrNil(s.group),
|
||||
)
|
||||
}
|
||||
|
||||
func (s *URLTest) Now() string {
|
||||
return s.group.Select().Tag()
|
||||
}
|
||||
|
||||
func (s *URLTest) All() []string {
|
||||
return s.tags
|
||||
}
|
||||
|
||||
func (s *URLTest) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||
return s.group.Select().DialContext(ctx, network, destination)
|
||||
}
|
||||
|
||||
func (s *URLTest) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||
return s.group.Select().ListenPacket(ctx, destination)
|
||||
}
|
||||
|
||||
func (s *URLTest) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
||||
return s.group.Select().NewConnection(ctx, conn, metadata)
|
||||
}
|
||||
|
||||
func (s *URLTest) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
|
||||
return s.group.Select().NewPacketConnection(ctx, conn, metadata)
|
||||
}
|
||||
|
||||
type URLTestGroup struct {
|
||||
router adapter.Router
|
||||
logger log.Logger
|
||||
outbounds []adapter.Outbound
|
||||
link string
|
||||
interval time.Duration
|
||||
tolerance uint16
|
||||
|
||||
ticker *time.Ticker
|
||||
close chan struct{}
|
||||
}
|
||||
|
||||
func NewURLTestGroup(router adapter.Router, logger log.Logger, outbounds []adapter.Outbound, link string, interval time.Duration, tolerance uint16) *URLTestGroup {
|
||||
if link == "" {
|
||||
//goland:noinspection HttpUrlsUsage
|
||||
link = "http://www.gstatic.com/generate_204"
|
||||
}
|
||||
if interval == 0 {
|
||||
interval = C.DefaultURLTestInterval
|
||||
}
|
||||
if tolerance == 0 {
|
||||
tolerance = 50
|
||||
}
|
||||
return &URLTestGroup{
|
||||
router: router,
|
||||
logger: logger,
|
||||
outbounds: outbounds,
|
||||
link: link,
|
||||
interval: interval,
|
||||
tolerance: tolerance,
|
||||
close: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (g *URLTestGroup) Start() error {
|
||||
g.ticker = time.NewTicker(g.interval)
|
||||
go g.loopCheck()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *URLTestGroup) Close() error {
|
||||
g.ticker.Stop()
|
||||
close(g.close)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *URLTestGroup) Select() adapter.Outbound {
|
||||
var minDelay uint16
|
||||
var minTime time.Time
|
||||
var minOutbound adapter.Outbound
|
||||
for _, detour := range g.outbounds {
|
||||
history := g.router.URLTestHistoryStorage(false).LoadURLTestHistory(RealTag(detour))
|
||||
if history == nil {
|
||||
continue
|
||||
}
|
||||
if minDelay == 0 || minDelay > history.Delay+g.tolerance || minDelay > history.Delay-g.tolerance && minTime.Before(history.Time) {
|
||||
minDelay = history.Delay
|
||||
minTime = history.Time
|
||||
minOutbound = detour
|
||||
}
|
||||
}
|
||||
if minOutbound == nil {
|
||||
minOutbound = g.outbounds[0]
|
||||
}
|
||||
return minOutbound
|
||||
}
|
||||
|
||||
func (g *URLTestGroup) Fallback(used adapter.Outbound) []adapter.Outbound {
|
||||
outbounds := make([]adapter.Outbound, 0, len(g.outbounds)-1)
|
||||
for _, detour := range g.outbounds {
|
||||
if detour != used {
|
||||
outbounds = append(outbounds, detour)
|
||||
}
|
||||
}
|
||||
sort.Slice(outbounds, func(i, j int) bool {
|
||||
oi := outbounds[i]
|
||||
oj := outbounds[j]
|
||||
hi := g.router.URLTestHistoryStorage(false).LoadURLTestHistory(RealTag(oi))
|
||||
if hi == nil {
|
||||
return false
|
||||
}
|
||||
hj := g.router.URLTestHistoryStorage(false).LoadURLTestHistory(RealTag(oj))
|
||||
if hj == nil {
|
||||
return false
|
||||
}
|
||||
return hi.Delay < hj.Delay
|
||||
})
|
||||
return outbounds
|
||||
}
|
||||
|
||||
func (g *URLTestGroup) loopCheck() {
|
||||
go g.checkOutbounds()
|
||||
for {
|
||||
select {
|
||||
case <-g.close:
|
||||
return
|
||||
case <-g.ticker.C:
|
||||
g.checkOutbounds()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (g *URLTestGroup) checkOutbounds() {
|
||||
b, _ := batch.New(context.Background(), batch.WithConcurrencyNum[any](10))
|
||||
checked := make(map[string]bool)
|
||||
for _, detour := range g.outbounds {
|
||||
tag := detour.Tag()
|
||||
realTag := RealTag(detour)
|
||||
if checked[realTag] {
|
||||
continue
|
||||
}
|
||||
history := g.router.URLTestHistoryStorage(false).LoadURLTestHistory(realTag)
|
||||
if history != nil && time.Now().Sub(history.Time) < g.interval {
|
||||
continue
|
||||
}
|
||||
checked[realTag] = true
|
||||
p, loaded := g.router.Outbound(realTag)
|
||||
if !loaded {
|
||||
continue
|
||||
}
|
||||
b.Go(realTag, func() (any, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), C.URLTestTimeout)
|
||||
defer cancel()
|
||||
t, err := urltest.URLTest(ctx, g.link, p)
|
||||
if err != nil {
|
||||
g.logger.Debug("outbound ", tag, " unavailable: ", err)
|
||||
g.router.URLTestHistoryStorage(true).DeleteURLTestHistory(realTag)
|
||||
} else {
|
||||
g.logger.Debug("outbound ", tag, " available: ", t, "ms")
|
||||
g.router.URLTestHistoryStorage(true).StoreURLTestHistory(realTag, &urltest.History{
|
||||
Time: time.Now(),
|
||||
Delay: t,
|
||||
})
|
||||
}
|
||||
return nil, nil
|
||||
})
|
||||
}
|
||||
b.Wait()
|
||||
}
|
||||
|
||||
func RealTag(detour adapter.Outbound) string {
|
||||
if group, isGroup := detour.(adapter.OutboundGroup); isGroup {
|
||||
return group.Now()
|
||||
}
|
||||
return detour.Tag()
|
||||
}
|
|
@ -21,7 +21,6 @@ import (
|
|||
"github.com/sagernet/sing-box/common/geosite"
|
||||
"github.com/sagernet/sing-box/common/process"
|
||||
"github.com/sagernet/sing-box/common/sniff"
|
||||
"github.com/sagernet/sing-box/common/urltest"
|
||||
"github.com/sagernet/sing-box/common/warning"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
|
@ -93,7 +92,6 @@ type Router struct {
|
|||
interfaceMonitor DefaultInterfaceMonitor
|
||||
defaultMark int
|
||||
trafficController adapter.TrafficController
|
||||
urlTestHistoryStorage *urltest.HistoryStorage
|
||||
processSearcher process.Searcher
|
||||
}
|
||||
|
||||
|
@ -699,13 +697,6 @@ func (r *Router) SetTrafficController(controller adapter.TrafficController) {
|
|||
r.trafficController = controller
|
||||
}
|
||||
|
||||
func (r *Router) URLTestHistoryStorage(create bool) *urltest.HistoryStorage {
|
||||
if r.urlTestHistoryStorage == nil && create {
|
||||
r.urlTestHistoryStorage = urltest.NewHistoryStorage()
|
||||
}
|
||||
return r.urlTestHistoryStorage
|
||||
}
|
||||
|
||||
func hasRule(rules []option.Rule, cond func(rule option.DefaultRule) bool) bool {
|
||||
for _, rule := range rules {
|
||||
switch rule.Type {
|
||||
|
|
Loading…
Reference in a new issue