mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-10-04 23:03:33 +00:00
https://github.com/XTLS/Xray-core/pull/6718#issuecomment-5894987590 Fixes https://github.com/XTLS/Xray-core/issues/6692
442 lines
8.7 KiB
Go
442 lines
8.7 KiB
Go
package xdns
|
|
|
|
import (
|
|
"context"
|
|
"crypto/rand"
|
|
"io"
|
|
mrand "math/rand"
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"github.com/xtls/xray-core/common"
|
|
"github.com/xtls/xray-core/common/errors"
|
|
"github.com/xtls/xray-core/common/net"
|
|
"github.com/xtls/xray-core/transport/internet/finalmask"
|
|
"golang.org/x/net/dns/dnsmessage"
|
|
)
|
|
|
|
const (
|
|
initPollDelay = 500 * time.Millisecond
|
|
maxPollDelay = 10 * time.Second
|
|
pollDelayMultiplier = 2.0
|
|
pollLimit = 16
|
|
)
|
|
|
|
var pool4K = sync.Pool{
|
|
New: func() any {
|
|
return make([]byte, 4096)
|
|
},
|
|
}
|
|
|
|
type packet struct {
|
|
p []byte
|
|
addr net.Addr
|
|
}
|
|
|
|
type xdnsClient struct {
|
|
dialer *finalmask.Dialer
|
|
|
|
clientID ClientID
|
|
fragID atomic.Uint32
|
|
domains []*Domain
|
|
extraPoll int32
|
|
|
|
resolvers []Resolver
|
|
resolverSends []atomic.Uint32
|
|
resolverIndex atomic.Uint32
|
|
|
|
readCh chan packet
|
|
sendCh chan []byte
|
|
poolCh chan struct{}
|
|
closeCh chan struct{}
|
|
wg sync.WaitGroup
|
|
mu sync.Mutex
|
|
}
|
|
|
|
func NewClient(c *Config, dialer *finalmask.Dialer) (net.PacketConn, error) {
|
|
if len(c.Domains) == 0 {
|
|
return nil, errors.New("empty domains")
|
|
}
|
|
if len(c.Resolvers) == 0 {
|
|
return nil, errors.New("empty resolvers")
|
|
}
|
|
if c.ExtraPoll < 0 || c.ExtraPoll > 3 {
|
|
return nil, errors.New("c.ExtraPoll < 0 || c.ExtraPoll > 3")
|
|
}
|
|
domains := make([]*Domain, 0, len(c.Domains))
|
|
for i := range c.Domains {
|
|
types := make([]uint16, 0, len(c.Domains[i].Types))
|
|
for j := range c.Domains[i].Types {
|
|
types = append(types, uint16(c.Domains[i].Types[j]))
|
|
}
|
|
domain, err := NewDomain(c.Domains[i].Name, int(c.Domains[i].LenLimit), int(c.Domains[i].LabelLimit), types, uint16(c.Domains[i].Edns0))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
domains = append(domains, domain)
|
|
}
|
|
resolvers := make([]Resolver, 0, len(c.Resolvers))
|
|
for i := range c.Resolvers {
|
|
resolver, err := NewResolver(c.Resolvers[i], dialer)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
resolvers = append(resolvers, resolver)
|
|
}
|
|
client := &xdnsClient{
|
|
dialer: dialer,
|
|
|
|
clientID: NewClientID(),
|
|
domains: domains,
|
|
extraPoll: c.ExtraPoll,
|
|
|
|
resolvers: resolvers,
|
|
resolverSends: make([]atomic.Uint32, len(c.Resolvers)),
|
|
|
|
readCh: make(chan packet),
|
|
sendCh: make(chan []byte, 16),
|
|
poolCh: make(chan struct{}, pollLimit),
|
|
closeCh: make(chan struct{}),
|
|
}
|
|
go client.run()
|
|
return client, nil
|
|
}
|
|
|
|
func (c *xdnsClient) closed() bool {
|
|
select {
|
|
case <-c.closeCh:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func (c *xdnsClient) read(buf []byte, addr net.Addr) bool {
|
|
msg := dnsmessage.Message{}
|
|
if err := msg.Unpack(buf); err != nil {
|
|
return false
|
|
}
|
|
if !msg.Header.Response || msg.Header.Truncated || msg.Header.RCode != dnsmessage.RCodeSuccess || len(msg.Questions) != 1 {
|
|
return false
|
|
}
|
|
|
|
var domain *Domain
|
|
for i := range c.domains {
|
|
if c.domains[i].IsDomain(msg.Questions[0].Name) {
|
|
domain = c.domains[i]
|
|
break
|
|
}
|
|
}
|
|
if domain == nil || !domain.HasType(uint16(msg.Questions[0].Type)) {
|
|
return false
|
|
}
|
|
|
|
edns0 := uint16(0)
|
|
for i := range msg.Additionals {
|
|
if msg.Additionals[i].Header.Type == dnsmessage.TypeOPT {
|
|
edns0 = uint16(msg.Additionals[i].Header.Class)
|
|
break
|
|
}
|
|
}
|
|
errors.LogDebug(context.Background(), addr, " edns0 ", edns0, " buf ", len(buf), " ", msg.Questions[0].Type)
|
|
|
|
resp := NewResp(msg, domain, 0)
|
|
|
|
p := pool4K.Get().([]byte)
|
|
n := resp.Decode(p)
|
|
p = p[:n]
|
|
|
|
b := p
|
|
var bs [][]byte
|
|
for len(b) > 1 {
|
|
last := b[0]&0xC0 == 0xC0
|
|
length := int(b[0]&0x3F)<<8 | int(b[1])
|
|
b = b[2:]
|
|
if length > len(b) {
|
|
bs = nil
|
|
break
|
|
}
|
|
packet := make([]byte, length)
|
|
copy(packet, b)
|
|
bs = append(bs, packet)
|
|
if last {
|
|
break
|
|
}
|
|
b = b[length:]
|
|
if len(b) < 2 {
|
|
bs = nil
|
|
}
|
|
}
|
|
pool4K.Put(p[:cap(p)])
|
|
|
|
for i := range bs {
|
|
select {
|
|
case <-c.closeCh:
|
|
return true
|
|
case c.readCh <- packet{p: bs[i], addr: addr}:
|
|
}
|
|
}
|
|
return len(bs) > 0
|
|
}
|
|
|
|
func (c *xdnsClient) run() {
|
|
for i := range len(c.resolvers) {
|
|
c.wg.Add(1)
|
|
go c.recv(i)
|
|
}
|
|
|
|
c.wg.Add(1)
|
|
go c.send()
|
|
|
|
c.wg.Wait()
|
|
close(c.readCh)
|
|
close(c.sendCh)
|
|
close(c.poolCh)
|
|
}
|
|
|
|
func (c *xdnsClient) recv(i int) {
|
|
defer c.wg.Done()
|
|
|
|
var buf [4096]byte
|
|
for {
|
|
n, err := c.resolvers[i].Read(buf[:])
|
|
if err != nil {
|
|
if c.closed() {
|
|
return
|
|
}
|
|
errors.LogErrorInner(context.Background(), err, "recv err ", i)
|
|
return
|
|
}
|
|
if c.read(buf[:n], c.resolvers[i].Addr()) {
|
|
c.resolverSends[i].Store(0)
|
|
select {
|
|
case c.poolCh <- struct{}{}:
|
|
default:
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *xdnsClient) send() {
|
|
defer c.wg.Done()
|
|
|
|
var buf [512]byte
|
|
var data [255]byte
|
|
|
|
sendMsg := func(p []byte, domain *Domain, qtype uint16) {
|
|
msg := dnsmessage.Message{
|
|
Header: dnsmessage.Header{
|
|
RecursionDesired: true,
|
|
},
|
|
Questions: []dnsmessage.Question{
|
|
{
|
|
Name: domain.Encode(p),
|
|
Type: dnsmessage.Type(qtype),
|
|
Class: dnsmessage.ClassINET,
|
|
},
|
|
},
|
|
}
|
|
if domain.edns0 > 0 {
|
|
msg.Additionals = []dnsmessage.Resource{
|
|
{
|
|
Header: dnsmessage.ResourceHeader{
|
|
Name: dnsmessage.MustNewName("."),
|
|
Type: dnsmessage.TypeOPT,
|
|
Class: dnsmessage.Class(domain.edns0),
|
|
TTL: 0,
|
|
},
|
|
Body: &dnsmessage.OPTResource{},
|
|
},
|
|
}
|
|
}
|
|
pack := common.Must2(msg.AppendPack(buf[:0]))
|
|
common.Must2(rand.Read(pack[:2]))
|
|
|
|
index := c.resolverIndex.Load()
|
|
cur := c.resolverSends[index].Add(1)
|
|
i := index
|
|
for {
|
|
i++
|
|
if i == uint32(len(c.resolvers)) {
|
|
i = 0
|
|
}
|
|
if i == index {
|
|
break
|
|
}
|
|
if cur > c.resolverSends[i].Load() {
|
|
break
|
|
}
|
|
}
|
|
c.resolverIndex.Store(i)
|
|
c.resolvers[index].Send(pack)
|
|
}
|
|
|
|
send := func(p []byte) {
|
|
domain := c.domains[mrand.Intn(len(c.domains))]
|
|
qtype := domain.types[mrand.Intn(len(domain.types))]
|
|
|
|
if len(p) == 0 {
|
|
copy(data[:], c.clientID[:])
|
|
data[0] |= TypeMap[qtype]
|
|
data[8] = 8
|
|
common.Must2(rand.Read(data[9:17]))
|
|
sendMsg(data[:17], domain, qtype)
|
|
return
|
|
}
|
|
|
|
if len(p) <= domain.cap-12 {
|
|
copy(data[:], c.clientID[:])
|
|
data[0] |= TypeMap[qtype]
|
|
data[8] = 3
|
|
common.Must2(rand.Read(data[9:12]))
|
|
copy(data[12:], p)
|
|
sendMsg(data[:12+len(p)], domain, qtype)
|
|
return
|
|
}
|
|
|
|
if len(p) <= 255*(domain.cap-15) {
|
|
copy(data[:], c.clientID[:])
|
|
data[0] |= TypeMap[qtype]
|
|
data[8] = 3 | 0xC0
|
|
common.Must2(rand.Read(data[9:12]))
|
|
|
|
fragID := byte(c.fragID.Add(1))
|
|
fragN := len(p) / (domain.cap - 15)
|
|
if len(p)%(domain.cap-15) > 0 {
|
|
fragN++
|
|
}
|
|
|
|
for i := range fragN {
|
|
data[12] = fragID
|
|
data[13] = byte(i)
|
|
data[14] = byte(fragN)
|
|
size := min(len(p), domain.cap-15)
|
|
copy(data[15:], p[:size])
|
|
sendMsg(data[:15+size], domain, qtype)
|
|
p = p[size:]
|
|
}
|
|
return
|
|
}
|
|
|
|
errors.LogError(context.Background(), "err size ", len(p))
|
|
}
|
|
|
|
ticker := time.NewTicker(initPollDelay)
|
|
defer ticker.Stop()
|
|
delay := initPollDelay
|
|
p := []byte(nil)
|
|
timeout := false
|
|
for {
|
|
select {
|
|
case <-c.closeCh:
|
|
return
|
|
default:
|
|
select {
|
|
case <-c.closeCh:
|
|
return
|
|
case p = <-c.sendCh:
|
|
case <-c.poolCh:
|
|
case <-ticker.C:
|
|
timeout = true
|
|
}
|
|
}
|
|
|
|
if len(p) > 0 {
|
|
select {
|
|
case <-c.poolCh:
|
|
default:
|
|
}
|
|
}
|
|
|
|
send(p)
|
|
for range c.extraPoll {
|
|
send(nil)
|
|
}
|
|
|
|
if timeout {
|
|
delay *= pollDelayMultiplier
|
|
if delay > maxPollDelay {
|
|
delay = maxPollDelay
|
|
}
|
|
timeout = false
|
|
} else {
|
|
delay = initPollDelay
|
|
}
|
|
ticker.Reset(delay)
|
|
}
|
|
}
|
|
|
|
func (c *xdnsClient) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
|
packet, ok := <-c.readCh
|
|
if ok {
|
|
return copy(p, packet.p), packet.addr, nil
|
|
}
|
|
return 0, nil, io.ErrClosedPipe
|
|
}
|
|
|
|
func (c *xdnsClient) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
if c.closed() {
|
|
return 0, io.ErrClosedPipe
|
|
}
|
|
if len(p) == 0 || len(p) > 4096 {
|
|
errors.LogError(context.Background(), "err size ", len(p))
|
|
return 0, errors.New("err size")
|
|
}
|
|
b := make([]byte, len(p))
|
|
copy(b, p)
|
|
select {
|
|
case c.sendCh <- b:
|
|
default:
|
|
}
|
|
return len(p), nil
|
|
}
|
|
|
|
func (c *xdnsClient) Close() error {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
if c.closed() {
|
|
return nil
|
|
}
|
|
close(c.closeCh)
|
|
for i := range c.resolvers {
|
|
c.resolvers[i].Close()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *xdnsClient) LocalAddr() net.Addr { return &net.UDPAddr{IP: []byte{0, 0, 0, 0}} }
|
|
|
|
func (c *xdnsClient) SetDeadline(t time.Time) error { return errors.New("not support") }
|
|
|
|
func (c *xdnsClient) SetReadDeadline(t time.Time) error { return errors.New("not support") }
|
|
|
|
func (c *xdnsClient) SetWriteDeadline(t time.Time) error { return errors.New("not support") }
|
|
|
|
type ClientID [8]byte
|
|
|
|
func NewClientID() ClientID {
|
|
var id ClientID
|
|
common.Must2(rand.Read(id[:]))
|
|
id[0] &= 0xFC
|
|
return id
|
|
}
|
|
|
|
func ClientIDFromRaw(id [8]byte) ClientID {
|
|
id[0] &= 0xFC
|
|
return id
|
|
}
|
|
|
|
func ClientIDFromAddr(addr *net.UDPAddr) ClientID {
|
|
return ClientID(addr.IP[8:])
|
|
}
|
|
|
|
func (id ClientID) Addr() *net.UDPAddr {
|
|
var ip [16]byte
|
|
ip[0] = 0xFD
|
|
copy(ip[8:], id[:])
|
|
return &net.UDPAddr{IP: ip[:]}
|
|
}
|