|
|
|
|
@ -6,16 +6,22 @@ package resolver |
|
|
|
|
|
|
|
|
|
import ( |
|
|
|
|
"bytes" |
|
|
|
|
"context" |
|
|
|
|
"encoding/hex" |
|
|
|
|
"encoding/json" |
|
|
|
|
"errors" |
|
|
|
|
"fmt" |
|
|
|
|
"math/rand" |
|
|
|
|
"net" |
|
|
|
|
"reflect" |
|
|
|
|
"runtime" |
|
|
|
|
"strconv" |
|
|
|
|
"strings" |
|
|
|
|
"testing" |
|
|
|
|
"time" |
|
|
|
|
|
|
|
|
|
miekdns "github.com/miekg/dns" |
|
|
|
|
"golang.org/x/net/dns/dnsmessage" |
|
|
|
|
dns "golang.org/x/net/dns/dnsmessage" |
|
|
|
|
"inet.af/netaddr" |
|
|
|
|
"tailscale.com/net/tsdial" |
|
|
|
|
@ -43,6 +49,8 @@ var dnsCfg = Config{ |
|
|
|
|
|
|
|
|
|
const noEdns = 0 |
|
|
|
|
|
|
|
|
|
const dnsHeaderLen = 12 |
|
|
|
|
|
|
|
|
|
func dnspacket(domain dnsname.FQDN, tp dns.Type, ednsSize uint16) []byte { |
|
|
|
|
var dnsHeader dns.Header |
|
|
|
|
question := dns.Question{ |
|
|
|
|
@ -1093,3 +1101,383 @@ func TestForwardLinkSelection(t *testing.T) { |
|
|
|
|
type linkSelFunc func(ip netaddr.IP) string |
|
|
|
|
|
|
|
|
|
func (f linkSelFunc) PickLink(ip netaddr.IP) string { return f(ip) } |
|
|
|
|
|
|
|
|
|
func TestHandleExitNodeDNSQueryWithNetPkg(t *testing.T) { |
|
|
|
|
if runtime.GOOS == "windows" { |
|
|
|
|
t.Skip("skipping test on Windows; waiting for golang.org/issue/33097") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
records := []interface{}{ |
|
|
|
|
"no-records.test.", |
|
|
|
|
dnsHandler(), |
|
|
|
|
|
|
|
|
|
"one-a.test.", |
|
|
|
|
dnsHandler(netaddr.MustParseIP("1.2.3.4")), |
|
|
|
|
|
|
|
|
|
"two-a.test.", |
|
|
|
|
dnsHandler(netaddr.MustParseIP("1.2.3.4"), netaddr.MustParseIP("5.6.7.8")), |
|
|
|
|
|
|
|
|
|
"one-aaaa.test.", |
|
|
|
|
dnsHandler(netaddr.MustParseIP("1::2")), |
|
|
|
|
|
|
|
|
|
"two-aaaa.test.", |
|
|
|
|
dnsHandler(netaddr.MustParseIP("1::2"), netaddr.MustParseIP("3::4")), |
|
|
|
|
|
|
|
|
|
"nx-domain.test.", |
|
|
|
|
resolveToNXDOMAIN, |
|
|
|
|
|
|
|
|
|
"4.3.2.1.in-addr.arpa.", |
|
|
|
|
dnsHandler(miekdns.PTR{Ptr: "foo.com."}), |
|
|
|
|
|
|
|
|
|
"cname.test.", |
|
|
|
|
weirdoGoCNAMEHandler("the-target.foo."), |
|
|
|
|
|
|
|
|
|
"txt.test.", |
|
|
|
|
dnsHandler( |
|
|
|
|
miekdns.TXT{Txt: []string{"txt1=one"}}, |
|
|
|
|
miekdns.TXT{Txt: []string{"txt2=two"}}, |
|
|
|
|
miekdns.TXT{Txt: []string{"txt3=three"}}, |
|
|
|
|
), |
|
|
|
|
|
|
|
|
|
"srv.test.", |
|
|
|
|
dnsHandler( |
|
|
|
|
miekdns.SRV{ |
|
|
|
|
Priority: 1, |
|
|
|
|
Weight: 2, |
|
|
|
|
Port: 3, |
|
|
|
|
Target: "foo.com.", |
|
|
|
|
}, |
|
|
|
|
miekdns.SRV{ |
|
|
|
|
Priority: 4, |
|
|
|
|
Weight: 5, |
|
|
|
|
Port: 6, |
|
|
|
|
Target: "bar.com.", |
|
|
|
|
}, |
|
|
|
|
), |
|
|
|
|
|
|
|
|
|
"ns.test.", |
|
|
|
|
dnsHandler(miekdns.NS{Ns: "ns1.foo."}, miekdns.NS{Ns: "ns2.bar."}), |
|
|
|
|
} |
|
|
|
|
v4server := serveDNS(t, "127.0.0.1:0", records...) |
|
|
|
|
defer v4server.Shutdown() |
|
|
|
|
|
|
|
|
|
// backendResolver is the resolver between
|
|
|
|
|
// handleExitNodeDNSQueryWithNetPkg and its upstream resolver,
|
|
|
|
|
// which in this test's case is the miekg/dns test DNS server
|
|
|
|
|
// (v4server).
|
|
|
|
|
backResolver := &net.Resolver{ |
|
|
|
|
PreferGo: true, |
|
|
|
|
Dial: func(ctx context.Context, network, addr string) (net.Conn, error) { |
|
|
|
|
var d net.Dialer |
|
|
|
|
return d.DialContext(ctx, "udp", v4server.PacketConn.LocalAddr().String()) |
|
|
|
|
}, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
t.Run("no_such_host", func(t *testing.T) { |
|
|
|
|
res, err := handleExitNodeDNSQueryWithNetPkg(context.Background(), backResolver, &response{ |
|
|
|
|
Header: dnsmessage.Header{ |
|
|
|
|
ID: 123, |
|
|
|
|
Response: true, |
|
|
|
|
OpCode: 0, // query
|
|
|
|
|
}, |
|
|
|
|
Question: dnsmessage.Question{ |
|
|
|
|
Name: dnsmessage.MustNewName("nx-domain.test."), |
|
|
|
|
Type: dnsmessage.TypeA, |
|
|
|
|
Class: dnsmessage.ClassINET, |
|
|
|
|
}, |
|
|
|
|
}) |
|
|
|
|
if err != nil { |
|
|
|
|
t.Fatal(err) |
|
|
|
|
} |
|
|
|
|
if len(res) < dnsHeaderLen { |
|
|
|
|
t.Fatal("short reply") |
|
|
|
|
} |
|
|
|
|
rcode := dns.RCode(res[3] & 0x0f) |
|
|
|
|
if rcode != dns.RCodeNameError { |
|
|
|
|
t.Errorf("RCode = %v; want dns.RCodeNameError", rcode) |
|
|
|
|
t.Logf("Response was: %q", res) |
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
matchPacked := func(want string) func(t testing.TB, got []byte) { |
|
|
|
|
return func(t testing.TB, got []byte) { |
|
|
|
|
if string(got) == want { |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
t.Errorf("unexpected reply.\n got: %q\nwant: %q\n", got, want) |
|
|
|
|
t.Errorf("\nin hex:\n got: % 2x\nwant: % 2x\n", got, want) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
tests := []struct { |
|
|
|
|
Type dnsmessage.Type |
|
|
|
|
Name string |
|
|
|
|
Check func(t testing.TB, got []byte) |
|
|
|
|
}{ |
|
|
|
|
{ |
|
|
|
|
Type: dnsmessage.TypeA, |
|
|
|
|
Name: "one-a.test.", |
|
|
|
|
Check: matchPacked("\x00{\x84\x00\x00\x01\x00\x01\x00\x00\x00\x00\x05one-a\x04test\x00\x00\x01\x00\x01\x05one-a\x04test\x00\x00\x01\x00\x01\x00\x00\x02X\x00\x04\x01\x02\x03\x04"), |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
Type: dnsmessage.TypeA, |
|
|
|
|
Name: "two-a.test.", |
|
|
|
|
Check: matchPacked("\x00{\x84\x00\x00\x01\x00\x02\x00\x00\x00\x00\x05two-a\x04test\x00\x00\x01\x00\x01\xc0\f\x00\x01\x00\x01\x00\x00\x02X\x00\x04\x01\x02\x03\x04\xc0\f\x00\x01\x00\x01\x00\x00\x02X\x00\x04\x05\x06\a\b"), |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
Type: dnsmessage.TypeAAAA, |
|
|
|
|
Name: "one-aaaa.test.", |
|
|
|
|
Check: matchPacked("\x00{\x84\x00\x00\x01\x00\x01\x00\x00\x00\x00\bone-aaaa\x04test\x00\x00\x1c\x00\x01\bone-aaaa\x04test\x00\x00\x1c\x00\x01\x00\x00\x02X\x00\x10\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02"), |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
Type: dnsmessage.TypeAAAA, |
|
|
|
|
Name: "two-aaaa.test.", |
|
|
|
|
Check: matchPacked("\x00{\x84\x00\x00\x01\x00\x02\x00\x00\x00\x00\btwo-aaaa\x04test\x00\x00\x1c\x00\x01\xc0\f\x00\x1c\x00\x01\x00\x00\x02X\x00\x10\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\xc0\f\x00\x1c\x00\x01\x00\x00\x02X\x00\x10\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04"), |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
Type: dnsmessage.TypePTR, |
|
|
|
|
Name: "4.3.2.1.in-addr.arpa.", |
|
|
|
|
Check: matchPacked("\x00{\x84\x00\x00\x01\x00\x01\x00\x00\x00\x00\x014\x013\x012\x011\ain-addr\x04arpa\x00\x00\f\x00\x01\x014\x013\x012\x011\ain-addr\x04arpa\x00\x00\f\x00\x01\x00\x00\x02X\x00\t\x03foo\x03com\x00"), |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
Type: dnsmessage.TypeCNAME, |
|
|
|
|
Name: "cname.test.", |
|
|
|
|
Check: matchPacked("\x00{\x84\x00\x00\x01\x00\x01\x00\x00\x00\x00\x05cname\x04test\x00\x00\x05\x00\x01\x05cname\x04test\x00\x00\x05\x00\x01\x00\x00\x02X\x00\x10\nthe-target\x03foo\x00"), |
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
// No records of various types
|
|
|
|
|
{ |
|
|
|
|
Type: dnsmessage.TypeA, |
|
|
|
|
Name: "no-records.test.", |
|
|
|
|
Check: matchPacked("\x00{\x84\x03\x00\x01\x00\x00\x00\x00\x00\x00\nno-records\x04test\x00\x00\x01\x00\x01"), |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
Type: dnsmessage.TypeAAAA, |
|
|
|
|
Name: "no-records.test.", |
|
|
|
|
Check: matchPacked("\x00{\x84\x03\x00\x01\x00\x00\x00\x00\x00\x00\nno-records\x04test\x00\x00\x1c\x00\x01"), |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
Type: dnsmessage.TypeCNAME, |
|
|
|
|
Name: "no-records.test.", |
|
|
|
|
Check: matchPacked("\x00{\x84\x03\x00\x01\x00\x00\x00\x00\x00\x00\nno-records\x04test\x00\x00\x05\x00\x01"), |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
Type: dnsmessage.TypeSRV, |
|
|
|
|
Name: "no-records.test.", |
|
|
|
|
Check: matchPacked("\x00{\x84\x03\x00\x01\x00\x00\x00\x00\x00\x00\nno-records\x04test\x00\x00!\x00\x01"), |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
Type: dnsmessage.TypeTXT, |
|
|
|
|
Name: "txt.test.", |
|
|
|
|
Check: matchPacked("\x00{\x84\x00\x00\x01\x00\x03\x00\x00\x00\x00\x03txt\x04test\x00\x00\x10\x00\x01\x03txt\x04test\x00\x00\x10\x00\x01\x00\x00\x02X\x00\t\btxt1=one\x03txt\x04test\x00\x00\x10\x00\x01\x00\x00\x02X\x00\t\btxt2=two\x03txt\x04test\x00\x00\x10\x00\x01\x00\x00\x02X\x00\v\ntxt3=three"), |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
Type: dnsmessage.TypeSRV, |
|
|
|
|
Name: "srv.test.", |
|
|
|
|
Check: matchPacked("\x00{\x84\x00\x00\x01\x00\x02\x00\x00\x00\x00\x03srv\x04test\x00\x00!\x00\x01\x03srv\x04test\x00\x00!\x00\x01\x00\x00\x02X\x00\x0f\x00\x01\x00\x02\x00\x03\x03foo\x03com\x00\x03srv\x04test\x00\x00!\x00\x01\x00\x00\x02X\x00\x0f\x00\x04\x00\x05\x00\x06\x03bar\x03com\x00"), |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
Type: dnsmessage.TypeNS, |
|
|
|
|
Name: "ns.test.", |
|
|
|
|
Check: matchPacked("\x00{\x84\x00\x00\x01\x00\x02\x00\x00\x00\x00\x02ns\x04test\x00\x00\x02\x00\x01\x02ns\x04test\x00\x00\x02\x00\x01\x00\x00\x02X\x00\t\x03ns1\x03foo\x00\x02ns\x04test\x00\x00\x02\x00\x01\x00\x00\x02X\x00\t\x03ns2\x03bar\x00"), |
|
|
|
|
}, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
for _, tt := range tests { |
|
|
|
|
t.Run(fmt.Sprintf("%v_%v", tt.Type, strings.Trim(tt.Name, ".")), func(t *testing.T) { |
|
|
|
|
got, err := handleExitNodeDNSQueryWithNetPkg(context.Background(), backResolver, &response{ |
|
|
|
|
Header: dnsmessage.Header{ |
|
|
|
|
ID: 123, |
|
|
|
|
Response: true, |
|
|
|
|
OpCode: 0, // query
|
|
|
|
|
}, |
|
|
|
|
Question: dnsmessage.Question{ |
|
|
|
|
Name: dnsmessage.MustNewName(tt.Name), |
|
|
|
|
Type: tt.Type, |
|
|
|
|
Class: dnsmessage.ClassINET, |
|
|
|
|
}, |
|
|
|
|
}) |
|
|
|
|
if err != nil { |
|
|
|
|
t.Fatal(err) |
|
|
|
|
} |
|
|
|
|
if len(got) < dnsHeaderLen { |
|
|
|
|
t.Errorf("short record") |
|
|
|
|
} |
|
|
|
|
if tt.Check != nil { |
|
|
|
|
tt.Check(t, got) |
|
|
|
|
if t.Failed() { |
|
|
|
|
t.Errorf("Got: %q\nIn hex: % 02x", got, got) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
wrapRes := newWrapResolver(backResolver) |
|
|
|
|
ctx := context.Background() |
|
|
|
|
|
|
|
|
|
t.Run("wrap_ip_a", func(t *testing.T) { |
|
|
|
|
ips, err := wrapRes.LookupIP(ctx, "ip", "two-a.test.") |
|
|
|
|
if err != nil { |
|
|
|
|
t.Fatal(err) |
|
|
|
|
} |
|
|
|
|
if got, want := ips, []net.IP{ |
|
|
|
|
net.ParseIP("1.2.3.4").To4(), |
|
|
|
|
net.ParseIP("5.6.7.8").To4(), |
|
|
|
|
}; !reflect.DeepEqual(got, want) { |
|
|
|
|
t.Errorf("LookupIP = %v; want %v", got, want) |
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
t.Run("wrap_ip_aaaa", func(t *testing.T) { |
|
|
|
|
ips, err := wrapRes.LookupIP(ctx, "ip", "two-aaaa.test.") |
|
|
|
|
if err != nil { |
|
|
|
|
t.Fatal(err) |
|
|
|
|
} |
|
|
|
|
if got, want := ips, []net.IP{ |
|
|
|
|
net.ParseIP("1::2"), |
|
|
|
|
net.ParseIP("3::4"), |
|
|
|
|
}; !reflect.DeepEqual(got, want) { |
|
|
|
|
t.Errorf("LookupIP(v6) = %v; want %v", got, want) |
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
t.Run("wrap_ip_nx", func(t *testing.T) { |
|
|
|
|
ips, err := wrapRes.LookupIP(ctx, "ip", "nx-domain.test.") |
|
|
|
|
if !isGoNoSuchHostError(err) { |
|
|
|
|
t.Errorf("no NX domain = (%v, %v); want no host error", ips, err) |
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
t.Run("wrap_srv", func(t *testing.T) { |
|
|
|
|
_, srvs, err := wrapRes.LookupSRV(ctx, "", "", "srv.test.") |
|
|
|
|
if err != nil { |
|
|
|
|
t.Fatal(err) |
|
|
|
|
} |
|
|
|
|
if got, want := srvs, []*net.SRV{ |
|
|
|
|
{ |
|
|
|
|
Target: "foo.com.", |
|
|
|
|
Priority: 1, |
|
|
|
|
Weight: 2, |
|
|
|
|
Port: 3, |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
Target: "bar.com.", |
|
|
|
|
Priority: 4, |
|
|
|
|
Weight: 5, |
|
|
|
|
Port: 6, |
|
|
|
|
}, |
|
|
|
|
}; !reflect.DeepEqual(got, want) { |
|
|
|
|
jgot, _ := json.Marshal(got) |
|
|
|
|
jwant, _ := json.Marshal(want) |
|
|
|
|
t.Errorf("SRV = %s; want %s", jgot, jwant) |
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
t.Run("wrap_txt", func(t *testing.T) { |
|
|
|
|
txts, err := wrapRes.LookupTXT(ctx, "txt.test.") |
|
|
|
|
if err != nil { |
|
|
|
|
t.Fatal(err) |
|
|
|
|
} |
|
|
|
|
if got, want := txts, []string{"txt1=one", "txt2=two", "txt3=three"}; !reflect.DeepEqual(got, want) { |
|
|
|
|
t.Errorf("TXT = %q; want %q", got, want) |
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
t.Run("wrap_ns", func(t *testing.T) { |
|
|
|
|
nss, err := wrapRes.LookupNS(ctx, "ns.test.") |
|
|
|
|
if err != nil { |
|
|
|
|
t.Fatal(err) |
|
|
|
|
} |
|
|
|
|
if got, want := nss, []*net.NS{ |
|
|
|
|
{Host: "ns1.foo."}, |
|
|
|
|
{Host: "ns2.bar."}, |
|
|
|
|
}; !reflect.DeepEqual(got, want) { |
|
|
|
|
jgot, _ := json.Marshal(got) |
|
|
|
|
jwant, _ := json.Marshal(want) |
|
|
|
|
t.Errorf("NS = %s; want %s", jgot, jwant) |
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// newWrapResolver returns a resolver that uses r (via handleExitNodeDNSQueryWithNetPkg)
|
|
|
|
|
// to make DNS requests.
|
|
|
|
|
func newWrapResolver(r *net.Resolver) *net.Resolver { |
|
|
|
|
if runtime.GOOS == "windows" { |
|
|
|
|
panic("doesn't work on Windows") // golang.org/issue/33097
|
|
|
|
|
} |
|
|
|
|
return &net.Resolver{ |
|
|
|
|
PreferGo: true, |
|
|
|
|
Dial: func(ctx context.Context, network, addr string) (net.Conn, error) { |
|
|
|
|
return &wrapResolverConn{ctx: ctx, r: r}, nil |
|
|
|
|
}, |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
type wrapResolverConn struct { |
|
|
|
|
ctx context.Context |
|
|
|
|
r *net.Resolver |
|
|
|
|
buf bytes.Buffer |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var _ net.PacketConn = (*wrapResolverConn)(nil) |
|
|
|
|
|
|
|
|
|
func (*wrapResolverConn) Close() error { return nil } |
|
|
|
|
func (*wrapResolverConn) LocalAddr() net.Addr { return fakeAddr{} } |
|
|
|
|
func (*wrapResolverConn) RemoteAddr() net.Addr { return fakeAddr{} } |
|
|
|
|
func (*wrapResolverConn) SetDeadline(t time.Time) error { return nil } |
|
|
|
|
func (*wrapResolverConn) SetReadDeadline(t time.Time) error { return nil } |
|
|
|
|
func (*wrapResolverConn) SetWriteDeadline(t time.Time) error { return nil } |
|
|
|
|
|
|
|
|
|
func (a *wrapResolverConn) Read(p []byte) (n int, err error) { |
|
|
|
|
n, _, err = a.ReadFrom(p) |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (a *wrapResolverConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) { |
|
|
|
|
n, err = a.buf.Read(p) |
|
|
|
|
return n, fakeAddr{}, err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (a *wrapResolverConn) Write(packet []byte) (n int, err error) { |
|
|
|
|
return a.WriteTo(packet, fakeAddr{}) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (a *wrapResolverConn) WriteTo(q []byte, _ net.Addr) (n int, err error) { |
|
|
|
|
resp := parseExitNodeQuery(q) |
|
|
|
|
if resp == nil { |
|
|
|
|
return 0, errors.New("bad query") |
|
|
|
|
} |
|
|
|
|
res, err := handleExitNodeDNSQueryWithNetPkg(context.Background(), a.r, resp) |
|
|
|
|
if err != nil { |
|
|
|
|
return 0, err |
|
|
|
|
} |
|
|
|
|
a.buf.Write(res) |
|
|
|
|
return len(q), nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
type fakeAddr struct{} |
|
|
|
|
|
|
|
|
|
func (fakeAddr) Network() string { return "unused" } |
|
|
|
|
func (fakeAddr) String() string { return "unused-todoAddr" } |
|
|
|
|
|
|
|
|
|
func TestUnARPA(t *testing.T) { |
|
|
|
|
tests := []struct { |
|
|
|
|
in, want string |
|
|
|
|
}{ |
|
|
|
|
{"", ""}, |
|
|
|
|
{"bad", ""}, |
|
|
|
|
{"4.4.8.8.in-addr.arpa.", "8.8.4.4"}, |
|
|
|
|
{".in-addr.arpa.", ""}, |
|
|
|
|
{"e.0.0.2.0.0.0.0.0.0.0.0.0.0.0.0.b.0.8.0.a.0.0.4.0.b.8.f.7.0.6.2.ip6.arpa.", "2607:f8b0:400a:80b::200e"}, |
|
|
|
|
{".ip6.arpa.", ""}, |
|
|
|
|
} |
|
|
|
|
for _, tt := range tests { |
|
|
|
|
got, ok := unARPA(tt.in) |
|
|
|
|
if ok != (got != "") { |
|
|
|
|
t.Errorf("inconsistent results for %q: (%q, %v)", tt.in, got, ok) |
|
|
|
|
} |
|
|
|
|
if got != tt.want { |
|
|
|
|
t.Errorf("unARPA(%q) = %q; want %q", tt.in, got, tt.want) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|