network-infra-utility 0.2.0 → 0.5.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 +4 -4
- data/.gitignore +21 -0
- data/CHANGELOG.md +31 -4
- data/Gemfile +2 -0
- data/Gemfile.lock +70 -0
- data/Rakefile +1 -1
- data/bin/dns-query +834 -0
- data/bin/geo-doc +135 -0
- data/bin/geo-get +1 -1
- data/document/ASNum/346/250/241/345/235/227/345/212/237/350/203/275/350/257/264/346/230/216.md +242 -0
- data/document/DNSQuery/345/267/245/345/205/267/344/275/277/347/224/250/346/226/271/346/263/225.md +248 -0
- data/document/Geo/345/221/275/344/273/244/345/267/245/345/205/267/344/275/277/347/224/250/346/226/271/346/263/225.md +441 -0
- data/document/IP/346/250/241/345/235/227/345/212/237/350/203/275/350/257/264/346/230/216.md +297 -0
- data/document/MAC/346/250/241/345/235/227/345/212/237/350/203/275/350/257/264/346/230/216.md +296 -0
- data/document/SSH/350/277/236/346/216/245/345/256/242/346/210/267/347/253/257/344/275/277/347/224/250/346/226/271/346/263/225.md +764 -0
- data/network-infra-utility.gemspec +4 -2
- data/network.rb +3 -1
- data/service/geodb/GeoAPI.md +1 -0
- data/service/geodb/geodb.rb +278 -1
- data/service/ssh/README.md +942 -0
- data/service/ssh/bin/ssh-client +198 -0
- data/service/ssh/design/SSH/350/277/236/346/216/245/345/256/242/346/210/267/347/253/257/345/212/237/350/203/275/351/234/200/346/261/202/346/226/207/346/241/243.md +292 -0
- data/service/ssh/design/SSH/350/277/236/346/216/245/345/256/242/346/210/267/347/253/257/350/257/246/347/273/206/350/256/276/350/256/241/346/226/207/346/241/243.md +1521 -0
- data/service/ssh/design/SSH/350/277/236/346/216/245/345/256/242/346/210/267/347/253/257/350/275/257/344/273/266/350/256/276/350/256/241/346/226/207/346/241/243.md +2493 -0
- data/service/ssh/ext/ssh_core/bin/ssh_core.cmd +28 -0
- data/service/ssh/ext/ssh_core/config/sys.config +0 -0
- data/service/ssh/ext/ssh_core/config/vm.args +0 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/CHECKSUM +1 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/LICENSE +21 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/README.md +696 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/VERSION +1 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/contents.tar.gz +0 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/metadata.config +15 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/rebar.config +17 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/rebar.lock +1 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx.app.src +10 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx.erl +506 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_config.erl +393 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_config.hrl +18 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_consult.erl +81 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_decoder.erl +1909 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_encoder.erl +116 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_parser.erl +1214 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_to_json.erl +408 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_to_term.erl +389 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_verify.erl +121 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx.erl +506 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_config.erl +393 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_config.hrl +18 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_consult.erl +81 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_decoder.erl +1909 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_encoder.erl +116 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_parser.erl +1214 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_to_json.erl +408 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_to_term.erl +389 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_verify.erl +121 -0
- data/service/ssh/ext/ssh_core/rebar.config +24 -0
- data/service/ssh/ext/ssh_core/rebar.lock +1 -0
- data/service/ssh/ext/ssh_core/src/ssh_auth_engine.erl +156 -0
- data/service/ssh/ext/ssh_core/src/ssh_channel_stm.erl +232 -0
- data/service/ssh/ext/ssh_core/src/ssh_codec.erl +83 -0
- data/service/ssh/ext/ssh_core/src/ssh_conn_sup.erl +48 -0
- data/service/ssh/ext/ssh_core/src/ssh_conn_worker.erl +535 -0
- data/service/ssh/ext/ssh_core/src/ssh_core.app.src +36 -0
- data/service/ssh/ext/ssh_core/src/ssh_core_app.erl +11 -0
- data/service/ssh/ext/ssh_core/src/ssh_core_sup.erl +46 -0
- data/service/ssh/ext/ssh_core/src/ssh_infra_sup.erl +104 -0
- data/service/ssh/ext/ssh_core/src/ssh_ipc.hrl +80 -0
- data/service/ssh/ext/ssh_core/src/ssh_ipc_coalesce.erl +94 -0
- data/service/ssh/ext/ssh_core/src/ssh_ipc_gateway.erl +467 -0
- data/service/ssh/ext/ssh_core/src/ssh_ipc_proto.erl +95 -0
- data/service/ssh/ext/ssh_core/src/ssh_jump_chain.erl +101 -0
- data/service/ssh/ext/ssh_core/src/ssh_keepalive_mgr.erl +222 -0
- data/service/ssh/ext/ssh_core/src/ssh_known_hosts_proxy.erl +67 -0
- data/service/ssh/ext/ssh_core/src/ssh_port_fwd.erl +225 -0
- data/service/ssh/ext/ssh_core/src/ssh_sftp_session.erl +250 -0
- data/service/ssh/ext/ssh_core/src/ssh_sftp_sup.erl +62 -0
- data/service/ssh/ext/ssh_core_rs/Cargo.lock +2345 -0
- data/service/ssh/ext/ssh_core_rs/Cargo.toml +30 -0
- data/service/ssh/ext/ssh_core_rs/bin/ssh_core_rs +34 -0
- data/service/ssh/ext/ssh_core_rs/bin/ssh_core_rs.cmd +40 -0
- data/service/ssh/ext/ssh_core_rs/src/channel.rs +296 -0
- data/service/ssh/ext/ssh_core_rs/src/coalesce.rs +143 -0
- data/service/ssh/ext/ssh_core_rs/src/codec.rs +71 -0
- data/service/ssh/ext/ssh_core_rs/src/conn.rs +628 -0
- data/service/ssh/ext/ssh_core_rs/src/gateway.rs +389 -0
- data/service/ssh/ext/ssh_core_rs/src/handler.rs +293 -0
- data/service/ssh/ext/ssh_core_rs/src/keepalive.rs +194 -0
- data/service/ssh/ext/ssh_core_rs/src/main.rs +351 -0
- data/service/ssh/ext/ssh_core_rs/src/portfwd.rs +378 -0
- data/service/ssh/ext/ssh_core_rs/src/proto.rs +198 -0
- data/service/ssh/ext/ssh_core_rs/src/sftp.rs +294 -0
- data/service/ssh/lib/network_infra_utility/ssh/automation/macro_engine.rb +213 -0
- data/service/ssh/lib/network_infra_utility/ssh/client.rb +257 -0
- data/service/ssh/lib/network_infra_utility/ssh/config/schema.rb +90 -0
- data/service/ssh/lib/network_infra_utility/ssh/config/settings.rb +103 -0
- data/service/ssh/lib/network_infra_utility/ssh/config/store.rb +90 -0
- data/service/ssh/lib/network_infra_utility/ssh/ipc/coalesce.rb +83 -0
- data/service/ssh/lib/network_infra_utility/ssh/ipc/errors.rb +36 -0
- data/service/ssh/lib/network_infra_utility/ssh/ipc/router.rb +212 -0
- data/service/ssh/lib/network_infra_utility/ssh/ipc/transport.rb +81 -0
- data/service/ssh/lib/network_infra_utility/ssh/security/host_key.rb +211 -0
- data/service/ssh/lib/network_infra_utility/ssh/security/vault.rb +211 -0
- data/service/ssh/lib/network_infra_utility/ssh/session/history.rb +56 -0
- data/service/ssh/lib/network_infra_utility/ssh/session/manager.rb +92 -0
- data/service/ssh/lib/network_infra_utility/ssh/session/session.rb +109 -0
- data/service/ssh/lib/network_infra_utility/ssh/session/tree.rb +95 -0
- data/service/ssh/lib/network_infra_utility/ssh/terminal/ansi_parser.rb +435 -0
- data/service/ssh/lib/network_infra_utility/ssh/terminal/buffer.rb +78 -0
- data/service/ssh/lib/network_infra_utility/ssh/terminal/emulator.rb +159 -0
- data/service/ssh/lib/network_infra_utility/ssh/terminal/logger.rb +195 -0
- data/service/ssh/lib/network_infra_utility/ssh/terminal/screen.rb +212 -0
- data/service/ssh/lib/network_infra_utility/ssh/terminal/theme.rb +127 -0
- data/service/ssh/lib/network_infra_utility/ssh/version.rb +7 -0
- data/service/ssh/lib/network_infra_utility/ssh.rb +44 -0
- data/support/basic/as_num.rb +221 -0
- data/support/basic/mac_address.rb +281 -0
- data/version.rb +1 -1
- metadata +138 -1
data/bin/dns-query
ADDED
|
@@ -0,0 +1,834 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
# coding: utf-8
|
|
4
|
+
#
|
|
5
|
+
# dns-query — 跨平台统一域名查询命令行工具 (network-infra-utility)
|
|
6
|
+
#
|
|
7
|
+
# 自动检测当前平台所有可用的 DNS 查询工具 (dig / nslookup / host /
|
|
8
|
+
# Resolve-DnsName 等),默认使用优先级最高的工具执行查询;也支持指定
|
|
9
|
+
# DNS 服务器、指定记录类型、遍历全部可用工具交叉查询。
|
|
10
|
+
#
|
|
11
|
+
# 用法:
|
|
12
|
+
# ruby bin/dns-query <目标> [选项]
|
|
13
|
+
# ruby bin/dns-query www.baidu.com # 正查 A 记录
|
|
14
|
+
# ruby bin/dns-query 8.8.8.8 # 反查 PTR (自动识别 IP)
|
|
15
|
+
# ruby bin/dns-query www.baidu.com -t MX # 指定记录类型
|
|
16
|
+
# ruby bin/dns-query www.baidu.com -s 8.8.8.8 # 指定 DNS 服务器
|
|
17
|
+
# ruby bin/dns-query www.baidu.com -a # 用所有可用工具查询
|
|
18
|
+
# ruby bin/dns-query --tools # 列出当前平台可用工具
|
|
19
|
+
# ruby bin/dns-query --matrix # 全平台工具矩阵
|
|
20
|
+
#
|
|
21
|
+
# 选项:
|
|
22
|
+
# -t, --type TYPE DNS 记录类型, 默认 A
|
|
23
|
+
# -s, --server SERVER 指定 DNS 服务器 (IP 或域名)
|
|
24
|
+
# -a, --all 使用所有可用工具交叉查询
|
|
25
|
+
# --tools 列出当前平台可用工具
|
|
26
|
+
# --matrix 显示全平台工具矩阵
|
|
27
|
+
# --no-color 禁用 ANSI 颜色输出
|
|
28
|
+
# -v, --version 显示版本
|
|
29
|
+
# -h, --help 显示帮助
|
|
30
|
+
#
|
|
31
|
+
# 退出码: 0 成功, 1 参数错误 / 无可用工具 / 查询全部失败
|
|
32
|
+
|
|
33
|
+
require "optparse"
|
|
34
|
+
require "rbconfig"
|
|
35
|
+
require "shellwords"
|
|
36
|
+
require "ipaddr"
|
|
37
|
+
require_relative "../version"
|
|
38
|
+
|
|
39
|
+
module DnsQuery
|
|
40
|
+
# ============================================================
|
|
41
|
+
# 平台检测
|
|
42
|
+
# ============================================================
|
|
43
|
+
module Platform
|
|
44
|
+
def self.os
|
|
45
|
+
@os ||= detect_os
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def self.linux?
|
|
49
|
+
os == :linux
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def self.windows?
|
|
53
|
+
os == :windows
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def self.wsl?
|
|
57
|
+
linux? && File.exist?("/proc/version") &&
|
|
58
|
+
File.read("/proc/version").downcase.include?("microsoft")
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def self.distro
|
|
62
|
+
@distro ||= detect_distro
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def self.windows_build
|
|
66
|
+
return @win_build if @win_build
|
|
67
|
+
return nil unless windows?
|
|
68
|
+
raw = cmd_utf8("ver 2>NUL").strip
|
|
69
|
+
if raw =~ /(\d+\.\d+\.(\d+))/
|
|
70
|
+
@win_build = Regexp.last_match(2).to_i
|
|
71
|
+
else
|
|
72
|
+
@win_build = 0
|
|
73
|
+
end
|
|
74
|
+
@win_build
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def self.windows_version_name
|
|
78
|
+
return nil unless windows?
|
|
79
|
+
b = windows_build
|
|
80
|
+
if b >= 22000 then "Win11"
|
|
81
|
+
elsif b >= 10240 then "Win10"
|
|
82
|
+
else "Win7"
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def self.ps_version
|
|
87
|
+
return @ps_ver if @ps_ver
|
|
88
|
+
if windows?
|
|
89
|
+
raw = cmd_utf8('powershell -Command "$PSVersionTable.PSVersion.Major" 2>NUL').strip
|
|
90
|
+
@ps_ver = raw.to_i
|
|
91
|
+
else
|
|
92
|
+
@ps_ver = 0
|
|
93
|
+
end
|
|
94
|
+
@ps_ver
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def self.to_s
|
|
98
|
+
if linux?
|
|
99
|
+
wsl_suffix = wsl? ? " (WSL)" : ""
|
|
100
|
+
"Linux/#{distro}#{wsl_suffix}"
|
|
101
|
+
elsif windows?
|
|
102
|
+
"Windows/#{windows_version_name}"
|
|
103
|
+
else
|
|
104
|
+
"Unknown"
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
private
|
|
109
|
+
|
|
110
|
+
# Windows 命令输出常为 GBK 编码,统一转成 UTF-8 字符串再处理
|
|
111
|
+
def self.cmd_utf8(cmd)
|
|
112
|
+
out = `#{cmd}`
|
|
113
|
+
if out.encoding == Encoding::UTF_8 && !out.valid_encoding?
|
|
114
|
+
out = out.force_encoding(Encoding::GBK)
|
|
115
|
+
end
|
|
116
|
+
out.encode(Encoding::UTF_8, invalid: :replace, undef: :replace)
|
|
117
|
+
rescue StandardError
|
|
118
|
+
out
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def self.detect_os
|
|
122
|
+
case RbConfig::CONFIG["host_os"]
|
|
123
|
+
when /linux/ then :linux
|
|
124
|
+
when /mswin|mingw|cygwin|windows/ then :windows
|
|
125
|
+
when /darwin/ then :darwin
|
|
126
|
+
else :unknown
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def self.detect_distro
|
|
131
|
+
return :unknown unless linux?
|
|
132
|
+
if File.exist?("/etc/alpine-release") then :alpine
|
|
133
|
+
elsif File.exist?("/etc/lsb-release") then :ubuntu
|
|
134
|
+
elsif File.exist?("/etc/debian_version") then :debian
|
|
135
|
+
elsif File.exist?("/etc/redhat-release") then :rhel
|
|
136
|
+
else :unknown_linux
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# ============================================================
|
|
142
|
+
# 工具定义:10个DNS查询工具的全平台信息
|
|
143
|
+
# ============================================================
|
|
144
|
+
TOOL_DEFS = [
|
|
145
|
+
{
|
|
146
|
+
key: :dig,
|
|
147
|
+
name: "dig",
|
|
148
|
+
desc: "DNS查询瑞士军刀,功能最全面",
|
|
149
|
+
features: %w[A AAAA CNAME MX NS PTR SOA TXT SRV ANY AXFR DNSSEC trace short],
|
|
150
|
+
linux: {
|
|
151
|
+
ubuntu: { avail: true, pkg: "dnsutils", install: "sudo apt install -y dnsutils" },
|
|
152
|
+
alpine: { avail: true, pkg: "bind-tools", install: "sudo apk add bind-tools" },
|
|
153
|
+
debian: { avail: true, pkg: "dnsutils", install: "sudo apt install -y dnsutils" },
|
|
154
|
+
rhel: { avail: true, pkg: "bind-utils", install: "sudo yum install -y bind-utils" },
|
|
155
|
+
},
|
|
156
|
+
windows: {
|
|
157
|
+
win7: { avail: false, note: "需手动安装ISC BIND" },
|
|
158
|
+
win10: { avail: false, note: "需手动安装ISC BIND" },
|
|
159
|
+
win11: { avail: false, note: "需手动安装ISC BIND" },
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
key: :nslookup,
|
|
164
|
+
name: "nslookup",
|
|
165
|
+
desc: "经典DNS查询工具,跨平台内置",
|
|
166
|
+
features: %w[A AAAA CNAME MX NS PTR SOA TXT SRV],
|
|
167
|
+
linux: {
|
|
168
|
+
ubuntu: { avail: true, pkg: "dnsutils", install: "sudo apt install -y dnsutils" },
|
|
169
|
+
alpine: { avail: true, pkg: "bind-tools", install: "sudo apk add bind-tools" },
|
|
170
|
+
debian: { avail: true, pkg: "dnsutils", install: "sudo apt install -y dnsutils" },
|
|
171
|
+
rhel: { avail: true, pkg: "bind-utils", install: "sudo yum install -y bind-utils" },
|
|
172
|
+
},
|
|
173
|
+
windows: {
|
|
174
|
+
win7: { avail: true, note: "系统内置" },
|
|
175
|
+
win10: { avail: true, note: "系统内置" },
|
|
176
|
+
win11: { avail: true, note: "系统内置" },
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
key: :host,
|
|
181
|
+
name: "host",
|
|
182
|
+
desc: "简洁的DNS查询工具,输出友好",
|
|
183
|
+
features: %w[A AAAA CNAME MX NS PTR SOA TXT SRV],
|
|
184
|
+
linux: {
|
|
185
|
+
ubuntu: { avail: true, pkg: "dnsutils", install: "sudo apt install -y dnsutils" },
|
|
186
|
+
alpine: { avail: true, pkg: "bind-tools", install: "sudo apk add bind-tools" },
|
|
187
|
+
debian: { avail: true, pkg: "dnsutils", install: "sudo apt install -y dnsutils" },
|
|
188
|
+
rhel: { avail: true, pkg: "bind-utils", install: "sudo yum install -y bind-utils" },
|
|
189
|
+
},
|
|
190
|
+
windows: {
|
|
191
|
+
win7: { avail: false, note: "Windows无此工具" },
|
|
192
|
+
win10: { avail: false, note: "Windows无此工具" },
|
|
193
|
+
win11: { avail: false, note: "Windows无此工具" },
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
key: :drill,
|
|
198
|
+
name: "drill",
|
|
199
|
+
desc: "ldns提供的DNS查询工具,支持DNSSEC",
|
|
200
|
+
features: %w[A AAAA CNAME MX NS PTR SOA TXT DNSSEC trace],
|
|
201
|
+
linux: {
|
|
202
|
+
ubuntu: { avail: true, pkg: "ldnsutils", install: "sudo apt install -y ldnsutils" },
|
|
203
|
+
alpine: { avail: true, pkg: "drill", install: "sudo apk add drill" },
|
|
204
|
+
debian: { avail: true, pkg: "ldnsutils", install: "sudo apt install -y ldnsutils" },
|
|
205
|
+
rhel: { avail: true, pkg: "ldns", install: "sudo yum install -y ldns" },
|
|
206
|
+
},
|
|
207
|
+
windows: {
|
|
208
|
+
win7: { avail: false, note: "Windows无此工具" },
|
|
209
|
+
win10: { avail: false, note: "Windows无此工具" },
|
|
210
|
+
win11: { avail: false, note: "Windows无此工具" },
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
key: :kdig,
|
|
215
|
+
name: "kdig",
|
|
216
|
+
desc: "Knot DNS查询工具,支持DNSSEC/DoH/DoT",
|
|
217
|
+
features: %w[A AAAA CNAME MX NS PTR SOA TXT DNSSEC DoH DoT trace],
|
|
218
|
+
linux: {
|
|
219
|
+
ubuntu: { avail: true, pkg: "knot-dnsutils", install: "sudo apt install -y knot-dnsutils" },
|
|
220
|
+
alpine: { avail: true, pkg: "knot", install: "sudo apk add knot" },
|
|
221
|
+
debian: { avail: true, pkg: "knot-dnsutils", install: "sudo apt install -y knot-dnsutils" },
|
|
222
|
+
rhel: { avail: true, pkg: "knot-utils", install: "sudo yum install -y knot-utils" },
|
|
223
|
+
},
|
|
224
|
+
windows: {
|
|
225
|
+
win7: { avail: false, note: "Windows无此工具" },
|
|
226
|
+
win10: { avail: false, note: "Windows无此工具" },
|
|
227
|
+
win11: { avail: false, note: "Windows无此工具" },
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
key: :dog,
|
|
232
|
+
name: "dog",
|
|
233
|
+
desc: "Rust编写的彩色DNS查询工具",
|
|
234
|
+
features: %w[A AAAA CNAME MX NS PTR SOA TXT SRV],
|
|
235
|
+
linux: {
|
|
236
|
+
ubuntu: { avail: true, pkg: "dog", install: "sudo apt install -y dog || cargo install dog" },
|
|
237
|
+
alpine: { avail: true, pkg: "dog", install: "sudo apk add dog (需community仓库)" },
|
|
238
|
+
debian: { avail: true, pkg: "dog", install: "sudo apt install -y dog || cargo install dog" },
|
|
239
|
+
rhel: { avail: true, pkg: "dog", install: "cargo install dog" },
|
|
240
|
+
},
|
|
241
|
+
windows: {
|
|
242
|
+
win7: { avail: false, note: "需cargo install dog" },
|
|
243
|
+
win10: { avail: false, note: "需cargo install dog" },
|
|
244
|
+
win11: { avail: false, note: "需cargo install dog" },
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
key: :resolve_dnspath,
|
|
249
|
+
name: "Resolve-DnsName",
|
|
250
|
+
desc: "PowerShell内置DNS查询Cmdlet",
|
|
251
|
+
features: %w[A AAAA CNAME MX NS PTR SOA TXT SRV],
|
|
252
|
+
linux: {
|
|
253
|
+
ubuntu: { avail: false, pkg: "-", install: "需安装PowerShell Core" },
|
|
254
|
+
alpine: { avail: false, pkg: "-", install: "需安装PowerShell Core" },
|
|
255
|
+
debian: { avail: false, pkg: "-", install: "需安装PowerShell Core" },
|
|
256
|
+
rhel: { avail: false, pkg: "-", install: "需安装PowerShell Core" },
|
|
257
|
+
},
|
|
258
|
+
windows: {
|
|
259
|
+
win7: { avail: false, note: "需PS4.0+,Win7自带PS2.0不支持" },
|
|
260
|
+
win10: { avail: true, note: "PS5.1内置" },
|
|
261
|
+
win11: { avail: true, note: "PS5.1内置" },
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
key: :ping_a,
|
|
266
|
+
name: "ping -a",
|
|
267
|
+
desc: "通过ping反查主机名(仅PTR,功能有限)",
|
|
268
|
+
features: %w[PTR],
|
|
269
|
+
linux: {
|
|
270
|
+
ubuntu: { avail: false, pkg: "-", install: "Linux ping无-a参数" },
|
|
271
|
+
alpine: { avail: false, pkg: "-", install: "Linux ping无-a参数" },
|
|
272
|
+
debian: { avail: false, pkg: "-", install: "Linux ping无-a参数" },
|
|
273
|
+
rhel: { avail: false, pkg: "-", install: "Linux ping无-a参数" },
|
|
274
|
+
},
|
|
275
|
+
windows: {
|
|
276
|
+
win7: { avail: true, note: "内置" },
|
|
277
|
+
win10: { avail: true, note: "内置" },
|
|
278
|
+
win11: { avail: true, note: "内置" },
|
|
279
|
+
},
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
key: :getent,
|
|
283
|
+
name: "getent hosts",
|
|
284
|
+
desc: "通过nsswitch查询主机名解析",
|
|
285
|
+
features: %w[A AAAA],
|
|
286
|
+
linux: {
|
|
287
|
+
ubuntu: { avail: true, pkg: "libc-bin", install: "sudo apt install -y libc-bin" },
|
|
288
|
+
alpine: { avail: true, pkg: "musl", install: "默认内置" },
|
|
289
|
+
debian: { avail: true, pkg: "libc-bin", install: "sudo apt install -y libc-bin" },
|
|
290
|
+
rhel: { avail: true, pkg: "glibc-common", install: "默认内置" },
|
|
291
|
+
},
|
|
292
|
+
windows: {
|
|
293
|
+
win7: { avail: false, note: "Windows无此工具" },
|
|
294
|
+
win10: { avail: false, note: "Windows无此工具" },
|
|
295
|
+
win11: { avail: false, note: "Windows无此工具" },
|
|
296
|
+
},
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
key: :resolvectl,
|
|
300
|
+
name: "resolvectl query",
|
|
301
|
+
desc: "systemd-resolved DNS查询接口",
|
|
302
|
+
features: %w[A AAAA CNAME MX NS PTR SOA TXT SRV],
|
|
303
|
+
linux: {
|
|
304
|
+
ubuntu: { avail: true, pkg: "systemd-resolved", install: "默认内置(16.04+)" },
|
|
305
|
+
alpine: { avail: false, pkg: "-", install: "Alpine用OpenRC,无systemd" },
|
|
306
|
+
debian: { avail: true, pkg: "systemd-resolved", install: "默认内置" },
|
|
307
|
+
rhel: { avail: true, pkg: "systemd-resolved", install: "默认内置(7+)" },
|
|
308
|
+
},
|
|
309
|
+
windows: {
|
|
310
|
+
win7: { avail: false, note: "Windows无此工具" },
|
|
311
|
+
win10: { avail: false, note: "Windows无此工具" },
|
|
312
|
+
win11: { avail: false, note: "Windows无此工具" },
|
|
313
|
+
},
|
|
314
|
+
},
|
|
315
|
+
].freeze
|
|
316
|
+
|
|
317
|
+
# ============================================================
|
|
318
|
+
# 工具运行时检测
|
|
319
|
+
# ============================================================
|
|
320
|
+
module ToolDetector
|
|
321
|
+
# 检测当前平台上哪些工具实际可用,返回 [{key:, name:, priority:}, ...]
|
|
322
|
+
def self.detect
|
|
323
|
+
tools = []
|
|
324
|
+
|
|
325
|
+
if Platform.linux?
|
|
326
|
+
add_if_exists(tools, :dig, "dig", 0)
|
|
327
|
+
add_if_exists(tools, :host, "host", 1)
|
|
328
|
+
add_if_exists(tools, :nslookup, "nslookup", 2)
|
|
329
|
+
add_if_exists(tools, :drill, "drill", 3)
|
|
330
|
+
add_if_exists(tools, :kdig, "kdig", 4)
|
|
331
|
+
add_if_exists(tools, :dog, "dog", 5)
|
|
332
|
+
add_if_exists(tools, :getent, "getent", 6)
|
|
333
|
+
add_if_exists(tools, :resolvectl, "resolvectl", 7)
|
|
334
|
+
elsif Platform.windows?
|
|
335
|
+
add_if_exists(tools, :dig, "dig", -1)
|
|
336
|
+
add_if_exists(tools, :nslookup, "nslookup", 0)
|
|
337
|
+
if Platform.ps_version >= 4
|
|
338
|
+
tools << { key: :resolve_dnspath, name: "Resolve-DnsName", priority: 1 }
|
|
339
|
+
end
|
|
340
|
+
tools << { key: :ping_a, name: "ping -a", priority: 10 }
|
|
341
|
+
add_if_exists(tools, :drill, "drill", 5)
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
tools.sort_by { |t| t[:priority] }
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
private
|
|
348
|
+
|
|
349
|
+
# 命令存在性检测,按平台用各自的命令与重定向符号,
|
|
350
|
+
# 避免 which 在 Windows / where 在 Linux 上互相踩坑
|
|
351
|
+
def self.add_if_exists(tools, key, cmd, priority)
|
|
352
|
+
exists = Platform.windows? ? windows_cmd?(cmd) : linux_cmd?(cmd)
|
|
353
|
+
if exists
|
|
354
|
+
tools << { key: key, name: cmd == "getent" ? "getent hosts" : cmd, priority: priority }
|
|
355
|
+
end
|
|
356
|
+
tools
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
def self.linux_cmd?(cmd)
|
|
360
|
+
system("command -v #{cmd} >/dev/null 2>&1")
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
def self.windows_cmd?(cmd)
|
|
364
|
+
system("where #{cmd} >NUL 2>&1")
|
|
365
|
+
end
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
# ============================================================
|
|
369
|
+
# 工具执行器:所有工具的调用逻辑统一在此
|
|
370
|
+
# ============================================================
|
|
371
|
+
module Executor
|
|
372
|
+
# 用指定工具执行查询,返回 [stdout, stderr, exit_code]
|
|
373
|
+
def self.run(tool_key, target, qtype, server = nil)
|
|
374
|
+
# PTR 反查统一转成反向域名 (in-addr.arpa / ip6.arpa),
|
|
375
|
+
# 保证 dig/drill/kdig 等所有工具行为一致
|
|
376
|
+
qtarget = qtype == "PTR" ? reverse_domain(target) : target
|
|
377
|
+
|
|
378
|
+
case tool_key
|
|
379
|
+
when :dig then run_dig(qtarget, qtype, server)
|
|
380
|
+
when :nslookup then run_nslookup(qtarget, qtype, server)
|
|
381
|
+
when :host then run_host(qtarget, qtype, server)
|
|
382
|
+
when :drill then run_drill(qtarget, qtype, server)
|
|
383
|
+
when :kdig then run_kdig(qtarget, qtype, server)
|
|
384
|
+
when :dog then run_dog(qtarget, qtype, server)
|
|
385
|
+
when :getent then run_getent(target, qtype)
|
|
386
|
+
when :resolvectl then run_resolvectl(qtarget)
|
|
387
|
+
when :resolve_dnspath then run_resolve_dnspath(qtarget, qtype, server)
|
|
388
|
+
when :ping_a then run_ping_a(target)
|
|
389
|
+
else ["[错误] 未知工具: #{tool_key}", "", 1]
|
|
390
|
+
end
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
# --- dig ---
|
|
394
|
+
def self.run_dig(target, qtype, server)
|
|
395
|
+
args = []
|
|
396
|
+
args << "@#{shell_quote(server)}" if server
|
|
397
|
+
args << shell_quote(target)
|
|
398
|
+
args << shell_quote(qtype)
|
|
399
|
+
args << "+short"
|
|
400
|
+
exec_cmd("dig #{args.join(' ')}")
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
# --- nslookup ---
|
|
404
|
+
def self.run_nslookup(target, qtype, server)
|
|
405
|
+
cmd = "nslookup -type=#{qtype} #{shell_quote(target)}"
|
|
406
|
+
cmd << " #{shell_quote(server)}" if server
|
|
407
|
+
exec_cmd(cmd)
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
# --- host ---
|
|
411
|
+
def self.run_host(target, qtype, server)
|
|
412
|
+
cmd = "host -t #{shell_quote(qtype)} #{shell_quote(target)}"
|
|
413
|
+
cmd << " #{shell_quote(server)}" if server
|
|
414
|
+
exec_cmd(cmd)
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
# --- drill ---
|
|
418
|
+
def self.run_drill(target, qtype, server)
|
|
419
|
+
cmd = "drill #{shell_quote(target)} #{shell_quote(qtype)}"
|
|
420
|
+
cmd << " @#{shell_quote(server)}" if server
|
|
421
|
+
exec_cmd(cmd)
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
# --- kdig ---
|
|
425
|
+
def self.run_kdig(target, qtype, server)
|
|
426
|
+
cmd = "kdig #{shell_quote(target)} #{shell_quote(qtype)}"
|
|
427
|
+
cmd << " @#{shell_quote(server)}" if server
|
|
428
|
+
exec_cmd(cmd)
|
|
429
|
+
end
|
|
430
|
+
|
|
431
|
+
# --- dog ---
|
|
432
|
+
def self.run_dog(target, qtype, server)
|
|
433
|
+
cmd = "dog #{shell_quote(qtype)} #{shell_quote(target)}"
|
|
434
|
+
cmd << " @#{shell_quote(server)}" if server
|
|
435
|
+
exec_cmd(cmd)
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
# --- getent hosts ---
|
|
439
|
+
def self.run_getent(target, _qtype)
|
|
440
|
+
exec_cmd("getent hosts #{shell_quote(target)}")
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
# --- resolvectl query ---
|
|
444
|
+
def self.run_resolvectl(target)
|
|
445
|
+
exec_cmd("resolvectl query #{shell_quote(target)}")
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
# --- Resolve-DnsName (Windows PowerShell) ---
|
|
449
|
+
def self.run_resolve_dnspath(target, qtype, server)
|
|
450
|
+
# 单引号在 PowerShell 字符串中需双写转义
|
|
451
|
+
t = target.to_s.gsub("'", "''")
|
|
452
|
+
s = server.to_s.gsub("'", "''")
|
|
453
|
+
ps_cmd = "Resolve-DnsName '#{t}' -DnsOnly"
|
|
454
|
+
ps_cmd << " -Type #{qtype}" unless qtype == "A"
|
|
455
|
+
ps_cmd << " -Server #{s}" unless s.empty?
|
|
456
|
+
exec_cmd("powershell -Command \"#{ps_cmd}\"")
|
|
457
|
+
end
|
|
458
|
+
|
|
459
|
+
# --- ping -a ---
|
|
460
|
+
def self.run_ping_a(target)
|
|
461
|
+
if Platform.windows?
|
|
462
|
+
exec_cmd("ping -a -n 1 #{shell_quote(target)}")
|
|
463
|
+
else
|
|
464
|
+
["[跳过] Linux ping不支持-a参数", "", 0]
|
|
465
|
+
end
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
private
|
|
469
|
+
|
|
470
|
+
def self.exec_cmd(cmd)
|
|
471
|
+
stdout = `#{cmd} 2>&1`
|
|
472
|
+
exit_code = $?.exitstatus
|
|
473
|
+
[normalize_output(stdout), "", exit_code]
|
|
474
|
+
rescue StandardError => e
|
|
475
|
+
["", e.message, 1]
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
# Windows 命令输出常为 GBK,统一转成 UTF-8,避免乱码
|
|
479
|
+
def self.normalize_output(str)
|
|
480
|
+
return str if str.encoding == Encoding::UTF_8 && str.valid_encoding?
|
|
481
|
+
if str.encoding == Encoding::UTF_8 && !str.valid_encoding?
|
|
482
|
+
str = str.force_encoding(Encoding::GBK)
|
|
483
|
+
end
|
|
484
|
+
if str.valid_encoding?
|
|
485
|
+
str.encode(Encoding::UTF_8, invalid: :replace, undef: :replace)
|
|
486
|
+
else
|
|
487
|
+
str.force_encoding(Encoding::UTF_8).scrub
|
|
488
|
+
end
|
|
489
|
+
rescue StandardError
|
|
490
|
+
str
|
|
491
|
+
end
|
|
492
|
+
|
|
493
|
+
def self.reverse_domain(host)
|
|
494
|
+
IPAddr.new(host).reverse
|
|
495
|
+
rescue IPAddr::InvalidAddressError
|
|
496
|
+
host
|
|
497
|
+
end
|
|
498
|
+
|
|
499
|
+
def self.shell_quote(str)
|
|
500
|
+
Shellwords.shellescape(str.to_s)
|
|
501
|
+
end
|
|
502
|
+
end
|
|
503
|
+
|
|
504
|
+
# ============================================================
|
|
505
|
+
# 查询入口:自动选择最佳工具或用全部工具
|
|
506
|
+
# ============================================================
|
|
507
|
+
class Query
|
|
508
|
+
def initialize(target, qtype: "A", server: nil)
|
|
509
|
+
@target = target
|
|
510
|
+
@qtype = qtype
|
|
511
|
+
@server = server
|
|
512
|
+
@is_ip = ip_like?(target)
|
|
513
|
+
|
|
514
|
+
# IP 反查自动切换为 PTR
|
|
515
|
+
@qtype = "PTR" if @is_ip && @qtype == "A"
|
|
516
|
+
end
|
|
517
|
+
|
|
518
|
+
# 自动选择最佳工具查询,返回 [stdout, exit_code]
|
|
519
|
+
def execute
|
|
520
|
+
tools = ToolDetector.detect
|
|
521
|
+
if tools.empty?
|
|
522
|
+
return ["[错误] 当前平台未找到任何可用的DNS查询工具", 1]
|
|
523
|
+
end
|
|
524
|
+
|
|
525
|
+
best = tools.first
|
|
526
|
+
puts DnsQuery.color("=== 使用 #{best[:name]} 查询 #{@target} (#{@qtype}) ===", :cyan)
|
|
527
|
+
puts ""
|
|
528
|
+
stdout, _stderr, code = Executor.run(best[:key], @target, @qtype, @server)
|
|
529
|
+
puts stdout
|
|
530
|
+
[stdout, code]
|
|
531
|
+
end
|
|
532
|
+
|
|
533
|
+
# 用所有可用工具查询,返回 [汇总文本, exit_code]
|
|
534
|
+
def execute_all
|
|
535
|
+
tools = ToolDetector.detect
|
|
536
|
+
if tools.empty?
|
|
537
|
+
return ["[错误] 未找到任何可用的DNS查询工具", 1]
|
|
538
|
+
end
|
|
539
|
+
|
|
540
|
+
puts DnsQuery.color("=== 使用所有可用工具查询 #{@target} (#{@qtype}) ===", :cyan)
|
|
541
|
+
puts ""
|
|
542
|
+
ok = 0
|
|
543
|
+
tools.each do |t|
|
|
544
|
+
puts DnsQuery.color("--- #{t[:name]} ---", :yellow)
|
|
545
|
+
stdout, _stderr, code = Executor.run(t[:key], @target, @qtype, @server)
|
|
546
|
+
if code != 0 && stdout.strip.empty?
|
|
547
|
+
puts " [查询失败或不可用]"
|
|
548
|
+
else
|
|
549
|
+
puts stdout
|
|
550
|
+
ok += 1
|
|
551
|
+
end
|
|
552
|
+
puts ""
|
|
553
|
+
end
|
|
554
|
+
[ok.positive? ? "done" : "", ok.zero? ? 1 : 0]
|
|
555
|
+
end
|
|
556
|
+
|
|
557
|
+
private
|
|
558
|
+
|
|
559
|
+
def ip_like?(s)
|
|
560
|
+
IPAddr.new(s).ipv4? || IPAddr.new(s).ipv6?
|
|
561
|
+
rescue IPAddr::InvalidAddressError
|
|
562
|
+
false
|
|
563
|
+
end
|
|
564
|
+
end
|
|
565
|
+
|
|
566
|
+
# ============================================================
|
|
567
|
+
# 工具列表展示
|
|
568
|
+
# ============================================================
|
|
569
|
+
module ToolList
|
|
570
|
+
def self.print
|
|
571
|
+
puts DnsQuery.color("============================================================", :cyan)
|
|
572
|
+
puts DnsQuery.color(" 当前平台可用DNS查询工具", :cyan)
|
|
573
|
+
puts DnsQuery.color(" OS: #{Platform}", :cyan)
|
|
574
|
+
puts DnsQuery.color("============================================================", :cyan)
|
|
575
|
+
puts ""
|
|
576
|
+
|
|
577
|
+
tools = ToolDetector.detect
|
|
578
|
+
if tools.empty?
|
|
579
|
+
puts " [!] 未检测到可用工具"
|
|
580
|
+
else
|
|
581
|
+
tools.each_with_index do |t, i|
|
|
582
|
+
puts " #{i + 1}. #{t[:name]} (优先级: #{t[:priority]})"
|
|
583
|
+
end
|
|
584
|
+
end
|
|
585
|
+
puts ""
|
|
586
|
+
puts " 默认将使用优先级最高的工具执行查询"
|
|
587
|
+
end
|
|
588
|
+
end
|
|
589
|
+
|
|
590
|
+
# ============================================================
|
|
591
|
+
# 全平台矩阵展示
|
|
592
|
+
# ============================================================
|
|
593
|
+
module Matrix
|
|
594
|
+
def self.print
|
|
595
|
+
puts "=" * 130
|
|
596
|
+
puts " 域名查询工具全平台矩阵"
|
|
597
|
+
puts "=" * 130
|
|
598
|
+
puts ""
|
|
599
|
+
|
|
600
|
+
distros = %i[ubuntu alpine debian rhel]
|
|
601
|
+
win_vers = %i[win7 win10 win11]
|
|
602
|
+
|
|
603
|
+
TOOL_DEFS.each do |tool|
|
|
604
|
+
puts "--- #{tool[:name]} ---"
|
|
605
|
+
puts " 说明: #{tool[:desc]}"
|
|
606
|
+
puts " 支持记录: #{tool[:features].join(', ')}"
|
|
607
|
+
puts ""
|
|
608
|
+
|
|
609
|
+
# Linux
|
|
610
|
+
printf " %-12s", "Linux发行版"
|
|
611
|
+
distros.each { |d| printf " %-16s", d }
|
|
612
|
+
puts ""
|
|
613
|
+
|
|
614
|
+
printf " %-12s", "可用性"
|
|
615
|
+
distros.each do |d|
|
|
616
|
+
info = tool[:linux][d] || {}
|
|
617
|
+
printf " %-16s", info[:avail] ? "Y" : "N"
|
|
618
|
+
end
|
|
619
|
+
puts ""
|
|
620
|
+
|
|
621
|
+
printf " %-12s", "安装包"
|
|
622
|
+
distros.each do |d|
|
|
623
|
+
info = tool[:linux][d] || {}
|
|
624
|
+
printf " %-16s", info[:pkg] || "-"
|
|
625
|
+
end
|
|
626
|
+
puts ""
|
|
627
|
+
|
|
628
|
+
printf " %-12s", "安装命令"
|
|
629
|
+
distros.each do |d|
|
|
630
|
+
info = tool[:linux][d] || {}
|
|
631
|
+
ic = info[:install] || "-"
|
|
632
|
+
printf " %-16s", ic.length > 16 ? ic[0..13] + "..." : ic
|
|
633
|
+
end
|
|
634
|
+
puts ""
|
|
635
|
+
puts ""
|
|
636
|
+
|
|
637
|
+
# Windows
|
|
638
|
+
printf " %-12s", "Windows"
|
|
639
|
+
win_vers.each { |w| printf " %-24s", w }
|
|
640
|
+
puts ""
|
|
641
|
+
|
|
642
|
+
printf " %-12s", "可用性"
|
|
643
|
+
win_vers.each do |w|
|
|
644
|
+
info = tool[:windows][w] || {}
|
|
645
|
+
printf " %-24s", info[:avail] ? "Y" : "N"
|
|
646
|
+
end
|
|
647
|
+
puts ""
|
|
648
|
+
|
|
649
|
+
printf " %-12s", "说明"
|
|
650
|
+
win_vers.each do |w|
|
|
651
|
+
info = tool[:windows][w] || {}
|
|
652
|
+
note = info[:note] || "-"
|
|
653
|
+
printf " %-24s", note.length > 24 ? note[0..21] + "..." : note
|
|
654
|
+
end
|
|
655
|
+
puts ""
|
|
656
|
+
puts ""
|
|
657
|
+
end
|
|
658
|
+
|
|
659
|
+
puts " Y = 可用 N = 不可用/需额外安装"
|
|
660
|
+
puts " * Alpine的dog需community仓库"
|
|
661
|
+
end
|
|
662
|
+
end
|
|
663
|
+
|
|
664
|
+
# ============================================================
|
|
665
|
+
# 颜色输出(终端自动检测,可用 --no-color 强制关闭)
|
|
666
|
+
# ============================================================
|
|
667
|
+
module Color
|
|
668
|
+
CODES = {
|
|
669
|
+
black: 30, red: 31, green: 32, yellow: 33,
|
|
670
|
+
blue: 34, magenta: 35, cyan: 36, white: 37,
|
|
671
|
+
}.freeze
|
|
672
|
+
|
|
673
|
+
def self.disabled=(v)
|
|
674
|
+
@disabled = v
|
|
675
|
+
@supported = nil if v
|
|
676
|
+
end
|
|
677
|
+
|
|
678
|
+
def self.supported?
|
|
679
|
+
return false if @disabled
|
|
680
|
+
return @supported if defined?(@supported)
|
|
681
|
+
@supported = $stdout.tty? || ENV["TERM"] || ENV["ANSICON"] ||
|
|
682
|
+
(Platform.windows? && ENV["WT_SESSION"])
|
|
683
|
+
end
|
|
684
|
+
|
|
685
|
+
def self.apply(text, color)
|
|
686
|
+
return text unless supported?
|
|
687
|
+
code = CODES[color]
|
|
688
|
+
return text unless code
|
|
689
|
+
"\e[0;#{code}m#{text}\e[0m"
|
|
690
|
+
end
|
|
691
|
+
end
|
|
692
|
+
|
|
693
|
+
def self.color(text, c)
|
|
694
|
+
Color.apply(text, c)
|
|
695
|
+
end
|
|
696
|
+
|
|
697
|
+
# ============================================================
|
|
698
|
+
# CLI 入口:参数解析 + 分发,返回进程退出码
|
|
699
|
+
# ============================================================
|
|
700
|
+
module CLI
|
|
701
|
+
SUPPORTED_TYPES = %w[A AAAA CNAME MX NS PTR SOA TXT SRV ANY].freeze
|
|
702
|
+
|
|
703
|
+
def self.run(argv)
|
|
704
|
+
options = {
|
|
705
|
+
type: "A", server: nil, all: false,
|
|
706
|
+
list_tools: false, matrix: false,
|
|
707
|
+
help: false, version: false, no_color: false,
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
parser = build_parser(options)
|
|
711
|
+
begin
|
|
712
|
+
parser.parse!(argv)
|
|
713
|
+
rescue OptionParser::ParseError => e
|
|
714
|
+
warn "错误: #{e.message}"
|
|
715
|
+
warn "用法: ruby bin/dns-query <目标> [选项] (用 -h 查看完整帮助)"
|
|
716
|
+
return 1
|
|
717
|
+
end
|
|
718
|
+
|
|
719
|
+
Color.disabled = true if options[:no_color]
|
|
720
|
+
|
|
721
|
+
return print_help(parser) if options[:help]
|
|
722
|
+
return print_version if options[:version]
|
|
723
|
+
return print_matrix if options[:matrix]
|
|
724
|
+
return print_tools if options[:list_tools]
|
|
725
|
+
|
|
726
|
+
target = argv.shift
|
|
727
|
+
if target.nil?
|
|
728
|
+
warn "错误: 缺少查询目标(域名或 IP 地址)"
|
|
729
|
+
warn "用法: ruby bin/dns-query <目标> [选项] (用 -h 查看完整帮助)"
|
|
730
|
+
return 1
|
|
731
|
+
end
|
|
732
|
+
|
|
733
|
+
unless argv.empty?
|
|
734
|
+
warn "注意: 忽略多余参数: #{argv.join(', ')}"
|
|
735
|
+
end
|
|
736
|
+
|
|
737
|
+
unless SUPPORTED_TYPES.include?(options[:type])
|
|
738
|
+
warn "错误: 不支持的记录类型 #{options[:type]}"
|
|
739
|
+
warn "支持的类型: #{SUPPORTED_TYPES.join(' / ')}"
|
|
740
|
+
return 1
|
|
741
|
+
end
|
|
742
|
+
|
|
743
|
+
unless valid_server?(options[:server])
|
|
744
|
+
warn "错误: DNS 服务器格式非法: #{options[:server]}"
|
|
745
|
+
warn "仅支持 IP 地址或域名"
|
|
746
|
+
return 1
|
|
747
|
+
end
|
|
748
|
+
|
|
749
|
+
query = DnsQuery::Query.new(target, qtype: options[:type], server: options[:server])
|
|
750
|
+
if options[:all]
|
|
751
|
+
_out, code = query.execute_all
|
|
752
|
+
else
|
|
753
|
+
_out, code = query.execute
|
|
754
|
+
end
|
|
755
|
+
code
|
|
756
|
+
end
|
|
757
|
+
|
|
758
|
+
def self.build_parser(options)
|
|
759
|
+
OptionParser.new do |opts|
|
|
760
|
+
opts.banner = <<~BAN
|
|
761
|
+
dns-query — 跨平台统一域名查询工具 (network-infra-utility)
|
|
762
|
+
|
|
763
|
+
自动检测当前系统可用的 DNS 查询工具 (dig / nslookup / host /
|
|
764
|
+
Resolve-DnsName 等),默认使用优先级最高的工具执行查询;
|
|
765
|
+
目标为 IP 地址时自动反查 PTR 记录。
|
|
766
|
+
|
|
767
|
+
用法:
|
|
768
|
+
ruby bin/dns-query <目标> [选项]
|
|
769
|
+
ruby bin/dns-query www.baidu.com # 正查 A 记录
|
|
770
|
+
ruby bin/dns-query 8.8.8.8 # 反查 PTR (自动识别 IP)
|
|
771
|
+
ruby bin/dns-query www.baidu.com -t MX # 指定记录类型
|
|
772
|
+
ruby bin/dns-query www.baidu.com -s 8.8.8.8 # 指定 DNS 服务器
|
|
773
|
+
ruby bin/dns-query www.baidu.com -a # 用所有可用工具查询
|
|
774
|
+
ruby bin/dns-query --tools # 列出当前平台可用工具
|
|
775
|
+
ruby bin/dns-query --matrix # 全平台工具矩阵
|
|
776
|
+
|
|
777
|
+
选项:
|
|
778
|
+
BAN
|
|
779
|
+
opts.on("-t", "--type TYPE", "DNS 记录类型 (A/AAAA/CNAME/MX/NS/PTR/SOA/TXT/SRV/ANY),默认 A") do |v|
|
|
780
|
+
options[:type] = v.upcase
|
|
781
|
+
end
|
|
782
|
+
opts.on("-s", "--server SERVER", "指定 DNS 服务器 (IP 或域名)") do |v|
|
|
783
|
+
options[:server] = v
|
|
784
|
+
end
|
|
785
|
+
opts.on("-a", "--all", "使用所有可用工具交叉查询") do
|
|
786
|
+
options[:all] = true
|
|
787
|
+
end
|
|
788
|
+
opts.on("--tools", "列出当前平台可用工具") do
|
|
789
|
+
options[:list_tools] = true
|
|
790
|
+
end
|
|
791
|
+
opts.on("--matrix", "显示全平台工具矩阵") do
|
|
792
|
+
options[:matrix] = true
|
|
793
|
+
end
|
|
794
|
+
opts.on("--no-color", "禁用 ANSI 颜色输出") do
|
|
795
|
+
options[:no_color] = true
|
|
796
|
+
end
|
|
797
|
+
opts.on("-v", "--version", "显示版本") do
|
|
798
|
+
options[:version] = true
|
|
799
|
+
end
|
|
800
|
+
opts.on("-h", "--help", "显示帮助") do
|
|
801
|
+
options[:help] = true
|
|
802
|
+
end
|
|
803
|
+
end
|
|
804
|
+
end
|
|
805
|
+
|
|
806
|
+
def self.print_help(parser)
|
|
807
|
+
puts parser
|
|
808
|
+
0
|
|
809
|
+
end
|
|
810
|
+
|
|
811
|
+
def self.print_version
|
|
812
|
+
puts "dns-query #{NetworkInfraUtility::VERSION}"
|
|
813
|
+
0
|
|
814
|
+
end
|
|
815
|
+
|
|
816
|
+
def self.print_tools
|
|
817
|
+
DnsQuery::ToolList.print
|
|
818
|
+
0
|
|
819
|
+
end
|
|
820
|
+
|
|
821
|
+
def self.print_matrix
|
|
822
|
+
DnsQuery::Matrix.print
|
|
823
|
+
0
|
|
824
|
+
end
|
|
825
|
+
|
|
826
|
+
def self.valid_server?(server)
|
|
827
|
+
return true if server.nil? || server.empty?
|
|
828
|
+
server.match?(/\A[0-9A-Za-z.\-:\[\]]+\z/)
|
|
829
|
+
end
|
|
830
|
+
end
|
|
831
|
+
end
|
|
832
|
+
|
|
833
|
+
# 仅作为命令行执行时运行 CLI;被 require 时只提供模块能力
|
|
834
|
+
exit DnsQuery::CLI.run(ARGV) if __FILE__ == $PROGRAM_NAME
|