async-dns 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +4 -0
  3. data/.travis.yml +18 -0
  4. data/Gemfile +14 -0
  5. data/README.md +144 -0
  6. data/Rakefile +32 -0
  7. data/async-dns.gemspec +31 -0
  8. data/lib/async/dns.rb +36 -0
  9. data/lib/async/dns/chunked.rb +34 -0
  10. data/lib/async/dns/extensions/resolv.rb +136 -0
  11. data/lib/async/dns/extensions/string.rb +28 -0
  12. data/lib/async/dns/handler.rb +229 -0
  13. data/lib/async/dns/logger.rb +31 -0
  14. data/lib/async/dns/message.rb +75 -0
  15. data/lib/async/dns/replace.rb +54 -0
  16. data/lib/async/dns/resolver.rb +280 -0
  17. data/lib/async/dns/server.rb +154 -0
  18. data/lib/async/dns/system.rb +146 -0
  19. data/lib/async/dns/transaction.rb +202 -0
  20. data/lib/async/dns/transport.rb +78 -0
  21. data/lib/async/dns/version.rb +25 -0
  22. data/spec/async/dns/handler_spec.rb +58 -0
  23. data/spec/async/dns/hosts.txt +2 -0
  24. data/spec/async/dns/ipv6_spec.rb +78 -0
  25. data/spec/async/dns/message_spec.rb +56 -0
  26. data/spec/async/dns/origin_spec.rb +106 -0
  27. data/spec/async/dns/replace_spec.rb +44 -0
  28. data/spec/async/dns/resolver_performance_spec.rb +110 -0
  29. data/spec/async/dns/resolver_spec.rb +151 -0
  30. data/spec/async/dns/server/bind9/generate-local.rb +25 -0
  31. data/spec/async/dns/server/bind9/local.zone +5014 -0
  32. data/spec/async/dns/server/bind9/named.conf +14 -0
  33. data/spec/async/dns/server/bind9/named.run +0 -0
  34. data/spec/async/dns/server/million.rb +85 -0
  35. data/spec/async/dns/server_performance_spec.rb +138 -0
  36. data/spec/async/dns/slow_server_spec.rb +97 -0
  37. data/spec/async/dns/socket_spec.rb +70 -0
  38. data/spec/async/dns/system_spec.rb +57 -0
  39. data/spec/async/dns/transaction_spec.rb +140 -0
  40. data/spec/async/dns/truncation_spec.rb +61 -0
  41. data/spec/spec_helper.rb +60 -0
  42. metadata +175 -0
