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,73 @@
1
+ require 'forwardable'
2
+ require 'thread_safe'
3
+
4
+ module MockDnsServer
5
+
6
+ class ConditionalActions
7
+
8
+ attr_reader :context
9
+
10
+ extend Forwardable
11
+
12
+ def_delegators :@context, :port, :history, :verbose
13
+
14
+ def initialize(context)
15
+ @context = context
16
+ @records = ThreadSafe::Array.new
17
+ end
18
+
19
+
20
+ def find_conditional_action(request, protocol)
21
+ @records.detect { |cond_action| cond_action.condition.call(request, protocol) }
22
+ end
23
+
24
+
25
+ def respond_to(request, sender, protocol)
26
+ conditional_action = find_conditional_action(request, protocol)
27
+
28
+ if conditional_action
29
+ puts 'Found action' if verbose
30
+ history.add_incoming(request, sender, protocol, conditional_action.description)
31
+ conditional_action.run(request, sender, context, protocol)
32
+ puts 'Completed action' if verbose
33
+ conditional_action.increment_use_count
34
+ handle_use_count(conditional_action)
35
+ else
36
+ puts 'Action not found' if verbose
37
+ history.add_action_not_found(request)
38
+ end
39
+ end
40
+
41
+
42
+ def handle_use_count(conditional_action)
43
+ max_uses = conditional_action.max_uses
44
+ we_care = max_uses && max_uses > 0
45
+ if we_care && conditional_action.use_count >= max_uses
46
+ history.add_conditional_action_removal(conditional_action.description)
47
+ @records.delete(conditional_action)
48
+ end
49
+
50
+ end
51
+
52
+ def add(conditional_action)
53
+ # Place new record at beginning of array, so that the most recently
54
+ # added records are found first.
55
+ @records.unshift(conditional_action)
56
+ end
57
+
58
+
59
+ def remove(conditional_action)
60
+ @records.delete(conditional_action)
61
+ end
62
+
63
+
64
+ def size
65
+ @records.size
66
+ end
67
+
68
+
69
+ def empty?
70
+ size == 0
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,19 @@
1
+ require 'dnsruby'
2
+
3
+ # When adding an RR to a Dnsruby::Message, add_answer checks to see if it already occurs,
4
+ # and, if so, does not add it again. We need to disable this behavior so that we can
5
+ # add a SOA record twice for an AXFR response. So we implement add_answer!,
6
+ # similar to add_answer except that it does not do the inclusion check.
7
+
8
+ module Dnsruby
9
+ class Message
10
+
11
+ def add_answer!(rr) #:nodoc: all
12
+ #if (!@answer.include?rr)
13
+ @answer << rr
14
+ update_counts
15
+ #end
16
+ end
17
+ end
18
+ end
19
+
@@ -0,0 +1,84 @@
1
+ module MockDnsServer
2
+
3
+ # Handles the history of events for this server.
4
+ class History
5
+
6
+ def initialize(context)
7
+ @context = context
8
+ @records = ThreadSafe::Array.new
9
+ end
10
+
11
+
12
+ def size
13
+ @records.size
14
+ end
15
+
16
+
17
+ def add(entry_hash)
18
+ entry_hash[:time] ||= Time.now
19
+ @records << entry_hash
20
+ entry_hash
21
+ end
22
+
23
+
24
+ def add_incoming(message, sender, protocol, description = nil)
25
+ add( {
26
+ type: :incoming,
27
+ message: message,
28
+ sender: sender,
29
+ protocol: protocol,
30
+ description: description
31
+ })
32
+ end
33
+
34
+
35
+ def add_action_not_found(message)
36
+ add( {
37
+ type: :action_not_found,
38
+ message: message
39
+ })
40
+ end
41
+
42
+
43
+ def add_conditional_action_removal(conditional_action)
44
+ add( {
45
+ type: :conditional_action_removal,
46
+ conditional_action: conditional_action
47
+ })
48
+ end
49
+
50
+
51
+ def add_notify_response(response, zts_host, zts_port, protocol)
52
+ add( {
53
+ type: :notify_response,
54
+ message: response,
55
+ host: zts_host,
56
+ port: zts_port,
57
+ protocol: protocol,
58
+ description: "notify response from #{zts_host}:#{zts_port}"
59
+ })
60
+ end
61
+
62
+
63
+ def occurred?(inspection)
64
+ HistoryInspections.new.apply(@records, inspection).size > 0
65
+ end
66
+
67
+
68
+ # @return a clone of the array
69
+ def to_a
70
+ @records.clone
71
+ end
72
+
73
+
74
+ def copy
75
+ @records.map { |record| record.clone }
76
+ end
77
+
78
+
79
+ def to_s
80
+ "#{super}: #{@records}"
81
+ end
82
+
83
+ end
84
+ end
@@ -0,0 +1,58 @@
1
+ require 'mock_dns_server/message_transformer'
2
+
3
+ module MockDnsServer
4
+
5
+ class HistoryInspections
6
+
7
+ MT = MessageTransformer
8
+
9
+ def type(type)
10
+ ->(record) { record[:type] == type }
11
+ end
12
+
13
+ def qtype(qtype)
14
+ ->(record) do
15
+ qtype_in_message = MT.new(record[:message]).qtype.to_s
16
+ qtype_in_message == qtype
17
+ end
18
+ end
19
+
20
+ def qname(qname)
21
+ ->(record) do
22
+ qname_in_message = MT.new(record[:message]).qname.to_s
23
+ qname_in_message == qname
24
+ end
25
+ end
26
+
27
+ def soa
28
+ qtype('SOA')
29
+ end
30
+
31
+ def protocol(protocol)
32
+ ->(record) { record[:protocol] == protocol }
33
+ end
34
+
35
+ def all(*inspections)
36
+ ->(record) do
37
+ inspections.all? { |inspection| inspection.(record) }
38
+ end
39
+ end
40
+
41
+ def any(*inspections)
42
+ ->(record) do
43
+ inspections.any? { |inspection| inspection.(record) }
44
+ end
45
+ end
46
+
47
+ def none(*inspections)
48
+ ->(record) do
49
+ inspections.none? { |inspection| inspection.(record) }
50
+ end
51
+ end
52
+
53
+ def apply(records, inspection)
54
+ records.select { |record| inspection.(record) }
55
+ end
56
+ end
57
+
58
+ end
@@ -0,0 +1,34 @@
1
+ require 'ipaddr'
2
+ require 'dnsruby'
3
+
4
+ module MockDnsServer
5
+
6
+ # Wraps Ruby's IPAddr class and yields successive IP addresses.
7
+ # Instantiate it with a starting address (IPV4 or IPV6), and when
8
+ # you call .next, it will provide addresses that increment by 1
9
+ # or optional step parameter, starting with your initial address.
10
+ #
11
+ # We'll probably want to instruct it to be more intelligent, e.g. skip .0 and .255.
12
+ class IpAddressDispenser
13
+
14
+ # @param initial_address first address dispensed, and basis for subsequent 'next' calls
15
+ def initialize(initial_address = '192.168.1.1')
16
+ @initial_address = initial_address
17
+ end
18
+
19
+
20
+ # @param step number of addresses to step in each next call, defaults to 1
21
+ def next(step = 1)
22
+ step.times do
23
+ if @address.nil?
24
+ @address = IPAddr.new(@initial_address)
25
+ elsif @address.to_s == '255.255.255.255'
26
+ @address = IPAddr.new('1.1.1.1')
27
+ else
28
+ @address = @address.succ
29
+ end
30
+ end
31
+ @address.to_s
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,199 @@
1
+ require 'ipaddr'
2
+
3
+ require 'dnsruby'
4
+ require 'mock_dns_server/dnsruby_monkey_patch'
5
+ require 'mock_dns_server/ip_address_dispenser'
6
+ require 'mock_dns_server/serial_number'
7
+
8
+ module MockDnsServer
9
+
10
+ module MessageBuilder
11
+
12
+ module_function
13
+
14
+ #def soa_response(zone, serial, expire = nil, refresh = nil)
15
+ #
16
+ # options = {
17
+ # type: 'SOA', klass: 'IN', name: zone, ttl: 3600, serial: serial
18
+ # }
19
+ # options[:refresh] = refresh if refresh
20
+ # options[:expire] = expire if expire
21
+ #
22
+ #
23
+ # response = Dnsruby::Message.new
24
+ #
25
+ # answer = Dnsruby::RR.new_from_hash(options)
26
+ # response.add_answer(answer)
27
+ #
28
+ # response
29
+ #
30
+ # Additional options:
31
+ # @mname = Name.create(hash[:mname])
32
+ # @rname = Name.create(hash[:rname])
33
+ # @serial = hash[:serial].to_i
34
+ # @refresh = hash[:refresh].to_i
35
+ # @retry = hash[:retry].to_i
36
+ # @expire = hash[:expire].to_i
37
+ # @minimum = hash[:minimum].to_i
38
+ #end
39
+
40
+
41
+ # Builds a response to an 'A' request from the encoded string passed.
42
+ # @param answer_string string in any format supported by Dnsruby::RR.create
43
+ # (see resource.rb, e.g. https://github.com/vertis/dnsruby/blob/master/lib/Dnsruby/resource/resource.rb,
44
+ # line 643 ff at the time of this writing):
45
+ #
46
+ # a = Dnsruby::RR.create("foo.example.com. 86400 A 10.1.2.3")
47
+ def specified_a_response(answer_string)
48
+ message = Dnsruby::Message.new
49
+ message.header.qr = true
50
+ answer = Dnsruby::RR.create(answer_string)
51
+ message.add_answer(answer)
52
+ message
53
+ end
54
+
55
+
56
+ def dummy_a_response(record_count, domain, ttl = 86400)
57
+ ip_dispenser = IpAddressDispenser.new
58
+ message = Dnsruby::Message.new
59
+ message.header.qr = true
60
+
61
+ record_count.times do
62
+ answer = Dnsruby::RR.new_from_hash(
63
+ name: domain, ttl: ttl, type: 'A', address: ip_dispenser.next, klass: 'IN')
64
+ message.add_answer(answer)
65
+ end
66
+ message
67
+ end
68
+
69
+
70
+ # Gets the serial value from the passed object; if it's a SerialNumber,
71
+ # calls its value method; if not, we assume it's a number and it's returned unchanged.
72
+ def serial_value(serial)
73
+ serial.is_a?(SerialNumber) ? serial.value : serial
74
+ end
75
+
76
+
77
+ def soa_request(name)
78
+ Dnsruby::Message.new(name, 'SOA', 'IN')
79
+ end
80
+
81
+
82
+ def soa_answer(options)
83
+ mname = options[:mname] || 'default.com'
84
+
85
+ Dnsruby::RR.create( {
86
+ name: options[:name],
87
+ ttl: options[:ttl] || 3600,
88
+ type: 'SOA',
89
+ serial: serial_value(options[:serial]),
90
+ mname: mname,
91
+ rname: 'admin.' + mname,
92
+ refresh: options[:refresh] || 3600,
93
+ retry: options[:retry] || 3600,
94
+ expire: options[:expire] || 3600,
95
+ minimum: options[:minimum] || 3600
96
+ } )
97
+ end
98
+
99
+
100
+ # Builds a Dnsruby::RR instance with the specified type, name, and rdata,
101
+ # with a hard coded TTL and class 'IN'.
102
+ def rr(type, name, rdata)
103
+ ttl = 3600
104
+ klass = 'IN'
105
+ string = [name, ttl, klass, type, rdata].join(' ')
106
+ Dnsruby::RR.new_from_string(string)
107
+ end
108
+
109
+
110
+ # Creates an NS RR record.
111
+ def ns(owner, address)
112
+ rr('NS', owner, address)
113
+ end
114
+
115
+
116
+ # Creates a Dnsruby::Update object from a hash,
117
+ # as it would be generated from a Cucumber table
118
+ # with the following headings:
119
+ # | Action | Type | Domain | RDATA |
120
+ def dns_update(zone, records)
121
+ update = Dnsruby::Update.new(zone)
122
+ records.each do |r|
123
+ if r.type.upcase == 'ADD'
124
+ s = "#{Domain} 3600 #{Type} #{RDATA}"
125
+ rr = Dnsruby::RR.create(s)
126
+ update.add(rr)
127
+ else
128
+ update.delete(r['Domain'], r['Type'], r['RDATA'])
129
+ end
130
+ end
131
+ update
132
+ end
133
+
134
+
135
+ # @param options a hash containing values for keys [:name, :serial, :mname]
136
+ # TODO: Eliminate duplication of soa_response and notify_message
137
+ def soa_response(options)
138
+ raise "Must provide zone name as options[:name]." if options[:name].nil?
139
+ message = Dnsruby::Message.new(options[:name], 'SOA', 'IN')
140
+ message.header.qr = true
141
+ message.add_answer(soa_answer(options))
142
+ message
143
+ end
144
+
145
+
146
+ # Builds a SOA RR suitable for inclusion in the authority section of an IXFR query.
147
+ def ixfr_request_soa_rr(zone, serial)
148
+ options = {
149
+ name: zone,
150
+ type: 'SOA',
151
+ ttl: 3600,
152
+ klass: 'IN',
153
+ mname: '.',
154
+ rname: '.',
155
+ serial: serial_value(serial),
156
+ refresh: 0,
157
+ retry: 0,
158
+ expire: 0,
159
+ minimum: 0
160
+ }
161
+
162
+ Dnsruby::RR.new_from_hash(options)
163
+ end
164
+
165
+
166
+ def ixfr_request(zone, serial)
167
+ query = Dnsruby::Message.new(zone, 'IXFR')
168
+ query.add_authority(ixfr_request_soa_rr(zone, serial_value(serial)))
169
+ query
170
+ end
171
+
172
+
173
+ def axfr_request(zone)
174
+ Dnsruby::Message.new(zone, 'AXFR')
175
+ end
176
+
177
+ # @param options a hash containing values for keys [:name, :serial, :mname]
178
+ def notify_message(options)
179
+
180
+ message = Dnsruby::Message.new(options[:name], 'SOA', 'IN')
181
+ message.header.opcode = Dnsruby::OpCode::Notify
182
+
183
+ mname = options[:mname] || 'default.com'
184
+
185
+ message.add_answer(Dnsruby::RR.new_from_hash( {
186
+ name: options[:name],
187
+ type: 'SOA',
188
+ serial: serial_value(options[:serial]),
189
+ mname: mname,
190
+ rname: 'admin.' + mname,
191
+ refresh: 3600,
192
+ retry: 3600,
193
+ expire: 3600,
194
+ minimum: 3600
195
+ } ))
196
+ message
197
+ end
198
+ end
199
+ end