Skip to content
This repository has been archived by the owner on Jun 20, 2024. It is now read-only.

DNS lookups should not be case sensitive. #1462

Merged
merged 2 commits into from
Oct 1, 2015
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
97 changes: 70 additions & 27 deletions nameserver/entry.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/gob"
"fmt"
"sort"
"strings"
"time"

"github.com/weaveworks/weave/net/address"
Expand All @@ -23,8 +24,26 @@ type Entry struct {
}

type Entries []Entry

func (e1 *Entry) equal(e2 *Entry) bool {
type CaseSensitive Entries
type CaseInsensitive Entries
type SortableEntries interface {
sort.Interface
Get(i int) Entry
}

// Gossip messages are sorted in a case sensitive order...
func (es CaseSensitive) Len() int { return len(es) }
func (es CaseSensitive) Swap(i, j int) { es[i], es[j] = es[j], es[i] }
func (es CaseSensitive) Get(i int) Entry { return es[i] }
func (es CaseSensitive) Less(i, j int) bool { return es[i].less(&es[j]) }

// ... but we store entries in a case insensitive order.
func (es CaseInsensitive) Len() int { return len(es) }
func (es CaseInsensitive) Swap(i, j int) { es[i], es[j] = es[j], es[i] }
func (es CaseInsensitive) Get(i int) Entry { return es[i] }
func (es CaseInsensitive) Less(i, j int) bool { return es[i].insensitiveLess(&es[j]) }

func (e1 Entry) equal(e2 Entry) bool {
return e1.ContainerID == e2.ContainerID &&
e1.Origin == e2.Origin &&
e1.Addr == e2.Addr &&
Expand All @@ -48,6 +67,24 @@ func (e1 *Entry) less(e2 *Entry) bool {
}
}

func (e1 *Entry) insensitiveLess(e2 *Entry) bool {
// Entries are kept sorted by Hostname, Origin, ContainerID then address
e1Hostname, e2Hostname := strings.ToLower(e1.Hostname), strings.ToLower(e2.Hostname)
switch {
case e1Hostname != e2Hostname:
return e1Hostname < e2Hostname

case e1.Origin != e2.Origin:
return e1.Origin < e2.Origin

case e1.ContainerID != e2.ContainerID:
return e1.ContainerID < e2.ContainerID

default:
return e1.Addr < e2.Addr
}
}

// returns true to indicate a change
func (e1 *Entry) merge(e2 *Entry) bool {
// we know container id, origin, add and hostname are equal
Expand All @@ -63,37 +100,37 @@ func (e1 *Entry) String() string {
return fmt.Sprintf("%s -> %s", e1.Hostname, e1.Addr.String())
}

func (es Entries) Len() int { return len(es) }
func (es Entries) Swap(i, j int) { panic("Swap") }
func (es Entries) Less(i, j int) bool { return es[i].less(&es[j]) }

func (es Entries) check() error {
func check(es SortableEntries) error {
if !sort.IsSorted(es) {
return fmt.Errorf("Not sorted!")
}
for i := 1; i < len(es); i++ {
if es[i].equal(&es[i-1]) {
return fmt.Errorf("Duplicate entry: %d:%v and %d:%v", i-1, es[i-1], i, es[i])
for i := 1; i < es.Len(); i++ {
if es.Get(i).equal(es.Get(i - 1)) {
return fmt.Errorf("Duplicate entry: %d:%v and %d:%v", i-1, es.Get(i-1), i, es.Get(i))
}
}
return nil
}

func (es *Entries) checkAndPanic() {
if err := es.check(); err != nil {
func checkAndPanic(es SortableEntries) {
if err := check(es); err != nil {
panic(err)
}
}

func (es *Entries) checkAndPanic() *Entries {
checkAndPanic(CaseInsensitive(*es))
return es
}

func (es *Entries) add(hostname, containerid string, origin router.PeerName, addr address.Address) Entry {
es.checkAndPanic()
defer es.checkAndPanic()
defer es.checkAndPanic().checkAndPanic()

entry := Entry{Hostname: hostname, Origin: origin, ContainerID: containerid, Addr: addr}
i := sort.Search(len(*es), func(i int) bool {
return !(*es)[i].less(&entry)
return !(*es)[i].insensitiveLess(&entry)
})
if i < len(*es) && (*es)[i].equal(&entry) {
if i < len(*es) && (*es)[i].equal(entry) {
if (*es)[i].Tombstone > 0 {
(*es)[i].Tombstone = 0
(*es)[i].Version++
Expand All @@ -107,16 +144,16 @@ func (es *Entries) add(hostname, containerid string, origin router.PeerName, add
}

func (es *Entries) merge(incoming Entries) Entries {
es.checkAndPanic()
defer es.checkAndPanic()
defer es.checkAndPanic().checkAndPanic()

newEntries := Entries{}
i := 0

for _, entry := range incoming {
for i < len(*es) && (*es)[i].less(&entry) {
for i < len(*es) && (*es)[i].insensitiveLess(&entry) {
i++
}
if i < len(*es) && (*es)[i].equal(&entry) {
if i < len(*es) && (*es)[i].equal(entry) {
if (*es)[i].merge(&entry) {
newEntries = append(newEntries, entry)
}
Expand All @@ -133,8 +170,8 @@ func (es *Entries) merge(incoming Entries) Entries {

// f returning true means keep the entry.
func (es *Entries) tombstone(ourname router.PeerName, f func(*Entry) bool) Entries {
es.checkAndPanic()
defer es.checkAndPanic()
defer es.checkAndPanic().checkAndPanic()

tombstoned := Entries{}
for i, e := range *es {
if f(&e) && e.Origin == ourname {
Expand All @@ -148,8 +185,8 @@ func (es *Entries) tombstone(ourname router.PeerName, f func(*Entry) bool) Entri
}

func (es *Entries) filter(f func(*Entry) bool) {
es.checkAndPanic()
defer es.checkAndPanic()
defer es.checkAndPanic().checkAndPanic()

i := 0
for _, e := range *es {
if !f(&e) {
Expand All @@ -163,22 +200,25 @@ func (es *Entries) filter(f func(*Entry) bool) {

func (es Entries) lookup(hostname string) Entries {
es.checkAndPanic()

lowerHostname := strings.ToLower(hostname)
i := sort.Search(len(es), func(i int) bool {
return es[i].Hostname >= hostname
return strings.ToLower(es[i].Hostname) >= lowerHostname
})
if i >= len(es) || es[i].Hostname != hostname {
if i >= len(es) || strings.ToLower(es[i].Hostname) != lowerHostname {
return Entries{}
}

j := sort.Search(len(es)-i, func(j int) bool {
return es[i+j].Hostname > hostname
return strings.ToLower(es[i+j].Hostname) > lowerHostname
})

return es[i : i+j]
}

func (es *Entries) first(f func(*Entry) bool) (*Entry, error) {
es.checkAndPanic()

for _, e := range *es {
if f(&e) {
return &e, nil
Expand All @@ -193,6 +233,8 @@ type GossipData struct {
}

func (g *GossipData) Merge(o router.GossipData) {
checkAndPanic(CaseSensitive(g.Entries))
defer func() { checkAndPanic(CaseSensitive(g.Entries)) }()
other := o.(*GossipData)
g.Entries.merge(other.Entries)
if g.Timestamp < other.Timestamp {
Expand All @@ -201,6 +243,7 @@ func (g *GossipData) Merge(o router.GossipData) {
}

func (g *GossipData) Encode() [][]byte {
checkAndPanic(CaseSensitive(g.Entries))
buf := &bytes.Buffer{}
if err := gob.NewEncoder(buf).Encode(g); err != nil {
panic(err)
Expand Down
6 changes: 3 additions & 3 deletions nameserver/nameserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
"encoding/gob"
"fmt"
"sort"
"sync"
"time"

Expand Down Expand Up @@ -198,6 +199,7 @@ func (n *Nameserver) Gossip() router.GossipData {
Timestamp: now(),
}
copy(gossip.Entries, n.entries)
sort.Sort(CaseSensitive(gossip.Entries))
return gossip
}

Expand All @@ -215,9 +217,7 @@ func (n *Nameserver) receiveGossip(msg []byte) (router.GossipData, router.Gossip
return nil, nil, fmt.Errorf("clock skew of %d detected", delta)
}

if err := gossip.Entries.check(); err != nil {
return nil, nil, err
}
sort.Sort(CaseInsensitive(gossip.Entries))

n.Lock()
defer n.Unlock()
Expand Down
31 changes: 31 additions & 0 deletions test/245_dns_case_insensitive_test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#! /bin/bash

. ./config.sh

C1=10.2.0.78
C2=10.2.0.79
C3=10.2.0.80

start_suite "DNS lookup case (in)sensitivity"

weave_on $HOST1 launch

start_container_with_dns $HOST1 --name=test

start_container $HOST1 $C1/24 --name=seeone
assert_dns_record $HOST1 test seeone.weave.local $C1
assert_dns_record $HOST1 test SeeOne.weave.local $C1
assert_dns_record $HOST1 test SEEONE.weave.local $C1

This comment was marked as abuse.

This comment was marked as abuse.

This comment was marked as abuse.


start_container $HOST1 $C2/24 --name=SeEtWo
assert_dns_record $HOST1 test seetwo.weave.local $C2
assert_dns_record $HOST1 test SeeTwo.weave.local $C2
assert_dns_record $HOST1 test SEETWO.weave.local $C2

start_container $HOST1 $C3/24 --name=seetwo
assert_dns_record $HOST1 test seetwo.weave.local $C2 $C3
assert_dns_record $HOST1 test SeeTwo.weave.local $C2 $C3
assert_dns_record $HOST1 test SEETWO.weave.local $C2 $C3
assert "exec_on $HOST1 test dig +short seetwo.weave.local A | grep -v ';;' | wc -l" 2

This comment was marked as abuse.

This comment was marked as abuse.


end_suite
2 changes: 1 addition & 1 deletion test/config.sh
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ assert_dns_record() {

[ -z "$DEBUG" ] || greyly echo "Checking whether the IPs '$@' exists at $host:$container"
for ip in "$@" ; do
assert "exec_on $host $container getent hosts $ip | tr -s ' '" "$ip $name"
assert "exec_on $host $container getent hosts $ip | tr -s ' ' | tr '[:upper:]' '[:lower:]'" "$(echo $ip $name | tr '[:upper:]' '[:lower:]')"
done
}

Expand Down