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,238 @@
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
+ $:.unshift(File.dirname($0))
6
+
7
+ require 'getoptlong'
8
+
9
+ $stdout.sync = true
10
+ $stderr.sync = true
11
+
12
+ =begin
13
+ Apple's dns-sd options:
14
+
15
+ mdns -E (Enumerate recommended registration domains)
16
+ mdns -F (Enumerate recommended browsing domains)
17
+ mdns -B <Type> <Domain> (Browse for service instances)
18
+ mdns -L <Name> <Type> <Domain> (Look up a service instance)
19
+ mdns -R <Name> <Type> <Domain> <Port> [<TXT>...] (Register a service)
20
+ mdns -P <Name> <Type> <Domain> <Port> <Host> <IP> [<TXT>...] (Proxy)
21
+ mdns -Q <FQDN> <rrtype> <rrclass> (Generic query for any record type)
22
+ =end
23
+
24
+ @debug = false
25
+ @log = nil
26
+
27
+ @recursive = false
28
+ @domain = 'local'
29
+ @type = nil
30
+ @name = nil
31
+ @port = nil
32
+ @txt = {}
33
+
34
+ @cmd = nil
35
+
36
+
37
+ # TODO - can I use introspection on class names to determine all supported
38
+ # RR types in DNS::Resource::IN?
39
+
40
+ HELP =<<EOF
41
+ Usage:
42
+ mdns [options] -B <type> [domain] (Browse for service instances)
43
+ mdns [options] -L <name> <type> [domain] (Look up a service instance)
44
+ mdns [options] -R <name> <type> [domain] <port> [<TXT>...] (Register a service)
45
+ mdns [options] -Q <fqdn> [rrtype] [rrclass] (Generic query for any record type)
46
+
47
+ Note: -Q is not yet implemented.
48
+
49
+ For -B, -L, and -R, [domain] is optional and defaults to "local".
50
+
51
+ For -Q, [rrtype] defaults to A, other values are TXT, PTR, SRV, CNAME, ...
52
+
53
+ For -Q, [rrclass] defaults to 1 (IN).
54
+
55
+
56
+ [<TXT>...] is optional for -R, it can be a series of key=value pairs.
57
+
58
+ You can use long names --browse, --lookup, and --register instead of -B, -L,
59
+ and -R.
60
+
61
+ Options:
62
+ -m,--mdnssd Attempt to use 'net/dns/mdns-sd', a pure-ruby DNS-SD resolver
63
+ library (this is the default).
64
+ -n,--dnssd Attempt to use 'dnssd', the interface to the native ("-n")
65
+ DNS-SD resolver library APIs, "dns_sd.h" from Apple.
66
+ -d,--debug Print debug messages to stderr.
67
+
68
+ Examples:
69
+ mdns -B _http._tcp
70
+ mdns -L "My Music" _daap._tcp
71
+ mdns -R me _example._tcp local 4321 key=value key2=value2
72
+
73
+ These work with the test modes of Apple's dns-sd utility:
74
+ mdns -L Test _testupdate._tcp (for dns-sd -A, -U, -N)
75
+ mdns -L Test _testlargetxt._tcp (for dns-sd -T)
76
+ mdns -L Test _testdualtxt._tcp (for dns-sd -M)
77
+ mdns -L Test _testtxt._tcp (for dns-sd -I)
78
+
79
+ EOF
80
+
81
+ opts = GetoptLong.new(
82
+ [ "--debug", "-d", GetoptLong::NO_ARGUMENT ],
83
+ [ "--help", "-h", GetoptLong::NO_ARGUMENT ],
84
+ [ "--dnssd", "-n", GetoptLong::NO_ARGUMENT ],
85
+ [ "--mdnssd", "-m", GetoptLong::NO_ARGUMENT ],
86
+
87
+ [ "--browse", "-B", GetoptLong::NO_ARGUMENT ],
88
+ [ "--lookup", "-L", GetoptLong::NO_ARGUMENT ],
89
+ [ "--register", "-R", GetoptLong::NO_ARGUMENT ]
90
+ )
91
+
92
+ opts.each do |opt, arg|
93
+ case opt
94
+ when "--debug"
95
+ require 'pp'
96
+ require 'logger'
97
+
98
+ @debug = true
99
+ @log = Logger.new(STDERR)
100
+ @log.level = Logger::DEBUG
101
+
102
+ when "--help"
103
+ print HELP
104
+ exit 0
105
+
106
+ when '--dnssd'
107
+ require 'dnssd'
108
+ require 'socket'
109
+
110
+ when "--browse"
111
+ @cmd = :browse
112
+ @type = ARGV.shift
113
+ @domain = ARGV.shift || @domain
114
+
115
+ when "--lookup"
116
+ @cmd = :lookup
117
+ @name = ARGV.shift
118
+ @type = ARGV.shift
119
+ @domain = ARGV.shift || @domain
120
+
121
+ unless @name && @type
122
+ puts 'name and type required for -L'
123
+ exit 1
124
+ end
125
+
126
+ when "--register"
127
+ @cmd = :register
128
+ @name = ARGV.shift
129
+ @type = ARGV.shift
130
+ @port = ARGV.shift
131
+ if @port.to_i == 0
132
+ @domain = @port
133
+ @port = ARGV.shift.to_i
134
+ else
135
+ @port = @port.to_i
136
+ end
137
+ ARGV.each do |kv|
138
+ kv.match(/([^=]+)=([^=]+)/)
139
+ @txt[$1] = $2
140
+ end
141
+ ARGV.replace([])
142
+ end
143
+ end
144
+
145
+ begin
146
+ DNSSD.class
147
+ puts "Using native DNSSD..."
148
+
149
+ Thread.abort_on_exception = true # So we notice exceptions in DNSSD threads.
150
+
151
+ module DNSSD
152
+ def self.namesplit(n)
153
+ n.scan(/(?:\\.|[^\.])+/)
154
+ end
155
+ # DNSSD > 0.6.0 uses class Reply which has these methods already
156
+ class ResolveReply
157
+ def domain
158
+ DNSSD.namesplit(fullname)[-1]
159
+ end
160
+ def type
161
+ DNSSD.namesplit(fullname)[1,2].join('.')
162
+ end
163
+ def name
164
+ DNSSD.namesplit(fullname)[0]
165
+ end
166
+ end
167
+ end
168
+ rescue NameError
169
+ require 'net/dns/mdns-sd'
170
+ DNSSD = Net::DNS::MDNSSD
171
+ Net::DNS::MDNS::Responder.instance.log = @log if @log
172
+ puts "Using Net::DNS::MDNSSD..."
173
+ end
174
+
175
+ unless @cmd
176
+ print HELP
177
+ exit 1
178
+ end
179
+
180
+ case @cmd
181
+ when :browse
182
+ STDERR.puts( "DNSSD.#{@cmd}(#{@type}, #{@domain}) =>" ) if @debug
183
+
184
+ fmt = "%-3.3s %-8.8s %-15.15s %-20.20s\n"
185
+ printf fmt, "Ttl", "Domain", "Service Type", "Instance Name"
186
+
187
+ handle = DNSSD.browse(@type, @domain) do |reply|
188
+ begin
189
+ printf fmt, reply.flags.to_i, reply.domain, reply.type, reply.name
190
+ rescue
191
+ p $!
192
+ end
193
+ end
194
+
195
+ $stdin.gets
196
+ handle.stop
197
+
198
+
199
+ when :lookup
200
+ STDERR.puts( "DNSSD.#{@cmd}(#{@name}, #{@type}, #{@domain}) =>" ) if @debug
201
+
202
+ fmt = "%-3.3s %-8.8s %-19.19s %-20.20s %-20.20s %s\n"
203
+ printf fmt, "Ttl", "Domain", "Service Type", "Instance Name", "Location", "Text"
204
+
205
+ handle = DNSSD.resolve(@name, @type, @domain) do |reply|
206
+ begin
207
+ location = "#{reply.target}:#{reply.port}"
208
+ text = reply.text_record.to_a.map { |kv| "#{kv[0]}=#{kv[1].inspect}" }.join(', ')
209
+ printf fmt, reply.flags.to_i, reply.domain, reply.type, reply.name, location, text
210
+ rescue
211
+ p $!
212
+ end
213
+ end
214
+
215
+ $stdin.gets
216
+ handle.stop
217
+
218
+ when :register
219
+ STDERR.puts( "DNSSD.#{@cmd}(#{@name}, #{@type}, #{@domain}, #{@port}, #{@txt.inspect}) =>" ) if @debug
220
+
221
+ fmt = "%-3.3s %-8.8s %-19.19s %-20.20s %-20.20s %s\n"
222
+ printf fmt, "Ttl", "Domain", "Service Type", "Instance Name", "Location", "Text"
223
+
224
+ handle = DNSSD.register(@name, @type, @domain, @port, @txt) do |notice|
225
+ begin
226
+ location = "#{Socket.gethostname}:#{@port}"
227
+ text = @txt.to_a.map { |kv| "#{kv[0]}=#{kv[1].inspect}" }.join(', ')
228
+ printf fmt, 'N/A', notice.domain, notice.type, notice.name, location, text
229
+ rescue
230
+ p $!
231
+ end
232
+ end
233
+
234
+ $stdin.gets
235
+ handle.stop
236
+
237
+ end
238
+
@@ -0,0 +1,128 @@
1
+ #!/usr/bin/ruby -w
2
+
3
+ $:.unshift File.dirname($0)
4
+
5
+ require 'net/dns/resolvx.rb'
6
+ require 'test/unit'
7
+
8
+ require 'pp'
9
+
10
+ Name = Resolv::DNS::Name
11
+
12
+ class TestDns < Test::Unit::TestCase
13
+
14
+ def test_name_what_I_think_are_odd_behaviours
15
+ # Why can't test against strings?
16
+ assert_equal(false, Name.create("example.CoM") == "example.com")
17
+ assert_equal(false, Name.create("example.CoM").eql?("example.com"))
18
+
19
+ # Why does making it absolute mean they aren't equal?
20
+ assert_equal(false, Name.create("example.CoM").eql?(Name.create("example.com.")))
21
+ assert_equal(false, Name.create("example.CoM") == Name.create("example.com."))
22
+ end
23
+
24
+ def test_name_CoMparisons
25
+
26
+ assert_equal(true, Name.create("example.CoM").eql?(Name.create("example.com")))
27
+ assert_equal(true, Name.create("example.CoM") == Name.create("example.com"))
28
+
29
+ assert_equal(true, Name.create("example.CoM").equal?("example.com."))
30
+ assert_equal(true, Name.create("example.CoM").equal?("example.com"))
31
+
32
+ assert_equal(true, Name.create("www.example.CoM") < "example.com")
33
+ assert_equal(true, Name.create("www.example.CoM") <= "example.com")
34
+ assert_equal(-1, Name.create("www.example.CoM") <=> "example.com")
35
+ assert_equal(false, Name.create("www.example.CoM") >= "example.com")
36
+ assert_equal(false, Name.create("www.example.CoM") > "example.com")
37
+
38
+ assert_equal(false, Name.create("example.CoM") < "example.com")
39
+ assert_equal(true, Name.create("example.CoM") <= "example.com")
40
+ assert_equal(0, Name.create("example.CoM") <=> "example.com")
41
+ assert_equal(true, Name.create("example.CoM") >= "example.com")
42
+ assert_equal(false, Name.create("example.CoM") > "example.com")
43
+
44
+ assert_equal(false, Name.create("CoM") < "example.com")
45
+ assert_equal(false, Name.create("CoM") <= "example.com")
46
+ assert_equal(+1, Name.create("CoM") <=> "example.com")
47
+ assert_equal(true, Name.create("CoM") >= "example.com")
48
+ assert_equal(true, Name.create("CoM") > "example.com")
49
+
50
+ assert_equal(nil, Name.create("bar.CoM") < "example.com")
51
+ assert_equal(nil, Name.create("bar.CoM") <= "example.com")
52
+ assert_equal(nil, Name.create("bar.CoM") <=> "example.com")
53
+ assert_equal(nil, Name.create("bar.CoM") >= "example.com")
54
+ assert_equal(nil, Name.create("bar.CoM") > "example.com")
55
+
56
+ assert_equal(nil, Name.create("net.") < "com")
57
+ assert_equal(nil, Name.create("net.") <= "com")
58
+ assert_equal(nil, Name.create("net.") <=> "com")
59
+ assert_equal(nil, Name.create("net.") >= "com")
60
+ assert_equal(nil, Name.create("net.") > "com")
61
+
62
+ end
63
+
64
+ def test_txt_with_0_strs
65
+ # Packet collected from the wild, it is non-conformant with DNS
66
+ # specification, TXT record has zero strings, but should have 1 or more.
67
+ d = "\000\000\204\000\000\000\000\005\000\000\000\000\002me\005local\000\000\001\200\001\000\000\000\360\000\004\300\250\003\003\005proxy\010_example\004_tcp\300\017\000!\200\001\000\000\000\360\000\010\000\000\000\000'\017\300\f\300$\000\020\200\001\000\000\000\360\000\000\t_services\a_dns-sd\004_udp\300\017\000\f\000\001\000\000\034 \000\002\300*\300*\000\f\000\001\000\000\034 \000\002\300$"
68
+
69
+ m = Resolv::DNS::Message.decode( d )
70
+
71
+ assert_equal('', m.answer[2][2].data)
72
+ assert_equal([''], m.answer[2][2].strings)
73
+ end
74
+
75
+ def txt_codec(*args)
76
+ m = Resolv::DNS::Message.new
77
+ m.add_answer('example.local', 0, Resolv::DNS::Resource::IN::TXT.new(*args))
78
+ # pp m
79
+ m = Resolv::DNS::Message.decode(m.encode)
80
+ txt = m.answer[0][2]
81
+ # pp txt
82
+ txt
83
+ end
84
+
85
+ def test_txt_with_large_str
86
+ # short or no strings
87
+ txt = txt_codec()
88
+ assert_equal('', txt.data)
89
+ assert_equal([''], txt.strings)
90
+
91
+ txt = txt_codec('')
92
+ assert_equal('', txt.data)
93
+ assert_equal([''], txt.strings)
94
+
95
+ txt = txt_codec('s')
96
+ assert_equal('s', txt.data)
97
+ assert_equal(['s'], txt.strings)
98
+
99
+ txt = txt_codec('s', 'a', 'm')
100
+ assert_equal('sam', txt.data)
101
+ assert_equal(['s', 'a', 'm'], txt.strings)
102
+
103
+ s = '0' * 255
104
+
105
+ # long strings
106
+ txt = txt_codec(s)
107
+ assert_equal(s, txt.data)
108
+ assert_equal([s], txt.strings)
109
+
110
+ # long strings
111
+ f = s + 'a'
112
+ txt = txt_codec(f)
113
+ assert_equal(f, txt.data)
114
+ assert_equal([s, 'a'], txt.strings)
115
+
116
+ f = s + s
117
+ txt = txt_codec(f)
118
+ assert_equal(f, txt.data)
119
+ assert_equal([s, s], txt.strings)
120
+
121
+ assert_raise(ArgumentError) { txt_codec(f, 'a') }
122
+ assert_raise(ArgumentError) { txt_codec('a', f) }
123
+
124
+ end
125
+
126
+
127
+ end
128
+
@@ -0,0 +1,167 @@
1
+ #!/usr/local/bin/ruby18 -w
2
+ #
3
+ # Example code to use the multicast library
4
+ # (c) 2005 Ben Giddings
5
+ # License: Ruby's License
6
+
7
+ # Include local dir
8
+ $: << File.dirname($0)
9
+
10
+ require 'net/dns/resolv-mdns'
11
+ require 'pp'
12
+
13
+ # default to browsing
14
+ operation = ARGV[0] || 'browse'
15
+
16
+ resolver = Resolv::MDNS.new
17
+ resolver.lazy_initialize
18
+
19
+ def usage
20
+ $stderr.puts <<-EOF
21
+ Usage: #{$0} [browse|discover|resolve|lookup] [service_str]
22
+
23
+ Use "browse" to find names offering the service requested.
24
+
25
+ Examples:
26
+ zsh% #{$0} browse
27
+ Searching for _http._tcp.local services
28
+ Matching entries in _http._tcp.local
29
+ Cool Webserver
30
+
31
+ zsh% #{$0} browse http
32
+ Searching for _http._tcp.local services
33
+ Matching entries in _http._tcp.local
34
+ Cool Webserver
35
+
36
+ zsh% #{$0} browse _http._tcp.local
37
+ Searching for _http._tcp.local services
38
+ Matching entries in _http._tcp.local
39
+ Cool Webserver
40
+
41
+ zsh% #{$0} browse _telnet._tcp.local
42
+ Searching for _telnet._tcp.local services
43
+ Matching entries in _telnet._tcp.local
44
+ Embedded Device (MAC addr: 00:01:02:03:04:05)
45
+
46
+ By default 'browse' looks for _http._tcp.local services/
47
+ If the only parameter supplied is a protocol, it looks for
48
+ local tcp services of that protocol
49
+
50
+ Use "resolve" to look up the dns name, port and ip address of a device
51
+
52
+ zsh% #{$0} resolve 'Cool Webserver'
53
+ Searching for Cool Webserver._http._tcp.local instance
54
+ Cool Webserver._http._tcp.local is
55
+ coolweb.local:80 at IP 192.168.0.12
56
+
57
+ zsh% #{$0} resolve 'Cool Webserver._http._tcp.local'
58
+ Searching for Cool Webserver._http._tcp.local instance
59
+ Cool Webserver._http._tcp.local is
60
+ coolweb.local:80 at IP 192.168.0.12
61
+
62
+ zsh% #{$0} resolve 'Embedded Device (MAC addr: 00:01:02:03:04:05)._telnet._tcp.local'
63
+ Searching for Embedded Device (MAC addr: 00:01:02:03:04:05)._telnet._tcp.local instance
64
+ Embedded Device (MAC addr: 00:01:02:03:04:05)._telnet._tcp.local is
65
+ emb000102030405.local:23 at IP 192.168.0.93
66
+
67
+ By default 'resolve' will append _http._tcp.local if it is missing
68
+
69
+ EOF
70
+ end
71
+
72
+ case (operation.downcase)
73
+ when 'browse', 'discover'
74
+ #
75
+ # To browse / discover services, use a ptr lookup on the service protocol
76
+ # A typical call might be
77
+ # resolver.getresources('_http._tcp.local', Resolv::DNS::Resource::IN::PTR)
78
+ #
79
+
80
+ # default to browsing for http hosts
81
+ service_str = ARGV[1] || 'http'
82
+
83
+ # prepend an underscore if necessary
84
+ # p service_str
85
+
86
+ if ?_ != service_str[0]
87
+ puts "prepending underscore to #{service_str}"
88
+ service_str = '_' + service_str
89
+ end
90
+
91
+ # append a ._tcp.local if necessary (assume tcp and local)
92
+ if service_str.index('.').nil?
93
+ puts "No dot found in service name, appending ._tcp.local"
94
+ service_str += '._tcp.local'
95
+ end
96
+
97
+ puts "Searching for #{service_str} services"
98
+
99
+ entries = resolver.getresources(service_str, Resolv::DNS::Resource::IN::PTR)
100
+
101
+ # p entries
102
+
103
+ puts "Matching entries in #{service_str}"
104
+ entries.each {
105
+ |entry|
106
+ # p entry
107
+ # I think this will always match but just in case...
108
+ friendly_name_regexp = /(.*?)\.#{service_str}/
109
+
110
+ match = friendly_name_regexp.match(entry.name.to_s)
111
+ if match
112
+ puts match[1]
113
+ else
114
+ puts entry.name
115
+ end
116
+ }
117
+ when 'resolve', 'lookup'
118
+ #
119
+ # To resolve / lookup services, first use a SRV lookup to lookup the service
120
+ # details, then extract the hostname from the result. Using this hostname,
121
+ # lookup the A record to find the IP address.
122
+ #
123
+
124
+ service_str = ARGV[1]
125
+ if service_str.nil?
126
+ $stderr.puts("Service string required for resolve / lookup")
127
+ usage()
128
+ exit(1)
129
+ end
130
+
131
+ # append a ._http._tcp.local if necessary (assume http, tcp and local)
132
+ if service_str.index('.').nil?
133
+ puts "No dot found in service name, appending ._http._tcp.local"
134
+ service_str += '._http._tcp.local'
135
+
136
+ end
137
+
138
+ puts "Searching for #{service_str} instance"
139
+ entries = resolver.getresources(service_str, Resolv::DNS::Resource::IN::SRV)
140
+
141
+ if 0 == entries.size
142
+ puts "Unable to find #{service_str}"
143
+ exit(0)
144
+ end
145
+
146
+ entry = entries[0]
147
+ # puts "Found match at #{entry.target}, port #{entry.port}"
148
+
149
+ hostname = entry.target
150
+ port = entry.port
151
+ entries = resolver.getresources(hostname, Resolv::DNS::Resource::IN::A)
152
+
153
+ if 0 == entries.size
154
+ puts "Unable to resolve #{hostname}"
155
+ exit(1)
156
+ end
157
+
158
+ entry = entries[0]
159
+ # p entry
160
+ # p entry.address
161
+ puts "#{service_str} is\n#{hostname}:#{port} at IP #{entry.address.to_s}"
162
+
163
+ else
164
+ $stderr.puts("unknown operation #{operation.inspect}")
165
+ usage()
166
+ exit(1)
167
+ end