Node.js GET请求IPv6地址不一致问题及原因问询
问题描述
我在Cloudflare为自己注册的CNAME配置了区域锁定规则,白名单内添加了谷歌搜索「whatsmyip」查到的本机IPv6地址,以及公司IPv4格式的VPN地址。实际请求时出现以下异常现象:
- curl、Golang程序、Insomnia工具请求该端点都能正常返回200
- 使用Node.js原生请求、axios发起请求时,仅连接IPv4网络(公司VPN、手机热点)时返回200,其他场景都返回403
相关测试代码如下:
curl https://my-restricted-endpoint.com # 200
package main import ( "fmt" "log" "net/http" ) func main() { resp, err := http.Get("https://my-restriced-endpoint.com") if err != nil { log.Fatal(err) } fmt.Println(resp.StatusCode) // 200 }
const requestAsync = (options: RequestOptions | string | URL): Promise<number> => { return new Promise((resolve, reject) => { const req = request(options, (res) => { if (typeof res.statusCode !== 'number') { reject('no status code returned'); return; } resolve(res.statusCode); }); req.on('error', (error) => { reject(error); }); req.on('timeout', () => { reject('request timed out'); }); req.end(); }); }; const statusCode = await requestAsync('https://my-restricted-endpoint.com') // 连VPN或连接有IPv4地址的手机热点时返回200,其他场景返回403
我已经找到临时解决方案,给请求参数添加family: 6后就能正常得到结果:
const options = { ...standardStuff, family: 6, }
根据Node.js官方文档对该参数的说明:
解析host或hostname时使用的IP地址族,有效值为4或6。未指定时会同时使用IPv4和IPv6。
我有两个疑问:
- 按照文档说明未指定参数时应该同时支持IPv4和IPv6,为什么需要额外添加
family: 6才能正常请求? - 为什么curl、Golang等其他工具不需要配置该参数就能正常访问?
问题解答
这个差异本质是Node.js默认的DNS解析和IP选择逻辑,和其他常用工具的实现不一致导致的:
- Node.js默认IP选择逻辑:Node.js的http/https模块默认使用
dns.lookup方法解析域名,该方法调用系统的getaddrinfo接口,会按照接口返回的IP顺序选择第一个可用IP发起请求,绝大多数系统的getaddrinfo默认会把IPv4地址放在返回结果的前面,所以Node.js默认会优先使用IPv4地址发起请求。你不在VPN环境下时,本机的公网IPv4地址不在Cloudflare的白名单内,自然会被拦截返回403。 - 其他工具的默认逻辑:curl、Golang标准库、大部分浏览器都默认实现了「Happy Eyeballs」算法,解析域名后会同时发起IPv6和IPv4的连接请求,哪个先返回就用哪个,绝大多数网络环境下IPv6的连接速度更快,所以这些工具会优先用IPv6地址发起请求,刚好你的IPv6地址在白名单内,所以请求正常。
- 为什么加
family:6就正常:指定该参数后,Node.js解析域名时只会返回IPv6地址,强制使用IPv6发起请求,自然就匹配上了白名单里的IPv6地址,请求正常。
如果不想每次手动指定该参数,也可以改用dns.resolve系列方法替代默认的dns.lookup,或者使用支持Happy Eyeballs的第三方请求库。
内容的提问来源于stack exchange,提问作者amwill04
相关产品推荐
相关产品推荐

