ddig 0.1.0

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
+ SHA256:
3
+ metadata.gz: 1d1bcdd660ab21b14d2e5572b27a50f2cc0fe541c64b9c6c8502f85d89a29b28
4
+ data.tar.gz: 6462363f2b075b9aad78f88a055e1d53af1901054a22c3a010fcdfb0a28e9fde
5
+ SHA512:
6
+ metadata.gz: 785ef898b4f773668131d56ed8596b11de8bfbe9908cc0de7383a2b0cfbb3aaf613dc813b73284382aaa6bbb3d9e89d6e4a45e0d1205aba45361213d6764cc19
7
+ data.tar.gz: c12f638d1899ddb47e6c482b22b315ea8912bce701243fef37a239566653e5e216c8ed01b910105bc06c28843e9d40ef81d653923e1b95a2e386f18c18f2fc72
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
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
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
data/exe/ddig ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "ddig/cli"
4
+
5
+ cli = Ddig::Cli.new(ARGV)
6
+ cli.exec
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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ddig
4
+ VERSION = "0.1.0"
5
+ end
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
@@ -0,0 +1,4 @@
1
+ module Ddig
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
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: []