-
Notifications
You must be signed in to change notification settings - Fork 34
/
browse.go
158 lines (137 loc) · 3.71 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
148
149
150
151
152
153
154
155
156
157
158
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 instances.
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)
}
// LookupTypeAtInterface browses for service instances at specific network interfaces.
func LookupTypeAtInterfaces(ctx context.Context, service string, add AddFunc, rmv RmvFunc, ifaces ...string) (err error) {
conn, err := newMDNSConn(ifaces...)
if err != nil {
return err
}
defer conn.close()
return lookupType(ctx, service, conn, add, rmv, ifaces...)
}
// 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, ifaces ...string) (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(ifaces...) {
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()
}
}
}