-
Notifications
You must be signed in to change notification settings - Fork 32
/
browse.go
147 lines (128 loc) · 3.35 KB
/
browse.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
package dnssd
import (
"github.com/brutella/dnssd/log"
"github.com/miekg/dns"
"context"
"fmt"
"net"
)
// BrowseEntry represents a discovered service instance.
type BrowseEntry struct {
IPs []net.IP
Host string
Port int
IfaceName string
Name string
Type string
Domain string
Text map[string]string
}
// AddFunc is called when a service instance was found.
type AddFunc func(BrowseEntry)
// RmvFunc is called when a service instance disappared.
type RmvFunc func(BrowseEntry)
// LookupType browses for service instanced with a specified service type.
func LookupType(ctx context.Context, service string, add AddFunc, rmv RmvFunc) (err error) {
conn, err := newMDNSConn()
if err != nil {
return err
}
defer conn.close()
return lookupType(ctx, service, conn, add, rmv)
}
// ServiceInstanceName returns the service instance name
// in the form of <instance name>.<service>.<domain>.
// (Note the trailing dot.)
func (e BrowseEntry) EscapedServiceInstanceName() string {
return fmt.Sprintf("%s.%s.%s.", escape.Replace(e.Name), e.Type, e.Domain)
}
// ServiceInstanceName returns the same as `ServiceInstanceName()`
// but removes any escape characters.
func (e BrowseEntry) ServiceInstanceName() string {
return fmt.Sprintf("%s.%s.%s.", e.Name, e.Type, e.Domain)
}
func lookupType(ctx context.Context, service string, conn MDNSConn, add AddFunc, rmv RmvFunc) (err error) {
var cache = NewCache()
m := new(dns.Msg)
m.Question = []dns.Question{
dns.Question{
Name: service,
Qtype: dns.TypePTR,
Qclass: dns.ClassINET,
},
}
// TODO include known answers which current ttl is more than half of the correct ttl (see TFC6772 7.1: Known-Answer Supression)
// m.Answer = ...
// m.Authoritive = false // because our answers are *believes*
readCtx, readCancel := context.WithCancel(ctx)
defer readCancel()
ch := conn.Read(readCtx)
qs := make(chan *Query)
go func() {
for _, iface := range MulticastInterfaces() {
iface := iface
q := &Query{msg: m, iface: iface}
qs <- q
}
}()
es := []*BrowseEntry{}
for {
select {
case q := <-qs:
log.Debug.Printf("Send browsing query at %s\n%s\n", q.IfaceName(), q.msg)
if err := conn.SendQuery(q); err != nil {
log.Debug.Println("SendQuery:", err)
}
case req := <-ch:
log.Debug.Printf("Receive message at %s\n%s\n", req.IfaceName(), req.msg)
cache.UpdateFrom(req)
for _, srv := range cache.Services() {
if srv.ServiceName() != service {
continue
}
for ifaceName, ips := range srv.ifaceIPs {
var found = false
for _, e := range es {
if e.Name == srv.Name && e.IfaceName == ifaceName {
found = true
break
}
}
if !found {
e := BrowseEntry{
IPs: ips,
Host: srv.Host,
Port: srv.Port,
IfaceName: ifaceName,
Name: srv.Name,
Type: srv.Type,
Domain: srv.Domain,
Text: srv.Text,
}
es = append(es, &e)
add(e)
}
}
}
tmp := []*BrowseEntry{}
for _, e := range es {
var found = false
for _, srv := range cache.Services() {
if srv.ServiceInstanceName() == e.ServiceInstanceName() {
found = true
break
}
}
if found {
tmp = append(tmp, e)
} else {
// TODO
rmv(*e)
}
}
es = tmp
case <-ctx.Done():
return ctx.Err()
}
}
}