nesser 0.0.1 → 0.0.2
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/lib/nesser/dns_exception.rb +0 -3
- data/lib/nesser/examples/client.rb +18 -0
- data/lib/nesser/examples/server.rb +33 -0
- data/lib/nesser/logger.rb +52 -0
- data/lib/nesser/packets/packer.rb +3 -3
- data/lib/nesser/packets/packet.rb +1 -1
- data/lib/nesser/packets/rr_types.rb +12 -12
- data/lib/nesser/packets/unpacker.rb +4 -4
- data/lib/nesser/transaction.rb +22 -0
- data/lib/nesser/version.rb +1 -1
- data/lib/nesser.rb +46 -51
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2647db1767dd49b7498c90b61a6db3e42ddd0baa
|
4
|
+
data.tar.gz: 1dca698d183e7e4415ec2cbc2dd75babbab39919
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 05a2335a136d62fa438592616af81e0a4d1aecf23b8a3c4cfb65e35d024d2af862e08bc62957f54f0db9de85795cdea42a06f46a133e4b2692c4c3f2207b85c1
|
7
|
+
data.tar.gz: cf8809e713363f6497bf0ecb7caafa9ab4643cbfbb658d926724519b9939c3d7dd6fbe552896f59fbb31b43338733d2f51b9db593fed6fca0a51bb50bce7359c
|
data/lib/nesser/dns_exception.rb
CHANGED
@@ -0,0 +1,18 @@
|
|
1
|
+
# Encoding: ASCII-8BIT
|
2
|
+
##
|
3
|
+
# client.rb
|
4
|
+
# Created July 8, 2017
|
5
|
+
# By Ron Bowes
|
6
|
+
#
|
7
|
+
# See: LICENSE.md
|
8
|
+
##
|
9
|
+
|
10
|
+
# If you're using a gem version, you wouldn't need this line
|
11
|
+
$LOAD_PATH.unshift File.expand_path('../../../', __FILE__)
|
12
|
+
|
13
|
+
require 'socket'
|
14
|
+
|
15
|
+
require 'nesser'
|
16
|
+
|
17
|
+
s = UDPSocket.new()
|
18
|
+
puts Nesser::Nesser.query(s: s, hostname: 'google.com', type: Nesser::TYPE_ANY)
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# Encoding: ASCII-8BIT
|
2
|
+
##
|
3
|
+
# server.rb
|
4
|
+
# Created July 8, 2017
|
5
|
+
# By Ron Bowes
|
6
|
+
#
|
7
|
+
# See: LICENSE.md
|
8
|
+
##
|
9
|
+
|
10
|
+
# If you're using a gem version, you wouldn't need this line
|
11
|
+
$LOAD_PATH.unshift File.expand_path('../../../', __FILE__)
|
12
|
+
|
13
|
+
require 'socket'
|
14
|
+
require 'nesser'
|
15
|
+
|
16
|
+
s = UDPSocket.new()
|
17
|
+
|
18
|
+
nesser = Nesser::Nesser.new(s: s) do |transaction|
|
19
|
+
if transaction.request_packet.questions[0].name == 'test.com'
|
20
|
+
transaction.answer!([Nesser::Answer.new(
|
21
|
+
name: 'test.com',
|
22
|
+
type: Nesser::TYPE_A,
|
23
|
+
cls: Nesser::CLS_IN,
|
24
|
+
ttl: 1337,
|
25
|
+
rr: Nesser::A.new(address: '1.2.3.4')
|
26
|
+
)])
|
27
|
+
else
|
28
|
+
transaction.error!(Nesser::RCODE_NAME_ERROR)
|
29
|
+
end
|
30
|
+
puts(transaction)
|
31
|
+
end
|
32
|
+
|
33
|
+
nesser.wait()
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# Encoding: ASCII-8BIT
|
2
|
+
##
|
3
|
+
# dns_exception.rb
|
4
|
+
# Created June 20, 2017
|
5
|
+
# By Ron Bowes
|
6
|
+
#
|
7
|
+
# See LICENSE.md
|
8
|
+
#
|
9
|
+
# Implements a simple exception class for dns errors.
|
10
|
+
##
|
11
|
+
|
12
|
+
module Nesser
|
13
|
+
class Logger
|
14
|
+
DEBUG = 0
|
15
|
+
INFO = 1
|
16
|
+
WARNING = 2
|
17
|
+
ERROR = 3
|
18
|
+
FATAL = 4
|
19
|
+
|
20
|
+
def initialize(min_level: INFO, stream: STDERR)
|
21
|
+
@min_level = min_level
|
22
|
+
@stream = stream
|
23
|
+
end
|
24
|
+
|
25
|
+
def debug(msg)
|
26
|
+
return if @min_level > DEBUG
|
27
|
+
@stream.puts('[DEBUG] ' + msg.to_s)
|
28
|
+
end
|
29
|
+
|
30
|
+
def info(msg)
|
31
|
+
return if @min_level > INFO
|
32
|
+
@stream.puts('[INFO] ' + msg.to_s)
|
33
|
+
end
|
34
|
+
|
35
|
+
def warning(msg)
|
36
|
+
return if @min_level > WARNING
|
37
|
+
@stream.puts('[WARNING] ' + msg.to_s)
|
38
|
+
end
|
39
|
+
|
40
|
+
def error(msg)
|
41
|
+
return if @min_level > ERROR
|
42
|
+
@stream.puts('[ERROR] ' + msg.to_s)
|
43
|
+
end
|
44
|
+
|
45
|
+
def fatal(msg, die: false)
|
46
|
+
return if @min_level > FATAL
|
47
|
+
@stream.puts('[FATAL] ' + msg.to_s)
|
48
|
+
|
49
|
+
exit() if die
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -32,14 +32,14 @@ module Nesser
|
|
32
32
|
private
|
33
33
|
def validate!(name)
|
34
34
|
if name.chars.detect { |ch| !LEGAL_CHARACTERS.include?(ch) }
|
35
|
-
raise(
|
35
|
+
raise(DnsException, "DNS name contains illegal characters")
|
36
36
|
end
|
37
37
|
if name.length > 253
|
38
|
-
raise(
|
38
|
+
raise(DnsException, "DNS name can't be longer than 253 characters")
|
39
39
|
end
|
40
40
|
name.split(/\./).each do |segment|
|
41
41
|
if segment.length == 0 || segment.length > 63
|
42
|
-
raise(
|
42
|
+
raise(DnsException, "DNS segments must be between 1 and 63 characters!")
|
43
43
|
end
|
44
44
|
end
|
45
45
|
end
|
@@ -20,24 +20,24 @@ module Nesser
|
|
20
20
|
|
21
21
|
def initialize(address:)
|
22
22
|
if !address.is_a?(String)
|
23
|
-
raise(
|
23
|
+
raise(DnsException, "String required!")
|
24
24
|
end
|
25
25
|
|
26
26
|
begin
|
27
27
|
@address = IPAddr.new(address)
|
28
28
|
rescue IPAddr::InvalidAddressError => e
|
29
|
-
raise(
|
29
|
+
raise(DnsException, "Invalid address: %s" % e)
|
30
30
|
end
|
31
31
|
|
32
32
|
if !@address.ipv4?()
|
33
|
-
raise(
|
33
|
+
raise(DnsException, "IPv4 address required!")
|
34
34
|
end
|
35
35
|
end
|
36
36
|
|
37
37
|
def self.unpack(unpacker)
|
38
38
|
length = unpacker.unpack_one('n')
|
39
39
|
if length != 4
|
40
|
-
raise(
|
40
|
+
raise(DnsException, "Invalid A record!")
|
41
41
|
end
|
42
42
|
|
43
43
|
data = unpacker.unpack('a4').join()
|
@@ -121,7 +121,7 @@ module Nesser
|
|
121
121
|
def self.unpack(unpacker)
|
122
122
|
length = unpacker.unpack_one('n')
|
123
123
|
if length < 22
|
124
|
-
raise(
|
124
|
+
raise(DnsException, "Invalid SOA record")
|
125
125
|
end
|
126
126
|
|
127
127
|
primary = unpacker.unpack_name()
|
@@ -166,7 +166,7 @@ module Nesser
|
|
166
166
|
def self.unpack(unpacker)
|
167
167
|
length = unpacker.unpack_one('n')
|
168
168
|
if length < 3
|
169
|
-
raise(
|
169
|
+
raise(DnsException, "Invalid MX record")
|
170
170
|
end
|
171
171
|
|
172
172
|
preference = unpacker.unpack_one('n')
|
@@ -198,13 +198,13 @@ module Nesser
|
|
198
198
|
def self.unpack(unpacker)
|
199
199
|
length = unpacker.unpack_one('n')
|
200
200
|
if length < 1
|
201
|
-
raise(
|
201
|
+
raise(DnsException, "Invalid TXT record")
|
202
202
|
end
|
203
203
|
|
204
204
|
len = unpacker.unpack_one("C")
|
205
205
|
|
206
206
|
if len != length - 1
|
207
|
-
raise(
|
207
|
+
raise(DnsException, "Invalid TXT record")
|
208
208
|
end
|
209
209
|
|
210
210
|
data = unpacker.unpack_one("a#{len}")
|
@@ -228,24 +228,24 @@ module Nesser
|
|
228
228
|
|
229
229
|
def initialize(address:)
|
230
230
|
if !address.is_a?(String)
|
231
|
-
raise(
|
231
|
+
raise(DnsException, "String required!")
|
232
232
|
end
|
233
233
|
|
234
234
|
begin
|
235
235
|
@address = IPAddr.new(address)
|
236
236
|
rescue IPAddr::InvalidAddressError => e
|
237
|
-
raise(
|
237
|
+
raise(DnsException, "Invalid address: %s" % e)
|
238
238
|
end
|
239
239
|
|
240
240
|
if !@address.ipv6?()
|
241
|
-
raise(
|
241
|
+
raise(DnsException, "IPv6 address required!")
|
242
242
|
end
|
243
243
|
end
|
244
244
|
|
245
245
|
def self.unpack(unpacker)
|
246
246
|
length = unpacker.unpack_one('n')
|
247
247
|
if length != 16
|
248
|
-
raise(
|
248
|
+
raise(DnsException, "Invalid AAAA record")
|
249
249
|
end
|
250
250
|
|
251
251
|
data = unpacker.unpack('a16').join()
|
@@ -32,7 +32,7 @@ module Nesser
|
|
32
32
|
def _verify_results(results)
|
33
33
|
# If there's at least one nil included in our results, bad stuff happened
|
34
34
|
if results.index(nil)
|
35
|
-
raise(
|
35
|
+
raise(DnsException, "DNS packet was truncated (or we messed up parsing it)!")
|
36
36
|
end
|
37
37
|
end
|
38
38
|
|
@@ -41,7 +41,7 @@ module Nesser
|
|
41
41
|
public
|
42
42
|
def unpack(format)
|
43
43
|
if @offset >= @data.length
|
44
|
-
raise(
|
44
|
+
raise(DnsException, "DNS packet was invalid!")
|
45
45
|
end
|
46
46
|
|
47
47
|
results = @data[@offset..-1].unpack(format + "a*")
|
@@ -59,7 +59,7 @@ module Nesser
|
|
59
59
|
|
60
60
|
_verify_results(results)
|
61
61
|
if results.length != 1
|
62
|
-
raise(
|
62
|
+
raise(DnsException, "unpack_one() was passed a bad format string")
|
63
63
|
end
|
64
64
|
|
65
65
|
return results.pop()
|
@@ -86,7 +86,7 @@ module Nesser
|
|
86
86
|
segments = []
|
87
87
|
|
88
88
|
if depth > MAX_RECURSION_DEPTH
|
89
|
-
raise(
|
89
|
+
raise(DnsException, "It looks like this packet contains recursive pointers!")
|
90
90
|
end
|
91
91
|
|
92
92
|
loop do
|
data/lib/nesser/transaction.rb
CHANGED
@@ -36,6 +36,11 @@ module Nesser
|
|
36
36
|
end
|
37
37
|
end
|
38
38
|
|
39
|
+
public
|
40
|
+
def open?()
|
41
|
+
return !@sent
|
42
|
+
end
|
43
|
+
|
39
44
|
public
|
40
45
|
def answer!(answers=[])
|
41
46
|
answers.each do |answer|
|
@@ -90,5 +95,22 @@ module Nesser
|
|
90
95
|
@s.send(@response_packet.to_bytes(), 0, @host, @port)
|
91
96
|
@sent = true
|
92
97
|
end
|
98
|
+
|
99
|
+
public
|
100
|
+
def to_s()
|
101
|
+
result = []
|
102
|
+
|
103
|
+
result << '== Nesser (DNS) Transaction =='
|
104
|
+
result << '-- Request --'
|
105
|
+
result << @request_packet.to_s()
|
106
|
+
if !sent()
|
107
|
+
result << '-- Response [not sent yet] --'
|
108
|
+
else
|
109
|
+
result << '-- Response [sent] --'
|
110
|
+
end
|
111
|
+
result << @response_packet.to_s()
|
112
|
+
|
113
|
+
return result.join("\n")
|
114
|
+
end
|
93
115
|
end
|
94
116
|
end
|
data/lib/nesser/version.rb
CHANGED
data/lib/nesser.rb
CHANGED
@@ -18,40 +18,28 @@
|
|
18
18
|
#
|
19
19
|
# To make a query, use Nesser.query:
|
20
20
|
#
|
21
|
-
#
|
22
|
-
# ...
|
23
|
-
# end
|
24
|
-
#
|
25
|
-
# `response` will be of type Nesser::Packet.
|
26
|
-
#
|
27
|
-
# To listen for queries, create a new instance of Nesser, which will begin
|
28
|
-
# listening on a port, but won't actually handle queries yet:
|
29
|
-
#
|
30
|
-
# nesser = Nesser.new("0.0.0.0", 53) do |transaction|
|
31
|
-
# ...
|
32
|
-
# end
|
33
|
-
#
|
34
|
-
# `transaction` is of type Nesser::Transaction, and allows you to respond to the
|
35
|
-
# request either immediately or asynchronously.
|
36
|
-
#
|
37
|
-
# Nesser currently supports the following record types: A, NS, CNAME, SOA, MX,
|
38
|
-
# TXT, and AAAA.
|
21
|
+
# ...TODO...
|
39
22
|
##
|
40
23
|
|
41
24
|
require 'ipaddr'
|
42
25
|
require 'socket'
|
43
26
|
require 'timeout'
|
44
27
|
|
45
|
-
require
|
28
|
+
require 'nesser/version'
|
29
|
+
|
30
|
+
require 'nesser/packets/packet'
|
31
|
+
require 'nesser/transaction'
|
32
|
+
require 'nesser/logger'
|
46
33
|
|
47
34
|
module Nesser
|
48
35
|
class Nesser
|
49
36
|
attr_reader :thread
|
50
37
|
|
51
|
-
def initialize(s:, logger
|
38
|
+
def initialize(s:, logger: nil, host:"0.0.0.0", port:53)
|
52
39
|
@s = s
|
53
40
|
@s.bind(host, port)
|
54
|
-
|
41
|
+
|
42
|
+
@logger = (logger = logger || Logger.new())
|
55
43
|
|
56
44
|
@thread = Thread.new() do
|
57
45
|
begin
|
@@ -65,6 +53,9 @@ module Nesser
|
|
65
53
|
rescue DnsException => e
|
66
54
|
logger.error("Failed to parse the DNS packet: %s" % e.to_s())
|
67
55
|
next
|
56
|
+
rescue Exception => e
|
57
|
+
logger.error("Error: %s" % e.to_s())
|
58
|
+
logger.info(e.backtrace().join("\n"))
|
68
59
|
end
|
69
60
|
|
70
61
|
# Create a transaction object, which we can use to respond
|
@@ -79,8 +70,11 @@ module Nesser
|
|
79
70
|
proc.call(transaction)
|
80
71
|
rescue StandardError => e
|
81
72
|
logger.error("Error thrown while processing the DNS packet: %s" % e.to_s())
|
82
|
-
logger.info(e.backtrace())
|
83
|
-
|
73
|
+
logger.info(e.backtrace().join("\n"))
|
74
|
+
|
75
|
+
if transaction.open?()
|
76
|
+
transaction.error!(RCODE_SERVER_FAILURE)
|
77
|
+
end
|
84
78
|
end
|
85
79
|
end
|
86
80
|
ensure
|
@@ -111,38 +105,39 @@ module Nesser
|
|
111
105
|
@thread.join()
|
112
106
|
end
|
113
107
|
|
114
|
-
# Send out a query
|
115
|
-
|
116
|
-
# represents the response (or nil, if there was a timeout).
|
117
|
-
def Nesser.query(s, hostname, params = {})
|
118
|
-
server = params[:server] || "8.8.8.8"
|
119
|
-
port = params[:port] || 53
|
120
|
-
type = params[:type] || Nesser::Packet::TYPE_A
|
121
|
-
cls = params[:cls] || Nesser::Packet::CLS_IN
|
122
|
-
timeout = params[:timeout] || 3
|
123
|
-
|
124
|
-
packet = Nesser::Packet.new(rand(65535), Nesser::Packet::QR_QUERY, Nesser::Packet::OPCODE_QUERY, Nesser::Packet::FLAG_RD, Nesser::Packet::RCODE_SUCCESS)
|
125
|
-
packet.add_question(Nesser::Packet::Question.new(hostname, type, cls))
|
126
|
-
|
108
|
+
# Send out a query
|
109
|
+
def self.query(s:, hostname:, server: '8.8.8.8', port: 53, type: TYPE_A, cls: CLS_IN, timeout: 3)
|
127
110
|
s = UDPSocket.new()
|
128
111
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
112
|
+
question = Question.new(
|
113
|
+
name: hostname,
|
114
|
+
type: type,
|
115
|
+
cls: cls,
|
116
|
+
)
|
117
|
+
|
118
|
+
packet = Packet.new(
|
119
|
+
trn_id: rand(65535),
|
120
|
+
qr: QR_QUERY,
|
121
|
+
opcode: OPCODE_QUERY,
|
122
|
+
flags: FLAG_RD,
|
123
|
+
rcode: RCODE_SUCCESS,
|
124
|
+
questions: [question],
|
125
|
+
answers: [],
|
126
|
+
)
|
127
|
+
|
128
|
+
begin
|
129
|
+
Timeout.timeout(timeout) do
|
130
|
+
s.send(packet.to_bytes(), 0, server, port)
|
131
|
+
response = s.recv(65536)
|
132
|
+
|
133
|
+
if response.nil?()
|
134
|
+
raise(DnsException, "Error communicating with the DNS server")
|
144
135
|
end
|
136
|
+
|
137
|
+
return Packet.parse(response)
|
145
138
|
end
|
139
|
+
rescue Timeout::Error
|
140
|
+
raise(DnsException, "Timeout communicating with the DNS server")
|
146
141
|
end
|
147
142
|
end
|
148
143
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: nesser
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- iagox86
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-07-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -82,6 +82,9 @@ files:
|
|
82
82
|
- bin/setup
|
83
83
|
- lib/nesser.rb
|
84
84
|
- lib/nesser/dns_exception.rb
|
85
|
+
- lib/nesser/examples/client.rb
|
86
|
+
- lib/nesser/examples/server.rb
|
87
|
+
- lib/nesser/logger.rb
|
85
88
|
- lib/nesser/packets/answer.rb
|
86
89
|
- lib/nesser/packets/constants.rb
|
87
90
|
- lib/nesser/packets/packer.rb
|