Browse Source

Migrate components to library

世界 3 years ago
parent
commit
dc127e2994

+ 2 - 0
.github/update_dependencies.sh

@@ -3,6 +3,8 @@
 PROJECTS=$(dirname "$0")/../..
 
 go get -x github.com/sagernet/sing@$(git -C $PROJECTS/sing rev-parse HEAD)
+go get -x github.com/sagernet/sing-dns@$(git -C $PROJECTS/sing-dns rev-parse HEAD)
+go get -x github.com/sagernet/sing-tun@$(git -C $PROJECTS/sing-dns rev-parse HEAD)
 go get -x github.com/sagernet/sing-shadowsocks@$(git -C $PROJECTS/sing-shadowsocks rev-parse HEAD)
 go mod tidy
 pushd test

+ 0 - 22
adapter/dns.go

@@ -1,22 +0,0 @@
-package adapter
-
-import (
-	"context"
-	"net/netip"
-
-	C "github.com/sagernet/sing-box/constant"
-
-	"golang.org/x/net/dns/dnsmessage"
-)
-
-type DNSClient interface {
-	Exchange(ctx context.Context, transport DNSTransport, message *dnsmessage.Message) (*dnsmessage.Message, error)
-	Lookup(ctx context.Context, transport DNSTransport, domain string, strategy C.DomainStrategy) ([]netip.Addr, error)
-}
-
-type DNSTransport interface {
-	Service
-	Raw() bool
-	Exchange(ctx context.Context, message *dnsmessage.Message) (*dnsmessage.Message, error)
-	Lookup(ctx context.Context, domain string, strategy C.DomainStrategy) ([]netip.Addr, error)
-}

+ 2 - 2
adapter/inbound.go

@@ -4,7 +4,7 @@ import (
 	"context"
 	"net/netip"
 
-	C "github.com/sagernet/sing-box/constant"
+	"github.com/sagernet/sing-dns"
 	M "github.com/sagernet/sing/common/metadata"
 )
 
