-
Notifications
You must be signed in to change notification settings - Fork 0
/
domainbrowser.go
238 lines (210 loc) · 7.1 KB
/
domainbrowser.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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
// CGo binding for Avahi
//
// Copyright (C) 2024 and up by Alexander Pevzner ([email protected])
// See LICENSE for license terms and conditions
//
// Service browser
//
//go:build linux || freebsd
package avahi
import (
"context"
"runtime/cgo"
"sync/atomic"
"unsafe"
)
// #include <stdlib.h>
// #include <avahi-client/lookup.h>
//
// void domainBrowserCallback (
// AvahiDomainBrowser *b,
// AvahiIfIndex interface,
// AvahiProtocol proto,
// AvahiBrowserEvent event,
// char *name,
// char *type,
// char *domain,
// AvahiLookupResultFlags flags,
// void *userdata);
import "C"
// DomainBrowser performs discovery of browsing and registration
// domains. See [NewDomainBrowser] and [RFC6763, 11] for details.
//
// [RFC6763, 11]: https://datatracker.ietf.org/doc/html/rfc6763#section-11
type DomainBrowser struct {
clnt *Client // Owning Client
handle cgo.Handle // Handle to self
avahiBrowser *C.AvahiDomainBrowser // Underlying object
queue eventqueue[*DomainBrowserEvent] // Event queue
closed atomic.Bool // Browser is closed
}
// DomainBrowserType specifies a type of domain to browse for.
type DomainBrowserType int
// DomainBrowserType values:
const (
// Request list of available browsing domains.
DomainBrowserBrowse DomainBrowserType = C.AVAHI_DOMAIN_BROWSER_BROWSE
// Request the default browsing domain.
DomainBrowserBrowseDefault DomainBrowserType = C.AVAHI_DOMAIN_BROWSER_BROWSE_DEFAULT
// Request list of available registering domains.
DomainBrowserRegister DomainBrowserType = C.AVAHI_DOMAIN_BROWSER_REGISTER
// Request the default registering domains.
DomainBrowserRegisterDefault DomainBrowserType = C.AVAHI_DOMAIN_BROWSER_REGISTER_DEFAULT
// Request for "legacy browsing" domains. See RFC6763, 11 for details.
DomainBrowserLegacy DomainBrowserType = C.AVAHI_DOMAIN_BROWSER_BROWSE_LEGACY
)
// DomainBrowserEvent represents events, generated by the
// [DomainBrowser].
type DomainBrowserEvent struct {
Event BrowserEvent // Event code
IfIdx IfIndex // Network interface index
Proto Protocol // Network protocol
Err ErrCode // In a case of BrowserFailure
Flags LookupResultFlags // Lookup flags
Domain string // Domain name
}
// NewDomainBrowser creates a new [DomainBrowser].
//
// DomainBrowser constantly monitors the network for the available
// browsing/registration domains reports discovered information as
// a series of [DomainBrowserEvent] events via channel returned by the
// [DomainBrowser.Chan]
//
// Avahi documentation doesn't give a lot of explanation about purpose
// of this functionality, but [RFC6763, 11] gives some technical for details.
// In short, DomainBrowser performs DNS PTR queries in the following
// special domains:
//
// DomainBrowserBrowse: b._dns-sd._udp.<domain>.
// DomainBrowserBrowseDefault: db._dns-sd._udp.<domain>.
// DomainBrowserRegister: r._dns-sd._udp.<domain>.
// DomainBrowserRegisterDefault: dr._dns-sd._udp.<domain>.
// DomainBrowserLegacy: lb._dns-sd._udp.<domain>.
//
// According to RFC6763, the <domain> is usually "local", (meaning
// "perform the query using link-local multicast") or it may be learned
// through some other mechanism, such as the DHCP "Domain" option
// (option code 15) [RFC2132].
//
// So network administrator can configure some MDNS responder located
// in the local network to provide this information for applications.
//
// In fact, this mechanism seems to be rarely used in practice and
// provided here just for consistency.
//
// Function parameters:
// - clnt is the pointer to [Client]
// - ifidx is the network interface index. Use [IfIndexUnspec]
// to monitor all interfaces.
// - proto is the IP4/IP6 protocol, used as transport for queries. If
// set to [ProtocolUnspec], both protocols will be used.
// - domain is domain where domains are looked. If set to "", the
// default domain is used, which depends on a avahi-daemon configuration
// and usually is ".local"
// - btype specified a type of domains being browses. See
// [DomainBrowserType] for details.
// - flags provide some lookup options. See [LookupFlags] for details.
//
// DomainBrowser must be closed after use with the [DomainBrowser.Close]
// function call.
//
// [RFC6763, 11]: https://datatracker.ietf.org/doc/html/rfc6763#section-11
// [RFC2132]: https://datatracker.ietf.org/doc/html/rfc2132
func NewDomainBrowser(
clnt *Client,
ifidx IfIndex,
proto Protocol,
domain string,
btype DomainBrowserType,
flags LookupFlags) (*DomainBrowser, error) {
// Initialize DomainBrowser structure
browser := &DomainBrowser{clnt: clnt}
browser.handle = cgo.NewHandle(browser)
browser.queue.init()
// Convert strings from Go to C
var cdomain *C.char
if domain != "" {
cdomain = C.CString(domain)
defer C.free(unsafe.Pointer(cdomain))
}
// Create AvahiDomainBrowser
avahiClient := clnt.begin()
defer clnt.end()
browser.avahiBrowser = C.avahi_domain_browser_new(
avahiClient,
C.AvahiIfIndex(ifidx),
C.AvahiProtocol(proto),
cdomain,
C.AvahiDomainBrowserType(btype),
C.AvahiLookupFlags(flags),
C.AvahiDomainBrowserCallback(C.domainBrowserCallback),
unsafe.Pointer(&browser.handle),
)
if browser.avahiBrowser == nil {
browser.queue.Close()
browser.handle.Delete()
return nil, clnt.errno()
}
// Register self to be closed if Client is closed
browser.clnt.addCloser(browser)
return browser, nil
}
// Chan returns channel where [DomainBrowserEvent]s are sent.
func (browser *DomainBrowser) Chan() <-chan *DomainBrowserEvent {
return browser.queue.Chan()
}
// Get waits for the next [DomainBrowserEvent].
//
// It returns:
// - event, nil - if event available
// - nil, error - if context is canceled
// - nil, nil - if DomainBrowser was closed
func (browser *DomainBrowser) Get(ctx context.Context) (*DomainBrowserEvent,
error) {
select {
case <-ctx.Done():
return nil, ctx.Err()
case evnt := <-browser.Chan():
return evnt, nil
}
}
// Close closes the [DomainBrowser] and releases allocated resources.
// It closes the event channel, effectively unblocking pending readers.
//
// Note, double close is safe.
func (browser *DomainBrowser) Close() {
if !browser.closed.Swap(true) {
browser.clnt.begin()
browser.clnt.delCloser(browser)
C.avahi_domain_browser_free(browser.avahiBrowser)
browser.avahiBrowser = nil
browser.clnt.end()
browser.queue.Close()
browser.handle.Delete()
}
}
// domainBrowserCallback called by AvahiDomainBrowser to
// report discovered services
//
//export domainBrowserCallback
func domainBrowserCallback(
b *C.AvahiDomainBrowser,
ifidx C.AvahiIfIndex,
proto C.AvahiProtocol,
event C.AvahiBrowserEvent,
name, svctype, domain *C.char,
flags C.AvahiLookupResultFlags,
p unsafe.Pointer) {
browser := (*cgo.Handle)(p).Value().(*DomainBrowser)
evnt := &DomainBrowserEvent{
Event: BrowserEvent(event),
IfIdx: IfIndex(ifidx),
Proto: Protocol(proto),
Flags: LookupResultFlags(flags),
Domain: C.GoString(domain),
}
if evnt.Event == BrowserFailure {
evnt.Err = browser.clnt.errno()
}
browser.queue.Push(evnt)
}