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,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: abc38df32ffd449faa5b893d4b608eb08c3bcc53
4
+ data.tar.gz: c088ed5ad24f4e41457bf747f36dab340b210865
5
+ SHA512:
6
+ metadata.gz: 08e6c68851d74ac431d6c492b36ff25dec0c3be893d0f58d491c8f47f8b06c905365ee8e798898de6bc0a1a68a1ea896d368cfe576c05b8a48e32f4ebf4e204a
7
+ data.tar.gz: 584ab899e561059c4f07b94342a770821bc3b5989c23d6cc635fa4041ddea8fba5b61f5c455c474b9bf3faacf6599262bb3d3aa0d5ae9bd0e20f6328f6fc2415
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .idea/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in mock_dns_server.gemspec
4
+ gemspec
@@ -0,0 +1,24 @@
1
+ Copyright (c) 2014, Verisign, Inc.
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions are met:
6
+ * Redistributions of source code must retain the above copyright
7
+ notice, this list of conditions and the following disclaimer.
8
+ * Redistributions in binary form must reproduce the above copyright
9
+ notice, this list of conditions and the following disclaimer in the
10
+ documentation and/or other materials provided with the distribution.
11
+ * Neither the name of Verisign, Inc. nor the
12
+ names of its contributors may be used to endorse or promote products
13
+ derived from this software without specific prior written permission.
14
+
15
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
16
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18
+ DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
19
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
22
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,127 @@
1
+ # MockDnsServer
2
+
3
+ A mock DNS server that can be instructed to perform actions based on
4
+ user-provided conditions, and queried for its history of inputs and outputs.
5
+ This server listens and responds on both UDP and TCP ports.
6
+
7
+ An admin interface is provided, currently in the form of Ruby methods that
8
+ can be called. In the future we will probably add an HTTP interface
9
+ to these methods. The admin methods:
10
+
11
+ * instructing the server how to respond given the characteristics of the request
12
+ * query the server for its history of inputs and outputs
13
+ * request shutdown
14
+
15
+
16
+ ## Implementation
17
+
18
+
19
+ ### Threads
20
+
21
+ ```server.start``` launches its own thread in which it runs. This thread is terminated when ```server.close``` is called.
22
+
23
+ Admin requests (configuration, analysis, etc.) will occur on the caller's thread.
24
+
25
+
26
+ ### Starting a Server
27
+
28
+ The simplest way to run a server is to call the convenience method ```Server#with_new_server```:
29
+
30
+ ```ruby
31
+ Server.with_new_server(options) do |server|
32
+ # configure server with conditional actions
33
+ server.start
34
+ end
35
+ ```
36
+
37
+ Options currently consist of:
38
+
39
+ Option Name|Default Value|Description
40
+ -----------|-------------|-----------|
41
+ port | 53 | port on which to listen (UDP and TCP)
42
+ timeout | 0.25 sec | timeout for IO.select call
43
+ verbose | false | Whether or not to output diagnostic messages
44
+
45
+ The code above will result in the creation of a new thread in which the server will listen
46
+ and respond indefinitely. Terminating the server is accomplished by calling server.close
47
+ in the caller's thread.
48
+
49
+
50
+ ### Locking
51
+
52
+ A single mutex will be used to protect access to the rules and history objects.
53
+ All mutex handling will be done by code in this gem, so the caller does not
54
+ need to know or care that it is being done.
55
+
56
+
57
+ ### Message Read Loop
58
+
59
+ The server will have the following flow of execution:
60
+
61
+ ```
62
+ loop do
63
+ read a packet
64
+ attempt to parse it into a Dnsruby::Message object; if not, the message will be a string
65
+ mutex.synchronize do
66
+ action = look up rule
67
+ action.call # (perform appropriate action -- send or don't send response, etc.)
68
+ add input and output to history
69
+ end
70
+ end
71
+ ```
72
+
73
+ The above loop is wrapped in an IO.select timeout loop, although currently nothing
74
+ is done at timeout time. (Closing the server is accomplished by calling server.close
75
+ on the caller's thread.)
76
+
77
+ For TCP, since the application layer requires that the transmission begin with a 2-byte
78
+ message length field (which is packed/unpacked with 'n'), this field is read first,
79
+ and the server continues reading until the entire transmission is read.
80
+
81
+ ### Conditional Actions
82
+
83
+ The server can be set up with conditional actions that will control how it responds to
84
+ incoming requests. Basically, a ConditionalAction consists of a proc (usually a lambda)
85
+ that will be called to determine whether or not the action should be executed,
86
+ and another proc (also usually a lambda) defining the action that should be performed.
87
+
88
+ Only one conditional action will be performed per incoming request.
89
+ The conditions in the conditional actions are evaluated in the order with which
90
+ they were added to the server. When a condition returns true, the corresponding
91
+ action will be performed, and the message loop iteration will end.
92
+
93
+ For more information about how conditional actions are created, see the ConditionalAction,
94
+ PredicateFactory, ActionFactory, and ConditionalActionFactory classes. For how
95
+ the conditional actions are searched and performed, see the ConditionalActions class.
96
+
97
+
98
+ ### History
99
+
100
+ To get a history of the server's events, call ```server.history_copy```.
101
+
102
+
103
+ ## Installation
104
+
105
+ Add this line to your application's Gemfile:
106
+
107
+ gem 'mock_dns_server'
108
+
109
+ And then execute:
110
+
111
+ $ bundle
112
+
113
+ Or install it yourself as:
114
+
115
+ $ gem install mock_dns_server
116
+
117
+ ## Usage
118
+
119
+ TODO: Write usage instructions here
120
+
121
+ ## Contributing
122
+
123
+ 1. Fork it
124
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
125
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
126
+ 4. Push to the branch (`git push origin my-new-feature`)
127
+ 5. Create new Pull Request
@@ -0,0 +1,3 @@
1
+ ## v0.1.0
2
+
3
+ * First open sourced version.
@@ -0,0 +1,19 @@
1
+ # encoding: utf-8
2
+ require 'rubygems'
3
+ require 'bundler'
4
+ require "bundler/gem_tasks"
5
+
6
+ begin
7
+ Bundler.setup(:default, :development)
8
+ rescue Bundler::BundlerError => e
9
+ $stderr.puts e.message
10
+ $stderr.puts "Run `bundle install` to install missing gems"
11
+ exit e.status_code
12
+ end
13
+ require 'rake'
14
+
15
+ require 'rspec/core/rake_task'
16
+ RSpec::Core::RakeTask.new(:spec)
17
+
18
+ task :test => :spec
19
+ task :default => :spec
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Stands up a Mock DNS Server to receive a dig request,
4
+ # and outputs the request to stdout using Dnsruby::Message#to_s,
5
+ # whose output differs somewhat from dig output, but has
6
+ # the same information..
7
+
8
+ require 'mock_dns_server'
9
+ require 'pry'
10
+ require 'hexdump'
11
+
12
+ MDS = MockDnsServer
13
+
14
+ def conditional_action
15
+ condition = MDS::PredicateFactory.new.always_true
16
+ action = MDS::ActionFactory.new.puts_and_echo
17
+ MDS::ConditionalAction.new(condition, action, 1)
18
+ end
19
+
20
+ def run
21
+ MDS::Server.with_new_server(host: 'localhost', port: 9999) do |server|
22
+ server.add_conditional_action(conditional_action)
23
+ server.start
24
+ puts "\nReflected message as per Dnsruby:\n\n"
25
+ output = `dig -p 9999 @localhost #{ARGV[0]} 2>&1`
26
+ puts "Reflected message as per dig:\n\n#{output}\n"
27
+ puts "Executing pry, press [Ctrl-D] to exit.\n\n"
28
+ binding.pry
29
+ raise output if $? != 0
30
+ end
31
+ end
32
+
33
+ def validate_input
34
+ if ARGV.empty?
35
+ puts "Syntax is show_dig_request \"[dig arguments]\" but without specifying host or port."
36
+ exit -1
37
+ end
38
+ end
39
+
40
+ validate_input
41
+ run
@@ -0,0 +1,12 @@
1
+
2
+ # This is put in a lambda so as not to pollute the namespace with variables that will be useless later
3
+ ->() {
4
+ file_mask = File.join(File.dirname(__FILE__), '**/*.rb')
5
+ files_to_require = Dir[file_mask]
6
+ files_to_require.each { |file| require file }
7
+ }.call
8
+
9
+
10
+ module MockDnsServer
11
+ # Your code goes here...
12
+ end
@@ -0,0 +1,84 @@
1
+ require 'mock_dns_server/message_builder'
2
+
3
+ module MockDnsServer
4
+
5
+ # Creates and returns actions that will be run upon receiving incoming messages.
6
+ class ActionFactory
7
+
8
+ include MessageBuilder
9
+
10
+ # Echos the request back to the sender.
11
+ def echo
12
+ ->(incoming_message, sender, context, protocol) do
13
+ context.server.send_response(sender, incoming_message, protocol)
14
+ end
15
+ end
16
+
17
+ def puts_and_echo
18
+ ->(incoming_message, sender, context, protocol) do
19
+ puts "Received #{protocol.to_s.upcase} message from #{sender}:\n#{incoming_message}\n\n"
20
+ puts "Hex:\n\n"
21
+ puts "#{incoming_message.encode.hexdump}\n\n"
22
+ echo.(incoming_message, sender, context, protocol)
23
+ end
24
+ end
25
+
26
+ # Responds with the same object regardless of the request content.
27
+ def constant(constant_object)
28
+ ->(_, sender, context, protocol) do
29
+ context.server.send_response(sender, constant_object, protocol)
30
+ end
31
+ end
32
+
33
+
34
+ # Sends a SOA response.
35
+ def send_soa(zone, serial, expire = nil, refresh = nil)
36
+ send_message(soa_response(
37
+ name: zone, serial: serial, expire: expire, refresh: refresh))
38
+ end
39
+
40
+
41
+ # Sends a fixed DNSRuby::Message.
42
+ def send_message(response)
43
+ ->(incoming_message, sender, context, protocol) do
44
+
45
+ if [incoming_message, response].all? { |m| m.is_a?(Dnsruby::Message) }
46
+ response.header.id = incoming_message.header.id
47
+ end
48
+ if response.is_a?(Dnsruby::Message)
49
+ response.header.qr = true
50
+ end
51
+ context.server.send_response(sender, response, protocol)
52
+ end
53
+ end
54
+
55
+ # Outputs the string representation of the incoming message to stdout.
56
+ def puts_message
57
+ ->(incoming_message, sender, context, protocol) do
58
+ puts incoming_message
59
+ end
60
+ end
61
+
62
+
63
+ def zone_load(serial_history)
64
+ ->(incoming_message, sender, context, protocol) do
65
+
66
+ mt = MessageTransformer.new(incoming_message)
67
+ zone = mt.qname
68
+ type = mt.qtype
69
+
70
+ if serial_history.zone.downcase != zone.downcase
71
+ raise "Zones differ (history: #{serial_history.zone}, request: #{zone}"
72
+ end
73
+
74
+ if %w(AXFR IXFR).include?(type)
75
+ xfr_response = serial_history.xfr_response(incoming_message)
76
+ send_message(xfr_response).(incoming_message, sender, context, :tcp)
77
+ elsif type == 'SOA'
78
+ send_soa(zone, serial_history.high_serial).(incoming_message, sender, context, protocol)
79
+ end
80
+ end
81
+ end
82
+
83
+ end
84
+ end
@@ -0,0 +1,42 @@
1
+ module MockDnsServer
2
+
3
+ class ConditionalAction
4
+
5
+ attr_accessor :condition, :action, :description, :max_uses, :use_count
6
+
7
+
8
+ # @param condition a proc/lambda that, when called with request as a param, returns
9
+ # true or false to determine whether or not the action will be executed
10
+ # @param action the code (lambda or proc) to be executed; takes incoming message,
11
+ # sender, server context, and (optionally) protocol as parameters
12
+ # and performs an action
13
+ # @param max_uses maximum number of times this action should be executed
14
+ # @return the value returned by the action, e.g. the message, or array of messages, it sent
15
+ def initialize(condition, action, max_uses)
16
+ @condition = condition
17
+ @action = action
18
+ @max_uses = max_uses
19
+ @use_count = 0
20
+ end
21
+
22
+
23
+ def increment_use_count
24
+ @use_count += 1
25
+ end
26
+
27
+ def to_s
28
+ "#{super.to_s}; condition: #{condition.to_s}, action = #{action.to_s}, max_uses = #{max_uses}"
29
+ end
30
+
31
+ def eligible_to_run?
32
+ max_not_reached = max_uses.nil? || use_count < max_uses
33
+ max_not_reached && condition.call
34
+ end
35
+
36
+ def run(request, sender, context, protocol)
37
+ # TODO: Output to history?
38
+ action.call(request, sender, context, protocol)
39
+ end
40
+
41
+ end
42
+ end
@@ -0,0 +1,53 @@
1
+ require 'mock_dns_server/conditional_action'
2
+ require 'mock_dns_server/action_factory'
3
+ require 'mock_dns_server/predicate_factory'
4
+
5
+ module MockDnsServer
6
+
7
+
8
+ # Provides conditional actions that may be commonly used.
9
+ class ConditionalActionFactory
10
+
11
+ attr_reader :action_factory
12
+
13
+ def initialize
14
+ @action_factory = ActionFactory.new
15
+ @predicate_factory = PredicateFactory.new
16
+ end
17
+
18
+
19
+ # Probably only used for testing, this ConditionalAction will unconditionally
20
+ # respond with the same object it receives.
21
+ def echo
22
+ ConditionalAction.new(PredicateFactory.new.always_true, action_factory.echo, 0)
23
+ end
24
+
25
+
26
+ def specified_a_response(qname, answer_string, times = 0)
27
+ predicate = @predicate_factory.a_request(qname)
28
+ response = MessageBuilder.specified_a_response(answer_string)
29
+ action = @action_factory.send_message(response)
30
+ ConditionalAction.new(predicate, action, times)
31
+ end
32
+
33
+
34
+ # Causes a SOA response with the specified serial to be sent upon receiving a SOA request.
35
+ # If zone is specified, the action will only be performed if the request specifies that zone.
36
+ def soa_response(serial, zone, times = 0)
37
+ predicate = @predicate_factory.all(@predicate_factory.qname(zone), @predicate_factory.soa)
38
+ response = MessageBuilder.soa_response(name: zone, serial: serial)
39
+ action = @action_factory.send_message(response)
40
+ ConditionalAction.new(predicate, action, times)
41
+ end
42
+
43
+
44
+ # Sets up the server to respond to SOA requests with the given SOA, and respond to AXFR
45
+ # requests with the specified data, wrapped in SOA responses.
46
+ def zone_load(serial_history, times = 0)
47
+ pr = @predicate_factory
48
+ predicate = pr.all(pr.qname(serial_history.zone), pr.zone_load)
49
+ action = ActionFactory.new.zone_load(serial_history)
50
+ ConditionalAction.new(predicate, action, times)
51
+ end
52
+ end
53
+ end