redns 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,16 @@
1
+ .DS_Store
2
+ *.gem
3
+
4
+ *.tmproj
5
+ tmtags
6
+
7
+ *~
8
+ \#*
9
+ .\#*
10
+
11
+ *.swp
12
+
13
+ coverage
14
+ rdoc
15
+ pkg
16
+
data/README.rdoc ADDED
@@ -0,0 +1,10 @@
1
+ = redns
2
+
3
+ Reactor-Ready DNS Library
4
+
5
+ This library includes native Ruby encoder/decoder classes for typical
6
+ DNS records that are specified in RFC1035.
7
+
8
+ == References
9
+
10
+ [RFC1035](http://www.faqs.org/rfcs/rfc1035.html)
data/Rakefile ADDED
@@ -0,0 +1,51 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "redns"
8
+ gem.summary = %Q{Ruby Reactor-Ready DNS Library}
9
+ gem.description = %Q{ReDNS is a pure Ruby DNS library with drivers for reactor-model engines such as EventMachine}
10
+ gem.email = "github@tadman.ca"
11
+ gem.homepage = "http://github.com/tadman/redns"
12
+ gem.authors = %w[ tadman ]
13
+ end
14
+ Jeweler::GemcutterTasks.new
15
+ rescue LoadError
16
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
17
+ end
18
+
19
+ require 'rake/testtask'
20
+ Rake::TestTask.new(:test) do |test|
21
+ test.libs << 'lib' << 'test'
22
+ test.pattern = 'test/**/test_*.rb'
23
+ test.verbose = true
24
+ end
25
+
26
+ begin
27
+ require 'rcov/rcovtask'
28
+ Rcov::RcovTask.new do |test|
29
+ test.libs << 'test'
30
+ test.pattern = 'test/**/test_*.rb'
31
+ test.verbose = true
32
+ end
33
+ rescue LoadError
34
+ task :rcov do
35
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
36
+ end
37
+ end
38
+
39
+ task :test => :check_dependencies
40
+
41
+ task :default => :test
42
+
43
+ require 'rake/rdoctask'
44
+ Rake::RDocTask.new do |rdoc|
45
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
46
+
47
+ rdoc.rdoc_dir = 'rdoc'
48
+ rdoc.title = "redns #{version}"
49
+ rdoc.rdoc_files.include('README*')
50
+ rdoc.rdoc_files.include('lib/**/*.rb')
51
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.0
@@ -0,0 +1,44 @@
1
+ class ReDNS::Address < ReDNS::Fragment
2
+ # == Attributes ===========================================================
3
+
4
+ attribute :address, :default => '0.0.0.0'
5
+
6
+ # == Class Methods ========================================================
7
+
8
+ # == Instance Methods =====================================================
9
+
10
+ def initialize(contents = nil)
11
+ case (contents)
12
+ when ReDNS::Buffer
13
+ # Ensure that this String subclass is handled using the default
14
+ # method, not intercepted and treated as an actual String
15
+ super(contents)
16
+ when String
17
+ super(:address => contents)
18
+ else
19
+ super(contents)
20
+ end
21
+ end
22
+
23
+ def empty?
24
+ self.address == '0.0.0.0'
25
+ end
26
+
27
+ def to_s
28
+ self.address
29
+ end
30
+
31
+ def to_a
32
+ [ to_s ]
33
+ end
34
+
35
+ def serialize(buffer = ReDNS::Buffer.new)
36
+ buffer.append(inet_aton(self.address))
37
+ buffer
38
+ end
39
+
40
+ def deserialize(buffer)
41
+ self.address = inet_ntoa(buffer)
42
+ self
43
+ end
44
+ end
@@ -0,0 +1,138 @@
1
+ class ReDNS::Buffer < String
2
+ # == Constants ============================================================
3
+
4
+ # == Properties ===========================================================
5
+
6
+ alias_method :total_length, :length
7
+ alias_method :total_size, :size
8
+
9
+ attr_reader :offset
10
+ attr_reader :size
11
+ alias_method :length, :size
12
+
13
+ # == Class Methods ========================================================
14
+
15
+ # == Instance Methods =====================================================
16
+
17
+ # Create a buffer with arbitrary String contents. The offset parameter
18
+ # indicates where to start reading, which defaults to character 0 at the
19
+ # start of the string. The size parameter is used to limit how much of
20
+ # the content is used
21
+
22
+ def initialize(contents = nil, offset = nil, size = nil)
23
+ if (contents.respond_to?(:serialize))
24
+ super('')
25
+
26
+ @offset = 0
27
+ @size = total_length
28
+
29
+ contents.serialize(self)
30
+ else
31
+ super(contents || '')
32
+
33
+ @offset = offset ? offset.to_i : 0
34
+ @size = size ? size.to_i : nil
35
+ end
36
+
37
+ advance(0)
38
+ end
39
+
40
+ def unpack(format)
41
+ return [ ] if (@size <= 0)
42
+
43
+ raise ReDNS::Exception::BufferUnderrun if (@offset > total_length)
44
+
45
+ data = to_s.unpack(format)
46
+ advance(data.pack(format).length)
47
+ data
48
+ end
49
+
50
+ def pack(contents, format)
51
+ append(contents.pack(format))
52
+ end
53
+
54
+ def read(chars = 1)
55
+ raise ReDNS::Exception::BufferUnderrun if (@offset + chars > total_length)
56
+
57
+ result = to_str[@offset, chars]
58
+ advance(chars)
59
+ result
60
+ end
61
+
62
+ def write(contents, length = nil)
63
+ insertion = length ? contents[0, length] : contents
64
+
65
+ self[@offset, 0] = insertion
66
+
67
+ advance(insertion.length)
68
+ end
69
+
70
+ def append(contents, length = nil)
71
+ insertion = length ? contents[0, length] : contents
72
+
73
+ self << insertion
74
+
75
+ @size += insertion.length
76
+
77
+ self
78
+ end
79
+
80
+ def advance(chars = 1)
81
+ if (chars < 0)
82
+ rewind(-chars)
83
+ else
84
+ @offset += chars
85
+
86
+ if (@offset > total_length)
87
+ @offset = total_length
88
+ end
89
+
90
+ max_length = (total_length - @offset)
91
+
92
+ if (!@size or @size > max_length)
93
+ @size = max_length
94
+ end
95
+ end
96
+
97
+ @size ||= total_length - @offset
98
+
99
+ self
100
+ end
101
+
102
+ def rewind(chars = nil)
103
+ if (chars)
104
+ if (chars < 0)
105
+ advance(-chars)
106
+ else
107
+ @offset -= chars
108
+ @size += chars
109
+
110
+ if (@offset < 0)
111
+ @size += @offset
112
+ @offset = 0
113
+ end
114
+ end
115
+ else
116
+ @size += @offset
117
+ @offset = 0
118
+ end
119
+
120
+ self
121
+ end
122
+
123
+ def restore_state
124
+ offset = @offset
125
+ size = @size
126
+ yield(self) if (block_given?)
127
+ @offset = offset
128
+ @size = size
129
+ end
130
+
131
+ def to_s
132
+ to_str[@offset, @size]
133
+ end
134
+
135
+ def inspect
136
+ "\#<#{self.class} #{to_s.inspect}>"
137
+ end
138
+ end
@@ -0,0 +1,105 @@
1
+ class ReDNS::Fragment
2
+ # This represents a piece of data serialized in a particular DNS format,
3
+ # which includes both queries, responses, and the individual records that
4
+ # they contain.
5
+
6
+ # == Properties ===========================================================
7
+
8
+ attr_reader :attributes
9
+
10
+ # == Extensions ===========================================================
11
+
12
+ include ReDNS::Support
13
+
14
+ # == Class Methods ========================================================
15
+
16
+ # Declares an attribute of this fragment.
17
+ # Options:
18
+ # * :default = The default value to use if the attribute is not assigned
19
+ # * :convert = The conversion method to call on incoming values, which can
20
+ # be a Symbol, a Class, or a Proc.
21
+ # * :boolean = Converted to a boolean value, also introduces name? method
22
+ # * :primary = Indicates this is the primary attribute to be assigned when
23
+ # onstructed with only a String.
24
+
25
+ def self.attribute(name, options = nil)
26
+ name = name.to_sym
27
+
28
+ attribute_config = options || { }
29
+
30
+ default = attribute_config[:default]
31
+
32
+ if (attribute_config[:boolean])
33
+ default ||= false
34
+
35
+ attribute_config[:convert] ||= lambda { |v| !!v }
36
+ end
37
+
38
+ convert = attribute_config[:convert] || attribute_config[:class]
39
+
40
+ define_method(name) do
41
+ case (@attributes[name])
42
+ when nil
43
+ new_default = default.respond_to?(:call) ? default.call : default
44
+
45
+ @attributes[name] = convert ? convert.call(new_default) : new_default
46
+ else
47
+ @attributes[name]
48
+ end
49
+ end
50
+
51
+ if (attribute_config[:boolean])
52
+ alias_method :"#{name}?", name
53
+ end
54
+
55
+ case (convert)
56
+ when Symbol
57
+ convert_sym = convert
58
+ convert = lambda { |o| o.send(convert_sym) }
59
+ when Class
60
+ convert_class = convert
61
+ convert = lambda { |o| o.is_a?(convert_class) ? o : convert_class.new(o) }
62
+ end
63
+
64
+ define_method(:"#{name}=") do |value|
65
+ if (convert)
66
+ value = convert.call(value)
67
+ end
68
+
69
+ @attributes[name] = value
70
+ end
71
+
72
+ if (attribute_config[:primary])
73
+ alias_method :primary_attribute=, :"#{name}="
74
+ end
75
+ end
76
+
77
+ # == Instance Methods =====================================================
78
+
79
+ def initialize(contents = nil)
80
+ @attributes = { }
81
+
82
+ case (contents)
83
+ when Hash
84
+ assign(contents)
85
+ when ReDNS::Buffer
86
+ deserialize(contents)
87
+ when String
88
+ if (respond_to?(:primary_attribute=))
89
+ self.primary_attribute = contents
90
+ end
91
+ else
92
+ # FUTURE: Raise exception on invalid or unexpected type, but for now
93
+ # allow the subclass to try.
94
+ end
95
+
96
+ yield(self) if (block_given?)
97
+ end
98
+
99
+ protected
100
+ def assign(attributes)
101
+ attributes.each do |k, v|
102
+ send(:"#{k}=", v)
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,144 @@
1
+ class ReDNS::Message < ReDNS::Fragment
2
+ # == Constants ============================================================
3
+
4
+ SECTIONS = [ :questions, :answers, :nameservers, :additional_records ]
5
+
6
+ # == Attributes ===========================================================
7
+
8
+ attribute :random_id, :default => lambda { rand(0x10000) }
9
+
10
+ attribute :id, :convert => lambda { |v| v.to_i % 0x10000 }, :default => 1
11
+ attribute :query, :boolean => true, :default => true
12
+ attribute :opcode, :default => :query
13
+ attribute :authorative, :boolean => true, :default => false
14
+ attribute :truncated, :boolean => true, :default => false
15
+ attribute :recursion_desired, :boolean => true, :default => false
16
+ attribute :recursion_available, :boolean => true, :default => false
17
+ attribute :response_code, :default => :noerror
18
+
19
+ attribute :questions_count, :convert => :to_i, :default => 0
20
+ attribute :answers_count, :convert => :to_i, :default => 0
21
+ attribute :nameservers_count, :convert => :to_i, :default => 0
22
+ attribute :additional_records_count, :convert => :to_i, :default => 0
23
+
24
+ attribute :questions, :default => lambda { [ ] }
25
+ attribute :answers, :default => lambda { [ ] }
26
+ attribute :nameservers, :default => lambda { [ ] }
27
+ attribute :additional_records, :default => lambda { [ ] }
28
+
29
+ # == Class Methods ========================================================
30
+
31
+ # == Instance Methods =====================================================
32
+
33
+ def increment_id!
34
+ @attributes[:id] += 1
35
+ end
36
+
37
+ def response?
38
+ !self.query?
39
+ end
40
+
41
+ def to_s
42
+ flags = [ ]
43
+ flags << 'qr' if (response?)
44
+ flags << 'aa' if (authorative?)
45
+ flags << 'tc' if (truncated?)
46
+ flags << 'rd' if (recursion_desired?)
47
+ flags << 'ra' if (recursion_available?)
48
+
49
+ ";; HEADER:\n;; opcode: #{opcode.to_s.upcase} status: #{response_code.to_s.upcase} id: #{id} \n" +
50
+ ";; flags: #{flags.join(' ')}; QUERY: #{questions.length}, ANSWER: #{answers.length}, AUTHORITY: #{nameservers.length}, ADDITIONAL: #{additional_records.length}" +
51
+ "\n" +
52
+ ";; QUESTION SECTION:\n" +
53
+ questions.collect(&:to_s).join("\n") + "\n" +
54
+ ";; ANSWER SECTION:\n" +
55
+ answers.collect(&:to_s).join("\n") + "\n" +
56
+ ";; NAMESERVER SECTION:\n" +
57
+ nameservers.collect(&:to_s).join("\n") + "\n" +
58
+ ";; ADDITIONAL SECTION:\n" +
59
+ additional_records.collect(&:to_s).join("\n") + "\n"
60
+ end
61
+
62
+ def length
63
+ to_dns.length
64
+ end
65
+
66
+ def empty?
67
+ questions.empty? and
68
+ answers.empty? and
69
+ nameservers.empty? and
70
+ additional_records.empty?
71
+ end
72
+
73
+ def to_yaml
74
+ @attributes.to_yaml
75
+ end
76
+
77
+ def serialize(buffer = ReDNS::Buffer.new)
78
+ buffer.pack(
79
+ [
80
+ self.id,
81
+ (
82
+ (self.query? ? 0 : 0x8000) |
83
+ (ReDNS::OPCODE[self.opcode] || ReDNS::OPCODE[:unknown]) << 12 |
84
+ (self.authorative? ? 0x0400 : 0) |
85
+ (self.truncated? ? 0x0200 : 0) |
86
+ (self.ecursion_desired? ? 0x0100 : 0) |
87
+ (self.recursion_available? ? 0x0080 : 0) |
88
+ (ReDNS::RCODE[self.reponse_code] || ReDNS::RCODE[:noerror])
89
+ ),
90
+ self.questions.length,
91
+ self.answers.length,
92
+ self.nameservers.length,
93
+ self.additional_records.length
94
+ ],
95
+ 'nnnnnn'
96
+ )
97
+
98
+ [ :questions, :answers, :nameservers, :additional_records ].each do |section|
99
+ @attributes[:group].each do |part|
100
+ part.serialize(buffer)
101
+ end
102
+ end
103
+
104
+ buffer
105
+ end
106
+
107
+ def deserialize(buffer)
108
+ data = buffer.unpack("nnnnnn")
109
+
110
+ self.id = data.shift
111
+
112
+ flags = data.shift
113
+ self.query = flags & 0x8000
114
+ self.opcode = (flags & 0x7800) >> 12
115
+ self.authorative = flags & 0x0400
116
+ self.truncated = flags & 0x0200
117
+ self.recursion_desired = flags & 0x0100
118
+
119
+ self.recursion_available = flags & 0x0080
120
+ self.response_code = flags & 0x000F
121
+
122
+ SECTIONS.each do |section|
123
+ @attributes[:"#{section}_count"] = data.shift
124
+ end
125
+
126
+ SECTIONS.each do |section|
127
+ collection = @attributes[section] = [ ]
128
+
129
+ decode_class =
130
+ case (section)
131
+ when :question
132
+ ReDNS::Question
133
+ else
134
+ ReDNS::Resource
135
+ end
136
+
137
+ @attributes[:"#{section}_count"].times do
138
+ collection << decode_class.new(buffer)
139
+ end
140
+ end
141
+
142
+ self
143
+ end
144
+ end
data/lib/redns/name.rb ADDED
@@ -0,0 +1,79 @@
1
+ class ReDNS::Name < ReDNS::Fragment
2
+ # == Attributes ===========================================================
3
+
4
+ attribute :name, :default => '.'
5
+
6
+ # == Instance Methods =====================================================
7
+
8
+ def initialize(contents = nil)
9
+ case (contents)
10
+ when ReDNS::Buffer
11
+ # Ensure that this String subclass is handled using the default
12
+ # method, not intercepted and treated as an actual String
13
+ super(contents)
14
+ when String
15
+ super(:name => contents)
16
+ self.name << '.' unless (self.name.match(/\.$/))
17
+ else
18
+ super(contents)
19
+ end
20
+ end
21
+
22
+ def to_s
23
+ self.name
24
+ end
25
+
26
+ def to_a
27
+ [ self.name ]
28
+ end
29
+
30
+ def length
31
+ to_s.length
32
+ end
33
+
34
+ def empty?
35
+ name == '.'
36
+ end
37
+
38
+ def serialize(buffer = ReDNS::Buffer.new)
39
+ buffer.append(
40
+ self.name.split(/\./).collect { |l| [ l.length, l ].pack("ca*") }.join('')
41
+ )
42
+
43
+ buffer.append("\0")
44
+
45
+ buffer
46
+ end
47
+
48
+ def deserialize(buffer)
49
+ self.name = ''
50
+
51
+ return_to_offset = nil
52
+
53
+ while (c = buffer.unpack('C')[0])
54
+ if (c & 0xC0 == 0xC0)
55
+ # This is part of a pointer to another section, so advance to that
56
+ # point and read from there, but preserve the position where the
57
+ # pointer was found to leave the buffer in that final state.
58
+
59
+ pointer = (c & 0x3F << 8) | buffer.unpack('C')[0]
60
+
61
+ return_to_offset = buffer.offset
62
+ buffer.rewind
63
+ buffer.advance(pointer)
64
+ elsif (c == 0)
65
+ break
66
+ else
67
+ name << buffer.read(c)
68
+ name << '.'
69
+ end
70
+ end
71
+
72
+ if (return_to_offset)
73
+ buffer.rewind
74
+ buffer.advance(return_to_offset)
75
+ end
76
+
77
+ self
78
+ end
79
+ end
@@ -0,0 +1,38 @@
1
+ class ReDNS::Question < ReDNS::Fragment
2
+ # == Constants ============================================================
3
+
4
+ SECTIONS = [ :questions, :answers, :nameservers, :additional_records ]
5
+
6
+ # == Attributes ===========================================================
7
+
8
+ attribute :name, :class => ReDNS::Name, :default => lambda { ReDNS::Name.new }
9
+
10
+ attribute :qclass, :default => :in
11
+ attribute :qtype, :default => :any
12
+
13
+ # == Class Methods ========================================================
14
+
15
+ # == Instance Methods =====================================================
16
+
17
+ def to_s
18
+ "#{name} #{qclass.to_s.upcase} #{qtype.to_s.upcase}"
19
+ end
20
+
21
+ def serialize(buffer = ReDNS::Buffer.new)
22
+ name.serialize(buffer)
23
+ buffer.pack([ self.qtype_rfc, self.qclass_rfc ], "nn")
24
+
25
+ buffer
26
+ end
27
+
28
+ def deserialize(buffer)
29
+ self.name = DNS::Name.new(buffer)
30
+
31
+ data = buffer.unpack('nn')
32
+
33
+ self.qtype = ReDNS::RR_TYPE_LABEL[data.shift]
34
+ self.qclass = ReDNS::RR_CLASS_LABEL[data.shift]
35
+
36
+ self
37
+ end
38
+ end
@@ -0,0 +1,28 @@
1
+ class ReDNS::Record::MX < ReDNS::Fragment
2
+ # == Attributes ===========================================================
3
+
4
+ attribute :name, :default => lambda { ReDNS::Name.new }, :class => ReDNS::Name
5
+ attribute :preference, :default => 0, :convert => :to_i
6
+
7
+ # == Class Methods ========================================================
8
+
9
+ # == Instance Methods =====================================================
10
+
11
+ def to_s
12
+ "#{preference} #{name}"
13
+ end
14
+
15
+ def to_a
16
+ [ name.to_s, preference ]
17
+ end
18
+
19
+ def serialize(buffer = ReDNS::Buffer.new)
20
+ buffer.pack([ self.preference ], 'n')
21
+ self.name.serialize(buffer)
22
+ end
23
+
24
+ def deserialize(buffer)
25
+ self.preference = buffer.unpack('n')[0]
26
+ self.name = ReDNS::Name.new(buffer)
27
+ end
28
+ end
@@ -0,0 +1,30 @@
1
+ class ReDNS::Record::Null < ReDNS::Fragment
2
+ # == Attributes ===========================================================
3
+
4
+ attribute :contents, :default => '', :primary => true
5
+
6
+ # == Class Methods ========================================================
7
+
8
+ # == Instance Methods =====================================================
9
+
10
+ def to_s
11
+ "#{self.contents}"
12
+ end
13
+
14
+ def to_a
15
+ [ self.contents ]
16
+ end
17
+
18
+ def serialize(buffer = ReDNS::Buffer.new)
19
+ buffer.append(self.contents)
20
+
21
+ buffer
22
+ end
23
+
24
+ def deserialize(buffer)
25
+ self.contents = buffer.to_s
26
+ buffer.advance(self.contents.length)
27
+
28
+ self
29
+ end
30
+ end