cyberspacesec/apnic-skills
GitHub: cyberspacesec/apnic-skills
全面封装 APNIC 公共数据服务的 Go SDK,提供 IP/ASN 统计、RDAP 查询、BGP 路由分析、IRR 数据库等接口,内置反爬虫与分块下载能力。
Stars: 0 | Forks: 0
# apnic-skills
[](README.zh-CN.md) | **English**
一个用于 APNIC (Asia-Pacific Network Information Centre) 公共数据服务的综合 Go SDK,全面覆盖所有 APNIC 数据端点和查询功能。
```
graph TB
subgraph SDK["apnic-skills SDK"]
direction TB
Client["Client
HTTP + Anti-scraping
+ Chunked Download"] subgraph Data["Data Fetchers"] Stats["Stats
delegated/extended
assigned/ipv6-assigned
legacy"] BGP["Thyme BGP
summary/raw-table
asn-map/bad-prefixes
used-autnums/spar"] RDAP["RDAP
ip/cidr/asn
domain/entity
search/help"] Whois["Whois + Reverse DNS"] IRR["IRR Database
inetnum/autnum/route
19 object types"] RPKI["RPKI/RRDP
notification/snapshot
delta"] REx["REx Cross-RIR
resources/holder
network/count"] Transfers["Transfers + Changes
+ telemetry"] end subgraph Features["Built-in Features"] Stealth["Browser Mimicry
Headers + Jitter"] Chunked["Chunked Download
Range requests
2MiB blocks ×4"] Cache["Token Bucket Cache
30min TTL default"] Filter["Chain Filtering
country/type/status
date range"] Verify["Data Integrity
MD5 + PGP signature"] end Client --> Data Client --> Features end subgraph CLI["CLI (cobra)"] Commands["24 Subcommands
Full SDK coverage"] Flags["Global Flags
--stealth --json
--bgp-source --date"] end CLI --> SDK subgraph APNIC["APNIC Services"] FTP["ftp.apnic.net
Stats/IRR/Transfers"] RDAP_Svc["rdap.apnic.net"] Thyme["thyme.apnic.net
BGP analysis"] RExAPI["api.rex.apnic.net
Cross-RIR registry"] WhoisSvc["whois.apnic.net:43"] end SDK --> APNIC ``` ## 安装说明 ``` go get github.com/cyberspacesec/apnic-skills ``` ## 快速开始 ``` package main import ( "context" "fmt" "log" apnic "github.com/cyberspacesec/apnic-skills" ) func main() { client := apnic.NewClient() ctx := context.Background() // RDAP IP lookup network, err := client.RDAPLookupIP(ctx, "1.1.1.1") if err != nil { log.Fatal(err) } fmt.Printf("Network: %s, Country: %s, Type: %s\n", network.Handle, network.Country, network.Type) // Fetch Delegated Stats entries, err := client.GetDelegatedEntries(ctx) if err != nil { log.Fatal(err) } fmt.Printf("Total entries: %d\n", len(entries)) // Chain filtering result := apnic.NewFilter(entries). ByCountry("CN"). ByType("ipv4"). ByStatus("allocated"). Result() fmt.Printf("CN allocated IPv4 entries: %d\n", len(result)) } ``` ## 架构概述 ``` flowchart LR subgraph Input["User Input"] CLI["CLI Command
apnic [flags]"]
SDK["SDK Method
client.FetchX(ctx, ...)"] end subgraph Core["SDK Core"] HTTP["HTTP Client
doHTTPRequest()"] Stealth["Browser Headers
applyBrowserHeaders()"] RateLimit["Token Bucket
waitRateLimit()"] Jitter["Random Delay
jitter()"] end subgraph Download["Download Strategy"] Probe["Range Probe
GET Range:0-0"] Chunked["Chunked Download
2MiB × N workers"] Single["Single Connection
Fallback"] end subgraph Output["Output"] Parse["Parser
parseXFull()"] JSON["JSON Output
--json flag"] Human["Human-readable
Tabular format"] end CLI --> SDK --> HTTP HTTP --> Stealth --> RateLimit --> Jitter Jitter --> Probe Probe -->|supports Range| Chunked --> Parse Probe -->|no Range| Single --> Parse Parse -->|flagJSON| JSON Parse -->|default| Human ``` ## API 概述 ### 1. Delegated Stats (IP/ASN 分配记录) ``` flowchart TB subgraph APNIC_FTP["APNIC FTP"] Latest["delegated-apnic-latest"] Archived["delegated-apnic-YYYYMMDD"] ByYear["delegated-apnic-YYYY"] end FetchDelegatedEntries --> Latest FetchDelegatedEntriesByDate --> Archived FetchDelegatedByYear --> ByYear subgraph Models["Result Models"] Entry["DelegatedEntry
registry|cc|type|start|value|date|status|opaque-id"] Result["DelegatedResult
Header + Summary + Entries"] end Latest --> Entry Archived --> Result ``` | 方法 | 描述 | |--------|-------------| | `FetchDelegatedEntries(ctx)` | 获取最新的标准 delegated stats | | `GetDelegatedEntries(ctx)` | 缓存的标准 delegated stats | | `FetchDelegatedEntriesByDate(ctx, date)` | 按日期获取 delegated stats | | `FetchDelegatedResult(ctx, date)` | 包含头部/摘要的完整结果 | | `FetchDelegatedByYear(ctx, year)` | 按年份获取 | | `FetchDelegatedResultByYear(ctx, year)` | 按年份获取完整结果 | ### 2. Extended Delegated Stats (包含组织 Opaque-IDs) | 方法 | 描述 | |--------|-------------| | `FetchExtendedEntries(ctx)` | 获取最新的 extended stats | | `GetExtendedEntries(ctx)` | 缓存的 extended stats | | `FetchExtendedEntriesByDate(ctx, date)` | 按日期获取 | | `FetchExtendedResult(ctx, date)` | 包含头部/摘要的完整结果 | | `FetchExtendedByYear(ctx, year)` | 按年份获取 | | `FetchExtendedResultByYear(ctx, year)` | 按年份获取完整结果 | ### 3. Assigned Stats (按前缀大小聚合) | 方法 | 描述 | |--------|-------------| | `FetchAssignedEntries(ctx)` | 获取最新的 assigned stats | | `GetAssignedEntries(ctx)` | 缓存的 assigned stats | | `FetchAssignedEntriesByDate(ctx, date)` | 按日期获取 | | `FetchAssignedResult(ctx, date)` | 包含头部/摘要的完整结果 | ### 3b. IPv6 Assigned Stats (每个前缀的 IPv6 记录) | 方法 | 描述 | |--------|-------------| | `FetchIPv6AssignedEntries(ctx)` | 获取最新的每个前缀 IPv6 记录 | | `FetchIPv6AssignedEntriesByDate(ctx, date)` | 按日期获取 | | `FetchIPv6AssignedResult(ctx, date)` | 包含头部/摘要的完整结果 | ### 4. Legacy Stats (历史遗留资源) | 方法 | 描述 | |--------|-------------| | `FetchLegacyEntries(ctx)` | 获取最新的 legacy 记录 | | `GetLegacyEntries(ctx)` | 缓存的 legacy 记录 | | `FetchLegacyEntriesByDate(ctx, date)` | 按日期获取 | | `FetchLegacyResult(ctx, date)` | 包含头部/摘要的完整结果 | ### 5. RDAP 查询 (结构化注册数据) ``` flowchart TB subgraph RDAP_Lookups["RDAP Lookups"] IP["RDAPLookupIP(ctx, ip)"] CIDR["RDAPLookupCIDR(ctx, cidr)"] ASN["RDAPLookupASN(ctx, asn)"] Domain["RDAPLookupDomain(ctx, domain)"] Entity["RDAPLookupEntity(ctx, handle)"] end subgraph RDAP_Search["RDAP Search"] Search["RDAPSearch(ctx, query)"] SearchEntities["RDAPSearchEntities(ctx, field, query)"] SearchDomains["RDAPSearchDomains(ctx, name)"] end subgraph RDAP_Meta["RDAP Meta"] Help["RDAPHelp(ctx)"] end subgraph RDAP_At["Point-in-time (*At)"] IPAt["RDAPLookupIPAt(ctx, ip, date)"] CIDRAt["RDAPLookupCIDRAt(ctx, cidr, date)"] ASNAt["RDAPLookupASNAt(ctx, asn, date)"] DomainAt["RDAPLookupDomainAt(ctx, domain, date)"] EntityAt["RDAPLookupEntityAt(ctx, handle, date)"] end RDAP_Lookups -->|"--date flag"| RDAP_At ``` | 方法 | 描述 | |--------|-------------| | `RDAPLookupIP(ctx, ip)` | RDAP IP 地址查询 | | `RDAPLookupCIDR(ctx, cidr)` | RDAP CIDR 块查询 | | `RDAPLookupASN(ctx, asn)` | RDAP ASN 查询 | | `RDAPLookupDomain(ctx, domain)` | RDAP 域名查询 (反向 DNS) | | `RDAPLookupEntity(ctx, handle)` | RDAP 实体/联系人查询 | | `RDAPSearch(ctx, query)` | RDAP 实体名称搜索 (等同于 `RDAPSearchEntities(ctx, "fn", query)`) | | `RDAPSearchEntities(ctx, field, query)` | RDAP 实体搜索: `field="fn"` (名称, 支持通配符 `*`) / `field="handle"` (精确匹配) | | `RDAPHelp(ctx)` | RDAP `/help`: 服务器能力 (rdapConformance) 和通知 | | `RDAPSearchDomains(ctx, name)` | RDAP `/domains`: 按名称搜索 in-addr.arpa 域名 | 所有 RDAP 查询都有 `*At` 变体 (例如 `RDAPLookupIPAt(ctx, ip, date)`),用于特定时间点的历史查询 (RFC3339),基于 APNIC `history_version_0` 扩展,返回该 UTC 时刻的资源状态。 ### 6. Transfers (IP/ASN 转移记录) | 方法 | 描述 | |--------|-------------| | `FetchTransfers(ctx)` | 获取最新的转移记录 (每日 JSON 快照) | | `GetTransfers(ctx)` | 缓存的转移记录 | | `FetchTransfersByYear(ctx, year)` | 按年份获取转移记录 | | `FetchTransfersAll(ctx, date)` | 获取累计 transfers-all (管道符分隔,包含自 2010 年以来的所有转移);`date=""` 获取最新,`YYYYMMDD` 获取归档 | | `FetchTransfersAllMD5(ctx, date)` | transfers-all 的 MD5 校验和 | | `FetchTransfersAllASC(ctx, date)` | transfers-all 的 PGP 签名 (.asc) | ### 7. Changes (资源变更记录) | 方法 | 描述 | |--------|-------------| | `FetchChanges(ctx)` | 获取最新的变更记录 | | `GetChanges(ctx)` | 缓存的变更记录 | | `FetchChangesByDate(ctx, date)` | 按日期获取变更记录 | ### 7b. Whois/RDAP 服务遥测 | 方法 | 描述 | |--------|-------------| | `FetchTelemetry(ctx, date)` | 获取 whois-rdap-stats 遥测数据 (每小时):查询量、类型分布、热门 ASN;`date=""` 获取最新 | | `FetchTelemetryMD5(ctx, date)` | 遥测 JSON 的 MD5 校验和 | ### 7c. IRR Database Dump (RPSL) ``` flowchart LR subgraph IRR_Types["IRR Object Types (19)"] inetnum["inetnum
IP allocation"] inet6num["inet6num
IPv6 allocation"] autnum["aut-num
ASN"] route["route
IPv4 prefix"] route6["route6
IPv6 prefix"] domain["domain
reverse DNS delegation"] as_set["as-set
AS group"] route_set["route-set
prefix group"] peering_set["peering-set"] other["... (19 types)"] end FetchIRRDatabase --> IRR_Types ``` | 方法 | 描述 | |--------|-------------| | `FetchIRRDatabase(ctx, objType)` | 获取并解析 IRR 数据库转储 (`apnic.db..gz`),`objType` 包含在 `IRRObjectTypes` 中 (19 种类型) |
| `GetIRRDatabase(ctx, objType)` | 缓存的 IRR 数据库转储 |
| `FetchIRRCurrentSerial(ctx)` | 获取 `APNIC.CURRENTSERIAL` (当前 IRR 序列号) |
### 7d. thyme BGP 路由表分析
```
flowchart TB
subgraph Thyme_Sources["thyme Data Sources"]
Current["current
Global view (default)"] AU["au
Brisbane"] HK["hk
HKIX"] end subgraph Thyme_Files["thyme Files"] Summary["data-summary
Key metrics
(entries, AS count,
ROA coverage, etc.)"] RawTable["data-raw-table
Every announced route
prefix → ASN"] BadPfx["data-badpfx-nos
Prefixes longer than /24
+ origin AS"] PfxNos["data-pfx-nos
Prefix count by length"] UsedAutnums["data-used-autnums
All in-use ASN
+ name + country"] SPAR["data-spar
RFC 6890
Special-purpose prefixes"] SinglePfx["data-singlepfx
ASN count
(<20 prefixes)"] end Current --> Thyme_Files AU --> Thyme_Files HK --> Thyme_Files FetchBGPSummary --> Summary FetchBGPRawTable --> RawTable FetchBGPASNMap -->|"local aggregation"| RawTable FetchBGPBadPrefixes --> BadPfx FetchBGPPerPrefixLength --> PfxNos FetchBGPUsedAutnums --> UsedAutnums FetchBGPSparPrefixes --> SPAR FetchBGPSinglePfx --> SinglePfx ``` | 方法 | 描述 | |--------|-------------| | `FetchBGPSummary(ctx)` | 获取 thyme `data-summary`:BGP 表指标 (条目数、AS 数量、ROA 覆盖率、地址空间百分比、冒号分隔的键值对) | | `FetchBGPRawTable(ctx)` | 获取 thyme `data-raw-table`:每条公告的路由 `prefix\tASN` | | `FetchBGPASNMap(ctx)` | 按源 ASN 聚合原始表 (本地推导,无额外请求) | | `FetchBGPBadPrefixes(ctx, source)` | 获取 thyme `data-badpfx-nos`:长于 /24 且带有源 AS 的前缀 (路由泄漏候选) | | `FetchBGPPerPrefixLength(ctx, source)` | 获取 thyme `data-pfx-nos`:按前缀长度的前缀计数 (/N:count 网格) | | `FetchBGPUsedAutnums(ctx, source)` | 获取 thyme `data-used-autnums`:所有正在使用的 ASN 及其注册名称、国家代码 | | `FetchBGPSparPrefixes(ctx, source)` | 获取 thyme `data-spar`:RFC 6890 专用目的地址注册表前缀及其源 AS | | `FetchBGPSinglePfx(ctx, source)` | 获取 thyme `data-singlepfx`:公告少于 20 个前缀的 ASN 计数 (按 RIR 分组) | ### 7e. RPKI / RRDP ``` flowchart LR subgraph RRDP_Files["RRDP Files"] Notification["notification.xml
session_id + serial
+ snapshot ref + deltas"] Snapshot["snapshot.xml
Full state
(publish/withdraw)"] Delta["delta.xml
Incremental update"] end FetchRRDPNotification --> Notification Notification -->|"URI"| FetchRRDPSnapshot --> Snapshot Notification -->|"URI"| FetchRRDPDelta --> Delta ``` | 方法 | 描述 | |--------|-------------| | `FetchRRDPNotification(ctx)` | 获取 RRDP `notification.xml`:session_id、serial、当前快照引用和增量列表 | | `FetchRRDPSnapshot(ctx, uri)` | 流式解析 RRDP snapshot.xml:仅包含 ``/`` rsync URI,丢弃 base64 CMS 主体 (受限于内存) |
| `FetchRRDPDelta(ctx, uri)` | 流式解析 RRDP delta.xml (增量,格式与快照相同) |
### 7f. REx 跨 RIR 资源注册表
```
flowchart TB
subgraph RIRs["5 RIRs"]
APNIC["APNIC"]
ARIN["ARIN"]
RIPE["RIPE NCC"]
LACNIC["LACNIC"]
AFRINIC["AFRINIC"]
end
subgraph REx["REx API (api.rex.apnic.net)"]
Network["network
Auto-detect caller's
covering prefix + ASN"] Resources["resources
Recent delegated
ipv4|ipv6|asn"] Holder["holder
Aggregate by
opaqueId + rir"] Count["count
Unique holder
count (5 RIR)"] end RIRs -->|"aggregated"| REx FetchRExUserNetwork --> Network FetchRExResources --> Resources FetchRExHolder --> Holder FetchRExHoldersUniqueCount --> Count ``` REx (Resource EXplorer, `api.rex.apnic.net/v1/*`) 是 APNIC 提供的公共 REST API,它将五个 RIR (APNIC/ARIN/RIPE/LACNIC/AFRINIC) 的 delegated 资源聚合到一个统一视图中,并按资源持有者 (opaqueId) 分组。此功能无法被单个 RIR 的 stats/RDAP 替代: | 方法 | 描述 | |--------|-------------| | `FetchRExUserNetwork(ctx)` | 自定位网络:基于调用者的源 IP 返回覆盖前缀、源 ASN、经济代码 (无需参数) | | `FetchRExResources(ctx, type)` | 跨 RIR 的近期 delegated 资源 (带有持有者属性),`type` 可为 `ipv4`/`ipv6`/`asn` 或留空 | | `FetchRExHolder(ctx, opaqueID, rir)` | 给定 opaqueId 和所属 RIR,聚合一个组织持有的所有 ASN 和前缀 | | `FetchRExHoldersUniqueCount(ctx)` | 所有 RIR 中唯一持有者的总数 | ### 8. Whois 查询 | 方法 | 描述 | |--------|-------------| | `QueryWhois(ctx, query)` | 原始 Whois 查询 | | `QueryWhoisIP(ctx, ip)` | IP 地址 Whois 查询 (解析后的结果) | | `QueryWhoisASN(ctx, asn)` | ASN Whois 查询 (解析后的结果) | | `QueryWhoisWithFlags(ctx, query, flags)` | 带 flags 的 Whois 查询 | | `ParseWhoisResponse(response)` | 解析 Whois 响应文本 | ### 9. 反向 DNS | 方法 | 描述 | |--------|-------------| | `ReverseDNS(ctx, ip)` | IP 反向 DNS (PTR) 查询 | ### 10. 历史数据 | 方法 | 描述 | |--------|-------------| | `FetchHistoricalDelegated(ctx, date)` | 按日期获取历史 delegated 数据 | | `FetchHistoricalExtended(ctx, date)` | 按日期获取历史 extended 数据 | | `FetchHistoricalAssigned(ctx, date)` | 按日期获取历史 assigned stats | | `FetchHistoricalLegacy(ctx, date)` | 按日期获取历史 legacy 数据 | | `FetchDelegatedByYear(ctx, year)` | 按年份获取 delegated 数据 | | `FetchExtendedByYear(ctx, year)` | 按年份获取 extended 数据 | | `ListAvailableYears()` | 列出可用的历史数据年份 | ### 11. 数据完整性验证 ``` flowchart LR subgraph Verify_Flow["Verification Flow"] Data["Download
data file"] MD5_File["Download
.md5 file"] ASC_File["Download
.asc file"] PubKey["Fetch
CURRENT_PUBLIC_KEY"] Calc["Compute
local MD5"] Compare["Compare
MD5 strings"] PGP_Verify["PGP
signature verify"] end Data --> Calc --> Compare MD5_File --> Compare ASC_File --> PGP_Verify PubKey --> PGP_Verify Data --> PGP_Verify ``` | 方法 | 描述 | |--------|-------------| | `VerifyMD5(ctx, dataType, date)` | 端到端验证:下载数据文件 + MD5 附带文件,本地计算并比对 | | `FetchMD5Checksum(ctx, dataType, date)` | 获取 MD5 校验和 (兼容 BSD `MD5 (file) =` 和 GNU 样式) | | `FetchASCSignature(ctx, dataType, date)` | 获取 PGP 签名 (.asc) | | `FetchPublicKey(ctx)` | 获取 APNIC 签名公钥 (CURRENT_PUBLIC_KEY) | ### 12. 过滤与分组 | 方法 | 描述 | |--------|-------------| | `FilterEntries(entries, country, resType)` | 按国家和类型过滤 | | `FilterByStatus(entries, status)` | 按状态过滤 | | `FilterByDateRange(entries, start, end)` | 按日期范围过滤 | | `FilterExtendedByOpaqueID(entries, opaqueID)` | 按组织 ID 过滤 | | `FilterExtendedByCountry(entries, country)` | 按国家过滤 extended | | `FilterExtendedByType(entries, resType)` | 按类型过滤 extended | | `FilterExtendedByStatus(entries, status)` | 按状态过滤 extended | | `GroupByCountry(entries)` | 按国家分组 | | `GroupExtendedByOpaqueID(entries)` | 按组织分组 | | `GroupExtendedByCountry(entries)` | 按国家分组 extended | ### 13. 链式过滤 API ``` flowchart LR Entries["Entries
Input"] subgraph Filter_Chain["Chain Filter"] ByCountry["ByCountry(cn)"] ByType["ByType(ipv4)"] ByStatus["ByStatus(allocated)"] ByDateRange["ByDateRange(start, end)"] end Result["Result
Filtered entries"] Entries --> ByCountry --> ByType --> ByStatus --> ByDateRange --> Result ``` ``` // Standard chain filtering result := apnic.NewFilter(entries). ByCountry("CN"). ByType("ipv4"). ByStatus("allocated"). ByDateRange(start, end). Result() // Extended chain filtering extResult := apnic.NewExtendedFilter(extEntries). ByCountry("JP"). ByType("ipv6"). ByOpaqueID("A92E1062"). Result() ``` ### 14. CIDR 计算 ``` // Standard version cidr, err := entry.CIDR() // Extended version cidr, err := extEntry.CIDR() // Legacy version cidr, err := legacyEntry.CIDR() ``` ## 客户端配置 ``` client := apnic.NewClient( apnic.WithCacheTTL(10 * time.Minute), apnic.WithUserAgent("my-app/1.0"), apnic.WithRDAPBaseURL("https://rdap.apnic.net"), apnic.WithWhoisServer("whois.apnic.net:43"), apnic.WithWhoisTimeout(15 * time.Second), apnic.WithHTTPClient(&http.Client{Timeout: 30 * time.Second}), ) ``` ### 反爬虫与浏览器模拟 (默认开启) 为避免被检测为爬,SDK 默认启用了浏览器模拟中间件:所有 HTTP 出口 (包含 whois 抖动) 都会注入主流的 Chrome 请求头 (UA、Accept-Language、Sec-Fetch-*、Sec-Ch-Ua-* 等),外加令牌桶速率限制和请求间的随机抖动。可通过 Options 进行调整: ``` client := apnic.NewClient( apnic.WithStealth(true), // default true; false sends only UA+Accept (backward compatible) apnic.WithBrowserUserAgent("Mozilla/5.0 ..."), // custom browser UA apnic.WithJitter(200*time.Millisecond, 800*time.Millisecond), // random delay range per request apnic.WithRateLimit(2.0), // global max requests per second (token bucket, 0=unlimited) apnic.WithFTPBaseURL(apnic.DefaultFTPBaseURL), // FTP root for IRR/transfers-all/telemetry apnic.WithRRDPBaseURL(apnic.DefaultRRDPBaseURL), apnic.WithThymeBaseURL(apnic.DefaultThymeBaseURL), apnic.WithRExBaseURL(apnic.DefaultRExBaseURL), // REx cross-RIR registry ) ``` ### 大文件分块下载 (默认开启) APNIC FTP 对大文件进行限流 (delegated 4.3MB、extended、IRR `apnic.db.inetnum.gz` 50MB+),将其限制在 **单连接带宽约 8-22 KB/s**——这不是反爬虫检测 (任何 UA 速度都一样,没有 403,正常的 `accept-ranges: bytes`),而是服务器级别的速率限制。单连接下载 50MB 的 IRR dump 大约需要 40 分钟,远远超出了典型的超时时间。 ``` flowchart TB subgraph Chunked_Download["Chunked Download Flow"] Probe["Probe Range
GET Range:0-0
→ Content-Length
→ supports Range?"] subgraph Strategy["Download Strategy"] Chunked["Chunked
Split into 2MiB
4 workers × N blocks"] Single["Single Connection
Fallback (no Range)"] end subgraph Workers["Concurrent Workers"] W1["Worker 1
Range 0-2MiB"] W2["Worker 2
Range 2MiB-4MiB"] W3["Worker 3
Range 4MiB-6MiB"] W4["Worker 4
Range 6MiB-..."] end Merge["Merge via io.Pipe
Ordered by index"] Decompress["gzip.NewReader
if .gz"] Result["Result
io.Reader"] end Probe -->|supports Range| Chunked --> Workers --> Merge --> Decompress --> Result Probe -->|no Range| Single --> Decompress --> Result ``` ``` client := apnic.NewClient( apnic.WithMaxConcurrentDownloads(4), // concurrent Range requests (0/1 disables chunking) apnic.WithChunkSize(2*1024*1024), // explicit chunk size; default 2MiB apnic.WithDownloadTimeout(5*time.Minute), // per-chunk timeout (recommend ≥ chunk_size/8KBps) ) ``` ## CLI (命令行界面) 本仓库包含一个基于 cobra 的 `apnic` CLI,涵盖了 SDK 的所有功能: ``` graph LR subgraph CLI_Commands["CLI Commands (24)"] Stats["delegated
extended
assigned
ipv6-assigned
legacy
years"] BGP_CLI["bgp summary
bgp raw-table
bgp asn-map
bgp bad-prefixes
bgp used-autnums
bgp spar-prefixes
bgp single-pfx"] RDAP_CLI["rdap ip/cidr/asn
rdap domain/entity
rdap search
rdap domains
rdap help"] Whois_CLI["whois ip/asn/raw"] Reverse["reverse-dns"] IRR_CLI["irr inetnum/autnum
irr route/route6
irr serial"] RPKI_CLI["rpki notification
rpki snapshot"] REx_CLI["rex network
rex resources
rex holder
rex count"] Transfer["transfers
transfers-all"] Change["changes"] Telemetry["stats-telemetry"] Verify["verify integrity
verify md5/asc
verify pubkey"] Filter["filter"] History["history"] end ``` ``` # 构建 go build -o bin/apnic ./cmd/apnic # 示例 apnic delegated --json | jq '.Entries | length' apnic filter --source delegated --country CN --type ipv4 apnic rdap ip 1.1.1.1 --date 2020-06-01T00:00:00Z apnic rdap help # RDAP server capability and notices apnic rdap domains 1 # Search in-addr.arpa reverse DNS domains apnic transfers-all # Cumulative transfer set (since 2010) apnic transfers-all --date 20220904 apnic stats-telemetry # whois/RDAP service query telemetry apnic irr inetnum # IRR database dump (RPSL) apnic irr serial # APNIC.CURRENTSERIAL apnic bgp summary # thyme BGP table analysis metrics apnic bgp raw-table # thyme raw routing table apnic bgp asn-map # Aggregate by origin ASN apnic bgp bad-prefixes # Prefixes longer than /24 + origin AS (route leak candidates) apnic bgp per-prefix-length # Prefix count by prefix length apnic bgp used-autnums # All in-use ASN + name/country apnic bgp spar-prefixes # RFC 6890 Special Purpose Address Registry prefixes apnic bgp single-pfx # ASN count with <20 prefixes (by RIR) apnic bgp --bgp-source au # Use Brisbane data source apnic rpki notification # RRDP notification (session/serial/snapshot/deltas) apnic rpki snapshot # Stream-parse current snapshot apnic rex network # REx self-locate network (covering prefix/ASN/economy by source IP) apnic rex resources ipv4 # Cross-RIR recent delegated resources (holder attribution) apnic rex holder # Aggregate all ASN/prefixes held by one organization
apnic rex count # Unique holder count across all RIRs
apnic verify integrity --type delegated
```
反爬虫全局 flags:`--stealth` (默认为 true)、`--browser-ua`、`--jitter 200ms-800ms`、`--rate-limit` (每秒请求数)、`--ftp-base-url`/`--rrdp-base-url`/`--thyme-base-url`/`--rex-base-url`、`--bgp-source` (thyme BGP 源:`current`/`au`/`hk`,默认为 `current`)。
- 完整的子命令和参数文档详见 [docs/SKILLS.md](docs/SKILLS.md)。
- 多命令工作流示例 (国家资源审计 / IP全景调查 / 转移/变更跟踪 / 数据完整性验证) 详见 [docs/workflows.md](docs/workflows.md)。
## 测试覆盖率
- SDK:**100.0%** 语句覆盖率
- CLI:所有命名函数 **100%** (整体 99.1%,差异在于 `main()` 入口 `osExit(runMain())` 不可达)
## 数据流示例:IP 调查
```
sequenceDiagram
participant User
participant CLI
participant SDK as apnic-skills SDK
participant APNIC as APNIC Services
User->>CLI: apnic rdap ip 1.1.1.1
CLI->>SDK: RDAPLookupIP(ctx, "1.1.1.1")
SDK->>APNIC: GET rdap.apnic.net/ip/1.1.1.1
APNIC-->>SDK: RDAPNetwork JSON
SDK-->>CLI: RDAPNetwork struct
CLI-->>User: Handle/Country/Type/CIDR
User->>CLI: apnic whois ip 1.1.1.1
CLI->>SDK: QueryWhoisIP(ctx, "1.1.1.1")
SDK->>APNIC: whois.apnic.net:43
APNIC-->>SDK: Whois text
SDK-->>CLI: WhoisInfo struct
CLI-->>User: Network/Org/Created/Updated
User->>CLI: apnic reverse-dns 1.1.1.1
CLI->>SDK: ReverseDNS(ctx, "1.1.1.1")
SDK->>APNIC: DNS PTR lookup
APNIC-->>SDK: PTR names
SDK-->>CLI: []string
CLI-->>User: one.one.one.one.
```
## 许可证
MIT
HTTP + Anti-scraping
+ Chunked Download"] subgraph Data["Data Fetchers"] Stats["Stats
delegated/extended
assigned/ipv6-assigned
legacy"] BGP["Thyme BGP
summary/raw-table
asn-map/bad-prefixes
used-autnums/spar"] RDAP["RDAP
ip/cidr/asn
domain/entity
search/help"] Whois["Whois + Reverse DNS"] IRR["IRR Database
inetnum/autnum/route
19 object types"] RPKI["RPKI/RRDP
notification/snapshot
delta"] REx["REx Cross-RIR
resources/holder
network/count"] Transfers["Transfers + Changes
+ telemetry"] end subgraph Features["Built-in Features"] Stealth["Browser Mimicry
Headers + Jitter"] Chunked["Chunked Download
Range requests
2MiB blocks ×4"] Cache["Token Bucket Cache
30min TTL default"] Filter["Chain Filtering
country/type/status
date range"] Verify["Data Integrity
MD5 + PGP signature"] end Client --> Data Client --> Features end subgraph CLI["CLI (cobra)"] Commands["24 Subcommands
Full SDK coverage"] Flags["Global Flags
--stealth --json
--bgp-source --date"] end CLI --> SDK subgraph APNIC["APNIC Services"] FTP["ftp.apnic.net
Stats/IRR/Transfers"] RDAP_Svc["rdap.apnic.net"] Thyme["thyme.apnic.net
BGP analysis"] RExAPI["api.rex.apnic.net
Cross-RIR registry"] WhoisSvc["whois.apnic.net:43"] end SDK --> APNIC ``` ## 安装说明 ``` go get github.com/cyberspacesec/apnic-skills ``` ## 快速开始 ``` package main import ( "context" "fmt" "log" apnic "github.com/cyberspacesec/apnic-skills" ) func main() { client := apnic.NewClient() ctx := context.Background() // RDAP IP lookup network, err := client.RDAPLookupIP(ctx, "1.1.1.1") if err != nil { log.Fatal(err) } fmt.Printf("Network: %s, Country: %s, Type: %s\n", network.Handle, network.Country, network.Type) // Fetch Delegated Stats entries, err := client.GetDelegatedEntries(ctx) if err != nil { log.Fatal(err) } fmt.Printf("Total entries: %d\n", len(entries)) // Chain filtering result := apnic.NewFilter(entries). ByCountry("CN"). ByType("ipv4"). ByStatus("allocated"). Result() fmt.Printf("CN allocated IPv4 entries: %d\n", len(result)) } ``` ## 架构概述 ``` flowchart LR subgraph Input["User Input"] CLI["CLI Command
apnic
client.FetchX(ctx, ...)"] end subgraph Core["SDK Core"] HTTP["HTTP Client
doHTTPRequest()"] Stealth["Browser Headers
applyBrowserHeaders()"] RateLimit["Token Bucket
waitRateLimit()"] Jitter["Random Delay
jitter()"] end subgraph Download["Download Strategy"] Probe["Range Probe
GET Range:0-0"] Chunked["Chunked Download
2MiB × N workers"] Single["Single Connection
Fallback"] end subgraph Output["Output"] Parse["Parser
parseXFull()"] JSON["JSON Output
--json flag"] Human["Human-readable
Tabular format"] end CLI --> SDK --> HTTP HTTP --> Stealth --> RateLimit --> Jitter Jitter --> Probe Probe -->|supports Range| Chunked --> Parse Probe -->|no Range| Single --> Parse Parse -->|flagJSON| JSON Parse -->|default| Human ``` ## API 概述 ### 1. Delegated Stats (IP/ASN 分配记录) ``` flowchart TB subgraph APNIC_FTP["APNIC FTP"] Latest["delegated-apnic-latest"] Archived["delegated-apnic-YYYYMMDD"] ByYear["delegated-apnic-YYYY"] end FetchDelegatedEntries --> Latest FetchDelegatedEntriesByDate --> Archived FetchDelegatedByYear --> ByYear subgraph Models["Result Models"] Entry["DelegatedEntry
registry|cc|type|start|value|date|status|opaque-id"] Result["DelegatedResult
Header + Summary + Entries"] end Latest --> Entry Archived --> Result ``` | 方法 | 描述 | |--------|-------------| | `FetchDelegatedEntries(ctx)` | 获取最新的标准 delegated stats | | `GetDelegatedEntries(ctx)` | 缓存的标准 delegated stats | | `FetchDelegatedEntriesByDate(ctx, date)` | 按日期获取 delegated stats | | `FetchDelegatedResult(ctx, date)` | 包含头部/摘要的完整结果 | | `FetchDelegatedByYear(ctx, year)` | 按年份获取 | | `FetchDelegatedResultByYear(ctx, year)` | 按年份获取完整结果 | ### 2. Extended Delegated Stats (包含组织 Opaque-IDs) | 方法 | 描述 | |--------|-------------| | `FetchExtendedEntries(ctx)` | 获取最新的 extended stats | | `GetExtendedEntries(ctx)` | 缓存的 extended stats | | `FetchExtendedEntriesByDate(ctx, date)` | 按日期获取 | | `FetchExtendedResult(ctx, date)` | 包含头部/摘要的完整结果 | | `FetchExtendedByYear(ctx, year)` | 按年份获取 | | `FetchExtendedResultByYear(ctx, year)` | 按年份获取完整结果 | ### 3. Assigned Stats (按前缀大小聚合) | 方法 | 描述 | |--------|-------------| | `FetchAssignedEntries(ctx)` | 获取最新的 assigned stats | | `GetAssignedEntries(ctx)` | 缓存的 assigned stats | | `FetchAssignedEntriesByDate(ctx, date)` | 按日期获取 | | `FetchAssignedResult(ctx, date)` | 包含头部/摘要的完整结果 | ### 3b. IPv6 Assigned Stats (每个前缀的 IPv6 记录) | 方法 | 描述 | |--------|-------------| | `FetchIPv6AssignedEntries(ctx)` | 获取最新的每个前缀 IPv6 记录 | | `FetchIPv6AssignedEntriesByDate(ctx, date)` | 按日期获取 | | `FetchIPv6AssignedResult(ctx, date)` | 包含头部/摘要的完整结果 | ### 4. Legacy Stats (历史遗留资源) | 方法 | 描述 | |--------|-------------| | `FetchLegacyEntries(ctx)` | 获取最新的 legacy 记录 | | `GetLegacyEntries(ctx)` | 缓存的 legacy 记录 | | `FetchLegacyEntriesByDate(ctx, date)` | 按日期获取 | | `FetchLegacyResult(ctx, date)` | 包含头部/摘要的完整结果 | ### 5. RDAP 查询 (结构化注册数据) ``` flowchart TB subgraph RDAP_Lookups["RDAP Lookups"] IP["RDAPLookupIP(ctx, ip)"] CIDR["RDAPLookupCIDR(ctx, cidr)"] ASN["RDAPLookupASN(ctx, asn)"] Domain["RDAPLookupDomain(ctx, domain)"] Entity["RDAPLookupEntity(ctx, handle)"] end subgraph RDAP_Search["RDAP Search"] Search["RDAPSearch(ctx, query)"] SearchEntities["RDAPSearchEntities(ctx, field, query)"] SearchDomains["RDAPSearchDomains(ctx, name)"] end subgraph RDAP_Meta["RDAP Meta"] Help["RDAPHelp(ctx)"] end subgraph RDAP_At["Point-in-time (*At)"] IPAt["RDAPLookupIPAt(ctx, ip, date)"] CIDRAt["RDAPLookupCIDRAt(ctx, cidr, date)"] ASNAt["RDAPLookupASNAt(ctx, asn, date)"] DomainAt["RDAPLookupDomainAt(ctx, domain, date)"] EntityAt["RDAPLookupEntityAt(ctx, handle, date)"] end RDAP_Lookups -->|"--date flag"| RDAP_At ``` | 方法 | 描述 | |--------|-------------| | `RDAPLookupIP(ctx, ip)` | RDAP IP 地址查询 | | `RDAPLookupCIDR(ctx, cidr)` | RDAP CIDR 块查询 | | `RDAPLookupASN(ctx, asn)` | RDAP ASN 查询 | | `RDAPLookupDomain(ctx, domain)` | RDAP 域名查询 (反向 DNS) | | `RDAPLookupEntity(ctx, handle)` | RDAP 实体/联系人查询 | | `RDAPSearch(ctx, query)` | RDAP 实体名称搜索 (等同于 `RDAPSearchEntities(ctx, "fn", query)`) | | `RDAPSearchEntities(ctx, field, query)` | RDAP 实体搜索: `field="fn"` (名称, 支持通配符 `*`) / `field="handle"` (精确匹配) | | `RDAPHelp(ctx)` | RDAP `/help`: 服务器能力 (rdapConformance) 和通知 | | `RDAPSearchDomains(ctx, name)` | RDAP `/domains`: 按名称搜索 in-addr.arpa 域名 | 所有 RDAP 查询都有 `*At` 变体 (例如 `RDAPLookupIPAt(ctx, ip, date)`),用于特定时间点的历史查询 (RFC3339),基于 APNIC `history_version_0` 扩展,返回该 UTC 时刻的资源状态。 ### 6. Transfers (IP/ASN 转移记录) | 方法 | 描述 | |--------|-------------| | `FetchTransfers(ctx)` | 获取最新的转移记录 (每日 JSON 快照) | | `GetTransfers(ctx)` | 缓存的转移记录 | | `FetchTransfersByYear(ctx, year)` | 按年份获取转移记录 | | `FetchTransfersAll(ctx, date)` | 获取累计 transfers-all (管道符分隔,包含自 2010 年以来的所有转移);`date=""` 获取最新,`YYYYMMDD` 获取归档 | | `FetchTransfersAllMD5(ctx, date)` | transfers-all 的 MD5 校验和 | | `FetchTransfersAllASC(ctx, date)` | transfers-all 的 PGP 签名 (.asc) | ### 7. Changes (资源变更记录) | 方法 | 描述 | |--------|-------------| | `FetchChanges(ctx)` | 获取最新的变更记录 | | `GetChanges(ctx)` | 缓存的变更记录 | | `FetchChangesByDate(ctx, date)` | 按日期获取变更记录 | ### 7b. Whois/RDAP 服务遥测 | 方法 | 描述 | |--------|-------------| | `FetchTelemetry(ctx, date)` | 获取 whois-rdap-stats 遥测数据 (每小时):查询量、类型分布、热门 ASN;`date=""` 获取最新 | | `FetchTelemetryMD5(ctx, date)` | 遥测 JSON 的 MD5 校验和 | ### 7c. IRR Database Dump (RPSL) ``` flowchart LR subgraph IRR_Types["IRR Object Types (19)"] inetnum["inetnum
IP allocation"] inet6num["inet6num
IPv6 allocation"] autnum["aut-num
ASN"] route["route
IPv4 prefix"] route6["route6
IPv6 prefix"] domain["domain
reverse DNS delegation"] as_set["as-set
AS group"] route_set["route-set
prefix group"] peering_set["peering-set"] other["... (19 types)"] end FetchIRRDatabase --> IRR_Types ``` | 方法 | 描述 | |--------|-------------| | `FetchIRRDatabase(ctx, objType)` | 获取并解析 IRR 数据库转储 (`apnic.db.
Global view (default)"] AU["au
Brisbane"] HK["hk
HKIX"] end subgraph Thyme_Files["thyme Files"] Summary["data-summary
Key metrics
(entries, AS count,
ROA coverage, etc.)"] RawTable["data-raw-table
Every announced route
prefix → ASN"] BadPfx["data-badpfx-nos
Prefixes longer than /24
+ origin AS"] PfxNos["data-pfx-nos
Prefix count by length"] UsedAutnums["data-used-autnums
All in-use ASN
+ name + country"] SPAR["data-spar
RFC 6890
Special-purpose prefixes"] SinglePfx["data-singlepfx
ASN count
(<20 prefixes)"] end Current --> Thyme_Files AU --> Thyme_Files HK --> Thyme_Files FetchBGPSummary --> Summary FetchBGPRawTable --> RawTable FetchBGPASNMap -->|"local aggregation"| RawTable FetchBGPBadPrefixes --> BadPfx FetchBGPPerPrefixLength --> PfxNos FetchBGPUsedAutnums --> UsedAutnums FetchBGPSparPrefixes --> SPAR FetchBGPSinglePfx --> SinglePfx ``` | 方法 | 描述 | |--------|-------------| | `FetchBGPSummary(ctx)` | 获取 thyme `data-summary`:BGP 表指标 (条目数、AS 数量、ROA 覆盖率、地址空间百分比、冒号分隔的键值对) | | `FetchBGPRawTable(ctx)` | 获取 thyme `data-raw-table`:每条公告的路由 `prefix\tASN` | | `FetchBGPASNMap(ctx)` | 按源 ASN 聚合原始表 (本地推导,无额外请求) | | `FetchBGPBadPrefixes(ctx, source)` | 获取 thyme `data-badpfx-nos`:长于 /24 且带有源 AS 的前缀 (路由泄漏候选) | | `FetchBGPPerPrefixLength(ctx, source)` | 获取 thyme `data-pfx-nos`:按前缀长度的前缀计数 (/N:count 网格) | | `FetchBGPUsedAutnums(ctx, source)` | 获取 thyme `data-used-autnums`:所有正在使用的 ASN 及其注册名称、国家代码 | | `FetchBGPSparPrefixes(ctx, source)` | 获取 thyme `data-spar`:RFC 6890 专用目的地址注册表前缀及其源 AS | | `FetchBGPSinglePfx(ctx, source)` | 获取 thyme `data-singlepfx`:公告少于 20 个前缀的 ASN 计数 (按 RIR 分组) | ### 7e. RPKI / RRDP ``` flowchart LR subgraph RRDP_Files["RRDP Files"] Notification["notification.xml
session_id + serial
+ snapshot ref + deltas"] Snapshot["snapshot.xml
Full state
(publish/withdraw)"] Delta["delta.xml
Incremental update"] end FetchRRDPNotification --> Notification Notification -->|"URI"| FetchRRDPSnapshot --> Snapshot Notification -->|"URI"| FetchRRDPDelta --> Delta ``` | 方法 | 描述 | |--------|-------------| | `FetchRRDPNotification(ctx)` | 获取 RRDP `notification.xml`:session_id、serial、当前快照引用和增量列表 | | `FetchRRDPSnapshot(ctx, uri)` | 流式解析 RRDP snapshot.xml:仅包含 `
Auto-detect caller's
covering prefix + ASN"] Resources["resources
Recent delegated
ipv4|ipv6|asn"] Holder["holder
Aggregate by
opaqueId + rir"] Count["count
Unique holder
count (5 RIR)"] end RIRs -->|"aggregated"| REx FetchRExUserNetwork --> Network FetchRExResources --> Resources FetchRExHolder --> Holder FetchRExHoldersUniqueCount --> Count ``` REx (Resource EXplorer, `api.rex.apnic.net/v1/*`) 是 APNIC 提供的公共 REST API,它将五个 RIR (APNIC/ARIN/RIPE/LACNIC/AFRINIC) 的 delegated 资源聚合到一个统一视图中,并按资源持有者 (opaqueId) 分组。此功能无法被单个 RIR 的 stats/RDAP 替代: | 方法 | 描述 | |--------|-------------| | `FetchRExUserNetwork(ctx)` | 自定位网络:基于调用者的源 IP 返回覆盖前缀、源 ASN、经济代码 (无需参数) | | `FetchRExResources(ctx, type)` | 跨 RIR 的近期 delegated 资源 (带有持有者属性),`type` 可为 `ipv4`/`ipv6`/`asn` 或留空 | | `FetchRExHolder(ctx, opaqueID, rir)` | 给定 opaqueId 和所属 RIR,聚合一个组织持有的所有 ASN 和前缀 | | `FetchRExHoldersUniqueCount(ctx)` | 所有 RIR 中唯一持有者的总数 | ### 8. Whois 查询 | 方法 | 描述 | |--------|-------------| | `QueryWhois(ctx, query)` | 原始 Whois 查询 | | `QueryWhoisIP(ctx, ip)` | IP 地址 Whois 查询 (解析后的结果) | | `QueryWhoisASN(ctx, asn)` | ASN Whois 查询 (解析后的结果) | | `QueryWhoisWithFlags(ctx, query, flags)` | 带 flags 的 Whois 查询 | | `ParseWhoisResponse(response)` | 解析 Whois 响应文本 | ### 9. 反向 DNS | 方法 | 描述 | |--------|-------------| | `ReverseDNS(ctx, ip)` | IP 反向 DNS (PTR) 查询 | ### 10. 历史数据 | 方法 | 描述 | |--------|-------------| | `FetchHistoricalDelegated(ctx, date)` | 按日期获取历史 delegated 数据 | | `FetchHistoricalExtended(ctx, date)` | 按日期获取历史 extended 数据 | | `FetchHistoricalAssigned(ctx, date)` | 按日期获取历史 assigned stats | | `FetchHistoricalLegacy(ctx, date)` | 按日期获取历史 legacy 数据 | | `FetchDelegatedByYear(ctx, year)` | 按年份获取 delegated 数据 | | `FetchExtendedByYear(ctx, year)` | 按年份获取 extended 数据 | | `ListAvailableYears()` | 列出可用的历史数据年份 | ### 11. 数据完整性验证 ``` flowchart LR subgraph Verify_Flow["Verification Flow"] Data["Download
data file"] MD5_File["Download
.md5 file"] ASC_File["Download
.asc file"] PubKey["Fetch
CURRENT_PUBLIC_KEY"] Calc["Compute
local MD5"] Compare["Compare
MD5 strings"] PGP_Verify["PGP
signature verify"] end Data --> Calc --> Compare MD5_File --> Compare ASC_File --> PGP_Verify PubKey --> PGP_Verify Data --> PGP_Verify ``` | 方法 | 描述 | |--------|-------------| | `VerifyMD5(ctx, dataType, date)` | 端到端验证:下载数据文件 + MD5 附带文件,本地计算并比对 | | `FetchMD5Checksum(ctx, dataType, date)` | 获取 MD5 校验和 (兼容 BSD `MD5 (file) =` 和 GNU 样式) | | `FetchASCSignature(ctx, dataType, date)` | 获取 PGP 签名 (.asc) | | `FetchPublicKey(ctx)` | 获取 APNIC 签名公钥 (CURRENT_PUBLIC_KEY) | ### 12. 过滤与分组 | 方法 | 描述 | |--------|-------------| | `FilterEntries(entries, country, resType)` | 按国家和类型过滤 | | `FilterByStatus(entries, status)` | 按状态过滤 | | `FilterByDateRange(entries, start, end)` | 按日期范围过滤 | | `FilterExtendedByOpaqueID(entries, opaqueID)` | 按组织 ID 过滤 | | `FilterExtendedByCountry(entries, country)` | 按国家过滤 extended | | `FilterExtendedByType(entries, resType)` | 按类型过滤 extended | | `FilterExtendedByStatus(entries, status)` | 按状态过滤 extended | | `GroupByCountry(entries)` | 按国家分组 | | `GroupExtendedByOpaqueID(entries)` | 按组织分组 | | `GroupExtendedByCountry(entries)` | 按国家分组 extended | ### 13. 链式过滤 API ``` flowchart LR Entries["Entries
Input"] subgraph Filter_Chain["Chain Filter"] ByCountry["ByCountry(cn)"] ByType["ByType(ipv4)"] ByStatus["ByStatus(allocated)"] ByDateRange["ByDateRange(start, end)"] end Result["Result
Filtered entries"] Entries --> ByCountry --> ByType --> ByStatus --> ByDateRange --> Result ``` ``` // Standard chain filtering result := apnic.NewFilter(entries). ByCountry("CN"). ByType("ipv4"). ByStatus("allocated"). ByDateRange(start, end). Result() // Extended chain filtering extResult := apnic.NewExtendedFilter(extEntries). ByCountry("JP"). ByType("ipv6"). ByOpaqueID("A92E1062"). Result() ``` ### 14. CIDR 计算 ``` // Standard version cidr, err := entry.CIDR() // Extended version cidr, err := extEntry.CIDR() // Legacy version cidr, err := legacyEntry.CIDR() ``` ## 客户端配置 ``` client := apnic.NewClient( apnic.WithCacheTTL(10 * time.Minute), apnic.WithUserAgent("my-app/1.0"), apnic.WithRDAPBaseURL("https://rdap.apnic.net"), apnic.WithWhoisServer("whois.apnic.net:43"), apnic.WithWhoisTimeout(15 * time.Second), apnic.WithHTTPClient(&http.Client{Timeout: 30 * time.Second}), ) ``` ### 反爬虫与浏览器模拟 (默认开启) 为避免被检测为爬,SDK 默认启用了浏览器模拟中间件:所有 HTTP 出口 (包含 whois 抖动) 都会注入主流的 Chrome 请求头 (UA、Accept-Language、Sec-Fetch-*、Sec-Ch-Ua-* 等),外加令牌桶速率限制和请求间的随机抖动。可通过 Options 进行调整: ``` client := apnic.NewClient( apnic.WithStealth(true), // default true; false sends only UA+Accept (backward compatible) apnic.WithBrowserUserAgent("Mozilla/5.0 ..."), // custom browser UA apnic.WithJitter(200*time.Millisecond, 800*time.Millisecond), // random delay range per request apnic.WithRateLimit(2.0), // global max requests per second (token bucket, 0=unlimited) apnic.WithFTPBaseURL(apnic.DefaultFTPBaseURL), // FTP root for IRR/transfers-all/telemetry apnic.WithRRDPBaseURL(apnic.DefaultRRDPBaseURL), apnic.WithThymeBaseURL(apnic.DefaultThymeBaseURL), apnic.WithRExBaseURL(apnic.DefaultRExBaseURL), // REx cross-RIR registry ) ``` ### 大文件分块下载 (默认开启) APNIC FTP 对大文件进行限流 (delegated 4.3MB、extended、IRR `apnic.db.inetnum.gz` 50MB+),将其限制在 **单连接带宽约 8-22 KB/s**——这不是反爬虫检测 (任何 UA 速度都一样,没有 403,正常的 `accept-ranges: bytes`),而是服务器级别的速率限制。单连接下载 50MB 的 IRR dump 大约需要 40 分钟,远远超出了典型的超时时间。 ``` flowchart TB subgraph Chunked_Download["Chunked Download Flow"] Probe["Probe Range
GET Range:0-0
→ Content-Length
→ supports Range?"] subgraph Strategy["Download Strategy"] Chunked["Chunked
Split into 2MiB
4 workers × N blocks"] Single["Single Connection
Fallback (no Range)"] end subgraph Workers["Concurrent Workers"] W1["Worker 1
Range 0-2MiB"] W2["Worker 2
Range 2MiB-4MiB"] W3["Worker 3
Range 4MiB-6MiB"] W4["Worker 4
Range 6MiB-..."] end Merge["Merge via io.Pipe
Ordered by index"] Decompress["gzip.NewReader
if .gz"] Result["Result
io.Reader"] end Probe -->|supports Range| Chunked --> Workers --> Merge --> Decompress --> Result Probe -->|no Range| Single --> Decompress --> Result ``` ``` client := apnic.NewClient( apnic.WithMaxConcurrentDownloads(4), // concurrent Range requests (0/1 disables chunking) apnic.WithChunkSize(2*1024*1024), // explicit chunk size; default 2MiB apnic.WithDownloadTimeout(5*time.Minute), // per-chunk timeout (recommend ≥ chunk_size/8KBps) ) ``` ## CLI (命令行界面) 本仓库包含一个基于 cobra 的 `apnic` CLI,涵盖了 SDK 的所有功能: ``` graph LR subgraph CLI_Commands["CLI Commands (24)"] Stats["delegated
extended
assigned
ipv6-assigned
legacy
years"] BGP_CLI["bgp summary
bgp raw-table
bgp asn-map
bgp bad-prefixes
bgp used-autnums
bgp spar-prefixes
bgp single-pfx"] RDAP_CLI["rdap ip/cidr/asn
rdap domain/entity
rdap search
rdap domains
rdap help"] Whois_CLI["whois ip/asn/raw"] Reverse["reverse-dns"] IRR_CLI["irr inetnum/autnum
irr route/route6
irr serial"] RPKI_CLI["rpki notification
rpki snapshot"] REx_CLI["rex network
rex resources
rex holder
rex count"] Transfer["transfers
transfers-all"] Change["changes"] Telemetry["stats-telemetry"] Verify["verify integrity
verify md5/asc
verify pubkey"] Filter["filter"] History["history"] end ``` ``` # 构建 go build -o bin/apnic ./cmd/apnic # 示例 apnic delegated --json | jq '.Entries | length' apnic filter --source delegated --country CN --type ipv4 apnic rdap ip 1.1.1.1 --date 2020-06-01T00:00:00Z apnic rdap help # RDAP server capability and notices apnic rdap domains 1 # Search in-addr.arpa reverse DNS domains apnic transfers-all # Cumulative transfer set (since 2010) apnic transfers-all --date 20220904 apnic stats-telemetry # whois/RDAP service query telemetry apnic irr inetnum # IRR database dump (RPSL) apnic irr serial # APNIC.CURRENTSERIAL apnic bgp summary # thyme BGP table analysis metrics apnic bgp raw-table # thyme raw routing table apnic bgp asn-map # Aggregate by origin ASN apnic bgp bad-prefixes # Prefixes longer than /24 + origin AS (route leak candidates) apnic bgp per-prefix-length # Prefix count by prefix length apnic bgp used-autnums # All in-use ASN + name/country apnic bgp spar-prefixes # RFC 6890 Special Purpose Address Registry prefixes apnic bgp single-pfx # ASN count with <20 prefixes (by RIR) apnic bgp --bgp-source au # Use Brisbane data source apnic rpki notification # RRDP notification (session/serial/snapshot/deltas) apnic rpki snapshot # Stream-parse current snapshot apnic rex network # REx self-locate network (covering prefix/ASN/economy by source IP) apnic rex resources ipv4 # Cross-RIR recent delegated resources (holder attribution) apnic rex holder
标签:APNIC, BGP, EVTX分析, Go SDK, IP地址查询, RDAP, 商业软件, 日志审计, 网络信息中心