Migrate components to library

This commit is contained in:
世界 2022-07-11 18:44:59 +08:00
parent 3c1190e2c3
commit dc127e2994
No known key found for this signature in database
GPG key ID: CD109927C34A63C4
46 changed files with 109 additions and 2438 deletions

View file

@ -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

View file

@ -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)
}

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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))

View file

@ -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)
}
}
}
}

View file

@ -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)
}
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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) {

View file

@ -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...)
}

View file

@ -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)
}

View file

@ -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"))
}

View file

@ -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)
}
}
}

View file

@ -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),
)
}

View file

@ -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,
})
}

View file

@ -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)
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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()
}

View file

@ -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
}

View file

@ -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))
}
}

View file

@ -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)
}
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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()
}
}

View file

@ -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
}

View file

@ -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()
}
}

View file

@ -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()
}
}

View file

@ -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" .

8
go.mod
View file

@ -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
)

18
go.sum
View file

@ -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=

View file

@ -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)

View file

@ -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)

View file

@ -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 {

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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))

View file

@ -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)

View file

@ -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
)

View file

@ -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=