zeroconf 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,399 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ZeroConf
4
+ class Service
5
+ attr_reader :service, :service_port, :hostname, :service_interfaces,
6
+ :service_name, :qualified_host, :text
7
+
8
+ def initialize service, service_port, hostname = Socket.gethostname, service_interfaces: ZeroConf.service_interfaces, text: [""]
9
+ @service = service
10
+ @service_port = service_port
11
+ @hostname = hostname
12
+ @service_interfaces = service_interfaces
13
+ @service_name = "#{hostname}.#{service}"
14
+ @qualified_host = "#{hostname}.local."
15
+ @text = text
16
+ @rd, @wr = IO.pipe
17
+ end
18
+
19
+ def announcement
20
+ msg = Resolv::DNS::Message.new(0)
21
+ msg.qr = 1
22
+ msg.aa = 1
23
+
24
+ msg.add_additional service_name, 60, MDNS::Announce::IN::SRV.new(0, 0, service_port, qualified_host)
25
+
26
+ service_interfaces.each do |iface|
27
+ if iface.addr.ipv4?
28
+ msg.add_additional qualified_host,
29
+ 60,
30
+ MDNS::Announce::IN::A.new(iface.addr.ip_address)
31
+ else
32
+ msg.add_additional qualified_host,
33
+ 60,
34
+ MDNS::Announce::IN::AAAA.new(iface.addr.ip_address)
35
+ end
36
+ end
37
+
38
+ if @text
39
+ msg.add_additional service_name,
40
+ 60,
41
+ MDNS::Announce::IN::TXT.new(*@text)
42
+ end
43
+
44
+ msg.add_answer service,
45
+ 60,
46
+ Resolv::DNS::Resource::IN::PTR.new(Resolv::DNS::Name.create(service_name))
47
+
48
+ msg
49
+ end
50
+
51
+ def disconnect_msg
52
+ msg = Resolv::DNS::Message.new(0)
53
+ msg.qr = 1
54
+ msg.aa = 1
55
+
56
+ msg.add_additional service_name, 0, Resolv::DNS::Resource::IN::SRV.new(0, 0, service_port, qualified_host)
57
+
58
+ service_interfaces.each do |iface|
59
+ if iface.addr.ipv4?
60
+ msg.add_additional qualified_host,
61
+ 0,
62
+ Resolv::DNS::Resource::IN::A.new(iface.addr.ip_address)
63
+ else
64
+ msg.add_additional qualified_host,
65
+ 0,
66
+ Resolv::DNS::Resource::IN::AAAA.new(iface.addr.ip_address)
67
+ end
68
+ end
69
+
70
+ if @text
71
+ msg.add_additional service_name,
72
+ 0,
73
+ Resolv::DNS::Resource::IN::TXT.new(*@text)
74
+ end
75
+
76
+ msg.add_answer service,
77
+ 0,
78
+ Resolv::DNS::Resource::IN::PTR.new(Resolv::DNS::Name.create(service_name))
79
+
80
+ msg
81
+ end
82
+
83
+ include Utils
84
+
85
+ def stop
86
+ @wr.write "x"
87
+ @wr.close
88
+ end
89
+
90
+ def start
91
+ sock = open_ipv4 Addrinfo.new(Socket.sockaddr_in(Resolv::MDNS::Port, Socket::INADDR_ANY)), Resolv::MDNS::Port
92
+
93
+ sockets = [sock, @rd]
94
+
95
+ msg = announcement
96
+
97
+ # announce
98
+ multicast_send(sock, msg.encode)
99
+
100
+ loop do
101
+ readers, = IO.select(sockets, [], [])
102
+ next unless readers
103
+
104
+ readers.each do |reader|
105
+ return if reader == @rd
106
+
107
+ buf, from = reader.recvfrom 2048
108
+ msg = Resolv::DNS::Message.decode(buf)
109
+
110
+ has_flags = (buf.getbyte(3) << 8 | buf.getbyte(2)) != 0
111
+
112
+ msg.question.each do |name, type|
113
+ class_type = type::ClassValue & ~MDNS_CACHE_FLUSH
114
+
115
+ break unless class_type == 1 || class_type == 255
116
+
117
+ unicast = type::ClassValue & PTR::MDNS_UNICAST_RESPONSE > 0
118
+
119
+ qn = name.to_s + "."
120
+
121
+ res = case qn
122
+ when DISCOVERY_NAME
123
+ break if has_flags
124
+
125
+ if unicast
126
+ dnssd_unicast_answer
127
+ else
128
+ dnssd_multicast_answer
129
+ end
130
+ when service
131
+ if unicast
132
+ service_unicast_answer
133
+ else
134
+ service_multicast_answer
135
+ end
136
+ when service_name
137
+ if unicast
138
+ service_instance_unicast_answer
139
+ else
140
+ service_instance_multicast_answer
141
+ end
142
+ when qualified_host
143
+ if unicast
144
+ name_answer_unicast
145
+ else
146
+ name_answer_multicast
147
+ end
148
+ else
149
+ #p [:QUERY2, type, type::ClassValue, name]
150
+ end
151
+
152
+ next unless res
153
+
154
+ if unicast
155
+ unicast_send reader, res.encode, from
156
+ else
157
+ multicast_send reader, res.encode
158
+ end
159
+ end
160
+
161
+ # only yield replies to this question
162
+ end
163
+ end
164
+ ensure
165
+ multicast_send sock, disconnect_msg.encode
166
+ sockets.map(&:close)
167
+ end
168
+
169
+ private
170
+
171
+ def service_instance_unicast_answer
172
+ msg = Resolv::DNS::Message.new(0)
173
+ msg.qr = 1
174
+ msg.aa = 1
175
+
176
+ service_interfaces.each do |iface|
177
+ if iface.addr.ipv4?
178
+ msg.add_additional qualified_host,
179
+ 10,
180
+ Resolv::DNS::Resource::IN::A.new(iface.addr.ip_address)
181
+ else
182
+ msg.add_additional qualified_host,
183
+ 10,
184
+ Resolv::DNS::Resource::IN::AAAA.new(iface.addr.ip_address)
185
+ end
186
+ end
187
+
188
+ if @text
189
+ msg.add_additional service_name,
190
+ 10,
191
+ Resolv::DNS::Resource::IN::TXT.new(*@text)
192
+ end
193
+ msg.add_answer service_name, 10, Resolv::DNS::Resource::IN::SRV.new(0, 0, service_port, qualified_host)
194
+ msg.add_question service_name, ZeroConf::MDNS::Announce::IN::SRV
195
+
196
+ msg
197
+ end
198
+
199
+ def service_unicast_answer
200
+ msg = Resolv::DNS::Message.new(0)
201
+ msg.qr = 1
202
+ msg.aa = 1
203
+
204
+ msg.add_additional service_name, 10, Resolv::DNS::Resource::IN::SRV.new(0, 0, service_port, qualified_host)
205
+
206
+ service_interfaces.each do |iface|
207
+ if iface.addr.ipv4?
208
+ msg.add_additional qualified_host,
209
+ 10,
210
+ Resolv::DNS::Resource::IN::A.new(iface.addr.ip_address)
211
+ else
212
+ msg.add_additional qualified_host,
213
+ 10,
214
+ Resolv::DNS::Resource::IN::AAAA.new(iface.addr.ip_address)
215
+ end
216
+ end
217
+
218
+ if @text
219
+ msg.add_additional service_name,
220
+ 10,
221
+ Resolv::DNS::Resource::IN::TXT.new(*@text)
222
+ end
223
+
224
+ msg.add_answer service,
225
+ 10,
226
+ Resolv::DNS::Resource::IN::PTR.new(Resolv::DNS::Name.create(service_name))
227
+
228
+ msg.add_question service, ZeroConf::PTR
229
+
230
+ msg
231
+ end
232
+
233
+ def dnssd_unicast_answer
234
+ msg = Resolv::DNS::Message.new(0)
235
+ msg.qr = 1
236
+ msg.aa = 1
237
+
238
+ msg.add_answer DISCOVERY_NAME, 10,
239
+ Resolv::DNS::Resource::IN::PTR.new(Resolv::DNS::Name.create(service))
240
+
241
+ msg.add_question DISCOVERY_NAME, ZeroConf::PTR
242
+ msg
243
+ end
244
+
245
+ def dnssd_multicast_answer
246
+ msg = Resolv::DNS::Message.new(0)
247
+ msg.qr = 1
248
+ msg.aa = 1
249
+
250
+ msg.add_answer DISCOVERY_NAME, 60,
251
+ Resolv::DNS::Resource::IN::PTR.new(Resolv::DNS::Name.create(service))
252
+ msg
253
+ end
254
+
255
+ def service_multicast_answer
256
+ msg = Resolv::DNS::Message.new(0)
257
+ msg.qr = 1
258
+ msg.aa = 1
259
+
260
+ msg.add_additional service_name, 60, Resolv::DNS::Resource::IN::SRV.new(0, 0, service_port, qualified_host)
261
+
262
+ service_interfaces.each do |iface|
263
+ if iface.addr.ipv4?
264
+ msg.add_additional qualified_host,
265
+ 60,
266
+ Resolv::DNS::Resource::IN::A.new(iface.addr.ip_address)
267
+ else
268
+ msg.add_additional qualified_host,
269
+ 60,
270
+ Resolv::DNS::Resource::IN::AAAA.new(iface.addr.ip_address)
271
+ end
272
+ end
273
+
274
+ if @text
275
+ msg.add_additional service_name,
276
+ 60,
277
+ Resolv::DNS::Resource::IN::TXT.new(*@text)
278
+ end
279
+
280
+ msg.add_answer service,
281
+ 60,
282
+ Resolv::DNS::Resource::IN::PTR.new(Resolv::DNS::Name.create(service_name))
283
+
284
+ msg
285
+ end
286
+
287
+ def service_instance_multicast_answer
288
+ msg = Resolv::DNS::Message.new(0)
289
+ msg.qr = 1
290
+ msg.aa = 1
291
+
292
+ msg.add_answer service_name, 60, Resolv::DNS::Resource::IN::SRV.new(0, 0, service_port, qualified_host)
293
+
294
+ service_interfaces.each do |iface|
295
+ if iface.addr.ipv4?
296
+ msg.add_additional qualified_host,
297
+ 60,
298
+ Resolv::DNS::Resource::IN::A.new(iface.addr.ip_address)
299
+ else
300
+ msg.add_additional qualified_host,
301
+ 60,
302
+ Resolv::DNS::Resource::IN::AAAA.new(iface.addr.ip_address)
303
+ end
304
+ end
305
+
306
+ if @text
307
+ msg.add_additional service_name,
308
+ 60,
309
+ Resolv::DNS::Resource::IN::TXT.new(*@text)
310
+ end
311
+
312
+ msg
313
+ end
314
+
315
+ def name_answer_unicast
316
+ msg = Resolv::DNS::Message.new(0)
317
+ msg.qr = 1
318
+ msg.aa = 1
319
+
320
+ first = true
321
+
322
+ service_interfaces.each do |iface|
323
+ if first
324
+ if iface.addr.ipv4?
325
+ msg.add_answer qualified_host,
326
+ 10,
327
+ Resolv::DNS::Resource::IN::A.new(iface.addr.ip_address)
328
+ else
329
+ msg.add_answer s.qualified_host,
330
+ 10,
331
+ Resolv::DNS::Resource::IN::AAAA.new(iface.addr.ip_address)
332
+ end
333
+ first = false
334
+ else
335
+ if iface.addr.ipv4?
336
+ msg.add_additional qualified_host,
337
+ 10,
338
+ Resolv::DNS::Resource::IN::A.new(iface.addr.ip_address)
339
+ else
340
+ msg.add_additional qualified_host,
341
+ 10,
342
+ Resolv::DNS::Resource::IN::AAAA.new(iface.addr.ip_address)
343
+ end
344
+ end
345
+ end
346
+
347
+ msg.add_question qualified_host, ZeroConf::MDNS::Announce::IN::A
348
+
349
+ if @text
350
+ msg.add_additional service_name,
351
+ 10,
352
+ Resolv::DNS::Resource::IN::TXT.new(*@text)
353
+ end
354
+
355
+ msg
356
+ end
357
+
358
+ def name_answer_multicast
359
+ msg = Resolv::DNS::Message.new(0)
360
+ msg.qr = 1
361
+ msg.aa = 1
362
+
363
+ first = true
364
+
365
+ service_interfaces.each do |iface|
366
+ if first
367
+ if iface.addr.ipv4?
368
+ msg.add_answer qualified_host,
369
+ 60,
370
+ Resolv::DNS::Resource::IN::A.new(iface.addr.ip_address)
371
+ else
372
+ msg.add_answer s.qualified_host,
373
+ 60,
374
+ Resolv::DNS::Resource::IN::AAAA.new(iface.addr.ip_address)
375
+ end
376
+ first = false
377
+ else
378
+ if iface.addr.ipv4?
379
+ msg.add_additional qualified_host,
380
+ 60,
381
+ Resolv::DNS::Resource::IN::A.new(iface.addr.ip_address)
382
+ else
383
+ msg.add_additional qualified_host,
384
+ 60,
385
+ Resolv::DNS::Resource::IN::AAAA.new(iface.addr.ip_address)
386
+ end
387
+ end
388
+ end
389
+
390
+ if @text
391
+ msg.add_additional service_name,
392
+ 60,
393
+ Resolv::DNS::Resource::IN::TXT.new(*@text)
394
+ end
395
+
396
+ msg
397
+ end
398
+ end
399
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "socket"
4
+ require "ipaddr"
5
+ require "fcntl"
6
+ require "resolv"
7
+
8
+ module ZeroConf
9
+ module Utils
10
+ DISCOVERY_NAME = "_services._dns-sd._udp.local."
11
+
12
+ def open_ipv4 saddr, port
13
+ sock = UDPSocket.new Socket::AF_INET
14
+ sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true)
15
+ sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEPORT, true)
16
+ sock.setsockopt(Socket::IPPROTO_IP, Socket::IP_MULTICAST_TTL, true)
17
+ sock.setsockopt(Socket::IPPROTO_IP, Socket::IP_MULTICAST_LOOP, true)
18
+ sock.setsockopt(Socket::IPPROTO_IP, Socket::IP_ADD_MEMBERSHIP,
19
+ IPAddr.new(Resolv::MDNS::AddressV4).hton + IPAddr.new(saddr.ip_address).hton)
20
+ sock.setsockopt(Socket::IPPROTO_IP, Socket::IP_MULTICAST_IF, IPAddr.new(saddr.ip_address).hton)
21
+ sock.bind saddr.ip_address, port
22
+ flags = sock.fcntl(Fcntl::F_GETFL, 0)
23
+ sock.fcntl(Fcntl::F_SETFL, Fcntl::O_NONBLOCK | flags)
24
+ sock
25
+ end
26
+
27
+ BROADCAST_V4 = Addrinfo.new Socket.sockaddr_in(Resolv::MDNS::Port, Resolv::MDNS::AddressV4)
28
+ BROADCAST_V6 = Addrinfo.new Socket.sockaddr_in(Resolv::MDNS::Port, Resolv::MDNS::AddressV6)
29
+
30
+ def multicast_send sock, query
31
+ dest = if sock.local_address.ipv4?
32
+ BROADCAST_V4
33
+ else
34
+ BROADCAST_V6
35
+ end
36
+
37
+ sock.send(query, 0, dest)
38
+ end
39
+
40
+ def unicast_send sock, data, to
41
+ sock.send(data, 0, Addrinfo.new(to))
42
+ end
43
+
44
+ def open_ipv6 saddr, port
45
+ sock = UDPSocket.new Socket::AF_INET6
46
+ sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true)
47
+ sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEPORT, true)
48
+ sock.setsockopt(Socket::IPPROTO_IPV6, Socket::IPV6_MULTICAST_HOPS, true)
49
+ sock.setsockopt(Socket::IPPROTO_IPV6, Socket::IPV6_MULTICAST_LOOP, true)
50
+
51
+ # This address isn't correct, but giving it to IPAddr seems to result
52
+ # in the right bytes back from hton.
53
+ # See: https://github.com/ruby/ipaddr/issues/63
54
+ s = IPAddr.new("ff02:0000:0000:0000:0000:00fb:0000:0000").hton
55
+ sock.setsockopt(Socket::IPPROTO_IPV6, Socket::IPV6_JOIN_GROUP, s)
56
+ sock.setsockopt(Socket::IPPROTO_IPV6, Socket::IPV6_MULTICAST_IF, IPAddr.new(saddr.ip_address).hton)
57
+ sock.bind saddr.ip_address, port
58
+ flags = sock.fcntl(Fcntl::F_GETFL, 0)
59
+ sock.fcntl(Fcntl::F_SETFL, Fcntl::O_NONBLOCK | flags)
60
+
61
+ sock
62
+ rescue SystemCallError
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,3 @@
1
+ module ZeroConf
2
+ VERSION = "1.0.0"
3
+ end