mirror of
https://github.com/SagerNet/sing-box.git
synced 2024-11-22 08:31:30 +00:00
Migrate components to library
This commit is contained in:
parent
3c1190e2c3
commit
dc127e2994
2
.github/update_dependencies.sh
vendored
2
.github/update_dependencies.sh
vendored
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 +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))
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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 +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
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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...)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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"))
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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),
|
||||
)
|
||||
}
|
|
@ -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,
|
||||
})
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
379
dns/client.go
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
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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
|
||||
}
|
33
dns/rcode.go
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))
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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,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
8
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
|
||||
)
|
||||
|
|
18
go.sum
18
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=
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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,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
|
||||
)
|
||||
|
|
18
test/go.sum
18
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=
|
||||
|
|
Loading…
Reference in a new issue