mirror of
https://github.com/SagerNet/sing-box.git
synced 2024-11-10 02:53:12 +00:00
Add urltest outbound
This commit is contained in:
parent
139127f1e4
commit
c4e46c35b5
|
@ -16,3 +16,8 @@ type TrafficController interface {
|
||||||
RoutedConnection(ctx context.Context, conn net.Conn, metadata InboundContext, matchedRule Rule) net.Conn
|
RoutedConnection(ctx context.Context, conn net.Conn, metadata InboundContext, matchedRule Rule) net.Conn
|
||||||
RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext, matchedRule Rule) N.PacketConn
|
RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext, matchedRule Rule) N.PacketConn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type OutboundGroup interface {
|
||||||
|
Now() string
|
||||||
|
All() []string
|
||||||
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/geoip"
|
"github.com/sagernet/sing-box/common/geoip"
|
||||||
|
"github.com/sagernet/sing-box/common/urltest"
|
||||||
"github.com/sagernet/sing-dns"
|
"github.com/sagernet/sing-dns"
|
||||||
"github.com/sagernet/sing/common/control"
|
"github.com/sagernet/sing/common/control"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
@ -38,6 +39,7 @@ type Router interface {
|
||||||
|
|
||||||
Rules() []Rule
|
Rules() []Rule
|
||||||
SetTrafficController(controller TrafficController)
|
SetTrafficController(controller TrafficController)
|
||||||
|
URLTestHistoryStorage(create bool) *urltest.HistoryStorage
|
||||||
}
|
}
|
||||||
|
|
||||||
type Rule interface {
|
type Rule interface {
|
||||||
|
|
107
common/urltest/urltest.go
Normal file
107
common/urltest/urltest.go
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
package urltest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
)
|
||||||
|
|
||||||
|
type History struct {
|
||||||
|
Time time.Time `json:"time"`
|
||||||
|
Delay uint16 `json:"delay"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type HistoryStorage struct {
|
||||||
|
access sync.RWMutex
|
||||||
|
delayHistory map[string]*History
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHistoryStorage() *HistoryStorage {
|
||||||
|
return &HistoryStorage{
|
||||||
|
delayHistory: make(map[string]*History),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *HistoryStorage) LoadURLTestHistory(tag string) *History {
|
||||||
|
if s == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
s.access.RLock()
|
||||||
|
defer s.access.RUnlock()
|
||||||
|
return s.delayHistory[tag]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *HistoryStorage) DeleteURLTestHistory(tag string) {
|
||||||
|
s.access.Lock()
|
||||||
|
defer s.access.Unlock()
|
||||||
|
delete(s.delayHistory, tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *HistoryStorage) StoreURLTestHistory(tag string, history *History) {
|
||||||
|
s.access.Lock()
|
||||||
|
defer s.access.Unlock()
|
||||||
|
s.delayHistory[tag] = history
|
||||||
|
}
|
||||||
|
|
||||||
|
func URLTest(ctx context.Context, link string, detour N.Dialer) (t uint16, err error) {
|
||||||
|
linkURL, err := url.Parse(link)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
hostname := linkURL.Hostname()
|
||||||
|
port := linkURL.Port()
|
||||||
|
if port == "" {
|
||||||
|
switch linkURL.Scheme {
|
||||||
|
case "http":
|
||||||
|
port = "80"
|
||||||
|
case "https":
|
||||||
|
port = "443"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
start := time.Now()
|
||||||
|
instance, err := detour.DialContext(ctx, "tcp", M.ParseSocksaddrHostPortStr(hostname, port))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer instance.Close()
|
||||||
|
|
||||||
|
req, err := http.NewRequest(http.MethodHead, link, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
req = req.WithContext(ctx)
|
||||||
|
|
||||||
|
transport := &http.Transport{
|
||||||
|
Dial: func(string, string) (net.Conn, error) {
|
||||||
|
return instance, nil
|
||||||
|
},
|
||||||
|
// from http.DefaultTransport
|
||||||
|
MaxIdleConns: 100,
|
||||||
|
IdleConnTimeout: 90 * time.Second,
|
||||||
|
TLSHandshakeTimeout: 10 * time.Second,
|
||||||
|
ExpectContinueTimeout: 1 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
client := http.Client{
|
||||||
|
Transport: transport,
|
||||||
|
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||||
|
return http.ErrUseLastResponse
|
||||||
|
},
|
||||||
|
}
|
||||||
|
defer client.CloseIdleConnections()
|
||||||
|
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp.Body.Close()
|
||||||
|
t = uint16(time.Since(start) / time.Millisecond)
|
||||||
|
return
|
||||||
|
}
|
|
@ -16,4 +16,5 @@ const (
|
||||||
|
|
||||||
const (
|
const (
|
||||||
TypeSelector = "selector"
|
TypeSelector = "selector"
|
||||||
|
TypeURLTest = "urltest"
|
||||||
)
|
)
|
||||||
|
|
|
@ -3,6 +3,8 @@ package constant
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
DefaultTCPTimeout = 5 * time.Second
|
DefaultTCPTimeout = 5 * time.Second
|
||||||
ReadPayloadTimeout = 300 * time.Millisecond
|
ReadPayloadTimeout = 300 * time.Millisecond
|
||||||
|
URLTestTimeout = DefaultTCPTimeout
|
||||||
|
DefaultURLTestInterval = 1 * time.Minute
|
||||||
)
|
)
|
||||||
|
|
|
@ -3,23 +3,21 @@ package clashapi
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/badjson"
|
"github.com/sagernet/sing-box/common/badjson"
|
||||||
|
"github.com/sagernet/sing-box/common/urltest"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/outbound"
|
"github.com/sagernet/sing-box/outbound"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
F "github.com/sagernet/sing/common/format"
|
F "github.com/sagernet/sing/common/format"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/go-chi/render"
|
"github.com/go-chi/render"
|
||||||
"sort"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func proxyRouter(server *Server, router adapter.Router) http.Handler {
|
func proxyRouter(server *Server, router adapter.Router) http.Handler {
|
||||||
|
@ -62,7 +60,7 @@ func findProxyByName(router adapter.Router) func(next http.Handler) http.Handler
|
||||||
func proxyInfo(server *Server, detour adapter.Outbound) *badjson.JSONObject {
|
func proxyInfo(server *Server, detour adapter.Outbound) *badjson.JSONObject {
|
||||||
var info badjson.JSONObject
|
var info badjson.JSONObject
|
||||||
var clashType string
|
var clashType string
|
||||||
var isSelector bool
|
var isGroup bool
|
||||||
switch detour.Type() {
|
switch detour.Type() {
|
||||||
case C.TypeDirect:
|
case C.TypeDirect:
|
||||||
clashType = "Direct"
|
clashType = "Direct"
|
||||||
|
@ -78,28 +76,26 @@ func proxyInfo(server *Server, detour adapter.Outbound) *badjson.JSONObject {
|
||||||
clashType = "Vmess"
|
clashType = "Vmess"
|
||||||
case C.TypeSelector:
|
case C.TypeSelector:
|
||||||
clashType = "Selector"
|
clashType = "Selector"
|
||||||
isSelector = true
|
isGroup = true
|
||||||
|
case C.TypeURLTest:
|
||||||
|
clashType = "URLTest"
|
||||||
|
isGroup = true
|
||||||
default:
|
default:
|
||||||
clashType = "Unknown"
|
clashType = "Unknown"
|
||||||
}
|
}
|
||||||
info.Put("type", clashType)
|
info.Put("type", clashType)
|
||||||
info.Put("name", detour.Tag())
|
info.Put("name", detour.Tag())
|
||||||
info.Put("udp", common.Contains(detour.Network(), C.NetworkUDP))
|
info.Put("udp", common.Contains(detour.Network(), C.NetworkUDP))
|
||||||
|
delayHistory := server.router.URLTestHistoryStorage(false).LoadURLTestHistory(outbound.RealTag(detour))
|
||||||
var delayHistory *DelayHistory
|
if delayHistory != nil {
|
||||||
var loaded bool
|
info.Put("history", []*urltest.History{delayHistory})
|
||||||
if isSelector {
|
} else {
|
||||||
selector := detour.(*outbound.Selector)
|
info.Put("history", []*urltest.History{})
|
||||||
|
}
|
||||||
|
if isGroup {
|
||||||
|
selector := detour.(adapter.OutboundGroup)
|
||||||
info.Put("now", selector.Now())
|
info.Put("now", selector.Now())
|
||||||
info.Put("all", selector.All())
|
info.Put("all", selector.All())
|
||||||
delayHistory, loaded = server.delayHistory[selector.Now()]
|
|
||||||
} else {
|
|
||||||
delayHistory, loaded = server.delayHistory[detour.Tag()]
|
|
||||||
}
|
|
||||||
if loaded {
|
|
||||||
info.Put("history", []*DelayHistory{delayHistory})
|
|
||||||
} else {
|
|
||||||
info.Put("history", []*DelayHistory{})
|
|
||||||
}
|
}
|
||||||
return &info
|
return &info
|
||||||
}
|
}
|
||||||
|
@ -135,7 +131,7 @@ func getProxies(server *Server, router adapter.Router) func(w http.ResponseWrite
|
||||||
"type": "Fallback",
|
"type": "Fallback",
|
||||||
"name": "GLOBAL",
|
"name": "GLOBAL",
|
||||||
"udp": true,
|
"udp": true,
|
||||||
"history": []*DelayHistory{},
|
"history": []*urltest.History{},
|
||||||
"all": allProxies,
|
"all": allProxies,
|
||||||
"now": defaultTag,
|
"now": defaultTag,
|
||||||
})
|
})
|
||||||
|
@ -218,7 +214,19 @@ func getProxyDelay(server *Server) func(w http.ResponseWriter, r *http.Request)
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(timeout))
|
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(timeout))
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
delay, err := URLTest(ctx, url, proxy)
|
delay, err := urltest.URLTest(ctx, url, proxy)
|
||||||
|
defer func() {
|
||||||
|
realTag := outbound.RealTag(proxy)
|
||||||
|
if err != nil {
|
||||||
|
server.router.URLTestHistoryStorage(true).DeleteURLTestHistory(realTag)
|
||||||
|
} else {
|
||||||
|
server.router.URLTestHistoryStorage(true).StoreURLTestHistory(realTag, &urltest.History{
|
||||||
|
Time: time.Now(),
|
||||||
|
Delay: delay,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
if ctx.Err() != nil {
|
if ctx.Err() != nil {
|
||||||
render.Status(r, http.StatusGatewayTimeout)
|
render.Status(r, http.StatusGatewayTimeout)
|
||||||
render.JSON(w, r, ErrRequestTimeout)
|
render.JSON(w, r, ErrRequestTimeout)
|
||||||
|
@ -231,70 +239,8 @@ func getProxyDelay(server *Server) func(w http.ResponseWriter, r *http.Request)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
server.delayHistory[proxy.Tag()] = &DelayHistory{
|
|
||||||
Time: time.Now(),
|
|
||||||
Delay: delay,
|
|
||||||
}
|
|
||||||
|
|
||||||
render.JSON(w, r, render.M{
|
render.JSON(w, r, render.M{
|
||||||
"delay": delay,
|
"delay": delay,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func URLTest(ctx context.Context, link string, detour adapter.Outbound) (t uint16, err error) {
|
|
||||||
linkURL, err := url.Parse(link)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
hostname := linkURL.Hostname()
|
|
||||||
port := linkURL.Port()
|
|
||||||
if port == "" {
|
|
||||||
switch linkURL.Scheme {
|
|
||||||
case "http":
|
|
||||||
port = "80"
|
|
||||||
case "https":
|
|
||||||
port = "443"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
start := time.Now()
|
|
||||||
instance, err := detour.DialContext(ctx, "tcp", M.ParseSocksaddrHostPortStr(hostname, port))
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer instance.Close()
|
|
||||||
|
|
||||||
req, err := http.NewRequest(http.MethodHead, link, nil)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
req = req.WithContext(ctx)
|
|
||||||
|
|
||||||
transport := &http.Transport{
|
|
||||||
Dial: func(string, string) (net.Conn, error) {
|
|
||||||
return instance, nil
|
|
||||||
},
|
|
||||||
// from http.DefaultTransport
|
|
||||||
MaxIdleConns: 100,
|
|
||||||
IdleConnTimeout: 90 * time.Second,
|
|
||||||
TLSHandshakeTimeout: 10 * time.Second,
|
|
||||||
ExpectContinueTimeout: 1 * time.Second,
|
|
||||||
}
|
|
||||||
|
|
||||||
client := http.Client{
|
|
||||||
Transport: transport,
|
|
||||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
|
||||||
return http.ErrUseLastResponse
|
|
||||||
},
|
|
||||||
}
|
|
||||||
defer client.CloseIdleConnections()
|
|
||||||
|
|
||||||
resp, err := client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
resp.Body.Close()
|
|
||||||
t = uint16(time.Since(start) / time.Millisecond)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -23,34 +24,28 @@ import (
|
||||||
"github.com/go-chi/render"
|
"github.com/go-chi/render"
|
||||||
"github.com/goccy/go-json"
|
"github.com/goccy/go-json"
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
"os"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ adapter.ClashServer = (*Server)(nil)
|
var _ adapter.ClashServer = (*Server)(nil)
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
|
router adapter.Router
|
||||||
logger log.Logger
|
logger log.Logger
|
||||||
httpServer *http.Server
|
httpServer *http.Server
|
||||||
trafficManager *trafficontrol.Manager
|
trafficManager *trafficontrol.Manager
|
||||||
delayHistory map[string]*DelayHistory
|
|
||||||
}
|
|
||||||
|
|
||||||
type DelayHistory struct {
|
|
||||||
Time time.Time `json:"time"`
|
|
||||||
Delay uint16 `json:"delay"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServer(router adapter.Router, logFactory log.ObservableFactory, options option.ClashAPIOptions) *Server {
|
func NewServer(router adapter.Router, logFactory log.ObservableFactory, options option.ClashAPIOptions) *Server {
|
||||||
trafficManager := trafficontrol.NewManager()
|
trafficManager := trafficontrol.NewManager()
|
||||||
chiRouter := chi.NewRouter()
|
chiRouter := chi.NewRouter()
|
||||||
server := &Server{
|
server := &Server{
|
||||||
|
router,
|
||||||
logFactory.NewLogger("clash-api"),
|
logFactory.NewLogger("clash-api"),
|
||||||
&http.Server{
|
&http.Server{
|
||||||
Addr: options.ExternalController,
|
Addr: options.ExternalController,
|
||||||
Handler: chiRouter,
|
Handler: chiRouter,
|
||||||
},
|
},
|
||||||
trafficManager,
|
trafficManager,
|
||||||
make(map[string]*DelayHistory),
|
|
||||||
}
|
}
|
||||||
cors := cors.New(cors.Options{
|
cors := cors.New(cors.Options{
|
||||||
AllowedOrigins: []string{"*"},
|
AllowedOrigins: []string{"*"},
|
||||||
|
@ -107,11 +102,11 @@ func (s *Server) Close() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) RoutedConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, matchedRule adapter.Rule) net.Conn {
|
func (s *Server) RoutedConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, matchedRule adapter.Rule) net.Conn {
|
||||||
return trafficontrol.NewTCPTracker(conn, s.trafficManager, castMetadata(metadata), matchedRule)
|
return trafficontrol.NewTCPTracker(conn, s.trafficManager, castMetadata(metadata), s.router, matchedRule)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, matchedRule adapter.Rule) N.PacketConn {
|
func (s *Server) RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, matchedRule adapter.Rule) N.PacketConn {
|
||||||
return trafficontrol.NewUDPTracker(conn, s.trafficManager, castMetadata(metadata), matchedRule)
|
return trafficontrol.NewUDPTracker(conn, s.trafficManager, castMetadata(metadata), s.router, matchedRule)
|
||||||
}
|
}
|
||||||
|
|
||||||
func castMetadata(metadata adapter.InboundContext) trafficontrol.Metadata {
|
func castMetadata(metadata adapter.InboundContext) trafficontrol.Metadata {
|
||||||
|
|
|
@ -6,6 +6,8 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/buf"
|
"github.com/sagernet/sing/common/buf"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
@ -73,9 +75,29 @@ func (tt *tcpTracker) Close() error {
|
||||||
return tt.Conn.Close()
|
return tt.Conn.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTCPTracker(conn net.Conn, manager *Manager, metadata Metadata, rule adapter.Rule) *tcpTracker {
|
func NewTCPTracker(conn net.Conn, manager *Manager, metadata Metadata, router adapter.Router, rule adapter.Rule) *tcpTracker {
|
||||||
uuid, _ := uuid.NewV4()
|
uuid, _ := uuid.NewV4()
|
||||||
|
|
||||||
|
var chain []string
|
||||||
|
var next string
|
||||||
|
if rule == nil {
|
||||||
|
next = router.DefaultOutbound(C.NetworkTCP).Tag()
|
||||||
|
} else {
|
||||||
|
next = rule.Outbound()
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
chain = append(chain, next)
|
||||||
|
detour, loaded := router.Outbound(next)
|
||||||
|
if !loaded {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
group, isGroup := detour.(adapter.OutboundGroup)
|
||||||
|
if !isGroup {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
next = group.Now()
|
||||||
|
}
|
||||||
|
|
||||||
t := &tcpTracker{
|
t := &tcpTracker{
|
||||||
Conn: conn,
|
Conn: conn,
|
||||||
manager: manager,
|
manager: manager,
|
||||||
|
@ -83,7 +105,7 @@ func NewTCPTracker(conn net.Conn, manager *Manager, metadata Metadata, rule adap
|
||||||
UUID: uuid,
|
UUID: uuid,
|
||||||
Start: time.Now(),
|
Start: time.Now(),
|
||||||
Metadata: metadata,
|
Metadata: metadata,
|
||||||
Chain: []string{},
|
Chain: common.Reverse(chain),
|
||||||
Rule: "",
|
Rule: "",
|
||||||
UploadTotal: atomic.NewInt64(0),
|
UploadTotal: atomic.NewInt64(0),
|
||||||
DownloadTotal: atomic.NewInt64(0),
|
DownloadTotal: atomic.NewInt64(0),
|
||||||
|
@ -91,8 +113,9 @@ func NewTCPTracker(conn net.Conn, manager *Manager, metadata Metadata, rule adap
|
||||||
}
|
}
|
||||||
|
|
||||||
if rule != nil {
|
if rule != nil {
|
||||||
t.trackerInfo.Rule = rule.Outbound()
|
t.trackerInfo.Rule = rule.String() + " => " + rule.Outbound()
|
||||||
t.trackerInfo.RulePayload = rule.String()
|
} else {
|
||||||
|
t.trackerInfo.Rule = "final"
|
||||||
}
|
}
|
||||||
|
|
||||||
manager.Join(t)
|
manager.Join(t)
|
||||||
|
@ -135,9 +158,29 @@ func (ut *udpTracker) Close() error {
|
||||||
return ut.PacketConn.Close()
|
return ut.PacketConn.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUDPTracker(conn N.PacketConn, manager *Manager, metadata Metadata, rule adapter.Rule) *udpTracker {
|
func NewUDPTracker(conn N.PacketConn, manager *Manager, metadata Metadata, router adapter.Router, rule adapter.Rule) *udpTracker {
|
||||||
uuid, _ := uuid.NewV4()
|
uuid, _ := uuid.NewV4()
|
||||||
|
|
||||||
|
var chain []string
|
||||||
|
var next string
|
||||||
|
if rule == nil {
|
||||||
|
next = router.DefaultOutbound(C.NetworkUDP).Tag()
|
||||||
|
} else {
|
||||||
|
next = rule.Outbound()
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
chain = append(chain, next)
|
||||||
|
detour, loaded := router.Outbound(next)
|
||||||
|
if !loaded {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
group, isGroup := detour.(adapter.OutboundGroup)
|
||||||
|
if !isGroup {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
next = group.Now()
|
||||||
|
}
|
||||||
|
|
||||||
ut := &udpTracker{
|
ut := &udpTracker{
|
||||||
PacketConn: conn,
|
PacketConn: conn,
|
||||||
manager: manager,
|
manager: manager,
|
||||||
|
@ -145,7 +188,7 @@ func NewUDPTracker(conn N.PacketConn, manager *Manager, metadata Metadata, rule
|
||||||
UUID: uuid,
|
UUID: uuid,
|
||||||
Start: time.Now(),
|
Start: time.Now(),
|
||||||
Metadata: metadata,
|
Metadata: metadata,
|
||||||
Chain: []string{},
|
Chain: common.Reverse(chain),
|
||||||
Rule: "",
|
Rule: "",
|
||||||
UploadTotal: atomic.NewInt64(0),
|
UploadTotal: atomic.NewInt64(0),
|
||||||
DownloadTotal: atomic.NewInt64(0),
|
DownloadTotal: atomic.NewInt64(0),
|
||||||
|
@ -153,8 +196,9 @@ func NewUDPTracker(conn N.PacketConn, manager *Manager, metadata Metadata, rule
|
||||||
}
|
}
|
||||||
|
|
||||||
if rule != nil {
|
if rule != nil {
|
||||||
ut.trackerInfo.Rule = rule.Outbound()
|
ut.trackerInfo.Rule = rule.String() + " => " + rule.Outbound()
|
||||||
ut.trackerInfo.RulePayload = rule.String()
|
} else {
|
||||||
|
ut.trackerInfo.Rule = "final"
|
||||||
}
|
}
|
||||||
|
|
||||||
manager.Join(ut)
|
manager.Join(ut)
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -11,7 +11,7 @@ require (
|
||||||
github.com/gorilla/websocket v1.5.0
|
github.com/gorilla/websocket v1.5.0
|
||||||
github.com/logrusorgru/aurora v2.0.3+incompatible
|
github.com/logrusorgru/aurora v2.0.3+incompatible
|
||||||
github.com/oschwald/maxminddb-golang v1.9.0
|
github.com/oschwald/maxminddb-golang v1.9.0
|
||||||
github.com/sagernet/sing v0.0.0-20220720140412-fd4ec74d5eca
|
github.com/sagernet/sing v0.0.0-20220722054850-4ce9815aca2b
|
||||||
github.com/sagernet/sing-dns v0.0.0-20220720045209-c44590ebeb0f
|
github.com/sagernet/sing-dns v0.0.0-20220720045209-c44590ebeb0f
|
||||||
github.com/sagernet/sing-shadowsocks v0.0.0-20220717063942-45a2ad9cd41f
|
github.com/sagernet/sing-shadowsocks v0.0.0-20220717063942-45a2ad9cd41f
|
||||||
github.com/sagernet/sing-tun v0.0.0-20220720051454-d35c334b46c9
|
github.com/sagernet/sing-tun v0.0.0-20220720051454-d35c334b46c9
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -35,8 +35,8 @@ github.com/oschwald/maxminddb-golang v1.9.0/go.mod h1:TK+s/Z2oZq0rSl4PSeAEoP0bgm
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/sagernet/sing v0.0.0-20220720140412-fd4ec74d5eca h1:xz/41NRDcjMm3w5UeojeU79Tu0aRiy/apQN+JadrWZ8=
|
github.com/sagernet/sing v0.0.0-20220722054850-4ce9815aca2b h1:V5gIp7HQOEEIaxV1TKhjhTu8RyAyXeYx8qeaHVrjFW4=
|
||||||
github.com/sagernet/sing v0.0.0-20220720140412-fd4ec74d5eca/go.mod h1:GbtQfZSpmtD3cXeD1qX2LCMwY8dH+bnnInDTqd92IsM=
|
github.com/sagernet/sing v0.0.0-20220722054850-4ce9815aca2b/go.mod h1:GbtQfZSpmtD3cXeD1qX2LCMwY8dH+bnnInDTqd92IsM=
|
||||||
github.com/sagernet/sing-dns v0.0.0-20220720045209-c44590ebeb0f h1:PCrkSLS+fQtBimPi/2WzjJqeTy0zJtBDaMLykyTAiwQ=
|
github.com/sagernet/sing-dns v0.0.0-20220720045209-c44590ebeb0f h1:PCrkSLS+fQtBimPi/2WzjJqeTy0zJtBDaMLykyTAiwQ=
|
||||||
github.com/sagernet/sing-dns v0.0.0-20220720045209-c44590ebeb0f/go.mod h1:y2fpvoxukw3G7eApIZwkcpcG/NE4AB8pCQI0Qd8rMqk=
|
github.com/sagernet/sing-dns v0.0.0-20220720045209-c44590ebeb0f/go.mod h1:y2fpvoxukw3G7eApIZwkcpcG/NE4AB8pCQI0Qd8rMqk=
|
||||||
github.com/sagernet/sing-shadowsocks v0.0.0-20220717063942-45a2ad9cd41f h1:F6yiuKbBoXgWiuoP7R0YA14pDEl3emxA1mL7M16Q7gc=
|
github.com/sagernet/sing-shadowsocks v0.0.0-20220717063942-45a2ad9cd41f h1:F6yiuKbBoXgWiuoP7R0YA14pDEl3emxA1mL7M16Q7gc=
|
||||||
|
|
|
@ -18,6 +18,7 @@ type _Outbound struct {
|
||||||
ShadowsocksOptions ShadowsocksOutboundOptions `json:"-"`
|
ShadowsocksOptions ShadowsocksOutboundOptions `json:"-"`
|
||||||
VMessOptions VMessOutboundOptions `json:"-"`
|
VMessOptions VMessOutboundOptions `json:"-"`
|
||||||
SelectorOptions SelectorOutboundOptions `json:"-"`
|
SelectorOptions SelectorOutboundOptions `json:"-"`
|
||||||
|
URLTestOptions URLTestOutboundOptions `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Outbound _Outbound
|
type Outbound _Outbound
|
||||||
|
@ -30,7 +31,8 @@ func (h Outbound) Equals(other Outbound) bool {
|
||||||
h.HTTPOptions == other.HTTPOptions &&
|
h.HTTPOptions == other.HTTPOptions &&
|
||||||
h.ShadowsocksOptions == other.ShadowsocksOptions &&
|
h.ShadowsocksOptions == other.ShadowsocksOptions &&
|
||||||
h.VMessOptions == other.VMessOptions &&
|
h.VMessOptions == other.VMessOptions &&
|
||||||
common.Equals(h.SelectorOptions, other.SelectorOptions)
|
common.Equals(h.SelectorOptions, other.SelectorOptions) &&
|
||||||
|
common.Equals(h.URLTestOptions, other.URLTestOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h Outbound) MarshalJSON() ([]byte, error) {
|
func (h Outbound) MarshalJSON() ([]byte, error) {
|
||||||
|
@ -50,6 +52,8 @@ func (h Outbound) MarshalJSON() ([]byte, error) {
|
||||||
v = h.VMessOptions
|
v = h.VMessOptions
|
||||||
case C.TypeSelector:
|
case C.TypeSelector:
|
||||||
v = h.SelectorOptions
|
v = h.SelectorOptions
|
||||||
|
case C.TypeURLTest:
|
||||||
|
v = h.URLTestOptions
|
||||||
default:
|
default:
|
||||||
return nil, E.New("unknown outbound type: ", h.Type)
|
return nil, E.New("unknown outbound type: ", h.Type)
|
||||||
}
|
}
|
||||||
|
@ -77,6 +81,8 @@ func (h *Outbound) UnmarshalJSON(bytes []byte) error {
|
||||||
v = &h.VMessOptions
|
v = &h.VMessOptions
|
||||||
case C.TypeSelector:
|
case C.TypeSelector:
|
||||||
v = &h.SelectorOptions
|
v = &h.SelectorOptions
|
||||||
|
case C.TypeURLTest:
|
||||||
|
v = &h.URLTestOptions
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -171,3 +177,17 @@ func (o SelectorOutboundOptions) Equals(other SelectorOutboundOptions) bool {
|
||||||
return common.ComparableSliceEquals(o.Outbounds, other.Outbounds) &&
|
return common.ComparableSliceEquals(o.Outbounds, other.Outbounds) &&
|
||||||
o.Default == other.Default
|
o.Default == other.Default
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type URLTestOutboundOptions struct {
|
||||||
|
Outbounds []string `json:"outbounds"`
|
||||||
|
URL string `json:"url,omitempty"`
|
||||||
|
Interval Duration `json:"interval,omitempty"`
|
||||||
|
Tolerance uint16 `json:"tolerance,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o URLTestOutboundOptions) Equals(other URLTestOutboundOptions) bool {
|
||||||
|
return common.ComparableSliceEquals(o.Outbounds, other.Outbounds) &&
|
||||||
|
o.URL == other.URL &&
|
||||||
|
o.Interval == other.Interval &&
|
||||||
|
o.Tolerance == other.Tolerance
|
||||||
|
}
|
||||||
|
|
|
@ -28,6 +28,8 @@ func New(router adapter.Router, logger log.ContextLogger, options option.Outboun
|
||||||
return NewVMess(router, logger, options.Tag, options.VMessOptions)
|
return NewVMess(router, logger, options.Tag, options.VMessOptions)
|
||||||
case C.TypeSelector:
|
case C.TypeSelector:
|
||||||
return NewSelector(router, logger, options.Tag, options.SelectorOptions)
|
return NewSelector(router, logger, options.Tag, options.SelectorOptions)
|
||||||
|
case C.TypeURLTest:
|
||||||
|
return NewURLTest(router, logger, options.Tag, options.URLTestOptions)
|
||||||
default:
|
default:
|
||||||
return nil, E.New("unknown outbound type: ", options.Type)
|
return nil, E.New("unknown outbound type: ", options.Type)
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,10 @@ import (
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ adapter.Outbound = (*Selector)(nil)
|
var (
|
||||||
|
_ adapter.Outbound = (*Selector)(nil)
|
||||||
|
_ adapter.OutboundGroup = (*Selector)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
type Selector struct {
|
type Selector struct {
|
||||||
myOutboundAdapter
|
myOutboundAdapter
|
||||||
|
|
225
outbound/urltest.go
Normal file
225
outbound/urltest.go
Normal file
|
@ -0,0 +1,225 @@
|
||||||
|
package outbound
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"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
|
||||||
|
router adapter.Router
|
||||||
|
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,
|
||||||
|
logger: logger,
|
||||||
|
tag: tag,
|
||||||
|
},
|
||||||
|
router: router,
|
||||||
|
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) 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 {
|
||||||
|
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 ", detour.Tag(), " unavailable: ", err)
|
||||||
|
g.router.URLTestHistoryStorage(true).DeleteURLTestHistory(realTag)
|
||||||
|
} else {
|
||||||
|
g.logger.Debug("outbound ", detour.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()
|
||||||
|
}
|
|
@ -18,6 +18,7 @@ import (
|
||||||
"github.com/sagernet/sing-box/common/geoip"
|
"github.com/sagernet/sing-box/common/geoip"
|
||||||
"github.com/sagernet/sing-box/common/geosite"
|
"github.com/sagernet/sing-box/common/geosite"
|
||||||
"github.com/sagernet/sing-box/common/sniff"
|
"github.com/sagernet/sing-box/common/sniff"
|
||||||
|
"github.com/sagernet/sing-box/common/urltest"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
|
@ -71,7 +72,8 @@ type Router struct {
|
||||||
defaultInterface string
|
defaultInterface string
|
||||||
interfaceMonitor DefaultInterfaceMonitor
|
interfaceMonitor DefaultInterfaceMonitor
|
||||||
|
|
||||||
trafficController adapter.TrafficController
|
trafficController adapter.TrafficController
|
||||||
|
urlTestHistoryStorage *urltest.HistoryStorage
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRouter(ctx context.Context, logger log.ContextLogger, dnsLogger log.ContextLogger, options option.RouteOptions, dnsOptions option.DNSOptions) (*Router, error) {
|
func NewRouter(ctx context.Context, logger log.ContextLogger, dnsLogger log.ContextLogger, options option.RouteOptions, dnsOptions option.DNSOptions) (*Router, error) {
|
||||||
|
@ -597,6 +599,13 @@ func (r *Router) SetTrafficController(controller adapter.TrafficController) {
|
||||||
r.trafficController = controller
|
r.trafficController = controller
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Router) URLTestHistoryStorage(create bool) *urltest.HistoryStorage {
|
||||||
|
if r.urlTestHistoryStorage == nil && create {
|
||||||
|
r.urlTestHistoryStorage = urltest.NewHistoryStorage()
|
||||||
|
}
|
||||||
|
return r.urlTestHistoryStorage
|
||||||
|
}
|
||||||
|
|
||||||
func hasGeoRule(rules []option.Rule, cond func(rule option.DefaultRule) bool) bool {
|
func hasGeoRule(rules []option.Rule, cond func(rule option.DefaultRule) bool) bool {
|
||||||
for _, rule := range rules {
|
for _, rule := range rules {
|
||||||
switch rule.Type {
|
switch rule.Type {
|
||||||
|
|
|
@ -10,7 +10,7 @@ require (
|
||||||
github.com/docker/docker v20.10.17+incompatible
|
github.com/docker/docker v20.10.17+incompatible
|
||||||
github.com/docker/go-connections v0.4.0
|
github.com/docker/go-connections v0.4.0
|
||||||
github.com/gofrs/uuid v4.2.0+incompatible
|
github.com/gofrs/uuid v4.2.0+incompatible
|
||||||
github.com/sagernet/sing v0.0.0-20220720140412-fd4ec74d5eca
|
github.com/sagernet/sing v0.0.0-20220722054850-4ce9815aca2b
|
||||||
github.com/spyzhov/ajson v0.7.1
|
github.com/spyzhov/ajson v0.7.1
|
||||||
github.com/stretchr/testify v1.8.0
|
github.com/stretchr/testify v1.8.0
|
||||||
golang.org/x/net v0.0.0-20220708220712-1185a9018129
|
golang.org/x/net v0.0.0-20220708220712-1185a9018129
|
||||||
|
|
|
@ -62,8 +62,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/sagernet/sing v0.0.0-20220720140412-fd4ec74d5eca h1:xz/41NRDcjMm3w5UeojeU79Tu0aRiy/apQN+JadrWZ8=
|
github.com/sagernet/sing v0.0.0-20220722054850-4ce9815aca2b h1:V5gIp7HQOEEIaxV1TKhjhTu8RyAyXeYx8qeaHVrjFW4=
|
||||||
github.com/sagernet/sing v0.0.0-20220720140412-fd4ec74d5eca/go.mod h1:GbtQfZSpmtD3cXeD1qX2LCMwY8dH+bnnInDTqd92IsM=
|
github.com/sagernet/sing v0.0.0-20220722054850-4ce9815aca2b/go.mod h1:GbtQfZSpmtD3cXeD1qX2LCMwY8dH+bnnInDTqd92IsM=
|
||||||
github.com/sagernet/sing-dns v0.0.0-20220720045209-c44590ebeb0f h1:PCrkSLS+fQtBimPi/2WzjJqeTy0zJtBDaMLykyTAiwQ=
|
github.com/sagernet/sing-dns v0.0.0-20220720045209-c44590ebeb0f h1:PCrkSLS+fQtBimPi/2WzjJqeTy0zJtBDaMLykyTAiwQ=
|
||||||
github.com/sagernet/sing-dns v0.0.0-20220720045209-c44590ebeb0f/go.mod h1:y2fpvoxukw3G7eApIZwkcpcG/NE4AB8pCQI0Qd8rMqk=
|
github.com/sagernet/sing-dns v0.0.0-20220720045209-c44590ebeb0f/go.mod h1:y2fpvoxukw3G7eApIZwkcpcG/NE4AB8pCQI0Qd8rMqk=
|
||||||
github.com/sagernet/sing-shadowsocks v0.0.0-20220717063942-45a2ad9cd41f h1:F6yiuKbBoXgWiuoP7R0YA14pDEl3emxA1mL7M16Q7gc=
|
github.com/sagernet/sing-shadowsocks v0.0.0-20220717063942-45a2ad9cd41f h1:F6yiuKbBoXgWiuoP7R0YA14pDEl3emxA1mL7M16Q7gc=
|
||||||
|
|
Loading…
Reference in a new issue