nesser 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|