dnsruby 1.57.0 → 1.58.0
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/.gitignore +1 -2
- data/.travis.yml +1 -1
- data/README.md +2 -2
- data/RELEASE_NOTES.md +16 -0
- data/Rakefile +2 -0
- data/dnsruby.gemspec +2 -0
- data/lib/dnsruby.rb +5 -0
- data/lib/dnsruby/bit_mapping.rb +138 -0
- data/lib/dnsruby/bitmap.rb +108 -0
- data/lib/dnsruby/message/decoder.rb +90 -80
- data/lib/dnsruby/message/message.rb +16 -3
- data/lib/dnsruby/message/section.rb +3 -3
- data/lib/dnsruby/name.rb +2 -2
- data/lib/dnsruby/packet_sender.rb +73 -1
- data/lib/dnsruby/resolv.rb +56 -42
- data/lib/dnsruby/resolver.rb +95 -73
- data/lib/dnsruby/resource/GPOS.rb +9 -3
- data/lib/dnsruby/resource/HIP.rb +1 -1
- data/lib/dnsruby/resource/IN.rb +3 -1
- data/lib/dnsruby/resource/NAPTR.rb +2 -2
- data/lib/dnsruby/resource/NSEC3.rb +2 -2
- data/lib/dnsruby/resource/NXT.rb +302 -0
- data/lib/dnsruby/resource/OPT.rb +2 -2
- data/lib/dnsruby/resource/TXT.rb +2 -2
- data/lib/dnsruby/resource/generic.rb +1 -0
- data/lib/dnsruby/resource/type_bitmap.rb +0 -0
- data/lib/dnsruby/select_thread.rb +174 -83
- data/lib/dnsruby/single_resolver.rb +2 -2
- data/lib/dnsruby/version.rb +1 -1
- data/lib/dnsruby/zone_reader.rb +19 -9
- data/lib/dnsruby/zone_transfer.rb +1 -1
- data/test/spec_helper.rb +9 -1
- data/test/tc_axfr.rb +17 -4
- data/test/tc_gpos.rb +3 -3
- data/test/tc_message.rb +7 -1
- data/test/tc_nxt.rb +192 -0
- data/test/tc_recur.rb +2 -1
- data/test/tc_resolv.rb +73 -0
- data/test/tc_resolver.rb +22 -4
- data/test/tc_rr-opt.rb +9 -8
- data/test/tc_rr.rb +22 -0
- data/test/tc_single_resolver.rb +15 -15
- data/test/tc_soak.rb +154 -65
- data/test/tc_soak_base.rb +15 -15
- data/test/tc_tcp.rb +1 -1
- data/test/tc_tcp_pipelining.rb +203 -0
- data/test/tc_zone_reader.rb +73 -0
- data/test/test_dnsserver.rb +208 -0
- data/test/test_utils.rb +49 -0
- data/test/ts_offline.rb +59 -41
- data/test/ts_online.rb +92 -63
- metadata +40 -3
- data/test/tc_dnsruby.rb +0 -51
data/test/tc_soak_base.rb
CHANGED
@@ -20,25 +20,25 @@ class TestSoakBase # < Minitest::Test
|
|
20
20
|
include Dnsruby
|
21
21
|
Rrs = [
|
22
22
|
{
|
23
|
-
:type
|
24
|
-
:name
|
25
|
-
:address
|
23
|
+
:type => Types.A,
|
24
|
+
:name => 'ns1.google.com.',
|
25
|
+
:address => '10.0.1.128'
|
26
26
|
},
|
27
27
|
{
|
28
|
-
:type
|
29
|
-
:name
|
30
|
-
:exchange
|
31
|
-
:preference
|
28
|
+
:type => Types::MX,
|
29
|
+
:name => 'ns1.google.com.',
|
30
|
+
:exchange => 'ns1.google.com.',
|
31
|
+
:preference => 10
|
32
32
|
},
|
33
33
|
{
|
34
|
-
:type
|
35
|
-
:name
|
36
|
-
:domainname
|
34
|
+
:type => 'CNAME',
|
35
|
+
:name => 'ns1.google.com.',
|
36
|
+
:domainname => 'a.t.dnsruby.validation-test-servers.nominet.org.uk'
|
37
37
|
},
|
38
38
|
{
|
39
|
-
:type
|
40
|
-
:name
|
41
|
-
:strings
|
39
|
+
:type => Types.TXT,
|
40
|
+
:name => 'ns1.google.com.',
|
41
|
+
:strings => ['Net-DNS']
|
42
42
|
}
|
43
43
|
]
|
44
44
|
|
@@ -92,7 +92,7 @@ class TestSoakBase # < Minitest::Test
|
|
92
92
|
assert(num_in_progress==0)
|
93
93
|
stop=Time.now
|
94
94
|
time_taken=stop-start
|
95
|
-
|
95
|
+
puts "Query count : #{num_sent}, #{timed_out} timed out. #{time_taken} time taken"
|
96
96
|
assert(timed_out < num_sent * 0.1, "#{timed_out} of #{num_sent} timed out!")
|
97
97
|
end
|
98
98
|
|
@@ -143,7 +143,7 @@ class TestSoakBase # < Minitest::Test
|
|
143
143
|
assert(num_in_progress==0)
|
144
144
|
stop=Time.now
|
145
145
|
time_taken=stop-start
|
146
|
-
|
146
|
+
puts "Query count : #{num_sent}, #{timed_out} timed out, #{error_count} other errors. #{time_taken} time taken"
|
147
147
|
assert(timed_out < num_sent * 0.1, "#{timed_out} of #{num_sent} timed out!")
|
148
148
|
assert(error_count == 0)
|
149
149
|
end
|
data/test/tc_tcp.rb
CHANGED
@@ -0,0 +1,203 @@
|
|
1
|
+
# --
|
2
|
+
# Copyright 2015 Verisign
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
# ++
|
16
|
+
|
17
|
+
require_relative 'spec_helper'
|
18
|
+
require_relative 'test_dnsserver'
|
19
|
+
|
20
|
+
# The TCPPipeliningServer links our TCPPipeliningHandler on
|
21
|
+
# the loopback interface.
|
22
|
+
class TCPPipeliningServer < RubyDNS::RuleBasedServer
|
23
|
+
|
24
|
+
PORT = 53937
|
25
|
+
IP = '127.0.0.1'
|
26
|
+
|
27
|
+
@@stats = Stats.new
|
28
|
+
|
29
|
+
def self.stats
|
30
|
+
@@stats
|
31
|
+
end
|
32
|
+
|
33
|
+
def run
|
34
|
+
fire(:setup)
|
35
|
+
|
36
|
+
link TCPPipeliningHandler.new(self,
|
37
|
+
IP,
|
38
|
+
PORT,
|
39
|
+
TCPPipeliningHandler::DEFAULT_MAX_REQUESTS,
|
40
|
+
TCPPipeliningHandler::DEFAULT_TIMEOUT)
|
41
|
+
|
42
|
+
fire(:start)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class TestTCPPipelining < Minitest::Test
|
47
|
+
|
48
|
+
QUERIES = %w(psi.net passport.net verisigninc.com google.com yahoo.com apple.com)
|
49
|
+
|
50
|
+
class << self
|
51
|
+
attr_accessor :query_id
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.init
|
55
|
+
unless @initialized
|
56
|
+
Celluloid.boot
|
57
|
+
# By default, Celluloid logs output to console. Use Dnsruby.log instead
|
58
|
+
Celluloid.logger = Dnsruby.log
|
59
|
+
@initialized = true
|
60
|
+
@query_id = 0
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def setup
|
65
|
+
self.class.init
|
66
|
+
@@upstream ||= RubyDNS::Resolver.new([
|
67
|
+
[:udp, '193.0.14.129', 53],
|
68
|
+
[:tcp, '193.0.14.129', 53]])
|
69
|
+
|
70
|
+
# Instantiate a new server that uses our tcp pipelining handler
|
71
|
+
# For each query the server sends the query upstream (193.0.14.129)
|
72
|
+
options = {
|
73
|
+
server_class: TCPPipeliningServer,
|
74
|
+
asynchronous: true
|
75
|
+
}
|
76
|
+
|
77
|
+
@@supervisor ||= RubyDNS::run_server(options) do
|
78
|
+
otherwise do |transaction|
|
79
|
+
transaction.passthrough!(@@upstream)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Instantiate our resolver. The resolver will use the same pipeline as much as possible.
|
84
|
+
# If a timeout occurs or max_request_per_connection a new connection should be initiated
|
85
|
+
@@resolver ||= Dnsruby::Resolver.new(
|
86
|
+
use_tcp: true,
|
87
|
+
do_caching: false,
|
88
|
+
tcp_pipelining: true,
|
89
|
+
dnssec: false,
|
90
|
+
packet_timeout: 10,
|
91
|
+
tcp_pipelining_max_queries: 10,
|
92
|
+
nameserver: TCPPipeliningServer::IP,
|
93
|
+
port: TCPPipeliningServer::PORT)
|
94
|
+
end
|
95
|
+
|
96
|
+
# Send x number of queries asynchronously to our resolver
|
97
|
+
def send_async_messages(number_of_messages, queue, wait_seconds = 0)
|
98
|
+
query_cycler = QUERIES.cycle
|
99
|
+
number_of_messages.times do
|
100
|
+
message = Dnsruby::Message.new(query_cycler.next)
|
101
|
+
# self.class.query_id identifies our query, must be different for each message
|
102
|
+
@@resolver.send_async(message, queue, self.class.query_id)
|
103
|
+
self.class.query_id += 1
|
104
|
+
|
105
|
+
# Note: For 0, we don't sleep at all instead of sleeping 0 since sleeping 0
|
106
|
+
# involves yielding the CPU.
|
107
|
+
sleep wait_seconds unless wait_seconds == 0
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# Verify x responses with no exception
|
112
|
+
def verify_responses(number_of_messages, queue)
|
113
|
+
number_of_messages.times do
|
114
|
+
_response_id, response, exception = queue.pop
|
115
|
+
assert_nil(exception)
|
116
|
+
assert(response.is_a?(Dnsruby::Message))
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# This test initiates multiple asynchronous requests and verifies they go on the same tcp
|
121
|
+
# pipeline or a new one depending on timeouts
|
122
|
+
def test_TCP_pipelining_timeout
|
123
|
+
accept_count = TCPPipeliningServer.stats.accept_count
|
124
|
+
timeout_count = TCPPipeliningServer.stats.timeout_count
|
125
|
+
|
126
|
+
# This is the main queue used to communicate between Dnsruby in async mode and the client
|
127
|
+
query_queue = Queue.new
|
128
|
+
|
129
|
+
# Test basic pipelining. All request should go on the same tcp connection.
|
130
|
+
# TCPPipeliningServer.stats.accept_count should be 1.
|
131
|
+
send_async_messages(3, query_queue)
|
132
|
+
verify_responses(3, query_queue)
|
133
|
+
|
134
|
+
assert_equal(accept_count + 1, TCPPipeliningServer.stats.accept_count)
|
135
|
+
|
136
|
+
# Wait for the timeout to occur (5s) and check timeout_count
|
137
|
+
sleep TCPPipeliningHandler::DEFAULT_TIMEOUT + 0.5
|
138
|
+
|
139
|
+
assert_equal(timeout_count + 1, TCPPipeliningServer.stats.timeout_count)
|
140
|
+
|
141
|
+
# Initiate another 3 queries, check accept_count and timeout_count
|
142
|
+
send_async_messages(3, query_queue)
|
143
|
+
verify_responses(3, query_queue)
|
144
|
+
|
145
|
+
assert_equal(accept_count + 2, TCPPipeliningServer.stats.accept_count)
|
146
|
+
|
147
|
+
# Wait for the timeout to occur and check timeout_count
|
148
|
+
sleep TCPPipeliningHandler::DEFAULT_TIMEOUT + 0.5
|
149
|
+
|
150
|
+
assert_equal(timeout_count + 2, TCPPipeliningServer.stats.timeout_count)
|
151
|
+
end
|
152
|
+
|
153
|
+
# Test timeout occurs and new connection is initiated inbetween 2 sends
|
154
|
+
def test_TCP_pipelining_timeout_in_send
|
155
|
+
accept_count = TCPPipeliningServer.stats.accept_count
|
156
|
+
timeout_count = TCPPipeliningServer.stats.timeout_count
|
157
|
+
|
158
|
+
query_queue = Queue.new
|
159
|
+
|
160
|
+
# Initiate another 3 queries but wait 3s after each query.
|
161
|
+
# Check accept_count. Wait for timeout and verify we got 2 additional timeouts.
|
162
|
+
send_async_messages(3, query_queue, TCPPipeliningHandler::DEFAULT_TIMEOUT / 2.0 + 0.5)
|
163
|
+
verify_responses(3, query_queue)
|
164
|
+
|
165
|
+
assert_equal(accept_count + 2, TCPPipeliningServer.stats.accept_count)
|
166
|
+
|
167
|
+
sleep TCPPipeliningHandler::DEFAULT_TIMEOUT + 0.5
|
168
|
+
|
169
|
+
assert_equal(timeout_count + 2, TCPPipeliningServer.stats.timeout_count)
|
170
|
+
end
|
171
|
+
|
172
|
+
# Test that we get a SocketEofResolvError if the servers closes the socket before
|
173
|
+
# all queries are answered
|
174
|
+
def test_TCP_pipelining_socket_eof
|
175
|
+
accept_count = TCPPipeliningServer.stats.accept_count
|
176
|
+
timeout_count = TCPPipeliningServer.stats.timeout_count
|
177
|
+
max_count = TCPPipeliningServer.stats.max_count
|
178
|
+
|
179
|
+
query_queue = Queue.new
|
180
|
+
|
181
|
+
# Issue 6 queries. Only 4 should be replied since max_request_per_connection = 4
|
182
|
+
# Verify we get Dnsruby::SocketEofResolvError on the last 2.
|
183
|
+
# Verify we got max_count was incremented
|
184
|
+
send_async_messages(6, query_queue)
|
185
|
+
|
186
|
+
step = 0
|
187
|
+
6.times do
|
188
|
+
_response_id, response, exception = query_queue.pop
|
189
|
+
if step < TCPPipeliningHandler::DEFAULT_MAX_REQUESTS
|
190
|
+
assert_nil(exception)
|
191
|
+
assert(response.is_a?(Dnsruby::Message))
|
192
|
+
else
|
193
|
+
assert_equal(Dnsruby::SocketEofResolvError, exception.class)
|
194
|
+
assert_nil(response)
|
195
|
+
end
|
196
|
+
step += 1
|
197
|
+
end
|
198
|
+
|
199
|
+
assert_equal(accept_count + 1, TCPPipeliningServer.stats.accept_count)
|
200
|
+
assert_equal(timeout_count, TCPPipeliningServer.stats.timeout_count)
|
201
|
+
assert_equal(max_count + 1, TCPPipeliningServer.stats.max_count)
|
202
|
+
end
|
203
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
|
2
|
+
require_relative 'spec_helper'
|
3
|
+
|
4
|
+
include Dnsruby
|
5
|
+
|
6
|
+
class ZoneReaderTest < Minitest::Test
|
7
|
+
|
8
|
+
def setup
|
9
|
+
@zone_data = <<ZONEDATA
|
10
|
+
$TTL 3600
|
11
|
+
; Comment
|
12
|
+
; Comment with whitespace in front
|
13
|
+
@ IN SOA ns1.example.com. hostmaster.example.com. (
|
14
|
+
1993112101
|
15
|
+
10800
|
16
|
+
3600
|
17
|
+
604800
|
18
|
+
7200
|
19
|
+
)
|
20
|
+
IN NS ns1.example.com.
|
21
|
+
IN NS ns2.example.com.
|
22
|
+
IN MX 10 mx.example.com.
|
23
|
+
IN TXT "v=spf1 mx ~all"
|
24
|
+
www IN A 192.0.2.10
|
25
|
+
IN AAAA 2001:DB8::10
|
26
|
+
ftp.example.com. IN CNAME www
|
27
|
+
db IN CNAME www.example.com.
|
28
|
+
ZONEDATA
|
29
|
+
|
30
|
+
@zone_file = Tempfile.new('zonefile')
|
31
|
+
@zone_file << @zone_data
|
32
|
+
@zone_file.flush
|
33
|
+
@zone_file.rewind
|
34
|
+
@reader = Dnsruby::ZoneReader.new("example.com.")
|
35
|
+
end
|
36
|
+
|
37
|
+
def teardown
|
38
|
+
@zone_file.close
|
39
|
+
@zone_file.unlink
|
40
|
+
end
|
41
|
+
|
42
|
+
def check_zone_data_is_valid(zone)
|
43
|
+
assert_equal(1993112101, zone[0].serial)
|
44
|
+
assert_equal("ns1.example.com.", zone[1].rdata)
|
45
|
+
assert_equal("ns2.example.com.", zone[2].rdata)
|
46
|
+
assert_equal("10 mx.example.com.", zone[3].rdata)
|
47
|
+
assert_equal("\"v=spf1 mx ~all\"", zone[4].rdata)
|
48
|
+
assert_equal("192.0.2.10", zone[5].rdata)
|
49
|
+
assert_equal("2001:DB8::10", zone[6].rdata)
|
50
|
+
assert_equal("www.example.com.", zone[7].rdata)
|
51
|
+
assert_equal("www.example.com.", zone[8].rdata)
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_process_file_with_filename
|
55
|
+
zone = @reader.process_file(@zone_file.path)
|
56
|
+
check_zone_data_is_valid(zone)
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_process_file_with_file_object
|
60
|
+
zone = @reader.process_file(@zone_file)
|
61
|
+
check_zone_data_is_valid(zone)
|
62
|
+
assert_equal(false, @zone_file.closed?)
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_process_file_with_stringio_object
|
66
|
+
stringio = StringIO.new(@zone_data)
|
67
|
+
zone = @reader.process_file(stringio)
|
68
|
+
check_zone_data_is_valid(zone)
|
69
|
+
assert_equal(false, stringio.closed?)
|
70
|
+
stringio.close
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
@@ -0,0 +1,208 @@
|
|
1
|
+
# --
|
2
|
+
# Copyright 2015 Verisign
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
# ++
|
16
|
+
|
17
|
+
require 'rubydns'
|
18
|
+
require 'nio'
|
19
|
+
require 'socket'
|
20
|
+
|
21
|
+
# TCPPipeliningHandler accepts new tcp connection and reads data from the sockets until
|
22
|
+
# either the client closes the connection, @max_requests_per_connection is reached
|
23
|
+
# or @timeout is attained.
|
24
|
+
|
25
|
+
class NioTcpPipeliningHandler < RubyDNS::GenericHandler
|
26
|
+
|
27
|
+
DEFAULT_MAX_REQUESTS = 4
|
28
|
+
|
29
|
+
# TODO Add timeout
|
30
|
+
def initialize(server, host, port, max_requests = DEFAULT_MAX_REQUESTS)
|
31
|
+
super(server)
|
32
|
+
@max_requests_per_connection = max_requests
|
33
|
+
@socket = TCPServer.new(host, port)
|
34
|
+
@count = {}
|
35
|
+
|
36
|
+
@selector = NIO::Selector.new
|
37
|
+
monitor = @selector.register(@socket, :r)
|
38
|
+
monitor.value = proc { accept }
|
39
|
+
|
40
|
+
async.run
|
41
|
+
end
|
42
|
+
|
43
|
+
finalizer :finalize
|
44
|
+
|
45
|
+
def finalize
|
46
|
+
@socket.close if @socket
|
47
|
+
@selector.close
|
48
|
+
@selector_thread.join
|
49
|
+
end
|
50
|
+
|
51
|
+
def run
|
52
|
+
@logger.debug "Running selector thread"
|
53
|
+
@selector_thread = create_selector_thread
|
54
|
+
end
|
55
|
+
|
56
|
+
def accept
|
57
|
+
handle_connection(@socket.accept)
|
58
|
+
end
|
59
|
+
|
60
|
+
def process_socket(socket)
|
61
|
+
@logger.debug "Processing socket"
|
62
|
+
_, _remote_port, remote_host = socket.peeraddr
|
63
|
+
options = { peer: remote_host }
|
64
|
+
|
65
|
+
input_data = RubyDNS::StreamTransport.read_chunk(socket)
|
66
|
+
response = process_query(input_data, options)
|
67
|
+
RubyDNS::StreamTransport.write_message(socket, response)
|
68
|
+
|
69
|
+
@count[socket] ||= 0
|
70
|
+
@count[socket] += 1
|
71
|
+
|
72
|
+
if @count[socket] >= @max_requests_per_connection
|
73
|
+
_, port, host = socket.peeraddr
|
74
|
+
@logger.debug("*** max request for #{host}:#{port}")
|
75
|
+
remove(socket)
|
76
|
+
end
|
77
|
+
rescue EOFError
|
78
|
+
_, port, host = socket.peeraddr
|
79
|
+
@logger.debug("*** #{host}:#{port} disconnected")
|
80
|
+
|
81
|
+
remove(socket)
|
82
|
+
end
|
83
|
+
|
84
|
+
def remove(socket)
|
85
|
+
@logger.debug("Removing socket from selector")
|
86
|
+
socket.close rescue nil
|
87
|
+
@selector.deregister(socket)
|
88
|
+
@count.delete(socket)
|
89
|
+
end
|
90
|
+
|
91
|
+
def create_selector_thread
|
92
|
+
Thread.new do
|
93
|
+
loop do
|
94
|
+
@selector.select { |monitor| monitor.value.call(monitor) }
|
95
|
+
break if @selector.closed?
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def handle_connection(socket)
|
101
|
+
@logger.debug "New connection"
|
102
|
+
@server.class.stats.increment_connection
|
103
|
+
|
104
|
+
@logger.debug "Add socket to @selector"
|
105
|
+
monitor = @selector.register(socket, :r)
|
106
|
+
monitor.value = proc { process_socket(socket) }
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
class TCPPipeliningHandler < RubyDNS::GenericHandler
|
111
|
+
DEFAULT_MAX_REQUESTS = 4
|
112
|
+
DEFAULT_TIMEOUT = 3.0
|
113
|
+
|
114
|
+
def initialize(server, host, port, max_requests = DEFAULT_MAX_REQUESTS, timeout = DEFAULT_TIMEOUT)
|
115
|
+
super(server)
|
116
|
+
@timeout = timeout
|
117
|
+
@max_requests_per_connection = max_requests
|
118
|
+
@socket = TCPServer.new(host, port)
|
119
|
+
|
120
|
+
async.run
|
121
|
+
end
|
122
|
+
|
123
|
+
finalizer :finalize
|
124
|
+
|
125
|
+
def finalize
|
126
|
+
@socket.close if @socket
|
127
|
+
end
|
128
|
+
|
129
|
+
def run
|
130
|
+
loop { async.handle_connection(@socket.accept) }
|
131
|
+
end
|
132
|
+
|
133
|
+
|
134
|
+
def handle_connection(socket)
|
135
|
+
_, _remote_port, remote_host = socket.peeraddr
|
136
|
+
options = { peer: remote_host }
|
137
|
+
|
138
|
+
@logger.debug "New connection"
|
139
|
+
@server.class.stats.increment_connection
|
140
|
+
|
141
|
+
timeout = @timeout
|
142
|
+
msg_count = 0
|
143
|
+
|
144
|
+
loop do
|
145
|
+
start_time = Time.now
|
146
|
+
@logger.debug "Waiting for #{timeout} max"
|
147
|
+
sockets = ::IO.select([socket], nil , nil, timeout)
|
148
|
+
duration = Time.now - start_time
|
149
|
+
|
150
|
+
@logger.debug "Slept for #{duration}"
|
151
|
+
|
152
|
+
timeout -= duration
|
153
|
+
|
154
|
+
if sockets
|
155
|
+
input_data = RubyDNS::StreamTransport.read_chunk(socket)
|
156
|
+
response = process_query(input_data, options)
|
157
|
+
RubyDNS::StreamTransport.write_message(socket, response)
|
158
|
+
|
159
|
+
msg_count += 1
|
160
|
+
@logger.debug "Responded to message #{msg_count}"
|
161
|
+
else
|
162
|
+
@logger.debug "TCP session timeout!"
|
163
|
+
@server.class.stats.increment_timeout
|
164
|
+
break
|
165
|
+
end
|
166
|
+
|
167
|
+
if msg_count >= @max_requests_per_connection
|
168
|
+
@logger.debug "Max number of requests attained (#{@max_requests_per_connection})"
|
169
|
+
@server.class.stats.increment_max
|
170
|
+
break
|
171
|
+
end
|
172
|
+
|
173
|
+
end
|
174
|
+
rescue EOFError
|
175
|
+
@logger.warn "TCP session ended (closed by client)"
|
176
|
+
rescue DecodeError
|
177
|
+
@logger.warn "Could not decode incoming TCP data!"
|
178
|
+
ensure
|
179
|
+
socket.close
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
# Stats collects statistics from our tcp handler
|
184
|
+
class Stats
|
185
|
+
def initialize()
|
186
|
+
@mutex = Mutex.new
|
187
|
+
@accept_count = 0
|
188
|
+
@timeout_count = 0
|
189
|
+
@max_count = 0
|
190
|
+
end
|
191
|
+
|
192
|
+
def increment_max; @mutex.synchronize { @max_count += 1 } end
|
193
|
+
def increment_timeout; @mutex.synchronize { @timeout_count += 1 } end
|
194
|
+
def increment_connection; @mutex.synchronize { @accept_count += 1 } end
|
195
|
+
|
196
|
+
def accept_count
|
197
|
+
@mutex.synchronize { @accept_count }
|
198
|
+
end
|
199
|
+
|
200
|
+
def timeout_count
|
201
|
+
@mutex.synchronize { @timeout_count }
|
202
|
+
end
|
203
|
+
|
204
|
+
def max_count
|
205
|
+
@mutex.synchronize { @max_count }
|
206
|
+
end
|
207
|
+
|
208
|
+
end
|