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.
Files changed (119) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +21 -0
  3. data/CHANGELOG.md +31 -4
  4. data/Gemfile +2 -0
  5. data/Gemfile.lock +70 -0
  6. data/Rakefile +1 -1
  7. data/bin/dns-query +834 -0
  8. data/bin/geo-doc +135 -0
  9. data/bin/geo-get +1 -1
  10. data/document/ASNum/346/250/241/345/235/227/345/212/237/350/203/275/350/257/264/346/230/216.md +242 -0
  11. data/document/DNSQuery/345/267/245/345/205/267/344/275/277/347/224/250/346/226/271/346/263/225.md +248 -0
  12. 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
  13. data/document/IP/346/250/241/345/235/227/345/212/237/350/203/275/350/257/264/346/230/216.md +297 -0
  14. data/document/MAC/346/250/241/345/235/227/345/212/237/350/203/275/350/257/264/346/230/216.md +296 -0
  15. 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
  16. data/network-infra-utility.gemspec +4 -2
  17. data/network.rb +3 -1
  18. data/service/geodb/GeoAPI.md +1 -0
  19. data/service/geodb/geodb.rb +278 -1
  20. data/service/ssh/README.md +942 -0
  21. data/service/ssh/bin/ssh-client +198 -0
  22. 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
  23. 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
  24. 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
  25. data/service/ssh/ext/ssh_core/bin/ssh_core.cmd +28 -0
  26. data/service/ssh/ext/ssh_core/config/sys.config +0 -0
  27. data/service/ssh/ext/ssh_core/config/vm.args +0 -0
  28. data/service/ssh/ext/ssh_core/local_deps/jsx/CHECKSUM +1 -0
  29. data/service/ssh/ext/ssh_core/local_deps/jsx/LICENSE +21 -0
  30. data/service/ssh/ext/ssh_core/local_deps/jsx/README.md +696 -0
  31. data/service/ssh/ext/ssh_core/local_deps/jsx/VERSION +1 -0
  32. data/service/ssh/ext/ssh_core/local_deps/jsx/contents.tar.gz +0 -0
  33. data/service/ssh/ext/ssh_core/local_deps/jsx/metadata.config +15 -0
  34. data/service/ssh/ext/ssh_core/local_deps/jsx/rebar.config +17 -0
  35. data/service/ssh/ext/ssh_core/local_deps/jsx/rebar.lock +1 -0
  36. data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx.app.src +10 -0
  37. data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx.erl +506 -0
  38. data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_config.erl +393 -0
  39. data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_config.hrl +18 -0
  40. data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_consult.erl +81 -0
  41. data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_decoder.erl +1909 -0
  42. data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_encoder.erl +116 -0
  43. data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_parser.erl +1214 -0
  44. data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_to_json.erl +408 -0
  45. data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_to_term.erl +389 -0
  46. data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_verify.erl +121 -0
  47. data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx.erl +506 -0
  48. data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_config.erl +393 -0
  49. data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_config.hrl +18 -0
  50. data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_consult.erl +81 -0
  51. data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_decoder.erl +1909 -0
  52. data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_encoder.erl +116 -0
  53. data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_parser.erl +1214 -0
  54. data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_to_json.erl +408 -0
  55. data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_to_term.erl +389 -0
  56. data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_verify.erl +121 -0
  57. data/service/ssh/ext/ssh_core/rebar.config +24 -0
  58. data/service/ssh/ext/ssh_core/rebar.lock +1 -0
  59. data/service/ssh/ext/ssh_core/src/ssh_auth_engine.erl +156 -0
  60. data/service/ssh/ext/ssh_core/src/ssh_channel_stm.erl +232 -0
  61. data/service/ssh/ext/ssh_core/src/ssh_codec.erl +83 -0
  62. data/service/ssh/ext/ssh_core/src/ssh_conn_sup.erl +48 -0
  63. data/service/ssh/ext/ssh_core/src/ssh_conn_worker.erl +535 -0
  64. data/service/ssh/ext/ssh_core/src/ssh_core.app.src +36 -0
  65. data/service/ssh/ext/ssh_core/src/ssh_core_app.erl +11 -0
  66. data/service/ssh/ext/ssh_core/src/ssh_core_sup.erl +46 -0
  67. data/service/ssh/ext/ssh_core/src/ssh_infra_sup.erl +104 -0
  68. data/service/ssh/ext/ssh_core/src/ssh_ipc.hrl +80 -0
  69. data/service/ssh/ext/ssh_core/src/ssh_ipc_coalesce.erl +94 -0
  70. data/service/ssh/ext/ssh_core/src/ssh_ipc_gateway.erl +467 -0
  71. data/service/ssh/ext/ssh_core/src/ssh_ipc_proto.erl +95 -0
  72. data/service/ssh/ext/ssh_core/src/ssh_jump_chain.erl +101 -0
  73. data/service/ssh/ext/ssh_core/src/ssh_keepalive_mgr.erl +222 -0
  74. data/service/ssh/ext/ssh_core/src/ssh_known_hosts_proxy.erl +67 -0
  75. data/service/ssh/ext/ssh_core/src/ssh_port_fwd.erl +225 -0
  76. data/service/ssh/ext/ssh_core/src/ssh_sftp_session.erl +250 -0
  77. data/service/ssh/ext/ssh_core/src/ssh_sftp_sup.erl +62 -0
  78. data/service/ssh/ext/ssh_core_rs/Cargo.lock +2345 -0
  79. data/service/ssh/ext/ssh_core_rs/Cargo.toml +30 -0
  80. data/service/ssh/ext/ssh_core_rs/bin/ssh_core_rs +34 -0
  81. data/service/ssh/ext/ssh_core_rs/bin/ssh_core_rs.cmd +40 -0
  82. data/service/ssh/ext/ssh_core_rs/src/channel.rs +296 -0
  83. data/service/ssh/ext/ssh_core_rs/src/coalesce.rs +143 -0
  84. data/service/ssh/ext/ssh_core_rs/src/codec.rs +71 -0
  85. data/service/ssh/ext/ssh_core_rs/src/conn.rs +628 -0
  86. data/service/ssh/ext/ssh_core_rs/src/gateway.rs +389 -0
  87. data/service/ssh/ext/ssh_core_rs/src/handler.rs +293 -0
  88. data/service/ssh/ext/ssh_core_rs/src/keepalive.rs +194 -0
  89. data/service/ssh/ext/ssh_core_rs/src/main.rs +351 -0
  90. data/service/ssh/ext/ssh_core_rs/src/portfwd.rs +378 -0
  91. data/service/ssh/ext/ssh_core_rs/src/proto.rs +198 -0
  92. data/service/ssh/ext/ssh_core_rs/src/sftp.rs +294 -0
  93. data/service/ssh/lib/network_infra_utility/ssh/automation/macro_engine.rb +213 -0
  94. data/service/ssh/lib/network_infra_utility/ssh/client.rb +257 -0
  95. data/service/ssh/lib/network_infra_utility/ssh/config/schema.rb +90 -0
  96. data/service/ssh/lib/network_infra_utility/ssh/config/settings.rb +103 -0
  97. data/service/ssh/lib/network_infra_utility/ssh/config/store.rb +90 -0
  98. data/service/ssh/lib/network_infra_utility/ssh/ipc/coalesce.rb +83 -0
  99. data/service/ssh/lib/network_infra_utility/ssh/ipc/errors.rb +36 -0
  100. data/service/ssh/lib/network_infra_utility/ssh/ipc/router.rb +212 -0
  101. data/service/ssh/lib/network_infra_utility/ssh/ipc/transport.rb +81 -0
  102. data/service/ssh/lib/network_infra_utility/ssh/security/host_key.rb +211 -0
  103. data/service/ssh/lib/network_infra_utility/ssh/security/vault.rb +211 -0
  104. data/service/ssh/lib/network_infra_utility/ssh/session/history.rb +56 -0
  105. data/service/ssh/lib/network_infra_utility/ssh/session/manager.rb +92 -0
  106. data/service/ssh/lib/network_infra_utility/ssh/session/session.rb +109 -0
  107. data/service/ssh/lib/network_infra_utility/ssh/session/tree.rb +95 -0
  108. data/service/ssh/lib/network_infra_utility/ssh/terminal/ansi_parser.rb +435 -0
  109. data/service/ssh/lib/network_infra_utility/ssh/terminal/buffer.rb +78 -0
  110. data/service/ssh/lib/network_infra_utility/ssh/terminal/emulator.rb +159 -0
  111. data/service/ssh/lib/network_infra_utility/ssh/terminal/logger.rb +195 -0
  112. data/service/ssh/lib/network_infra_utility/ssh/terminal/screen.rb +212 -0
  113. data/service/ssh/lib/network_infra_utility/ssh/terminal/theme.rb +127 -0
  114. data/service/ssh/lib/network_infra_utility/ssh/version.rb +7 -0
  115. data/service/ssh/lib/network_infra_utility/ssh.rb +44 -0
  116. data/support/basic/as_num.rb +221 -0
  117. data/support/basic/mac_address.rb +281 -0
  118. data/version.rb +1 -1
  119. 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