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