Refactor client.go.
- Add WantUnicastResponse to allow sending mDNS queries that desire unicast responses (see section 5.4 in RFC6762). - Listen for UDP packets sent to the mDNS multicast addresses ("ipv4: 224.0.0.251", ipv6: "ff02::fb") - Fix bug on line 177 of client.go: Ignored error during sendQuery. - Add TODO comments regarding unimplemented features of mDNS protocol. - Add error logging statement when DNS packet parsing failed.
This commit is contained in:
parent
f5542c2469
commit
6f752e8251
135
client.go
135
client.go
@ -1,25 +1,26 @@
|
|||||||
package mdns
|
package mdns
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"code.google.com/p/go.net/ipv4"
|
|
||||||
"code.google.com/p/go.net/ipv6"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/miekg/dns"
|
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"code.google.com/p/go.net/ipv4"
|
||||||
|
"code.google.com/p/go.net/ipv6"
|
||||||
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ServiceEntry is returned after we query for a service
|
// ServiceEntry is returned after we query for a service
|
||||||
type ServiceEntry struct {
|
type ServiceEntry struct {
|
||||||
Name string
|
Name string
|
||||||
Host string
|
Host string
|
||||||
AddrV4 net.IP
|
AddrV4 net.IP
|
||||||
AddrV6 net.IP
|
AddrV6 net.IP
|
||||||
Port int
|
Port int
|
||||||
Info string
|
Info string
|
||||||
|
|
||||||
Addr net.IP // @Deprecated
|
Addr net.IP // @Deprecated
|
||||||
|
|
||||||
@ -34,20 +35,22 @@ func (s *ServiceEntry) complete() bool {
|
|||||||
|
|
||||||
// QueryParam is used to customize how a Lookup is performed
|
// QueryParam is used to customize how a Lookup is performed
|
||||||
type QueryParam struct {
|
type QueryParam struct {
|
||||||
Service string // Service to lookup
|
Service string // Service to lookup
|
||||||
Domain string // Lookup domain, default "local"
|
Domain string // Lookup domain, default "local"
|
||||||
Timeout time.Duration // Lookup timeout, default 1 second
|
Timeout time.Duration // Lookup timeout, default 1 second
|
||||||
Interface *net.Interface // Multicast interface to use
|
Interface *net.Interface // Multicast interface to use
|
||||||
Entries chan<- *ServiceEntry // Entries Channel
|
Entries chan<- *ServiceEntry // Entries Channel
|
||||||
|
WantUnicastResponse bool // Unicast response desired, as per 5.4 in RFC
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultParams is used to return a default set of QueryParam's
|
// DefaultParams is used to return a default set of QueryParam's
|
||||||
func DefaultParams(service string) *QueryParam {
|
func DefaultParams(service string) *QueryParam {
|
||||||
return &QueryParam{
|
return &QueryParam{
|
||||||
Service: service,
|
Service: service,
|
||||||
Domain: "local",
|
Domain: "local",
|
||||||
Timeout: time.Second,
|
Timeout: time.Second,
|
||||||
Entries: make(chan *ServiceEntry),
|
Entries: make(chan *ServiceEntry),
|
||||||
|
WantUnicastResponse: false, // TODO(reddaly): Change this default.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,35 +95,54 @@ func Lookup(service string, entries chan<- *ServiceEntry) error {
|
|||||||
// Client provides a query interface that can be used to
|
// Client provides a query interface that can be used to
|
||||||
// search for service providers using mDNS
|
// search for service providers using mDNS
|
||||||
type client struct {
|
type client struct {
|
||||||
ipv4List *net.UDPConn
|
ipv4UnicastConn *net.UDPConn
|
||||||
ipv6List *net.UDPConn
|
ipv6UnicastConn *net.UDPConn
|
||||||
|
|
||||||
|
ipv4MulticastConn *net.UDPConn
|
||||||
|
ipv6MulticastConn *net.UDPConn
|
||||||
|
|
||||||
closed bool
|
closed bool
|
||||||
closedCh chan struct{}
|
closedCh chan struct{} // TODO(reddaly): This doesn't appear to be used.
|
||||||
closeLock sync.Mutex
|
closeLock sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewClient creates a new mdns Client that can be used to query
|
// NewClient creates a new mdns Client that can be used to query
|
||||||
// for records
|
// for records
|
||||||
func newClient() (*client, error) {
|
func newClient() (*client, error) {
|
||||||
|
// TODO(reddaly): At least attempt to bind to the port required in the spec.
|
||||||
// Create a IPv4 listener
|
// Create a IPv4 listener
|
||||||
ipv4, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IPv4zero, Port: 0})
|
uconn4, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IPv4zero, Port: 0})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[ERR] mdns: Failed to bind to udp4 port: %v", err)
|
log.Printf("[ERR] mdns: Failed to bind to udp4 port: %v", err)
|
||||||
}
|
}
|
||||||
ipv6, err := net.ListenUDP("udp6", &net.UDPAddr{IP: net.IPv6zero, Port: 0})
|
uconn6, err := net.ListenUDP("udp6", &net.UDPAddr{IP: net.IPv6zero, Port: 0})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[ERR] mdns: Failed to bind to udp6 port: %v", err)
|
log.Printf("[ERR] mdns: Failed to bind to udp6 port: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if ipv4 == nil && ipv6 == nil {
|
if uconn4 == nil && uconn6 == nil {
|
||||||
return nil, fmt.Errorf("Failed to bind to any udp port!")
|
return nil, fmt.Errorf("failed to bind to any unicast udp port")
|
||||||
|
}
|
||||||
|
|
||||||
|
mconn4, err := net.ListenMulticastUDP("udp4", nil, ipv4Addr)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[ERR] mdns: Failed to bind to udp4 port: %v", err)
|
||||||
|
}
|
||||||
|
mconn6, err := net.ListenMulticastUDP("udp6", nil, ipv6Addr)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[ERR] mdns: Failed to bind to udp6 port: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if mconn4 == nil && mconn6 == nil {
|
||||||
|
return nil, fmt.Errorf("failed to bind to any multicast udp port")
|
||||||
}
|
}
|
||||||
|
|
||||||
c := &client{
|
c := &client{
|
||||||
ipv4List: ipv4,
|
ipv4MulticastConn: mconn4,
|
||||||
ipv6List: ipv6,
|
ipv6MulticastConn: mconn6,
|
||||||
closedCh: make(chan struct{}),
|
ipv4UnicastConn: uconn4,
|
||||||
|
ipv6UnicastConn: uconn6,
|
||||||
|
closedCh: make(chan struct{}),
|
||||||
}
|
}
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
@ -134,25 +156,42 @@ func (c *client) Close() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
c.closed = true
|
c.closed = true
|
||||||
|
|
||||||
|
log.Printf("[INFO] mdns: Closing client %v", *c)
|
||||||
close(c.closedCh)
|
close(c.closedCh)
|
||||||
|
|
||||||
if c.ipv4List != nil {
|
if c.ipv4UnicastConn != nil {
|
||||||
c.ipv4List.Close()
|
c.ipv4UnicastConn.Close()
|
||||||
}
|
}
|
||||||
if c.ipv6List != nil {
|
if c.ipv6UnicastConn != nil {
|
||||||
c.ipv6List.Close()
|
c.ipv6UnicastConn.Close()
|
||||||
}
|
}
|
||||||
|
if c.ipv4MulticastConn != nil {
|
||||||
|
c.ipv4MulticastConn.Close()
|
||||||
|
}
|
||||||
|
if c.ipv6MulticastConn != nil {
|
||||||
|
c.ipv6MulticastConn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// setInterface is used to set the query interface, uses sytem
|
// setInterface is used to set the query interface, uses sytem
|
||||||
// default if not provided
|
// 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)
|
p := ipv4.NewPacketConn(c.ipv4UnicastConn)
|
||||||
if err := p.SetMulticastInterface(iface); err != nil {
|
if err := p.SetMulticastInterface(iface); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
p2 := ipv6.NewPacketConn(c.ipv6List)
|
p2 := ipv6.NewPacketConn(c.ipv6UnicastConn)
|
||||||
|
if err := p2.SetMulticastInterface(iface); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p = ipv4.NewPacketConn(c.ipv4MulticastConn)
|
||||||
|
if err := p.SetMulticastInterface(iface); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p2 = ipv6.NewPacketConn(c.ipv6MulticastConn)
|
||||||
if err := p2.SetMulticastInterface(iface); err != nil {
|
if err := p2.SetMulticastInterface(iface); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -166,15 +205,26 @@ func (c *client) query(params *QueryParam) error {
|
|||||||
|
|
||||||
// Start listening for response packets
|
// Start listening for response packets
|
||||||
msgCh := make(chan *dns.Msg, 32)
|
msgCh := make(chan *dns.Msg, 32)
|
||||||
go c.recv(c.ipv4List, msgCh)
|
go c.recv(c.ipv4UnicastConn, msgCh)
|
||||||
go c.recv(c.ipv6List, msgCh)
|
go c.recv(c.ipv6UnicastConn, msgCh)
|
||||||
|
go c.recv(c.ipv4MulticastConn, msgCh)
|
||||||
|
go c.recv(c.ipv6MulticastConn, msgCh)
|
||||||
|
|
||||||
// Send the query
|
// Send the query
|
||||||
m := new(dns.Msg)
|
m := new(dns.Msg)
|
||||||
m.SetQuestion(serviceAddr, dns.TypePTR)
|
m.SetQuestion(serviceAddr, dns.TypePTR)
|
||||||
|
// RFC 6762, section 18.12. Repurposing of Top Bit of qclass in Question
|
||||||
|
// Section
|
||||||
|
//
|
||||||
|
// In the Question Section of a Multicast DNS query, the top bit of the qclass
|
||||||
|
// field is used to indicate that unicast responses are preferred for this
|
||||||
|
// particular question. (See Section 5.4.)
|
||||||
|
if params.WantUnicastResponse {
|
||||||
|
m.Question[0].Qclass |= 1 << 15
|
||||||
|
}
|
||||||
m.RecursionDesired = false
|
m.RecursionDesired = false
|
||||||
if err := c.sendQuery(m); err != nil {
|
if err := c.sendQuery(m); err != nil {
|
||||||
return nil
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Map the in-progress responses
|
// Map the in-progress responses
|
||||||
@ -187,6 +237,7 @@ func (c *client) query(params *QueryParam) error {
|
|||||||
case resp := <-msgCh:
|
case resp := <-msgCh:
|
||||||
var inp *ServiceEntry
|
var inp *ServiceEntry
|
||||||
for _, answer := range resp.Answer {
|
for _, answer := range resp.Answer {
|
||||||
|
// TODO(reddaly): Check that response corresponds to serviceAddr?
|
||||||
switch rr := answer.(type) {
|
switch rr := answer.(type) {
|
||||||
case *dns.PTR:
|
case *dns.PTR:
|
||||||
// Create new entry for this
|
// Create new entry for this
|
||||||
@ -223,6 +274,10 @@ func (c *client) query(params *QueryParam) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if inp == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// Check if this entry is complete
|
// Check if this entry is complete
|
||||||
if inp.complete() {
|
if inp.complete() {
|
||||||
if inp.sent {
|
if inp.sent {
|
||||||
@ -246,7 +301,6 @@ func (c *client) query(params *QueryParam) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// sendQuery is used to multicast a query out
|
// sendQuery is used to multicast a query out
|
||||||
@ -255,11 +309,11 @@ func (c *client) sendQuery(q *dns.Msg) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if c.ipv4List != nil {
|
if c.ipv4UnicastConn != nil {
|
||||||
c.ipv4List.WriteTo(buf, ipv4Addr)
|
c.ipv4UnicastConn.WriteToUDP(buf, ipv4Addr)
|
||||||
}
|
}
|
||||||
if c.ipv6List != nil {
|
if c.ipv6UnicastConn != nil {
|
||||||
c.ipv6List.WriteTo(buf, ipv6Addr)
|
c.ipv6UnicastConn.WriteToUDP(buf, ipv6Addr)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -273,6 +327,7 @@ func (c *client) recv(l *net.UDPConn, msgCh chan *dns.Msg) {
|
|||||||
for !c.closed {
|
for !c.closed {
|
||||||
n, err := l.Read(buf)
|
n, err := l.Read(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Printf("[ERR] mdns: Failed to read packet: %v", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
msg := new(dns.Msg)
|
msg := new(dns.Msg)
|
||||||
|
Loading…
Reference in New Issue
Block a user