🚫 ErrReservedIP — 保留 IP 地址错误
当查询的目标 IP 属于私有、回环、链路本地等 保留地址段(如
10.0.0.1、127.0.0.1、192.168.x.x)时,服务端ipapi.co不提供地理位置归属信息,返回Reserved IP Address,SDK 在handleError中将其映射为本错误。属于业务语义错误,不可重试。
🎨 一图抵千言
以下决策流展示 ErrReservedIP 从触发到调用方处理的完整路径。注意保留地址在客户端格式校验阶段会放行,错误只能在服务端响应阶段被识别。
作为对照,可重试错误(ErrRateLimited、ErrServerError、ErrNotFound)遵循的是带退避的状态流转,最终可恢复;而 ErrReservedIP 是终态业务错误,不进入该流转:
📦 错误定义
// ErrReservedIP 表示查询的 IP 属于保留地址段(私有/回环等)
var ErrReservedIP = errors.New("reserved IP address")| 属性 | 值 |
|---|---|
| 🔣 符号 | ipapi.ErrReservedIP |
| 💬 本地消息 | "reserved IP address" |
| 🌐 服务端 Reason | "Reserved IP Address" |
| 🔁 可重试 | ❌ 否 |
🎯 触发场景
该错误在以下情形下会被触发:
🌐 服务端返回
Reserved IP Address传入的 IP 虽然格式合法(能通过net.ParseIP校验),但属于 IANA 划定的保留地址段,ipapi.co对此类地址不维护地理位置数据,会在响应中以reason: "Reserved IP Address"标识。SDK 会将其映射为ErrReservedIP。典型保留地址段:
类别 示例网段 示例地址 🔒 IPv4 私有地址(RFC 1918) 10.0.0.0/8、172.16.0.0/12、192.168.0.0/1610.0.0.1、192.168.1.1🔁 IPv4 回环地址 127.0.0.0/8127.0.0.1🔗 IPv4 链路本地 169.254.0.0/16169.254.1.1🧷 IPv4 测试网段 192.0.2.0/24、198.51.100.0/24192.0.2.1🔒 IPv6 唯一本地地址 fc00::/7fd00::1🔁 IPv6 回环地址 ::1/128::1🔗 IPv6 链路本地 fe80::/10fe80::1🧪 客户端校验通过但语义非法 注意:与
ErrInvalidIP不同,保留地址在格式上是合法的,ValidateIP会放行;只有服务端的地理库判定其为保留段后才会触发本错误。
⚠️ 常见误用
- 误把保留地址当作网络错误重试:
ErrReservedIP属于业务语义错误,IsRetryableError对其返回false,纳入退避重试队列只会反复命中同一响应,浪费配额。 - 误用字符串比较判断错误类型:服务端 Reason 文案(
"Reserved IP Address")与 SDK 本地消息("reserved IP address")大小写与措辞均不同,务必使用errors.Is(err, ipapi.ErrReservedIP)精准匹配,而非strings.Contains。 - 误以为客户端能前置拦截:保留地址在格式上合法,
net.ParseIP会放行,本错误只能由服务端响应触发,无法通过客户端校验提前拦截。 - 混淆
ErrInvalidIP与ErrReservedIP:前者是格式非法(解析失败),后者是格式合法但属保留段,二者触发阶段与处理方式完全不同。
📍 触发位置
| 位置 | 说明 |
|---|---|
GetIPInfo | 客户端格式校验通过、发起请求后,服务端返回 reason: "Reserved IP Address",经 handleError 映射为 ErrReservedIP。 |
GetIPInfoRaw | 同上,非 JSON 格式场景下也会由服务端响应触发。 |
GetField | 查询保留 IP 的单个字段时,服务端同样会返回保留地址错误。 |
handleError | 错误映射中枢:case "Reserved IP Address": return fmt.Errorf("%w: %s", ErrReservedIP, apiErr.IP),将服务端 Reason 转译为 SDK 哨兵错误,并附上原始 IP。 |
| 响应解析层 | doRequest 在 StatusCode >= 400 时解码出 *APIError,其中 Reserved: true 与 Reason: "Reserved IP Address" 字段共同标识本情形。 |
💻 示例代码
package main
import (
"context"
"errors"
"fmt"
"log"
"github.com/cyberspacesec/ipapi"
)
func main() {
client := ipapi.NewClient()
ctx := context.Background()
// 查询私有地址段,服务端返回 Reserved IP Address
_, err := client.GetIPInfo(ctx, "10.0.0.1", "json")
if err != nil {
log.Printf("查询失败: %v", err) // 查询失败: reserved IP address: 10.0.0.1
}
// 使用 errors.Is 精准匹配
fmt.Println(errors.Is(err, ipapi.ErrReservedIP)) // true
}⚠️ 注意:保留地址在格式上合法,因此
ipapi.ValidateIP("10.0.0.1")返回nil;本错误只能由服务端响应触发,无法在客户端前置拦截。
🛠️ 错误处理
使用 errors.Is 精准匹配本错误,避免字符串比较带来的脆弱性:
result, err := client.GetIPInfo(ctx, userInput, "json")
if err != nil {
switch {
case errors.Is(err, ipapi.ErrReservedIP):
// 👉 目标 IP 属于保留地址段,无地理位置数据
fmt.Println("该 IP 属于私有/回环等保留地址段,不提供地理信息")
return
case errors.Is(err, ipapi.ErrInvalidIP):
// 参见 ./err-invalid-ip
fmt.Println("请输入合法的 IPv4 / IPv6 地址,例如 8.8.8.8")
return
case errors.Is(err, ipapi.ErrRateLimited):
// 参见 ./err-rate-limited
handleRateLimited()
return
default:
// 其他网络或解析错误
log.Printf("未知错误: %v", err)
return
}
}💡 提示:若业务侧只关心公网地理定位,可在调用前用
net.IP.IsPrivate()、net.IP.IsLoopback()等方法预过滤保留地址,避免无效请求。
🔍 排查清单:收到 ErrReservedIP 时
当 errors.Is(err, ipapi.ErrReservedIP) 返回 true 时,按以下步骤定位根因:
- 确认目标 IP 是否属保留段:用
net.ParseIP(ip)解析后调用.IsPrivate()、.IsLoopback()、.IsLinkLocalUnicast()、.IsUnspecified()验证;若任一为true,则本错误符合预期。 - 检查输入来源:若 IP 来自用户输入或配置文件,确认是否误填了内网地址(如
127.0.0.1、192.168.x.x)或示例地址(如192.0.2.x)。 - 核对日志中的原始 IP:SDK 在
handleError中以fmt.Errorf("%w: %s", ErrReservedIP, apiErr.IP)附上原始 IP,检查该 IP 是否与预期一致。 - 确认是否被误纳入重试:检查重试逻辑是否调用
IsRetryableError,本错误应返回false,重试队列对其应跳过。 - 业务侧预过滤:在入口处用
net.IP的IsPrivate()/IsLoopback()等方法过滤,从源头规避无效请求,参见上方示例代码。 - 若确属公网 IP 却仍报错:极少数情况下可能是 IP 段归属数据库未更新,可在 ipapi.co 官网 手工查询同一 IP 复核,若一致则反馈给上游。
🔁 可重试性
| 是否可重试 | ❌ 否 |
|---|
本错误源于 目标 IP 本身属于保留地址段,而非瞬时性故障(如网络抖动、限流)。对同一个保留地址重试任意次数,服务端都会持续返回 Reserved IP Address,因此:
- ❌ 不要纳入指数退避 / 自动重试队列
- ❌
ipapi.IsRetryableError(err)对本错误返回false - ✅ 应当向调用方明确提示“保留地址无地理信息”,并引导更换为公网 IP
🔗 相关错误
- 🚫
ErrInvalidIP— IP 格式非法,net.ParseIP解析失败,不可重试 - ⚡
ErrRateLimited— 请求频率超限,可重试 - 🚫
ErrInvalidKey— 无权限访问目标 IP - 📭
ErrNotFound— 查询结果为空,可重试 - 🌐
ErrServerError— 底层网络异常,可重试 - 🧾 完整错误列表参见
错误总览
👉 下一步
- 📖 阅读
API 参考 · 错误总览了解全部错误类型与映射关系 - 🧪 在 [示例代码#-示例代码) 中替换为不同保留地址(如
127.0.0.1、192.168.1.1、::1),观察触发行为 - 🛡️ 结合
net.IP.IsPrivate()/IsLoopback()在入口做预过滤,从源头规避保留地址查询