plugin/host: don't append the names when reparsing hosts file (#3045)

The host plugin kept on adding entries instead of overwriting. Split the
inline cache off from the /etc/hosts file cache and clear /etc/hosts
file cache and re-parsing.

A bunch of other cleanup as well. Use functions defined in the plugin
package, don't re-parse strings if you don't have to and use To4() to
check the family for IP addresses. Fix all test cases a carried entries
are always fqdn-ed. Various smaller cleanup in unnessacry constants.

Fixes: #3014

Signed-off-by: Miek Gieben <miek@miek.nl>
This commit is contained in:
Miek Gieben
2019-07-25 18:53:07 +00:00
committed by Yong Tang
parent 2a41b9a93b
commit 89fa9bc61e
9 changed files with 147 additions and 171 deletions

1
go.mod
View File

@@ -54,6 +54,7 @@ require (
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 // indirect
golang.org/x/net v0.0.0-20190628185345-da137c7871d7 // indirect
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 // indirect
google.golang.org/genproto v0.0.0-20190701230453-710ae3a149df // indirect
google.golang.org/grpc v1.22.0
gopkg.in/DataDog/dd-trace-go.v1 v1.16.0

2
go.sum
View File

@@ -325,6 +325,8 @@ golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBn
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190624190245-7f2218787638/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=

View File

@@ -11,14 +11,16 @@ file that exists on disk. It checks the file for changes and updates the zones a
plugin only supports A, AAAA, and PTR records. The hosts plugin can be used with readily
available hosts files that block access to advertising servers.
The plugin reloads the content of the hosts file every 5 seconds. Upon reload, CoreDNS will use the new definitions.
Should the file be deleted, any inlined content will continue to be served. When the file is restored, it will then again be used.
The plugin reloads the content of the hosts file every 5 seconds. Upon reload, CoreDNS will use the
new definitions. Should the file be deleted, any inlined content will continue to be served. When
the file is restored, it will then again be used.
This plugin can only be used once per Server Block.
## The hosts file
Commonly the entries are of the form `IP_address canonical_hostname [aliases...]` as explained by the hosts(5) man page.
Commonly the entries are of the form `IP_address canonical_hostname [aliases...]` as explained by
the hosts(5) man page.
Examples:
@@ -34,7 +36,8 @@ fdfc:a744:27b5:3b0e::1 example.com example
### PTR records
PTR records for reverse lookups are generated automatically by CoreDNS (based on the hosts file entries) and cannot be created manually.
PTR records for reverse lookups are generated automatically by CoreDNS (based on the hosts file
entries) and cannot be created manually.
## Syntax

View File

@@ -31,7 +31,7 @@ func (h Hosts) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (
if zone == "" {
// PTR zones don't need to be specified in Origins
if state.Type() != "PTR" {
// If this doesn't match we need to fall through regardless of h.Fallthrough
// if this doesn't match we need to fall through regardless of h.Fallthrough
return plugin.NextOrFailure(h.Name(), h.Next, ctx, w, r)
}
}
@@ -89,7 +89,6 @@ func (h Hosts) otherRecordsExist(qtype uint16, qname string) bool {
}
}
return false
}
// Name implements the plugin.Handle interface.
@@ -97,39 +96,36 @@ func (h Hosts) Name() string { return "hosts" }
// a takes a slice of net.IPs and returns a slice of A RRs.
func a(zone string, ttl uint32, ips []net.IP) []dns.RR {
answers := []dns.RR{}
for _, ip := range ips {
answers := make([]dns.RR, len(ips))
for i, ip := range ips {
r := new(dns.A)
r.Hdr = dns.RR_Header{Name: zone, Rrtype: dns.TypeA,
Class: dns.ClassINET, Ttl: ttl}
r.Hdr = dns.RR_Header{Name: zone, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: ttl}
r.A = ip
answers = append(answers, r)
answers[i] = r
}
return answers
}
// aaaa takes a slice of net.IPs and returns a slice of AAAA RRs.
func aaaa(zone string, ttl uint32, ips []net.IP) []dns.RR {
answers := []dns.RR{}
for _, ip := range ips {
answers := make([]dns.RR, len(ips))
for i, ip := range ips {
r := new(dns.AAAA)
r.Hdr = dns.RR_Header{Name: zone, Rrtype: dns.TypeAAAA,
Class: dns.ClassINET, Ttl: ttl}
r.Hdr = dns.RR_Header{Name: zone, Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: ttl}
r.AAAA = ip
answers = append(answers, r)
answers[i] = r
}
return answers
}
// ptr takes a slice of host names and filters out the ones that aren't in Origins, if specified, and returns a slice of PTR RRs.
func (h *Hosts) ptr(zone string, ttl uint32, names []string) []dns.RR {
answers := []dns.RR{}
for _, n := range names {
answers := make([]dns.RR, len(names))
for i, n := range names {
r := new(dns.PTR)
r.Hdr = dns.RR_Header{Name: zone, Rrtype: dns.TypePTR,
Class: dns.ClassINET, Ttl: ttl}
r.Hdr = dns.RR_Header{Name: zone, Rrtype: dns.TypePTR, Class: dns.ClassINET, Ttl: ttl}
r.Ptr = dns.Fqdn(n)
answers = append(answers, r)
answers[i] = r
}
return answers
}

View File

@@ -2,7 +2,6 @@ package hosts
import (
"context"
"io"
"strings"
"testing"
@@ -12,20 +11,17 @@ import (
"github.com/miekg/dns"
)
func (h *Hostsfile) parseReader(r io.Reader) {
h.hmap = h.parse(r)
}
func TestLookupA(t *testing.T) {
h := Hosts{
Next: test.ErrorHandler(),
Hostsfile: &Hostsfile{
Origins: []string{"."},
hmap: newHostsMap(),
hmap: newMap(),
inline: newMap(),
options: newOptions(),
},
}
h.parseReader(strings.NewReader(hostsExample))
h.hmap = h.parse(strings.NewReader(hostsExample))
ctx := context.TODO()

View File

@@ -19,7 +19,8 @@ import (
"github.com/coredns/coredns/plugin"
)
func parseLiteralIP(addr string) net.IP {
// parseIP calls discards any v6 zone info, before calling net.ParseIP.
func parseIP(addr string) net.IP {
if i := strings.Index(addr, "%"); i >= 0 {
// discard ipv6 zone
addr = addr[0:i]
@@ -28,10 +29,6 @@ func parseLiteralIP(addr string) net.IP {
return net.ParseIP(addr)
}
func absDomainName(b string) string {
return plugin.Name(b).Normalize()
}
type options struct {
// automatically generate IP to Hostname PTR entries
// for host entries we parse
@@ -48,48 +45,40 @@ func newOptions() *options {
return &options{
autoReverse: true,
ttl: 3600,
reload: durationOf5s,
reload: time.Duration(5 * time.Second),
}
}
type hostsMap struct {
// Key for the list of literal IP addresses must be a host
// name. It would be part of DNS labels, a FQDN or an absolute
// FQDN.
// For now the key is converted to lower case for convenience.
byNameV4 map[string][]net.IP
byNameV6 map[string][]net.IP
// Map contains the IPv4/IPv6 and reverse mapping.
type Map struct {
// Key for the list of literal IP addresses must be a FQDN lowercased host name.
name4 map[string][]net.IP
name6 map[string][]net.IP
// Key for the list of host names must be a literal IP address
// including IPv6 address with zone identifier.
// including IPv6 address without zone identifier.
// We don't support old-classful IP address notation.
byAddr map[string][]string
addr map[string][]string
}
const (
durationOf0s = time.Duration(0)
durationOf5s = time.Duration(5 * time.Second)
)
func newHostsMap() *hostsMap {
return &hostsMap{
byNameV4: make(map[string][]net.IP),
byNameV6: make(map[string][]net.IP),
byAddr: make(map[string][]string),
func newMap() *Map {
return &Map{
name4: make(map[string][]net.IP),
name6: make(map[string][]net.IP),
addr: make(map[string][]string),
}
}
// Len returns the total number of addresses in the hostmap, this includes
// V4/V6 and any reverse addresses.
func (h *hostsMap) Len() int {
// Len returns the total number of addresses in the hostmap, this includes V4/V6 and any reverse addresses.
func (h *Map) Len() int {
l := 0
for _, v4 := range h.byNameV4 {
for _, v4 := range h.name4 {
l += len(v4)
}
for _, v6 := range h.byNameV6 {
for _, v6 := range h.name6 {
l += len(v6)
}
for _, a := range h.byAddr {
for _, a := range h.addr {
l += len(a)
}
return l
@@ -103,11 +92,10 @@ type Hostsfile struct {
Origins []string
// hosts maps for lookups
hmap *hostsMap
hmap *Map
// inline saves the hosts file that is inlined in a Corefile.
// We need a copy here as we want to use it to initialize the maps for parse.
inline *hostsMap
inline *Map
// path to the hosts file
path string
@@ -132,6 +120,7 @@ func (h *Hostsfile) readHosts() {
h.RLock()
size := h.size
h.RUnlock()
if err == nil && h.mtime.Equal(stat.ModTime()) && size == stat.Size() {
return
}
@@ -155,12 +144,11 @@ func (h *Hostsfile) initInline(inline []string) {
}
h.inline = h.parse(strings.NewReader(strings.Join(inline, "\n")))
*h.hmap = *h.inline
}
// Parse reads the hostsfile and populates the byName and byAddr maps.
func (h *Hostsfile) parse(r io.Reader) *hostsMap {
hmap := newHostsMap()
// Parse reads the hostsfile and populates the byName and addr maps.
func (h *Hostsfile) parse(r io.Reader) *Map {
hmap := newMap()
scanner := bufio.NewScanner(r)
for scanner.Scan() {
@@ -173,73 +161,52 @@ func (h *Hostsfile) parse(r io.Reader) *hostsMap {
if len(f) < 2 {
continue
}
addr := parseLiteralIP(string(f[0]))
addr := parseIP(string(f[0]))
if addr == nil {
continue
}
ver := ipVersion(string(f[0]))
family := 0
if addr.To4() != nil {
family = 1
} else {
family = 2
}
for i := 1; i < len(f); i++ {
name := absDomainName(string(f[i]))
name := plugin.Name(string(f[i])).Normalize()
if plugin.Zones(h.Origins).Matches(name) == "" {
// name is not in Origins
continue
}
switch ver {
case 4:
hmap.byNameV4[name] = append(hmap.byNameV4[name], addr)
case 6:
hmap.byNameV6[name] = append(hmap.byNameV6[name], addr)
switch family {
case 1:
hmap.name4[name] = append(hmap.name4[name], addr)
case 2:
hmap.name6[name] = append(hmap.name6[name], addr)
default:
continue
}
if !h.options.autoReverse {
continue
}
hmap.byAddr[addr.String()] = append(hmap.byAddr[addr.String()], name)
hmap.addr[addr.String()] = append(hmap.addr[addr.String()], name)
}
}
for name := range h.hmap.byNameV4 {
hmap.byNameV4[name] = append(hmap.byNameV4[name], h.hmap.byNameV4[name]...)
}
for name := range h.hmap.byNameV4 {
hmap.byNameV6[name] = append(hmap.byNameV6[name], h.hmap.byNameV6[name]...)
}
for addr := range h.hmap.byAddr {
hmap.byAddr[addr] = append(hmap.byAddr[addr], h.hmap.byAddr[addr]...)
}
return hmap
}
// ipVersion returns what IP version was used textually
// For why the string is parsed end to start,
// see IPv4-Compatible IPv6 addresses - RFC 4291 section 2.5.5
func ipVersion(s string) int {
for i := len(s) - 1; i >= 0; i-- {
switch s[i] {
case '.':
return 4
case ':':
return 6
}
}
return 0
}
// LookupStaticHost looks up the IP addresses for the given host from the hosts file.
func (h *Hostsfile) lookupStaticHost(hmapByName map[string][]net.IP, host string) []net.IP {
fqhost := absDomainName(host)
// lookupStaticHost looks up the IP addresses for the given host from the hosts file.
func (h *Hostsfile) lookupStaticHost(m map[string][]net.IP, host string) []net.IP {
h.RLock()
defer h.RUnlock()
if len(hmapByName) == 0 {
if len(m) == 0 {
return nil
}
ips, ok := hmapByName[fqhost]
ips, ok := m[host]
if !ok {
return nil
}
@@ -250,30 +217,38 @@ func (h *Hostsfile) lookupStaticHost(hmapByName map[string][]net.IP, host string
// LookupStaticHostV4 looks up the IPv4 addresses for the given host from the hosts file.
func (h *Hostsfile) LookupStaticHostV4(host string) []net.IP {
return h.lookupStaticHost(h.hmap.byNameV4, host)
host = strings.ToLower(host)
ip1 := h.lookupStaticHost(h.hmap.name4, host)
ip2 := h.lookupStaticHost(h.inline.name4, host)
return append(ip1, ip2...)
}
// LookupStaticHostV6 looks up the IPv6 addresses for the given host from the hosts file.
func (h *Hostsfile) LookupStaticHostV6(host string) []net.IP {
return h.lookupStaticHost(h.hmap.byNameV6, host)
host = strings.ToLower(host)
ip1 := h.lookupStaticHost(h.hmap.name6, host)
ip2 := h.lookupStaticHost(h.inline.name6, host)
return append(ip1, ip2...)
}
// LookupStaticAddr looks up the hosts for the given address from the hosts file.
func (h *Hostsfile) LookupStaticAddr(addr string) []string {
h.RLock()
defer h.RUnlock()
addr = parseLiteralIP(addr).String()
addr = parseIP(addr).String()
if addr == "" {
return nil
}
if len(h.hmap.byAddr) == 0 {
h.RLock()
defer h.RUnlock()
hosts1, _ := h.hmap.addr[addr]
hosts2, _ := h.inline.addr[addr]
if len(hosts1) == 0 && len(hosts2) == 0 {
return nil
}
hosts, ok := h.hmap.byAddr[addr]
if !ok {
return nil
}
hostsCp := make([]string, len(hosts))
copy(hostsCp, hosts)
hostsCp := make([]string, len(hosts1)+len(hosts2))
copy(hostsCp, hosts1)
copy(hostsCp[len(hosts1):], hosts2)
return hostsCp
}

View File

@@ -9,15 +9,18 @@ import (
"reflect"
"strings"
"testing"
"github.com/coredns/coredns/plugin"
)
func testHostsfile(file string) *Hostsfile {
h := &Hostsfile{
Origins: []string{"."},
hmap: newHostsMap(),
hmap: newMap(),
inline: newMap(),
options: newOptions(),
}
h.parseReader(strings.NewReader(file))
h.hmap = h.parse(strings.NewReader(file))
return h
}
@@ -74,44 +77,43 @@ var lookupStaticHostTests = []struct {
{
hosts,
[]staticHostEntry{
{"odin", []string{"127.0.0.2", "127.0.0.3"}, []string{"::2"}},
{"thor", []string{"127.1.1.1"}, []string{}},
{"ullr", []string{"127.1.1.2"}, []string{}},
{"ullrhost", []string{"127.1.1.2"}, []string{}},
{"localhost", []string{}, []string{"fe80::1"}},
{"odin.", []string{"127.0.0.2", "127.0.0.3"}, []string{"::2"}},
{"thor.", []string{"127.1.1.1"}, []string{}},
{"ullr.", []string{"127.1.1.2"}, []string{}},
{"ullrhost.", []string{"127.1.1.2"}, []string{}},
{"localhost.", []string{}, []string{"fe80::1"}},
},
},
{
singlelinehosts, // see golang.org/issue/6646
[]staticHostEntry{
{"odin", []string{"127.0.0.2"}, []string{}},
{"odin.", []string{"127.0.0.2"}, []string{}},
},
},
{
ipv4hosts,
[]staticHostEntry{
{"localhost", []string{"127.0.0.1", "127.0.0.2", "127.0.0.3"}, []string{}},
{"localhost.localdomain", []string{"127.0.0.3"}, []string{}},
{"localhost.", []string{"127.0.0.1", "127.0.0.2", "127.0.0.3"}, []string{}},
{"localhost.localdomain.", []string{"127.0.0.3"}, []string{}},
},
},
{
ipv6hosts,
[]staticHostEntry{
{"localhost", []string{}, []string{"::1", "fe80::1", "fe80::2", "fe80::3"}},
{"localhost.localdomain", []string{}, []string{"fe80::3"}},
{"localhost.", []string{}, []string{"::1", "fe80::1", "fe80::2", "fe80::3"}},
{"localhost.localdomain.", []string{}, []string{"fe80::3"}},
},
},
{
casehosts,
[]staticHostEntry{
{"PreserveMe", []string{"127.0.0.1"}, []string{"::1"}},
{"PreserveMe.local", []string{"127.0.0.1"}, []string{"::1"}},
{"PreserveMe.", []string{"127.0.0.1"}, []string{"::1"}},
{"PreserveMe.local.", []string{"127.0.0.1"}, []string{"::1"}},
},
},
}
func TestLookupStaticHost(t *testing.T) {
for _, tt := range lookupStaticHostTests {
h := testHostsfile(tt.file)
for _, ent := range tt.ents {
@@ -121,7 +123,7 @@ func TestLookupStaticHost(t *testing.T) {
}
func testStaticHost(t *testing.T, ent staticHostEntry, h *Hostsfile) {
ins := []string{ent.in, absDomainName(ent.in), strings.ToLower(ent.in), strings.ToUpper(ent.in)}
ins := []string{ent.in, plugin.Name(ent.in).Normalize(), strings.ToLower(ent.in), strings.ToUpper(ent.in)}
for k, in := range ins {
addrsV4 := h.LookupStaticHostV4(in)
if len(addrsV4) != len(ent.v4) {
@@ -156,43 +158,43 @@ var lookupStaticAddrTests = []struct {
{
hosts,
[]staticIPEntry{
{"255.255.255.255", []string{"broadcasthost"}},
{"127.0.0.2", []string{"odin"}},
{"127.0.0.3", []string{"odin"}},
{"::2", []string{"odin"}},
{"127.1.1.1", []string{"thor"}},
{"127.1.1.2", []string{"ullr", "ullrhost"}},
{"fe80::1", []string{"localhost"}},
{"255.255.255.255", []string{"broadcasthost."}},
{"127.0.0.2", []string{"odin."}},
{"127.0.0.3", []string{"odin."}},
{"::2", []string{"odin."}},
{"127.1.1.1", []string{"thor."}},
{"127.1.1.2", []string{"ullr.", "ullrhost."}},
{"fe80::1", []string{"localhost."}},
},
},
{
singlelinehosts, // see golang.org/issue/6646
[]staticIPEntry{
{"127.0.0.2", []string{"odin"}},
{"127.0.0.2", []string{"odin."}},
},
},
{
ipv4hosts, // see golang.org/issue/8996
[]staticIPEntry{
{"127.0.0.1", []string{"localhost"}},
{"127.0.0.2", []string{"localhost"}},
{"127.0.0.3", []string{"localhost", "localhost.localdomain"}},
{"127.0.0.1", []string{"localhost."}},
{"127.0.0.2", []string{"localhost."}},
{"127.0.0.3", []string{"localhost.", "localhost.localdomain."}},
},
},
{
ipv6hosts, // see golang.org/issue/8996
[]staticIPEntry{
{"::1", []string{"localhost"}},
{"fe80::1", []string{"localhost"}},
{"fe80::2", []string{"localhost"}},
{"fe80::3", []string{"localhost", "localhost.localdomain"}},
{"::1", []string{"localhost."}},
{"fe80::1", []string{"localhost."}},
{"fe80::2", []string{"localhost."}},
{"fe80::3", []string{"localhost.", "localhost.localdomain."}},
},
},
{
casehosts, // see golang.org/issue/12806
[]staticIPEntry{
{"127.0.0.1", []string{"PreserveMe", "PreserveMe.local"}},
{"::1", []string{"PreserveMe", "PreserveMe.local"}},
{"127.0.0.1", []string{"PreserveMe.", "PreserveMe.local."}},
{"::1", []string{"PreserveMe.", "PreserveMe.local."}},
},
},
}
@@ -209,7 +211,7 @@ func TestLookupStaticAddr(t *testing.T) {
func testStaticAddr(t *testing.T, ent staticIPEntry, h *Hostsfile) {
hosts := h.LookupStaticAddr(ent.in)
for i := range ent.out {
ent.out[i] = absDomainName(ent.out[i])
ent.out[i] = plugin.Name(ent.out[i]).Normalize()
}
if !reflect.DeepEqual(hosts, ent.out) {
t.Errorf("%s, lookupStaticAddr(%s) = %v; want %v", h.path, ent.in, hosts, h)
@@ -221,7 +223,7 @@ func TestHostCacheModification(t *testing.T) {
// See https://github.com/golang/go/issues/14212.
h := testHostsfile(ipv4hosts)
ent := staticHostEntry{"localhost", []string{"127.0.0.1", "127.0.0.2", "127.0.0.3"}, []string{}}
ent := staticHostEntry{"localhost.", []string{"127.0.0.1", "127.0.0.2", "127.0.0.3"}, []string{}}
testStaticHost(t, ent, h)
// Modify the addresses return by lookupStaticHost.
addrs := h.LookupStaticHostV6(ent.in)
@@ -231,7 +233,7 @@ func TestHostCacheModification(t *testing.T) {
testStaticHost(t, ent, h)
h = testHostsfile(ipv6hosts)
entip := staticIPEntry{"::1", []string{"localhost"}}
entip := staticIPEntry{"::1", []string{"localhost."}}
testStaticAddr(t, entip, h)
// Modify the hosts return by lookupStaticAddr.
hosts := h.LookupStaticAddr(entip.in)

View File

@@ -26,7 +26,7 @@ func init() {
func periodicHostsUpdate(h *Hosts) chan bool {
parseChan := make(chan bool)
if h.options.reload == durationOf0s {
if h.options.reload == 0 {
return parseChan
}
@@ -78,7 +78,7 @@ func hostsParse(c *caddy.Controller) (Hosts, error) {
h := Hosts{
Hostsfile: &Hostsfile{
path: "/etc/hosts",
hmap: newHostsMap(),
hmap: newMap(),
options: options,
},
}
@@ -152,7 +152,7 @@ func hostsParse(c *caddy.Controller) (Hosts, error) {
if err != nil {
return h, c.Errf("invalid duration for reload '%s'", remaining[0])
}
if reload < durationOf0s {
if reload < 0 {
return h, c.Errf("invalid negative duration for reload '%s'", remaining[0])
}
options.reload = reload

View File

@@ -100,7 +100,7 @@ func TestHostsInlineParse(t *testing.T) {
tests := []struct {
inputFileRules string
shouldErr bool
expectedbyAddr map[string][]string
expectedaddr map[string][]string
expectedFallthrough fall.F
}{
{
@@ -148,19 +148,20 @@ func TestHostsInlineParse(t *testing.T) {
t.Fatalf("Test %d expected no errors, but got '%v'", i, err)
} else if !test.shouldErr {
if !h.Fall.Equal(test.expectedFallthrough) {
t.Fatalf("Test %d expected fallthrough of %v, got %v", i, test.expectedFallthrough, h.Fall)
t.Errorf("Test %d expected fallthrough of %v, got %v", i, test.expectedFallthrough, h.Fall)
}
for k, expectedVal := range test.expectedaddr {
val, ok := h.inline.addr[k]
if !ok {
t.Errorf("Test %d expected %v, got no entry", i, k)
continue
}
for k, expectedVal := range test.expectedbyAddr {
if val, ok := h.hmap.byAddr[k]; !ok {
t.Fatalf("Test %d expected %v, got no entry", i, k)
} else {
if len(expectedVal) != len(val) {
t.Fatalf("Test %d expected %v records for %v, got %v", i, len(expectedVal), k, len(val))
t.Errorf("Test %d expected %v records for %v, got %v", i, len(expectedVal), k, len(val))
}
for j := range expectedVal {
if expectedVal[j] != val[j] {
t.Fatalf("Test %d expected %v for %v, got %v", i, expectedVal[j], j, val[j])
}
t.Errorf("Test %d expected %v for %v, got %v", i, expectedVal[j], j, val[j])
}
}
}