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 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