Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix!: Only resolve the first DNS-like component #61

Merged
merged 3 commits into from
Oct 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
285 changes: 137 additions & 148 deletions resolve.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@
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,166 @@
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
}
})
if maddr == nil {
return nil, nil
}

// 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 {
MarcoPolo marked this conversation as resolved.
Show resolved Hide resolved
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 154 in resolve.go

View check run for this annotation

Codecov / codecov/patch

resolve.go#L154

Added line #L154 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 195 in resolve.go

View check run for this annotation

Codecov / codecov/patch

resolve.go#L195

Added line #L195 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 250 in resolve.go

View check run for this annotation

Codecov / codecov/patch

resolve.go#L249-L250

Added lines #L249 - L250 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