Skip to content

Commit

Permalink
Merge pull request #3 from ninoseki/add-dns-server-feature
Browse files Browse the repository at this point in the history
Add "act as DNS server" feature
  • Loading branch information
ninoseki committed Aug 26, 2018
2 parents 5e59b37 + 561496a commit 3c7bd28
Show file tree
Hide file tree
Showing 12 changed files with 605 additions and 19 deletions.
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
[![Maintainability](https://api.codeclimate.com/v1/badges/e9119ee2021f1cfb895d/maintainability)](https://codeclimate.com/github/ninoseki/doh_client/maintainability)
[![Coverage Status](https://coveralls.io/repos/github/ninoseki/doh_client/badge.svg?branch=master)](https://coveralls.io/github/ninoseki/doh_client?branch=master)

DNS over HTTPS(DoH) client for Ruby
DNS over HTTPS (DoH) client for Ruby

## Installation

Expand Down Expand Up @@ -46,14 +46,18 @@ DoHClient::Cloudflare.resolve("example.com", { type: "A", do: true, cd: false })
```bash
$ doh_client
Commands:
console help [COMMAND] # Describe available commands or one specific command
console resolve [NAME] # resolve a given name
doh_client act_as_server # act as a local DNS server on a given port (default: 5300)
doh_client help [COMMAND] # Describe available commands or one specific command
doh_client resolve [NAME] # resolve a given name

Options:
[--resolver=RESOLVER] # a resolver to use ('google' or 'cloudflare', default: google)

$ doh_client resolve example.com --type A
# => {"Status":0,"TC":false,"RD":true,"RA":true,"AD":true,"CD":false,"Question":[{"name":"example.com.","type":1}],"Answer":[{"name":"example.com.","type":1,"TTL":5169,"data":"93.184.216.34"}]}
# => {"Status":0,"TC":false,"RD":true,"RA":true,"AD":true,"CD":false,"Question":[{"name":"example.com.","type":1}],"Answer":[{"name":"example.com.","type":1,"TTL":5169,"data":"93.184.216.34"}]

$ doh_client act_as_server
# => Starting DNS server 0.0.0.0:5300 (tcp/udp)
```

## License
Expand Down
2 changes: 2 additions & 0 deletions doh_client.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,15 @@ Gem::Specification.new do |spec|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
spec.require_paths = ["lib"]

spec.add_development_dependency "async-rspec", "~> 1.11"
spec.add_development_dependency "bundler", "~> 1.16"
spec.add_development_dependency "coveralls", "~> 0.8"
spec.add_development_dependency "rake", "~> 12.3"
spec.add_development_dependency "rspec", "~> 3.8"
spec.add_development_dependency "vcr", "~> 4.0"
spec.add_development_dependency "webmock", "~> 3.4"

spec.add_dependency "async-dns", "~> 1.2"
spec.add_dependency "http", "~> 3.3"
spec.add_dependency "thor", "~> 0.19"
end
2 changes: 2 additions & 0 deletions lib/doh_client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
require "doh_client/client/cloudflare"
require "doh_client/client/google"

require "doh_client/server"

require "doh_client/cli"

module DoHClient
Expand Down
16 changes: 16 additions & 0 deletions lib/doh_client/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,22 @@ def resolve(name)
puts hash.to_json
end

desc "act_as_server", "act as a local DNS server on a given port (default: 5300)"
method_option :port, type: :numeric, default: 5300
def act_as_server
port = options[:port]
interfaces = [[:udp, "0.0.0.0", port], [:tcp, "0.0.0.0", port]]
server = DoHClient::Server.new(interfaces)
puts "Starting DNS server 0.0.0.0:#{port} (tcp/udp)"
begin
server.run
rescue Interrupt
puts "\nStopping DNS server..."
ensure
puts "Stopped"
end
end

no_commands do
def resolver
case options[:resolver]
Expand Down
33 changes: 33 additions & 0 deletions lib/doh_client/server.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# frozen_string_literal: true

require 'async/dns'

module DoHClient
class Server < Async::DNS::Server
attr_reader :client
def initialize(resolver)
super
@client = resolver == "cloudflare" ? DoHClient::Client::Cloudflare : DoHClient::Client::Google
end

def process(name, resource_class, transaction)
type = resource_class.to_s.split('::').last

begin
response = client.resolve(name, type: type)
rescue ResponseError => _
return transaction.fail!(:NXDomain) unless answers
end

answers = response["Answer"]
return transaction.fail!(:NXDomain) unless answers

transaction.append_question!
answers.each do |answer|
next unless klass = Resolv::DNS::Resource.get_class(answer["type"], resource_class::ClassValue)
resource = klass < Resolv::DNS::Resource::DomainName ? klass.new(Resolv::DNS::Name.create(answer["data"])) : klass.new(answer["data"])
transaction.response.add_answer(answer["name"], answer["TTL"], resource)
end
end
end
end
44 changes: 39 additions & 5 deletions spec/cli_spec.rb
Original file line number Diff line number Diff line change
@@ -1,22 +1,56 @@
# frozen_string_literal: true

require "json"
require 'async/dns'
require "async/rspec"

RSpec.describe DoHClient::CLI, :vcr do
context "google" do
describe "#resolve" do
describe "#resolve" do
context "google" do
it "should output a valid JSON" do
output = capture(:stdout) { DoHClient::CLI.start %w(resolve github.com --resolver=google) }
json = JSON.parse(output)
expect(json).to be_a(Hash)
end
end
end
context "cloudflare" do
describe "#resolve" do
context "cloudflare" do
it "should output a valid JSON" do
output = capture(:stdout) { DoHClient::CLI.start %w(resolve github.com --resolver=cloudflare) }
json = JSON.parse(output)
expect(json).to be_a(Hash)
end
end
end
describe "#act_as_server" do
include_context Async::RSpec::Reactor

it "should act as a server" do
interfaces = [[:udp, "0.0.0.0", 5300], [:tcp, "0.0.0.0", 5300]]
resolver = Async::DNS::Resolver.new(interfaces)
task = DoHClient::CLI.start %w(act_as_server)

%w(example.com github.com google.com).each do |name|
resolved = resolver.addresses_for(name)
resolved.each do |result|
expect(result.address).to be_a(String)
end
end

task.stop
end
it "should act as a server on a given port" do
interfaces = [[:udp, "0.0.0.0", 5311], [:tcp, "0.0.0.0", 5311]]
resolver = Async::DNS::Resolver.new(interfaces)
task = DoHClient::CLI.start %w(act_as_server --port 5311)

%w(example.com github.com google.com).each do |name|
resolved = resolver.addresses_for(name)
resolved.each do |result|
expect(result.address).to be_a(String)
end
end

task.stop
end
end
end

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 3c7bd28

Please sign in to comment.