zeroconf 1.0.0
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 +7 -0
- data/CODE_OF_CONDUCT.md +77 -0
- data/Gemfile +3 -0
- data/LICENSE +201 -0
- data/README.md +129 -0
- data/Rakefile +9 -0
- data/lib/zeroconf/service.rb +399 -0
- data/lib/zeroconf/utils.rb +65 -0
- data/lib/zeroconf/version.rb +3 -0
- data/lib/zeroconf.rb +288 -0
- data/test/client_test.rb +139 -0
- data/test/helper.rb +42 -0
- data/test/service_test.rb +334 -0
- data/zeroconf.gemspec +18 -0
- metadata +100 -0
@@ -0,0 +1,334 @@
|
|
1
|
+
require "helper"
|
2
|
+
|
3
|
+
module ZeroConf
|
4
|
+
class ServiceTest < Test
|
5
|
+
include ZeroConf::Utils
|
6
|
+
|
7
|
+
attr_reader :iface
|
8
|
+
|
9
|
+
def setup
|
10
|
+
super
|
11
|
+
@iface = ZeroConf.interfaces.find_all { |x| x.addr.ipv4? }.first
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_service_info
|
15
|
+
s = Service.new "_test-mdns._tcp.local.",
|
16
|
+
42424,
|
17
|
+
"tc-lan-adapter",
|
18
|
+
service_interfaces: [iface]
|
19
|
+
|
20
|
+
assert_equal "_test-mdns._tcp.local.", s.service
|
21
|
+
assert_equal 42424, s.service_port
|
22
|
+
assert_equal "tc-lan-adapter", s.hostname
|
23
|
+
assert_equal [iface], s.service_interfaces
|
24
|
+
assert_equal "#{s.hostname}.#{s.service}", s.service_name
|
25
|
+
assert_equal "#{s.hostname}.local.", s.qualified_host
|
26
|
+
assert_equal [""], s.text
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_unicast_service_instance_answer
|
30
|
+
s = make_server iface
|
31
|
+
runner = Thread.new { s.start }
|
32
|
+
|
33
|
+
query = Resolv::DNS::Message.new 0
|
34
|
+
query.add_question "tc-lan-adapter._test-mdns._tcp.local.", ZeroConf::SRV
|
35
|
+
|
36
|
+
sock = open_ipv4 iface.addr, 0
|
37
|
+
multicast_send sock, query.encode
|
38
|
+
res = Resolv::DNS::Message.decode sock.recvfrom(2048).first
|
39
|
+
s.stop
|
40
|
+
runner.join
|
41
|
+
|
42
|
+
expected = Resolv::DNS::Message.new(0)
|
43
|
+
expected.qr = 1
|
44
|
+
expected.aa = 1
|
45
|
+
|
46
|
+
expected.add_additional s.qualified_host,
|
47
|
+
10,
|
48
|
+
Resolv::DNS::Resource::IN::A.new(iface.addr.ip_address)
|
49
|
+
|
50
|
+
expected.add_additional s.service_name,
|
51
|
+
10,
|
52
|
+
Resolv::DNS::Resource::IN::TXT.new(*s.text)
|
53
|
+
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, ZeroConf::MDNS::Announce::IN::SRV
|
55
|
+
|
56
|
+
assert_equal expected, res
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_multicast_discover
|
60
|
+
q = Queue.new
|
61
|
+
rd, wr = IO.pipe
|
62
|
+
|
63
|
+
listen = make_listener rd, q
|
64
|
+
s = make_server iface
|
65
|
+
server = Thread.new { s.start }
|
66
|
+
|
67
|
+
query = Resolv::DNS::Message.new 0
|
68
|
+
query.add_question "_services._dns-sd._udp.local.", Resolv::DNS::Resource::IN::PTR
|
69
|
+
sock = open_ipv4 iface.addr, 0
|
70
|
+
multicast_send sock, query.encode
|
71
|
+
|
72
|
+
while res = q.pop
|
73
|
+
if res.answer.find { |name, ttl, data| name.to_s == "_services._dns-sd._udp.local" && data.name.to_s == SERVICE }
|
74
|
+
wr.write "x"
|
75
|
+
break
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
listen.join
|
80
|
+
s.stop
|
81
|
+
server.join
|
82
|
+
|
83
|
+
msg = Resolv::DNS::Message.new(0)
|
84
|
+
msg.qr = 1
|
85
|
+
msg.aa = 1
|
86
|
+
|
87
|
+
msg.add_answer DISCOVERY_NAME, 60,
|
88
|
+
Resolv::DNS::Resource::IN::PTR.new(Resolv::DNS::Name.create(s.service))
|
89
|
+
|
90
|
+
assert_equal msg, res
|
91
|
+
end
|
92
|
+
|
93
|
+
def test_announcement
|
94
|
+
ann = "\x00\x00\x84\x00\x00\x00\x00\x01\x00\x00\x00\x04\n_test-mdns\x04_tcp\x05local\x00\x00\f\x00\x01\x00\x00\x00<\x00\x15\x0Etc-lan-adapter\x03lan\xC0\f\xC0-\x00!\x80\x01\x00\x00\x00<\x00 \x00\x00\x00\x00\xA5\xB8\x0Etc-lan-adapter\x03lan\x05local\x00\xC0T\x00\x01\x80\x01\x00\x00\x00<\x00\x04\n\x00\x01\x95\xC0T\x00\x1C\x80\x01\x00\x00\x00<\x00\x10\xFD\xDA\x85k\tL\x00\x00\x10\xF6\x892\xEA\xBB\\H\xC0-\x00\x10\x80\x01\x00\x00\x00<\x00\x01\x00".b
|
95
|
+
msg = Resolv::DNS::Message.decode ann
|
96
|
+
service = Service.new "_test-mdns._tcp.local.", 42424
|
97
|
+
assert_equal msg, service.announcement
|
98
|
+
end
|
99
|
+
|
100
|
+
def test_disconnect
|
101
|
+
data = "\x00\x00\x84\x00\x00\x00\x00\x01\x00\x00\x00\x03\n_test-mdns\x04_tcp\x05local\x00\x00\f\x00\x01\x00\x00\x00\x00\x00\x11\x0Etc-lan-adapter\xC0\f\xC0-\x00!\x00\x01\x00\x00\x00\x00\x00\x1C\x00\x00\x00\x00\xA5\xB8\x0Etc-lan-adapter\x05local\x00\xC0P\x00\x01\x00\x01\x00\x00\x00\x00\x00\x04\n\x00\x01\x95\xC0-\x00\x10\x00\x01\x00\x00\x00\x00\x00\x13\x06test=1\vother=value".b
|
102
|
+
m = Resolv::DNS::Message.decode data
|
103
|
+
s = make_server iface
|
104
|
+
assert_equal m, s.disconnect_msg
|
105
|
+
end
|
106
|
+
|
107
|
+
def test_dnssd_unicast_answer
|
108
|
+
s = make_server iface
|
109
|
+
runner = Thread.new { s.start }
|
110
|
+
|
111
|
+
query = Resolv::DNS::Message.new 0
|
112
|
+
query.add_question "_services._dns-sd._udp.local.", ZeroConf::PTR
|
113
|
+
|
114
|
+
sock = open_ipv4 iface.addr, 0
|
115
|
+
multicast_send sock, query.encode
|
116
|
+
|
117
|
+
res = nil
|
118
|
+
loop do
|
119
|
+
buf, from = sock.recvfrom(2048)
|
120
|
+
res = Resolv::DNS::Message.decode buf
|
121
|
+
if from.last == iface.addr.ip_address
|
122
|
+
break if res.answer.find { |name, ttl, data| data.name.to_s == SERVICE }
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
s.stop
|
127
|
+
runner.join
|
128
|
+
|
129
|
+
expected = Resolv::DNS::Message.new(0)
|
130
|
+
expected.qr = 1
|
131
|
+
expected.aa = 1
|
132
|
+
|
133
|
+
expected.add_answer DISCOVERY_NAME, 10,
|
134
|
+
Resolv::DNS::Resource::IN::PTR.new(Resolv::DNS::Name.create(s.service))
|
135
|
+
|
136
|
+
expected.add_question DISCOVERY_NAME, ZeroConf::PTR
|
137
|
+
|
138
|
+
assert_equal expected, res
|
139
|
+
end
|
140
|
+
|
141
|
+
def test_service_multicast_answer
|
142
|
+
q = Queue.new
|
143
|
+
rd, wr = IO.pipe
|
144
|
+
|
145
|
+
listen = make_listener rd, q
|
146
|
+
s = make_server iface
|
147
|
+
server = Thread.new { s.start }
|
148
|
+
|
149
|
+
query = Resolv::DNS::Message.new 0
|
150
|
+
query.add_question "_test-mdns._tcp.local.", Resolv::DNS::Resource::IN::PTR
|
151
|
+
sock = open_ipv4 iface.addr, 0
|
152
|
+
multicast_send sock, query.encode
|
153
|
+
|
154
|
+
service = Resolv::DNS::Name.create s.service
|
155
|
+
service_name = Resolv::DNS::Name.create s.service_name
|
156
|
+
|
157
|
+
while res = q.pop
|
158
|
+
if res.answer.find { |name, ttl, data| name == service && data.name == service_name }
|
159
|
+
wr.write "x"
|
160
|
+
break
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
listen.join
|
165
|
+
s.stop
|
166
|
+
server.join
|
167
|
+
|
168
|
+
msg = Resolv::DNS::Message.new(0)
|
169
|
+
msg.qr = 1
|
170
|
+
msg.aa = 1
|
171
|
+
|
172
|
+
msg.add_additional s.service_name, 60, MDNS::Announce::IN::SRV.new(0, 0, s.service_port, s.qualified_host)
|
173
|
+
|
174
|
+
msg.add_additional s.qualified_host,
|
175
|
+
60,
|
176
|
+
MDNS::Announce::IN::A.new(iface.addr.ip_address)
|
177
|
+
|
178
|
+
msg.add_additional s.service_name,
|
179
|
+
60,
|
180
|
+
MDNS::Announce::IN::TXT.new(*s.text)
|
181
|
+
|
182
|
+
msg.add_answer s.service,
|
183
|
+
60,
|
184
|
+
Resolv::DNS::Resource::IN::PTR.new(Resolv::DNS::Name.create(s.service_name))
|
185
|
+
|
186
|
+
assert_equal msg.encode, res.encode
|
187
|
+
end
|
188
|
+
|
189
|
+
def test_service_unicast_answer
|
190
|
+
s = make_server iface
|
191
|
+
runner = Thread.new { s.start }
|
192
|
+
|
193
|
+
query = Resolv::DNS::Message.new 0
|
194
|
+
query.add_question "_test-mdns._tcp.local.", ZeroConf::PTR
|
195
|
+
|
196
|
+
sock = open_ipv4 iface.addr, 0
|
197
|
+
multicast_send sock, query.encode
|
198
|
+
res = Resolv::DNS::Message.decode sock.recvfrom(2048).first
|
199
|
+
s.stop
|
200
|
+
runner.join
|
201
|
+
|
202
|
+
expected = Resolv::DNS::Message.new(0)
|
203
|
+
expected.qr = 1
|
204
|
+
expected.aa = 1
|
205
|
+
|
206
|
+
expected.add_additional s.service_name, 10, Resolv::DNS::Resource::IN::SRV.new(0, 0, s.service_port, s.qualified_host)
|
207
|
+
expected.add_additional s.qualified_host,
|
208
|
+
10,
|
209
|
+
Resolv::DNS::Resource::IN::A.new(iface.addr.ip_address)
|
210
|
+
|
211
|
+
expected.add_additional s.service_name,
|
212
|
+
10,
|
213
|
+
Resolv::DNS::Resource::IN::TXT.new(*s.text)
|
214
|
+
expected.add_answer s.service,
|
215
|
+
10,
|
216
|
+
Resolv::DNS::Resource::IN::PTR.new(Resolv::DNS::Name.create(s.service_name))
|
217
|
+
expected.add_question s.service, ZeroConf::PTR
|
218
|
+
|
219
|
+
assert_equal expected, res
|
220
|
+
end
|
221
|
+
|
222
|
+
def test_multicast_service_instance_answer
|
223
|
+
q = Queue.new
|
224
|
+
rd, wr = IO.pipe
|
225
|
+
|
226
|
+
listen = make_listener rd, q
|
227
|
+
s = make_server iface
|
228
|
+
server = Thread.new { s.start }
|
229
|
+
|
230
|
+
query = Resolv::DNS::Message.new 0
|
231
|
+
query.add_question "tc-lan-adapter._test-mdns._tcp.local.", Resolv::DNS::Resource::IN::PTR
|
232
|
+
sock = open_ipv4 iface.addr, 0
|
233
|
+
multicast_send sock, query.encode
|
234
|
+
|
235
|
+
service_name = Resolv::DNS::Name.create s.service_name
|
236
|
+
|
237
|
+
while res = q.pop
|
238
|
+
if res.answer.find { |name, ttl, data| name == service_name }
|
239
|
+
wr.write "x"
|
240
|
+
break
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
listen.join
|
245
|
+
s.stop
|
246
|
+
server.join
|
247
|
+
|
248
|
+
msg = Resolv::DNS::Message.new(0)
|
249
|
+
msg.qr = 1
|
250
|
+
msg.aa = 1
|
251
|
+
|
252
|
+
msg.add_answer s.service_name, 60, Resolv::DNS::Resource::IN::SRV.new(0, 0, s.service_port, s.qualified_host)
|
253
|
+
|
254
|
+
msg.add_additional s.qualified_host,
|
255
|
+
60,
|
256
|
+
Resolv::DNS::Resource::IN::A.new(iface.addr.ip_address)
|
257
|
+
|
258
|
+
msg.add_additional service_name,
|
259
|
+
60,
|
260
|
+
Resolv::DNS::Resource::IN::TXT.new(*s.text)
|
261
|
+
|
262
|
+
|
263
|
+
assert_equal msg, res
|
264
|
+
end
|
265
|
+
|
266
|
+
def test_unicast_name_lookup
|
267
|
+
s = make_server iface
|
268
|
+
runner = Thread.new { s.start }
|
269
|
+
|
270
|
+
query = Resolv::DNS::Message.new 0
|
271
|
+
query.add_question "tc-lan-adapter.local.", ZeroConf::A
|
272
|
+
|
273
|
+
sock = open_ipv4 iface.addr, 0
|
274
|
+
multicast_send sock, query.encode
|
275
|
+
res = Resolv::DNS::Message.decode sock.recvfrom(2048).first
|
276
|
+
s.stop
|
277
|
+
runner.join
|
278
|
+
|
279
|
+
expected = Resolv::DNS::Message.new(0)
|
280
|
+
expected.qr = 1
|
281
|
+
expected.aa = 1
|
282
|
+
|
283
|
+
expected.add_answer s.qualified_host, 10, Resolv::DNS::Resource::IN::A.new(iface.addr.ip_address)
|
284
|
+
|
285
|
+
expected.add_additional s.service_name,
|
286
|
+
10,
|
287
|
+
Resolv::DNS::Resource::IN::TXT.new(*s.text)
|
288
|
+
|
289
|
+
expected.add_question s.qualified_host,
|
290
|
+
ZeroConf::MDNS::Announce::IN::A
|
291
|
+
|
292
|
+
assert_equal expected, res
|
293
|
+
end
|
294
|
+
|
295
|
+
def test_multicast_name
|
296
|
+
q = Queue.new
|
297
|
+
rd, wr = IO.pipe
|
298
|
+
|
299
|
+
listen = make_listener rd, q
|
300
|
+
s = make_server iface
|
301
|
+
server = Thread.new { s.start }
|
302
|
+
|
303
|
+
query = Resolv::DNS::Message.new 0
|
304
|
+
query.add_question "tc-lan-adapter.local.", Resolv::DNS::Resource::IN::A
|
305
|
+
sock = open_ipv4 iface.addr, 0
|
306
|
+
multicast_send sock, query.encode
|
307
|
+
|
308
|
+
host = Resolv::DNS::Name.create s.qualified_host
|
309
|
+
|
310
|
+
while res = q.pop
|
311
|
+
if res.answer.find { |name, ttl, data| name == host }
|
312
|
+
wr.write "x"
|
313
|
+
break
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
listen.join
|
318
|
+
s.stop
|
319
|
+
server.join
|
320
|
+
|
321
|
+
expected = Resolv::DNS::Message.new(0)
|
322
|
+
expected.qr = 1
|
323
|
+
expected.aa = 1
|
324
|
+
|
325
|
+
expected.add_answer s.qualified_host, 60, Resolv::DNS::Resource::IN::A.new(iface.addr.ip_address)
|
326
|
+
|
327
|
+
expected.add_additional s.service_name,
|
328
|
+
60,
|
329
|
+
Resolv::DNS::Resource::IN::TXT.new(*s.text)
|
330
|
+
|
331
|
+
assert_equal expected, res
|
332
|
+
end
|
333
|
+
end
|
334
|
+
end
|
data/zeroconf.gemspec
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require_relative "lib/zeroconf/version"
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = "zeroconf"
|
5
|
+
s.version = ZeroConf::VERSION
|
6
|
+
s.summary = "Multicast DNS client and server"
|
7
|
+
s.description = "This is a multicast DNS client and server written in Ruby"
|
8
|
+
s.authors = [ "Aaron Patterson" ]
|
9
|
+
s.email = "tenderlove@ruby-lang.org"
|
10
|
+
s.files = `git ls-files -z`.split("\x0")
|
11
|
+
s.test_files = s.files.grep(%r{^test/})
|
12
|
+
s.homepage = "https://github.com/tenderlove/zeroconf"
|
13
|
+
s.license = "Apache-2.0"
|
14
|
+
|
15
|
+
s.add_dependency("resolv", "~> 0.3.0")
|
16
|
+
s.add_development_dependency("rake", "~> 13.0")
|
17
|
+
s.add_development_dependency("minitest", "~> 5.20")
|
18
|
+
end
|
metadata
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: zeroconf
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Aaron Patterson
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-12-16 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: resolv
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.3.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.3.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '13.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '13.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '5.20'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '5.20'
|
55
|
+
description: This is a multicast DNS client and server written in Ruby
|
56
|
+
email: tenderlove@ruby-lang.org
|
57
|
+
executables: []
|
58
|
+
extensions: []
|
59
|
+
extra_rdoc_files: []
|
60
|
+
files:
|
61
|
+
- CODE_OF_CONDUCT.md
|
62
|
+
- Gemfile
|
63
|
+
- LICENSE
|
64
|
+
- README.md
|
65
|
+
- Rakefile
|
66
|
+
- lib/zeroconf.rb
|
67
|
+
- lib/zeroconf/service.rb
|
68
|
+
- lib/zeroconf/utils.rb
|
69
|
+
- lib/zeroconf/version.rb
|
70
|
+
- test/client_test.rb
|
71
|
+
- test/helper.rb
|
72
|
+
- test/service_test.rb
|
73
|
+
- zeroconf.gemspec
|
74
|
+
homepage: https://github.com/tenderlove/zeroconf
|
75
|
+
licenses:
|
76
|
+
- Apache-2.0
|
77
|
+
metadata: {}
|
78
|
+
post_install_message:
|
79
|
+
rdoc_options: []
|
80
|
+
require_paths:
|
81
|
+
- lib
|
82
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
83
|
+
requirements:
|
84
|
+
- - ">="
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: '0'
|
87
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
88
|
+
requirements:
|
89
|
+
- - ">="
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: '0'
|
92
|
+
requirements: []
|
93
|
+
rubygems_version: 3.5.0.dev
|
94
|
+
signing_key:
|
95
|
+
specification_version: 4
|
96
|
+
summary: Multicast DNS client and server
|
97
|
+
test_files:
|
98
|
+
- test/client_test.rb
|
99
|
+
- test/helper.rb
|
100
|
+
- test/service_test.rb
|