├── .rspec
├── .gitignore
├── Gemfile
├── spec
├── fixtures
│ └── resolv.conf
├── spec_helper.rb
├── unit
│ └── resolver
│ │ ├── dns_timeout_spec.rb
│ │ ├── tcp_timeout_spec.rb
│ │ └── udp_timeout_spec.rb
└── resolver_spec.rb
├── .travis.yml
├── lib
└── net
│ ├── dns
│ ├── version.rb
│ ├── rr
│ │ ├── spf.rb
│ │ ├── srv.rb
│ │ ├── null.rb
│ │ ├── txt.rb
│ │ ├── mr.rb
│ │ ├── ns.rb
│ │ ├── ptr.rb
│ │ ├── cname.rb
│ │ ├── mx.rb
│ │ ├── soa.rb
│ │ ├── aaaa.rb
│ │ ├── hinfo.rb
│ │ ├── a.rb
│ │ ├── classes.rb
│ │ └── types.rb
│ ├── core_ext.rb
│ ├── resolver
│ │ ├── timeouts.rb
│ │ └── socks.rb
│ ├── names.rb
│ ├── question.rb
│ ├── rr.rb
│ ├── packet.rb
│ ├── header.rb
│ └── resolver.rb
│ └── dns.rb
├── demo
├── threads.rb
└── check_soa.rb
├── THANKS.rdoc
├── Rakefile
├── BSDL
├── net-dns.gemspec
├── LICENSE
├── CHANGELOG.md
└── README.md
/.rspec:
--------------------------------------------------------------------------------
1 | --colour
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Bundler
2 | .bundle
3 | pkg/*
4 | Gemfile.lock
5 |
6 | # YARD
7 | .yardoc
8 | yardoc/
9 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "http://rubygems.org"
2 |
3 | # Specify your gem's dependencies in whois.gemspec
4 | gemspec
5 |
--------------------------------------------------------------------------------
/spec/fixtures/resolv.conf:
--------------------------------------------------------------------------------
1 | search corporate.thoughtworks.com
2 | nameserver 192.168.1.1
3 | nameserver 192.168.1.2
4 | nameserver 192.168.1.3 192.168.1.4
5 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: ruby
2 |
3 | rvm:
4 | - 1.9.2
5 | - 1.9.3
6 | - 2.0.0
7 | - 2.1.0
8 | - ruby-head
9 |
10 | notifications:
11 | recipients:
12 | - mordocai@mordocai.net
13 |
--------------------------------------------------------------------------------
/lib/net/dns/version.rb:
--------------------------------------------------------------------------------
1 | module Net
2 | module DNS
3 | module Version
4 |
5 | MAJOR = 0
6 | MINOR = 8
7 | PATCH = 0
8 | BUILD = nil
9 |
10 | STRING = [MAJOR, MINOR, PATCH, BUILD].compact.join(".")
11 | end
12 |
13 | VERSION = Version::STRING
14 |
15 | end
16 | end
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | require 'rspec'
2 | require 'net/dns'
3 |
4 | unless defined?(SPEC_ROOT)
5 | SPEC_ROOT = File.expand_path("../", __FILE__)
6 | end
7 |
8 | # Requires supporting ruby files with custom matchers and macros, etc,
9 | # in spec/support/ and its subdirectories.
10 | Dir[File.join(SPEC_ROOT, "support/**/*.rb")].each { |f| require f }
11 |
12 | RSpec.configure do |config|
13 | # config.mock_with :mocha
14 | # config.syntax = :expect
15 | config.order = :random
16 | end
17 |
--------------------------------------------------------------------------------
/demo/threads.rb:
--------------------------------------------------------------------------------
1 | require 'rubygems' if "#{RUBY_VERSION}" < "1.9.0"
2 | require 'net/dns'
3 |
4 | a = ["ibm.com", "sun.com", "redhat.com"]
5 |
6 | threads = []
7 |
8 | for dom in a
9 | threads << Thread.new(dom) do |domain|
10 | res = Net::DNS::Resolver.new
11 | res.query(domain, Net::DNS::NS).each_nameserver do |ns|
12 | puts "Domain #{domain} has nameserver #{ns}"
13 | end
14 | puts ""
15 | end
16 | end
17 |
18 | threads.each do |t|
19 | t.join
20 | end
21 |
22 |
23 |
--------------------------------------------------------------------------------
/lib/net/dns/rr/spf.rb:
--------------------------------------------------------------------------------
1 | module Net # :nodoc:
2 | module DNS
3 | class RR
4 |
5 | #------------------------------------------------------------
6 | # RR type SPF
7 | #------------------------------------------------------------
8 | class SPF < TXT
9 |
10 | def spf
11 | txt
12 | end
13 |
14 | private
15 |
16 | def set_type
17 | @type = Net::DNS::RR::Types.new("SPF")
18 | end
19 |
20 | end
21 |
22 | end
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/THANKS.rdoc:
--------------------------------------------------------------------------------
1 | Many thanks to these wonderful people:
2 |
3 | - Paul Barry for his excellent article on May07 issue of LinuxJournal, for the kind words and for bug reports
4 | - Dan Janowski (and his SRV RR)
5 | - Joshua Abraham
6 | - Ben April
7 | - Gene Rogers
8 | - Nicholas Veeser
9 | - Gildas Cherruel
10 | - Alex Dalitz
11 | - Cory Wright
12 | - Nicolas Pugnant
13 | - Andrea Peltrin
14 | - Giovanni Corriga
15 | - Luca Russo
16 | - Pierguido Lambri
17 | - Andrea Pampuri
18 | - _koo_
19 | - Oyku Gencay
20 | - Eric Liedtke
21 | - Justin Mercier
22 | - Daniel Berger
23 | and all #ruby-lang people
24 |
25 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require 'rubygems'
2 | require 'bundler'
3 | require 'rspec'
4 |
5 | $:.unshift(File.dirname(__FILE__) + '/lib')
6 | require 'net/dns'
7 |
8 |
9 | task :default => [:spec, :gem]
10 |
11 | require 'rake'
12 | require 'rspec/core/rake_task'
13 |
14 | RSpec::Core::RakeTask.new(:spec) do |t|
15 | t.pattern = FileList['spec/**/*_spec.rb']
16 | t.rspec_opts = ["-r ./spec/spec_helper.rb"]
17 | end
18 |
19 | require 'rubygems/package_task'
20 | Gem::PackageTask.new(Gem::Specification.load("net-dns.gemspec")).define
21 |
22 | require 'yard'
23 | YARD::Rake::YardocTask.new(:yardoc) do |y|
24 | y.options = ["--output-dir", "yardoc"]
25 | end
26 |
27 | namespace :yardoc do
28 | task :clobber do
29 | rm_r "yardoc" rescue nil
30 | end
31 | end
32 |
33 | task :clobber => "yardoc:clobber"
34 |
35 |
36 | desc "Open an irb session preloaded with this library"
37 | task :console do
38 | sh "irb -rubygems -I lib -r net/dns.rb"
39 | end
40 |
--------------------------------------------------------------------------------
/spec/unit/resolver/dns_timeout_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 | require 'net/dns/resolver/timeouts'
3 |
4 | describe Net::DNS::Resolver::DnsTimeout do
5 |
6 | subject { described_class.new(10) }
7 |
8 | describe "#initialize" do
9 | it "returns an instance of DnsTimeout" do
10 | expect(subject.class).to be(described_class)
11 | end
12 |
13 | it "sets timeout" do
14 | expect(described_class.new(0).seconds).to eq(0)
15 | expect(described_class.new(10).seconds).to eq(10)
16 | end
17 |
18 | it "raises ArgumentError when timeout is invalid" do
19 | expect { described_class.new(nil) }.to raise_error(ArgumentError)
20 | expect { described_class.new("") }.to raise_error(ArgumentError)
21 | expect { described_class.new("foo") }.to raise_error(ArgumentError)
22 | expect { described_class.new(-1) }.to raise_error(ArgumentError)
23 | end
24 | end
25 |
26 | describe "#to_s" do
27 | it "returns the seconds" do
28 | expect(subject.to_s).to eq("10")
29 | end
30 | end
31 |
32 | describe "#timeout" do
33 | it "requires a block" do
34 | expect { subject.timeout }.to raise_error(LocalJumpError)
35 | end
36 | end
37 |
38 | end
39 |
--------------------------------------------------------------------------------
/lib/net/dns/rr/srv.rb:
--------------------------------------------------------------------------------
1 | module Net # :nodoc:
2 | module DNS
3 | class RR
4 |
5 | #------------------------------------------------------------
6 | # RR type SRV
7 | #------------------------------------------------------------
8 | class SRV < RR
9 |
10 | attr_reader :priority, :weight, :port, :host
11 |
12 | private
13 |
14 | def build_pack
15 | str = ""
16 | end
17 |
18 | def subclass_new_from_binary(data,offset)
19 | off_end = offset + @rdlength
20 | @priority, @weight, @port = data.unpack("@#{offset} n n n")
21 | offset+=6
22 |
23 | @host=[]
24 | while offset < off_end
25 | len = data.unpack("@#{offset} C")[0]
26 | offset += 1
27 | str = data[offset..offset+len-1]
28 | offset += len
29 | @host << str
30 | end
31 | @host=@host.join(".")
32 | offset
33 | end
34 |
35 | private
36 |
37 | def set_type
38 | @type = Net::DNS::RR::Types.new("SRV")
39 | end
40 |
41 | end
42 | end
43 |
44 | end
45 | end
46 |
--------------------------------------------------------------------------------
/lib/net/dns/core_ext.rb:
--------------------------------------------------------------------------------
1 | module Net # :nodoc:
2 | module DNS
3 |
4 | module HashKeys # :nodoc:
5 |
6 | # Returns an hash with all the
7 | # keys turned into downcase
8 | #
9 | # hsh = {"Test" => 1, "FooBar" => 2}
10 | # hsh.downcase_keys!
11 | # #=> {"test"=>1,"foobar"=>2}
12 | #
13 | def downcase_keys!
14 | hsh = Hash.new
15 | self.each do |key,val|
16 | hsh[key.downcase] = val
17 | end
18 | self.replace(hsh)
19 | end
20 |
21 | end
22 |
23 | module HashOperators # :nodoc:
24 |
25 | # Performs a sort of group difference
26 | # operation on hashes or arrays
27 | #
28 | # a = {:a=>1,:b=>2,:c=>3}
29 | # b = {:a=>1,:b=>2}
30 | # c = [:a,:c]
31 | # a-b #=> {:c=>3}
32 | # a-c #=> {:b=>2}
33 | #
34 | def -(other)
35 | case other
36 | when Hash
37 | delete_if { |k,v| other.has_key?(k) }
38 | when Array
39 | delete_if { |k,v| other.include?(k) }
40 | end
41 | end
42 |
43 | end
44 |
45 | end
46 | end
47 |
48 |
49 | class Hash # :nodoc:
50 | include Net::DNS::HashKeys
51 | include Net::DNS::HashOperators
52 | end
--------------------------------------------------------------------------------
/lib/net/dns/rr/null.rb:
--------------------------------------------------------------------------------
1 | module Net # :nodoc:
2 | module DNS
3 | class RR
4 |
5 | #------------------------------------------------------------
6 | # RR type NULL
7 | #------------------------------------------------------------
8 | class NULL < RR
9 | attr_reader :null
10 |
11 | private
12 |
13 | def build_pack
14 | @null_pack = @null
15 | @rdlength = @null_pack.size
16 | end
17 |
18 | def get_data
19 | @null_pack
20 | end
21 |
22 | def get_inspect
23 | "#@null"
24 | end
25 |
26 | def subclass_new_from_hash(args)
27 | if args.has_key? :null
28 | @null = args[:null]
29 | else
30 | raise ArgumentError, ":null field is mandatory but missing"
31 | end
32 | end
33 |
34 | def subclass_new_from_string(str)
35 | @null = str.strip
36 | end
37 |
38 | def subclass_new_from_binary(data,offset)
39 | @null = data[offset..offset+@rdlength]
40 | return offset + @rdlength
41 | end
42 |
43 | private
44 |
45 | def set_type
46 | @type = Net::DNS::RR::Types.new("NULL")
47 | end
48 |
49 | end
50 |
51 | end
52 | end
53 | end
54 |
--------------------------------------------------------------------------------
/BSDL:
--------------------------------------------------------------------------------
1 | Copyright (c) 2014 Christopher Carpenter, Marco Ceresa, and Simone Carletti
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
5 |
6 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
7 |
8 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
9 |
10 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
11 |
--------------------------------------------------------------------------------
/spec/unit/resolver/tcp_timeout_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 | require 'net/dns/resolver/timeouts'
3 |
4 | describe Net::DNS::Resolver::TcpTimeout do
5 |
6 | subject { described_class.new(10) }
7 |
8 | it "inherits from DnsTimeout" do
9 | expect(described_class.ancestors).to include(Net::DNS::Resolver::DnsTimeout)
10 | end
11 |
12 | describe "#initialize" do
13 | it "returns an instance of TcpTimeout" do
14 | expect(subject.class).to be(described_class)
15 | end
16 |
17 | it "sets timeout" do
18 | expect(described_class.new(0).seconds).to eq(0)
19 | expect(described_class.new(10).seconds).to eq(10)
20 | end
21 |
22 | it "raises ArgumentError when timeout is invalid" do
23 | expect { described_class.new(nil) }.to raise_error(ArgumentError)
24 | expect { described_class.new("") }.to raise_error(ArgumentError)
25 | expect { described_class.new("foo") }.to raise_error(ArgumentError)
26 | expect { described_class.new(-1) }.to raise_error(ArgumentError)
27 | end
28 | end
29 |
30 | describe "#to_s" do
31 | it "returns infinite when seconds is 0" do
32 | expect(described_class.new(0).to_s).to eq("infinite")
33 | end
34 |
35 | it "returns the seconds" do
36 | expect(subject.to_s).to eq("10")
37 | end
38 | end
39 |
40 | describe "#pretty_to_s" do
41 | it "returns a more verbose version" do
42 | expect(described_class.new(30).pretty_to_s).to eq("30 seconds")
43 | expect(described_class.new(90).pretty_to_s).to eq("1 minutes and 30 seconds")
44 | expect(described_class.new(3690).pretty_to_s).to eq("1 hours, 1 minutes and 30 seconds")
45 | end
46 | end
47 |
48 | end
49 |
--------------------------------------------------------------------------------
/spec/unit/resolver/udp_timeout_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 | require 'net/dns/resolver/timeouts'
3 |
4 | describe Net::DNS::Resolver::UdpTimeout do
5 |
6 | subject { described_class.new(10) }
7 |
8 | it "inherits from DnsTimeout" do
9 | expect(described_class.ancestors).to include(Net::DNS::Resolver::DnsTimeout)
10 | end
11 |
12 | describe "#initialize" do
13 | it "returns an instance of TcpTimeout" do
14 | expect(subject.class).to be(described_class)
15 | end
16 |
17 | it "sets timeout" do
18 | expect(described_class.new(0).seconds).to eq(0)
19 | expect(described_class.new(10).seconds).to eq(10)
20 | end
21 |
22 | it "raises ArgumentError when timeout is invalid" do
23 | expect { described_class.new(nil) }.to raise_error(ArgumentError)
24 | expect { described_class.new("") }.to raise_error(ArgumentError)
25 | expect { described_class.new("foo") }.to raise_error(ArgumentError)
26 | expect { described_class.new(-1) }.to raise_error(ArgumentError)
27 | end
28 | end
29 |
30 | describe "#to_s" do
31 | it "returns infinite when seconds is 0" do
32 | expect(described_class.new(0).to_s).to eq("not defined")
33 | end
34 |
35 | it "returns the seconds" do
36 | expect(subject.to_s).to eq("10")
37 | end
38 | end
39 |
40 | describe "#pretty_to_s" do
41 | it "returns a more verbose version" do
42 | expect(described_class.new(30).pretty_to_s).to eq("30 seconds")
43 | expect(described_class.new(90).pretty_to_s).to eq("1 minutes and 30 seconds")
44 | expect(described_class.new(3690).pretty_to_s).to eq("1 hours, 1 minutes and 30 seconds")
45 | end
46 | end
47 |
48 | end
49 |
--------------------------------------------------------------------------------
/lib/net/dns/rr/txt.rb:
--------------------------------------------------------------------------------
1 | module Net # :nodoc:
2 | module DNS
3 | class RR
4 |
5 | #------------------------------------------------------------
6 | # RR type TXT
7 | #------------------------------------------------------------
8 | class TXT < RR
9 | attr_reader :txt
10 |
11 | private
12 |
13 | def build_pack
14 | str = ""
15 | @txt.split(" ").each do |txt|
16 | str += [txt.length,txt].pack("C a*")
17 | end
18 | @txt_pack = str
19 | @rdlength = @txt_pack.size
20 | end
21 |
22 | def get_inspect
23 | "\"#{@txt}\""
24 | end
25 |
26 | def get_data
27 | @txt_pack
28 | end
29 |
30 | def subclass_new_from_hash(args)
31 | if args.has_key? :txt
32 | @txt = args[:txt].strip
33 | else
34 | raise ArgumentError, ":txt field is mandatory but missing"
35 | end
36 | end
37 |
38 | def subclass_new_from_string(str)
39 | @txt = str.strip
40 | if @txt[0] == '"' and @txt[-1] == '"'
41 | @txt = @txt[1, @txt.length-2]
42 | else
43 | raise ArgumentError, "TXT RR data must be enclosed in \"quotes\""
44 | end
45 | end
46 |
47 | def subclass_new_from_binary(data,offset)
48 | off_end = offset + @rdlength
49 | rs = []
50 | while offset < off_end
51 | len = data.unpack("@#{offset} C")[0]
52 | offset += 1
53 | str = data[offset..offset+len-1]
54 | offset += len
55 | rs << str
56 | end
57 | @txt = rs.join(" ")
58 | return offset
59 | end
60 |
61 | private
62 |
63 | def set_type
64 | @type = Net::DNS::RR::Types.new("TXT")
65 | end
66 |
67 | end
68 |
69 | end
70 | end
71 | end
72 |
--------------------------------------------------------------------------------
/lib/net/dns/rr/mr.rb:
--------------------------------------------------------------------------------
1 | module Net # :nodoc:
2 | module DNS
3 |
4 | class RR
5 |
6 | #
7 | # = Mail Rename Record (MR)
8 | #
9 | # Class for DNS MR resource records.
10 | #
11 | class MR < RR
12 |
13 | # Gets the newname value.
14 | #
15 | # Returns a String.
16 | def newname
17 | @newname
18 | end
19 |
20 | # Gets the standardized value for this record,
21 | # represented by the value of newname.
22 | #
23 | # Returns a String.
24 | def value
25 | newname.to_s
26 | end
27 |
28 |
29 | private
30 |
31 | def subclass_new_from_hash(options)
32 | if options.has_key?(:newname)
33 | @newname = check_name(options[:newname])
34 | else
35 | raise ArgumentError, ":newname field is mandatory"
36 | end
37 | end
38 |
39 | def subclass_new_from_string(str)
40 | @newname = check_name(str)
41 | end
42 |
43 | def subclass_new_from_binary(data, offset)
44 | @newname = dn_expand(data,offset)
45 | offset
46 | end
47 |
48 |
49 | def set_type
50 | @type = Net::DNS::RR::Types.new("MR")
51 | end
52 |
53 | def get_inspect
54 | value
55 | end
56 |
57 |
58 | def check_name(input)
59 | name = input.to_s
60 | unless name =~ /(\w\.?)+\s*$/
61 | raise ArgumentError, "Invalid Domain Name `#{name}'"
62 | end
63 | name
64 | end
65 |
66 | def build_pack
67 | @newname_pack = pack_name(@newname)
68 | @rdlength = @newname_pack.size
69 | end
70 |
71 | def get_data
72 | @newname_pack
73 | end
74 |
75 | end
76 |
77 | end
78 | end
79 | end
80 |
--------------------------------------------------------------------------------
/lib/net/dns/rr/ns.rb:
--------------------------------------------------------------------------------
1 | module Net # :nodoc:
2 | module DNS
3 | class RR
4 |
5 | #
6 | # = Name Server Record (NS)
7 | #
8 | # Class for DNS NS resource records.
9 | #
10 | class NS < RR
11 |
12 | # Gets the name server value.
13 | #
14 | # Returns a String.
15 | def nsdname
16 | @nsdname
17 | end
18 |
19 | # Gets the standardized value for this record,
20 | # represented by the value of nsdname.
21 | #
22 | # Returns a String.
23 | def value
24 | nsdname.to_s
25 | end
26 |
27 |
28 | private
29 |
30 | def subclass_new_from_hash(options)
31 | if options.has_key?(:nsdname)
32 | @nsdname = check_name(options[:nsdname])
33 | else
34 | raise ArgumentError, ":nsdname field is mandatory"
35 | end
36 | end
37 |
38 | def subclass_new_from_string(str)
39 | @nsdname = check_name(str)
40 | end
41 |
42 | def subclass_new_from_binary(data, offset)
43 | @nsdname, offset = dn_expand(data, offset)
44 | offset
45 | end
46 |
47 |
48 | def set_type
49 | @type = Net::DNS::RR::Types.new("NS")
50 | end
51 |
52 | def get_inspect
53 | value
54 | end
55 |
56 |
57 | def check_name(input)
58 | name = input.to_s
59 | unless name =~ /(\w\.?)+\s*$/ and name =~ /[a-zA-Z]/
60 | raise ArgumentError, "Invalid Name Server `#{name}'"
61 | end
62 | name
63 | end
64 |
65 | def build_pack
66 | @nsdname_pack = pack_name(@nsdname)
67 | @rdlength = @nsdname_pack.size
68 | end
69 |
70 | def get_data
71 | @nsdname_pack
72 | end
73 |
74 | end
75 |
76 | end
77 | end
78 | end
79 |
--------------------------------------------------------------------------------
/lib/net/dns/rr/ptr.rb:
--------------------------------------------------------------------------------
1 | module Net
2 | module DNS
3 | class RR
4 |
5 | #
6 | # = Pointer Record (PTR)
7 | #
8 | # Class for DNS Pointer (PTR) resource records.
9 | #
10 | # Pointer records are the opposite of A and AAAA RRs
11 | # and are used in Reverse Map zone files to map
12 | # an IP address (IPv4 or IPv6) to a host name.
13 | #
14 | class PTR < RR
15 |
16 | # Gets the PTR value.
17 | #
18 | # Returns a String.
19 | def ptrdname
20 | @ptrdname.to_s
21 | end
22 |
23 | alias_method :ptr, :ptrdname
24 |
25 | # Gets the standardized value for this record,
26 | # represented by the value of ptrdname.
27 | #
28 | # Returns a String.
29 | def value
30 | ptrdname.to_s
31 | end
32 |
33 |
34 | private
35 |
36 | def build_pack
37 | @ptrdname_pack = pack_name(@ptrdname)
38 | @rdlength = @ptrdname_pack.size
39 | end
40 |
41 | def get_data
42 | @ptrdname_pack
43 | end
44 |
45 | def subclass_new_from_hash(args)
46 | if args.has_key?(:ptrdname) or args.has_key?(:ptr)
47 | @ptrdname = args[:ptrdname]
48 | else
49 | raise ArgumentError, ":ptrdname or :ptr field is mandatory"
50 | end
51 | end
52 |
53 | def subclass_new_from_string(str)
54 | @ptrdname = check_name(str)
55 | end
56 |
57 | def subclass_new_from_binary(data, offset)
58 | @ptrdname, offset = dn_expand(data, offset)
59 | offset
60 | end
61 |
62 | private
63 |
64 | def set_type
65 | @type = Net::DNS::RR::Types.new("PTR")
66 | end
67 |
68 | def get_inspect
69 | value
70 | end
71 |
72 |
73 | def check_name(input)
74 | IPAddr.new(str)
75 | rescue
76 | raise ArgumentError, "Invalid PTR Section `#{input}'"
77 | end
78 |
79 | end
80 |
81 | end
82 | end
83 | end
84 |
--------------------------------------------------------------------------------
/lib/net/dns/resolver/timeouts.rb:
--------------------------------------------------------------------------------
1 | require 'timeout'
2 |
3 | module Net # :nodoc:
4 | module DNS
5 | class Resolver
6 |
7 | class DnsTimeout
8 |
9 | attr_reader :seconds
10 |
11 |
12 | def initialize(seconds)
13 | if seconds.is_a?(Numeric) && seconds >= 0
14 | @seconds = seconds
15 | else
16 | raise ArgumentError, "Invalid value for tcp timeout"
17 | end
18 | end
19 |
20 | # Returns a string representation of the timeout corresponding
21 | # to the number of @seconds.
22 | def to_s
23 | @seconds == 0 ? @output.to_s : @seconds.to_s
24 | end
25 |
26 | def pretty_to_s
27 | transform(@seconds)
28 | end
29 |
30 | # Executes the method's block. If the block execution terminates before +sec+
31 | # seconds has passed, it returns true. If not, it terminates the execution
32 | # and raises Timeout::Error.
33 | # If @seconds is 0 or nil, no timeout is set.
34 | def timeout(&block)
35 | raise LocalJumpError, "no block given" unless block_given?
36 | Timeout.timeout(@seconds, &block)
37 | end
38 |
39 |
40 | private
41 |
42 | def transform(secs)
43 | case secs
44 | when 0
45 | to_s
46 | when 1..59
47 | "#{secs} seconds"
48 | when 60..3559
49 | "#{secs / 60} minutes and #{secs % 60} seconds"
50 | else
51 | hours = secs / 3600
52 | secs -= (hours * 3600)
53 | "#{hours} hours, #{secs / 60} minutes and #{secs % 60} seconds"
54 | end
55 | end
56 |
57 | end
58 |
59 | class TcpTimeout < DnsTimeout
60 | def initialize(seconds)
61 | @output = "infinite"
62 | super
63 | end
64 | end
65 |
66 | class UdpTimeout < DnsTimeout
67 | def initialize(seconds)
68 | @output = "not defined"
69 | super
70 | end
71 | end
72 |
73 | end
74 | end
75 | end
--------------------------------------------------------------------------------
/lib/net/dns/rr/cname.rb:
--------------------------------------------------------------------------------
1 | module Net # :nodoc:
2 | module DNS
3 | class RR
4 |
5 | #
6 | # = Canonical Name Record (CNAME)
7 | #
8 | # Class for DNS CNAME resource records.
9 | #
10 | # A CNAME record maps an alias or nickname to the real or Canonical name
11 | # which may lie outside the current zone.
12 | # Canonical means expected or real name.
13 | #
14 | class CNAME < RR
15 |
16 | # Gets the canonical name value.
17 | #
18 | # Returns a String.
19 | def cname
20 | @cname
21 | end
22 |
23 | # Gets the standardized value for this record,
24 | # represented by the value of cname.
25 | #
26 | # Returns a String.
27 | def value
28 | cname.to_s
29 | end
30 |
31 |
32 | private
33 |
34 | def subclass_new_from_hash(options)
35 | if options.has_key?(:cname)
36 | @cname = check_name(options[:cname])
37 | else
38 | raise ArgumentError, ":cname field is mandatory"
39 | end
40 | end
41 |
42 | def subclass_new_from_string(str)
43 | @cname = check_name(str)
44 | end
45 |
46 | def subclass_new_from_binary(data, offset)
47 | @cname, offset = dn_expand(data, offset)
48 | offset
49 | end
50 |
51 |
52 | def set_type
53 | @type = Net::DNS::RR::Types.new("CNAME")
54 | end
55 |
56 | def get_inspect
57 | value
58 | end
59 |
60 |
61 | def check_name(input)
62 | name = input.to_s
63 | unless name =~ /(\w\.?)+\s*$/ and name =~ /[a-zA-Z]/
64 | raise ArgumentError, "Invalid Canonical Name `#{name}'"
65 | end
66 | name
67 | end
68 |
69 | def build_pack
70 | @cname_pack = pack_name(@cname)
71 | @rdlength = @cname_pack.size
72 | end
73 |
74 | def get_data
75 | @cname_pack
76 | end
77 |
78 | end
79 |
80 | end
81 | end
82 | end
83 |
--------------------------------------------------------------------------------
/lib/net/dns.rb:
--------------------------------------------------------------------------------
1 | require 'net/dns/core_ext'
2 | require 'net/dns/version'
3 | require 'net/dns/resolver'
4 |
5 | module Net
6 | module DNS
7 |
8 | # Packet size in bytes
9 | PACKETSZ = 512
10 |
11 | # Size of the header
12 | HFIXEDSZ = 12
13 |
14 | # Size of the question portion (type and class)
15 | QFIXEDSZ = 4
16 |
17 | # Size of an RR portion (type,class,lenght and ttl)
18 | RRFIXEDSZ = 10
19 |
20 | # Size of an int 32 bit
21 | INT32SZ = 4
22 |
23 | # Size of a short int
24 | INT16SZ = 2
25 |
26 |
27 | module QueryTypes
28 |
29 | SIGZERO = 0
30 | A = 1
31 | NS = 2
32 | MD = 3
33 | MF = 4
34 | CNAME = 5
35 | SOA = 6
36 | MB = 7
37 | MG = 8
38 | MR = 9
39 | NULL = 10
40 | WKS = 11
41 | PTR = 12
42 | HINFO = 13
43 | MINFO = 14
44 | MX = 15
45 | TXT = 16
46 | RP = 17
47 | AFSDB = 18
48 | X25 = 19
49 | ISDN = 20
50 | RT = 21
51 | NSAP = 22
52 | NSAPPTR = 23
53 | SIG = 24
54 | KEY = 25
55 | PX = 26
56 | GPOS = 27
57 | AAAA = 28
58 | LOC = 29
59 | NXT = 30
60 | EID = 31
61 | NIMLOC = 32
62 | SRV = 33
63 | ATMA = 34
64 | NAPTR = 35
65 | KX = 36
66 | CERT = 37
67 | DNAME = 39
68 | OPT = 41
69 | DS = 43
70 | SSHFP = 44
71 | RRSIG = 46
72 | NSEC = 47
73 | DNSKEY = 48
74 | SPF = 99
75 | UINFO = 100
76 | UID = 101
77 | GID = 102
78 | UNSPEC = 103
79 | TKEY = 249
80 | TSIG = 250
81 | IXFR = 251
82 | AXFR = 252
83 | MAILB = 253
84 | MAILA = 254
85 | ANY = 255
86 |
87 | end
88 |
89 | module QueryClasses
90 |
91 | # Internet class
92 | IN = 1
93 |
94 | # Chaos class
95 | CH = 3
96 |
97 | # Hesiod class
98 | HS = 4
99 |
100 | # None class
101 | NONE = 254
102 |
103 | # Any class
104 | ANY = 255
105 |
106 | end
107 |
108 | include QueryTypes
109 | include QueryClasses
110 |
111 | end
112 |
113 | end
114 |
--------------------------------------------------------------------------------
/lib/net/dns/rr/mx.rb:
--------------------------------------------------------------------------------
1 | module Net # :nodoc:
2 | module DNS
3 | class RR
4 |
5 | #
6 | # = Mail Exchange Record (MX)
7 | #
8 | # Class for DNS MX resource records.
9 | #
10 | # A MX record specifies the name and relative preference of mail servers
11 | # (mail exchangers in the DNS jargon) for the zone.
12 | # The MX RR is used by SMTP (Mail) Agents to route mail for the domain.
13 | #
14 | class MX < RR
15 |
16 | # Gets the preference value.
17 | #
18 | # Returns an Integer.
19 | def preference
20 | @preference
21 | end
22 |
23 | # Gets the exchange value.
24 | #
25 | # Returns a String.
26 | def exchange
27 | @exchange
28 | end
29 |
30 | # Gets the standardized value for this record,
31 | # represented by the value of preference and exchange.
32 | #
33 | # Returns a String.
34 | def value
35 | "#{preference} #{exchange}"
36 | end
37 |
38 |
39 | private
40 |
41 | def subclass_new_from_hash(options)
42 | if options.has_key?(:preference) && options.has_key?(:exchange)
43 | @preference = options[:preference].to_i
44 | @exchange = options[:exchange]
45 | else
46 | raise ArgumentError, ":preference and :exchange fields are mandatory"
47 | end
48 | end
49 |
50 | def subclass_new_from_string(str)
51 | @preference, @exchange = check_mx(str)
52 | end
53 |
54 | def subclass_new_from_binary(data, offset)
55 | @preference = data.unpack("@#{offset} n")[0]
56 | offset += 2
57 | @exchange, offset = dn_expand(data, offset)
58 | offset
59 | end
60 |
61 |
62 | def set_type
63 | @type = Net::DNS::RR::Types.new("MX")
64 | end
65 |
66 | def get_inspect
67 | value
68 | end
69 |
70 |
71 | def check_mx(input)
72 | str = input.to_s
73 | unless str.strip =~ /^(\d+)\s+(\S+)$/
74 | raise ArgumentError, "Invalid MX section `#{str}'"
75 | end
76 | [$1.to_i, $2]
77 | end
78 |
79 | def build_pack
80 | @mx_pack = [@preference].pack("n") + pack_name(@exchange)
81 | @rdlength = @mx_pack.size
82 | end
83 |
84 | def get_data
85 | @mx_pack
86 | end
87 |
88 | end
89 |
90 | end
91 | end
92 | end
93 |
--------------------------------------------------------------------------------
/net-dns.gemspec:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 |
3 | Gem::Specification.new do |s|
4 | s.name = "net-dns2"
5 | s.version = "0.8.4"
6 |
7 | s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8 | s.authors = ["Marco Ceresa", "Simone Carletti", "Christopher Carpenter"]
9 | s.date = "2014-05-12"
10 | s.description = "Net::DNS is a pure Ruby DNS library, with a clean OO interface and an extensible API. The net-dns2 ruby gem is an actively maintained fork of the original net-dns."
11 | s.email = "mordocai@mordocai.net"
12 | s.files = [
13 | ".gitignore", ".travis.yml", "CHANGELOG.md", "Gemfile", "README.md",
14 | "Rakefile", "THANKS.rdoc", "demo/check_soa.rb", "demo/threads.rb",
15 | "lib/net/dns.rb", "lib/net/dns/core_ext.rb", "lib/net/dns/header.rb",
16 | "lib/net/dns/names.rb", "lib/net/dns/packet.rb", "lib/net/dns/question.rb",
17 | "lib/net/dns/resolver.rb", "lib/net/dns/resolver/socks.rb",
18 | "lib/net/dns/resolver/timeouts.rb", "lib/net/dns/rr.rb",
19 | "lib/net/dns/rr/a.rb", "lib/net/dns/rr/aaaa.rb", "lib/net/dns/rr/classes.rb",
20 | "lib/net/dns/rr/cname.rb", "lib/net/dns/rr/hinfo.rb", "lib/net/dns/rr/mr.rb",
21 | "lib/net/dns/rr/mx.rb", "lib/net/dns/rr/ns.rb", "lib/net/dns/rr/null.rb",
22 | "lib/net/dns/rr/ptr.rb", "lib/net/dns/rr/soa.rb", "lib/net/dns/rr/spf.rb",
23 | "lib/net/dns/rr/srv.rb", "lib/net/dns/rr/txt.rb", "lib/net/dns/rr/types.rb",
24 | "lib/net/dns/version.rb", "net-dns.gemspec"
25 | ]
26 | s.homepage = "http://github.com/mordocai/net-dns"
27 | s.require_paths = ["lib"]
28 | s.summary = "Pure Ruby DNS library, fork with fixes."
29 | # s.test_files = [
30 | # "spec/fixtures/resolv.conf", "spec/resolver_spec.rb", "spec/spec_helper.rb",
31 | # "spec/unit/resolver/dns_timeout_spec.rb", "spec/unit/tcp_timeout_spec.rb",
32 | # "spec/unit/udp_timeout_spec.rb"
33 | # ]
34 | s.required_ruby_version = Gem::Requirement.new(">= 1.9.2")
35 | s.license = 'Ruby'
36 |
37 | if s.respond_to? :specification_version then
38 | s.specification_version = 4
39 |
40 | if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
41 | s.add_development_dependency(%q, ["~> 10.0"])
42 | s.add_development_dependency(%q, [">= 0"])
43 | s.add_development_dependency(%q, [">= 0"])
44 | else
45 | s.add_dependency(%q, ["~> 10.0"])
46 | s.add_dependency(%q, [">= 0"])
47 | s.add_dependency(%q, [">= 0"])
48 | end
49 | else
50 | s.add_dependency(%q, ["~> 10.0"])
51 | s.add_dependency(%q, [">= 0"])
52 | s.add_dependency(%q, [">= 0"])
53 | end
54 |
55 | s.add_runtime_dependency "packetfu"
56 | end
57 |
--------------------------------------------------------------------------------
/lib/net/dns/rr/soa.rb:
--------------------------------------------------------------------------------
1 | module Net # :nodoc:
2 | module DNS
3 | class RR
4 |
5 | #------------------------------------------------------------
6 | # RR type SOA
7 | #------------------------------------------------------------
8 | class SOA < RR
9 | attr_reader :mname, :rname, :serial, :refresh, :retry, :expire, :minimum
10 |
11 | private
12 |
13 | def build_pack
14 | @soa_pack = pack_name(@mname)
15 | @soa_pack += pack_name(@rname)
16 | @soa_pack += [@serial,@refresh,@retry,@expire,@minimum].pack("N5")
17 | @rdlength = @soa_pack.size
18 | end
19 |
20 | def get_data
21 | @soa_pack
22 | end
23 |
24 | def get_inspect
25 | "#@mname #@rname #@serial #@refresh #@retry #@expire #@minimum"
26 | end
27 |
28 | def subclass_new_from_hash(args)
29 | if args.has_key? :rdata
30 | subclass_new_from_string(args[:rdata])
31 | else
32 | [:mname,:rname,:serial,:refresh,:retry,:expire,:minimum].each do |key|
33 | raise ArgumentError, "Missing field :#{key}" unless args.has_key? key
34 | end
35 | @mname = args[:mname] if valid? args[:mname]
36 | @rname = args[:rname] if rname? args[:rname]
37 | @serial = args[:serial] if number? args[:serial]
38 | @refresh = args[:refresh] if number? args[:refresh]
39 | @retry = args[:retry] if number? args[:retry]
40 | @expire = args[:expire] if number? args[:expire]
41 | @minimum = args[:minimum] if number? args[:minimum]
42 | end
43 | end
44 |
45 | def number?(num)
46 | if num.kind_of? Integer and num > 0
47 | true
48 | else
49 | false
50 | end
51 | end
52 |
53 | def rname?(name)
54 | mailbox, domain = name.split(/(?String or an IPAddr object.
21 | #
22 | # Examples
23 | #
24 | # a.address = "192.168.0.1"
25 | # a.address = IPAddr.new("10.0.0.1")
26 | #
27 | # Returns the new allocated instance of IPAddr.
28 | def address=(string_or_ipaddr)
29 | @address = check_address(string_or_ipaddr)
30 | build_pack
31 | @address
32 | end
33 |
34 | # Gets the standardized value for this record,
35 | # represented by the value of address.
36 | #
37 | # Returns a String.
38 | def value
39 | address.to_s
40 | end
41 |
42 |
43 | private
44 |
45 | def subclass_new_from_hash(options)
46 | if options.has_key?(:address)
47 | @address = check_address(options[:address])
48 | else
49 | raise ArgumentError, ":address field is mandatory"
50 | end
51 | end
52 |
53 | def subclass_new_from_string(str)
54 | @address = check_address(str)
55 | end
56 |
57 | def subclass_new_from_binary(data, offset)
58 | tokens = data.unpack("@#{offset} n8")
59 | @address = IPAddr.new(sprintf("%x:%x:%x:%x:%x:%x:%x:%x", *tokens))
60 | offset + 16
61 | end
62 |
63 |
64 | def set_type
65 | @type = Net::DNS::RR::Types.new("AAAA")
66 | end
67 |
68 | def get_inspect
69 | value
70 | end
71 |
72 |
73 | def check_address(input)
74 | address = case input
75 | when IPAddr
76 | input
77 | when String
78 | IPAddr.new(input)
79 | else
80 | raise ArgumentError, "Invalid IP address `#{input}'"
81 | end
82 |
83 | if !address.ipv6?
84 | raise(ArgumentError, "Must specify an IPv6 address")
85 | end
86 |
87 | address
88 | end
89 |
90 | def build_pack
91 | @address_pack = @address.hton
92 | @rdlength = @address_pack.size
93 | end
94 |
95 | def get_data
96 | @address_pack
97 | end
98 |
99 | end
100 |
101 | end
102 | end
103 | end
104 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Original license statement by https://github.com/bluemonk/net-dns (pre-fork) stated that the code was
2 | "distributed under the same license Ruby is".
3 |
4 | I have interpreted that as meaning it is under the "Ruby License" which, at the time of this writing,
5 | is reproduced below (taken from https://www.ruby-lang.org/en/about/license.txt).
6 |
7 | Net::DNS is copyrighted free software by Christopher Carpenter (current maintainer),
8 | Marco Ceresa, and Simone Carletti(weppos@weppos.net).
9 |
10 | You can redistribute it and/or modify it under either the terms of the
11 | 2-clause BSDL (see the file BSDL), or the conditions below:
12 |
13 | 1. You may make and give away verbatim copies of the source form of the
14 | software without restriction, provided that you duplicate all of the
15 | original copyright notices and associated disclaimers.
16 |
17 | 2. You may modify your copy of the software in any way, provided that
18 | you do at least ONE of the following:
19 |
20 | a) place your modifications in the Public Domain or otherwise
21 | make them Freely Available, such as by posting said
22 | modifications to Usenet or an equivalent medium, or by allowing
23 | the author to include your modifications in the software.
24 |
25 | b) use the modified software only within your corporation or
26 | organization.
27 |
28 | c) give non-standard binaries non-standard names, with
29 | instructions on where to get the original software distribution.
30 |
31 | d) make other distribution arrangements with the author.
32 |
33 | 3. You may distribute the software in object code or binary form,
34 | provided that you do at least ONE of the following:
35 |
36 | a) distribute the binaries and library files of the software,
37 | together with instructions (in the manual page or equivalent)
38 | on where to get the original distribution.
39 |
40 | b) accompany the distribution with the machine-readable source of
41 | the software.
42 |
43 | c) give non-standard binaries non-standard names, with
44 | instructions on where to get the original software distribution.
45 |
46 | d) make other distribution arrangements with the author.
47 |
48 | 4. You may modify and include the part of the software into any other
49 | software (possibly commercial). But some files in the distribution
50 | are not written by the author, so that they are not under these terms.
51 |
52 | For the list of those files and their copying conditions, see the
53 | file LEGAL.
54 |
55 | 5. The scripts and library files supplied as input to or produced as
56 | output from the software do not automatically fall under the
57 | copyright of the software, but belong to whomever generated them,
58 | and may be sold commercially, and may be aggregated with this
59 | software.
60 |
61 | 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
62 | IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
63 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
64 | PURPOSE.
65 |
--------------------------------------------------------------------------------
/lib/net/dns/rr/hinfo.rb:
--------------------------------------------------------------------------------
1 | module Net # :nodoc:
2 | module DNS
3 | class RR
4 |
5 | #
6 | # = System Information Record (HINFO)
7 | #
8 | # Class for DNS HINFO resource records.
9 | #
10 | # Allows definition of the Hardware type and Operating System (OS) in use at a host.
11 | # For security reasons these records are rarely used on public servers.
12 | # If a space exists in the field it must be enclosed in quotes.
13 | # Single space between CPU and OS parameters.
14 | #
15 | class HINFO < RR
16 |
17 | # Gets the CPU value.
18 | #
19 | # Returns a String.
20 | def cpu
21 | @cpu
22 | end
23 |
24 | # Gets the OS value.
25 | #
26 | # Returns a String.
27 | def os
28 | @os
29 | end
30 |
31 | # Gets the standardized value for this record,
32 | # represented by the value of cpu and os.
33 | #
34 | # Returns a String.
35 | def value
36 | %Q{"#{cpu}" "#{os}"}
37 | end
38 |
39 |
40 | # Gets a list of all the attributes for this record.
41 | #
42 | # Returns an Array of values.
43 | def to_a
44 | [nil, nil, cls.to_s, type.to_s, value]
45 | end
46 |
47 |
48 | private
49 |
50 | def subclass_new_from_hash(options)
51 | if options.has_key?(:cpu) && options.has_key?(:os)
52 | @cpu = options[:cpu]
53 | @os = options[:os]
54 | else
55 | raise ArgumentError, ":cpu and :os fields are mandatory"
56 | end
57 | end
58 |
59 | def subclass_new_from_string(str)
60 | @cpu, @os = check_hinfo(str)
61 | end
62 |
63 | def subclass_new_from_binary(data, offset)
64 | len = data.unpack("@#{offset} C").first
65 | offset += 1
66 | @cpu = data[offset..(offset + len)]
67 | offset += len
68 |
69 | len = data.unpack("@#{offset} C").first
70 | offset += 1
71 | @os = data[offset..(offset + len)]
72 | offset += len
73 | end
74 |
75 |
76 | def set_type
77 | @type = Net::DNS::RR::Types.new("HINFO")
78 | end
79 |
80 | def get_inspect
81 | value
82 | end
83 |
84 |
85 | def check_hinfo(input)
86 | if input.to_s.strip =~ /^(?:["']?(.*?)["']?)\s+(?:["']?(.*?)["']?)$/
87 | [$1, $2]
88 | else
89 | raise ArgumentError, "Invalid HINFO Section `#{input}'"
90 | end
91 | end
92 |
93 | def build_pack
94 | @hinfo_pack = ""
95 | @hinfo_pack += [cpu.size].pack("C") + cpu
96 | @hinfo_pack += [os.size ].pack("C") + os
97 | @rdlength = @hinfo_pack.size
98 | end
99 |
100 | def get_data
101 | @hinfo_pack
102 | end
103 |
104 | end
105 |
106 | end
107 | end
108 | end
109 |
--------------------------------------------------------------------------------
/spec/resolver_spec.rb:
--------------------------------------------------------------------------------
1 | require 'net/dns'
2 |
3 | describe Net::DNS::Resolver do
4 | before :each do
5 | @resolv = Net::DNS::Resolver.new
6 | end
7 |
8 | describe "packet_size=" do
9 | it "can set packet_size as integer" do
10 | @resolv.packet_size = 2048
11 | @resolv.packet_size.should eq 2048
12 | end
13 |
14 | it "can set packet_size as string" do
15 | @resolv.packet_size = "2048"
16 | @resolv.packet_size.should eq 2048
17 | end
18 |
19 | it "can set packet_size as arbitrary class" do
20 | class Blah
21 | def to_i
22 | 2048
23 | end
24 | end
25 | x = Blah.new
26 | @resolv.packet_size = x
27 | @resolv.packet_size.should eq 2048
28 | end
29 | end
30 |
31 | describe "nameservers=" do
32 | let(:dns_ipaddr) {IPAddr.new("8.8.8.8")}
33 | let(:dns_string) {"8.8.8.8"}
34 |
35 | it "can set nameserver as IPAddr" do
36 | @resolv.nameserver = dns_ipaddr
37 | @resolv.nameserver.should eq [dns_string]
38 | end
39 |
40 | it "can set nameserver as String ip" do
41 | @resolv.nameserver = "8.8.8.8"
42 | @resolv.nameserver.should eq [dns_string]
43 | end
44 |
45 | it "can set nameserver as String domain" do
46 | @resolv.nameserver = "google-public-dns-a.google.com"
47 | @resolv.nameserver.should eq [dns_string]
48 | end
49 |
50 | it "can set nameserver as Array" do
51 | @resolv.nameservers = [
52 | "google-public-dns-a.google.com",
53 | dns_ipaddr,
54 | "8.8.8.8"
55 | ]
56 |
57 | @resolv.nameserver.should eq [
58 | dns_string,
59 | dns_string,
60 | dns_string
61 | ]
62 | end
63 |
64 | it "can set nameserver as Arbitrary to_a object" do
65 | class Blah
66 | def to_a
67 | [
68 | "google-public-dns-a.google.com",
69 | "8.8.8.8"
70 | ]
71 | end
72 | end
73 | @resolv.nameservers = Blah.new
74 | @resolv.nameserver.should eq [
75 | dns_string,
76 | dns_string
77 | ]
78 | end
79 |
80 | it "can set nameserver as Arbitrary map object" do
81 | class Blah
82 | def initialize
83 | @array = [
84 | "google-public-dns-a.google.com",
85 | "8.8.8.8"
86 | ]
87 | end
88 |
89 | def map &block
90 | ret = []
91 | @array.each do |item|
92 | ret << block.call(item)
93 | end
94 | ret
95 | end
96 | end
97 |
98 | @resolv.nameservers = Blah.new
99 | @resolv.nameserver.should eq [
100 | dns_string,
101 | dns_string
102 | ]
103 | end
104 | end
105 |
106 | it "returns TXT records properly" do
107 | result = Net::DNS::Resolver.new.query "google.com", Net::DNS::TXT
108 | result.answer.first.should be_a Net::DNS::RR::TXT
109 | end
110 |
111 | context "packet_size set" do
112 | before :all do
113 | @resolv = Net::DNS::Resolver.new({packet_size: 2048})
114 | end
115 |
116 | it "can query" do
117 | @resolv.query "google.com"
118 | end
119 | end
120 |
121 | context "nameservers set" do
122 | before :all do
123 | @resolv = Net::DNS::Resolver.new({nameservers: ["8.8.8.8", "8.8.4.4"]})
124 | end
125 |
126 | it "can query" do
127 | @resolv.query "google.com"
128 | end
129 |
130 | #TODO: Add tests that ensure that the correct nameservers are used for lookup
131 | end
132 | end
133 |
--------------------------------------------------------------------------------
/lib/net/dns/rr/a.rb:
--------------------------------------------------------------------------------
1 | module Net
2 | module DNS
3 | class RR
4 |
5 | #
6 | # = IPv4 Address Record (A)
7 | #
8 | # Class for DNS IPv4 Address (A) resource records.
9 | #
10 | # The resource data is an IPv4 (i.e. 32 bit long) address,
11 | # hold in the instance variable +address+.
12 | #
13 | # a = Net::DNS::RR::A.new("localhost.movie.edu. 360 IN A 127.0.0.1")
14 | #
15 | # a = Net::DNS::RR::A.new(:name => "localhost.movie.edu.",
16 | # :ttl => 360,
17 | # :cls => Net::DNS::IN,
18 | # :type => Net::DNS::A,
19 | # :address => "127.0.0.1" )
20 | #
21 | # When computing binary data to transmit the RR, the RDATA section is an
22 | # Internet address expressed as four decimal numbers separated by dots
23 | # without any embedded space (e.g. "10.2.0.52" or "192.0.5.6").
24 | #
25 | class A < RR
26 |
27 | # Gets the current IPv4 address for this record.
28 | #
29 | # Returns an instance of IPAddr.
30 | def address
31 | @address
32 | end
33 |
34 | # Assigns a new IPv4 address to this record, which can be in the
35 | # form of a String or an IPAddr object.
36 | #
37 | # Examples
38 | #
39 | # a.address = "192.168.0.1"
40 | # a.address = IPAddr.new("10.0.0.1")
41 | #
42 | # Returns the new allocated instance of IPAddr.
43 | def address=(string_or_ipaddr)
44 | @address = check_address(string_or_ipaddr)
45 | build_pack
46 | @address
47 | end
48 |
49 | # Gets the standardized value for this record,
50 | # represented by the value of address.
51 | #
52 | # Returns a String.
53 | def value
54 | address.to_s
55 | end
56 |
57 |
58 | private
59 |
60 | def subclass_new_from_hash(options)
61 | if options.has_key?(:address)
62 | @address = check_address(options[:address])
63 | elsif options.has_key?(:rdata)
64 | @address = check_address(options[:rdata])
65 | else
66 | raise ArgumentError, ":address or :rdata field is mandatory"
67 | end
68 | end
69 |
70 | def subclass_new_from_string(str)
71 | @address = check_address(str)
72 | end
73 |
74 | def subclass_new_from_binary(data, offset)
75 | a, b, c, d = data.unpack("@#{offset} CCCC")
76 | @address = IPAddr.new("#{a}.#{b}.#{c}.#{d}")
77 | offset + 4
78 | end
79 |
80 |
81 | def set_type
82 | @type = Net::DNS::RR::Types.new("A")
83 | end
84 |
85 | def get_inspect
86 | value
87 | end
88 |
89 |
90 | def check_address(input)
91 | address = case input
92 | when IPAddr
93 | input
94 | when Integer # Address in numeric form
95 | IPAddr.new(input, Socket::AF_INET) # We know we are IPv4
96 | when String
97 | IPAddr.new(input)
98 | else
99 | raise ArgumentError, "Invalid IP address `#{input}'"
100 | end
101 |
102 | if !address.ipv4?
103 | raise(ArgumentError, "Must specify an IPv4 address")
104 | end
105 |
106 | address
107 | end
108 |
109 | def build_pack
110 | @address_pack = @address.hton
111 | @rdlength = @address_pack.size
112 | end
113 |
114 | def get_data
115 | @address_pack
116 | end
117 |
118 | end
119 |
120 | end
121 | end
122 | end
123 |
--------------------------------------------------------------------------------
/demo/check_soa.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | require 'rubygems' if "#{RUBY_VERSION}" < "1.9.0"
4 | require 'net/dns'
5 |
6 |
7 | #------------------------------------------------------------------------------
8 | # Get the domain from the command line.
9 | #------------------------------------------------------------------------------
10 |
11 | raise ArgumentError, "Usage: check_soa.rb domain\n" unless ARGV.size == 1
12 |
13 | domain = ARGV[0]
14 |
15 | #------------------------------------------------------------------------------
16 | # Find all the nameservers for the domain.
17 | #------------------------------------------------------------------------------
18 |
19 | res = Net::DNS::Resolver.new(:defname => false, :retry => 2)
20 |
21 | ns_req = res.query(domain, Net::DNS::NS)
22 | unless ns_req and ns_req.header.anCount > 0
23 | raise ArgumentError, "No nameservers found for domain: #{res.errorstring}"
24 | end
25 |
26 |
27 | # Send out non-recursive queries
28 | res.recurse = false
29 | # Do not buffer standard out
30 | #| = 1;
31 |
32 |
33 | #------------------------------------------------------------------------------
34 | # Check the SOA record on each nameserver.
35 | #------------------------------------------------------------------------------
36 |
37 | ns_req.each_nameserver do |ns|
38 |
39 | #----------------------------------------------------------------------
40 | # Set the resolver to query this nameserver.
41 | #----------------------------------------------------------------------
42 |
43 | # In order to lookup the IP(s) of the nameserver, we need a Resolver
44 | # object that is set to our local, recursive nameserver. So we create
45 | # a new object just to do that.
46 |
47 | local_res = Net::DNS::Resolver.new
48 |
49 | a_req = local_res.query(ns, Net::DNS::A)
50 |
51 |
52 | unless a_req
53 | puts "Can not find address for ns: " + res.errorstring + "\n"
54 | next
55 | end
56 |
57 |
58 | a_req.each_address do |ip|
59 |
60 | #----------------------------------------------------------------------
61 | # Ask this IP.
62 | #----------------------------------------------------------------------
63 | res.nameservers=ip
64 |
65 | print "#{ns} (#{ip}): "
66 |
67 | #----------------------------------------------------------------------
68 | # Get the SOA record.
69 | #----------------------------------------------------------------------
70 |
71 | soa_req = res.send(domain, Net::DNS::SOA, Net::DNS::IN)
72 |
73 | if soa_req == nil
74 | puts res.errorstring, "\n"
75 | next
76 | end
77 |
78 | #----------------------------------------------------------------------
79 | # Is this nameserver authoritative for the domain?
80 | #----------------------------------------------------------------------
81 |
82 | unless soa_req.header.auth?
83 | print "isn't authoritative for domain\n"
84 | next
85 | end
86 |
87 | #----------------------------------------------------------------------
88 | # We should have received exactly one answer.
89 | #----------------------------------------------------------------------
90 |
91 | unless soa_req.header.anCount == 1
92 | print "expected 1 answer, got " + soa_req.header.anCount.to_s + "\n"
93 | next
94 | end
95 |
96 | #----------------------------------------------------------------------
97 | # Did we receive an SOA record?
98 | #----------------------------------------------------------------------
99 |
100 | unless soa_req.answer[0].class == Net::DNS::RR::SOA
101 | print "expected SOA, got " + Net::DNS::RR::RRTypes.to_str(soa_req.answer[0].type) + "\n"
102 | next
103 | end
104 |
105 | #----------------------------------------------------------------------
106 | # Print the serial number.
107 | #----------------------------------------------------------------------
108 |
109 | print "has serial number " + soa_req.answer[0].serial.to_s + "\n"
110 |
111 | end
112 | end
113 |
114 |
115 |
116 |
--------------------------------------------------------------------------------
/lib/net/dns/names.rb:
--------------------------------------------------------------------------------
1 | module Net # :nodoc:
2 | module DNS
3 |
4 | module Names
5 |
6 | # Base error class.
7 | class Error < StandardError
8 | end
9 |
10 | # Generic Names Error.
11 | class ExpandError < Error
12 | end
13 |
14 |
15 | INT16SZ = 2
16 |
17 | # Expand a compressed name in a DNS Packet object. Please
18 | # see RFC1035 for an explanation of how the compression
19 | # in DNS packets works, how may it be useful and how should
20 | # be handled.
21 | #
22 | # This method accept two parameters: a raw packet data and an
23 | # offset, which indicates the point in the packet in which the
24 | # parsing has arrived.
25 | #
26 | def dn_expand(packet,offset)
27 | name = ""
28 | packetlen = packet.size
29 | while true
30 | raise ExpandError, "Offset is greater than packet length!" if packetlen < (offset+1)
31 | len = packet.unpack("@#{offset} C")[0]
32 |
33 | if len == 0
34 | offset += 1
35 | break
36 | elsif (len & 0xC0) == 0xC0
37 | raise ExpandError, "Packet ended before offset expand" if packetlen < (offset+INT16SZ)
38 | ptr = packet.unpack("@#{offset} n")[0]
39 | ptr &= 0x3FFF
40 | name2 = dn_expand(packet,ptr)[0]
41 | raise ExpandError, "Packet is malformed!" if name2 == nil
42 | name += name2
43 | offset += INT16SZ
44 | break
45 | else
46 | offset += 1
47 | raise ExpandError, "No expansion found" if packetlen < (offset+len)
48 | elem = packet[offset..offset+len-1]
49 | name += "#{elem}."
50 | offset += len
51 | end
52 | end
53 | [name, offset] # name.chomp(".") if trailing dot has to be omitted
54 | end
55 |
56 | def pack_name(name)
57 | if name.size > 255
58 | raise ArgumentError, "Name may not exceed 255 chars"
59 | end
60 | arr = name.split(".")
61 | str = ""
62 | arr.each do |elem|
63 | if elem.size > 63
64 | raise ArgumentError, "Label may not exceed 63 chars"
65 | end
66 | str += [elem.size,elem].pack("Ca*")
67 | end
68 | str += [0].pack("C")
69 | str
70 | end
71 |
72 | def names_array(name)
73 | arr = name.split(".")
74 | ar = []
75 | string = ""
76 | arr.size.times do |i|
77 | x = i+1
78 | elem = arr[-x]
79 | len = elem.size
80 | string = ((string.reverse)+([len,elem].pack("Ca*")).reverse).reverse
81 | ar.unshift(string)
82 | end
83 | return ar
84 | end
85 |
86 | def dn_comp(name,offset,compnames)
87 | names = {}
88 | ptr = 0
89 | str = ""
90 | arr = names_array(name)
91 | arr.each do |entry|
92 | if compnames.has_key?(entry)
93 | ptr = 0xC000 | compnames[entry]
94 | str += [ptr].pack("n")
95 | offset += INT16SZ
96 | break
97 | else
98 | len = entry.unpack("C")[0]
99 | elem = entry[1..len]
100 | str += [len,elem].pack("Ca*")
101 | names.update({"#{entry}" => offset})
102 | offset += len
103 | end
104 | end
105 | return str,offset,names
106 | end
107 |
108 | def valid?(name)
109 | return false if name.length < 1 or name.length > 255
110 |
111 | return true if name == '.' # the root domain is the only valid domain that begins with a dot
112 |
113 | parts = name.split('.', -1)
114 | parts.delete_at(parts.length-1) if parts.last.empty? # the domain may end with a dot
115 |
116 | parts.each do |part|
117 | if not part =~ /^[a-z0-9]([-a-z0-9]*[a-z0-9])?$/i or part.length < 1 or part.length > 63
118 | return false
119 | end
120 | end
121 |
122 | return true
123 | end
124 | end
125 | end
126 | end
127 |
--------------------------------------------------------------------------------
/lib/net/dns/rr/classes.rb:
--------------------------------------------------------------------------------
1 | module Net
2 | module DNS
3 |
4 | class RR
5 |
6 | #
7 | # = Net::DNS::Classes
8 | #
9 | # This is an auxiliary class to handle Net::DNS::RR
10 | # class field in a DNS packet.
11 | #
12 | class Classes
13 |
14 | # Hash with the values of each RR class stored with the
15 | # respective id number.
16 | CLASSES = {
17 | 'IN' => 1, # RFC 1035
18 | 'CH' => 3, # RFC 1035
19 | 'HS' => 4, # RFC 1035
20 | 'NONE' => 254, # RFC 2136
21 | 'ANY' => 255, # RFC 1035
22 | }
23 |
24 | # The default value when class is nil in Resource Records
25 | @@default = CLASSES["IN"]
26 |
27 |
28 | # Creates a new object representing an RR class. Performs some
29 | # checks on the argument validity too. Il +cls+ is +nil+, the
30 | # default value is +ANY+ or the one set with Classes.default=
31 | def initialize(cls)
32 | case cls
33 | when String
34 | initialize_from_str(cls)
35 | when Fixnum
36 | initialize_from_num(cls)
37 | when nil
38 | initialize_from_num(@@default)
39 | end
40 |
41 | if @str.nil? || @num.nil?
42 | raise ArgumentError, "Unable to create a `Classes' from `#{cls}'"
43 | end
44 | end
45 |
46 | # Returns the class in number format
47 | # (default for normal use)
48 | #
49 | # FIXME: inspect must return a String.
50 | #
51 | def inspect
52 | @num
53 | end
54 |
55 | # Returns the class in string format,
56 | # ex. "IN" or "CH" or such a string.
57 | def to_s
58 | @str.to_s
59 | end
60 |
61 | # Returns the class in numeric format,
62 | # usable by the pack methods for data transfers.
63 | def to_i
64 | @num.to_i
65 | end
66 |
67 |
68 |
69 | # Be able to control the default class to assign when
70 | # cls argument is +nil+. Default to +IN+
71 | def self.default=(str)
72 | if CLASSES[str]
73 | @@default = CLASSES[str]
74 | else
75 | raise ArgumentError, "Unknown class `#{str}'"
76 | end
77 | end
78 |
79 | # Returns whether cls is a valid RR class.
80 | #
81 | # Net::DNS::RR::Classes.valid?("IN")
82 | # # => true
83 | # Net::DNS::RR::Classes.valid?(1)
84 | # # => true
85 | # Net::DNS::RR::Classes.valid?("Q")
86 | # # => false
87 | # Net::DNS::RR::Classes.valid?(256)
88 | # # => false
89 | # Net::DNS::RR::Classes.valid?(Hash.new)
90 | # # => ArgumentError
91 | #
92 | # FIXME: valid? should never raise.
93 | #
94 | # ==== Raises
95 | # ArgumentError:: if cls isn't either a String or a Fixnum
96 | #
97 | def self.valid?(cls)
98 | case cls
99 | when String
100 | CLASSES.has_key?(cls)
101 | when Fixnum
102 | CLASSES.invert.has_key?(cls)
103 | else
104 | raise ArgumentError, "Wrong cls class: #{cls.class}"
105 | end
106 | end
107 |
108 | # Gives in output the keys from the +Classes+ hash
109 | # in a format suited for regexps
110 | def self.regexp
111 | CLASSES.keys.sort.join("|")
112 | end
113 |
114 |
115 | private
116 |
117 | # Initialize a new instance from a Class name.
118 | def initialize_from_str(str)
119 | key = str.to_s.upcase
120 | @num, @str = CLASSES[key], key
121 | end
122 |
123 | # Initialize a new instance from the Class value.
124 | def initialize_from_num(num)
125 | key = num.to_i
126 | @num, @str = key, CLASSES.invert[key]
127 | end
128 |
129 | end
130 |
131 | end
132 | end
133 | end
134 |
--------------------------------------------------------------------------------
/lib/net/dns/resolver/socks.rb:
--------------------------------------------------------------------------------
1 | require 'socket'
2 | require 'ipaddr'
3 |
4 | class RawSocket # :nodoc:
5 |
6 | @@id_arr = []
7 |
8 | def initialize(src_addr,dest_addr)
9 |
10 | # Define socket
11 | begin
12 | @socket = Socket.new PF_INET, SOCK_RAW, IPPROTO_RAW
13 | rescue SystemCallError => e
14 | raise SystemCallError, "You must be root to use raw sockets! #{e}"
15 | end
16 |
17 | @socket.setsockopt IPPROTO_IP, IP_HDRINCL, 1
18 |
19 | # Checks addresses
20 | @src_addr = check_addr src_addr
21 | @dest_addr = check_addr dest_addr
22 |
23 | # Source and destination port are zero
24 | @src_port = 0
25 | @dest_port = 0
26 |
27 | # Set correct protocol version in the header
28 | @version = @dest_addr.ipv4? ? "0100" : "0110"
29 |
30 | # Total lenght: must be overridden by subclasses
31 | @tot_lenght = 20
32 |
33 | # Protocol: must be overridden by subclasses
34 | @protocol = 1 # ICMP by default
35 |
36 | # Generate a new id
37 | # @id = genID
38 | @id = 1234
39 |
40 | # Generate peer sockaddr
41 | @to = Socket.pack_sockaddr_in @dest_port, @dest_addr.to_s
42 | end
43 |
44 | def send(payload = '')
45 | packet = make_ip_header([[ @version+'0101', 'B8' ], # version, hlen
46 | [ 0, 'C' ], # tos
47 | [ @tot_lenght + payload.size, 'n' ], # total len
48 | [ @id, 'n' ], # id
49 | [ 0, 'n' ], # flags, offset
50 | [ 64, 'C' ], # ttl
51 | [ @protocol, 'C' ], # protocol
52 | [ 0, 'n' ], # checksum
53 | [ @src_addr.to_i, 'N' ], # source
54 | [ @dest_addr.to_i, 'N' ], # destination
55 | ])
56 | packet << make_transport_header(payload.size)
57 | packet << [payload].pack("a*")
58 | @socket.send(packet,0,@to)
59 | end
60 |
61 | private
62 |
63 | def check_addr addr
64 | case addr
65 | when String
66 | IPAddr.new(addr)
67 | when IPAddr
68 | addr
69 | else
70 | raise ArgumentError, "Wrong address format: #{addr}"
71 | end
72 | end
73 |
74 | def check_port port
75 | if (1..65535).include? port and port.kind_of? Integer
76 | port
77 | else
78 | raise ArgumentError, "Port #{port} not valid"
79 | end
80 | end
81 |
82 | def genID
83 | while (@@id_arr.include?(q = rand(65535)))
84 | end
85 | @@id_arr.push(q)
86 | q
87 | end
88 |
89 | def ipchecksum(data)
90 | checksum = data.unpack("n*").inject(0) { |s, x| s + x }
91 | ((checksum >> 16) + (checksum & 0xffff)) ^ 0xffff
92 | end
93 |
94 | def make_ip_header(parts)
95 | template = ''
96 | data = []
97 | parts.each do |part|
98 | data += part[0..-2]
99 | template << part[-1]
100 | end
101 | data_str = data.pack(template)
102 | checksum = ipchecksum(data_str)
103 | data[-3] = checksum
104 | data.pack(template)
105 | end
106 |
107 | def make_transport_header
108 | ""
109 | end
110 |
111 | end
112 |
113 | class UdpRawSocket < RawSocket # :nodoc:
114 |
115 | def initialize(src_addr,src_port,dest_addr,dest_port)
116 |
117 | super(src_addr,dest_addr)
118 |
119 | # Check ports
120 | @src_port = check_port src_port
121 | @dest_port = check_port dest_port
122 |
123 | # Total lenght: must be overridden by subclasses
124 | @tot_lenght = 20 + 8 # 8 bytes => UDP Header
125 |
126 | # Protocol: must be overridden by subclasses
127 | @protocol = 17 # UDP protocol
128 |
129 | @to = Socket.pack_sockaddr_in @dest_port, @dest_addr.to_s
130 | end
131 |
132 | private
133 |
134 | def make_udp_header(parts)
135 | template = ''
136 | data = []
137 | parts.each do |part|
138 | data += part[0..-2]
139 | template << part[-1]
140 | end
141 | data.pack(template)
142 | end
143 |
144 | def make_transport_header(pay_size)
145 | make_udp_header([
146 | [ @src_port, 'n'], # source port
147 | [ @dest_port, 'n' ], # destination port
148 | [ 8 + pay_size, 'n' ], # len
149 | [ 0, 'n' ] # checksum (mandatory)
150 | ])
151 | end
152 |
153 | end
154 |
155 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## Release 0.8.3
4 | - FIXED: Packet size cannot be set issue #1
5 | - FIXED: Now raise exception if raw sockets are requested and we are not
6 | running as root issue #23
7 | - FIXED: Rake can build gem, travis file updated to test up to
8 | ruby-head issue #30
9 | - FIXED: Merged in pending bluemonk pull requests issue #29, remaining
10 | fixes are from these
11 | - FIXED: Cannot get TXT record issue #13
12 | https://github.com/bluemonk/net-dns/pull/42
13 | - FIXED: Various fixes by neoraider
14 | https://github.com/bluemonk/net-dns/pull/36
15 | - FIXED: query_tcp fix by nbaum
16 | https://github.com/bluemonk/net-dns/pull/63
17 | - FIXED: Text fixes by chrislundquist
18 | https://github.com/bluemonk/net-dns/pull/52
19 | https://github.com/bluemonk/net-dns/pull/51
20 | - CHANGED: Support SPF records by winebarrel
21 | https://github.com/bluemonk/net-dns/pull/57
22 | - CHANGED: Simplified nameserver settings
23 | https://github.com/bluemonk/net-dns/pull/60
24 |
25 | ## Release 0.8.2
26 | - FIXED: Exception when using new raw support issue #2
27 | - FIXED: Gemspec file has wrong emails
28 | - FIXED: Removed debug statement from raw support
29 |
30 | ## Release 0.8.1
31 | - First release from fork by Christopher Carpenter(mordocai@mordocai.net)
32 | - FIXED: Multiple bugs with Resolver::source_address= preventing it from
33 | executing.
34 | - FIXED: Implemented send_raw_tcp and send_raw_udp
35 | - CHANGED: Added interface config parameter for Net::DNS::Resolver,
36 | currently only used by send_raw*
37 | - CHANGED: Added TODO file for known things that I need to do.
38 |
39 | ## Release 0.8.0
40 |
41 | - FIXED: undefined local variable or method `source_address_inet6' (GH-40). [Thanks @simsicon]
42 |
43 | - FIXED: Fixed bug on parsing multiple nameservers on different lines (GH-45). [Thanks @nicholasren]
44 |
45 | - CHANGED: Dropped duplicate query ID filter. Query ID is now randomically generated but it's not guaranteed to be unique (GH-39). [Thanks @ebroder]
46 |
47 | - CHANGED: require 'net/dns' is now the preferred way to load the library (GH-37). [Thanks @johnroa]
48 |
49 | - CHANGED: Removed setup.rb installation script.
50 |
51 |
52 | ## Release 0.7.1
53 |
54 | - FIXED: Invalid file permissions on several files (GH-35) [Thanks @jamespharaoh]
55 |
56 |
57 | ## Release 0.7.0
58 |
59 | - ADDED: Added (experimental) Support for HINFO record.
60 |
61 | - FIXED: Use Net::DNS::Resolver::Error class (not ResolverError, which does not exist).
62 |
63 | - FIXED: Cleaned up require dependency and recursive require statements.
64 |
65 | - FIXED: Use RbConfig instead of obsolete and deprecated Config (GH-28, GH-33) [Thanks @shadowbq, @eik3]
66 |
67 | - FIXED: SRV record not required by Net::DNS::RR (GH-27) [Thanks @sebastian]
68 |
69 | - FIXED: Resolver now supports IPv6 (GH-32) [Thanks @jamesotron]
70 |
71 | - FIXED: Net::DNS::RR::PTR references an invalid parameter (GH-19) [Thanks @dd23]
72 |
73 | - FIXED: Net::DNS::Question changes input arguments (GH-7) [Thanks @gfarfl]
74 |
75 | - CHANGED: Refactoring unit test to follow most used Ruby conventions.
76 |
77 | - CHANGED: Rewritten and simplified Net::DNS::Classes. Improved test harness.
78 |
79 | - CHANGED: Removed Jeweler development dependency.
80 |
81 | - CHANGED: The library is now compatible with Bundler.
82 |
83 | - CHANGED: Minimum supported Ruby version changed to Ruby 1.8.7.
84 |
85 | - CHANGED: Rescue NameError so unsupported record types only result in a warning.
86 |
87 | - CHANGED: Renamed Net::DNS::Resolver#send to Net::DNS::Resolver#query to avoid overriding default meaning of send method.
88 |
89 |
90 | ## Release 0.6.1
91 |
92 | - ADDED: Net::DNS::Packet#to_s method (alias of #inspect)
93 |
94 | - FIXED: typo in lib/net/dns/rr/ptr.rb [Thanks Chris Lundquist]
95 |
96 | - FIXED: warning: method redefined; discarding old inspect (GH-3) [Thanks Kevin Baker]
97 |
98 | - FIXED: issue with rescue ArgumentError (GH-5) and with IPAddr handling (GH-6)
99 |
100 |
101 | ## Release 0.6.0
102 |
103 | *WARNING:- If you are upgrading from a previous minor release, check out the Compatibility issue list below.
104 |
105 | - FIXED: Added missing #to_s method to Net::DNS::Question.
106 |
107 | - FIXED: Compatibility with Ruby 1.9
108 |
109 | - FIXED: Types regexp order issue
110 |
111 | - CHANGED: Refactoring unit test to follow most used Ruby conventions
112 |
113 | - CHANGED: default timeout is now 5 seconds for both UDP and TCP
114 |
115 | - CHANGED: Moved main dns.rb file to lib/net folder as default for GEMs. In this way it can be autoloaded when the gem is required.
116 |
117 | ### Compatibility issues
118 |
119 | - CHANGED: RR#set_stype scope is now private to prevent invalid usage.
120 |
121 | - CHANGED: DnsTimeout#timeout now raises LocalJumpError instead of DnsTimeoutArgumentError when block is missing.
122 |
123 | - CHANGED: Renamed Net::DNS::RR::Types::Types to Net::DNS::RR::Types::TYPES to follow Ruby coding standards.
124 |
125 |
126 | ## Release 0.4
127 |
128 | - many bug fixes (thanks guys!)
129 | - a whole new class Net::DNS::Header::RCode
130 | - new methods in Net::DNS::Resolver class to do AXFR queries
131 | - a new SRV resource record written by Dan Janowski
132 | - more documentation written and corrected
133 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Net::DNS
2 | [](https://travis-ci.org/mordocai/net-dns)
3 |
4 | https://rubygems.org/gems/net-dns2
5 |
6 | Net::DNS is a DNS library written in pure Ruby. It started as a port of Perl Net::DNS module, but it evolved in time into a full Ruby library.
7 |
8 |
9 | ## Features
10 |
11 | - Complete OO interface
12 | - Clean and intuitive API
13 | - Modular and flexible
14 | - Source address spoofing (via source_address= on Net::DNS::Resolver)
15 |
16 |
17 | ## Requirements
18 |
19 | * Ruby >= 1.8.7
20 |
21 |
22 | ## Installation
23 |
24 | The best way to install this library is via [RubyGems](https://rubygems.org/).
25 |
26 | $ gem install net-dns2
27 |
28 | You might need administrator privileges on your system to install the gem.
29 |
30 |
31 | ## API Documentation
32 |
33 | Visit the page http://rdoc.info/gems/net-dns2
34 |
35 |
36 | ## Trivial resolver
37 |
38 | The simplest way to use the library is to invoke the Resolver() method:
39 |
40 | require 'rubygems'
41 | require 'net/dns'
42 | p Resolver("www.google.com")
43 |
44 | The output is compatible with BIND zone files and it's the same you would get with the dig utility.
45 |
46 | ;; Answer received from localhost:53 (212 bytes)
47 | ;;
48 | ;; HEADER SECTION
49 | ;; id = 64075
50 | ;; qr = 1 opCode: QUERY aa = 0 tc = 0 rd = 1
51 | ;; ra = 1 ad = 0 cd = 0 rcode = NoError
52 | ;; qdCount = 1 anCount = 3 nsCount = 4 arCount = 4
53 |
54 | ;; QUESTION SECTION (1 record):
55 | ;; google.com. IN A
56 |
57 | ;; ANSWER SECTION (3 records):
58 | google.com. 212 IN A 74.125.45.100
59 | google.com. 212 IN A 74.125.67.100
60 | google.com. 212 IN A 209.85.171.100
61 |
62 | ;; AUTHORITY SECTION (4 records):
63 | google.com. 345512 IN NS ns1.google.com.
64 | google.com. 345512 IN NS ns4.google.com.
65 | google.com. 345512 IN NS ns2.google.com.
66 | google.com. 345512 IN NS ns3.google.com.
67 |
68 | ;; ADDITIONAL SECTION (4 records):
69 | ns1.google.com. 170275 IN A 216.239.32.10
70 | ns2.google.com. 170275 IN A 216.239.34.10
71 | ns3.google.com. 170275 IN A 216.239.36.10
72 | ns4.google.com. 170275 IN A 216.239.38.10
73 |
74 | An optional block can be passed yielding the Net::DNS::Packet object
75 |
76 | Resolver("www.google.com") { |packet| puts packet.size + " bytes" }
77 | # => 484 bytes
78 |
79 | Same for Net::DNS::Resolver.start():
80 |
81 | Net::DNS::Resolver.start("google.com").answer.size
82 | # => 5
83 |
84 | As optional parameters, +TYPE+ and +CLASS+ can be specified.
85 |
86 | p Net::DNS::Resolver.start("google.com", Net::DNS::MX)
87 |
88 | ;; Answer received from localhost:53 (316 bytes)
89 | ;;
90 | ;; HEADER SECTION
91 | ;; id = 59980
92 | ;; qr = 1 opCode: QUERY aa = 0 tc = 0 rd = 1
93 | ;; ra = 1 ad = 0 cd = 0 rcode = NoError
94 | ;; qdCount = 1 anCount = 4 nsCount = 4 arCount = 8
95 |
96 | ;; QUESTION SECTION (1 record):
97 | ;; google.com. IN MX
98 |
99 | ;; ANSWER SECTION (4 records):
100 | google.com. 10800 IN MX 10 smtp2.google.com.
101 | google.com. 10800 IN MX 10 smtp3.google.com.
102 | google.com. 10800 IN MX 10 smtp4.google.com.
103 | google.com. 10800 IN MX 10 smtp1.google.com.
104 |
105 |
106 | ## Handling the response packet
107 |
108 | The method Net::DNS::Resolver.start is a wrapper around Resolver.new. It returns a new Net::DNS::Packet object.
109 |
110 | A DNS packet is divided into 5 sections:
111 |
112 | - header section # => a Net::DNS::Header object
113 | - question section # => a Net::DNS::Question object
114 | - answer section # => an Array of Net::DNS::RR objects
115 | - authority section # => an Array of Net::DNS::RR objects
116 | - additional section # => an Array of Net::DNS::RR objects
117 |
118 | You can access each section by calling the attribute with the same name on a Packet object:
119 |
120 | packet = Net::DNS::Resolver.start("google.com")
121 |
122 | header = packet.header
123 | answer = packet.answer
124 |
125 | puts "The packet is #{packet.data.size} bytes"
126 | puts "It contains #{header.anCount} answer entries"
127 |
128 | answer.any? {|ans| p ans}
129 |
130 | The output is
131 |
132 | The packet is 378 bytes
133 | It contains 3 answer entries
134 | google.com. 244 IN A 74.125.45.100
135 | google.com. 244 IN A 74.125.67.100
136 | google.com. 244 IN A 209.85.171.100
137 |
138 | A better way to handle the answer section is to use the iterators directly on a Packet object:
139 |
140 | packet.each_address do |ip|
141 | puts "#{ip} is alive" if Ping.pingecho(ip.to_s, 10, 80)
142 | end
143 |
144 | Gives:
145 |
146 | 74.125.45.100 is alive
147 | 74.125.67.100 is alive
148 | 209.85.171.100 is alive
149 |
150 |
151 | ## License
152 |
153 | See License
154 |
155 | ## Authors
156 |
157 | - Marco Ceresa (@bluemonk)
158 | - Simone Carletti (@weppos)
159 | - Christopher Carpenter (@mordocai)
160 |
--------------------------------------------------------------------------------
/lib/net/dns/question.rb:
--------------------------------------------------------------------------------
1 | module Net
2 | module DNS
3 |
4 | #
5 | # =Name
6 | #
7 | # Net::DNS::Question - DNS packet question class
8 | #
9 | # =Synopsis
10 | #
11 | # require 'net/dns/question'
12 | #
13 | # =Description
14 | #
15 | # This class represent the Question portion of a DNS packet. The number
16 | # of question entries is stored in the +qdCount+ variable of an Header
17 | # object.
18 | #
19 | # A new object can be created passing the name of the query and the type
20 | # of answer desired, plus an optional argument containing the class:
21 | #
22 | # question = Net::DNS::Question.new("google.com.", Net::DNS::A)
23 | # #=> "google.com. A IN"
24 | #
25 | # Alternatevly, a new object is created when processing a binary
26 | # packet, as when an answer is received.
27 | # To obtain the binary data from a question object you can use
28 | # the method Question#data:
29 | #
30 | # question.data
31 | # #=> "\006google\003com\000\000\001\000\001"
32 | #
33 | # A lot of methods were written to keep a compatibility layer with
34 | # the Perl version of the library, as long as methods name which are
35 | # more or less the same.
36 | #
37 | class Question
38 | include Names
39 |
40 | # Base error class.
41 | class Error < StandardError
42 | end
43 |
44 | # An error in the +name+ part of a Question entry
45 | class NameInvalid < Error
46 | end
47 |
48 | # +name+ part of a Question entry
49 | attr_reader :qName
50 | # +type+ part of a Question entry
51 | attr_reader :qType
52 | # +class+ part of a Question entry
53 | attr_reader :qClass
54 |
55 | # Creates a new Net::DNS::Question object:
56 | #
57 | # question = Net::DNS::Question.new("example.com")
58 | # #=> "example.com A IN"
59 | # question = Net::DNS::Question.new("example.com", Net::DNS::MX)
60 | # #=> "example.com MX IN"
61 | # question = Net::DNS::Question.new("example.com", Net::DNS::TXT, Net::DNS::HS)
62 | # #=> "example.com TXT HS"
63 |
64 | # If not specified, +type+ and +cls+ arguments defaults
65 | # to Net::DNS::A and Net::DNS::IN respectively.
66 | #
67 | def initialize(name, type = Net::DNS::A, cls = Net::DNS::IN)
68 | @qName = check_name name
69 | @qType = Net::DNS::RR::Types.new(type)
70 | @qClass = Net::DNS::RR::Classes.new(cls)
71 | end
72 |
73 | # Return a new Net::DNS::Question object created by
74 | # parsing binary data, such as an answer from the
75 | # nameserver.
76 | #
77 | # question = Net::DNS::Question.parse(data)
78 | # puts "Queried for #{question.qName} type #{question.qType.to_s}"
79 | # #=> Queried for example.com type A
80 | #
81 | def self.parse(arg)
82 | o = allocate
83 | o.send(:new_from_binary, arg.to_s)
84 | o
85 | end
86 |
87 | # Outputs binary data from a Question object
88 | #
89 | # question.data
90 | # #=> "\006google\003com\000\000\001\000\001"
91 | #
92 | def data
93 | [pack_name(@qName),@qType.to_i,@qClass.to_i].pack("a*nn")
94 | end
95 |
96 | # Return the binary data of the objects, plus an offset
97 | # and an Hash with references to compressed names. For use in
98 | # Net::DNS::Packet compressed packet creation.
99 | def comp_data
100 | arr = @qName.split(".")
101 | str = pack_name(@qName)
102 | string = ""
103 | names = {}
104 | offset = Net::DNS::HFIXEDSZ
105 | arr.size.times do |i|
106 | x = i+1
107 | elem = arr[-x]
108 | len = elem.size
109 | string = ((string.reverse)+([len,elem].pack("Ca*")).reverse).reverse
110 | names[string] = offset
111 | offset += len
112 | end
113 | offset += 2 * Net::DNS::INT16SZ
114 | str += "\000"
115 | [[str,@qType.to_i,@qClass.to_i].pack("a*nn"),offset,names]
116 | end
117 |
118 |
119 | #
120 | # call-seq:
121 | # question.inspect -> string
122 | #
123 | # Returns a printable version of question with nice formatting.
124 | #
125 | # q = Net::DNS::Question.new("google.com.", Net::DNS::A)
126 | # q.inspect # => "google.com. IN A "
127 | #
128 | def inspect
129 | if @qName.size > 29 then
130 | len = @qName.size + 1
131 | else
132 | len = 29
133 | end
134 | [@qName, @qClass.to_s, @qType.to_s].pack("A#{len} A8 A8")
135 | end
136 |
137 | #
138 | # call-seq:
139 | # question.to_s -> string
140 | #
141 | # Returns a string representation of question.
142 | # It is the same as inspect.
143 | #
144 | # q = Net::DNS::Question.new("google.com.", Net::DNS::A)
145 | # q.inspect # => "google.com. IN A "
146 | #
147 | def to_s
148 | "#{self.inspect}"
149 | end
150 |
151 |
152 | private
153 |
154 | def build_qName(str)
155 | result = ""
156 | offset = 0
157 | loop do
158 | len = str.unpack("@#{offset} C")[0]
159 | break if len == 0
160 | offset += 1
161 | result += str[offset..offset+len-1]
162 | result += "."
163 | offset += len
164 | end
165 | result
166 | end
167 |
168 | def check_name(input)
169 | name = input.to_s.strip
170 | if name =~ /[^\w\.\-_]/
171 | raise NameInvalid, "Invalid Question Name `#{name}'"
172 | end
173 | name
174 | end
175 |
176 | def new_from_binary(data)
177 | str,type,cls = data.unpack("a#{data.size - 4}nn")
178 | @qName = build_qName(str)
179 | @qType = Net::DNS::RR::Types.new type
180 | @qClass = Net::DNS::RR::Classes.new cls
181 | rescue StandardError => e
182 | raise ArgumentError, "Invalid data: #{data.inspect}"
183 | end
184 |
185 | end
186 |
187 | end
188 | end
189 |
--------------------------------------------------------------------------------
/lib/net/dns/rr/types.rb:
--------------------------------------------------------------------------------
1 | module Net # :nodoc:
2 | module DNS
3 |
4 | class RR
5 |
6 | # This is an auxiliary class to handle RR type field in a DNS packet.
7 | class Types
8 |
9 | TYPES = {
10 | 'SIGZERO' => 0, # RFC2931 consider this a pseudo type
11 | 'A' => 1, # RFC 1035, Section 3.4.1
12 | 'NS' => 2, # RFC 1035, Section 3.3.11
13 | 'MD' => 3, # RFC 1035, Section 3.3.4 (obsolete)
14 | 'MF' => 4, # RFC 1035, Section 3.3.5 (obsolete)
15 | 'CNAME' => 5, # RFC 1035, Section 3.3.1
16 | 'SOA' => 6, # RFC 1035, Section 3.3.13
17 | 'MB' => 7, # RFC 1035, Section 3.3.3
18 | 'MG' => 8, # RFC 1035, Section 3.3.6
19 | 'MR' => 9, # RFC 1035, Section 3.3.8
20 | 'NULL' => 10, # RFC 1035, Section 3.3.10
21 | 'WKS' => 11, # RFC 1035, Section 3.4.2 (deprecated)
22 | 'PTR' => 12, # RFC 1035, Section 3.3.12
23 | 'HINFO' => 13, # RFC 1035, Section 3.3.2
24 | 'MINFO' => 14, # RFC 1035, Section 3.3.7
25 | 'MX' => 15, # RFC 1035, Section 3.3.9
26 | 'TXT' => 16, # RFC 1035, Section 3.3.14
27 | 'RP' => 17, # RFC 1183, Section 2.2
28 | 'AFSDB' => 18, # RFC 1183, Section 1
29 | 'X25' => 19, # RFC 1183, Section 3.1
30 | 'ISDN' => 20, # RFC 1183, Section 3.2
31 | 'RT' => 21, # RFC 1183, Section 3.3
32 | 'NSAP' => 22, # RFC 1706, Section 5
33 | 'NSAP_PTR' => 23, # RFC 1348 (obsolete)
34 | # The following 2 RRs are impemented in Net::DNS::SEC, TODO
35 | 'SIG' => 24, # RFC 2535, Section 4.1
36 | 'KEY' => 25, # RFC 2535, Section 3.1
37 | 'PX' => 26, # RFC 2163,
38 | 'GPOS' => 27, # RFC 1712 (obsolete)
39 | 'AAAA' => 28, # RFC 1886, Section 2.1
40 | 'LOC' => 29, # RFC 1876
41 | # The following RR is implemented in Net::DNS::SEC, TODO
42 | 'NXT' => 30, # RFC 2535, Section 5.2
43 | 'EID' => 31, # draft-ietf-nimrod-dns-xx.txt
44 | 'NIMLOC' => 32, # draft-ietf-nimrod-dns-xx.txt
45 | 'SRV' => 33, # RFC 2052
46 | 'ATMA' => 34, # ???
47 | 'NAPTR' => 35, # RFC 2168
48 | 'KX' => 36, # RFC 2230
49 | 'CERT' => 37, # RFC 2538
50 | 'DNAME' => 39, # RFC 2672
51 | 'OPT' => 41, # RFC 2671
52 | # The following 4 RRs are implemented in Net::DNS::SEC TODO
53 | 'DS' => 43, # draft-ietf-dnsext-delegation-signer
54 | 'SSHFP' => 44, # draft-ietf-secsh-dns (No RFC # yet at time of coding)
55 | 'RRSIG' => 46, # draft-ietf-dnsext-dnssec-2535typecode-change
56 | 'NSEC' => 47, # draft-ietf-dnsext-dnssec-2535typecode-change
57 | 'DNSKEY' => 48, # draft-ietf-dnsext-dnssec-2535typecode-change
58 | 'SPF' => 99, # RFC 4408
59 | 'UINFO' => 100, # non-standard
60 | 'UID' => 101, # non-standard
61 | 'GID' => 102, # non-standard
62 | 'UNSPEC' => 103, # non-standard
63 | 'TKEY' => 249, # RFC 2930
64 | 'TSIG' => 250, # RFC 2931
65 | 'IXFR' => 251, # RFC 1995
66 | 'AXFR' => 252, # RFC 1035
67 | 'MAILB' => 253, # RFC 1035 (MB, MG, MR)
68 | 'MAILA' => 254, # RFC 1035 (obsolete - see MX)
69 | 'ANY' => 255, # RFC 1035
70 | }
71 |
72 | # The default value when type is nil in Resource Records
73 | @@default = TYPES["A"]
74 |
75 | # Be able to control the default type to assign when
76 | # type is +nil+. Default to +A+
77 | def self.default=(str)
78 | if TYPES.has_key? str
79 | @@default = TYPES[str]
80 | else
81 | raise ArgumentError, "Unknown type #{str}"
82 | end
83 | end
84 |
85 | # Checks whether +type+ is a valid RR type.
86 | def self.valid?(type)
87 | case type
88 | when String
89 | TYPES.has_key?(type)
90 | when Fixnum
91 | TYPES.invert.has_key?(type)
92 | else
93 | raise ArgumentError, "Wrong type class: #{type.class}"
94 | end
95 | end
96 |
97 | # Returns the type in string format, as "A" or "NS",
98 | # given the numeric value
99 | def self.to_str(type)
100 | case type
101 | when Fixnum
102 | if TYPES.invert.has_key? type
103 | TYPES.invert[type]
104 | else
105 | raise ArgumentError, "Unknown type number #{type}"
106 | end
107 | else
108 | raise ArgumentError, "Wrong type class: #{type.class}"
109 | end
110 | end
111 |
112 | # Gives in output the keys from the +Types+ hash
113 | # in a format suited for regexps
114 | def self.regexp
115 | # Longest ones go first, so the regex engine will match AAAA before A.
116 | TYPES.keys.sort { |a,b| b.length <=> a.length }.join("|")
117 | end
118 |
119 | # Creates a new object representing an RR type. Performs some
120 | # checks on the argument validity too. Il +type+ is +nil+, the
121 | # default value is +ANY+ or the one set with Types.default=
122 | def initialize(type)
123 | case type
124 | when String
125 | # type in the form "A" or "NS"
126 | new_from_string(type.upcase)
127 | when Fixnum
128 | # type in numeric form
129 | new_from_num(type)
130 | when nil
131 | # default type, control with Types.default=
132 | @str = TYPES.invert[@@default]
133 | @num = @@default
134 | else
135 | raise ArgumentError, "Wrong type class: #{type.class}"
136 | end
137 | end
138 |
139 | # Returns the type in number format
140 | # (default for normal use)
141 | def inspect
142 | @num
143 | end
144 |
145 | # Returns the type in string format,
146 | # i.d. "A" or "NS" or such a string.
147 | def to_s
148 | @str
149 | end
150 |
151 | # Returns the type in numeric format,
152 | # usable by the pack methods for data transfers
153 | def to_i
154 | @num.to_i
155 | end
156 |
157 | def to_str
158 | @num.to_s
159 | end
160 |
161 |
162 | private
163 |
164 | # Constructor for string data type.
165 | def new_from_string(type)
166 | case type
167 | when /^TYPE\\d+/
168 | # TODO!!!
169 | else
170 | # String with name of type
171 | if TYPES.has_key? type
172 | @str = type
173 | @num = TYPES[type]
174 | else
175 | raise ArgumentError, "Unknown type #{type}"
176 | end
177 | end
178 | end
179 |
180 | # Contructor for numeric data type.
181 | def new_from_num(type)
182 | if TYPES.invert.has_key? type
183 | @num = type
184 | @str = TYPES.invert[type]
185 | else
186 | raise ArgumentError, "Unkown type number #{type}"
187 | end
188 | end
189 |
190 | end
191 |
192 | end
193 | end
194 | end
195 |
--------------------------------------------------------------------------------
/lib/net/dns/rr.rb:
--------------------------------------------------------------------------------
1 | require 'ipaddr'
2 | require 'net/dns/names'
3 | require 'net/dns/rr/types'
4 | require 'net/dns/rr/classes'
5 |
6 | %w(a aaaa cname hinfo mr mx ns ptr soa srv txt spf).each do |file|
7 | require "net/dns/rr/#{file}"
8 | end
9 |
10 | module Net
11 | module DNS
12 |
13 | #
14 | # = Net::DNS::RR - DNS Resource Record class
15 | #
16 | # The Net::DNS::RR is the base class for DNS Resource
17 | # Record (RR) objects. A RR is a pack of data that represents
18 | # resources for a DNS zone. The form in which this data is
19 | # shows can be drawed as follow:
20 | #
21 | # "name ttl class type data"
22 | #
23 | # The +name+ is the name of the resource, like an canonical
24 | # name for an +A+ record (internet ip address). The +ttl+ is the
25 | # time to live, expressed in seconds. +type+ and +class+ are
26 | # respectively the type of resource (+A+ for ip addresses, +NS+
27 | # for nameservers, and so on) and the class, which is almost
28 | # always +IN+, the Internet class. At the end, +data+ is the
29 | # value associated to the name for that particular type of
30 | # resource record. An example:
31 | #
32 | # # A record for IP address
33 | # "www.example.com 86400 IN A 172.16.100.1"
34 | #
35 | # # NS record for name server
36 | # "www.example.com 86400 IN NS ns.example.com"
37 | #
38 | # A new RR object can be created in 2 ways: passing a string
39 | # such the ones above, or specifying each field as the pair
40 | # of an hash. See the Net::DNS::RR.new method for details.
41 | #
42 | class RR
43 | include Names
44 |
45 |
46 | # Base error class.
47 | class Error < StandardError
48 | end
49 |
50 | # Error in parsing binary data, maybe from a malformed packet.
51 | class DataError < Error
52 | end
53 |
54 |
55 | # Regexp matching an RR string
56 | RR_REGEXP = Regexp.new("^\\s*(\\S+)\\s*(\\d+)?\\s+(" +
57 | Net::DNS::RR::Classes.regexp +
58 | "|CLASS\\d+)?\\s*(" +
59 | Net::DNS::RR::Types.regexp +
60 | "|TYPE\\d+)?\\s*(.*)$", Regexp::IGNORECASE)
61 |
62 | # Dimension of the sum of class, type, TTL and rdlength fields in a
63 | # RR portion of the packet, in bytes
64 | RRFIXEDSZ = 10
65 |
66 |
67 | # Create a new instance of Net::DNS::RR class, or an instance of
68 | # any of the subclass of the appropriate type.
69 | #
70 | # Argument can be a string or an hash. With a sting, we can pass
71 | # a RR resource record in the canonical format:
72 | #
73 | # a = Net::DNS::RR.new("foo.example.com. 86400 A 10.1.2.3")
74 | # mx = Net::DNS::RR.new("example.com. 7200 MX 10 mailhost.example.com.")
75 | # cname = Net::DNS::RR.new("www.example.com 300 IN CNAME www1.example.com")
76 | # txt = Net::DNS::RR.new('baz.example.com 3600 HS TXT "text record"')
77 | #
78 | # Incidentally, +a+, +mx+, +cname+ and +txt+ objects will be instances of
79 | # respectively Net::DNS::RR::A, Net::DNS::RR::MX, Net::DNS::RR::CNAME and
80 | # Net::DNS::RR::TXT classes.
81 | #
82 | # The name and RR data are required; all other informations are optional.
83 | # If omitted, the +TTL+ defaults to 10800, +type+ default to +A+ and the RR class
84 | # defaults to +IN+. Omitting the optional fields is useful for creating the
85 | # empty RDATA sections required for certain dynamic update operations.
86 | # All names must be fully qualified. The trailing dot (.) is optional.
87 | #
88 | # The preferred method is however passing an hash with keys and values:
89 | #
90 | # rr = Net::DNS::RR.new(
91 | # :name => "foo.example.com",
92 | # :ttl => 86400,
93 | # :cls => "IN",
94 | # :type => "A",
95 | # :address => "10.1.2.3"
96 | # )
97 | #
98 | # rr = Net::DNS::RR.new(
99 | # :name => "foo.example.com",
100 | # :rdata => "10.1.2.3"
101 | # )
102 | #
103 | # Name and data are required; all the others fields are optionals like
104 | # we've seen before. The data field can be specified either with the
105 | # right name of the resource (+:address+ in the example above) or with
106 | # the generic key +:rdata+. Consult documentation to find the exact name
107 | # for the resource in each subclass.
108 | #
109 | def initialize(arg)
110 | instance = case arg
111 | when String
112 | new_from_string(arg)
113 | when Hash
114 | new_from_hash(arg)
115 | else
116 | raise ArgumentError, "Invalid argument, must be a RR string or an hash of values"
117 | end
118 |
119 | if @type.to_s == "ANY"
120 | @cls = Net::DNS::RR::Classes.new("IN")
121 | end
122 |
123 | build_pack
124 | set_type
125 |
126 | instance
127 | end
128 |
129 | # Return a new RR object of the correct type (like Net::DNS::RR::A
130 | # if the type is A) from a binary string, usually obtained from
131 | # network stream.
132 | #
133 | # This method is used when parsing a binary packet by the Packet
134 | # class.
135 | #
136 | def RR.parse(data)
137 | o = allocate
138 | obj, offset = o.send(:new_from_binary, data, 0)
139 | obj
140 | end
141 |
142 | # Same as RR.parse, but takes an entire packet binary data to
143 | # perform name expansion. Default when analizing a packet
144 | # just received from a network stream.
145 | #
146 | # Return an instance of appropriate class and the offset
147 | # pointing at the end of the data parsed.
148 | #
149 | def RR.parse_packet(data, offset)
150 | o = allocate
151 | o.send(:new_from_binary, data, offset)
152 | end
153 |
154 | def name
155 | @name
156 | end
157 |
158 | def ttl
159 | @ttl
160 | end
161 |
162 | # Type accessor
163 | def type
164 | @type.to_s
165 | end
166 |
167 | # Class accessor
168 | def cls
169 | @cls.to_s
170 | end
171 |
172 |
173 | def value
174 | get_inspect
175 | end
176 |
177 | # Data belonging to that appropriate class,
178 | # not to be used (use real accessors instead)
179 | def rdata
180 | @rdata
181 | end
182 |
183 | # Return the RR object in binary data format, suitable
184 | # for using in network streams.
185 | #
186 | # raw_data = rr.data
187 | # puts "RR is #{raw_data.size} bytes long"
188 | #
189 | def data
190 | str = pack_name(@name)
191 | str + [@type.to_i, @cls.to_i, ttl, @rdlength].pack("n2 N n") + get_data
192 | end
193 |
194 | # Return the RR object in binary data format, suitable
195 | # for using in network streams, with names compressed.
196 | # Must pass as arguments the offset inside the packet
197 | # and an hash of compressed names.
198 | #
199 | # This method is to be used in other classes and is
200 | # not intended for user space programs.
201 | #
202 | # TO FIX in one of the future releases
203 | #
204 | def comp_data(offset,compnames)
205 | str, offset, names = dn_comp(@name, offset, compnames)
206 | str += [@type.to_i, @cls.to_i, ttl, @rdlength].pack("n2 N n")
207 | offset += Net::DNS::RRFIXEDSZ
208 | [str, offset, names]
209 | end
210 |
211 |
212 | # Returns a human readable representation of this record.
213 | # The value is always a String.
214 | #
215 | # mx = Net::DNS::RR.new("example.com. 7200 MX 10 mailhost.example.com.")
216 | # #=> example.com. 7200 IN MX 10 mailhost.example.com.
217 | #
218 | def inspect
219 | to_s
220 | end
221 |
222 | # Returns a String representation of this record.
223 | #
224 | # mx = Net::DNS::RR.new("example.com. 7200 MX 10 mailhost.example.com.")
225 | # mx.to_s
226 | # #=> "example.com. 7200 IN MX 10 mailhost.example.com."
227 | #
228 | def to_s
229 | items = to_a.map { |e| e.to_s }
230 | if @name.size < 24
231 | items.pack("A24 A8 A8 A8 A*")
232 | else
233 | items.join(" ")
234 | end.to_s
235 | end
236 |
237 | # Returns an Array with all the attributes for this record.
238 | #
239 | # mx = Net::DNS::RR.new("example.com. 7200 MX 10 mailhost.example.com.")
240 | # mx.to_a
241 | # #=> ["example.com.", 7200, "IN", "MX", "10 mailhost.example.com."]
242 | #
243 | def to_a
244 | [name, ttl, cls.to_s, type.to_s, value]
245 | end
246 |
247 |
248 | private
249 |
250 | def new_from_string(rrstring)
251 | unless rrstring =~ RR_REGEXP
252 | raise ArgumentError,
253 | "Format error for RR string (maybe CLASS and TYPE not valid?)"
254 | end
255 |
256 | # Name of RR - mandatory
257 | begin
258 | @name = $1.downcase
259 | rescue NoMethodError
260 | raise ArgumentError, "Missing name field in RR string #{rrstring}"
261 | end
262 |
263 | # Time to live for RR, default 3 hours
264 | @ttl = $2 ? $2.to_i : 10800
265 |
266 | # RR class, default to IN
267 | @cls = Net::DNS::RR::Classes.new $3
268 |
269 | # RR type, default to A
270 | @type = Net::DNS::RR::Types.new $4
271 |
272 | # All the rest is data
273 | @rdata = $5 ? $5.strip : ""
274 |
275 | if self.class == Net::DNS::RR
276 | Net::DNS::RR.const_get(@type.to_s).new(rrstring)
277 | else
278 | subclass_new_from_string(@rdata)
279 | self.class
280 | end
281 | end
282 |
283 | def new_from_hash(args)
284 | # Name field is mandatory
285 | unless args.has_key? :name
286 | raise ArgumentError, ":name field is mandatory"
287 | end
288 |
289 | @name = args[:name].downcase
290 | @ttl = args[:ttl] ? args[:ttl].to_i : 10800 # Default 3 hours
291 | @type = Net::DNS::RR::Types.new args[:type]
292 | @cls = Net::DNS::RR::Classes.new args[:cls]
293 |
294 | @rdata = args[:rdata] ? args[:rdata].strip : ""
295 | @rdlength = args[:rdlength] || @rdata.size
296 |
297 | if self.class == Net::DNS::RR
298 | Net::DNS::RR.const_get(@type.to_s).new(args)
299 | else
300 | hash = args - [:name, :ttl, :type, :cls]
301 | if hash.has_key? :rdata
302 | subclass_new_from_string(hash[:rdata])
303 | else
304 | subclass_new_from_hash(hash)
305 | end
306 | self.class
307 | end
308 | end
309 |
310 | def new_from_binary(data,offset)
311 | if self.class == Net::DNS::RR
312 | temp = dn_expand(data,offset)[1]
313 | type = Net::DNS::RR::Types.new data.unpack("@#{temp} n")[0]
314 | (eval "Net::DNS::RR::#{type}").parse_packet(data,offset)
315 | else
316 | @name,offset = dn_expand(data,offset)
317 | rrtype,cls,@ttl,@rdlength = data.unpack("@#{offset} n2 N n")
318 | @type = Net::DNS::RR::Types.new rrtype
319 | @cls = Net::DNS::RR::Classes.new cls
320 | offset += RRFIXEDSZ
321 | offset = subclass_new_from_binary(data,offset)
322 | build_pack
323 | set_type
324 | [self, offset]
325 | end
326 | end
327 |
328 | # Methods to be overridden by subclasses
329 | def subclass_new_from_array(arr)
330 | end
331 | def subclass_new_from_string(str)
332 | end
333 | def subclass_new_from_hash(hash)
334 | end
335 | def subclass_new_from_binary(data, offset)
336 | end
337 | def build_pack
338 | end
339 | def get_inspect
340 | @rdata
341 | end
342 | def get_data
343 | @rdata
344 | end
345 |
346 | def set_type
347 | # TODO: Here we should probably
348 | # raise NotImplementedError
349 | # if we want the method to be implemented in any subclass.
350 | end
351 |
352 |
353 | def self.new(*args)
354 | o = allocate
355 | obj = o.send(:initialize,*args)
356 | if self == Net::DNS::RR
357 | obj
358 | else
359 | o
360 | end
361 | end
362 |
363 | end
364 |
365 | end
366 | end
367 |
--------------------------------------------------------------------------------
/lib/net/dns/packet.rb:
--------------------------------------------------------------------------------
1 | require 'logger'
2 | require 'net/dns/names'
3 | require 'net/dns/header'
4 | require 'net/dns/question'
5 | require 'net/dns/rr'
6 |
7 | module Net
8 | module DNS
9 |
10 | #
11 | # = Net::DNS::Packet
12 | #
13 | # The Net::DNS::Packet class represents an entire DNS packet,
14 | # divided in his main section:
15 | #
16 | # * Header (instance of Net::DNS::Header)
17 | # * Question (array of Net::DNS::Question objects)
18 | # * Answer, Authority, Additional (each formed by an array of Net::DNS::RR
19 | # objects)
20 | #
21 | # You can use this class whenever you need to create a DNS packet, whether
22 | # in an user application, in a resolver instance (have a look, for instance,
23 | # at the Net::DNS::Resolver#send method) or for a nameserver.
24 | #
25 | # For example:
26 | #
27 | # # Create a packet
28 | # packet = Net::DNS::Packet.new("www.example.com")
29 | # mx = Net::DNS::Packet.new("example.com", Net::DNS::MX)
30 | #
31 | # # Getting packet binary data, suitable for network transmission
32 | # data = packet.data
33 | #
34 | # A packet object can be created from binary data too, like an
35 | # answer packet just received from a network stream:
36 | #
37 | # packet = Net::DNS::Packet::parse(data)
38 | #
39 | # Each part of a packet can be gotten by the right accessors:
40 | #
41 | # header = packet.header # Instance of Net::DNS::Header class
42 | # question = packet.question # Instance of Net::DNS::Question class
43 | #
44 | # # Iterate over additional RRs
45 | # packet.additional.each do |rr|
46 | # puts "Got an #{rr.type} record"
47 | # end
48 | #
49 | # Some iterators have been written to easy the access of those RRs,
50 | # which are often the most important. So instead of doing:
51 | #
52 | # packet.answer.each do |rr|
53 | # if rr.type == Net::DNS::RR::Types::A
54 | # # do something with +rr.address+
55 | # end
56 | # end
57 | #
58 | # we can do:
59 | #
60 | # packet.each_address do |ip|
61 | # # do something with +ip+
62 | # end
63 | #
64 | # Be sure you don't miss all the iterators in the class documentation.
65 | #
66 | # == Logging facility
67 | #
68 | # As Net::DNS::Resolver class, Net::DNS::Packet class has its own logging
69 | # facility too. It work in the same way the other one do, so you can
70 | # maybe want to override it or change the file descriptor.
71 | #
72 | # packet = Net::DNS::Packet.new("www.example.com")
73 | # packet.logger = $stderr
74 | #
75 | # # or even
76 | # packet.logger = Logger.new("/tmp/packet.log")
77 | #
78 | # If the Net::DNS::Packet class is directly instantiated by the Net::DNS::Resolver
79 | # class, like the great majority of the time, it will use the same logger facility.
80 | #
81 | # Logger level will be set to Logger::Debug if $DEBUG variable is set.
82 | #
83 | class Packet
84 | include Names
85 |
86 | # Base error class.
87 | class Error < StandardError
88 | end
89 |
90 | # Generic Packet Error.
91 | class PacketError < Error
92 | end
93 |
94 |
95 | attr_reader :header, :question, :answer, :authority, :additional
96 | attr_reader :answerfrom, :answersize
97 |
98 | # Creates a new instance of Net::DNS::Packet class. Arguments are the
99 | # canonical name of the resource, an optional type field and an optional
100 | # class field. If the arguments are omitted, no question is added to the new packet;
101 | # type and class default to +A+ and +IN+ if a name is given.
102 | #
103 | # packet = Net::DNS::Packet.new
104 | # packet = Net::DNS::Packet.new("www.example.com")
105 | # packet = Net::DNS::Packet.new("example.com", Net::DNS::MX)
106 | # packet = Net::DNS::Packet.new("example.com", Net::DNS::TXT, Net::DNS::CH)
107 | #
108 | # This class no longer instantiate object from binary data coming from
109 | # network streams. Please use Net::DNS::Packet.parse instead.
110 | def initialize(name = nil, type = Net::DNS::A, cls = Net::DNS::IN)
111 | default_qdcount = 0
112 | @question = []
113 |
114 | if not name.nil?
115 | default_qdcount = 1
116 | @question = [Net::DNS::Question.new(name, type, cls)]
117 | end
118 |
119 | @header = Net::DNS::Header.new(:qdCount => default_qdcount)
120 | @answer = []
121 | @authority = []
122 | @additional = []
123 | @logger = Logger.new $stdout
124 | @logger.level = $DEBUG ? Logger::DEBUG : Logger::WARN
125 | end
126 |
127 |
128 | # Checks if the packet is a QUERY packet
129 | def query?
130 | @header.query?
131 | end
132 |
133 | # Returns the packet object in binary data, suitable
134 | # for sending across a network stream.
135 | #
136 | # packet_data = packet.data
137 | # puts "Packet is #{packet_data.size} bytes long"
138 | #
139 | def data
140 | qdcount=ancount=nscount=arcount=0
141 | data = @header.data
142 | headerlength = data.length
143 |
144 | @question.each do |question|
145 | data += question.data
146 | qdcount += 1
147 | end
148 | @answer.each do |rr|
149 | data += rr.data#(data.length)
150 | ancount += 1
151 | end
152 | @authority.each do |rr|
153 | data += rr.data#(data.length)
154 | nscount += 1
155 | end
156 | @additional.each do |rr|
157 | data += rr.data#(data.length)
158 | arcount += 1
159 | end
160 |
161 | @header.qdCount = qdcount
162 | @header.anCount = ancount
163 | @header.nsCount = nscount
164 | @header.arCount = arcount
165 |
166 | @header.data + data[Net::DNS::HFIXEDSZ..data.size]
167 | end
168 |
169 | # Same as Net::DNS::Packet#data, but implements name compression
170 | # (see RFC1025) for a considerable save of bytes.
171 | #
172 | # packet = Net::DNS::Packet.new("www.example.com")
173 | # puts "Size normal is #{packet.data.size} bytes"
174 | # puts "Size compressed is #{packet.data_comp.size} bytes"
175 | #
176 | def data_comp
177 | offset = 0
178 | compnames = {}
179 | qdcount=ancount=nscount=arcount=0
180 | data = @header.data
181 | headerlength = data.length
182 |
183 | @question.each do |question|
184 | str,offset,names = question.data
185 | data += str
186 | compnames.update(names)
187 | qdcount += 1
188 | end
189 |
190 | @answer.each do |rr|
191 | str,offset,names = rr.data(offset,compnames)
192 | data += str
193 | compnames.update(names)
194 | ancount += 1
195 | end
196 |
197 | @authority.each do |rr|
198 | str,offset,names = rr.data(offset,compnames)
199 | data += str
200 | compnames.update(names)
201 | nscount += 1
202 | end
203 |
204 | @additional.each do |rr|
205 | str,offset,names = rr.data(offset,compnames)
206 | data += str
207 | compnames.update(names)
208 | arcount += 1
209 | end
210 |
211 | @header.qdCount = qdcount
212 | @header.anCount = ancount
213 | @header.nsCount = nscount
214 | @header.arCount = arcount
215 |
216 | @header.data + data[Net::DNS::HFIXEDSZ..data.size]
217 | end
218 |
219 | # Returns a string containing a human-readable representation
220 | # of this Net::DNS::Packet instance.
221 | def inspect
222 | retval = ""
223 | if @answerfrom != "0.0.0.0:0" and @answerfrom
224 | retval += ";; Answer received from #@answerfrom (#{@answersize} bytes)\n;;\n"
225 | end
226 |
227 | retval += ";; HEADER SECTION\n"
228 | retval += @header.inspect
229 |
230 | retval += "\n"
231 | section = (@header.opCode == "UPDATE") ? "ZONE" : "QUESTION"
232 | retval += ";; #{section} SECTION (#{@header.qdCount} record#{@header.qdCount == 1 ? '' : 's'}):\n"
233 | @question.each do |qr|
234 | retval += ";; " + qr.inspect + "\n"
235 | end
236 |
237 | unless @answer.size == 0
238 | retval += "\n"
239 | section = (@header.opCode == "UPDATE") ? "PREREQUISITE" : "ANSWER"
240 | retval += ";; #{section} SECTION (#{@header.anCount} record#{@header.anCount == 1 ? '' : 's'}):\n"
241 | @answer.each do |rr|
242 | retval += rr.inspect + "\n"
243 | end
244 | end
245 |
246 | unless @authority.size == 0
247 | retval += "\n"
248 | section = (@header.opCode == "UPDATE") ? "UPDATE" : "AUTHORITY"
249 | retval += ";; #{section} SECTION (#{@header.nsCount} record#{@header.nsCount == 1 ? '' : 's'}):\n"
250 | @authority.each do |rr|
251 | retval += rr.inspect + "\n"
252 | end
253 | end
254 |
255 | unless @additional.size == 0
256 | retval += "\n"
257 | retval += ";; ADDITIONAL SECTION (#{@header.arCount} record#{@header.arCount == 1 ? '' : 's'}):\n"
258 | @additional.each do |rr|
259 | retval += rr.inspect + "\n"
260 | end
261 | end
262 |
263 | retval
264 | end
265 | alias_method :to_s, :inspect
266 |
267 | # Delegates to Net::DNS::Header#truncated?.
268 | def truncated?
269 | @header.truncated?
270 | end
271 |
272 | # Assigns a Net::DNS::Header object
273 | # to this Net::DNS::Packet instance.
274 | def header=(object)
275 | if object.kind_of? Net::DNS::Header
276 | @header = object
277 | else
278 | raise ArgumentError, "Argument must be a Net::DNS::Header object"
279 | end
280 | end
281 |
282 | # Assigns a Net::DNS::Question object
283 | # to this Net::DNS::Packet instance.
284 | def question=(object)
285 | case object
286 | when Array
287 | if object.all? {|x| x.kind_of? Net::DNS::Question}
288 | @question = object
289 | else
290 | raise ArgumentError, "Some of the elements is not an Net::DNS::Question object"
291 | end
292 | when Net::DNS::Question
293 | @question = [object]
294 | else
295 | raise ArgumentError, "Invalid argument, not a Question object nor an array of objects"
296 | end
297 | end
298 |
299 | # Assigns one or an array of Net::DNS::RR objects
300 | # to the answer section of this Net::DNS::Packet instance.
301 | def answer=(object)
302 | case object
303 | when Array
304 | if object.all? {|x| x.kind_of? Net::DNS::RR}
305 | @answer = object
306 | else
307 | raise ArgumentError, "Some of the elements is not an Net::DNS::RR object"
308 | end
309 | when Net::DNS::RR
310 | @answer = [object]
311 | else
312 | raise ArgumentError, "Invalid argument, not a RR object nor an array of objects"
313 | end
314 | end
315 |
316 | # Assigns one or an array of Net::DNS::RR objects
317 | # to the additional section of this Net::DNS::Packet instance.
318 | def additional=(object)
319 | case object
320 | when Array
321 | if object.all? {|x| x.kind_of? Net::DNS::RR}
322 | @additional = object
323 | else
324 | raise ArgumentError, "Some of the elements is not an Net::DNS::RR object"
325 | end
326 | when Net::DNS::RR
327 | @additional = [object]
328 | else
329 | raise ArgumentError, "Invalid argument, not a RR object nor an array of objects"
330 | end
331 | end
332 |
333 | # Assigns one or an array of Net::DNS::RR objects
334 | # to the authority section of this Net::DNS::Packet instance.
335 | def authority=(object)
336 | case object
337 | when Array
338 | if object.all? {|x| x.kind_of? Net::DNS::RR}
339 | @authority = object
340 | else
341 | raise ArgumentError, "Some of the elements is not an Net::DNS::RR object"
342 | end
343 | when Net::DNS::RR
344 | @authority = [object]
345 | else
346 | raise ArgumentError, "Invalid argument, not a RR object nor an array of objects"
347 | end
348 | end
349 |
350 | # Filters the elements in the +answer+ section based on the class given
351 | def elements(*types)
352 | if types.any?
353 | @answer.select do |elem|
354 | types.any? { |type| elem.kind_of?(type) }
355 | end
356 | else
357 | @answer
358 | end
359 | end
360 |
361 | # Iterates every address in the +answer+ section
362 | # of this Net::DNS::Packet instance.
363 | #
364 | # packet.each_address do |ip|
365 | # ping ip.to_s
366 | # end
367 | #
368 | # As you can see in the documentation for the Net::DNS::RR::A class,
369 | # the address returned is an instance of IPAddr class.
370 | def each_address(&block)
371 | elements(Net::DNS::RR::A, Net::DNS::RR::AAAA).map(&:address).each(&block)
372 | end
373 |
374 | # Iterates every nameserver in the +answer+ section
375 | # of this Net::DNS::Packet instance.
376 | #
377 | # packet.each_nameserver do |ns|
378 | # puts "Nameserver found: #{ns}"
379 | # end
380 | #
381 | def each_nameserver(&block)
382 | elements(Net::DNS::RR::NS).map(&:nsdname).each(&block)
383 | end
384 |
385 | # Iterates every exchange record in the +answer+ section
386 | # of this Net::DNS::Packet instance.
387 | #
388 | # packet.each_mx do |pref,name|
389 | # puts "Mail exchange #{name} has preference #{pref}"
390 | # end
391 | #
392 | def each_mx(&block)
393 | elements(Net::DNS::RR::MX).map{|elem| [elem.preference, elem.exchange]}.each(&block)
394 | end
395 |
396 | # Iterates every canonical name in the +answer+ section
397 | # of this Net::DNS::Packet instance.
398 | #
399 | # packet.each_cname do |cname|
400 | # puts "Canonical name: #{cname}"
401 | # end
402 | #
403 | def each_cname(&block)
404 | elements(Net::DNS::RR::CNAME).map(&:cname).each(&block)
405 | end
406 |
407 | # Iterates every pointer in the +answer+ section
408 | # of this Net::DNS::Packet instance.
409 | #
410 | # packet.each_ptr do |ptr|
411 | # puts "Pointer for resource: #{ptr}"
412 | # end
413 | #
414 | def each_ptr(&block)
415 | elements(Net::DNS::RR::PTR).map(&:ptrdname).each(&block)
416 | end
417 |
418 | # Returns the packet size in bytes.
419 | #
420 | # Resolver("www.google.com") do |packet|
421 | # puts packet.size + " bytes"}
422 | # end
423 | # # => 484 bytes
424 | #
425 | def size
426 | data.size
427 | end
428 |
429 | # Checks whether the query returned a NXDOMAIN error,
430 | # meaning the queried domain name doesn't exist.
431 | #
432 | # %w[a.com google.com ibm.com d.com].each do |domain|
433 | # response = Net::DNS::Resolver.new.send(domain)
434 | # puts "#{domain} doesn't exist" if response.nxdomain?
435 | # end
436 | # # => a.com doesn't exist
437 | # # => d.com doesn't exist
438 | #
439 | def nxdomain?
440 | header.rCode.code == Net::DNS::Header::RCode::NAME
441 | end
442 |
443 |
444 | # Creates a new instance of Net::DNS::Packet class from binary data,
445 | # taken out from a network stream. For example:
446 | #
447 | # # udp_socket is an UDPSocket waiting for a response
448 | # ans = udp_socket.recvfrom(1500)
449 | # packet = Net::DNS::Packet::parse(ans)
450 | #
451 | # An optional +from+ argument can be used to specify the information
452 | # of the sender. If data is passed as is from a Socket#recvfrom call,
453 | # the method will accept it.
454 | #
455 | # Be sure that your network data is clean from any UDP/TCP header,
456 | # especially when using RAW sockets.
457 | #
458 | def self.parse(*args)
459 | o = allocate
460 | o.send(:new_from_data, *args)
461 | o
462 | end
463 |
464 | private
465 |
466 | # New packet from binary data
467 | def new_from_data(data, from = nil)
468 | unless from
469 | if data.kind_of? Array
470 | data, from = data
471 | else
472 | from = [0, 0, "0.0.0.0", "unknown"]
473 | end
474 | end
475 |
476 | @answerfrom = from[2] + ":" + from[1].to_s
477 | @answersize = data.size
478 | @logger = Logger.new $stdout
479 | @logger.level = $DEBUG ? Logger::DEBUG : Logger::WARN
480 |
481 | #------------------------------------------------------------
482 | # Header section
483 | #------------------------------------------------------------
484 | offset = Net::DNS::HFIXEDSZ
485 | @header = Net::DNS::Header.parse(data[0..offset-1])
486 |
487 | @logger.debug ";; HEADER SECTION"
488 | @logger.debug @header.inspect
489 |
490 | #------------------------------------------------------------
491 | # Question section
492 | #------------------------------------------------------------
493 | section = @header.opCode == "UPDATE" ? "ZONE" : "QUESTION"
494 | @logger.debug ";; #{section} SECTION (#{@header.qdCount} record#{@header.qdCount == 1 ? '': 's'})"
495 |
496 | @question = []
497 | @header.qdCount.times do
498 | qobj,offset = parse_question(data,offset)
499 | @question << qobj
500 | @logger.debug ";; #{qobj.inspect}"
501 | end
502 |
503 | #------------------------------------------------------------
504 | # Answer/prerequisite section
505 | #------------------------------------------------------------
506 | section = @header.opCode == "UPDATE" ? "PREREQUISITE" : "ANSWER"
507 | @logger.debug ";; #{section} SECTION (#{@header.qdCount} record#{@header.qdCount == 1 ? '': 's'})"
508 |
509 | @answer = []
510 | @header.anCount.times do
511 | begin
512 | rrobj,offset = Net::DNS::RR.parse_packet(data,offset)
513 | @answer << rrobj
514 | @logger.debug rrobj.inspect
515 | rescue NameError => e
516 | warn "Net::DNS unsupported record type: #{e.message}"
517 | end
518 | end
519 |
520 | #------------------------------------------------------------
521 | # Authority/update section
522 | #------------------------------------------------------------
523 | section = @header.opCode == "UPDATE" ? "UPDATE" : "AUTHORITY"
524 | @logger.debug ";; #{section} SECTION (#{@header.nsCount} record#{@header.nsCount == 1 ? '': 's'})"
525 |
526 | @authority = []
527 | @header.nsCount.times do
528 | begin
529 | rrobj,offset = Net::DNS::RR.parse_packet(data,offset)
530 | @authority << rrobj
531 | @logger.debug rrobj.inspect
532 | rescue NameError => e
533 | warn "Net::DNS unsupported record type: #{e.message}"
534 | end
535 | end
536 |
537 | #------------------------------------------------------------
538 | # Additional section
539 | #------------------------------------------------------------
540 | @logger.debug ";; ADDITIONAL SECTION (#{@header.arCount} record#{@header.arCount == 1 ? '': 's'})"
541 |
542 | @additional = []
543 | @header.arCount.times do
544 | begin
545 | rrobj,offset = Net::DNS::RR.parse_packet(data,offset)
546 | @additional << rrobj
547 | @logger.debug rrobj.inspect
548 | rescue NameError => e
549 | warn "Net::DNS unsupported record type: #{e.message}"
550 | end
551 | end
552 |
553 | end
554 |
555 |
556 | # Parse question section
557 | def parse_question(data,offset)
558 | size = (dn_expand(data, offset)[1] - offset) + (2 * Net::DNS::INT16SZ)
559 | return [Net::DNS::Question.parse(data[offset, size]), offset + size]
560 | rescue StandardError => e
561 | raise PacketError, "Caught exception, maybe packet malformed => #{e.message}"
562 | end
563 |
564 | end
565 |
566 | end
567 | end
568 |
--------------------------------------------------------------------------------
/lib/net/dns/header.rb:
--------------------------------------------------------------------------------
1 | module Net
2 | module DNS
3 |
4 | # DNS packet header class
5 | #
6 | # The Net::DNS::Header class represents the header portion of a
7 | # DNS packet. An Header object is created whenever a new packet
8 | # is parsed or as user request.
9 | #
10 | # header = Net::DNS::Header.new
11 | # # ;; id = 18123
12 | # # ;; qr = 0 opCode: 0 aa = 0 tc = 0 rd = 1
13 | # # ;; ra = 0 ad = 0 cd = 0 rcode = 0
14 | # # ;; qdCount = 1 anCount = 0 nsCount = 0 arCount = 0
15 | #
16 | # header.format
17 | # # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
18 | # # | 18123 |
19 | # # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
20 | # # |0| 0 |0|0|1|0|0| 0 | 0 |
21 | # # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
22 | # # | 1 |
23 | # # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
24 | # # | 0 |
25 | # # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
26 | # # | 0 |
27 | # # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
28 | # # | 0 |
29 | # # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
30 | #
31 | # # packet is an instance of Net::DNS::Packet
32 | # header = packet.header
33 | # puts "Answer is #{header.auth? ? '' : 'non'} authoritative"
34 | #
35 | # A lot of methods were written to keep a compatibility layer with
36 | # the Perl version of the library, as long as methods name which are
37 | # more or less the same.
38 | #
39 | class Header
40 |
41 | # A wrong +count+ parameter has been passed.
42 | class WrongCountError < ArgumentError
43 | end
44 |
45 | # A wrong +recursive+ parameter has been passed.
46 | class WrongRecursiveError < ArgumentError
47 | end
48 |
49 | # An invalid +opCode+ has been specified.
50 | class WrongOpcodeError < ArgumentError
51 | end
52 |
53 | # Base error class.
54 | class Error < StandardError
55 | end
56 |
57 |
58 | # DNS Header RCode handling class
59 | #
60 | # It should be used internally by Net::DNS::Header class. However, it's still
61 | # possible to instantiate it directly.
62 | #
63 | # require 'net/dns/header'
64 | # rcode = Net::DNS::Header::RCode.new 0
65 | #
66 | # The RCode class represents the RCode field in the Header portion of a
67 | # DNS packet. This field (called Response Code) is used to get informations
68 | # about the status of a DNS operation, such as a query or an update. These
69 | # are the values in the original Mockapetris's standard (RFC1035):
70 | #
71 | # * 0 No error condition
72 | # * 1 Format error - The name server was unable to interpret
73 | # the query.
74 | # * 2 Server failure - The name server was
75 | # unable to process this query due to a
76 | # problem with the name server.
77 | # * 3 Name Error - Meaningful only for
78 | # responses from an authoritative name
79 | # server, this code means that the
80 | # domain name referenced in the query does
81 | # not exist.
82 | # * 4 Not Implemented - The name server does
83 | # not support the requested kind of query.
84 | # * 5 Refused - The name server refuses to
85 | # perform the specified operation for
86 | # policy reasons. For example, a name
87 | # server may not wish to provide the
88 | # information to the particular requester,
89 | # or a name server may not wish to perform
90 | # a particular operation (e.g., zone
91 | # transfer) for particular data.
92 | # * 6-15 Reserved for future use.
93 | #
94 | # In the next DNS RFCs, codes 6-15 has been assigned to the following
95 | # errors:
96 | #
97 | # * 6 YXDomain
98 | # * 7 YXRRSet
99 | # * 8 NXRRSet
100 | # * 9 NotAuth
101 | # * 10 NotZone
102 | #
103 | # More RCodes has to come for TSIGs and other operations.
104 | #
105 | class RCode
106 |
107 | # Constant for +rcode+ Response Code No Error
108 | NOERROR = 0
109 | # Constant for +rcode+ Response Code Format Error
110 | FORMAT = 1
111 | # Constant for +rcode+ Response Code Server Format Error
112 | SERVER = 2
113 | # Constant for +rcode+ Response Code Name Error
114 | NAME = 3
115 | # Constant for +rcode+ Response Code Not Implemented Error
116 | NOTIMPLEMENTED = 4
117 | # Constant for +rcode+ Response Code Refused Error
118 | REFUSED = 5
119 |
120 |
121 |
122 | RCodeType = %w[NoError FormErr ServFail NXDomain NotImp
123 | Refused YXDomain YXRRSet NXRRSet NotAuth NotZone]
124 |
125 | RCodeErrorString = ["No errors",
126 | "The name server was unable to interpret the query",
127 | "The name server was unable to process this query due to problem with the name server",
128 | "Domain name referenced in the query does not exists",
129 | "The name server does not support the requested kind of query",
130 | "The name server refuses to perform the specified operation for policy reasons",
131 | "",
132 | "",
133 | "",
134 | "",
135 | ""]
136 |
137 | attr_reader :code, :type, :explanation
138 |
139 | def initialize(code)
140 | if (0..10).include? code
141 | @code = code
142 | @type = RCodeType[code]
143 | @explanation = RCodeErrorString[code]
144 | else
145 | raise ArgumentError, "RCode `#{code}' out of range"
146 | end
147 | end
148 |
149 | def to_s
150 | @code.to_s
151 | end
152 | end
153 |
154 |
155 | # Listing: http://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-5
156 | # Constant for +opCode+ query
157 | QUERY = 0
158 | # Constant for +opCode+ iquery
159 | IQUERY = 1
160 | # Constant for +opCode+ status
161 | STATUS = 2
162 | # Constant for +opCode+ notify
163 | NOTIFY = 4
164 | # Array with given strings
165 | OPARR = %w[QUERY IQUERY STATUS UNASSIGNED NOTIFY]
166 |
167 | # Reader for +id+ attribute
168 | attr_reader :id
169 | # Reader for the operational code
170 | attr_reader :opCode
171 | # Reader for the rCode instance
172 | attr_reader :rCode
173 | # Reader for question section entries number
174 | attr_reader :qdCount
175 | # Reader for answer section entries number
176 | attr_reader :anCount
177 | # Reader for authority section entries number
178 | attr_reader :nsCount
179 | # Reader for addictional section entries number
180 | attr_reader :arCount
181 |
182 | # Creates a new Net::DNS::Header object with the desired values,
183 | # which can be specified as an Hash argument. When called without
184 | # arguments, defaults are used. If a data string is passed, values
185 | # are taken from parsing the string.
186 | #
187 | # Examples:
188 | #
189 | # # Create a new Net::DNS::Header object
190 | # header = Net::DNS::Header.new
191 | #
192 | # # Create a new Net::DNS::Header object passing values
193 | # header = Net::DNS::Header.new(:opCode => 1, :rd => 0)
194 | #
195 | # # Create a new Net::DNS::Header object with binary data
196 | # header = Net::DNS::Header.new(data)
197 | #
198 | # Default values are:
199 | #
200 | # :id => auto generated
201 | # :qr => 0 # Query response flag
202 | # :aa => 0 # Authoritative answer flag
203 | # :tc => 0 # Truncated packet flag
204 | # :ra => 0 # Recursiond available flag
205 | # :rCode => 0 # Response code (status of the query)
206 | # :opCode => 0 # Operational code (purpose of the query)
207 | # :cd => 0 # Checking disable flag
208 | # :ad => 0 # Only relevant in DNSSEC context
209 | # :rd => 1 # Recursion desired flag
210 | # :qdCount => 1 # Number of questions in the dns packet
211 | # :anCount => 0 # Number of answer RRs in the dns packet
212 | # :nsCount => 0 # Number of authoritative RRs in the dns packet
213 | # :arCount => 0 # Number of additional RRs in the dns packet
214 | #
215 | # See also each option for a detailed explanation of usage.
216 | #
217 | def initialize(arg = {})
218 | if arg.kind_of? Hash
219 | new_from_hash(arg)
220 | else
221 | raise ArgumentError, "Wrong argument class `#{arg.class}'"
222 | end
223 | end
224 |
225 | # Creates a new Net::DNS::Header object from binary data, which is
226 | # passed as a string object as argument.
227 | # The configurations parameters are taken from parsing the string.
228 | #
229 | # Example:
230 | #
231 | # # Create a new Net::DNS::Header object with binary data
232 | # header = Net::DNS::Header.new(data)
233 | #
234 | # header.auth?
235 | # #=> "true" if it comes from authoritative name server
236 | #
237 | def self.parse(arg)
238 | if arg.kind_of? String
239 | o = allocate
240 | o.send(:new_from_binary, arg)
241 | o
242 | else
243 | raise ArgumentError, "Wrong argument class `#{arg.class}'"
244 | end
245 | end
246 |
247 | # Inspect method, prints out all the options and relative values.
248 | #
249 | # p Net::DNS::Header.new
250 | # # ;; id = 18123
251 | # # ;; qr = 0 opCode: 0 aa = 0 tc = 0 rd = 1
252 | # # ;; ra = 0 ad = 0 cd = 0 rcode = 0
253 | # # ;; qdCount = 1 anCount = 0 nsCount = 0 arCount = 0
254 | #
255 | # This method will maybe be changed in the future to a more pretty
256 | # way of display output.
257 | #
258 | def inspect
259 | ";; id = #@id\n" +
260 | if false # @opCode == "UPDATE"
261 | #do stuff
262 | else
263 | ";; qr = #@qr\t" +
264 | "opCode: #{opCode_str}\t" +
265 | "aa = #@aa\t" +
266 | "tc = #@tc\t" +
267 | "rd = #@rd\n" +
268 | ";; ra = #@ra\t" +
269 | "ad = #@ad\t" +
270 | "cd = #@cd\t" +
271 | "rcode = #{@rCode.type}\n" +
272 | ";; qdCount = #@qdCount\t"+
273 | "anCount = #@anCount\t"+
274 | "nsCount = #@nsCount\t"+
275 | "arCount = #@arCount\n"
276 | end
277 | end
278 |
279 | # The Net::DNS::Header#format method prints out the header
280 | # in a special ascii representation of data, in a way
281 | # similar to those often found on RFCs.
282 | #
283 | # p Net::DNS::Header.new.format
284 | # # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
285 | # # | 18123 |
286 | # # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
287 | # # |0| 0 |0|0|1|0|0| 0 | 0 |
288 | # # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
289 | # # | 1 |
290 | # # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
291 | # # | 0 |
292 | # # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
293 | # # | 0 |
294 | # # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
295 | # # | 0 |
296 | # # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
297 | #
298 | # This can be very usefull for didactical purpouses :)
299 | #
300 | def format
301 | del = ("+-" * 16) + "+\n"
302 | len = del.length
303 | str = del + "|" + @id.to_s.center(len-3) + "|\n"
304 | str += del + "|" + @qr.to_s
305 | str += "|" + @opCode.to_s.center(7)
306 | str += "|" + @aa.to_s
307 | str += "|" + @tc.to_s
308 | str += "|" + @rd.to_s
309 | str += "|" + @ra.to_s
310 | str += "|" + @ad.to_s
311 | str += "|" + @cd.to_s.center(3)
312 | str += "|" + @rCode.to_s.center(7) + "|\n"
313 | str += del + "|" + @qdCount.to_s.center(len-3) + "|\n"
314 | str += del + "|" + @anCount.to_s.center(len-3) + "|\n"
315 | str += del + "|" + @nsCount.to_s.center(len-3) + "|\n"
316 | str += del + "|" + @arCount.to_s.center(len-3) + "|\n" + del
317 | str
318 | end
319 |
320 | # Returns the header data in binary format, appropriate
321 | # for use in a DNS query packet.
322 | #
323 | # hdata = header.data
324 | # puts "Header is #{hdata.size} bytes"
325 | #
326 | def data
327 | arr = []
328 | arr.push(@id)
329 | arr.push((@qr<<7)|(@opCode<<3)|(@aa<<2)|(@tc<<1)|@rd)
330 | arr.push((@ra<<7)|(@ad<<5)|(@cd<<4)|@rCode.code)
331 | arr.push(@qdCount)
332 | arr.push(@anCount)
333 | arr.push(@nsCount)
334 | arr.push(@arCount)
335 | arr.pack("n C2 n4")
336 | end
337 |
338 | # Set the ID for the current header. Useful when
339 | # performing security tests.
340 | #
341 | def id=(val)
342 | if (0..65535).include? val
343 | @id = val
344 | else
345 | raise ArgumentError, "ID `#{val}' out of range"
346 | end
347 | end
348 |
349 | # Checks whether the header is a query (+qr+ bit set to 0)
350 | #
351 | def query?
352 | @qr == 0
353 | end
354 |
355 | # Set the +qr+ query response flag to be either +true+ or
356 | # +false+. You can also use the values 0 and 1. This flag
357 | # indicates if the DNS packet contains a query or an answer,
358 | # so it should be set to +true+ in DNS answer packets.
359 | # If +qr+ is +true+, the packet is a response.
360 | #
361 | def qr=(val)
362 | case val
363 | when true
364 | @qr = 1
365 | when false
366 | @qr = 0
367 | when 0,1
368 | @qr = val
369 | else
370 | raise ArgumentError, ":qr must be true(or 1) or false(or 0)"
371 | end
372 | end
373 |
374 | # Checks whether the header is a response
375 | # (+qr+ bit set to 1)
376 | #
377 | def response?
378 | @qr == 1
379 | end
380 |
381 | # Returns a string representation of the +opCode+
382 | #
383 | # puts "Packet is a #{header.opCode_str}"
384 | # #=> Packet is a QUERY
385 | #
386 | def opCode_str
387 | OPARR[@opCode]
388 | end
389 |
390 | # Set the +opCode+ variable to a new value. This fields indicates
391 | # the type of the question present in the DNS packet; +val+ can be
392 | # one of the values QUERY, IQUERY or STATUS.
393 | #
394 | # * QUERY is the standard DNS query
395 | # * IQUERY is the inverse query
396 | # * STATUS is used to query the nameserver for its status
397 | # * NOTIFY is to notify a slave that it should check for an updated SOA on the master
398 | #
399 | # Example:
400 | #
401 | # include Net::DNS
402 | # header = Header.new
403 | # header.opCode = Header::STATUS
404 | #
405 | def opCode=(val)
406 | if [0, 1, 2, 4].include? val
407 | @opCode = val
408 | else
409 | raise WrongOpcodeError, "Wrong opCode value (#{val}), must be QUERY, IQUERY, STATUS, or NOTIFY"
410 | end
411 | end
412 |
413 | # Checks whether the response is authoritative
414 | #
415 | # if header.auth?
416 | # puts "Response is authoritative"
417 | # else
418 | # puts "Answer is NOT authoritative"
419 | # end
420 | #
421 | def auth?
422 | @aa == 1
423 | end
424 |
425 | # Set the +aa+ flag (authoritative answer) to either +true+
426 | # or +false+. You can also use 0 or 1.
427 | #
428 | # This flag indicates whether a DNS answer packet contains
429 | # authoritative data, meaning that is was generated by a
430 | # nameserver authoritative for the domain of the question.
431 | #
432 | # Must only be set to +true+ in DNS answer packets.
433 | #
434 | def aa=(val)
435 | case val
436 | when true
437 | @aa = 1
438 | when false
439 | @aa = 0
440 | when 0,1
441 | @aa = val
442 | else
443 | raise ArgumentError, ":aa must be true(or 1) or false(or 0)"
444 | end
445 | end
446 |
447 | # Checks whether the packet was truncated
448 | #
449 | # # Sending packet using UDP
450 | # if header.truncated?
451 | # puts "Warning, packet has been truncated!"
452 | # # Sending packet using TCP
453 | # end
454 | # # Do something with the answer
455 | #
456 | def truncated?
457 | @tc == 1
458 | end
459 |
460 | # Set the +tc+ flag (truncated packet) to either +true+
461 | # ot +false+. You can also use 0 or 1.
462 | #
463 | # The truncated flag is used in response packets to indicate
464 | # that the amount of data to be trasmitted exceedes the
465 | # maximum allowed by the protocol in use, tipically UDP, and
466 | # that the data present in the packet has been truncated.
467 | # A different protocol (such has TCP) need to be used to
468 | # retrieve full data.
469 | #
470 | # Must only be set in DNS answer packets.
471 | #
472 | def tc=(val)
473 | case val
474 | when true
475 | @tc = 1
476 | when false
477 | @tc = 0
478 | when 0,1
479 | @tc = val
480 | else
481 | raise ArgumentError, ":tc must be true(or 1) or false(or 0)"
482 | end
483 | end
484 |
485 | # Checks whether the packet has a recursion bit
486 | # set, meaning that recursion is desired
487 | #
488 | def recursive?
489 | @rd == 1
490 | end
491 |
492 | # Sets the recursion desidered bit.
493 | # Remember that recursion query support is
494 | # optional.
495 | #
496 | # header.recursive = true
497 | # hdata = header.data # suitable for sending
498 | #
499 | # Consult RFC1034 and RFC1035 for a detailed explanation
500 | # of how recursion works.
501 | #
502 | def recursive=(val)
503 | case val
504 | when true
505 | @rd = 1
506 | when false
507 | @rd = 0
508 | when 1
509 | @rd = 1
510 | when 0
511 | @rd = 0
512 | else
513 | raise WrongRecursiveError, "Wrong value (#{val}), please specify true (1) or false (0)"
514 | end
515 | end
516 |
517 | # Alias for Header#recursive= to keep compatibility
518 | # with the Perl version.
519 | #
520 | def rd=(val)
521 | self.recursive = val
522 | end
523 |
524 | # Checks whether recursion is available.
525 | # This flag is usually set by nameservers to indicate
526 | # that they support recursive-type queries.
527 | #
528 | def r_available?
529 | @ra == 1
530 | end
531 |
532 | # Set the +ra+ flag (recursion available) to either +true+ or
533 | # +false+. You can also use 0 and 1.
534 | #
535 | # This flag must only be set in DNS answer packets.
536 | #
537 | def ra=(val)
538 | case val
539 | when true
540 | @ra = 1
541 | when false
542 | @ra = 0
543 | when 0,1
544 | @ra = val
545 | else
546 | raise ArgumentError, ":ra must be true(or 1) or false(or 0)"
547 | end
548 | end
549 |
550 | # Checks whether checking is enabled or disabled.
551 | #
552 | # Checking is enabled by default.
553 | #
554 | def checking?
555 | @cd == 0
556 | end
557 |
558 | # Set the +cd+ flag (checking disabled) to either +true+
559 | # ot +false+. You can also use 0 or 1.
560 | #
561 | def cd=(val)
562 | case val
563 | when true
564 | @cd = 1
565 | when false
566 | @cd = 0
567 | when 0,1
568 | @cd = val
569 | else
570 | raise ArgumentError, ":cd must be true(or 1) or false(or 0)"
571 | end
572 | end
573 |
574 | # Checks whether +ad+ flag has been set.
575 | #
576 | # This flag is only relevant in DNSSEC context.
577 | #
578 | def verified?
579 | @ad == 1
580 | end
581 |
582 | # Set the +ad+ flag to either +true+
583 | # ot +false+. You can also use 0 or 1.
584 | #
585 | # The AD bit is only set on answers where signatures have
586 | # been cryptographically verified or the server is
587 | # authoritative for the data and is allowed to set the bit by policy.
588 | #
589 | def ad=(val)
590 | case val
591 | when true
592 | @ad = 1
593 | when false
594 | @ad = 0
595 | when 0,1
596 | @ad = val
597 | else
598 | raise ArgumentError, ":ad must be true(or 1) or false(or 0)"
599 | end
600 | end
601 |
602 | # Returns an error array for the header response code, or
603 | # +nil+ if no error is generated.
604 | #
605 | # error, cause = header.rCode_str
606 | # puts "Error #{error} cause by: #{cause}" if error
607 | # #=> Error ForErr caused by: The name server
608 | # #=> was unable to interpret the query
609 | #
610 | def rCode_str
611 | return rCode.type, rCode.explanation
612 | end
613 |
614 | # Checks for errors in the DNS packet
615 | #
616 | # unless header.error?
617 | # puts "No errors in DNS answer packet"
618 | # end
619 | #
620 | def error?
621 | @rCode.code > 0
622 | end
623 |
624 | # Set the rCode value. This should only be done in DNS
625 | # answer packets.
626 | #
627 | def rCode=(val)
628 | @rCode = RCode.new(val)
629 | end
630 |
631 | # Sets the number of entries in a question section
632 | #
633 | def qdCount=(val)
634 | if (0..65535).include? val
635 | @qdCount = val
636 | else
637 | raise WrongCountError, "Wrong number of count (#{val}), must be 0-65535"
638 | end
639 | end
640 |
641 | # Sets the number of RRs in an answer section
642 | #
643 | def anCount=(val)
644 | if (0..65535).include? val
645 | @anCount = val
646 | else
647 | raise WrongCountError, "Wrong number of count (#{val}), must be 0-65535"
648 | end
649 | end
650 |
651 | # Sets the number of RRs in an authority section
652 | #
653 | def nsCount=(val)
654 | if (0..65535).include? val
655 | @nsCount = val
656 | else
657 | raise WrongCountError, "Wrong number of count (#{val}), must be 0-65535"
658 | end
659 | end
660 |
661 | # Sets the number of RRs in an addictional section
662 | #
663 | def arCount=(val)
664 | if (0..65535).include? val
665 | @arCount = val
666 | else
667 | raise WrongCountError, "Wrong number of count: `#{val}' must be 0-65535"
668 | end
669 | end
670 |
671 | private
672 |
673 | def new_from_scratch
674 | @id = genID # generate ad unique id
675 | @qr = @aa = @tc = @ra = @ad = @cd = 0
676 | @rCode = RCode.new(0) # no error
677 | @anCount = @nsCount = @arCount = 0
678 | @rd = @qdCount = 1
679 | @opCode = QUERY # standard query, default message
680 | end
681 |
682 | def new_from_binary(str)
683 | unless str.size == Net::DNS::HFIXEDSZ
684 | raise ArgumentError, "Header binary data has wrong size: `#{str.size}' bytes"
685 | end
686 | arr = str.unpack("n C2 n4")
687 | @id = arr[0]
688 | @qr = (arr[1] >> 7) & 0x01
689 | @opCode = (arr[1] >> 3) & 0x0F
690 | @aa = (arr[1] >> 2) & 0x01
691 | @tc = (arr[1] >> 1) & 0x01
692 | @rd = arr[1] & 0x1
693 | @ra = (arr[2] >> 7) & 0x01
694 | @ad = (arr[2] >> 5) & 0x01
695 | @cd = (arr[2] >> 4) & 0x01
696 | @rCode = RCode.new(arr[2] & 0xf)
697 | @qdCount = arr[3]
698 | @anCount = arr[4]
699 | @nsCount = arr[5]
700 | @arCount = arr[6]
701 | end
702 |
703 | def new_from_hash(hash)
704 | new_from_scratch
705 | hash.each do |key,val|
706 | eval "self.#{key.to_s} = val"
707 | end
708 | end
709 |
710 | def genID
711 | rand(65535)
712 | end
713 |
714 | end
715 |
716 | end
717 | end
718 |
--------------------------------------------------------------------------------
/lib/net/dns/resolver.rb:
--------------------------------------------------------------------------------
1 | require 'rbconfig'
2 | require 'socket'
3 | require 'timeout'
4 | require 'net/dns/packet'
5 | require 'net/dns/resolver/timeouts'
6 | require 'packetfu'
7 | require 'ipaddr'
8 |
9 | # Resolver helper method.
10 | #
11 | # Calling the resolver directly:
12 | #
13 | # puts Resolver("www.google.com").answer.size
14 | # # => 5
15 | #
16 | # An optional block can be passed yielding the Net::DNS::Packet object.
17 | #
18 | # Resolver("www.google.com") { |packet| puts packet.size + " bytes" }
19 | # # => 484 bytes
20 | #
21 | def Resolver(name, type = Net::DNS::A, cls = Net::DNS::IN, &block)
22 | resolver = Net::DNS::Resolver.start(name, type, cls)
23 | if block_given?
24 | yield resolver
25 | else
26 | resolver
27 | end
28 | end
29 |
30 | module Net
31 | module DNS
32 |
33 | include Logger::Severity
34 |
35 | # = Net::DNS::Resolver - DNS resolver class
36 | #
37 | # The Net::DNS::Resolver class implements a complete DNS resolver written
38 | # in pure Ruby, without a single C line of code. It has all of the
39 | # tipical properties of an evoluted resolver, and a bit of OO which
40 | # comes from having used Ruby.
41 | #
42 | # This project started as a porting of the Net::DNS Perl module,
43 | # written by Martin Fuhr, but turned out (in the last months) to be
44 | # an almost complete rewriting. Well, maybe some of the features of
45 | # the Perl version are still missing, but guys, at least this is
46 | # readable code!
47 | #
48 | # == Environment
49 | #
50 | # The Following Environment variables can also be used to configure
51 | # the resolver:
52 | #
53 | # * +RES_NAMESERVERS+: A space-separated list of nameservers to query.
54 | #
55 | # # Bourne Shell
56 | # $ RES_NAMESERVERS="192.168.1.1 192.168.2.2 192.168.3.3"
57 | # $ export RES_NAMESERVERS
58 | #
59 | # # C Shell
60 | # % setenv RES_NAMESERVERS "192.168.1.1 192.168.2.2 192.168.3.3"
61 | #
62 | # * +RES_SEARCHLIST+: A space-separated list of domains to put in the
63 | # search list.
64 | #
65 | # # Bourne Shell
66 | # $ RES_SEARCHLIST="example.com sub1.example.com sub2.example.com"
67 | # $ export RES_SEARCHLIST
68 | #
69 | # # C Shell
70 | # % setenv RES_SEARCHLIST "example.com sub1.example.com sub2.example.com"
71 | #
72 | # * +LOCALDOMAIN+: The default domain.
73 | #
74 | # # Bourne Shell
75 | # $ LOCALDOMAIN=example.com
76 | # $ export LOCALDOMAIN
77 | #
78 | # # C Shell
79 | # % setenv LOCALDOMAIN example.com
80 | #
81 | # * +RES_OPTIONS+: A space-separated list of resolver options to set.
82 | # Options that take values are specified as option:value.
83 | #
84 | # # Bourne Shell
85 | # $ RES_OPTIONS="retrans:3 retry:2 debug"
86 | # $ export RES_OPTIONS
87 | #
88 | # # C Shell
89 | # % setenv RES_OPTIONS "retrans:3 retry:2 debug"
90 | #
91 | class Resolver
92 |
93 | class Error < StandardError
94 | end
95 |
96 | class NoResponseError < Error
97 | end
98 |
99 | # An hash with the defaults values of almost all the
100 | # configuration parameters of a resolver object. See
101 | # the description for each parameter to have an
102 | # explanation of its usage.
103 | Defaults = {
104 | :config_file => "/etc/resolv.conf",
105 | :log_file => $stdout,
106 | :port => 53,
107 | :searchlist => [],
108 | :nameservers => [IPAddr.new("127.0.0.1")],
109 | :domain => "",
110 | :source_port => 0,
111 | :source_address => IPAddr.new("0.0.0.0"),
112 | :source_address_inet6 => IPAddr.new('::'),
113 | :interface => "eth0",
114 | :retry_interval => 5,
115 | :retry_number => 4,
116 | :recursive => true,
117 | :defname => true,
118 | :dns_search => true,
119 | :use_tcp => false,
120 | :ignore_truncated => false,
121 | :packet_size => 512,
122 | :tcp_timeout => TcpTimeout.new(5),
123 | :udp_timeout => UdpTimeout.new(5),
124 | }
125 |
126 |
127 | class << self
128 |
129 | C = Object.const_get(defined?(RbConfig) ? :RbConfig : :Config)::CONFIG
130 |
131 | # Quick resolver method. Bypass the configuration using
132 | # the defaults.
133 | #
134 | # Net::DNS::Resolver.start "www.google.com"
135 | #
136 | def start(*params)
137 | new.search(*params)
138 | end
139 |
140 | # Returns true if running on a Windows platform.
141 | #
142 | # Note. This method doesn't rely on the RUBY_PLATFORM constant
143 | # because the comparison will fail when running on JRuby.
144 | # On JRuby RUBY_PLATFORM == 'java'.
145 | def platform_windows?
146 | !!(C["host_os"] =~ /msdos|mswin|djgpp|mingw/i)
147 | end
148 |
149 | end
150 |
151 |
152 | # Creates a new resolver object.
153 | #
154 | # Argument +config+ can either be empty or be an hash with
155 | # some configuration parameters. To know what each parameter
156 | # do, look at the description of each.
157 | # Some example:
158 | #
159 | # # Use the sistem defaults
160 | # res = Net::DNS::Resolver.new
161 | #
162 | # # Specify a configuration file
163 | # res = Net::DNS::Resolver.new(:config_file => '/my/dns.conf')
164 | #
165 | # # Set some option
166 | # res = Net::DNS::Resolver.new(:nameservers => "172.16.1.1",
167 | # :recursive => false,
168 | # :retry => 10)
169 | #
170 | # == Config file
171 | #
172 | # Net::DNS::Resolver uses a config file to read the usual
173 | # values a resolver needs, such as nameserver list and
174 | # domain names. On UNIX systems the defaults are read from the
175 | # following files, in the order indicated:
176 | #
177 | # * /etc/resolv.conf
178 | # * $HOME/.resolv.conf
179 | # * ./.resolv.conf
180 | #
181 | # The following keywords are recognized in resolver configuration files:
182 | #
183 | # * domain: the default domain.
184 | # * search: a space-separated list of domains to put in the search list.
185 | # * nameserver: a space-separated list of nameservers to query.
186 | #
187 | # Files except for /etc/resolv.conf must be owned by the effective userid
188 | # running the program or they won't be read. In addition, several environment
189 | # variables can also contain configuration information; see Environment
190 | # in the main description for Resolver class.
191 | #
192 | # On Windows Systems, an attempt is made to determine the system defaults
193 | # using the registry. This is still a work in progress; systems with many
194 | # dynamically configured network interfaces may confuse Net::DNS.
195 | #
196 | # You can include a configuration file of your own when creating a resolver
197 | # object:
198 | #
199 | # # Use my own configuration file
200 | # my $res = Net::DNS::Resolver->new(config_file => '/my/dns.conf');
201 | #
202 | # This is supported on both UNIX and Windows. Values pulled from a custom
203 | # configuration file override the the system's defaults, but can still be
204 | # overridden by the other arguments to Resolver::new.
205 | #
206 | # Explicit arguments to Resolver::new override both the system's defaults
207 | # and the values of the custom configuration file, if any.
208 | #
209 | # == Parameters
210 | #
211 | # The following arguments to Resolver::new are supported:
212 | #
213 | # * nameservers: an array reference of nameservers to query.
214 | # * searchlist: an array reference of domains.
215 | # * recurse
216 | # * debug
217 | # * domain
218 | # * port
219 | # * srcaddr
220 | # * srcport
221 | # * tcp_timeout
222 | # * udp_timeout
223 | # * retrans
224 | # * retry
225 | # * usevc
226 | # * stayopen
227 | # * igntc
228 | # * defnames
229 | # * dnsrch
230 | # * persistent_tcp
231 | # * persistent_udp
232 | # * dnssec
233 | #
234 | # For more information on any of these options, please consult the
235 | # method of the same name.
236 | #
237 | # == Disclaimer
238 | #
239 | # Part of the above documentation is taken from the one in the
240 | # Net::DNS::Resolver Perl module.
241 | #
242 | def initialize(config = {})
243 | raise ArgumentError, "Expected `config' to be a Hash" unless config.is_a?(Hash)
244 |
245 | # config.downcase_keys!
246 | @config = Defaults.merge config
247 | @raw = false
248 |
249 | # New logger facility
250 | @logger = Logger.new(@config[:log_file])
251 | @logger.level = $DEBUG ? Logger::DEBUG : Logger::WARN
252 |
253 | #------------------------------------------------------------
254 | # Resolver configuration will be set in order from:
255 | # 1) initialize arguments
256 | # 2) ENV variables
257 | # 3) config file
258 | # 4) defaults (and /etc/resolv.conf for config)
259 | #------------------------------------------------------------
260 |
261 |
262 |
263 | #------------------------------------------------------------
264 | # Parsing config file
265 | #------------------------------------------------------------
266 | parse_config_file
267 |
268 | #------------------------------------------------------------
269 | # Parsing ENV variables
270 | #------------------------------------------------------------
271 | parse_environment_variables
272 |
273 | #------------------------------------------------------------
274 | # Parsing arguments
275 | #------------------------------------------------------------
276 | config.each do |key,val|
277 | next if key == :log_file or key == :config_file
278 | begin
279 | eval "self.#{key.to_s} = val"
280 | rescue NoMethodError
281 | raise ArgumentError, "Option #{key} not valid"
282 | end
283 | end
284 | end
285 |
286 | # Get the resolver search list, returned as an array of entries.
287 | #
288 | # res.searchlist
289 | # #=> ["example.com","a.example.com","b.example.com"]
290 | #
291 | def searchlist
292 | @config[:searchlist].inspect
293 | end
294 |
295 | # Set the resolver searchlist.
296 | # +arg+ can be a single string or an array of strings.
297 | #
298 | # res.searchstring = "example.com"
299 | # res.searchstring = ["example.com","a.example.com","b.example.com"]
300 | #
301 | # Note that you can also append a new name to the searchlist.
302 | #
303 | # res.searchlist << "c.example.com"
304 | # res.searchlist
305 | # #=> ["example.com","a.example.com","b.example.com","c.example.com"]
306 | #
307 | # The default is an empty array.
308 | #
309 | def searchlist=(arg)
310 | case arg
311 | when String
312 | @config[:searchlist] = [arg] if valid? arg
313 | @logger.info "Searchlist changed to value #{@config[:searchlist].inspect}"
314 | when Array
315 | @config[:searchlist] = arg if arg.all? {|x| valid? x}
316 | @logger.info "Searchlist changed to value #{@config[:searchlist].inspect}"
317 | else
318 | raise ArgumentError, "Wrong argument format, neither String nor Array"
319 | end
320 | end
321 |
322 | # Get the list of resolver nameservers, in a dotted decimal format-
323 | #
324 | # res.nameservers
325 | # #=> ["192.168.0.1","192.168.0.2"]
326 | #
327 | def nameservers
328 | @config[:nameservers].map(&:to_s)
329 | end
330 |
331 | alias_method :nameserver, :nameservers
332 |
333 | # Set the list of resolver nameservers.
334 | # +arg+ can be a single ip address or an array of addresses.
335 | #
336 | # res.nameservers = "192.168.0.1"
337 | # res.nameservers = ["192.168.0.1","192.168.0.2"]
338 | #
339 | # If you want you can specify the addresses as IPAddr instances.
340 | #
341 | # ip = IPAddr.new("192.168.0.3")
342 | # res.nameservers << ip
343 | # #=> ["192.168.0.1","192.168.0.2","192.168.0.3"]
344 | #
345 | # The default is 127.0.0.1 (localhost)
346 | #
347 | def nameservers=(arg)
348 | @config[:nameservers] = convert_nameservers_arg_to_ips(arg)
349 | @logger.info "Nameservers list changed to value #{@config[:nameservers].inspect}"
350 | end
351 | alias_method("nameserver=","nameservers=")
352 |
353 | # Return a string with the default domain.
354 | def domain
355 | @config[:domain].inspect
356 | end
357 |
358 | # Set the domain for the query.
359 | def domain=(name)
360 | @config[:domain] = name if valid? name
361 | end
362 |
363 | # Return the defined size of the packet.
364 | def packet_size
365 | @config[:packet_size]
366 | end
367 |
368 | def packet_size=(arg)
369 | if arg.respond_to? :to_i
370 | @config[:packet_size] = arg.to_i
371 | @logger.info "Packet size changed to value #{@config[:packet_size].inspect}"
372 | else
373 | @logger.error "Packet size not set, #{arg.class} does not respond to to_i"
374 | end
375 | end
376 |
377 | # Get the port number to which the resolver sends queries.
378 | #
379 | # puts "Sending queries to port #{res.port}"
380 | #
381 | def port
382 | @config[:port]
383 | end
384 |
385 | # Set the port number to which the resolver sends queries. This can be useful
386 | # for testing a nameserver running on a non-standard port.
387 | #
388 | # res.port = 10053
389 | #
390 | # The default is port 53.
391 | #
392 | def port=(num)
393 | if (0..65535).include? num
394 | @config[:port] = num
395 | @logger.info "Port number changed to #{num}"
396 | else
397 | raise ArgumentError, "Wrong port number #{num}"
398 | end
399 | end
400 |
401 | # Get the value of the source port number.
402 | #
403 | # puts "Sending queries using port #{res.source_port}"
404 | #
405 | def source_port
406 | @config[:source_port]
407 | end
408 | alias srcport source_port
409 |
410 | # Set the local source port from which the resolver sends its queries.
411 | #
412 | # res.source_port = 40000
413 | #
414 | # Note that if you want to set a port you need root priviledges, as
415 | # raw sockets will be used to generate packets. The class will then
416 | # generate the exception ResolverPermissionError if you're not root.
417 | #
418 | # The default is 0, which means that the port will be chosen by the
419 | # underlaying layers.
420 | #
421 | def source_port=(num)
422 | unless root?
423 | raise ResolverPermissionError, "Are you root?"
424 | end
425 | if (0..65535).include?(num)
426 | @config[:source_port] = num
427 | else
428 | raise ArgumentError, "Wrong port number #{num}"
429 | end
430 | end
431 | alias srcport= source_port=
432 |
433 | # Get the local address from which the resolver sends queries
434 | #
435 | # puts "Sending queries using source address #{res.source_address}"
436 | #
437 | def source_address
438 | @config[:source_address].to_s
439 | end
440 | alias srcaddr source_address
441 |
442 | # Get the local ipv6 address from which the resolver sends queries
443 | #
444 | def source_address_inet6
445 | @config[:source_address_inet6].to_s
446 | end
447 |
448 | def interface
449 | @config[:interface]
450 | end
451 |
452 | # Set the local source address from which the resolver sends its queries.
453 | #
454 | # res.source_address = "172.16.100.1"
455 | # res.source_address = IPAddr.new("172.16.100.1")
456 | #
457 | # You can specify +arg+ as either a string containing the ip address
458 | # or an instance of IPAddr class.
459 | #
460 | # Normally this can be used to force queries out a specific interface
461 | # on a multi-homed host. In this case, you should of course need to
462 | # know the addresses of the interfaces.
463 | #
464 | # Another way to use this option is for some kind of spoofing attacks
465 | # towards weak nameservers, to probe the security of your network.
466 | # This includes specifing ranged attacks such as DoS and others. For
467 | # a paper on DNS security, checks htpt://www.marcoceresa.com/security/
468 | #
469 | # Note that if you want to set a non-binded source address you need
470 | # root priviledges, as raw sockets will be used to generate packets.
471 | # The class will then generate an exception if you're not root.
472 | #
473 | # The default is 0.0.0.0, meaning any local address (chosen on routing needs).
474 | #
475 | def source_address=(addr)
476 | unless addr.respond_to? :to_s
477 | raise ArgumentError, "Wrong address argument #{addr}"
478 | end
479 |
480 | begin
481 | port = rand(64000)+1024
482 | @logger.warn "Try to determine state of source address #{addr} with port #{port}"
483 | a = TCPServer.new(addr.to_s,port)
484 | rescue SystemCallError => e
485 | case e.errno
486 | when 98 # Port already in use!
487 | @logger.warn "Port already in use"
488 | retry
489 | when 99 # Address is not valid: raw socket
490 | if Process.uid == 0
491 | @raw = true
492 | @logger.warn "Using raw sockets"
493 | else
494 | raise RuntimeError, "Raw sockets requested but not running as root."
495 | end
496 | else
497 | raise SystemCallError, e
498 | end
499 | else
500 | a.close
501 | end
502 |
503 | case addr
504 | when String
505 | @config[:source_address] = IPAddr.new(addr)
506 | @logger.info "Using new source address: #{@config[:source_address]}"
507 | when IPAddr
508 | @config[:source_address] = addr
509 | @logger.info "Using new source address: #{@config[:source_address]}"
510 | else
511 | raise ArgumentError, "Unknown dest_address format"
512 | end
513 | end
514 | alias srcaddr= source_address=
515 |
516 | def interface=(iface)
517 | @config[:interface] = iface
518 | end
519 |
520 | # Return the retrasmission interval (in seconds) the resolvers has
521 | # been set on.
522 | def retry_interval
523 | @config[:retry_interval]
524 | end
525 | alias retrans retry_interval
526 |
527 | # Set the retrasmission interval in seconds. Default 5 seconds.
528 | def retry_interval=(num)
529 | if num > 0
530 | @config[:retry_interval] = num
531 | @logger.info "Retransmission interval changed to #{num} seconds"
532 | else
533 | raise ArgumentError, "Interval must be positive"
534 | end
535 | end
536 | alias retrans= retry_interval=
537 |
538 | # The number of times the resolver will try a query.
539 | #
540 | # puts "Will try a max of #{res.retry_number} queries"
541 | #
542 | def retry_number
543 | @config[:retry_number]
544 | end
545 |
546 | # Set the number of times the resolver will try a query.
547 | # Default 4 times.
548 | def retry_number=(num)
549 | if num.kind_of? Integer and num > 0
550 | @config[:retry_number] = num
551 | @logger.info "Retrasmissions number changed to #{num}"
552 | else
553 | raise ArgumentError, "Retry value must be a positive integer"
554 | end
555 | end
556 | alias_method('retry=', 'retry_number=')
557 |
558 | # This method will return true if the resolver is configured to
559 | # perform recursive queries.
560 | #
561 | # print "The resolver will perform a "
562 | # print res.recursive? ? "" : "not "
563 | # puts "recursive query"
564 | #
565 | def recursive?
566 | @config[:recursive]
567 | end
568 | alias_method :recurse, :recursive?
569 | alias_method :recursive, :recursive?
570 |
571 | # Sets whether or not the resolver should perform recursive
572 | # queries. Default is true.
573 | #
574 | # res.recursive = false # perform non-recursive query
575 | #
576 | def recursive=(bool)
577 | case bool
578 | when TrueClass,FalseClass
579 | @config[:recursive] = bool
580 | @logger.info("Recursive state changed to #{bool}")
581 | else
582 | raise ArgumentError, "Argument must be boolean"
583 | end
584 | end
585 | alias_method :recurse=, :recursive=
586 |
587 | # Return a string representing the resolver state, suitable
588 | # for printing on the screen.
589 | #
590 | # puts "Resolver state:"
591 | # puts res.state
592 | #
593 | def state
594 | str = ";; RESOLVER state:\n;; "
595 | i = 1
596 | @config.each do |key,val|
597 | if key == :log_file or key == :config_file
598 | str << "#{key}: #{val} \t"
599 | else
600 | str << "#{key}: #{eval(key.to_s)} \t"
601 | end
602 | str << "\n;; " if i % 2 == 0
603 | i += 1
604 | end
605 | str
606 | end
607 | alias print state
608 | alias inspect state
609 |
610 | # Checks whether the +defname+ flag has been activate.
611 | def defname?
612 | @config[:defname]
613 | end
614 | alias defname defname?
615 |
616 | # Set the flag +defname+ in a boolean state. if +defname+ is true,
617 | # calls to Resolver#query will append the default domain to names
618 | # that contain no dots.
619 | # Example:
620 | #
621 | # # Domain example.com
622 | # res.defname = true
623 | # res.query("machine1")
624 | # #=> This will perform a query for machine1.example.com
625 | #
626 | # Default is true.
627 | #
628 | def defname=(bool)
629 | case bool
630 | when TrueClass,FalseClass
631 | @config[:defname] = bool
632 | @logger.info("Defname state changed to #{bool}")
633 | else
634 | raise ArgumentError, "Argument must be boolean"
635 | end
636 | end
637 |
638 | # Get the state of the dns_search flag.
639 | def dns_search
640 | @config[:dns_search]
641 | end
642 | alias_method :dnsrch, :dns_search
643 |
644 | # Set the flag +dns_search+ in a boolean state. If +dns_search+
645 | # is true, when using the Resolver#search method will be applied
646 | # the search list. Default is true.
647 | def dns_search=(bool)
648 | case bool
649 | when TrueClass,FalseClass
650 | @config[:dns_search] = bool
651 | @logger.info("DNS search state changed to #{bool}")
652 | else
653 | raise ArgumentError, "Argument must be boolean"
654 | end
655 | end
656 | alias_method("dnsrch=","dns_search=")
657 |
658 | # Get the state of the use_tcp flag.
659 | #
660 | def use_tcp?
661 | @config[:use_tcp]
662 | end
663 | alias_method :usevc, :use_tcp?
664 | alias_method :use_tcp, :use_tcp?
665 |
666 | # If +use_tcp+ is true, the resolver will perform all queries
667 | # using TCP virtual circuits instead of UDP datagrams, which
668 | # is the default for the DNS protocol.
669 | #
670 | # res.use_tcp = true
671 | # res.query "host.example.com"
672 | # #=> Sending TCP segments...
673 | #
674 | # Default is false.
675 | #
676 | def use_tcp=(bool)
677 | case bool
678 | when TrueClass,FalseClass
679 | @config[:use_tcp] = bool
680 | @logger.info("Use tcp flag changed to #{bool}")
681 | else
682 | raise ArgumentError, "Argument must be boolean"
683 | end
684 | end
685 | alias usevc= use_tcp=
686 |
687 | def ignore_truncated?
688 | @config[:ignore_truncated]
689 | end
690 | alias_method :ignore_truncated, :ignore_truncated?
691 |
692 | def ignore_truncated=(bool)
693 | case bool
694 | when TrueClass,FalseClass
695 | @config[:ignore_truncated] = bool
696 | @logger.info("Ignore truncated flag changed to #{bool}")
697 | else
698 | raise ArgumentError, "Argument must be boolean"
699 | end
700 | end
701 |
702 | # Return an object representing the value of the stored TCP
703 | # timeout the resolver will use in is queries. This object
704 | # is an instance of the class +TcpTimeout+, and two methods
705 | # are available for printing informations: TcpTimeout#to_s
706 | # and TcpTimeout#pretty_to_s.
707 | #
708 | # Here's some example:
709 | #
710 | # puts "Timeout of #{res.tcp_timeout} seconds" # implicit to_s
711 | # #=> Timeout of 150 seconds
712 | #
713 | # puts "You set a timeout of " + res.tcp_timeout.pretty_to_s
714 | # #=> You set a timeout of 2 minutes and 30 seconds
715 | #
716 | # If the timeout is infinite, a string "infinite" will be returned.
717 | #
718 | def tcp_timeout
719 | @config[:tcp_timeout].to_s
720 | end
721 |
722 | # Set the value of TCP timeout for resolver queries that
723 | # will be performed using TCP. A value of 0 means that
724 | # the timeout will be infinite.
725 | # The value is stored internally as a +TcpTimeout+ object, see
726 | # the description for Resolver#tcp_timeout
727 | #
728 | # Default is 5 seconds.
729 | #
730 | def tcp_timeout=(secs)
731 | @config[:tcp_timeout] = TcpTimeout.new(secs)
732 | @logger.info("New TCP timeout value: #{@config[:tcp_timeout]} seconds")
733 | end
734 |
735 | # Return an object representing the value of the stored UDP
736 | # timeout the resolver will use in is queries. This object
737 | # is an instance of the class +UdpTimeout+, and two methods
738 | # are available for printing information: UdpTimeout#to_s
739 | # and UdpTimeout#pretty_to_s.
740 | #
741 | # Here's some example:
742 | #
743 | # puts "Timeout of #{res.udp_timeout} seconds" # implicit to_s
744 | # #=> Timeout of 150 seconds
745 | #
746 | # puts "You set a timeout of " + res.udp_timeout.pretty_to_s
747 | # #=> You set a timeout of 2 minutes and 30 seconds
748 | #
749 | # If the timeout is zero, a string "not defined" will
750 | # be returned.
751 | #
752 | def udp_timeout
753 | @config[:udp_timeout].to_s
754 | end
755 |
756 | # Set the value of UDP timeout for resolver queries that
757 | # will be performed using UDP. A value of 0 means that
758 | # the timeout will not be used, and the resolver will use
759 | # only +retry_number+ and +retry_interval+ parameters.
760 | #
761 | # Default is 5 seconds.
762 | #
763 | # The value is stored internally as a +UdpTimeout+ object, see
764 | # the description for Resolver#udp_timeout.
765 | #
766 | def udp_timeout=(secs)
767 | @config[:udp_timeout] = UdpTimeout.new(secs)
768 | @logger.info("New UDP timeout value: #{@config[:udp_timeout]} seconds")
769 | end
770 |
771 | # Set a new log file for the logger facility of the resolver
772 | # class. Could be a file descriptor too:
773 | #
774 | # res.log_file = $stderr
775 | #
776 | # Note that a new logging facility will be create, destroing
777 | # the old one, which will then be impossibile to recover.
778 | #
779 | def log_file=(log)
780 | @logger.close
781 | @config[:log_file] = log
782 | @logger = Logger.new(@config[:log_file])
783 | @logger.level = $DEBUG ? Logger::DEBUG : Logger::WARN
784 | end
785 |
786 | # This one permits to have a personal logger facility to handle
787 | # resolver messages, instead of new built-in one, which is set up
788 | # for a +$stdout+ (or +$stderr+) use.
789 | #
790 | # If you want your own logging facility you can create a new instance
791 | # of the +Logger+ class:
792 | #
793 | # log = Logger.new("/tmp/resolver.log","weekly",2*1024*1024)
794 | # log.level = Logger::DEBUG
795 | # log.progname = "ruby_resolver"
796 | #
797 | # and then pass it to the resolver:
798 | #
799 | # res.logger = log
800 | #
801 | # Note that this will destroy the precedent logger.
802 | #
803 | def logger=(logger)
804 | if logger.kind_of? Logger
805 | @logger.close
806 | @logger = logger
807 | else
808 | raise ArgumentError, "Argument must be an instance of Logger class"
809 | end
810 | end
811 |
812 | # Set the log level for the built-in logging facility.
813 | #
814 | # The log level can be one of the following:
815 | #
816 | # - +Net::DNS::DEBUG+
817 | # - +Net::DNS::INFO+
818 | # - +Net::DNS::WARN+
819 | # - +Net::DNS::ERROR+
820 | # - +Net::DNS::FATAL+
821 | #
822 | # Note that if the global variable $DEBUG is set (like when the
823 | # -d switch is used at the command line) the logger level is
824 | # automatically set at DEGUB.
825 | #
826 | # For further informations, see Logger documentation in the
827 | # Ruby standard library.
828 | #
829 | def log_level=(level)
830 | @logger.level = level
831 | end
832 |
833 | # Performs a DNS query for the given name, applying the searchlist if
834 | # appropriate. The search algorithm is as follows:
835 | #
836 | # 1. If the name contains at least one dot, try it as is.
837 | # 2. If the name doesn't end in a dot then append each item in the search
838 | # list to the name. This is only done if +dns_search+ is true.
839 | # 3. If the name doesn't contain any dots, try it as is.
840 | #
841 | # The record type and class can be omitted; they default to +A+ and +IN+.
842 | #
843 | # packet = res.search('mailhost')
844 | # packet = res.search('mailhost.example.com')
845 | # packet = res.search('example.com', Net::DNS::MX)
846 | # packet = res.search('user.passwd.example.com', Net::DNS::TXT, Net::DNS::HS)
847 | #
848 | # If the name is an IP address (Ipv4 or IPv6), in the form of a string
849 | # or a +IPAddr+ object, then an appropriate PTR query will be performed:
850 | #
851 | # ip = IPAddr.new("172.16.100.2")
852 | # packet = res.search(ip)
853 | # packet = res.search("192.168.10.254")
854 | #
855 | # Returns a Net::DNS::Packet object. If you need to examine the response packet
856 | # whether it contains any answers or not, use the Resolver#query method instead.
857 | #
858 | def search(name,type=Net::DNS::A,cls=Net::DNS::IN)
859 |
860 | return query(name,type,cls) if name.class == IPAddr
861 |
862 | # If the name contains at least one dot then try it as is first.
863 | if name.include? "."
864 | @logger.debug "Search(#{name},#{Net::DNS::RR::Types.new(type)},#{Net::DNS::RR::Classes.new(cls)})"
865 | ans = query(name,type,cls)
866 | return ans if ans.header.anCount > 0
867 | end
868 |
869 | # If the name doesn't end in a dot then apply the search list.
870 | if name !~ /\.$/ and @config[:dns_search]
871 | @config[:searchlist].each do |domain|
872 | newname = name + "." + domain
873 | @logger.debug "Search(#{newname},#{Net::DNS::RR::Types.new(type)},#{Net::DNS::RR::Classes.new(cls)})"
874 | ans = query(newname,type,cls)
875 | return ans if ans.header.anCount > 0
876 | end
877 | end
878 |
879 | # Finally, if the name has no dots then try it as is.
880 | @logger.debug "Search(#{name},#{Net::DNS::RR::Types.new(type)},#{Net::DNS::RR::Classes.new(cls)})"
881 | query(name+".",type,cls)
882 |
883 | end
884 |
885 | # Performs a DNS query for the given name; the search list
886 | # is not applied. If the name doesn't contain any dots and
887 | # +defname+ is true then the default domain will be appended.
888 | #
889 | # The record type and class can be omitted; they default to +A+
890 | # and +IN+. If the name looks like an IP address (IPv4 or IPv6),
891 | # then an appropriate PTR query will be performed.
892 | #
893 | # packet = res.query('mailhost')
894 | # packet = res.query('mailhost.example.com')
895 | # packet = res.query('example.com', Net::DNS::MX)
896 | # packet = res.query('user.passwd.example.com', Net::DNS::TXT, Net::DNS::HS)
897 | #
898 | # If the name is an IP address (Ipv4 or IPv6), in the form of a string
899 | # or a +IPAddr+ object, then an appropriate PTR query will be performed:
900 | #
901 | # ip = IPAddr.new("172.16.100.2")
902 | # packet = res.query(ip)
903 | # packet = res.query("192.168.10.254")
904 | #
905 | # Returns a Net::DNS::Packet object. If you need to examine the response
906 | # packet whether it contains any answers or not, use the Resolver#query
907 | # method instead.
908 | #
909 | def query(name,type=Net::DNS::A,cls=Net::DNS::IN)
910 |
911 | return send(name,type,cls) if name.class == IPAddr
912 |
913 | # If the name doesn't contain any dots then append the default domain.
914 | if name !~ /\./ and name !~ /:/ and @config[:defname]
915 | name += "." + @config[:domain]
916 | end
917 |
918 | @logger.debug "Query(#{name},#{Net::DNS::RR::Types.new(type)},#{Net::DNS::RR::Classes.new(cls)})"
919 |
920 | send(name,type,cls)
921 |
922 | end
923 |
924 | # Performs a DNS query for the given name. Neither the
925 | # searchlist nor the default domain will be appended.
926 | #
927 | # The argument list can be either a Net::DNS::Packet object
928 | # or a name string plus optional type and class, which if
929 | # omitted default to +A+ and +IN+.
930 | #
931 | # Returns a Net::DNS::Packet object.
932 | #
933 | # # Executes the query with a +Packet+ object
934 | # send_packet = Net::DNS::Packet.new("host.example.com", Net::DNS::NS, Net::DNS::HS)
935 | # packet = res.query(send_packet)
936 | #
937 | # # Executes the query with a host, type and cls
938 | # packet = res.query("host.example.com")
939 | # packet = res.query("host.example.com", Net::DNS::NS)
940 | # packet = res.query("host.example.com", Net::DNS::NS, Net::DNS::HS)
941 | #
942 | # If the name is an IP address (Ipv4 or IPv6), in the form of a string
943 | # or a IPAddr object, then an appropriate PTR query will be performed:
944 | #
945 | # ip = IPAddr.new("172.16.100.2")
946 | # packet = res.query(ip)
947 | #
948 | # packet = res.query("172.16.100.2")
949 | #
950 | # Use +packet.header.ancount+ or +packet.answer+ to find out if there
951 | # were any records in the answer section.
952 | #
953 | def query(argument, type = Net::DNS::A, cls = Net::DNS::IN)
954 | if @config[:nameservers].size == 0
955 | raise Resolver::Error, "No nameservers specified!"
956 | end
957 |
958 | method = :query_udp
959 | packet = if argument.kind_of? Net::DNS::Packet
960 | argument
961 | else
962 | make_query_packet(argument, type, cls)
963 | end
964 |
965 | # Store packet_data for performance improvements,
966 | # so methods don't keep on calling Packet#data
967 | packet_data = packet.data
968 | packet_size = packet_data.size
969 |
970 | # Choose whether use TCP, UDP or RAW
971 | if packet_size > @config[:packet_size] # Must use TCP, either plain or raw
972 | if @raw # Use raw sockets?
973 | @logger.info "Sending #{packet_size} bytes using TCP over RAW socket"
974 | method = :send_raw_tcp
975 | else
976 | @logger.info "Sending #{packet_size} bytes using TCP"
977 | method = :query_tcp
978 | end
979 | else # Packet size is inside the boundaries
980 | if @raw # Use raw sockets?
981 | @logger.info "Sending #{packet_size} bytes using UDP over RAW socket"
982 | method = :send_raw_udp
983 | elsif use_tcp? # User requested TCP
984 | @logger.info "Sending #{packet_size} bytes using TCP"
985 | method = :query_tcp
986 | else # Finally use UDP
987 | @logger.info "Sending #{packet_size} bytes using UDP"
988 | method = :query_udp
989 | end
990 | end
991 |
992 | if type == Net::DNS::AXFR
993 | if @raw
994 | @logger.warn "AXFR query, switching to TCP over RAW socket"
995 | method = :send_raw_tcp
996 | else
997 | @logger.warn "AXFR query, switching to TCP"
998 | method = :query_tcp
999 | end
1000 | end
1001 |
1002 | ans = self.send(method, packet, packet_data)
1003 |
1004 | # Don't have any responses with the raw,
1005 | # since currently raw is only used when source_address is changed
1006 | if @raw
1007 | return nil
1008 | end
1009 |
1010 | if not ans
1011 | message = "No response from nameservers list"
1012 | @logger.fatal(message)
1013 | raise NoResponseError, message
1014 | end
1015 |
1016 | @logger.info "Received #{ans[0].size} bytes from #{ans[1][2]+":"+ans[1][1].to_s}"
1017 | response = Net::DNS::Packet.parse(ans[0],ans[1])
1018 |
1019 | if response.header.truncated? and not ignore_truncated?
1020 | @logger.warn "Packet truncated, retrying using TCP"
1021 | self.use_tcp = true
1022 | begin
1023 | return query(argument,type,cls)
1024 | ensure
1025 | self.use_tcp = false
1026 | end
1027 | end
1028 |
1029 | return response
1030 | end
1031 |
1032 | # Performs a zone transfer for the zone passed as a parameter.
1033 | #
1034 | # It is actually only a wrapper to a send with type set as Net::DNS::AXFR,
1035 | # since it is using the same infrastucture.
1036 | #
1037 | def axfr(name, cls = Net::DNS::IN)
1038 | @logger.info "Requested AXFR transfer, zone #{name} class #{cls}"
1039 | query(name, Net::DNS::AXFR, cls)
1040 | end
1041 |
1042 | # Performs an MX query for the domain name passed as parameter.
1043 | #
1044 | # It actually uses the same methods a normal Resolver query would
1045 | # use, but automatically sort the results based on preferences
1046 | # and returns an ordered array.
1047 | #
1048 | # res = Net::DNS::Resolver.new
1049 | # res.mx("google.com")
1050 | #
1051 | def mx(name, cls = Net::DNS::IN)
1052 | arr = []
1053 | query(name, Net::DNS::MX, cls).answer.each do |entry|
1054 | arr << entry if entry.type == 'MX'
1055 | end
1056 | arr.sort_by { |a| a.preference }
1057 | end
1058 |
1059 | private
1060 |
1061 | # Parses a configuration file specified as the argument.
1062 | def parse_config_file
1063 | if self.class.platform_windows?
1064 | require 'win32/resolv'
1065 | arr = Win32::Resolv.get_resolv_info
1066 | self.domain = arr[0].first.to_s
1067 | self.nameservers = arr[1]
1068 | else
1069 | nameservers = []
1070 | IO.foreach(@config[:config_file]) do |line|
1071 | line.gsub!(/\s*[;#].*/,"")
1072 | next unless line =~ /\S/
1073 | case line
1074 | when /^\s*domain\s+(\S+)/
1075 | self.domain = $1
1076 | when /^\s*search\s+(.*)/
1077 | self.searchlist = $1.split(" ")
1078 | when /^\s*nameserver\s+(.*)/
1079 | nameservers << $1.split(" ")
1080 | end
1081 | end
1082 | self.nameservers = nameservers.flatten
1083 | end
1084 | end
1085 |
1086 | # Parses environment variables.
1087 | def parse_environment_variables
1088 | if ENV['RES_NAMESERVERS']
1089 | self.nameservers = ENV['RES_NAMESERVERS'].split(" ")
1090 | end
1091 | if ENV['RES_SEARCHLIST']
1092 | self.searchlist = ENV['RES_SEARCHLIST'].split(" ")
1093 | end
1094 | if ENV['LOCALDOMAIN']
1095 | self.domain = ENV['LOCALDOMAIN']
1096 | end
1097 | if ENV['RES_OPTIONS']
1098 | ENV['RES_OPTIONS'].split(" ").each do |opt|
1099 | name,val = opt.split(":")
1100 | begin
1101 | eval("self.#{name} = #{val}")
1102 | rescue NoMethodError
1103 | raise ArgumentError, "Invalid ENV option #{name}"
1104 | end
1105 | end
1106 | end
1107 | end
1108 |
1109 | def convert_nameservers_arg_to_ips(arg)
1110 | if arg.kind_of? IPAddr
1111 | [arg]
1112 | elsif arg.respond_to? :map
1113 | arg.map{|x| convert_nameservers_arg_to_ips(x) }.flatten
1114 | elsif arg.respond_to? :to_a
1115 | arg.to_a.map{|x| convert_nameservers_arg_to_ips(x) }.flatten
1116 | elsif arg.respond_to? :to_s
1117 | begin
1118 | [IPAddr.new(arg.to_s)]
1119 | rescue ArgumentError # arg is in the name form, not IP
1120 | nameservers_from_name(arg)
1121 | end
1122 | else
1123 | raise ArgumentError, "Wrong nameservers argument format, cannot convert to array of IPAddrs"
1124 | end
1125 | end
1126 |
1127 | def nameservers_from_name(arg)
1128 | arr = []
1129 | arg.split(" ").each do |name|
1130 | Resolver.new.search(name).each_address do |ip|
1131 | arr << ip
1132 | end
1133 | end
1134 | arr
1135 | end
1136 |
1137 | def make_query_packet(string, type, cls)
1138 | begin
1139 | name = IPAddr.new(string.chomp(".")).reverse
1140 | type = Net::DNS::PTR
1141 | rescue ArgumentError
1142 | name = string if valid? string
1143 | end
1144 |
1145 | if name.nil?
1146 | raise ArgumentError, "Bad query string"
1147 | end
1148 |
1149 | # Create the packet
1150 | packet = Net::DNS::Packet.new(name, type, cls)
1151 |
1152 | if packet.query?
1153 | packet.header.recursive = @config[:recursive] ? 1 : 0
1154 | end
1155 |
1156 | # DNSSEC and TSIG stuff to be inserted here
1157 |
1158 | packet
1159 | end
1160 |
1161 | def query_tcp(packet, packet_data)
1162 |
1163 | ans = nil
1164 | length = [packet_data.size].pack("n")
1165 |
1166 | @config[:nameservers].each do |ns|
1167 | begin
1168 | buffer = ""
1169 | socket = Socket.new(Socket::AF_INET,Socket::SOCK_STREAM,0)
1170 | socket.bind(Socket.pack_sockaddr_in(@config[:source_port],@config[:source_address].to_s))
1171 |
1172 | sockaddr = Socket.pack_sockaddr_in(@config[:port],ns.to_s)
1173 |
1174 | @config[:tcp_timeout].timeout do
1175 | socket.connect(sockaddr)
1176 | @logger.info "Contacting nameserver #{ns} port #{@config[:port]}"
1177 | socket.write(length+packet_data)
1178 | ans = socket.recv(Net::DNS::INT16SZ)
1179 | len = ans.unpack("n")[0]
1180 |
1181 | @logger.info "Receiving #{len} bytes..."
1182 |
1183 | if len == 0
1184 | @logger.warn "Receiving 0 lenght packet from nameserver #{ns}, trying next."
1185 | next
1186 | end
1187 |
1188 | while (buffer.size < len)
1189 | left = len - buffer.size
1190 | temp,from = socket.recvfrom(left)
1191 | buffer += temp
1192 | end
1193 |
1194 | unless buffer.size == len
1195 | @logger.warn "Malformed packet from nameserver #{ns}, trying next."
1196 | next
1197 | end
1198 | end
1199 | return [buffer,["",@config[:port],ns.to_s,ns.to_s]]
1200 | rescue TimeoutError
1201 | @logger.warn "Nameserver #{ns} not responding within TCP timeout, trying next one"
1202 | next
1203 | ensure
1204 | socket.close
1205 | end
1206 | end
1207 | ans
1208 | end
1209 |
1210 | def query_udp(packet, packet_data)
1211 | socket4 = UDPSocket.new
1212 | socket4.bind(@config[:source_address].to_s,@config[:source_port])
1213 | if @config[:nameservers].any? { |ns| ns.ipv6? }
1214 | socket6 = UDPSocket.new(Socket::AF_INET6)
1215 | socket6.bind(@config[:source_address_inet6].to_s,@config[:source_port])
1216 | end
1217 |
1218 | ans = nil
1219 | response = ""
1220 | @config[:nameservers].each do |ns|
1221 | begin
1222 | @config[:udp_timeout].timeout do
1223 | @logger.info "Contacting nameserver #{ns} port #{@config[:port]}"
1224 | ans = if ns.ipv6?
1225 | socket6.send(packet_data, 0, ns.to_s, @config[:port])
1226 | socket6.recvfrom(@config[:packet_size])
1227 | else
1228 | socket4.send(packet_data, 0, ns.to_s, @config[:port])
1229 | socket4.recvfrom(@config[:packet_size])
1230 | end
1231 | end
1232 | break if ans
1233 | rescue TimeoutError
1234 | @logger.warn "Nameserver #{ns} not responding within UDP timeout, trying next one"
1235 | next
1236 | end
1237 | end
1238 | ans
1239 | end
1240 |
1241 | def send_raw_tcp(packet, packet_data)
1242 | socket = nil
1243 | packet = PacketFu::TCPPacket.new({body: packet_data})
1244 |
1245 |
1246 | if @config[:source_address]
1247 | octet = PacketFu::Octets.new
1248 | octet.read_quad @config[:source_address].to_s
1249 | packet.ip_src = octet
1250 | packet.udp_src =rand(0xffff-1024) + 1024
1251 | packet.eth_saddr = PacketFu::Utils.arp(@config[:source_address].to_s, {iface: @config[:interface]})
1252 | elsif @config[:source_address_inet6]
1253 | octet = PacketFu::Octets.new
1254 | octet.read_quad @config[:source_address_inet6].to_s
1255 | packet.ip_src = octet
1256 | packet.udp_src = @config[:source_address_inet6].to_i
1257 | packet.eth_saddr = PacketFu::Utils.arp(@config[:source_address_inet6].to_s, {iface: @config[:interface]})
1258 | else
1259 | raise ArgumentError, "No source address specified, cannot send"
1260 | end
1261 |
1262 | @config[:nameservers].each do |ns|
1263 | octet = PacketFu::Octets.new
1264 | packet.eth_daddr = PacketFu::Utils.arp(ns.to_s, {iface: @config[:interface]})
1265 | octet.read_quad ns.to_s
1266 | packet.ip_dst = octet
1267 | packet.udp_dst = 53
1268 | packet.recalc arg=:all
1269 | packet.to_w @config[:interface]
1270 | end
1271 | nil
1272 | end
1273 |
1274 | def send_raw_udp(packet, packet_data)
1275 | socket = nil
1276 | packet = PacketFu::UDPPacket.new({body: packet_data})
1277 |
1278 |
1279 | if @config[:source_address]
1280 | octet = PacketFu::Octets.new
1281 | octet.read_quad @config[:source_address].to_s
1282 | packet.ip_src = octet
1283 | packet.udp_src =rand(0xffff-1024) + 1024
1284 | packet.eth_saddr = PacketFu::Utils.arp(@config[:source_address].to_s, {iface: @config[:interface]})
1285 | elsif @config[:source_address_inet6]
1286 | octet = PacketFu::Octets.new
1287 | octet.read_quad @config[:source_address_inet6].to_s
1288 | packet.ip_src = octet
1289 | packet.udp_src = @config[:source_address_inet6].to_i
1290 | packet.eth_saddr = PacketFu::Utils.arp(@config[:source_address_inet6].to_s, {iface: @config[:interface]})
1291 | else
1292 | raise ArgumentError, "No source address specified, cannot send"
1293 | end
1294 |
1295 | @config[:nameservers].each do |ns|
1296 | octet = PacketFu::Octets.new
1297 | packet.eth_daddr = PacketFu::Utils.arp(ns.to_s, {iface: @config[:interface]})
1298 | octet.read_quad ns.to_s
1299 | packet.ip_dst = octet
1300 | packet.udp_dst = 53
1301 | packet.recalc arg=:all
1302 | packet.to_w @config[:interface]
1303 | end
1304 | nil
1305 | end
1306 |
1307 | def valid?(name)
1308 | if name =~ /[^-\w\.]/
1309 | false
1310 | else
1311 | true
1312 | end
1313 | end
1314 | end
1315 | end
1316 | end
1317 |
--------------------------------------------------------------------------------