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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c389ff9fea665db344da8713c4cb1e3a8af014eee5c7fef1201dba139b6e8ea6
4
- data.tar.gz: 340781dbba64cef0228016f79685d4f48de201c6aacd9df16da502dadcec492c
3
+ metadata.gz: 39d1c369a77bb16908b77e600e166c264662ec1fc624de8aee2f62fc752e3aae
4
+ data.tar.gz: 6f58a84ef9926d9e900182390249b6b029312332a7376cc1f1229b182812bb59
5
5
  SHA512:
6
- metadata.gz: '0928363c430656e6d72454462d2d53e265073abb83c54b405161fe48342825c10400fe6038c6265558c1471bb48e070917b1a44fc8854b5c4446b5f024c73caa'
7
- data.tar.gz: a59a327c0bd4391c1511b5367d642e498f56e528c3881e17b4861a06a79398f59771ea1fce76ad17e7762e74b141e854b144cc5fd1b2e91ec33281f41dbedde6
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
@@ -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
- unicast = type::ClassValue & PTR::MDNS_UNICAST_RESPONSE > 0
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 sock, disconnect_msg.encode
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, ZeroConf::MDNS::Announce::IN::SRV
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, ZeroConf::PTR
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, ZeroConf::PTR
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, ZeroConf::MDNS::Announce::IN::A
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,
@@ -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
- BROADCAST_V4
78
+ broadcast_v4
33
79
  else
34
- BROADCAST_V6
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
 
@@ -1,3 +1,3 @@
1
1
  module ZeroConf
2
- VERSION = "1.0.0"
2
+ VERSION = "1.0.1"
3
3
  end
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
- MDNS_CACHE_FLUSH = 0x8000
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
- port = 0
76
- sockets = interfaces.map { |iface|
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
- port = 0
153
- sockets = interfaces.map { |iface|
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
- port = 0
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.1
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.1
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 == ZeroConf::PTR }
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.1
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 == ZeroConf::PTR }
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 0
34
- query.add_question "tc-lan-adapter._test-mdns._tcp.local.", ZeroConf::SRV
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, 0
37
+ sock = open_ipv4 iface.addr, Resolv::MDNS::Port
37
38
  multicast_send sock, query.encode
38
- res = Resolv::DNS::Message.decode sock.recvfrom(2048).first
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, ZeroConf::MDNS::Announce::IN::SRV
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 0
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, 0
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
- 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
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
- 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
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
- assert_equal m, s.disconnect_msg
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 0
112
- query.add_question "_services._dns-sd._udp.local.", ZeroConf::PTR
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, 0
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.recvfrom(2048)
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, ZeroConf::PTR
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 0
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, 0
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.encode, res.encode
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 0
194
- query.add_question "_test-mdns._tcp.local.", ZeroConf::PTR
323
+ query = Resolv::DNS::Message.new 10
324
+ query.add_question "_test-mdns._tcp.local.", PTR
195
325
 
196
- sock = open_ipv4 iface.addr, 0
326
+ sock = open_ipv4 iface.addr, Resolv::MDNS::Port
197
327
  multicast_send sock, query.encode
198
- res = Resolv::DNS::Message.decode sock.recvfrom(2048).first
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, ZeroConf::PTR
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 0
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, 0
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 0
271
- query.add_question "tc-lan-adapter.local.", ZeroConf::A
437
+ query = Resolv::DNS::Message.new 10
438
+ query.add_question "tc-lan-adapter.local.", A
272
439
 
273
- sock = open_ipv4 iface.addr, 0
440
+ sock = open_ipv4 iface.addr, Resolv::MDNS::Port
274
441
  multicast_send sock, query.encode
275
- res = Resolv::DNS::Message.decode sock.recvfrom(2048).first
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
- ZeroConf::MDNS::Announce::IN::A
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 0
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, 0
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.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: 2023-12-16 00:00:00.000000000 Z
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.0.dev
98
+ rubygems_version: 3.5.11
94
99
  signing_key:
95
100
  specification_version: 4
96
101
  summary: Multicast DNS client and server