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
@@ -0,0 +1,441 @@
1
+
2
+ # Geo 命令工具使用方法
3
+
4
+ > 命令文件:`bin/geo-load` / `bin/geo-api` / `bin/geo-get`
5
+ > 安装方式:`gem install` 后三个命令自动进入 PATH,直接全局可用
6
+
7
+ 三个命令构成完整工作流:
8
+
9
+ ```
10
+ geo-load (CSV→JSON) → geo-api (启动查询服务) → geo-get (查询 IP 归属)
11
+ ```
12
+
13
+ ---
14
+
15
+ ## 一、命令行用法
16
+
17
+ ### 1. `geo-load` — GeoLite2 CSV 转 JSON
18
+
19
+ 把 GeoLite2 解压后的 CSV 目录转换为 geo-api 可用的 JSON 数据文件。一次调用依次执行 ASN / City / Country 三类转换,生成 7 个 JSON 文件。
20
+
21
+ **用法:**
22
+
23
+ ```
24
+ geo-load [RAW_DIR] [DOC_DIR] [options]
25
+ ```
26
+
27
+ **参数:**
28
+
29
+ | 参数 | 说明 |
30
+ |------|------|
31
+ | `RAW_DIR` | GeoLite2 CSV 目录(可省,省则在当前目录查找) |
32
+ | `DOC_DIR` | JSON 输出目录(可省,省则输出到 `./geodb/`) |
33
+
34
+ **参数规则:**
35
+
36
+ - **RAW_DIR 不写**:在当前工作目录下递归查找 `GeoLite2-ASN-CSV_*` / `GeoLite2-City-CSV_*` / `GeoLite2-Country-CSV_*` 目录,找不到则报错退出
37
+ - **RAW_DIR 写了但路径不存在**:报错退出
38
+ - **DOC_DIR 不写**:在当前工作目录下生成 `geodb/` 子目录
39
+ - **DOC_DIR 写了但路径不存在**:自动创建该目录;创建不了则报错退出
40
+ - **DOC_DIR 写了且路径已存在**:直接往里写(覆盖同名文件)
41
+
42
+ **选项:**
43
+
44
+ | 选项 | 说明 |
45
+ |------|------|
46
+ | `--asn-only` | 仅转换 ASN 数据 |
47
+ | `--city-only` | 仅转换 City 数据 |
48
+ | `--country-only` | 仅转换 Country 数据 |
49
+ | `-h, --help` | 显示帮助 |
50
+ | `-v, --version` | 显示版本 |
51
+
52
+ 不指定 `--*-only` 时三类全转。
53
+
54
+ **生成的文件:**
55
+
56
+ ```
57
+ asn.json city-IPv4.json city-IPv6.json
58
+ country-IPv4.json country-IPv6.json
59
+ geo-city.json geo-country.json
60
+ ```
61
+
62
+ **示例:**
63
+
64
+ ```bash
65
+ # 当前目录下有 GeoLite2 CSV 解压包,输出到 ./geodb/
66
+ geo-load
67
+
68
+ # 指定 CSV 目录,输出到默认 ./geodb/
69
+ geo-load /data/GeoLite2-CSV
70
+
71
+ # 指定输入和输出
72
+ geo-load /data/GeoLite2-CSV /var/lib/geodb
73
+
74
+ # 只转 ASN(最轻量,约 30 秒)
75
+ geo-load /data/GeoLite2-CSV --asn-only
76
+
77
+ # 只转 Country
78
+ geo-load /data/GeoLite2-CSV --country-only
79
+ ```
80
+
81
+ **输出目录结构要求:**
82
+
83
+ RAW_DIR 期望包含以下子目录之一(或全部),每个子目录内含对应 CSV 文件:
84
+
85
+ ```
86
+ GeoLite2-ASN-CSV_*/
87
+ ├── GeoLite2-ASN-Blocks-IPv4.csv
88
+ └── GeoLite2-ASN-Blocks-IPv6.csv
89
+
90
+ GeoLite2-City-CSV_*/
91
+ ├── GeoLite2-City-Locations-zh-CN.csv
92
+ ├── GeoLite2-City-Blocks-IPv4.csv
93
+ └── GeoLite2-City-Blocks-IPv6.csv
94
+
95
+ GeoLite2-Country-CSV_*/
96
+ ├── GeoLite2-Country-Locations-zh-CN.csv
97
+ ├── GeoLite2-Country-Blocks-IPv4.csv
98
+ └── GeoLite2-Country-Blocks-IPv6.csv
99
+ ```
100
+
101
+ 工具会递归扫描 RAW_DIR 下的 `**/GeoLite2-*-CSV_*/` 目录,不要求三类目录都在。
102
+
103
+ ---
104
+
105
+ ### 2. `geo-api` — 启动 IP 归属查询服务
106
+
107
+ 把 JSON 数据文件加载到内存,启动 HTTP 服务供 geo-get 或其他客户端查询。
108
+
109
+ **用法:**
110
+
111
+ ```
112
+ geo-api [options]
113
+ ```
114
+
115
+ **选项:**
116
+
117
+ | 选项 | 默认值 | 说明 |
118
+ |------|--------|------|
119
+ | `-d, --data-dir DIR` | `./geodb/` | JSON 数据文件目录(或环境变量 `GEODB_DATA_DIR`) |
120
+ | `-b, --host HOST` | `0.0.0.0` | 监听地址 |
121
+ | `-p, --port PORT` | `9292` | 监听端口 |
122
+ | `-s, --server NAME` | `puma` | Rack 服务器 |
123
+ | `-h, --help` | | 显示帮助 |
124
+ | `-v, --version` | | 显示版本 |
125
+
126
+ **启动前校验:** 目录不存在或目录下没有任何 `.json` 文件会直接报错退出。
127
+
128
+ **示例:**
129
+
130
+ ```bash
131
+ # 默认配置(读取 ./geodb/,监听 0.0.0.0:9292)
132
+ geo-api
133
+
134
+ # 指定数据目录
135
+ geo-api -d /var/lib/geodb
136
+
137
+ # 指定数据和端口
138
+ geo-api -d /var/lib/geodb -p 8080
139
+
140
+ # 仅本机访问
141
+ geo-api -b 127.0.0.1 -d ./geodb
142
+
143
+ # 通过环境变量指定数据目录
144
+ GEODB_DATA_DIR=/data/geodb geo-api
145
+ ```
146
+
147
+ **HTTP 接口一览:**
148
+
149
+ | 接口 | 参数 | 说明 |
150
+ |------|------|------|
151
+ | `GET /` | 无 | 服务信息与接口清单 |
152
+ | `GET /geo/asn?num=XXX` | AS 编号 | 查该 AS 名下所有地址段 |
153
+ | `GET /geo/asn?addr=X.X.X.X` | IP 地址 | 按 IP 反查所属 AS |
154
+ | `GET /geo/city?id=XXX` | geoname_id | 查城市级定位信息 |
155
+ | `GET /geo/city?addr=X.X.X.X` | IP 地址 | 按 IP 查城市归属 |
156
+ | `GET /geo/country?id=XXX` | geoname_id | 查国别信息 |
157
+ | `GET /geo/country?addr=X.X.X.X` | IP 地址 | 按 IP 查国家归属 |
158
+
159
+ 完整接口文档见 `service/geodb/GeoAPI.md`。
160
+
161
+ ---
162
+
163
+ ### 3. `geo-get` — 查询单个 IP 的归属信息
164
+
165
+ 向运行中的 geo-api 服务发起查询,一次输入 IP,同时拉取 country / city / asn 三类信息并汇总输出。
166
+
167
+ **用法:**
168
+
169
+ ```
170
+ geo-get [IP] [options]
171
+ ```
172
+
173
+ **选项:**
174
+
175
+ | 选项 | 说明 |
176
+ |------|------|
177
+ | `-t, --text` | 文字格式输出(默认) |
178
+ | `-j, --json` | JSON 格式输出 |
179
+ | `--country` | 仅查询国家接口 |
180
+ | `--city` | 仅查询城市接口 |
181
+ | `--asn` | 仅查询 ASN 接口 |
182
+ | `-h, --help` | 显示帮助 |
183
+ | `-v, --version` | 显示版本 |
184
+
185
+ 不指定 `--country` / `--city` / `--asn` 时三接口齐查。不指定 IP 时用默认演示 IP `1.181.240.251`。
186
+
187
+ 默认连 `http://127.0.0.1:9292`(geo-api 默认监听)。服务未运行时给出提示与启动命令。
188
+
189
+ **示例:**
190
+
191
+ ```bash
192
+ # 默认:三接口齐查,文字格式
193
+ geo-get 1.181.240.251
194
+
195
+ # JSON 格式(适合管道处理)
196
+ geo-get 8.8.8.8 -j
197
+
198
+ # 仅查 ASN
199
+ geo-get 8.8.8.8 --asn
200
+
201
+ # 仅查国家,JSON 格式
202
+ geo-get 1.1.1.1 --country --json
203
+
204
+ # 不给 IP,用默认演示 IP
205
+ geo-get
206
+ ```
207
+
208
+ **文字格式输出示例:**
209
+
210
+ ```
211
+ 查询 IP: 1.181.240.251 范围: country / city / asn 三接口齐查 服务: http://127.0.0.1:9292
212
+ ------------------------------------------------------------
213
+ 1.181.240.251 的归属信息:
214
+ [国家] 中国 (CN) 网段 1.180.0.0/14
215
+ [城市] 未知 坐标 无 网段 1.180.0.0/14
216
+ [ASN] AS4134 Chinanet 网段 1.180.0.0/15
217
+ ```
218
+
219
+ **JSON 格式输出示例:**
220
+
221
+ ```json
222
+ {
223
+ "country": { ... },
224
+ "city": { ... },
225
+ "asn": { ... }
226
+ }
227
+ ```
228
+
229
+ 某个接口未命中(404)时,JSON 中对应字段为 `null`,不逐条报错。三个接口全未命中时,文字格式给一行提示。
230
+
231
+ ---
232
+
233
+ ### 完整工作流示例
234
+
235
+ ```bash
236
+ # 1. 下载 GeoLite2 CSV 包并解压
237
+ cd /data
238
+ tar xzf GeoLite2-CSV.zip
239
+
240
+ # 2. 转换为 JSON(当前目录找 CSV,输出到 ./geodb/)
241
+ geo-load
242
+
243
+ # 3. 启动查询服务
244
+ geo-api -d ./geodb -p 9292 &
245
+
246
+ # 4. 另一个终端查询
247
+ geo-get 8.8.8.8
248
+ geo-get 1.1.1.1 -j
249
+ ```
250
+
251
+ ---
252
+
253
+ ## 二、代码级用法
254
+
255
+ ### 1. GeoDB 模块 — CSV 加载(对应 geo-load)
256
+
257
+ > 源码位置:`service/geodb/geodb.rb`
258
+ > 加载方式:`require 'network'` 后 `require 'geodb'`
259
+
260
+ 三个模块方法,各自处理一类数据,均可单独调用:
261
+
262
+ ```ruby
263
+ require 'network'
264
+ require 'geodb'
265
+
266
+ # 把 GeoLite2 ASN CSV 转成 asn.json
267
+ # 第一个参数: CSV glob 模式 (相对调用目录或绝对路径)
268
+ # 第二个参数: 输出目录 (必须以 / 结尾)
269
+ GeoDB.load_asn(
270
+ '/data/GeoLite2-ASN-CSV_20260725/*.csv',
271
+ '/var/lib/geodb/'
272
+ )
273
+
274
+ # City: 生成 geo-city.json + city-IPv4.json + city-IPv6.json
275
+ # 内部按文件名 Location-zh-CN / Blocks-IPv4 / Blocks-IPv6 分流
276
+ GeoDB.load_city(
277
+ '/data/GeoLite2-City-CSV_20260724/*.csv',
278
+ '/var/lib/geodb/'
279
+ )
280
+
281
+ # Country: 生成 geo-country.json + country-IPv4.json + country-IPv6.json
282
+ GeoDB.load_country(
283
+ '/data/GeoLite2-Country-CSV_20260724/*.csv',
284
+ '/var/lib/geodb/'
285
+ )
286
+ ```
287
+
288
+ **方法签名:**
289
+
290
+ ```ruby
291
+ GeoDB.load_asn(dir_path, out_path) → Hash { range => record }
292
+ GeoDB.load_city(dir_path, out_path) → [city_Hash, geo_Hash]
293
+ GeoDB.load_country(dir_path, out_path) → [country_Hash, geo_Hash]
294
+ ```
295
+
296
+ **JSON 键格式(范围型):**
297
+
298
+ geodb.rb 把 CIDR 通过 `IP.range` 展开为 `[start_num, end_num]`,作为 JSON 的键:
299
+
300
+ ```ruby
301
+ # CSV 中的 network 字段 "1.0.4.0/22"
302
+ # 转换后 JSON 键为 Ruby 数组序列化字符串:
303
+ # "[16777984, 16778239]"
304
+ # 值为原始 CSV 记录 (行头映射的 Hash)
305
+ ```
306
+
307
+ **内部实现要点:**
308
+
309
+ - `Dir[glob]` 匹配 CSV 文件,多个文件依次处理
310
+ - `CSV.parse File.read(path)` 读入后用 `table.first` 作表头,`mapping` 方法把每行映射为 `{列名=>值}` 的 Hash
311
+ - `IP.range(record['network'])` 把 CIDR 展开为 `[start_ip, end_ip]`,再 `.map(&:number)` 转整数
312
+ - City / Country 的 Locations 文件单独提取为 geo 表(按 `geoname_id` 索引),Blocks 文件按 IPv4/IPv6 分两份输出
313
+
314
+ ---
315
+
316
+ ### 2. GeoAPI 服务 — 启动与接口(对应 geo-api)
317
+
318
+ > 源码位置:`service/geodb/api.rb`
319
+ > 加载方式:`require 'network'` 后 `require 'api'`(需把 `service/geodb` 加入 `$LOAD_PATH`)
320
+
321
+ #### 启动服务
322
+
323
+ ```ruby
324
+ require 'network'
325
+ $LOAD_PATH.unshift File.join(__dir__, 'service', 'geodb')
326
+ require 'api'
327
+
328
+ ENV['GEODB_DATA_DIR'] = '/var/lib/geodb' # 指定数据目录
329
+
330
+ require 'rackup'
331
+ Rackup::Server.start(
332
+ app: GeoAPI.app,
333
+ server: 'puma',
334
+ Host: '0.0.0.0',
335
+ Port: 9292
336
+ )
337
+ ```
338
+
339
+ #### GeoDB 模块方法(接口内部逻辑,也可代码调用)
340
+
341
+ ```ruby
342
+ # 数据目录 (读 ENV['GEODB_DATA_DIR'], 默认 ./geodb/)
343
+ GeoDB.data_dir # => "/var/lib/geodb/"
344
+
345
+ # ASN 查询
346
+ GeoDB.asn_by_num('13335') # 该 AS 名下所有地址段 (走反向索引, O(1))
347
+ GeoDB.asn_by_addr('1.0.0.5') # 按 IP 查所属 AS → record 或 nil 或 :invalid
348
+
349
+ # City 查询
350
+ GeoDB.city_by_id('1814991') # 按 geoname_id 查 → record / nil / :invalid
351
+ GeoDB.city_by_addr('1.0.1.1') # 按 IP 查 → enriched record / nil / :invalid
352
+
353
+ # Country 查询
354
+ GeoDB.country_by_id('6252001') # 按 geoname_id 查
355
+ GeoDB.country_by_addr('1.0.1.1') # 按 IP 查
356
+
357
+ # 预热 (后台异步加载, 避免首次查询卡顿)
358
+ GeoDB.preload('asn', 'country-IPv4', 'country-IPv6')
359
+ ```
360
+
361
+ **返回值约定:**
362
+
363
+ | 场景 | 返回 |
364
+ |------|------|
365
+ | IP 不合法 | `:invalid` |
366
+ | 无结果 | `nil` |
367
+ | 命中 | 纯 Record (Hash) 或 enrich 后的 Hash(含 `geoname` / `registered_country` 嵌套对象) |
368
+
369
+ #### enrich 关联机制
370
+
371
+ `city_by_addr` / `country_by_addr` 命中后会调用 `enrich`,把范围记录里的三个 geoname_id 外键关联到 geo 表:
372
+
373
+ ```ruby
374
+ record = {
375
+ 'network' => '1.0.1.0/24',
376
+ 'geoname_id' => '1814991',
377
+ 'registered_country_geoname_id' => '1814991',
378
+ 'represented_country_geoname_id' => nil,
379
+ ...
380
+ }
381
+
382
+ GeoDB.enrich(record, 'geo-city')
383
+ # => {
384
+ # ...原字段...,
385
+ # 'geoname' => { geoname_id, country_name, city_name, ... },
386
+ # 'registered_country' => { ... },
387
+ # 'represented_country' => nil
388
+ # }
389
+ ```
390
+
391
+ #### 加载与缓存机制
392
+
393
+ - 范围型 JSON(`asn` / `city-IPv4` 等)首次访问时全量加载、按 start 排序、缓存到 `@store`,后续走内存二分
394
+ - ASN 额外建 `@asn_index` 反向索引(`as_number → [record...]`),`num` 查询 O(1)
395
+ - 地理型 JSON(`geo-city` / `geo-country`)缓存为扁平 Hash,按 `geoname_id` 字符串键取值
396
+ - 文件缺失时 `@store[name] = nil`,对应接口返回 404 `无结果`,不影响其他接口
397
+
398
+ ---
399
+
400
+ ### 3. geo-get 的查询逻辑(HTTP 客户端代码)
401
+
402
+ > 源码位置:`bin/geo-get`
403
+ > geo-get 是纯 HTTP 客户端,不加载 GeoDB 模块,不依赖数据文件
404
+
405
+ ```ruby
406
+ require 'net/http'
407
+ require 'json'
408
+
409
+ BASE = 'http://127.0.0.1:9292'
410
+
411
+ # 发起一次 GET, 返回 {code:, body:}
412
+ def fetch(base, path, params)
413
+ uri = URI("#{base}#{path}")
414
+ uri.query = URI.encode_www_form(params) unless params.empty?
415
+ res = Net::HTTP.get_response(uri)
416
+ { code: res.code.to_i, body: (JSON.parse(res.body) rescue res.body) }
417
+ end
418
+
419
+ # 三接口齐查
420
+ results = {}
421
+ results[:country] = fetch(BASE, '/geo/country', addr: '8.8.8.8')
422
+ results[:city] = fetch(BASE, '/geo/city', addr: '8.8.8.8')
423
+ results[:asn] = fetch(BASE, '/geo/asn', addr: '8.8.8.8')
424
+
425
+ # 命中提取
426
+ results.each do |key, r|
427
+ puts key if r[:code] == 200
428
+ end
429
+ ```
430
+
431
+ **查询顺序与错误处理:**
432
+
433
+ 1. 依次(非并行)请求 country → city → asn 三个接口
434
+ 2. 每个接口返回 200 时收集命中数据,404/400 等静默跳过
435
+ 3. 三个接口全部未命中时统一输出一行提示,不逐条刷 404
436
+ 4. 连接被拒(`Errno::ECONNREFUSED`)时直接 abort 并提示启动命令
437
+
438
+ **文字格式 vs JSON 格式:**
439
+
440
+ - 文字格式:从响应体中提取关键字段(国家名、城市名、ASN 组织、网段等)拼成可读行
441
+ - JSON 格式:把三个接口的响应体原样放进 `{country:, city:, asn:}` 结构,未命中的字段为 `null`,用 `JSON.pretty_generate` 输出