From adcfd31f0f056636bf0c3e34192b59001c830ab8 Mon Sep 17 00:00:00 2001 From: lsowen Date: Sun, 27 Jul 2014 21:27:58 -0400 Subject: [PATCH 1/9] listen on multicast and return all answers --- client.go | 24 ++++-------------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/client.go b/client.go index 358d138..58ea8b1 100644 --- a/client.go +++ b/client.go @@ -25,6 +25,7 @@ type ServiceEntry struct { // complete is used to check if we have all the info we need func (s *ServiceEntry) complete() bool { + fmt.Println(s) return s.Addr != nil && s.Port != 0 && s.hasTXT } @@ -100,11 +101,11 @@ type client struct { // for records func newClient() (*client, error) { // Create a IPv4 listener - ipv4, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IPv4zero, Port: 0}) + ipv4, err := net.ListenMulticastUDP("udp4", nil, ipv4Addr) if err != nil { log.Printf("[ERR] mdns: Failed to bind to udp4 port: %v", err) } - ipv6, err := net.ListenUDP("udp6", &net.UDPAddr{IP: net.IPv6zero, Port: 0}) + ipv6, err := net.ListenMulticastUDP("udp6", nil, ipv6Addr) if err != nil { log.Printf("[ERR] mdns: Failed to bind to udp6 port: %v", err) } @@ -214,25 +215,8 @@ func (c *client) query(params *QueryParam) error { inp = ensureName(inprogress, rr.Hdr.Name) inp.Addr = rr.AAAA } - } - // Check if this entry is complete - if inp.complete() { - if inp.sent { - continue - } - inp.sent = true - select { - case params.Entries <- inp: - default: - } - } else { - // Fire off a node specific query - m := new(dns.Msg) - m.SetQuestion(inp.Name, dns.TypeANY) - if err := c.sendQuery(m); err != nil { - log.Printf("[ERR] mdns: Failed to query instance %s: %v", inp.Name, err) - } + params.Entries <- inp } case <-finish: return nil From 12ffc4ecedae2f4f23f8642bbf70a6f1222fefc6 Mon Sep 17 00:00:00 2001 From: lsowen Date: Mon, 28 Jul 2014 18:52:19 -0400 Subject: [PATCH 2/9] return raw DNS results --- client.go | 53 ++++++++++------------------------------------------- 1 file changed, 10 insertions(+), 43 deletions(-) diff --git a/client.go b/client.go index 58ea8b1..f48d4a9 100644 --- a/client.go +++ b/client.go @@ -25,7 +25,6 @@ type ServiceEntry struct { // complete is used to check if we have all the info we need func (s *ServiceEntry) complete() bool { - fmt.Println(s) return s.Addr != nil && s.Port != 0 && s.hasTXT } @@ -35,7 +34,8 @@ type QueryParam struct { Domain string // Lookup domain, default "local" Timeout time.Duration // Lookup timeout, default 1 second Interface *net.Interface // Multicast interface to use - Entries chan<- *ServiceEntry // Entries Channel + QueryType uint16 // dns Type Constant to use + Entries chan<- dns.RR // Entries Channel } // DefaultParams is used to return a default set of QueryParam's @@ -43,8 +43,9 @@ func DefaultParams(service string) *QueryParam { return &QueryParam{ Service: service, Domain: "local", + QueryType: dns.TypeANY, Timeout: time.Second, - Entries: make(chan *ServiceEntry), + Entries: make(chan dns.RR), } } @@ -80,7 +81,7 @@ func Query(params *QueryParam) error { } // Lookup is the same as Query, however it uses all the default parameters -func Lookup(service string, entries chan<- *ServiceEntry) error { +func Lookup(service string, entries chan<- dns.RR) error { params := DefaultParams(service) params.Entries = entries return Query(params) @@ -142,7 +143,7 @@ func (c *client) Close() error { return nil } -// setInterface is used to set the query interface, uses sytem +// setInterface is used to set the query interface, uses system // default if not provided func (c *client) setInterface(iface *net.Interface) error { p := ipv4.NewPacketConn(c.ipv4List) @@ -160,6 +161,7 @@ func (c *client) setInterface(iface *net.Interface) error { func (c *client) query(params *QueryParam) error { // Create the service name serviceAddr := fmt.Sprintf("%s.%s.", trimDot(params.Service), trimDot(params.Domain)) + serviceAddr = strings.Replace(serviceAddr, " ", "\\ ", -1) // Start listening for response packets msgCh := make(chan *dns.Msg, 32) @@ -168,55 +170,20 @@ func (c *client) query(params *QueryParam) error { // Send the query m := new(dns.Msg) - m.SetQuestion(serviceAddr, dns.TypeANY) + m.SetQuestion(serviceAddr, params.QueryType) if err := c.sendQuery(m); err != nil { return nil } - // Map the in-progress responses - inprogress := make(map[string]*ServiceEntry) - // Listen until we reach the timeout finish := time.After(params.Timeout) for { select { case resp := <-msgCh: - var inp *ServiceEntry for _, answer := range resp.Answer { - switch rr := answer.(type) { - case *dns.PTR: - // Create new entry for this - inp = ensureName(inprogress, rr.Ptr) - - case *dns.SRV: - // Check for a target mismatch - if rr.Target != rr.Hdr.Name { - alias(inprogress, rr.Hdr.Name, rr.Target) - } - - // Get the port - inp = ensureName(inprogress, rr.Hdr.Name) - inp.Name = rr.Target - inp.Port = int(rr.Port) - - case *dns.TXT: - // Pull out the txt - inp = ensureName(inprogress, rr.Hdr.Name) - inp.Info = strings.Join(rr.Txt, "|") - inp.hasTXT = true - - case *dns.A: - // Pull out the IP - inp = ensureName(inprogress, rr.Hdr.Name) - inp.Addr = rr.A - - case *dns.AAAA: - // Pull out the IP - inp = ensureName(inprogress, rr.Hdr.Name) - inp.Addr = rr.AAAA + if (answer.Header().Name == serviceAddr) && (params.QueryType == dns.TypeANY || answer.Header().Rrtype == params.QueryType) { + params.Entries <- answer } - - params.Entries <- inp } case <-finish: return nil From 23a567518c1d6b3292cca1c41a387992a584e7ff Mon Sep 17 00:00:00 2001 From: lsowen Date: Thu, 11 Sep 2014 15:12:15 -0400 Subject: [PATCH 3/9] Client now operates more like a server, caching all responses MDNS responders will often send extra records before they are requested (eg SRV and TXT records) and not respond to explicit requests for the same record until the 1 second cooldown period. To handle this, cache all records and replay the cache when to new queries. --- client.go | 206 +++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 148 insertions(+), 58 deletions(-) diff --git a/client.go b/client.go index f48d4a9..20e1812 100644 --- a/client.go +++ b/client.go @@ -7,55 +7,71 @@ import ( "github.com/miekg/dns" "log" "net" - "strings" "sync" "time" + //"strings" + "bytes" ) -// ServiceEntry is returned after we query for a service -type ServiceEntry struct { - Name string - Addr net.IP - Port int - Info string - - hasTXT bool - sent bool -} - -// complete is used to check if we have all the info we need -func (s *ServiceEntry) complete() bool { - return s.Addr != nil && s.Port != 0 && s.hasTXT -} - // QueryParam is used to customize how a Lookup is performed type QueryParam struct { - Service string // Service to lookup - Domain string // Lookup domain, default "local" + RecordName string // RecordName to lookup Timeout time.Duration // Lookup timeout, default 1 second Interface *net.Interface // Multicast interface to use QueryType uint16 // dns Type Constant to use Entries chan<- dns.RR // Entries Channel } +type operationType string +const ( + SUBSCRIBE operationType = "SUBSCRIBE" + UNSUBSCRIBE operationType = "UNSUBSCRIBE" + CLOSE operationType = "CLOSE" +) + +type subscriptionMessage struct { + Operation operationType + Channel chan<- dns.RR +} + // DefaultParams is used to return a default set of QueryParam's -func DefaultParams(service string) *QueryParam { +func DefaultParams(recordName string) *QueryParam { return &QueryParam{ - Service: service, - Domain: "local", + RecordName: recordName, QueryType: dns.TypeANY, Timeout: time.Second, Entries: make(chan dns.RR), } } -// Query looks up a given service, in a domain, waiting at most +func EscapeName(name string) string { + var outputBuffer bytes.Buffer + + previousIsSlash := false + for _, c := range name { + if c == ' ' && !previousIsSlash { + outputBuffer.WriteRune('\\') + } + + outputBuffer.WriteRune(c) + + if c == '\\' { + previousIsSlash = true + } else { + previousIsSlash = false + } + } + + return outputBuffer.String() +} + +// Query looks up a given recordName, in a domain, waiting at most // for a timeout before finishing the query. The results are streamed // to a channel. Sends will not block, so clients should make sure to // either read or buffer. func Query(params *QueryParam) error { // Create a new client - client, err := newClient() + client, err := NewClient() if err != nil { return err } @@ -63,32 +79,28 @@ func Query(params *QueryParam) error { // Set the multicast interface if params.Interface != nil { - if err := client.setInterface(params.Interface); err != nil { + if err := client.SetInterface(params.Interface); err != nil { return err } } - // Ensure defaults are set - if params.Domain == "" { - params.Domain = "local" - } if params.Timeout == 0 { params.Timeout = time.Second } // Run the query - return client.query(params) + return client.Query(params) } // Lookup is the same as Query, however it uses all the default parameters -func Lookup(service string, entries chan<- dns.RR) error { - params := DefaultParams(service) +func Lookup(recordName string, entries chan<- dns.RR) error { + params := DefaultParams(recordName) params.Entries = entries return Query(params) } // Client provides a query interface that can be used to -// search for service providers using mDNS +// search for recordName providers using mDNS type client struct { ipv4List *net.UDPConn ipv6List *net.UDPConn @@ -96,11 +108,16 @@ type client struct { closed bool closedCh chan struct{} closeLock sync.Mutex + + entryCache []dns.RR + msgChan chan *dns.Msg + subscriptionChannel chan subscriptionMessage + //subscriberChans []chan dns.RR } // NewClient creates a new mdns Client that can be used to query // for records -func newClient() (*client, error) { +func NewClient() (*client, error) { // Create a IPv4 listener ipv4, err := net.ListenMulticastUDP("udp4", nil, ipv4Addr) if err != nil { @@ -119,10 +136,67 @@ func newClient() (*client, error) { ipv4List: ipv4, ipv6List: ipv6, closedCh: make(chan struct{}), + entryCache: make([]dns.RR, 0), + msgChan: make(chan *dns.Msg, 32), + subscriptionChannel: make(chan subscriptionMessage, 32), + //subscriberChans: make([]chan dns.RR, 0), } + go c.cacheAll() + go c.broadcastAll() return c, nil } +func (c *client) cacheAll() { + channel := c.Subscribe() + for answer := range channel { + // TODO: suppress duplicates and expire cache entries + c.entryCache = append(c.entryCache, answer) + //fmt.Println("Cache Add: " + answer.String()) + } +} + +func (c *client) broadcastAll() { + go c.recv(c.ipv4List, c.msgChan) + go c.recv(c.ipv6List, c.msgChan) + + subscriberChans := make([]chan<- dns.RR, 0) + + for { + select { + case msg := <- c.msgChan: + for _, answer := range msg.Answer { + for _, channel := range subscriberChans { + channel <- answer + } + } + for _, answer := range msg.Extra { + for _, channel := range subscriberChans { + channel <- answer + } + } + + case msg := <- c.subscriptionChannel: + switch msg.Operation { + case SUBSCRIBE: + //fmt.Println("Subscribe") + subscriberChans = append(subscriberChans, msg.Channel) + break + case UNSUBSCRIBE: + //fmt.Println("Unsubscribe") + for idx, channel := range subscriberChans { + if channel == msg.Channel { + subscriberChans = append(subscriberChans[:idx],subscriberChans[idx + 1:]...) + break + } + } + break + case CLOSE: + return + } + } + } +} + // Close is used to cleanup the client func (c *client) Close() error { c.closeLock.Lock() @@ -145,7 +219,7 @@ func (c *client) Close() error { // setInterface is used to set the query interface, uses system // default if not provided -func (c *client) setInterface(iface *net.Interface) error { +func (c *client) SetInterface(iface *net.Interface) error { p := ipv4.NewPacketConn(c.ipv4List) if err := p.SetMulticastInterface(iface); err != nil { return err @@ -157,39 +231,55 @@ func (c *client) setInterface(iface *net.Interface) error { return nil } +func (c *client) Subscribe() chan dns.RR { + channel := make(chan dns.RR) + c.subscriptionChannel <- subscriptionMessage{ + Operation: SUBSCRIBE, + Channel: channel, + } + return channel +} + +func (c *client) Unsubscribe(channel chan dns.RR) { + c.subscriptionChannel <- subscriptionMessage{ + Operation: UNSUBSCRIBE, + Channel: channel, + } +} + // query is used to perform a lookup and stream results -func (c *client) query(params *QueryParam) error { - // Create the service name - serviceAddr := fmt.Sprintf("%s.%s.", trimDot(params.Service), trimDot(params.Domain)) - serviceAddr = strings.Replace(serviceAddr, " ", "\\ ", -1) +func (c *client) Query(params *QueryParam) error { + // Create the recordName name + recordName := EscapeName(params.RecordName) + answerChan := c.Subscribe() - // Start listening for response packets - msgCh := make(chan *dns.Msg, 32) - go c.recv(c.ipv4List, msgCh) - go c.recv(c.ipv6List, msgCh) + go func() { + for answer := range answerChan { + if (answer.Header().Name == recordName) && (params.QueryType == dns.TypeANY || answer.Header().Rrtype == params.QueryType) { + params.Entries <- answer + } + } + }() + + for _, answer := range c.entryCache { + // Replay all entries in the cache + answerChan <- answer + //fmt.Println("Cache: " + answer.Header().Name) + } // Send the query m := new(dns.Msg) - m.SetQuestion(serviceAddr, params.QueryType) + m.SetQuestion(recordName, params.QueryType) if err := c.sendQuery(m); err != nil { return nil } - // Listen until we reach the timeout - finish := time.After(params.Timeout) - for { - select { - case resp := <-msgCh: - for _, answer := range resp.Answer { - if (answer.Header().Name == serviceAddr) && (params.QueryType == dns.TypeANY || answer.Header().Rrtype == params.QueryType) { - params.Entries <- answer - } - } - case <-finish: - return nil - } + select { + case <- time.After(params.Timeout): + c.Unsubscribe(answerChan) + close(answerChan) + return nil } - return nil } // sendQuery is used to multicast a query out From 72befb0a3a3bcbf23f3bb5a569c2655956cbb626 Mon Sep 17 00:00:00 2001 From: lsowen Date: Thu, 11 Sep 2014 15:51:32 -0400 Subject: [PATCH 4/9] change where the channel is closed to prevent race --- client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client.go b/client.go index 20e1812..343b9f9 100644 --- a/client.go +++ b/client.go @@ -186,6 +186,7 @@ func (c *client) broadcastAll() { for idx, channel := range subscriberChans { if channel == msg.Channel { subscriberChans = append(subscriberChans[:idx],subscriberChans[idx + 1:]...) + close(channel) break } } @@ -277,7 +278,6 @@ func (c *client) Query(params *QueryParam) error { select { case <- time.After(params.Timeout): c.Unsubscribe(answerChan) - close(answerChan) return nil } } From 47b3b38bb8d35e478af4afb4fe839ff37066abd7 Mon Sep 17 00:00:00 2001 From: lsowen Date: Thu, 11 Sep 2014 16:16:39 -0400 Subject: [PATCH 5/9] move cache replay into 'main loop' to stop cache race --- client.go | 25 ++++++------------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/client.go b/client.go index 343b9f9..5271df5 100644 --- a/client.go +++ b/client.go @@ -109,7 +109,6 @@ type client struct { closedCh chan struct{} closeLock sync.Mutex - entryCache []dns.RR msgChan chan *dns.Msg subscriptionChannel chan subscriptionMessage //subscriberChans []chan dns.RR @@ -136,40 +135,31 @@ func NewClient() (*client, error) { ipv4List: ipv4, ipv6List: ipv6, closedCh: make(chan struct{}), - entryCache: make([]dns.RR, 0), msgChan: make(chan *dns.Msg, 32), subscriptionChannel: make(chan subscriptionMessage, 32), - //subscriberChans: make([]chan dns.RR, 0), } - go c.cacheAll() go c.broadcastAll() return c, nil } -func (c *client) cacheAll() { - channel := c.Subscribe() - for answer := range channel { - // TODO: suppress duplicates and expire cache entries - c.entryCache = append(c.entryCache, answer) - //fmt.Println("Cache Add: " + answer.String()) - } -} - func (c *client) broadcastAll() { go c.recv(c.ipv4List, c.msgChan) go c.recv(c.ipv6List, c.msgChan) + entryCache := make([]dns.RR, 0) subscriberChans := make([]chan<- dns.RR, 0) for { select { case msg := <- c.msgChan: for _, answer := range msg.Answer { + entryCache = append(entryCache, answer) for _, channel := range subscriberChans { channel <- answer } } for _, answer := range msg.Extra { + entryCache = append(entryCache, answer) for _, channel := range subscriberChans { channel <- answer } @@ -180,6 +170,9 @@ func (c *client) broadcastAll() { case SUBSCRIBE: //fmt.Println("Subscribe") subscriberChans = append(subscriberChans, msg.Channel) + for _, entry := range entryCache { + msg.Channel <- entry + } break case UNSUBSCRIBE: //fmt.Println("Unsubscribe") @@ -262,12 +255,6 @@ func (c *client) Query(params *QueryParam) error { } }() - for _, answer := range c.entryCache { - // Replay all entries in the cache - answerChan <- answer - //fmt.Println("Cache: " + answer.Header().Name) - } - // Send the query m := new(dns.Msg) m.SetQuestion(recordName, params.QueryType) From 37dfde29f3c6369fe6c8cea386c8e2a151e4e241 Mon Sep 17 00:00:00 2001 From: lsowen Date: Thu, 11 Sep 2014 16:17:42 -0400 Subject: [PATCH 6/9] begin work on dnssd implementation --- dnssd_client.go | 94 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 dnssd_client.go diff --git a/dnssd_client.go b/dnssd_client.go new file mode 100644 index 0000000..4a78c53 --- /dev/null +++ b/dnssd_client.go @@ -0,0 +1,94 @@ +package mdns + +import ( + "net" + "github.com/miekg/dns" + "fmt" + //"time" +) + +// ServiceEntry is returned after we query for a service +type ServiceEntry struct { + Name string + Addr net.IP + Port uint16 + Info string + + hasTXT bool + sent bool +} + +func findTXT(client *client, params *QueryParam, entries chan<- ServiceEntry, service ServiceEntry) { + //fmt.Println("TXT") + entries <- service +} + +func findSRV(client *client, params *QueryParam, entries chan<- ServiceEntry, service ServiceEntry) { + seenList := map[string]bool{} + resultChannel := make(chan dns.RR) + params.Entries = resultChannel + + go func() { + for result := range resultChannel { + if result, ok := result.(*dns.SRV); ok { + if !seenList[result.Target] { + seenList[result.Target] = true + //fmt.Println("SRV: " + result.Target) + newService := ServiceEntry{ + Name: service.Name, + Port: result.Port, + } + newParams := DefaultParams(service.Name) + newParams.QueryType = dns.TypeTXT + findTXT(client, newParams, entries, newService) + } + } + } + }() + + go client.Query(params) +} + + + + +func findPTR(client *client, params *QueryParam, entries chan<- ServiceEntry) { + seenList := map[string]bool{} + resultChannel := make(chan dns.RR) + params.Entries = resultChannel + + go func() { + for result := range resultChannel { + if result, ok := result.(*dns.PTR); ok { + if !seenList[result.Ptr] { + seenList[result.Ptr] = true + service := ServiceEntry{ + Name: result.Ptr, + } + //fmt.Println("PTR: " + result.Ptr) + params := DefaultParams(result.Ptr) + params.QueryType = dns.TypeSRV + findSRV(client, params, entries, service) + } + } + } + }() +} + +func ResolveService(service string, entries chan<- ServiceEntry) error { + params := DefaultParams(service) + params.QueryType = dns.TypePTR + + client, err := NewClient() + if err != nil { + fmt.Println(err) + return err + } + + findPTR(client, params, entries) + //processResultsSRV(client, resultCh, entries) + + + client.Query(params) + return nil +} From 1b738bfa7b91931c8a3129bfbb613b027047fc49 Mon Sep 17 00:00:00 2001 From: lsowen Date: Mon, 15 Sep 2014 12:01:10 -0400 Subject: [PATCH 7/9] remove unused functions --- client.go | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/client.go b/client.go index 5271df5..f887291 100644 --- a/client.go +++ b/client.go @@ -307,21 +307,3 @@ func (c *client) recv(l *net.UDPConn, msgCh chan *dns.Msg) { } } } - -// ensureName is used to ensure the named node is in progress -func ensureName(inprogress map[string]*ServiceEntry, name string) *ServiceEntry { - if inp, ok := inprogress[name]; ok { - return inp - } - inp := &ServiceEntry{ - Name: name, - } - inprogress[name] = inp - return inp -} - -// alias is used to setup an alias between two entries -func alias(inprogress map[string]*ServiceEntry, src, dst string) { - srcEntry := ensureName(inprogress, src) - inprogress[dst] = srcEntry -} From ba498fa068d1f20d982435f64b4d7b7e8b03a499 Mon Sep 17 00:00:00 2001 From: lsowen Date: Mon, 15 Sep 2014 12:01:33 -0400 Subject: [PATCH 8/9] full DNS-SD resolver support --- dnssd_client.go | 169 ++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 151 insertions(+), 18 deletions(-) diff --git a/dnssd_client.go b/dnssd_client.go index 4a78c53..faf369e 100644 --- a/dnssd_client.go +++ b/dnssd_client.go @@ -4,28 +4,161 @@ import ( "net" "github.com/miekg/dns" "fmt" - //"time" + "strings" ) // ServiceEntry is returned after we query for a service type ServiceEntry struct { - Name string + ServiceName string + ServiceInstanceName string + ServiceHost string + Priority uint16 + Weight uint16 Addr net.IP Port uint16 - Info string + PropertyList map[string]interface{} +} + +func findIP(client *client, entries chan<- ServiceEntry, service ServiceEntry) { + seenList := map[string]bool{} + resultChannel := make(chan dns.RR) + + go func() { + for result := range resultChannel { + if result, ok := result.(*dns.A); ok { + if !seenList[string(result.A)] { + seenList[string(result.A)] = true + newService := ServiceEntry{ + ServiceName: service.ServiceName, + ServiceInstanceName: service.ServiceInstanceName, + ServiceHost: service.ServiceHost, + Port: service.Port, + Priority: service.Priority, + Weight: service.Weight, + PropertyList: service.PropertyList, + Addr: result.A, + } + + entries <- newService + } + } + + if result, ok := result.(*dns.AAAA); ok { + if !seenList[string(result.AAAA)] { + seenList[string(result.AAAA)] = true + newService := ServiceEntry{ + ServiceName: service.ServiceName, + ServiceInstanceName: service.ServiceInstanceName, + ServiceHost: service.ServiceHost, + Port: service.Port, + Priority: service.Priority, + Weight: service.Weight, + PropertyList: service.PropertyList, + Addr: result.AAAA, + } + + entries <- newService + } + } + } + }() + + params := DefaultParams(service.ServiceHost) + params.QueryType = dns.TypeA + params.Entries = resultChannel + go client.Query(params) + + params = DefaultParams(service.ServiceHost) + params.QueryType = dns.TypeAAAA + params.Entries = resultChannel + go client.Query(params) + +} + + +func parseTXT(items []string) map[string]interface{} { + propertyList := map[string]interface{}{} - hasTXT bool - sent bool + for _, item := range items { + if len(item) == 0 { + continue + } + + if item[0] == '=' { + // key cannot start with a '=' + continue + } + + var key string + var value interface{} + for idx, c := range item { + // Find first instance of '=', everything after is value + if c == '=' { + // Keys are case insensitive + key = strings.ToUpper(string(item[:idx])) + value = item[idx + 1:] + break + } else if idx + 1 == len(item) { + // If item does not have a '=', interpret as a bool + key = strings.ToUpper(item) + value = true + fmt.Println(key) + break + } + } + + if propertyList[key] == nil { + // Only the first instance of a key is respected + propertyList[key] = value + } + } + + return propertyList } -func findTXT(client *client, params *QueryParam, entries chan<- ServiceEntry, service ServiceEntry) { - //fmt.Println("TXT") - entries <- service +func findTXT(client *client, entries chan<- ServiceEntry, service ServiceEntry) { + // DNS-SD is required to have a TXT record + + seenList := map[string]bool{} + resultChannel := make(chan dns.RR) + + params := DefaultParams(service.ServiceInstanceName) + params.QueryType = dns.TypeTXT + params.Entries = resultChannel + + go func() { + for result := range resultChannel { + if result, ok := result.(*dns.TXT); ok { + if !seenList[result.Hdr.Name] { + seenList[result.Hdr.Name] = true + newService := ServiceEntry{ + ServiceName: service.ServiceName, + ServiceInstanceName: service.ServiceInstanceName, + ServiceHost: service.ServiceHost, + Port: service.Port, + Priority: service.Priority, + Weight: service.Weight, + PropertyList: parseTXT(result.Txt), + } + + + //entries <- newService + findIP(client, entries, newService) + } + } + } + }() + + go client.Query(params) + } -func findSRV(client *client, params *QueryParam, entries chan<- ServiceEntry, service ServiceEntry) { +func findSRV(client *client, entries chan<- ServiceEntry, service ServiceEntry) { seenList := map[string]bool{} resultChannel := make(chan dns.RR) + + params := DefaultParams(service.ServiceInstanceName) + params.QueryType = dns.TypeSRV params.Entries = resultChannel go func() { @@ -33,14 +166,15 @@ func findSRV(client *client, params *QueryParam, entries chan<- ServiceEntry, se if result, ok := result.(*dns.SRV); ok { if !seenList[result.Target] { seenList[result.Target] = true - //fmt.Println("SRV: " + result.Target) newService := ServiceEntry{ - Name: service.Name, + ServiceName: service.ServiceName, + ServiceInstanceName: service.ServiceInstanceName, + ServiceHost: result.Target, Port: result.Port, + Priority: result.Priority, + Weight: result.Weight, } - newParams := DefaultParams(service.Name) - newParams.QueryType = dns.TypeTXT - findTXT(client, newParams, entries, newService) + findTXT(client, entries, newService) } } } @@ -63,12 +197,11 @@ func findPTR(client *client, params *QueryParam, entries chan<- ServiceEntry) { if !seenList[result.Ptr] { seenList[result.Ptr] = true service := ServiceEntry{ - Name: result.Ptr, + ServiceName: params.RecordName, + ServiceInstanceName: result.Ptr, } //fmt.Println("PTR: " + result.Ptr) - params := DefaultParams(result.Ptr) - params.QueryType = dns.TypeSRV - findSRV(client, params, entries, service) + findSRV(client, entries, service) } } } From 7fc8d39a24da127a35b0f59c9e0643ce01d72bf5 Mon Sep 17 00:00:00 2001 From: lsowen Date: Mon, 15 Sep 2014 12:33:13 -0400 Subject: [PATCH 9/9] update README for new client usage --- README.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index bd902f5..db26365 100644 --- a/README.md +++ b/README.md @@ -32,12 +32,11 @@ Doing a lookup for service providers is also very simple: // Make a channel for results and start listening entriesCh := make(chan *mdns.ServiceEntry, 4) go func() { - for entry := range entriesCh { - fmt.Printf("Got new entry: %v\n", entry) - } + for entry := range entriesCh { + fmt.Printf("Got new entry: %v\n", entry) + } }() // Start the lookup - mdns.Lookup("_foobar._tcp", entriesCh) + mdns.ResolveService("_foobar._tcp.local.", entriesCh) close(entriesCh) -