ddig 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/LICENSE.txt +21 -0
- data/README.md +102 -0
- data/Rakefile +8 -0
- data/exe/ddig +6 -0
- data/lib/ddig/cli.rb +55 -0
- data/lib/ddig/ddr/designated_resolver.rb +51 -0
- data/lib/ddig/ddr/verify_cert.rb +68 -0
- data/lib/ddig/ddr.rb +140 -0
- data/lib/ddig/nameserver.rb +65 -0
- data/lib/ddig/resolver/do53.rb +49 -0
- data/lib/ddig/resolver/dot.rb +94 -0
- data/lib/ddig/version.rb +5 -0
- data/lib/ddig.rb +31 -0
- data/sig/ddig.rbs +4 -0
- metadata +76 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 1d1bcdd660ab21b14d2e5572b27a50f2cc0fe541c64b9c6c8502f85d89a29b28
|
4
|
+
data.tar.gz: 6462363f2b075b9aad78f88a055e1d53af1901054a22c3a010fcdfb0a28e9fde
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 785ef898b4f773668131d56ed8596b11de8bfbe9908cc0de7383a2b0cfbb3aaf613dc813b73284382aaa6bbb3d9e89d6e4a45e0d1205aba45361213d6764cc19
|
7
|
+
data.tar.gz: c12f638d1899ddb47e6c482b22b315ea8912bce701243fef37a239566653e5e216c8ed01b910105bc06c28843e9d40ef81d653923e1b95a2e386f18c18f2fc72
|
data/.rspec
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2024 Taketo Takashima
|
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.
|
data/README.md
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
# ddig
|
2
|
+
|
3
|
+
ddig is DNS lookup utility for Ruby.
|
4
|
+
|
5
|
+
## Features
|
6
|
+
|
7
|
+
- DNS Resolvers
|
8
|
+
- UDP (Do53)
|
9
|
+
- DoT (DNS over TLS)
|
10
|
+
- https://datatracker.ietf.org/doc/html/rfc7858
|
11
|
+
- ~~DoH (DNS over HTTPS)~~
|
12
|
+
- Not yet Supported
|
13
|
+
- https://datatracker.ietf.org/doc/html/rfc8484
|
14
|
+
- DDR (Discovery of Designated Resolvers)
|
15
|
+
- https://www.rfc-editor.org/rfc/rfc9462.html
|
16
|
+
|
17
|
+
## Installation
|
18
|
+
|
19
|
+
Install the gem and add to the application's Gemfile by executing:
|
20
|
+
|
21
|
+
$ bundle add ddig
|
22
|
+
|
23
|
+
If bundler is not being used to manage dependencies, install the gem by executing:
|
24
|
+
|
25
|
+
$ gem install ddig
|
26
|
+
|
27
|
+
## Usage
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
ddig = Ddig.lookup('dns.google', nameservers: ['8.8.8.8', '2001:4860:4860::8888'])
|
31
|
+
|
32
|
+
ddig[:do53][:ipv4]
|
33
|
+
=> #<Ddig::Resolver::Do53:0x00000001207aaeb0 @a=["8.8.4.4", "8.8.8.8"], @aaaa=["2001:4860:4860::8844", "2001:4860:4860::8888"], @hostname="dns.google", @ip=:ipv4, @nameservers=["8.8.8.8"]>
|
34
|
+
ddig[:do53][:ipv6]
|
35
|
+
=> #<Ddig::Resolver::Do53:0x000000012073d2c0 @a=["8.8.4.4", "8.8.8.8"], @aaaa=["2001:4860:4860::8844", "2001:4860:4860::8888"], @hostname="dns.google", @ip=:ipv4, @nameservers=["2001:4860:4860::8888"]>
|
36
|
+
|
37
|
+
ddig[:ddr]
|
38
|
+
=> [#<Ddig::Ddr::DesignatedResolver:0x0000000120735480
|
39
|
+
@address="8.8.8.8",
|
40
|
+
@dohpath=nil,
|
41
|
+
@ip=:ipv4,
|
42
|
+
@port=853,
|
43
|
+
@protocol="dot",
|
44
|
+
@target="dns.google",
|
45
|
+
@unencrypted_resolver="8.8.8.8",
|
46
|
+
@verify_cert=
|
47
|
+
#<Ddig::Ddr::VerifyCert:0x00000001207321e0
|
48
|
+
@address="8.8.8.8",
|
49
|
+
@hostname="dns.google",
|
50
|
+
@open_timeout=3,
|
51
|
+
@port=853,
|
52
|
+
@subject_alt_name=
|
53
|
+
["DNS:dns.google",
|
54
|
+
"IP Address:8.8.8.8",
|
55
|
+
"IP Address:2001:4860:4860:0:0:0:0:8888",
|
56
|
+
...
|
57
|
+
"IP Address:2001:4860:4860:0:0:0:0:64"],
|
58
|
+
@unencrypted_resolver="8.8.8.8",
|
59
|
+
@verify=true>>,
|
60
|
+
#<Ddig::Ddr::DesignatedResolver:0x0000000120733b30
|
61
|
+
@address="8.8.8.8",
|
62
|
+
@dohpath="/dns-query{?dns}",
|
63
|
+
@ip=:ipv4,
|
64
|
+
@port=443,
|
65
|
+
@protocol="h2",
|
66
|
+
@target="dns.google",
|
67
|
+
@unencrypted_resolver="8.8.8.8",
|
68
|
+
@verify_cert=
|
69
|
+
#<Ddig::Ddr::VerifyCert:0x0000000120451bd8
|
70
|
+
@address="8.8.8.8",
|
71
|
+
@hostname="dns.google",
|
72
|
+
@open_timeout=3,
|
73
|
+
@port=443,
|
74
|
+
@subject_alt_name=
|
75
|
+
["DNS:dns.google",
|
76
|
+
"IP Address:8.8.8.8",
|
77
|
+
"IP Address:2001:4860:4860:0:0:0:0:8888",
|
78
|
+
...
|
79
|
+
"IP Address:2001:4860:4860:0:0:0:0:64"],
|
80
|
+
@unencrypted_resolver="8.8.8.8",
|
81
|
+
@verify=true>>,
|
82
|
+
...
|
83
|
+
]
|
84
|
+
```
|
85
|
+
|
86
|
+
### CLI
|
87
|
+
|
88
|
+
(TBD)
|
89
|
+
|
90
|
+
## Development
|
91
|
+
|
92
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
93
|
+
|
94
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
95
|
+
|
96
|
+
## Contributing
|
97
|
+
|
98
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/taketo1113/ddig.
|
99
|
+
|
100
|
+
## License
|
101
|
+
|
102
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/exe/ddig
ADDED
data/lib/ddig/cli.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require "optparse"
|
2
|
+
|
3
|
+
require "ddig"
|
4
|
+
|
5
|
+
module Ddig
|
6
|
+
class Cli
|
7
|
+
def initialize(args)
|
8
|
+
@args = args
|
9
|
+
@options = {
|
10
|
+
format: 'text',
|
11
|
+
}
|
12
|
+
|
13
|
+
parse_options
|
14
|
+
|
15
|
+
if @hostname.nil?
|
16
|
+
puts @option_parser
|
17
|
+
exit
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def parse_options
|
22
|
+
@option_parser = OptionParser.new do |opts|
|
23
|
+
opts.banner = "Usage: ddig [options] hostname"
|
24
|
+
|
25
|
+
opts.on("--nameserver=ipaddress", "nameserver ip address") { |v| @options[:nameserver] = v }
|
26
|
+
opts.on("--format={text|json}", "output format (default: text)") { |v| @options[:format] = v }
|
27
|
+
|
28
|
+
opts.separator ""
|
29
|
+
|
30
|
+
opts.on("-v", "--verbose", "run verbosely") { |v| @options[:verbose] = v }
|
31
|
+
|
32
|
+
opts.on("-h", "--help", "show this help message.") { puts opts; exit }
|
33
|
+
opts.on("--version", "show version.") { puts Ddig::VERSION; exit }
|
34
|
+
end
|
35
|
+
@option_parser.parse!
|
36
|
+
|
37
|
+
@hostname = @args[0]
|
38
|
+
end
|
39
|
+
|
40
|
+
def exec
|
41
|
+
@ddig = Ddig.lookup(@hostname, nameservers: [@options[:nameserver]])
|
42
|
+
|
43
|
+
if @options[:format] == 'json'
|
44
|
+
# TODO: to_json
|
45
|
+
puts @ddig
|
46
|
+
else
|
47
|
+
print_result
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def print_result
|
52
|
+
puts @ddig
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Ddig
|
2
|
+
class Ddr
|
3
|
+
class DesignatedResolver
|
4
|
+
attr_reader :unencrypted_resolver, :target, :protocol, :port, :dohpath, :address, :ip
|
5
|
+
attr_reader :verify_cert
|
6
|
+
|
7
|
+
PROTOCOLS = ['http/1.1', 'h2', 'h3', 'dot', 'doq']
|
8
|
+
|
9
|
+
def initialize(unencrypted_resolver:, target:, protocol: nil, port: nil, dohpath: nil, address: nil, ip: nil)
|
10
|
+
@target = target
|
11
|
+
@unencrypted_resolver = unencrypted_resolver
|
12
|
+
@protocol = protocol
|
13
|
+
@port = port
|
14
|
+
@dohpath = dohpath
|
15
|
+
@address = address
|
16
|
+
@ip = ip
|
17
|
+
|
18
|
+
# check protocol
|
19
|
+
unless PROTOCOLS.include?(@protocol)
|
20
|
+
raise Error.new("Not Supportted Protocol (protocol: #{@protocol}). Suported protocol is #{PROTOCOLS.join(' / ')}")
|
21
|
+
end
|
22
|
+
|
23
|
+
if @port.nil?
|
24
|
+
set_default_port
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def verify
|
29
|
+
@verify_cert = VerifyCert.new(hostname: @target, address: @address, port: @port, unencrypted_resolver: @unencrypted_resolver)
|
30
|
+
@verify_cert.verify
|
31
|
+
end
|
32
|
+
|
33
|
+
# Set default port by protocol
|
34
|
+
# ref: https://www.rfc-editor.org/rfc/rfc9461.html#section-4.2
|
35
|
+
def set_default_port
|
36
|
+
case @protocol
|
37
|
+
when 'http/1.1'
|
38
|
+
@port = 80
|
39
|
+
when 'h2', 'h3'
|
40
|
+
@port = 443
|
41
|
+
when 'dot', 'doq'
|
42
|
+
@port = 853
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def uniq_key
|
47
|
+
"#{@unencrypted_resolver}-#{@target}-#{@protocol}-#{@port}-#{@dohpath}-#{@address}-#{@ip}"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require "openssl"
|
2
|
+
require 'net/protocol'
|
3
|
+
|
4
|
+
module Ddig
|
5
|
+
class Ddr
|
6
|
+
class VerifyCert
|
7
|
+
attr_reader :hostname, :address, :port, :unencrypted_resolver
|
8
|
+
attr_reader :verify, :subject_alt_name, :error_message
|
9
|
+
|
10
|
+
def initialize(hostname:, address:, port:, unencrypted_resolver:)
|
11
|
+
@hostname = hostname
|
12
|
+
@address = address
|
13
|
+
@port = port
|
14
|
+
@unencrypted_resolver = unencrypted_resolver
|
15
|
+
|
16
|
+
@open_timeout = 3
|
17
|
+
end
|
18
|
+
|
19
|
+
def verify
|
20
|
+
begin
|
21
|
+
socket = Timeout.timeout(@open_timeout) {
|
22
|
+
TCPSocket.open(@address, @port)
|
23
|
+
}
|
24
|
+
|
25
|
+
ctx = OpenSSL::SSL::SSLContext.new
|
26
|
+
ctx.set_params
|
27
|
+
|
28
|
+
ssl_socket = OpenSSL::SSL::SSLSocket.new(socket, ctx)
|
29
|
+
ssl_socket.sync_close = true
|
30
|
+
ssl_socket.hostname = @hostname
|
31
|
+
|
32
|
+
# connect
|
33
|
+
Timeout.timeout(@open_timeout) {
|
34
|
+
ssl_socket.connect
|
35
|
+
}
|
36
|
+
|
37
|
+
# verify
|
38
|
+
set_subject_alt_name(ssl_socket)
|
39
|
+
|
40
|
+
unless ssl_socket.post_connection_check(@hostname)
|
41
|
+
@verify = false
|
42
|
+
return @verify
|
43
|
+
end
|
44
|
+
|
45
|
+
unless ssl_socket.post_connection_check(@unencrypted_resolver)
|
46
|
+
@verify = false
|
47
|
+
return @verify
|
48
|
+
end
|
49
|
+
|
50
|
+
@verify = true
|
51
|
+
return @verify
|
52
|
+
|
53
|
+
rescue => e
|
54
|
+
@verify = false
|
55
|
+
@error_message = e.message
|
56
|
+
|
57
|
+
return @verify
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def set_subject_alt_name(ssl_socket)
|
62
|
+
socket = Net::BufferedIO.new(ssl_socket)
|
63
|
+
|
64
|
+
@subject_alt_name = socket.io.peer_cert.extensions.select { |ext| ext.to_h['oid'] == 'subjectAltName' }.first.to_h['value'].split(', ')
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
data/lib/ddig/ddr.rb
ADDED
@@ -0,0 +1,140 @@
|
|
1
|
+
require 'resolv'
|
2
|
+
|
3
|
+
require_relative "ddr/verify_cert"
|
4
|
+
require_relative "ddr/designated_resolver"
|
5
|
+
|
6
|
+
module Ddig
|
7
|
+
# DDR client (Discovery of Designated Resolvers)
|
8
|
+
class Ddr
|
9
|
+
attr_reader :nameservers, :ip
|
10
|
+
attr_reader :svcb_records, :designated_resolvers
|
11
|
+
|
12
|
+
def initialize(nameservers: nil, ip: nil)
|
13
|
+
@ip = ip
|
14
|
+
|
15
|
+
@nameserver = Ddig::Nameserver.new(nameservers: nameservers)
|
16
|
+
set_nameservers
|
17
|
+
|
18
|
+
# discover designated resolvers
|
19
|
+
query_svcb_records
|
20
|
+
discover_designated_resolvers
|
21
|
+
verify_discovery
|
22
|
+
end
|
23
|
+
|
24
|
+
def query_svcb_records
|
25
|
+
@svcb_records = []
|
26
|
+
|
27
|
+
if @nameservers.empty?
|
28
|
+
return @svcb_records
|
29
|
+
end
|
30
|
+
|
31
|
+
@nameservers.each do |nameserver|
|
32
|
+
svcb_rrset = Resolv::DNS.open(nameserver: nameserver) do |dns|
|
33
|
+
dns.getresources('_dns.resolver.arpa', Resolv::DNS::Resource::IN::SVCB)
|
34
|
+
end
|
35
|
+
|
36
|
+
svcb_rrset.sort_by!(&:priority)
|
37
|
+
|
38
|
+
@svcb_records += svcb_rrset.map { |svcb_record| { unencrypted_resolver: nameserver, svcb_record: svcb_record } }
|
39
|
+
end
|
40
|
+
|
41
|
+
@svcb_records
|
42
|
+
end
|
43
|
+
|
44
|
+
# Discovery Designated Resolvers from SVCB RR Set
|
45
|
+
# ref. https://www.rfc-editor.org/rfc/rfc9461.html
|
46
|
+
def discover_designated_resolvers
|
47
|
+
@designated_resolvers = []
|
48
|
+
|
49
|
+
@svcb_records.each do |item|
|
50
|
+
unencrypted_resolver = item[:unencrypted_resolver]
|
51
|
+
svcb_record = item[:svcb_record]
|
52
|
+
|
53
|
+
target = svcb_record.target.to_s
|
54
|
+
priority = svcb_record.priority
|
55
|
+
protocols = svcb_record.params[:alpn].protocol_ids
|
56
|
+
port = svcb_record.params[:port]&.port
|
57
|
+
dohpath = svcb_record.params[:dohpath]&.template
|
58
|
+
ipv4hint = svcb_record.params[:ipv4hint]&.addresses
|
59
|
+
ipv6hint = svcb_record.params[:ipv6hint]&.addresses
|
60
|
+
|
61
|
+
# Skip AliasMode of SVCB RR
|
62
|
+
if priority.zero?
|
63
|
+
next
|
64
|
+
end
|
65
|
+
|
66
|
+
protocols.each do |protocol|
|
67
|
+
do53_v4 = ::Ddig::Resolver::Do53.new(hostname: target, nameservers: [unencrypted_resolver], ip: :ipv4).lookup
|
68
|
+
do53_v6 = ::Ddig::Resolver::Do53.new(hostname: target, nameservers: [unencrypted_resolver], ip: :ipv6).lookup
|
69
|
+
|
70
|
+
# ipv4
|
71
|
+
unless do53_v4.nil? || do53_v4.a.nil?
|
72
|
+
do53_v4.a.each do |address|
|
73
|
+
designated_resolver = ::Ddig::Ddr::DesignatedResolver.new(unencrypted_resolver: unencrypted_resolver, target: target, protocol: protocol, port: port, dohpath: dohpath, address: address.to_s, ip: :ipv4)
|
74
|
+
@designated_resolvers << designated_resolver
|
75
|
+
end
|
76
|
+
end
|
77
|
+
unless do53_v6.nil? || do53_v6.a.nil?
|
78
|
+
do53_v6.a.each do |address|
|
79
|
+
designated_resolver = ::Ddig::Ddr::DesignatedResolver.new(unencrypted_resolver: unencrypted_resolver, target: target, protocol: protocol, port: port, dohpath: dohpath, address: address.to_s, ip: :ipv4)
|
80
|
+
@designated_resolvers << designated_resolver
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# ipv6
|
85
|
+
unless do53_v4.nil? || do53_v4.aaaa.nil?
|
86
|
+
do53_v4.aaaa.each do |address|
|
87
|
+
designated_resolver = ::Ddig::Ddr::DesignatedResolver.new(unencrypted_resolver: unencrypted_resolver, target: target, protocol: protocol, port: port, dohpath: dohpath, address: address.to_s, ip: :ipv6)
|
88
|
+
@designated_resolvers << designated_resolver
|
89
|
+
end
|
90
|
+
end
|
91
|
+
unless do53_v6.nil? || do53_v6.aaaa.nil?
|
92
|
+
do53_v6.aaaa.each do |address|
|
93
|
+
designated_resolver = ::Ddig::Ddr::DesignatedResolver.new(unencrypted_resolver: unencrypted_resolver, target: target, protocol: protocol, port: port, dohpath: dohpath, address: address.to_s, ip: :ipv6)
|
94
|
+
@designated_resolvers << designated_resolver
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# ipv4hint
|
99
|
+
unless ipv4hint.nil?
|
100
|
+
ipv4hint.each do |address|
|
101
|
+
ip = :ipv4
|
102
|
+
designated_resolver = ::Ddig::Ddr::DesignatedResolver.new(unencrypted_resolver: unencrypted_resolver, target: target, protocol: protocol, port: port, dohpath: dohpath, address: address.to_s, ip: ip)
|
103
|
+
@designated_resolvers << designated_resolver
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# ipv6hint
|
108
|
+
unless ipv6hint.nil?
|
109
|
+
ipv6hint.each do |address|
|
110
|
+
ip = :ipv6
|
111
|
+
designated_resolver = ::Ddig::Ddr::DesignatedResolver.new(unencrypted_resolver: unencrypted_resolver, target: target, protocol: protocol, port: port, dohpath: dohpath, address: address.to_s, ip: ip)
|
112
|
+
@designated_resolvers << designated_resolver
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
@designated_resolvers.uniq! { |designated_resolver| designated_resolver.uniq_key }
|
119
|
+
end
|
120
|
+
|
121
|
+
def verify_discovery
|
122
|
+
@designated_resolvers.map! do |designated_resolver|
|
123
|
+
designated_resolver.verify
|
124
|
+
designated_resolver
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def set_nameservers
|
129
|
+
@nameservers = @nameserver.servers
|
130
|
+
|
131
|
+
if @ip == :ipv4
|
132
|
+
@nameservers = @nameserver.servers_ipv4
|
133
|
+
end
|
134
|
+
|
135
|
+
if @ip == :ipv6
|
136
|
+
@nameservers = @nameserver.servers_ipv6
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'resolv'
|
2
|
+
|
3
|
+
module Ddig
|
4
|
+
class Nameserver
|
5
|
+
attr_reader :servers
|
6
|
+
|
7
|
+
def initialize(nameservers: nil)
|
8
|
+
@nameservers = nameservers
|
9
|
+
|
10
|
+
if @nameservers.nil?
|
11
|
+
@servers = default_servers
|
12
|
+
elsif @nameservers.is_a?(Array)
|
13
|
+
@servers = @nameservers
|
14
|
+
else
|
15
|
+
@servers = [@nameservers]
|
16
|
+
end
|
17
|
+
|
18
|
+
validation_servers
|
19
|
+
end
|
20
|
+
|
21
|
+
def servers
|
22
|
+
if @servers.count.zero?
|
23
|
+
raise Ddig::Error.new('nameservers required')
|
24
|
+
end
|
25
|
+
|
26
|
+
@servers
|
27
|
+
end
|
28
|
+
|
29
|
+
def default_servers
|
30
|
+
Resolv::DNS::Config.default_config_hash[:nameserver]
|
31
|
+
end
|
32
|
+
|
33
|
+
def servers_ipv4
|
34
|
+
@servers.map do |nameserver|
|
35
|
+
if IPAddr.new(nameserver).ipv4?
|
36
|
+
nameserver
|
37
|
+
end
|
38
|
+
end.compact
|
39
|
+
end
|
40
|
+
|
41
|
+
def servers_ipv6
|
42
|
+
@servers.map do |nameserver|
|
43
|
+
if IPAddr.new(nameserver).ipv6?
|
44
|
+
nameserver
|
45
|
+
end
|
46
|
+
end.compact
|
47
|
+
end
|
48
|
+
|
49
|
+
def validation_servers
|
50
|
+
@servers.uniq.each do |server|
|
51
|
+
addr = IPAddr.new(server) rescue nil
|
52
|
+
if addr.nil?
|
53
|
+
puts "Warning: nameservers has invalid ip address (nameserver: #{server})"
|
54
|
+
@servers.delete(server)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
if @servers.count.zero?
|
59
|
+
return false
|
60
|
+
end
|
61
|
+
|
62
|
+
return true
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'resolv'
|
2
|
+
|
3
|
+
module Ddig
|
4
|
+
module Resolver
|
5
|
+
# DNS Resolver of UDP/53
|
6
|
+
class Do53
|
7
|
+
attr_reader :hostname, :nameservers, :ip
|
8
|
+
attr_reader :a, :aaaa
|
9
|
+
|
10
|
+
def initialize(hostname:, nameservers: nil, ip: nil)
|
11
|
+
@hostname = hostname
|
12
|
+
@ip = ip
|
13
|
+
|
14
|
+
@nameserver = Ddig::Nameserver.new(nameservers: nameservers)
|
15
|
+
set_nameservers
|
16
|
+
end
|
17
|
+
|
18
|
+
def lookup
|
19
|
+
if @nameservers.empty?
|
20
|
+
return nil
|
21
|
+
end
|
22
|
+
|
23
|
+
@a = Resolv::DNS.open(nameserver: @nameservers) do |dns|
|
24
|
+
ress = dns.getresources(@hostname, Resolv::DNS::Resource::IN::A)
|
25
|
+
ress.map { |resource| resource.address.to_s }
|
26
|
+
end
|
27
|
+
|
28
|
+
@aaaa = Resolv::DNS.open(nameserver: @nameservers) do |dns|
|
29
|
+
ress = dns.getresources(@hostname, Resolv::DNS::Resource::IN::AAAA)
|
30
|
+
ress.map { |resource| resource.address.to_s }
|
31
|
+
end
|
32
|
+
|
33
|
+
self
|
34
|
+
end
|
35
|
+
|
36
|
+
def set_nameservers
|
37
|
+
@nameservers = @nameserver.servers
|
38
|
+
|
39
|
+
if @ip == :ipv4
|
40
|
+
@nameservers = @nameserver.servers_ipv4
|
41
|
+
end
|
42
|
+
|
43
|
+
if @ip == :ipv6
|
44
|
+
@nameservers = @nameserver.servers_ipv6
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
require 'resolv'
|
3
|
+
|
4
|
+
module Ddig
|
5
|
+
module Resolver
|
6
|
+
# DNS over TLS/TCP
|
7
|
+
class Dot
|
8
|
+
attr_reader :hostname, :server, :server_name, :port
|
9
|
+
attr_reader :a, :aaaa
|
10
|
+
|
11
|
+
def initialize(hostname:, server:, server_name: nil, port: 853)
|
12
|
+
@hostname = hostname
|
13
|
+
@server = server
|
14
|
+
@server_name = server_name
|
15
|
+
@port = port
|
16
|
+
|
17
|
+
@open_timeout = 3
|
18
|
+
end
|
19
|
+
|
20
|
+
def lookup
|
21
|
+
if @server.nil?
|
22
|
+
return nil
|
23
|
+
end
|
24
|
+
|
25
|
+
@a = get_resources(@hostname, Resolv::DNS::Resource::IN::A).map { |resource| resource.address.to_s if resource.is_a?(Resolv::DNS::Resource::IN::A) }.compact
|
26
|
+
|
27
|
+
@aaaa = get_resources(@hostname, Resolv::DNS::Resource::IN::AAAA).map { |resource| resource.address.to_s if resource.is_a?(Resolv::DNS::Resource::IN::AAAA) }.compact
|
28
|
+
|
29
|
+
self
|
30
|
+
end
|
31
|
+
|
32
|
+
def get_resources(hostname, typeclass)
|
33
|
+
ssl_socket = get_socket
|
34
|
+
|
35
|
+
# send query
|
36
|
+
message = dns_message(hostname, typeclass)
|
37
|
+
|
38
|
+
request = [message.encode.length].pack('n') + message.encode
|
39
|
+
ssl_socket.write(request)
|
40
|
+
|
41
|
+
# recive answer
|
42
|
+
len = ssl_socket.read(2).unpack1('n')
|
43
|
+
response = Resolv::DNS::Message.decode(ssl_socket.read(len))
|
44
|
+
|
45
|
+
resources = response.answer.map { |name, ttl, resource| resource }
|
46
|
+
|
47
|
+
resources
|
48
|
+
end
|
49
|
+
|
50
|
+
def get_socket
|
51
|
+
begin
|
52
|
+
socket = Timeout.timeout(@open_timeout) {
|
53
|
+
TCPSocket.open(@server, @port)
|
54
|
+
}
|
55
|
+
|
56
|
+
ctx = OpenSSL::SSL::SSLContext.new
|
57
|
+
ctx.set_params
|
58
|
+
ctx.alpn_protocols = ['dot']
|
59
|
+
|
60
|
+
ssl_socket = OpenSSL::SSL::SSLSocket.new(socket, ctx)
|
61
|
+
ssl_socket.sync_close = true
|
62
|
+
unless @server_name.nil?
|
63
|
+
ssl_socket.hostname = @server_name
|
64
|
+
end
|
65
|
+
|
66
|
+
# connect
|
67
|
+
Timeout.timeout(@open_timeout) {
|
68
|
+
ssl_socket.connect
|
69
|
+
unless @server_name.nil?
|
70
|
+
ssl_socket.post_connection_check(@server_name)
|
71
|
+
end
|
72
|
+
}
|
73
|
+
|
74
|
+
ssl_socket
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def dns_message(hostname, typeclass)
|
79
|
+
if hostname.nil?
|
80
|
+
return nil
|
81
|
+
end
|
82
|
+
if typeclass.nil?
|
83
|
+
return nil
|
84
|
+
end
|
85
|
+
|
86
|
+
message = Resolv::DNS::Message.new
|
87
|
+
message.rd = 1 # recursive query
|
88
|
+
message.add_question(hostname, typeclass)
|
89
|
+
|
90
|
+
message
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
data/lib/ddig/version.rb
ADDED
data/lib/ddig.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "ddig/version"
|
4
|
+
require_relative "ddig/nameserver"
|
5
|
+
require_relative "ddig/resolver/do53"
|
6
|
+
require_relative "ddig/resolver/dot"
|
7
|
+
require_relative "ddig/ddr"
|
8
|
+
|
9
|
+
module Ddig
|
10
|
+
class Error < StandardError; end
|
11
|
+
|
12
|
+
def self.lookup(hostname, nameservers: nil)
|
13
|
+
@hostname = hostname
|
14
|
+
@nameservers = nameservers
|
15
|
+
|
16
|
+
@nameserver = Ddig::Nameserver.new(nameservers: @nameservers)
|
17
|
+
|
18
|
+
@do53_ipv4 = Ddig::Resolver::Do53.new(hostname: @hostname, nameservers: @nameserver.servers, ip: :ipv4).lookup
|
19
|
+
@do53_ipv6 = Ddig::Resolver::Do53.new(hostname: @hostname, nameservers: @nameserver.servers, ip: :ipv6).lookup
|
20
|
+
|
21
|
+
@ddr = Ddig::Ddr.new(nameservers: @nameservers)
|
22
|
+
|
23
|
+
{
|
24
|
+
do53: {
|
25
|
+
ipv4: @do53_ipv4,
|
26
|
+
ipv6: @do53_ipv6,
|
27
|
+
},
|
28
|
+
ddr: @ddr.designated_resolvers
|
29
|
+
}
|
30
|
+
end
|
31
|
+
end
|
data/sig/ddig.rbs
ADDED
metadata
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ddig
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Taketo Takashima
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-03-16 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: resolv
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.3.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.3.0
|
27
|
+
description: DNS lookup utility for Ruby
|
28
|
+
email:
|
29
|
+
- t.taketo1113@gmail.com
|
30
|
+
executables:
|
31
|
+
- ddig
|
32
|
+
extensions: []
|
33
|
+
extra_rdoc_files: []
|
34
|
+
files:
|
35
|
+
- ".rspec"
|
36
|
+
- LICENSE.txt
|
37
|
+
- README.md
|
38
|
+
- Rakefile
|
39
|
+
- exe/ddig
|
40
|
+
- lib/ddig.rb
|
41
|
+
- lib/ddig/cli.rb
|
42
|
+
- lib/ddig/ddr.rb
|
43
|
+
- lib/ddig/ddr/designated_resolver.rb
|
44
|
+
- lib/ddig/ddr/verify_cert.rb
|
45
|
+
- lib/ddig/nameserver.rb
|
46
|
+
- lib/ddig/resolver/do53.rb
|
47
|
+
- lib/ddig/resolver/dot.rb
|
48
|
+
- lib/ddig/version.rb
|
49
|
+
- sig/ddig.rbs
|
50
|
+
homepage: https://github.com/taketo1113/ddig
|
51
|
+
licenses:
|
52
|
+
- MIT
|
53
|
+
metadata:
|
54
|
+
homepage_uri: https://github.com/taketo1113/ddig
|
55
|
+
source_code_uri: https://github.com/taketo1113/ddig
|
56
|
+
changelog_uri: https://github.com/taketo1113/ddig/releases
|
57
|
+
post_install_message:
|
58
|
+
rdoc_options: []
|
59
|
+
require_paths:
|
60
|
+
- lib
|
61
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
62
|
+
requirements:
|
63
|
+
- - ">="
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: 2.6.0
|
66
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: '0'
|
71
|
+
requirements: []
|
72
|
+
rubygems_version: 3.5.3
|
73
|
+
signing_key:
|
74
|
+
specification_version: 4
|
75
|
+
summary: DNS lookup utility for Ruby
|
76
|
+
test_files: []
|