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.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +24 -0
  5. data/README.md +127 -0
  6. data/RELEASE_NOTES.md +3 -0
  7. data/Rakefile +19 -0
  8. data/bin/show_dig_request +41 -0
  9. data/lib/mock_dns_server.rb +12 -0
  10. data/lib/mock_dns_server/action_factory.rb +84 -0
  11. data/lib/mock_dns_server/conditional_action.rb +42 -0
  12. data/lib/mock_dns_server/conditional_action_factory.rb +53 -0
  13. data/lib/mock_dns_server/conditional_actions.rb +73 -0
  14. data/lib/mock_dns_server/dnsruby_monkey_patch.rb +19 -0
  15. data/lib/mock_dns_server/history.rb +84 -0
  16. data/lib/mock_dns_server/history_inspections.rb +58 -0
  17. data/lib/mock_dns_server/ip_address_dispenser.rb +34 -0
  18. data/lib/mock_dns_server/message_builder.rb +199 -0
  19. data/lib/mock_dns_server/message_helper.rb +86 -0
  20. data/lib/mock_dns_server/message_transformer.rb +74 -0
  21. data/lib/mock_dns_server/predicate_factory.rb +108 -0
  22. data/lib/mock_dns_server/serial_history.rb +385 -0
  23. data/lib/mock_dns_server/serial_number.rb +129 -0
  24. data/lib/mock_dns_server/serial_transaction.rb +46 -0
  25. data/lib/mock_dns_server/server.rb +422 -0
  26. data/lib/mock_dns_server/server_context.rb +57 -0
  27. data/lib/mock_dns_server/server_thread.rb +13 -0
  28. data/lib/mock_dns_server/version.rb +3 -0
  29. data/mock_dns_server.gemspec +32 -0
  30. data/spec/mock_dns_server/conditions_factory_spec.rb +58 -0
  31. data/spec/mock_dns_server/history_inspections_spec.rb +84 -0
  32. data/spec/mock_dns_server/history_spec.rb +65 -0
  33. data/spec/mock_dns_server/ip_address_dispenser_spec.rb +30 -0
  34. data/spec/mock_dns_server/message_builder_spec.rb +18 -0
  35. data/spec/mock_dns_server/predicate_factory_spec.rb +147 -0
  36. data/spec/mock_dns_server/serial_history_spec.rb +385 -0
  37. data/spec/mock_dns_server/serial_number_spec.rb +119 -0
  38. data/spec/mock_dns_server/serial_transaction_spec.rb +37 -0
  39. data/spec/mock_dns_server/server_context_spec.rb +20 -0
  40. data/spec/mock_dns_server/server_spec.rb +411 -0
  41. data/spec/mock_dns_server/socket_research_spec.rb +59 -0
  42. data/spec/spec_helper.rb +44 -0
  43. data/todo.txt +0 -0
  44. metadata +212 -0
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+ require 'mock_dns_server/serial_transaction'
3
+ require 'mock_dns_server/message_builder'
4
+
5
+ module MockDnsServer
6
+
7
+ describe SerialTransaction do
8
+
9
+ let(:rrs) do [
10
+ MessageBuilder.rr('A', 'abc.com', '1.2.3.4'),
11
+ MessageBuilder.rr('A', 'abc.com', '5.6.7.8')
12
+ ] end
13
+
14
+ context 'SerialTransaction#ixfr_records' do
15
+
16
+ it "positions SOA's, additions, and deletions correctly" do
17
+ txn = SerialTransaction.new('ruby-lang.org', 1002)
18
+ txn.additions = [rrs.first]
19
+ txn.deletions = [rrs.last]
20
+ records = txn.ixfr_records(1001)
21
+
22
+ expect(records[0].type.to_s).to eq('SOA')
23
+ expect(records[0].serial).to eq(1001)
24
+
25
+ expect(records[1].type.to_s).to eq('A')
26
+ expect(records[1].rdata.to_s).to eq('5.6.7.8')
27
+
28
+ expect(records[2].type.to_s).to eq('SOA')
29
+ expect(records[2].serial).to eq(1002)
30
+
31
+ expect(records[3].type.to_s).to eq('A')
32
+ expect(records[3].rdata.to_s).to eq('1.2.3.4')
33
+ end
34
+ end
35
+
36
+ end
37
+ end
@@ -0,0 +1,20 @@
1
+ require_relative '../spec_helper'
2
+
3
+ require 'mock_dns_server/server_context'
4
+
5
+ module MockDnsServer
6
+
7
+ describe ServerContext do
8
+
9
+ subject { ServerContext.new(nil) }
10
+
11
+ it 'should wrap a block in a mutex without raising an exception' do
12
+ n = 0
13
+ block = ->() do
14
+ subject.with_mutex { n = 1 }
15
+ end
16
+ expect(block).to_not raise_exception
17
+ expect(n).to eq(1) # prove the block was executed
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,411 @@
1
+ require_relative '../spec_helper'
2
+
3
+ require 'dnsruby'
4
+ require 'awesome_print'
5
+
6
+ require 'mock_dns_server/action_factory'
7
+ require 'mock_dns_server/conditional_action_factory'
8
+ require 'mock_dns_server/message_builder'
9
+ require 'mock_dns_server/message_helper'
10
+ require 'mock_dns_server/message_transformer'
11
+ require 'mock_dns_server/predicate_factory'
12
+ require 'mock_dns_server/serial_history'
13
+ require 'mock_dns_server/server'
14
+
15
+ module MockDnsServer
16
+
17
+ MB = MessageBuilder
18
+
19
+ describe Server do
20
+
21
+ # This can be enabled temporarily if there are problems with the port being in use.
22
+ # before(:each) do
23
+ # Server.kill_all_servers
24
+ # end
25
+
26
+ let(:port) { 9999 }
27
+ let(:host) { '127.0.0.1' }
28
+ let(:options) { { host: host, port: port } }
29
+ # let(:options) { { host: host, port: port, verbose: true } }
30
+ let(:verbose_options) { options.merge({ verbose: true }) }
31
+ let(:sample_message) { 'Hello from RSpec' }
32
+ let(:pf) { PredicateFactory.new }
33
+ let(:af) { ActionFactory.new }
34
+ let(:caf) { ConditionalActionFactory.new }
35
+ let(:mb) { MessageBuilder.new }
36
+
37
+ #it 'runs an echo server' do
38
+ # opts = verbose_options.merge(host: '10.0.1.8')
39
+ # ap opts
40
+ # Server.with_new_server(opts) do |server|
41
+ # server.add_conditional_action(ConditionalActionFactory.new.echo)
42
+ # server.start
43
+ # sleep 1000000
44
+ # end
45
+ #end
46
+
47
+
48
+ it 'can be created and destroyed twice successfully' do
49
+ 2.times do
50
+ server = Server.new(options).start
51
+ server.wait_until_ready
52
+ server.close
53
+ end
54
+ end
55
+
56
+ it 'gets created and destroyed' do
57
+
58
+ server = Server.new(options)
59
+
60
+ expect(server.ready?).to be false
61
+ expect(server.closed?).to be false
62
+
63
+ server.start.wait_until_ready
64
+ expect(server.ready?).to be true
65
+ expect(server.closed?).to be false
66
+ server.close
67
+ expect(server.closed?).to be true
68
+ end
69
+
70
+
71
+ it 'can be created and destroyed twice successfully' do
72
+ 2.times do
73
+ server = Server.new(options).start
74
+ server.wait_until_ready
75
+ server.close
76
+ end
77
+ end
78
+
79
+ it 'gets and replies to a simple message with TCP' do
80
+ Server.with_new_server(options) do |server|
81
+ server.add_conditional_action(ConditionalActionFactory.new.echo)
82
+ server.start.wait_until_ready
83
+ client_socket = TCPSocket.open(host, port)
84
+ client_socket.write(MessageHelper.tcp_message_package_for_write(sample_message))
85
+ message = MessageHelper.read_tcp_message(client_socket)
86
+ expect(message).to eq(sample_message)
87
+ # TODO: Close these sockets?
88
+ end
89
+ end
90
+
91
+ it 'instructs the server correctly to be an echo server' do
92
+ Server.with_new_server(options) do |server|
93
+ server.add_conditional_action(ConditionalActionFactory.new.echo)
94
+ server.start.wait_until_ready
95
+ socket = UDPSocket.new
96
+ socket.send(sample_message, 0, host, port)
97
+ request_received, _ = socket.recvfrom(10_000)
98
+ expect(request_received).to eq(sample_message)
99
+ end
100
+ end
101
+
102
+
103
+
104
+ it 'gets a simple message with UDP' do
105
+
106
+ Server.with_new_server(options) do |server|
107
+
108
+ server.start do |request, sender, _|
109
+ server.send_udp_response(sender, request)
110
+ end
111
+
112
+ socket = UDPSocket.new
113
+ server.wait_until_ready
114
+ socket.send(sample_message, 0, host, port)
115
+ request_received, _ = socket.recvfrom(10_000)
116
+ expect(request_received).to eq(sample_message)
117
+ end
118
+
119
+ end
120
+
121
+
122
+ it 'calculates conditional action counts correctly' do
123
+ Server.with_new_server(options) do |server|
124
+ expect(server.conditional_action_count).to eq(0)
125
+ server.add_conditional_action(ConditionalActionFactory.new.echo)
126
+ expect(server.conditional_action_count).to eq(1)
127
+ end
128
+ end
129
+
130
+
131
+ it 'handles maximum counts correctly' do
132
+
133
+ # TODO: Research whether the placing of the sleep calls in this test
134
+ # (and possibly elsewhere) indicate design or documentation error.
135
+ #
136
+ ca1 = ConditionalAction.new(pf.always_true, af.constant('1'), 1)
137
+ ca2 = ConditionalAction.new(pf.always_true, af.constant('2'), 1)
138
+
139
+ Server.with_new_server(options) do |server|
140
+ server.add_conditional_actions([ca1, ca2])
141
+ expect(server.conditional_action_count).to eq(2)
142
+ server.start.wait_until_ready
143
+
144
+ socket = UDPSocket.new
145
+ socket.send(sample_message, 0, host, port)
146
+ request_received, _ = socket.recvfrom(10_000)
147
+ expect(request_received).to eq('2')
148
+ sleep 0.01
149
+ expect(server.conditional_action_count).to eq(1)
150
+
151
+ socket.send(sample_message, 0, host, port)
152
+ request_received, _ = socket.recvfrom(10_000)
153
+ expect(request_received).to eq('1')
154
+ sleep 0.01
155
+ expect(server.conditional_action_count).to eq(0)
156
+ end
157
+ end
158
+
159
+
160
+ it 'correctly returns an A response' do
161
+
162
+ domain_name = 'foo.example.com'
163
+ response_data = "#{domain_name} 86400 A 10.1.2.3"
164
+
165
+ Server.with_new_server(options) do |server|
166
+ server.add_conditional_action(caf.specified_a_response(domain_name, response_data))
167
+ server.start.wait_until_ready
168
+
169
+ request = Dnsruby::Message.new(domain_name).encode
170
+ socket = UDPSocket.new
171
+ socket.send(request, 0, host, port)
172
+ encoded_message, _ = socket.recvfrom(10_000)
173
+ response = Dnsruby::Message.decode(encoded_message)
174
+
175
+ expect(response.answer.size).to eq(1)
176
+ answer = response.answer.first
177
+ expect(answer).to be_a(Dnsruby::RR::IN::A)
178
+ expect(answer.ttl).to eq(86400)
179
+ expect(answer.name.to_s).to eq(domain_name)
180
+ expect(answer.rdata.to_s).to eq('10.1.2.3')
181
+ expect(answer.rr_type).to eq('A')
182
+ end
183
+ end
184
+
185
+
186
+ it 'should not provide access to its internals' do
187
+ internal_methods = [:history, :conditional_actions]
188
+ server = Server.new(options)
189
+ errors = internal_methods.select { |pm| server.respond_to?(pm) }
190
+ server.close
191
+ expect(errors).to eq([])
192
+ end
193
+
194
+
195
+ it 'should get a DNS message correctly with UDP' do
196
+ Server.with_new_server(options) do |server|
197
+
198
+ server.add_conditional_action(caf.echo)
199
+ server.start.wait_until_ready
200
+
201
+ socket = UDPSocket.new
202
+ wire_data = Dnsruby::Message.new('a.com').encode
203
+ socket.send(wire_data, 0, host, port)
204
+ response_wire_data, _ = socket.recvfrom(2000)
205
+ expect(server.is_dns_packet?(response_wire_data, :udp)).to be true
206
+ end
207
+ end
208
+
209
+
210
+ it 'should instantiate without specifying an options hash' do
211
+ old_verbose, $VERBOSE = $VERBOSE, nil # disable 'constant already initialized' warning
212
+ default_port_sav = Server::DEFAULT_PORT
213
+ Server::DEFAULT_PORT = 9999
214
+ expect(->() { Server.new.close } ).to_not raise_error
215
+ Server::DEFAULT_PORT = default_port_sav
216
+ $VERBOSE = old_verbose
217
+ end
218
+
219
+
220
+ def test_dig_request(protocol)
221
+ Server.with_new_server(options) do |server|
222
+ response_data = "foo.example.com 86400 A 10.1.2.3"
223
+ server.add_conditional_action(caf.specified_a_response('foo.example.com', response_data))
224
+ server.start.wait_until_ready
225
+ dig_command = "dig @#{options[:host]} -p #{options[:port]} foo.example.com #{protocol == :tcp ? ' +tcp' : ''}"
226
+ dig_output = `#{dig_command}`
227
+ expect(dig_output).to include('status: NOERROR')
228
+ # expect(/foo.example.com.\s+86400\s+IN\s+A\s+10.1.2.3/ === dig_output).to be_true
229
+ expect(dig_output).to match(/foo.example.com.\s+86400\s+IN\s+A\s+10.1.2.3/)
230
+ end
231
+
232
+ end
233
+
234
+ it 'should respond correctly to a TCP dig request' do
235
+ test_dig_request(:tcp)
236
+ end
237
+
238
+
239
+ it 'should respond correctly to a UDP dig request' do
240
+ test_dig_request(:udp)
241
+ end
242
+
243
+
244
+ it 'should get a DNS message correctly with TCP' do
245
+ Server.with_new_server(options) do |server|
246
+
247
+ server.add_conditional_action(caf.echo)
248
+ server.start.wait_until_ready
249
+
250
+ client = TCPSocket.new('localhost', options[:port])
251
+
252
+ message = Dnsruby::Message.new('a.com')
253
+ wire_data = MessageHelper.tcp_message_package_for_write(message)
254
+ client.write(wire_data)
255
+ response_wire_data, _ = client.read(wire_data.size)
256
+ expect(server.is_dns_packet?(response_wire_data, :udp)).to be true
257
+
258
+ client.close
259
+ end
260
+ end
261
+
262
+
263
+ it 'should return an AXFR response correctly' do
264
+
265
+ zone = 'ruby-lang.org'
266
+ dns_records = [
267
+ MB.rr('A', 'x.ruby-lang.org', '1.1.1.2'),
268
+ MB.rr('A', 'x.ruby-lang.org', '1.1.1.3'),
269
+ ]
270
+
271
+ Server.with_new_server(options) do |server|
272
+
273
+ serial_history = SerialHistory.new(zone, 3001, dns_records)
274
+ server.add_conditional_action(caf.zone_load(serial_history))
275
+ server.start.wait_until_ready
276
+
277
+ client = TCPSocket.new('localhost', options[:port])
278
+ message = MB.axfr_request(zone)
279
+ wire_data = MessageHelper.tcp_message_package_for_write(message)
280
+ client.write(wire_data)
281
+ response, _ = MessageHelper.read_tcp_message(client)
282
+
283
+ answers = response.answer.to_a
284
+ check_soa = check_soa_fn(answers, zone)
285
+ check_a = check_a_fn(answers, 'x.ruby-lang.org')
286
+
287
+ check_soa.(0, 3001)
288
+ check_a.(1, '1.1.1.2')
289
+ check_a.(2, '1.1.1.3')
290
+ check_soa.(3, 3001)
291
+ end
292
+ end
293
+
294
+
295
+ it 'should return an IXFR response correctly' do
296
+
297
+ zone = 'ruby-lang.org'
298
+
299
+ serial_history = ->() do
300
+ history = SerialHistory.new(zone, 3001)
301
+ history.set_serial_additions(3002, MB.rr('A', 'x.ruby-lang.org', '1.1.1.2'))
302
+ history.set_serial_additions(3003, MB.rr('A', 'x.ruby-lang.org', '1.1.1.3'))
303
+ history.set_serial_additions(3004, MB.rr('A', 'x.ruby-lang.org', '1.1.1.4'))
304
+ history.set_serial_deletions(3004, MB.rr('A', 'x.ruby-lang.org', '1.1.1.2'))
305
+ history.set_serial_additions(3005, MB.rr('A', 'x.ruby-lang.org', '1.1.1.5'))
306
+ history
307
+ end
308
+
309
+ Server.with_new_server(options) do |server|
310
+
311
+ server.add_conditional_action(caf.zone_load(serial_history.()))
312
+ server.start.wait_until_ready
313
+
314
+ # Can put a pry here and then:
315
+ # .dig -p 9999 @127.0.0.1 ruby-lang.org ixfr=3003
316
+
317
+ client = TCPSocket.new('localhost', options[:port])
318
+ query = MB.ixfr_request(zone, 3003)
319
+ wire_data = MessageHelper.tcp_message_package_for_write(query)
320
+ client.write(wire_data)
321
+ response, _ = MessageHelper.read_tcp_message(client)
322
+ #puts `dig -p 9999 @127.0.0.1 ruby-lang.org ixfr=3001 2>&1`
323
+
324
+ expect(response).to be_a(Dnsruby::Message)
325
+
326
+ answers = response.answer.to_a
327
+
328
+ check_soa = check_soa_fn(answers, zone)
329
+ check_a = check_a_fn(answers, 'x.ruby-lang.org')
330
+
331
+ check_soa.(0, 3005)
332
+
333
+ check_soa.(1, 3003)
334
+ check_a.(2, '1.1.1.2')
335
+
336
+ check_soa.(3, 3004)
337
+ check_a.(4, '1.1.1.4')
338
+
339
+ check_soa.(5, 3004)
340
+
341
+ check_soa.(6, 3005)
342
+ check_a.(7, '1.1.1.5')
343
+
344
+ check_soa.(8, 3005)
345
+ end
346
+ end
347
+
348
+
349
+ it 'servers work when there are multiple servers running' do
350
+ begin
351
+ ports = [9998, 9999]
352
+ servers = ports.map { |port| Server.new(options.merge(port: port)) }
353
+ servers.each do |server|
354
+ serial = server.port # for convenience, use server's port as the serial
355
+ server.add_conditional_action(caf.soa_response(serial, 'com'))
356
+ server.start.wait_until_ready
357
+ end
358
+
359
+ servers.each do |server|
360
+ request = MB.soa_request('com')
361
+ response = MessageHelper.send_udp_and_get_response(request, host, server.port)
362
+ expect(MessageTransformer.new(response).serial).to eq(server.port)
363
+ end
364
+
365
+ ensure
366
+ if servers
367
+ servers.each { |server| server.close }
368
+ end
369
+ end
370
+ end
371
+
372
+
373
+ it 'can close multiple times without error' do
374
+ expect(->() { Server.with_new_server(options) do |server|
375
+ server.close
376
+ server.close
377
+ end }).not_to raise_error
378
+ end
379
+
380
+
381
+ it 'will not allow starting a server (and spawning a new thread) more than once' do
382
+ test_lambda = ->() do
383
+ Server.with_new_server(options) do |server|
384
+ server.start.wait_until_ready
385
+ server.start
386
+ end
387
+ end
388
+ expect(test_lambda).to raise_error
389
+ end
390
+
391
+
392
+ it 'kill_all_servers works' do
393
+
394
+ Server.kill_all_servers # make sure we're starting with zero servers
395
+ ports = [9998, 9999]
396
+ servers = ports.map { |port| Server.new(options.merge({ port: port }))}
397
+
398
+ servers.each do |server|
399
+ # for convenience, use server's port as the serial
400
+ server.add_conditional_action(caf.soa_response(1001, 'com'))
401
+ server.start.wait_until_ready
402
+ end
403
+
404
+ expect(servers.size).to eq(2)
405
+ expect(ServerThread.all.size).to eq(2)
406
+ Server.kill_all_servers
407
+ sleep 1
408
+ expect(ServerThread.all.size).to eq(0)
409
+ end
410
+ end
411
+ end