net-mdns 0.4

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