不用后端,纯前端读出"网站眼里你的真实出口 IP"——聊聊 Cloudflare 的 cdn-cgi/trace

起因:两个页面,两个不同的 IP

前段时间调一个问题:同一台机器、同一个浏览器,访问 A 站和 B 站,后台日志里记录到的客户端 IP 居然不一样。

一开始以为是日志记错了。后来才反应过来------ "我的 IP"这个说法本身就不严谨。你以为你只有一个公网 IP,但真实网络环境里,出口 IP 取决于走哪条路由:多线路的机器、企业网关、IPv4/IPv6 双栈、运营商的 CGNAT,都可能让"不同的目标站点,看到不同的你"。

问题来了:我怎么在浏览器里,不装抓包工具、不自己搭后端,就知道"某个具体网站看到的我的出口 IP 是哪个"?

翻常规办法,基本都要靠第三方 API(ip.sbipify 之类)。但它们只能告诉你"那个 API 站 看到的你的 IP",不能告诉你"claude.ai 或任意某站看到的你的 IP"。而我恰恰想知道后者。

转机:Cloudflare 的 /cdn-cgi/trace

后来发现一个冷门端点。凡是套了 Cloudflare 的站点(现在这类站占比很高),都会在 /cdn-cgi/trace 暴露一段纯文本。直接 curl 一下:

arduino 复制代码
curl -s https://claude.ai/cdn-cgi/trace

拿到的是这么一坨 key=value(我实测的真实输出,IP 打码):

ini 复制代码
fl=1009f28
h=claude.ai
ip=207.56.226.xxx
ts=1784612887.000
visit_scheme=https
uag=curl/8.7.1
colo=NRT
http=http/2
loc=JP
tls=TLSv1.3
warp=off
kex=X25519

逐个字段拆一下,有用的几个:

字段 含义
ip 该站点这一侧看到的你的出口 IP(重点)
loc 这个 IP 被判定的国家(两位码,如 JP)
colo 接住你请求的 Cloudflare 数据中心(机场三字码,NRT=东京成田)
http 协商用的 HTTP 版本
tls TLS 版本
warp 是否走了 Cloudflare 自家的 WARP

colo=NRT 这个挺有意思------它是 Anycast 就近接入的结果,顺带能看出你的流量从哪个地理节点进的 Cloudflare 网络。

关键一步:它跨域可读

光能 curl 没用,curl 是在我本机跑的。我要的是在浏览器页面里、用 JS 读到 。这就撞上浏览器的同源策略------默认情况下,你在 a.com 的页面里 fetch('https://b.com/...'),响应体是拿不到的。

于是去看它的响应头。这里我踩了个小坑:一开始用 curl -I(HEAD 请求)去看,返回的 content-typetext/html,还没有 CORS 头,我以为读不了。后来改成正常 GET + 加个 Origin 头再看,才是真相:

bash 复制代码
curl -s -D - -o /dev/null -H "Origin: https://foo.example" https://claude.ai/cdn-cgi/trace

响应头里明明白白:

less 复制代码
content-type: text/plain
access-control-allow-origin: *

access-control-allow-origin: * ------任意来源都允许跨域读取。也就是说,在任何网页里都能直接 fetch 它、拿到响应体。(教训:验 CORS 别用 HEAD,很多端点根本不按 HEAD 的语义返回,得用真实方法 + 带 Origin 头。)

封装成一个函数

原理通了,代码就很短:

javascript 复制代码
// 读取"指定 CF 站点这一侧,看到的你的出口 IP 及网络信息"
// 纯前端,无需任何后端或第三方 API
async function readTrace(host) {
  const res = await fetch(`https://${host}/cdn-cgi/trace`, { cache: 'no-store' });
  if (!res.ok) throw new Error(`${host} 未返回 trace(可能没走 Cloudflare)`);
  const text = await res.text();


  // 响应是一行一个 key=value 的纯文本,手动解析
  return Object.fromEntries(
    text.trim().split('\n').map((line) => {
      const i = line.indexOf('=');
      return [line.slice(0, i), line.slice(i + 1)];
    })
  );
}


// 用法
const t = await readTrace('claude.ai');
console.log(t.ip, t.loc, t.colo);  // 该站看到的你的出口 IP / 国家 / 接入节点

