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,230 @@
1
+ =begin
2
+ Copyright (C) 2005 Sam Roberts
3
+
4
+ This library is free software; you can redistribute it and/or modify it
5
+ under the same terms as the ruby language itself, see the file COPYING for
6
+ details.
7
+ =end
8
+
9
+ require 'net/dns/resolvx'
10
+ require 'net/dns/mdns'
11
+
12
+ class Resolv
13
+ # == Address Lookups
14
+ # Requiring 'net/dns/mdns-resolv' causes a Resolv::MDNS resolver to be added
15
+ # to list of default resolvers queried when using Resolv#getaddress, and the
16
+ # other Resolv module methods.
17
+ #
18
+ # It can be used by doing:
19
+ # require 'net/dns/resolv-mdns'
20
+ # Resolv.getaddress('localhost') # resolved using Resolv::Hosts("/etc/hosts")
21
+ # Resolv.getaddress('www.example.com') # resolved using Resolv::DNS
22
+ # Resolv.getaddress('example.local') # resolved using Resolv::MDNS
23
+ #
24
+ # Using this approach means that both global DNS names and local names can be
25
+ # resolved. When doing this, you may also consider doing:
26
+ #
27
+ # require 'net/dns/resolv-mdns'
28
+ # require 'net/dns/resolv-replace'
29
+ #
30
+ # This has the effect of replacing the default ruby implementation of address
31
+ # lookup using the C library in IPSocket, TCPSocket, UDPSocket, and
32
+ # SOCKSocket with Resolv.getaddress. Since 'net/dns/resolv-mdns' has been
33
+ # required Resolv.getaddress and the standard libraries TCP/IP classes will
34
+ # use mDNS for name lookups in the .local mDNS domain, without even knowing
35
+ # it themselves.
36
+ #
37
+ # NOTE: the version of resolv.rb and resolv-replace.rb in net-mdns are based
38
+ # on the head of ruby 1.8.x cvs + bug fixes required by net-mdns and not
39
+ # present in the cvs. They must be used in place of the standard library's
40
+ # resolv implementation!
41
+ #
42
+ # == Service Discovery (DNS-SD)
43
+ #
44
+ # Service discovery consists of 2 stages:
45
+ # - enumerating the names of the instances of the service
46
+ # - resolving the instance names
47
+ #
48
+ # The Net::DNS::MDNSSD API is better documented and easier to use for DNS-SD.
49
+ # Still, here's some information on using the Resolv APIs for DNS-SD, and
50
+ # examples of doing so are:
51
+ # - link:exhttpv1.txt
52
+ # - link:v1mdns.txt
53
+ # - link:v1demo.txt.
54
+ #
55
+ # = Service Enumeration
56
+ #
57
+ # To do this query the pointer records (Resolv::DNS::Resource::IN::PTR) for
58
+ # names of the form _svc._prot.local. The values of svc and prot for common
59
+ # services can be found at http://www.dns-sd.org/ServiceTypes.html.
60
+ # The first label of the name returned is suitable for display to people, and
61
+ # should be unique in the network.
62
+ #
63
+ # = Service Resolution
64
+ #
65
+ # In order to resolve a service name query the service record
66
+ # (Resolv::DNS::Resource::IN::SRV) for the name. The service record contains
67
+ # a host and port to connect to. The host name will have to be resolved to an
68
+ # address. This can be done explicitly using mDNS or, if resolv-replace has
69
+ # been required, it will be done by the standard library. In addition, some
70
+ # services put "extra" information about the service in a text
71
+ # (Resolv::DNS::Resource::IN::TXT) record associated with the service name.
72
+ # The format of the text record is service-specific.
73
+ class MDNS
74
+
75
+ # How many seconds to wait before assuming all responses have been seen.
76
+ DefaultTimeout = 2
77
+
78
+ # See Resolv::DNS#new.
79
+ def initialize(config_info=nil)
80
+ @mutex = Mutex.new
81
+ @config = DNS::Config.new(config_info)
82
+ @initialized = nil
83
+ end
84
+
85
+ def lazy_initialize # :nodoc:
86
+ @mutex.synchronize do
87
+ unless @initialized
88
+ @config.lazy_initialize
89
+ @initialized = true
90
+ end
91
+ end
92
+ end
93
+
94
+ # See Resolv::DNS#getaddress.
95
+ def getaddress(name)
96
+ each_address(name) {|address| return address}
97
+ raise ResolvError.new("mDNS result has no information for #{name}")
98
+ end
99
+
100
+ # See Resolv::DNS#getaddresss.
101
+ def getaddresses(name)
102
+ ret = []
103
+ each_address(name) {|address| ret << address}
104
+ return ret
105
+ end
106
+
107
+ # See Resolv::DNS#each_address.
108
+ def each_address(name)
109
+ each_resource(name, DNS::Resource::IN::A) {|resource| yield resource.address}
110
+ end
111
+
112
+ # See Resolv::DNS#getname.
113
+ def getname(address)
114
+ each_name(address) {|name| return name}
115
+ raise ResolvError.new("mDNS result has no information for #{address}")
116
+ end
117
+
118
+ # See Resolv::DNS#getnames.
119
+ def getnames(address)
120
+ ret = []
121
+ each_name(address) {|name| ret << name}
122
+ return ret
123
+ end
124
+
125
+ # See Resolv::DNS#each_name.
126
+ def each_name(address)
127
+ case address
128
+ when DNS::Name
129
+ ptr = address
130
+ when IPv4::Regex
131
+ ptr = IPv4.create(address).to_name
132
+ when IPv6::Regex
133
+ ptr = IPv6.create(address).to_name
134
+ else
135
+ raise ResolvError.new("cannot interpret as address: #{address}")
136
+ end
137
+ each_resource(ptr, DNS::Resource::IN::PTR) {|resource| yield resource.name}
138
+ end
139
+
140
+ # See Resolv::DNS#getresource.
141
+ def getresource(name, typeclass)
142
+ each_resource(name, typeclass) {|resource| return resource}
143
+ raise ResolvError.new("mDNS result has no information for #{name}")
144
+ end
145
+
146
+ # See Resolv::DNS#getresources.
147
+ def getresources(name, typeclass)
148
+ ret = []
149
+ each_resource(name, typeclass) {|resource| ret << resource}
150
+ return ret
151
+ end
152
+
153
+ def generate_candidates(name) # :nodoc:
154
+ # Names ending in .local MUST be resolved using mDNS. Other names may be, but
155
+ # SHOULD NOT be, so a local machine can't spoof a non-local address.
156
+ #
157
+ # Reverse lookups in the domain '.254.169.in-addr.arpa' should also be resolved
158
+ # using mDNS.
159
+ #
160
+ # TODO - those are the IPs auto-allocated with ZeroConf. In my (common)
161
+ # situation, I have a net of OS X machines behind and ADSL firewall box,
162
+ # and all IPs were allocated in 192.168.123.*. I can do mDNS queries to
163
+ # get these addrs, but I can't do an mDNS query to reverse lookup the
164
+ # addrs. There are security reasons to not allow all addrs to be reversed
165
+ # on the local network, but maybe it wouldn't be so bad if MDNS was after
166
+ # DNS, so it only did it for addrs that were unmatched by DNS?
167
+ #
168
+ # Or perhaps IP addrs in the netmask of the ifx should be considered local,
169
+ # and mDNS allowed on them?
170
+ #
171
+ # If the search domains includes .local, we can add .local to it only if
172
+ # it has no dots and wasn't absolute.
173
+ lazy_initialize
174
+ dotlocal = DNS::Name.create('local')
175
+ search_dotlocal = @config.search.map.include?( dotlocal.to_a )
176
+ name = DNS::Name.create(name)
177
+ if name.absolute?
178
+ name = name
179
+ elsif name.length == 1 && search_dotlocal
180
+ name = name + dotlocal
181
+ elsif name.length > 1
182
+ name = name
183
+ else
184
+ name = nil
185
+ end
186
+ if name.subdomain_of?('local') || name.subdomain_of?('254.169.in-addr.arpa')
187
+ name.absolute = true
188
+ name
189
+ else
190
+ nil
191
+ end
192
+ end
193
+
194
+ # See Resolv::DNS#eachresource.
195
+ def each_resource(name, typeclass)
196
+ name = generate_candidates(name)
197
+
198
+ query = Net::DNS::MDNS::Query.new(name, typeclass)
199
+
200
+ begin
201
+ # We want all the answers we can get, within the timeout period.
202
+ begin
203
+ timeout(DefaultTimeout) do
204
+ query.each do |answers|
205
+ answers.each do |an|
206
+ yield an.data
207
+ end
208
+ end
209
+ end
210
+ rescue TimeoutError
211
+ end
212
+ ensure
213
+ query.stop
214
+ end
215
+ end
216
+
217
+ Default = Resolv::MDNS.new
218
+
219
+ # Return the default MDNS Resolver. This is what is used when
220
+ # Resolv.getaddress and friends are called. Use it unless you need to
221
+ # specify config_info to Resolv::MDNS.new.
222
+ def self.default
223
+ Default
224
+ end
225
+
226
+ end
227
+
228
+ DefaultResolver.resolvers.push( Resolv::MDNS.default )
229
+ end
230
+
@@ -0,0 +1,66 @@
1
+ # net/dns/resolv-replace.rb is a copy of resolv-replace.rb from the ruby
2
+ # library, where it is maintained by Tanaka Akira. See net/dns/resolv.rb for
3
+ # more information.
4
+ require 'socket'
5
+ require 'net/dns/resolv'
6
+
7
+ class << IPSocket
8
+ alias original_resolv_getaddress getaddress
9
+ def getaddress(host)
10
+ return original_resolv_getaddress(host) if Fixnum === host
11
+ begin
12
+ return Resolv.getaddress(host).to_s
13
+ rescue Resolv::ResolvError
14
+ raise SocketError, "Hostname not known: #{host}"
15
+ end
16
+ end
17
+ end
18
+
19
+ class TCPSocket
20
+ alias original_resolv_initialize initialize
21
+ def initialize(host, serv, *rest)
22
+ rest[0] = IPSocket.getaddress(rest[0]) unless rest.empty?
23
+ original_resolv_initialize(IPSocket.getaddress(host), serv, *rest)
24
+ end
25
+ end
26
+
27
+ class UDPSocket
28
+ alias original_resolv_bind bind
29
+ def bind(host, port)
30
+ original_resolv_bind(IPSocket.getaddress(host), port)
31
+ end
32
+
33
+ alias original_resolv_connect connect
34
+ def connect(host, port)
35
+ original_resolv_connect(IPSocket.getaddress(host), port)
36
+ end
37
+
38
+ alias original_resolv_send send
39
+ def send(mesg, flags, *rest)
40
+ if rest.length == 2
41
+ host, port = rest
42
+ begin
43
+ addrs = Resolv.getaddresses(host)
44
+ rescue Resolv::ResolvError
45
+ raise SocketError, "Hostname not known: #{host}"
46
+ end
47
+ err = nil
48
+ addrs[0...-1].each {|addr|
49
+ begin
50
+ return original_resolv_send(mesg, flags, addr, port)
51
+ rescue SystemCallError
52
+ end
53
+ }
54
+ original_resolv_send(mesg, flags, addrs[-1], port)
55
+ else
56
+ original_resolv_send(mesg, flags, *rest)
57
+ end
58
+ end
59
+ end
60
+
61
+ class SOCKSSocket
62
+ alias original_resolv_initialize initialize
63
+ def initialize(host, serv)
64
+ original_resolv_initialize(IPSocket.getaddress(host), port)
65
+ end
66
+ end if defined? SOCKSSocket
@@ -0,0 +1,2012 @@
1
+ # net/dns/resolv.rb is a copy of resolv.rb from the ruby library, where it is
2
+ # maintained by Tanaka Akira.
3
+ #
4
+ # It contains modifications I found necessary, some of which have been accepted
5
+ # into ruby 1.8's cvs, and others that I hope will be accepted.
6
+ #
7
+ # net/dns/resolvx.rb contains extensions to resolv.rb (as opposed to modifications),
8
+ # some of these may also be worth accepting into the standard library.
9
+ #
10
+ # Note that until net/dns/resolv.rb is accepted AND released in ruby 1.8.x's
11
+ # resolv.rb I still need a copy in net-mdns. Without it, it would be necessary
12
+ # to install ruby from CVS in order to use net-mdns.
13
+ #
14
+ # = Bug fixes/Required changes
15
+ # - resolv-replace.rb: IPSocket#getaddress fails when passed a Fixnum, such as when
16
+ # calling UDPSocket#bind(Socket:INADDR_ANY, 5353)
17
+ # - MessageEncoder#put_string: would silently create a garbage record if string was
18
+ # longer than 255 characters.
19
+ # - TXT.new: correctly deal with TXT records longer than 255 characters.
20
+ # - TXT#data: correctly concatenate strings into a TXT record longer than 255 characters.
21
+ # - Message#encode/Message#decode: question and answer arrays now contain the
22
+ # mDNS unicast and cacheflush bit, respectively. All APIs, including
23
+ # #each_question and #each_answer, are backwards compatible.
24
+ # - A.new(A#address) failed because IPv4.create() wouldn't accept an address in the
25
+ # form of A#address (4 bytes in network byte order).
26
+ #
27
+ # = Ease-of-use changes
28
+ #
29
+ # - partial rdocifying
30
+ # - Str#inspect: difficult to notice whitespace at beginning of string, added quotes.
31
+ # - Name#==: allow arg to be String or Name, as does Name#create.
32
+ # - Name#subdomain_of?: allow arg to be String or Name, as does Name#create.
33
+ # - Name#subdomain_of?: disregard absolute, it doesn't make sense that:
34
+ # www.example.com subdomain_of? www.example.com. => false
35
+ # www.example.com subdomain_of? www.example.com => true
36
+ # If you can't compare a variable thing to a known thing.. how can you compare a
37
+ # variable thing to a variable thing?
38
+ #
39
+ # I had a lot of bugs using Name comparison related to trailing dots. Name#==
40
+ # is almost impossible to use correctly when comparing against an other which
41
+ # is a Name or a String, and may have come from a DNS Message (in which case it
42
+ # will be absolute), or from input from a user, in which case they probably did
43
+ # not type the trailing dot.
44
+
45
+ =begin
46
+ = resolv library
47
+ resolv.rb is a resolver library written in Ruby.
48
+ Since it is written in Ruby, it is thread-aware.
49
+ I.e. it can resolv many hostnames concurrently.
50
+
51
+ It is possible to lookup various resources of DNS using DNS module directly.
52
+
53
+ == example
54
+ p Resolv.getaddress("www.ruby-lang.org")
55
+ p Resolv.getname("210.251.121.214")
56
+
57
+ Resolv::DNS.open {|dns|
58
+ p dns.getresources("www.ruby-lang.org", Resolv::DNS::Resource::IN::A).collect {|r| r.address}
59
+ p dns.getresources("ruby-lang.org", Resolv::DNS::Resource::IN::MX).collect {|r| [r.exchange.to_s, r.preference]}
60
+ }
61
+
62
+ == Resolv class
63
+
64
+ === class methods
65
+ --- Resolv.getaddress(name)
66
+ --- Resolv.getaddresses(name)
67
+ --- Resolv.each_address(name) {|address| ...}
68
+ They lookups IP addresses of ((|name|)) which represents a hostname
69
+ as a string by default resolver.
70
+
71
+ getaddress returns first entry of lookupped addresses.
72
+ getaddresses returns lookupped addresses as an array.
73
+ each_address iterates over lookupped addresses.
74
+
75
+ --- Resolv.getname(address)
76
+ --- Resolv.getnames(address)
77
+ --- Resolv.each_name(address) {|name| ...}
78
+ lookups hostnames of ((|address|)) which represents IP address as a string.
79
+
80
+ getname returns first entry of lookupped names.
81
+ getnames returns lookupped names as an array.
82
+ each_names iterates over lookupped names.
83
+
84
+ == Resolv::Hosts class
85
+ hostname resolver using /etc/hosts format.
86
+
87
+ === class methods
88
+ --- Resolv::Hosts.new(hosts='/etc/hosts')
89
+
90
+ === methods
91
+ --- Resolv::Hosts#getaddress(name)
92
+ --- Resolv::Hosts#getaddresses(name)
93
+ --- Resolv::Hosts#each_address(name) {|address| ...}
94
+ address lookup methods.
95
+
96
+ --- Resolv::Hosts#getname(address)
97
+ --- Resolv::Hosts#getnames(address)
98
+ --- Resolv::Hosts#each_name(address) {|name| ...}
99
+ hostnames lookup methods.
100
+
101
+ == Resolv::DNS class
102
+ DNS stub resolver.
103
+
104
+ === class methods
105
+ --- Resolv::DNS.new(config_info=nil)
106
+
107
+ ((|config_info|)) should be nil, a string or a hash.
108
+ If nil is given, /etc/resolv.conf and platform specific information is used.
109
+ If a string is given, it should be a filename which format is same as /etc/resolv.conf.
110
+ If a hash is given, it may contains information for nameserver, search and ndots as follows.
111
+
112
+ Resolv::DNS.new({:nameserver=>["210.251.121.21"], :search=>["ruby-lang.org"], :ndots=>1})
113
+
114
+ --- Resolv::DNS.open(config_info=nil)
115
+ --- Resolv::DNS.open(config_info=nil) {|dns| ...}
116
+
117
+ === methods
118
+ --- Resolv::DNS#close
119
+
120
+ --- Resolv::DNS#getaddress(name)
121
+ --- Resolv::DNS#getaddresses(name)
122
+ --- Resolv::DNS#each_address(name) {|address| ...}
123
+ address lookup methods.
124
+
125
+ ((|name|)) must be a instance of Resolv::DNS::Name or String. Lookupped
126
+ address is represented as an instance of Resolv::IPv4 or Resolv::IPv6.
127
+
128
+ --- Resolv::DNS#getname(address)
129
+ --- Resolv::DNS#getnames(address)
130
+ --- Resolv::DNS#each_name(address) {|name| ...}
131
+ hostnames lookup methods.
132
+
133
+ ((|address|)) must be a instance of Resolv::IPv4, Resolv::IPv6 or String.
134
+ Lookupped name is represented as an instance of Resolv::DNS::Name.
135
+
136
+ --- Resolv::DNS#getresource(name, typeclass)
137
+ --- Resolv::DNS#getresources(name, typeclass)
138
+ --- Resolv::DNS#each_resource(name, typeclass) {|resource| ...}
139
+ They lookup DNS resources of ((|name|)).
140
+ ((|name|)) must be a instance of Resolv::Name or String.
141
+
142
+ ((|typeclass|)) should be one of follows:
143
+ * Resolv::DNS::Resource::IN::ANY
144
+ * Resolv::DNS::Resource::IN::NS
145
+ * Resolv::DNS::Resource::IN::CNAME
146
+ * Resolv::DNS::Resource::IN::SOA
147
+ * Resolv::DNS::Resource::IN::HINFO
148
+ * Resolv::DNS::Resource::IN::MINFO
149
+ * Resolv::DNS::Resource::IN::MX
150
+ * Resolv::DNS::Resource::IN::TXT
151
+ * Resolv::DNS::Resource::IN::ANY
152
+ * Resolv::DNS::Resource::IN::A
153
+ * Resolv::DNS::Resource::IN::WKS
154
+ * Resolv::DNS::Resource::IN::PTR
155
+ * Resolv::DNS::Resource::IN::SRV
156
+ * Resolv::DNS::Resource::IN::AAAA
157
+
158
+ Lookupped resource is represented as an instance of (a subclass of)
159
+ Resolv::DNS::Resource.
160
+ (Resolv::DNS::Resource::IN::A, etc.)
161
+
162
+ == Resolv::DNS::Resource::IN::NS class
163
+ --- name
164
+ == Resolv::DNS::Resource::IN::CNAME class
165
+ --- name
166
+ == Resolv::DNS::Resource::IN::SOA class
167
+ --- mname
168
+ --- rname
169
+ --- serial
170
+ --- refresh
171
+ --- retry
172
+ --- expire
173
+ --- minimum
174
+ == Resolv::DNS::Resource::IN::HINFO class
175
+ --- cpu
176
+ --- os
177
+ == Resolv::DNS::Resource::IN::MINFO class
178
+ --- rmailbx
179
+ --- emailbx
180
+ == Resolv::DNS::Resource::IN::MX class
181
+ --- preference
182
+ --- exchange
183
+ == Resolv::DNS::Resource::IN::TXT class
184
+ --- data
185
+ == Resolv::DNS::Resource::IN::A class
186
+ --- address
187
+ == Resolv::DNS::Resource::IN::WKS class
188
+ --- address
189
+ --- protocol
190
+ --- bitmap
191
+ == Resolv::DNS::Resource::IN::PTR class
192
+ --- name
193
+ == Resolv::DNS::Resource::IN::AAAA class
194
+ --- address
195
+
196
+ == Resolv::DNS::Name class
197
+
198
+ === class methods
199
+ --- Resolv::DNS::Name.create(name)
200
+
201
+ === methods
202
+ --- Resolv::DNS::Name#to_s
203
+
204
+ == Resolv::DNS::Resource class
205
+
206
+ == Resolv::IPv4 class
207
+ === class methods
208
+ --- Resolv::IPv4.create(address)
209
+
210
+ === methods
211
+ --- Resolv::IPv4#to_s
212
+ --- Resolv::IPv4#to_name
213
+
214
+ === constants
215
+ --- Resolv::IPv4::Regex
216
+ regular expression for IPv4 address.
217
+
218
+ == Resolv::IPv6 class
219
+ === class methods
220
+ --- Resolv::IPv6.create(address)
221
+
222
+ === methods
223
+ --- Resolv::IPv6#to_s
224
+ --- Resolv::IPv6#to_name
225
+
226
+ === constants
227
+ --- Resolv::IPv6::Regex
228
+ regular expression for IPv6 address.
229
+
230
+ == Bugs
231
+ * NIS is not supported.
232
+ * /etc/nsswitch.conf is not supported.
233
+ * IPv6 is not supported.
234
+
235
+ =end
236
+
237
+ require 'socket'
238
+ require 'fcntl'
239
+ require 'timeout'
240
+ require 'thread'
241
+
242
+ class Resolv
243
+ def self.getaddress(name)
244
+ DefaultResolver.getaddress(name)
245
+ end
246
+
247
+ def self.getaddresses(name)
248
+ DefaultResolver.getaddresses(name)
249
+ end
250
+
251
+ def self.each_address(name, &block)
252
+ DefaultResolver.each_address(name, &block)
253
+ end
254
+
255
+ def self.getname(address)
256
+ DefaultResolver.getname(address)
257
+ end
258
+
259
+ def self.getnames(address)
260
+ DefaultResolver.getnames(address)
261
+ end
262
+
263
+ def self.each_name(address, &proc)
264
+ DefaultResolver.each_name(address, &proc)
265
+ end
266
+
267
+ def initialize(resolvers=[Hosts.new, DNS.new])
268
+ @resolvers = resolvers
269
+ end
270
+
271
+ def getaddress(name)
272
+ each_address(name) {|address| return address}
273
+ raise ResolvError.new("no address for #{name}")
274
+ end
275
+
276
+ def getaddresses(name)
277
+ ret = []
278
+ each_address(name) {|address| ret << address}
279
+ return ret
280
+ end
281
+
282
+ def each_address(name)
283
+ if AddressRegex =~ name
284
+ yield name
285
+ return
286
+ end
287
+ yielded = false
288
+ @resolvers.each {|r|
289
+ r.each_address(name) {|address|
290
+ yield address.to_s
291
+ yielded = true
292
+ }
293
+ return if yielded
294
+ }
295
+ end
296
+
297
+ def getname(address)
298
+ each_name(address) {|name| return name}
299
+ raise ResolvError.new("no name for #{address}")
300
+ end
301
+
302
+ def getnames(address)
303
+ ret = []
304
+ each_name(address) {|name| ret << name}
305
+ return ret
306
+ end
307
+
308
+ def each_name(address)
309
+ yielded = false
310
+ @resolvers.each {|r|
311
+ r.each_name(address) {|name|
312
+ yield name.to_s
313
+ yielded = true
314
+ }
315
+ return if yielded
316
+ }
317
+ end
318
+
319
+ class ResolvError < StandardError
320
+ end
321
+
322
+ class ResolvTimeout < TimeoutError
323
+ end
324
+
325
+ # Resolves names and addresses using the hosts file, "/etc/hosts" or
326
+ # whatever it is on Windows.
327
+ class Hosts
328
+ if /mswin32|cygwin|mingw|bccwin/ =~ RUBY_PLATFORM
329
+ require 'win32/resolv'
330
+ DefaultFileName = Win32::Resolv.get_hosts_path
331
+ else
332
+ DefaultFileName = '/etc/hosts'
333
+ end
334
+
335
+ def initialize(filename = DefaultFileName)
336
+ @filename = filename
337
+ @mutex = Mutex.new
338
+ @initialized = nil
339
+ end
340
+
341
+ def lazy_initialize # :nodoc:
342
+ @mutex.synchronize {
343
+ unless @initialized
344
+ @name2addr = {}
345
+ @addr2name = {}
346
+ open(@filename) {|f|
347
+ f.each {|line|
348
+ line.sub!(/#.*/, '')
349
+ addr, hostname, *aliases = line.split(/\s+/)
350
+ next unless addr
351
+ addr.untaint
352
+ hostname.untaint
353
+ @addr2name[addr] = [] unless @addr2name.include? addr
354
+ @addr2name[addr] << hostname
355
+ @addr2name[addr] += aliases
356
+ @name2addr[hostname] = [] unless @name2addr.include? hostname
357
+ @name2addr[hostname] << addr
358
+ aliases.each {|n|
359
+ n.untaint
360
+ @name2addr[n] = [] unless @name2addr.include? n
361
+ @name2addr[n] << addr
362
+ }
363
+ }
364
+ }
365
+ @name2addr.each {|name, arr| arr.reverse!}
366
+ @initialized = true
367
+ end
368
+ }
369
+ self
370
+ end
371
+
372
+ def getaddress(name)
373
+ each_address(name) {|address| return address}
374
+ raise ResolvError.new("#{@filename} has no name: #{name}")
375
+ end
376
+
377
+ def getaddresses(name)
378
+ ret = []
379
+ each_address(name) {|address| ret << address}
380
+ return ret
381
+ end
382
+
383
+ def each_address(name, &proc)
384
+ lazy_initialize
385
+ if @name2addr.include?(name)
386
+ @name2addr[name].each(&proc)
387
+ end
388
+ end
389
+
390
+ def getname(address)
391
+ each_name(address) {|name| return name}
392
+ raise ResolvError.new("#{@filename} has no address: #{address}")
393
+ end
394
+
395
+ def getnames(address)
396
+ ret = []
397
+ each_name(address) {|name| ret << name}
398
+ return ret
399
+ end
400
+
401
+ def each_name(address, &proc)
402
+ lazy_initialize
403
+ if @addr2name.include?(address)
404
+ @addr2name[address].each(&proc)
405
+ end
406
+ end
407
+ end
408
+
409
+ class DNS
410
+ # STD0013 (RFC 1035, etc.)
411
+ # ftp://ftp.isi.edu/in-notes/iana/assignments/dns-parameters
412
+
413
+ Port = 53
414
+ UDPSize = 512
415
+
416
+ DNSThreadGroup = ThreadGroup.new # :nodoc:
417
+
418
+ def self.open(*args)
419
+ dns = new(*args)
420
+ return dns unless block_given?
421
+ begin
422
+ yield dns
423
+ ensure
424
+ dns.close
425
+ end
426
+ end
427
+
428
+ def initialize(config_info=nil)
429
+ @mutex = Mutex.new
430
+ @config = Config.new(config_info)
431
+ @initialized = nil
432
+ end
433
+
434
+ def lazy_initialize # :nodoc:
435
+ @mutex.synchronize {
436
+ unless @initialized
437
+ @config.lazy_initialize
438
+
439
+ if nameserver = @config.single?
440
+ @requester = Requester::ConnectedUDP.new(nameserver)
441
+ else
442
+ @requester = Requester::UnconnectedUDP.new
443
+ end
444
+
445
+ @initialized = true
446
+ end
447
+ }
448
+ self
449
+ end
450
+
451
+ def close
452
+ @mutex.synchronize {
453
+ if @initialized
454
+ @requester.close if @requester
455
+ @requester = nil
456
+ @initialized = false
457
+ end
458
+ }
459
+ end
460
+
461
+ def getaddress(name)
462
+ each_address(name) {|address| return address}
463
+ raise ResolvError.new("DNS result has no information for #{name}")
464
+ end
465
+
466
+ def getaddresses(name)
467
+ ret = []
468
+ each_address(name) {|address| ret << address}
469
+ return ret
470
+ end
471
+
472
+ def each_address(name)
473
+ each_resource(name, Resource::IN::A) {|resource| yield resource.address}
474
+ end
475
+
476
+ def getname(address)
477
+ each_name(address) {|name| return name}
478
+ raise ResolvError.new("DNS result has no information for #{address}")
479
+ end
480
+
481
+ def getnames(address)
482
+ ret = []
483
+ each_name(address) {|name| ret << name}
484
+ return ret
485
+ end
486
+
487
+ def each_name(address)
488
+ case address
489
+ when Name
490
+ ptr = address
491
+ when IPv4::Regex
492
+ ptr = IPv4.create(address).to_name
493
+ when IPv6::Regex
494
+ ptr = IPv6.create(address).to_name
495
+ else
496
+ raise ResolvError.new("cannot interpret as address: #{address}")
497
+ end
498
+ each_resource(ptr, Resource::IN::PTR) {|resource| yield resource.name}
499
+ end
500
+
501
+ def getresource(name, typeclass)
502
+ each_resource(name, typeclass) {|resource| return resource}
503
+ raise ResolvError.new("DNS result has no information for #{name}")
504
+ end
505
+
506
+ def getresources(name, typeclass)
507
+ ret = []
508
+ each_resource(name, typeclass) {|resource| ret << resource}
509
+ return ret
510
+ end
511
+
512
+ def each_resource(name, typeclass, &proc)
513
+ lazy_initialize
514
+ q = Queue.new
515
+ senders = {}
516
+ begin
517
+ @config.resolv(name) {|candidate, tout, nameserver|
518
+ msg = Message.new
519
+ msg.rd = 1
520
+ msg.add_question(candidate, typeclass)
521
+ unless sender = senders[[candidate, nameserver]]
522
+ sender = senders[[candidate, nameserver]] =
523
+ @requester.sender(msg, candidate, q, nameserver)
524
+ end
525
+ sender.send
526
+ reply = reply_name = nil
527
+ timeout(tout, ResolvTimeout) { reply, reply_name = q.pop }
528
+ case reply.rcode
529
+ when RCode::NoError
530
+ extract_resources(reply, reply_name, typeclass, &proc)
531
+ return
532
+ when RCode::NXDomain
533
+ raise Config::NXDomain.new(reply_name.to_s)
534
+ else
535
+ raise Config::OtherResolvError.new(reply_name.to_s)
536
+ end
537
+ }
538
+ ensure
539
+ @requester.delete(q)
540
+ end
541
+ end
542
+
543
+ def extract_resources(msg, name, typeclass) # :nodoc:
544
+ if typeclass < Resource::ANY
545
+ n0 = Name.create(name)
546
+ msg.each_answer {|n, ttl, data|
547
+ yield data if n0 == n
548
+ }
549
+ end
550
+ yielded = false
551
+ n0 = Name.create(name)
552
+ msg.each_answer {|n, ttl, data|
553
+ if n0 == n
554
+ case data
555
+ when typeclass
556
+ yield data
557
+ yielded = true
558
+ when Resource::CNAME
559
+ n0 = data.name
560
+ end
561
+ end
562
+ }
563
+ return if yielded
564
+ msg.each_answer {|n, ttl, data|
565
+ if n0 == n
566
+ case data
567
+ when typeclass
568
+ yield data
569
+ end
570
+ end
571
+ }
572
+ end
573
+
574
+ class Requester # :nodoc:
575
+ def initialize
576
+ @senders = {}
577
+ end
578
+
579
+ def close
580
+ thread, sock, @thread, @sock = @thread, @sock
581
+ begin
582
+ if thread
583
+ thread.kill
584
+ thread.join
585
+ end
586
+ ensure
587
+ sock.close if sock
588
+ end
589
+ end
590
+
591
+ def delete(arg)
592
+ case arg
593
+ when Sender
594
+ @senders.delete_if {|k, s| s == arg }
595
+ when Queue
596
+ @senders.delete_if {|k, s| s.queue == arg }
597
+ else
598
+ raise ArgumentError.new("neither Sender or Queue: #{arg}")
599
+ end
600
+ end
601
+
602
+ class Sender # :nodoc:
603
+ def initialize(msg, data, sock, queue)
604
+ @msg = msg
605
+ @data = data
606
+ @sock = sock
607
+ @queue = queue
608
+ end
609
+ attr_reader :queue
610
+
611
+ def recv(msg)
612
+ @queue.push([msg, @data])
613
+ end
614
+ end
615
+
616
+ class UnconnectedUDP < Requester # :nodoc:
617
+ def initialize
618
+ super()
619
+ @sock = UDPSocket.new
620
+ @sock.fcntl(Fcntl::F_SETFD, 1) if defined? Fcntl::F_SETFD
621
+ @id = {}
622
+ @id.default = -1
623
+ @thread = Thread.new {
624
+ DNSThreadGroup.add Thread.current
625
+ loop {
626
+ reply, from = @sock.recvfrom(UDPSize)
627
+ msg = begin
628
+ Message.decode(reply)
629
+ rescue DecodeError
630
+ STDERR.print("DNS message decoding error: #{reply.inspect}\n")
631
+ next
632
+ end
633
+ if s = @senders[[[from[3],from[1]],msg.id]]
634
+ s.recv msg
635
+ else
636
+ #STDERR.print("non-handled DNS message: #{msg.inspect} from #{from.inspect}\n")
637
+ end
638
+ }
639
+ }
640
+ end
641
+
642
+ def sender(msg, data, queue, host, port=Port)
643
+ service = [host, port]
644
+ id = Thread.exclusive {
645
+ @id[service] = (@id[service] + 1) & 0xffff
646
+ }
647
+ request = msg.encode
648
+ request[0,2] = [id].pack('n')
649
+ return @senders[[service, id]] =
650
+ Sender.new(request, data, @sock, host, port, queue)
651
+ end
652
+
653
+ class Sender < Requester::Sender # :nodoc:
654
+ def initialize(msg, data, sock, host, port, queue)
655
+ super(msg, data, sock, queue)
656
+ @host = host
657
+ @port = port
658
+ end
659
+
660
+ def send
661
+ @sock.send(@msg, 0, @host, @port)
662
+ end
663
+ end
664
+ end
665
+
666
+ class ConnectedUDP < Requester # :nodoc:
667
+ def initialize(host, port=Port)
668
+ super()
669
+ @host = host
670
+ @port = port
671
+ @sock = UDPSocket.new
672
+ @sock.connect(host, port)
673
+ @sock.fcntl(Fcntl::F_SETFD, 1) if defined? Fcntl::F_SETFD
674
+ @id = -1
675
+ @thread = Thread.new {
676
+ DNSThreadGroup.add Thread.current
677
+ loop {
678
+ reply = @sock.recv(UDPSize)
679
+ msg = begin
680
+ Message.decode(reply)
681
+ rescue DecodeError
682
+ STDERR.print("DNS message decoding error: #{reply.inspect}")
683
+ next
684
+ end
685
+ if s = @senders[msg.id]
686
+ s.recv msg
687
+ else
688
+ #STDERR.print("non-handled DNS message: #{msg.inspect}")
689
+ end
690
+ }
691
+ }
692
+ end
693
+
694
+ def sender(msg, data, queue, host=@host, port=@port)
695
+ unless host == @host && port == @port
696
+ raise RequestError.new("host/port don't match: #{host}:#{port}")
697
+ end
698
+ id = Thread.exclusive { @id = (@id + 1) & 0xffff }
699
+ request = msg.encode
700
+ request[0,2] = [id].pack('n')
701
+ return @senders[id] = Sender.new(request, data, @sock, queue)
702
+ end
703
+
704
+ class Sender < Requester::Sender # :nodoc:
705
+ def send
706
+ @sock.send(@msg, 0)
707
+ end
708
+ end
709
+ end
710
+
711
+ class TCP < Requester # :nodoc:
712
+ def initialize(host, port=Port)
713
+ super()
714
+ @host = host
715
+ @port = port
716
+ @sock = TCPSocket.new
717
+ @sock.connect(host, port)
718
+ @sock.fcntl(Fcntl::F_SETFD, 1) if defined? Fcntl::F_SETFD
719
+ @id = -1
720
+ @senders = {}
721
+ @thread = Thread.new {
722
+ DNSThreadGroup.add Thread.current
723
+ loop {
724
+ len = @sock.read(2).unpack('n')
725
+ reply = @sock.read(len)
726
+ msg = begin
727
+ Message.decode(reply)
728
+ rescue DecodeError
729
+ STDERR.print("DNS message decoding error: #{reply.inspect}")
730
+ next
731
+ end
732
+ if s = @senders[msg.id]
733
+ s.push msg
734
+ else
735
+ #STDERR.print("non-handled DNS message: #{msg.inspect}")
736
+ end
737
+ }
738
+ }
739
+ end
740
+
741
+ def sender(msg, data, queue, host=@host, port=@port)
742
+ unless host == @host && port == @port
743
+ raise RequestError.new("host/port don't match: #{host}:#{port}")
744
+ end
745
+ id = Thread.exclusive { @id = (@id + 1) & 0xffff }
746
+ request = msg.encode
747
+ request[0,2] = [request.length, id].pack('nn')
748
+ return @senders[id] = Sender.new(request, data, @sock, queue)
749
+ end
750
+
751
+ class Sender < Requester::Sender # :nodoc:
752
+ def send
753
+ @sock.print(@msg)
754
+ @sock.flush
755
+ end
756
+ end
757
+ end
758
+
759
+ class RequestError < StandardError
760
+ end
761
+ end
762
+
763
+ # Encapsulates the resolver configuration information.
764
+ #
765
+ # +config_info+ can be nil, a String or a Hash:
766
+ # - nil is the default, configuration is read from /etc/resolv.conf, or
767
+ # from Win32::Resolv.get_resolv_info on Windows.
768
+ # - String is used as the name of the config file to parse instead of
769
+ # /etc/resolv.conf.
770
+ # - Hash must map the :nameserver and/or :search symbol keys to a single
771
+ # String or an array of String to use as the value of those config options.
772
+ class Config
773
+ def initialize(config_info=nil)
774
+ @mutex = Mutex.new
775
+ @config_info = config_info
776
+ @initialized = nil
777
+ end
778
+
779
+ def Config.parse_resolv_conf(filename) # :nodoc:
780
+ nameserver = []
781
+ search = nil
782
+ ndots = 1
783
+ open(filename) {|f|
784
+ f.each {|line|
785
+ line.sub!(/[#;].*/, '')
786
+ keyword, *args = line.split(/\s+/)
787
+ args.each { |arg|
788
+ arg.untaint
789
+ }
790
+ next unless keyword
791
+ case keyword
792
+ when 'nameserver'
793
+ nameserver += args
794
+ when 'domain'
795
+ next if args.empty?
796
+ search = [args[0]]
797
+ when 'search'
798
+ next if args.empty?
799
+ search = args
800
+ when 'options'
801
+ args.each {|arg|
802
+ case arg
803
+ when /\Andots:(\d+)\z/
804
+ ndots = $1.to_i
805
+ end
806
+ }
807
+ end
808
+ }
809
+ }
810
+ return { :nameserver => nameserver, :search => search, :ndots => ndots }
811
+ end
812
+
813
+ def Config.default_config_hash(filename="/etc/resolv.conf") # :nodoc:
814
+ if File.exist? filename
815
+ config_hash = Config.parse_resolv_conf(filename)
816
+ else
817
+ if /mswin32|cygwin|mingw|bccwin/ =~ RUBY_PLATFORM
818
+ search, nameserver = Win32::Resolv.get_resolv_info
819
+ config_hash = {}
820
+ config_hash[:nameserver] = nameserver if nameserver
821
+ config_hash[:search] = [search].flatten if search
822
+ end
823
+ end
824
+ config_hash
825
+ end
826
+
827
+ def lazy_initialize # :nodoc:
828
+ @mutex.synchronize {
829
+ unless @initialized
830
+ @nameserver = []
831
+ @search = nil
832
+ @ndots = 1
833
+ case @config_info
834
+ when nil
835
+ config_hash = Config.default_config_hash
836
+ when String
837
+ config_hash = Config.parse_resolv_conf(@config_info)
838
+ when Hash
839
+ config_hash = @config_info.dup
840
+ if String === config_hash[:nameserver]
841
+ config_hash[:nameserver] = [config_hash[:nameserver]]
842
+ end
843
+ if String === config_hash[:search]
844
+ config_hash[:search] = [config_hash[:search]]
845
+ end
846
+ else
847
+ raise ArgumentError.new("invalid resolv configuration: #{@config_info.inspect}")
848
+ end
849
+ @nameserver = config_hash[:nameserver] if config_hash.include? :nameserver
850
+ @search = config_hash[:search] if config_hash.include? :search
851
+ @ndots = config_hash[:ndots] if config_hash.include? :ndots
852
+
853
+ @nameserver = ['0.0.0.0'] if @nameserver.empty?
854
+ if @search
855
+ @search = @search.map {|arg| Label.split(arg) }
856
+ else
857
+ hostname = Socket.gethostname
858
+ if /\./ =~ hostname
859
+ @search = [Label.split($')]
860
+ else
861
+ @search = [[]]
862
+ end
863
+ end
864
+
865
+ if !@nameserver.kind_of?(Array) ||
866
+ !@nameserver.all? {|ns| String === ns }
867
+ raise ArgumentError.new("invalid nameserver config: #{@nameserver.inspect}")
868
+ end
869
+
870
+ if !@search.kind_of?(Array) ||
871
+ !@search.all? {|ls| ls.all? {|l| Label::Str === l } }
872
+ raise ArgumentError.new("invalid search config: #{@search.inspect}")
873
+ end
874
+
875
+ if !@ndots.kind_of?(Integer)
876
+ raise ArgumentError.new("invalid ndots config: #{@ndots.inspect}")
877
+ end
878
+
879
+ @initialized = true
880
+ end
881
+ }
882
+ self
883
+ end
884
+
885
+ def single?
886
+ lazy_initialize
887
+ if @nameserver.length == 1
888
+ return @nameserver[0]
889
+ else
890
+ return nil
891
+ end
892
+ end
893
+
894
+ def generate_candidates(name) # :nodoc:
895
+ candidates = nil
896
+ name = Name.create(name)
897
+ if name.absolute?
898
+ candidates = [name]
899
+ else
900
+ if @ndots <= name.length - 1
901
+ candidates = [Name.new(name.to_a)]
902
+ else
903
+ candidates = []
904
+ end
905
+ candidates.concat(@search.map {|domain| Name.new(name.to_a + domain)})
906
+ end
907
+ return candidates
908
+ end
909
+
910
+ InitialTimeout = 5 # :nodoc:
911
+
912
+ def generate_timeouts # :nodoc:
913
+ ts = [InitialTimeout]
914
+ ts << ts[-1] * 2 / @nameserver.length
915
+ ts << ts[-1] * 2
916
+ ts << ts[-1] * 2
917
+ return ts
918
+ end
919
+
920
+ def resolv(name) # :nodoc:
921
+ candidates = generate_candidates(name)
922
+ timeouts = generate_timeouts
923
+ begin
924
+ candidates.each {|candidate|
925
+ begin
926
+ timeouts.each {|tout|
927
+ @nameserver.each {|nameserver|
928
+ begin
929
+ yield candidate, tout, nameserver
930
+ rescue ResolvTimeout
931
+ end
932
+ }
933
+ }
934
+ raise ResolvError.new("DNS resolv timeout: #{name}")
935
+ rescue NXDomain
936
+ end
937
+ }
938
+ rescue ResolvError
939
+ end
940
+ end
941
+
942
+ class NXDomain < ResolvError
943
+ end
944
+
945
+ class OtherResolvError < ResolvError
946
+ end
947
+ end
948
+
949
+ module OpCode # :nodoc:
950
+ Query = 0
951
+ IQuery = 1
952
+ Status = 2
953
+ Notify = 4
954
+ Update = 5
955
+ end
956
+
957
+ module RCode # :nodoc:
958
+ NoError = 0
959
+ FormErr = 1
960
+ ServFail = 2
961
+ NXDomain = 3
962
+ NotImp = 4
963
+ Refused = 5
964
+ YXDomain = 6
965
+ YXRRSet = 7
966
+ NXRRSet = 8
967
+ NotAuth = 9
968
+ NotZone = 10
969
+ BADVERS = 16
970
+ BADSIG = 16
971
+ BADKEY = 17
972
+ BADTIME = 18
973
+ BADMODE = 19
974
+ BADNAME = 20
975
+ BADALG = 21
976
+ end
977
+
978
+ class DecodeError < StandardError
979
+ end
980
+
981
+ class EncodeError < StandardError
982
+ end
983
+
984
+ module Label #:nodoc:
985
+ def self.split(arg)
986
+ labels = []
987
+ arg.scan(/[^\.]+/) {labels << Str.new($&)}
988
+ return labels
989
+ end
990
+
991
+ # A String wrapper that compares (and hashes) case insensitively, used to
992
+ # represent DNS labels.
993
+ class Str
994
+ def initialize(string) # :nodoc:
995
+ @string = string
996
+ @downcase = string.downcase
997
+ end
998
+ attr_reader :string, :downcase
999
+
1000
+ def to_s
1001
+ return @string
1002
+ end
1003
+
1004
+ def inspect
1005
+ return "#<#{self.class} #{self.to_s.inspect}>"
1006
+ end
1007
+
1008
+ def ==(other)
1009
+ return @downcase == other.downcase
1010
+ end
1011
+
1012
+ def eql?(other)
1013
+ return self == other
1014
+ end
1015
+
1016
+ def hash
1017
+ return @downcase.hash
1018
+ end
1019
+ end
1020
+ end
1021
+
1022
+ # A DNS name is a sequence of labels seperated by (or followed by) a dot
1023
+ # (".").
1024
+ #
1025
+ # The labels are defined to be case-insensitive ("example.COM" is the same
1026
+ # as "EXAMPLE.com").
1027
+ #
1028
+ # Names created from DNS messages names are always absolute. Names created
1029
+ # from a String are absolute only if they have a trailing dot.
1030
+ class Name
1031
+ # Create a Name from a Name (in which case +arg+ is returned
1032
+ # directly), or from a String (in which case new Name is returned).
1033
+ def self.create(arg)
1034
+ case arg
1035
+ when Name
1036
+ return arg
1037
+ when String
1038
+ return Name.new(Label.split(arg), /\.\z/ =~ arg ? true : false)
1039
+ else
1040
+ raise ArgumentError.new("cannot interpret as DNS name: #{arg.inspect}")
1041
+ end
1042
+ end
1043
+
1044
+ def initialize(labels, absolute=true) # :nodoc:
1045
+ @labels = labels
1046
+ @absolute = absolute
1047
+ end
1048
+
1049
+ def inspect
1050
+ "#<#{self.class}: #{self.to_s}#{@absolute ? '.' : ''}>"
1051
+ end
1052
+
1053
+ # Tests if +self+ is absolute, i.e., had a trailing dot.
1054
+ # For example:
1055
+ # example.com.
1056
+ # is absolute, whereas:
1057
+ # example.com
1058
+ # is not. Absolute names will never have the default search domains
1059
+ # added to them during resolution.
1060
+ def absolute?
1061
+ return @absolute
1062
+ end
1063
+
1064
+ # Tests equivalence to +other+, a Name or a String.
1065
+ #
1066
+ # Names are equivalent if their labels are equal (comparison is
1067
+ # case-insensitive) and their "absoluteness" is equal.
1068
+ #
1069
+ # p Name.create("example.COM") == "EXAMPLE.com" => true
1070
+ # p Name.create("example.com.") == "example.com" => false
1071
+ def ==(other)
1072
+ other = Name.create(other)
1073
+ return false unless Name === other
1074
+ return @labels == other.to_a && @absolute == other.absolute?
1075
+ end
1076
+ alias eql? ==
1077
+
1078
+ # Tests subdomain-of relation.
1079
+ #
1080
+ # domain = Resolv::DNS::Name.create("y.z")
1081
+ # p Resolv::DNS::Name.create("w.x.y.z").subdomain_of?(domain) #=> true
1082
+ # p Resolv::DNS::Name.create("x.y.z").subdomain_of?(domain) #=> true
1083
+ # p Resolv::DNS::Name.create("y.z").subdomain_of?(domain) #=> false
1084
+ # p Resolv::DNS::Name.create("z").subdomain_of?(domain) #=> false
1085
+ # p Resolv::DNS::Name.create("x.y.z.").subdomain_of?(domain) #=> false
1086
+ # p Resolv::DNS::Name.create("w.z").subdomain_of?(domain) #=> false
1087
+ #
1088
+ def subdomain_of?(other)
1089
+ other = Name.create(other)
1090
+ other_len = other.length
1091
+ return false if @labels.length <= other_len
1092
+ return @labels[-other_len, other_len] == other.to_a
1093
+ end
1094
+
1095
+ def hash
1096
+ return @labels.hash ^ @absolute.hash
1097
+ end
1098
+
1099
+ # Returns the array of labels, each label is a Label::Str.
1100
+ def to_a
1101
+ return @labels
1102
+ end
1103
+
1104
+ # Returns the length of the array of labels.
1105
+ def length
1106
+ return @labels.length
1107
+ end
1108
+
1109
+ # Returns the +i+th label.
1110
+ def [](i)
1111
+ return @labels[i]
1112
+ end
1113
+
1114
+ # Returns the domain name as a string.
1115
+ #
1116
+ # The domain name doesn't have a trailing dot even if the name object is
1117
+ # absolute.
1118
+ #
1119
+ # p Resolv::DNS::Name.create("x.y.z.").to_s #=> "x.y.z"
1120
+ # p Resolv::DNS::Name.create("x.y.z").to_s #=> "x.y.z"
1121
+ #
1122
+ def to_s
1123
+ return @labels.join('.')
1124
+ end
1125
+ end
1126
+
1127
+ class Message # :nodoc:
1128
+ @@identifier = -1
1129
+
1130
+ def initialize(id = (@@identifier += 1) & 0xffff)
1131
+ @id = id
1132
+ @qr = 0
1133
+ @opcode = 0
1134
+ @aa = 0
1135
+ @tc = 0
1136
+ @rd = 0 # recursion desired
1137
+ @ra = 0 # recursion available
1138
+ @rcode = 0
1139
+ @question = []
1140
+ @answer = []
1141
+ @authority = []
1142
+ @additional = []
1143
+ end
1144
+
1145
+ attr_accessor :id, :qr, :opcode, :aa, :tc, :rd, :ra, :rcode
1146
+ attr_reader :question, :answer, :authority, :additional
1147
+
1148
+ def ==(other)
1149
+ return @id == other.id &&
1150
+ @qr == other.qr &&
1151
+ @opcode == other.opcode &&
1152
+ @aa == other.aa &&
1153
+ @tc == other.tc &&
1154
+ @rd == other.rd &&
1155
+ @ra == other.ra &&
1156
+ @rcode == other.rcode &&
1157
+ @question == other.question &&
1158
+ @answer == other.answer &&
1159
+ @authority == other.authority &&
1160
+ @additional == other.additional
1161
+ end
1162
+
1163
+ def add_question(name, typeclass, unicast = false)
1164
+ @question << [Name.create(name), typeclass, unicast]
1165
+ end
1166
+
1167
+ # Can accept either |name, typeclass| as block arguments
1168
+ # (backwards-compatible/DNS-style), or |name, typeclass, unicast|
1169
+ # (mDNS-style).
1170
+ def each_question # :yields: name, typeclass, unicast
1171
+ @question.each {|ary|
1172
+ yield ary
1173
+ }
1174
+ end
1175
+
1176
+ def add_answer(name, ttl, data, cacheflush = false)
1177
+ @answer << [Name.create(name), ttl, data, cacheflush]
1178
+ end
1179
+
1180
+ # Can accept either |name, ttl, data| as block arguments
1181
+ # (backwards-compatible/DNS-style), or |name, ttl, data, cacheflush|
1182
+ # (mDNS-style).
1183
+ def each_answer # :yields: name, ttl, data, cacheflush
1184
+ @answer.each {|ary|
1185
+ yield ary
1186
+ }
1187
+ end
1188
+
1189
+ def add_authority(name, ttl, data)
1190
+ @authority << [Name.create(name), ttl, data]
1191
+ end
1192
+
1193
+ def each_authority
1194
+ @authority.each {|name, ttl, data|
1195
+ yield name, ttl, data
1196
+ }
1197
+ end
1198
+
1199
+ def add_additional(name, ttl, data)
1200
+ @additional << [Name.create(name), ttl, data]
1201
+ end
1202
+
1203
+ def each_additional
1204
+ @additional.each {|name, ttl, data|
1205
+ yield name, ttl, data
1206
+ }
1207
+ end
1208
+
1209
+ def each_resource
1210
+ each_answer {|name, ttl, data| yield name, ttl, data}
1211
+ each_authority {|name, ttl, data| yield name, ttl, data}
1212
+ each_additional {|name, ttl, data| yield name, ttl, data}
1213
+ end
1214
+
1215
+ def encode
1216
+ return MessageEncoder.new {|msg|
1217
+ msg.put_pack('nnnnnn',
1218
+ @id,
1219
+ (@qr & 1) << 15 |
1220
+ (@opcode & 15) << 11 |
1221
+ (@aa & 1) << 10 |
1222
+ (@tc & 1) << 9 |
1223
+ (@rd & 1) << 8 |
1224
+ (@ra & 1) << 7 |
1225
+ (@rcode & 15),
1226
+ @question.length,
1227
+ @answer.length,
1228
+ @authority.length,
1229
+ @additional.length)
1230
+ @question.each {|q|
1231
+ name, typeclass, unicast = q
1232
+ hibit = unicast ? (1<<15) : 0x00
1233
+ msg.put_name(name)
1234
+ msg.put_pack('nn', typeclass::TypeValue, typeclass::ClassValue|hibit)
1235
+ }
1236
+ [@answer, @authority, @additional].each {|rr|
1237
+ rr.each {|r|
1238
+ name, ttl, data, cacheflush = r
1239
+ hibit = cacheflush ? (1<<15) : 0x00
1240
+ msg.put_name(name)
1241
+ msg.put_pack('nnN', data.class::TypeValue, data.class::ClassValue|hibit, ttl)
1242
+ msg.put_length16 {data.encode_rdata(msg)}
1243
+ }
1244
+ }
1245
+ }.to_s
1246
+ end
1247
+
1248
+ class MessageEncoder # :nodoc: used to implement Message.encode
1249
+ def initialize
1250
+ @data = ''
1251
+ @names = {}
1252
+ yield self
1253
+ end
1254
+
1255
+ def to_s
1256
+ return @data
1257
+ end
1258
+
1259
+ def put_bytes(d)
1260
+ @data << d
1261
+ end
1262
+
1263
+ def put_pack(template, *d)
1264
+ @data << d.pack(template)
1265
+ end
1266
+
1267
+ def put_length16
1268
+ length_index = @data.length
1269
+ @data << "\0\0"
1270
+ data_start = @data.length
1271
+ yield
1272
+ data_end = @data.length
1273
+ @data[length_index, 2] = [data_end - data_start].pack("n")
1274
+ end
1275
+
1276
+ def put_string(d)
1277
+ raise ArgumentError, "strings longer than 255 characters cannot be encoded" if d.length > 255
1278
+ self.put_pack("C", d.length)
1279
+ @data << d
1280
+ end
1281
+
1282
+ def put_string_list(ds)
1283
+ ds.each {|d|
1284
+ self.put_string(d)
1285
+ }
1286
+ end
1287
+
1288
+ def put_name(d)
1289
+ put_labels(d.to_a)
1290
+ end
1291
+
1292
+ def put_labels(d)
1293
+ d.each_index {|i|
1294
+ domain = d[i..-1]
1295
+ if idx = @names[domain]
1296
+ self.put_pack("n", 0xc000 | idx)
1297
+ return
1298
+ else
1299
+ @names[domain] = @data.length
1300
+ self.put_label(d[i])
1301
+ end
1302
+ }
1303
+ @data << "\0"
1304
+ end
1305
+
1306
+ def put_label(d)
1307
+ self.put_string(d.string)
1308
+ end
1309
+ end
1310
+
1311
+ def Message.decode(m)
1312
+ o = Message.new(0)
1313
+ MessageDecoder.new(m) {|msg|
1314
+ id, flag, qdcount, ancount, nscount, arcount =
1315
+ msg.get_unpack('nnnnnn')
1316
+ o.id = id
1317
+ o.qr = (flag >> 15) & 1
1318
+ o.opcode = (flag >> 11) & 15
1319
+ o.aa = (flag >> 10) & 1
1320
+ o.tc = (flag >> 9) & 1
1321
+ o.rd = (flag >> 8) & 1
1322
+ o.ra = (flag >> 7) & 1
1323
+ o.rcode = flag & 15
1324
+ (1..qdcount).each {
1325
+ name, typeclass, unicast = msg.get_question
1326
+ o.add_question(name, typeclass, unicast)
1327
+ }
1328
+ (1..ancount).each {
1329
+ name, ttl, data, cacheflush = msg.get_rr
1330
+ o.add_answer(name, ttl, data)
1331
+ }
1332
+ (1..nscount).each {
1333
+ name, ttl, data = msg.get_rr
1334
+ o.add_authority(name, ttl, data)
1335
+ }
1336
+ (1..arcount).each {
1337
+ name, ttl, data = msg.get_rr
1338
+ o.add_additional(name, ttl, data)
1339
+ }
1340
+ }
1341
+ return o
1342
+ end
1343
+
1344
+ class MessageDecoder # :nodoc: used to implement Message.decode
1345
+ def initialize(data)
1346
+ @data = data
1347
+ @index = 0
1348
+ @limit = data.length
1349
+ yield self
1350
+ end
1351
+
1352
+ def get_length16
1353
+ len, = self.get_unpack('n')
1354
+ save_limit = @limit
1355
+ @limit = @index + len
1356
+ d = yield(len)
1357
+ if @index < @limit
1358
+ raise DecodeError.new("junk exists")
1359
+ elsif @limit < @index
1360
+ raise DecodeError.new("limit exceeded")
1361
+ end
1362
+ @limit = save_limit
1363
+ return d
1364
+ end
1365
+
1366
+ def get_bytes(len = @limit - @index)
1367
+ d = @data[@index, len]
1368
+ @index += len
1369
+ return d
1370
+ end
1371
+
1372
+ def get_unpack(template)
1373
+ len = 0
1374
+ template.each_byte {|byte|
1375
+ case byte
1376
+ when ?c, ?C
1377
+ len += 1
1378
+ when ?n
1379
+ len += 2
1380
+ when ?N
1381
+ len += 4
1382
+ else
1383
+ raise StandardError.new("unsupported template: '#{byte.chr}' in '#{template}'")
1384
+ end
1385
+ }
1386
+ raise DecodeError.new("limit exceeded") if @limit < @index + len
1387
+ arr = @data.unpack("@#{@index}#{template}")
1388
+ @index += len
1389
+ return arr
1390
+ end
1391
+
1392
+ def get_string
1393
+ len = @data[@index]
1394
+ raise DecodeError.new("limit exceeded") if @limit < @index + 1 + len
1395
+ d = @data[@index + 1, len]
1396
+ @index += 1 + len
1397
+ return d
1398
+ end
1399
+
1400
+ def get_string_list
1401
+ strings = []
1402
+ while @index < @limit
1403
+ strings << self.get_string
1404
+ end
1405
+ strings
1406
+ end
1407
+
1408
+ def get_name
1409
+ return Name.new(self.get_labels)
1410
+ end
1411
+
1412
+ def get_labels(limit=nil)
1413
+ limit = @index if !limit || @index < limit
1414
+ d = []
1415
+ while true
1416
+ case @data[@index]
1417
+ when 0
1418
+ @index += 1
1419
+ return d
1420
+ when 192..255
1421
+ idx = self.get_unpack('n')[0] & 0x3fff
1422
+ if limit <= idx
1423
+ raise DecodeError.new("non-backward name pointer")
1424
+ end
1425
+ save_index = @index
1426
+ @index = idx
1427
+ d += self.get_labels(limit)
1428
+ @index = save_index
1429
+ return d
1430
+ else
1431
+ d << self.get_label
1432
+ end
1433
+ end
1434
+ return d
1435
+ end
1436
+
1437
+ def get_label
1438
+ return Label::Str.new(self.get_string)
1439
+ end
1440
+
1441
+ def get_question
1442
+ name = self.get_name
1443
+ type, klass = self.get_unpack("nn")
1444
+ typeclass = Resource.get_class(type, klass % 0x8000)
1445
+ unicast = (klass >> 15) == 1
1446
+ return name, typeclass, unicast
1447
+ end
1448
+
1449
+ def get_rr
1450
+ name = self.get_name
1451
+ type, klass, ttl = self.get_unpack('nnN')
1452
+ typeclass = Resource.get_class(type, klass % 0x8000)
1453
+ data = self.get_length16 {typeclass.decode_rdata(self)}
1454
+ cacheflush = (klass >> 15) == 1
1455
+ return name, ttl, data, cacheflush
1456
+ end
1457
+ end
1458
+ end
1459
+
1460
+ class Query # :nodoc:
1461
+ def encode_rdata(msg)
1462
+ raise EncodeError.new("#{self.class} is query.")
1463
+ end
1464
+
1465
+ def self.decode_rdata(msg)
1466
+ raise DecodeError.new("#{self.class} is query.")
1467
+ end
1468
+ end
1469
+
1470
+ class Resource < Query
1471
+ ClassHash = {} # :nodoc:
1472
+
1473
+ def encode_rdata(msg) # :nodoc:
1474
+ raise NotImplementedError.new
1475
+ end
1476
+
1477
+ def self.decode_rdata(msg) # :nodoc:
1478
+ raise NotImplementedError.new
1479
+ end
1480
+
1481
+ def ==(other)
1482
+ return self.class == other.class &&
1483
+ self.instance_variables == other.instance_variables &&
1484
+ self.instance_variables.collect {|name| self.instance_eval name} ==
1485
+ other.instance_variables.collect {|name| other.instance_eval name}
1486
+ end
1487
+
1488
+ def eql?(other)
1489
+ return self == other
1490
+ end
1491
+
1492
+ def hash
1493
+ h = 0
1494
+ self.instance_variables.each {|name|
1495
+ h ^= self.instance_eval("#{name}.hash")
1496
+ }
1497
+ return h
1498
+ end
1499
+
1500
+ def self.get_class(type_value, class_value) # :nodoc:
1501
+ return ClassHash[[type_value, class_value]] ||
1502
+ Generic.create(type_value, class_value)
1503
+ end
1504
+
1505
+ class Generic < Resource
1506
+ def initialize(data)
1507
+ @data = data
1508
+ end
1509
+ attr_reader :data
1510
+
1511
+ def encode_rdata(msg) # :nodoc:
1512
+ msg.put_bytes(data)
1513
+ end
1514
+
1515
+ def self.decode_rdata(msg) # :nodoc:
1516
+ return self.new(msg.get_bytes)
1517
+ end
1518
+
1519
+ def self.create(type_value, class_value) # :nodoc:
1520
+ c = Class.new(Generic)
1521
+ c.const_set(:TypeValue, type_value)
1522
+ c.const_set(:ClassValue, class_value)
1523
+ Generic.const_set("Type#{type_value}_Class#{class_value}", c)
1524
+ ClassHash[[type_value, class_value]] = c
1525
+ return c
1526
+ end
1527
+ end
1528
+
1529
+ class DomainName < Resource
1530
+ def initialize(name)
1531
+ @name = name
1532
+ end
1533
+ attr_reader :name
1534
+
1535
+ def encode_rdata(msg) # :nodoc:
1536
+ msg.put_name(@name)
1537
+ end
1538
+
1539
+ def self.decode_rdata(msg) # :nodoc:
1540
+ return self.new(msg.get_name)
1541
+ end
1542
+ end
1543
+
1544
+ # Standard (class generic) RRs
1545
+ ClassValue = nil
1546
+
1547
+ class NS < DomainName
1548
+ TypeValue = 2
1549
+ end
1550
+
1551
+ class CNAME < DomainName
1552
+ TypeValue = 5
1553
+ end
1554
+
1555
+ class SOA < Resource
1556
+ TypeValue = 6
1557
+
1558
+ def initialize(mname, rname, serial, refresh, retry_, expire, minimum)
1559
+ @mname = mname
1560
+ @rname = rname
1561
+ @serial = serial
1562
+ @refresh = refresh
1563
+ @retry = retry_
1564
+ @expire = expire
1565
+ @minimum = minimum
1566
+ end
1567
+ attr_reader :mname, :rname, :serial, :refresh, :retry, :expire, :minimum
1568
+
1569
+ def encode_rdata(msg) # :nodoc:
1570
+ msg.put_name(@mname)
1571
+ msg.put_name(@rname)
1572
+ msg.put_pack('NNNNN', @serial, @refresh, @retry, @expire, @minimum)
1573
+ end
1574
+
1575
+ def self.decode_rdata(msg) # :nodoc:
1576
+ mname = msg.get_name
1577
+ rname = msg.get_name
1578
+ serial, refresh, retry_, expire, minimum = msg.get_unpack('NNNNN')
1579
+ return self.new(
1580
+ mname, rname, serial, refresh, retry_, expire, minimum)
1581
+ end
1582
+ end
1583
+
1584
+ class PTR < DomainName
1585
+ TypeValue = 12
1586
+ end
1587
+
1588
+ class HINFO < Resource
1589
+ TypeValue = 13
1590
+
1591
+ def initialize(cpu, os)
1592
+ @cpu = cpu
1593
+ @os = os
1594
+ end
1595
+ attr_reader :cpu, :os
1596
+
1597
+ def encode_rdata(msg) # :nodoc:
1598
+ msg.put_string(@cpu)
1599
+ msg.put_string(@os)
1600
+ end
1601
+
1602
+ def self.decode_rdata(msg) # :nodoc:
1603
+ cpu = msg.get_string
1604
+ os = msg.get_string
1605
+ return self.new(cpu, os)
1606
+ end
1607
+ end
1608
+
1609
+ class MINFO < Resource
1610
+ TypeValue = 14
1611
+
1612
+ def initialize(rmailbx, emailbx)
1613
+ @rmailbx = rmailbx
1614
+ @emailbx = emailbx
1615
+ end
1616
+ attr_reader :rmailbx, :emailbx
1617
+
1618
+ def encode_rdata(msg) # :nodoc:
1619
+ msg.put_name(@rmailbx)
1620
+ msg.put_name(@emailbx)
1621
+ end
1622
+
1623
+ def self.decode_rdata(msg) # :nodoc:
1624
+ rmailbx = msg.get_string
1625
+ emailbx = msg.get_string
1626
+ return self.new(rmailbx, emailbx)
1627
+ end
1628
+ end
1629
+
1630
+ class MX < Resource
1631
+ TypeValue= 15
1632
+
1633
+ def initialize(preference, exchange)
1634
+ @preference = preference
1635
+ @exchange = exchange
1636
+ end
1637
+ attr_reader :preference, :exchange
1638
+
1639
+ def encode_rdata(msg) # :nodoc:
1640
+ msg.put_pack('n', @preference)
1641
+ msg.put_name(@exchange)
1642
+ end
1643
+
1644
+ def self.decode_rdata(msg) # :nodoc:
1645
+ preference, = msg.get_unpack('n')
1646
+ exchange = msg.get_name
1647
+ return self.new(preference, exchange)
1648
+ end
1649
+ end
1650
+
1651
+ class TXT < Resource
1652
+ TypeValue = 16
1653
+
1654
+ # TXT resource records must have one or more character strings, but the
1655
+ # string may be zero-length.
1656
+ #
1657
+ # If only the +first_string+ is supplied, it may be longer than 255
1658
+ # characters (internally, it will split into multiple
1659
+ # character-strings). If multiple strings are supplied, each string
1660
+ # must not be longer than 255 characters.
1661
+ def initialize(first_string = '', *rest_strings)
1662
+ if first_string.length > 255
1663
+ raise ArgumentError, 'TXT strings are longer than 255 characters' if rest_strings.first
1664
+
1665
+ @strings = []
1666
+ first_string.scan(/.{1,255}/) { |s| @strings << s }
1667
+ else
1668
+ @strings = [first_string, *rest_strings].compact
1669
+ end
1670
+ end
1671
+
1672
+ # Returns an array of all the strings making up the resource data.
1673
+ # There may be multiple strings if this is a mDNS record or if the
1674
+ # resource data is longer than 255 bytes. In the case of mDNS, each
1675
+ # individual string has the form: "key=value".
1676
+ attr_reader :strings
1677
+
1678
+ # Returns the resource data as a single string. DNS uses multiple
1679
+ # character strings to represent the data of a TXT record if the
1680
+ # data is longer than 255 characters.
1681
+ def data
1682
+ @strings.join
1683
+ end
1684
+
1685
+ def encode_rdata(msg) # :nodoc:
1686
+ msg.put_string_list(@strings)
1687
+ end
1688
+
1689
+ def self.decode_rdata(msg) # :nodoc:
1690
+ strings = msg.get_string_list
1691
+ return self.new(*strings)
1692
+ end
1693
+ end
1694
+
1695
+ class ANY < Query
1696
+ TypeValue = 255 # :nodoc:
1697
+ end
1698
+
1699
+ ClassInsensitiveTypes = [ # :nodoc:
1700
+ NS, CNAME, SOA, PTR, HINFO, MINFO, MX, TXT, ANY
1701
+ ]
1702
+
1703
+ # ARPA Internet specific RRs
1704
+ module IN
1705
+ ClassValue = 1
1706
+
1707
+ ClassInsensitiveTypes.each {|s|
1708
+ c = Class.new(s)
1709
+ c.const_set(:TypeValue, s::TypeValue)
1710
+ c.const_set(:ClassValue, ClassValue)
1711
+ ClassHash[[s::TypeValue, ClassValue]] = c
1712
+ self.const_set(s.name.sub(/.*::/, ''), c)
1713
+ }
1714
+
1715
+ class A < Resource
1716
+ ClassHash[[TypeValue = 1, ClassValue = ClassValue]] = self
1717
+
1718
+ def initialize(address)
1719
+ @address = IPv4.create(address)
1720
+ end
1721
+ attr_reader :address
1722
+
1723
+ def encode_rdata(msg) # :nodoc:
1724
+ msg.put_bytes(@address.address)
1725
+ end
1726
+
1727
+ def self.decode_rdata(msg) # :nodoc:
1728
+ return self.new(IPv4.new(msg.get_bytes(4)))
1729
+ end
1730
+ end
1731
+
1732
+ class WKS < Resource
1733
+ ClassHash[[TypeValue = 11, ClassValue = ClassValue]] = self
1734
+
1735
+ def initialize(address, protocol, bitmap)
1736
+ @address = IPv4.create(address)
1737
+ @protocol = protocol
1738
+ @bitmap = bitmap
1739
+ end
1740
+ attr_reader :address, :protocol, :bitmap
1741
+
1742
+ def encode_rdata(msg) # :nodoc:
1743
+ msg.put_bytes(@address.address)
1744
+ msg.put_pack("n", @protocol)
1745
+ msg.put_bytes(@bitmap)
1746
+ end
1747
+
1748
+ def self.decode_rdata(msg) # :nodoc:
1749
+ address = IPv4.new(msg.get_bytes(4))
1750
+ protocol, = msg.get_unpack("n")
1751
+ bitmap = msg.get_bytes
1752
+ return self.new(address, protocol, bitmap)
1753
+ end
1754
+ end
1755
+
1756
+ class AAAA < Resource
1757
+ ClassHash[[TypeValue = 28, ClassValue = ClassValue]] = self
1758
+
1759
+ def initialize(address)
1760
+ @address = IPv6.create(address)
1761
+ end
1762
+ attr_reader :address
1763
+
1764
+ def encode_rdata(msg) # :nodoc:
1765
+ msg.put_bytes(@address.address)
1766
+ end
1767
+
1768
+ def self.decode_rdata(msg) # :nodoc:
1769
+ return self.new(IPv6.new(msg.get_bytes(16)))
1770
+ end
1771
+ end
1772
+
1773
+ # SRV resource record defined in RFC 2782
1774
+ #
1775
+ # These records identify the hostname and port that a service is
1776
+ # available at.
1777
+ #
1778
+ # The format is:
1779
+ # _Service._Proto.Name TTL Class SRV Priority Weight Port Target
1780
+ #
1781
+ # The fields specific to SRV are defined in RFC 2782 as meaning:
1782
+ # - +priority+ The priority of this target host. A client MUST attempt
1783
+ # to contact the target host with the lowest-numbered priority it can
1784
+ # reach; target hosts with the same priority SHOULD be tried in an
1785
+ # order defined by the weight field. The range is 0-65535. Note that
1786
+ # it is not widely implemented and is commonly set to zero.
1787
+ #
1788
+ # - +weight+ A server selection mechanism. The weight field specifies
1789
+ # a relative weight for entries with the same priority. Larger weights
1790
+ # SHOULD be given a proportionately higher probability of being
1791
+ # selected. The range of this number is 0-65535. Domain administrators
1792
+ # SHOULD use Weight 0 when there isn't any server selection to do, to
1793
+ # make the RR easier to read for humans (less noisy). Note that it is
1794
+ # not widely implemented and should be set to zero.
1795
+ #
1796
+ # - +port+ The port on this target host of this service. The range is 0-
1797
+ # 65535.
1798
+ #
1799
+ # - +target+ The domain name of the target host. A target of "." means
1800
+ # that the service is decidedly not available at this domain.
1801
+ class SRV < Resource
1802
+ ClassHash[[TypeValue = 33, ClassValue = ClassValue]] = self # :nodoc:
1803
+
1804
+ # Create a SRV resource record.
1805
+ #
1806
+ # TODO - switch args to (target, port, priority = 0, weight = 0)? It
1807
+ # would be more convenient to use.
1808
+ def initialize(priority, weight, port, target)
1809
+ @priority = priority.to_int
1810
+ @weight = weight.to_int
1811
+ @port = port.to_int
1812
+ @target = Name.create(target)
1813
+ end
1814
+
1815
+ attr_reader :target, :port, :priority, :weight
1816
+
1817
+ def encode_rdata(msg) # :nodoc:
1818
+ msg.put_pack("n", @priority)
1819
+ msg.put_pack("n", @weight)
1820
+ msg.put_pack("n", @port)
1821
+ msg.put_name(@target)
1822
+ end
1823
+
1824
+ def self.decode_rdata(msg) # :nodoc:
1825
+ priority, = msg.get_unpack("n")
1826
+ weight, = msg.get_unpack("n")
1827
+ port, = msg.get_unpack("n")
1828
+ target = msg.get_name
1829
+ return self.new(priority, weight, port, target)
1830
+ end
1831
+ end
1832
+
1833
+ end
1834
+ end
1835
+ end
1836
+
1837
+ # Obsoleted by ipaddr.rb?
1838
+ class IPv4 # :nodoc:
1839
+ Regex = /\A(\d+)\.(\d+)\.(\d+)\.(\d+)\z/
1840
+
1841
+ def self.create(arg)
1842
+ if(arg.kind_of?(String) && arg.length == 4)
1843
+ return self.new(arg)
1844
+ end
1845
+ case arg
1846
+ when IPv4
1847
+ return arg
1848
+ when Regex
1849
+ if (0..255) === (a = $1.to_i) &&
1850
+ (0..255) === (b = $2.to_i) &&
1851
+ (0..255) === (c = $3.to_i) &&
1852
+ (0..255) === (d = $4.to_i)
1853
+ return self.new([a, b, c, d].pack("CCCC"))
1854
+ else
1855
+ raise ArgumentError.new("IPv4 address with invalid value: " + arg)
1856
+ end
1857
+ else
1858
+ raise ArgumentError.new("cannot interpret as IPv4 address: #{arg.inspect}")
1859
+ end
1860
+ end
1861
+
1862
+ def initialize(address)
1863
+ unless address.kind_of?(String) && address.length == 4
1864
+ raise ArgumentError.new('IPv4 address must be 4 bytes')
1865
+ end
1866
+ @address = address
1867
+ end
1868
+ attr_reader :address
1869
+
1870
+ def to_s
1871
+ return sprintf("%d.%d.%d.%d", *@address.unpack("CCCC"))
1872
+ end
1873
+
1874
+ def inspect
1875
+ return "#<#{self.class} #{self.to_s}>"
1876
+ end
1877
+
1878
+ def to_name
1879
+ return DNS::Name.create(
1880
+ '%d.%d.%d.%d.in-addr.arpa.' % @address.unpack('CCCC').reverse)
1881
+ end
1882
+
1883
+ def ==(other)
1884
+ return @address == other.address
1885
+ end
1886
+
1887
+ def eql?(other)
1888
+ return self == other
1889
+ end
1890
+
1891
+ def hash
1892
+ return @address.hash
1893
+ end
1894
+ end
1895
+
1896
+ # Obsoleted by ipaddr.rb?
1897
+ class IPv6 # :nodoc:
1898
+ Regex_8Hex = /\A
1899
+ (?:[0-9A-Fa-f]{1,4}:){7}
1900
+ [0-9A-Fa-f]{1,4}
1901
+ \z/x
1902
+
1903
+ Regex_CompressedHex = /\A
1904
+ ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) ::
1905
+ ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)
1906
+ \z/x
1907
+
1908
+ Regex_6Hex4Dec = /\A
1909
+ ((?:[0-9A-Fa-f]{1,4}:){6,6})
1910
+ (\d+)\.(\d+)\.(\d+)\.(\d+)
1911
+ \z/x
1912
+
1913
+ Regex_CompressedHex4Dec = /\A
1914
+ ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) ::
1915
+ ((?:[0-9A-Fa-f]{1,4}:)*)
1916
+ (\d+)\.(\d+)\.(\d+)\.(\d+)
1917
+ \z/x
1918
+
1919
+ Regex = /
1920
+ (?:#{Regex_8Hex}) |
1921
+ (?:#{Regex_CompressedHex}) |
1922
+ (?:#{Regex_6Hex4Dec}) |
1923
+ (?:#{Regex_CompressedHex4Dec})/x
1924
+
1925
+ def self.create(arg)
1926
+ case arg
1927
+ when IPv6
1928
+ return arg
1929
+ when String
1930
+ address = ''
1931
+ if Regex_8Hex =~ arg
1932
+ arg.scan(/[0-9A-Fa-f]+/) {|hex| address << [hex.hex].pack('n')}
1933
+ elsif Regex_CompressedHex =~ arg
1934
+ prefix = $1
1935
+ suffix = $2
1936
+ a1 = ''
1937
+ a2 = ''
1938
+ prefix.scan(/[0-9A-Fa-f]+/) {|hex| a1 << [hex.hex].pack('n')}
1939
+ suffix.scan(/[0-9A-Fa-f]+/) {|hex| a2 << [hex.hex].pack('n')}
1940
+ omitlen = 16 - a1.length - a2.length
1941
+ address << a1 << "\0" * omitlen << a2
1942
+ elsif Regex_6Hex4Dec =~ arg
1943
+ prefix, a, b, c, d = $1, $2.to_i, $3.to_i, $4.to_i, $5.to_i
1944
+ if (0..255) === a && (0..255) === b && (0..255) === c && (0..255) === d
1945
+ prefix.scan(/[0-9A-Fa-f]+/) {|hex| address << [hex.hex].pack('n')}
1946
+ address << [a, b, c, d].pack('CCCC')
1947
+ else
1948
+ raise ArgumentError.new("not numeric IPv6 address: " + arg)
1949
+ end
1950
+ elsif Regex_CompressedHex4Dec =~ arg
1951
+ prefix, suffix, a, b, c, d = $1, $2, $3.to_i, $4.to_i, $5.to_i, $6.to_i
1952
+ if (0..255) === a && (0..255) === b && (0..255) === c && (0..255) === d
1953
+ a1 = ''
1954
+ a2 = ''
1955
+ prefix.scan(/[0-9A-Fa-f]+/) {|hex| a1 << [hex.hex].pack('n')}
1956
+ suffix.scan(/[0-9A-Fa-f]+/) {|hex| a2 << [hex.hex].pack('n')}
1957
+ omitlen = 12 - a1.length - a2.length
1958
+ address << a1 << "\0" * omitlen << a2 << [a, b, c, d].pack('CCCC')
1959
+ else
1960
+ raise ArgumentError.new("not numeric IPv6 address: " + arg)
1961
+ end
1962
+ else
1963
+ raise ArgumentError.new("not numeric IPv6 address: " + arg)
1964
+ end
1965
+ return IPv6.new(address)
1966
+ else
1967
+ raise ArgumentError.new("cannot interpret as IPv6 address: #{arg.inspect}")
1968
+ end
1969
+ end
1970
+
1971
+ def initialize(address)
1972
+ unless address.kind_of?(String) && address.length == 16
1973
+ raise ArgumentError.new('IPv6 address must be 16 bytes')
1974
+ end
1975
+ @address = address
1976
+ end
1977
+ attr_reader :address
1978
+
1979
+ def to_s
1980
+ address = sprintf("%X:%X:%X:%X:%X:%X:%X:%X", *@address.unpack("nnnnnnnn"))
1981
+ unless address.sub!(/(^|:)0(:0)+(:|$)/, '::')
1982
+ address.sub!(/(^|:)0(:|$)/, '::')
1983
+ end
1984
+ return address
1985
+ end
1986
+
1987
+ def inspect
1988
+ return "#<#{self.class} #{self.to_s}>"
1989
+ end
1990
+
1991
+ def to_name
1992
+ # ip6.arpa should be searched too. [RFC3152]
1993
+ return DNS::Name.new(
1994
+ @address.unpack("H32")[0].split(//).reverse + ['ip6', 'int'])
1995
+ end
1996
+
1997
+ def ==(other)
1998
+ return @address == other.address
1999
+ end
2000
+
2001
+ def eql?(other)
2002
+ return self == other
2003
+ end
2004
+
2005
+ def hash
2006
+ return @address.hash
2007
+ end
2008
+ end
2009
+
2010
+ DefaultResolver = self.new
2011
+ AddressRegex = /(?:#{IPv4::Regex})|(?:#{IPv6::Regex})/
2012
+ end