@@ -0,0 +1,14 @@
1
+
2
+ options {
3
+ directory "./";
4
+
5
+ listen-on-v6 { none; };
6
+ listen-on { 127.0.0.1; };
7
+
8
+ pid-file "./named.pid";
9
+ };
10
+
11
+ zone "local" {
12
+ type master;
13
+ file "./local.zone";
14
+ };
File without changes
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Copyright, 2009, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+ require 'async/dns'
24
+ require 'benchmark'
25
+
26
+ require 'stackprof'
27
+
28
+ Name = Resolv::DNS::Name
29
+ IN = Resolv::DNS::Resource::IN
30
+
31
+ # Generate a million A record "domains":
32
+
33
+ million = {}
34
+
35
+ Benchmark.bm do |x|
36
+ x.report("Generate names") do
37
+ (1..1_000_000).each do |i|
38
+ domain = "domain#{i}.local"
39
+
40
+ million[domain] = "#{69}.#{(i >> 16)%256}.#{(i >> 8)%256}.#{i%256}"
41
+ end
42
+ end
43
+ end
44
+
45
+ # Run the server:
46
+
47
+ StackProf.run(mode: :cpu, out: 'async/dns.stackprof') do
48
+ Async::DNS::run_server(:listen => [[:udp, '0.0.0.0', 5300]]) do
49
+ @logger.level = Logger::WARN
50
+
51
+ match(//, IN::A) do |transaction|
52
+ transaction.respond!(million[transaction.name])
53
+ end
54
+
55
+ # Default DNS handler
56
+ otherwise do |transaction|
57
+ logger.info "Passing DNS request upstream..."
58
+ transaction.fail!(:NXDomain)
59
+ end
60
+ end
61
+ end
62
+
63
+ # Expected output:
64
+ #
65
+ # > dig @localhost -p 5300 domain1000000
66
+ #
67
+ # ; <<>> DiG 9.8.3-P1 <<>> @localhost -p 5300 domain1000000
68
+ # ; (3 servers found)
69
+ # ;; global options: +cmd
70
+ # ;; Got answer:
71
+ # ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 50336
72
+ # ;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
73
+ # ;; WARNING: recursion requested but not available
74
+ #
75
+ # ;; QUESTION SECTION:
76
+ # ;domain1000000. IN A
77
+ #
78
+ # ;; ANSWER SECTION:
79
+ # domain1000000. 86400 IN A 69.15.66.64
80
+ #
81
+ # ;; Query time: 1 msec
82
+ # ;; SERVER: 127.0.0.1#5300(127.0.0.1)
83
+ # ;; WHEN: Fri May 16 19:17:48 2014
84
+ # ;; MSG SIZE rcvd: 47
85
+ #
@@ -0,0 +1,138 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Copyright, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+ require 'async/dns'
24
+ require 'benchmark'
25
+ require 'process/daemon'
26
+
27
+ module Async::DNS::ServerPerformanceSpec
28
+ describe Async::DNS::Server do
29
+ include_context "reactor"
30
+
31
+ context 'benchmark' do
32
+ class MillionServer < Async::DNS::Server
33
+ def initialize(*)
34
+ super
35
+
36
+ @million = {}
37
+
38
+ (1..5_000).each do |i|
39
+ domain = "domain#{i}.local"
40
+
41
+ @million[domain] = "#{69}.#{(i >> 16)%256}.#{(i >> 8)%256}.#{i%256}"
42
+ end
43
+ end
44
+
45
+ def process(name, resource_class, transaction)
46
+ transaction.respond!(@million[name])
47
+ end
48
+ end
49
+
50
+ class AsyncServerDaemon < Process::Daemon
51
+ def working_directory
52
+ File.expand_path("../tmp", __FILE__)
53
+ end
54
+
55
+ def reactor
56
+ @reactor ||= Async::Reactor.new
57
+ end
58
+
59
+ def startup
60
+ puts "Starting DNS server..."
61
+ @server = MillionServer.new(listen: [[:udp, '0.0.0.0', 5300]])
62
+
63
+ reactor.async do
64
+ @task = @server.run
65
+ end
66
+ end
67
+
68
+ def run
69
+ reactor.run
70
+ end
71
+
72
+ def shutdown
73
+ @task.stop if @task
74
+ end
75
+ end
76
+
77
+ class Bind9ServerDaemon < Process::Daemon
78
+ def working_directory
79
+ File.expand_path("../server/bind9", __FILE__)
80
+ end
81
+
82
+ def run
83
+ exec(self.class.named_executable, "-c", "named.conf", "-f", "-p", "5400", "-g")
84
+ end
85
+
86
+ def self.named_executable
87
+ # Check if named executable is available:
88
+ @named ||= `which named`.chomp
89
+ end
90
+ end
91
+
92
+ before(:all) do
93
+ @servers = []
94
+ @servers << ["Async::DNS::Server", 5300]
95
+
96
+ @domains = (1..1000).collect do |i|
97
+ "domain#{i}.local"
98
+ end
99
+
100
+ AsyncServerDaemon.start
101
+
102
+ unless Bind9ServerDaemon.named_executable.empty?
103
+ Bind9ServerDaemon.start
104
+ @servers << ["Bind9", 5400]
105
+ end
106
+ end
107
+
108
+ after(:all) do
109
+ AsyncServerDaemon.stop
110
+
111
+ unless Bind9ServerDaemon.named_executable.empty?
112
+ Bind9ServerDaemon.stop
113
+ end
114
+ end
115
+
116
+ it 'takes time' do
117
+ Async.logger.level = Logger::ERROR
118
+
119
+ Benchmark.bm(30) do |x|
120
+ @servers.each do |name, port|
121
+ resolver = Async::DNS::Resolver.new([[:udp, '127.0.0.1', port]])
122
+
123
+ x.report(name) do
124
+ # Number of requests remaining since this is an asynchronous event loop:
125
+ 5.times do
126
+ pending = @domains.size
127
+
128
+ resolved = @domains.collect{|domain| resolver.addresses_for(domain)}
129
+
130
+ expect(resolved).to_not include(nil)
131
+ end
132
+ end
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,97 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Copyright, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+ require 'async/dns'
24
+
25
+ module Async::DNS::SlowServerSpec
26
+ IN = Resolv::DNS::Resource::IN
27
+
28
+ class SlowServer < Async::DNS::Server
29
+ def process(name, resource_class, transaction)
30
+ @resolver ||= Async::DNS::Resolver.new([[:udp, "8.8.8.8", 53], [:tcp, "8.8.8.8", 53]])
31
+
32
+ Async::Task.current.sleep(2) if name.end_with?('.com')
33
+
34
+ transaction.fail!(:NXDomain)
35
+ end
36
+ end
37
+
38
+ describe "Async::DNS Slow Server" do
39
+ include_context "reactor"
40
+
41
+ let(:server_interfaces) {[[:udp, '0.0.0.0', 5330], [:tcp, '0.0.0.0', 5330]]}
42
+ let(:server) {SlowServer.new(listen: server_interfaces)}
43
+
44
+ around(:each) do |example|
45
+ begin
46
+ task = server.run
47
+
48
+ example.run
49
+ ensure
50
+ task.stop
51
+ end
52
+ end
53
+
54
+ it "get no answer after 2 seconds" do
55
+ start_time = Time.now
56
+
57
+ resolver = Async::DNS::Resolver.new(server_interfaces, :timeout => 10)
58
+
59
+ response = resolver.query("apple.com", IN::A)
60
+
61
+ expect(response.answer.length).to be == 0
62
+
63
+ end_time = Time.now
64
+
65
+ expect(end_time - start_time).to be_within(0.1).of(2.0)
66
+ end
67
+
68
+ it "times out after 1 second" do
69
+ start_time = Time.now
70
+
71
+ # Two server interfaces, timeout of 0.5s each:
72
+ resolver = Async::DNS::Resolver.new(server_interfaces, :timeout => 0.5)
73
+
74
+ response = resolver.query("apple.com", IN::A)
75
+
76
+ expect(response).to be nil
77
+
78
+ end_time = Time.now
79
+
80
+ expect(end_time - start_time).to be_within(0.1).of(1.0)
81
+ end
82
+
83
+ it "gets no answer immediately" do
84
+ start_time = Time.now
85
+
86
+ resolver = Async::DNS::Resolver.new(server_interfaces, :timeout => 0.5)
87
+
88
+ response = resolver.query("oriontransfer.org", IN::A)
89
+
90
+ expect(response.answer.length).to be 0
91
+
92
+ end_time = Time.now
93
+
94
+ expect(end_time - start_time).to be_within(0.1).of(0.0)
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Copyright, 2014, by Samuel G. D. Williams. <http://www.codeotaku.com>
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+ require 'async/dns'
24
+ require 'async/dns/system'
25
+
26
+ module Async::DNS::SocketSpec
27
+ IN = Resolv::DNS::Resource::IN
28
+
29
+ class TestServer < Async::DNS::Server
30
+ def process(name, resource_class, transaction)
31
+ @resolver ||= Async::DNS::Resolver.new([[:udp, "8.8.8.8", 53], [:tcp, "8.8.8.8", 53]])
32
+
33
+ transaction.passthrough!(@resolver)
34
+ end
35
+ end
36
+
37
+ describe Async::DNS::TCPSocketHandler do
38
+ include_context "reactor"
39
+
40
+ let(:server_interfaces) {[TCPServer.new('127.0.0.1', 2002)]}
41
+ let(:server) {TestServer.new(listen: server_interfaces)}
42
+
43
+ it "should create server with existing TCP socket" do
44
+ task = server.run
45
+
46
+ resolver = Async::DNS::Resolver.new([[:tcp, '127.0.0.1', 2002]])
47
+ response = resolver.query('google.com')
48
+ expect(response.class).to be == Async::DNS::Message
49
+
50
+ task.stop
51
+ end
52
+ end
53
+
54
+ describe Async::DNS::UDPSocketHandler do
55
+ include_context "reactor"
56
+
57
+ let(:server_interfaces) {[UDPSocket.new.tap{|socket| socket.bind('127.0.0.1', 2002)}]}
58
+ let(:server) {TestServer.new(listen: server_interfaces)}
59
+
60
+ it "should create server with existing UDP socket" do
61
+ task = server.run
62
+
63
+ resolver = Async::DNS::Resolver.new([[:udp, '127.0.0.1', 2002]])
64
+ response = resolver.query('google.com')
65
+ expect(response.class).to be == Async::DNS::Message
66
+
67
+ task.stop
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Copyright, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+ require 'async/dns'
24
+ require 'async/dns/system'
25
+
26
+ module Async::DNS::SystemSpec
27
+ describe Async::DNS::System do
28
+ include_context "reactor"
29
+
30
+ it "should have at least one namesever" do
31
+ expect(Async::DNS::System::nameservers.length).to be > 0
32
+ end
33
+
34
+ it "should respond to query for google.com" do
35
+ resolver = Async::DNS::Resolver.new(Async::DNS::System::nameservers)
36
+
37
+ response = resolver.query('google.com')
38
+
39
+ expect(response.class).to be == Async::DNS::Message
40
+ expect(response.rcode).to be == Resolv::DNS::RCode::NoError
41
+ end
42
+ end
43
+
44
+ describe Async::DNS::System::Hosts do
45
+ it "should parse the hosts file" do
46
+ hosts = Async::DNS::System::Hosts.new
47
+
48
+ # Load the test hosts data:
49
+ File.open(File.expand_path("../hosts.txt", __FILE__)) do |file|
50
+ hosts.parse_hosts(file)
51
+ end
52
+
53
+ expect(hosts.call('testing')).to be == true
54
+ expect(hosts['testing']).to be == '1.2.3.4'
55
+ end
56
+ end
57
+ end