unbound 0.0.1

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 44bef7bb342a48227c09e4c002a815bd4b7a4b47
4
+ data.tar.gz: a78682e4f9191e66a78e9a9c30da43602ec618ae
5
+ SHA512:
6
+ metadata.gz: 2ac8d61b74fa8fc29e1f2d414d54782e70ad33acfdff9bc55e09b6d89b34f6557c21c5b2db2044b8873988d232707f3b8412accf2fe04c85cdb42231d4c8ba9b
7
+ data.tar.gz: 8a9af9603f3f472dad7d2fe38902fa5316f6288ca6e6c92acc2c8076db8449ee8e9b09c81279681061863ffe9f568f0318ae8c2db25cce204ab6f019c3e4e46e
data/Gemfile ADDED
@@ -0,0 +1,14 @@
1
+ # A sample Gemfile
2
+ source "https://rubygems.org"
3
+
4
+ gem "ffi"
5
+
6
+ group :development do
7
+ gem "jeweler"
8
+ gem "rake"
9
+ end
10
+
11
+ group :test do
12
+ gem "rspec"
13
+ gem "simplecov"
14
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2014 Michael Ryan
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,77 @@
1
+ ## Synopsis
2
+
3
+ Ruby FFI bindings for [Unbound](http://www.unbound.net/), a validating, recursive, and caching DNS resolver. Specifically, we are binding to the [libunbound](http://www.unbound.net/documentation/libunbound.html) API, allowing a for a full Unbound DNS resolver to be embedded within a Ruby application.
4
+
5
+ These bindings allow for asynchronous and synchronous name resolution. You really should familiarize yourself with [libunbound](http://www.unbound.net/documentation/libunbound.html) in order to leverage this library. The current bindings will support versions of versions of libunbound distributed with Unbound 1.4.2 or newer.
6
+
7
+ ## Code Example
8
+
9
+ ```
10
+ require 'unbound/context'
11
+ require 'unbound/result'
12
+
13
+ ctx = Unbound::Context.new
14
+
15
+ CB = Proc.new do |mydata, err, result_ptr|
16
+ if (err == 0)
17
+ result = Unbound::Result.new(result_ptr)
18
+ puts "Got response rcode: #{result[:rcode]}, qname: #{result[:qname]}"
19
+ end
20
+ end
21
+
22
+ fd = ctx.fd
23
+ io = IO.for_fd(fd)
24
+
25
+ google_id = ctx.resolve_async("www.google.com", 1, 1, CB)
26
+ yahoo_id = ctx.resolve_async("www.yahoo.com", 1, 1, CB)
27
+ github_id = ctx.resolve_async("www.github.com", 1, 1, CB)
28
+
29
+ # Keep looping until we haven't gotten any
30
+ while (::IO.select([io], nil, nil, 5))
31
+ ctx.process()
32
+ end
33
+
34
+ ctx.close()
35
+ puts "all done!"
36
+ ```
37
+
38
+ outputs:
39
+ ```
40
+ Got response rcode: 0, qname: www.google.com
41
+ Got response rcode: 0, qname: www.github.com
42
+ Got response rcode: 0, qname: www.yahoo.com
43
+ all done!
44
+ ```
45
+
46
+
47
+ ## Motivation
48
+
49
+ I needed to resolve millions of DNS queries, and didn't want to spend more than 5 seconds waiting for answers to each question. I had found myself subject to timeouts imposed by my recursive resolver, specifically that these were longer than the 5 seconds.My resolver was only capable of handling so many queries concurrently, so it was very easy to knock it over. I needed a way to cancel queries, freeing resources on my resolver, something that is not possible through the typical UDP DNS interface.
50
+
51
+ By creating bindings for libunbound, I gained access to the ability to cancel DNS queries (specifically ub_cancel), allowing my name resolution process to move forward at a fair clip.
52
+
53
+ ## Installation
54
+
55
+ You'll need libunbound for Unbound 1.4.2 or greater. This can typically be achieved by installing the 'unbound' DNS package for your distribution.
56
+
57
+ To install this gem:
58
+ ```
59
+ gem install unbound
60
+ ```
61
+
62
+ ## API Reference
63
+
64
+
65
+ ## Tests
66
+
67
+ ```
68
+ rake spec
69
+ ```
70
+
71
+ ## Contributors
72
+
73
+ * [Mike Ryan](https://github.com/justfalter)
74
+
75
+ ## License
76
+
77
+ See LICENSE.txt
data/Rakefile ADDED
@@ -0,0 +1,35 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development, :test)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'rake'
11
+
12
+ require 'jeweler'
13
+ Jeweler::Tasks.new do |gem|
14
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
15
+ gem.name = "unbound"
16
+ gem.homepage = "http://github.com/justfalter/unbound-ruby"
17
+ gem.license = "MIT"
18
+ gem.summary = %Q{Unbound DNS resolver bindings for Ruby}
19
+ gem.description = %Q{Unbound DNS resolver bindings for Ruby}
20
+ gem.email = "falter@gmail.com"
21
+ gem.authors = ["Mike Ryan"]
22
+ gem.files = Dir.glob("lib/**/*.rb") +
23
+ Dir.glob("spec/{*.rb}") +
24
+ Dir.glob("spec/conf/{*.conf}") +
25
+ %w(LICENSE.txt Gemfile README.md Rakefile VERSION)
26
+
27
+ end
28
+ Jeweler::RubygemsDotOrgTasks.new
29
+
30
+ require 'rspec/core'
31
+ require 'rspec/core/rake_task'
32
+ RSpec::Core::RakeTask.new(:spec) do |spec|
33
+ spec.pattern = FileList['spec/**/*_spec.rb']
34
+ end
35
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
@@ -0,0 +1,66 @@
1
+ require 'ffi'
2
+ module Unbound
3
+ module Bindings
4
+ extend FFI::Library
5
+
6
+ LIBNAMES = ['unbound', 'libunbound.so', 'libunbound.so.2']
7
+ if ENV['LIBUNBOUND']
8
+ LIBNAMES.unshift(ENV['LIBUNBOUND'])
9
+ end
10
+
11
+ ffi_lib LIBNAMES
12
+
13
+
14
+ typedef :pointer, :mydata
15
+ typedef :pointer, :ub_ctx_ptr
16
+ typedef :pointer, :ub_result_ptr
17
+ typedef :pointer, :ub_result_ptrptr
18
+
19
+ callback :ub_callback_t, [:mydata, :int, :ub_result_ptr], :void
20
+
21
+
22
+ attach_function :ub_ctx_create, [], :ub_ctx_ptr
23
+ attach_function :ub_ctx_delete, [:ub_ctx_ptr], :void
24
+
25
+ attach_function :ub_ctx_set_option, [:ub_ctx_ptr, :string, :string], :int
26
+ attach_function :ub_ctx_get_option, [:ub_ctx_ptr, :string, :pointer], :int
27
+ attach_function :ub_ctx_config, [:ub_ctx_ptr, :string], :int
28
+
29
+ attach_function :ub_ctx_set_fwd, [:ub_ctx_ptr, :string], :int
30
+ attach_function :ub_ctx_resolvconf, [:ub_ctx_ptr, :string], :int
31
+ attach_function :ub_ctx_hosts, [:ub_ctx_ptr, :string], :int
32
+
33
+ attach_function :ub_ctx_add_ta, [:ub_ctx_ptr, :string], :int
34
+ attach_function :ub_ctx_add_ta_file, [:ub_ctx_ptr, :string], :int
35
+
36
+ attach_function :ub_ctx_trustedkeys, [:ub_ctx_ptr, :string], :int
37
+
38
+ attach_function :ub_ctx_debugout, [:ub_ctx_ptr, :pointer], :int # TODO: FILE pointer
39
+ attach_function :ub_ctx_debuglevel, [:ub_ctx_ptr, :int], :int
40
+
41
+ attach_function :ub_ctx_async, [:ub_ctx_ptr, :int], :int
42
+
43
+ attach_function :ub_poll, [:ub_ctx_ptr], :int
44
+ attach_function :ub_wait, [:ub_ctx_ptr], :int, :blocking => true
45
+ attach_function :ub_fd, [:ub_ctx_ptr], :int
46
+ attach_function :ub_process, [:ub_ctx_ptr], :int, :blocking => true
47
+
48
+ attach_function :ub_resolve, [:ub_ctx_ptr, :string, :int, :int, :ub_result_ptrptr], :int, :blocking => true
49
+ attach_function :ub_resolve_async, [:ub_ctx_ptr, :string, :int, :int, :mydata, :ub_callback_t, :pointer], :int
50
+ attach_function :ub_cancel, [:ub_ctx_ptr, :int], :int
51
+
52
+ attach_function :ub_resolve_free, [:ub_result_ptr], :void
53
+ attach_function :ub_strerror, [:int], :string
54
+
55
+ attach_function :ub_ctx_print_local_zones, [:ub_ctx_ptr], :int
56
+
57
+ attach_function :ub_ctx_zone_add, [:ub_ctx_ptr, :string, :string], :int
58
+ attach_function :ub_ctx_zone_remove, [:ub_ctx_ptr, :string], :int
59
+
60
+ attach_function :ub_ctx_data_add, [:ub_ctx_ptr, :string], :int
61
+ attach_function :ub_ctx_data_remove, [:ub_ctx_ptr, :string], :int
62
+
63
+ ## This wasn't added until unbound 1.4.15
64
+ # attach_function :ub_version, [], :string
65
+ end
66
+ end
@@ -0,0 +1,77 @@
1
+ require 'unbound/bindings'
2
+ require 'unbound/exceptions'
3
+
4
+ module Unbound
5
+ class Context
6
+ def initialize
7
+ @ub_ctx = Unbound::Bindings.ub_ctx_create()
8
+ end
9
+
10
+ def check_closed!
11
+ if @ub_ctx.nil?
12
+ raise ContextClosedError.new
13
+ end
14
+ end
15
+
16
+ def raise_if_error!(retval)
17
+ if retval != 0
18
+ raise APIError.new(retval)
19
+ end
20
+ return retval
21
+ end
22
+
23
+ def closed?
24
+ @ub_ctx.nil?
25
+ end
26
+
27
+ def load_config(filename)
28
+ check_closed!
29
+ raise_if_error!(Unbound::Bindings.ub_ctx_config(@ub_ctx, filename))
30
+ end
31
+
32
+ def get_option(varname)
33
+ check_closed!
34
+ ret_ptr = FFI::MemoryPointer.new(:pointer)
35
+ raise_if_error!(Unbound::Bindings.ub_ctx_get_option(@ub_ctx, varname, ret_ptr))
36
+ ret_ptr.read_pointer().read_string()
37
+ end
38
+
39
+ def set_option(varname, val)
40
+ check_closed!
41
+ unless varname.end_with?(':')
42
+ varname = varname + ":"
43
+ end
44
+ raise_if_error!(Unbound::Bindings.ub_ctx_set_option(@ub_ctx, varname, val))
45
+ end
46
+
47
+ def close
48
+ check_closed!
49
+ Unbound::Bindings.ub_ctx_delete(@ub_ctx)
50
+ @ub_ctx = nil
51
+ end
52
+
53
+ def process
54
+ check_closed!
55
+ raise_if_error!(Unbound::Bindings.ub_process(@ub_ctx))
56
+ end
57
+
58
+ def cancel_async_query(async_id)
59
+ check_closed!
60
+ raise_if_error!(Unbound::Bindings.ub_cancel(@ub_ctx, async_id))
61
+ end
62
+
63
+ def fd
64
+ check_closed!
65
+ Unbound::Bindings.ub_fd(@ub_ctx)
66
+ end
67
+
68
+ def resolve_async(name, rrtype, rrclass, callback, private_data = nil)
69
+ check_closed!
70
+ async_id_ptr = FFI::MemoryPointer.new :int
71
+ raise_if_error!(
72
+ Unbound::Bindings.ub_resolve_async(
73
+ @ub_ctx, name, rrtype, rrclass, private_data, callback, async_id_ptr))
74
+ return async_id_ptr.get_int(0)
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,26 @@
1
+ require 'unbound/bindings'
2
+
3
+ module Unbound
4
+ class Error < StandardError
5
+ end
6
+
7
+ # An APIError is the result of a failure in an unbound api call. It expects
8
+ # an error code and translates that into a human-readable string.
9
+ class APIError < Error
10
+ attr_reader :error_code
11
+ def initialize(error_code)
12
+ @error_code = error_code
13
+ msg = Unbound::Bindings.ub_strerror(@error_code)
14
+ super(msg)
15
+ end
16
+ end
17
+
18
+ # Indicates that an API call was attempted, but no context was provided.
19
+ class MissingContextError < Error
20
+ end
21
+
22
+ # Indicates that the context was closed (for Unbound::Context)
23
+ class ContextClosedError < MissingContextError
24
+ end
25
+
26
+ end
@@ -0,0 +1,36 @@
1
+ require 'unbound/bindings'
2
+ require 'unbound/result'
3
+ require 'unbound/context'
4
+
5
+ module Unbound
6
+ class Resolver
7
+ def initialize()
8
+ @ctx = Unbound::Context.new
9
+ @ctx.load_config("unbound/etc/unbound.conf")
10
+
11
+ cb = Proc.new { |a,b,c| nil }
12
+ self.query("localhost", 1, 1, cb)
13
+ @ctx.wait()
14
+ end
15
+
16
+ def close
17
+ @ctx.close
18
+ end
19
+
20
+ def fd
21
+ @ctx.fd
22
+ end
23
+
24
+ def process
25
+ @ctx.process
26
+ end
27
+
28
+ def cancel(async_id)
29
+ @ctx.cancel_async_query(async_id)
30
+ end
31
+
32
+ def query(name, rrtype, rrclass, callback, private_data = nil)
33
+ @ctx.resolve_async(name, rrtype, rrclass, callback, private_data)
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,66 @@
1
+ require 'ffi'
2
+ require 'resolv'
3
+ module Unbound
4
+ module ResultLayout
5
+ # TODO Figure out how to determine version of libunbound and,
6
+ # thus, proper structure of ub_result
7
+ def self.included(base)
8
+ base.class_eval do
9
+ layout(
10
+ :qname, :string, # char* qname
11
+ :qtype, :int, # int qtype
12
+ :qclass, :int, # int qclass
13
+
14
+ :data, :pointer, # char** data
15
+ :len, :pointer, # int* len
16
+
17
+ :canonname, :string, # char* canonname
18
+
19
+ :rcode, :int, # int rcode
20
+
21
+ :answer_packet, :pointer, # void* answer_packet
22
+ :answer_len, :int, # int answer_len
23
+
24
+ :havedata, :int, # int havedata
25
+
26
+ :secure, :int, # int secure
27
+
28
+ :bogus, :int, # int bogus
29
+ :why_bogus, :string, # char* why_bogus
30
+
31
+ # This wasn't added until unbound version 1.4.20
32
+ #:ttl, :int # int ttl
33
+ )
34
+
35
+ end
36
+ end
37
+
38
+ def answer_packet
39
+ if self[:answer_len] <= 0
40
+ return nil
41
+ end
42
+ self[:answer_packet].read_bytes(self[:answer_len])
43
+ end
44
+
45
+ def to_resolv
46
+ if ap = answer_packet()
47
+ return Resolv::DNS::Message.decode(ap)
48
+ end
49
+ return nil
50
+ end
51
+ end
52
+
53
+ class Result < FFI::ManagedStruct
54
+ include ResultLayout
55
+
56
+ def self.release(ptr)
57
+ Unbound.ub_resolve_free(ptr)
58
+ end
59
+ end
60
+
61
+ class ResultCast < FFI::Struct
62
+ include ResultLayout
63
+ end
64
+ end
65
+
66
+
data/lib/unbound.rb ADDED
@@ -0,0 +1,3 @@
1
+ module Unbound
2
+ require 'unbound/bindings'
3
+ end
@@ -0,0 +1,5 @@
1
+
2
+ server:
3
+ local-zone: 'local.' static
4
+ local-data: 'mycomputer.local IN A 192.0.2.51'
5
+ local-data: 'mytext.local TXT "data for a TXT record"'
@@ -0,0 +1,3 @@
1
+ server:
2
+ do-ip6: yes
3
+ version: "test config loaded!"
@@ -0,0 +1,148 @@
1
+ require 'spec_helper'
2
+ require 'unbound/context'
3
+ require 'unbound/exceptions'
4
+ require 'unbound/result'
5
+ require 'pp'
6
+
7
+ describe Unbound::Context do
8
+ before :each do
9
+ @ctx = Unbound::Context.new
10
+ end
11
+
12
+ after :each do
13
+ @ctx.close unless @ctx.closed?
14
+ end
15
+
16
+ describe "#closed?" do
17
+ it "should indicate whether the context is closed or not" do
18
+ expect(@ctx.closed?).to be_false
19
+ @ctx.close
20
+ expect(@ctx.closed?).to be_true
21
+ end
22
+ end
23
+
24
+ describe "#close" do
25
+ it "should delete the underlying context" do
26
+ Unbound::Bindings.should_receive(:ub_ctx_delete)
27
+ @ctx.close
28
+ end
29
+
30
+ it "should raise an Unbound::ContextClosedError if we try to close it more than once" do
31
+ @ctx.close
32
+ expect(lambda do
33
+ @ctx.close
34
+ end).to raise_error(Unbound::ContextClosedError)
35
+ end
36
+
37
+ it "should raise an Unbound::ContextClosedError if we make an API call after the context is closed." do
38
+ @ctx.close
39
+ expect(lambda do
40
+ @ctx.fd
41
+ end).to raise_error(Unbound::ContextClosedError)
42
+ end
43
+ end
44
+
45
+ it "should let me set and retrieve unbound configuration options" do
46
+ @ctx.set_option("do-ip6", "no")
47
+ expect(@ctx.get_option("do-ip6")).to eq("no")
48
+ @ctx.set_option("do-ip6", "yes")
49
+ expect(@ctx.get_option("do-ip6")).to eq("yes")
50
+ end
51
+
52
+ describe "#load_config" do
53
+ it "should load a proper unbound configuration file" do
54
+ @ctx.set_option("do-ip6", "no")
55
+ expect(@ctx.get_option("do-ip6")).to eq("no")
56
+ @ctx.load_config(UnboundHelper.config_file("test_config.conf"))
57
+ expect(@ctx.get_option("do-ip6")).to eq("yes")
58
+ expect(@ctx.get_option("version")).to eq("test config loaded!")
59
+ end
60
+
61
+ it "should raise an Unbound::APIError if the configuration file was not found" do
62
+ expect(lambda do
63
+ @ctx.load_config(UnboundHelper.config_file("this_config_doesnt_exist.conf"))
64
+ end).to raise_error(Unbound::APIError)
65
+ end
66
+ end
67
+
68
+ shared_examples_for "an asynchronous name resolution method" do
69
+ # expects:
70
+ # @processing_proc = A Proc
71
+ # @ctx
72
+ before :each do
73
+ @ctx.load_config(UnboundHelper.config_file("local_zone.conf"))
74
+ expect(1).to be(1)
75
+
76
+ @cb_result_ptr = nil
77
+ @cb_result = nil
78
+ @cb_err = nil
79
+ @cb_mydata = nil
80
+
81
+ @cb = Proc.new do |mydata, err, result_ptr|
82
+ @cb_mydata = mydata
83
+ @cb_err = err
84
+ @cb_result_ptr = result_ptr
85
+ if (@cb_err == 0)
86
+ @cb_result = Unbound::Result.new(@cb_result_ptr)
87
+ end
88
+ end
89
+ end
90
+
91
+ describe "resolving mycomputer.local IN A" do
92
+ before :each do
93
+ @async_id = @ctx.resolve_async("mycomputer.local", 1, 1, @cb)
94
+ @processing_proc.call()
95
+ end
96
+
97
+ it "should have an error in the callback" do
98
+ expect(@cb_err).to be(0)
99
+ end
100
+
101
+ it "should have a result_ptr" do
102
+ expect(@cb_result_ptr).not_to be_nil
103
+ end
104
+
105
+ describe "result.to_resolv" do
106
+ subject {Unbound::Result.new(@cb_result_ptr).to_resolv}
107
+ its(:rcode) {should eq(0)}
108
+
109
+ it "should have the proper answer" do
110
+ expect(subject.answer.length).to be(1)
111
+ answer = subject.answer[0]
112
+ expect(answer[0].to_s).to eq("mycomputer.local")
113
+ expect(answer[2].address.to_s).to eq("192.0.2.51")
114
+ end
115
+ end
116
+ end
117
+ describe "#cancel_async_query" do
118
+ before :each do
119
+ @async_id = @ctx.resolve_async("mycomputer.local", 1, 1, @cb)
120
+ end
121
+
122
+ it "should cancel a query" do
123
+ expect(@ctx.cancel_async_query(@async_id)).to be(0)
124
+ end
125
+
126
+ it "should raise an APIError when canceling a non-existent query" do
127
+ expect(lambda do
128
+ @ctx.cancel_async_query(@async_id + 1)
129
+ end).to raise_error(Unbound::APIError)
130
+ end
131
+ end
132
+ end
133
+
134
+
135
+ describe "asynchronous query processing via select/process" do
136
+ before :each do
137
+ @processing_proc = lambda do
138
+ fd = @ctx.fd
139
+ io = IO.for_fd(fd)
140
+ ret = ::IO.select([io], nil, nil, 5)
141
+ expect(ret).not_to be_nil
142
+ @ctx.process
143
+ end
144
+ end
145
+ it_should_behave_like "an asynchronous name resolution method"
146
+ end
147
+ end
148
+
@@ -0,0 +1,31 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ Bundler.setup :default, :test
4
+
5
+ module UnboundHelper
6
+ require 'pathname'
7
+ SPEC_ROOT = Pathname.new(__FILE__).dirname.expand_path
8
+ PROJECT_ROOT = (SPEC_ROOT + '../').expand_path
9
+ CONF_ROOT = SPEC_ROOT + 'conf'
10
+ def self.config_file(name)
11
+ (CONF_ROOT + name).to_s
12
+ end
13
+ end
14
+
15
+ require 'simplecov'
16
+ SimpleCov.start
17
+
18
+
19
+ SimpleCov.start do
20
+ project_root = RSpec::Core::RubyProject.root
21
+ add_filter UnboundHelper::PROJECT_ROOT.join('spec').to_s
22
+ add_filter UnboundHelper::PROJECT_ROOT.join('.gem').to_s
23
+ end
24
+
25
+ RSpec.configure do |config|
26
+ config.expect_with :rspec do |c|
27
+ c.syntax = :expect
28
+ end
29
+ end
30
+
31
+
metadata ADDED
@@ -0,0 +1,102 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: unbound
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Mike Ryan
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-01-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: ffi
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: jeweler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: Unbound DNS resolver bindings for Ruby
56
+ email: falter@gmail.com
57
+ executables: []
58
+ extensions: []
59
+ extra_rdoc_files:
60
+ - LICENSE.txt
61
+ - README.md
62
+ files:
63
+ - Gemfile
64
+ - LICENSE.txt
65
+ - README.md
66
+ - Rakefile
67
+ - VERSION
68
+ - lib/unbound.rb
69
+ - lib/unbound/bindings.rb
70
+ - lib/unbound/context.rb
71
+ - lib/unbound/exceptions.rb
72
+ - lib/unbound/resolver.rb
73
+ - lib/unbound/result.rb
74
+ - spec/conf/local_zone.conf
75
+ - spec/conf/test_config.conf
76
+ - spec/context_spec.rb
77
+ - spec/spec_helper.rb
78
+ homepage: http://github.com/justfalter/unbound-ruby
79
+ licenses:
80
+ - MIT
81
+ metadata: {}
82
+ post_install_message:
83
+ rdoc_options: []
84
+ require_paths:
85
+ - lib
86
+ required_ruby_version: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - '>='
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ required_rubygems_version: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - '>='
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ requirements: []
97
+ rubyforge_project:
98
+ rubygems_version: 2.0.14
99
+ signing_key:
100
+ specification_version: 4
101
+ summary: Unbound DNS resolver bindings for Ruby
102
+ test_files: []