zeroconf 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +25 -0
- data/lib/zeroconf/browser.rb +16 -0
- data/lib/zeroconf/client.rb +68 -0
- data/lib/zeroconf/discoverer.rb +24 -0
- data/lib/zeroconf/resolver.rb +15 -0
- data/lib/zeroconf/service.rb +31 -17
- data/lib/zeroconf/utils.rb +58 -4
- data/lib/zeroconf/version.rb +1 -1
- data/lib/zeroconf.rb +11 -166
- data/test/client_test.rb +11 -5
- data/test/helper.rb +25 -0
- data/test/service_test.rb +239 -36
- metadata +8 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 39d1c369a77bb16908b77e600e166c264662ec1fc624de8aee2f62fc752e3aae
|
4
|
+
data.tar.gz: 6f58a84ef9926d9e900182390249b6b029312332a7376cc1f1229b182812bb59
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5a515ce4490d93f37439bec362b84a6bb99c4d42098892f8250d9059537984c9ac6f0c90e9cbf005374f0fdb9abd0a78cb4fd2f7b54e87e05f8acce6eee892cb
|
7
|
+
data.tar.gz: be3a38c8bfab29934aca1d2d1100fa4fdda4aa7bfc8c31fe095a461337cffc67b44ac1d982c2faf8f6131891c9eee1a9d444932fa5899b353b0f12e9b8968210
|
@@ -0,0 +1,25 @@
|
|
1
|
+
name: CI
|
2
|
+
|
3
|
+
on: [push, pull_request]
|
4
|
+
|
5
|
+
jobs:
|
6
|
+
test:
|
7
|
+
runs-on: ${{ matrix.os }}-latest
|
8
|
+
|
9
|
+
strategy:
|
10
|
+
fail-fast: false
|
11
|
+
matrix:
|
12
|
+
os: [ubuntu, macos]
|
13
|
+
ruby: [ head, 3.2 ]
|
14
|
+
|
15
|
+
steps:
|
16
|
+
- uses: actions/checkout@v3
|
17
|
+
- name: Set up Ruby
|
18
|
+
uses: ruby/setup-ruby@v1
|
19
|
+
with:
|
20
|
+
ruby-version: ${{ matrix.ruby }}
|
21
|
+
|
22
|
+
- name: Install dependencies
|
23
|
+
run: bundle install
|
24
|
+
- name: Run tests
|
25
|
+
run: bundle exec rake test
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "zeroconf/client"
|
4
|
+
|
5
|
+
module ZeroConf
|
6
|
+
class Browser < Client
|
7
|
+
private
|
8
|
+
|
9
|
+
def get_query
|
10
|
+
q = PTR.new(name)
|
11
|
+
query = Resolv::DNS::Message.new 0
|
12
|
+
query.add_question q.name, q.class
|
13
|
+
query
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "zeroconf/utils"
|
4
|
+
|
5
|
+
module ZeroConf
|
6
|
+
class Client
|
7
|
+
include Utils
|
8
|
+
|
9
|
+
attr_reader :name, :interfaces
|
10
|
+
|
11
|
+
def initialize name, interfaces: ZeroConf.interfaces
|
12
|
+
@name = name
|
13
|
+
@interfaces = interfaces
|
14
|
+
end
|
15
|
+
|
16
|
+
def run timeout: 3
|
17
|
+
sockets = open_interfaces interfaces.map(&:addr), Resolv::MDNS::Port
|
18
|
+
|
19
|
+
query = get_query
|
20
|
+
sockets.each { |socket| multicast_send(socket, query.encode) }
|
21
|
+
|
22
|
+
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
23
|
+
now = start
|
24
|
+
msgs = block_given? ? nil : []
|
25
|
+
|
26
|
+
loop do
|
27
|
+
wait = timeout && timeout - (now - start)
|
28
|
+
return if wait && wait < 0
|
29
|
+
|
30
|
+
readers, = IO.select(sockets, [], [], wait)
|
31
|
+
|
32
|
+
return msgs unless readers
|
33
|
+
|
34
|
+
readers.each do |reader|
|
35
|
+
buf, _ = reader.recvfrom 2048
|
36
|
+
msg = Resolv::DNS::Message.decode(buf)
|
37
|
+
# only yield replies to this question
|
38
|
+
if interested? msg
|
39
|
+
if block_given?
|
40
|
+
if :done == yield(msg)
|
41
|
+
return msg
|
42
|
+
end
|
43
|
+
else
|
44
|
+
msgs << msg
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
49
|
+
end
|
50
|
+
ensure
|
51
|
+
sockets.each(&:close) if sockets
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def interested? _; true; end
|
57
|
+
|
58
|
+
def open_interfaces addrs, port
|
59
|
+
addrs.map { |addr|
|
60
|
+
if addr.ipv4?
|
61
|
+
open_ipv4 addr, port
|
62
|
+
else
|
63
|
+
open_ipv6 addr, port
|
64
|
+
end
|
65
|
+
}.compact
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "zeroconf/client"
|
4
|
+
|
5
|
+
module ZeroConf
|
6
|
+
class Discoverer < Client
|
7
|
+
DISCOVER_QUERY = Resolv::DNS::Message.new 0
|
8
|
+
DISCOVER_QUERY.add_question DISCOVERY_NAME, PTR
|
9
|
+
|
10
|
+
def initialize interfaces: ZeroConf.interfaces
|
11
|
+
super(nil, interfaces:)
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def interested? msg
|
17
|
+
msg.question.length > 0 && msg.question.first.last == PTR
|
18
|
+
end
|
19
|
+
|
20
|
+
def get_query
|
21
|
+
DISCOVER_QUERY
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "zeroconf/client"
|
4
|
+
|
5
|
+
module ZeroConf
|
6
|
+
class Resolver < Client
|
7
|
+
private
|
8
|
+
|
9
|
+
def get_query
|
10
|
+
query = Resolv::DNS::Message.new 0
|
11
|
+
query.add_question Resolv::DNS::Name.create(name), A
|
12
|
+
query
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/zeroconf/service.rb
CHANGED
@@ -1,7 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "zeroconf/utils"
|
4
|
+
|
3
5
|
module ZeroConf
|
4
6
|
class Service
|
7
|
+
include Utils
|
8
|
+
|
5
9
|
attr_reader :service, :service_port, :hostname, :service_interfaces,
|
6
10
|
:service_name, :qualified_host, :text
|
7
11
|
|
@@ -13,9 +17,12 @@ module ZeroConf
|
|
13
17
|
@service_name = "#{hostname}.#{service}"
|
14
18
|
@qualified_host = "#{hostname}.local."
|
15
19
|
@text = text
|
20
|
+
@started = false
|
16
21
|
@rd, @wr = IO.pipe
|
17
22
|
end
|
18
23
|
|
24
|
+
def started?; @started; end
|
25
|
+
|
19
26
|
def announcement
|
20
27
|
msg = Resolv::DNS::Message.new(0)
|
21
28
|
msg.qr = 1
|
@@ -80,11 +87,10 @@ module ZeroConf
|
|
80
87
|
msg
|
81
88
|
end
|
82
89
|
|
83
|
-
include Utils
|
84
|
-
|
85
90
|
def stop
|
86
91
|
@wr.write "x"
|
87
92
|
@wr.close
|
93
|
+
@started = false
|
88
94
|
end
|
89
95
|
|
90
96
|
def start
|
@@ -97,6 +103,8 @@ module ZeroConf
|
|
97
103
|
# announce
|
98
104
|
multicast_send(sock, msg.encode)
|
99
105
|
|
106
|
+
@started = true
|
107
|
+
|
100
108
|
loop do
|
101
109
|
readers, = IO.select(sockets, [], [])
|
102
110
|
next unless readers
|
@@ -114,7 +122,9 @@ module ZeroConf
|
|
114
122
|
|
115
123
|
break unless class_type == 1 || class_type == 255
|
116
124
|
|
117
|
-
|
125
|
+
legacy = from[1] != Resolv::MDNS::Port
|
126
|
+
unicast = legacy || type::ClassValue & PTR::MDNS_UNICAST_RESPONSE > 0
|
127
|
+
reply_id = legacy ? msg.id : 0
|
118
128
|
|
119
129
|
qn = name.to_s + "."
|
120
130
|
|
@@ -123,25 +133,25 @@ module ZeroConf
|
|
123
133
|
break if has_flags
|
124
134
|
|
125
135
|
if unicast
|
126
|
-
dnssd_unicast_answer
|
136
|
+
dnssd_unicast_answer(id: reply_id, legacy: legacy)
|
127
137
|
else
|
128
138
|
dnssd_multicast_answer
|
129
139
|
end
|
130
140
|
when service
|
131
141
|
if unicast
|
132
|
-
service_unicast_answer
|
142
|
+
service_unicast_answer(id: reply_id, legacy: legacy)
|
133
143
|
else
|
134
144
|
service_multicast_answer
|
135
145
|
end
|
136
146
|
when service_name
|
137
147
|
if unicast
|
138
|
-
service_instance_unicast_answer
|
148
|
+
service_instance_unicast_answer(id: reply_id, legacy: legacy)
|
139
149
|
else
|
140
150
|
service_instance_multicast_answer
|
141
151
|
end
|
142
152
|
when qualified_host
|
143
153
|
if unicast
|
144
|
-
name_answer_unicast
|
154
|
+
name_answer_unicast(id: reply_id, legacy: legacy)
|
145
155
|
else
|
146
156
|
name_answer_multicast
|
147
157
|
end
|
@@ -162,16 +172,17 @@ module ZeroConf
|
|
162
172
|
end
|
163
173
|
end
|
164
174
|
ensure
|
165
|
-
multicast_send
|
166
|
-
sockets.map(&:close)
|
175
|
+
multicast_send(sock, disconnect_msg.encode) if sock
|
176
|
+
sockets.map(&:close) if sockets
|
167
177
|
end
|
168
178
|
|
169
179
|
private
|
170
180
|
|
171
|
-
def service_instance_unicast_answer
|
181
|
+
def service_instance_unicast_answer(id:, legacy:)
|
172
182
|
msg = Resolv::DNS::Message.new(0)
|
173
183
|
msg.qr = 1
|
174
184
|
msg.aa = 1
|
185
|
+
msg.id = id
|
175
186
|
|
176
187
|
service_interfaces.each do |iface|
|
177
188
|
if iface.addr.ipv4?
|
@@ -191,15 +202,16 @@ module ZeroConf
|
|
191
202
|
Resolv::DNS::Resource::IN::TXT.new(*@text)
|
192
203
|
end
|
193
204
|
msg.add_answer service_name, 10, Resolv::DNS::Resource::IN::SRV.new(0, 0, service_port, qualified_host)
|
194
|
-
msg.add_question service_name,
|
205
|
+
msg.add_question service_name, legacy ? Resolv::DNS::Resource::IN::SRV : MDNS::Announce::IN::SRV
|
195
206
|
|
196
207
|
msg
|
197
208
|
end
|
198
209
|
|
199
|
-
def service_unicast_answer
|
210
|
+
def service_unicast_answer(id:, legacy:)
|
200
211
|
msg = Resolv::DNS::Message.new(0)
|
201
212
|
msg.qr = 1
|
202
213
|
msg.aa = 1
|
214
|
+
msg.id = id
|
203
215
|
|
204
216
|
msg.add_additional service_name, 10, Resolv::DNS::Resource::IN::SRV.new(0, 0, service_port, qualified_host)
|
205
217
|
|
@@ -225,20 +237,21 @@ module ZeroConf
|
|
225
237
|
10,
|
226
238
|
Resolv::DNS::Resource::IN::PTR.new(Resolv::DNS::Name.create(service_name))
|
227
239
|
|
228
|
-
msg.add_question service,
|
240
|
+
msg.add_question service, legacy ? Resolv::DNS::Resource::IN::PTR : PTR
|
229
241
|
|
230
242
|
msg
|
231
243
|
end
|
232
244
|
|
233
|
-
def dnssd_unicast_answer
|
245
|
+
def dnssd_unicast_answer(id:, legacy:)
|
234
246
|
msg = Resolv::DNS::Message.new(0)
|
235
247
|
msg.qr = 1
|
236
248
|
msg.aa = 1
|
249
|
+
msg.id = id
|
237
250
|
|
238
251
|
msg.add_answer DISCOVERY_NAME, 10,
|
239
252
|
Resolv::DNS::Resource::IN::PTR.new(Resolv::DNS::Name.create(service))
|
240
253
|
|
241
|
-
msg.add_question DISCOVERY_NAME,
|
254
|
+
msg.add_question DISCOVERY_NAME, legacy ? Resolv::DNS::Resource::IN::PTR : PTR
|
242
255
|
msg
|
243
256
|
end
|
244
257
|
|
@@ -312,10 +325,11 @@ module ZeroConf
|
|
312
325
|
msg
|
313
326
|
end
|
314
327
|
|
315
|
-
def name_answer_unicast
|
328
|
+
def name_answer_unicast(id:, legacy:)
|
316
329
|
msg = Resolv::DNS::Message.new(0)
|
317
330
|
msg.qr = 1
|
318
331
|
msg.aa = 1
|
332
|
+
msg.id = id
|
319
333
|
|
320
334
|
first = true
|
321
335
|
|
@@ -344,7 +358,7 @@ module ZeroConf
|
|
344
358
|
end
|
345
359
|
end
|
346
360
|
|
347
|
-
msg.add_question qualified_host,
|
361
|
+
msg.add_question qualified_host, legacy ? Resolv::DNS::Resource::IN::A : MDNS::Announce::IN::A
|
348
362
|
|
349
363
|
if @text
|
350
364
|
msg.add_additional service_name,
|
data/lib/zeroconf/utils.rb
CHANGED
@@ -4,15 +4,61 @@ require "socket"
|
|
4
4
|
require "ipaddr"
|
5
5
|
require "fcntl"
|
6
6
|
require "resolv"
|
7
|
+
require "rbconfig"
|
7
8
|
|
8
9
|
module ZeroConf
|
10
|
+
MDNS_CACHE_FLUSH = 0x8000
|
11
|
+
|
12
|
+
# :stopdoc:
|
13
|
+
class PTR < Resolv::DNS::Resource::IN::PTR
|
14
|
+
MDNS_UNICAST_RESPONSE = 0x8000
|
15
|
+
|
16
|
+
ClassValue = Resolv::DNS::Resource::IN::ClassValue | MDNS_UNICAST_RESPONSE
|
17
|
+
ClassHash[[TypeValue, ClassValue]] = self # :nodoc:
|
18
|
+
end
|
19
|
+
|
20
|
+
class ANY < Resolv::DNS::Resource::IN::ANY
|
21
|
+
MDNS_UNICAST_RESPONSE = 0x8000
|
22
|
+
|
23
|
+
ClassValue = Resolv::DNS::Resource::IN::ClassValue | MDNS_UNICAST_RESPONSE
|
24
|
+
::Resolv::DNS::Resource::ClassHash[[TypeValue, ClassValue]] = self # :nodoc:
|
25
|
+
end
|
26
|
+
|
27
|
+
class A < Resolv::DNS::Resource::IN::A
|
28
|
+
MDNS_UNICAST_RESPONSE = 0x8000
|
29
|
+
|
30
|
+
ClassValue = Resolv::DNS::Resource::IN::ClassValue | MDNS_UNICAST_RESPONSE
|
31
|
+
ClassHash[[TypeValue, ClassValue]] = self # :nodoc:
|
32
|
+
end
|
33
|
+
|
34
|
+
class SRV < Resolv::DNS::Resource::IN::SRV
|
35
|
+
MDNS_UNICAST_RESPONSE = 0x8000
|
36
|
+
|
37
|
+
ClassValue = Resolv::DNS::Resource::IN::ClassValue | MDNS_UNICAST_RESPONSE
|
38
|
+
ClassHash[[TypeValue, ClassValue]] = self # :nodoc:
|
39
|
+
end
|
40
|
+
|
41
|
+
module MDNS
|
42
|
+
module Announce
|
43
|
+
module IN
|
44
|
+
[:SRV, :A, :AAAA, :TXT].each do |name|
|
45
|
+
const_set(name, Class.new(Resolv::DNS::Resource::IN.const_get(name)) {
|
46
|
+
const_set(:ClassValue, superclass::ClassValue | MDNS_CACHE_FLUSH)
|
47
|
+
self::ClassHash[[self::TypeValue, self::ClassValue]] = self
|
48
|
+
})
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
# :startdoc:
|
54
|
+
|
9
55
|
module Utils
|
10
56
|
DISCOVERY_NAME = "_services._dns-sd._udp.local."
|
11
57
|
|
12
58
|
def open_ipv4 saddr, port
|
13
59
|
sock = UDPSocket.new Socket::AF_INET
|
14
60
|
sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true)
|
15
|
-
sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEPORT, true)
|
61
|
+
sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEPORT, true) unless RbConfig::CONFIG["host_os"].include?("linux")
|
16
62
|
sock.setsockopt(Socket::IPPROTO_IP, Socket::IP_MULTICAST_TTL, true)
|
17
63
|
sock.setsockopt(Socket::IPPROTO_IP, Socket::IP_MULTICAST_LOOP, true)
|
18
64
|
sock.setsockopt(Socket::IPPROTO_IP, Socket::IP_ADD_MEMBERSHIP,
|
@@ -29,14 +75,22 @@ module ZeroConf
|
|
29
75
|
|
30
76
|
def multicast_send sock, query
|
31
77
|
dest = if sock.local_address.ipv4?
|
32
|
-
|
78
|
+
broadcast_v4
|
33
79
|
else
|
34
|
-
|
80
|
+
broadcast_v6
|
35
81
|
end
|
36
82
|
|
37
83
|
sock.send(query, 0, dest)
|
38
84
|
end
|
39
85
|
|
86
|
+
def broadcast_v4
|
87
|
+
BROADCAST_V4
|
88
|
+
end
|
89
|
+
|
90
|
+
def broadcast_v6
|
91
|
+
BROADCAST_V6
|
92
|
+
end
|
93
|
+
|
40
94
|
def unicast_send sock, data, to
|
41
95
|
sock.send(data, 0, Addrinfo.new(to))
|
42
96
|
end
|
@@ -44,7 +98,7 @@ module ZeroConf
|
|
44
98
|
def open_ipv6 saddr, port
|
45
99
|
sock = UDPSocket.new Socket::AF_INET6
|
46
100
|
sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true)
|
47
|
-
sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEPORT, true)
|
101
|
+
sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEPORT, true) unless RbConfig::CONFIG["host_os"].include?("linux")
|
48
102
|
sock.setsockopt(Socket::IPPROTO_IPV6, Socket::IPV6_MULTICAST_HOPS, true)
|
49
103
|
sock.setsockopt(Socket::IPPROTO_IPV6, Socket::IPV6_MULTICAST_LOOP, true)
|
50
104
|
|
data/lib/zeroconf/version.rb
CHANGED
data/lib/zeroconf.rb
CHANGED
@@ -1,60 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "zeroconf/utils"
|
4
3
|
require "zeroconf/service"
|
4
|
+
require "zeroconf/browser"
|
5
|
+
require "zeroconf/resolver"
|
6
|
+
require "zeroconf/discoverer"
|
5
7
|
|
6
8
|
module ZeroConf
|
7
|
-
|
8
|
-
|
9
|
-
extend Utils
|
10
|
-
include Utils
|
11
|
-
|
12
|
-
# :stopdoc:
|
13
|
-
class PTR < Resolv::DNS::Resource::IN::PTR
|
14
|
-
MDNS_UNICAST_RESPONSE = 0x8000
|
15
|
-
|
16
|
-
ClassValue = Resolv::DNS::Resource::IN::ClassValue | MDNS_UNICAST_RESPONSE
|
17
|
-
ClassHash[[TypeValue, ClassValue]] = self # :nodoc:
|
18
|
-
end
|
19
|
-
|
20
|
-
class ANY < Resolv::DNS::Resource::IN::ANY
|
21
|
-
MDNS_UNICAST_RESPONSE = 0x8000
|
22
|
-
|
23
|
-
ClassValue = Resolv::DNS::Resource::IN::ClassValue | MDNS_UNICAST_RESPONSE
|
24
|
-
::Resolv::DNS::Resource::ClassHash[[TypeValue, ClassValue]] = self # :nodoc:
|
25
|
-
end
|
26
|
-
|
27
|
-
class A < Resolv::DNS::Resource::IN::A
|
28
|
-
MDNS_UNICAST_RESPONSE = 0x8000
|
29
|
-
|
30
|
-
ClassValue = Resolv::DNS::Resource::IN::ClassValue | MDNS_UNICAST_RESPONSE
|
31
|
-
ClassHash[[TypeValue, ClassValue]] = self # :nodoc:
|
32
|
-
end
|
33
|
-
|
34
|
-
class SRV < Resolv::DNS::Resource::IN::SRV
|
35
|
-
MDNS_UNICAST_RESPONSE = 0x8000
|
36
|
-
|
37
|
-
ClassValue = Resolv::DNS::Resource::IN::ClassValue | MDNS_UNICAST_RESPONSE
|
38
|
-
ClassHash[[TypeValue, ClassValue]] = self # :nodoc:
|
39
|
-
end
|
40
|
-
|
41
|
-
module MDNS
|
42
|
-
module Announce
|
43
|
-
module IN
|
44
|
-
[:SRV, :A, :AAAA, :TXT].each do |name|
|
45
|
-
const_set(name, Class.new(Resolv::DNS::Resource::IN.const_get(name)) {
|
46
|
-
const_set(:ClassValue, superclass::ClassValue | MDNS_CACHE_FLUSH)
|
47
|
-
self::ClassHash[[self::TypeValue, self::ClassValue]] = self
|
48
|
-
})
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
DISCOVER_QUERY = Resolv::DNS::Message.new 0
|
55
|
-
DISCOVER_QUERY.add_question DISCOVERY_NAME, PTR
|
56
|
-
|
57
|
-
# :startdoc:
|
9
|
+
#include Utils
|
58
10
|
|
59
11
|
##
|
60
12
|
# ZeroConf.browse
|
@@ -72,45 +24,8 @@ module ZeroConf
|
|
72
24
|
# @param [Array<Socket::Ifaddr>] interfaces list of interfaces to query
|
73
25
|
# @param [Numeric] timeout number of seconds before returning
|
74
26
|
def self.browse name, interfaces: self.interfaces, timeout: 3, &blk
|
75
|
-
|
76
|
-
|
77
|
-
if iface.addr.ipv4?
|
78
|
-
open_ipv4 iface.addr, port
|
79
|
-
else
|
80
|
-
open_ipv6 iface.addr, port
|
81
|
-
end
|
82
|
-
}.compact
|
83
|
-
|
84
|
-
q = PTR.new(name)
|
85
|
-
|
86
|
-
sockets.each { |socket|
|
87
|
-
query = Resolv::DNS::Message.new 0
|
88
|
-
|
89
|
-
query.add_question q.name, q.class
|
90
|
-
|
91
|
-
multicast_send socket, query.encode
|
92
|
-
}
|
93
|
-
|
94
|
-
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
95
|
-
now = start
|
96
|
-
msgs = block_given? ? nil : []
|
97
|
-
|
98
|
-
loop do
|
99
|
-
readers, = IO.select(sockets, [], [], timeout - (now - start))
|
100
|
-
return msgs unless readers
|
101
|
-
readers.each do |reader|
|
102
|
-
buf, = reader.recvfrom 2048
|
103
|
-
msg = Resolv::DNS::Message.decode(buf)
|
104
|
-
if block_given?
|
105
|
-
return msg if :done == yield(msg)
|
106
|
-
else
|
107
|
-
msgs << msg
|
108
|
-
end
|
109
|
-
end
|
110
|
-
now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
111
|
-
end
|
112
|
-
ensure
|
113
|
-
sockets.map(&:close) if sockets
|
27
|
+
browser = ZeroConf::Browser.new(name, interfaces:)
|
28
|
+
browser.run(timeout:, &blk)
|
114
29
|
end
|
115
30
|
|
116
31
|
###
|
@@ -131,7 +46,6 @@ module ZeroConf
|
|
131
46
|
port = nil
|
132
47
|
ipv4 = []
|
133
48
|
ipv6 = []
|
134
|
-
pp r
|
135
49
|
r.additional.each { |name, ttl, data|
|
136
50
|
case data
|
137
51
|
when Resolv::DNS::Resource::IN::SRV
|
@@ -149,37 +63,8 @@ module ZeroConf
|
|
149
63
|
end
|
150
64
|
|
151
65
|
def self.resolve name, interfaces: self.interfaces, timeout: 3, &blk
|
152
|
-
|
153
|
-
|
154
|
-
if iface.addr.ipv4?
|
155
|
-
open_ipv4 iface.addr, port
|
156
|
-
else
|
157
|
-
open_ipv6 iface.addr, port
|
158
|
-
end
|
159
|
-
}.compact
|
160
|
-
|
161
|
-
query = Resolv::DNS::Message.new 0
|
162
|
-
query.add_question Resolv::DNS::Name.create(name), A
|
163
|
-
|
164
|
-
sockets.each do |sock|
|
165
|
-
multicast_send sock, query.encode
|
166
|
-
end
|
167
|
-
|
168
|
-
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
169
|
-
now = start
|
170
|
-
|
171
|
-
loop do
|
172
|
-
readers, = IO.select(sockets, [], [], timeout - (now - start))
|
173
|
-
return unless readers
|
174
|
-
readers.each do |reader|
|
175
|
-
buf, = reader.recvfrom 2048
|
176
|
-
msg = Resolv::DNS::Message.decode(buf)
|
177
|
-
return msg if :done == yield(msg)
|
178
|
-
end
|
179
|
-
now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
180
|
-
end
|
181
|
-
ensure
|
182
|
-
sockets.map(&:close) if sockets
|
66
|
+
resolver = ZeroConf::Resolver.new(name, interfaces:)
|
67
|
+
resolver.run(timeout:, &blk)
|
183
68
|
end
|
184
69
|
|
185
70
|
def self.service service, service_port, hostname = Socket.gethostname, service_interfaces: self.service_interfaces, text: [""]
|
@@ -222,46 +107,9 @@ module ZeroConf
|
|
222
107
|
#
|
223
108
|
# @param [Array<Socket::Ifaddr>] interfaces list of interfaces to query
|
224
109
|
# @param [Numeric] timeout number of seconds before returning
|
225
|
-
def self.discover interfaces: self.interfaces, timeout: 3
|
226
|
-
|
227
|
-
|
228
|
-
sockets = interfaces.map { |iface|
|
229
|
-
if iface.addr.ipv4?
|
230
|
-
open_ipv4 iface.addr, port
|
231
|
-
else
|
232
|
-
open_ipv6 iface.addr, port
|
233
|
-
end
|
234
|
-
}.compact
|
235
|
-
|
236
|
-
discover_query = DISCOVER_QUERY
|
237
|
-
sockets.each { |socket| multicast_send(socket, discover_query.encode) }
|
238
|
-
|
239
|
-
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
240
|
-
now = start
|
241
|
-
msgs = nil
|
242
|
-
|
243
|
-
loop do
|
244
|
-
readers, = IO.select(sockets, [], [], timeout && (timeout - (now - start)))
|
245
|
-
return msgs unless readers
|
246
|
-
readers.each do |reader|
|
247
|
-
buf, _ = reader.recvfrom 2048
|
248
|
-
msg = Resolv::DNS::Message.decode(buf)
|
249
|
-
# only yield replies to this question
|
250
|
-
if msg.question.length > 0 && msg.question.first.last == PTR
|
251
|
-
if block_given?
|
252
|
-
if :done == yield(msg)
|
253
|
-
return msg
|
254
|
-
end
|
255
|
-
else
|
256
|
-
msgs ||= []
|
257
|
-
msgs << msg
|
258
|
-
end
|
259
|
-
end
|
260
|
-
end
|
261
|
-
now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
262
|
-
end
|
263
|
-
ensure
|
264
|
-
sockets.each(&:close) if sockets
|
110
|
+
def self.discover interfaces: self.interfaces, timeout: 3, &blk
|
111
|
+
discoverer = ZeroConf::Discoverer.new(interfaces:)
|
112
|
+
discoverer.run(timeout:, &blk)
|
265
113
|
end
|
266
114
|
|
267
115
|
def self.interfaces
|
@@ -282,7 +130,4 @@ module ZeroConf
|
|
282
130
|
ipv4, ipv6 = interfaces.partition { |ifa| ifa.addr.ipv4? }
|
283
131
|
[ipv4.first, ipv6&.first].compact
|
284
132
|
end
|
285
|
-
|
286
|
-
private_class_method def self.multiquery_send sock, queries, query_id
|
287
|
-
end
|
288
133
|
end
|
data/test/client_test.rb
CHANGED
@@ -12,6 +12,7 @@ module ZeroConf
|
|
12
12
|
def test_resolve
|
13
13
|
s = make_server iface, "coolhostname"
|
14
14
|
runner = Thread.new { s.start }
|
15
|
+
Thread.pass until s.started?
|
15
16
|
found = nil
|
16
17
|
|
17
18
|
name = "coolhostname.local"
|
@@ -28,12 +29,13 @@ module ZeroConf
|
|
28
29
|
runner.join
|
29
30
|
|
30
31
|
assert found
|
31
|
-
assert_in_delta 3, took, 0.
|
32
|
+
assert_in_delta 3, took, 0.2
|
32
33
|
end
|
33
34
|
|
34
35
|
def test_resolve_returns_early
|
35
36
|
s = make_server iface, "coolhostname"
|
36
37
|
runner = Thread.new { s.start }
|
38
|
+
Thread.pass until s.started?
|
37
39
|
found = nil
|
38
40
|
|
39
41
|
name = "coolhostname.local"
|
@@ -57,6 +59,7 @@ module ZeroConf
|
|
57
59
|
def test_discover_works
|
58
60
|
s = make_server iface
|
59
61
|
runner = Thread.new { s.start }
|
62
|
+
Thread.pass until s.started?
|
60
63
|
found = nil
|
61
64
|
|
62
65
|
took = time_it do
|
@@ -71,12 +74,13 @@ module ZeroConf
|
|
71
74
|
runner.join
|
72
75
|
|
73
76
|
assert found
|
74
|
-
assert_in_delta 3, took, 0.
|
77
|
+
assert_in_delta 3, took, 0.2
|
75
78
|
end
|
76
79
|
|
77
80
|
def test_discover_return_early
|
78
81
|
s = make_server iface
|
79
82
|
runner = Thread.new { s.start }
|
83
|
+
Thread.pass until s.started?
|
80
84
|
found = nil
|
81
85
|
|
82
86
|
took = time_it do
|
@@ -97,11 +101,12 @@ module ZeroConf
|
|
97
101
|
def test_browse
|
98
102
|
s = make_server iface
|
99
103
|
runner = Thread.new { s.start }
|
104
|
+
Thread.pass until s.started?
|
100
105
|
found = nil
|
101
106
|
|
102
107
|
took = time_it do
|
103
108
|
ZeroConf.browse SERVICE do |msg|
|
104
|
-
if msg.question.find { |name, type| name.to_s == SERVICE && type ==
|
109
|
+
if msg.question.find { |name, type| name.to_s == SERVICE && type == PTR }
|
105
110
|
found = msg
|
106
111
|
end
|
107
112
|
end
|
@@ -112,17 +117,18 @@ module ZeroConf
|
|
112
117
|
|
113
118
|
assert found
|
114
119
|
assert_equal Resolv::DNS::Name.create(SERVICE_NAME + "."), found.answer.first.last.name
|
115
|
-
assert_in_delta 3, took, 0.
|
120
|
+
assert_in_delta 3, took, 0.2
|
116
121
|
end
|
117
122
|
|
118
123
|
def test_browse_returns_early
|
119
124
|
s = make_server iface
|
120
125
|
runner = Thread.new { s.start }
|
126
|
+
Thread.pass until s.started?
|
121
127
|
found = nil
|
122
128
|
|
123
129
|
took = time_it do
|
124
130
|
found = ZeroConf.browse SERVICE do |msg|
|
125
|
-
if msg.question.find { |name, type| name.to_s == SERVICE && type ==
|
131
|
+
if msg.question.find { |name, type| name.to_s == SERVICE && type == PTR }
|
126
132
|
:done
|
127
133
|
end
|
128
134
|
end
|
data/test/helper.rb
CHANGED
@@ -3,6 +3,30 @@ ENV["MT_NO_PLUGINS"] = "1"
|
|
3
3
|
require "minitest/autorun"
|
4
4
|
require "zeroconf"
|
5
5
|
|
6
|
+
Thread.abort_on_exception = true
|
7
|
+
|
8
|
+
class NotParallel
|
9
|
+
def self.start; end
|
10
|
+
def self.shutdown; end
|
11
|
+
end
|
12
|
+
|
13
|
+
Minitest.parallel_executor = NotParallel
|
14
|
+
|
15
|
+
Thread.new do
|
16
|
+
# this test suite shouldn't take any more than 60 seconds
|
17
|
+
sleep 60
|
18
|
+
|
19
|
+
Thread.list.each do |t|
|
20
|
+
next if t == Thread.current
|
21
|
+
puts "#" * 90
|
22
|
+
p t
|
23
|
+
puts t.backtrace
|
24
|
+
puts "#" * 90
|
25
|
+
end
|
26
|
+
|
27
|
+
exit!
|
28
|
+
end
|
29
|
+
|
6
30
|
module ZeroConf
|
7
31
|
class Test < Minitest::Test
|
8
32
|
SERVICE = "_test-mdns._tcp.local"
|
@@ -25,6 +49,7 @@ module ZeroConf
|
|
25
49
|
def make_listener rd, q
|
26
50
|
Thread.new do
|
27
51
|
sock = open_ipv4 Addrinfo.new(Socket.sockaddr_in(Resolv::MDNS::Port, Socket::INADDR_ANY)), Resolv::MDNS::Port
|
52
|
+
Thread.current[:started] = true
|
28
53
|
loop do
|
29
54
|
readers, = IO.select([sock, rd])
|
30
55
|
read = readers.first
|
data/test/service_test.rb
CHANGED
@@ -29,13 +29,14 @@ module ZeroConf
|
|
29
29
|
def test_unicast_service_instance_answer
|
30
30
|
s = make_server iface
|
31
31
|
runner = Thread.new { s.start }
|
32
|
+
Thread.pass until s.started?
|
32
33
|
|
33
|
-
query = Resolv::DNS::Message.new
|
34
|
-
query.add_question "tc-lan-adapter._test-mdns._tcp.local.",
|
34
|
+
query = Resolv::DNS::Message.new 10
|
35
|
+
query.add_question "tc-lan-adapter._test-mdns._tcp.local.", SRV
|
35
36
|
|
36
|
-
sock = open_ipv4 iface.addr,
|
37
|
+
sock = open_ipv4 iface.addr, Resolv::MDNS::Port
|
37
38
|
multicast_send sock, query.encode
|
38
|
-
res = Resolv::DNS::Message.decode sock
|
39
|
+
res = Resolv::DNS::Message.decode read_with_timeout(sock).first
|
39
40
|
s.stop
|
40
41
|
runner.join
|
41
42
|
|
@@ -51,7 +52,38 @@ module ZeroConf
|
|
51
52
|
10,
|
52
53
|
Resolv::DNS::Resource::IN::TXT.new(*s.text)
|
53
54
|
expected.add_answer s.service_name, 10, Resolv::DNS::Resource::IN::SRV.new(0, 0, s.service_port, s.qualified_host)
|
54
|
-
expected.add_question s.service_name,
|
55
|
+
expected.add_question s.service_name, MDNS::Announce::IN::SRV
|
56
|
+
|
57
|
+
assert_equal expected, res
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_legacy_unicast_service_instance_answer
|
61
|
+
s = make_server iface
|
62
|
+
runner = Thread.new { s.start }
|
63
|
+
Thread.pass until s.started?
|
64
|
+
|
65
|
+
query = Resolv::DNS::Message.new 10
|
66
|
+
query.add_question "tc-lan-adapter._test-mdns._tcp.local.", Resolv::DNS::Resource::IN::SRV
|
67
|
+
|
68
|
+
sock = open_ipv4 iface.addr, 0
|
69
|
+
multicast_send sock, query.encode
|
70
|
+
res = Resolv::DNS::Message.decode read_with_timeout(sock).first
|
71
|
+
s.stop
|
72
|
+
runner.join
|
73
|
+
|
74
|
+
expected = Resolv::DNS::Message.new(10)
|
75
|
+
expected.qr = 1
|
76
|
+
expected.aa = 1
|
77
|
+
|
78
|
+
expected.add_additional s.qualified_host,
|
79
|
+
10,
|
80
|
+
Resolv::DNS::Resource::IN::A.new(iface.addr.ip_address)
|
81
|
+
|
82
|
+
expected.add_additional s.service_name,
|
83
|
+
10,
|
84
|
+
Resolv::DNS::Resource::IN::TXT.new(*s.text)
|
85
|
+
expected.add_answer s.service_name, 10, Resolv::DNS::Resource::IN::SRV.new(0, 0, s.service_port, s.qualified_host)
|
86
|
+
expected.add_question s.service_name, Resolv::DNS::Resource::IN::SRV
|
55
87
|
|
56
88
|
assert_equal expected, res
|
57
89
|
end
|
@@ -63,10 +95,11 @@ module ZeroConf
|
|
63
95
|
listen = make_listener rd, q
|
64
96
|
s = make_server iface
|
65
97
|
server = Thread.new { s.start }
|
98
|
+
Thread.pass until s.started?
|
66
99
|
|
67
|
-
query = Resolv::DNS::Message.new
|
100
|
+
query = Resolv::DNS::Message.new 10
|
68
101
|
query.add_question "_services._dns-sd._udp.local.", Resolv::DNS::Resource::IN::PTR
|
69
|
-
sock = open_ipv4 iface.addr,
|
102
|
+
sock = open_ipv4 iface.addr, Resolv::MDNS::Port
|
70
103
|
multicast_send sock, query.encode
|
71
104
|
|
72
105
|
while res = q.pop
|
@@ -91,32 +124,91 @@ module ZeroConf
|
|
91
124
|
end
|
92
125
|
|
93
126
|
def test_announcement
|
94
|
-
|
95
|
-
|
127
|
+
# FIXME: this test should be converted to an integration test.
|
128
|
+
# We need to make a client listen for the announcement and then decode that
|
96
129
|
service = Service.new "_test-mdns._tcp.local.", 42424
|
130
|
+
|
131
|
+
msg = Resolv::DNS::Message.new(0)
|
132
|
+
msg.qr = 1
|
133
|
+
msg.aa = 1
|
134
|
+
|
135
|
+
msg.add_additional service.service_name, 60, MDNS::Announce::IN::SRV.new(0, 0, service.service_port, service.qualified_host)
|
136
|
+
|
137
|
+
service.service_interfaces.each do |iface|
|
138
|
+
if iface.addr.ipv4?
|
139
|
+
msg.add_additional service.qualified_host,
|
140
|
+
60,
|
141
|
+
MDNS::Announce::IN::A.new(iface.addr.ip_address)
|
142
|
+
else
|
143
|
+
msg.add_additional service.qualified_host,
|
144
|
+
60,
|
145
|
+
MDNS::Announce::IN::AAAA.new(iface.addr.ip_address)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
if service.text
|
150
|
+
msg.add_additional service.service_name,
|
151
|
+
60,
|
152
|
+
MDNS::Announce::IN::TXT.new(*service.text)
|
153
|
+
end
|
154
|
+
|
155
|
+
msg.add_answer service.service,
|
156
|
+
60,
|
157
|
+
Resolv::DNS::Resource::IN::PTR.new(Resolv::DNS::Name.create(service.service_name))
|
158
|
+
|
97
159
|
assert_equal msg, service.announcement
|
98
160
|
end
|
99
161
|
|
100
162
|
def test_disconnect
|
101
|
-
|
102
|
-
|
163
|
+
# FIXME: this test should be converted to an integration test.
|
164
|
+
# We need to make a client listen for the disconnect and then decode that
|
103
165
|
s = make_server iface
|
104
|
-
|
166
|
+
|
167
|
+
msg = Resolv::DNS::Message.new(0)
|
168
|
+
msg.qr = 1
|
169
|
+
msg.aa = 1
|
170
|
+
|
171
|
+
msg.add_additional s.service_name, 0, Resolv::DNS::Resource::IN::SRV.new(0, 0, s.service_port, s.qualified_host)
|
172
|
+
|
173
|
+
s.service_interfaces.each do |iface|
|
174
|
+
if iface.addr.ipv4?
|
175
|
+
msg.add_additional s.qualified_host,
|
176
|
+
0,
|
177
|
+
Resolv::DNS::Resource::IN::A.new(iface.addr.ip_address)
|
178
|
+
else
|
179
|
+
msg.add_additional s.qualified_host,
|
180
|
+
0,
|
181
|
+
Resolv::DNS::Resource::IN::AAAA.new(iface.addr.ip_address)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
if s.text
|
186
|
+
msg.add_additional s.service_name,
|
187
|
+
0,
|
188
|
+
Resolv::DNS::Resource::IN::TXT.new(*s.text)
|
189
|
+
end
|
190
|
+
|
191
|
+
msg.add_answer s.service,
|
192
|
+
0,
|
193
|
+
Resolv::DNS::Resource::IN::PTR.new(Resolv::DNS::Name.create(s.service_name))
|
194
|
+
|
195
|
+
assert_equal msg, s.disconnect_msg
|
105
196
|
end
|
106
197
|
|
107
198
|
def test_dnssd_unicast_answer
|
108
199
|
s = make_server iface
|
109
200
|
runner = Thread.new { s.start }
|
201
|
+
Thread.pass until s.started?
|
110
202
|
|
111
|
-
query = Resolv::DNS::Message.new
|
112
|
-
query.add_question "_services._dns-sd._udp.local.",
|
203
|
+
query = Resolv::DNS::Message.new 10
|
204
|
+
query.add_question "_services._dns-sd._udp.local.", PTR
|
113
205
|
|
114
|
-
sock = open_ipv4 iface.addr,
|
206
|
+
sock = open_ipv4 iface.addr, Resolv::MDNS::Port
|
115
207
|
multicast_send sock, query.encode
|
116
208
|
|
117
209
|
res = nil
|
118
210
|
loop do
|
119
|
-
buf, from = sock
|
211
|
+
buf, from = read_with_timeout sock
|
120
212
|
res = Resolv::DNS::Message.decode buf
|
121
213
|
if from.last == iface.addr.ip_address
|
122
214
|
break if res.answer.find { |name, ttl, data| data.name.to_s == SERVICE }
|
@@ -133,29 +225,66 @@ module ZeroConf
|
|
133
225
|
expected.add_answer DISCOVERY_NAME, 10,
|
134
226
|
Resolv::DNS::Resource::IN::PTR.new(Resolv::DNS::Name.create(s.service))
|
135
227
|
|
136
|
-
expected.add_question DISCOVERY_NAME,
|
228
|
+
expected.add_question DISCOVERY_NAME, PTR
|
229
|
+
|
230
|
+
assert_equal expected, res
|
231
|
+
end
|
232
|
+
|
233
|
+
def test_dnssd_legacy_unicast_answer
|
234
|
+
s = make_server iface
|
235
|
+
runner = Thread.new { s.start }
|
236
|
+
Thread.pass until s.started?
|
237
|
+
|
238
|
+
query = Resolv::DNS::Message.new 10
|
239
|
+
query.add_question "_services._dns-sd._udp.local.", Resolv::DNS::Resource::IN::PTR
|
240
|
+
|
241
|
+
sock = open_ipv4 iface.addr, 0
|
242
|
+
multicast_send sock, query.encode
|
243
|
+
|
244
|
+
res = nil
|
245
|
+
loop do
|
246
|
+
buf, from = read_with_timeout sock
|
247
|
+
res = Resolv::DNS::Message.decode buf
|
248
|
+
if from.last == iface.addr.ip_address
|
249
|
+
break if res.answer.find { |name, ttl, data| data.name.to_s == SERVICE }
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
s.stop
|
254
|
+
runner.join
|
255
|
+
|
256
|
+
expected = Resolv::DNS::Message.new(10)
|
257
|
+
expected.qr = 1
|
258
|
+
expected.aa = 1
|
259
|
+
|
260
|
+
expected.add_answer DISCOVERY_NAME, 10,
|
261
|
+
Resolv::DNS::Resource::IN::PTR.new(Resolv::DNS::Name.create(s.service))
|
262
|
+
|
263
|
+
expected.add_question DISCOVERY_NAME, Resolv::DNS::Resource::IN::PTR
|
137
264
|
|
138
265
|
assert_equal expected, res
|
139
266
|
end
|
140
267
|
|
141
268
|
def test_service_multicast_answer
|
142
|
-
q = Queue.new
|
269
|
+
q = Thread::Queue.new
|
143
270
|
rd, wr = IO.pipe
|
144
271
|
|
145
272
|
listen = make_listener rd, q
|
146
273
|
s = make_server iface
|
147
274
|
server = Thread.new { s.start }
|
275
|
+
Thread.pass until s.started? && listen[:started]
|
148
276
|
|
149
|
-
query = Resolv::DNS::Message.new
|
277
|
+
query = Resolv::DNS::Message.new 10
|
150
278
|
query.add_question "_test-mdns._tcp.local.", Resolv::DNS::Resource::IN::PTR
|
151
|
-
sock = open_ipv4 iface.addr,
|
279
|
+
sock = open_ipv4 iface.addr, Resolv::MDNS::Port
|
152
280
|
multicast_send sock, query.encode
|
153
281
|
|
154
282
|
service = Resolv::DNS::Name.create s.service
|
155
283
|
service_name = Resolv::DNS::Name.create s.service_name
|
156
284
|
|
157
285
|
while res = q.pop
|
158
|
-
if res.answer.find { |name, ttl, data| name == service && data.name == service_name }
|
286
|
+
if res.answer.find { |name, ttl, data| name == service && data.name == service_name } &&
|
287
|
+
res.additional.find { |_, _, data| ZeroConf::MDNS::Announce::IN::SRV == data.class }
|
159
288
|
wr.write "x"
|
160
289
|
break
|
161
290
|
end
|
@@ -183,19 +312,20 @@ module ZeroConf
|
|
183
312
|
60,
|
184
313
|
Resolv::DNS::Resource::IN::PTR.new(Resolv::DNS::Name.create(s.service_name))
|
185
314
|
|
186
|
-
assert_equal msg
|
315
|
+
assert_equal msg, res
|
187
316
|
end
|
188
317
|
|
189
318
|
def test_service_unicast_answer
|
190
319
|
s = make_server iface
|
191
320
|
runner = Thread.new { s.start }
|
321
|
+
Thread.pass until s.started?
|
192
322
|
|
193
|
-
query = Resolv::DNS::Message.new
|
194
|
-
query.add_question "_test-mdns._tcp.local.",
|
323
|
+
query = Resolv::DNS::Message.new 10
|
324
|
+
query.add_question "_test-mdns._tcp.local.", PTR
|
195
325
|
|
196
|
-
sock = open_ipv4 iface.addr,
|
326
|
+
sock = open_ipv4 iface.addr, Resolv::MDNS::Port
|
197
327
|
multicast_send sock, query.encode
|
198
|
-
res = Resolv::DNS::Message.decode sock
|
328
|
+
res = Resolv::DNS::Message.decode read_with_timeout(sock).first
|
199
329
|
s.stop
|
200
330
|
runner.join
|
201
331
|
|
@@ -214,11 +344,46 @@ module ZeroConf
|
|
214
344
|
expected.add_answer s.service,
|
215
345
|
10,
|
216
346
|
Resolv::DNS::Resource::IN::PTR.new(Resolv::DNS::Name.create(s.service_name))
|
217
|
-
expected.add_question s.service,
|
347
|
+
expected.add_question s.service, PTR
|
348
|
+
|
349
|
+
assert_equal expected, res
|
350
|
+
end
|
351
|
+
|
352
|
+
def test_service_legacy_unicast_answer
|
353
|
+
s = make_server iface
|
354
|
+
runner = Thread.new { s.start }
|
355
|
+
Thread.pass until s.started?
|
356
|
+
|
357
|
+
query = Resolv::DNS::Message.new 10
|
358
|
+
query.add_question "_test-mdns._tcp.local.", PTR
|
359
|
+
|
360
|
+
sock = open_ipv4 iface.addr, 0
|
361
|
+
multicast_send sock, query.encode
|
362
|
+
res = Resolv::DNS::Message.decode read_with_timeout(sock).first
|
363
|
+
s.stop
|
364
|
+
runner.join
|
365
|
+
|
366
|
+
expected = Resolv::DNS::Message.new(10)
|
367
|
+
expected.qr = 1
|
368
|
+
expected.aa = 1
|
369
|
+
|
370
|
+
expected.add_additional s.service_name, 10, Resolv::DNS::Resource::IN::SRV.new(0, 0, s.service_port, s.qualified_host)
|
371
|
+
expected.add_additional s.qualified_host,
|
372
|
+
10,
|
373
|
+
Resolv::DNS::Resource::IN::A.new(iface.addr.ip_address)
|
374
|
+
|
375
|
+
expected.add_additional s.service_name,
|
376
|
+
10,
|
377
|
+
Resolv::DNS::Resource::IN::TXT.new(*s.text)
|
378
|
+
expected.add_answer s.service,
|
379
|
+
10,
|
380
|
+
Resolv::DNS::Resource::IN::PTR.new(Resolv::DNS::Name.create(s.service_name))
|
381
|
+
expected.add_question s.service, Resolv::DNS::Resource::IN::PTR
|
218
382
|
|
219
383
|
assert_equal expected, res
|
220
384
|
end
|
221
385
|
|
386
|
+
|
222
387
|
def test_multicast_service_instance_answer
|
223
388
|
q = Queue.new
|
224
389
|
rd, wr = IO.pipe
|
@@ -226,10 +391,11 @@ module ZeroConf
|
|
226
391
|
listen = make_listener rd, q
|
227
392
|
s = make_server iface
|
228
393
|
server = Thread.new { s.start }
|
394
|
+
Thread.pass until s.started?
|
229
395
|
|
230
|
-
query = Resolv::DNS::Message.new
|
396
|
+
query = Resolv::DNS::Message.new 10
|
231
397
|
query.add_question "tc-lan-adapter._test-mdns._tcp.local.", Resolv::DNS::Resource::IN::PTR
|
232
|
-
sock = open_ipv4 iface.addr,
|
398
|
+
sock = open_ipv4 iface.addr, Resolv::MDNS::Port
|
233
399
|
multicast_send sock, query.encode
|
234
400
|
|
235
401
|
service_name = Resolv::DNS::Name.create s.service_name
|
@@ -266,13 +432,14 @@ module ZeroConf
|
|
266
432
|
def test_unicast_name_lookup
|
267
433
|
s = make_server iface
|
268
434
|
runner = Thread.new { s.start }
|
435
|
+
Thread.pass until s.started?
|
269
436
|
|
270
|
-
query = Resolv::DNS::Message.new
|
271
|
-
query.add_question "tc-lan-adapter.local.",
|
437
|
+
query = Resolv::DNS::Message.new 10
|
438
|
+
query.add_question "tc-lan-adapter.local.", A
|
272
439
|
|
273
|
-
sock = open_ipv4 iface.addr,
|
440
|
+
sock = open_ipv4 iface.addr, Resolv::MDNS::Port
|
274
441
|
multicast_send sock, query.encode
|
275
|
-
res = Resolv::DNS::Message.decode sock
|
442
|
+
res = Resolv::DNS::Message.decode read_with_timeout(sock).first
|
276
443
|
s.stop
|
277
444
|
runner.join
|
278
445
|
|
@@ -287,7 +454,37 @@ module ZeroConf
|
|
287
454
|
Resolv::DNS::Resource::IN::TXT.new(*s.text)
|
288
455
|
|
289
456
|
expected.add_question s.qualified_host,
|
290
|
-
|
457
|
+
MDNS::Announce::IN::A
|
458
|
+
|
459
|
+
assert_equal expected, res
|
460
|
+
end
|
461
|
+
|
462
|
+
def test_legacy_unicast_name_lookup
|
463
|
+
s = make_server iface
|
464
|
+
runner = Thread.new { s.start }
|
465
|
+
Thread.pass until s.started?
|
466
|
+
|
467
|
+
query = Resolv::DNS::Message.new 10
|
468
|
+
query.add_question "tc-lan-adapter.local.", Resolv::DNS::Resource::IN::A
|
469
|
+
|
470
|
+
sock = open_ipv4 iface.addr, 0
|
471
|
+
multicast_send sock, query.encode
|
472
|
+
res = Resolv::DNS::Message.decode read_with_timeout(sock).first
|
473
|
+
s.stop
|
474
|
+
runner.join
|
475
|
+
|
476
|
+
expected = Resolv::DNS::Message.new(10)
|
477
|
+
expected.qr = 1
|
478
|
+
expected.aa = 1
|
479
|
+
|
480
|
+
expected.add_answer s.qualified_host, 10, Resolv::DNS::Resource::IN::A.new(iface.addr.ip_address)
|
481
|
+
|
482
|
+
expected.add_additional s.service_name,
|
483
|
+
10,
|
484
|
+
Resolv::DNS::Resource::IN::TXT.new(*s.text)
|
485
|
+
|
486
|
+
expected.add_question s.qualified_host,
|
487
|
+
Resolv::DNS::Resource::IN::A
|
291
488
|
|
292
489
|
assert_equal expected, res
|
293
490
|
end
|
@@ -299,10 +496,11 @@ module ZeroConf
|
|
299
496
|
listen = make_listener rd, q
|
300
497
|
s = make_server iface
|
301
498
|
server = Thread.new { s.start }
|
499
|
+
Thread.pass until s.started?
|
302
500
|
|
303
|
-
query = Resolv::DNS::Message.new
|
501
|
+
query = Resolv::DNS::Message.new 10
|
304
502
|
query.add_question "tc-lan-adapter.local.", Resolv::DNS::Resource::IN::A
|
305
|
-
sock = open_ipv4 iface.addr,
|
503
|
+
sock = open_ipv4 iface.addr, Resolv::MDNS::Port
|
306
504
|
multicast_send sock, query.encode
|
307
505
|
|
308
506
|
host = Resolv::DNS::Name.create s.qualified_host
|
@@ -330,5 +528,10 @@ module ZeroConf
|
|
330
528
|
|
331
529
|
assert_equal expected, res
|
332
530
|
end
|
531
|
+
|
532
|
+
def read_with_timeout sock
|
533
|
+
return unless sock.wait_readable(3)
|
534
|
+
sock.recvfrom(2048)
|
535
|
+
end
|
333
536
|
end
|
334
537
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: zeroconf
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Aaron Patterson
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-08-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: resolv
|
@@ -58,12 +58,17 @@ executables: []
|
|
58
58
|
extensions: []
|
59
59
|
extra_rdoc_files: []
|
60
60
|
files:
|
61
|
+
- ".github/workflows/ci.yml"
|
61
62
|
- CODE_OF_CONDUCT.md
|
62
63
|
- Gemfile
|
63
64
|
- LICENSE
|
64
65
|
- README.md
|
65
66
|
- Rakefile
|
66
67
|
- lib/zeroconf.rb
|
68
|
+
- lib/zeroconf/browser.rb
|
69
|
+
- lib/zeroconf/client.rb
|
70
|
+
- lib/zeroconf/discoverer.rb
|
71
|
+
- lib/zeroconf/resolver.rb
|
67
72
|
- lib/zeroconf/service.rb
|
68
73
|
- lib/zeroconf/utils.rb
|
69
74
|
- lib/zeroconf/version.rb
|
@@ -90,7 +95,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
90
95
|
- !ruby/object:Gem::Version
|
91
96
|
version: '0'
|
92
97
|
requirements: []
|
93
|
-
rubygems_version: 3.5.
|
98
|
+
rubygems_version: 3.5.11
|
94
99
|
signing_key:
|
95
100
|
specification_version: 4
|
96
101
|
summary: Multicast DNS client and server
|