@@ -25,7 +25,7 @@ type InboundContext struct {
 
 	// cache
 
-	DomainStrategy           C.DomainStrategy
+	DomainStrategy           dns.DomainStrategy
 	SniffEnabled             bool
 	SniffOverrideDestination bool
 	DestinationAddresses     []netip.Addr

+ 2 - 2
adapter/router.go

@@ -6,7 +6,7 @@ import (
 	"net/netip"
 
 	"github.com/sagernet/sing-box/common/geoip"
-	C "github.com/sagernet/sing-box/constant"
+	"github.com/sagernet/sing-dns"
 	N "github.com/sagernet/sing/common/network"
 
 	"golang.org/x/net/dns/dnsmessage"
@@ -21,7 +21,7 @@ type Router interface {
 	GeoIPReader() *geoip.Reader
 	LoadGeosite(code string) (Rule, error)
 	Exchange(ctx context.Context, message *dnsmessage.Message) (*dnsmessage.Message, error)
-	Lookup(ctx context.Context, domain string, strategy C.DomainStrategy) ([]netip.Addr, error)
+	Lookup(ctx context.Context, domain string, strategy dns.DomainStrategy) ([]netip.Addr, error)
 	LookupDefault(ctx context.Context, domain string) ([]netip.Addr, error)
 	AutoDetectInterface() bool
 	DefaultInterfaceName() string

+ 2 - 2
common/dialer/default.go

@@ -37,8 +37,8 @@ func NewDefault(router adapter.Router, options option.DialerOptions) *DefaultDia
 		listener.Control = control.Append(listener.Control, control.ReuseAddr())
 	}
 	if options.ProtectPath != "" {
-		dialer.Control = control.Append(dialer.Control, ProtectPath(options.ProtectPath))
-		listener.Control = control.Append(listener.Control, ProtectPath(options.ProtectPath))
+		dialer.Control = control.Append(dialer.Control, control.ProtectPath(options.ProtectPath))
+		listener.Control = control.Append(listener.Control, control.ProtectPath(options.ProtectPath))
 	}
 	if options.ConnectTimeout != 0 {
 		dialer.Timeout = time.Duration(options.ConnectTimeout)

+ 4 - 8
common/dialer/dialer.go

@@ -4,8 +4,8 @@ import (
 	"time"
 
 	"github.com/sagernet/sing-box/adapter"
-	C "github.com/sagernet/sing-box/constant"
 	"github.com/sagernet/sing-box/option"
+	"github.com/sagernet/sing-dns"
 	"github.com/sagernet/sing/common"
 	N "github.com/sagernet/sing/common/network"
 )
@@ -20,13 +20,9 @@ func New(router adapter.Router, options option.DialerOptions) N.Dialer {
 
 func NewOutbound(router adapter.Router, options option.OutboundDialerOptions) N.Dialer {
 	dialer := New(router, options.DialerOptions)
-	domainStrategy := C.DomainStrategy(options.DomainStrategy)
-	if domainStrategy != C.DomainStrategyAsIS || options.Detour == "" {
-		fallbackDelay := time.Duration(options.FallbackDelay)
-		if fallbackDelay == 0 {
-			fallbackDelay = time.Millisecond * 300
-		}
-		dialer = NewResolveDialer(router, dialer, domainStrategy, fallbackDelay)
+	domainStrategy := dns.DomainStrategy(options.DomainStrategy)
+	if domainStrategy != dns.DomainStrategyAsIS || options.Detour == "" {
+		dialer = NewResolveDialer(router, dialer, domainStrategy, time.Duration(options.FallbackDelay))
 	}
 	if options.OverrideOptions.IsValid() {
 		dialer = NewOverride(dialer, common.PtrValueOrDefault(options.OverrideOptions))

+ 0 - 90
common/dialer/parallel.go

@@ -1,90 +0,0 @@
-package dialer
-
-import (
-	"context"
-	"net"
-	"net/netip"
-	"time"
-
-	C "github.com/sagernet/sing-box/constant"
-	"github.com/sagernet/sing/common"
-	M "github.com/sagernet/sing/common/metadata"
-	N "github.com/sagernet/sing/common/network"
-)
-
-func DialParallel(ctx context.Context, dialer N.Dialer, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy C.DomainStrategy, fallbackDelay time.Duration) (net.Conn, error) {
-	// kanged form net.Dial
-
-	returned := make(chan struct{})
-	defer close(returned)
-
-	addresses4 := common.Filter(destinationAddresses, func(address netip.Addr) bool {
-		return address.Is4() || address.Is4In6()
-	})
-	addresses6 := common.Filter(destinationAddresses, func(address netip.Addr) bool {
-		return address.Is6() && !address.Is4In6()
-	})
-	if len(addresses4) == 0 || len(addresses6) == 0 {
-		return DialSerial(ctx, dialer, network, destination, destinationAddresses)
-	}
-	var primaries, fallbacks []netip.Addr
-	switch strategy {
-	case C.DomainStrategyPreferIPv6:
-		primaries = addresses6
-		fallbacks = addresses4
-	default:
-		primaries = addresses4
-		fallbacks = addresses6
-	}
-	type dialResult struct {
-		net.Conn
-		error
-		primary bool
-		done    bool
-	}
-	results := make(chan dialResult) // unbuffered
-	startRacer := func(ctx context.Context, primary bool) {
-		ras := primaries
-		if !primary {
-			ras = fallbacks
-		}
-		c, err := DialSerial(ctx, dialer, network, destination, ras)
-		select {
-		case results <- dialResult{Conn: c, error: err, primary: primary, done: true}:
-		case <-returned:
-			if c != nil {
-				c.Close()
-			}
-		}
-	}
-	var primary, fallback dialResult
-	primaryCtx, primaryCancel := context.WithCancel(ctx)
-	defer primaryCancel()
-	go startRacer(primaryCtx, true)
-	fallbackTimer := time.NewTimer(fallbackDelay)
-	defer fallbackTimer.Stop()
-	for {
-		select {
-		case <-fallbackTimer.C:
-			fallbackCtx, fallbackCancel := context.WithCancel(ctx)
-			defer fallbackCancel()
-			go startRacer(fallbackCtx, false)
-
-		case res := <-results:
-			if res.error == nil {
-				return res.Conn, nil
-			}
-			if res.primary {
-				primary = res
-			} else {
-				fallback = res
-			}
-			if primary.done && fallback.done {
-				return nil, primary.error
-			}
-			if res.primary && fallbackTimer.Stop() {
-				fallbackTimer.Reset(0)
-			}
-		}
-	}
-}

+ 0 - 46
common/dialer/protect.go

@@ -1,46 +0,0 @@
-//go:build android || with_protect
-
-package dialer
-
-import (
-	"syscall"
-
-	"github.com/sagernet/sing/common/control"
-	E "github.com/sagernet/sing/common/exceptions"
-)
-
-func sendAncillaryFileDescriptors(protectPath string, fileDescriptors []int) error {
-	socket, err := syscall.Socket(syscall.AF_UNIX, syscall.SOCK_STREAM, 0)
-	if err != nil {
-		return E.Cause(err, "open protect socket")
-	}
-	defer syscall.Close(socket)
-	err = syscall.Connect(socket, &syscall.SockaddrUnix{Name: protectPath})
-	if err != nil {
-		return E.Cause(err, "connect protect path")
-	}
-	oob := syscall.UnixRights(fileDescriptors...)
-	dummy := []byte{1}
-	err = syscall.Sendmsg(socket, dummy, oob, nil, 0)
-	if err != nil {
-		return err
-	}
-	n, err := syscall.Read(socket, dummy)
-	if err != nil {
-		return err
-	}
-	if n != 1 {
-		return E.New("failed to protect fd")
-	}
-	return nil
-}
-
-func ProtectPath(protectPath string) control.Func {
-	return func(network, address string, conn syscall.RawConn) error {
-		var innerErr error
-		err := conn.Control(func(fd uintptr) {
-			innerErr = sendAncillaryFileDescriptors(protectPath, []int{int(fd)})
-		})
-		return E.Errors(innerErr, err)
-	}
-}

+ 0 - 9
common/dialer/protect_stub.go

@@ -1,9 +0,0 @@
-//go:build !android && !with_protect
-
-package dialer
-
-import "github.com/sagernet/sing/common/control"
-
-func ProtectPath(protectPath string) control.Func {
-	return nil
-}

+ 7 - 7
common/dialer/resolve.go

@@ -7,7 +7,7 @@ import (
 	"time"
 
 	"github.com/sagernet/sing-box/adapter"
-	C "github.com/sagernet/sing-box/constant"
+	"github.com/sagernet/sing-dns"
 	M "github.com/sagernet/sing/common/metadata"
 	N "github.com/sagernet/sing/common/network"
 )
@@ -15,11 +15,11 @@ import (
 type ResolveDialer struct {
 	dialer        N.Dialer
 	router        adapter.Router
-	strategy      C.DomainStrategy
+	strategy      dns.DomainStrategy
 	fallbackDelay time.Duration
 }
 
-func NewResolveDialer(router adapter.Router, dialer N.Dialer, strategy C.DomainStrategy, fallbackDelay time.Duration) *ResolveDialer {
+func NewResolveDialer(router adapter.Router, dialer N.Dialer, strategy dns.DomainStrategy, fallbackDelay time.Duration) *ResolveDialer {
 	return &ResolveDialer{
 		dialer,
 		router,
@@ -37,7 +37,7 @@ func (d *ResolveDialer) DialContext(ctx context.Context, network string, destina
 	metadata.Domain = ""
 	var addresses []netip.Addr
 	var err error
-	if d.strategy == C.DomainStrategyAsIS {
+	if d.strategy == dns.DomainStrategyAsIS {
 		addresses, err = d.router.LookupDefault(ctx, destination.Fqdn)
 	} else {
 		addresses, err = d.router.Lookup(ctx, destination.Fqdn, d.strategy)
@@ -45,7 +45,7 @@ func (d *ResolveDialer) DialContext(ctx context.Context, network string, destina
 	if err != nil {
 		return nil, err
 	}
-	return DialParallel(ctx, d.dialer, network, destination, addresses, d.strategy, d.fallbackDelay)
+	return N.DialParallel(ctx, d.dialer, network, destination, addresses, d.strategy == dns.DomainStrategyPreferIPv6, d.fallbackDelay)
 }
 
 func (d *ResolveDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
@@ -57,7 +57,7 @@ func (d *ResolveDialer) ListenPacket(ctx context.Context, destination M.Socksadd
 	metadata.Domain = ""
 	var addresses []netip.Addr
 	var err error
-	if d.strategy == C.DomainStrategyAsIS {
+	if d.strategy == dns.DomainStrategyAsIS {
 		addresses, err = d.router.LookupDefault(ctx, destination.Fqdn)
 	} else {
 		addresses, err = d.router.Lookup(ctx, destination.Fqdn, d.strategy)
@@ -65,7 +65,7 @@ func (d *ResolveDialer) ListenPacket(ctx context.Context, destination M.Socksadd
 	if err != nil {
 		return nil, err
 	}
-	conn, err := ListenSerial(ctx, d.dialer, destination, addresses)
+	conn, err := N.ListenSerial(ctx, d.dialer, destination, addresses)
 	if err != nil {
 		return nil, err
 	}

+ 4 - 4
common/dialer/resolve_conn.go

@@ -5,14 +5,14 @@ import (
 	"net"
 
 	"github.com/sagernet/sing-box/adapter"
-	C "github.com/sagernet/sing-box/constant"
+	"github.com/sagernet/sing-dns"
 	"github.com/sagernet/sing/common"
 	"github.com/sagernet/sing/common/buf"
 	M "github.com/sagernet/sing/common/metadata"
 	N "github.com/sagernet/sing/common/network"
 )
 
-func NewResolvePacketConn(router adapter.Router, strategy C.DomainStrategy, conn net.PacketConn) N.NetPacketConn {
+func NewResolvePacketConn(router adapter.Router, strategy dns.DomainStrategy, conn net.PacketConn) N.NetPacketConn {
 	if udpConn, ok := conn.(*net.UDPConn); ok {
 		return &ResolveUDPConn{udpConn, router, strategy}
 	} else {
@@ -23,7 +23,7 @@ func NewResolvePacketConn(router adapter.Router, strategy C.DomainStrategy, conn
 type ResolveUDPConn struct {
 	*net.UDPConn
 	router   adapter.Router
-	strategy C.DomainStrategy
+	strategy dns.DomainStrategy
 }
 
 func (w *ResolveUDPConn) ReadPacket(buffer *buf.Buffer) (M.Socksaddr, error) {
@@ -54,7 +54,7 @@ func (w *ResolveUDPConn) Upstream() any {
 type ResolvePacketConn struct {
 	net.PacketConn
 	router   adapter.Router
-	strategy C.DomainStrategy
+	strategy dns.DomainStrategy
 }
 
 func (w *ResolvePacketConn) ReadPacket(buffer *buf.Buffer) (M.Socksaddr, error) {

+ 0 - 41
common/dialer/serial.go

@@ -1,41 +0,0 @@
-package dialer
-
-import (
-	"context"
-	"net"
-	"net/netip"
-
-	E "github.com/sagernet/sing/common/exceptions"
-	M "github.com/sagernet/sing/common/metadata"
-	N "github.com/sagernet/sing/common/network"
-)
-
-func DialSerial(ctx context.Context, dialer N.Dialer, network string, destination M.Socksaddr, destinationAddresses []netip.Addr) (net.Conn, error) {
-	var conn net.Conn
-	var err error
-	var connErrors []error
-	for _, address := range destinationAddresses {
-		conn, err = dialer.DialContext(ctx, network, M.SocksaddrFromAddrPort(address, destination.Port))
-		if err != nil {
-			connErrors = append(connErrors, err)
-			continue
-		}
-		return conn, nil
-	}
-	return nil, E.Errors(connErrors...)
-}
-
-func ListenSerial(ctx context.Context, dialer N.Dialer, destination M.Socksaddr, destinationAddresses []netip.Addr) (net.PacketConn, error) {
-	var conn net.PacketConn
-	var err error
-	var connErrors []error
-	for _, address := range destinationAddresses {
-		conn, err = dialer.ListenPacket(ctx, M.SocksaddrFromAddrPort(address, destination.Port))
-		if err != nil {
-			connErrors = append(connErrors, err)
-			continue
-		}
-		return conn, nil
-	}
-	return nil, E.Errors(connErrors...)
-}

+ 0 - 60
common/domain/matcher.go

@@ -1,60 +0,0 @@
-package domain
-
-import (
-	"sort"
-	"unicode/utf8"
-)
-
-type Matcher struct {
-	set *succinctSet
-}
-
-func NewMatcher(domains []string, domainSuffix []string) *Matcher {
-	domainList := make([]string, 0, len(domains)+len(domainSuffix))
-	seen := make(map[string]bool, len(domainList))
-	for _, domain := range domainSuffix {
-		if seen[domain] {
-			continue
-		}
-		seen[domain] = true
-		domainList = append(domainList, reverseDomainSuffix(domain))
-	}
-	for _, domain := range domains {
-		if seen[domain] {
-			continue
-		}
-		seen[domain] = true
-		domainList = append(domainList, reverseDomain(domain))
-	}
-	sort.Strings(domainList)
-	return &Matcher{
-		newSuccinctSet(domainList),
-	}
-}
-
-func (m *Matcher) Match(domain string) bool {
-	return m.set.Has(reverseDomain(domain))
-}
-
-func reverseDomain(domain string) string {
-	l := len(domain)
-	b := make([]byte, l)
-	for i := 0; i < l; {
-		r, n := utf8.DecodeRuneInString(domain[i:])
-		i += n
-		utf8.EncodeRune(b[l-i:], r)
-	}
-	return string(b)
-}
-
-func reverseDomainSuffix(domain string) string {
-	l := len(domain)
-	b := make([]byte, l+1)
-	for i := 0; i < l; {
-		r, n := utf8.DecodeRuneInString(domain[i:])
-		i += n
-		utf8.EncodeRune(b[l-i:], r)
-	}
-	b[l] = prefixLabel
-	return string(b)
-}

+ 0 - 21
common/domain/matcher_test.go

@@ -1,21 +0,0 @@
-package domain_test
-
-import (
-	"testing"
-
-	"github.com/sagernet/sing-box/common/domain"
-
-	"github.com/stretchr/testify/require"
-)
-
-func TestMatch(t *testing.T) {
-	t.Parallel()
-	r := require.New(t)
-	matcher := domain.NewMatcher([]string{"domain.com"}, []string{"suffix.com", ".suffix.org"})
-	r.True(matcher.Match("domain.com"))
-	r.False(matcher.Match("my.domain.com"))
-	r.True(matcher.Match("suffix.com"))
-	r.True(matcher.Match("my.suffix.com"))
-	r.False(matcher.Match("suffix.org"))
-	r.True(matcher.Match("my.suffix.org"))
-}

+ 0 - 231
common/domain/set.go

@@ -1,231 +0,0 @@
-package domain
-
-import (
-	"math/bits"
-)
-
-const prefixLabel = '\r'
-
-// mod from https://github.com/openacid/succinct
-
-type succinctSet struct {
-	leaves, labelBitmap []uint64
-	labels              []byte
-	ranks, selects      []int32
-}
-
-func newSuccinctSet(keys []string) *succinctSet {
-	ss := &succinctSet{}
-	lIdx := 0
-	type qElt struct{ s, e, col int }
-	queue := []qElt{{0, len(keys), 0}}
-	for i := 0; i < len(queue); i++ {
-		elt := queue[i]
-		if elt.col == len(keys[elt.s]) {
-			// a leaf node
-			elt.s++
-			setBit(&ss.leaves, i, 1)
-		}
-		for j := elt.s; j < elt.e; {
-			frm := j
-			for ; j < elt.e && keys[j][elt.col] == keys[frm][elt.col]; j++ {
-			}
-			queue = append(queue, qElt{frm, j, elt.col + 1})
-			ss.labels = append(ss.labels, keys[frm][elt.col])
-			setBit(&ss.labelBitmap, lIdx, 0)
-			lIdx++
-		}
-		setBit(&ss.labelBitmap, lIdx, 1)
-		lIdx++
-	}
-	ss.init()
-	return ss
-}
-
-func (ss *succinctSet) Has(key string) bool {
-	var nodeId, bmIdx int
-	for i := 0; i < len(key); i++ {
-		currentChar := key[i]
-		for ; ; bmIdx++ {
-			if getBit(ss.labelBitmap, bmIdx) != 0 {
-				return false
-			}
-			nextLabel := ss.labels[bmIdx-nodeId]
-			if nextLabel == prefixLabel {
-				return true
-			}
-			if nextLabel == currentChar {
-				break
-			}
-		}
-		nodeId = countZeros(ss.labelBitmap, ss.ranks, bmIdx+1)
-		bmIdx = selectIthOne(ss.labelBitmap, ss.ranks, ss.selects, nodeId-1) + 1
-	}
-	if getBit(ss.leaves, nodeId) != 0 {
-		return true
-	}
-	for ; ; bmIdx++ {
-		if getBit(ss.labelBitmap, bmIdx) != 0 {
-			return false
-		}
-		if ss.labels[bmIdx-nodeId] == prefixLabel {
-			return true
-		}
-	}
-}
-
-func setBit(bm *[]uint64, i int, v int) {
-	for i>>6 >= len(*bm) {
-		*bm = append(*bm, 0)
-	}
-	(*bm)[i>>6] |= uint64(v) << uint(i&63)
-}
-
-func getBit(bm []uint64, i int) uint64 {
-	return bm[i>>6] & (1 << uint(i&63))
-}
-
-func (ss *succinctSet) init() {
-	ss.selects, ss.ranks = indexSelect32R64(ss.labelBitmap)
-}
-
-func countZeros(bm []uint64, ranks []int32, i int) int {
-	a, _ := rank64(bm, ranks, int32(i))
-	return i - int(a)
-}
-
-func selectIthOne(bm []uint64, ranks, selects []int32, i int) int {
-	a, _ := select32R64(bm, selects, ranks, int32(i))
-	return int(a)
-}
-
-func rank64(words []uint64, rindex []int32, i int32) (int32, int32) {
-	wordI := i >> 6
-	j := uint32(i & 63)
-	n := rindex[wordI]
-	w := words[wordI]
-	c1 := n + int32(bits.OnesCount64(w&mask[j]))
-	return c1, int32(w>>uint(j)) & 1
-}
-
-func indexRank64(words []uint64, opts ...bool) []int32 {
-	trailing := false
-	if len(opts) > 0 {
-		trailing = opts[0]
-	}
-	l := len(words)
-	if trailing {
-		l++
-	}
-	idx := make([]int32, l)
-	n := int32(0)
-	for i := 0; i < len(words); i++ {
-		idx[i] = n
-		n += int32(bits.OnesCount64(words[i]))
-	}
-	if trailing {
-		idx[len(words)] = n
-	}
-	return idx
-}
-
-func select32R64(words []uint64, selectIndex, rankIndex []int32, i int32) (int32, int32) {
-	a := int32(0)
-	l := int32(len(words))
-	wordI := selectIndex[i>>5] >> 6
-	for ; rankIndex[wordI+1] <= i; wordI++ {
-	}
-	w := words[wordI]
-	ww := w
-	base := wordI << 6
-	findIth := int(i - rankIndex[wordI])
-	offset := int32(0)
-	ones := bits.OnesCount32(uint32(ww))
-	if ones <= findIth {
-		findIth -= ones
-		offset |= 32
-		ww >>= 32
-	}
-	ones = bits.OnesCount16(uint16(ww))
-	if ones <= findIth {
-		findIth -= ones
-		offset |= 16
-		ww >>= 16
-	}
-	ones = bits.OnesCount8(uint8(ww))
-	if ones <= findIth {
-		a = int32(select8Lookup[(ww>>5)&(0x7f8)|uint64(findIth-ones)]) + offset + 8
-	} else {
-		a = int32(select8Lookup[(ww&0xff)<<3|uint64(findIth)]) + offset
-	}
-	a += base
-	w &= rMaskUpto[a&63]
-	if w != 0 {
-		return a, base + int32(bits.TrailingZeros64(w))
-	}
-	wordI++
-	for ; wordI < l; wordI++ {
-		w = words[wordI]
-		if w != 0 {
-			return a, wordI<<6 + int32(bits.TrailingZeros64(w))
-		}
-	}
-	return a, l << 6
-}
-
-func indexSelect32R64(words []uint64) ([]int32, []int32) {
-	l := len(words) << 6
-	sidx := make([]int32, 0, len(words))
-
-	ith := -1
-	for i := 0; i < l; i++ {
-		if words[i>>6]&(1<<uint(i&63)) != 0 {
-			ith++
-			if ith&31 == 0 {
-				sidx = append(sidx, int32(i))
-			}
-		}
-	}
-
-	// clone to reduce cap to len
-	sidx = append(sidx[:0:0], sidx...)
-	return sidx, indexRank64(words, true)
-}
-
-func init() {
-	initMasks()
-	initSelectLookup()
-}
-
-var (
-	mask      [65]uint64
-	rMaskUpto [64]uint64
-)
-
-func initMasks() {
-	for i := 0; i < 65; i++ {
-		mask[i] = (1 << uint(i)) - 1
-	}
-
-	var maskUpto [64]uint64
-	for i := 0; i < 64; i++ {
-		maskUpto[i] = (1 << uint(i+1)) - 1
-		rMaskUpto[i] = ^maskUpto[i]
-	}
-}
-
-var select8Lookup [256 * 8]uint8
-
-func initSelectLookup() {
-	for i := 0; i < 256; i++ {
-		w := uint8(i)
-		for j := 0; j < 8; j++ {
-			// x-th 1 in w
-			// if x-th 1 is not found, it is 8
-			x := bits.TrailingZeros8(w)
-			w &= w - 1
-
-			select8Lookup[i*8+j] = uint8(x)
-		}
-	}
-}

+ 0 - 151
common/tun/gvisor.go

@@ -1,151 +0,0 @@
-package tun
-
-import (
-	"context"
-
-	"github.com/sagernet/sing-box/adapter"
-	"github.com/sagernet/sing-box/log"
-	"github.com/sagernet/sing/common"
-	"github.com/sagernet/sing/common/bufio"
-	E "github.com/sagernet/sing/common/exceptions"
-	M "github.com/sagernet/sing/common/metadata"
-
-	"gvisor.dev/gvisor/pkg/tcpip"
-	"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
-	"gvisor.dev/gvisor/pkg/tcpip/header"
-	"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
-	"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
-	"gvisor.dev/gvisor/pkg/tcpip/stack"
-	"gvisor.dev/gvisor/pkg/tcpip/transport/icmp"
-	"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
-	"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
-	"gvisor.dev/gvisor/pkg/waiter"
-)
-
-const defaultNIC tcpip.NICID = 1
-
-var _ adapter.Service = (*GVisorTun)(nil)
-
-type GVisorTun struct {
-	ctx     context.Context
-	tunFd   uintptr
-	tunMtu  uint32
-	handler Handler
-	stack   *stack.Stack
-}
-
-func NewGVisor(ctx context.Context, tunFd uintptr, tunMtu uint32, handler Handler) *GVisorTun {
-	return &GVisorTun{
-		ctx:     ctx,
-		tunFd:   tunFd,
-		tunMtu:  tunMtu,
-		handler: handler,
-	}
-}
-
-func (t *GVisorTun) Start() error {
-	linkEndpoint, err := NewEndpoint(t.tunFd, t.tunMtu)
-	if err != nil {
-		return err
-	}
-	ipStack := stack.New(stack.Options{
-		NetworkProtocols: []stack.NetworkProtocolFactory{
-			ipv4.NewProtocol,
-			ipv6.NewProtocol,
-		},
-		TransportProtocols: []stack.TransportProtocolFactory{
-			tcp.NewProtocol,
-			udp.NewProtocol,
-			icmp.NewProtocol4,
-			icmp.NewProtocol6,
-		},
-	})
-	tErr := ipStack.CreateNIC(defaultNIC, linkEndpoint)
-	if tErr != nil {
-		return E.New("create nic: ", tErr)
-	}
-	ipStack.SetRouteTable([]tcpip.Route{
-		{Destination: header.IPv4EmptySubnet, NIC: defaultNIC},
-		{Destination: header.IPv6EmptySubnet, NIC: defaultNIC},
-	})
-	ipStack.SetSpoofing(defaultNIC, true)
-	ipStack.SetPromiscuousMode(defaultNIC, true)
-	bufSize := 20 * 1024
-	ipStack.SetTransportProtocolOption(tcp.ProtocolNumber, &tcpip.TCPReceiveBufferSizeRangeOption{
-		Min:     1,
-		Default: bufSize,
-		Max:     bufSize,
-	})
-	ipStack.SetTransportProtocolOption(tcp.ProtocolNumber, &tcpip.TCPSendBufferSizeRangeOption{
-		Min:     1,
-		Default: bufSize,
-		Max:     bufSize,
-	})
-	sOpt := tcpip.TCPSACKEnabled(true)
-	ipStack.SetTransportProtocolOption(tcp.ProtocolNumber, &sOpt)
-	mOpt := tcpip.TCPModerateReceiveBufferOption(true)
-	ipStack.SetTransportProtocolOption(tcp.ProtocolNumber, &mOpt)
-	tcpForwarder := tcp.NewForwarder(ipStack, 0, 1024, func(r *tcp.ForwarderRequest) {
-		var wq waiter.Queue
-		endpoint, err := r.CreateEndpoint(&wq)
-		if err != nil {
-			r.Complete(true)
-			return
-		}
-		r.Complete(false)
-		endpoint.SocketOptions().SetKeepAlive(true)
-		tcpConn := gonet.NewTCPConn(&wq, endpoint)
-		lAddr := tcpConn.RemoteAddr()
-		rAddr := tcpConn.LocalAddr()
-		if lAddr == nil || rAddr == nil {
-			tcpConn.Close()
-			return
-		}
-		go func() {
-			var metadata M.Metadata
-			metadata.Source = M.SocksaddrFromNet(lAddr)
-			metadata.Destination = M.SocksaddrFromNet(rAddr)
-			ctx := log.ContextWithID(t.ctx)
-			hErr := t.handler.NewConnection(ctx, tcpConn, metadata)
-			if hErr != nil {
-				t.handler.NewError(ctx, hErr)
-			}
-		}()
-	})
-	ipStack.SetTransportProtocolHandler(tcp.ProtocolNumber, func(id stack.TransportEndpointID, buffer *stack.PacketBuffer) bool {
-		return tcpForwarder.HandlePacket(id, buffer)
-	})
-	udpForwarder := udp.NewForwarder(ipStack, func(request *udp.ForwarderRequest) {
-		var wq waiter.Queue
-		endpoint, err := request.CreateEndpoint(&wq)
-		if err != nil {
-			return
-		}
-		udpConn := gonet.NewUDPConn(ipStack, &wq, endpoint)
-		lAddr := udpConn.RemoteAddr()
-		rAddr := udpConn.LocalAddr()
-		if lAddr == nil || rAddr == nil {
-			udpConn.Close()
-			return
-		}
-		go func() {
-			var metadata M.Metadata
-			metadata.Source = M.SocksaddrFromNet(lAddr)
-			metadata.Destination = M.SocksaddrFromNet(rAddr)
-			ctx := log.ContextWithID(t.ctx)
-			hErr := t.handler.NewPacketConnection(ctx, bufio.NewPacketConn(&bufio.UnbindPacketConn{ExtendedConn: bufio.NewExtendedConn(udpConn), Addr: M.SocksaddrFromNet(rAddr)}), metadata)
-			if hErr != nil {
-				t.handler.NewError(ctx, hErr)
-			}
-		}()
-	})
-	ipStack.SetTransportProtocolHandler(udp.ProtocolNumber, udpForwarder.HandlePacket)
-	t.stack = ipStack
-	return nil
-}
-
-func (t *GVisorTun) Close() error {
-	return common.Close(
-		common.PtrOrNil(t.stack),
-	)
-}

+ 0 - 24
common/tun/gvisor_linux.go

@@ -1,24 +0,0 @@
-//go:build linux
-
-package tun
-
-import (
-	"runtime"
-
-	"gvisor.dev/gvisor/pkg/tcpip/link/fdbased"
-	"gvisor.dev/gvisor/pkg/tcpip/stack"
-)
-
-func NewEndpoint(tunFd uintptr, tunMtu uint32) (stack.LinkEndpoint, error) {
-	var packetDispatchMode fdbased.PacketDispatchMode
-	if runtime.GOARCH == "amd64" || runtime.GOARCH == "arm64" {
-		packetDispatchMode = fdbased.PacketMMap
-	} else {
-		packetDispatchMode = fdbased.RecvMMsg
-	}
-	return fdbased.New(&fdbased.Options{
-		FDs:                []int{int(tunFd)},
-		MTU:                tunMtu,
-		PacketDispatchMode: packetDispatchMode,
-	})
-}

+ 0 - 9
common/tun/gvisor_nonlinux.go

@@ -1,9 +0,0 @@
-//go:build !linux
-
-package tun
-
-import "gvisor.dev/gvisor/pkg/tcpip/stack"
-
-func NewEndpoint(tunFd uintptr, tunMtu uint32) (stack.LinkEndpoint, error) {
-	return NewPosixEndpoint(tunFd, tunMtu)
-}

+ 0 - 118
common/tun/gvisor_posix.go

@@ -1,118 +0,0 @@
-package tun
-
-import (
-	"os"
-
-	"github.com/sagernet/sing/common"
-	"github.com/sagernet/sing/common/buf"
-	"github.com/sagernet/sing/common/rw"
-
-	gBuffer "gvisor.dev/gvisor/pkg/buffer"
-	"gvisor.dev/gvisor/pkg/tcpip"
-	"gvisor.dev/gvisor/pkg/tcpip/header"
-	"gvisor.dev/gvisor/pkg/tcpip/stack"
-)
-
-var _ stack.LinkEndpoint = (*PosixEndpoint)(nil)
-
-type PosixEndpoint struct {
-	fd         uintptr
-	mtu        uint32
-	file       *os.File
-	dispatcher stack.NetworkDispatcher
-}
-
-func NewPosixEndpoint(tunFd uintptr, tunMtu uint32) (stack.LinkEndpoint, error) {
-	return &PosixEndpoint{
-		fd:   tunFd,
-		mtu:  tunMtu,
-		file: os.NewFile(tunFd, "tun"),
-	}, nil
-}
-
-func (e *PosixEndpoint) MTU() uint32 {
-	return e.mtu
-}
-
-func (e *PosixEndpoint) MaxHeaderLength() uint16 {
-	return 0
-}
-
-func (e *PosixEndpoint) LinkAddress() tcpip.LinkAddress {
-	return ""
-}
-
-func (e *PosixEndpoint) Capabilities() stack.LinkEndpointCapabilities {
-	return stack.CapabilityNone
-}
-
-func (e *PosixEndpoint) Attach(dispatcher stack.NetworkDispatcher) {
-	if dispatcher == nil && e.dispatcher != nil {
-		e.dispatcher = nil
-		return
-	}
-	if dispatcher != nil && e.dispatcher == nil {
-		e.dispatcher = dispatcher
-		go e.dispatchLoop()
-	}
-}
-
-func (e *PosixEndpoint) dispatchLoop() {
-	_buffer := buf.StackNewPacket()
-	defer common.KeepAlive(_buffer)
-	buffer := common.Dup(_buffer)
-	defer buffer.Release()
-	for {
-		n, err := e.file.Read(buffer.FreeBytes())
-		if err != nil {
-			break
-		}
-		var view gBuffer.View
-		view.Append(buffer.To(n))
-		pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
-			Payload:           view,
-			IsForwardedPacket: true,
-		})
-		defer pkt.DecRef()
-		var p tcpip.NetworkProtocolNumber
-		ipHeader, ok := pkt.Data().PullUp(1)
-		if !ok {
-			continue
-		}
-		switch header.IPVersion(ipHeader) {
-		case header.IPv4Version:
-			p = header.IPv4ProtocolNumber
-		case header.IPv6Version:
-			p = header.IPv6ProtocolNumber
-		default:
-			continue
-		}
-		e.dispatcher.DeliverNetworkPacket(p, pkt)
-	}
-}
-
-func (e *PosixEndpoint) IsAttached() bool {
-	return e.dispatcher != nil
-}
-
-func (e *PosixEndpoint) Wait() {
-}
-
-func (e *PosixEndpoint) ARPHardwareType() header.ARPHardwareType {
-	return header.ARPHardwareNone
-}
-
-func (e *PosixEndpoint) AddHeader(buffer *stack.PacketBuffer) {
-}
-
-func (e *PosixEndpoint) WritePackets(pkts stack.PacketBufferList) (int, tcpip.Error) {
-	var n int
-	for _, packet := range pkts.AsSlice() {
-		_, err := rw.WriteV(e.fd, packet.Slices())
-		if err != nil {
-			return n, &tcpip.ErrAborted{}
-		}
-		n++
-	}
-	return n, nil
-}

+ 0 - 12
common/tun/tun.go

@@ -1,12 +0,0 @@
-package tun
-
-import (
-	E "github.com/sagernet/sing/common/exceptions"
-	N "github.com/sagernet/sing/common/network"
-)
-
-type Handler interface {
-	N.TCPConnectionHandler
-	N.UDPConnectionHandler
-	E.Handler
-}

+ 0 - 113
common/tun/tun_linux.go

@@ -1,113 +0,0 @@
-package tun
-
-import (
-	"net"
-	"net/netip"
-
-	"github.com/vishvananda/netlink"
-	"gvisor.dev/gvisor/pkg/tcpip/link/tun"
-)
-
-func Open(name string) (uintptr, error) {
-	tunFd, err := tun.Open(name)
-	if err != nil {
-		return 0, err
-	}
-	return uintptr(tunFd), nil
-}
-
-func Configure(name string, inet4Address netip.Prefix, inet6Address netip.Prefix, mtu uint32, autoRoute bool) error {
-	tunLink, err := netlink.LinkByName(name)
-	if err != nil {
-		return err
-	}
-
-	if inet4Address.IsValid() {
-		addr4, _ := netlink.ParseAddr(inet4Address.String())
-		err = netlink.AddrAdd(tunLink, addr4)
-		if err != nil {
-			return err
-		}
-	}
-
-	if inet6Address.IsValid() {
-		addr6, _ := netlink.ParseAddr(inet6Address.String())
-		err = netlink.AddrAdd(tunLink, addr6)
-		if err != nil {
-			return err
-		}
-	}
-
-	err = netlink.LinkSetMTU(tunLink, int(mtu))
-	if err != nil {
-		return err
-	}
-
-	err = netlink.LinkSetUp(tunLink)
-	if err != nil {
-		return err
-	}
-
-	if autoRoute {
-		if inet4Address.IsValid() {
-			err = netlink.RouteAdd(&netlink.Route{
-				Dst: &net.IPNet{
-					IP:   net.IPv4zero,
-					Mask: net.CIDRMask(0, 32),
-				},
-				LinkIndex: tunLink.Attrs().Index,
-			})
-			if err != nil {
-				return err
-			}
-		}
-		if inet6Address.IsValid() {
-			err = netlink.RouteAdd(&netlink.Route{
-				Dst: &net.IPNet{
-					IP:   net.IPv6zero,
-					Mask: net.CIDRMask(0, 128),
-				},
-				LinkIndex: tunLink.Attrs().Index,
-			})
-			if err != nil {
-				return err
-			}
-		}
-	}
-
-	return nil
-}
-
-func UnConfigure(name string, inet4Address netip.Prefix, inet6Address netip.Prefix, autoRoute bool) error {
-	if autoRoute {
-		tunLink, err := netlink.LinkByName(name)
-		if err != nil {
-			return err
-		}
-		if inet4Address.IsValid() {
-			err = netlink.RouteDel(&netlink.Route{
-				Dst: &net.IPNet{
-					IP:   net.IPv4zero,
-					Mask: net.CIDRMask(0, 32),
-				},
-				LinkIndex: tunLink.Attrs().Index,
-			})
-			if err != nil {
-				return err
-			}
-		}
-		if inet6Address.IsValid() {
-			err = netlink.RouteDel(&netlink.Route{
-				Dst: &net.IPNet{
-					IP:   net.IPv6zero,
-					Mask: net.CIDRMask(0, 128),
-				},
-				LinkIndex: tunLink.Attrs().Index,
-			})
-			if err != nil {
-				return err
-			}
-		}
-	}
-	return nil
-}

+ 0 - 20
common/tun/tun_other.go

@@ -1,20 +0,0 @@
-//go:build !linux
-
-package tun
-
-import (
-	"net/netip"
-	"os"
-)
-
-func Open(name string) (uintptr, error) {
-	return 0, os.ErrInvalid
-}
-
-func Configure(name string, inet4Address netip.Prefix, inet6Address netip.Prefix, mtu uint32, autoRoute bool) error {
-	return os.ErrInvalid
-}
-
-func UnConfigure(name string, inet4Address netip.Prefix, inet6Address netip.Prefix, autoRoute bool) error {
-	return os.ErrInvalid
-}

+ 0 - 379
dns/client.go

@@ -1,379 +0,0 @@
-package dns
-
-import (
-	"context"
-	"net"
-	"net/netip"
-	"strings"
-	"time"
-
-	"github.com/sagernet/sing-box/adapter"
-	C "github.com/sagernet/sing-box/constant"
-	"github.com/sagernet/sing-box/option"
-	"github.com/sagernet/sing/common"
-	"github.com/sagernet/sing/common/cache"
-	E "github.com/sagernet/sing/common/exceptions"
-	"github.com/sagernet/sing/common/task"
-
-	"golang.org/x/net/dns/dnsmessage"
-)
-
-const DefaultTTL = 600
-
-var (
-	ErrNoRawSupport = E.New("no raw query support by current transport")
-	ErrNotCached    = E.New("not cached")
-)
-
-var _ adapter.DNSClient = (*Client)(nil)
-
-type Client struct {
-	cache         *cache.LruCache[dnsmessage.Question, *dnsmessage.Message]
-	disableCache  bool
-	disableExpire bool
-	strategy      C.DomainStrategy
-}
-
-func NewClient(options option.DNSClientOptions) *Client {
-	client := &Client{
-		disableCache:  options.DisableCache,
-		disableExpire: options.DisableExpire,
-		strategy:      C.DomainStrategy(options.Strategy),
-	}
-	if !options.DisableCache {
-		client.cache = cache.New[dnsmessage.Question, *dnsmessage.Message]()
-	}
-	return client
-}
-
-func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, message *dnsmessage.Message) (*dnsmessage.Message, error) {
-	if len(message.Questions) != 1 {
-		responseMessage := dnsmessage.Message{
-			Header: dnsmessage.Header{
-				ID:               message.ID,
-				RCode:            dnsmessage.RCodeFormatError,
-				Response:         true,
-				RecursionDesired: true,
-			},
-		}
-		return &responseMessage, nil
-	}
-	question := message.Questions[0]
-	if !c.disableCache {
-		cachedAnswer, cached := c.cache.Load(question)
-		if cached {
-			cachedAnswer.ID = message.ID
-			return cachedAnswer, nil
-		}
-	}
-	if !transport.Raw() {
-		if question.Type == dnsmessage.TypeA || question.Type == dnsmessage.TypeAAAA {
-			return c.exchangeToLookup(ctx, transport, message, question)
-		}
-		return nil, ErrNoRawSupport
-	}
-	if question.Type == dnsmessage.TypeA && c.strategy == C.DomainStrategyUseIPv6 || question.Type == dnsmessage.TypeAAAA && c.strategy == C.DomainStrategyUseIPv4 {
-		responseMessage := dnsmessage.Message{
-			Header: dnsmessage.Header{
-				ID:               message.ID,
-				RCode:            dnsmessage.RCodeNameError,
-				Response:         true,
-				RecursionDesired: true,
-			},
-			Questions: []dnsmessage.Question{question},
-		}
-		return &responseMessage, nil
-	}
-	messageId := message.ID
-	response, err := transport.Exchange(ctx, message)
-	if err != nil {
-		return nil, err
-	}
-	response.ID = messageId
-	if !c.disableCache {
-		c.storeCache(question, response)
-	}
-	return response, err
-}
-
-func (c *Client) Lookup(ctx context.Context, transport adapter.DNSTransport, domain string, strategy C.DomainStrategy) ([]netip.Addr, error) {
-	if strings.HasPrefix(domain, ".") {
-		domain = domain[:len(domain)-1]
-	}
-	dnsName, err := dnsmessage.NewName(domain + ".")
-	if err != nil {
-		return nil, wrapError(err)
-	}
-	if transport.Raw() {
-		if strategy == C.DomainStrategyUseIPv4 {
-			return c.lookupToExchange(ctx, transport, dnsName, dnsmessage.TypeA)
-		} else if strategy == C.DomainStrategyUseIPv6 {
-			return c.lookupToExchange(ctx, transport, dnsName, dnsmessage.TypeAAAA)
-		}
-		var response4 []netip.Addr
-		var response6 []netip.Addr
-		err = task.Run(ctx, func() error {
-			response, err := c.lookupToExchange(ctx, transport, dnsName, dnsmessage.TypeA)
-			if err != nil {
-				return err
-			}
-			response4 = response
-			return nil
-		}, func() error {
-			response, err := c.lookupToExchange(ctx, transport, dnsName, dnsmessage.TypeAAAA)
-			if err != nil {
-				return err
-			}
-			response6 = response
-			return nil
-		})
-		if len(response4) == 0 && len(response6) == 0 {
-			return nil, err
-		}
-		return sortAddresses(response4, response6, strategy), nil
-	}
-	if !c.disableCache {
-		if strategy == C.DomainStrategyUseIPv4 {
-			response, err := c.questionCache(dnsmessage.Question{
-				Name:  dnsName,
-				Type:  dnsmessage.TypeA,
-				Class: dnsmessage.ClassINET,
-			})
-			if err != ErrNotCached {
-				return response, err
-			}
-		} else if strategy == C.DomainStrategyUseIPv6 {
-			response, err := c.questionCache(dnsmessage.Question{
-				Name:  dnsName,
-				Type:  dnsmessage.TypeAAAA,
-				Class: dnsmessage.ClassINET,
-			})
-			if err != ErrNotCached {
-				return response, err
-			}
-		} else {
-			response4, _ := c.questionCache(dnsmessage.Question{
-				Name:  dnsName,
-				Type:  dnsmessage.TypeA,
-				Class: dnsmessage.ClassINET,
-			})
-			response6, _ := c.questionCache(dnsmessage.Question{
-				Name:  dnsName,
-				Type:  dnsmessage.TypeAAAA,
-				Class: dnsmessage.ClassINET,
-			})
-			if len(response4) > 0 || len(response6) > 0 {
-				return sortAddresses(response4, response6, strategy), nil
-			}
-		}
-	}
-	var rCode dnsmessage.RCode
-	response, err := transport.Lookup(ctx, domain, strategy)
-	if err != nil {
-		err = wrapError(err)
-		if rCodeError, isRCodeError := err.(RCodeError); !isRCodeError {
-			return nil, err
-		} else {
-			rCode = dnsmessage.RCode(rCodeError)
-		}
-		if c.disableCache {
-			return nil, err
-		}
-	}
-	header := dnsmessage.Header{
-		Response:      true,
-		Authoritative: true,
-		RCode:         rCode,
-	}
-	if !c.disableCache {
-		if strategy != C.DomainStrategyUseIPv6 {
-			question4 := dnsmessage.Question{
-				Name:  dnsName,
-				Type:  dnsmessage.TypeA,
-				Class: dnsmessage.ClassINET,
-			}
-			response4 := common.Filter(response, func(addr netip.Addr) bool {
-				return addr.Is4() || addr.Is4In6()
-			})
-			message4 := &dnsmessage.Message{
-				Header:    header,
-				Questions: []dnsmessage.Question{question4},
-			}
-			if len(response4) > 0 {
-				for _, address := range response4 {
-					message4.Answers = append(message4.Answers, dnsmessage.Resource{
-						Header: dnsmessage.ResourceHeader{
-							Name:  question4.Name,
-							Class: question4.Class,
-							TTL:   DefaultTTL,
-						},
-						Body: &dnsmessage.AResource{
-							A: address.As4(),
-						},
-					})
-				}
-			}
-			c.storeCache(question4, message4)
-		}
-		if strategy != C.DomainStrategyUseIPv4 {
-			question6 := dnsmessage.Question{
-				Name:  dnsName,
-				Type:  dnsmessage.TypeAAAA,
-				Class: dnsmessage.ClassINET,
-			}
-			response6 := common.Filter(response, func(addr netip.Addr) bool {
-				return addr.Is6() && !addr.Is4In6()
-			})
-			message6 := &dnsmessage.Message{
-				Header:    header,
-				Questions: []dnsmessage.Question{question6},
-			}
-			if len(response6) > 0 {
-				for _, address := range response6 {
-					message6.Answers = append(message6.Answers, dnsmessage.Resource{
-						Header: dnsmessage.ResourceHeader{
-							Name:  question6.Name,
-							Class: question6.Class,
-							TTL:   DefaultTTL,
-						},
-						Body: &dnsmessage.AAAAResource{
-							AAAA: address.As16(),
-						},
-					})
-				}
-			}
-			c.storeCache(question6, message6)
-		}
-	}
-	return response, err
-}
-
-func sortAddresses(response4 []netip.Addr, response6 []netip.Addr, strategy C.DomainStrategy) []netip.Addr {
-	if strategy == C.DomainStrategyPreferIPv6 {
-		return append(response6, response4...)
-	} else {
-		return append(response4, response6...)
-	}
-}
-
-func (c *Client) storeCache(question dnsmessage.Question, message *dnsmessage.Message) {
-	if c.disableExpire {
-		c.cache.Store(question, message)
-		return
-	}
-	timeToLive := DefaultTTL
-	for _, answer := range message.Answers {
-		if int(answer.Header.TTL) < timeToLive {
-			timeToLive = int(answer.Header.TTL)
-		}
-	}
-	expire := time.Now().Add(time.Second * time.Duration(timeToLive))
-	c.cache.StoreWithExpire(question, message, expire)
-}
-
-func (c *Client) exchangeToLookup(ctx context.Context, transport adapter.DNSTransport, message *dnsmessage.Message, question dnsmessage.Question) (*dnsmessage.Message, error) {
-	domain := question.Name.String()
-	var strategy C.DomainStrategy
-	if question.Type == dnsmessage.TypeA {
-		strategy = C.DomainStrategyUseIPv4
-	} else {
-		strategy = C.DomainStrategyUseIPv6
-	}
-	var rCode dnsmessage.RCode
-	result, err := c.Lookup(ctx, transport, domain, strategy)
-	if err != nil {
-		err = wrapError(err)
-		if rCodeError, isRCodeError := err.(RCodeError); !isRCodeError {
-			return nil, err
-		} else {
-			rCode = dnsmessage.RCode(rCodeError)
-		}
-	}
-	response := dnsmessage.Message{
-		Header: dnsmessage.Header{
-			ID:                 message.ID,
-			RCode:              rCode,
-			RecursionAvailable: true,
-			RecursionDesired:   true,
-			Response:           true,
-		},
-		Questions: message.Questions,
-	}
-	for _, address := range result {
-		var resource dnsmessage.Resource
-		resource.Header = dnsmessage.ResourceHeader{
-			Name:  question.Name,
-			Class: question.Class,
-			TTL:   DefaultTTL,
-		}
-		if address.Is4() || address.Is4In6() {
-			resource.Body = &dnsmessage.AResource{
-				A: address.As4(),
-			}
-		} else {
-			resource.Body = &dnsmessage.AAAAResource{
-				AAAA: address.As16(),
-			}
-		}
-	}
-	return &response, nil
-}
-
-func (c *Client) lookupToExchange(ctx context.Context, transport adapter.DNSTransport, name dnsmessage.Name, qType dnsmessage.Type) ([]netip.Addr, error) {
-	question := dnsmessage.Question{
-		Name:  name,
-		Type:  qType,
-		Class: dnsmessage.ClassINET,
-	}
-	if !c.disableCache {
-		cachedAddresses, err := c.questionCache(question)
-		if err != ErrNotCached {
-			return cachedAddresses, err
-		}
-	}
-	message := dnsmessage.Message{
-		Header: dnsmessage.Header{
-			ID:               0,
-			RecursionDesired: true,
-		},
-		Questions: []dnsmessage.Question{question},
-	}
-	response, err := c.Exchange(ctx, transport, &message)
-	if err != nil {
-		return nil, err
-	}
-	return messageToAddresses(response)
-}
-
-func (c *Client) questionCache(question dnsmessage.Question) ([]netip.Addr, error) {
-	response, cached := c.cache.Load(question)
-	if !cached {
-		return nil, ErrNotCached
-	}
-	return messageToAddresses(response)
-}
-
-func messageToAddresses(response *dnsmessage.Message) ([]netip.Addr, error) {
-	if response.RCode != dnsmessage.RCodeSuccess {
-		return nil, RCodeError(response.RCode)
-	}
-	addresses := make([]netip.Addr, 0, len(response.Answers))
-	for _, answer := range response.Answers {
-		switch resource := answer.Body.(type) {
-		case *dnsmessage.AResource:
-			addresses = append(addresses, netip.AddrFrom4(resource.A))
-		case *dnsmessage.AAAAResource:
-			addresses = append(addresses, netip.AddrFrom16(resource.AAAA))
-		}
-	}
-	return addresses, nil
-}
-
-func wrapError(err error) error {
-	if dnsErr, isDNSError := err.(*net.DNSError); isDNSError {
-		if dnsErr.IsNotFound {
-			return RCodeNameError
-		}
-	}
-	return err
-}

+ 0 - 33
dns/client_test.go

@@ -1,33 +0,0 @@
-package dns_test
-
-import (
-	"context"
-	"testing"
-	"time"
-
-	C "github.com/sagernet/sing-box/constant"
-	"github.com/sagernet/sing-box/dns"
-	"github.com/sagernet/sing-box/log"
-	"github.com/sagernet/sing-box/option"
-	M "github.com/sagernet/sing/common/metadata"
-	N "github.com/sagernet/sing/common/network"
-
-	"github.com/stretchr/testify/require"
-)
-
-func TestClient(t *testing.T) {
-	t.Parallel()
-	ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
-	client := dns.NewClient(option.DNSClientOptions{})
-	dnsTransport := dns.NewTCPTransport(context.Background(), N.SystemDialer, log.NewNopLogger(), M.ParseSocksaddr("1.0.0.1:53"))
-	response, err := client.Exchange(ctx, dnsTransport, makeQuery())
-	require.NoError(t, err)
-	require.NotEmpty(t, response.Answers, "no answers")
-	response, err = client.Exchange(ctx, dnsTransport, makeQuery())
-	require.NoError(t, err)
-	require.NotEmpty(t, response.Answers, "no answers")
-	addresses, err := client.Lookup(ctx, dnsTransport, "www.google.com", C.DomainStrategyAsIS)
-	require.NoError(t, err)
-	require.NotEmpty(t, addresses, "no answers")
-	cancel()
-}

+ 0 - 49
dns/dialer.go

@@ -1,49 +0,0 @@
-package dns
-
-import (
-	"context"
-	"net"
-
-	"github.com/sagernet/sing-box/adapter"
-	"github.com/sagernet/sing-box/common/dialer"
-	C "github.com/sagernet/sing-box/constant"
-	M "github.com/sagernet/sing/common/metadata"
-	N "github.com/sagernet/sing/common/network"
-)
-
-type DialerWrapper struct {
-	dialer    N.Dialer
-	strategy  C.DomainStrategy
-	client    adapter.DNSClient
-	transport adapter.DNSTransport
-}
-
-func NewDialerWrapper(dialer N.Dialer, strategy C.DomainStrategy, client adapter.DNSClient, transport adapter.DNSTransport) N.Dialer {
-	return &DialerWrapper{dialer, strategy, client, transport}
-}
-
-func (d *DialerWrapper) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
-	if destination.IsIP() {
-		return d.dialer.DialContext(ctx, network, destination)
-	}
-	addresses, err := d.client.Lookup(ctx, d.transport, destination.Fqdn, d.strategy)
-	if err != nil {
-		return nil, err
-	}
-	return dialer.DialSerial(ctx, d.dialer, network, destination, addresses)
-}
-
-func (d *DialerWrapper) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
-	if destination.IsIP() {
-		return d.dialer.ListenPacket(ctx, destination)
-	}
-	addresses, err := d.client.Lookup(ctx, d.transport, destination.Fqdn, d.strategy)
-	if err != nil {
-		return nil, err
-	}
-	return dialer.ListenSerial(ctx, d.dialer, destination, addresses)
-}
-
-func (d *DialerWrapper) Upstream() any {
-	return d.dialer
-}

+ 0 - 33
dns/rcode.go

@@ -1,33 +0,0 @@
-package dns
-
-import F "github.com/sagernet/sing/common/format"
-
-const (
-	RCodeSuccess        RCodeError = 0 // NoError
-	RCodeFormatError    RCodeError = 1 // FormErr
-	RCodeServerFailure  RCodeError = 2 // ServFail
-	RCodeNameError      RCodeError = 3 // NXDomain
-	RCodeNotImplemented RCodeError = 4 // NotImp
-	RCodeRefused        RCodeError = 5 // Refused
-)
-
-type RCodeError uint16
-
-func (e RCodeError) Error() string {
-	switch e {
-	case RCodeSuccess:
-		return "success"
-	case RCodeFormatError:
-		return "format error"
-	case RCodeServerFailure:
-		return "server failure"
-	case RCodeNameError:
-		return "name error"
-	case RCodeNotImplemented:
-		return "not implemented"
-	case RCodeRefused:
-		return "refused"
-	default:
-		return F.ToString("unknown error: ", uint16(e))
-	}
-}

+ 0 - 50
dns/transport.go

@@ -1,50 +0,0 @@
-package dns
-
-import (
-	"context"
-	"net/url"
-
-	"github.com/sagernet/sing-box/adapter"
-	"github.com/sagernet/sing-box/log"
-	E "github.com/sagernet/sing/common/exceptions"
-	M "github.com/sagernet/sing/common/metadata"
-	N "github.com/sagernet/sing/common/network"
-)
-
-func NewTransport(ctx context.Context, dialer N.Dialer, logger log.Logger, address string) (adapter.DNSTransport, error) {
-	if address == "local" {
-		return NewLocalTransport(), nil
-	}
-	serverURL, err := url.Parse(address)
-	if err != nil {
-		return nil, err
-	}
-	host := serverURL.Hostname()
-	if host == "" {
-		host = address
-	}
-	port := serverURL.Port()
-	switch serverURL.Scheme {
-	case "tls":
-		if port == "" {
-			port = "853"
-		}
-	default:
-		if port == "" {
-			port = "53"
-		}
-	}
-	destination := M.ParseSocksaddrHostPortStr(host, port)
-	switch serverURL.Scheme {
-	case "", "udp":
-		return NewUDPTransport(ctx, dialer, logger, destination), nil
-	case "tcp":
-		return NewTCPTransport(ctx, dialer, logger, destination), nil
-	case "tls":
-		return NewTLSTransport(ctx, dialer, logger, destination), nil
-	case "https":
-		return NewHTTPSTransport(dialer, serverURL.String()), nil
-	default:
-		return nil, E.New("unknown dns scheme: " + serverURL.Scheme)
-	}
-}

+ 0 - 45
dns/transport_base.go

@@ -1,45 +0,0 @@
-package dns
-
-import (
-	"context"
-	"net/netip"
-	"os"
-	"sync"
-
-	C "github.com/sagernet/sing-box/constant"
-	"github.com/sagernet/sing-box/log"
-	M "github.com/sagernet/sing/common/metadata"
-	N "github.com/sagernet/sing/common/network"
-)
-
-type myTransportAdapter struct {
-	ctx         context.Context
-	dialer      N.Dialer
-	logger      log.Logger
-	destination M.Socksaddr
-	done        chan struct{}
-	access      sync.RWMutex
-	connection  *dnsConnection
-}
-
-func (t *myTransportAdapter) Start() error {
-	return nil
-}
-
-func (t *myTransportAdapter) Close() error {
-	select {
-	case <-t.done:
-		return os.ErrClosed
-	default:
-	}
-	close(t.done)
-	return nil
-}
-
-func (t *myTransportAdapter) Raw() bool {
-	return true
-}
-
-func (t *myTransportAdapter) Lookup(ctx context.Context, domain string, strategy C.DomainStrategy) ([]netip.Addr, error) {
-	return nil, os.ErrInvalid
-}

+ 0 - 94
dns/transport_https.go

@@ -1,94 +0,0 @@
-package dns
-
-import (
-	"bytes"
-	"context"
-	"net"
-	"net/http"
-	"net/netip"
-	"os"
-
-	"github.com/sagernet/sing-box/adapter"
-	C "github.com/sagernet/sing-box/constant"
-	"github.com/sagernet/sing/common"
-	"github.com/sagernet/sing/common/buf"
-	M "github.com/sagernet/sing/common/metadata"
-	N "github.com/sagernet/sing/common/network"
-
-	"golang.org/x/net/dns/dnsmessage"
-)
-
-const dnsMimeType = "application/dns-message"
-
-var _ adapter.DNSTransport = (*HTTPSTransport)(nil)
-
-type HTTPSTransport struct {
-	destination string
-	transport   *http.Transport
-}
-
-func NewHTTPSTransport(dialer N.Dialer, destination string) *HTTPSTransport {
-	return &HTTPSTransport{
-		destination: destination,
-		transport: &http.Transport{
-			ForceAttemptHTTP2: true,
-			DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
-				return dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
-			},
-		},
-	}
-}
-
-func (t *HTTPSTransport) Start() error {
-	return nil
-}
-
-func (t *HTTPSTransport) Close() error {
-	t.transport.CloseIdleConnections()
-	return nil
-}
-
-func (t *HTTPSTransport) Raw() bool {
-	return true
-}
-
-func (t *HTTPSTransport) Exchange(ctx context.Context, message *dnsmessage.Message) (*dnsmessage.Message, error) {
-	message.ID = 0
-	_buffer := buf.StackNewSize(1024)
-	defer common.KeepAlive(_buffer)
-	buffer := common.Dup(_buffer)
-	defer buffer.Release()
-	rawMessage, err := message.AppendPack(buffer.Index(0))
-	if err != nil {
-		return nil, err
-	}
-	buffer.Truncate(len(rawMessage))
-	request, err := http.NewRequestWithContext(ctx, http.MethodPost, t.destination, bytes.NewReader(buffer.Bytes()))
-	if err != nil {
-		return nil, err
-	}
-	request.Header.Set("content-type", dnsMimeType)
-	request.Header.Set("accept", dnsMimeType)
-
-	client := &http.Client{Transport: t.transport}
-	response, err := client.Do(request)
-	if err != nil {
-		return nil, err
-	}
-	defer response.Body.Close()
-	buffer.FullReset()
-	_, err = buffer.ReadAllFrom(response.Body)
-	if err != nil {
-		return nil, err
-	}
-	var responseMessage dnsmessage.Message
-	err = responseMessage.Unpack(buffer.Bytes())
-	if err != nil {
-		return nil, err
-	}
-	return &responseMessage, nil
-}
-
-func (t *HTTPSTransport) Lookup(ctx context.Context, domain string, strategy C.DomainStrategy) ([]netip.Addr, error) {
-	return nil, os.ErrInvalid
-}

+ 0 - 79
dns/transport_local.go

@@ -1,79 +0,0 @@
-package dns
-
-import (
-	"context"
-	"net"
-	"net/netip"
-	"os"
-	"sort"
-
-	"github.com/sagernet/sing-box/adapter"
-	C "github.com/sagernet/sing-box/constant"
-	"github.com/sagernet/sing/common"
-
-	"golang.org/x/net/dns/dnsmessage"
-)
-
-var LocalTransportConstructor func() adapter.DNSTransport
-
-func NewLocalTransport() adapter.DNSTransport {
-	if LocalTransportConstructor != nil {
-		return LocalTransportConstructor()
-	}
-	return &LocalTransport{}
-}
-
-var _ adapter.DNSTransport = (*LocalTransport)(nil)
-
-type LocalTransport struct {
-	resolver net.Resolver
-}
-
-func (t *LocalTransport) Start() error {
-	return nil
-}
-
-func (t *LocalTransport) Close() error {
-	return nil
-}
-
-func (t *LocalTransport) Raw() bool {
-	return false
-}
-
-func (t *LocalTransport) Exchange(ctx context.Context, message *dnsmessage.Message) (*dnsmessage.Message, error) {
-	return nil, os.ErrInvalid
-}
-
-func (t *LocalTransport) Lookup(ctx context.Context, domain string, strategy C.DomainStrategy) ([]netip.Addr, error) {
-	var network string
-	switch strategy {
-	case C.DomainStrategyAsIS, C.DomainStrategyPreferIPv4, C.DomainStrategyPreferIPv6:
-		network = "ip"
-	case C.DomainStrategyUseIPv4:
-		network = "ip4"
-	case C.DomainStrategyUseIPv6:
-		network = "ip6"
-	}
-	addrs, err := t.resolver.LookupNetIP(ctx, network, domain)
-	if err != nil {
-		return nil, err
-	}
-	addrs = common.Map(addrs, func(it netip.Addr) netip.Addr {
-		if it.Is4In6() {
-			return netip.AddrFrom4(it.As4())
-		}
-		return it
-	})
-	switch strategy {
-	case C.DomainStrategyPreferIPv4:
-		sort.Slice(addrs, func(i, j int) bool {
-			return addrs[i].Is4() && addrs[j].Is6()
-		})
-	case C.DomainStrategyPreferIPv6:
-		sort.Slice(addrs, func(i, j int) bool {
-			return addrs[i].Is6() && addrs[j].Is4()
-		})
-	}
-	return addrs, nil
-}

+ 0 - 183
dns/transport_tcp.go

@@ -1,183 +0,0 @@
-package dns
-
-import (
-	"context"
-	"encoding/binary"
-	"net"
-	"os"
-	"sync"
-
-	"github.com/sagernet/sing-box/adapter"
-	"github.com/sagernet/sing-box/log"
-	"github.com/sagernet/sing/common"
-	"github.com/sagernet/sing/common/buf"
-	E "github.com/sagernet/sing/common/exceptions"
-	M "github.com/sagernet/sing/common/metadata"
-	N "github.com/sagernet/sing/common/network"
-	"github.com/sagernet/sing/common/task"
-
-	"golang.org/x/net/dns/dnsmessage"
-)
-
-var _ adapter.DNSTransport = (*TCPTransport)(nil)
-
-type TCPTransport struct {
-	myTransportAdapter
-}
-
-func NewTCPTransport(ctx context.Context, dialer N.Dialer, logger log.Logger, destination M.Socksaddr) *TCPTransport {
-	return &TCPTransport{
-		myTransportAdapter{
-			ctx:         ctx,
-			dialer:      dialer,
-			logger:      logger,
-			destination: destination,
-			done:        make(chan struct{}),
-		},
-	}
-}
-
-func (t *TCPTransport) offer() (*dnsConnection, error) {
-	t.access.RLock()
-	connection := t.connection
-	t.access.RUnlock()
-	if connection != nil {
-		select {
-		case <-connection.done:
-		default:
-			return connection, nil
-		}
-	}
-	t.access.Lock()
-	connection = t.connection
-	if connection != nil {
-		select {
-		case <-connection.done:
-		default:
-			t.access.Unlock()
-			return connection, nil
-		}
-	}
-	tcpConn, err := t.dialer.DialContext(t.ctx, "tcp", t.destination)
-	if err != nil {
-		return nil, err
-	}
-	connection = &dnsConnection{
-		Conn:      tcpConn,
-		done:      make(chan struct{}),
-		callbacks: make(map[uint16]chan *dnsmessage.Message),
-	}
-	t.connection = connection
-	t.access.Unlock()
-	go t.newConnection(connection)
-	return connection, nil
-}
-
-func (t *TCPTransport) newConnection(conn *dnsConnection) {
-	defer close(conn.done)
-	defer conn.Close()
-	err := task.Any(t.ctx, func(ctx context.Context) error {
-		return t.loopIn(conn)
-	}, func(ctx context.Context) error {
-		select {
-		case <-ctx.Done():
-			return nil
-		case <-t.done:
-			return os.ErrClosed
-		}
-	})
-	conn.err = err
-	if err != nil && !E.IsClosed(err) {
-		t.logger.Debug("connection closed: ", err)
-	}
-}
-
-func (t *TCPTransport) loopIn(conn *dnsConnection) error {
-	_buffer := buf.StackNewSize(1024)
-	defer common.KeepAlive(_buffer)
-	buffer := common.Dup(_buffer)
-	defer buffer.Release()
-	for {
-		buffer.FullReset()
-		_, err := buffer.ReadFullFrom(conn, 2)
-		if err != nil {
-			return err
-		}
-		length := binary.BigEndian.Uint16(buffer.Bytes())
-		if length > 512 {
-			return E.New("invalid length received: ", length)
-		}
-		buffer.FullReset()
-		_, err = buffer.ReadFullFrom(conn, int(length))
-		if err != nil {
-			return err
-		}
-		var message dnsmessage.Message
-		err = message.Unpack(buffer.Bytes())
-		if err != nil {
-			return err
-		}
-		conn.access.Lock()
-		callback, loaded := conn.callbacks[message.ID]
-		if loaded {
-			delete(conn.callbacks, message.ID)
-		}
-		conn.access.Unlock()
-		if !loaded {
-			continue
-		}
-		callback <- &message
-	}
-}
-
-type dnsConnection struct {
-	net.Conn
-	done      chan struct{}
-	err       error
-	access    sync.Mutex
-	queryId   uint16
-	callbacks map[uint16]chan *dnsmessage.Message
-}
-
-func (t *TCPTransport) Exchange(ctx context.Context, message *dnsmessage.Message) (*dnsmessage.Message, error) {
-	var connection *dnsConnection
-	err := task.Run(ctx, func() error {
-		var innerErr error
-		connection, innerErr = t.offer()
-		return innerErr
-	})
-	if err != nil {
-		return nil, err
-	}
-	connection.access.Lock()
-	connection.queryId++
-	message.ID = connection.queryId
-	callback := make(chan *dnsmessage.Message)
-	connection.callbacks[message.ID] = callback
-	connection.access.Unlock()
-	_buffer := buf.StackNewSize(1024)
-	defer common.KeepAlive(_buffer)
-	buffer := common.Dup(_buffer)
-	defer buffer.Release()
-	length := buffer.Extend(2)
-	rawMessage, err := message.AppendPack(buffer.Index(2))
-	if err != nil {
-		return nil, err
-	}
-	buffer.Truncate(2 + len(rawMessage))
-	binary.BigEndian.PutUint16(length, uint16(len(rawMessage)))
-	err = task.Run(ctx, func() error {
-		return common.Error(connection.Write(buffer.Bytes()))
-	})
-	if err != nil {
-		return nil, err
-	}
-	select {
-	case response := <-callback:
-		return response, nil
-	case <-connection.done:
-		return nil, connection.err
-	case <-ctx.Done():
-		return nil, ctx.Err()
-	}
-}

+ 0 - 93
dns/transport_test.go

@@ -1,93 +0,0 @@
-package dns_test
-
-import (
-	"context"
-	"testing"
-	"time"
-
-	C "github.com/sagernet/sing-box/constant"
-	"github.com/sagernet/sing-box/dns"
-	"github.com/sagernet/sing-box/log"
-	M "github.com/sagernet/sing/common/metadata"
-	N "github.com/sagernet/sing/common/network"
-
-	"github.com/stretchr/testify/require"
-	"golang.org/x/net/dns/dnsmessage"
-)
-
-func TestTCPDNS(t *testing.T) {
-	t.Parallel()
-	ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
-	transport := dns.NewTCPTransport(ctx, N.SystemDialer, log.NewNopLogger(), M.ParseSocksaddr("1.0.0.1:53"))
-	response, err := transport.Exchange(ctx, makeQuery())
-	cancel()
-	require.NoError(t, err)
-	require.NotEmpty(t, response.Answers, "no answers")
-	for _, answer := range response.Answers {
-		t.Log(answer)
-	}
-}
-
-func TestTLSDNS(t *testing.T) {
-	t.Parallel()
-	ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
-	transport := dns.NewTLSTransport(ctx, N.SystemDialer, log.NewNopLogger(), M.ParseSocksaddr("1.0.0.1:853"))
-	response, err := transport.Exchange(ctx, makeQuery())
-	cancel()
-	require.NoError(t, err)
-	require.NotEmpty(t, response.Answers, "no answers")
-	for _, answer := range response.Answers {
-		t.Log(answer)
-	}
-}
-
-func TestHTTPSDNS(t *testing.T) {
-	t.Parallel()
-	ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
-	transport := dns.NewHTTPSTransport(N.SystemDialer, "https://1.0.0.1:443/dns-query")
-	response, err := transport.Exchange(ctx, makeQuery())
-	cancel()
-	require.NoError(t, err)
-	require.NotEmpty(t, response.Answers, "no answers")
-	for _, answer := range response.Answers {
-		t.Log(answer)
-	}
-}
-
-func TestUDPDNS(t *testing.T) {
-	t.Parallel()
-	ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
-	transport := dns.NewUDPTransport(ctx, N.SystemDialer, log.NewNopLogger(), M.ParseSocksaddr("1.0.0.1:53"))
-	response, err := transport.Exchange(ctx, makeQuery())
-	cancel()
-	require.NoError(t, err)
-	require.NotEmpty(t, response.Answers, "no answers")
-	for _, answer := range response.Answers {
-		t.Log(answer)
-	}
-}
-
-func TestLocalDNS(t *testing.T) {
-	t.Parallel()
-	ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
-	transport := dns.NewLocalTransport()
-	response, err := transport.Lookup(ctx, "google.com", C.DomainStrategyAsIS)
-	cancel()
-	require.NoError(t, err)
-	require.NotEmpty(t, response, "no answers")
-	for _, answer := range response {
-		t.Log(answer)
-	}
-}
-
-func makeQuery() *dnsmessage.Message {
-	message := &dnsmessage.Message{}
-	message.Header.ID = 1
-	message.Header.RecursionDesired = true
-	message.Questions = append(message.Questions, dnsmessage.Question{
-		Name:  dnsmessage.MustNewName("google.com."),
-		Type:  dnsmessage.TypeA,
-		Class: dnsmessage.ClassINET,
-	})
-	return message
-}

+ 0 - 182
dns/transport_tls.go

@@ -1,182 +0,0 @@
-package dns
-
-import (
-	"context"
-	"crypto/tls"
-	"encoding/binary"
-	"os"
-
-	"github.com/sagernet/sing-box/adapter"
-	"github.com/sagernet/sing-box/log"
-	"github.com/sagernet/sing/common"
-	"github.com/sagernet/sing/common/buf"
-	E "github.com/sagernet/sing/common/exceptions"
-	M "github.com/sagernet/sing/common/metadata"
-	N "github.com/sagernet/sing/common/network"
-	"github.com/sagernet/sing/common/task"
-
-	"golang.org/x/net/dns/dnsmessage"
-)
-
-var _ adapter.DNSTransport = (*TLSTransport)(nil)
-
-type TLSTransport struct {
-	myTransportAdapter
-}
-
-func NewTLSTransport(ctx context.Context, dialer N.Dialer, logger log.Logger, destination M.Socksaddr) *TLSTransport {
-	return &TLSTransport{
-		myTransportAdapter{
-			ctx:         ctx,
-			dialer:      dialer,
-			logger:      logger,
-			destination: destination,
-			done:        make(chan struct{}),
-		},
-	}
-}
-
-func (t *TLSTransport) offer(ctx context.Context) (*dnsConnection, error) {
-	t.access.RLock()
-	connection := t.connection
-	t.access.RUnlock()
-	if connection != nil {
-		select {
-		case <-connection.done:
-		default:
-			return connection, nil
-		}
-	}
-	t.access.Lock()
-	connection = t.connection
-	if connection != nil {
-		select {
-		case <-connection.done:
-		default:
-			t.access.Unlock()
-			return connection, nil
-		}
-	}
-	tcpConn, err := t.dialer.DialContext(t.ctx, "tcp", t.destination)
-	if err != nil {
-		return nil, err
-	}
-	tlsConn := tls.Client(tcpConn, &tls.Config{
-		ServerName: t.destination.AddrString(),
-	})
-	err = task.Run(t.ctx, func() error {
-		return tlsConn.HandshakeContext(ctx)
-	})
-	if err != nil {
-		return nil, err
-	}
-	connection = &dnsConnection{
-		Conn:      tlsConn,
-		done:      make(chan struct{}),
-		callbacks: make(map[uint16]chan *dnsmessage.Message),
-	}
-	t.connection = connection
-	t.access.Unlock()
-	go t.newConnection(connection)
-	return connection, nil
-}
-
-func (t *TLSTransport) newConnection(conn *dnsConnection) {
-	defer close(conn.done)
-	defer conn.Close()
-	err := task.Any(t.ctx, func(ctx context.Context) error {
-		return t.loopIn(conn)
-	}, func(ctx context.Context) error {
-		select {
-		case <-ctx.Done():
-			return nil
-		case <-t.done:
-			return os.ErrClosed
-		}
-	})
-	conn.err = err
-	if err != nil && !E.IsClosed(err) {
-		t.logger.Debug("connection closed: ", err)
-	}
-}
-
-func (t *TLSTransport) loopIn(conn *dnsConnection) error {
-	_buffer := buf.StackNewSize(1024)
-	defer common.KeepAlive(_buffer)
-	buffer := common.Dup(_buffer)
-	defer buffer.Release()
-	for {
-		buffer.FullReset()
-		_, err := buffer.ReadFullFrom(conn, 2)
-		if err != nil {
-			return err
-		}
-		length := binary.BigEndian.Uint16(buffer.Bytes())
-		if length > 512 {
-			return E.New("invalid length received: ", length)
-		}
-		buffer.FullReset()
-		_, err = buffer.ReadFullFrom(conn, int(length))
-		if err != nil {
-			return err
-		}
-		var message dnsmessage.Message
-		err = message.Unpack(buffer.Bytes())
-		if err != nil {
-			return err
-		}
-		conn.access.Lock()
-		callback, loaded := conn.callbacks[message.ID]
-		if loaded {
-			delete(conn.callbacks, message.ID)
-		}
-		conn.access.Unlock()
-		if !loaded {
-			continue
-		}
-		callback <- &message
-	}
-}
-
-func (t *TLSTransport) Exchange(ctx context.Context, message *dnsmessage.Message) (*dnsmessage.Message, error) {
-	var connection *dnsConnection
-	err := task.Run(ctx, func() error {
-		var innerErr error
-		connection, innerErr = t.offer(ctx)
-		return innerErr
-	})
-	if err != nil {
-		return nil, err
-	}
-	connection.access.Lock()
-	connection.queryId++
-	message.ID = connection.queryId
-	callback := make(chan *dnsmessage.Message)
-	connection.callbacks[message.ID] = callback
-	connection.access.Unlock()
-	_buffer := buf.StackNewSize(1024)
-	defer common.KeepAlive(_buffer)
-	buffer := common.Dup(_buffer)
-	defer buffer.Release()
-	length := buffer.Extend(2)
-	rawMessage, err := message.AppendPack(buffer.Index(2))
-	if err != nil {
-		return nil, err
-	}
-	buffer.Truncate(2 + len(rawMessage))
-	binary.BigEndian.PutUint16(length, uint16(len(rawMessage)))
-	err = task.Run(ctx, func() error {
-		return common.Error(connection.Write(buffer.Bytes()))
-	})
-	if err != nil {
-		return nil, err
-	}
-	select {
-	case response := <-callback:
-		return response, nil
-	case <-connection.done:
-		return nil, connection.err
-	case <-ctx.Done():
-		return nil, ctx.Err()
-	}
-}

+ 0 - 160
dns/transport_udp.go

@@ -1,160 +0,0 @@
-package dns
-
-import (
-	"context"
-	"os"
-
-	"github.com/sagernet/sing-box/adapter"
-	"github.com/sagernet/sing-box/log"
-	"github.com/sagernet/sing/common"
-	"github.com/sagernet/sing/common/buf"
-	E "github.com/sagernet/sing/common/exceptions"
-	M "github.com/sagernet/sing/common/metadata"
-	N "github.com/sagernet/sing/common/network"
-	"github.com/sagernet/sing/common/task"
-
-	"golang.org/x/net/dns/dnsmessage"
-)
-
-var _ adapter.DNSTransport = (*UDPTransport)(nil)
-
-type UDPTransport struct {
-	myTransportAdapter
-}
-
-func NewUDPTransport(ctx context.Context, dialer N.Dialer, logger log.Logger, destination M.Socksaddr) *UDPTransport {
-	return &UDPTransport{
-		myTransportAdapter{
-			ctx:         ctx,
-			dialer:      dialer,
-			logger:      logger,
-			destination: destination,
-			done:        make(chan struct{}),
-		},
-	}
-}
-
-func (t *UDPTransport) offer() (*dnsConnection, error) {
-	t.access.RLock()
-	connection := t.connection
-	t.access.RUnlock()
-	if connection != nil {
-		select {
-		case <-connection.done:
-		default:
-			return connection, nil
-		}
-	}
-	t.access.Lock()
-	connection = t.connection
-	if connection != nil {
-		select {
-		case <-connection.done:
-		default:
-			t.access.Unlock()
-			return connection, nil
-		}
-	}
-	tcpConn, err := t.dialer.DialContext(t.ctx, "udp", t.destination)
-	if err != nil {
-		return nil, err
-	}
-	connection = &dnsConnection{
-		Conn:      tcpConn,
-		done:      make(chan struct{}),
-		callbacks: make(map[uint16]chan *dnsmessage.Message),
-	}
-	t.connection = connection
-	t.access.Unlock()
-	go t.newConnection(connection)
-	return connection, nil
-}
-
-func (t *UDPTransport) newConnection(conn *dnsConnection) {
-	defer close(conn.done)
-	defer conn.Close()
-	err := task.Any(t.ctx, func(ctx context.Context) error {
-		return t.loopIn(conn)
-	}, func(ctx context.Context) error {
-		select {
-		case <-ctx.Done():
-			return nil
-		case <-t.done:
-			return os.ErrClosed
-		}
-	})
-	conn.err = err
-	if err != nil && !E.IsClosed(err) {
-		t.logger.Debug("connection closed: ", err)
-	}
-}
-
-func (t *UDPTransport) loopIn(conn *dnsConnection) error {
-	_buffer := buf.StackNewSize(1024)
-	defer common.KeepAlive(_buffer)
-	buffer := common.Dup(_buffer)
-	defer buffer.Release()
-	for {
-		buffer.FullReset()
-		_, err := buffer.ReadFrom(conn)
-		if err != nil {
-			return err
-		}
-		var message dnsmessage.Message
-		err = message.Unpack(buffer.Bytes())
-		if err != nil {
-			return err
-		}
-		conn.access.Lock()
-		callback, loaded := conn.callbacks[message.ID]
-		if loaded {
-			delete(conn.callbacks, message.ID)
-		}
-		conn.access.Unlock()
-		if !loaded {
-			continue
-		}
-		callback <- &message
-	}
-}
-
-func (t *UDPTransport) Exchange(ctx context.Context, message *dnsmessage.Message) (*dnsmessage.Message, error) {
-	var connection *dnsConnection
-	err := task.Run(ctx, func() error {
-		var innerErr error
-		connection, innerErr = t.offer()
-		return innerErr
-	})
-	if err != nil {
-		return nil, err
-	}
-	connection.access.Lock()
-	connection.queryId++
-	message.ID = connection.queryId
-	callback := make(chan *dnsmessage.Message)
-	connection.callbacks[message.ID] = callback
-	connection.access.Unlock()
-	_buffer := buf.StackNewSize(1024)
-	defer common.KeepAlive(_buffer)
-	buffer := common.Dup(_buffer)
-	defer buffer.Release()
-	rawMessage, err := message.AppendPack(buffer.Index(0))
-	if err != nil {
-		return nil, err
-	}
-	buffer.Truncate(len(rawMessage))
-	err = task.Run(ctx, func() error {
-		return common.Error(connection.Write(buffer.Bytes()))
-	})
-	if err != nil {
-		return nil, err
-	}
-	select {
-	case response := <-callback:
-		return response, nil
-	case <-connection.done:
-		return nil, connection.err
-	case <-ctx.Done():
-		return nil, ctx.Err()
-	}
-}

+ 1 - 1
format.go

@@ -1,7 +1,7 @@
 package box
 
 //go:generate go install -v mvdan.cc/gofumpt@latest
-//go:generate go install -v github.com/daixiang0/gci@latest
+//go:generate go install -v github.com/daixiang0/gci@v0.4.0
 //go:generate gofumpt -l -w .
 //go:generate gofmt -s -w .
 //go:generate gci write -s "standard,prefix(github.com/sagernet/),default" .

+ 5 - 3
go.mod

@@ -7,15 +7,16 @@ require (
 	github.com/goccy/go-json v0.9.8
 	github.com/logrusorgru/aurora v2.0.3+incompatible
 	github.com/oschwald/maxminddb-golang v1.9.0
-	github.com/sagernet/sing v0.0.0-20220710135805-84be1c5eb67a
+	github.com/sagernet/sing v0.0.0-20220711103842-d3fb2260ef61
+	github.com/sagernet/sing-dns v0.0.0-20220711062726-c64e938e4619
 	github.com/sagernet/sing-shadowsocks v0.0.0-20220701084835-2208da1d8649
+	github.com/sagernet/sing-tun v0.0.0-20220711091522-4f7247190c96
 	github.com/sirupsen/logrus v1.8.1
 	github.com/spf13/cobra v1.5.0
 	github.com/stretchr/testify v1.8.0
-	github.com/vishvananda/netlink v1.0.1-0.20190930145447-2ec5bdc52b86
+	github.com/vishvananda/netlink v1.1.0
 	golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d
 	golang.org/x/net v0.0.0-20220708220712-1185a9018129
-	gvisor.dev/gvisor v0.0.0-20220708233959-72bdef768f07
 )
 
 require (
@@ -31,5 +32,6 @@ require (
 	golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect
 	gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
+	gvisor.dev/gvisor v0.0.0-20220711011657-cecae2f4234d // indirect
 	lukechampine.com/blake3 v1.1.7 // indirect
 )

+ 12 - 6
go.sum

@@ -25,10 +25,14 @@ 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-20220710135805-84be1c5eb67a h1:1SquyxA41EGvKGBrhj/HQkj4zhteThBPRFvJby0k2HE=
-github.com/sagernet/sing v0.0.0-20220710135805-84be1c5eb67a/go.mod h1:3ZmoGNg/nNJTyHAZFNRSPaXpNIwpDvyIiAUd0KIWV5c=
+github.com/sagernet/sing v0.0.0-20220711103842-d3fb2260ef61 h1:sOx7t+MFssiCAY2afRHQSmkWZNpLQnjF0Hwv/TNVMvk=
+github.com/sagernet/sing v0.0.0-20220711103842-d3fb2260ef61/go.mod h1:3ZmoGNg/nNJTyHAZFNRSPaXpNIwpDvyIiAUd0KIWV5c=
+github.com/sagernet/sing-dns v0.0.0-20220711062726-c64e938e4619 h1:oHbOmq1WS0XaZmXp6WpxzyB2xeyRIA1/L8EJKuNntfY=
+github.com/sagernet/sing-dns v0.0.0-20220711062726-c64e938e4619/go.mod h1:y2fpvoxukw3G7eApIZwkcpcG/NE4AB8pCQI0Qd8rMqk=
 github.com/sagernet/sing-shadowsocks v0.0.0-20220701084835-2208da1d8649 h1:whNDUGOAX5GPZkSy4G3Gv9QyIgk5SXRyjkRuP7ohF8k=
 github.com/sagernet/sing-shadowsocks v0.0.0-20220701084835-2208da1d8649/go.mod h1:MuyT+9fEPjvauAv0fSE0a6Q+l0Tv2ZrAafTkYfnxBFw=
+github.com/sagernet/sing-tun v0.0.0-20220711091522-4f7247190c96 h1:BPsCEEKmww4PCuL2qCKGpwuS/HllNz4/G7EjvSHlXXg=
+github.com/sagernet/sing-tun v0.0.0-20220711091522-4f7247190c96/go.mod h1:OLQnVTGk8NMVdoegQvenGHsGEv3diSMWe9Uh02cel0E=
 github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
 github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
 github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU=
@@ -41,14 +45,16 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
-github.com/vishvananda/netlink v1.0.1-0.20190930145447-2ec5bdc52b86 h1:7SWt9pGCMaw+N1ZhRsaLKaYNviFhxambdoaoYlDqz1w=
-github.com/vishvananda/netlink v1.0.1-0.20190930145447-2ec5bdc52b86/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk=
+github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0=
+github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
+github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
 github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg=
 github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
 golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY=
 golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/net v0.0.0-20220708220712-1185a9018129 h1:vucSRfWwTsoXro7P+3Cjlr6flUMtzCwzlvkxEQtHHB0=
 golang.org/x/net v0.0.0-20220708220712-1185a9018129/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d h1:/m5NbqQelATgoSPVC2Z23sR4kVNokFwDDyWh/3rGY+I=
@@ -62,7 +68,7 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gvisor.dev/gvisor v0.0.0-20220708233959-72bdef768f07 h1:EBa4BJfuxvdpvMGq6gw347MDptuFgqDHhuTEKxCCQ5U=
-gvisor.dev/gvisor v0.0.0-20220708233959-72bdef768f07/go.mod h1:TIvkJD0sxe8pIob3p6T8IzxXunlp6yfgktvTNp+DGNM=
+gvisor.dev/gvisor v0.0.0-20220711011657-cecae2f4234d h1:KjI6i6P1ib9DiNdNIN8pb2TXfBewpKHf3O58cjj9vw4=
+gvisor.dev/gvisor v0.0.0-20220711011657-cecae2f4234d/go.mod h1:TIvkJD0sxe8pIob3p6T8IzxXunlp6yfgktvTNp+DGNM=
 lukechampine.com/blake3 v1.1.7 h1:GgRMhmdsuK8+ii6UZFDL8Nb+VyMwadAgcJyfYHxG6n0=
 lukechampine.com/blake3 v1.1.7/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA=

+ 4 - 3
inbound/default.go

@@ -12,6 +12,7 @@ import (
 	C "github.com/sagernet/sing-box/constant"
 	"github.com/sagernet/sing-box/log"
 	"github.com/sagernet/sing-box/option"
+	"github.com/sagernet/sing-dns"
 	"github.com/sagernet/sing/common"
 	"github.com/sagernet/sing/common/buf"
 	E "github.com/sagernet/sing/common/exceptions"
@@ -135,7 +136,7 @@ func (a *myInboundAdapter) loopTCPIn() {
 			metadata.Inbound = a.tag
 			metadata.SniffEnabled = a.listenOptions.SniffEnabled
 			metadata.SniffOverrideDestination = a.listenOptions.SniffOverrideDestination
-			metadata.DomainStrategy = C.DomainStrategy(a.listenOptions.DomainStrategy)
+			metadata.DomainStrategy = dns.DomainStrategy(a.listenOptions.DomainStrategy)
 			metadata.Network = C.NetworkTCP
 			metadata.Source = M.SocksaddrFromNet(conn.RemoteAddr())
 			a.logger.WithContext(ctx).Info("inbound connection from ", metadata.Source)
@@ -168,7 +169,7 @@ func (a *myInboundAdapter) loopUDPIn() {
 		metadata.Inbound = a.tag
 		metadata.SniffEnabled = a.listenOptions.SniffEnabled
 		metadata.SniffOverrideDestination = a.listenOptions.SniffOverrideDestination
-		metadata.DomainStrategy = C.DomainStrategy(a.listenOptions.DomainStrategy)
+		metadata.DomainStrategy = dns.DomainStrategy(a.listenOptions.DomainStrategy)
 		metadata.Network = C.NetworkUDP
 		metadata.Source = M.SocksaddrFromNetIP(addr)
 		err = a.packetHandler.NewPacket(a.ctx, packetService, buffer, metadata)
@@ -193,7 +194,7 @@ func (a *myInboundAdapter) loopUDPInThreadSafe() {
 		metadata.Inbound = a.tag
 		metadata.SniffEnabled = a.listenOptions.SniffEnabled
 		metadata.SniffOverrideDestination = a.listenOptions.SniffOverrideDestination
-		metadata.DomainStrategy = C.DomainStrategy(a.listenOptions.DomainStrategy)
+		metadata.DomainStrategy = dns.DomainStrategy(a.listenOptions.DomainStrategy)
 		metadata.Network = C.NetworkUDP
 		metadata.Source = M.SocksaddrFromNetIP(addr)
 		err = a.packetHandler.NewPacket(a.ctx, packetService, buffer, metadata)

+ 6 - 3
inbound/tun.go

@@ -12,10 +12,11 @@ import (
 	"strings"
 
 	"github.com/sagernet/sing-box/adapter"
-	"github.com/sagernet/sing-box/common/tun"
 	C "github.com/sagernet/sing-box/constant"
 	"github.com/sagernet/sing-box/log"
 	"github.com/sagernet/sing-box/option"
+	"github.com/sagernet/sing-dns"
+	"github.com/sagernet/sing-tun"
 	E "github.com/sagernet/sing/common/exceptions"
 	F "github.com/sagernet/sing/common/format"
 	M "github.com/sagernet/sing/common/metadata"
@@ -106,6 +107,7 @@ func (t *Tun) Close() error {
 }
 
 func (t *Tun) NewConnection(ctx context.Context, conn net.Conn, upstreamMetadata M.Metadata) error {
+	ctx = log.ContextWithID(ctx)
 	var metadata adapter.InboundContext
 	metadata.Inbound = t.tag
 	metadata.Network = C.NetworkTCP
@@ -113,7 +115,7 @@ func (t *Tun) NewConnection(ctx context.Context, conn net.Conn, upstreamMetadata
 	metadata.Destination = upstreamMetadata.Destination
 	metadata.SniffEnabled = t.inboundOptions.SniffEnabled
 	metadata.SniffOverrideDestination = t.inboundOptions.SniffOverrideDestination
-	metadata.DomainStrategy = C.DomainStrategy(t.inboundOptions.DomainStrategy)
+	metadata.DomainStrategy = dns.DomainStrategy(t.inboundOptions.DomainStrategy)
 	if t.hijackDNS && upstreamMetadata.Destination.Port == 53 {
 		return task.Run(ctx, func() error {
 			return NewDNSConnection(ctx, t.router, t.logger, conn, metadata)
@@ -125,6 +127,7 @@ func (t *Tun) NewConnection(ctx context.Context, conn net.Conn, upstreamMetadata
 }
 
 func (t *Tun) NewPacketConnection(ctx context.Context, conn N.PacketConn, upstreamMetadata M.Metadata) error {
+	ctx = log.ContextWithID(ctx)
 	var metadata adapter.InboundContext
 	metadata.Inbound = t.tag
 	metadata.Network = C.NetworkUDP
@@ -132,7 +135,7 @@ func (t *Tun) NewPacketConnection(ctx context.Context, conn N.PacketConn, upstre
 	metadata.Destination = upstreamMetadata.Destination
 	metadata.SniffEnabled = t.inboundOptions.SniffEnabled
 	metadata.SniffOverrideDestination = t.inboundOptions.SniffOverrideDestination
-	metadata.DomainStrategy = C.DomainStrategy(t.inboundOptions.DomainStrategy)
+	metadata.DomainStrategy = dns.DomainStrategy(t.inboundOptions.DomainStrategy)
 	if t.hijackDNS && upstreamMetadata.Destination.Port == 53 {
 		return task.Run(ctx, func() error {
 			return NewDNSPacketConnection(ctx, t.router, t.logger, conn, metadata)

+ 6 - 5
option/dns.go

@@ -29,11 +29,12 @@ type DNSClientOptions struct {
 }
 
 type DNSServerOptions struct {
-	Tag             string         `json:"tag,omitempty"`
-	Address         string         `json:"address"`
-	AddressResolver string         `json:"address_resolver,omitempty"`
-	AddressStrategy DomainStrategy `json:"address_strategy,omitempty"`
-	Detour          string         `json:"detour,omitempty"`
+	Tag                  string         `json:"tag,omitempty"`
+	Address              string         `json:"address"`
+	AddressResolver      string         `json:"address_resolver,omitempty"`
+	AddressStrategy      DomainStrategy `json:"address_strategy,omitempty"`
+	AddressFallbackDelay Duration       `json:"address_fallback_delay,omitempty"`
+	Detour               string         `json:"detour,omitempty"`
 }
 
 type _DNSRule struct {

+ 13 - 12
option/types.go

@@ -6,6 +6,7 @@ import (
 	"time"
 
 	C "github.com/sagernet/sing-box/constant"
+	"github.com/sagernet/sing-dns"
 	E "github.com/sagernet/sing/common/exceptions"
 
 	"github.com/goccy/go-json"
@@ -91,21 +92,21 @@ func (l *Listable[T]) UnmarshalJSON(content []byte) error {
 	return nil
 }
 
-type DomainStrategy C.DomainStrategy
+type DomainStrategy dns.DomainStrategy
 
 func (s DomainStrategy) MarshalJSON() ([]byte, error) {
 	var value string
-	switch C.DomainStrategy(s) {
-	case C.DomainStrategyAsIS:
+	switch dns.DomainStrategy(s) {
+	case dns.DomainStrategyAsIS:
 		value = ""
 		// value = "AsIS"
-	case C.DomainStrategyPreferIPv4:
+	case dns.DomainStrategyPreferIPv4:
 		value = "prefer_ipv4"
-	case C.DomainStrategyPreferIPv6:
+	case dns.DomainStrategyPreferIPv6:
 		value = "prefer_ipv6"
-	case C.DomainStrategyUseIPv4:
+	case dns.DomainStrategyUseIPv4:
 		value = "ipv4_only"
-	case C.DomainStrategyUseIPv6:
+	case dns.DomainStrategyUseIPv6:
 		value = "ipv6_only"
 	default:
 		return nil, E.New("unknown domain strategy: ", s)
@@ -121,15 +122,15 @@ func (s *DomainStrategy) UnmarshalJSON(bytes []byte) error {
 	}
 	switch value {
 	case "", "AsIS":
-		*s = DomainStrategy(C.DomainStrategyAsIS)
+		*s = DomainStrategy(dns.DomainStrategyAsIS)
 	case "prefer_ipv4":
-		*s = DomainStrategy(C.DomainStrategyPreferIPv4)
+		*s = DomainStrategy(dns.DomainStrategyPreferIPv4)
 	case "prefer_ipv6":
-		*s = DomainStrategy(C.DomainStrategyPreferIPv6)
+		*s = DomainStrategy(dns.DomainStrategyPreferIPv6)
 	case "ipv4_only":
-		*s = DomainStrategy(C.DomainStrategyUseIPv4)
+		*s = DomainStrategy(dns.DomainStrategyUseIPv4)
 	case "ipv6_only":
-		*s = DomainStrategy(C.DomainStrategyUseIPv6)
+		*s = DomainStrategy(dns.DomainStrategyUseIPv6)
 	default:
 		return E.New("unknown domain strategy: ", value)
 	}

+ 3 - 4
outbound/default.go

@@ -7,7 +7,6 @@ import (
 	"time"
 
 	"github.com/sagernet/sing-box/adapter"
-	"github.com/sagernet/sing-box/common/dialer"
 	C "github.com/sagernet/sing-box/constant"
 	"github.com/sagernet/sing-box/log"
 	"github.com/sagernet/sing/common"
@@ -41,7 +40,7 @@ func NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata a
 	var outConn net.Conn
 	var err error
 	if len(metadata.DestinationAddresses) > 0 {
-		outConn, err = dialer.DialSerial(ctx, this, C.NetworkTCP, metadata.Destination, metadata.DestinationAddresses)
+		outConn, err = N.DialSerial(ctx, this, C.NetworkTCP, metadata.Destination, metadata.DestinationAddresses)
 	} else {
 		outConn, err = this.DialContext(ctx, C.NetworkTCP, metadata.Destination)
 	}
@@ -56,7 +55,7 @@ func NewEarlyConnection(ctx context.Context, this N.Dialer, conn net.Conn, metad
 	var outConn net.Conn
 	var err error
 	if len(metadata.DestinationAddresses) > 0 {
-		outConn, err = dialer.DialSerial(ctx, this, C.NetworkTCP, metadata.Destination, metadata.DestinationAddresses)
+		outConn, err = N.DialSerial(ctx, this, C.NetworkTCP, metadata.Destination, metadata.DestinationAddresses)
 	} else {
 		outConn, err = this.DialContext(ctx, C.NetworkTCP, metadata.Destination)
 	}
@@ -71,7 +70,7 @@ func NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn,
 	var outConn net.PacketConn
 	var err error
 	if len(metadata.DestinationAddresses) > 0 {
-		outConn, err = dialer.ListenSerial(ctx, this, metadata.Destination, metadata.DestinationAddresses)
+		outConn, err = N.ListenSerial(ctx, this, metadata.Destination, metadata.DestinationAddresses)
 	} else {
 		outConn, err = this.ListenPacket(ctx, metadata.Destination)
 	}

+ 18 - 19
route/router.go

@@ -20,9 +20,9 @@ import (
 	"github.com/sagernet/sing-box/common/iffmonitor"
 	"github.com/sagernet/sing-box/common/sniff"
 	C "github.com/sagernet/sing-box/constant"
-	"github.com/sagernet/sing-box/dns"
 	"github.com/sagernet/sing-box/log"
 	"github.com/sagernet/sing-box/option"
+	"github.com/sagernet/sing-dns"
 	"github.com/sagernet/sing/common"
 	"github.com/sagernet/sing/common/buf"
 	"github.com/sagernet/sing/common/bufio"
@@ -57,13 +57,12 @@ type Router struct {
 	geositeReader       *geosite.Reader
 	geositeCache        map[string]adapter.Rule
 
-	dnsClient             adapter.DNSClient
-	defaultDomainStrategy C.DomainStrategy
+	dnsClient             *dns.Client
+	defaultDomainStrategy dns.DomainStrategy
 	dnsRules              []adapter.Rule
-
-	defaultTransport adapter.DNSTransport
-	transports       []adapter.DNSTransport
-	transportMap     map[string]adapter.DNSTransport
+	defaultTransport      dns.Transport
+	transports            []dns.Transport
+	transportMap          map[string]dns.Transport
 
 	autoDetectInterface bool
 	interfaceMonitor    iffmonitor.InterfaceMonitor
@@ -83,8 +82,8 @@ func NewRouter(ctx context.Context, logger log.Logger, options option.RouteOptio
 		geositeOptions:        common.PtrValueOrDefault(options.Geosite),
 		geositeCache:          make(map[string]adapter.Rule),
 		defaultDetour:         options.Final,
-		dnsClient:             dns.NewClient(dnsOptions.DNSClientOptions),
-		defaultDomainStrategy: C.DomainStrategy(dnsOptions.Strategy),
+		dnsClient:             dns.NewClient(dns.DomainStrategy(dnsOptions.DNSClientOptions.Strategy), dnsOptions.DNSClientOptions.DisableCache, dnsOptions.DNSClientOptions.DisableExpire),
+		defaultDomainStrategy: dns.DomainStrategy(dnsOptions.Strategy),
 		autoDetectInterface:   options.AutoDetectInterface,
 	}
 	for i, ruleOptions := range options.Rules {
@@ -101,9 +100,9 @@ func NewRouter(ctx context.Context, logger log.Logger, options option.RouteOptio
 		}
 		router.dnsRules = append(router.dnsRules, dnsRule)
 	}
-	transports := make([]adapter.DNSTransport, len(dnsOptions.Servers))
-	dummyTransportMap := make(map[string]adapter.DNSTransport)
-	transportMap := make(map[string]adapter.DNSTransport)
+	transports := make([]dns.Transport, len(dnsOptions.Servers))
+	dummyTransportMap := make(map[string]dns.Transport)
+	transportMap := make(map[string]dns.Transport)
 	transportTags := make([]string, len(dnsOptions.Servers))
 	transportTagMap := make(map[string]bool)
 	for i, server := range dnsOptions.Servers {
@@ -144,7 +143,7 @@ func NewRouter(ctx context.Context, logger log.Logger, options option.RouteOptio
 						return nil, E.New("parse dns server[", tag, "]: address resolver not found: ", server.AddressResolver)
 					}
 					if upstream, exists := dummyTransportMap[server.AddressResolver]; exists {
-						detour = dns.NewDialerWrapper(detour, C.DomainStrategy(server.AddressStrategy), router.dnsClient, upstream)
+						detour = dns.NewDialerWrapper(detour, router.dnsClient, upstream, dns.DomainStrategy(server.AddressStrategy), time.Duration(server.AddressFallbackDelay))
 					} else {
 						continue
 					}
@@ -152,7 +151,7 @@ func NewRouter(ctx context.Context, logger log.Logger, options option.RouteOptio
 					return nil, E.New("parse dns server[", tag, "]: missing address_resolver")
 				}
 			}
-			transport, err := dns.NewTransport(ctx, detour, logger, server.Address)
+			transport, err := dns.NewTransport(ctx, detour, server.Address)
 			if err != nil {
 				return nil, E.Cause(err, "parse dns server[", tag, "]")
 			}
@@ -176,7 +175,7 @@ func NewRouter(ctx context.Context, logger log.Logger, options option.RouteOptio
 		})
 		return nil, E.New("found circular reference in dns servers: ", strings.Join(unresolvedTags, " "))
 	}
-	var defaultTransport adapter.DNSTransport
+	var defaultTransport dns.Transport
 	if options.Final != "" {
 		defaultTransport = dummyTransportMap[options.Final]
 		if defaultTransport == nil {
@@ -408,7 +407,7 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad
 			conn = bufio.NewCachedConn(conn, buffer)
 		}
 	}
-	if metadata.Destination.IsFqdn() && metadata.DomainStrategy != C.DomainStrategyAsIS {
+	if metadata.Destination.IsFqdn() && metadata.DomainStrategy != dns.DomainStrategyAsIS {
 		addresses, err := r.Lookup(adapter.WithContext(ctx, &metadata), metadata.Destination.Fqdn, metadata.DomainStrategy)
 		if err != nil {
 			return err
@@ -450,7 +449,7 @@ func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, m
 		}
 		conn = bufio.NewCachedPacketConn(conn, buffer, originDestination)
 	}
-	if metadata.Destination.IsFqdn() && metadata.DomainStrategy != C.DomainStrategyAsIS {
+	if metadata.Destination.IsFqdn() && metadata.DomainStrategy != dns.DomainStrategyAsIS {
 		addresses, err := r.Lookup(adapter.WithContext(ctx, &metadata), metadata.Destination.Fqdn, metadata.DomainStrategy)
 		if err != nil {
 			return err
@@ -470,7 +469,7 @@ func (r *Router) Exchange(ctx context.Context, message *dnsmessage.Message) (*dn
 	return r.dnsClient.Exchange(ctx, r.matchDNS(ctx), message)
 }
 
-func (r *Router) Lookup(ctx context.Context, domain string, strategy C.DomainStrategy) ([]netip.Addr, error) {
+func (r *Router) Lookup(ctx context.Context, domain string, strategy dns.DomainStrategy) ([]netip.Addr, error) {
 	return r.dnsClient.Lookup(ctx, r.matchDNS(ctx), domain, strategy)
 }
 
@@ -492,7 +491,7 @@ func (r *Router) match(ctx context.Context, metadata adapter.InboundContext, def
 	return defaultOutbound
 }
 
-func (r *Router) matchDNS(ctx context.Context) adapter.DNSTransport {
+func (r *Router) matchDNS(ctx context.Context) dns.Transport {
 	metadata := adapter.ContextFrom(ctx)
 	if metadata == nil {
 		r.dnsLogger.WithContext(ctx).Warn("no context: ", reflect.TypeOf(ctx))

+ 1 - 1
route/rule_domain.go

@@ -4,8 +4,8 @@ import (
 	"strings"
 
 	"github.com/sagernet/sing-box/adapter"
-	"github.com/sagernet/sing-box/common/domain"
 	"github.com/sagernet/sing/common"
+	"github.com/sagernet/sing/common/domain"
 )
 
 var _ RuleItem = (*DomainItem)(nil)

+ 5 - 3
test/go.mod

@@ -5,7 +5,7 @@ go 1.18
 require (
 	github.com/docker/docker v20.10.17+incompatible
 	github.com/docker/go-connections v0.4.0
-	github.com/sagernet/sing v0.0.0-20220710135805-84be1c5eb67a
+	github.com/sagernet/sing v0.0.0-20220711103842-d3fb2260ef61
 	github.com/sagernet/sing-box v0.0.0
 	github.com/sirupsen/logrus v1.8.1
 	github.com/stretchr/testify v1.8.0
@@ -33,14 +33,16 @@ require (
 	github.com/oschwald/maxminddb-golang v1.9.0 // indirect
 	github.com/pkg/errors v0.9.1 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
+	github.com/sagernet/sing-dns v0.0.0-20220711062726-c64e938e4619 // indirect
 	github.com/sagernet/sing-shadowsocks v0.0.0-20220701084835-2208da1d8649 // indirect
-	github.com/vishvananda/netlink v1.0.1-0.20190930145447-2ec5bdc52b86 // indirect
+	github.com/sagernet/sing-tun v0.0.0-20220711091522-4f7247190c96 // indirect
+	github.com/vishvananda/netlink v1.1.0 // indirect
 	github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
 	golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect
 	golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d // indirect
 	golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
 	gotest.tools/v3 v3.3.0 // indirect
-	gvisor.dev/gvisor v0.0.0-20220708233959-72bdef768f07 // indirect
+	gvisor.dev/gvisor v0.0.0-20220711011657-cecae2f4234d // indirect
 	lukechampine.com/blake3 v1.1.7 // indirect
 )

+ 12 - 6
test/go.sum

@@ -52,10 +52,14 @@ 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-20220710135805-84be1c5eb67a h1:1SquyxA41EGvKGBrhj/HQkj4zhteThBPRFvJby0k2HE=
-github.com/sagernet/sing v0.0.0-20220710135805-84be1c5eb67a/go.mod h1:3ZmoGNg/nNJTyHAZFNRSPaXpNIwpDvyIiAUd0KIWV5c=
+github.com/sagernet/sing v0.0.0-20220711103842-d3fb2260ef61 h1:sOx7t+MFssiCAY2afRHQSmkWZNpLQnjF0Hwv/TNVMvk=
+github.com/sagernet/sing v0.0.0-20220711103842-d3fb2260ef61/go.mod h1:3ZmoGNg/nNJTyHAZFNRSPaXpNIwpDvyIiAUd0KIWV5c=
+github.com/sagernet/sing-dns v0.0.0-20220711062726-c64e938e4619 h1:oHbOmq1WS0XaZmXp6WpxzyB2xeyRIA1/L8EJKuNntfY=
+github.com/sagernet/sing-dns v0.0.0-20220711062726-c64e938e4619/go.mod h1:y2fpvoxukw3G7eApIZwkcpcG/NE4AB8pCQI0Qd8rMqk=
 github.com/sagernet/sing-shadowsocks v0.0.0-20220701084835-2208da1d8649 h1:whNDUGOAX5GPZkSy4G3Gv9QyIgk5SXRyjkRuP7ohF8k=
 github.com/sagernet/sing-shadowsocks v0.0.0-20220701084835-2208da1d8649/go.mod h1:MuyT+9fEPjvauAv0fSE0a6Q+l0Tv2ZrAafTkYfnxBFw=
+github.com/sagernet/sing-tun v0.0.0-20220711091522-4f7247190c96 h1:BPsCEEKmww4PCuL2qCKGpwuS/HllNz4/G7EjvSHlXXg=
+github.com/sagernet/sing-tun v0.0.0-20220711091522-4f7247190c96/go.mod h1:OLQnVTGk8NMVdoegQvenGHsGEv3diSMWe9Uh02cel0E=
 github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
 github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
 github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
@@ -66,8 +70,9 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
-github.com/vishvananda/netlink v1.0.1-0.20190930145447-2ec5bdc52b86 h1:7SWt9pGCMaw+N1ZhRsaLKaYNviFhxambdoaoYlDqz1w=
-github.com/vishvananda/netlink v1.0.1-0.20190930145447-2ec5bdc52b86/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk=
+github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0=
+github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
+github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
 github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg=
 github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -91,6 +96,7 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -121,7 +127,7 @@ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
 gotest.tools/v3 v3.3.0 h1:MfDY1b1/0xN1CyMlQDac0ziEy9zJQd9CXBRRDHw2jJo=
 gotest.tools/v3 v3.3.0/go.mod h1:Mcr9QNxkg0uMvy/YElmo4SpXgJKWgQvYrT7Kw5RzJ1A=
-gvisor.dev/gvisor v0.0.0-20220708233959-72bdef768f07 h1:EBa4BJfuxvdpvMGq6gw347MDptuFgqDHhuTEKxCCQ5U=
-gvisor.dev/gvisor v0.0.0-20220708233959-72bdef768f07/go.mod h1:TIvkJD0sxe8pIob3p6T8IzxXunlp6yfgktvTNp+DGNM=
+gvisor.dev/gvisor v0.0.0-20220711011657-cecae2f4234d h1:KjI6i6P1ib9DiNdNIN8pb2TXfBewpKHf3O58cjj9vw4=
+gvisor.dev/gvisor v0.0.0-20220711011657-cecae2f4234d/go.mod h1:TIvkJD0sxe8pIob3p6T8IzxXunlp6yfgktvTNp+DGNM=
 lukechampine.com/blake3 v1.1.7 h1:GgRMhmdsuK8+ii6UZFDL8Nb+VyMwadAgcJyfYHxG6n0=
 lukechampine.com/blake3 v1.1.7/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA=