mirror of
https://github.com/xvzc/SpoofDPI.git
synced 2024-12-22 14:26:31 +00:00
feat: add IPv6 support (#161)
* Add support for IPv6 lookups. * Refactor DNSResolver. * Make listener support IPv6.
This commit is contained in:
parent
ab4d6819c7
commit
15163ca5fe
27
dns/addrselect/LICENSE
Normal file
27
dns/addrselect/LICENSE
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
Copyright 2009 The Go Authors.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google LLC nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
377
dns/addrselect/addrselect.go
Normal file
377
dns/addrselect/addrselect.go
Normal file
@ -0,0 +1,377 @@
|
|||||||
|
package addrselect
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Copyright 2015 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Minimal RFC 6724 address selection.
|
||||||
|
|
||||||
|
func SortByRFC6724(addrs []net.IPAddr) {
|
||||||
|
if len(addrs) < 2 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sortByRFC6724withSrcs(addrs, srcAddrs(addrs))
|
||||||
|
}
|
||||||
|
|
||||||
|
func sortByRFC6724withSrcs(addrs []net.IPAddr, srcs []netip.Addr) {
|
||||||
|
if len(addrs) != len(srcs) {
|
||||||
|
panic("internal error")
|
||||||
|
}
|
||||||
|
addrAttr := make([]ipAttr, len(addrs))
|
||||||
|
srcAttr := make([]ipAttr, len(srcs))
|
||||||
|
for i, v := range addrs {
|
||||||
|
addrAttrIP, _ := netip.AddrFromSlice(v.IP)
|
||||||
|
addrAttr[i] = ipAttrOf(addrAttrIP)
|
||||||
|
srcAttr[i] = ipAttrOf(srcs[i])
|
||||||
|
}
|
||||||
|
sort.Stable(&byRFC6724{
|
||||||
|
addrs: addrs,
|
||||||
|
addrAttr: addrAttr,
|
||||||
|
srcs: srcs,
|
||||||
|
srcAttr: srcAttr,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// srcAddrs tries to UDP-connect to each address to see if it has a
|
||||||
|
// route. (This doesn't send any packets). The destination port
|
||||||
|
// number is irrelevant.
|
||||||
|
func srcAddrs(addrs []net.IPAddr) []netip.Addr {
|
||||||
|
srcs := make([]netip.Addr, len(addrs))
|
||||||
|
dst := net.UDPAddr{Port: 9}
|
||||||
|
for i := range addrs {
|
||||||
|
dst.IP = addrs[i].IP
|
||||||
|
dst.Zone = addrs[i].Zone
|
||||||
|
c, err := net.DialUDP("udp", nil, &dst)
|
||||||
|
if err == nil {
|
||||||
|
if src, ok := c.LocalAddr().(*net.UDPAddr); ok {
|
||||||
|
srcs[i], _ = netip.AddrFromSlice(src.IP)
|
||||||
|
}
|
||||||
|
c.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return srcs
|
||||||
|
}
|
||||||
|
|
||||||
|
type ipAttr struct {
|
||||||
|
Scope scope
|
||||||
|
Precedence uint8
|
||||||
|
Label uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
func ipAttrOf(ip netip.Addr) ipAttr {
|
||||||
|
if !ip.IsValid() {
|
||||||
|
return ipAttr{}
|
||||||
|
}
|
||||||
|
match := rfc6724policyTable.Classify(ip)
|
||||||
|
return ipAttr{
|
||||||
|
Scope: classifyScope(ip),
|
||||||
|
Precedence: match.Precedence,
|
||||||
|
Label: match.Label,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type byRFC6724 struct {
|
||||||
|
addrs []net.IPAddr // addrs to sort
|
||||||
|
addrAttr []ipAttr
|
||||||
|
srcs []netip.Addr // or not valid addr if unreachable
|
||||||
|
srcAttr []ipAttr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *byRFC6724) Len() int { return len(s.addrs) }
|
||||||
|
|
||||||
|
func (s *byRFC6724) Swap(i, j int) {
|
||||||
|
s.addrs[i], s.addrs[j] = s.addrs[j], s.addrs[i]
|
||||||
|
s.srcs[i], s.srcs[j] = s.srcs[j], s.srcs[i]
|
||||||
|
s.addrAttr[i], s.addrAttr[j] = s.addrAttr[j], s.addrAttr[i]
|
||||||
|
s.srcAttr[i], s.srcAttr[j] = s.srcAttr[j], s.srcAttr[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Less reports whether i is a better destination address for this
|
||||||
|
// host than j.
|
||||||
|
//
|
||||||
|
// The algorithm and variable names comes from RFC 6724 section 6.
|
||||||
|
func (s *byRFC6724) Less(i, j int) bool {
|
||||||
|
DA := s.addrs[i].IP
|
||||||
|
DB := s.addrs[j].IP
|
||||||
|
SourceDA := s.srcs[i]
|
||||||
|
SourceDB := s.srcs[j]
|
||||||
|
attrDA := &s.addrAttr[i]
|
||||||
|
attrDB := &s.addrAttr[j]
|
||||||
|
attrSourceDA := &s.srcAttr[i]
|
||||||
|
attrSourceDB := &s.srcAttr[j]
|
||||||
|
|
||||||
|
const preferDA = true
|
||||||
|
const preferDB = false
|
||||||
|
|
||||||
|
// Rule 1: Avoid unusable destinations.
|
||||||
|
// If DB is known to be unreachable or if Source(DB) is undefined, then
|
||||||
|
// prefer DA. Similarly, if DA is known to be unreachable or if
|
||||||
|
// Source(DA) is undefined, then prefer DB.
|
||||||
|
if !SourceDA.IsValid() && !SourceDB.IsValid() {
|
||||||
|
return false // "equal"
|
||||||
|
}
|
||||||
|
if !SourceDB.IsValid() {
|
||||||
|
return preferDA
|
||||||
|
}
|
||||||
|
if !SourceDA.IsValid() {
|
||||||
|
return preferDB
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rule 2: Prefer matching scope.
|
||||||
|
// If Scope(DA) = Scope(Source(DA)) and Scope(DB) <> Scope(Source(DB)),
|
||||||
|
// then prefer DA. Similarly, if Scope(DA) <> Scope(Source(DA)) and
|
||||||
|
// Scope(DB) = Scope(Source(DB)), then prefer DB.
|
||||||
|
if attrDA.Scope == attrSourceDA.Scope && attrDB.Scope != attrSourceDB.Scope {
|
||||||
|
return preferDA
|
||||||
|
}
|
||||||
|
if attrDA.Scope != attrSourceDA.Scope && attrDB.Scope == attrSourceDB.Scope {
|
||||||
|
return preferDB
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rule 3: Avoid deprecated addresses.
|
||||||
|
// If Source(DA) is deprecated and Source(DB) is not, then prefer DB.
|
||||||
|
// Similarly, if Source(DA) is not deprecated and Source(DB) is
|
||||||
|
// deprecated, then prefer DA.
|
||||||
|
|
||||||
|
// TODO(bradfitz): implement? low priority for now.
|
||||||
|
|
||||||
|
// Rule 4: Prefer home addresses.
|
||||||
|
// If Source(DA) is simultaneously a home address and care-of address
|
||||||
|
// and Source(DB) is not, then prefer DA. Similarly, if Source(DB) is
|
||||||
|
// simultaneously a home address and care-of address and Source(DA) is
|
||||||
|
// not, then prefer DB.
|
||||||
|
|
||||||
|
// TODO(bradfitz): implement? low priority for now.
|
||||||
|
|
||||||
|
// Rule 5: Prefer matching label.
|
||||||
|
// If Label(Source(DA)) = Label(DA) and Label(Source(DB)) <> Label(DB),
|
||||||
|
// then prefer DA. Similarly, if Label(Source(DA)) <> Label(DA) and
|
||||||
|
// Label(Source(DB)) = Label(DB), then prefer DB.
|
||||||
|
if attrSourceDA.Label == attrDA.Label &&
|
||||||
|
attrSourceDB.Label != attrDB.Label {
|
||||||
|
return preferDA
|
||||||
|
}
|
||||||
|
if attrSourceDA.Label != attrDA.Label &&
|
||||||
|
attrSourceDB.Label == attrDB.Label {
|
||||||
|
return preferDB
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rule 6: Prefer higher precedence.
|
||||||
|
// If Precedence(DA) > Precedence(DB), then prefer DA. Similarly, if
|
||||||
|
// Precedence(DA) < Precedence(DB), then prefer DB.
|
||||||
|
if attrDA.Precedence > attrDB.Precedence {
|
||||||
|
return preferDA
|
||||||
|
}
|
||||||
|
if attrDA.Precedence < attrDB.Precedence {
|
||||||
|
return preferDB
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rule 7: Prefer native transport.
|
||||||
|
// If DA is reached via an encapsulating transition mechanism (e.g.,
|
||||||
|
// IPv6 in IPv4) and DB is not, then prefer DB. Similarly, if DB is
|
||||||
|
// reached via encapsulation and DA is not, then prefer DA.
|
||||||
|
|
||||||
|
// TODO(bradfitz): implement? low priority for now.
|
||||||
|
|
||||||
|
// Rule 8: Prefer smaller scope.
|
||||||
|
// If Scope(DA) < Scope(DB), then prefer DA. Similarly, if Scope(DA) >
|
||||||
|
// Scope(DB), then prefer DB.
|
||||||
|
if attrDA.Scope < attrDB.Scope {
|
||||||
|
return preferDA
|
||||||
|
}
|
||||||
|
if attrDA.Scope > attrDB.Scope {
|
||||||
|
return preferDB
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rule 9: Use the longest matching prefix.
|
||||||
|
// When DA and DB belong to the same address family (both are IPv6 or
|
||||||
|
// both are IPv4 [but see below]): If CommonPrefixLen(Source(DA), DA) >
|
||||||
|
// CommonPrefixLen(Source(DB), DB), then prefer DA. Similarly, if
|
||||||
|
// CommonPrefixLen(Source(DA), DA) < CommonPrefixLen(Source(DB), DB),
|
||||||
|
// then prefer DB.
|
||||||
|
//
|
||||||
|
// However, applying this rule to IPv4 addresses causes
|
||||||
|
// problems (see issues 13283 and 18518), so limit to IPv6.
|
||||||
|
if DA.To4() == nil && DB.To4() == nil {
|
||||||
|
commonA := commonPrefixLen(SourceDA, DA)
|
||||||
|
commonB := commonPrefixLen(SourceDB, DB)
|
||||||
|
|
||||||
|
if commonA > commonB {
|
||||||
|
return preferDA
|
||||||
|
}
|
||||||
|
if commonA < commonB {
|
||||||
|
return preferDB
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rule 10: Otherwise, leave the order unchanged.
|
||||||
|
// If DA preceded DB in the original list, prefer DA.
|
||||||
|
// Otherwise, prefer DB.
|
||||||
|
return false // "equal"
|
||||||
|
}
|
||||||
|
|
||||||
|
type policyTableEntry struct {
|
||||||
|
Prefix netip.Prefix
|
||||||
|
Precedence uint8
|
||||||
|
Label uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
type policyTable []policyTableEntry
|
||||||
|
|
||||||
|
// RFC 6724 section 2.1.
|
||||||
|
// Items are sorted by the size of their Prefix.Mask.Size,
|
||||||
|
var rfc6724policyTable = policyTable{
|
||||||
|
{
|
||||||
|
// "::1/128"
|
||||||
|
Prefix: netip.PrefixFrom(netip.AddrFrom16([16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01}), 128),
|
||||||
|
Precedence: 50,
|
||||||
|
Label: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// "::ffff:0:0/96"
|
||||||
|
// IPv4-compatible, etc.
|
||||||
|
Prefix: netip.PrefixFrom(netip.AddrFrom16([16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff}), 96),
|
||||||
|
Precedence: 35,
|
||||||
|
Label: 4,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// "::/96"
|
||||||
|
Prefix: netip.PrefixFrom(netip.AddrFrom16([16]byte{}), 96),
|
||||||
|
Precedence: 1,
|
||||||
|
Label: 3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// "2001::/32"
|
||||||
|
// Teredo
|
||||||
|
Prefix: netip.PrefixFrom(netip.AddrFrom16([16]byte{0x20, 0x01}), 32),
|
||||||
|
Precedence: 5,
|
||||||
|
Label: 5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// "2002::/16"
|
||||||
|
// 6to4
|
||||||
|
Prefix: netip.PrefixFrom(netip.AddrFrom16([16]byte{0x20, 0x02}), 16),
|
||||||
|
Precedence: 30,
|
||||||
|
Label: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// "3ffe::/16"
|
||||||
|
Prefix: netip.PrefixFrom(netip.AddrFrom16([16]byte{0x3f, 0xfe}), 16),
|
||||||
|
Precedence: 1,
|
||||||
|
Label: 12,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// "fec0::/10"
|
||||||
|
Prefix: netip.PrefixFrom(netip.AddrFrom16([16]byte{0xfe, 0xc0}), 10),
|
||||||
|
Precedence: 1,
|
||||||
|
Label: 11,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// "fc00::/7"
|
||||||
|
Prefix: netip.PrefixFrom(netip.AddrFrom16([16]byte{0xfc}), 7),
|
||||||
|
Precedence: 3,
|
||||||
|
Label: 13,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// "::/0"
|
||||||
|
Prefix: netip.PrefixFrom(netip.AddrFrom16([16]byte{}), 0),
|
||||||
|
Precedence: 40,
|
||||||
|
Label: 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Classify returns the policyTableEntry of the entry with the longest
|
||||||
|
// matching prefix that contains ip.
|
||||||
|
// The table t must be sorted from largest mask size to smallest.
|
||||||
|
func (t policyTable) Classify(ip netip.Addr) policyTableEntry {
|
||||||
|
// Prefix.Contains() will not match an IPv6 prefix for an IPv4 address.
|
||||||
|
if ip.Is4() {
|
||||||
|
ip = netip.AddrFrom16(ip.As16())
|
||||||
|
}
|
||||||
|
for _, ent := range t {
|
||||||
|
if ent.Prefix.Contains(ip) {
|
||||||
|
return ent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return policyTableEntry{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RFC 6724 section 3.1.
|
||||||
|
type scope uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
scopeInterfaceLocal scope = 0x1
|
||||||
|
scopeLinkLocal scope = 0x2
|
||||||
|
scopeAdminLocal scope = 0x4
|
||||||
|
scopeSiteLocal scope = 0x5
|
||||||
|
scopeOrgLocal scope = 0x8
|
||||||
|
scopeGlobal scope = 0xe
|
||||||
|
)
|
||||||
|
|
||||||
|
func classifyScope(ip netip.Addr) scope {
|
||||||
|
if ip.IsLoopback() || ip.IsLinkLocalUnicast() {
|
||||||
|
return scopeLinkLocal
|
||||||
|
}
|
||||||
|
ipv6 := ip.Is6() && !ip.Is4In6()
|
||||||
|
ipv6AsBytes := ip.As16()
|
||||||
|
if ipv6 && ip.IsMulticast() {
|
||||||
|
return scope(ipv6AsBytes[1] & 0xf)
|
||||||
|
}
|
||||||
|
// Site-local addresses are defined in RFC 3513 section 2.5.6
|
||||||
|
// (and deprecated in RFC 3879).
|
||||||
|
if ipv6 && ipv6AsBytes[0] == 0xfe && ipv6AsBytes[1]&0xc0 == 0xc0 {
|
||||||
|
return scopeSiteLocal
|
||||||
|
}
|
||||||
|
return scopeGlobal
|
||||||
|
}
|
||||||
|
|
||||||
|
// commonPrefixLen reports the length of the longest prefix (looking
|
||||||
|
// at the most significant, or leftmost, bits) that the
|
||||||
|
// two addresses have in common, up to the length of a's prefix (i.e.,
|
||||||
|
// the portion of the address not including the interface ID).
|
||||||
|
//
|
||||||
|
// If a or b is an IPv4 address as an IPv6 address, the IPv4 addresses
|
||||||
|
// are compared (with max common prefix length of 32).
|
||||||
|
// If a and b are different IP versions, 0 is returned.
|
||||||
|
//
|
||||||
|
// See https://tools.ietf.org/html/rfc6724#section-2.2
|
||||||
|
func commonPrefixLen(a netip.Addr, b net.IP) (cpl int) {
|
||||||
|
if b4 := b.To4(); b4 != nil {
|
||||||
|
b = b4
|
||||||
|
}
|
||||||
|
aAsSlice := a.AsSlice()
|
||||||
|
if len(aAsSlice) != len(b) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
// If IPv6, only up to the prefix (first 64 bits)
|
||||||
|
if len(aAsSlice) > 8 {
|
||||||
|
aAsSlice = aAsSlice[:8]
|
||||||
|
b = b[:8]
|
||||||
|
}
|
||||||
|
for len(aAsSlice) > 0 {
|
||||||
|
if aAsSlice[0] == b[0] {
|
||||||
|
cpl += 8
|
||||||
|
aAsSlice = aAsSlice[1:]
|
||||||
|
b = b[1:]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
bits := 8
|
||||||
|
ab, bb := aAsSlice[0], b[0]
|
||||||
|
for {
|
||||||
|
ab >>= 1
|
||||||
|
bb >>= 1
|
||||||
|
bits--
|
||||||
|
if ab == bb {
|
||||||
|
cpl += bits
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
302
dns/dns.go
302
dns/dns.go
@ -3,108 +3,256 @@ package dns
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"net"
|
"fmt"
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/xvzc/SpoofDPI/dns/addrselect"
|
||||||
"github.com/xvzc/SpoofDPI/util"
|
"github.com/xvzc/SpoofDPI/util"
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DnsResolver struct {
|
type client interface {
|
||||||
|
Resolve(ctx context.Context, host string) ([]net.IPAddr, error)
|
||||||
|
String() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Resolver struct {
|
||||||
host string
|
host string
|
||||||
port string
|
port string
|
||||||
enableDoh bool
|
enableDoh bool
|
||||||
|
systemClient client
|
||||||
|
customClient client
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewResolver(config *util.Config) *DnsResolver {
|
func NewResolver(config *util.Config) *Resolver {
|
||||||
return &DnsResolver{
|
addr := *config.DnsAddr
|
||||||
|
port := strconv.Itoa(*config.DnsPort)
|
||||||
|
server := net.JoinHostPort(addr, port)
|
||||||
|
|
||||||
|
var systemClient client
|
||||||
|
if config.AllowedPatterns != nil {
|
||||||
|
systemClient = NewSystemClient()
|
||||||
|
}
|
||||||
|
|
||||||
|
var customClient client
|
||||||
|
if *config.EnableDoh {
|
||||||
|
customClient = NewDoHClient(addr)
|
||||||
|
} else {
|
||||||
|
customClient = NewCustomClient(server)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Resolver{
|
||||||
host: *config.DnsAddr,
|
host: *config.DnsAddr,
|
||||||
port: strconv.Itoa(*config.DnsPort),
|
port: port,
|
||||||
enableDoh: *config.EnableDoh,
|
enableDoh: *config.EnableDoh,
|
||||||
|
systemClient: systemClient,
|
||||||
|
customClient: customClient,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DnsResolver) Lookup(domain string, useSystemDns bool) (string, error) {
|
func (d *Resolver) Lookup(domain string, useSystemDns bool) (string, error) {
|
||||||
ipRegex := "^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"
|
if _, err := parseAddr(domain); err == nil {
|
||||||
|
|
||||||
if r, _ := regexp.MatchString(ipRegex, domain); r {
|
|
||||||
return domain, nil
|
return domain, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if useSystemDns {
|
clt := d.getClient(useSystemDns)
|
||||||
log.Debug("[DNS] ", domain, " resolving with system dns")
|
|
||||||
return systemLookup(domain)
|
|
||||||
}
|
|
||||||
|
|
||||||
if d.enableDoh {
|
|
||||||
log.Debug("[DNS] ", domain, " resolving with dns over https")
|
|
||||||
return dohLookup(d.host, domain)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug("[DNS] ", domain, " resolving with custom dns")
|
|
||||||
return customLookup(d.host, d.port, domain)
|
|
||||||
}
|
|
||||||
|
|
||||||
func customLookup(host string, port string, domain string) (string, error) {
|
|
||||||
|
|
||||||
dnsServer := host + ":" + port
|
|
||||||
|
|
||||||
msg := new(dns.Msg)
|
|
||||||
msg.SetQuestion(dns.Fqdn(domain), dns.TypeA)
|
|
||||||
|
|
||||||
c := new(dns.Client)
|
|
||||||
|
|
||||||
response, _, err := c.Exchange(msg, dnsServer)
|
|
||||||
if err != nil {
|
|
||||||
return "", errors.New("could not resolve the domain(custom)")
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, answer := range response.Answer {
|
|
||||||
if record, ok := answer.(*dns.A); ok {
|
|
||||||
return record.A.String(), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", errors.New("no record found(custom)")
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func systemLookup(domain string) (string, error) {
|
|
||||||
systemResolver := net.Resolver{PreferGo: true}
|
|
||||||
ips, err := systemResolver.LookupIPAddr(context.Background(), domain)
|
|
||||||
if err != nil {
|
|
||||||
return "", errors.New("could not resolve the domain(system)")
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, ip := range ips {
|
|
||||||
return ip.String(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", errors.New("no record found(system)")
|
|
||||||
}
|
|
||||||
|
|
||||||
func dohLookup(host string, domain string) (string, error) {
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
client := getDOHClient(host)
|
log.Debugf("[DNS] resolving %s using %s", domain, clt)
|
||||||
|
t := time.Now()
|
||||||
msg := new(dns.Msg)
|
addrs, err := clt.Resolve(ctx, domain)
|
||||||
msg.SetQuestion(dns.Fqdn(domain), dns.TypeA)
|
|
||||||
|
|
||||||
response, err := client.dohExchange(ctx, msg)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", errors.New("could not resolve the domain(doh)")
|
return "", fmt.Errorf("%s: %w", clt, err)
|
||||||
|
}
|
||||||
|
lookupTime := time.Since(t).Milliseconds()
|
||||||
|
|
||||||
|
addr := addrs[0].String()
|
||||||
|
log.Debugf("[DNS] resolved %s to %s in %d ms", domain, addr, lookupTime)
|
||||||
|
return addr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, answer := range response.Answer {
|
func (d *Resolver) getClient(useSystemDns bool) client {
|
||||||
if record, ok := answer.(*dns.A); ok {
|
if useSystemDns {
|
||||||
return record.A.String(), nil
|
return d.systemClient
|
||||||
|
} else {
|
||||||
|
return d.customClient
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", errors.New("no record found(doh)")
|
type SystemClient struct {
|
||||||
|
client *net.Resolver
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSystemClient() *SystemClient {
|
||||||
|
return &SystemClient{
|
||||||
|
client: &net.Resolver{PreferGo: true},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *SystemClient) String() string {
|
||||||
|
return "SystemClient"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *SystemClient) Resolve(ctx context.Context, host string) ([]net.IPAddr, error) {
|
||||||
|
addrs, err := c.client.LookupIPAddr(ctx, host)
|
||||||
|
if err != nil {
|
||||||
|
return []net.IPAddr{}, err
|
||||||
|
}
|
||||||
|
return addrs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type sendMsgFunc = func(ctx context.Context, msg *dns.Msg) (*dns.Msg, error)
|
||||||
|
|
||||||
|
type customDNSResult struct {
|
||||||
|
msg *dns.Msg
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
type CustomClient struct {
|
||||||
|
server string
|
||||||
|
sendMsgFn sendMsgFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CustomClient) Resolve(ctx context.Context, host string) ([]net.IPAddr, error) {
|
||||||
|
queryTypes := []uint16{dns.TypeAAAA, dns.TypeA}
|
||||||
|
resultCh := c.makeLookups(ctx, host, queryTypes)
|
||||||
|
|
||||||
|
addrs, err := c.processResults(ctx, resultCh)
|
||||||
|
return addrs, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CustomClient) makeLookups(ctx context.Context, host string, queryTypes []uint16) <-chan *customDNSResult {
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
resCh := make(chan *customDNSResult)
|
||||||
|
|
||||||
|
lookup := func(qType uint16) {
|
||||||
|
defer wg.Done()
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case resCh <- c.makeLookup(ctx, host, qType):
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, queryType := range queryTypes {
|
||||||
|
wg.Add(1)
|
||||||
|
go lookup(queryType)
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
wg.Wait()
|
||||||
|
close(resCh)
|
||||||
|
}()
|
||||||
|
return resCh
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CustomClient) makeLookup(ctx context.Context, host string, queryType uint16) *customDNSResult {
|
||||||
|
msg := c.newMsg(host, queryType)
|
||||||
|
resp, err := c.sendMsg(ctx, msg)
|
||||||
|
if err != nil {
|
||||||
|
queryName := recordTypeIDToName(queryType)
|
||||||
|
err = fmt.Errorf("resolving %s, query type %s: %w", host, queryName, err)
|
||||||
|
return &customDNSResult{err: err}
|
||||||
|
}
|
||||||
|
return &customDNSResult{msg: resp}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CustomClient) newMsg(host string, qType uint16) *dns.Msg {
|
||||||
|
msg := new(dns.Msg)
|
||||||
|
msg.SetQuestion(dns.Fqdn(host), qType)
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CustomClient) sendMsg(ctx context.Context, msg *dns.Msg) (*dns.Msg, error) {
|
||||||
|
resp, err := c.sendMsgFn(ctx, msg)
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CustomClient) processResults(ctx context.Context, resCh <-chan *customDNSResult) ([]net.IPAddr, error) {
|
||||||
|
var errs []error
|
||||||
|
var addrs []net.IPAddr
|
||||||
|
|
||||||
|
for result := range resCh {
|
||||||
|
if result.err != nil {
|
||||||
|
errs = append(errs, result.err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
resultAddrs := parseAddrsFromMsg(result.msg)
|
||||||
|
addrs = append(addrs, resultAddrs...)
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, errors.New("cancelled")
|
||||||
|
default:
|
||||||
|
if len(addrs) == 0 {
|
||||||
|
return addrs, errors.Join(errs...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sortAddrs(addrs)
|
||||||
|
return addrs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CustomClient) String() string {
|
||||||
|
return fmt.Sprintf("CustomClient for %s", c.server)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCustomClient(server string) *CustomClient {
|
||||||
|
clt := &dns.Client{}
|
||||||
|
return &CustomClient{
|
||||||
|
server: server,
|
||||||
|
sendMsgFn: func(ctx context.Context, msg *dns.Msg) (*dns.Msg, error) {
|
||||||
|
resp, _, err := clt.Exchange(msg, server)
|
||||||
|
return resp, err
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDoHClient(host string) *CustomClient {
|
||||||
|
server := net.JoinHostPort(host, "443")
|
||||||
|
clt := getDOHClient(server)
|
||||||
|
return &CustomClient{
|
||||||
|
server: server,
|
||||||
|
sendMsgFn: func(ctx context.Context, msg *dns.Msg) (*dns.Msg, error) {
|
||||||
|
return clt.dohExchange(ctx, msg)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func recordTypeIDToName(id uint16) string {
|
||||||
|
switch id {
|
||||||
|
case 1:
|
||||||
|
return "A"
|
||||||
|
case 28:
|
||||||
|
return "AAAA"
|
||||||
|
}
|
||||||
|
return strconv.FormatUint(uint64(id), 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseAddrsFromMsg(msg *dns.Msg) []net.IPAddr {
|
||||||
|
var addrs []net.IPAddr
|
||||||
|
|
||||||
|
for _, record := range msg.Answer {
|
||||||
|
switch ipRecord := record.(type) {
|
||||||
|
case *dns.A:
|
||||||
|
addrs = append(addrs, net.IPAddr{IP: ipRecord.A})
|
||||||
|
case *dns.AAAA:
|
||||||
|
addrs = append(addrs, net.IPAddr{IP: ipRecord.AAAA})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return addrs
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseAddr(addr string) (net.IP, error) {
|
||||||
|
parsed, err := netip.ParseAddr(addr)
|
||||||
|
if err != nil {
|
||||||
|
return net.IP{}, fmt.Errorf("parsing %s as an IP address: %w", addr, err)
|
||||||
|
}
|
||||||
|
return parsed.AsSlice(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func sortAddrs(addrs []net.IPAddr) {
|
||||||
|
addrselect.SortByRFC6724(addrs)
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ type Proxy struct {
|
|||||||
addr string
|
addr string
|
||||||
port int
|
port int
|
||||||
timeout int
|
timeout int
|
||||||
resolver *dns.DnsResolver
|
resolver *dns.Resolver
|
||||||
windowSize int
|
windowSize int
|
||||||
allowedPattern []*regexp.Regexp
|
allowedPattern []*regexp.Regexp
|
||||||
}
|
}
|
||||||
@ -34,7 +34,7 @@ func New(config *util.Config) *Proxy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *Proxy) Start() {
|
func (pxy *Proxy) Start() {
|
||||||
l, err := net.ListenTCP("tcp4", &net.TCPAddr{IP: net.ParseIP(pxy.addr), Port: pxy.port})
|
l, err := net.ListenTCP("tcp", &net.TCPAddr{IP: net.ParseIP(pxy.addr), Port: pxy.port})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("[PROXY] error creating listener: ", err)
|
log.Fatal("[PROXY] error creating listener: ", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@ -114,11 +114,6 @@ func (pxy *Proxy) patternMatches(bytes []byte) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func isLoopedRequest(ip net.IP) bool {
|
func isLoopedRequest(ip net.IP) bool {
|
||||||
// we don't handle IPv6 at all it seems
|
|
||||||
if ip.To4() == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if ip.IsLoopback() {
|
if ip.IsLoopback() {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -133,7 +128,7 @@ func isLoopedRequest(ip net.IP) bool {
|
|||||||
|
|
||||||
for _, addr := range addr {
|
for _, addr := range addr {
|
||||||
if ipnet, ok := addr.(*net.IPNet); ok {
|
if ipnet, ok := addr.(*net.IPNet); ok {
|
||||||
if ipnet.IP.To4() != nil && ipnet.IP.To4().Equal(ip) {
|
if ipnet.IP.Equal(ip) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user