toolmantim-zeroconf 0.0.2

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