diff --git a/common/canceler/instance.go b/common/canceler/instance.go new file mode 100644 index 00000000..df9c1680 --- /dev/null +++ b/common/canceler/instance.go @@ -0,0 +1,48 @@ +package canceler + +import ( + "context" + "time" +) + +type Instance struct { + ctx context.Context + cancelFunc context.CancelFunc + timer *time.Timer + timeout time.Duration +} + +func New(ctx context.Context, cancelFunc context.CancelFunc, timeout time.Duration) *Instance { + instance := &Instance{ + ctx, + cancelFunc, + time.NewTimer(timeout), + timeout, + } + go instance.wait() + return instance +} + +func (i *Instance) Update() bool { + if !i.timer.Stop() { + return false + } + if !i.timer.Reset(i.timeout) { + return false + } + return true +} + +func (i *Instance) wait() { + select { + case <-i.timer.C: + case <-i.ctx.Done(): + } + i.Close() +} + +func (i *Instance) Close() error { + i.timer.Stop() + i.cancelFunc() + return nil +} diff --git a/common/canceler/packet.go b/common/canceler/packet.go new file mode 100644 index 00000000..1590e7ed --- /dev/null +++ b/common/canceler/packet.go @@ -0,0 +1,41 @@ +package canceler + +import ( + "context" + "time" + + "github.com/sagernet/sing/common/buf" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" +) + +type PacketConn struct { + N.PacketConn + instance *Instance +} + +func NewPacketConn(ctx context.Context, conn N.PacketConn, timeout time.Duration) (context.Context, N.PacketConn) { + ctx, cancel := context.WithCancel(ctx) + instance := New(ctx, cancel, timeout) + return ctx, &PacketConn{conn, instance} +} + +func (c *PacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) { + destination, err = c.PacketConn.ReadPacket(buffer) + if err == nil { + c.instance.Update() + } + return +} + +func (c *PacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error { + err := c.PacketConn.WritePacket(buffer, destination) + if err == nil { + c.instance.Update() + } + return err +} + +func (c *PacketConn) Upstream() any { + return c.PacketConn +} diff --git a/common/dialer/default.go b/common/dialer/default.go index 10ed752b..a068a105 100644 --- a/common/dialer/default.go +++ b/common/dialer/default.go @@ -103,7 +103,7 @@ func NewDefault(router adapter.Router, options option.DialerOptions) *DefaultDia if options.ConnectTimeout != 0 { dialer.Timeout = time.Duration(options.ConnectTimeout) } else { - dialer.Timeout = C.DefaultTCPTimeout + dialer.Timeout = C.TCPTimeout } if options.TCPFastOpen { warnTFOOnUnsupportedPlatform.Check() @@ -118,7 +118,7 @@ func (d *DefaultDialer) DialContext(ctx context.Context, network string, address } if tcpConn, isTCP := common.Cast[*net.TCPConn](conn); isTCP { tcpConn.SetKeepAlive(true) - tcpConn.SetKeepAlivePeriod(C.DefaultTCPKeepAlivePeriod) + tcpConn.SetKeepAlivePeriod(C.TCPKeepAlivePeriod) } return conn, nil } diff --git a/common/dialer/tls.go b/common/dialer/tls.go index a99817dd..fff10871 100644 --- a/common/dialer/tls.go +++ b/common/dialer/tls.go @@ -129,7 +129,7 @@ func (d *TLSDialer) DialContext(ctx context.Context, network string, destination return nil, err } tlsConn := tls.Client(conn, d.config) - ctx, cancel := context.WithTimeout(ctx, C.DefaultTCPTimeout) + ctx, cancel := context.WithTimeout(ctx, C.TCPTimeout) defer cancel() err = tlsConn.HandshakeContext(ctx) return tlsConn, err diff --git a/constant/timeout.go b/constant/timeout.go index 57cced2c..7b9e55af 100644 --- a/constant/timeout.go +++ b/constant/timeout.go @@ -3,9 +3,12 @@ package constant import "time" const ( - DefaultTCPTimeout = 5 * time.Second - DefaultTCPKeepAlivePeriod = 20 * time.Second - ReadPayloadTimeout = 300 * time.Millisecond - URLTestTimeout = DefaultTCPTimeout - DefaultURLTestInterval = 1 * time.Minute + TCPTimeout = 5 * time.Second + TCPKeepAlivePeriod = 20 * time.Second + ReadPayloadTimeout = 300 * time.Millisecond + URLTestTimeout = TCPTimeout + DefaultURLTestInterval = 1 * time.Minute + DNSTimeout = 10 * time.Second + QUICTimeout = 30 * time.Second + STUNTimeout = 15 * time.Second ) diff --git a/go.mod b/go.mod index 34c12c95..09866a41 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/gorilla/websocket v1.5.0 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/oschwald/maxminddb-golang v1.9.0 - github.com/sagernet/sing v0.0.0-20220725031620-096223b346f3 + github.com/sagernet/sing v0.0.0-20220725141316-c15de13f4f68 github.com/sagernet/sing-dns v0.0.0-20220724053927-eb8d0d542175 github.com/sagernet/sing-shadowsocks v0.0.0-20220717063942-45a2ad9cd41f github.com/sagernet/sing-tun v0.0.0-20220720051454-d35c334b46c9 diff --git a/go.sum b/go.sum index e22241cd..7d395e66 100644 --- a/go.sum +++ b/go.sum @@ -37,8 +37,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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sagernet/sing v0.0.0-20220725031620-096223b346f3 h1:Qm57CtqzaZ6Cq0ZDz1dX4vSNUoTYKn7qfXnpleKE0z0= -github.com/sagernet/sing v0.0.0-20220725031620-096223b346f3/go.mod h1:GbtQfZSpmtD3cXeD1qX2LCMwY8dH+bnnInDTqd92IsM= +github.com/sagernet/sing v0.0.0-20220725141316-c15de13f4f68 h1:EQ80ogJM1Xk4zgYI5lioDc15SCFeP0vdM5DMlmAOqnc= +github.com/sagernet/sing v0.0.0-20220725141316-c15de13f4f68/go.mod h1:GbtQfZSpmtD3cXeD1qX2LCMwY8dH+bnnInDTqd92IsM= github.com/sagernet/sing-dns v0.0.0-20220724053927-eb8d0d542175 h1:YpacS9+rDFcLG8lSkmwU21capz2gewk4LQfCE/x73U0= github.com/sagernet/sing-dns v0.0.0-20220724053927-eb8d0d542175/go.mod h1:2A34p89do4H4E9Ke046cJCMTdVqmvsXGWXzRwgeO2TQ= github.com/sagernet/sing-shadowsocks v0.0.0-20220717063942-45a2ad9cd41f h1:F6yiuKbBoXgWiuoP7R0YA14pDEl3emxA1mL7M16Q7gc= diff --git a/outbound/default.go b/outbound/default.go index b0cfb445..bed0556a 100644 --- a/outbound/default.go +++ b/outbound/default.go @@ -7,6 +7,7 @@ import ( "time" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/canceler" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing/common" @@ -78,6 +79,16 @@ func NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn, if err != nil { return err } + if metadata.Protocol != "" { + switch metadata.Protocol { + case C.ProtocolQUIC: + ctx, conn = canceler.NewPacketConn(ctx, conn, C.QUICTimeout) + case C.ProtocolDNS: + ctx, conn = canceler.NewPacketConn(ctx, conn, C.DNSTimeout) + case C.ProtocolSTUN: + ctx, conn = canceler.NewPacketConn(ctx, conn, C.STUNTimeout) + } + } return bufio.CopyPacketConn(ctx, conn, bufio.NewPacketConn(outConn)) } diff --git a/outbound/dns.go b/outbound/dns.go index 03539131..f398d240 100644 --- a/outbound/dns.go +++ b/outbound/dns.go @@ -6,10 +6,9 @@ import ( "io" "net" "os" - "sync" - "time" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/canceler" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing/common" @@ -103,14 +102,14 @@ func (d *DNS) NewConnection(ctx context.Context, conn net.Conn, metadata adapter func (d *DNS) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { defer conn.Close() ctx = adapter.WithContext(ctx, &metadata) - _buffer := buf.StackNewSize(1024) - defer common.KeepAlive(_buffer) - buffer := common.Dup(_buffer) - defer buffer.Release() - var wg sync.WaitGroup fastClose, cancel := context.WithCancel(ctx) - err := task.Run(fastClose, func() error { - var count int + timeout := canceler.New(fastClose, cancel, C.DNSTimeout) + return task.Run(fastClose, func() error { + defer cancel() + _buffer := buf.StackNewSize(1024) + defer common.KeepAlive(_buffer) + buffer := common.Dup(_buffer) + defer buffer.Release() for { buffer.FullReset() destination, err := conn.ReadPacket(buffer) @@ -127,13 +126,13 @@ func (d *DNS) NewPacketConnection(ctx context.Context, conn N.PacketConn, metada metadata.Domain = string(question.Name.Data[:question.Name.Length-1]) d.logger.DebugContext(ctx, "inbound dns query ", formatDNSQuestion(question), " from ", metadata.Source) } - wg.Add(1) + timeout.Update() go func() error { - defer wg.Done() response, err := d.router.Exchange(ctx, &message) if err != nil { return err } + timeout.Update() _responseBuffer := buf.StackNewSize(1024) defer common.KeepAlive(_responseBuffer) responseBuffer := common.Dup(_responseBuffer) @@ -146,24 +145,8 @@ func (d *DNS) NewPacketConnection(ctx context.Context, conn N.PacketConn, metada err = conn.WritePacket(responseBuffer, destination) return err }() - count++ - if count == 2 { - break - } } - cancel() - return nil - }, func() error { - timer := time.NewTimer(5 * time.Second) - select { - case <-timer.C: - cancel() - case <-fastClose.Done(): - } - return nil }) - wg.Wait() - return err } func formatDNSQuestion(question dnsmessage.Question) string { diff --git a/outbound/urltest.go b/outbound/urltest.go index b7f7923b..7ebb0252 100644 --- a/outbound/urltest.go +++ b/outbound/urltest.go @@ -3,6 +3,7 @@ package outbound import ( "context" "net" + "sort" "time" "github.com/sagernet/sing-box/adapter" @@ -167,6 +168,29 @@ func (g *URLTestGroup) Select() adapter.Outbound { 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 { diff --git a/route/router.go b/route/router.go index efc75e99..e8a125fd 100644 --- a/route/router.go +++ b/route/router.go @@ -576,16 +576,22 @@ func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, m func (r *Router) Exchange(ctx context.Context, message *dnsmessage.Message) (*dnsmessage.Message, error) { ctx, transport := r.matchDNS(ctx) + ctx, cancel := context.WithTimeout(ctx, C.DNSTimeout) + defer cancel() return r.dnsClient.Exchange(ctx, transport, message) } func (r *Router) Lookup(ctx context.Context, domain string, strategy dns.DomainStrategy) ([]netip.Addr, error) { ctx, transport := r.matchDNS(ctx) + ctx, cancel := context.WithTimeout(ctx, C.DNSTimeout) + defer cancel() return r.dnsClient.Lookup(ctx, transport, domain, strategy) } func (r *Router) LookupDefault(ctx context.Context, domain string) ([]netip.Addr, error) { ctx, transport := r.matchDNS(ctx) + ctx, cancel := context.WithTimeout(ctx, C.DNSTimeout) + defer cancel() return r.dnsClient.Lookup(ctx, transport, domain, r.defaultDomainStrategy) } diff --git a/test/go.mod b/test/go.mod index 808377a2..1a21b999 100644 --- a/test/go.mod +++ b/test/go.mod @@ -10,7 +10,7 @@ require ( github.com/docker/docker v20.10.17+incompatible github.com/docker/go-connections v0.4.0 github.com/gofrs/uuid v4.2.0+incompatible - github.com/sagernet/sing v0.0.0-20220725031620-096223b346f3 + github.com/sagernet/sing v0.0.0-20220725141316-c15de13f4f68 github.com/spyzhov/ajson v0.7.1 github.com/stretchr/testify v1.8.0 golang.org/x/net v0.0.0-20220722155237-a158d28d115b diff --git a/test/go.sum b/test/go.sum index bfd48107..52241716 100644 --- a/test/go.sum +++ b/test/go.sum @@ -64,8 +64,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/sagernet/sing v0.0.0-20220725031620-096223b346f3 h1:Qm57CtqzaZ6Cq0ZDz1dX4vSNUoTYKn7qfXnpleKE0z0= -github.com/sagernet/sing v0.0.0-20220725031620-096223b346f3/go.mod h1:GbtQfZSpmtD3cXeD1qX2LCMwY8dH+bnnInDTqd92IsM= +github.com/sagernet/sing v0.0.0-20220725141316-c15de13f4f68 h1:EQ80ogJM1Xk4zgYI5lioDc15SCFeP0vdM5DMlmAOqnc= +github.com/sagernet/sing v0.0.0-20220725141316-c15de13f4f68/go.mod h1:GbtQfZSpmtD3cXeD1qX2LCMwY8dH+bnnInDTqd92IsM= github.com/sagernet/sing-dns v0.0.0-20220724053927-eb8d0d542175 h1:YpacS9+rDFcLG8lSkmwU21capz2gewk4LQfCE/x73U0= github.com/sagernet/sing-dns v0.0.0-20220724053927-eb8d0d542175/go.mod h1:2A34p89do4H4E9Ke046cJCMTdVqmvsXGWXzRwgeO2TQ= github.com/sagernet/sing-shadowsocks v0.0.0-20220717063942-45a2ad9cd41f h1:F6yiuKbBoXgWiuoP7R0YA14pDEl3emxA1mL7M16Q7gc=