{ cache: 'no-store' } 别省------trace 端点响应短,某些情况下会被缓存,不加可能读到旧值。

顺手能做的一件事:横向对比多个站的出口

一个站能读,那就把一批 CF 站点挨个读一遍,看看它们各自看到的出口 IP 是不是同一个:

ini 复制代码
const hosts = ['claude.ai', 'openai.com', 'www.cloudflare.com', 'anthropic.com'];


const results = await Promise.allSettled(
  hosts.map(async (h) => ({ host: h, ...(await readTrace(h)) }))
);


for (const r of results) {
  if (r.status === 'fulfilled') {
    const { host, ip, loc, colo } = r.value;
    console.log(`${host.padEnd(22)} ip=${ip}  loc=${loc}  colo=${colo}`);
  }
}

如果所有站看到的 ip 都一致,说明你的出口是统一的;如果某个站看到的 IP 或 loc 跟别的不一样,那就定位到了"是哪一段网络路径把这个站的流量引到了不同出口"。前面那个"两个页面两个 IP"的诡异问题,我就是这么定位到的------比翻日志直观多了

(用 Promise.allSettled 而不是 all:总有些站不走 CF,或路径不同会抛错,别让一个失败拖垮整批。)

再往下一层:这个 IP 本身"干不干净"?

知道了出口 IP,自然会想再问一句:这个 IP 的"网络身份"是什么?是住宅宽带、还是机房 IP?属于哪个 ASN?有没有被各类情报源标记过?

这属于另一个话题了------IP 信誉评估。简单说,判断一个 IP 的"成分",通常看这几个维度:

  • 类型:住宅 / 机房(IDC)/ 移动。反查 ASN 和 rDNS 能大致区分。
  • ASN 归属:这个 IP 属于哪个自治域,是电信运营商还是云厂商。
  • 同段邻居 :同一 /24 网段里跑的是住宅域名还是一堆机房主机,能反推整段性质。
  • 黑名单命中:是否进过公开的 DNSBL、是否被蜜罐网络记录过。

这些数据分散在十几个不同的免费情报源里,单独查很累。我后来把"读出口 IP + 多源聚合打个信誉分"这套流程整个做成了个页面,平时排查网络问题顺手用。

小结

  • /cdn-cgi/trace 是 Cloudflare 在每个托管站点都会暴露的纯文本诊断端点;
  • access-control-allow-origin: *,跨域可读,所以能在任意网页里纯前端 fetch;
  • 由此可以读到"任意 CF 站点这一侧看到的你的出口 IP",而不只是某个 IP 查询 API 看到的;
  • 横向对比多个站的出口,能直观定位路由差异;
  • 验 CORS 记得用真实请求方法 + Origin 头,别用 HEAD 被误导。

一个挺冷门、但排查网络问题时很趁手的小技巧。

相关推荐
Z兽兽6 分钟前
HBuilder打包web地址的apk,更新dist后如何自动清缓存
前端·android应用打包
奔跑的卡卡29 分钟前
线上Web异常发现为什么总是滞后?一套自研Web实时异常分析监控系统整体架构拆解
前端·web安全·架构
梦曦i1 小时前
create-uni-app v1.1.0:结构化生成,工程骨架更规范
前端·uni-app
流光D1 小时前
AI时代,搭建 web 站点并配置 nginx 反向代理流程
运维·服务器·前端·人工智能·nginx·ai·ai编程
PBitW1 小时前
Travel Planner — 智能旅行行程规划助手
前端·后端
梨想橙汁1 小时前
CSS Grid 网格布局:二维布局神器,轻松实现复杂网页排版
前端·css
lhldsg1 小时前
酒吧点餐小程序开发:从需求分析到技术落地实战
java·前端·小程序·架构·交友
Csvn1 小时前
微前端样式隔离:为什么你的 CSS 串到别人家了?Shadow DOM、CSS Variables 与 @layer 实战
前端
金花顺1 小时前
android_media_AudioTrack
前端·面试
木公子1 小时前
Vue3源码精读04:Scheduler 调度器深度解析|异步批量更新与任务队列源码全解
前端·vue.js