nesser 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE.md +10 -0
- data/README.md +152 -1
- data/lib/nesser.rb +82 -41
- data/lib/nesser/examples/client.rb +10 -2
- data/lib/nesser/examples/server.rb +29 -5
- data/lib/nesser/logger.rb +2 -2
- data/lib/nesser/packets/answer.rb +23 -3
- data/lib/nesser/packets/constants.rb +5 -3
- data/lib/nesser/packets/packer.rb +41 -3
- data/lib/nesser/packets/packet.rb +46 -0
- data/lib/nesser/packets/question.rb +15 -3
- data/lib/nesser/packets/rr_types.rb +35 -0
- data/lib/nesser/packets/unpacker.rb +45 -10
- data/lib/nesser/transaction.rb +52 -14
- data/lib/nesser/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7c6c93d335d6983d60154b25ed5251f122a30534
|
4
|
+
data.tar.gz: 4d8dca606688b56847e0e82712e92b5467173659
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c048c7eccf69dcd6091308ab2d95b485a3706a02749b5b20ef97f15c104dec50eb56ae20f52df64dd2d1f2bba2faf9cd0c5092f07922ffd2491b7431a88c5017
|
7
|
+
data.tar.gz: 0d18a1aee601408787d45d87bd3f0477ed89219cf5e7baa08db44115d27a333390647a27ec318311890a2922f4e448cb72f5c58db1267e70dec6ac7dab0e357c
|
data/LICENSE.md
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
Copyright (c) 2013-2017, Ron Bowes
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
5
|
+
|
6
|
+
- Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
7
|
+
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
8
|
+
- Neither the name of the organization nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
9
|
+
|
10
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
CHANGED
@@ -20,9 +20,160 @@ Or install it yourself as:
|
|
20
20
|
|
21
21
|
## Usage
|
22
22
|
|
23
|
-
|
23
|
+
### Client
|
24
|
+
|
25
|
+
After installing the gem, using it as a client is pretty straight forward.
|
26
|
+
I wrote an example file in [lib/nesser/examples](lib/nesser/examples), but in
|
27
|
+
a nutshell, here's the code:
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
require 'socket'
|
31
|
+
require 'nesser'
|
32
|
+
|
33
|
+
# Create a UDP socket
|
34
|
+
s = UDPSocket.new()
|
35
|
+
|
36
|
+
# Perform a query for 'google.com', of type ANY. I chose this because it has a
|
37
|
+
# wide array of varied answers.
|
38
|
+
#
|
39
|
+
# See packets/constants.rb for a full list of possible request types.
|
40
|
+
result = Nesser::Nesser.query(s: s, hostname: 'google.com', type: Nesser::TYPE_ANY)
|
41
|
+
|
42
|
+
# Print the result
|
43
|
+
puts result
|
44
|
+
```
|
45
|
+
|
46
|
+
You can find all constant definitions in
|
47
|
+
[the constants.rb file](lib/nesser/packets/constants.rb)
|
48
|
+
|
49
|
+
The return from Nesser.query() is an instance of
|
50
|
+
[Nesser::Answer](lib/nesser/packets/answer.rb).
|
51
|
+
|
52
|
+
### Server
|
53
|
+
|
54
|
+
Writing a server is a little more complicated, because you have to deal with
|
55
|
+
much more of the nitty gritty DNS stuff.
|
56
|
+
|
57
|
+
To start a server, create an instance of Nesser, and pass in a block that
|
58
|
+
handles queries. From [examples/server.rb](lib/nesser/examples/server.rb):
|
59
|
+
|
60
|
+
```ruby
|
61
|
+
nesser = Nesser::Nesser.new(s: s) do |transaction|
|
62
|
+
puts transaction
|
63
|
+
|
64
|
+
# ...
|
65
|
+
end
|
66
|
+
nesser.wait()
|
67
|
+
```
|
68
|
+
|
69
|
+
This is called once per packet received - since many servers send the same
|
70
|
+
request several times, that has to be handled by the application. Since the
|
71
|
+
function starts a thread and returns instantly, `nesser.wait()` is necessary
|
72
|
+
so the program doesn't end until the thread does.
|
73
|
+
|
74
|
+
The transaction is an instance of [Nesser::Transaction](lib/nesser/transaction.rb).
|
75
|
+
Any methods of transaction that end with an exclamation mark
|
76
|
+
(`transaction.answer!()`, for example) will send a response and can only be
|
77
|
+
called once, after which the transaction is done and shouldn't be used anymore.
|
78
|
+
|
79
|
+
The request packet can be accessed via `transaction.request`, and the response
|
80
|
+
can be directly accessed (or changed, though I don't recommend that) via
|
81
|
+
`transaction.response` and `transaction.response=()`. Both are instances of
|
82
|
+
[Nesser::Packet](lib/nesser/packets/packet.rb). The response already has the
|
83
|
+
appropriate `trn_id` and `flags` and the `question`, so all you have to do is
|
84
|
+
add answers and send it off using `transaction.reply!()`.
|
85
|
+
|
86
|
+
A much easier way to reply to a request is to use one of the two helper
|
87
|
+
functions, `transaction.answer!()` or `transaction.error!()`. Each of these
|
88
|
+
will take the `transaction.response` packet, with whatever changes have been
|
89
|
+
made to it, add to it, and send it off.
|
90
|
+
|
91
|
+
`transaction.answer!()` takes an optional array of
|
92
|
+
[Nesser::Answer](lib/nesser/packets/answer.rb), adds them to the packet, then sends
|
93
|
+
it.
|
94
|
+
|
95
|
+
`transaction.error!()` takes a response code (see
|
96
|
+
[constants.rb](lib/nesser/packets/constants.rb) for the list), updates the
|
97
|
+
packet with that code, then sends it.
|
98
|
+
|
99
|
+
You'll rarely need to do anything with the transaction other than inspecting the
|
100
|
+
request and using one of those two functions to answer.
|
101
|
+
|
102
|
+
Here's a full example that replies to requests for test.com with '1.2.3.4' and
|
103
|
+
sends a "name not found" error for anything else:
|
104
|
+
|
105
|
+
```ruby
|
106
|
+
require 'socket'
|
107
|
+
require 'nesser'
|
108
|
+
|
109
|
+
# Create a UDP socket
|
110
|
+
s = UDPSocket.new()
|
111
|
+
|
112
|
+
# Create a new instance of Nesser to handle transactions
|
113
|
+
nesser = Nesser::Nesser.new(s: s) do |transaction|
|
114
|
+
# We only have an answer for 'test.com' (this is, after all, an example)
|
115
|
+
if transaction.request.questions[0].name == 'test.com'
|
116
|
+
# Create an A-type resource record pointing to 1.2.3.4. See README.md for
|
117
|
+
# details on how to create other record types
|
118
|
+
rr = Nesser::A.new(address: '1.2.3.4')
|
119
|
+
|
120
|
+
# Create an answer. The name will almost always be the same as the original
|
121
|
+
# name, but the type and cls don't necessarily have to match the request
|
122
|
+
# type (in this case, we don't even check what the request type was).
|
123
|
+
#
|
124
|
+
# You'll probably want the rr's type to match the type: argument. I'm not
|
125
|
+
# sure if it'll work otherwise, but the client it's sent to sure as heck
|
126
|
+
# won't know what to do with it. :)
|
127
|
+
answer = Nesser::Answer.new(
|
128
|
+
name: 'test.com',
|
129
|
+
type: Nesser::TYPE_A, # See constants.rb for other options
|
130
|
+
cls: Nesser::CLS_IN,
|
131
|
+
ttl: 1337,
|
132
|
+
rr: rr,
|
133
|
+
)
|
134
|
+
|
135
|
+
# The transaction's functions that end with '!' actually send the message -
|
136
|
+
# in this case, answer!() sends an array of the one answre that we created.
|
137
|
+
transaction.answer!([answer])
|
138
|
+
else
|
139
|
+
# Response NXDomain - aka, no such domain name - to everything other than
|
140
|
+
# 'test.com'.
|
141
|
+
transaction.error!(Nesser::RCODE_NAME_ERROR)
|
142
|
+
end
|
143
|
+
|
144
|
+
# Display the transaction
|
145
|
+
puts(transaction)
|
146
|
+
end
|
147
|
+
|
148
|
+
# Since Nesser::Nesser.new() runs in a new thread, we have to basically join
|
149
|
+
# the thread to prevent the program from ending
|
150
|
+
nesser.wait()
|
151
|
+
```
|
152
|
+
|
153
|
+
We currently support A, NS, CNAME, SOA, MX, TXT, and AAAA records. We can also
|
154
|
+
parse and send unknown types as well. You can find the definitions in
|
155
|
+
[rr_types.rb](lib/nesser/packets/rr_types.rb).
|
156
|
+
|
157
|
+
For quick reference:
|
158
|
+
|
159
|
+
* `a = Nesser::A.new(address: '1.2.3.4')`
|
160
|
+
* `ns = Nesser::NS.new(name: 'google.com')`
|
161
|
+
* `cname = Nesser::CNAME.new(name: 'google.com')`
|
162
|
+
* `soa = Nesser::SOA.new(primary: 'google.com', responsible: 'test.google.com', serial: 1, refresh: 2, retry_interval: 3, expire: 4, ttl: 5)`
|
163
|
+
* `mx = Nesser::MX.new(name: 'mail.google.com', preference: 10)`
|
164
|
+
* `txt = Nesser::TXT.new(data: 'hello this is data!')`
|
165
|
+
* `aaaa = Nesser::AAAA.new(address: '::1')`
|
166
|
+
* `unknown = Nesser::RRUnknown.new(type: 0x1337, data: 'datagoeshere')`
|
24
167
|
|
25
168
|
## Contributing
|
26
169
|
|
27
170
|
Bug reports and pull requests are welcome on GitHub at https://github.com/iagox86/nesser
|
28
171
|
|
172
|
+
Please try to follow my style as much as possible, and update test coverage
|
173
|
+
when necessary!
|
174
|
+
|
175
|
+
## Version history / changelog
|
176
|
+
|
177
|
+
* 0.0.1 - Test deploy
|
178
|
+
* 0.0.2 - Code complete
|
179
|
+
* 0.0.3 - First actual release
|
data/lib/nesser.rb
CHANGED
@@ -16,9 +16,9 @@
|
|
16
16
|
# There are two methods for using this library: as a client (to make a query)
|
17
17
|
# or as a server (to listen for queries and respond to them).
|
18
18
|
#
|
19
|
-
# To
|
20
|
-
#
|
21
|
-
#
|
19
|
+
# To avoid putting too much narrative in this header, I wrote full usage
|
20
|
+
# details in README.md in the root directory. Check that out for full usage
|
21
|
+
# details!
|
22
22
|
##
|
23
23
|
|
24
24
|
require 'ipaddr'
|
@@ -35,6 +35,35 @@ module Nesser
|
|
35
35
|
class Nesser
|
36
36
|
attr_reader :thread
|
37
37
|
|
38
|
+
##
|
39
|
+
# Create a new instance and start listening for requests in a new thread.
|
40
|
+
# Returns instantly (use the `wait()` method to pause until the thread is
|
41
|
+
# over (pretty much indefinitely).
|
42
|
+
#
|
43
|
+
# The `s` parameter should be an instance of `UDPSocket` in `socket`. The
|
44
|
+
# logger will default to an instance of `Nesser::Logger` if it's not
|
45
|
+
# specified, which simply logs to stdout.
|
46
|
+
#
|
47
|
+
# The other parameters are reasonably self explanatory.
|
48
|
+
#
|
49
|
+
# When you create an instance, you must also specify a proc:
|
50
|
+
#
|
51
|
+
# ```ruby
|
52
|
+
# Nesser::Nesser.new(s: s) do |transaction|
|
53
|
+
# # ...
|
54
|
+
# end
|
55
|
+
# ```
|
56
|
+
#
|
57
|
+
# Whenever a valid DNS message comes in, a new `Nesser::Transaction` is
|
58
|
+
# created and the proc is called with it (an invalid packet will be printed
|
59
|
+
# to the logger and discarded). It's up to the user to answer it using
|
60
|
+
# `transaction.answer!()`, `transaction.error!()`, etc. (see README.md for
|
61
|
+
# examples).
|
62
|
+
#
|
63
|
+
# If the handler function throws an Exception (most non-system errors),
|
64
|
+
# a SERVFAIL message will be returned automatically and the error will be
|
65
|
+
# logged to the logger.
|
66
|
+
##
|
38
67
|
def initialize(s:, logger: nil, host:"0.0.0.0", port:53)
|
39
68
|
@s = s
|
40
69
|
@s.bind(host, port)
|
@@ -42,48 +71,47 @@ module Nesser
|
|
42
71
|
@logger = (logger = logger || Logger.new())
|
43
72
|
|
44
73
|
@thread = Thread.new() do
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
end
|
74
|
+
loop do
|
75
|
+
# Grab all the data we can off the socket
|
76
|
+
data = @s.recvfrom(65536)
|
77
|
+
|
78
|
+
begin
|
79
|
+
# Data is an array where the first element is the actual data, and the second is the host/port
|
80
|
+
request = Packet.parse(data[0])
|
81
|
+
rescue DnsException => e
|
82
|
+
logger.error("Failed to parse the DNS packet: %s" % e.to_s())
|
83
|
+
next
|
84
|
+
rescue Exception => e
|
85
|
+
logger.error("Error: %s" % e.to_s())
|
86
|
+
logger.info(e.backtrace().join("\n"))
|
87
|
+
end
|
60
88
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
end
|
89
|
+
# Create a transaction object, which we can use to respond
|
90
|
+
transaction = Transaction.new(
|
91
|
+
s: @s,
|
92
|
+
request: request,
|
93
|
+
host: data[1][3],
|
94
|
+
port: data[1][1],
|
95
|
+
)
|
96
|
+
|
97
|
+
begin
|
98
|
+
proc.call(transaction)
|
99
|
+
rescue Exception => e
|
100
|
+
logger.error("Error thrown while processing the DNS packet: %s" % e.to_s())
|
101
|
+
logger.info(e.backtrace().join("\n"))
|
102
|
+
|
103
|
+
if transaction.open?()
|
104
|
+
transaction.error!(RCODE_SERVER_FAILURE)
|
78
105
|
end
|
79
106
|
end
|
80
|
-
ensure
|
81
|
-
@s.close()
|
82
107
|
end
|
83
108
|
end
|
84
109
|
end
|
85
110
|
|
86
|
-
|
111
|
+
##
|
112
|
+
# Kill the listener thread (once this is called, the class instance is
|
113
|
+
# worthless).
|
114
|
+
##
|
87
115
|
def stop()
|
88
116
|
if(@thread.nil?)
|
89
117
|
@logger.error("Tried to stop a listener that wasn't listening!")
|
@@ -94,8 +122,10 @@ module Nesser
|
|
94
122
|
@thread = nil
|
95
123
|
end
|
96
124
|
|
97
|
-
|
98
|
-
#
|
125
|
+
##
|
126
|
+
# Pauses as long as the listener thread is alive (generally, that means
|
127
|
+
# indefinitely).
|
128
|
+
##
|
99
129
|
def wait()
|
100
130
|
if(@thread.nil?)
|
101
131
|
@logger.error("Tried to wait on a Nesser instance that wasn't listening!")
|
@@ -105,7 +135,18 @@ module Nesser
|
|
105
135
|
@thread.join()
|
106
136
|
end
|
107
137
|
|
108
|
-
|
138
|
+
##
|
139
|
+
# Send a query.
|
140
|
+
#
|
141
|
+
# * `s`: an instance of `UDPSocket` from 'socket'.
|
142
|
+
# * `hostname`: the name being queried - eg, 'example.org'.
|
143
|
+
# * `server` and `port`: The upstream DNS server to query.
|
144
|
+
# * `type` and `cls`: typical DNS values, you can find a list of them in
|
145
|
+
# packets/constants.rb.
|
146
|
+
# * `timeout`: The number of seconds to wait for a response before giving up.
|
147
|
+
#
|
148
|
+
# Returns a Nesser::Packet (nesser/packets/packet.rb).
|
149
|
+
##
|
109
150
|
def self.query(s:, hostname:, server: '8.8.8.8', port: 53, type: TYPE_A, cls: CLS_IN, timeout: 3)
|
110
151
|
s = UDPSocket.new()
|
111
152
|
|
@@ -11,8 +11,16 @@
|
|
11
11
|
$LOAD_PATH.unshift File.expand_path('../../../', __FILE__)
|
12
12
|
|
13
13
|
require 'socket'
|
14
|
-
|
15
14
|
require 'nesser'
|
16
15
|
|
16
|
+
# Create a UDP socket
|
17
17
|
s = UDPSocket.new()
|
18
|
-
|
18
|
+
|
19
|
+
# Perform a query for 'google.com', of type ANY. I chose this because it has a
|
20
|
+
# wide array of varied answers.
|
21
|
+
#
|
22
|
+
# See packets/constants.rb for a full list of possible request types.
|
23
|
+
result = Nesser::Nesser.query(s: s, hostname: 'google.com', type: Nesser::TYPE_ANY)
|
24
|
+
|
25
|
+
# Print the result
|
26
|
+
puts result
|
@@ -13,21 +13,45 @@ $LOAD_PATH.unshift File.expand_path('../../../', __FILE__)
|
|
13
13
|
require 'socket'
|
14
14
|
require 'nesser'
|
15
15
|
|
16
|
+
# Create a UDP socket
|
16
17
|
s = UDPSocket.new()
|
17
18
|
|
19
|
+
# Create a new instance of Nesser to handle transactions
|
18
20
|
nesser = Nesser::Nesser.new(s: s) do |transaction|
|
19
|
-
|
20
|
-
|
21
|
+
# We only have an answer for 'test.com' (this is, after all, an example)
|
22
|
+
if transaction.request.questions[0].name == 'test.com'
|
23
|
+
# Create an A-type resource record pointing to 1.2.3.4. See README.md for
|
24
|
+
# details on how to create other record types
|
25
|
+
rr = Nesser::A.new(address: '1.2.3.4')
|
26
|
+
|
27
|
+
# Create an answer. The name will almost always be the same as the original
|
28
|
+
# name, but the type and cls don't necessarily have to match the request
|
29
|
+
# type (in this case, we don't even check what the request type was).
|
30
|
+
#
|
31
|
+
# You'll probably want the rr's type to match the type: argument. I'm not
|
32
|
+
# sure if it'll work otherwise, but the client it's sent to sure as heck
|
33
|
+
# won't know what to do with it. :)
|
34
|
+
answer = Nesser::Answer.new(
|
21
35
|
name: 'test.com',
|
22
|
-
type: Nesser::TYPE_A,
|
36
|
+
type: Nesser::TYPE_A, # See constants.rb for other options
|
23
37
|
cls: Nesser::CLS_IN,
|
24
38
|
ttl: 1337,
|
25
|
-
rr:
|
26
|
-
)
|
39
|
+
rr: rr,
|
40
|
+
)
|
41
|
+
|
42
|
+
# The transaction's functions that end with '!' actually send the message -
|
43
|
+
# in this case, answer!() sends an array of the one answre that we created.
|
44
|
+
transaction.answer!([answer])
|
27
45
|
else
|
46
|
+
# Response NXDomain - aka, no such domain name - to everything other than
|
47
|
+
# 'test.com'.
|
28
48
|
transaction.error!(Nesser::RCODE_NAME_ERROR)
|
29
49
|
end
|
50
|
+
|
51
|
+
# Display the transaction
|
30
52
|
puts(transaction)
|
31
53
|
end
|
32
54
|
|
55
|
+
# Since Nesser::Nesser.new() runs in a new thread, we have to basically join
|
56
|
+
# the thread to prevent the program from ending
|
33
57
|
nesser.wait()
|
data/lib/nesser/logger.rb
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
# Encoding: ASCII-8BIT
|
2
2
|
##
|
3
|
-
#
|
3
|
+
# logger.rb.rb
|
4
4
|
# Created June 20, 2017
|
5
5
|
# By Ron Bowes
|
6
6
|
#
|
7
7
|
# See LICENSE.md
|
8
8
|
#
|
9
|
-
#
|
9
|
+
# A super simple logger implementation.
|
10
10
|
##
|
11
11
|
|
12
12
|
module Nesser
|
@@ -6,15 +6,26 @@
|
|
6
6
|
#
|
7
7
|
# See: LICENSE.md
|
8
8
|
#
|
9
|
-
# A DNS answer. A DNS
|
10
|
-
#
|
11
|
-
#
|
9
|
+
# A DNS answer. A DNS packet contains zero or more Answer records (defined in
|
10
|
+
# the 'ancount' value in the header). An answer contains the name of the domain
|
11
|
+
# followed by a resource record.
|
12
12
|
##
|
13
13
|
|
14
14
|
module Nesser
|
15
15
|
class Answer
|
16
16
|
attr_reader :name, :type, :cls, :ttl, :rr
|
17
17
|
|
18
|
+
##
|
19
|
+
# Create an answer.
|
20
|
+
#
|
21
|
+
# * `name`: Should match the name from the question.
|
22
|
+
# * `type`: The type of resource record (eg, TYPE_A, TYPE_NS, etc). You can
|
23
|
+
# find a list of types in constants.rb.
|
24
|
+
# * `cls`: The DNS class - this will almost certainly be `Nesser::CLS_IN`,
|
25
|
+
# since 'IN' means 'Internet'. I'm not familiar with any others.
|
26
|
+
# * `ttl`: The time-to-live for the response, in seconds
|
27
|
+
# * `rr`: A resource record - you can find these classes in rr_types.rb.
|
28
|
+
##
|
18
29
|
def initialize(name:, type:, cls:, ttl:, rr:)
|
19
30
|
@name = name
|
20
31
|
@type = type
|
@@ -23,6 +34,11 @@ module Nesser
|
|
23
34
|
@rr = rr
|
24
35
|
end
|
25
36
|
|
37
|
+
##
|
38
|
+
# Parse an answer from a DNS packet. You won't likely need to use this, but
|
39
|
+
# if you do, it's necessary to use a Nesser::Unpacker that's loaded with the
|
40
|
+
# full DNS message (due to in-packet pointers).
|
41
|
+
##
|
26
42
|
def self.unpack(unpacker)
|
27
43
|
name = unpacker.unpack_name()
|
28
44
|
type, cls, ttl = unpacker.unpack("nnN")
|
@@ -55,6 +71,10 @@ module Nesser
|
|
55
71
|
)
|
56
72
|
end
|
57
73
|
|
74
|
+
##
|
75
|
+
# Pack this into a Nesser::Packer in preparation for being sent over the
|
76
|
+
# wire.
|
77
|
+
##
|
58
78
|
def pack(packer)
|
59
79
|
# The name is echoed back
|
60
80
|
packer.pack_name(@name)
|
@@ -5,6 +5,9 @@
|
|
5
5
|
# By Ron Bowes
|
6
6
|
#
|
7
7
|
# See: LICENSE.md
|
8
|
+
#
|
9
|
+
# A bunch of constants used with DNS - some of them for the protocol, others for
|
10
|
+
# error checking the outgoing messages.
|
8
11
|
##
|
9
12
|
|
10
13
|
module Nesser
|
@@ -21,6 +24,8 @@ module Nesser
|
|
21
24
|
MAX_SEGMENT_LENGTH = 63
|
22
25
|
MAX_TOTAL_LENGTH = 253
|
23
26
|
|
27
|
+
# DNS classes - I only define IN (Internet) because I don't even know what to
|
28
|
+
# do with others.
|
24
29
|
CLS_IN = 0x0001 # Internet
|
25
30
|
CLSES = {
|
26
31
|
CLS_IN => "IN",
|
@@ -42,7 +47,6 @@ module Nesser
|
|
42
47
|
RCODE_NAME_ERROR = 0x0003 # :NXDomain
|
43
48
|
RCODE_NOT_IMPLEMENTED = 0x0004
|
44
49
|
RCODE_REFUSED = 0x0005
|
45
|
-
|
46
50
|
RCODES = {
|
47
51
|
RCODE_SUCCESS => ":NoError (RCODE_SUCCESS)",
|
48
52
|
RCODE_FORMAT_ERROR => ":FormErr (RCODE_FORMAT_ERROR)",
|
@@ -56,7 +60,6 @@ module Nesser
|
|
56
60
|
OPCODE_QUERY = 0x0000
|
57
61
|
OPCODE_IQUERY = 0x0800
|
58
62
|
OPCODE_STATUS = 0x1000
|
59
|
-
|
60
63
|
OPCODES = {
|
61
64
|
OPCODE_QUERY => "OPCODE_QUERY",
|
62
65
|
OPCODE_IQUERY => "OPCODE_IQUERY",
|
@@ -72,7 +75,6 @@ module Nesser
|
|
72
75
|
TYPE_TXT = 0x0010
|
73
76
|
TYPE_AAAA = 0x001c
|
74
77
|
TYPE_ANY = 0x00FF
|
75
|
-
|
76
78
|
TYPES = {
|
77
79
|
TYPE_A => "A",
|
78
80
|
TYPE_NS => "NS",
|
@@ -24,11 +24,18 @@ module Nesser
|
|
24
24
|
@segment_cache = {}
|
25
25
|
end
|
26
26
|
|
27
|
+
##
|
28
|
+
# This is simply a wrapper around String.pack() - it's for packing perfectly
|
29
|
+
# ordinary data into a DNS packet.
|
30
|
+
##
|
27
31
|
public
|
28
32
|
def pack(format, *data)
|
29
33
|
@data += data.pack(format)
|
30
34
|
end
|
31
35
|
|
36
|
+
##
|
37
|
+
# Sanity check a name (length, legal characters, etc).
|
38
|
+
##
|
32
39
|
private
|
33
40
|
def validate!(name)
|
34
41
|
if name.chars.detect { |ch| !LEGAL_CHARACTERS.include?(ch) }
|
@@ -44,9 +51,37 @@ module Nesser
|
|
44
51
|
end
|
45
52
|
end
|
46
53
|
|
47
|
-
|
48
|
-
#
|
49
|
-
#
|
54
|
+
##
|
55
|
+
# This function is sort of the point of this class's existance.
|
56
|
+
#
|
57
|
+
# You pass in a typical DNS name, such as "google.com". If that name
|
58
|
+
# doesn't appear in the packet yet, it's simply encoded with length-
|
59
|
+
# prefixed segments - "\x06google\x03com\x00".
|
60
|
+
#
|
61
|
+
# However, if all or part of the name already exist in the packet, this will
|
62
|
+
# save packet space by re-using those segments. For example, let's say that
|
63
|
+
# "google.com" exists 0x0c bytes into the packet (which it normally does).
|
64
|
+
# In that case, instead of including "\x06google\x03com\x00" a second time,
|
65
|
+
# it will simply encode the offset with "\xc0" in front - "\xc0\x0c".
|
66
|
+
#
|
67
|
+
# Let's say that later in the packet, we have "www.google.com". That string
|
68
|
+
# as a whole hasn't appeared yet, but "google.com" appeared at offset 0x0c.
|
69
|
+
# It will then be encoded "\x03www\xc0\x0c" - the longest possible segment
|
70
|
+
# is encoded.
|
71
|
+
#
|
72
|
+
# This logic is somewhat complicated, but this function seems to work
|
73
|
+
# pretty well. :)
|
74
|
+
#
|
75
|
+
# * `name`: The name to encode, as a normal dotted string.
|
76
|
+
# * `dry_run`: If set to true, don't actually "write" the name. This is
|
77
|
+
# unfortunately needed to check the size of something that *would* be
|
78
|
+
# encoded, since we occasionally need the length written to the buffer
|
79
|
+
# before the name.
|
80
|
+
# * `compress`: If set (which it always probably should be), will attempt
|
81
|
+
# to do the compression ("\xc0") stuff discussed earlier.
|
82
|
+
#
|
83
|
+
# Returns the actual length of the name.
|
84
|
+
##
|
50
85
|
public
|
51
86
|
def pack_name(name, dry_run:false, compress:true)
|
52
87
|
length = 0
|
@@ -89,6 +124,9 @@ module Nesser
|
|
89
124
|
return length
|
90
125
|
end
|
91
126
|
|
127
|
+
##
|
128
|
+
# Retrieve the buffer as a string.
|
129
|
+
##
|
92
130
|
public
|
93
131
|
def get()
|
94
132
|
return @data
|
@@ -5,6 +5,9 @@
|
|
5
5
|
# By Ron Bowes
|
6
6
|
#
|
7
7
|
# See: LICENSE.md
|
8
|
+
#
|
9
|
+
# An implementation of a DNS packet, including encoding and parsing. This covers
|
10
|
+
# the header and questions/answers.
|
8
11
|
##
|
9
12
|
|
10
13
|
require 'nesser/dns_exception'
|
@@ -19,6 +22,25 @@ module Nesser
|
|
19
22
|
class Packet
|
20
23
|
attr_accessor :trn_id, :qr, :opcode, :flags, :rcode, :questions, :answers
|
21
24
|
|
25
|
+
##
|
26
|
+
# Create a new packet.
|
27
|
+
#
|
28
|
+
# * `trn_id`: The 16-bit transaction id - should be random for clients, and
|
29
|
+
# match the incoming trn_id for servers.
|
30
|
+
# * `qr`: QR_QUERY or QR_RESPONSE.
|
31
|
+
# * `opcode`: will likely be OPCODE_QUERY.
|
32
|
+
# * `flags`: A combination of the flags FLAG_AA, FLAG_TC, FLAG_RD, and
|
33
|
+
# FLAG_RA.
|
34
|
+
# * `rcode`: A response code - RCODE_SUCCESS for requests or good responses,
|
35
|
+
# or an error code (RCODE_NAME_ERROR, RCODE_SERVER_FAILURE, etc) for
|
36
|
+
# errors. Find the list in constants.rb.
|
37
|
+
# * `questions`: An array (although most implementations only handle exactly
|
38
|
+
# one) of questions - Nesser::Question.
|
39
|
+
# * `answers`: An array of zero or more ansewrs - Nesser::Answer.
|
40
|
+
#
|
41
|
+
# We don't support authority or additional records right now (or perhaps
|
42
|
+
# ever).
|
43
|
+
##
|
22
44
|
def initialize(trn_id:, qr:, opcode:, flags:, rcode:, questions:[], answers:[])
|
23
45
|
@trn_id = trn_id
|
24
46
|
@qr = qr
|
@@ -33,6 +55,9 @@ module Nesser
|
|
33
55
|
@answers = answers
|
34
56
|
end
|
35
57
|
|
58
|
+
##
|
59
|
+
# Add a Nesser::Question.
|
60
|
+
##
|
36
61
|
def add_question(question)
|
37
62
|
if !question.is_a?(Question)
|
38
63
|
raise(DnsException, "Questions must be of type Question!")
|
@@ -41,6 +66,9 @@ module Nesser
|
|
41
66
|
@questions << question
|
42
67
|
end
|
43
68
|
|
69
|
+
##
|
70
|
+
# Add a Nesser::Answer.
|
71
|
+
##
|
44
72
|
def add_answer(answer)
|
45
73
|
if !answer.is_a?(Answer)
|
46
74
|
raise(DnsException, "Questions must be of type Question!")
|
@@ -49,6 +77,12 @@ module Nesser
|
|
49
77
|
@answers << answer
|
50
78
|
end
|
51
79
|
|
80
|
+
##
|
81
|
+
# Parse an incoming DNS packet. Takes a byte string as an argument, and
|
82
|
+
# returns an instance of Nesser::Packet - this class.
|
83
|
+
#
|
84
|
+
# Raises a DnsException if things go badly.
|
85
|
+
##
|
52
86
|
def self.parse(data)
|
53
87
|
unpacker = Unpacker.new(data)
|
54
88
|
trn_id, full_flags, qdcount, ancount, _, _ = unpacker.unpack("nnnnnn")
|
@@ -81,6 +115,11 @@ module Nesser
|
|
81
115
|
return packet
|
82
116
|
end
|
83
117
|
|
118
|
+
##
|
119
|
+
# Convert a query packet to the corresponding answer - the trn_id is copied,
|
120
|
+
# the qr is changed to QR_RESPONSE, the opcode and flags are updated, and
|
121
|
+
# the question from the query is added.
|
122
|
+
##
|
84
123
|
def answer(answers:[], question:nil)
|
85
124
|
question = question || @questions[0]
|
86
125
|
|
@@ -95,6 +134,10 @@ module Nesser
|
|
95
134
|
)
|
96
135
|
end
|
97
136
|
|
137
|
+
##
|
138
|
+
# Convert a query packet to the corresponding error answer with the given
|
139
|
+
# rcode (see constants.rb for a list of rcodes).
|
140
|
+
##
|
98
141
|
def error(rcode:, question:nil)
|
99
142
|
question = question || @questions[0]
|
100
143
|
|
@@ -109,6 +152,9 @@ module Nesser
|
|
109
152
|
)
|
110
153
|
end
|
111
154
|
|
155
|
+
##
|
156
|
+
# Serialize the packet to an array of bytes.
|
157
|
+
##
|
112
158
|
def to_bytes()
|
113
159
|
packer = Packer.new()
|
114
160
|
|
@@ -6,20 +6,29 @@
|
|
6
6
|
#
|
7
7
|
# See: LICENSE.md
|
8
8
|
#
|
9
|
-
# This defines a DNS question.
|
10
|
-
# and
|
11
|
-
#
|
9
|
+
# This defines a DNS question. Typically, a single question is sent in both
|
10
|
+
# incoming and outgoing packets. Most implementations can't handle any other
|
11
|
+
# situation.
|
12
12
|
##
|
13
13
|
module Nesser
|
14
14
|
class Question
|
15
15
|
attr_reader :name, :type, :cls
|
16
16
|
|
17
|
+
##
|
18
|
+
# Create a question.
|
19
|
+
#
|
20
|
+
# The name is a typical dotted name, like "google.com". The type and cls
|
21
|
+
# are DNS-specific values that can be found in constants.rb.
|
22
|
+
##
|
17
23
|
def initialize(name:, type:, cls:)
|
18
24
|
@name = name
|
19
25
|
@type = type
|
20
26
|
@cls = cls
|
21
27
|
end
|
22
28
|
|
29
|
+
##
|
30
|
+
# Parse a question from a DNS packet.
|
31
|
+
##
|
23
32
|
def self.unpack(unpacker)
|
24
33
|
name = unpacker.unpack_name()
|
25
34
|
type, cls = unpacker.unpack("nn")
|
@@ -27,6 +36,9 @@ module Nesser
|
|
27
36
|
return self.new(name: name, type: type, cls: cls)
|
28
37
|
end
|
29
38
|
|
39
|
+
##
|
40
|
+
# Serialize the question.
|
41
|
+
##
|
30
42
|
def pack(packer)
|
31
43
|
packer.pack_name(@name)
|
32
44
|
packer.pack('nn', type, cls)
|
@@ -5,6 +5,16 @@
|
|
5
5
|
# By Ron Bowes
|
6
6
|
#
|
7
7
|
# See: LICENSE.md
|
8
|
+
#
|
9
|
+
# These are implementations of resource records - ie, the records found in a DNS
|
10
|
+
# answer that contain, for example, an ip address, a mail exchange, etc.
|
11
|
+
#
|
12
|
+
# Every one of these classes follows the same paradigm (I guess in Java you'd
|
13
|
+
# say they implement the same interface). They can be initialized with
|
14
|
+
# type-dependent parameters; they implement `self.unpack()`, which takes a
|
15
|
+
# `Nesser::Unpacker` and returns an instance of itself; they implement `pack()`,
|
16
|
+
# which serialized itself into a `Nesser::Packer` instance; and they have a
|
17
|
+
# `to_s()` function, which stringifies the record in a fairly user-friendly way.
|
8
18
|
##
|
9
19
|
|
10
20
|
require 'ipaddr'
|
@@ -15,6 +25,9 @@ require 'nesser/packets/packer'
|
|
15
25
|
require 'nesser/packets/unpacker'
|
16
26
|
|
17
27
|
module Nesser
|
28
|
+
##
|
29
|
+
# An A record is a typical IPv4 address - eg, '1.2.3.4'.
|
30
|
+
##
|
18
31
|
class A
|
19
32
|
attr_accessor :address
|
20
33
|
|
@@ -54,6 +67,9 @@ module Nesser
|
|
54
67
|
end
|
55
68
|
end
|
56
69
|
|
70
|
+
##
|
71
|
+
# Nameserver record: eg, 'ns1.google.com'.
|
72
|
+
##
|
57
73
|
class NS
|
58
74
|
attr_accessor :name
|
59
75
|
|
@@ -80,6 +96,9 @@ module Nesser
|
|
80
96
|
end
|
81
97
|
end
|
82
98
|
|
99
|
+
##
|
100
|
+
# Alias record: eg, 'www.google.com'->'google.com'.
|
101
|
+
##
|
83
102
|
class CNAME
|
84
103
|
attr_accessor :name
|
85
104
|
|
@@ -105,6 +124,9 @@ module Nesser
|
|
105
124
|
end
|
106
125
|
end
|
107
126
|
|
127
|
+
##
|
128
|
+
# Statement of authority record.
|
129
|
+
##
|
108
130
|
class SOA
|
109
131
|
attr_accessor :primary, :responsible, :serial, :refresh, :retry_interval, :expire, :ttl
|
110
132
|
|
@@ -155,6 +177,9 @@ module Nesser
|
|
155
177
|
end
|
156
178
|
end
|
157
179
|
|
180
|
+
##
|
181
|
+
# Mail exchange record - eg, 'mail.google.com' 10.
|
182
|
+
##
|
158
183
|
class MX
|
159
184
|
attr_accessor :preference, :name
|
160
185
|
|
@@ -188,6 +213,10 @@ module Nesser
|
|
188
213
|
end
|
189
214
|
end
|
190
215
|
|
216
|
+
##
|
217
|
+
# A TXT record, with is simply binary data (except on some libraries where it
|
218
|
+
# can't contain a NUL byte).
|
219
|
+
##
|
191
220
|
class TXT
|
192
221
|
attr_accessor :data
|
193
222
|
|
@@ -223,6 +252,9 @@ module Nesser
|
|
223
252
|
end
|
224
253
|
end
|
225
254
|
|
255
|
+
##
|
256
|
+
# IPv6 record, eg, "::1".
|
257
|
+
##
|
226
258
|
class AAAA
|
227
259
|
attr_accessor :address
|
228
260
|
|
@@ -264,6 +296,9 @@ module Nesser
|
|
264
296
|
end
|
265
297
|
end
|
266
298
|
|
299
|
+
##
|
300
|
+
# An unknown record type.
|
301
|
+
##
|
267
302
|
class RRUnknown
|
268
303
|
attr_reader :type, :data
|
269
304
|
def initialize(type:, data:)
|
@@ -22,12 +22,21 @@ module Nesser
|
|
22
22
|
class Unpacker
|
23
23
|
attr_accessor :data, :offset
|
24
24
|
|
25
|
+
##
|
26
|
+
# Create a new unpacker with a string (the string must be the full DNS
|
27
|
+
# request, starting at the `trn_id` - otherwise, unpacking compressed
|
28
|
+
# fields won't work properly!
|
29
|
+
##
|
25
30
|
public
|
26
31
|
def initialize(data)
|
27
32
|
@data = data
|
28
33
|
@offset = 0
|
29
34
|
end
|
30
35
|
|
36
|
+
##
|
37
|
+
# Does some basic error checking to make sure we didn't run off the end of
|
38
|
+
# packet - doesn't catch every case, though.
|
39
|
+
##
|
31
40
|
private
|
32
41
|
def _verify_results(results)
|
33
42
|
# If there's at least one nil included in our results, bad stuff happened
|
@@ -36,8 +45,11 @@ module Nesser
|
|
36
45
|
end
|
37
46
|
end
|
38
47
|
|
39
|
-
|
40
|
-
#
|
48
|
+
##
|
49
|
+
# Unpack from the string, exactly like the normal `String.unpack()` method
|
50
|
+
# in Ruby, except that a pointer offset into the string is maintained and
|
51
|
+
# updated (which is required for unpacking names).
|
52
|
+
##
|
41
53
|
public
|
42
54
|
def unpack(format)
|
43
55
|
if @offset >= @data.length
|
@@ -53,6 +65,13 @@ module Nesser
|
|
53
65
|
return *results
|
54
66
|
end
|
55
67
|
|
68
|
+
##
|
69
|
+
# Unpack a single element from the buffer and return it (this is a simple
|
70
|
+
# little utility function that I wrote to save myself time).
|
71
|
+
#
|
72
|
+
# The `format` argument works exactly like in `String.unpack()`, but only
|
73
|
+
# one element can be given.
|
74
|
+
##
|
56
75
|
public
|
57
76
|
def unpack_one(format)
|
58
77
|
results = unpack(format)
|
@@ -65,9 +84,10 @@ module Nesser
|
|
65
84
|
return results.pop()
|
66
85
|
end
|
67
86
|
|
68
|
-
|
69
|
-
#
|
70
|
-
# unpacking names.
|
87
|
+
##
|
88
|
+
# Temporarily changes the offset that we're reading from, runs the given
|
89
|
+
# block, then changes it back. This is used internally while unpacking names.
|
90
|
+
##
|
71
91
|
private
|
72
92
|
def _with_offset(offset)
|
73
93
|
old_offset = @offset
|
@@ -76,11 +96,26 @@ module Nesser
|
|
76
96
|
@offset = old_offset
|
77
97
|
end
|
78
98
|
|
79
|
-
|
80
|
-
#
|
81
|
-
#
|
82
|
-
#
|
83
|
-
#
|
99
|
+
##
|
100
|
+
# Unpack a name from the packet and convert it into a standard dotted name
|
101
|
+
# that we all understand.
|
102
|
+
#
|
103
|
+
# At the simplest, names are encoded as length-prefixed blocks. For example,
|
104
|
+
# "google.com" is encoded as "\x06google\x03com\x00".
|
105
|
+
#
|
106
|
+
# More complex, however, is that all or part of a name can be replaced with
|
107
|
+
# "\xc0" followed by an offset into the packet where the remainder of the
|
108
|
+
# name (or the full name) can be found. For example, if
|
109
|
+
# "\x06google\x03com\x00" is found at index 0x0c (which it frequently is),
|
110
|
+
# then "www.google.com" can be encoded as "\x03www\xc0\x0c". In other words,
|
111
|
+
# "www" followed by the rest of the name at offset 0x0c".
|
112
|
+
#
|
113
|
+
# The point of this class is that that situation is handled as cleanly as
|
114
|
+
# possible.
|
115
|
+
#
|
116
|
+
# The `depth` argument is used internally for recursion, the default value
|
117
|
+
# of 0 should be used externally.
|
118
|
+
##
|
84
119
|
public
|
85
120
|
def unpack_name(depth:0)
|
86
121
|
segments = []
|
data/lib/nesser/transaction.rb
CHANGED
@@ -6,27 +6,40 @@
|
|
6
6
|
#
|
7
7
|
# See: LICENSE.md
|
8
8
|
#
|
9
|
-
# When a request comes in, a transaction is created
|
10
|
-
#
|
11
|
-
# future.
|
9
|
+
# When a request comes in, a transaction is created to represent it. The
|
10
|
+
# transaction can be used to respond to the request at any point in the
|
11
|
+
# future - the trn_id and socket and stuff are all set up. Though, keep in
|
12
|
+
# mind, most clients will only wait like 3 seconds, so responding at *any*
|
13
|
+
# point, while technically true, isn't really a useful distinction.
|
12
14
|
#
|
13
|
-
# Any methods with a bang ('!') in
|
15
|
+
# Any methods with a bang ('!') in their name will send the response back to the
|
14
16
|
# requester. Only one bang method can be called, any subsequent calls will
|
15
17
|
# throw an exception.
|
18
|
+
#
|
19
|
+
# You'll almost always want to use either `transaction.answer!()` or
|
20
|
+
# `transaction.error!()`. While it's possible to change and/or add to
|
21
|
+
# `transaction.response` and send it with `transaction.reply!()`, that's more
|
22
|
+
# complex and generally not needed.
|
16
23
|
##
|
24
|
+
|
17
25
|
module Nesser
|
18
26
|
class Transaction
|
19
|
-
attr_reader :
|
27
|
+
attr_reader :request, :sent
|
28
|
+
attr_accessor :response
|
20
29
|
|
30
|
+
##
|
31
|
+
# Create a new instance of Transaction. This is used internally - it's
|
32
|
+
# unlikely you'll ever need to create an instance.
|
33
|
+
##
|
21
34
|
public
|
22
|
-
def initialize(s:,
|
35
|
+
def initialize(s:, request:, host:, port:)
|
23
36
|
@s = s
|
24
|
-
@
|
37
|
+
@request = request
|
25
38
|
@host = host
|
26
39
|
@port = port
|
27
40
|
@sent = false
|
28
41
|
|
29
|
-
@
|
42
|
+
@response = request.answer()
|
30
43
|
end
|
31
44
|
|
32
45
|
private
|
@@ -36,25 +49,41 @@ module Nesser
|
|
36
49
|
end
|
37
50
|
end
|
38
51
|
|
52
|
+
##
|
53
|
+
# Check whether or not the transaction has been sent already.
|
54
|
+
##
|
39
55
|
public
|
40
56
|
def open?()
|
41
57
|
return !@sent
|
42
58
|
end
|
43
59
|
|
60
|
+
##
|
61
|
+
# Reply with zero or more answers, specified in the array.
|
62
|
+
#
|
63
|
+
# Only one "bang function" can be called per transaction, subsequent calls
|
64
|
+
# will throw an exception.
|
65
|
+
##
|
44
66
|
public
|
45
67
|
def answer!(answers=[])
|
46
68
|
answers.each do |answer|
|
47
|
-
@
|
69
|
+
@response.add_answer(answer)
|
48
70
|
end
|
49
71
|
|
50
72
|
reply!()
|
51
73
|
end
|
52
74
|
|
75
|
+
##
|
76
|
+
# Reply with an error defined by rcode (you can find a full list in
|
77
|
+
# packets/constants.rb).
|
78
|
+
#
|
79
|
+
# Only one "bang function" can be called per transaction, subsequent calls
|
80
|
+
# will throw an exception.
|
81
|
+
##
|
53
82
|
public
|
54
83
|
def error!(rcode)
|
55
84
|
not_sent!()
|
56
85
|
|
57
|
-
@
|
86
|
+
@response.rcode = rcode
|
58
87
|
reply!()
|
59
88
|
end
|
60
89
|
|
@@ -88,11 +117,19 @@ module Nesser
|
|
88
117
|
# @sent = true
|
89
118
|
# end
|
90
119
|
|
91
|
-
|
120
|
+
##
|
121
|
+
# Reply with the response packet, in whatever state it's in. While this is
|
122
|
+
# public and gives you find control over the packet being sent back,
|
123
|
+
# realistically answer!() and error!() are probably all you'll need. Only
|
124
|
+
# use this is those won't work for whatever reason.
|
125
|
+
#
|
126
|
+
# Only one "bang function" can be called per transaction, subsequent calls
|
127
|
+
# will throw an exception.
|
128
|
+
public
|
92
129
|
def reply!()
|
93
130
|
not_sent!()
|
94
131
|
|
95
|
-
@s.send(@
|
132
|
+
@s.send(@response.to_bytes(), 0, @host, @port)
|
96
133
|
@sent = true
|
97
134
|
end
|
98
135
|
|
@@ -102,13 +139,14 @@ module Nesser
|
|
102
139
|
|
103
140
|
result << '== Nesser (DNS) Transaction =='
|
104
141
|
result << '-- Request --'
|
105
|
-
result << @
|
142
|
+
result << @request.to_s()
|
106
143
|
if !sent()
|
107
144
|
result << '-- Response [not sent yet] --'
|
108
145
|
else
|
109
146
|
result << '-- Response [sent] --'
|
110
147
|
end
|
111
|
-
result << @
|
148
|
+
result << @response.to_s()
|
149
|
+
result << ''
|
112
150
|
|
113
151
|
return result.join("\n")
|
114
152
|
end
|
data/lib/nesser/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
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.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- iagox86
|
@@ -76,6 +76,7 @@ files:
|
|
76
76
|
- ".gitignore"
|
77
77
|
- ".travis.yml"
|
78
78
|
- Gemfile
|
79
|
+
- LICENSE.md
|
79
80
|
- README.md
|
80
81
|
- Rakefile
|
81
82
|
- bin/console
|