Skip to content

Commit

Permalink
fix!: Only resolve a single DNS-like component in multiaddrs
Browse files Browse the repository at this point in the history
BREAKING CHANGE: Previously Resolve would resolve all DNS components in
a multiaddr. Now it will only resolve the first one. Users may
iteratively call Resolve to get the old behavior. See the tests for an
example.
  • Loading branch information
MarcoPolo committed Sep 30, 2024
1 parent 6da5310 commit 1ee735b
Show file tree
Hide file tree
Showing 3 changed files with 188 additions and 191 deletions.
283 changes: 134 additions & 149 deletions resolve.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ var (
dnsProtocol = ma.ProtocolWithCode(ma.P_DNS)
)

var ResolvableProtocols = []ma.Protocol{dnsaddrProtocol, dns4Protocol, dns6Protocol, dnsProtocol}
var DefaultResolver = &Resolver{def: net.DefaultResolver}
var (
ResolvableProtocols = []ma.Protocol{dnsaddrProtocol, dns4Protocol, dns6Protocol, dnsProtocol}
DefaultResolver = &Resolver{def: net.DefaultResolver}
)

const dnsaddrTXTPrefix = "dnsaddr="

Expand Down Expand Up @@ -104,179 +106,162 @@ func (r *Resolver) getResolver(domain string) BasicResolver {
return r.def
}

// Resolve resolves a DNS multiaddr.
// Resolve resolves a DNS multiaddr. It will only resolve the first DNS component in the multiaddr.
// If you need to resolve multiple DNS components, you may call this function again with each returned address.
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

// Find the next dns component.
keep, 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
}
})

// 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)
}
}
// Find the next dns component.
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 the rest is empty, we've hit the end (there _was_ no dns component).
if maddr == nil {
break
}
// If the rest is empty, we've hit the end (there _was_ no dns component).
if maddr == nil {
return []ma.Multiaddr{preDNS}, nil
}

// split off the dns component.
var resolve *ma.Component
resolve, maddr = ma.SplitFirst(maddr)

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
}
// split off the dns component.
resolve, postDNS := ma.SplitFirst(maddr)

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

// 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())
// 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

Check warning on line 150 in resolve.go

View check run for this annotation

Codecov / codecov/patch

resolve.go#L150

Added line #L150 was not covered by tests
}

// 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
}
if err != nil {
return nil, err
rmaddr, err = ma.NewMultiaddr("/ip6/" + r.IP.String())
} else {
if v6only {
continue
}
resolved = append(resolved, rmaddr)
rmaddr, err = ma.NewMultiaddr("/ip4/" + ip4.String())
}
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
}
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

Check warning on line 191 in resolve.go

View check run for this annotation

Codecov / codecov/patch

resolve.go#L191

Added line #L191 was not covered by tests
}

// Then, calculate the length of the suffix we're
// looking for.
length := 0
if maddr != nil {
length = addrLen(maddr)
// Then, calculate the length of the suffix we're
// looking for.
length := 0
if postDNS != nil {
length = addrLen(postDNS)
}

for _, r := range records {
// Ignore non dnsaddr TXT records.
if !strings.HasPrefix(r, dnsaddrTXTPrefix) {
continue
}

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
}

// 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.
// If we have a suffix to match on.
if postDNS != 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
}

// 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
}
// 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 !postDNS.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")
// remove the suffix from the multiaddr, we'll add it back at the end.
if postDNS != nil {
rmaddr = rmaddr.Decapsulate(postDNS)
}
resolved = append(resolved, rmaddr)
}
default:
panic("unreachable")

Check warning on line 246 in resolve.go

View check run for this annotation

Codecov / codecov/patch

resolve.go#L245-L246

Added lines #L245 - L246 were not covered by tests
}

if len(resolved) == 0 {
return nil, nil
}

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)
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 results, nil
return resolved, nil
}

func (r *Resolver) LookupIPAddr(ctx context.Context, domain string) ([]net.IPAddr, error) {
Expand Down
Loading

0 comments on commit 1ee735b

Please sign in to comment.