redns 0.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/.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