zeroconf 1.0.0 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|