Files
Xray-core/transport/internet/finalmask/xdns/resp.go
T

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