mock_dns_server 0.3.0

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