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