فهرست منبع

Improve udp timeout

世界 3 سال پیش
والد
کامیت
816d7b734c
13فایلهای تغییر یافته به همراه157 افزوده شده و 41 حذف شده
  1. 48 0
      common/canceler/instance.go
  2. 41 0
      common/canceler/packet.go
  3. 2 2
      common/dialer/default.go
  4. 1 1
      common/dialer/tls.go
  5. 8 5
      constant/timeout.go
  6. 1 1
      go.mod
  7. 2 2
      go.sum
  8. 11 0
      outbound/default.go
  9. 10 27
      outbound/dns.go
  10. 24 0
      outbound/urltest.go
  11. 6 0
      route/router.go
  12. 1 1
      test/go.mod
  13. 2 2
      test/go.sum

+ 48 - 0
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
+}

+ 41 - 0
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
+}

+ 2 - 2
common/dialer/default.go

@@ -103,7 +103,7 @@ func NewDefault(router adapter.Router, options option.DialerOptions) *DefaultDia
 	if options.ConnectTimeout != 0 {
 	if options.ConnectTimeout != 0 {
 		dialer.Timeout = time.Duration(options.ConnectTimeout)
 		dialer.Timeout = time.Duration(options.ConnectTimeout)
 	} else {
 	} else {
-		dialer.Timeout = C.DefaultTCPTimeout
+		dialer.Timeout = C.TCPTimeout
 	}
 	}
 	if options.TCPFastOpen {
 	if options.TCPFastOpen {
 		warnTFOOnUnsupportedPlatform.Check()
 		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 {
 	if tcpConn, isTCP := common.Cast[*net.TCPConn](conn); isTCP {
 		tcpConn.SetKeepAlive(true)
 		tcpConn.SetKeepAlive(true)
-		tcpConn.SetKeepAlivePeriod(C.DefaultTCPKeepAlivePeriod)
+		tcpConn.SetKeepAlivePeriod(C.TCPKeepAlivePeriod)
 	}
 	}
 	return conn, nil
 	return conn, nil
 }
 }

+ 1 - 1
common/dialer/tls.go

@@ -129,7 +129,7 @@ func (d *TLSDialer) DialContext(ctx context.Context, network string, destination
 		return nil, err
 		return nil, err
 	}
 	}
 	tlsConn := tls.Client(conn, d.config)
 	tlsConn := tls.Client(conn, d.config)
-	ctx, cancel := context.WithTimeout(ctx, C.DefaultTCPTimeout)
+	ctx, cancel := context.WithTimeout(ctx, C.TCPTimeout)
 	defer cancel()
 	defer cancel()
 	err = tlsConn.HandshakeContext(ctx)
 	err = tlsConn.HandshakeContext(ctx)
 	return tlsConn, err
 	return tlsConn, err

+ 8 - 5
constant/timeout.go

@@ -3,9 +3,12 @@ package constant
 import "time"
 import "time"
 
 
 const (
 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
 )
 )

+ 1 - 1
go.mod

@@ -12,7 +12,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-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-dns v0.0.0-20220724053927-eb8d0d542175
 	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

+ 2 - 2
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 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-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 h1:YpacS9+rDFcLG8lSkmwU21capz2gewk4LQfCE/x73U0=
 github.com/sagernet/sing-dns v0.0.0-20220724053927-eb8d0d542175/go.mod h1:2A34p89do4H4E9Ke046cJCMTdVqmvsXGWXzRwgeO2TQ=
 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=
 github.com/sagernet/sing-shadowsocks v0.0.0-20220717063942-45a2ad9cd41f h1:F6yiuKbBoXgWiuoP7R0YA14pDEl3emxA1mL7M16Q7gc=

+ 11 - 0
outbound/default.go

@@ -7,6 +7,7 @@ import (
 	"time"
 	"time"
 
 
 	"github.com/sagernet/sing-box/adapter"
 	"github.com/sagernet/sing-box/adapter"
+	"github.com/sagernet/sing-box/common/canceler"
 	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/common"
 	"github.com/sagernet/sing/common"
@@ -78,6 +79,16 @@ func NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn,
 	if err != nil {
 	if err != nil {
 		return err
 		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))
 	return bufio.CopyPacketConn(ctx, conn, bufio.NewPacketConn(outConn))
 }
 }
 
 

+ 10 - 27
outbound/dns.go

