mirror of
https://source.quilibrium.com/quilibrium/ceremonyclient.git
synced 2024-12-24 23:55:18 +00:00
297 lines
7.6 KiB
Go
297 lines
7.6 KiB
Go
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}
|
|
var DefaultResolver = &Resolver{def: net.DefaultResolver}
|
|
|
|
const dnsaddrTXTPrefix = "dnsaddr="
|
|
|
|
// BasicResolver is a low level interface for DNS resolution
|
|
type BasicResolver interface {
|
|
LookupIPAddr(context.Context, string) ([]net.IPAddr, error)
|
|
LookupTXT(context.Context, string) ([]string, error)
|
|
}
|
|
|
|
// Resolver is an object capable of resolving dns multiaddrs by using one or more BasicResolvers;
|
|
// it supports custom per domain/TLD resolvers.
|
|
// It also implements the BasicResolver interface so that it can act as a custom per domain/TLD
|
|
// resolver.
|
|
type Resolver struct {
|
|
def BasicResolver
|
|
custom map[string]BasicResolver
|
|
}
|
|
|
|
var _ BasicResolver = (*Resolver)(nil)
|
|
|
|
// NewResolver creates a new Resolver instance with the specified options
|
|
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
|
|
|
|
// WithDefaultResolver is an option that specifies the default basic resolver,
|
|
// which resolves any TLD that doesn't have a custom resolver.
|
|
// Defaults to net.DefaultResolver
|
|
func WithDefaultResolver(def BasicResolver) Option {
|
|
return func(r *Resolver) error {
|
|
r.def = def
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// WithDomainResolver specifies a custom resolver for a domain/TLD.
|
|
// Custom resolver selection matches domains left to right, with more specific resolvers
|
|
// superseding generic ones.
|
|
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)
|
|
|
|
// we match left-to-right, with more specific resolvers superseding generic ones.
|
|
// So for a domain a.b.c, we will try a.b,c, b.c, c, and fallback to the default if
|
|
// there is no match
|
|
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 == "" {
|
|
// the . is the default resolver
|
|
break
|
|
}
|
|
|
|
rslv, ok = r.custom[fqdn]
|
|
if ok {
|
|
return rslv
|
|
}
|
|
}
|
|
|
|
return r.def
|
|
}
|
|
|
|
// Resolve resolves a DNS multiaddr.
|
|
func (r *Resolver) Resolve(ctx context.Context, maddr ma.Multiaddr) ([]ma.Multiaddr, error) {
|
|
var results []ma.Multiaddr
|
|
for i := 0; maddr != nil; i++ {
|
|
var keep ma.Multiaddr
|
|
var err error
|
|
|
|
// Find the next dns component.
|
|
keep, maddr, err = 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 err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Keep everything before the dns component.
|
|
if keep != nil {
|
|
if len(results) == 0 {
|
|
results = []ma.Multiaddr{keep}
|
|
} else {
|
|
for i, r := range results {
|
|
results[i] = r.Encapsulate(keep)
|
|
}
|
|
}
|
|
}
|
|
|
|
// If the rest is empty, we've hit the end (there _was_ no dns component).
|
|
if maddr == nil {
|
|
break
|
|
}
|
|
|
|
// split off the dns component.
|
|
var resolve *ma.Component
|
|
resolve, maddr, err = ma.SplitFirst(maddr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
proto := resolve.Protocol()
|
|
value := resolve.Value()
|
|
rslv := r.getResolver(value)
|
|
|
|
// resolve the dns component
|
|
var resolved []ma.Multiaddr
|
|
switch proto.Code {
|
|
case dns4Protocol.Code, dns6Protocol.Code, dnsProtocol.Code:
|
|
// The dns, dns4, and dns6 resolver simply resolves each
|
|
// dns* component into an ipv4/ipv6 address.
|
|
|
|
v4only := proto.Code == dns4Protocol.Code
|
|
v6only := proto.Code == dns6Protocol.Code
|
|
|
|
// XXX: Unfortunately, go does a pretty terrible job of
|
|
// differentiating between IPv6 and IPv4. A v4-in-v6
|
|
// AAAA record will _look_ like an A record to us and
|
|
// there's nothing we can do about that.
|
|
records, err := rslv.LookupIPAddr(ctx, value)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Convert each DNS record into a multiaddr. If the
|
|
// protocol is dns4, throw away any IPv6 addresses. If
|
|
// the protocol is dns6, throw away any IPv4 addresses.
|
|
|
|
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:
|
|
// The dnsaddr resolver is a bit more complicated. We:
|
|
//
|
|
// 1. Lookup the dnsaddr txt record on _dnsaddr.DOMAIN.TLD
|
|
// 2. Take everything _after_ the `/dnsaddr/DOMAIN.TLD`
|
|
// part of the multiaddr.
|
|
// 3. Find the dnsaddr records (if any) with suffixes
|
|
// matching the result of step 2.
|
|
|
|
// First, lookup the TXT record
|
|
records, err := rslv.LookupTXT(ctx, "_dnsaddr."+value)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Then, calculate the length of the suffix we're
|
|
// looking for.
|
|
length := 0
|
|
if maddr != nil {
|
|
length = addrLen(maddr)
|
|
}
|
|
|
|
for _, r := range records {
|
|
// Ignore non dnsaddr TXT records.
|
|
if !strings.HasPrefix(r, dnsaddrTXTPrefix) {
|
|
continue
|
|
}
|
|
|
|
// Extract and decode the multiaddr.
|
|
rmaddr, err := ma.NewMultiaddr(r[len(dnsaddrTXTPrefix):])
|
|
if err != nil {
|
|
// discard multiaddrs we don't understand.
|
|
// XXX: Is this right? It's the best we
|
|
// can do for now, really.
|
|
continue
|
|
}
|
|
|
|
// If we have a suffix to match on.
|
|
if maddr != nil {
|
|
// Make sure the new address is at least
|
|
// as long as the suffix we're looking
|
|
// for.
|
|
rmlen := addrLen(rmaddr)
|
|
if rmlen < length {
|
|
// not long enough.
|
|
continue
|
|
}
|
|
|
|
// Matches everything after the /dnsaddr/... with the end of the
|
|
// dnsaddr record:
|
|
//
|
|
// v----------rmlen-----------------v
|
|
// /ip4/1.2.3.4/tcp/1234/p2p/QmFoobar
|
|
// /p2p/QmFoobar
|
|
// ^--(rmlen - length)--^---length--^
|
|
if !maddr.Equal(offset(rmaddr, rmlen-length)) {
|
|
continue
|
|
}
|
|
}
|
|
|
|
resolved = append(resolved, rmaddr)
|
|
}
|
|
|
|
// consumes the rest of the multiaddr as part of the "match" process.
|
|
maddr = nil
|
|
default:
|
|
panic("unreachable")
|
|
}
|
|
|
|
if len(resolved) == 0 {
|
|
return nil, nil
|
|
} else if len(results) == 0 {
|
|
results = resolved
|
|
} else {
|
|
// We take the cross product here as we don't have any
|
|
// better way to represent "ORs" in multiaddrs. For
|
|
// example, `/dns/foo.com/p2p-circuit/dns/bar.com` could
|
|
// resolve to:
|
|
//
|
|
// * /ip4/1.1.1.1/p2p-circuit/ip4/2.1.1.1
|
|
// * /ip4/1.1.1.1/p2p-circuit/ip4/2.1.1.2
|
|
// * /ip4/1.1.1.2/p2p-circuit/ip4/2.1.1.1
|
|
// * /ip4/1.1.1.2/p2p-circuit/ip4/2.1.1.2
|
|
results = cross(results, resolved)
|
|
}
|
|
}
|
|
|
|
return results, 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)
|
|
}
|