toolmantim-zeroconf 0.0.2

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.
@@ -0,0 +1,278 @@
1
+ TODO
2
+
3
+ Release: 0.0 "seeing if its possible" - done
4
+
5
+ Release: 0.1 "making it work"
6
+
7
+ A complete rewrite, too many changes to even begin to mention.
8
+
9
+ Release: 0.2 "doing the right thing"
10
+
11
+ - must always respond to requests for the A record for this host's .local
12
+ address
13
+
14
+ - cache the answers in the additional section
15
+
16
+ - in MDNSSD, put the handle into the reply as the "service" attribute
17
+
18
+ - need to send TTL=0 message on service shutdown
19
+
20
+ - implement flags, particularly add/remove flags
21
+
22
+ - remove all use of absolute in name comparisons, I have had too many bugs
23
+ involving this
24
+
25
+ - deal with timeouts in Resolv::MDNS, how long, can they be set, etc.
26
+
27
+ - reverse lookup of names by addresses using mDNS, see notes in
28
+ #generate_candidates. I think I should allow reverse lookup of addresss in
29
+ the private address spaces [RFC1918] (address prefixes of 10/8, 172.16/12,
30
+ and 192.168/16).
31
+
32
+ - correctly encode a nil in a text_record, and allow "key" w/no value for "mdns.rb -R"
33
+
34
+ - probe for name conflicts before registering a service
35
+
36
+ - when we see a question, flush answers over a few seconds old from cache
37
+ .. and do we notify queries that the answers are being deleted, too?
38
+
39
+ - escape/parse DNS-SD names ...
40
+
41
+ - try the meta-queries (_services._dns-sd._udp.local.)
42
+
43
+ - add known answers to queries
44
+
45
+ - answer unicast questions
46
+
47
+ - implement a MDNSSD api for DNSServiceQueryRecord, and 'mdns.rb -Q"
48
+
49
+ Release: 0.3 "keep it working"
50
+
51
+ - Change the technique for looking up IPv4 address of the default interface,
52
+ the old technique stopped working on OS X boxes that had IPv6 enabled (which
53
+ was the default).
54
+
55
+ - Fixed a bug causing infinite loops (in the wrong place) in background query.
56
+
57
+
58
+ Todo:
59
+
60
+ - define constants in module MDNSSD::Types like Http = '_http._tcp', ...?
61
+
62
+ - mdns.rb -K, watch for records and immediately register replacements pointing
63
+ to somewhere else
64
+
65
+ - general code cleanups
66
+
67
+ - Add #to_s to the RR types.
68
+
69
+ - Add #to_s the *classes* of the RR types ("IN::TXT", ...)
70
+
71
+ - HINFO RR service
72
+
73
+ - A RR service
74
+
75
+ - wildcard answering services
76
+
77
+ - Query#start, #restart
78
+
79
+ - let Query.new take a block, and yield itself, or yield with every answer?
80
+
81
+ - move hierarchal DNS Name comparison operators to an optional file.
82
+
83
+ - DNS packet dumper
84
+
85
+ - to make both TXT objects and TXT classes have a value,
86
+ add methods type_value and type_class.
87
+
88
+
89
+
90
+ dns-sd test mode results:
91
+ -A ok, but don't support -Q so don't see HINFO add/update/remove
92
+ -U ok
93
+ -N ok, but don't support -Q so don't see NULL add
94
+ -T ok
95
+ -M no, we don't support multiple TXT records for a service
96
+ -I ok
97
+
98
+
99
+
100
+ Open resolv.rb issues:
101
+
102
+ - Reverse DNS lookups are done by sock.recv, seems like its a problem for a
103
+ DNS library...
104
+
105
+ Shouldn't BasicSocket.do_not_reverse_lookup be set? Or be set on our sockets?
106
+ It's weird that in @sock.recv we get a tuple with the address... when we ARE
107
+ a resolver library.... and if you call resolv-replace, won't we call
108
+ ourselves in order to resolve the IP when you call recv to get the peer info?
109
+ And isn't resolv-replace ignoring BasicSocket.do_not_reverse_lookup? So, it
110
+ is very weird that resolv.rb does ad socket.recv that causes (blocking) DNS
111
+ queries to occure for its peer IP address...
112
+
113
+ - Resolv::Hosts returns IPv6 addresses before IPv4, so Resolv.getaddress('localhost')
114
+ is ::1 on my system - this is not good.
115
+
116
+ - See [DNSOPV6:3.1], does resolv.rb do this?
117
+
118
+
119
+ [DNSOPV6]
120
+ [DNSOPBADRES] draft-ietf-dnsop-bad-dns-res-03.tx
121
+
122
+ Open design issues:
123
+
124
+ - how to get the local ifx ipv4 address?
125
+
126
+ - Net classes that do socksetopts, so you don't have to pack the structs
127
+ yourself! or maybe implement the 8 functions of Stevens?
128
+
129
+
130
+ System differences in multicast:
131
+
132
+ [EACCES] The destination address is a broadcast address, and SO_BROADCAST has not been set on the socket.
133
+
134
+
135
+ Comments for DNS-SD:
136
+
137
+ * domain and type end in a '.', is that necessary?
138
+
139
+ * Names of Stuff
140
+
141
+ I'm getting lost in trying to remember what goes into a method, and what is available
142
+ in it's Reply object. I think the following convention would help:
143
+
144
+ Every function argument maps to a Reply attribute of the same name.
145
+
146
+ The Reply attributes and function arguments should be the same.
147
+
148
+ This makes things easy to remember, as data goes into #browse, comes out
149
+ BrowseReply, goes into #resolve, comes out ResolveReply it doesn't change its
150
+ name!
151
+
152
+
153
+
154
+ Currently:
155
+
156
+ DNSSD::BrowseReply.instance_methods:
157
+ ["flags", "service", "domain", "fullname", "interface", "name", "type"]
158
+
159
+ Note that #type overrides Object#type.
160
+ service_type had its name changed to type
161
+
162
+ DNSSD::ResolveReply.instance_methods:
163
+ [ "flags", "service", "fullname", "interface", "port", "target", "text_record"]
164
+
165
+ Missing #domain!
166
+ Missing #name!
167
+ Missing #type!
168
+
169
+ #resolve takes argument #service_name, it comes from BrowseReply#name
170
+ #resolve takes argument #service_type, it comes from BrowseReply#type
171
+ #resolve takes argument #service_domain, it comes from BrowseReply#domain
172
+
173
+ Note that interface is still interface.
174
+
175
+ #browse has argument domain, #register has argument #service_domain
176
+
177
+
178
+ DNSSD::RegisterReply.instance_methods:
179
+
180
+ [ "flags", "service", "domain", "name", "type" ]
181
+
182
+ Missing #interface, #port, #target, #text_record.
183
+
184
+
185
+ As it is, I find the naming really confusing. Changing the names in the replies
186
+ causes interface breakage, so how about changing the names in the arguments so
187
+ they are the same as the attributes in the reply, and so that all 3 functions
188
+ use the same name for the same thing?
189
+
190
+
191
+ This code supplies some missing methods:
192
+
193
+ module DNSSD
194
+ def self.namesplit(n)
195
+ n.scan(/(?:\\.|[^\.])+/)
196
+ end
197
+ class ResolveReply
198
+ def domain
199
+ DNSSD.namesplit(fullname)[-1]
200
+ end
201
+ def type
202
+ DNSSD.namesplit(fullname)[1,2].join('.')
203
+ end
204
+ def name
205
+ DNSSD.namesplit(fullname)[0]
206
+ end
207
+ end
208
+ end
209
+
210
+
211
+
212
+
213
+ Comments for Apple:
214
+
215
+ * [MDNS:5] Reverse Address Mapping - should allow reverse mapping
216
+ in the [RFC1918] address space as well. Does Apple's resolver
217
+ do this?
218
+
219
+ * TXT record is required by mDNSResponder
220
+
221
+ DNS-SD does not require a responder advertising a service to have
222
+ a TXT record.
223
+
224
+
225
+ Problem:
226
+
227
+ I assume it is done because if you ask for SRV and TXT (or ANY), and
228
+ get only SRV, you don't know if it is because there is no TXT, or because
229
+ it didn't make it across unreliable UDP.
230
+
231
+ Solutions:
232
+
233
+ Require a TXT record (curent mDNSResponder behaviour).
234
+
235
+ Require that all the questions in a single DNS message be responded
236
+ to in a single DNS message. This allows:
237
+
238
+ 1 - asking for ANY?, and if you get a SRV but no TXT, it means there
239
+ is no TXT
240
+ 2 - asking for TXT? and SRV?, and if you get a SRV but no TXT, it means
241
+ there is no TXT
242
+
243
+ Recommend:
244
+
245
+ I prefer the latter. It scales to more than two records associated with a
246
+ service, so in the future if a service needs a SRV, TXT, and NUL record, a
247
+ single query can be sent, and a single query can be expected as a response.
248
+
249
+ For legacy interop reasons, we are probably stuck with having to advertise a
250
+ TXT record. I still think all questions from a single message should be answered
251
+ in a single message. Possible exception is when to large, in which case the
252
+ TC bit should be set so the resolver knows to expect another message.
253
+
254
+
255
+ * TXT records from mDNSResponder may have zero character-strings
256
+
257
+ Violates DNS spec.
258
+
259
+ DNS-SD claims to conform to DNS.
260
+
261
+ DNS-SD says TXT has zero or more character-strings.
262
+
263
+ Two statements not in agreement.
264
+
265
+ Recommend that:
266
+ - DNS-SD be altered to state TXT records MUST conform to DNS specs.
267
+ - DNS-SD include a warning that deployed responders generate TXT
268
+ with zero char-strings, and implementations SHOULD be capable of handling
269
+ this as being equivalent to a TXT with one zero-length character string.
270
+
271
+
272
+ * OS X puts additional answers in the answers section, not the additional section
273
+
274
+ * OS X resolver generates queries for ensemble.local.local
275
+
276
+ This violates DNS-SD.
277
+
278
+
@@ -0,0 +1,50 @@
1
+ #!/usr/local/bin/ruby18 -w
2
+ # Author: Sam Roberts <sroberts@uniserve.com>
3
+ # Licence: this file is placed in the public domain
4
+
5
+ require 'net/http'
6
+ require 'thread'
7
+ require 'pp'
8
+
9
+ # For MDNSSD
10
+ require 'net/dns/mdns-sd'
11
+
12
+ # To make Resolv aware of mDNS
13
+ require 'net/dns/resolv-mdns'
14
+
15
+ # To make TCPSocket use Resolv, not the C library resolver.
16
+ require 'net/dns/resolv-replace'
17
+
18
+ # Use a short name.
19
+ DNSSD = Net::DNS::MDNSSD
20
+
21
+ # Sync stdout, and don't write to console from multiple threads.
22
+ $stdout.sync
23
+ $lock = Mutex.new
24
+
25
+ # Be quiet.
26
+ debug = false
27
+
28
+ DNSSD.browse('_http._tcp') do |b|
29
+ $lock.synchronize { pp b } if debug
30
+ DNSSD.resolve(b.name, b.type) do |r|
31
+ $lock.synchronize { pp r } if debug
32
+ begin
33
+ http = Net::HTTP.new(r.target, r.port)
34
+
35
+ path = r.text_record['path'] || '/'
36
+
37
+ headers = http.head(path)
38
+
39
+ $lock.synchronize do
40
+ puts "#{r.name.inspect} on #{r.target}:#{r.port}#{path} using server #{headers['server']}"
41
+ end
42
+ rescue
43
+ $lock.synchronize { puts $!; puts $!.backtrace }
44
+ end
45
+ end
46
+ end
47
+
48
+ # Hit enter when you think that's all.
49
+ STDIN.gets
50
+
@@ -0,0 +1,29 @@
1
+ #!/usr/local/bin/ruby18 -w
2
+ # Author: Sam Roberts <sroberts@uniserve.com>
3
+ # Licence: this file is placed in the public domain
4
+
5
+ require 'net/http'
6
+ require 'net/dns/resolv-mdns'
7
+
8
+ mdns = Resolv::MDNS.default
9
+
10
+ mdns.each_resource('_http._tcp.local', Resolv::DNS::Resource::IN::PTR) do |rrhttp|
11
+ service = rrhttp.name
12
+ host = nil
13
+ port = nil
14
+ path = '/'
15
+
16
+ rrsrv = mdns.getresource(rrhttp.name, Resolv::DNS::Resource::IN::SRV)
17
+ host, port = rrsrv.target.to_s, rrsrv.port
18
+ rrtxt = mdns.getresource(rrhttp.name, Resolv::DNS::Resource::IN::TXT)
19
+ if rrtxt.data =~ /path=(.*)/
20
+ path = $1
21
+ end
22
+
23
+ http = Net::HTTP.new(host, port)
24
+
25
+ headers = http.head(path)
26
+
27
+ puts "#{service[0]} on #{host}:#{port}#{path} was last-modified #{headers['last-modified']}"
28
+ end
29
+
@@ -0,0 +1,56 @@
1
+ #!/usr/local/bin/ruby18 -w
2
+ # Author: Sam Roberts <sroberts@uniserve.com>
3
+ # Licence: this file is placed in the public domain
4
+ #
5
+ # Advertise a webrick server over mDNS.
6
+
7
+ require 'webrick'
8
+ require 'net/dns/mdns-sd'
9
+
10
+ DNSSD = Net::DNS::MDNSSD
11
+
12
+ class HelloServlet < WEBrick::HTTPServlet::AbstractServlet
13
+ def do_GET(req, resp)
14
+ resp.body = "hello, world\n"
15
+ resp['content-type'] = 'text/plain'
16
+ raise WEBrick::HTTPStatus::OK
17
+ end
18
+ end
19
+
20
+ # This may seem convoluted... but if there are multiple address families
21
+ # available, like AF_INET6 and AF_INET, this should create multiple TCPServer
22
+ # sockets for them.
23
+ families = Socket.getaddrinfo(nil, 1, Socket::AF_UNSPEC, Socket::SOCK_STREAM, 0, Socket::AI_PASSIVE)
24
+
25
+ listeners = []
26
+ port = 0
27
+
28
+ families.each do |af, one, dns, addr|
29
+ p port, addr
30
+ listeners << TCPServer.new(addr, port)
31
+ port = listeners.first.addr[1] unless port != 0
32
+ end
33
+
34
+ listeners.each do |s|
35
+ puts "listen on #{s.addr.inspect}"
36
+ end
37
+
38
+ # This will dynamically allocate multiple TCPServers, each on a different port.
39
+ server = WEBrick::HTTPServer.new( :Port => 0 )
40
+
41
+ # So we replace them with our TCPServer sockets which are all on the same
42
+ # (dynamically assigned) port.
43
+ server.listeners.each do |s| s.close end
44
+ server.listeners.replace listeners
45
+ server.config[:Port] = port
46
+
47
+ server.mount( '/hello/', HelloServlet )
48
+
49
+ handle = DNSSD.register("hello", '_http._tcp', 'local', port, 'path' => '/hello/')
50
+
51
+ ['INT', 'TERM'].each { |signal|
52
+ trap(signal) { server.shutdown; handle.stop; }
53
+ }
54
+
55
+ server.start
56
+
@@ -0,0 +1,132 @@
1
+ #!/usr/local/bin/ruby18
2
+
3
+ require 'socket'
4
+ require 'ipaddr'
5
+ require 'net/dns'
6
+
7
+ $stderr.sync = true
8
+ $stdout.sync = true
9
+
10
+ Addr = "224.0.0.251"
11
+ Port = 5353
12
+
13
+ include Net::DNS
14
+
15
+ @hostname = Name.create(Socket.gethostname)
16
+ @hostname.absolute = true
17
+ @hostaddr = Socket.getaddrinfo(@hostname.to_s, 0, Socket::AF_INET, Socket::SOCK_STREAM)[0][3]
18
+ @hostrr = [ @hostname, 240, IN::A.new(@hostaddr) ]
19
+ @hostaddr = IPAddr.new(@hostaddr).hton
20
+
21
+ @sock = UDPSocket.new
22
+
23
+ # TODO - do we need this?
24
+ @sock.fcntl(Fcntl::F_SETFD, 1)
25
+
26
+ # Allow 5353 to be shared.
27
+ so_reuseport = 0x0200 # The definition on OS X, where it is required.
28
+ if Socket.constants.include? 'SO_REUSEPORT'
29
+ so_reuseport = Socket::SO_REUSEPORT
30
+ end
31
+ begin
32
+ @sock.setsockopt(Socket::SOL_SOCKET, so_reuseport, 1)
33
+ rescue
34
+ warn( "set SO_REUSEPORT raised #{$!}, try SO_REUSEADDR" )
35
+ so_reuseport = Socket::SO_REUSEADDR
36
+ @sock.setsockopt(Socket::SOL_SOCKET, so_reuseport, 1)
37
+ end
38
+
39
+ # Request dest addr and ifx ids... no.
40
+
41
+ # Join the multicast group.
42
+ # option is a struct ip_mreq { struct in_addr, struct in_addr }
43
+ ip_mreq = IPAddr.new(Addr).hton + @hostaddr
44
+ @sock.setsockopt(Socket::IPPROTO_IP, Socket::IP_ADD_MEMBERSHIP, ip_mreq)
45
+ @sock.setsockopt(Socket::IPPROTO_IP, Socket::IP_MULTICAST_IF, @hostaddr)
46
+
47
+ # Set IP TTL for outgoing packets.
48
+ @sock.setsockopt(Socket::IPPROTO_IP, Socket::IP_TTL, 255)
49
+ @sock.setsockopt(Socket::IPPROTO_IP, Socket::IP_MULTICAST_TTL, 255)
50
+
51
+ # Apple source makes it appear that optval may need to be a "char" on
52
+ # some systems:
53
+ # @sock.setsockopt(Socket::IPPROTO_IP, Socket::IP_MULTICAST_TTL, 255 as int)
54
+ # - or -
55
+ # @sock.setsockopt(Socket::IPPROTO_IP, Socket::IP_MULTICAST_TTL, 255 as byte)
56
+
57
+ # Bind to our port.
58
+ @sock.bind(Socket::INADDR_ANY, Port)
59
+
60
+ class Resolv
61
+ class DNS
62
+ class Resource
63
+ module IN
64
+ class SRV
65
+ def inspect
66
+ "#{target}:#{port} weight=#{weight} priority=#{priority}"
67
+ end
68
+ end
69
+ class TXT
70
+ def inspect
71
+ strings.inspect
72
+ end
73
+ end
74
+ class PTR
75
+ def inspect
76
+ name.to_s
77
+ end
78
+ end
79
+ class A
80
+ def inspect
81
+ address.to_s
82
+ end
83
+ end
84
+ class HINFO
85
+ def inspect
86
+ "os=#{os.inspect}\ncpu=#{cpu.inspect}"
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
93
+
94
+ loop do
95
+
96
+ reply, from = @sock.recvfrom(9000)
97
+
98
+ puts "++ from #{from.inspect}"
99
+
100
+ if false
101
+ puts reply.inspect
102
+ puts "--"
103
+ end
104
+
105
+ msg = Resolv::DNS::Message.decode(reply)
106
+
107
+ qr = msg.qr==0 ? 'Q' : 'R'
108
+ qrstr = msg.qr==0 ? 'Query' : 'Resp'
109
+
110
+ opcode = { 0=>'QUERY', 1=>'IQUERY', 2=>'STATUS'}[msg.opcode]
111
+
112
+ puts "#{qrstr}: id #{msg.id} qr #{qr} opcode #{opcode} aa #{msg.aa} tc #{msg.tc} rd #{msg.rd} ra #{msg.ra} rcode #{msg.rcode}"
113
+
114
+ msg.question.each do |name, type, unicast|
115
+ puts "qu #{Net::DNS.rrname type} #{name.to_s.inspect} unicast=#{unicast}"
116
+ end
117
+ msg.answer.each do |name, ttl, data, cacheflush|
118
+ puts "an #{Net::DNS.rrname data} #{name.to_s.inspect} ttl=#{ttl} cacheflush=#{cacheflush}"
119
+ puts " #{data.inspect}"
120
+ end
121
+ msg.authority.each do |name, ttl, data, cacheflush|
122
+ puts "au #{Net::DNS.rrname data} #{name.to_s.inspect} ttl=#{ttl} cacheflush=#{cacheflush.inspect}"
123
+ puts " #{data.inspect}"
124
+ end
125
+ msg.additional.each do |name, ttl, data, cacheflush|
126
+ puts "ad #{Net::DNS.rrname data} #{name.to_s.inspect} ttl=#{ttl} cacheflush=#{cacheflush.inspect}"
127
+ puts " #{data.inspect}"
128
+ end
129
+
130
+ puts
131
+ end
132
+