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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: bffba7aa303feb0c9264e87bea0b4071dd43de0a
4
- data.tar.gz: f9d20c7da26f034657c8fbb16ded9222571d1b1b
3
+ metadata.gz: 2647db1767dd49b7498c90b61a6db3e42ddd0baa
4
+ data.tar.gz: 1dca698d183e7e4415ec2cbc2dd75babbab39919
5
5
  SHA512:
6
- metadata.gz: 5a92a2bd9131b145d848cfc2204dbcbc5468424e43fb60cf8a8cd08d1e5480cfae325f5ec47c0e5824c20c7fcb99285a923bfa6a747e6c2bbdfaf616bb631232
7
- data.tar.gz: 42c424af1ba877d784b05f1a40cb1577067d47ff6b06b672f62bb5f7a5393ace29abbcd8a1e305d282e098fe2751862dc4a1063637884b8c7ede1717370ec79c
6
+ metadata.gz: 05a2335a136d62fa438592616af81e0a4d1aecf23b8a3c4cfb65e35d024d2af862e08bc62957f54f0db9de85795cdea42a06f46a133e4b2692c4c3f2207b85c1
7
+ data.tar.gz: cf8809e713363f6497bf0ecb7caafa9ab4643cbfbb658d926724519b9939c3d7dd6fbe552896f59fbb31b43338733d2f51b9db593fed6fca0a51bb50bce7359c
@@ -12,7 +12,4 @@
12
12
  module Nesser
13
13
  class DnsException < StandardError
14
14
  end
15
-
16
- class FormatException < DnsException
17
- end
18
15
  end
@@ -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(FormatException, "DNS name contains illegal characters")
35
+ raise(DnsException, "DNS name contains illegal characters")
36
36
  end
37
37
  if name.length > 253
38
- raise(FormatException, "DNS name can't be longer than 253 characters")
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(FormatException, "DNS segments must be between 1 and 63 characters!")
42
+ raise(DnsException, "DNS segments must be between 1 and 63 characters!")
43
43
  end
44
44
  end
45
45
  end
@@ -166,7 +166,7 @@ module Nesser
166
166
  QRS[@qr] || "unknown",
167
167
  @trn_id,
168
168
  OPCODES[@opcode] || "unknown opcode",
169
- Nesser::FLAGS(@flags),
169
+ ::Nesser::FLAGS(@flags),
170
170
  RCODES[@rcode] || "unknown",
171
171
  @questions.length,
172
172
  @answers.length,
@@ -20,24 +20,24 @@ module Nesser
20
20
 
21
21
  def initialize(address:)
22
22
  if !address.is_a?(String)
23
- raise(FormatException, "String required!")
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(FormatException, "Invalid address: %s" % e)
29
+ raise(DnsException, "Invalid address: %s" % e)
30
30
  end
31
31
 
32
32
  if !@address.ipv4?()
33
- raise(FormatException, "IPv4 address required!")
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(FormatException, "Invalid A record!")
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(FormatException, "Invalid SOA record")
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(FormatException, "Invalid MX record")
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(FormatException, "Invalid TXT record")
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(FormatException, "Invalid TXT record")
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(FormatException, "String required!")
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(FormatException, "Invalid address: %s" % e)
237
+ raise(DnsException, "Invalid address: %s" % e)
238
238
  end
239
239
 
240
240
  if !@address.ipv6?()
241
- raise(FormatException, "IPv6 address required!")
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(FormatException, "Invalid AAAA record")
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(FormatException, "DNS packet was truncated (or we messed up parsing it)!")
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(FormatException, "DNS packet was invalid!")
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(FormatException, "unpack_one() was passed a bad format string")
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(FormatException, "It looks like this packet contains recursive pointers!")
89
+ raise(DnsException, "It looks like this packet contains recursive pointers!")
90
90
  end
91
91
 
92
92
  loop do
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Nesser
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
data/lib/nesser.rb CHANGED
@@ -18,40 +18,28 @@
18
18
  #
19
19
  # To make a query, use Nesser.query:
20
20
  #
21
- # Nesser.query("google.com") do |response|
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 "nesser/version"
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:, host:"0.0.0.0", port:53)
38
+ def initialize(s:, logger: nil, host:"0.0.0.0", port:53)
52
39
  @s = s
53
40
  @s.bind(host, port)
54
- @logger = logger
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
- transaction.error!(RCODE_SERVER_FAILURE)
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, asynchronously. This immediately returns, then, when the
115
- # query is finished, the callback block is called with a Nesser::Packet that
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
- return Thread.new() do
130
- begin
131
- s.send(packet.serialize(), 0, server, port)
132
-
133
- timeout(timeout) do
134
- response = s.recv(65536)
135
- proc.call(Nesser::Packet.unpack(response))
136
- end
137
- rescue Timeout::Error
138
- proc.call(nil)
139
- rescue Exception => e
140
- @logger.error("There was an exception sending a query for #{hostname} to #{server}:#{port}: #{e}")
141
- ensure
142
- if(s)
143
- s.close()
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.1
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-06-30 00:00:00.000000000 Z
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