# apnic-skills **简体中文** | [English](README.md) APNIC(亚太互联网络信息中心)Go 语言 SDK,完整覆盖 APNIC 提供的所有公开数据服务与查询能力。 ```mermaid graph TB subgraph SDK["apnic-skills SDK"] direction TB Client["Client
HTTP + 反爬
+ 分块下载"] subgraph Data["数据获取器"] 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 + 反向 DNS"] IRR["IRR 数据库
inetnum/autnum/route
19 种对象类型"] RPKI["RPKI/RRDP
notification/snapshot
delta"] REx["REx 跨 RIR
resources/holder
network/count"] Transfers["转移 + 变更
+ 遥测"] end subgraph Features["内置特性"] Stealth["浏览器伪装
请求头 + 抖动"] Chunked["分块下载
Range 请求
2MiB 块 ×4"] Cache["令牌桶缓存
默认 30min TTL"] Filter["链式过滤
country/type/status
日期范围"] Verify["数据完整性
MD5 + PGP 签名"] end Client --> Data Client --> Features end subgraph CLI["CLI (cobra)"] Commands["24 个子命令
完整覆盖 SDK"] Flags["全局标志
--stealth --json
--bgp-source --date"] end CLI --> SDK subgraph APNIC["APNIC 服务"] FTP["ftp.apnic.net
Stats/IRR/Transfers"] RDAP_Svc["rdap.apnic.net"] Thyme["thyme.apnic.net
BGP 分析"] RExAPI["api.rex.apnic.net
跨 RIR 注册库"] WhoisSvc["whois.apnic.net:43"] end SDK --> APNIC ``` ## 架构总览 ## 安装 ```bash go get github.com/cyberspacesec/apnic-skills ``` ## 快速开始 ```go package main import ( "context" "fmt" "log" apnic "github.com/cyberspacesec/apnic-skills" ) func main() { client := apnic.NewClient() ctx := context.Background() // RDAP 查询 IP 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) // 获取 Delegated Stats entries, err := client.GetDelegatedEntries(ctx) if err != nil { log.Fatal(err) } fmt.Printf("Total entries: %d\n", len(entries)) // 链式过滤 result := apnic.NewFilter(entries). ByCountry("CN"). ByType("ipv4"). ByStatus("allocated"). Result() fmt.Printf("CN allocated IPv4 entries: %d\n", len(result)) } ``` ## API 概览 ### 1. Delegated Stats(IP/ASN 分配记录) | 方法 | 说明 | |------|------| | `FetchDelegatedEntries(ctx)` | 获取最新标准版分配记录 | | `GetDelegatedEntries(ctx)` | 带缓存的标准版分配记录 | | `FetchDelegatedEntriesByDate(ctx, date)` | 获取指定日期的分配记录 | | `FetchDelegatedResult(ctx, date)` | 获取完整结果(含 header/summary) | ### 2. Extended Delegated Stats(扩展版,含组织标识) | 方法 | 说明 | |------|------| | `FetchExtendedEntries(ctx)` | 获取最新扩展版分配记录 | | `GetExtendedEntries(ctx)` | 带缓存的扩展版分配记录 | | `FetchExtendedEntriesByDate(ctx, date)` | 获取指定日期的扩展版 | | `FetchExtendedResult(ctx, date)` | 获取完整结果(含 header/summary) | ### 3. Assigned Stats(按前缀大小聚合的分配统计) | 方法 | 说明 | |------|------| | `FetchAssignedEntries(ctx)` | 获取最新分配统计 | | `GetAssignedEntries(ctx)` | 带缓存的分配统计 | | `FetchAssignedEntriesByDate(ctx, date)` | 获取指定日期的分配统计 | ### 3b. IPv6 Assigned Stats(逐条 IPv6 分配记录) | 方法 | 说明 | |------|------| | `FetchIPv6AssignedEntries(ctx)` | 获取最新逐条 IPv6 分配记录 | | `FetchIPv6AssignedEntriesByDate(ctx, date)` | 获取指定日期的逐条 IPv6 分配记录 | | `FetchIPv6AssignedResult(ctx, date)` | 获取完整结果(含 header/summary) | > 与聚合版 `assigned` 不同,`ipv6-assigned` 列出每一条 IPv6 分配(`registry|cc|ipv6|start|prefix|date`),无 status/opaque-id 列。 ### 4. Legacy Stats(历史遗留资源) | 方法 | 说明 | |------|------| | `FetchLegacyEntries(ctx)` | 获取最新历史遗留记录 | | `GetLegacyEntries(ctx)` | 带缓存的历史遗留记录 | | `FetchLegacyEntriesByDate(ctx, date)` | 获取指定日期的历史遗留记录 | ### 5. RDAP 查询(结构化数据) | 方法 | 说明 | |------|------| | `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 反向 DNS 域 | 所有 RDAP lookup 均提供 `*At` 变体(如 `RDAPLookupIPAt(ctx, ip, date)`)用于点对点历史查询(RFC3339),返回该 UTC 时刻的资源状态,基于 APNIC `history_version_0` 扩展。 ### 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 遥测(每小时发布):查询总量、按类型分布、Top ASN;`date=""` 取最新 | | `FetchTelemetryMD5(ctx, date)` | 遥测 JSON 的 MD5 校验值 | ### 7c. IRR 全量转储(RPSL) | 方法 | 说明 | |------|------| | `FetchIRRDatabase(ctx, objType)` | 获取并解析指定类型的 IRR 数据库转储(`apnic.db..gz`),`objType` 见 `IRRObjectTypes`(19 类) | | `GetIRRDatabase(ctx, objType)` | 带缓存的 IRR 数据库转储 | | `FetchIRRCurrentSerial(ctx)` | 获取 `APNIC.CURRENTSERIAL`(IRR 数据库当前序号) | > `domain` 类型的 IRR 转储承载反向 DNS 委派信息(`x.in-addr.arpa` + `nserver`/`zone-c`)。 ### 7d. thyme BGP 路由表分析 | 方法 | 说明 | |------|------| | `FetchBGPSummary(ctx)` | 获取 thyme `data-summary`:BGP 路由表分析指标(条目数、AS 数、ROA 覆盖、地址空间占比等,冒号键值) | | `FetchBGPRawTable(ctx)` | 获取 thyme `data-raw-table`:每条已宣告路由 `prefix\tASN` | | `FetchBGPASNMap(ctx)` | 按 origin ASN 聚合 raw table(本地派生,无额外请求) | | `FetchBGPBadPrefixes(ctx, source)` | 获取 thyme `data-badpfx-nos`:长度超过 /24 的前缀及其 origin 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)前缀及 origin AS | | `FetchBGPSinglePfx(ctx, source)` | 获取 thyme `data-singlepfx`:宣告少于 20 个前缀的 ASN 计数(按 RIR 分组) | > `source` 为 per-call 数据源参数:`"current"`(默认,全球视图)、`"au"`(Brisbane)、`"hk"`(HKIX),空串走客户端默认(`WithThymeSource` 设置,默认 `current`)。 ### 7e. RPKI / RRDP | 方法 | 说明 | |------|------| | `FetchRRDPNotification(ctx)` | 获取 RRDP `notification.xml`:session_id、serial、当前 snapshot 引用与 deltas 列表 | | `FetchRRDPSnapshot(ctx, uri)` | 流式解析 RRDP snapshot.xml:仅保留 ``/`` 的 rsync URI,丢弃 base64 CMS body(边界内存) | | `FetchRRDPDelta(ctx, uri)` | 流式解析 RRDP delta.xml(增量更新,格式同 snapshot) | ### 7f. REx 跨 RIR 资源注册库 REx(Resource EXplorer,`api.rex.apnic.net/v1/*`)是 APNIC 提供的公开 REST API,将五大 RIR(APNIC/ARIN/RIPE/LACNIC/AFRINIC)的委派资源聚合为统一视图,并按持有组织(opaqueId)归集。这是各 RIR 独立 stats/RDAP 无法替代的能力: | 方法 | 说明 | |------|------| | `FetchRExUserNetwork(ctx)` | 自定位网络:按调用方源 IP 返回覆盖前缀、起源 ASN、经济体代码(无需参数) | | `FetchRExResources(ctx, type)` | 跨 RIR 最近委派资源视图(带持有者归因),`type` 可为 `ipv4`/`ipv6`/`asn` 或空 | | `FetchRExHolder(ctx, opaqueID, rir)` | 按 opaqueId 聚合某组织持有的全部 ASN 与前缀及规模指标 | | `FetchRExHoldersUniqueCount(ctx)` | 全 RIR 去重持有者总数 | > `rir` 取值为 `afrinic`/`apnic`/`arin`/`lacnic`/`ripencc`(RIPE NCC 的代码是 `ripencc`,不是 `ripe`)。`opaqueId` 可从 `RExResource.OpaqueID` 或扩展 delegated stats 获取。REx 走 HTTPS 公开 API,复用统一 HTTP 出口,自动获益于反爬伪装。 ### 8. Whois 查询 | 方法 | 说明 | |------|------| | `QueryWhois(ctx, query)` | 原始 Whois 查询 | | `QueryWhoisIP(ctx, ip)` | IP 地址 Whois 查询(返回解析结果) | | `QueryWhoisASN(ctx, asn)` | ASN Whois 查询(返回解析结果) | | `QueryWhoisWithFlags(ctx, query, flags)` | 带标志的 Whois 查询 | | `ParseWhoisResponse(response)` | 解析 Whois 响应文本 | ### 9. 反向 DNS | 方法 | 说明 | |------|------| | `ReverseDNS(ctx, ip)` | IP 反向 DNS 解析 | ### 10. 历史数据 | 方法 | 说明 | |------|------| | `FetchHistoricalDelegated(ctx, date)` | 获取指定日期的历史分配数据 | | `FetchHistoricalExtended(ctx, date)` | 获取指定日期的历史扩展数据 | | `FetchHistoricalAssigned(ctx, date)` | 获取指定日期的历史分配统计 | | `FetchHistoricalLegacy(ctx, date)` | 获取指定日期的历史遗留数据 | | `FetchDelegatedByYear(ctx, year)` | 获取指定年份的分配数据 | | `FetchExtendedByYear(ctx, year)` | 获取指定年份的扩展数据 | | `ListAvailableYears()` | 列出可用的历史数据年份 | ### 11. 数据校验 | 方法 | 说明 | |------|------| | `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)` | 按组织标识过滤 | | `FilterExtendedByCountry(entries, country)` | 按国家过滤扩展版 | | `FilterExtendedByType(entries, resType)` | 按类型过滤扩展版 | | `FilterExtendedByStatus(entries, status)` | 按状态过滤扩展版 | | `GroupByCountry(entries)` | 按国家分组 | | `GroupExtendedByOpaqueID(entries)` | 按组织分组 | | `GroupExtendedByCountry(entries)` | 按国家分组扩展版 | ### 13. 链式过滤 API ```go // 标准版链式过滤 result := apnic.NewFilter(entries). ByCountry("CN"). ByType("ipv4"). ByStatus("allocated"). ByDateRange(start, end). Result() // 扩展版链式过滤 extResult := apnic.NewExtendedFilter(extEntries). ByCountry("JP"). ByType("ipv6"). ByOpaqueID("A92E1062"). Result() ``` ### 14. CIDR 计算 ```go // 标准版 cidr, err := entry.CIDR() // 扩展版 cidr, err := extEntry.CIDR() // 历史遗留版 cidr, err := legacyEntry.CIDR() ``` ## 客户端配置 ```go 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-* 等),并在请求间加入令牌桶限速与随机抖动。可通过 Option 调整或关闭: ```go client := apnic.NewClient( apnic.WithStealth(true), // 默认 true;false 时仅发 UA+Accept(向后兼容) apnic.WithBrowserUserAgent("Mozilla/5.0 ..."), // 自定义浏览器 UA apnic.WithJitter(200*time.Millisecond, 800*time.Millisecond), // 每请求随机延迟区间 apnic.WithRateLimit(2.0), // 全局每秒最大请求数(令牌桶,0=不限速) apnic.WithFTPBaseURL(apnic.DefaultFTPBaseURL), // IRR/transfers-all/telemetry 的 FTP 根 apnic.WithRRDPBaseURL(apnic.DefaultRRDPBaseURL), apnic.WithThymeBaseURL(apnic.DefaultThymeBaseURL), apnic.WithRExBaseURL(apnic.DefaultRExBaseURL), // REx 跨 RIR 资源注册库 ) ``` > 兼容性说明:stealth 开启时设置 `Accept-Encoding: gzip`,Go Transport 不会自动解压,故 `fetchText`、RRDP 流式解析器与 REx `fetchJSON` 均显式处理 `Content-Encoding: gzip`,避免双重解压。测试中可用 `APNIC_NO_JITTER=1` 跳过抖动以加速。 ### 大文件分块下载(默认开启) 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 分钟,远超常规超时。 SDK 默认启用**多连接分块下载**:探测支持 Range 后,将文件切成 ~2MB 的小块,以 4 个并发 Range 请求轮转下载(每连接独立限速,总吞吐提升 3-4 倍),再用 `io.Pipe` 流式合并、按需 gzip 解压。每个分块请求仍走 `doHTTPRequest`,完整继承浏览器伪装头 + 令牌桶限速 + 抖动。不支持 Range 或传输层 gzip 的端点透明回退单连接。 ```go client := apnic.NewClient( apnic.WithMaxConcurrentDownloads(4), // 并发 Range 请求数(0/1 禁用分块) apnic.WithChunkSize(2*1024*1024), // 显式每块大小;默认 2MiB apnic.WithDownloadTimeout(5*time.Minute), // 单块超时(建议 ≥ 单块大小/8KBps) ) ``` > 选块策略:默认每块 2MiB(在 22KB/s 限速下约 90 秒下完,远低于建议的 5 分钟单块超时)。`maxConcurrent` 仅控制并发数,不限制块数——50MB 文件会切成 ~25 块经 4 个 worker 轮转。块数硬上限 64,并发硬上限 16,避免对服务器造成压力。若仍遇单块超时,可调小 `WithChunkSize`(如 1MiB)或调大 `WithDownloadTimeout`。 > > 慢块容错:当某个分块因连接卡住触发 `context deadline exceeded` 时,SDK 会自动将该块拆成两个子块以新连接并发重试,绕开卡死的 TCP 连接,避免单点抖动拖垮整个下载。 ## 命令行工具(CLI) 仓库附带基于 cobra 的 `apnic` CLI,封装全部 SDK 能力: ```bash # 构建 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 服务能力声明与通知 apnic rdap domains 1 # 搜索 in-addr.arpa 反向 DNS 域 apnic transfers-all # 累积转移全集(自 2010) apnic transfers-all --date 20220904 apnic stats-telemetry # whois/RDAP 服务查询遥测 apnic irr inetnum # IRR 数据库转储(RPSL) apnic irr serial # APNIC.CURRENTSERIAL apnic bgp summary # thyme BGP 路由表分析指标 apnic bgp raw-table # thyme 原始路由表 apnic bgp asn-map # 按 origin ASN 聚合 apnic bgp bad-prefixes # 长度超过 /24 的前缀及 origin AS(路由泄漏候选) apnic bgp per-prefix-length # 按前缀长度统计已宣告前缀数 apnic bgp used-autnums # 所有在用 ASN 及注册名/国家码 apnic bgp spar-prefixes # 特殊用途地址注册表(RFC 6890)前缀 apnic bgp single-pfx # 宣告少于 20 前缀的 ASN 计数(按 RIR) apnic rpki notification # RRDP notification(session/serial/snapshot/deltas) apnic rpki snapshot # 流式解析当前 snapshot apnic rex network # REx 自定位网络(源 IP 的覆盖前缀/ASN/经济体) apnic rex resources ipv4 # 跨 RIR 最近委派资源(持有者归因) apnic rex holder # 聚合某组织持有的全部 ASN/前缀 apnic rex count # 全 RIR 去重持有者总数 apnic verify integrity --type delegated ``` 反爬相关全局标志:`--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)。 ## License MIT