From 3866bd5e6337534080cc19e5d3f7c866b708b30f Mon Sep 17 00:00:00 2001 From: Michael Bridgen Date: Wed, 7 Jan 2015 14:29:35 +0000 Subject: [PATCH 1/8] Parse reverse address lookup IPs in the Zone .. rather than making clients of the zone do it. At the minute there's only one call site, but soon there will be more. --- nameserver/server.go | 25 ++++++++----------------- nameserver/zone.go | 32 ++++++++++++++++++++++++-------- nameserver/zone_test.go | 2 +- 3 files changed, 33 insertions(+), 26 deletions(-) diff --git a/nameserver/server.go b/nameserver/server.go index 896f9e26..61540836 100644 --- a/nameserver/server.go +++ b/nameserver/server.go @@ -9,12 +9,8 @@ import ( const ( LOCAL_DOMAIN = "weave.local." - RDNS_DOMAIN = "in-addr.arpa." ) -// +1 to also exclude a dot -var rdnsDomainLen = len(RDNS_DOMAIN) + 1 - func checkFatal(e error) { if e != nil { Error.Fatal(e) @@ -77,19 +73,14 @@ func rdnsHandler(zone Zone, mdnsClient *MDNSClient) dns.HandlerFunc { q := r.Question[0] Debug.Printf("Local rdns query: %+v", q) if q.Qtype == dns.TypePTR { - if ip := net.ParseIP(q.Name[:len(q.Name)-rdnsDomainLen]); ip != nil { - ip4 := ip.To4() - revIP := []byte{ip4[3], ip4[2], ip4[1], ip4[0]} - Debug.Printf("Looking for address: %+v", revIP) - if name, err := zone.ReverseLookupLocal(revIP); err == nil { - Debug.Printf("Found name: %s", name) - m := makePTRReply(r, &q, []string{name}) - w.WriteMsg(m) - } else { - Debug.Printf("Failed lookup for %s; sending mDNS query", q.Name) - // We don't know the answer; see if someone else does - // TODO - } + if name, err := zone.ReverseLookupLocal(q.Name); err == nil { + Debug.Printf("Found name: %s", name) + m := makePTRReply(r, &q, []string{name}) + w.WriteMsg(m) + } else { + Debug.Printf("Failed lookup for %s; sending mDNS query", q.Name) + // We don't know the answer; see if someone else does + // TODO } } } diff --git a/nameserver/zone.go b/nameserver/zone.go index 44c194b9..82f28f38 100644 --- a/nameserver/zone.go +++ b/nameserver/zone.go @@ -2,16 +2,24 @@ package nameserver import ( "github.com/miekg/dns" + . "github.com/zettio/weave/common" "net" "sync" ) +const ( + RDNS_DOMAIN = "in-addr.arpa." +) + +// +1 to also exclude a dot +var rdnsDomainLen = len(RDNS_DOMAIN) + 1 + type Zone interface { AddRecord(ident string, name string, ip net.IP) error DeleteRecord(ident string, ip net.IP) error DeleteRecordsFor(ident string) error LookupLocal(name string) (net.IP, error) - ReverseLookupLocal(ip net.IP) (string, error) + ReverseLookupLocal(inaddr string) (string, error) } type Record struct { @@ -60,15 +68,23 @@ func (zone *ZoneDb) LookupLocal(name string) (net.IP, error) { return nil, LookupError(name) } -func (zone *ZoneDb) ReverseLookupLocal(ip net.IP) (string, error) { - zone.mx.RLock() - defer zone.mx.RUnlock() - for _, r := range zone.recs { - if r.IP.Equal(ip) { - return r.Name, nil +func (zone *ZoneDb) ReverseLookupLocal(inaddr string) (string, 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("Looking for address: %+v", ip) + zone.mx.RLock() + defer zone.mx.RUnlock() + for _, r := range zone.recs { + if r.IP.Equal(ip) { + return r.Name, nil + } } + return "", LookupError(inaddr) + } else { + Warning.Printf("Asked to reverse lookup %s", inaddr) + return "", LookupError(inaddr) } - return "", LookupError(ip.String()) } func (zone *ZoneDb) AddRecord(ident string, name string, ip net.IP) error { diff --git a/nameserver/zone_test.go b/nameserver/zone_test.go index 6d99cf2a..86db36ec 100644 --- a/nameserver/zone_test.go +++ b/nameserver/zone_test.go @@ -35,7 +35,7 @@ func TestZone(t *testing.T) { } // See if we can find the address by IP. - foundName, err := zone.ReverseLookupLocal(ip) + foundName, err := zone.ReverseLookupLocal("1.2.0.10.in-addr.arpa.") wt.AssertNoErr(t, err) if foundName != successTestName { From bf752d2d1f939bb9df306d80be0cc0b1972ec55f Mon Sep 17 00:00:00 2001 From: Michael Bridgen Date: Wed, 7 Jan 2015 15:59:44 +0000 Subject: [PATCH 2/8] Implement zone lookup with MDNSCLient This separates Zone into the DB ops and the Lookup ops, and implements the latter for MDNSClient so it can be used just like the zonedb. A notable change is that Lookup methods only ever return one result, while MDNSClient.SendQuery could in theory return multiple results (though it wouldn't, because no server would supply more than one result in a response, and it stops waiting after getting one response ..). ReverseLookupLocal is implemented even though no MDNSServer will answer at present (hence no test yet). Lastly, the handler (creating) functions can now be given a list of Lookup implementations to try in order. --- nameserver/mdns_client_test.go | 15 ++++++++ nameserver/mdns_lookup.go | 37 +++++++++++++++++++ nameserver/server.go | 66 ++++++++++++++-------------------- nameserver/zone.go | 8 +++-- 4 files changed, 85 insertions(+), 41 deletions(-) create mode 100644 nameserver/mdns_lookup.go diff --git a/nameserver/mdns_client_test.go b/nameserver/mdns_client_test.go index b50fcfd5..1297b4c1 100644 --- a/nameserver/mdns_client_test.go +++ b/nameserver/mdns_client_test.go @@ -152,3 +152,18 @@ outerloop: t.Fatal("Unexpected result for", successTestName, context1.receivedAddr, context2.receivedAddr, context1.receivedCount, context2.receivedCount) } } + +func TestAsLookup(t *testing.T) { + mdnsClient, server, _ := setup(t) + defer mdnsClient.Shutdown() + defer server.Shutdown() + + ip, err := mdnsClient.LookupLocal(successTestName) + wt.AssertNoErr(t, err) + if !testAddr.Equal(ip) { + t.Fatalf("Returned address incorrect %s", ip) + } + + ip, err = mdnsClient.LookupLocal("foo.example.com.") + wt.AssertErrorType(t, err, (*LookupError)(nil), "unknown hostname") +} diff --git a/nameserver/mdns_lookup.go b/nameserver/mdns_lookup.go new file mode 100644 index 00000000..5fa9cdd4 --- /dev/null +++ b/nameserver/mdns_lookup.go @@ -0,0 +1,37 @@ +package nameserver + +import ( + "github.com/miekg/dns" + . "github.com/zettio/weave/common" + "net" +) + +func mdnsLookup(client *MDNSClient, name string, qtype uint16) (*ResponseA, error) { + channel := make(chan *ResponseA) + client.SendQuery(name, qtype, channel) + for resp := range channel { + Debug.Printf("Got response name %s addr %s", resp.Name, resp.Addr) + if err := resp.Err; err != nil { + return nil, err + } else { + return resp, nil + } + } + return nil, LookupError(name) +} + +func (client *MDNSClient) LookupLocal(name string) (net.IP, error) { + if r, e := mdnsLookup(client, name, dns.TypeA); r != nil { + return r.Addr, nil + } else { + return nil, e + } +} + +func (client *MDNSClient) ReverseLookupLocal(inaddr string) (string, error) { + if r, e := mdnsLookup(client, inaddr, dns.TypePTR); r != nil { + return r.Name, nil + } else { + return "", e + } +} diff --git a/nameserver/server.go b/nameserver/server.go index 61540836..723e3483 100644 --- a/nameserver/server.go +++ b/nameserver/server.go @@ -31,57 +31,45 @@ func makeDNSFailResponse(r *dns.Msg) *dns.Msg { return m } -func queryHandler(zone Zone, mdnsClient *MDNSClient) dns.HandlerFunc { +func queryHandler(lookups []Lookup) dns.HandlerFunc { return func(w dns.ResponseWriter, r *dns.Msg) { q := r.Question[0] - Debug.Printf("Local query: %+v", q) + Debug.Printf("Query: %+v", q) if q.Qtype == dns.TypeA { - if ip, err := zone.LookupLocal(q.Name); err == nil { - m := makeAddressReply(r, &q, []net.IP{ip}) - w.WriteMsg(m) - } else { - Debug.Printf("Failed lookup for %s; sending mDNS query", q.Name) - // We don't know the answer; see if someone else does - channel := make(chan *ResponseA) - replies := make([]net.IP, 0) - go func() { - for resp := range channel { - Debug.Printf("Got address response %s to query %s addr %s", resp.Name, q.Name, resp.Addr) - replies = append(replies, resp.Addr) - } - var responseMsg *dns.Msg - if len(replies) > 0 { - responseMsg = makeAddressReply(r, &q, replies) - } else { - responseMsg = makeDNSFailResponse(r) - } - w.WriteMsg(responseMsg) - }() - mdnsClient.SendQuery(q.Name, dns.TypeA, channel) + for _, lookup := range lookups { + if ip, err := lookup.LookupLocal(q.Name); err == nil { + m := makeAddressReply(r, &q, []net.IP{ip}) + w.WriteMsg(m) + return + } } + Debug.Printf("Query failed for %s", q.Name) + w.WriteMsg(makeDNSFailResponse(r)) } else { - Warning.Printf("Local query not handled: %+v", q) - m := makeDNSFailResponse(r) - w.WriteMsg(m) + Warning.Printf("Query not handled: %+v", q) + w.WriteMsg(makeDNSFailResponse(r)) } return } } -func rdnsHandler(zone Zone, mdnsClient *MDNSClient) dns.HandlerFunc { +func rdnsHandler(lookups []Lookup) dns.HandlerFunc { return func(w dns.ResponseWriter, r *dns.Msg) { q := r.Question[0] - Debug.Printf("Local rdns query: %+v", q) + Debug.Printf("Reverse query: %+v", q) if q.Qtype == dns.TypePTR { - if name, err := zone.ReverseLookupLocal(q.Name); err == nil { - Debug.Printf("Found name: %s", name) - m := makePTRReply(r, &q, []string{name}) - w.WriteMsg(m) - } else { - Debug.Printf("Failed lookup for %s; sending mDNS query", q.Name) - // We don't know the answer; see if someone else does - // TODO + for _, lookup := range lookups { + if name, err := lookup.ReverseLookupLocal(q.Name); err == nil { + m := makePTRReply(r, &q, []string{name}) + w.WriteMsg(m) + return + } } + Debug.Printf("Reverse query failed for %s", q.Name) + w.WriteMsg(makeDNSFailResponse(r)) + } else { + Warning.Printf("Reverse query not handled: %+v", q) + w.WriteMsg(makeDNSFailResponse(r)) } } } @@ -125,8 +113,8 @@ func StartServer(zone Zone, iface *net.Interface, dnsPort int, httpPort int, wai checkFatal(err) LocalServeMux := dns.NewServeMux() - LocalServeMux.HandleFunc(LOCAL_DOMAIN, queryHandler(zone, mdnsClient)) - LocalServeMux.HandleFunc(RDNS_DOMAIN, rdnsHandler(zone, mdnsClient)) + LocalServeMux.HandleFunc(LOCAL_DOMAIN, queryHandler([]Lookup{zone, mdnsClient})) + LocalServeMux.HandleFunc(RDNS_DOMAIN, rdnsHandler([]Lookup{zone, mdnsClient})) LocalServeMux.HandleFunc(".", notUsHandler()) mdnsServer, err := NewMDNSServer(zone) diff --git a/nameserver/zone.go b/nameserver/zone.go index 82f28f38..fd2ceaaf 100644 --- a/nameserver/zone.go +++ b/nameserver/zone.go @@ -14,12 +14,16 @@ const ( // +1 to also exclude a dot var rdnsDomainLen = len(RDNS_DOMAIN) + 1 +type Lookup interface { + LookupLocal(name string) (net.IP, error) + ReverseLookupLocal(inaddr string) (string, error) +} + type Zone interface { AddRecord(ident string, name string, ip net.IP) error DeleteRecord(ident string, ip net.IP) error DeleteRecordsFor(ident string) error - LookupLocal(name string) (net.IP, error) - ReverseLookupLocal(inaddr string) (string, error) + Lookup } type Record struct { From dd6ca5ab925e5b234e502f0997812841c4c25f05 Mon Sep 17 00:00:00 2001 From: Michael Bridgen Date: Thu, 8 Jan 2015 12:42:41 +0000 Subject: [PATCH 3/8] Factor out the particulars of looking up A or PTR queries --- nameserver/mdns_server.go | 54 ++++++++++++++++++++++++++-------- nameserver/mdns_server_test.go | 8 ++--- 2 files changed, 45 insertions(+), 17 deletions(-) diff --git a/nameserver/mdns_server.go b/nameserver/mdns_server.go index 71b9f2a1..0516036d 100644 --- a/nameserver/mdns_server.go +++ b/nameserver/mdns_server.go @@ -50,23 +50,51 @@ func (s *MDNSServer) Start(ifi *net.Interface) error { return err } - handleMDNS := func(w dns.ResponseWriter, r *dns.Msg) { - // Ignore answers to other questions - if len(r.Answer) == 0 && len(r.Question) > 0 { - q := r.Question[0] - if ip, err := s.zone.LookupLocal(q.Name); err == nil { - m := makeAddressReply(r, &q, []net.IP{ip}) - if err = s.sendResponse(m); err != nil { - Warning.Printf("Error writing to %s", w) + handler := func(qtype uint16, lookup func(*dns.Msg, *dns.Question) *dns.Msg) dns.HandlerFunc { + return func(w dns.ResponseWriter, r *dns.Msg) { + // Handle only questions, ignore answers. We might also + // ignore questions that arise locally (i.e., that come + // from an IP we think is local), but in the interest of + // avoiding complication, and easier testing, this is + // elided on the assumption that the client wouldn't ask + // if it already knew the answer, and if it does ask, + // it'll be happy to get an answer. + if len(r.Answer) == 0 && len(r.Question) > 0 { + q := &r.Question[0] + if q.Qtype == qtype { + if m := lookup(r, q); m != nil { + if err = s.sendResponse(m); err != nil { + Warning.Printf("Error writing to %s", w) + } + } else { + Debug.Printf("No local answer for mDNS query %s", q.Name) + } } - } else if s.addrIsLocal(w.RemoteAddr()) { - // ignore this - it's our own query received via multicast - } else { - Debug.Printf("Failed MDNS lookup for %s", q.Name) } } } - go dns.ActivateAndServe(nil, conn, dns.HandlerFunc(handleMDNS)) + + handleLocal := handler(dns.TypeA, func(r *dns.Msg, q *dns.Question) *dns.Msg { + if ip, err := s.zone.LookupLocal(q.Name); err == nil { + return makeAddressReply(r, q, []net.IP{ip}) + } else { + return nil + } + }) + + handleReverse := handler(dns.TypePTR, func(r *dns.Msg, q *dns.Question) *dns.Msg { + if name, err := s.zone.ReverseLookupLocal(q.Name); err == nil { + return makePTRReply(r, q, []string{name}) + } else { + return nil + } + }) + + mux := dns.NewServeMux() + mux.HandleFunc(LOCAL_DOMAIN, handleLocal) + mux.HandleFunc(RDNS_DOMAIN, handleReverse) + + go dns.ActivateAndServe(nil, conn, mux) return err } diff --git a/nameserver/mdns_server_test.go b/nameserver/mdns_server_test.go index 894ed120..9b1bde54 100644 --- a/nameserver/mdns_server_test.go +++ b/nameserver/mdns_server_test.go @@ -34,7 +34,7 @@ func TestServerSimpleQuery(t *testing.T) { log.Println("TestServerSimpleQuery starting") var zone = new(ZoneDb) ip, _, _ := net.ParseCIDR(testAddr1) - zone.AddRecord(containerID, "test.weave.", ip) + zone.AddRecord(containerID, "test.weave.local.", ip) mdnsServer, err := NewMDNSServer(zone) wt.AssertNoErr(t, err) @@ -67,15 +67,15 @@ func TestServerSimpleQuery(t *testing.T) { time.Sleep(100 * time.Millisecond) // Allow for server to get going - sendQuery("test.weave.", dns.TypeA) + sendQuery("test.weave.local.", dns.TypeA) time.Sleep(time.Second) if receivedCount != 1 { - t.Fatal("Unexpected result count for test.weave", receivedCount) + t.Fatal("Unexpected result count for test.weave.local", receivedCount) } if !receivedAddr.Equal(ip) { - t.Fatal("Unexpected result for test.weave", receivedAddr) + t.Fatal("Unexpected result for test.weave.local", receivedAddr) } receivedCount = 0 From e9b84c968da67752045c4ccf74f0bfea48c4722f Mon Sep 17 00:00:00 2001 From: Michael Bridgen Date: Thu, 8 Jan 2015 13:35:10 +0000 Subject: [PATCH 4/8] Clean up the mdns_server tests so we can test PTR queries --- nameserver/mdns_server.go | 1 + nameserver/mdns_server_test.go | 55 ++++++++++++++++++++++++++++------ 2 files changed, 47 insertions(+), 9 deletions(-) diff --git a/nameserver/mdns_server.go b/nameserver/mdns_server.go index 0516036d..2cca64de 100644 --- a/nameserver/mdns_server.go +++ b/nameserver/mdns_server.go @@ -63,6 +63,7 @@ func (s *MDNSServer) Start(ifi *net.Interface) error { q := &r.Question[0] if q.Qtype == qtype { if m := lookup(r, q); m != nil { + Debug.Printf("Found local answer to mDNS query %s", q.Name) if err = s.sendResponse(m); err != nil { Warning.Printf("Error writing to %s", w) } diff --git a/nameserver/mdns_server_test.go b/nameserver/mdns_server_test.go index 9b1bde54..4319b609 100644 --- a/nameserver/mdns_server_test.go +++ b/nameserver/mdns_server_test.go @@ -11,7 +11,9 @@ import ( var ( containerID = "deadbeef" + testName = "test.weave.local." testAddr1 = "10.0.2.1/24" + testInAddr1 = "1.2.0.10.in-addr.arpa." ) func sendQuery(name string, querytype uint16) error { @@ -31,10 +33,13 @@ func sendQuery(name string, querytype uint16) error { } func TestServerSimpleQuery(t *testing.T) { + // The ff can be handy for debugging (obvs) + //wt.InitDefaultLogging(true) + log.Println("TestServerSimpleQuery starting") var zone = new(ZoneDb) ip, _, _ := net.ParseCIDR(testAddr1) - zone.AddRecord(containerID, "test.weave.local.", ip) + zone.AddRecord(containerID, testName, ip) mdnsServer, err := NewMDNSServer(zone) wt.AssertNoErr(t, err) @@ -42,8 +47,26 @@ func TestServerSimpleQuery(t *testing.T) { wt.AssertNoErr(t, err) var receivedAddr net.IP + var receivedName string + var recvChan chan interface{} receivedCount := 0 + reset := func() { + receivedAddr = nil + receivedName = "" + receivedCount = 0 + recvChan = make(chan interface{}) + } + + wait := func() { + select { + case <-recvChan: + return + case <-time.After(100 * time.Millisecond): + return + } + } + // Implement a minimal listener for responses multicast, err := LinkLocalMulticastListener(nil) wt.AssertNoErr(t, err) @@ -56,8 +79,12 @@ func TestServerSimpleQuery(t *testing.T) { case *dns.A: receivedAddr = rr.A receivedCount++ + case *dns.PTR: + receivedName = rr.Ptr + receivedCount++ } } + recvChan <- "ok" } } @@ -67,22 +94,32 @@ func TestServerSimpleQuery(t *testing.T) { time.Sleep(100 * time.Millisecond) // Allow for server to get going - sendQuery("test.weave.local.", dns.TypeA) - - time.Sleep(time.Second) + reset() + sendQuery(testName, dns.TypeA) + wait() if receivedCount != 1 { - t.Fatal("Unexpected result count for test.weave.local", receivedCount) + t.Fatalf("Unexpected result count %d for %s", receivedCount, testName) } if !receivedAddr.Equal(ip) { - t.Fatal("Unexpected result for test.weave.local", receivedAddr) + t.Fatalf("Unexpected result %s for %s", receivedAddr, testName) } - receivedCount = 0 - + reset() sendQuery("testfail.weave.", dns.TypeA) + wait() if receivedCount != 0 { - t.Fatal("Unexpected result count for testfail.weave", receivedCount) + t.Fatalf("Unexpected result count %d for testfail.weave", receivedCount) + } + + reset() + sendQuery(testInAddr1, dns.TypePTR) + wait() + + if receivedCount != 1 { + t.Fatalf("Expected an answer to %s, got %d answers", testInAddr1, receivedCount) + } else if !(testName == receivedName) { + t.Fatalf("Expected answer %s to query for %s, got %s", testName, testInAddr1, receivedName) } } From 89fa52818c41b2c4cdf5a3f30da26696d0dc890f Mon Sep 17 00:00:00 2001 From: Michael Bridgen Date: Thu, 8 Jan 2015 18:30:30 +0000 Subject: [PATCH 5/8] Account for PTR answers in mdns_client En passant, ResponseA -> the more versatile Response. --- nameserver/mdns_client.go | 42 ++++++++++++-------- nameserver/mdns_client_test.go | 71 +++++++++++++++++++++------------- nameserver/mdns_lookup.go | 4 +- 3 files changed, 72 insertions(+), 45 deletions(-) diff --git a/nameserver/mdns_client.go b/nameserver/mdns_client.go index 7acf45a5..d602d1d9 100644 --- a/nameserver/mdns_client.go +++ b/nameserver/mdns_client.go @@ -26,7 +26,7 @@ var ( } ) -type ResponseA struct { +type Response struct { Name string Addr net.IP Err error @@ -34,7 +34,7 @@ type ResponseA struct { type responseInfo struct { timeout time.Time // if no answer by this time, give up - ch chan<- *ResponseA + ch chan<- *Response } // Represents one query that we have sent for one name. @@ -60,7 +60,7 @@ type MDNSClient struct { type mDNSQueryInfo struct { name string querytype uint16 - responseCh chan<- *ResponseA + responseCh chan<- *Response } func NewMDNSClient() (*MDNSClient, error) { @@ -121,7 +121,7 @@ func (c *MDNSClient) Shutdown() { } // Async -func (c *MDNSClient) SendQuery(name string, querytype uint16, responseCh chan<- *ResponseA) { +func (c *MDNSClient) SendQuery(name string, querytype uint16, responseCh chan<- *Response) { c.queryChan <- &MDNSInteraction{ code: CSendQuery, payload: mDNSQueryInfo{name, querytype, responseCh}, @@ -209,7 +209,7 @@ func (c *MDNSClient) handleSendQuery(q mDNSQueryInfo) { m.RecursionDesired = false buf, err := m.Pack() if err != nil { - q.responseCh <- &ResponseA{Err: err} + q.responseCh <- &Response{Err: err} close(q.responseCh) return } @@ -218,7 +218,7 @@ func (c *MDNSClient) handleSendQuery(q mDNSQueryInfo) { id: m.Id, } if _, err = c.conn.WriteTo(buf, c.addr); err != nil { - q.responseCh <- &ResponseA{Err: err} + q.responseCh <- &Response{Err: err} close(q.responseCh) return } @@ -235,19 +235,29 @@ func (c *MDNSClient) handleSendQuery(q mDNSQueryInfo) { func (c *MDNSClient) handleResponse(r *dns.Msg) { for _, answer := range r.Answer { + var name string + var res *Response + switch rr := answer.(type) { case *dns.A: - name := rr.Hdr.Name - if query, found := c.inflight[name]; found { - for _, resp := range query.responseInfos { - resp.ch <- &ResponseA{Name: rr.Hdr.Name, Addr: rr.A} - close(resp.ch) - } - delete(c.inflight, name) - } else { - // We've received a response that didn't match a query - // Do we want to cache it? + name = rr.Hdr.Name + res = &Response{Addr: rr.A} + case *dns.PTR: + name = rr.Hdr.Name + res = &Response{Name: rr.Ptr} + default: + return + } + + if query, found := c.inflight[name]; found { + for _, resp := range query.responseInfos { + resp.ch <- res + close(resp.ch) } + delete(c.inflight, name) + } else { + // We've received a response that didn't match a query + // Do we want to cache it? } } } diff --git a/nameserver/mdns_client_test.go b/nameserver/mdns_client_test.go index 1297b4c1..012bc107 100644 --- a/nameserver/mdns_client_test.go +++ b/nameserver/mdns_client_test.go @@ -13,9 +13,27 @@ var ( successTestName = "test1.weave." failTestName = "test2.weave." testAddr = net.ParseIP("9.8.7.6") + testInAddr = "6.7.8.9.in-addr.arpa." ) func minimalServer(w dns.ResponseWriter, req *dns.Msg) { + + sendAnswer := func(m *dns.Msg) { + buf, err := m.Pack() + checkFatal(err) + if buf == nil { + log.Fatal("Nil buffer") + } + //log.Println("minimalServer sending:", buf) + // This is a bit of a kludge - per the RFC we should send responses from 5353, but that doesn't seem to work + sendconn, err := net.DialUDP("udp", nil, ipv4Addr) + checkFatal(err) + + _, err = sendconn.Write(buf) + sendconn.Close() + checkFatal(err) + } + //log.Println("minimalServer received:", req) if len(req.Answer) > 0 { return // Only interested in questions. @@ -23,31 +41,24 @@ func minimalServer(w dns.ResponseWriter, req *dns.Msg) { if len(req.Question) != 1 { return // We only handle single-question messages } - if req.Question[0].Name != successTestName { - return // This is not the DNS record you are looking for + + if req.Question[0].Qtype == dns.TypeA && req.Question[0].Name == successTestName { + m := new(dns.Msg) + m.SetReply(req) + hdr := dns.RR_Header{Name: m.Question[0].Name, Rrtype: dns.TypeA, + Class: dns.ClassINET, Ttl: 3600} + a := &dns.A{hdr, testAddr} + m.Answer = append(m.Answer, a) + sendAnswer(m) + } else if req.Question[0].Qtype == dns.TypePTR && req.Question[0].Name == testInAddr { + m := new(dns.Msg) + m.SetReply(req) + hdr := dns.RR_Header{Name: m.Question[0].Name, Rrtype: dns.TypePTR, + Class: dns.ClassINET, Ttl: 3600} + a := &dns.PTR{hdr, successTestName} + m.Answer = append(m.Answer, a) + sendAnswer(m) } - - m := new(dns.Msg) - m.SetReply(req) - - hdr := dns.RR_Header{Name: m.Question[0].Name, Rrtype: dns.TypeA, - Class: dns.ClassINET, Ttl: 3600} - a := &dns.A{hdr, testAddr} - m.Answer = append(m.Answer, a) - - buf, err := m.Pack() - checkFatal(err) - if buf == nil { - log.Fatal("Nil buffer") - } - //log.Println("minimalServer sending:", buf) - // This is a bit of a kludge - per the RFC we should send responses from 5353, but that doesn't seem to work - sendconn, err := net.DialUDP("udp", nil, ipv4Addr) - checkFatal(err) - - _, err = sendconn.Write(buf) - sendconn.Close() - checkFatal(err) } func RunLocalMulticastServer() (*dns.Server, error) { @@ -76,14 +87,14 @@ func setup(t *testing.T) (*MDNSClient, *dns.Server, error) { type testContext struct { receivedAddr net.IP receivedCount int - channel chan *ResponseA + channel chan *Response } func newTestContext() *testContext { - return &testContext{channel: make(chan *ResponseA, 4)} + return &testContext{channel: make(chan *Response, 4)} } -func (c *testContext) checkResponse(t *testing.T, channelOk bool, resp *ResponseA) { +func (c *testContext) checkResponse(t *testing.T, channelOk bool, resp *Response) { if !channelOk { c.channel = nil return @@ -166,4 +177,10 @@ func TestAsLookup(t *testing.T) { ip, err = mdnsClient.LookupLocal("foo.example.com.") wt.AssertErrorType(t, err, (*LookupError)(nil), "unknown hostname") + + name, err := mdnsClient.ReverseLookupLocal(testInAddr) + wt.AssertNoErr(t, err) + if !(successTestName == name) { + t.Fatalf("Expected name %s, got %s", successTestName, name) + } } diff --git a/nameserver/mdns_lookup.go b/nameserver/mdns_lookup.go index 5fa9cdd4..dddc03b3 100644 --- a/nameserver/mdns_lookup.go +++ b/nameserver/mdns_lookup.go @@ -6,8 +6,8 @@ import ( "net" ) -func mdnsLookup(client *MDNSClient, name string, qtype uint16) (*ResponseA, error) { - channel := make(chan *ResponseA) +func mdnsLookup(client *MDNSClient, name string, qtype uint16) (*Response, error) { + channel := make(chan *Response) client.SendQuery(name, qtype, channel) for resp := range channel { Debug.Printf("Got response name %s addr %s", resp.Name, resp.Addr) From bd2844d1860e3cc07f6e30c7744ccfb35a2697d1 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Thu, 8 Jan 2015 17:15:16 +0000 Subject: [PATCH 6/8] Split http-startup code from server-startup --- nameserver/server.go | 4 +--- weavedns/main.go | 3 ++- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/nameserver/server.go b/nameserver/server.go index 723e3483..94a0c91a 100644 --- a/nameserver/server.go +++ b/nameserver/server.go @@ -98,9 +98,7 @@ func notUsHandler() dns.HandlerFunc { } } -func StartServer(zone Zone, iface *net.Interface, dnsPort int, httpPort int, wait int) error { - go ListenHttp(LOCAL_DOMAIN, zone, httpPort) - +func StartServer(zone Zone, iface *net.Interface, dnsPort int, wait int) error { mdnsClient, err := NewMDNSClient() checkFatal(err) diff --git a/weavedns/main.go b/weavedns/main.go index d7e62b46..d00a90bc 100644 --- a/weavedns/main.go +++ b/weavedns/main.go @@ -63,7 +63,8 @@ func main() { } } - err := weavedns.StartServer(zone, iface, dnsPort, httpPort, wait) + go weavedns.ListenHttp(weavedns.LOCAL_DOMAIN, zone, httpPort) + err := weavedns.StartServer(zone, iface, dnsPort, wait) if err != nil { Error.Fatal("Failed to start server", err) } From f24edf755fa32286e976a26c0765ab420ab24cdd Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Thu, 8 Jan 2015 17:16:56 +0000 Subject: [PATCH 7/8] Add tests for server.go --- common/testing_utils.go | 19 ++++++++++ nameserver/server_test.go | 78 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 nameserver/server_test.go diff --git a/common/testing_utils.go b/common/testing_utils.go index 669b6e9c..fc03ba9b 100644 --- a/common/testing_utils.go +++ b/common/testing_utils.go @@ -30,6 +30,18 @@ func AssertNoErr(t *testing.T, err error) { } } +func AssertEqualInt(t *testing.T, got, wanted int, desc string) { + if got != wanted { + t.Fatalf("%s: Expected %s %d but got %d", CallSite(), desc, wanted, got) + } +} + +func AssertEqualString(t *testing.T, got, wanted string, desc string) { + if got != wanted { + t.Fatalf("%s: Expected %s '%s' but got '%s'", CallSite(), desc, wanted, got) + } +} + func AssertStatus(t *testing.T, got int, wanted int, desc string) { if got != wanted { t.Fatalf("%s: Expected %s %d but got %d", CallSite(), desc, wanted, got) @@ -49,3 +61,10 @@ func AssertErrorType(t *testing.T, got interface{}, wanted interface{}, desc str t.Fatalf("%s: Expected %s but got %s (%s)", CallSite(), wantedT.String(), gotT.String(), desc) } } + +func AssertType(t *testing.T, got interface{}, wanted interface{}, desc string) { + gotT, wantedT := reflect.TypeOf(got), reflect.TypeOf(wanted) + if gotT != wantedT { + t.Fatalf("%s: Expected %s but got %s (%s)", CallSite(), wantedT.String(), gotT.String(), desc) + } +} diff --git a/nameserver/server_test.go b/nameserver/server_test.go new file mode 100644 index 00000000..a3703d90 --- /dev/null +++ b/nameserver/server_test.go @@ -0,0 +1,78 @@ +package nameserver + +import ( + "fmt" + "github.com/miekg/dns" + wt "github.com/zettio/weave/common" + "net" + "testing" + "time" +) + +func TestDNSServer(t *testing.T) { + const ( + port = 17625 + successTestName = "test1.weave.local." + failTestName = "test2.weave.local." + nonLocalName = "weave.works." + testAddr1 = "10.0.2.1" + testRDNSsuccess = "1.2.0.10.in-addr.arpa." + testRDNSfail = "4.3.2.1.in-addr.arpa." + ) + dnsAddr := fmt.Sprintf("localhost:%d", port) + testCIDR1 := testAddr1 + "/24" + + wt.InitDefaultLogging(true) + var zone = new(ZoneDb) + ip, _, _ := net.ParseCIDR(testCIDR1) + zone.AddRecord(containerID, successTestName, ip) + + go StartServer(zone, nil, port, 0) + time.Sleep(100 * time.Millisecond) // Allow sever goroutine to start + + c := new(dns.Client) + m := new(dns.Msg) + m.SetQuestion(successTestName, dns.TypeA) + m.RecursionDesired = true + r, _, err := c.Exchange(m, dnsAddr) + wt.AssertNoErr(t, err) + wt.AssertStatus(t, r.Rcode, dns.RcodeSuccess, "DNS response code") + wt.AssertEqualInt(t, len(r.Answer), 1, "Number of answers") + wt.AssertType(t, r.Answer[0], (*dns.A)(nil), "DNS record") + wt.AssertEqualString(t, r.Answer[0].(*dns.A).A.String(), testAddr1, "IP address") + + m.SetQuestion(failTestName, dns.TypeA) + r, _, err = c.Exchange(m, dnsAddr) + wt.AssertNoErr(t, err) + wt.AssertStatus(t, r.Rcode, dns.RcodeNameError, "DNS response code") + wt.AssertEqualInt(t, len(r.Answer), 0, "Number of answers") + + m.SetQuestion(testRDNSsuccess, dns.TypePTR) + r, _, err = c.Exchange(m, dnsAddr) + wt.AssertNoErr(t, err) + wt.AssertStatus(t, r.Rcode, dns.RcodeSuccess, "DNS response code") + wt.AssertEqualInt(t, len(r.Answer), 1, "Number of answers") + wt.AssertType(t, r.Answer[0], (*dns.PTR)(nil), "DNS record") + wt.AssertEqualString(t, r.Answer[0].(*dns.PTR).Ptr, successTestName, "IP address") + m.SetQuestion(testRDNSfail, dns.TypePTR) + r, _, err = c.Exchange(m, dnsAddr) + wt.AssertNoErr(t, err) + wt.AssertStatus(t, r.Rcode, dns.RcodeNameError, "DNS response code") + wt.AssertEqualInt(t, len(r.Answer), 0, "Number of answers") + + // This should fail because we don't handle MX records + m.SetQuestion(successTestName, dns.TypeMX) + r, _, err = c.Exchange(m, dnsAddr) + wt.AssertNoErr(t, err) + wt.AssertStatus(t, r.Rcode, dns.RcodeNameError, "DNS response code") + wt.AssertEqualInt(t, len(r.Answer), 0, "Number of answers") + + // This non-local query should fail because we don't handle MX records + m.SetQuestion(nonLocalName, dns.TypeMX) + r, _, err = c.Exchange(m, dnsAddr) + wt.AssertNoErr(t, err) + wt.AssertStatus(t, r.Rcode, dns.RcodeNameError, "DNS response code") + wt.AssertEqualInt(t, len(r.Answer), 0, "Number of answers") + + // Not testing MDNS functionality of server here (yet) +} From 15ebc2c9a74ec9133752ccfa2b6dd6cceb266feb Mon Sep 17 00:00:00 2001 From: Michael Bridgen Date: Fri, 9 Jan 2015 10:54:44 +0000 Subject: [PATCH 8/8] Factor the making of handlers out NB I removed free variables from the LookupFunc implementations, to avoid confusion between `s` the receiver and the closed-over `s`. Also corrected a mistake in the write error log message. --- nameserver/mdns_server.go | 80 ++++++++++++++++++++------------------- 1 file changed, 42 insertions(+), 38 deletions(-) diff --git a/nameserver/mdns_server.go b/nameserver/mdns_server.go index 2cca64de..cfab5721 100644 --- a/nameserver/mdns_server.go +++ b/nameserver/mdns_server.go @@ -50,46 +50,23 @@ func (s *MDNSServer) Start(ifi *net.Interface) error { return err } - handler := func(qtype uint16, lookup func(*dns.Msg, *dns.Question) *dns.Msg) dns.HandlerFunc { - return func(w dns.ResponseWriter, r *dns.Msg) { - // Handle only questions, ignore answers. We might also - // ignore questions that arise locally (i.e., that come - // from an IP we think is local), but in the interest of - // avoiding complication, and easier testing, this is - // elided on the assumption that the client wouldn't ask - // if it already knew the answer, and if it does ask, - // it'll be happy to get an answer. - if len(r.Answer) == 0 && len(r.Question) > 0 { - q := &r.Question[0] - if q.Qtype == qtype { - if m := lookup(r, q); m != nil { - Debug.Printf("Found local answer to mDNS query %s", q.Name) - if err = s.sendResponse(m); err != nil { - Warning.Printf("Error writing to %s", w) - } - } else { - Debug.Printf("No local answer for mDNS query %s", q.Name) - } - } + handleLocal := s.makeHandler(dns.TypeA, + func(zone Lookup, r *dns.Msg, q *dns.Question) *dns.Msg { + if ip, err := zone.LookupLocal(q.Name); err == nil { + return makeAddressReply(r, q, []net.IP{ip}) + } else { + return nil } - } - } + }) - handleLocal := handler(dns.TypeA, func(r *dns.Msg, q *dns.Question) *dns.Msg { - if ip, err := s.zone.LookupLocal(q.Name); err == nil { - return makeAddressReply(r, q, []net.IP{ip}) - } else { - return nil - } - }) - - handleReverse := handler(dns.TypePTR, func(r *dns.Msg, q *dns.Question) *dns.Msg { - if name, err := s.zone.ReverseLookupLocal(q.Name); err == nil { - return makePTRReply(r, q, []string{name}) - } else { - return nil - } - }) + handleReverse := s.makeHandler(dns.TypePTR, + func(zone Lookup, r *dns.Msg, q *dns.Question) *dns.Msg { + if name, err := zone.ReverseLookupLocal(q.Name); err == nil { + return makePTRReply(r, q, []string{name}) + } else { + return nil + } + }) mux := dns.NewServeMux() mux.HandleFunc(LOCAL_DOMAIN, handleLocal) @@ -99,6 +76,33 @@ func (s *MDNSServer) Start(ifi *net.Interface) error { return err } +type LookupFunc func(Lookup, *dns.Msg, *dns.Question) *dns.Msg + +func (s *MDNSServer) makeHandler(qtype uint16, lookup LookupFunc) dns.HandlerFunc { + return func(_ dns.ResponseWriter, r *dns.Msg) { + // Handle only questions, ignore answers. We might also ignore + // questions that arise locally (i.e., that come from an IP we + // think is local), but in the interest of avoiding + // complication, and easier testing, this is elided on the + // assumption that the client wouldn't ask if it already knew + // the answer, and if it does ask, it'll be happy to get an + // answer. + if len(r.Answer) == 0 && len(r.Question) > 0 { + q := &r.Question[0] + if q.Qtype == qtype { + if m := lookup(s.zone, r, q); m != nil { + Debug.Printf("Found local answer to mDNS query %s", q.Name) + if err := s.sendResponse(m); err != nil { + Warning.Printf("Error writing to %s", s.sendconn) + } + } else { + Debug.Printf("No local answer for mDNS query %s", q.Name) + } + } + } + } +} + func (s *MDNSServer) sendResponse(m *dns.Msg) error { buf, err := m.Pack() if err != nil {