mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-10-13 11:05:46 +00:00
https://github.com/XTLS/Xray-core/pull/6718#issuecomment-5894987590 Fixes https://github.com/XTLS/Xray-core/issues/6692
393 lines
8.2 KiB
Go
393 lines
8.2 KiB
Go
package xdns
|
|
|
|
import (
|
|
"sort"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/xtls/xray-core/common"
|
|
"golang.org/x/net/dns/dnsmessage"
|
|
)
|
|
|
|
const (
|
|
sendTTL = 4 * time.Second
|
|
)
|
|
|
|
type Resp struct {
|
|
msg dnsmessage.Message
|
|
domain *Domain
|
|
edns0 uint16
|
|
|
|
cap int
|
|
}
|
|
|
|
func NewResp(msg dnsmessage.Message, domain *Domain, edns0 uint16) *Resp {
|
|
if msg.Header.Response {
|
|
return &Resp{
|
|
msg: msg,
|
|
domain: domain,
|
|
}
|
|
}
|
|
|
|
size := min(max(int(edns0), 512), max(int(domain.edns0), 512))
|
|
|
|
left := size - 12 - int(msg.Questions[0].Name.Length) - 1 - 2 - 2
|
|
if edns0 > 0 {
|
|
left -= 1 + 2 + 2 + 4 + 2 + 0
|
|
}
|
|
cap := 0
|
|
switch msg.Questions[0].Type {
|
|
case dnsmessage.TypeA:
|
|
single := 2 + 2 + 2 + 4 + 2 + 4
|
|
n := left / single
|
|
if n > 255 {
|
|
n = 255
|
|
}
|
|
cap = 4*n - n - 1
|
|
case dnsmessage.TypeCNAME:
|
|
single := 2 + 2 + 2 + 4 + 2 + domain.lenMax
|
|
n := left / single
|
|
if n > 255 {
|
|
n = 255
|
|
}
|
|
cap = domain.cap*n - n - 1
|
|
case dnsmessage.TypeTXT:
|
|
left -= 2 + 2 + 2 + 4 + 2
|
|
single := 255
|
|
n := left / single
|
|
m := left % single
|
|
cap = 255*n - n
|
|
if m > 1 {
|
|
cap += m - 1
|
|
}
|
|
case dnsmessage.TypeAAAA:
|
|
single := 2 + 2 + 2 + 4 + 2 + 16
|
|
n := left / single
|
|
if n > 255 {
|
|
n = 255
|
|
}
|
|
cap = 16*n - n - 1
|
|
}
|
|
|
|
return &Resp{
|
|
msg: msg,
|
|
domain: domain,
|
|
edns0: edns0,
|
|
|
|
cap: cap,
|
|
}
|
|
}
|
|
|
|
func (r *Resp) Encode(encoded []byte, data []byte) []byte {
|
|
msg := r.msg
|
|
msg.Header = dnsmessage.Header{
|
|
ID: msg.Header.ID,
|
|
Response: true,
|
|
Authoritative: true,
|
|
RCode: dnsmessage.RCodeSuccess,
|
|
}
|
|
msg.Answers = nil
|
|
msg.Authorities = nil
|
|
msg.Additionals = nil
|
|
switch msg.Questions[0].Type {
|
|
case dnsmessage.TypeA:
|
|
fragN := 0
|
|
if len(data) > 0 {
|
|
fragN = 1
|
|
}
|
|
if (len(data) - (4 - 2)) > 0 {
|
|
fragN += (len(data) - (4 - 2)) / (4 - 1)
|
|
if (len(data)-(4-2))%(4-1) > 0 {
|
|
fragN++
|
|
}
|
|
}
|
|
|
|
for i := range fragN {
|
|
A := [4]byte{byte(i)}
|
|
if i == 0 {
|
|
A[1] = byte(fragN)
|
|
n := copy(A[2:], data)
|
|
data = data[n:]
|
|
} else {
|
|
n := copy(A[1:], data)
|
|
data = data[n:]
|
|
}
|
|
msg.Answers = append(msg.Answers, dnsmessage.Resource{
|
|
Header: dnsmessage.ResourceHeader{
|
|
Name: msg.Questions[0].Name,
|
|
Type: msg.Questions[0].Type,
|
|
Class: dnsmessage.ClassINET,
|
|
TTL: 60,
|
|
},
|
|
Body: &dnsmessage.AResource{A: A},
|
|
})
|
|
}
|
|
case dnsmessage.TypeCNAME:
|
|
fragN := 0
|
|
if len(data) > 0 {
|
|
fragN = 1
|
|
}
|
|
if (len(data) - (r.domain.cap - 2)) > 0 {
|
|
fragN += (len(data) - (r.domain.cap - 2)) / (r.domain.cap - 1)
|
|
if (len(data)-(r.domain.cap-2))%(r.domain.cap-1) > 0 {
|
|
fragN++
|
|
}
|
|
}
|
|
|
|
DATA := make([]byte, r.domain.cap)
|
|
for i := range fragN {
|
|
DATA[0] = byte(i)
|
|
if i == 0 {
|
|
DATA[1] = byte(fragN)
|
|
n := copy(DATA[2:], data)
|
|
data = data[n:]
|
|
msg.Answers = append(msg.Answers, dnsmessage.Resource{
|
|
Header: dnsmessage.ResourceHeader{
|
|
Name: msg.Questions[0].Name,
|
|
Type: msg.Questions[0].Type,
|
|
Class: dnsmessage.ClassINET,
|
|
TTL: 60,
|
|
},
|
|
Body: &dnsmessage.CNAMEResource{CNAME: r.domain.Encode(DATA[:2+n])},
|
|
})
|
|
} else {
|
|
n := copy(DATA[1:], data)
|
|
data = data[n:]
|
|
msg.Answers = append(msg.Answers, dnsmessage.Resource{
|
|
Header: dnsmessage.ResourceHeader{
|
|
Name: msg.Questions[0].Name,
|
|
Type: msg.Questions[0].Type,
|
|
Class: dnsmessage.ClassINET,
|
|
TTL: 60,
|
|
},
|
|
Body: &dnsmessage.CNAMEResource{CNAME: r.domain.Encode(DATA[:1+n])},
|
|
})
|
|
}
|
|
}
|
|
case dnsmessage.TypeTXT:
|
|
var txt []string
|
|
for len(data) > 0 {
|
|
size := min(len(data), 255)
|
|
txt = append(txt, string(data[:size]))
|
|
data = data[size:]
|
|
}
|
|
msg.Answers = append(msg.Answers, dnsmessage.Resource{
|
|
Header: dnsmessage.ResourceHeader{
|
|
Name: msg.Questions[0].Name,
|
|
Type: msg.Questions[0].Type,
|
|
Class: dnsmessage.ClassINET,
|
|
TTL: 60,
|
|
},
|
|
Body: &dnsmessage.TXTResource{TXT: txt},
|
|
})
|
|
case dnsmessage.TypeAAAA:
|
|
fragN := 0
|
|
if len(data) > 0 {
|
|
fragN = 1
|
|
}
|
|
if (len(data) - (16 - 2)) > 0 {
|
|
fragN += (len(data) - (16 - 2)) / (16 - 1)
|
|
if (len(data)-(16-2))%(16-1) > 0 {
|
|
fragN++
|
|
}
|
|
}
|
|
|
|
for i := range fragN {
|
|
AAAA := [16]byte{byte(i)}
|
|
if i == 0 {
|
|
AAAA[1] = byte(fragN)
|
|
n := copy(AAAA[2:], data)
|
|
data = data[n:]
|
|
} else {
|
|
n := copy(AAAA[1:], data)
|
|
data = data[n:]
|
|
}
|
|
msg.Answers = append(msg.Answers, dnsmessage.Resource{
|
|
Header: dnsmessage.ResourceHeader{
|
|
Name: msg.Questions[0].Name,
|
|
Type: msg.Questions[0].Type,
|
|
Class: dnsmessage.ClassINET,
|
|
TTL: 60,
|
|
},
|
|
Body: &dnsmessage.AAAAResource{AAAA: AAAA},
|
|
})
|
|
}
|
|
}
|
|
if r.edns0 > 0 {
|
|
msg.Additionals = append(msg.Additionals, dnsmessage.Resource{
|
|
Header: dnsmessage.ResourceHeader{
|
|
Name: dnsmessage.MustNewName("."),
|
|
Type: dnsmessage.TypeOPT,
|
|
Class: dnsmessage.Class(r.edns0),
|
|
TTL: 0,
|
|
},
|
|
Body: &dnsmessage.OPTResource{},
|
|
})
|
|
}
|
|
return common.Must2(msg.AppendPack(encoded[:0]))
|
|
}
|
|
|
|
func (r *Resp) Decode(decoded []byte) int {
|
|
decoded = decoded[:0]
|
|
msg := r.msg
|
|
if msg.Questions[0].Type == dnsmessage.TypeTXT {
|
|
if len(msg.Answers) == 1 && r.domain.IsDomain(msg.Answers[0].Header.Name) && msg.Answers[0].Header.Type == dnsmessage.TypeTXT {
|
|
for i := range msg.Answers[0].Body.(*dnsmessage.TXTResource).TXT {
|
|
decoded = append(decoded, msg.Answers[0].Body.(*dnsmessage.TXTResource).TXT[i]...)
|
|
}
|
|
}
|
|
return len(decoded)
|
|
} else {
|
|
var frags [][]byte
|
|
for i := range msg.Answers {
|
|
if !r.domain.IsDomain(msg.Answers[i].Header.Name) || msg.Answers[i].Header.Type != msg.Questions[0].Type {
|
|
continue
|
|
}
|
|
switch msg.Questions[0].Type {
|
|
case dnsmessage.TypeA:
|
|
frags = append(frags, msg.Answers[i].Body.(*dnsmessage.AResource).A[:])
|
|
case dnsmessage.TypeCNAME:
|
|
var decoded [255]byte
|
|
n := r.domain.Decode(&decoded, msg.Answers[i].Body.(*dnsmessage.CNAMEResource).CNAME)
|
|
if n == 0 {
|
|
continue
|
|
}
|
|
frags = append(frags, decoded[:n])
|
|
case dnsmessage.TypeAAAA:
|
|
frags = append(frags, msg.Answers[i].Body.(*dnsmessage.AAAAResource).AAAA[:])
|
|
}
|
|
}
|
|
sort.Slice(frags, func(i, j int) bool {
|
|
return frags[i][0] < frags[j][0]
|
|
})
|
|
if len(frags) < 1 || len(frags[0]) < 2 || int(frags[0][1]) > len(frags) {
|
|
return 0
|
|
}
|
|
decoded = append(decoded, frags[0][2:]...)
|
|
for i := range frags {
|
|
if i > 0 {
|
|
if frags[i][0] == frags[i-1][0] {
|
|
return 0
|
|
}
|
|
decoded = append(decoded, frags[i][1:]...)
|
|
}
|
|
}
|
|
return len(decoded)
|
|
}
|
|
}
|
|
|
|
type SendInfo struct {
|
|
stash chan []byte
|
|
ch chan []byte
|
|
deadline time.Time
|
|
}
|
|
|
|
type SendManager struct {
|
|
m map[ClientID]*SendInfo
|
|
ch chan struct{}
|
|
mu sync.Mutex
|
|
}
|
|
|
|
func NewSendManager() *SendManager {
|
|
m := &SendManager{
|
|
m: make(map[ClientID]*SendInfo),
|
|
ch: make(chan struct{}),
|
|
}
|
|
go m.gc()
|
|
return m
|
|
}
|
|
|
|
func (m *SendManager) closed() bool {
|
|
select {
|
|
case <-m.ch:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func (m *SendManager) gc() {
|
|
ticker := time.NewTicker(sendTTL)
|
|
defer ticker.Stop()
|
|
for {
|
|
select {
|
|
case <-m.ch:
|
|
return
|
|
case now := <-ticker.C:
|
|
m.mu.Lock()
|
|
for key, info := range m.m {
|
|
if now.After(info.deadline) {
|
|
close(info.stash)
|
|
close(info.ch)
|
|
delete(m.m, key)
|
|
}
|
|
}
|
|
m.mu.Unlock()
|
|
ticker.Reset(sendTTL)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (m *SendManager) Push(clientID ClientID, p []byte) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
info := m.m[clientID]
|
|
if info == nil {
|
|
info = &SendInfo{
|
|
stash: make(chan []byte, 1),
|
|
ch: make(chan []byte, 128),
|
|
deadline: time.Now().Add(sendTTL),
|
|
}
|
|
m.m[clientID] = info
|
|
}
|
|
b := make([]byte, len(p))
|
|
copy(b, p)
|
|
select {
|
|
case info.ch <- b:
|
|
default:
|
|
}
|
|
}
|
|
|
|
func (m *SendManager) Stash(clientID ClientID, p []byte) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
info := m.m[clientID]
|
|
if info == nil {
|
|
return
|
|
}
|
|
info.deadline = time.Now().Add(sendTTL)
|
|
select {
|
|
case info.stash <- p:
|
|
default:
|
|
}
|
|
}
|
|
|
|
func (m *SendManager) Pop(clientID ClientID) (chan []byte, chan []byte) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
info := m.m[clientID]
|
|
if info == nil {
|
|
info = &SendInfo{
|
|
stash: make(chan []byte, 1),
|
|
ch: make(chan []byte, 128),
|
|
}
|
|
m.m[clientID] = info
|
|
}
|
|
info.deadline = time.Now().Add(sendTTL)
|
|
return info.ch, info.stash
|
|
}
|
|
|
|
func (m *SendManager) Close() {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
if m.closed() {
|
|
return
|
|
}
|
|
close(m.ch)
|
|
for key, info := range m.m {
|
|
close(info.stash)
|
|
close(info.ch)
|
|
delete(m.m, key)
|
|
}
|
|
}
|