-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Keith Bennett
committed
Oct 30, 2014
0 parents
commit 8dee673
Showing
42 changed files
with
3,726 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
*.gem | ||
*.rbc | ||
.bundle | ||
.config | ||
.yardoc | ||
Gemfile.lock | ||
InstalledFiles | ||
_yardoc | ||
coverage | ||
doc/ | ||
lib/bundler/man | ||
pkg | ||
rdoc | ||
spec/reports | ||
test/tmp | ||
test/version_tmp | ||
tmp | ||
.idea/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
source 'https://rubygems.org' | ||
|
||
# Specify your gem's dependencies in mock_dns_server.gemspec | ||
gemspec |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
Copyright (c) 2014, Verisign, Inc. | ||
All rights reserved. | ||
|
||
Redistribution and use in source and binary forms, with or without | ||
modification, are permitted provided that the following conditions are met: | ||
* Redistributions of source code must retain the above copyright | ||
notice, this list of conditions and the following disclaimer. | ||
* Redistributions in binary form must reproduce the above copyright | ||
notice, this list of conditions and the following disclaimer in the | ||
documentation and/or other materials provided with the distribution. | ||
* Neither the name of Verisign, Inc. nor the | ||
names of its contributors may be used to endorse or promote products | ||
derived from this software without specific prior written permission. | ||
|
||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | ||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY | ||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
# MockDnsServer | ||
|
||
A mock DNS server that can be instructed to perform actions based on | ||
user-provided conditions, and queried for its history of inputs and outputs. | ||
This server listens and responds on both UDP and TCP ports. | ||
|
||
An admin interface is provided, currently in the form of Ruby methods that | ||
can be called. In the future we will probably add an HTTP interface | ||
to these methods. The admin methods: | ||
|
||
* instructing the server how to respond given the characteristics of the request | ||
* query the server for its history of inputs and outputs | ||
* request shutdown | ||
|
||
|
||
## Implementation | ||
|
||
|
||
### Threads | ||
|
||
```server.start``` launches its own thread in which it runs. This thread is terminated when ```server.close``` is called. | ||
|
||
Admin requests (configuration, analysis, etc.) will occur on the caller's thread. | ||
|
||
|
||
### Starting a Server | ||
|
||
The simplest way to run a server is to call the convenience method ```Server#with_new_server```: | ||
|
||
```ruby | ||
Server.with_new_server(options) do |server| | ||
# configure server with conditional actions | ||
server.start | ||
end | ||
``` | ||
|
||
Options currently consist of: | ||
|
||
Option Name|Default Value|Description | ||
-----------|-------------|-----------| | ||
port | 53 | port on which to listen (UDP and TCP) | ||
timeout | 0.25 sec | timeout for IO.select call | ||
verbose | false | Whether or not to output diagnostic messages | ||
|
||
The code above will result in the creation of a new thread in which the server will listen | ||
and respond indefinitely. Terminating the server is accomplished by calling server.close | ||
in the caller's thread. | ||
|
||
|
||
### Locking | ||
|
||
A single mutex will be used to protect access to the rules and history objects. | ||
All mutex handling will be done by code in this gem, so the caller does not | ||
need to know or care that it is being done. | ||
|
||
|
||
### Message Read Loop | ||
|
||
The server will have the following flow of execution: | ||
|
||
``` | ||
loop do | ||
read a packet | ||
attempt to parse it into a Dnsruby::Message object; if not, the message will be a string | ||
mutex.synchronize do | ||
action = look up rule | ||
action.call # (perform appropriate action -- send or don't send response, etc.) | ||
add input and output to history | ||
end | ||
end | ||
``` | ||
|
||
The above loop is wrapped in an IO.select timeout loop, although currently nothing | ||
is done at timeout time. (Closing the server is accomplished by calling server.close | ||
on the caller's thread.) | ||
|
||
For TCP, since the application layer requires that the transmission begin with a 2-byte | ||
message length field (which is packed/unpacked with 'n'), this field is read first, | ||
and the server continues reading until the entire transmission is read. | ||
|
||
### Conditional Actions | ||
|
||
The server can be set up with conditional actions that will control how it responds to | ||
incoming requests. Basically, a ConditionalAction consists of a proc (usually a lambda) | ||
that will be called to determine whether or not the action should be executed, | ||
and another proc (also usually a lambda) defining the action that should be performed. | ||
|
||
Only one conditional action will be performed per incoming request. | ||
The conditions in the conditional actions are evaluated in the order with which | ||
they were added to the server. When a condition returns true, the corresponding | ||
action will be performed, and the message loop iteration will end. | ||
|
||
For more information about how conditional actions are created, see the ConditionalAction, | ||
PredicateFactory, ActionFactory, and ConditionalActionFactory classes. For how | ||
the conditional actions are searched and performed, see the ConditionalActions class. | ||
|
||
|
||
### History | ||
|
||
To get a history of the server's events, call ```server.history_copy```. | ||
|
||
|
||
## Installation | ||
|
||
Add this line to your application's Gemfile: | ||
|
||
gem 'mock_dns_server' | ||
|
||
And then execute: | ||
|
||
$ bundle | ||
|
||
Or install it yourself as: | ||
|
||
$ gem install mock_dns_server | ||
|
||
## Usage | ||
|
||
TODO: Write usage instructions here | ||
|
||
## Contributing | ||
|
||
1. Fork it | ||
2. Create your feature branch (`git checkout -b my-new-feature`) | ||
3. Commit your changes (`git commit -am 'Add some feature'`) | ||
4. Push to the branch (`git push origin my-new-feature`) | ||
5. Create new Pull Request |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
## v0.1.0 | ||
|
||
* First open sourced version. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
# encoding: utf-8 | ||
require 'rubygems' | ||
require 'bundler' | ||
require "bundler/gem_tasks" | ||
|
||
begin | ||
Bundler.setup(:default, :development) | ||
rescue Bundler::BundlerError => e | ||
$stderr.puts e.message | ||
$stderr.puts "Run `bundle install` to install missing gems" | ||
exit e.status_code | ||
end | ||
require 'rake' | ||
|
||
require 'rspec/core/rake_task' | ||
RSpec::Core::RakeTask.new(:spec) | ||
|
||
task :test => :spec | ||
task :default => :spec |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
#!/usr/bin/env ruby | ||
|
||
# Stands up a Mock DNS Server to receive a dig request, | ||
# and outputs the request to stdout using Dnsruby::Message#to_s, | ||
# whose output differs somewhat from dig output, but has | ||
# the same information.. | ||
|
||
require 'mock_dns_server' | ||
require 'pry' | ||
require 'hexdump' | ||
|
||
MDS = MockDnsServer | ||
|
||
def conditional_action | ||
condition = MDS::PredicateFactory.new.always_true | ||
action = MDS::ActionFactory.new.puts_and_echo | ||
MDS::ConditionalAction.new(condition, action, 1) | ||
end | ||
|
||
def run | ||
MDS::Server.with_new_server(host: 'localhost', port: 9999) do |server| | ||
server.add_conditional_action(conditional_action) | ||
server.start | ||
puts "\nReflected message as per Dnsruby:\n\n" | ||
output = `dig -p 9999 @localhost #{ARGV[0]} 2>&1` | ||
puts "Reflected message as per dig:\n\n#{output}\n" | ||
puts "Executing pry, press [Ctrl-D] to exit.\n\n" | ||
binding.pry | ||
raise output if $? != 0 | ||
end | ||
end | ||
|
||
def validate_input | ||
if ARGV.empty? | ||
puts "Syntax is show_dig_request \"[dig arguments]\" but without specifying host or port." | ||
exit -1 | ||
end | ||
end | ||
|
||
validate_input | ||
run |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
|
||
# This is put in a lambda so as not to pollute the namespace with variables that will be useless later | ||
->() { | ||
file_mask = File.join(File.dirname(__FILE__), '**/*.rb') | ||
files_to_require = Dir[file_mask] | ||
files_to_require.each { |file| require file } | ||
}.call | ||
|
||
|
||
module MockDnsServer | ||
# Your code goes here... | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
require 'mock_dns_server/message_builder' | ||
|
||
module MockDnsServer | ||
|
||
# Creates and returns actions that will be run upon receiving incoming messages. | ||
class ActionFactory | ||
|
||
include MessageBuilder | ||
|
||
# Echos the request back to the sender. | ||
def echo | ||
->(incoming_message, sender, context, protocol) do | ||
context.server.send_response(sender, incoming_message, protocol) | ||
end | ||
end | ||
|
||
def puts_and_echo | ||
->(incoming_message, sender, context, protocol) do | ||
puts "Received #{protocol.to_s.upcase} message from #{sender}:\n#{incoming_message}\n\n" | ||
puts "Hex:\n\n" | ||
puts "#{incoming_message.encode.hexdump}\n\n" | ||
echo.(incoming_message, sender, context, protocol) | ||
end | ||
end | ||
|
||
# Responds with the same object regardless of the request content. | ||
def constant(constant_object) | ||
->(_, sender, context, protocol) do | ||
context.server.send_response(sender, constant_object, protocol) | ||
end | ||
end | ||
|
||
|
||
# Sends a SOA response. | ||
def send_soa(zone, serial, expire = nil, refresh = nil) | ||
send_message(soa_response( | ||
name: zone, serial: serial, expire: expire, refresh: refresh)) | ||
end | ||
|
||
|
||
# Sends a fixed DNSRuby::Message. | ||
def send_message(response) | ||
->(incoming_message, sender, context, protocol) do | ||
|
||
if [incoming_message, response].all? { |m| m.is_a?(Dnsruby::Message) } | ||
response.header.id = incoming_message.header.id | ||
end | ||
if response.is_a?(Dnsruby::Message) | ||
response.header.qr = true | ||
end | ||
context.server.send_response(sender, response, protocol) | ||
end | ||
end | ||
|
||
# Outputs the string representation of the incoming message to stdout. | ||
def puts_message | ||
->(incoming_message, sender, context, protocol) do | ||
puts incoming_message | ||
end | ||
end | ||
|
||
|
||
def zone_load(serial_history) | ||
->(incoming_message, sender, context, protocol) do | ||
|
||
mt = MessageTransformer.new(incoming_message) | ||
zone = mt.qname | ||
type = mt.qtype | ||
|
||
if serial_history.zone.downcase != zone.downcase | ||
raise "Zones differ (history: #{serial_history.zone}, request: #{zone}" | ||
end | ||
|
||
if %w(AXFR IXFR).include?(type) | ||
xfr_response = serial_history.xfr_response(incoming_message) | ||
send_message(xfr_response).(incoming_message, sender, context, :tcp) | ||
elsif type == 'SOA' | ||
send_soa(zone, serial_history.high_serial).(incoming_message, sender, context, protocol) | ||
end | ||
end | ||
end | ||
|
||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
module MockDnsServer | ||
|
||
class ConditionalAction | ||
|
||
attr_accessor :condition, :action, :description, :max_uses, :use_count | ||
|
||
|
||
# @param condition a proc/lambda that, when called with request as a param, returns | ||
# true or false to determine whether or not the action will be executed | ||
# @param action the code (lambda or proc) to be executed; takes incoming message, | ||
# sender, server context, and (optionally) protocol as parameters | ||
# and performs an action | ||
# @param max_uses maximum number of times this action should be executed | ||
# @return the value returned by the action, e.g. the message, or array of messages, it sent | ||
def initialize(condition, action, max_uses) | ||
@condition = condition | ||
@action = action | ||
@max_uses = max_uses | ||
@use_count = 0 | ||
end | ||
|
||
|
||
def increment_use_count | ||
@use_count += 1 | ||
end | ||
|
||
def to_s | ||
"#{super.to_s}; condition: #{condition.to_s}, action = #{action.to_s}, max_uses = #{max_uses}" | ||
end | ||
|
||
def eligible_to_run? | ||
max_not_reached = max_uses.nil? || use_count < max_uses | ||
max_not_reached && condition.call | ||
end | ||
|
||
def run(request, sender, context, protocol) | ||
# TODO: Output to history? | ||
action.call(request, sender, context, protocol) | ||
end | ||
|
||
end | ||
end |
Oops, something went wrong.