Skip to content

Commit

Permalink
v0.1.0 (#1)
Browse files Browse the repository at this point in the history
  • Loading branch information
ninoseki committed Aug 26, 2018
1 parent 7a04277 commit 0a29bf6
Show file tree
Hide file tree
Showing 29 changed files with 762 additions and 1 deletion.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,12 @@ build-iPhoneSimulator/

# for a library or gem, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# Gemfile.lock
Gemfile.lock
# .ruby-version
# .ruby-gemset

# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
.rvmrc


.rspec_status
3 changes: 3 additions & 0 deletions .rspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
--format documentation
--color
--require spec_helper
7 changes: 7 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
sudo: false
language: ruby
cache: bundler
rvm:
- 2.5
before_install: gem install bundler -v 1.16.4
6 changes: 6 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
source "https://rubygems.org"

git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }

# Specify your gem's dependencies in doh_client.gemspec
gemspec
59 changes: 59 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,61 @@
# doh_client

[![Build Status](https://travis-ci.org/ninoseki/doh_client.svg?branch=master)](https://travis-ci.org/ninoseki/doh_client)
[![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

## Installation

Add this line to your application's Gemfile:

```ruby
gem 'doh_client'
```

And then execute:

$ bundle

Or install it yourself as:

$ gem install doh_client

## Supported platforms

- [Google Public DNS](https://developers.google.com/speed/public-dns/docs/dns-over-https)
- [Cloudflare](https://developers.cloudflare.com/1.1.1.1/dns-over-https/)

## Usage

### As a Library

```rb
require 'doh_client'

DoHClient::Google.resolve("example.com", { type: "A" })
DoHClient::Google.resolve("example.com", { type: "A", edns_client_subnet: "0.0.0.0/0", random_padding: "XmkMw~o_mgP2pf.gpw-Oi5dK" })

DoHClient::Cloudflare.resolve("example.com", { type: "A" })
DoHClient::Cloudflare.resolve("example.com", { type: "A", do: true, cd: false })
```

### As a CLI

```bash
$ doh_client
Commands:
console help [COMMAND] # Describe available commands or one specific command
console 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"}]}
```

## License

The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
6 changes: 6 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
require "bundler/gem_tasks"
require "rspec/core/rake_task"

RSpec::Core::RakeTask.new(:spec)

task :default => :spec
14 changes: 14 additions & 0 deletions bin/console
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/usr/bin/env ruby

require "bundler/setup"
require "doh_client"

# You can add fixtures and/or initialization code here to make experimenting
# with your gem easier. You can also use a different console, if you like.

# (If you use this, don't forget to add pry to your Gemfile!)
# require "pry"
# Pry.start

require "irb"
IRB.start(__FILE__)
8 changes: 8 additions & 0 deletions bin/setup
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
set -vx

bundle install

# Do any other automated setup that you need to do here
36 changes: 36 additions & 0 deletions doh_client.gemspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# frozen_string_literal: true

lib = File.expand_path('lib', __dir__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require "doh_client/version"

Gem::Specification.new do |spec|
spec.name = "doh_client"
spec.version = DoHClient::VERSION
spec.authors = ["Manabu Niseki"]
spec.email = ["manabu.niseki@gmail.com"]

spec.summary = "DNS over HTTPS(DoH) client for Ruby"
spec.description = "DNS over HTTPS(DoH) client for Ruby"
spec.homepage = "https://github.com/ninoseki/doh_client"
spec.license = "MIT"

# Specify which files should be added to the gem when it is released.
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
spec.files = Dir.chdir(File.expand_path(__dir__)) do
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
end
spec.bindir = "exe"
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
spec.require_paths = ["lib"]

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 "http", "~> 3.3"
spec.add_dependency "thor", "~> 0.19"
end
15 changes: 15 additions & 0 deletions lib/doh_client.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# frozen_string_literal: true

require "doh_client/version"
require "doh_client/error"

require "doh_client/client/request"
require "doh_client/client/base"
require "doh_client/client/cloudflare"
require "doh_client/client/google"

require "doh_client/cli"

module DoHClient
# Your code goes here...
end
42 changes: 42 additions & 0 deletions lib/doh_client/cli.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# frozen_string_literal: true

require "thor"
require "json"

module DoHClient
class CLI < Thor
class_option :resolver, type: :string, desc: "a resolver to use ('google' or 'cloudflare', default: google)"

desc "resolve [NAME]", "resolve a given name"
method_option :type, type: :string, default: "A"
method_option :cd, type: :boolean
method_option :do, type: :boolean
method_option :edns_client_subnet, type: :string
method_option :random_padding, type: :string
def resolve(name)
hash = resolver.resolve(name, options)
puts hash.to_json
end

no_commands do
def resolver
case options[:resolver]
when "google"
DoHClient::Client::Google
when "cloudflare"
DoHClient::Client::Cloudflare
else
DoHClient::Client::Google
end
end

def with_error_handling
yield
rescue ResponseError => e
puts "Warning: #{e}"
rescue ArgumentError => _
puts "Warning: #{e}"
end
end
end
end
40 changes: 40 additions & 0 deletions lib/doh_client/client/base.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# frozen_string_literal: true

require "json"
require "http"

module DoHClient
module Client
class Base
def resolve(name, options)
query = build_query(name, options)
validate(query)
Request.get(endpoint, query)
end

def self.resolve(name, options)
new.resolve(name, options)
end

def endpoint
raise NotImplementedError, "You must implement #{self.class}##{__method__}"
end

def build_query
raise NotImplementedError, "You must implement #{self.class}##{__method__}"
end

def validate(query)
raise ArgumentError, "name is a required parameter" if query[:name].nil?
raise ArgumentError, "type is a required parameter" if query[:type].nil?
raise ArgumentError, "cd must be a boolean value" if query[:cd] && !boolean?(query[:cd])
end

private

def boolean?(param)
[true, false].include? param
end
end
end
end
25 changes: 25 additions & 0 deletions lib/doh_client/client/cloudflare.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# frozen_string_literal: true

module DoHClient
module Client
class Cloudflare < Base
def endpoint
"https://cloudflare-dns.com/dns-query"
end

def build_query(name, options)
{
name: name,
type: options[:type],
cd: options[:cd],
do: options[:do]
}.compact
end

def validate(query)
super(query)
raise ArgumentError, "do must be a boolean value" if query[:do] && !boolean?(query[:do])
end
end
end
end
21 changes: 21 additions & 0 deletions lib/doh_client/client/google.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# frozen_string_literal: true

module DoHClient
module Client
class Google < Base
def endpoint
"https://dns.google.com/resolve"
end

def build_query(name, options)
{
name: name,
type: options[:type],
cd: options[:cd],
edns_client_subnet: options[:edns_client_subnet],
random_padding: options[:random_padding]
}.compact
end
end
end
end
33 changes: 33 additions & 0 deletions lib/doh_client/client/request.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# frozen_string_literal: true

module DoHClient
module Client
class Request
def get(url, query)
res = http.headers(headers).get(url, params: query);
return JSON.parse(res.body.to_s) if res.code == 200
raise ResponseError, res.body.to_s
end

def self.get(url, query)
new.get(url, query);
end

def headers
{
accept: "application/dns-json",
user_agent: "curl/7.54.0"
}
end

def http
if proxy = ENV["HTTPS_RPOXY"] || ENV["https_proxy"]
uri = URI(proxy)
HTTP.via(uri.hostname, uri.port)
else
HTTP
end
end
end
end
end
5 changes: 5 additions & 0 deletions lib/doh_client/error.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# frozen_string_literal: true

module DoHClient
class ResponseError < StandardError; end
end
5 changes: 5 additions & 0 deletions lib/doh_client/version.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# frozen_string_literal: true

module DoHClient
VERSION = "0.1.0"
end
18 changes: 18 additions & 0 deletions spec/base_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# frozen_string_literal: true

RSpec.describe DoHClient::Client::Base, :vcr do
subject { DoHClient::Client::Base.new }
describe "#validate" do
it "should raise an ArgumentError when given an invaid query" do
q = { type: "A" }
expect { subject.validate q }.to raise_error(ArgumentError)
q = { name: "example.com" }
expect { subject.validate q }.to raise_error(ArgumentError)

q = { name: "example.com", type: "A", cd: "invalid"}
expect { subject.validate q }.to raise_error(ArgumentError)
q = { name: "example.com", type: "A", cd: false }
subject.validate q
end
end
end
22 changes: 22 additions & 0 deletions spec/cli_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
require "json"

RSpec.describe DoHClient::CLI, :vcr do
context "google" do
describe "#resolve" 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
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
end

0 comments on commit 0a29bf6

Please sign in to comment.