network-infra-utility 0.5.0 → 0.8.3
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 +6 -0
- data/CHANGELOG.md +31 -0
- data/GUIDE.md +201 -0
- data/README.md +1 -6
- data/Rakefile +1 -1
- data/bin/dns-query +4 -2
- data/bin/gen-get +144 -0
- data/bin/geo-api +40 -1
- data/bin/geo-get +18 -36
- data/bin/geo-update +429 -0
- data/bin/ngeo-get +253 -0
- data/bin/packbit +227 -0
- data/bin/probe +134 -0
- data/document/reference/DNS/346/212/200/346/234/257/346/226/207/346/241/243.md +912 -0
- data/network-infra-utility.gemspec +6 -4
- data/network.rb +19 -1
- data/service/geodb/GeoAPI.md +64 -0
- data/service/geodb/api.rb +77 -19
- data/service/geoquery/GeoQuery.md +309 -0
- data/service/geoquery/cache.rb +78 -0
- data/service/geoquery/geo_cache.rb +233 -0
- data/service/geoquery/geoquery.rb +327 -0
- data/service/geoquery/local.rb +151 -0
- data/service/geoquery/merge.rb +52 -0
- data/service/geoquery/normalize.rb +102 -0
- data/service/geoquery/online.rb +136 -0
- data/service/simlab/SimLab.md +140 -0
- data/service/simlab/bin/simctl +152 -0
- data/service/simlab/lib/simlab.rb +124 -0
- data/service/simlab/lib/simlab_engine.rb +146 -0
- data/service/simlab/lib/simlab_observer.rb +176 -0
- data/service/simlab/lib/simlab_router.rb +330 -0
- data/service/simlab/lib/simlab_scenario.rb +200 -0
- data/service/simlab/lib/simlab_switch.rb +182 -0
- data/service/simlab/lib/simlab_topology.rb +196 -0
- data/service/simlab/lib/simlab_traffic.rb +167 -0
- data/service/ssh/README.md +50 -37
- data/service/ssh/ext/ssh_core/src/ssh_infra_sup.erl +14 -1
- data/support/basic/packet.rb +167 -0
- data/support/routing/bgp.rb +279 -0
- data/support/routing/lpm_trie.rb +145 -0
- data/support/routing/ospf.rb +243 -0
- data/support/routing/prefix.rb +155 -0
- data/support/routing/rib.rb +167 -0
- data/support/routing/rip.rb +223 -0
- data/support/routing/static_route.rb +102 -0
- data/support/switching/frame_forward.rb +87 -0
- data/support/switching/mac_table.rb +127 -0
- data/support/switching/stp.rb +230 -0
- data/support/switching/vlan.rb +155 -0
- data/tool/packbit/README.md +136 -0
- data/tool/packbit/config/five-tuple.yml +25 -0
- data/tool/packbit/config/flow.yml +17 -0
- data/tool/packbit/config/packbit.yml +41 -0
- data/tool/packbit/erl/packbit.erl +588 -0
- data/tool/probe/README.md +88 -0
- data/tool/probe/lib/probe.rb +54 -0
- data/tool/probe/lib/probe_base.rb +98 -0
- data/tool/probe/lib/probe_dns.rb +64 -0
- data/tool/probe/lib/probe_icmp.rb +71 -0
- data/tool/probe/lib/probe_ntp.rb +51 -0
- data/tool/probe/lib/probe_radius.rb +58 -0
- data/tool/probe/lib/probe_registry.rb +44 -0
- data/tool/probe/lib/probe_result.rb +75 -0
- data/tool/probe/lib/probe_snmp.rb +114 -0
- data/tool/probe/lib/probe_tcp.rb +82 -0
- data/tool/probe/lib/probe_version.rb +7 -0
- data/version.rb +1 -1
- metadata +78 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 47ea2aa6788e6fcf4256ca20f32bb21933d45ac19ba9775d9234b8839413e435
|
|
4
|
+
data.tar.gz: 1b9393d9081bd78e93e1895e2002695eee940775cba0779a721ab72af8690668
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0ff7d883a36bf28a873a871c14cb5b8c2fd42ef5ef07263adbd6bbe850bd6b787e00603217232e3d8b1b9c2d33aecf2a27b0fe4285451de34d9460783f581a36
|
|
7
|
+
data.tar.gz: 9db0ae3bb4f4b74965439f192570de14346a82950c3460494c69941e2621a62de3a97458a27102cef16f850c6d8a1e53049c61d31ad1caa1b2733811e82d1457
|
data/.gitignore
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
|
|
2
|
+
# SPEC
|
|
3
|
+
document/example/ssh/config_login_example.rb
|
|
4
|
+
document/example/ssh/login_config.yml
|
|
5
|
+
|
|
1
6
|
*.gem
|
|
2
7
|
*.rbc
|
|
3
8
|
/.config
|
|
@@ -10,6 +15,7 @@
|
|
|
10
15
|
/test/version_tmp/
|
|
11
16
|
/tmp/
|
|
12
17
|
.temp
|
|
18
|
+
.workbuddy
|
|
13
19
|
|
|
14
20
|
# Used by dotenv library to load environment variables.
|
|
15
21
|
# .env
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
---
|
|
2
|
+
AIGC:
|
|
3
|
+
ContentProducer: '001191110102MAD55U9H0F10002'
|
|
4
|
+
ContentPropagator: '001191110102MAD55U9H0F10002'
|
|
5
|
+
Label: '1'
|
|
6
|
+
ProduceID: '88e1794b-cd8d-483e-b3d2-ee4485fccf96'
|
|
7
|
+
PropagateID: '88e1794b-cd8d-483e-b3d2-ee4485fccf96'
|
|
8
|
+
ReservedCode1: '20f6bf17-f91c-4c4c-b721-e45f1eb19345'
|
|
9
|
+
ReservedCode2: '20f6bf17-f91c-4c4c-b721-e45f1eb19345'
|
|
10
|
+
---
|
|
1
11
|
|
|
2
12
|
# Changelog
|
|
3
13
|
|
|
@@ -8,6 +18,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
8
18
|
|
|
9
19
|
## [Unreleased]
|
|
10
20
|
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- 新增 `gen-get` 命令行工具:调用免费互联网接口查询 IP 归属,默认 ip-api.com 及其参数(`lang=zh-CN` + `status,message,country,regionName,city,isp,as,query`,`message` 为诊断扩展),内置 45 req/min 限速(最小间隔 1.4s)与本地 JSON 缓存;互联网不通时返回明确的 `unreachable`(互联网查询不可达)空结果状态并区分于 `empty`(确认无归属,如私有地址)。
|
|
24
|
+
- 新增 `ngeo-get` 命令行工具:综合 geo-get(本地 geo-api)与 gen-get(互联网)两个数据源——本地接口可用时优先使用;归属不满意(省/市/ASN 缺失)时自动补互联网查询;本地与互联网结果字段级叠加(省/城市/用途 IDC·云等准确字段择优,网段保留本地,运营商按合并后组织名归一化)。结果状态可信度链:`unreachable < local-partial < empty < local < online < merged`,互联网不可达时本地部分结果保留;连续 3 次不可达触发 60s 熔断。
|
|
25
|
+
- 新增 `service/geoquery/` 服务模块:`OnlineClient`(ip-api.com 客户端)、`LocalClient`(geo-api 客户端)、`Merge`(字段叠加)、`Normalize`(运营商/用途归一化)、`Cache`(线程安全 JSON 缓存)、`NGeo`(综合查询门面),移植自 `ip_geo_lookup.py` 的多级回退 + 缓存 + 归一化设计。
|
|
26
|
+
- `gen-get` / `ngeo-get` 加入 gemspec executables,`network.rb` 统一入口挂载 geoquery;新增 `spec/geoquery_spec.rb`(状态机/归一化/合并/缓存/熔断)与 `document/example/geoquery_example.rb` 功能用例;接口文档见 `service/geoquery/GeoQuery.md`。
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
|
|
30
|
+
- `geo-get` 重构为 `GeoQuery::LocalClient` 的瘦封装:三接口拉取与汇总逻辑统一收口在 `service/geoquery/local.rb`(新增 `fetch_raw` 原始接口查询,保持 geo-get `-j` 输出的原始响应契约不变),geo-get 与 ngeo-get / gen-get 共享同一套客户端实现,消除双实现;CLI 选项、文字/JSON 输出格式、服务未启动提示均保持向后兼容,另支持 `GEO_API_BASE` 环境变量覆盖服务地址。
|
|
31
|
+
- 合并 `geo_api_query_example.rb`(geo-api 服务三接口)与旧版 `geoquery_example.rb`(GeoQuery 查询编排)为单一 `document/example/geoquery_example.rb`:按“Part A 服务接口 → Part B 查询编排”组织为 11 个案例块,新增 LocalClient(`fetch_raw` / `lookup`)演示;Part B 复用 Part A 启动的服务实例(支持 `GEOAPI=` 覆盖为外部服务),通过 `$LOAD_PATH` 前插仓库根消除工作区与已安装 gem 的双重加载警告。
|
|
32
|
+
- `example/` 目录整体移至 `document/example/`(并入文档层),`Rakefile` example 任务 pattern、gemspec 文件排除规则(新增 `document/example/`)与根 README 项目结构表同步更新。
|
|
33
|
+
- 新增 `probe` 服务连通性探测工具箱(`tool/probe/`),覆盖 icmp / telnet / ssh / netconf / snmp / twamp / dns / ntp / radius 九类协议,纯 Ruby 标准库实现、零第三方运行时依赖。
|
|
34
|
+
- 统一探测框架:`Probe::Base` 基类 + `Probe::Result` 统一结果 + `Probe::Registry` 注册表,协议名从类名自动派生,支持 `Registry.register` 扩展自定义探针。
|
|
35
|
+
- 内置 BER 编码器(SNMPv1/v2c GET 请求自编码)、NTP 48 字节客户端报文、RADIUS Access-Request 报文构造。
|
|
36
|
+
- TCP 探针区分 timeout / refused / unreachable;SSH/NETCONF 附加横幅校验;ICMP 复用系统 ping 并兼容中英文输出解析 RTT。
|
|
37
|
+
- 提供 `Probe.run(protocol, host)` / `Probe.check(host, protocols:)` 代码级 API 与 `bin/probe` 命令行工具(支持 `--all` / `--json` / `--timeout` / `--community` 等选项)。
|
|
38
|
+
- `probe` 加入 gemspec executables,`tool/probe/lib` 加入 require_paths,`network.rb` 统一入口已挂载。
|
|
39
|
+
|
|
11
40
|
## [0.3.0] - 2026-07-26
|
|
12
41
|
|
|
13
42
|
### Added
|
|
@@ -44,3 +73,5 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
44
73
|
- 初始化 gem 骨架,按 document / service / support / tool 四层 require_paths 组织代码。
|
|
45
74
|
- 配置 RSpec 原子能力测试(`spec/`)与功能用例(`example/`)双套结构。
|
|
46
75
|
- 提供标准 gem 文件:gemspec / Gemfile / Rakefile / .rspec / bin/console / bin/setup 等。
|
|
76
|
+
|
|
77
|
+
> AI生成
|
data/GUIDE.md
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# GUIDE
|
|
2
|
+
|
|
3
|
+
## 在 Alpine 设备上重新编译 Rust 和 Erlang 引擎
|
|
4
|
+
|
|
5
|
+
项目 SSH 核心引擎有两个后端,均可在 Alpine(musl libc)上原生编译:
|
|
6
|
+
|
|
7
|
+
| 引擎 | 目录 | 构建工具 | 产物 |
|
|
8
|
+
|------|------|---------|------|
|
|
9
|
+
| **Rust 引擎**(默认) | `service/ssh/ext/ssh_core_rs/` | cargo | `target/release/ssh_core_rs` |
|
|
10
|
+
| **Erlang 引擎**(可选) | `service/ssh/ext/ssh_core/` | rebar3 | `_build/default/lib/ssh_core/ebin/*.beam` |
|
|
11
|
+
|
|
12
|
+
### 2.1 安装编译依赖
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
# 更新包索引
|
|
16
|
+
apk update
|
|
17
|
+
|
|
18
|
+
# 通用编译工具链(gcc、make、musl-dev、libc Headers 等)
|
|
19
|
+
apk add build-base
|
|
20
|
+
|
|
21
|
+
# ── Rust 引擎依赖 ──
|
|
22
|
+
apk add rust cargo
|
|
23
|
+
apk add pkgconf openssl-dev # openssl-dev 预防部分 crate 需要链接系统 OpenSSL
|
|
24
|
+
|
|
25
|
+
# ── Erlang 引擎依赖 ──
|
|
26
|
+
# 方式 A:Alpine 仓库安装(Alpine 3.19+ 提供 OTP 26)
|
|
27
|
+
apk add erlang rebar3
|
|
28
|
+
|
|
29
|
+
# 方式 B:如果仓库 OTP 版本低于 26,从源码编译 Erlang/OTP
|
|
30
|
+
# 见下方 2.3 节
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### 2.2 编译 Rust 引擎
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
cd service/ssh/ext/ssh_core_rs
|
|
37
|
+
|
|
38
|
+
# 清理旧产物(如从其他平台拷贝过来)
|
|
39
|
+
rm -rf target/
|
|
40
|
+
|
|
41
|
+
# Release 编译
|
|
42
|
+
cargo build --release
|
|
43
|
+
|
|
44
|
+
# 验证产物
|
|
45
|
+
ls -lh target/release/ssh_core_rs
|
|
46
|
+
./target/release/ssh_core_rs --help 2>&1 || true
|
|
47
|
+
# 引擎启动后会在 /tmp/ssh_core_<uid>.endpoint 写入端点文件
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**说明:**
|
|
51
|
+
- Alpine 默认 host triple 是 `x86_64-unknown-linux-musl`(ARM 则为 `aarch64-unknown-linux-musl`),cargo 会自动选择,无需额外配置 target。
|
|
52
|
+
- `Cargo.toml` 中 russh 使用 `ring` 特性(纯 Rust 加密后端),不依赖系统 OpenSSL / NASM,musl 环境可直接编译。
|
|
53
|
+
- 编译后 `bin/ssh_core_rs`(bash 启动脚本)已存在,Ruby 端 `Client.new(backend: :rust)` 会自动调用它。
|
|
54
|
+
|
|
55
|
+
### 2.3 编译 Erlang 引擎
|
|
56
|
+
|
|
57
|
+
> 如果 `apk add erlang` 安装的 OTP 版本 >= 26,直接跳到 2.3.2。
|
|
58
|
+
|
|
59
|
+
#### 2.3.1 从源码编译 Erlang/OTP(仅当仓库版本 < 26 时需要)
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
# 安装 Erlang 编译依赖
|
|
63
|
+
apk add build-base perl ncurses-dev openssl-dev unixodbc-dev
|
|
64
|
+
|
|
65
|
+
# 下载 OTP 27(或 26)
|
|
66
|
+
cd /tmp
|
|
67
|
+
wget https://github.com/erlang/otp/releases/download/OTP-27.3.3/otp_src_27.3.3.tar.gz
|
|
68
|
+
tar xzf otp_src_27.3.3.tar.gz
|
|
69
|
+
cd otp_src_27.3.3
|
|
70
|
+
|
|
71
|
+
# 编译(musl 环境,禁用 wx/mac 等 GUI 组件,保留 ssh/crypto/public_key)
|
|
72
|
+
export ERL_TOP=/tmp/otp_src_27.3.3
|
|
73
|
+
./configure \
|
|
74
|
+
--prefix=/usr/local/erlang \
|
|
75
|
+
--without-ssl-verify \
|
|
76
|
+
--disable-nls \
|
|
77
|
+
--without-javac \
|
|
78
|
+
--without-wx \
|
|
79
|
+
--without-debugger
|
|
80
|
+
make -j$(nproc)
|
|
81
|
+
make install
|
|
82
|
+
|
|
83
|
+
# 加入 PATH
|
|
84
|
+
export PATH=/usr/local/erlang/bin:$PATH
|
|
85
|
+
echo 'export PATH=/usr/local/erlang/bin:$PATH' >> ~/.profile
|
|
86
|
+
|
|
87
|
+
# 验证
|
|
88
|
+
erl -version
|
|
89
|
+
# Erlang (SHELL) version 27.3.3
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
#### 2.3.2 安装 rebar3
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
# 方式 A:Alpine 仓库已安装则跳过
|
|
96
|
+
rebar3 --version
|
|
97
|
+
|
|
98
|
+
# 方式 B:下载 standalone 二进制
|
|
99
|
+
wget https://github.com/erlang/rebar3/releases/download/3.24.0/rebar3
|
|
100
|
+
chmod +x rebar3
|
|
101
|
+
mv rebar3 /usr/local/bin/
|
|
102
|
+
rebar3 --version
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
#### 2.3.3 编译 ssh_core
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
cd service/ssh/ext/ssh_core
|
|
109
|
+
|
|
110
|
+
# 清理旧产物
|
|
111
|
+
rm -rf _build/ rebar3.crashdump
|
|
112
|
+
|
|
113
|
+
# 编译
|
|
114
|
+
# rebar.config 中 {deps, []}(无外部依赖),jsx 已内嵌为本地源码
|
|
115
|
+
# (local_deps/jsx/src/src/),通过 src_dirs 直接编译,无需从 hex.pm 拉取
|
|
116
|
+
rebar3 compile
|
|
117
|
+
|
|
118
|
+
# 验证产物
|
|
119
|
+
ls _build/default/lib/ssh_core/ebin/*.beam | head -5
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
> **常见问题**:启动时如果报 `undefined function os:getuid/0`,是因为 OTP 版本 < 26 缺少此 BIF。代码已修复为自动 fallback 到 `os:cmd("id -u")`,确保 `rebar3 compile` 重新编译即可。
|
|
123
|
+
|
|
124
|
+
#### 2.3.4 创建 Unix 启动脚本
|
|
125
|
+
|
|
126
|
+
项目当前只有 `bin/ssh_core.cmd`(Windows),Linux/Alpine 需要创建 `bin/ssh_core`:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
cat > service/ssh/ext/ssh_core/bin/ssh_core << 'SCRIPT'
|
|
130
|
+
#!/usr/bin/env sh
|
|
131
|
+
# ssh_core engine launcher for Linux/macOS/Alpine
|
|
132
|
+
set -e
|
|
133
|
+
|
|
134
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
135
|
+
SSH_CORE_DIR="$(dirname "$SCRIPT_DIR")"
|
|
136
|
+
EBIN_DIR="$SSH_CORE_DIR/_build/default/lib/ssh_core/ebin"
|
|
137
|
+
|
|
138
|
+
# Delete stale endpoint file
|
|
139
|
+
ENDPOINT_FILE="/tmp/ssh_core_$(id -u).endpoint"
|
|
140
|
+
rm -f "$ENDPOINT_FILE"
|
|
141
|
+
|
|
142
|
+
# Start the Erlang VM
|
|
143
|
+
exec erl -noshell -noinput -pa "$EBIN_DIR" \
|
|
144
|
+
-eval "application:ensure_all_started(ssh_core)"
|
|
145
|
+
SCRIPT
|
|
146
|
+
chmod +x service/ssh/ext/ssh_core/bin/ssh_core
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
#### 2.3.5 验证 Erlang 引擎可启动
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
cd service/ssh/ext/ssh_core
|
|
153
|
+
./bin/ssh_core &
|
|
154
|
+
# 等 2 秒后查看端点文件
|
|
155
|
+
sleep 2 && cat /tmp/ssh_core_$(id -u).endpoint
|
|
156
|
+
# 应输出类似:tcp://127.0.0.1:xxxxx\n<auth_token>
|
|
157
|
+
|
|
158
|
+
# 停止引擎
|
|
159
|
+
kill %1
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### 2.4 编译后的 Ruby 端验证
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
# 在项目根目录,确认 Ruby 可连接引擎
|
|
166
|
+
ruby -e '
|
|
167
|
+
$LOAD_PATH.unshift("service/ssh/lib")
|
|
168
|
+
require "network_infra_utility/ssh"
|
|
169
|
+
|
|
170
|
+
# Rust 引擎(默认)
|
|
171
|
+
client = NetworkInfraUtility::SSH::Client.new(backend: :rust)
|
|
172
|
+
client.start_engine
|
|
173
|
+
puts "Rust engine: #{client.started?}"
|
|
174
|
+
client.stop
|
|
175
|
+
|
|
176
|
+
# Erlang 引擎
|
|
177
|
+
client2 = NetworkInfraUtility::SSH::Client.new(backend: :erlang)
|
|
178
|
+
client2.start_engine
|
|
179
|
+
puts "Erlang engine: #{client2.started?}"
|
|
180
|
+
client2.stop
|
|
181
|
+
'
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### 2.5 常见问题
|
|
185
|
+
|
|
186
|
+
| 问题 | 原因 | 解决 |
|
|
187
|
+
|------|------|------|
|
|
188
|
+
| `cargo build` 报 `linker cc not found` | 未安装 build-base | `apk add build-base` |
|
|
189
|
+
| `cargo build` 拉 crates.io 超时 | 国内网络限制 | `~/.cargo/config.toml` 配中科大/清华镜像源 |
|
|
190
|
+
| `cargo build` 报 `edition2024 is required` | Alpine 仓库 Rust 版本过旧 (< 1.85) | `apk del rust cargo` 后用 rustup 装最新稳定版 |
|
|
191
|
+
| `curl https://sh.rustup.rs` 下载不动 | rustup 安装脚本在国外 | 设 `RUSTUP_DIST_SERVER` 中科大镜像,或直接下载 rustup-init 二进制 |
|
|
192
|
+
| `rebar3 compile` 报 `undef ssh` 模块 | OTP 版本 < 26 | 按本文 2.3.1 从源码编译 OTP 27 |
|
|
193
|
+
| Erlang 引擎报 `undefined function os:getuid/0` | OTP < 26 缺少此 BIF | 代码已修复自动 fallback,`rebar3 compile` 重新编译 |
|
|
194
|
+
| Erlang 引擎启动报 `ssh_core beam not found` | `bin/ssh_core` 脚本 EBIN_DIR 路径不对 | 确认 `_build/default/lib/ssh_core/ebin/` 下有 beam 文件 |
|
|
195
|
+
| Ruby 报 `Engine binary not found` | 缺少启动脚本 | Rust: 确认 `bin/ssh_core_rs` 存在;Erlang: 按本文 2.3.4 创建 `bin/ssh_core` |
|
|
196
|
+
| Ruby 报 `Permission denied` 启动脚本 | Windows 拷来的脚本丢失执行位 | `chmod +x service/ssh/ext/ssh_core_rs/bin/ssh_core_rs` |
|
|
197
|
+
| Ruby 报 `ECONNREFUSED` 连接引擎 | 旧引擎残留进程占用端口 | `pkill ssh_core_rs; rm -f /tmp/ssh_core_*.endpoint` |
|
|
198
|
+
| `conn.connect` 超时 (60s) | 网络不通或设备 SSH 不兼容 | 先 `ping`/`nc -zv` 确认网络,再用 `RUST_LOG=debug` 定位 |
|
|
199
|
+
| `auth_rejected` 认证被拒绝 | 设备只支持 keyboard-interactive | 配置文件 `auth.type` 改为 `keyboard_interactive` |
|
|
200
|
+
| Rust 编译卡在 `ring` 或 `aws-lc-rs` | 误用了 aws-lc-rs 后端 | Cargo.toml 已指定 `features = ["ring"]`,确认未被覆盖 |
|
|
201
|
+
| `error: musl-gcc: not found` | 部分 crate 尝试用 musl-gcc | `apk add musl-dev`(已含于 build-base) |
|
data/README.md
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
# Network Infrastructure Utility
|
|
3
2
|
|
|
4
3
|
重新编排 Network(`gem: network-utility`)。
|
|
@@ -17,7 +16,7 @@
|
|
|
17
16
|
测试体系:
|
|
18
17
|
|
|
19
18
|
- `spec/` —— 原子能力验证(单元规格)
|
|
20
|
-
- `example/` —— 常用场景的功能用例
|
|
19
|
+
- `document/example/` —— 常用场景的功能用例
|
|
21
20
|
|
|
22
21
|
## 安装
|
|
23
22
|
|
|
@@ -39,7 +38,3 @@ bundle exec rake spec # 跑原子能力
|
|
|
39
38
|
bundle exec rake example # 跑功能用例
|
|
40
39
|
bundle exec rake # 两者都跑(默认)
|
|
41
40
|
```
|
|
42
|
-
|
|
43
|
-
## License
|
|
44
|
-
|
|
45
|
-
采用 [GNU Affero General Public License v3.0 或更高版本](https://www.gnu.org/licenses/agpl-3.0.html)(AGPL-3.0-or-later),详见 [LICENSE.txt](LICENSE.txt)。
|
data/Rakefile
CHANGED
data/bin/dns-query
CHANGED
|
@@ -830,5 +830,7 @@ module DnsQuery
|
|
|
830
830
|
end
|
|
831
831
|
end
|
|
832
832
|
|
|
833
|
-
# 仅作为命令行执行时运行 CLI;被 require
|
|
834
|
-
|
|
833
|
+
# 仅作为命令行执行时运行 CLI;被 require 时只提供模块能力。
|
|
834
|
+
# 注意: gem 安装后经 wrapper 以 load 方式加载,__FILE__ 与 $PROGRAM_NAME
|
|
835
|
+
# 路径不同,须按文件名 (basename) 判断才能同时覆盖两种运行方式。
|
|
836
|
+
exit DnsQuery::CLI.run(ARGV) if File.basename($PROGRAM_NAME) == File.basename(__FILE__)
|
data/bin/gen-get
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
# coding: utf-8
|
|
4
|
+
#
|
|
5
|
+
# gen-get — network-infra-utility 的互联网 IP 归属查询工具 (geo 系列)
|
|
6
|
+
#
|
|
7
|
+
# 调用免费互联网接口查询 IP 归属, 默认 ip-api.com 及其参数
|
|
8
|
+
# (lang=zh-CN, fields=status,message,country,regionName,city,isp,as,query),
|
|
9
|
+
# 内置 45 req/min 限速与本地 JSON 缓存。
|
|
10
|
+
#
|
|
11
|
+
# 用法:
|
|
12
|
+
# gen-get 8.8.8.8 # 单 IP, 文字格式
|
|
13
|
+
# gen-get 8.8.8.8 -j # JSON 格式
|
|
14
|
+
# gen-get 1.1.1.1 8.8.8.8 ... # 多 IP (自动限速)
|
|
15
|
+
# cat ips.txt | gen-get -j # 从 stdin 逐行读 IP
|
|
16
|
+
# gen-get 8.8.8.8 --refresh # 忽略缓存强刷
|
|
17
|
+
# gen-get --cache-stats # 查看缓存统计
|
|
18
|
+
#
|
|
19
|
+
# 选项:
|
|
20
|
+
# -t, --text 文字格式输出 (默认)
|
|
21
|
+
# -j, --json JSON 格式输出 (多 IP 时输出数组)
|
|
22
|
+
# --refresh 忽略缓存强制重查
|
|
23
|
+
# --no-cache 查询结果不写入缓存
|
|
24
|
+
# --api URL 覆盖默认接口 ({ip} 占位符)
|
|
25
|
+
# --timeout SEC HTTP 超时秒数, 默认 10
|
|
26
|
+
# --cache-stats 查看缓存统计后退出
|
|
27
|
+
# -h, --help 显示帮助
|
|
28
|
+
# -v, --version 显示版本
|
|
29
|
+
#
|
|
30
|
+
# 退出码: 0 = 查询完成 (含确认无归属); 1 = IP 不合法; 2 = 互联网查询不可达
|
|
31
|
+
#
|
|
32
|
+
# 结果状态 (state):
|
|
33
|
+
# ok 互联网查询成功
|
|
34
|
+
# empty 互联网确认无归属 (如私有/保留地址)
|
|
35
|
+
# unreachable 互联网查询不可达 (网络不通/超时/限速), 返回空结果状态
|
|
36
|
+
# invalid IP 不合法
|
|
37
|
+
|
|
38
|
+
require "optparse"
|
|
39
|
+
require "json"
|
|
40
|
+
require_relative "../service/geoquery/geoquery"
|
|
41
|
+
|
|
42
|
+
options = { format: :text, refresh: false, no_cache: false, timeout: 10, api: nil }
|
|
43
|
+
parser = OptionParser.new do |opts|
|
|
44
|
+
opts.banner = <<~BAN
|
|
45
|
+
gen-get — 互联网 IP 归属查询工具 (network-infra-utility)
|
|
46
|
+
|
|
47
|
+
默认接口: ip-api.com 免费接口 (限 45 req/min, 无需 Key)
|
|
48
|
+
用法:
|
|
49
|
+
gen-get <IP> [options]
|
|
50
|
+
gen-get 8.8.8.8 -j
|
|
51
|
+
cat ips.txt | gen-get -j
|
|
52
|
+
|
|
53
|
+
选项:
|
|
54
|
+
BAN
|
|
55
|
+
opts.on("-t", "--text", "文字格式输出 (默认)") { options[:format] = :text }
|
|
56
|
+
opts.on("-j", "--json", "JSON 格式输出") { options[:format] = :json }
|
|
57
|
+
opts.on("--refresh", "忽略缓存强制重查") { options[:refresh] = true }
|
|
58
|
+
opts.on("--no-cache", "查询结果不写入缓存") { options[:no_cache] = true }
|
|
59
|
+
opts.on("--api URL", "覆盖默认互联网接口 ({ip} 占位符)") { |v| options[:api] = v }
|
|
60
|
+
opts.on("--timeout SEC", Integer, "HTTP 超时秒数 (默认 10)") { |v| options[:timeout] = v }
|
|
61
|
+
opts.on("--cache-stats", "查看缓存统计") do
|
|
62
|
+
cache = GeoQuery::Cache.new(File.join(GeoQuery::Cache.default_dir, "gen-cache.json"))
|
|
63
|
+
stats = cache.stats
|
|
64
|
+
puts "缓存文件: #{stats['file']}"
|
|
65
|
+
puts "缓存条目: #{stats['total']} 大小: #{stats['size_kb']} KB"
|
|
66
|
+
unless stats["states"].empty?
|
|
67
|
+
puts "状态分布: " + stats["states"].map { |k, v| "#{k}=#{v}" }.join(" ")
|
|
68
|
+
end
|
|
69
|
+
exit 0
|
|
70
|
+
end
|
|
71
|
+
opts.on("-v", "--version", "显示版本") do
|
|
72
|
+
require_relative "../version"
|
|
73
|
+
puts "gen-get #{NetworkInfraUtility::VERSION}"
|
|
74
|
+
exit 0
|
|
75
|
+
end
|
|
76
|
+
opts.on("-h", "--help", "显示帮助") do
|
|
77
|
+
puts opts
|
|
78
|
+
exit 0
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
parser.parse!
|
|
82
|
+
|
|
83
|
+
# IP 来源: 命令行参数, 无参数且 stdin 非 TTY 时逐行读
|
|
84
|
+
ips = ARGV.reject { |a| a.start_with?("-") }.map(&:strip).reject(&:empty?)
|
|
85
|
+
if ips.empty? && !$stdin.tty?
|
|
86
|
+
ips = $stdin.each_line.map(&:strip).reject(&:empty?)
|
|
87
|
+
end
|
|
88
|
+
if ips.empty?
|
|
89
|
+
warn "用法: gen-get <IP> [options] (或管道输入 IP 列表, 详见 --help)"
|
|
90
|
+
exit 1
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
cache = GeoQuery::Cache.new(File.join(GeoQuery::Cache.default_dir, "gen-cache.json"))
|
|
94
|
+
client = GeoQuery::OnlineClient.new(api: options[:api], timeout: options[:timeout])
|
|
95
|
+
|
|
96
|
+
results = ips.map do |ip|
|
|
97
|
+
result = nil
|
|
98
|
+
unless options[:refresh]
|
|
99
|
+
cached = cache.get(ip)
|
|
100
|
+
if cached
|
|
101
|
+
cached["source"] = "gen-get(cache)"
|
|
102
|
+
result = cached
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
result ||= client.lookup(ip)
|
|
106
|
+
cache.put(ip, result) unless options[:no_cache]
|
|
107
|
+
result
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
cache.save unless options[:no_cache]
|
|
111
|
+
|
|
112
|
+
# ---- 输出 -------------------------------------------------------------------
|
|
113
|
+
case options[:format]
|
|
114
|
+
when :json
|
|
115
|
+
puts JSON.pretty_generate(results.size == 1 ? results.first : results)
|
|
116
|
+
when :text
|
|
117
|
+
results.each do |r|
|
|
118
|
+
puts "查询 IP: #{r['ip']} 接口: ip-api.com (免费, 限 45 req/min)"
|
|
119
|
+
puts "-" * 64
|
|
120
|
+
case r["state"]
|
|
121
|
+
when "ok"
|
|
122
|
+
cached = r["cached"] ? "缓存命中" : "实时查询"
|
|
123
|
+
puts "国家: #{r['country']} 省份: #{r['province']} 城市: #{r['city']}"
|
|
124
|
+
puts "运营商: #{r['isp']} 用途: #{r['usage']}"
|
|
125
|
+
puts "ASN: #{r['asn'].empty? ? '未知' : "AS#{r['asn']}"} #{r['asn_org']}"
|
|
126
|
+
puts "状态: ok 来源: #{r['source']} #{cached}"
|
|
127
|
+
when "empty"
|
|
128
|
+
puts "互联网确认无该 IP 归属数据 (#{r['message']})"
|
|
129
|
+
puts "状态: empty 来源: #{r['source']}"
|
|
130
|
+
when "unreachable"
|
|
131
|
+
puts "✗ 互联网查询不可达: #{r['message']}"
|
|
132
|
+
puts "状态: unreachable —— 无法连接互联网查询接口, 返回空结果"
|
|
133
|
+
when "invalid"
|
|
134
|
+
puts "✗ #{r['message']}"
|
|
135
|
+
puts "状态: invalid"
|
|
136
|
+
end
|
|
137
|
+
puts
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# 退出码取最严重: unreachable(2) > invalid(1) > 其他(0)
|
|
142
|
+
exit 2 if results.any? { |r| r["state"] == "unreachable" }
|
|
143
|
+
exit 1 if results.any? { |r| r["state"] == "invalid" }
|
|
144
|
+
exit 0
|
data/bin/geo-api
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
# 用法:
|
|
12
12
|
# geo-api # 默认 ./geodb/ + 0.0.0.0:9292
|
|
13
13
|
# geo-api -d /data/ipdb # 指定数据文件目录
|
|
14
|
+
# geo-api -d /data/ipdb -a ./GEO_CACHE # 外带 GEO_CACHE 缓存目录
|
|
14
15
|
# geo-api -p 8080 # 指定端口
|
|
15
16
|
# geo-api -b 127.0.0.1 -p 9292 -d ./ipdb
|
|
16
17
|
# geo-api --server puma # 指定 Rack 服务器
|
|
@@ -18,6 +19,9 @@
|
|
|
18
19
|
#
|
|
19
20
|
# 选项:
|
|
20
21
|
# -d, --data-dir DIR IP 数据文件目录,默认 ./geodb/
|
|
22
|
+
# -a, --cache-dir DIR GEO_CACHE 外带缓存目录 (geocacheYYYYMMDD.json,
|
|
23
|
+
# ngeo-get -c 产出; addr 查询兑底)
|
|
24
|
+
# --priority ORDER 查询顺位 local,cache 的排列 (默认 local,cache)
|
|
21
25
|
# -b, --host HOST 监听地址,默认 0.0.0.0
|
|
22
26
|
# -p, --port PORT 监听端口,默认 9292
|
|
23
27
|
# -s, --server NAME Rack 服务器,默认 puma
|
|
@@ -29,6 +33,13 @@
|
|
|
29
33
|
# country-IPv4.json / country-IPv6.json / city-IPv6.json / geo-city.json /
|
|
30
34
|
# geo-country.json)。目录不存在或没有任何 *.json 文件将直接报错退出。
|
|
31
35
|
# 查询时若具体文件缺失,对应接口容错返回 404 无结果,不影响其他接口。
|
|
36
|
+
#
|
|
37
|
+
# 缓存目录说明:
|
|
38
|
+
# -a 指定的 GEO_CACHE 目录内存放 geocacheYYYYMMDD.json (客户端 ngeo-get
|
|
39
|
+
# 查询产出)。addr 查询按 --priority 顺位 (默认 local,cache) 编排:
|
|
40
|
+
# 先查本地 GeoLite2 库,查不到时用缓存定位信息兑底。目录不存在仅警告
|
|
41
|
+
# 不退出 (首次使用可能尚无产出文件,后续创建自动感知); 目录内文件
|
|
42
|
+
# 增删改自动感知,无需重启。缓存命中响应附 "cached": true 标记。
|
|
32
43
|
|
|
33
44
|
require "optparse"
|
|
34
45
|
require "rackup"
|
|
@@ -40,6 +51,8 @@ $LOAD_PATH.unshift File.join(ROOT, "service", "geodb")
|
|
|
40
51
|
# 命令行默认值
|
|
41
52
|
options = {
|
|
42
53
|
data_dir: nil,
|
|
54
|
+
cache_dir: nil,
|
|
55
|
+
priority: nil,
|
|
43
56
|
host: "0.0.0.0",
|
|
44
57
|
port: 9292,
|
|
45
58
|
server: "puma"
|
|
@@ -56,6 +69,8 @@ parser = OptionParser.new do |opts|
|
|
|
56
69
|
选项:
|
|
57
70
|
BAN
|
|
58
71
|
opts.on("-d", "--data-dir DIR", "IP 数据文件目录,默认 ./geodb/") { |v| options[:data_dir] = v }
|
|
72
|
+
opts.on("-a", "--cache-dir DIR", "GEO_CACHE 外带缓存目录 (geocache*.json, 查询兑底)") { |v| options[:cache_dir] = v }
|
|
73
|
+
opts.on("--priority ORDER", "查询顺位 local,cache 排列 (默认 local,cache)") { |v| options[:priority] = v }
|
|
59
74
|
opts.on("-b", "--host HOST", "监听地址,默认 0.0.0.0") { |v| options[:host] = v }
|
|
60
75
|
opts.on("-p", "--port PORT", Integer, "监听端口,默认 9292") { |v| options[:port] = v }
|
|
61
76
|
opts.on("-s", "--server NAME", "Rack 服务器,默认 puma") { |v| options[:server] = v }
|
|
@@ -94,10 +109,34 @@ if json_files.empty?
|
|
|
94
109
|
exit 1
|
|
95
110
|
end
|
|
96
111
|
|
|
112
|
+
# ---- 缓存目录与顺位 ---------------------------------------------------------
|
|
113
|
+
cache_order = options[:priority] || "local,cache"
|
|
114
|
+
parts = cache_order.split(/[,\s]+/).map(&:strip).reject(&:empty?)
|
|
115
|
+
unless !parts.empty? && parts.all? { |p| %w[local cache].include?(p) } && parts.uniq.size == parts.size
|
|
116
|
+
warn "✗ 无效顺位: #{cache_order} (geo-api 仅支持 local / cache 的排列)"
|
|
117
|
+
warn " 示例: --priority local,cache (默认, 先本地库再缓存)"
|
|
118
|
+
exit 1
|
|
119
|
+
end
|
|
120
|
+
ENV["GEODB_CACHE_ORDER"] = parts.join(",")
|
|
121
|
+
|
|
122
|
+
cache_dir = nil
|
|
123
|
+
if options[:cache_dir]
|
|
124
|
+
cache_dir = File.expand_path(options[:cache_dir])
|
|
125
|
+
# 目录不存在仅警告: 首次使用可能尚无产出文件, 后续创建自动感知
|
|
126
|
+
unless File.directory?(cache_dir)
|
|
127
|
+
warn "⚠ 缓存目录不存在: #{cache_dir} (将继续启动, 目录创建后自动感知)"
|
|
128
|
+
end
|
|
129
|
+
ENV["GEODB_CACHE_DIR"] = cache_dir
|
|
130
|
+
end
|
|
131
|
+
|
|
97
132
|
require_relative "../version"
|
|
98
|
-
puts "╭─ geo-api #{NetworkInfraUtility::VERSION}
|
|
133
|
+
puts "╭─ geo-api #{NetworkInfraUtility::VERSION} ─────────────────────────────"
|
|
99
134
|
puts "│ 数据目录: #{data_dir} "
|
|
100
135
|
puts "│ #{json_files.length}个数据文件:#{json_files.map { |f| File.basename(f) }.join(' ')}"
|
|
136
|
+
if cache_dir
|
|
137
|
+
n_cache = Dir.glob(File.join(cache_dir, "geocache*.json")).select { |f| /\Ageocache\d{8}\.json\z/ =~ File.basename(f) }.size
|
|
138
|
+
puts "│ 缓存目录: #{cache_dir} (#{n_cache}个缓存文件, 兑底顺位: #{parts.join(',')})"
|
|
139
|
+
end
|
|
101
140
|
puts "│ 监听: http://#{options[:host]}:#{options[:port]}"
|
|
102
141
|
puts "│ 服务: #{options[:server]}"
|
|
103
142
|
puts "╰─ Ctrl+C 退出 ─────────────────────────────────────"
|
data/bin/geo-get
CHANGED
|
@@ -7,6 +7,10 @@
|
|
|
7
7
|
# 向运行中的 geo-api 服务发起查询,一次输入 IP,同时拉取 country / city /
|
|
8
8
|
# asn 三类归属信息并汇总输出。默认展示文字,加 -j 则打印 JSON。
|
|
9
9
|
#
|
|
10
|
+
# 本命令为 GeoQuery::LocalClient (service/geoquery/local.rb) 的瘦封装:
|
|
11
|
+
# 三接口拉取与汇总逻辑统一收口在 LocalClient,与 ngeo-get / gen-get
|
|
12
|
+
# 共享同一套客户端实现,消除双实现。
|
|
13
|
+
#
|
|
10
14
|
# 用法:
|
|
11
15
|
# geo-get 1.181.240.251 # 默认三接口齐查, 文字格式
|
|
12
16
|
# geo-get 8.8.8.8 -j # JSON 格式输出
|
|
@@ -24,16 +28,14 @@
|
|
|
24
28
|
# -v, --version 显示版本
|
|
25
29
|
#
|
|
26
30
|
# 默认连 http://127.0.0.1:9292 (geo-api 默认监听)。
|
|
31
|
+
# 可用环境变量 GEO_API_BASE 覆盖服务地址。
|
|
27
32
|
# 服务未运行时给出明确提示与启动命令。
|
|
28
33
|
|
|
29
34
|
require "optparse"
|
|
30
|
-
require "net/http"
|
|
31
35
|
require "json"
|
|
32
|
-
|
|
36
|
+
require_relative "../service/geoquery/geoquery"
|
|
33
37
|
|
|
34
|
-
|
|
35
|
-
PORT = 9292
|
|
36
|
-
BASE = "http://#{HOST}:#{PORT}"
|
|
38
|
+
BASE = ENV["GEO_API_BASE"] || GeoQuery::LocalClient::DEFAULT_BASE
|
|
37
39
|
|
|
38
40
|
# ---- 命令行解析 -------------------------------------------------------------
|
|
39
41
|
options = { format: :text, only: nil }
|
|
@@ -70,59 +72,39 @@ parser.parse!
|
|
|
70
72
|
ip = ARGV.find { |a| !a.start_with?("-") } || "1.181.240.251"
|
|
71
73
|
|
|
72
74
|
only = options[:only]
|
|
73
|
-
|
|
74
|
-
do_city = only.nil? || only == :city
|
|
75
|
-
do_asn = only.nil? || only == :asn
|
|
75
|
+
endpoints = only ? [only] : GeoQuery::LocalClient::ENDPOINTS
|
|
76
76
|
|
|
77
|
-
# ----
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
uri = URI("#{base}#{path}")
|
|
81
|
-
uri.query = URI.encode_www_form(params) unless params.empty?
|
|
82
|
-
res = Net::HTTP.get_response(uri)
|
|
83
|
-
{ code: res.code.to_i, body: (JSON.parse(res.body) rescue res.body) }
|
|
84
|
-
rescue Errno::ECONNREFUSED
|
|
85
|
-
abort "✗ 无法连接 #{base} —— geo-api 服务未启动。\n 启动命令: geo-api -d ./geodb"
|
|
86
|
-
rescue => e
|
|
87
|
-
abort "✗ 请求失败: #{e.class}: #{e.message}"
|
|
88
|
-
end
|
|
77
|
+
# ---- 查询 (LocalClient 底座) ------------------------------------------------
|
|
78
|
+
client = GeoQuery::LocalClient.new(base: BASE)
|
|
79
|
+
raw = client.fetch_raw(ip, endpoints: endpoints)
|
|
89
80
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
results[:city] = fetch(BASE, "/geo/city", addr: ip) if do_city
|
|
94
|
-
results[:asn] = fetch(BASE, "/geo/asn", addr: ip) if do_asn
|
|
81
|
+
if raw == :unreachable
|
|
82
|
+
abort "✗ 无法连接 #{BASE} —— geo-api 服务未启动。\n 启动命令: geo-api -d ./geodb"
|
|
83
|
+
end
|
|
95
84
|
|
|
96
85
|
# ---- 输出: --json / --text 两路分发 -----------------------------------------
|
|
97
86
|
case options[:format]
|
|
98
87
|
when :json
|
|
99
88
|
# JSON 格式: 把每个接口的响应原样放进一个对象打印。
|
|
100
89
|
# 命中的接口放解析后的 body; 404/无结果放 null, 不逐条刷错误。
|
|
101
|
-
|
|
102
|
-
results.each do |key, r|
|
|
103
|
-
payload[key] = (r[:code] == 200 ? r[:body] : nil)
|
|
104
|
-
end
|
|
105
|
-
puts JSON.pretty_generate(payload)
|
|
90
|
+
puts JSON.pretty_generate(raw.transform_keys(&:to_s))
|
|
106
91
|
when :text
|
|
107
92
|
puts "查询 IP: #{ip} 范围: #{only ? "仅 #{only}" : 'country / city / asn 三接口齐查'} 服务: #{BASE}"
|
|
108
93
|
puts "-" * 60
|
|
109
94
|
|
|
110
95
|
hits = []
|
|
111
|
-
if
|
|
112
|
-
d = r[:body]
|
|
96
|
+
if (d = raw[:country])
|
|
113
97
|
g = d["geoname"]; rc = d["registered_country"]
|
|
114
98
|
country = (g && g["country_name"]) || (rc && rc["country_name"]) || "未知"
|
|
115
99
|
hits << "[国家] #{country} (#{(g && g["country_iso_code"]) || (rc && rc["country_iso_code"])}) 网段 #{d["network"]}"
|
|
116
100
|
end
|
|
117
|
-
if
|
|
118
|
-
d = r[:body]
|
|
101
|
+
if (d = raw[:city])
|
|
119
102
|
g = d["geoname"]
|
|
120
103
|
city = (g && (g["city_namezh"] || g["city_name"])) || "未知"
|
|
121
104
|
coord = g && g["latitude"] && g["longitude"] ? "#{g["latitude"]}, #{g["longitude"]}" : "无"
|
|
122
105
|
hits << "[城市] #{city} 坐标 #{coord} 网段 #{d["network"]}"
|
|
123
106
|
end
|
|
124
|
-
if
|
|
125
|
-
d = r[:body]
|
|
107
|
+
if (d = raw[:asn])
|
|
126
108
|
hits << "[ASN] AS#{d["autonomous_system_number"]} #{d["autonomous_system_organization"]} 网段 #{d["network"]}"
|
|
127
109
|
end
|
|
128
110
|
|