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