mock_dns_server 0.3.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 +7 -0
- data/.gitignore +18 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +24 -0
- data/README.md +127 -0
- data/RELEASE_NOTES.md +3 -0
- data/Rakefile +19 -0
- data/bin/show_dig_request +41 -0
- data/lib/mock_dns_server.rb +12 -0
- data/lib/mock_dns_server/action_factory.rb +84 -0
- data/lib/mock_dns_server/conditional_action.rb +42 -0
- data/lib/mock_dns_server/conditional_action_factory.rb +53 -0
- data/lib/mock_dns_server/conditional_actions.rb +73 -0
- data/lib/mock_dns_server/dnsruby_monkey_patch.rb +19 -0
- data/lib/mock_dns_server/history.rb +84 -0
- data/lib/mock_dns_server/history_inspections.rb +58 -0
- data/lib/mock_dns_server/ip_address_dispenser.rb +34 -0
- data/lib/mock_dns_server/message_builder.rb +199 -0
- data/lib/mock_dns_server/message_helper.rb +86 -0
- data/lib/mock_dns_server/message_transformer.rb +74 -0
- data/lib/mock_dns_server/predicate_factory.rb +108 -0
- data/lib/mock_dns_server/serial_history.rb +385 -0
- data/lib/mock_dns_server/serial_number.rb +129 -0
- data/lib/mock_dns_server/serial_transaction.rb +46 -0
- data/lib/mock_dns_server/server.rb +422 -0
- data/lib/mock_dns_server/server_context.rb +57 -0
- data/lib/mock_dns_server/server_thread.rb +13 -0
- data/lib/mock_dns_server/version.rb +3 -0
- data/mock_dns_server.gemspec +32 -0
- data/spec/mock_dns_server/conditions_factory_spec.rb +58 -0
- data/spec/mock_dns_server/history_inspections_spec.rb +84 -0
- data/spec/mock_dns_server/history_spec.rb +65 -0
- data/spec/mock_dns_server/ip_address_dispenser_spec.rb +30 -0
- data/spec/mock_dns_server/message_builder_spec.rb +18 -0
- data/spec/mock_dns_server/predicate_factory_spec.rb +147 -0
- data/spec/mock_dns_server/serial_history_spec.rb +385 -0
- data/spec/mock_dns_server/serial_number_spec.rb +119 -0
- data/spec/mock_dns_server/serial_transaction_spec.rb +37 -0
- data/spec/mock_dns_server/server_context_spec.rb +20 -0
- data/spec/mock_dns_server/server_spec.rb +411 -0
- data/spec/mock_dns_server/socket_research_spec.rb +59 -0
- data/spec/spec_helper.rb +44 -0
- data/todo.txt +0 -0
- metadata +212 -0
@@ -0,0 +1,129 @@
|
|
1
|
+
# Handles serial values, adjusting correctly for wraparound.
|
2
|
+
#
|
3
|
+
# From the DNS and Bind book, p. 139:
|
4
|
+
#
|
5
|
+
# "The DNS serial number is a
|
6
|
+
# 32-bit unsigned integer whose value ranges from 0 to 4,294,967,295. The serial number
|
7
|
+
# uses sequence space arithmetic, which means that for any serial number, half the
|
8
|
+
# numbers in the number space (2,147,483,647 numbers) are less than the serial number,
|
9
|
+
# and half the numbers are larger.""
|
10
|
+
|
11
|
+
class SerialNumber
|
12
|
+
|
13
|
+
|
14
|
+
MIN_VALUE = 0
|
15
|
+
MAX_VALUE = 0xFFFF_FFFF # (4_294_967_295)
|
16
|
+
MAX_DIFFERENCE = 0x8000_0000 # (2_147_483_648)
|
17
|
+
|
18
|
+
attr_accessor :value
|
19
|
+
|
20
|
+
def initialize(value)
|
21
|
+
self.class.validate(value)
|
22
|
+
@value = value
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
# Call this when you have an object that may be either a SerialNumber
|
27
|
+
# or a Fixnum/Bignum, and you want to ensure that you have
|
28
|
+
# a SerialNumber.
|
29
|
+
def self.object(thing)
|
30
|
+
if thing.is_a?(SerialNumber)
|
31
|
+
thing
|
32
|
+
elsif thing.nil?
|
33
|
+
nil
|
34
|
+
else
|
35
|
+
SerialNumber.new(thing)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
def self.validate(value)
|
41
|
+
if value < MIN_VALUE || value > MAX_VALUE
|
42
|
+
raise "Invalid value (#{value}), must be between #{MIN_VALUE} and #{MAX_VALUE}."
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
def self.compare_values(value_1, value_2)
|
48
|
+
distance = (value_1 - value_2).abs
|
49
|
+
|
50
|
+
if distance == 0
|
51
|
+
0
|
52
|
+
elsif distance < MAX_DIFFERENCE
|
53
|
+
value_1 - value_2
|
54
|
+
elsif distance > MAX_DIFFERENCE
|
55
|
+
value_2 - value_1
|
56
|
+
else # distance == MAX_DIFFERENCE
|
57
|
+
raise "Cannot compare 2 numbers whose difference is exactly #{MAX_DIFFERENCE.to_s(16)} (#{value_1}, #{value_2})."
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
def self.compare(sss1, sss2)
|
63
|
+
compare_values(sss1.value, sss2.value)
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
def self.next_serial_value(value)
|
68
|
+
validate(value)
|
69
|
+
value == MAX_VALUE ? 0 : value + 1
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
def next_serial
|
74
|
+
self.class.new(self.class.next_serial_value(value))
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
def <=>(other)
|
79
|
+
self.class.compare(self, other)
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
def >(other)
|
84
|
+
self.<=>(other) > 0
|
85
|
+
end
|
86
|
+
|
87
|
+
|
88
|
+
def <(other)
|
89
|
+
self.<=>(other) < 0
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
def >=(other)
|
94
|
+
self.<=>(other) >= 0
|
95
|
+
end
|
96
|
+
|
97
|
+
|
98
|
+
def <=(other)
|
99
|
+
self.<=>(other) <= 0
|
100
|
+
end
|
101
|
+
|
102
|
+
|
103
|
+
def ==(other)
|
104
|
+
self.class == other.class &&
|
105
|
+
self.value == other.value
|
106
|
+
end
|
107
|
+
|
108
|
+
|
109
|
+
def hash
|
110
|
+
value.hash
|
111
|
+
end
|
112
|
+
|
113
|
+
def eql?(other)
|
114
|
+
self.==(other)
|
115
|
+
end
|
116
|
+
|
117
|
+
# Can be used to normalize an object that may be a Fixnum or a SerialNumber to an int:
|
118
|
+
def to_i
|
119
|
+
value
|
120
|
+
end
|
121
|
+
|
122
|
+
|
123
|
+
def to_s
|
124
|
+
"#{self.class}: value = #{value}"
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
128
|
+
|
129
|
+
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module MockDnsServer
|
2
|
+
|
3
|
+
# Manages RR additions and deletions for a given serial.
|
4
|
+
class SerialTransaction
|
5
|
+
|
6
|
+
# serial is the starting serial, i.e. the serial to which
|
7
|
+
# the additions and changes will be applied to get to
|
8
|
+
# the next serial value.
|
9
|
+
attr_accessor :serial, :additions, :deletions, :zone
|
10
|
+
|
11
|
+
# An object containing serial change information
|
12
|
+
#
|
13
|
+
# @param zone the zone for which this data applies
|
14
|
+
# @param serial a number from 0 to 2^32 - 1, or a SerialNumber instance
|
15
|
+
# @param deletions a single RR or an array of RR's representing deletions
|
16
|
+
# @param additions a single RR or an array of RR's representing additions
|
17
|
+
def initialize(zone, serial, deletions = [], additions = [])
|
18
|
+
@zone = zone
|
19
|
+
@serial = SerialNumber.object(serial)
|
20
|
+
@deletions = Array(deletions)
|
21
|
+
@additions = Array(additions)
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
# Returns an array of records corresponding to a serial change of 1,
|
26
|
+
# including delimiting SOA records, suitable for inclusion in an
|
27
|
+
# IXFR response.
|
28
|
+
def ixfr_records(start_serial)
|
29
|
+
records = []
|
30
|
+
records << MessageBuilder.soa_answer(name: zone, serial: start_serial)
|
31
|
+
deletions.each { |record| records << record }
|
32
|
+
records << MessageBuilder.soa_answer(name: zone, serial: serial)
|
33
|
+
additions.each { |record| records << record }
|
34
|
+
#require 'awesome_print'; puts ''; ap records; puts ''
|
35
|
+
records
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
def to_s
|
40
|
+
s = "Changes to serial #{serial}:\n"
|
41
|
+
deletions.each { |d| s << "- #{d}\n" }
|
42
|
+
additions.each { |a| s << "+ #{a}\n" }
|
43
|
+
s
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,422 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'forwardable'
|
3
|
+
require 'thread_safe'
|
4
|
+
|
5
|
+
require 'mock_dns_server/message_helper'
|
6
|
+
require 'mock_dns_server/server_context'
|
7
|
+
require 'mock_dns_server/server_thread'
|
8
|
+
|
9
|
+
|
10
|
+
module MockDnsServer
|
11
|
+
|
12
|
+
# Starts a UDP and TCP server that listens for DNS and/or other messages.
|
13
|
+
class Server
|
14
|
+
|
15
|
+
extend Forwardable
|
16
|
+
def_delegators :@context, :host, :port, :conditional_actions, :timeout_secs, :verbose
|
17
|
+
|
18
|
+
# Do we want serials to be attributes of the server, or configured in conditional actions?
|
19
|
+
|
20
|
+
attr_reader :context, :sockets, :serials, :control_queue
|
21
|
+
|
22
|
+
DEFAULT_PORT = 53
|
23
|
+
DEFAULT_TIMEOUT = 1.0
|
24
|
+
|
25
|
+
|
26
|
+
def initialize(options = {})
|
27
|
+
|
28
|
+
@closed = false
|
29
|
+
defaults = {
|
30
|
+
port: DEFAULT_PORT,
|
31
|
+
timeout_secs: DEFAULT_TIMEOUT,
|
32
|
+
verbose: false
|
33
|
+
}
|
34
|
+
options = defaults.merge(options)
|
35
|
+
|
36
|
+
@context = ServerContext.new(self, options)
|
37
|
+
|
38
|
+
self.class.open_servers << self
|
39
|
+
create_sockets
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
# Creates a server, executes the passed block, and then closes the server.
|
44
|
+
def create_sockets
|
45
|
+
@tcp_listener_socket = TCPServer.new(host, port)
|
46
|
+
@tcp_listener_socket.setsockopt(:SOCKET, :REUSEADDR, true)
|
47
|
+
|
48
|
+
@udp_socket = UDPSocket.new
|
49
|
+
@udp_socket.bind(host, port)
|
50
|
+
|
51
|
+
@control_queue = SizedQueue.new(1000)
|
52
|
+
|
53
|
+
@sockets = [@tcp_listener_socket, @udp_socket]
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
# Closes the sockets and exits the server thread if it has already been created.
|
58
|
+
def close
|
59
|
+
return if closed?
|
60
|
+
puts "Closing #{self}..." if verbose
|
61
|
+
@closed = true
|
62
|
+
|
63
|
+
sockets.each { |socket| socket.close unless socket.closed? }
|
64
|
+
|
65
|
+
self.class.open_servers.delete(self)
|
66
|
+
|
67
|
+
if @server_thread
|
68
|
+
@server_thread.exit
|
69
|
+
@server_thread.join
|
70
|
+
@server_thread = nil
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
|
75
|
+
def closed?
|
76
|
+
@closed
|
77
|
+
end
|
78
|
+
|
79
|
+
|
80
|
+
# Determines whether the server has reached the point in its lifetime when it is ready,
|
81
|
+
# but it may still be true after the server is closed. Intended to be used during server
|
82
|
+
# startup and not thereafter.
|
83
|
+
def ready?
|
84
|
+
!! @ready
|
85
|
+
end
|
86
|
+
|
87
|
+
|
88
|
+
def add_conditional_action(conditional_action)
|
89
|
+
conditional_actions.add(conditional_action)
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
def add_conditional_actions(conditional_actions)
|
94
|
+
conditional_actions.each { |ca| add_conditional_action(ca) }
|
95
|
+
end
|
96
|
+
|
97
|
+
|
98
|
+
def record_receipt(request, sender, protocol)
|
99
|
+
history.add_incoming(request, sender, protocol)
|
100
|
+
end
|
101
|
+
|
102
|
+
|
103
|
+
def handle_request(request, sender, protocol)
|
104
|
+
|
105
|
+
request = MessageHelper.to_dns_message(request)
|
106
|
+
|
107
|
+
context.with_mutex do
|
108
|
+
if block_given?
|
109
|
+
yield(request, sender, protocol)
|
110
|
+
else
|
111
|
+
record_receipt(request, sender, protocol)
|
112
|
+
context.conditional_actions.respond_to(request, sender, protocol)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
|
118
|
+
def conditional_action_count
|
119
|
+
context.conditional_actions.size
|
120
|
+
end
|
121
|
+
|
122
|
+
|
123
|
+
# Starts this server and returns the new thread in which it will run.
|
124
|
+
# If a block is passed, it will be passed in turn to handle_request
|
125
|
+
# to be executed (on the server's thread).
|
126
|
+
def start(&block)
|
127
|
+
raise "Server already started." if @server_thread
|
128
|
+
|
129
|
+
puts "Starting server on host #{host}:#{port}..." if verbose
|
130
|
+
@server_thread = ServerThread.new do
|
131
|
+
begin
|
132
|
+
|
133
|
+
Thread.current.server = self
|
134
|
+
|
135
|
+
loop do
|
136
|
+
unless @control_queue.empty?
|
137
|
+
action = @control_queue.pop
|
138
|
+
action.()
|
139
|
+
end
|
140
|
+
|
141
|
+
@ready = true
|
142
|
+
reads, _, errors = IO.select(sockets, nil, sockets, timeout_secs)
|
143
|
+
|
144
|
+
error_occurred = errors && errors.first # errors.first will be nil on timeout
|
145
|
+
if error_occurred
|
146
|
+
puts errors if verbose
|
147
|
+
break
|
148
|
+
end
|
149
|
+
|
150
|
+
if reads
|
151
|
+
reads.each do |read_socket|
|
152
|
+
handle_read(block, read_socket)
|
153
|
+
#if conditional_actions.empty?
|
154
|
+
# puts "No more conditional actions. Closing server..." if verbose
|
155
|
+
# break
|
156
|
+
#end
|
157
|
+
end
|
158
|
+
else
|
159
|
+
# TODO: This is where we can put things to do periodically when the server is not busy
|
160
|
+
end
|
161
|
+
end
|
162
|
+
rescue => e
|
163
|
+
self.close
|
164
|
+
# Errno::EBADF is raised when the server object is closed normally,
|
165
|
+
# so we don't want to report it. All other errors should be reported.
|
166
|
+
raise e unless e.is_a?(Errno::EBADF)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
self # for chaining, especially with wait_until_ready
|
170
|
+
end
|
171
|
+
|
172
|
+
|
173
|
+
# Handles the receiving of a single message.
|
174
|
+
def handle_read(block, read_socket)
|
175
|
+
|
176
|
+
request = nil
|
177
|
+
sender = nil
|
178
|
+
protocol = nil
|
179
|
+
|
180
|
+
if read_socket == @tcp_listener_socket
|
181
|
+
sockets << @tcp_listener_socket.accept
|
182
|
+
puts "Got new TCP socket: #{sockets.last}" if verbose
|
183
|
+
|
184
|
+
elsif read_socket == @udp_socket
|
185
|
+
protocol = :udp
|
186
|
+
request, sender = udp_recvfrom_with_timeout(read_socket)
|
187
|
+
request = MessageHelper.to_dns_message(request)
|
188
|
+
puts "Got incoming message from UDP socket:\n#{request}\n" if verbose
|
189
|
+
|
190
|
+
else # it must be a spawned TCP read socket
|
191
|
+
if read_socket.eof? # we're here because it closed on the client side
|
192
|
+
sockets.delete(read_socket)
|
193
|
+
puts "received EOF from socket #{read_socket}...deleted it from listener list." if verbose
|
194
|
+
else # read from it
|
195
|
+
protocol = :tcp
|
196
|
+
|
197
|
+
# Read message size:
|
198
|
+
request = MessageHelper.read_tcp_message(read_socket)
|
199
|
+
sender = read_socket
|
200
|
+
|
201
|
+
if verbose
|
202
|
+
if request.nil? || request == ''
|
203
|
+
puts "Got no request."
|
204
|
+
else
|
205
|
+
puts "Got incoming message from TCP socket:\n#{request}\n"
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
handle_request(request, sender, protocol, &block) if request
|
211
|
+
end
|
212
|
+
|
213
|
+
|
214
|
+
def send_tcp_response(socket, content)
|
215
|
+
socket.write(MessageHelper.tcp_message_package_for_write(content))
|
216
|
+
end
|
217
|
+
|
218
|
+
|
219
|
+
def send_udp_response(sender, content)
|
220
|
+
send_data = MessageHelper.udp_message_package_for_write(content)
|
221
|
+
_, client_port, ip_addr, _ = sender
|
222
|
+
@udp_socket.send(send_data, 0, ip_addr, client_port)
|
223
|
+
end
|
224
|
+
|
225
|
+
|
226
|
+
def send_response(sender, content, protocol)
|
227
|
+
if protocol == :tcp
|
228
|
+
send_tcp_response(sender, content)
|
229
|
+
elsif protocol == :udp
|
230
|
+
send_udp_response(sender, content)
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
|
235
|
+
def history_copy
|
236
|
+
copy = nil
|
237
|
+
context.with_mutex { copy = history.copy }
|
238
|
+
copy
|
239
|
+
end
|
240
|
+
|
241
|
+
|
242
|
+
def history
|
243
|
+
context.history
|
244
|
+
end; private :history
|
245
|
+
|
246
|
+
|
247
|
+
def occurred?(inspection)
|
248
|
+
history.occurred?(inspection)
|
249
|
+
end
|
250
|
+
|
251
|
+
|
252
|
+
def conditional_actions
|
253
|
+
context.conditional_actions
|
254
|
+
end; private :conditional_actions
|
255
|
+
|
256
|
+
|
257
|
+
def is_dns_packet?(packet, protocol)
|
258
|
+
raise "protocol must be :tcp or :udp" unless [:tcp, :udp].include?(protocol)
|
259
|
+
|
260
|
+
encoded_message = :udp ? packet : packet[2..-1]
|
261
|
+
message = MessageHelper.to_dns_message(encoded_message)
|
262
|
+
message.is_a?(Dnsruby::Message)
|
263
|
+
end
|
264
|
+
|
265
|
+
|
266
|
+
# @param message_options - name (zone), serial, mname
|
267
|
+
def send_notify(zts_host, zts_port, message_options, notify_message_override = nil, wait_for_response = true)
|
268
|
+
notify_message = notify_message_override ? notify_message_override : MessageBuilder::notify_message(message_options)
|
269
|
+
|
270
|
+
socket = UDPSocket.new
|
271
|
+
|
272
|
+
puts "Sending notify message to host #{zts_host}, port #{zts_port}" if verbose
|
273
|
+
socket.send(notify_message.encode, 0, zts_host, zts_port)
|
274
|
+
|
275
|
+
if wait_for_response
|
276
|
+
response_wire_data, _ = udp_recvfrom_with_timeout(socket)
|
277
|
+
response = MessageHelper.to_dns_message(response_wire_data)
|
278
|
+
context.with_mutex { history.add_notify_response(response, zts_host, zts_port, :udp) }
|
279
|
+
response
|
280
|
+
else
|
281
|
+
nil
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
|
286
|
+
def udp_recvfrom_with_timeout(udp_socket, timeout_secs = 10, max_data_size = 10_000) # TODO: better default max?
|
287
|
+
request = nil
|
288
|
+
sender = nil
|
289
|
+
|
290
|
+
recv_thread = Thread.new do
|
291
|
+
request, sender = udp_socket.recvfrom(max_data_size)
|
292
|
+
end
|
293
|
+
timeout_expired = recv_thread.join(timeout_secs).nil?
|
294
|
+
if timeout_expired
|
295
|
+
recv_thread.exit
|
296
|
+
raise "Response not received from UDP socket."
|
297
|
+
end
|
298
|
+
[request, sender]
|
299
|
+
end
|
300
|
+
|
301
|
+
# For an already initialized server, perform the passed block and ensure that the server
|
302
|
+
# will be closed, even if an error is raised.
|
303
|
+
def do_then_close
|
304
|
+
begin
|
305
|
+
start
|
306
|
+
yield
|
307
|
+
ensure
|
308
|
+
close
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
|
313
|
+
# Waits until the server is ready, sleeping between calls to ready.
|
314
|
+
# @return elapsed time until ready
|
315
|
+
def wait_until_ready(sleep_duration = 0.000_02)
|
316
|
+
|
317
|
+
if Thread.current == @server_thread
|
318
|
+
raise "This method must not be called in the server's thread."
|
319
|
+
end
|
320
|
+
|
321
|
+
start = Time.now
|
322
|
+
sleep(sleep_duration) until ready?
|
323
|
+
duration = Time.now - start
|
324
|
+
duration
|
325
|
+
end
|
326
|
+
|
327
|
+
|
328
|
+
# Sets up the SOA and records to serve on IXFR/AXFR queries.
|
329
|
+
# mname is set to "default.#{zone}
|
330
|
+
#
|
331
|
+
# @param options hash containing the following keys:
|
332
|
+
# zone
|
333
|
+
# serial (SOA)
|
334
|
+
# dns_records array of RR's
|
335
|
+
# times times for the action to be performed before removal (optional, defaults to forever)
|
336
|
+
# zts_hosts array of ZTS hosts
|
337
|
+
# zts_port (optional, defaults to 53)
|
338
|
+
#
|
339
|
+
def load_zone(options)
|
340
|
+
|
341
|
+
validate_options = ->() do
|
342
|
+
required_options = [:zone, :serial_history]
|
343
|
+
missing_options = required_options.select { |o| options[o].nil? }
|
344
|
+
unless missing_options.empty?
|
345
|
+
raise "Options required for load_zone were missing: #{missing_options.join(', ')}."
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
validate_options.()
|
350
|
+
|
351
|
+
serial_history = options[:serial_history]
|
352
|
+
zone = serial_history.zone
|
353
|
+
zts_hosts = Array(options[:zts_hosts])
|
354
|
+
zts_port = options[:zts_port] || 53
|
355
|
+
times = options[:times] || 0
|
356
|
+
mname = "default.#{zone}"
|
357
|
+
|
358
|
+
cond_action = ConditionalActionFactory.new.zone_load(serial_history, times)
|
359
|
+
conditional_actions.add(cond_action)
|
360
|
+
|
361
|
+
notify_options = { name: zone, serial: serial_history.high_serial, mname: mname }
|
362
|
+
zts_hosts.each do |zts_host|
|
363
|
+
send_notify(zts_host, zts_port, notify_options)
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
|
368
|
+
def to_s
|
369
|
+
"#{self.class.name}: host: #{host}, port: #{port}, ready: #{ready?}, closed: #{closed?}"
|
370
|
+
end
|
371
|
+
|
372
|
+
|
373
|
+
def self.open_servers
|
374
|
+
@servers ||= ThreadSafe::Array.new
|
375
|
+
end
|
376
|
+
|
377
|
+
|
378
|
+
# Creates a new server, yields to the passed block, then closes the server.
|
379
|
+
def self.with_new_server(options = {})
|
380
|
+
begin
|
381
|
+
server = self.new(options)
|
382
|
+
yield(server)
|
383
|
+
ensure
|
384
|
+
server.close if server
|
385
|
+
end
|
386
|
+
nil # don't want to return server because it should no longer be used
|
387
|
+
end
|
388
|
+
|
389
|
+
|
390
|
+
def self.close_all_servers
|
391
|
+
open_servers.clone.each { |server| server.close }
|
392
|
+
end
|
393
|
+
|
394
|
+
|
395
|
+
def self.kill_all_servers
|
396
|
+
threads_needing_exit = ServerThread.all.select { |thread| ['sleep', 'run'].include?(thread.status) }
|
397
|
+
|
398
|
+
threads_needing_exit.each do |thread|
|
399
|
+
server = thread.server
|
400
|
+
# If we can get a handle on the server, close it; else, just exit the thread.
|
401
|
+
if server
|
402
|
+
server.close
|
403
|
+
raise "Sockets not closed." unless server.closed?
|
404
|
+
else
|
405
|
+
raise "Could not get server reference"
|
406
|
+
end
|
407
|
+
thread.join
|
408
|
+
end
|
409
|
+
end
|
410
|
+
|
411
|
+
|
412
|
+
# Returns the IP addresses (as strings) of the host on which this is running
|
413
|
+
# that are eligible to be used for a Server instance. Eligibility is defined
|
414
|
+
# as IPV4, not loopback, and not multicast.
|
415
|
+
def self.eligible_interfaces
|
416
|
+
addrinfos = Socket.ip_address_list.select do |intf|
|
417
|
+
intf.ipv4? && !intf.ipv4_loopback? && !intf.ipv4_multicast?
|
418
|
+
end
|
419
|
+
addrinfos.map(&:ip_address)
|
420
|
+
end
|
421
|
+
end
|
422
|
+
end
|