mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-10-17 21:14:01 +00:00
https://github.com/XTLS/Xray-core/pull/6718#issuecomment-5894987590 Fixes https://github.com/XTLS/Xray-core/issues/6692
216 lines
4.1 KiB
Go
216 lines
4.1 KiB
Go
package xdns
|
|
|
|
import (
|
|
"encoding/base32"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"golang.org/x/net/dns/dnsmessage"
|
|
"golang.org/x/net/idna"
|
|
)
|
|
|
|
func Lower(c byte) byte {
|
|
if c >= 'A' && c <= 'Z' {
|
|
return c + ('a' - 'A')
|
|
}
|
|
return c
|
|
}
|
|
|
|
func ToUpper(b []byte) {
|
|
for i, c := range b {
|
|
if c >= 'a' && c <= 'z' {
|
|
b[i] = c - 'a' + 'A'
|
|
}
|
|
}
|
|
}
|
|
|
|
func ToLower(b []byte) {
|
|
for i, c := range b {
|
|
if c >= 'A' && c <= 'Z' {
|
|
b[i] = c - 'A' + 'a'
|
|
}
|
|
}
|
|
}
|
|
|
|
func NewTable() ([256]int, [256]int) {
|
|
var t, t_ [256]int
|
|
for i := range t {
|
|
t[i] = base32Encoding.DecodedLen(i)
|
|
}
|
|
for i := range t_ {
|
|
t_[i] = base32Encoding.EncodedLen(i)
|
|
}
|
|
return t, t_
|
|
}
|
|
|
|
const (
|
|
TypeA uint16 = 1
|
|
TypeCNAME uint16 = 5
|
|
TypeTXT uint16 = 16
|
|
TypeAAAA uint16 = 28
|
|
)
|
|
|
|
var (
|
|
base32Encoding = base32.StdEncoding.WithPadding(base32.NoPadding)
|
|
table, table_ = NewTable()
|
|
TypeMap = map[uint16]byte{
|
|
TypeA: 0,
|
|
TypeCNAME: 1,
|
|
TypeTXT: 2,
|
|
TypeAAAA: 3,
|
|
}
|
|
TypeMap_ = map[byte]uint16{
|
|
0: TypeA,
|
|
1: TypeCNAME,
|
|
2: TypeTXT,
|
|
3: TypeAAAA,
|
|
}
|
|
)
|
|
|
|
type Domain struct {
|
|
name dnsmessage.Name
|
|
lenLimit int
|
|
labelLimit int
|
|
types []uint16
|
|
edns0 uint16
|
|
|
|
cap int
|
|
lenMax int
|
|
}
|
|
|
|
func NewDomain(domain string, lenLimit int, labelLimit int, types []uint16, edns0 uint16) (*Domain, error) {
|
|
if strings.Contains(domain, "..") {
|
|
return nil, errors.New("invalid domain")
|
|
}
|
|
if lenLimit < 0 || lenLimit > 255 {
|
|
return nil, errors.New("lenLimit < 0 || lenLimit > 255")
|
|
}
|
|
if labelLimit < 0 || labelLimit > 63 {
|
|
return nil, errors.New("labelLimit < 0 || labelLimit > 63")
|
|
}
|
|
if len(types) == 0 {
|
|
return nil, errors.New("empty types")
|
|
}
|
|
for i := range types {
|
|
switch types[i] {
|
|
case uint16(dnsmessage.TypeA), uint16(dnsmessage.TypeCNAME), uint16(dnsmessage.TypeTXT), uint16(dnsmessage.TypeAAAA):
|
|
default:
|
|
return nil, errors.New("unknown types")
|
|
}
|
|
}
|
|
if edns0 != 0 && (edns0 < 512 || edns0 > 4096) {
|
|
return nil, errors.New("edns0 != 0 && (edns0 < 512 || edns0 > 4096)")
|
|
}
|
|
|
|
ascii, err := idna.ToASCII(domain)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ascii = strings.Trim(ascii, ".")
|
|
|
|
name, err := dnsmessage.NewName(domain + ".")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if lenLimit < int(name.Length)+1 {
|
|
return nil, errors.New("lenLimit < int(name.Length)+1")
|
|
}
|
|
n := (lenLimit - int(name.Length) - 1) / (labelLimit + 1)
|
|
left := (lenLimit - int(name.Length) - 1) % (labelLimit + 1)
|
|
total := n * labelLimit
|
|
if left > 1 {
|
|
total += left - 1
|
|
}
|
|
cap := table[total]
|
|
if cap < 17 {
|
|
return nil, errors.New("cap < 17")
|
|
}
|
|
total = table_[cap]
|
|
lenMax := int(name.Length) + 1 + total + total/labelLimit
|
|
if total%labelLimit > 0 {
|
|
lenMax += 1
|
|
}
|
|
return &Domain{
|
|
name: name,
|
|
lenLimit: lenLimit,
|
|
labelLimit: labelLimit,
|
|
types: types,
|
|
edns0: edns0,
|
|
|
|
cap: cap,
|
|
lenMax: lenMax,
|
|
}, nil
|
|
}
|
|
|
|
func (d *Domain) Show() string {
|
|
return fmt.Sprint(d.name, d.cap)
|
|
}
|
|
|
|
func (d *Domain) IsDomain(name dnsmessage.Name) bool {
|
|
if d.name.Length >= name.Length {
|
|
return false
|
|
}
|
|
i := d.name.Length
|
|
j := name.Length
|
|
for i > 0 {
|
|
i--
|
|
j--
|
|
if Lower(d.name.Data[i]) != Lower(name.Data[j]) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (d *Domain) HasType(qtype uint16) bool {
|
|
for i := range d.types {
|
|
if d.types[i] == qtype {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (d *Domain) Encode(data []byte) dnsmessage.Name {
|
|
var name dnsmessage.Name
|
|
var encoded [255]byte
|
|
base32Encoding.Encode(encoded[:], data)
|
|
ToLower(encoded[:table_[len(data)]])
|
|
b1 := name.Data[:0]
|
|
b2 := encoded[:table_[len(data)]]
|
|
for len(b2) > 0 {
|
|
size := min(len(b2), d.labelLimit)
|
|
b1 = append(b1, b2[:size]...)
|
|
b1 = append(b1, '.')
|
|
b2 = b2[size:]
|
|
}
|
|
b1 = append(b1, d.name.Data[:d.name.Length]...)
|
|
if len(b1) > 254 {
|
|
panic("len(b1) > 254")
|
|
}
|
|
name.Length = byte(len(b1))
|
|
return name
|
|
}
|
|
|
|
func (d *Domain) Decode(decoded *[255]byte, name dnsmessage.Name) int {
|
|
if !d.IsDomain(name) {
|
|
return 0
|
|
}
|
|
var encoded [255]byte
|
|
b1 := encoded[:0]
|
|
b2 := name.Data[:name.Length-d.name.Length]
|
|
for i := range b2 {
|
|
if b2[i] != '.' {
|
|
b1 = append(b1, b2[i])
|
|
}
|
|
}
|
|
ToUpper(b1)
|
|
n, err := base32Encoding.Decode(decoded[:], b1)
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
return n
|
|
}
|