dnsmessage 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +3 -3
- data/.rubocop.yml +33 -0
- data/Gemfile +5 -0
- data/README.md +3 -3
- data/Rakefile +7 -1
- data/bin/console +1 -0
- data/dnsmessage.gemspec +14 -6
- data/examples/server.rb +11 -8
- data/lib/dnsmessage.rb +2 -0
- data/lib/dnsmessage/enums.rb +57 -53
- data/lib/dnsmessage/message.rb +76 -38
- data/lib/dnsmessage/name.rb +22 -18
- data/lib/dnsmessage/pointer.rb +16 -12
- data/lib/dnsmessage/question.rb +13 -12
- data/lib/dnsmessage/resource_record.rb +26 -29
- data/lib/dnsmessage/version.rb +3 -1
- metadata +8 -8
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8443bcf9f586254aba74f3e64dec4da233e3b379f3e713dd90082de832a7b010
|
|
4
|
+
data.tar.gz: 8369031d2199b20dd50317fc422f9f6124fc8e313465effab31d549762fda05a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 594e07ee15de5c2cfffd76a4134e99b600fbea140584712ce4e103e28d2b97431ee5db6d55d2a7b0e04e2ab138686e35e891cac38d796730dfcb5bbcb41875f2
|
|
7
|
+
data.tar.gz: ea04b8007c81825812fe20f78d1b62e33046636e3a71a777725d675c07bca68af1008d6fce77d07f06806788203a56dc85aae5f6f559c111354ee04df1e319d2
|
data/.github/workflows/ruby.yml
CHANGED
|
@@ -9,9 +9,9 @@ name: Ruby
|
|
|
9
9
|
|
|
10
10
|
on:
|
|
11
11
|
push:
|
|
12
|
-
branches: [
|
|
12
|
+
branches: [ main ]
|
|
13
13
|
pull_request:
|
|
14
|
-
branches: [
|
|
14
|
+
branches: [ main ]
|
|
15
15
|
|
|
16
16
|
jobs:
|
|
17
17
|
test:
|
|
@@ -19,7 +19,7 @@ jobs:
|
|
|
19
19
|
runs-on: ubuntu-latest
|
|
20
20
|
strategy:
|
|
21
21
|
matrix:
|
|
22
|
-
ruby-version: ['2.
|
|
22
|
+
ruby-version: ['2.7', '3.0']
|
|
23
23
|
|
|
24
24
|
steps:
|
|
25
25
|
- uses: actions/checkout@v2
|
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
require:
|
|
2
|
+
- rubocop-rake
|
|
3
|
+
- rubocop-rspec
|
|
4
|
+
|
|
5
|
+
# Commonly used screens these days easily fit more than 80 characters.
|
|
6
|
+
Layout/LineLength:
|
|
7
|
+
Max: 120
|
|
8
|
+
|
|
9
|
+
# Too short methods lead to extraction of single-use methods, which can make
|
|
10
|
+
# the code easier to read (by naming things), but can also clutter the class
|
|
11
|
+
Metrics/MethodLength:
|
|
12
|
+
Max: 20
|
|
13
|
+
|
|
14
|
+
# The guiding principle of classes is SRP, SRP can't be accurately measured by LoC
|
|
15
|
+
Metrics/ClassLength:
|
|
16
|
+
Max: 1500
|
|
17
|
+
|
|
18
|
+
Style/StringLiterals:
|
|
19
|
+
Enabled: true
|
|
20
|
+
EnforcedStyle: double_quotes
|
|
21
|
+
|
|
22
|
+
Style/StringLiteralsInInterpolation:
|
|
23
|
+
Enabled: true
|
|
24
|
+
EnforcedStyle: double_quotes
|
|
25
|
+
|
|
26
|
+
# Most readable form.
|
|
27
|
+
Layout/HashAlignment:
|
|
28
|
+
EnforcedHashRocketStyle: table
|
|
29
|
+
EnforcedColonStyle: table
|
|
30
|
+
|
|
31
|
+
# Rspec kind of breaks without this
|
|
32
|
+
Metrics/BlockLength:
|
|
33
|
+
IgnoredMethods: ['describe', 'context']
|
data/Gemfile
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
source "https://rubygems.org"
|
|
2
4
|
|
|
3
5
|
# Specify your gem's dependencies in dnsmessage.gemspec
|
|
@@ -5,3 +7,6 @@ gemspec
|
|
|
5
7
|
|
|
6
8
|
gem "rake", "~> 12.0"
|
|
7
9
|
gem "rspec", "~> 3.0"
|
|
10
|
+
gem "rubocop", "~> 1.0"
|
|
11
|
+
gem "rubocop-rake", require: false
|
|
12
|
+
gem "rubocop-rspec", require: false
|
data/README.md
CHANGED
|
@@ -72,9 +72,9 @@ Currently implemented types are:
|
|
|
72
72
|
* OPT
|
|
73
73
|
* TXT
|
|
74
74
|
|
|
75
|
-
Other records should be easy to add on a per-need basis as they
|
|
76
|
-
|
|
77
|
-
contributing for details on adding RR types.
|
|
75
|
+
Other records should be easy to add on a per-need basis as they can be
|
|
76
|
+
based on the building blocks of already existing parsers and builders.
|
|
77
|
+
Look at contributing for details on adding RR types.
|
|
78
78
|
|
|
79
79
|
## Development
|
|
80
80
|
|
data/Rakefile
CHANGED
data/bin/console
CHANGED
data/dnsmessage.gemspec
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "lib/dnsmessage/version"
|
|
2
4
|
|
|
3
5
|
Gem::Specification.new do |spec|
|
|
4
6
|
spec.name = "dnsmessage"
|
|
@@ -6,13 +8,13 @@ Gem::Specification.new do |spec|
|
|
|
6
8
|
spec.authors = ["Claus Lensbøl"]
|
|
7
9
|
spec.email = ["cmol@cmol.dk"]
|
|
8
10
|
|
|
9
|
-
spec.summary =
|
|
10
|
-
spec.description =
|
|
11
|
+
spec.summary = "Ruby library to build and parse DNS messages"
|
|
12
|
+
spec.description = 'A full featured DNS parser. The library supports DNS
|
|
11
13
|
name compression and gives access to parse, build, and manipulate all aspects
|
|
12
|
-
of the DNS queries and replies.
|
|
14
|
+
of the DNS queries and replies.'
|
|
13
15
|
spec.homepage = "https://github.com/cmol/dnsmessage"
|
|
14
16
|
spec.license = "MIT"
|
|
15
|
-
spec.required_ruby_version = Gem::Requirement.new(">= 2.
|
|
17
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 2.7.0")
|
|
16
18
|
|
|
17
19
|
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
|
18
20
|
|
|
@@ -22,10 +24,16 @@ of the DNS queries and replies.}
|
|
|
22
24
|
|
|
23
25
|
# Specify which files should be added to the gem when it is released.
|
|
24
26
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
25
|
-
spec.files
|
|
27
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
26
28
|
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
|
27
29
|
end
|
|
28
30
|
spec.bindir = "exe"
|
|
29
31
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
30
32
|
spec.require_paths = ["lib"]
|
|
33
|
+
|
|
34
|
+
# Uncomment to register a new dependency of your gem
|
|
35
|
+
# spec.add_dependency "example-gem", "~> 1.0"
|
|
36
|
+
|
|
37
|
+
# For more information and examples about making a new gem, checkout our
|
|
38
|
+
# guide at: https://bundler.io/guides/creating_gem.html
|
|
31
39
|
end
|
data/examples/server.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
# This example implements an IP discover mechanism for IPv4 and IPv6.
|
|
2
4
|
# Run server with `ruby server.rb` and query with something like
|
|
3
5
|
# `dig my.ip @[address_of_server]`
|
|
@@ -8,7 +10,7 @@ require "socket"
|
|
|
8
10
|
require "dnsmessage"
|
|
9
11
|
|
|
10
12
|
LISTEN_ADDR = "::"
|
|
11
|
-
LISTEN_PORT =
|
|
13
|
+
LISTEN_PORT = 12_345
|
|
12
14
|
MSG_LENGTH = 1400
|
|
13
15
|
FLAGS = 0
|
|
14
16
|
|
|
@@ -23,10 +25,10 @@ loop do
|
|
|
23
25
|
|
|
24
26
|
# Extract client information given as array and log connection
|
|
25
27
|
addr_info = Addrinfo.new(client)
|
|
26
|
-
puts "Client connected from #{addr_info.ip_address} using "
|
|
27
|
-
|
|
28
|
+
puts "Client connected from #{addr_info.ip_address} using " \
|
|
29
|
+
"#{addr_info.ipv6_v4mapped? ? "IPv4" : "IPv6"}"
|
|
28
30
|
|
|
29
|
-
response = DNSMessage::Message
|
|
31
|
+
response = DNSMessage::Message.reply_to(msg)
|
|
30
32
|
opt = DNSMessage::RR.default_opt(512)
|
|
31
33
|
|
|
32
34
|
# Set IPv6 defaults
|
|
@@ -39,10 +41,11 @@ loop do
|
|
|
39
41
|
ip = addr_info.ipv6_to_ipv4.ip_address
|
|
40
42
|
end
|
|
41
43
|
response.answers << DNSMessage::RR.new(
|
|
42
|
-
name:
|
|
43
|
-
type:
|
|
44
|
-
ttl:
|
|
45
|
-
rdata: IPAddr.new(ip)
|
|
44
|
+
name: "your.ip",
|
|
45
|
+
type: type,
|
|
46
|
+
ttl: 10,
|
|
47
|
+
rdata: IPAddr.new(ip)
|
|
48
|
+
)
|
|
46
49
|
|
|
47
50
|
# Be nice and add an EDNS record
|
|
48
51
|
response.additionals << opt
|
data/lib/dnsmessage.rb
CHANGED
data/lib/dnsmessage/enums.rb
CHANGED
|
@@ -1,64 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Generel DNS enum definitions
|
|
1
4
|
module DNSMessage
|
|
5
|
+
# Enums for ResourceRecord types
|
|
2
6
|
module Type
|
|
3
7
|
TYPE_STRINGS = {
|
|
4
|
-
1
|
|
5
|
-
28
|
|
6
|
-
18
|
|
7
|
-
42
|
|
8
|
-
257
|
|
9
|
-
60
|
|
10
|
-
59
|
|
11
|
-
37
|
|
12
|
-
5
|
|
13
|
-
62
|
|
14
|
-
49
|
|
15
|
-
|
|
16
|
-
39
|
|
17
|
-
48
|
|
18
|
-
43
|
|
19
|
-
108
|
|
20
|
-
109
|
|
21
|
-
13
|
|
22
|
-
55
|
|
23
|
-
45
|
|
24
|
-
25
|
|
25
|
-
36
|
|
26
|
-
29
|
|
27
|
-
15
|
|
28
|
-
35
|
|
29
|
-
2
|
|
30
|
-
47
|
|
31
|
-
50
|
|
32
|
-
51
|
|
33
|
-
61
|
|
34
|
-
12
|
|
35
|
-
46
|
|
36
|
-
17
|
|
37
|
-
24
|
|
38
|
-
53
|
|
39
|
-
6
|
|
40
|
-
33
|
|
41
|
-
44
|
|
42
|
-
|
|
43
|
-
249
|
|
44
|
-
52
|
|
45
|
-
250
|
|
46
|
-
16
|
|
47
|
-
256
|
|
48
|
-
63
|
|
49
|
-
64
|
|
50
|
-
65
|
|
51
|
-
252
|
|
52
|
-
251
|
|
53
|
-
41
|
|
54
|
-
}
|
|
8
|
+
1 => :A,
|
|
9
|
+
28 => :AAAA,
|
|
10
|
+
18 => :AFSDB,
|
|
11
|
+
42 => :APL,
|
|
12
|
+
257 => :CAA,
|
|
13
|
+
60 => :CDNSKEY,
|
|
14
|
+
59 => :CDS,
|
|
15
|
+
37 => :CERT,
|
|
16
|
+
5 => :CNAME,
|
|
17
|
+
62 => :CSYNC,
|
|
18
|
+
49 => :DHCID,
|
|
19
|
+
32_769 => :DLV,
|
|
20
|
+
39 => :DNAME,
|
|
21
|
+
48 => :DNSKEY,
|
|
22
|
+
43 => :DS,
|
|
23
|
+
108 => :EUI48,
|
|
24
|
+
109 => :EUI64,
|
|
25
|
+
13 => :HINFO,
|
|
26
|
+
55 => :HIP,
|
|
27
|
+
45 => :IPSECKEY,
|
|
28
|
+
25 => :KEY,
|
|
29
|
+
36 => :KX,
|
|
30
|
+
29 => :LOC,
|
|
31
|
+
15 => :MX,
|
|
32
|
+
35 => :NAPTR,
|
|
33
|
+
2 => :NS,
|
|
34
|
+
47 => :NSE,
|
|
35
|
+
50 => :NSEC3,
|
|
36
|
+
51 => :NSEC3PARAM,
|
|
37
|
+
61 => :OPENPGPKEY,
|
|
38
|
+
12 => :PTR,
|
|
39
|
+
46 => :RRSIG,
|
|
40
|
+
17 => :RP,
|
|
41
|
+
24 => :SIG,
|
|
42
|
+
53 => :SMIMEA,
|
|
43
|
+
6 => :SOA,
|
|
44
|
+
33 => :SRV,
|
|
45
|
+
44 => :SSHFP,
|
|
46
|
+
32_768 => :TA,
|
|
47
|
+
249 => :TKEY,
|
|
48
|
+
52 => :TLSA,
|
|
49
|
+
250 => :TSIG,
|
|
50
|
+
16 => :TXT,
|
|
51
|
+
256 => :URI,
|
|
52
|
+
63 => :ZONEMD,
|
|
53
|
+
64 => :SVCB,
|
|
54
|
+
65 => :HTTPS,
|
|
55
|
+
252 => :AXFR,
|
|
56
|
+
251 => :IXFR,
|
|
57
|
+
41 => :OPT
|
|
58
|
+
}.freeze
|
|
55
59
|
|
|
56
|
-
TYPE_STRINGS.each do |num,name|
|
|
60
|
+
TYPE_STRINGS.each do |num, name|
|
|
57
61
|
const_set(name, num)
|
|
58
62
|
end
|
|
59
|
-
|
|
60
63
|
end
|
|
61
64
|
|
|
65
|
+
# The supported DNS classes
|
|
62
66
|
module Class
|
|
63
67
|
IN = 1
|
|
64
68
|
end
|
data/lib/dnsmessage/message.rb
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module DNSMessage
|
|
4
|
+
# Message is the central class that most users of the API will use
|
|
2
5
|
class Message
|
|
3
|
-
|
|
4
6
|
NAME_POINTER = 0xc0
|
|
5
7
|
POINTER_MASK = 0x3fff
|
|
6
8
|
QUERY = 0
|
|
@@ -8,46 +10,54 @@ module DNSMessage
|
|
|
8
10
|
HEADER_SIZE = 12
|
|
9
11
|
|
|
10
12
|
attr_accessor :questions, :answers, :authority, :additionals,
|
|
11
|
-
:id, :qr, :opcode, :aa, :tc, :rd, :ra, :z, :rcode
|
|
12
|
-
|
|
13
|
+
:id, :qr, :opcode, :aa, :tc, :rd, :ra, :z, :rcode
|
|
14
|
+
attr_reader :qdcount, :ancount, :nscount, :arcount
|
|
13
15
|
|
|
14
|
-
def initialize
|
|
16
|
+
def initialize
|
|
15
17
|
@questions = []
|
|
16
18
|
@answers = []
|
|
17
19
|
@additionals = []
|
|
18
20
|
@authority = []
|
|
19
|
-
@
|
|
21
|
+
@qdcount = 0
|
|
22
|
+
@ancount = 0
|
|
23
|
+
@nscount = 0
|
|
24
|
+
@arcount = 0
|
|
25
|
+
@id = @qr = @opcode = @aa = @tc = @rd = @ra = @z = @rcode = 0
|
|
20
26
|
end
|
|
21
27
|
|
|
22
28
|
def parse(input)
|
|
23
|
-
ptr = Pointer.new
|
|
29
|
+
ptr = Pointer.new
|
|
24
30
|
parse_header(input)
|
|
25
|
-
idx
|
|
26
|
-
@answers, idx
|
|
27
|
-
@authority, idx
|
|
28
|
-
@additionals,
|
|
31
|
+
idx = parse_questions(input, @qdcount, ptr)
|
|
32
|
+
@answers, idx = parse_records(input, @ancount, idx, ptr)
|
|
33
|
+
@authority, idx = parse_records(input, @nscount, idx, ptr)
|
|
34
|
+
@additionals, = parse_records(input, @arcount, idx, ptr)
|
|
29
35
|
end
|
|
30
36
|
|
|
31
37
|
def self.parse(input)
|
|
32
|
-
|
|
38
|
+
new.tap do |m|
|
|
33
39
|
m.parse(input)
|
|
34
40
|
end
|
|
35
41
|
end
|
|
36
42
|
|
|
37
|
-
def self.reply_to(
|
|
38
|
-
|
|
39
|
-
r.id =
|
|
43
|
+
def self.reply_to(query)
|
|
44
|
+
new.tap do |r|
|
|
45
|
+
r.id = query.id
|
|
40
46
|
r.qr = REPLY
|
|
41
|
-
r.questions =
|
|
42
|
-
r.qdcount = q.qdcount
|
|
47
|
+
r.questions = query.questions
|
|
43
48
|
end
|
|
44
49
|
end
|
|
45
50
|
|
|
46
|
-
|
|
47
51
|
def parse_header(message)
|
|
48
52
|
return nil if message.nil? || message.empty?
|
|
53
|
+
|
|
49
54
|
@id, opts, @qdcount, @ancount, @nscount, @arcount =
|
|
50
55
|
message[0...12].unpack("n6")
|
|
56
|
+
|
|
57
|
+
parse_opts(opts)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def parse_opts(opts)
|
|
51
61
|
@qr = (opts >> 15) & 0x1
|
|
52
62
|
@opcode = (opts >> 11) & 0xf
|
|
53
63
|
@aa = (opts >> 10) & 0x1
|
|
@@ -61,7 +71,7 @@ module DNSMessage
|
|
|
61
71
|
def parse_questions(message, num_questions, ptr)
|
|
62
72
|
idx = HEADER_SIZE # Header takes up the first 12 bytes
|
|
63
73
|
@questions = (0...num_questions).map do
|
|
64
|
-
Question.parse(message, ptr, idx).tap do |
|
|
74
|
+
Question.parse(message, ptr, idx).tap do |q|
|
|
65
75
|
idx += q.size
|
|
66
76
|
end
|
|
67
77
|
end
|
|
@@ -70,42 +80,71 @@ module DNSMessage
|
|
|
70
80
|
|
|
71
81
|
def parse_records(message, num_records, idx, ptr)
|
|
72
82
|
[num_records.times.map do
|
|
73
|
-
ResourceRecord.parse(message[idx
|
|
74
|
-
ptr.add_arr(rr.add_to_hash,idx)
|
|
83
|
+
ResourceRecord.parse(message[idx..], ptr).tap do |rr|
|
|
84
|
+
ptr.add_arr(rr.add_to_hash, idx)
|
|
75
85
|
idx += rr.size
|
|
76
86
|
end
|
|
77
87
|
end,
|
|
78
|
-
|
|
88
|
+
idx]
|
|
79
89
|
end
|
|
80
90
|
|
|
81
91
|
def build
|
|
82
|
-
ptr = Pointer.new
|
|
83
|
-
packet
|
|
92
|
+
ptr = Pointer.new
|
|
93
|
+
packet = build_header
|
|
84
94
|
packet << build_questions(ptr, packet.size)
|
|
85
95
|
packet << build_answers(ptr, packet.size)
|
|
86
96
|
packet << build_authority(ptr, packet.size)
|
|
87
|
-
packet << build_additionals(ptr,packet.size)
|
|
97
|
+
packet << build_additionals(ptr, packet.size)
|
|
88
98
|
end
|
|
89
99
|
|
|
90
100
|
def build_header
|
|
91
|
-
|
|
92
|
-
(@opcode & 0xf) << 11 |
|
|
93
|
-
(@aa & 0x1) << 10 |
|
|
94
|
-
(@tc & 0x1) << 9 |
|
|
95
|
-
(@rd & 0x1) << 8 |
|
|
96
|
-
(@ra & 0x1) << 7 |
|
|
97
|
-
(@z & 0x7) << 4 |
|
|
98
|
-
(@z & 0xf)
|
|
99
|
-
[@id, opts,
|
|
101
|
+
[@id, build_opts,
|
|
100
102
|
@questions.length,
|
|
101
103
|
@answers.length,
|
|
102
104
|
@authority.length,
|
|
103
105
|
@additionals.length].pack("n6")
|
|
104
106
|
end
|
|
105
107
|
|
|
108
|
+
def build_opts
|
|
109
|
+
build_qr | build_opcode | build_aa | build_tc | build_rd | build_ra |
|
|
110
|
+
build_z | build_rcode
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def build_qr
|
|
114
|
+
(@qr & 0x1) << 15
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def build_opcode
|
|
118
|
+
(@opcode & 0xf) << 11
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def build_aa
|
|
122
|
+
(@aa & 0x1) << 10
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def build_tc
|
|
126
|
+
(@tc & 0x1) << 9
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def build_rd
|
|
130
|
+
(@rd & 0x1) << 8
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def build_ra
|
|
134
|
+
(@ra & 0x1) << 7
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def build_z
|
|
138
|
+
(@z & 0x1) << 7
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def build_rcode
|
|
142
|
+
(@rcode & 0xf)
|
|
143
|
+
end
|
|
144
|
+
|
|
106
145
|
def build_questions(ptr, idx)
|
|
107
|
-
@questions.map do |
|
|
108
|
-
q.build(ptr,idx).tap do |
|
|
146
|
+
@questions.map do |q|
|
|
147
|
+
q.build(ptr, idx).tap do |bytes|
|
|
109
148
|
idx += bytes.length
|
|
110
149
|
end
|
|
111
150
|
end.join("")
|
|
@@ -124,8 +163,8 @@ module DNSMessage
|
|
|
124
163
|
end
|
|
125
164
|
|
|
126
165
|
def build_record(ptr, idx, records)
|
|
127
|
-
records.map do |
|
|
128
|
-
rr.build(ptr,idx).tap do |
|
|
166
|
+
records.map do |rr|
|
|
167
|
+
rr.build(ptr, idx).tap do |r|
|
|
129
168
|
idx += r.length
|
|
130
169
|
end
|
|
131
170
|
end.join("")
|
|
@@ -136,6 +175,5 @@ module DNSMessage
|
|
|
136
175
|
raise(StandardError, "No questions in query") if @qdcount < 1
|
|
137
176
|
raise(StandardError, "Empty domain") if @domain_name.empty?
|
|
138
177
|
end
|
|
139
|
-
|
|
140
178
|
end
|
|
141
179
|
end
|
data/lib/dnsmessage/name.rb
CHANGED
|
@@ -1,39 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module DNSMessage
|
|
4
|
+
# Parsing DNS names directly or using pointers
|
|
2
5
|
module Name
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
POINTER_MASK = 0x3fff
|
|
6
|
+
NAME_POINTER = 0xc0
|
|
7
|
+
POINTER_MASK = 0x3fff
|
|
6
8
|
|
|
7
9
|
def self.parse(record, ptr)
|
|
8
10
|
# Read name loop
|
|
9
11
|
idx = 0
|
|
10
12
|
name = []
|
|
11
13
|
loop do
|
|
12
|
-
length = record[idx].
|
|
14
|
+
length = record[idx].unpack1("c")
|
|
13
15
|
idx += 1
|
|
14
|
-
if length
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
idx += length
|
|
22
|
-
end
|
|
16
|
+
break if length.zero?
|
|
17
|
+
|
|
18
|
+
return parse_from_pointer(ptr, record, length, idx) \
|
|
19
|
+
if length & NAME_POINTER == NAME_POINTER
|
|
20
|
+
|
|
21
|
+
name << record[idx...idx + length]
|
|
22
|
+
idx += length
|
|
23
23
|
end
|
|
24
24
|
[name.join("."), idx, true]
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
-
def self.build(name,ptr)
|
|
27
|
+
def self.build(name, ptr)
|
|
28
28
|
if ptr.find(name)
|
|
29
|
-
[[(ptr.find(name) | NAME_POINTER << 8)].pack("n"),false]
|
|
29
|
+
[[(ptr.find(name) | NAME_POINTER << 8)].pack("n"), false]
|
|
30
30
|
else
|
|
31
|
-
[
|
|
31
|
+
[name.split(".").map do |section|
|
|
32
32
|
section.length.chr + section
|
|
33
|
-
end.join("")
|
|
34
|
-
|
|
33
|
+
end.join("") << "\x0", # Terminate will nullptr
|
|
34
|
+
true]
|
|
35
35
|
end
|
|
36
36
|
end
|
|
37
37
|
|
|
38
|
+
def self.parse_from_pointer(ptr, record, length, idx)
|
|
39
|
+
[ptr.find(((length << 8) | record[idx].unpack1("c")) & POINTER_MASK),
|
|
40
|
+
idx + 1, false]
|
|
41
|
+
end
|
|
38
42
|
end
|
|
39
43
|
end
|
data/lib/dnsmessage/pointer.rb
CHANGED
|
@@ -1,24 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module DNSMessage
|
|
4
|
+
# Handle DNS pointers
|
|
2
5
|
class Pointer
|
|
3
|
-
|
|
4
6
|
def initialize(hash = {})
|
|
5
7
|
@hash = hash
|
|
6
8
|
end
|
|
7
9
|
|
|
8
10
|
def add_arr(arr, offset)
|
|
9
11
|
return unless arr
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
|
|
13
|
+
arr.each do |k, v|
|
|
14
|
+
k += offset if k.instance_of?(Integer)
|
|
15
|
+
v += offset if v.instance_of?(Integer)
|
|
16
|
+
add(k, v)
|
|
14
17
|
end
|
|
15
18
|
end
|
|
16
19
|
|
|
17
|
-
def add(key,value)
|
|
18
|
-
if key.
|
|
19
|
-
add_name(key,value)
|
|
20
|
+
def add(key, value)
|
|
21
|
+
if key.instance_of?(Integer)
|
|
22
|
+
add_name(key, value)
|
|
20
23
|
else
|
|
21
|
-
add_ptr(key,value)
|
|
24
|
+
add_ptr(key, value)
|
|
22
25
|
end
|
|
23
26
|
end
|
|
24
27
|
|
|
@@ -32,23 +35,24 @@ module DNSMessage
|
|
|
32
35
|
|
|
33
36
|
private
|
|
34
37
|
|
|
35
|
-
def add_name(key,value)
|
|
38
|
+
def add_name(key, value)
|
|
36
39
|
value = value.split(".")
|
|
37
40
|
loop do
|
|
38
41
|
return unless value.length > 1
|
|
42
|
+
|
|
39
43
|
@hash[key] = value.join(".")
|
|
40
44
|
key += value.shift.length + 1
|
|
41
45
|
end
|
|
42
46
|
end
|
|
43
47
|
|
|
44
|
-
def add_ptr(key,value)
|
|
48
|
+
def add_ptr(key, value)
|
|
45
49
|
key = key.split(".")
|
|
46
50
|
loop do
|
|
47
51
|
return unless key.length > 1
|
|
52
|
+
|
|
48
53
|
@hash[key.join(".")] = value
|
|
49
54
|
value += key.shift.length + 1
|
|
50
55
|
end
|
|
51
56
|
end
|
|
52
|
-
|
|
53
57
|
end
|
|
54
58
|
end
|
data/lib/dnsmessage/question.rb
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module DNSMessage
|
|
4
|
+
# Parse and build DNS questions
|
|
2
5
|
class Question
|
|
3
|
-
|
|
4
6
|
attr_accessor :name, :type, :klass
|
|
5
7
|
attr_reader :size, :add_to_hash
|
|
6
8
|
|
|
@@ -11,25 +13,24 @@ module DNSMessage
|
|
|
11
13
|
end
|
|
12
14
|
|
|
13
15
|
def self.parse(question, ptr, idx)
|
|
14
|
-
|
|
16
|
+
new.tap do |q|
|
|
15
17
|
q.parse(question, ptr, idx)
|
|
16
18
|
end
|
|
17
19
|
end
|
|
18
20
|
|
|
19
21
|
def parse(question, ptr, idx)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
+
@name, @size, add = Name.parse(question[idx..], ptr)
|
|
23
|
+
ptr.add(idx, @name) if add
|
|
22
24
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
# take last four bytes
|
|
26
|
+
@type, @klass = question[(idx + @size)..].unpack("n2")
|
|
27
|
+
@size += 4
|
|
26
28
|
end
|
|
27
29
|
|
|
28
|
-
def build(ptr,idx)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
def build(ptr, idx)
|
|
31
|
+
name_bytes, add = Name.build(@name, ptr)
|
|
32
|
+
ptr.add(@name, idx) if add
|
|
33
|
+
name_bytes + [type, klass].pack("n2")
|
|
32
34
|
end
|
|
33
|
-
|
|
34
35
|
end
|
|
35
36
|
end
|
|
@@ -1,13 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module DNSMessage
|
|
4
|
+
# Parse and build Resource Records
|
|
2
5
|
class ResourceRecord
|
|
3
|
-
|
|
4
6
|
PARSERS = {
|
|
5
7
|
Type::A => :parse_ip,
|
|
6
8
|
Type::AAAA => :parse_ip,
|
|
7
9
|
Type::CNAME => :parse_name,
|
|
8
10
|
Type::OPT => :parse_opt,
|
|
9
11
|
Type::TXT => :parse_text
|
|
10
|
-
}
|
|
12
|
+
}.freeze
|
|
11
13
|
|
|
12
14
|
BUILDERS = {
|
|
13
15
|
Type::A => :build_ip,
|
|
@@ -15,10 +17,10 @@ module DNSMessage
|
|
|
15
17
|
Type::CNAME => :build_name,
|
|
16
18
|
Type::OPT => :build_opt,
|
|
17
19
|
Type::TXT => :build_text
|
|
18
|
-
}
|
|
20
|
+
}.freeze
|
|
19
21
|
|
|
20
22
|
attr_accessor :name, :type, :klass, :ttl, :rdata,
|
|
21
|
-
|
|
23
|
+
:opt_udp, :opt_rcode, :opt_edns0_version, :opt_z_dnssec
|
|
22
24
|
attr_reader :size, :add_to_hash
|
|
23
25
|
|
|
24
26
|
def initialize(name: nil, type: nil, klass: Class::IN, ttl: 0,
|
|
@@ -31,36 +33,32 @@ module DNSMessage
|
|
|
31
33
|
@add_to_hash = []
|
|
32
34
|
end
|
|
33
35
|
|
|
34
|
-
def add_to_hash
|
|
35
|
-
@add_to_hash
|
|
36
|
-
end
|
|
37
|
-
|
|
38
36
|
def self.parse(record, ptr)
|
|
39
|
-
|
|
37
|
+
new.tap do |rr|
|
|
40
38
|
rr.parse(record, ptr)
|
|
41
39
|
end
|
|
42
40
|
end
|
|
43
41
|
|
|
44
42
|
def parse(record, ptr)
|
|
45
|
-
@name, idx, add = Name.parse(record,ptr)
|
|
43
|
+
@name, idx, add = Name.parse(record, ptr)
|
|
46
44
|
@add_to_hash << [idx, @name] if add
|
|
47
|
-
@type, @klass, @ttl, rdata_length = record[idx...idx+10].unpack("nnNn")
|
|
48
|
-
@rdata = send(parser(@type), record, idx+10, rdata_length, ptr)
|
|
45
|
+
@type, @klass, @ttl, rdata_length = record[idx...idx + 10].unpack("nnNn")
|
|
46
|
+
@rdata = send(parser(@type), record, idx + 10, rdata_length, ptr)
|
|
49
47
|
@size = idx + 10 + rdata_length
|
|
50
48
|
end
|
|
51
49
|
|
|
52
|
-
def build(ptr,idx)
|
|
50
|
+
def build(ptr, idx)
|
|
53
51
|
return "" unless BUILDERS[type]
|
|
54
52
|
|
|
55
53
|
name_bytes, add = Name.build(@name, ptr)
|
|
56
54
|
ptr.add(@name, idx) if add
|
|
57
|
-
data = send(builder(@type),ptr, idx + name_bytes.length)
|
|
55
|
+
data = send(builder(@type), ptr, idx + name_bytes.length)
|
|
58
56
|
@rdata_length = data.length
|
|
59
57
|
name_bytes + [@type, @klass, @ttl, @rdata_length].pack("nnNn") + data
|
|
60
58
|
end
|
|
61
59
|
|
|
62
60
|
def self.default_opt(size)
|
|
63
|
-
|
|
61
|
+
new.tap do |opt|
|
|
64
62
|
opt.name = ""
|
|
65
63
|
opt.type = Type::OPT
|
|
66
64
|
opt.opt_udp = size
|
|
@@ -84,25 +82,25 @@ module DNSMessage
|
|
|
84
82
|
## Parsers
|
|
85
83
|
##
|
|
86
84
|
|
|
87
|
-
def parse_ip(rdata, start, length,
|
|
88
|
-
IPAddr.new_ntoh(rdata[start...start+length])
|
|
85
|
+
def parse_ip(rdata, start, length, _ptr)
|
|
86
|
+
IPAddr.new_ntoh(rdata[start...start + length])
|
|
89
87
|
end
|
|
90
88
|
|
|
91
|
-
def parse_opt(
|
|
92
|
-
@opt_udp
|
|
89
|
+
def parse_opt(_rdata, _start, _length, _ptr)
|
|
90
|
+
@opt_udp = @klass
|
|
93
91
|
@opt_rcode, @opt_edns0_version, @opt_z_dnssec =
|
|
94
92
|
[@ttl].pack("N").unpack("CCn")
|
|
95
93
|
@opt_z_dnssec = @opt_z_dnssec >> 15
|
|
96
94
|
end
|
|
97
95
|
|
|
98
|
-
def parse_text(rdata, start,
|
|
96
|
+
def parse_text(rdata, start, _length, _ptr)
|
|
99
97
|
txt_length = rdata[start].ord
|
|
100
|
-
rdata[start+1..start+txt_length]
|
|
98
|
+
rdata[start + 1..start + txt_length]
|
|
101
99
|
end
|
|
102
100
|
|
|
103
101
|
def parse_name(rdata, start, length, ptr)
|
|
104
102
|
name, idx, add = Name.parse(rdata[0...length], ptr)
|
|
105
|
-
@add_to_hash << [start+idx, name] if add
|
|
103
|
+
@add_to_hash << [start + idx, name] if add
|
|
106
104
|
name
|
|
107
105
|
end
|
|
108
106
|
|
|
@@ -110,29 +108,28 @@ module DNSMessage
|
|
|
110
108
|
## Builders
|
|
111
109
|
##
|
|
112
110
|
|
|
113
|
-
def build_ip(
|
|
111
|
+
def build_ip(_ptr, _)
|
|
114
112
|
@rdata.hton
|
|
115
113
|
end
|
|
116
114
|
|
|
117
|
-
def build_opt(
|
|
115
|
+
def build_opt(_ptr, _)
|
|
118
116
|
@klass = @opt_udp
|
|
119
117
|
@ttl = [@opt_rcode,
|
|
120
118
|
@opt_edns0_version,
|
|
121
|
-
@opt_z_dnssec << 15].pack("CCn").
|
|
119
|
+
@opt_z_dnssec << 15].pack("CCn").unpack1("N")
|
|
122
120
|
"" # Set RDATA to nothing
|
|
123
121
|
end
|
|
124
122
|
|
|
125
|
-
def build_text(
|
|
123
|
+
def build_text(_ptr, _)
|
|
126
124
|
@rdata.length.chr + rdata
|
|
127
125
|
end
|
|
128
126
|
|
|
129
127
|
def build_name(ptr, idx)
|
|
130
|
-
Name.build(@rdata, ptr).tap do |
|
|
131
|
-
ptr.add(@rdata,idx) if add
|
|
128
|
+
Name.build(@rdata, ptr).tap do |bytes, add|
|
|
129
|
+
ptr.add(@rdata, idx) if add
|
|
132
130
|
return bytes
|
|
133
131
|
end
|
|
134
132
|
end
|
|
135
|
-
|
|
136
133
|
end
|
|
137
134
|
|
|
138
135
|
# Add "alias"
|
data/lib/dnsmessage/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: dnsmessage
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Claus Lensbøl
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2021-02-
|
|
11
|
+
date: 2021-02-23 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: |-
|
|
14
14
|
A full featured DNS parser. The library supports DNS
|
|
@@ -23,6 +23,7 @@ files:
|
|
|
23
23
|
- ".github/workflows/ruby.yml"
|
|
24
24
|
- ".gitignore"
|
|
25
25
|
- ".rspec"
|
|
26
|
+
- ".rubocop.yml"
|
|
26
27
|
- ".travis.yml"
|
|
27
28
|
- CODE_OF_CONDUCT.md
|
|
28
29
|
- Gemfile
|
|
@@ -49,7 +50,7 @@ metadata:
|
|
|
49
50
|
homepage_uri: https://github.com/cmol/dnsmessage
|
|
50
51
|
source_code_uri: https://github.com/cmol/dnsmessage
|
|
51
52
|
changelog_uri: https://github.com/cmol/dnsmessage
|
|
52
|
-
post_install_message:
|
|
53
|
+
post_install_message:
|
|
53
54
|
rdoc_options: []
|
|
54
55
|
require_paths:
|
|
55
56
|
- lib
|
|
@@ -57,16 +58,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
57
58
|
requirements:
|
|
58
59
|
- - ">="
|
|
59
60
|
- !ruby/object:Gem::Version
|
|
60
|
-
version: 2.
|
|
61
|
+
version: 2.7.0
|
|
61
62
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
62
63
|
requirements:
|
|
63
64
|
- - ">="
|
|
64
65
|
- !ruby/object:Gem::Version
|
|
65
66
|
version: '0'
|
|
66
67
|
requirements: []
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
signing_key:
|
|
68
|
+
rubygems_version: 3.2.3
|
|
69
|
+
signing_key:
|
|
70
70
|
specification_version: 4
|
|
71
71
|
summary: Ruby library to build and parse DNS messages
|
|
72
72
|
test_files: []
|