diff --git a/dns.go b/dns.go index 6b8ccd0..4a5a934 100644 --- a/dns.go +++ b/dns.go @@ -6,6 +6,8 @@ import ( // Extracted from source of truth for multicodec codes: https://github.com/multiformats/multicodec const ( + // Deprecated: use ma.P_DNS + P_DNS = ma.P_DNS // Deprecated: use ma.P_DNS4 P_DNS4 = ma.P_DNS4 // Deprecated: use ma.P_DNS6 @@ -14,6 +16,9 @@ const ( P_DNSADDR = ma.P_DNSADDR ) +// Deprecated: use ma.ProtocolWithCode(P_DNS) +var DnsProtocol = ma.ProtocolWithCode(P_DNS) + // Deprecated: use ma.ProtocolWithCode(P_DNS4) var Dns4Protocol = ma.ProtocolWithCode(P_DNS4) diff --git a/go.mod b/go.mod index c1d394a..0824647 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,5 @@ module github.com/multiformats/go-multiaddr-dns -require github.com/multiformats/go-multiaddr v0.1.0 +require github.com/multiformats/go-multiaddr v0.1.1 go 1.12 diff --git a/go.sum b/go.sum index 87f7a09..0061b94 100644 --- a/go.sum +++ b/go.sum @@ -1,18 +1,20 @@ -github.com/gxed/hashland/keccakpg v0.0.1 h1:wrk3uMNaMxbXiHibbPO4S0ymqJMm41WiudyFSs7UnsU= -github.com/gxed/hashland/keccakpg v0.0.1/go.mod h1:kRzw3HkwxFU1mpmPP8v1WyQzwdGfmKFJ6tItnhQ67kU= -github.com/gxed/hashland/murmur3 v0.0.1 h1:SheiaIt0sda5K+8FLz952/1iWS9zrnKsEJaOJu4ZbSc= -github.com/gxed/hashland/murmur3 v0.0.1/go.mod h1:KjXop02n4/ckmZSnY2+HKcLud/tcmvhST0bie/0lS48= github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 h1:lYpkrQH5ajf0OXOcUbGjvZxxijuBwbbmlSxLiuofa+g= github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ= -github.com/minio/sha256-simd v0.0.0-20190131020904-2d45a736cd16 h1:5W7KhL8HVF3XCFOweFD3BNESdnO8ewyYTFT2R+/b8FQ= -github.com/minio/sha256-simd v0.0.0-20190131020904-2d45a736cd16/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U= -github.com/mr-tron/base58 v1.1.0 h1:Y51FGVJ91WBqCEabAi5OPUz38eAx8DakuAm5svLcsfQ= -github.com/mr-tron/base58 v1.1.0/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8= -github.com/multiformats/go-multiaddr v0.1.0 h1:fkISCUNDb3xIpCcI6BGlPsQE+ywcxzimOsUnHWnrE74= -github.com/multiformats/go-multiaddr v0.1.0/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44= -github.com/multiformats/go-multihash v0.0.1 h1:HHwN1K12I+XllBCrqKnhX949Orn4oawPkegHMu2vDqQ= -github.com/multiformats/go-multihash v0.0.1/go.mod h1:w/5tugSrLEbWqlcgJabL3oHFKTwfvkofsjW2Qa1ct4U= -golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67 h1:ng3VDlRp5/DHpSWl02R4rM9I+8M2rhmsuLwAMmkLQWE= -golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/sys v0.0.0-20190219092855-153ac476189d h1:Z0Ahzd7HltpJtjAHHxX8QFP3j1yYgiuvjbjRzDj/KH0= -golang.org/x/sys v0.0.0-20190219092855-153ac476189d/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771 h1:MHkK1uRtFbVqvAgvWxafZe54+5uBxLluGylDiKgdhwo= +github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= +github.com/mr-tron/base58 v1.1.2 h1:ZEw4I2EgPKDJ2iEw0cNmLB3ROrEmkOtXIkaG7wZg+78= +github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= +github.com/multiformats/go-multiaddr v0.1.1 h1:rVAztJYMhCQ7vEFr8FvxW3mS+HF2eY/oPbOMeS0ZDnE= +github.com/multiformats/go-multiaddr v0.1.1/go.mod h1:aMKBKNEYmzmDmxfX88/vz+J5IU55txyt0p4aiWVohjo= +github.com/multiformats/go-multihash v0.0.8 h1:wrYcW5yxSi3dU07n5jnuS5PrNwyHy0zRHGVoUugWvXg= +github.com/multiformats/go-multihash v0.0.8/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew= +github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8 h1:1wopBVtVdWnn03fZelqdXTqk7U7zPQCb+T4rbU9ZEoU= +golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/resolve.go b/resolve.go index 6808ca1..64d8f70 100644 --- a/resolve.go +++ b/resolve.go @@ -2,16 +2,17 @@ package madns import ( "context" - "fmt" "net" "strings" ma "github.com/multiformats/go-multiaddr" ) -var ResolvableProtocols = []ma.Protocol{DnsaddrProtocol, Dns4Protocol, Dns6Protocol} +var ResolvableProtocols = []ma.Protocol{DnsaddrProtocol, Dns4Protocol, Dns6Protocol, DnsProtocol} var DefaultResolver = &Resolver{Backend: net.DefaultResolver} +const dnsaddrTXTPrefix = "dnsaddr=" + type backend interface { LookupIPAddr(context.Context, string) ([]net.IPAddr, error) LookupTXT(context.Context, string) ([]string, error) @@ -44,19 +45,15 @@ func (r *MockBackend) LookupTXT(ctx context.Context, name string) ([]string, err } } -func Matches(maddr ma.Multiaddr) bool { - protos := maddr.Protocols() - if len(protos) == 0 { - return false - } - - for _, p := range ResolvableProtocols { - if protos[0].Code == p.Code { - return true +func Matches(maddr ma.Multiaddr) (matches bool) { + ma.ForEach(maddr, func(c ma.Component) bool { + switch c.Protocol().Code { + case DnsProtocol.Code, Dns4Protocol.Code, Dns6Protocol.Code, DnsaddrProtocol.Code: + matches = true } - } - - return false + return !matches + }) + return matches } func Resolve(ctx context.Context, maddr ma.Multiaddr) ([]ma.Multiaddr, error) { @@ -64,121 +61,209 @@ func Resolve(ctx context.Context, maddr ma.Multiaddr) ([]ma.Multiaddr, error) { } func (r *Resolver) Resolve(ctx context.Context, maddr ma.Multiaddr) ([]ma.Multiaddr, error) { - if !Matches(maddr) { - return []ma.Multiaddr{maddr}, nil - } + var results []ma.Multiaddr + for i := 0; maddr != nil; i++ { + var keep ma.Multiaddr - protos := maddr.Protocols() - if protos[0].Code == Dns4Protocol.Code { - return r.resolveDns4(ctx, maddr) - } - if protos[0].Code == Dns6Protocol.Code { - return r.resolveDns6(ctx, maddr) - } - if protos[0].Code == DnsaddrProtocol.Code { - return r.resolveDnsaddr(ctx, maddr) - } + // 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 + } + }) - panic("unreachable") -} + // 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) + } + } + } -func (r *Resolver) resolveDns4(ctx context.Context, maddr ma.Multiaddr) ([]ma.Multiaddr, error) { - value, err := maddr.ValueForProtocol(Dns4Protocol.Code) - if err != nil { - return nil, fmt.Errorf("error resolving %s: %s", maddr.String(), err) - } + // If the rest is empty, we've hit the end (there _was_ no dns component). + if maddr == nil { + break + } - encap := ma.Split(maddr)[1:] + // split off the dns component. + var resolve *ma.Component + resolve, maddr = ma.SplitFirst(maddr) - result := []ma.Multiaddr{} - records, err := r.Backend.LookupIPAddr(ctx, value) - if err != nil { - return result, err - } + proto := resolve.Protocol() + value := resolve.Value() - for _, r := range records { - ip4 := r.IP.To4() - if ip4 == nil { - continue - } - ip4maddr, err := ma.NewMultiaddr("/ip4/" + ip4.String()) - if err != nil { - return result, err - } - parts := append([]ma.Multiaddr{ip4maddr}, encap...) - result = append(result, ma.Join(parts...)) - } - return result, nil -} + // 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. -func (r *Resolver) resolveDns6(ctx context.Context, maddr ma.Multiaddr) ([]ma.Multiaddr, error) { - value, err := maddr.ValueForProtocol(Dns6Protocol.Code) - if err != nil { - return nil, fmt.Errorf("error resolving %s: %s", maddr.String(), err) - } + v4only := proto.Code == Dns4Protocol.Code + v6only := proto.Code == Dns6Protocol.Code - encap := ma.Split(maddr)[1:] + // 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 := r.Backend.LookupIPAddr(ctx, value) + if err != nil { + return nil, err + } - result := []ma.Multiaddr{} - records, err := r.Backend.LookupIPAddr(ctx, value) - if err != nil { - return result, 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 { - if r.IP.To4() != nil { - continue - } - ip6maddr, err := ma.NewMultiaddr("/ip6/" + r.IP.To16().String()) - if err != nil { - return result, err - } - parts := append([]ma.Multiaddr{ip6maddr}, encap...) - result = append(result, ma.Join(parts...)) - } - return result, nil -} + 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. -func (r *Resolver) resolveDnsaddr(ctx context.Context, maddr ma.Multiaddr) ([]ma.Multiaddr, error) { - value, err := maddr.ValueForProtocol(DnsaddrProtocol.Code) - if err != nil { - return nil, fmt.Errorf("error resolving %s: %s", maddr.String(), err) - } + // First, lookup the TXT record + records, err := r.Backend.LookupTXT(ctx, "_dnsaddr."+value) + if err != nil { + return nil, err + } - trailer := ma.Split(maddr)[1:] + // Then, calculate the length of the suffix we're + // looking for. + length := 0 + if maddr != nil { + length = addrLen(maddr) + } - result := []ma.Multiaddr{} - records, err := r.Backend.LookupTXT(ctx, "_dnsaddr."+value) - if err != nil { - return result, err - } + for _, r := range records { + // Ignore non dnsaddr TXT records. + if !strings.HasPrefix(r, dnsaddrTXTPrefix) { + continue + } - for _, r := range records { - rv := strings.Split(r, "dnsaddr=") - if len(rv) != 2 { - 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 + } + } - rmaddr, err := ma.NewMultiaddr(rv[1]) - if err != nil { - return result, err + resolved = append(resolved, rmaddr) + } + + // consumes the rest of the multiaddr as part of the "match" process. + maddr = nil + default: + panic("unreachable") } - if matchDnsaddr(rmaddr, trailer) { - result = append(result, rmaddr) + 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 result, nil + + return results, nil } -// XXX probably insecure -func matchDnsaddr(maddr ma.Multiaddr, trailer []ma.Multiaddr) bool { - parts := ma.Split(maddr) - if len(trailer) > len(parts) { - return false - } - if ma.Join(parts[len(parts)-len(trailer):]...).Equal(ma.Join(trailer...)) { +// counts the number of components in the multiaddr +func addrLen(maddr ma.Multiaddr) int { + length := 0 + ma.ForEach(maddr, func(_ ma.Component) bool { + length++ return true + }) + return length +} + +// trims `offset` components from the beginning of the multiaddr. +func offset(maddr ma.Multiaddr, offset int) ma.Multiaddr { + _, after := ma.SplitFunc(maddr, func(c ma.Component) bool { + if offset == 0 { + return true + } + offset-- + return false + }) + return after +} + +// takes the cross product of two sets of multiaddrs +// +// assumes `a` is non-empty. +func cross(a, b []ma.Multiaddr) []ma.Multiaddr { + res := make([]ma.Multiaddr, 0, len(a)*len(b)) + for _, x := range a { + for _, y := range b { + res = append(res, x.Encapsulate(y)) + } } - return false + return res } diff --git a/resolve_test.go b/resolve_test.go index e0d1baa..1334611 100644 --- a/resolve_test.go +++ b/resolve_test.go @@ -35,7 +35,7 @@ func makeResolver() *Resolver { }, TXT: map[string][]string{ "_dnsaddr.example.com": []string{txta, txtb}, - "_dnsaddr.matching.com": []string{txtc, txtd, txte}, + "_dnsaddr.matching.com": []string{txtc, txtd, txte, "not a dnsaddr", "dnsaddr=/foobar"}, }, } resolver := &Resolver{Backend: mock} @@ -43,6 +43,14 @@ func makeResolver() *Resolver { } func TestMatches(t *testing.T) { + if !Matches(ma.StringCast("/tcp/1234/dns6/example.com")) { + // Pretend this is a p2p-circuit address. Unfortunately, we'd + // need to depend on the circuit package to parse it. + t.Fatalf("expected match, didn't: /tcp/1234/dns6/example.com") + } + if !Matches(ma.StringCast("/dns/example.com")) { + t.Fatalf("expected match, didn't: /dns/example.com") + } if !Matches(ma.StringCast("/dns4/example.com")) { t.Fatalf("expected match, didn't: /dns4/example.com") } @@ -76,6 +84,73 @@ func TestSimpleIPResolve(t *testing.T) { if len(addrs6) != 2 || !addrs6[0].Equal(ip6ma) || addrs6[0].Equal(ip6mb) { t.Fatalf("expected [%s %s], got %+v", ip6ma, ip6mb, addrs6) } + + addrs, err := resolver.Resolve(ctx, ma.StringCast("/dns/example.com")) + if err != nil { + t.Error(err) + } + for i, expected := range []ma.Multiaddr{ip4ma, ip4mb, ip6ma, ip6mb} { + if !expected.Equal(addrs[i]) { + t.Fatalf("%d: expected %s, got %s", i, expected, addrs[i]) + } + } +} + +func TestResolveMultiple(t *testing.T) { + ctx := context.Background() + resolver := makeResolver() + + addrs, err := resolver.Resolve(ctx, ma.StringCast("/dns4/example.com/quic/dns6/example.com")) + if err != nil { + t.Error(err) + } + for i, x := range []ma.Multiaddr{ip4ma, ip4mb} { + for j, y := range []ma.Multiaddr{ip6ma, ip6mb} { + expected := ma.Join(x, ma.StringCast("/quic"), y) + actual := addrs[i*2+j] + if !expected.Equal(actual) { + t.Fatalf("expected %s, got %s", expected, actual) + } + } + } +} + +func TestResolveMultipleAdjacent(t *testing.T) { + ctx := context.Background() + resolver := makeResolver() + + addrs, err := resolver.Resolve(ctx, ma.StringCast("/dns4/example.com/dns6/example.com")) + if err != nil { + t.Error(err) + } + for i, x := range []ma.Multiaddr{ip4ma, ip4mb} { + for j, y := range []ma.Multiaddr{ip6ma, ip6mb} { + expected := ma.Join(x, y) + actual := addrs[i*2+j] + if !expected.Equal(actual) { + t.Fatalf("expected %s, got %s", expected, actual) + } + } + } +} + +func TestResolveMultipleSandwitch(t *testing.T) { + ctx := context.Background() + resolver := makeResolver() + + addrs, err := resolver.Resolve(ctx, ma.StringCast("/quic/dns4/example.com/dns6/example.com/http")) + if err != nil { + t.Error(err) + } + for i, x := range []ma.Multiaddr{ip4ma, ip4mb} { + for j, y := range []ma.Multiaddr{ip6ma, ip6mb} { + expected := ma.Join(ma.StringCast("/quic"), x, y, ma.StringCast("/http")) + actual := addrs[i*2+j] + if !expected.Equal(actual) { + t.Fatalf("expected %s, got %s", expected, actual) + } + } + } } func TestSimpleTXTResolve(t *testing.T) {