@@ -6,10 +6,9 @@ import (
 	"io"
 	"io"
 	"net"
 	"net"
 	"os"
 	"os"
-	"sync"
-	"time"
 
 
 	"github.com/sagernet/sing-box/adapter"
 	"github.com/sagernet/sing-box/adapter"
+	"github.com/sagernet/sing-box/common/canceler"
 	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/common"
 	"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 {
 func (d *DNS) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
 	defer conn.Close()
 	defer conn.Close()
 	ctx = adapter.WithContext(ctx, &metadata)
 	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)
 	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 {
 		for {
 			buffer.FullReset()
 			buffer.FullReset()
 			destination, err := conn.ReadPacket(buffer)
 			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])
 				metadata.Domain = string(question.Name.Data[:question.Name.Length-1])
 				d.logger.DebugContext(ctx, "inbound dns query ", formatDNSQuestion(question), " from ", metadata.Source)
 				d.logger.DebugContext(ctx, "inbound dns query ", formatDNSQuestion(question), " from ", metadata.Source)
 			}
 			}
-			wg.Add(1)
+			timeout.Update()
 			go func() error {
 			go func() error {
-				defer wg.Done()
 				response, err := d.router.Exchange(ctx, &message)
 				response, err := d.router.Exchange(ctx, &message)
 				if err != nil {
 				if err != nil {
 					return err
 					return err
 				}
 				}
+				timeout.Update()
 				_responseBuffer := buf.StackNewSize(1024)
 				_responseBuffer := buf.StackNewSize(1024)
 				defer common.KeepAlive(_responseBuffer)
 				defer common.KeepAlive(_responseBuffer)
 				responseBuffer := common.Dup(_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)
 				err = conn.WritePacket(responseBuffer, destination)
 				return err
 				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 {
 func formatDNSQuestion(question dnsmessage.Question) string {

+ 24 - 0
outbound/urltest.go

@@ -3,6 +3,7 @@ package outbound
 import (
 import (
 	"context"
 	"context"
 	"net"
 	"net"
+	"sort"
 	"time"
 	"time"
 
 
 	"github.com/sagernet/sing-box/adapter"
 	"github.com/sagernet/sing-box/adapter"
@@ -167,6 +168,29 @@ func (g *URLTestGroup) Select() adapter.Outbound {
 	return minOutbound
 	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() {
 func (g *URLTestGroup) loopCheck() {
 	go g.checkOutbounds()
 	go g.checkOutbounds()
 	for {
 	for {

+ 6 - 0
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) {
 func (r *Router) Exchange(ctx context.Context, message *dnsmessage.Message) (*dnsmessage.Message, error) {
 	ctx, transport := r.matchDNS(ctx)
 	ctx, transport := r.matchDNS(ctx)
+	ctx, cancel := context.WithTimeout(ctx, C.DNSTimeout)
+	defer cancel()
 	return r.dnsClient.Exchange(ctx, transport, message)
 	return r.dnsClient.Exchange(ctx, transport, message)
 }
 }
 
 
 func (r *Router) Lookup(ctx context.Context, domain string, strategy dns.DomainStrategy) ([]netip.Addr, error) {
 func (r *Router) Lookup(ctx context.Context, domain string, strategy dns.DomainStrategy) ([]netip.Addr, error) {
 	ctx, transport := r.matchDNS(ctx)
 	ctx, transport := r.matchDNS(ctx)
+	ctx, cancel := context.WithTimeout(ctx, C.DNSTimeout)
+	defer cancel()
 	return r.dnsClient.Lookup(ctx, transport, domain, strategy)
 	return r.dnsClient.Lookup(ctx, transport, domain, strategy)
 }
 }
 
 
 func (r *Router) LookupDefault(ctx context.Context, domain string) ([]netip.Addr, error) {
 func (r *Router) LookupDefault(ctx context.Context, domain string) ([]netip.Addr, error) {
 	ctx, transport := r.matchDNS(ctx)
 	ctx, transport := r.matchDNS(ctx)
+	ctx, cancel := context.WithTimeout(ctx, C.DNSTimeout)
+	defer cancel()
 	return r.dnsClient.Lookup(ctx, transport, domain, r.defaultDomainStrategy)
 	return r.dnsClient.Lookup(ctx, transport, domain, r.defaultDomainStrategy)
 }
 }
 
 

+ 1 - 1
test/go.mod

@@ -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-20220725031620-096223b346f3
+	github.com/sagernet/sing v0.0.0-20220725141316-c15de13f4f68
 	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-20220722155237-a158d28d115b
 	golang.org/x/net v0.0.0-20220722155237-a158d28d115b

+ 2 - 2
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/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-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 h1:YpacS9+rDFcLG8lSkmwU21capz2gewk4LQfCE/x73U0=
 github.com/sagernet/sing-dns v0.0.0-20220724053927-eb8d0d542175/go.mod h1:2A34p89do4H4E9Ke046cJCMTdVqmvsXGWXzRwgeO2TQ=
 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=
 github.com/sagernet/sing-shadowsocks v0.0.0-20220717063942-45a2ad9cd41f h1:F6yiuKbBoXgWiuoP7R0YA14pDEl3emxA1mL7M16Q7gc=