New zone database
This commit is contained in:
1 parent
9c48c83701
commit
8a509168ee
8 files changed
+791
-148
No files matched your search
+1
-1
@@ -25,7 +25,7 @@ type ZoneLookup interface {
|
||||
LookupInaddr(inaddr string) ([]ZoneRecord, error)
|
||||
}
|
||||
|
||||
type ZoneObserver interface {
|
||||
type ZoneObservable interface {
|
||||
// Observe anything that affects a particular name in the zone
|
||||
ObserveName(name string, observer ZoneRecordObserver) error
|
||||
// Observe anything that affects a particular IP in the zone
|
||||
|
||||
@@ -27,7 +27,11 @@ func TestHttp(t *testing.T) {
|
||||
dockerIP = "9.8.7.6"
|
||||
)
|
||||
|
||||
zone := NewZoneDb(DefaultLocalDomain)
|
||||
zone, err := NewZoneDb(ZoneConfig{})
|
||||
wt.AssertNoErr(t, err)
|
||||
err = zone.Start()
|
||||
wt.AssertNoErr(t, err)
|
||||
defer zone.Stop()
|
||||
|
||||
port := rand.Intn(10000) + 32768
|
||||
fmt.Println("Http test on port", port)
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultLocalDomain = "weave.local." // The default name used for the local domain
|
||||
DefaultServerPort = 53 // The default server port
|
||||
DefaultCLICfgFile = "/etc/resolv.conf" // default "resolv.conf" file to try to load
|
||||
DefaultUDPBuflen = 4096 // bigger than the default 512
|
||||
|
||||
@@ -44,8 +44,15 @@ func TestUDPDNSServer(t *testing.T) {
|
||||
)
|
||||
testCIDR1 := testAddr1 + "/24"
|
||||
|
||||
InitDefaultLogging(true)
|
||||
var zone = NewZoneDb(DefaultLocalDomain)
|
||||
InitDefaultLogging(testing.Verbose())
|
||||
Info.Println("TestUDPDNSServer starting")
|
||||
|
||||
zone, err := NewZoneDb(ZoneConfig{})
|
||||
wt.AssertNoErr(t, err)
|
||||
err = zone.Start()
|
||||
wt.AssertNoErr(t, err)
|
||||
defer zone.Stop()
|
||||
|
||||
ip, _, _ := net.ParseCIDR(testCIDR1)
|
||||
zone.AddRecord(containerID, successTestName, ip)
|
||||
|
||||
@@ -125,9 +132,15 @@ func TestTCPDNSServer(t *testing.T) {
|
||||
)
|
||||
dnsAddr := fmt.Sprintf("localhost:%d", testPort)
|
||||
|
||||
InitDefaultLogging(true)
|
||||
var zone = NewZoneDb(DefaultLocalDomain)
|
||||
InitDefaultLogging(testing.Verbose())
|
||||
Info.Println("TestTCPDNSServer starting")
|
||||
|
||||
zone, err := NewZoneDb(ZoneConfig{})
|
||||
wt.AssertNoErr(t, err)
|
||||
err = zone.Start()
|
||||
wt.AssertNoErr(t, err)
|
||||
defer zone.Stop()
|
||||
|
||||
// generate a list of `numAnswers` IP addresses
|
||||
var addrs []ZoneRecord
|
||||
bs := make([]byte, 4)
|
||||
|
||||
+593
-101
@@ -3,41 +3,27 @@ package nameserver
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/benbjohnson/clock"
|
||||
"github.com/miekg/dns"
|
||||
. "github.com/weaveworks/weave/common"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
RDNSDomain = "in-addr.arpa."
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultLocalDomain = "weave.local." // The default name used for the local domain
|
||||
|
||||
defaultRemoteIdent = "weave:remote" // Ident used for info obtained from mDNS
|
||||
)
|
||||
|
||||
// +1 to also exclude a dot
|
||||
var rdnsDomainLen = len(RDNSDomain) + 1
|
||||
|
||||
type Zone interface {
|
||||
AddRecord(ident string, name string, ip net.IP) error
|
||||
DeleteRecord(ident string, ip net.IP) error
|
||||
DeleteRecordsFor(ident string) error
|
||||
Domain() string
|
||||
ZoneLookup
|
||||
}
|
||||
|
||||
type dbRecord struct {
|
||||
Ident string
|
||||
Name string
|
||||
IP net.IP
|
||||
}
|
||||
|
||||
// Very simple data structure for now, with linear searching.
|
||||
// TODO: make more sophisticated to improve performance.
|
||||
type ZoneDb struct {
|
||||
mx sync.RWMutex
|
||||
recs []dbRecord
|
||||
domain string
|
||||
}
|
||||
|
||||
type LookupError string
|
||||
|
||||
func (ops LookupError) Error() string {
|
||||
@@ -51,103 +37,609 @@ func (dup DuplicateError) Error() string {
|
||||
return "Tried to add a duplicate entry"
|
||||
}
|
||||
|
||||
func NewZoneDb(domain string) *ZoneDb {
|
||||
return &ZoneDb{
|
||||
domain: domain,
|
||||
type Zone interface {
|
||||
ZoneObservable
|
||||
ZoneLookup
|
||||
// The domain where we operate (eg, "weave.local.")
|
||||
Domain() string
|
||||
// Add a record in the local database
|
||||
AddRecord(ident string, name string, ip net.IP) error
|
||||
// Delete a record in the local database
|
||||
DeleteRecord(ident string, ip net.IP) error
|
||||
// Delete all records for an ident in the local database
|
||||
DeleteRecordsFor(ident string) error
|
||||
// Return a status string
|
||||
Status() string
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Zone database:
|
||||
//
|
||||
// idents names entry
|
||||
// *-------* *------* *------*
|
||||
// zone -> | ident | --*--> | name | --*-> | IPv4 |
|
||||
// *-------* | *------* | *------*
|
||||
// | ident | *--> | name | *-> | IPv4 |
|
||||
// *-------* *------* *------*
|
||||
// ... ...
|
||||
//
|
||||
// The zone database keeps an ident per container, but also a special
|
||||
// ident for names/IPs obtained from remote peers.
|
||||
//
|
||||
// Each name can have multiple IPs (so far, we only consider IPv4s), and
|
||||
// each IP is stored in a `recordEntry` in the database. Entries store
|
||||
// some additional information like priority and weight of the IP (for
|
||||
// example, for future use with SRV records)
|
||||
//
|
||||
|
||||
// An entry in the zone database
|
||||
type recordEntry struct {
|
||||
Record
|
||||
|
||||
observers []ZoneRecordObserver // the observers for this record
|
||||
insTime time.Time // time when this record was inserted
|
||||
}
|
||||
|
||||
func newRecordEntry(zr ZoneRecord, now time.Time) *recordEntry {
|
||||
return &recordEntry{
|
||||
Record: Record{
|
||||
name: zr.Name(),
|
||||
ip: zr.IP(),
|
||||
priority: zr.Priority(),
|
||||
weight: zr.Weight(),
|
||||
ttl: zr.TTL(),
|
||||
},
|
||||
insTime: now,
|
||||
observers: make([]ZoneRecordObserver, 0),
|
||||
}
|
||||
}
|
||||
|
||||
func (zone *ZoneDb) Domain() string {
|
||||
return zone.domain
|
||||
func (i *recordEntry) addIPObserver(zro ZoneRecordObserver) {
|
||||
i.observers = append(i.observers, zro)
|
||||
}
|
||||
|
||||
func (zone *ZoneDb) indexOf(match func(dbRecord) bool) int {
|
||||
for i, r := range zone.recs {
|
||||
if match(r) {
|
||||
return i
|
||||
// Notify all the IP observers and then remove them all
|
||||
// IP address observers are notified when
|
||||
// - the IP is removed
|
||||
// - the name is removed
|
||||
func (re *recordEntry) notifyIPObservers() {
|
||||
numObservers := len(re.observers)
|
||||
if numObservers > 0 {
|
||||
Debug.Printf("[zonedb] Notifying %d observers of '%s'", numObservers, re.ip)
|
||||
for _, observer := range re.observers {
|
||||
observer()
|
||||
}
|
||||
re.observers = make([]ZoneRecordObserver, 0)
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func (zone *ZoneDb) String() string {
|
||||
zone.mx.RLock()
|
||||
defer zone.mx.RUnlock()
|
||||
func (re recordEntry) TTL() int {
|
||||
// if we never set the TTL (eg, when using AddRecord()), return the standard value...
|
||||
if re.ttl == 0 {
|
||||
return int(localTTL)
|
||||
}
|
||||
return re.ttl
|
||||
}
|
||||
|
||||
// Check if the info in this record is still valid according to the TTL
|
||||
func (re recordEntry) hasExpired(now time.Time) bool {
|
||||
return re.ttl > 0 && now.Sub(re.insTime).Seconds() > float64(re.ttl)
|
||||
}
|
||||
|
||||
func (re recordEntry) String() string {
|
||||
var buf bytes.Buffer
|
||||
for _, r := range zone.recs {
|
||||
fmt.Fprintf(&buf, "%.12s %s %v\n", r.Ident, r.IP, r.Name)
|
||||
fmt.Fprintf(&buf, "%s", re.Record)
|
||||
lobs := len(re.observers)
|
||||
if lobs > 0 {
|
||||
fmt.Fprintf(&buf, "/OBS:%d", lobs)
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func (zone *ZoneDb) LookupName(name string) ([]ZoneRecord, error) {
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
// a name, with group of records by IPv4
|
||||
type name struct {
|
||||
name string
|
||||
ipv4 map[IPv4]*recordEntry // all the IPv4 records for this name
|
||||
observers []ZoneRecordObserver // the observers for this name
|
||||
lastAccessTime time.Time // last time a Lookup* used info from this name
|
||||
}
|
||||
|
||||
func newName(n string) *name {
|
||||
return &name{
|
||||
name: n,
|
||||
ipv4: make(map[IPv4]*recordEntry),
|
||||
observers: make([]ZoneRecordObserver, 0),
|
||||
}
|
||||
}
|
||||
func (n name) len() int { return len(n.ipv4) }
|
||||
func (n name) empty() bool { return n.len() == 0 }
|
||||
func (n name) hasIP(ip net.IP) bool { return n.hasIPv4(ipToIPv4(ip)) }
|
||||
func (n name) hasIPv4(ip IPv4) bool { _, found := n.ipv4[ip]; return found }
|
||||
|
||||
// Get all the entries for this name
|
||||
func (n *name) getAllEntries() (res []*recordEntry) {
|
||||
for _, entry := range n.ipv4 {
|
||||
res = append(res, entry)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Get the entry for an IP in this name
|
||||
func (n *name) getEntryForIP(ip IPv4) (res *recordEntry) {
|
||||
if entry, found := n.ipv4[ip]; found {
|
||||
res = entry
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Add a new IPv4 to this name
|
||||
func (n *name) addIP(zr ZoneRecord, now time.Time) (*recordEntry, error) {
|
||||
ne := newRecordEntry(zr, now)
|
||||
ipv4 := ipToIPv4(zr.IP())
|
||||
n.ipv4[ipv4] = ne
|
||||
n.notifyNameObservers()
|
||||
return ne, nil
|
||||
}
|
||||
|
||||
// Delete an IP for this name
|
||||
func (n *name) deleteIP(ip IPv4) bool {
|
||||
if ipRecord, found := n.ipv4[ip]; found {
|
||||
ipRecord.notifyIPObservers()
|
||||
n.notifyNameObservers()
|
||||
delete(n.ipv4, ip)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Update the list of IPs for this name, adding new ones and removing old IPs...
|
||||
func (n *name) updateIPs(records []ZoneRecord, now time.Time) (added int, removed int) {
|
||||
ipsMap := make(map[IPv4]bool)
|
||||
|
||||
// add the new IPs
|
||||
for _, record := range records {
|
||||
ipv4 := ipToIPv4(record.IP())
|
||||
ipsMap[ipv4] = true
|
||||
if !n.hasIPv4(ipv4) {
|
||||
n.addIP(record, now)
|
||||
added++
|
||||
}
|
||||
}
|
||||
|
||||
// remove the old IPs
|
||||
for ipv4, ipRecord := range n.ipv4 {
|
||||
if _, isNew := ipsMap[ipv4]; !isNew {
|
||||
ipRecord.notifyIPObservers() // we must notify the observers before removing the IP record...
|
||||
delete(n.ipv4, ipv4)
|
||||
removed++
|
||||
}
|
||||
}
|
||||
|
||||
// notify observers and update times
|
||||
if added > 0 || removed > 0 {
|
||||
n.notifyNameObservers()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Add a observer for this name
|
||||
func (n *name) addNameObserver(observer ZoneRecordObserver) {
|
||||
n.observers = append(n.observers, observer)
|
||||
}
|
||||
|
||||
// Notify and flush all the observers
|
||||
// Name observers are notified when
|
||||
// - an IP is added
|
||||
// - an IP is removed
|
||||
// - the name is removed
|
||||
func (n *name) notifyNameObservers() {
|
||||
numObservers := len(n.observers)
|
||||
if numObservers > 0 {
|
||||
Debug.Printf("[zonedb] Notifying %d observers of '%s'", numObservers, n.name)
|
||||
for _, observer := range n.observers {
|
||||
observer()
|
||||
}
|
||||
n.observers = make([]ZoneRecordObserver, 0)
|
||||
}
|
||||
}
|
||||
|
||||
// Convert a set of IP records to a comma-separated string
|
||||
func (n name) String() string {
|
||||
var buf bytes.Buffer
|
||||
for _, ip := range n.ipv4 {
|
||||
fmt.Fprintf(&buf, "%s, ", ip)
|
||||
}
|
||||
l := buf.Len()
|
||||
if l > 2 {
|
||||
buf.Truncate(l - 2)
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
// all the names in a ident
|
||||
type namesSet struct {
|
||||
names map[string]*name
|
||||
}
|
||||
|
||||
func newNamesSet() *namesSet { return &namesSet{names: make(map[string]*name)} }
|
||||
func (ns namesSet) len() int { return len(ns.names) }
|
||||
func (ns namesSet) empty() bool { return ns.len() == 0 }
|
||||
func (ns namesSet) hasName(name string) bool {
|
||||
if n, found := ns.names[name]; !found {
|
||||
return false
|
||||
} else {
|
||||
// the name must have some valid IPs in order to be considered as "existent"
|
||||
return len(n.ipv4) > 0
|
||||
}
|
||||
}
|
||||
|
||||
// Get the zone entries for a name
|
||||
// If the name does not exist and `create` is true, a new name will be created
|
||||
func (ns *namesSet) getName(name string, create bool) (n *name) {
|
||||
if n, found := ns.names[name]; !found && create {
|
||||
newName := newName(name)
|
||||
ns.names[name] = newName
|
||||
return newName
|
||||
} else {
|
||||
return n
|
||||
}
|
||||
}
|
||||
|
||||
// Get all the entries for a name
|
||||
func (ns *namesSet) getEntriesForName(name string) (res []*recordEntry) {
|
||||
n := ns.getName(name, false)
|
||||
if n != nil {
|
||||
res = n.getAllEntries()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Get all the entries for an IP, for all names
|
||||
func (ns *namesSet) getEntriesForIP(ip IPv4) (res []*recordEntry) {
|
||||
for _, name := range ns.names {
|
||||
entry := name.getEntryForIP(ip)
|
||||
if entry != nil {
|
||||
res = append(res, entry)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Add a new IPv4 to a name
|
||||
func (ns *namesSet) addIPToName(zr ZoneRecord, now time.Time) (*recordEntry, error) {
|
||||
n := ns.getName(zr.Name(), true)
|
||||
ipv4 := ipToIPv4(zr.IP())
|
||||
if n.hasIPv4(ipv4) {
|
||||
return n.getEntryForIP(ipv4), DuplicateError{}
|
||||
}
|
||||
ns.touchName(zr.Name(), now)
|
||||
return n.addIP(zr, now)
|
||||
}
|
||||
|
||||
// Delete a name in the names set
|
||||
func (ns *namesSet) deleteName(n string) error {
|
||||
if name, found := ns.names[n]; found {
|
||||
for _, ipRecord := range name.getAllEntries() {
|
||||
ipRecord.notifyIPObservers()
|
||||
}
|
||||
name.notifyNameObservers()
|
||||
delete(ns.names, n)
|
||||
return nil
|
||||
} else {
|
||||
return LookupError(fmt.Sprintf("%+v", name))
|
||||
}
|
||||
}
|
||||
|
||||
// Delete an IPv4 from all names
|
||||
func (ns *namesSet) deleteIP(ip IPv4) error {
|
||||
cnt := 0
|
||||
for nkey, n := range ns.names {
|
||||
if n.deleteIP(ip) {
|
||||
cnt++
|
||||
}
|
||||
if n.empty() {
|
||||
delete(ns.names, nkey)
|
||||
}
|
||||
}
|
||||
|
||||
// we must return an error if no records have been found and deleted
|
||||
if cnt == 0 {
|
||||
return LookupError(fmt.Sprintf("%+v", ip))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get the name query time
|
||||
func (ns namesSet) getNameLastAccess(n string) time.Time {
|
||||
if name, found := ns.names[n]; found {
|
||||
return name.lastAccessTime
|
||||
}
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
// Touch the last access time for a name
|
||||
// The access time is saved only for locally-introduced records (otherwise it could
|
||||
// be lost when names are irrelevant...)
|
||||
func (ns *namesSet) touchName(name string, now time.Time) {
|
||||
Debug.Printf("[zonedb] Touching name %s", name)
|
||||
n := ns.getName(name, false)
|
||||
if n != nil {
|
||||
n.lastAccessTime = now
|
||||
}
|
||||
}
|
||||
|
||||
func (ns namesSet) String() string {
|
||||
var buf bytes.Buffer
|
||||
for _, name := range ns.names {
|
||||
fmt.Fprintf(&buf, "%s, ", name)
|
||||
}
|
||||
l := buf.Len()
|
||||
if l > 2 {
|
||||
buf.Truncate(l - 2)
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
// Names sets, by ident name
|
||||
type identRecordsSet map[string]*namesSet
|
||||
|
||||
func (irs identRecordsSet) String() string {
|
||||
var buf bytes.Buffer
|
||||
for ident, nameset := range irs {
|
||||
fmt.Fprintf(&buf, "%.12s: %s\n", ident, nameset)
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
type ZoneConfig struct {
|
||||
// The local domain
|
||||
Domain string
|
||||
// The interface where mDNS operates
|
||||
Iface *net.Interface
|
||||
// Force a specific mDNS client
|
||||
MDNSClient ZoneMDNSClient
|
||||
// Force a specific mDNS server
|
||||
MDNSServer ZoneMDNSServer
|
||||
// For a specific clock provider
|
||||
Clock clock.Clock
|
||||
}
|
||||
|
||||
// Zone database
|
||||
// The zone database contains the locally known information about the zone.
|
||||
// The zone database uses some other mechanisms (eg, mDNS) for finding unknown
|
||||
// information or for keeping current information up to date.
|
||||
type zoneDb struct {
|
||||
mx sync.RWMutex
|
||||
idents identRecordsSet
|
||||
|
||||
mdnsCli ZoneMDNSClient
|
||||
mdnsSrv ZoneMDNSServer
|
||||
iface *net.Interface
|
||||
domain string // the local domain
|
||||
clock clock.Clock
|
||||
}
|
||||
|
||||
// Create a new zone database
|
||||
func NewZoneDb(config ZoneConfig) (zone *zoneDb, err error) {
|
||||
zone = &zoneDb{
|
||||
domain: config.Domain,
|
||||
idents: make(identRecordsSet),
|
||||
mdnsCli: config.MDNSClient,
|
||||
mdnsSrv: config.MDNSServer,
|
||||
iface: config.Iface,
|
||||
clock: config.Clock,
|
||||
}
|
||||
|
||||
// fix the default configuration parameters
|
||||
if zone.clock == nil {
|
||||
zone.clock = clock.New()
|
||||
}
|
||||
if len(zone.domain) == 0 {
|
||||
zone.domain = DefaultLocalDomain
|
||||
}
|
||||
|
||||
// Create the mDNS client and server
|
||||
if zone.mdnsCli == nil {
|
||||
if zone.mdnsCli, err = NewMDNSClient(); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if zone.mdnsSrv == nil {
|
||||
if zone.mdnsSrv, err = NewMDNSServer(zone); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Start the zone database
|
||||
func (zone *zoneDb) Start() (err error) {
|
||||
if zone.iface != nil {
|
||||
Info.Printf("[zonedb] Using mDNS on %s", zone.iface)
|
||||
} else {
|
||||
Info.Printf("[zonedb] Using mDNS on all interfaces")
|
||||
}
|
||||
|
||||
if err = zone.mdnsCli.Start(zone.iface); err != nil {
|
||||
return
|
||||
}
|
||||
if err = zone.mdnsSrv.Start(zone.iface); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Perform a graceful shutdown of the zone database
|
||||
func (zone *zoneDb) Stop() error {
|
||||
Debug.Printf("[zonedb] Exiting mDNS client and server...")
|
||||
zone.mdnsCli.Stop()
|
||||
zone.mdnsSrv.Stop()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Obtain the domain where this database keeps information for
|
||||
func (zone *zoneDb) Domain() string {
|
||||
return zone.domain
|
||||
}
|
||||
|
||||
// Return the status string
|
||||
func (zone *zoneDb) Status() string {
|
||||
var buf bytes.Buffer
|
||||
fmt.Fprintln(&buf, "Local domain", zone.domain)
|
||||
fmt.Fprintln(&buf, "Interface", zone.iface)
|
||||
fmt.Fprintf(&buf, "Zone database:\n%s", zone)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// Add a record with `name` pointing to `ip`
|
||||
func (zone *zoneDb) AddRecord(ident string, name string, ip net.IP) (err error) {
|
||||
zone.mx.Lock()
|
||||
defer zone.mx.Unlock()
|
||||
Debug.Printf("[zonedb] Adding record: '%s'/'%s'[%s]", ident, name, ip)
|
||||
record := Record{dns.Fqdn(name), ip, 0, 0, 0}
|
||||
_, err = zone.getNamesSet(ident).addIPToName(record, zone.clock.Now())
|
||||
return
|
||||
}
|
||||
|
||||
// Delete all records for an ident
|
||||
func (zone *zoneDb) DeleteRecordsFor(ident string) (err error) {
|
||||
zone.mx.Lock()
|
||||
defer zone.mx.Unlock()
|
||||
Debug.Printf("[zonedb] Deleting records for ident '%s'", ident)
|
||||
zone.deleteIdent(ident)
|
||||
return
|
||||
}
|
||||
|
||||
// Delete an IP in a ident
|
||||
func (zone *zoneDb) DeleteRecord(ident string, ip net.IP) (err error) {
|
||||
zone.mx.Lock()
|
||||
defer zone.mx.Unlock()
|
||||
|
||||
Debug.Printf("[zonedb] Deleting record '%s'/[%s]", ident, ip)
|
||||
if _, found := zone.idents[ident]; found {
|
||||
err = zone.idents[ident].deleteIP(ipToIPv4(ip))
|
||||
if zone.idents[ident].empty() {
|
||||
zone.deleteIdent(ident)
|
||||
}
|
||||
} else {
|
||||
err = LookupError(ident)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Observe a name.
|
||||
// The name must have at least one valid IP address.
|
||||
// Name observers are notified when
|
||||
// - an IP is added
|
||||
// - an IP is removed
|
||||
// - the name is removed
|
||||
// The observer will be invoked on any change/removal that affects this name.
|
||||
// Each observer will be invoked at least once (but possibly more). After that, they will be removed.
|
||||
// The observer should not try to lock the ZoneDB (you will get a deadlock)
|
||||
func (zone *zoneDb) ObserveName(name string, observer ZoneRecordObserver) (err error) {
|
||||
zone.mx.Lock()
|
||||
defer zone.mx.Unlock()
|
||||
|
||||
cnt := 0
|
||||
name = dns.Fqdn(name)
|
||||
for _, nameset := range zone.idents {
|
||||
if n := nameset.getName(name, false); n != nil {
|
||||
n.addNameObserver(observer)
|
||||
cnt += 1
|
||||
}
|
||||
}
|
||||
if cnt == 0 {
|
||||
err = LookupError(fmt.Sprintf("%s", name))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Observe a IP address.
|
||||
// The IP address must be exit in the database.
|
||||
// IP address observers are notified when
|
||||
// - the IP is removed
|
||||
// - the name is removed
|
||||
// The observer will be invoked on any change/removal that affects this IP.
|
||||
// Each observer will be invoked at least once (but possibly more). After that, they will be removed.
|
||||
// The observer should not try to lock the ZoneDB (you will get a deadlock)
|
||||
func (zone *zoneDb) ObserveInaddr(inaddr string, observer ZoneRecordObserver) (err error) {
|
||||
zone.mx.Lock()
|
||||
defer zone.mx.Unlock()
|
||||
|
||||
cnt := 0
|
||||
revIP, err := raddrToIPv4(inaddr)
|
||||
if err != nil {
|
||||
return newParseError("lookup address", inaddr)
|
||||
}
|
||||
for _, nameset := range zone.idents {
|
||||
for _, entry := range nameset.getEntriesForIP(revIP) {
|
||||
entry.addIPObserver(observer)
|
||||
cnt += 1
|
||||
}
|
||||
}
|
||||
if cnt == 0 {
|
||||
err = LookupError(fmt.Sprintf("%+v/%+v", inaddr, revIP))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Get the string representation of a zone
|
||||
func (zone zoneDb) String() string {
|
||||
zone.mx.RLock()
|
||||
defer zone.mx.RUnlock()
|
||||
for _, r := range zone.recs {
|
||||
if r.Name == name {
|
||||
return []ZoneRecord{Record{r.Name, r.IP, 0, 0, 0}}, nil
|
||||
}
|
||||
}
|
||||
return nil, LookupError(name)
|
||||
return zone.idents.String()
|
||||
}
|
||||
|
||||
func (zone *ZoneDb) LookupInaddr(inaddr string) ([]ZoneRecord, error) {
|
||||
if revIP := net.ParseIP(inaddr[:len(inaddr)-rdnsDomainLen]); revIP != nil {
|
||||
revIP4 := revIP.To4()
|
||||
ip := []byte{revIP4[3], revIP4[2], revIP4[1], revIP4[0]}
|
||||
Debug.Printf("[zonedb] Looking for address: %+v", ip)
|
||||
zone.mx.RLock()
|
||||
defer zone.mx.RUnlock()
|
||||
for _, r := range zone.recs {
|
||||
if r.IP.Equal(ip) {
|
||||
return []ZoneRecord{Record{r.Name, r.IP, 0, 0, 0}}, nil
|
||||
}
|
||||
}
|
||||
return nil, LookupError(inaddr)
|
||||
}
|
||||
Warning.Printf("[zonedb] Asked to reverse lookup %s", inaddr)
|
||||
return nil, LookupError(inaddr)
|
||||
}
|
||||
|
||||
func (zone *ZoneDb) AddRecord(ident string, name string, ip net.IP) error {
|
||||
zone.mx.Lock()
|
||||
defer zone.mx.Unlock()
|
||||
fqdn := dns.Fqdn(name)
|
||||
pred := func(r dbRecord) bool { return r.Name == fqdn && r.IP.Equal(ip) && r.Ident == ident }
|
||||
if index := zone.indexOf(pred); index != -1 {
|
||||
return DuplicateError{}
|
||||
}
|
||||
zone.recs = append(zone.recs, dbRecord{ident, fqdn, ip})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (zone *ZoneDb) DeleteRecord(ident string, ip net.IP) error {
|
||||
zone.mx.Lock()
|
||||
defer zone.mx.Unlock()
|
||||
pred := func(r dbRecord) bool { return r.Ident == ident && r.IP.Equal(ip) }
|
||||
if index := zone.indexOf(pred); index != -1 {
|
||||
zone.recs = append(zone.recs[:index], zone.recs[index+1:]...)
|
||||
return nil
|
||||
}
|
||||
return LookupError(ident)
|
||||
}
|
||||
|
||||
func (zone *ZoneDb) DeleteRecordsFor(ident string) error {
|
||||
zone.mx.Lock()
|
||||
defer zone.mx.Unlock()
|
||||
w := 0 // write index
|
||||
|
||||
for _, r := range zone.recs {
|
||||
if r.Ident != ident {
|
||||
zone.recs[w] = r
|
||||
w++
|
||||
}
|
||||
}
|
||||
zone.recs = zone.recs[:w]
|
||||
return nil
|
||||
}
|
||||
|
||||
func (zone *ZoneDb) ContainerDied(ident string) error {
|
||||
// Notify that a container has died
|
||||
func (zone *zoneDb) ContainerDied(ident string) error {
|
||||
Info.Printf("[zonedb] Container %s down. Removing records", ident)
|
||||
return zone.DeleteRecordsFor(ident)
|
||||
}
|
||||
|
||||
// Return true if the we have obtained some information for a name from remote peers
|
||||
// We can have both local (eg, introduced through the HTTP API) and remote (eg, mDNS)
|
||||
// information for a name. This method only checks if there is remote information...
|
||||
func (zone *zoneDb) HasNameRemoteInfo(n string) bool {
|
||||
zone.mx.Lock()
|
||||
defer zone.mx.Unlock()
|
||||
|
||||
remoteNamesSet := zone.getNamesSet(defaultRemoteIdent)
|
||||
if remoteNamesSet != nil {
|
||||
return remoteNamesSet.hasName(n)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Get or create a names set
|
||||
func (zone *zoneDb) getNamesSet(ident string) *namesSet {
|
||||
ns, found := zone.idents[ident]
|
||||
if !found {
|
||||
ns = newNamesSet()
|
||||
zone.idents[ident] = ns
|
||||
}
|
||||
return ns
|
||||
}
|
||||
|
||||
// Delete a ident
|
||||
func (zone *zoneDb) deleteIdent(ident string) {
|
||||
Info.Printf("[zonedb] Removing everything for ident '%s'", ident)
|
||||
if ns, found := zone.idents[ident]; found {
|
||||
for _, n := range ns.names {
|
||||
for _, entry := range n.getAllEntries() {
|
||||
entry.notifyIPObservers()
|
||||
}
|
||||
n.notifyNameObservers()
|
||||
}
|
||||
delete(zone.idents, ident)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package nameserver
|
||||
|
||||
import (
|
||||
"github.com/miekg/dns"
|
||||
. "github.com/weaveworks/weave/common"
|
||||
)
|
||||
|
||||
// Perform a lookup for a name in the zone
|
||||
// The name can be resolved locally with the local database
|
||||
func (zone *zoneDb) LookupName(name string) (res []ZoneRecord, err error) {
|
||||
zone.mx.RLock()
|
||||
defer zone.mx.RUnlock()
|
||||
|
||||
// note: LookupName() is usually called from the mDNS server, so we do not touch the name
|
||||
name = dns.Fqdn(name)
|
||||
Debug.Printf("[zonedb] Looking for name '%s' in local database", name)
|
||||
for identName, nameset := range zone.idents {
|
||||
if identName != defaultRemoteIdent {
|
||||
for _, ze := range nameset.getEntriesForName(name) {
|
||||
res = append(res, ze)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(res) == 0 {
|
||||
err = LookupError(name)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Perform a lookup for a IP address in the zone
|
||||
// The address can be resolved locally with the local database
|
||||
func (zone *zoneDb) LookupInaddr(inaddr string) (res []ZoneRecord, err error) {
|
||||
zone.mx.RLock()
|
||||
defer zone.mx.RUnlock()
|
||||
|
||||
// note: LookupInaddr() is usually called from the mDNS server, so we do not touch the name
|
||||
|
||||
revIPv4, err := raddrToIPv4(inaddr)
|
||||
if err != nil {
|
||||
return nil, newParseError("lookup address", inaddr)
|
||||
}
|
||||
Debug.Printf("[zonedb] Looking for address in local database: '%s' (%s)", revIPv4, inaddr)
|
||||
for identName, nameset := range zone.idents {
|
||||
if identName != defaultRemoteIdent {
|
||||
for _, ze := range nameset.getEntriesForIP(revIPv4) {
|
||||
res = append(res, ZoneRecord(ze))
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(res) == 0 {
|
||||
err = LookupError(inaddr)
|
||||
}
|
||||
return
|
||||
}
|
||||
+99
-31
@@ -1,6 +1,8 @@
|
||||
package nameserver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
. "github.com/weaveworks/weave/common"
|
||||
wt "github.com/weaveworks/weave/testing"
|
||||
"net"
|
||||
"testing"
|
||||
@@ -8,60 +10,118 @@ import (
|
||||
|
||||
func TestZone(t *testing.T) {
|
||||
var (
|
||||
containerID = "deadbeef"
|
||||
otherContainerID = "cowjuice"
|
||||
successTestName = "test1.weave."
|
||||
testAddr1 = "10.2.2.1/24"
|
||||
container1 = "deadbeef"
|
||||
container2 = "cowjuice"
|
||||
name1 = "test1.weave."
|
||||
name1Addr1 = "10.9.2.1/24"
|
||||
name1Addr2 = "10.9.2.2/24"
|
||||
revName1Addr1 = "1.2.9.10.in-addr.arpa."
|
||||
)
|
||||
|
||||
var zone = NewZoneDb(DefaultLocalDomain)
|
||||
InitDefaultLogging(testing.Verbose())
|
||||
|
||||
ip, _, _ := net.ParseCIDR(testAddr1)
|
||||
err := zone.AddRecord(containerID, successTestName, ip)
|
||||
zone, err := NewZoneDb(ZoneConfig{})
|
||||
wt.AssertNoErr(t, err)
|
||||
err = zone.Start()
|
||||
wt.AssertNoErr(t, err)
|
||||
defer zone.Stop()
|
||||
|
||||
ip1, _, _ := net.ParseCIDR(name1Addr1)
|
||||
t.Logf("Adding '%s'/%s to '%s'", name1, ip1, container1)
|
||||
err = zone.AddRecord(container1, name1, ip1)
|
||||
wt.AssertNoErr(t, err)
|
||||
|
||||
// Add a few more records to make the job harder
|
||||
err = zone.AddRecord("abcdef0123", "adummy.weave.", net.ParseIP("10.2.0.1"))
|
||||
t.Logf("Adding 'adummy.weave.' to 'abcdef0123'")
|
||||
err = zone.AddRecord("abcdef0123", "adummy.weave.", net.ParseIP("10.9.0.1"))
|
||||
wt.AssertNoErr(t, err)
|
||||
err = zone.AddRecord("0123abcdef", "zdummy.weave.", net.ParseIP("10.2.0.2"))
|
||||
t.Logf("Adding 'zdummy.weave.' to '0123abcdef'")
|
||||
err = zone.AddRecord("0123abcdef", "zdummy.weave.", net.ParseIP("10.9.0.2"))
|
||||
wt.AssertNoErr(t, err)
|
||||
t.Logf("Zone database:\n%s", zone)
|
||||
|
||||
t.Logf("Checking if we can find the name '%s'", name1)
|
||||
foundIPs, err := zone.LookupName(name1)
|
||||
wt.AssertNoErr(t, err)
|
||||
|
||||
// Check that the address is now there.
|
||||
foundIP, err := zone.LookupName(successTestName)
|
||||
wt.AssertNoErr(t, err)
|
||||
|
||||
if !foundIP[0].IP().Equal(ip) {
|
||||
t.Fatal("Unexpected result for", successTestName, foundIP)
|
||||
if !foundIPs[0].IP().Equal(ip1) {
|
||||
t.Fatal("Unexpected result for", name1, foundIPs)
|
||||
}
|
||||
|
||||
// See if we can find the address by IP.
|
||||
foundName, err := zone.LookupInaddr("1.2.2.10.in-addr.arpa.")
|
||||
t.Logf("Checking if we cannot find some silly name like 'something.wrong'")
|
||||
foundIPs, err = zone.LookupName("something.wrong")
|
||||
wt.AssertErrorType(t, err, (*LookupError)(nil), fmt.Sprintf("unknown name: %+v", foundIPs))
|
||||
|
||||
ip2, _, _ := net.ParseCIDR(name1Addr2)
|
||||
t.Logf("Adding a second IP for '%s'/%s to '%s'", name1, ip2, container1)
|
||||
err = zone.AddRecord(container1, name1, ip2)
|
||||
wt.AssertNoErr(t, err)
|
||||
|
||||
if foundName[0].Name() != successTestName {
|
||||
t.Fatal("Unexpected result for", ip, foundName)
|
||||
t.Logf("Checking if we can find both the old IP and the new IP for '%s'", name1)
|
||||
foundIPs, err = zone.LookupName(name1)
|
||||
wt.AssertNoErr(t, err)
|
||||
if !(foundIPs[0].IP().Equal(ip1) || foundIPs[1].IP().Equal(ip1)) {
|
||||
t.Fatal("Unexpected result for", name1, foundIPs)
|
||||
}
|
||||
if !(foundIPs[0].IP().Equal(ip2) || foundIPs[1].IP().Equal(ip2)) {
|
||||
t.Fatal("Unexpected result for", name1, foundIPs)
|
||||
}
|
||||
|
||||
err = zone.AddRecord(containerID, successTestName, ip)
|
||||
t.Logf("Checking if we can find the address by IP '1.2.9.10.in-addr.arpa.'")
|
||||
foundNames, err := zone.LookupInaddr("1.2.9.10.in-addr.arpa.")
|
||||
wt.AssertNoErr(t, err)
|
||||
|
||||
if foundNames[0].Name() != name1 {
|
||||
t.Fatal("Unexpected result for", ip1, foundNames)
|
||||
}
|
||||
|
||||
t.Logf("Checking we can not find an unknown address '30.20.10.1.in-addr.arpa.'")
|
||||
foundNames, err = zone.LookupInaddr("30.20.10.1.in-addr.arpa.")
|
||||
wt.AssertErrorType(t, err, (*LookupError)(nil), fmt.Sprintf("unknown IP: %+v", foundNames))
|
||||
|
||||
t.Logf("Checking if adding again '%s'/%s in %s results in an error", name1, ip1, container1)
|
||||
err = zone.AddRecord(container1, name1, ip1)
|
||||
wt.AssertErrorType(t, err, (*DuplicateError)(nil), "duplicate add")
|
||||
|
||||
err = zone.AddRecord(otherContainerID, successTestName, ip)
|
||||
// Delete the record for the original container
|
||||
err = zone.DeleteRecord(containerID, ip)
|
||||
t.Logf("Adding '%s'/%s in %s too", name1, ip1, container2)
|
||||
err = zone.AddRecord(container2, name1, ip1)
|
||||
wt.AssertNoErr(t, err)
|
||||
|
||||
_, err = zone.LookupName(successTestName)
|
||||
name1Removed := 0
|
||||
err = zone.ObserveName(name1, func() { t.Logf("Observer #1 for '%s' notified.", name1); name1Removed++ })
|
||||
wt.AssertNoErr(t, err)
|
||||
err = zone.ObserveName(name1, func() { t.Logf("Observer #2 for '%s' notified.", name1); name1Removed++ })
|
||||
wt.AssertNoErr(t, err)
|
||||
err = zone.ObserveInaddr(revName1Addr1, func() { t.Logf("Observer #1 for '%s' notified.", revName1Addr1); name1Removed++ })
|
||||
wt.AssertNoErr(t, err)
|
||||
|
||||
err = zone.DeleteRecord(otherContainerID, ip)
|
||||
t.Logf("Zone database:\n%s", zone)
|
||||
t.Logf("Deleting the %s in %s", ip1, container1)
|
||||
err = zone.DeleteRecord(container1, ip1)
|
||||
wt.AssertNoErr(t, err)
|
||||
t.Logf("Zone database:\n%s", zone)
|
||||
|
||||
t.Logf("Checking %s's observers have been notified on removal", name1)
|
||||
if name1Removed < 3 {
|
||||
t.Logf("Zone database:\n%s", zone)
|
||||
t.Fatalf("Unexpected number (%d) of calls to observers", name1Removed)
|
||||
}
|
||||
|
||||
t.Logf("Checking %s can be found", name1)
|
||||
_, err = zone.LookupName(name1)
|
||||
wt.AssertNoErr(t, err)
|
||||
|
||||
// Check that the address is not there now.
|
||||
_, err = zone.LookupName(successTestName)
|
||||
t.Logf("Checking %s is not found after removing %s it in %s and %s in %s",
|
||||
name1, ip1, container2, ip2, container1)
|
||||
err = zone.DeleteRecord(container1, ip2)
|
||||
err = zone.DeleteRecord(container2, ip1)
|
||||
wt.AssertNoErr(t, err)
|
||||
t.Logf("Zone database:\n%s", zone)
|
||||
_, err = zone.LookupName(name1)
|
||||
wt.AssertErrorType(t, err, (*LookupError)(nil), "after deleting record")
|
||||
|
||||
// Delete a record that isn't there
|
||||
err = zone.DeleteRecord(containerID, net.ParseIP("0.0.0.0"))
|
||||
t.Logf("Checking if removing an unknown record results in an error")
|
||||
err = zone.DeleteRecord(container1, net.ParseIP("0.0.0.0"))
|
||||
wt.AssertErrorType(t, err, (*LookupError)(nil), "when deleting record that doesn't exist")
|
||||
}
|
||||
|
||||
@@ -72,14 +132,22 @@ func TestDeleteFor(t *testing.T) {
|
||||
addr1 = "10.2.2.3/24"
|
||||
addr2 = "10.2.7.8/24"
|
||||
)
|
||||
zone := NewZoneDb(DefaultLocalDomain)
|
||||
|
||||
InitDefaultLogging(testing.Verbose())
|
||||
|
||||
zone, err := NewZoneDb(ZoneConfig{})
|
||||
wt.AssertNoErr(t, err)
|
||||
err = zone.Start()
|
||||
wt.AssertNoErr(t, err)
|
||||
defer zone.Stop()
|
||||
|
||||
for _, addr := range []string{addr1, addr2} {
|
||||
ip, _, _ := net.ParseCIDR(addr)
|
||||
err := zone.AddRecord(id, name, ip)
|
||||
wt.AssertNoErr(t, err)
|
||||
}
|
||||
|
||||
_, err := zone.LookupName(name)
|
||||
_, err = zone.LookupName(name)
|
||||
wt.AssertNoErr(t, err)
|
||||
|
||||
err = zone.DeleteRecordsFor(id)
|
||||
|
||||
+21
-9
@@ -52,15 +52,6 @@ func main() {
|
||||
InitDefaultLogging(debug)
|
||||
Info.Printf("WeaveDNS version %s\n", version) // first thing in log: the version
|
||||
|
||||
var zone = weavedns.NewZoneDb(domain)
|
||||
|
||||
if watch {
|
||||
err := updater.Start(apiPath, zone)
|
||||
if err != nil {
|
||||
Error.Fatal("Unable to start watcher", err)
|
||||
}
|
||||
}
|
||||
|
||||
var iface *net.Interface
|
||||
if ifaceName != "" {
|
||||
var err error
|
||||
@@ -73,6 +64,27 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
zoneConfig := weavedns.ZoneConfig{
|
||||
Domain: domain,
|
||||
Iface: iface,
|
||||
}
|
||||
zone, err := weavedns.NewZoneDb(zoneConfig)
|
||||
if err != nil {
|
||||
Error.Fatal("[main] Unable to initialize the Zone database", err)
|
||||
}
|
||||
err = zone.Start()
|
||||
if err != nil {
|
||||
Error.Fatal("[main] Unable to start the Zone database", err)
|
||||
}
|
||||
defer zone.Stop()
|
||||
|
||||
if watch {
|
||||
err := updater.Start(apiPath, zone)
|
||||
if err != nil {
|
||||
Error.Fatal("[main] Unable to start watcher", err)
|
||||
}
|
||||
}
|
||||
|
||||
srvConfig := weavedns.DNSServerConfig{
|
||||
Port: dnsPort,
|
||||
CacheLen: cacheLen,
|
||||
|
||||
Reference in new issue
Block a user