package madns
import (
"context"
"net"
"strings"
"github.com/miekg/dns"
ma "github.com/multiformats/go-multiaddr"
)
var (
dnsaddrProtocol = ma .ProtocolWithCode (ma .P_DNSADDR )
dns4Protocol = ma .ProtocolWithCode (ma .P_DNS4 )
dns6Protocol = ma .ProtocolWithCode (ma .P_DNS6 )
dnsProtocol = ma .ProtocolWithCode (ma .P_DNS )
)
var (
ResolvableProtocols = []ma .Protocol {dnsaddrProtocol , dns4Protocol , dns6Protocol , dnsProtocol }
DefaultResolver = &Resolver {def : net .DefaultResolver }
)
const maxResolvedAddrs = 100
const dnsaddrTXTPrefix = "dnsaddr="
type BasicResolver interface {
LookupIPAddr (context .Context , string ) ([]net .IPAddr , error )
LookupTXT (context .Context , string ) ([]string , error )
}
type Resolver struct {
def BasicResolver
custom map [string ]BasicResolver
}
var _ BasicResolver = (*Resolver )(nil )
func NewResolver (opts ...Option ) (*Resolver , error ) {
r := &Resolver {def : net .DefaultResolver }
for _ , opt := range opts {
err := opt (r )
if err != nil {
return nil , err
}
}
return r , nil
}
type Option func (*Resolver ) error
func WithDefaultResolver (def BasicResolver ) Option {
return func (r *Resolver ) error {
r .def = def
return nil
}
}
func WithDomainResolver (domain string , rslv BasicResolver ) Option {
return func (r *Resolver ) error {
if r .custom == nil {
r .custom = make (map [string ]BasicResolver )
}
fqdn := dns .Fqdn (domain )
r .custom [fqdn ] = rslv
return nil
}
}
func (r *Resolver ) getResolver (domain string ) BasicResolver {
fqdn := dns .Fqdn (domain )
rslv , ok := r .custom [fqdn ]
if ok {
return rslv
}
for i := strings .Index (fqdn , "." ); i != -1 ; i = strings .Index (fqdn , "." ) {
fqdn = fqdn [i +1 :]
if fqdn == "" {
break
}
rslv , ok = r .custom [fqdn ]
if ok {
return rslv
}
}
return r .def
}
func (r *Resolver ) Resolve (ctx context .Context , maddr ma .Multiaddr ) ([]ma .Multiaddr , error ) {
if maddr == nil {
return nil , nil
}
preDNS , maddr := ma .SplitFunc (maddr , func (c ma .Component ) bool {
switch c .Protocol ().Code {
case dnsProtocol .Code , dns4Protocol .Code , dns6Protocol .Code , dnsaddrProtocol .Code :
return true
default :
return false
}
})
if maddr == nil {
return []ma .Multiaddr {preDNS }, nil
}
resolve , postDNS := ma .SplitFirst (maddr )
proto := resolve .Protocol ()
value := resolve .Value ()
rslv := r .getResolver (value )
var resolved []ma .Multiaddr
switch proto .Code {
case dns4Protocol .Code , dns6Protocol .Code , dnsProtocol .Code :
v4only := proto .Code == dns4Protocol .Code
v6only := proto .Code == dns6Protocol .Code
records , err := rslv .LookupIPAddr (ctx , value )
if err != nil {
return nil , err
}
for _ , r := range records {
var (
rmaddr ma .Multiaddr
err error
)
ip4 := r .IP .To4 ()
if ip4 == nil {
if v4only {
continue
}
rmaddr , err = ma .NewMultiaddr ("/ip6/" + r .IP .String ())
} else {
if v6only {
continue
}
rmaddr , err = ma .NewMultiaddr ("/ip4/" + ip4 .String ())
}
if err != nil {
return nil , err
}
resolved = append (resolved , rmaddr )
}
case dnsaddrProtocol .Code :
records , err := rslv .LookupTXT (ctx , "_dnsaddr." +value )
if err != nil {
return nil , err
}
length := 0
if postDNS != nil {
length = addrLen (postDNS )
}
for _ , r := range records {
if !strings .HasPrefix (r , dnsaddrTXTPrefix ) {
continue
}
rmaddr , err := ma .NewMultiaddr (r [len (dnsaddrTXTPrefix ):])
if err != nil {
continue
}
if postDNS != nil {
rmlen := addrLen (rmaddr )
if rmlen < length {
continue
}
if !postDNS .Equal (offset (rmaddr , rmlen -length )) {
continue
}
}
if postDNS != nil {
rmaddr = rmaddr .Decapsulate (postDNS )
}
if rmaddr == nil {
continue
}
resolved = append (resolved , rmaddr )
}
default :
panic ("unreachable" )
}
if len (resolved ) == 0 {
return nil , nil
}
if len (resolved ) > maxResolvedAddrs {
resolved = resolved [:maxResolvedAddrs ]
}
if preDNS != nil {
for i , m := range resolved {
resolved [i ] = preDNS .Encapsulate (m )
}
}
if postDNS != nil {
for i , m := range resolved {
resolved [i ] = m .Encapsulate (postDNS )
}
}
return resolved , nil
}
func (r *Resolver ) LookupIPAddr (ctx context .Context , domain string ) ([]net .IPAddr , error ) {
return r .getResolver (domain ).LookupIPAddr (ctx , domain )
}
func (r *Resolver ) LookupTXT (ctx context .Context , txt string ) ([]string , error ) {
return r .getResolver (txt ).LookupTXT (ctx , txt )
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .