前言

起因是广东联通这条宽带无法通过 UPnP 让 BRAS 打开端口映射,IPv6 一侧又出现了手机访问软路由一直转圈的问题。两件事看着不相干,但都指向运营商的边界设备,于是打算把它探一遍,看看对面挂的到底是什么设备、跑了什么协议。这次探测的目的不是找漏洞,只是想搞清楚自己这条线路上到底发生了什么。全程只针对自己名下的这一条宽带,没有扫别人。

这类症状可以指向很多原因:路由器配置、防火墙策略、运营商 NAT,甚至设备本身。凭症状猜是没意义的,唯一可靠的办法是把运营商那侧的设备当成一个黑盒,逐层测它的行为。这次用的工具是 PyCharm 和 Python,手上能用的资源有:

  • 一条联通广州家庭宽带,PPPoE 接入
  • 一台 4 口 x86 软路由(iStoreOS 25.12.5),WAN 口直连运营商
  • 一台腾讯云 VPS(公网 IPv6 出口)
  • 一张联通流量卡(手机)

但第一件事不是发探测包,是先把地图画出来。

结论

从物理位置到协议行为,一共测了五层,结论如下:

层次结论证据强度
PPPoE 发现层双 BRAS 热备,华为设备,AC-Name 已提取实测
认证层账号并发上限 = 1,不支持多拨实测(AAA 原文)
入向过滤层不存在 TCP 标志级过滤,56/56 全到达实测(含 IPv4 对照)
NAT 类型对称型 NAT4,4 组独立复现实测
端口控制协议运营商侧 PCP / NAT-PMP 均无响应实测(含对照组)
IPv6 互通固网↔移动单向不可达实测(抓包定位)

表里每一个「实测」,后面都有对应的报文原文、抓包或对照实验,下面逐层展开。

第零层:先把地图画出来

本机相对位置

Get-NetIPConfiguration | Select-Object InterfaceAlias, IPv4Address, IPv4DefaultGateway
Get-NetRoute -AddressFamily IPv4 -DestinationPrefix '0.0.0.0/0'
PowerShell 执行 Get-NetIPConfiguration 看到本机地址 192.168.100.237、默认网关 192.168.100.1

结果:192.168.100.237/24,网关 192.168.100.1。标准家庭网段,没有意外。

软路由内部

登录软路由(SSH 走 22822 端口,不是默认的 22),第一轮只读侦察:

ip -4 addr show; ip -6 addr show scope global
ip route; ip -6 route
ip -br link
iStoreOS 软路由的 ip route 与 ip -br link 输出:pppoe-wan 拿到 10.1.151.74 与 2408:8256 开头的 IPv6 地址

关键发现:

pppoe-wan: inet 10.1.151.74 peer 10.1.128.1/32
inet6 2408:8256:a6c:f718:…:1dae/64

这是第一条硬事实:WAN 侧是 PPPoE 点对点,对端 10.1.128.1。两端都在运营商 10.0.0.0/8 城域网内,这就是 CGNAT 的内侧。同时 IPv6 拿到了全球可路由的 /64,说明运营商在这条线路上没做 NAT66。这一点后来变成整场调查的关键。

出口在哪

curl -s http://ip.3322.net
curl ip.3322.net 返回联通广东出口 58.249.141.202,与 PPPoE 会话地址 10.1.151.74 完全不同

返回 58.249.143.142(联通广东)。与 PPPoE 会话地址 10.1.151.74 明显不同,确认 CGNAT 边界就在 BRAS 上。

traceroute 印证了这一点:

traceroute 百度:第二跳直接是联通公网 120.80.126.90,说明 NAT 边界就在 BRAS 上

从 BRAS 直接跳到公网,中间没有第二层 NAT。

第一层:从 PADI 里挖出 BRAS 身份

为什么选 PADI

计算机网络当中,识别对端设备,常规的手段是端口扫描加服务指纹。但 BRAS 几乎不会开放端口,扫描反而会触发安全告警。PPPoE 恰好提供了一个更好的入口:发现阶段是明文、且无需认证。

PADI (广播) → PADO (BRAS 应答) → PADR (单播) → PADS (分配 Session-ID)

只要发一个 PADI,BRAS 就会返回一个 PADO,而 PADO 里带着 AC-Name(Access Concentrator Name),那是设备的自报家门。

实现

用 AF_PACKET 裸套接字手工构造以太网帧:

Python 用 AF_PACKET 裸套接字手工拼 PPPoE PADI 以太网帧,代码里定义了 Host-Uniq 与 0x8863 以太类型

核心就几行:Service-Name 留空、Host-Uniq 随便填,以太类型 0x8863,目的 MAC 全 ff。

完整的探测脚本把发现阶段的报文类型和 tag 都定义好了,另外还得用 SIOCGIFFLAGS 打开混杂模式,否则收不到 BRAS 回的 PADO:

pppoe_mac_probe.py 脚本:定义 PPPoE 发现阶段的报文类型、Service-Name 等 tag,以及用 SIOCGIFFLAGS 打开混杂模式

解析回来的 PADO,重点看 0x0102(AC-Name)和 0x0101(Service-Name)两个 tag。

结果

一次探测,收到了两个 PADO:

AC-Name源 MAC
b1-s-gdgz-gq08:19:a6:f4:11:5c
b2-s-gdgz-xt8c:fd:18:f5:72:bb

两条结论同时拿到:

  1. 存在两台 BRAS,同时响应同一个客户端,可以理解为做了双机热备,会话可热备切换。
  2. 命名规则可读:gdgz = 广东广州,gq / xt 为局站代号,b1/b2 疑似机框或板卡编号(此项为推断)。

后来通过 PPPoE 会话表确认,当前会话挂在 08:19:a6:f4:11:5c(即 b1)上,另一台空闲。

这个方法的边界

PADI 只能拿到 AC-Name 和 MAC,拿不到型号。想要型号得靠 TCP 栈指纹,而 BRAS 的 22 端口 DROP、其余端口 RST,连接建立不起来,抓不到 SYN-ACK 的 TCP 选项。所以型号这一层,到此为止。

第二层:认证面与多拨能力

一次险些误判

第一个想法:同账号能否建多个 PPPoE 会话(多拨叠加带宽)。直觉做法是再发一次完整的 PADI→PADR,看能否拿到第二个 Session-ID。于是照做,三次全部超时(PADS 无响应)。差点就此写下「BRAS 拒绝多会话」的结论。

但那是错的。复查代码发现:我把 PADR 发成了广播(目的 MAC = ff:ff:ff:ff:ff:ff)。PPPoE 规范里只有 PADI 是广播,PADR 必须单播给选定的那个 AC。BRAS 收到广播 PADR 直接忽略,不会回 error,静默丢弃。修正为单播后,结果完全反转:

Session-ID=0x7E2A   (b1-s-gdgz-gq)
Session-ID=0xB719   (b2-s-gdgz-xt)
Session-ID=0x4811   (b1-s-gdgz-gq)

这是本次调查最有价值的一次教训:一个字节的差异(广播 vs 单播),把「拒绝」变成了「允许」,方向完全相反。而错误答案的表现形式是「超时」,和真实的过滤行为一模一样。

分层设计:把风险切开

发现层允许 ≠ 账号允许。真正的并发限制在 CHAP 认证层。而贸然做第二次认证,有可能触发「后拨顶前拨」把主会话踢掉。

所以设计成三个阶段,风险递进:

阶段动作风险
stage1只做发现层(PADI/PADR/PADS)零账号风险
stage2完整 PPP 认证,但用错误口令不建立账号会话
stage3完整 PPP 认证,用真实口令唯一可能触发顶号的阶段

隔离手段:

  • 测试流量走独立 macvlan(伪造 MAC 02:e2:59:00:31:ae),与生产 MAC 00:e2:59:00:31:ae 完全分离
  • nodefaultroute + nopersist,不抢默认路由、不循环重试
  • 全程有看门狗监控生产会话,掉线立即记录并触发重拨
  • 退出即 PADT 拆除 + 删除 macvlan

AAA 错误码把答案写在了脸上

stage2(错误口令):

rcvd [CHAP Challenge id=0x1 <40d9...>, name = "Huawei"]
rcvd [CHAP Failure "29;User(...)'s Authen Attrib(ai-Service-Password) Check Error,
     Request Deny by gz.cncnet.net"]

stage3(真实口令):

rcvd [CHAP Failure "21;User(...) Checking LM,
     (online count=1)>=(Max num=1)., Request Deny by gz.cncnet.net"]

两个错误码不同,这一点本身就是结论:

  • 29; = 密码校验失败
  • 21; + (online count=1)>=(Max num=1) = 并发会话数超限

stage3 返回 21 而不是 29,说明凭据是对的、通过了密码校验,然后才被并发策略挡下。

结论:该账号最大并发会话数 = 1,不支持多拨。

附带结论:运营商策略是「拒绝新会话」,不是「后拨顶前拨」,主会话在整轮测试中 Session-ID 始终为 0000C03B,未受影响。

顺带拿到设备厂商:CHAP Challenge 的 name = "Huawei"。华为。

第三层:入向过滤探测

问题定义

想验证一个假设:BRAS 是否对入向的 TCP SYN+ACK 和 UDP 报文做差异化拦截。

这个问题的难点不在发包,在判据。「没收到响应」和「被拦截」是两件不同的事。

拦截成立必须同时满足三个条件:

  1. 观测点在 netdev 层(早于目标主机的 netfilter),否则测到的是主机防火墙
  2. 发送侧可确认已发出、且源侧无过滤
  3. 有同源对照,只改一个变量(标志位 / 协议)

第 1 条决定了工具选择:必须用 tcpdump,而不是 nft 的 counter。因为 tcpdump 挂在 netdev 上,报文能在这里出现,就证明路径放行了;后续被本地防火墙丢弃不影响这个判断。

发送端设计

12 种 TCP 标志组合加 10 个 UDP 端口,每个探测独占一个源端口(从 41000 递增),接收端据此归因,不需要解析载荷。

标志矩阵特意包含畸形组合:

SYN / SYN+ACK / ACK / FIN / FIN+ACK / RST / PSH+ACK
FIN+SYN / FIN+PSH+URG(XMAS) / NULL(无标志) / SYN+FIN+RST / ACK+URG+PSH

TCP 头必须手工构造,其中有个坑:data-offset 与 flags 是一个 16 位字段,不是两个字节。写成两个 B 会让 off_fl 被截断,报文直接构造错误。

IPv6 的校验和是强制项,伪首部算法:

def csum6(src, dst, nxt, payload):
    pseudo = src + dst + struct.pack("!I", len(payload)) + b"\x00\x00\x00" + bytes([nxt])
    return csum(pseudo + payload)

校验和算错 → 目标静默丢弃 → 和「被过滤」表现完全一致。

结果:56/56 全部到达

接收端在软路由 WAN 口抓包,源地址过滤只抓 VPS 发来的报文。2 轮 × 28 个探测:

proto  flag               sent   recv     rate   verdict
tcp    SYN                   6      6     100%   delivered
tcp    SYN+ACK               6      6     100%   delivered    <- 核心怀疑项
tcp    NULL                  2      2     100%   delivered    <- 无任何标志
tcp    FIN+PSH+URG           2      2     100%   delivered    <- XMAS 树
tcp    SYN+FIN+RST           2      2     100%   delivered
udp    dport=53              2      2     100%   delivered
udp    dport=50000           2      2     100%   delivered

22 种探测类型,全部 100% 到达,无一例外。

连 NULL(零标志)和 SYN+FIN+RST(自相矛盾)这种畸形组合都完整到达了网卡。

结论:BRAS 对 IPv6 入向报文没有任何标志级过滤。 原假设不成立。

IPv4 对照:把因果锁死

如果只测 IPv6,会有人说「那是因为你 IPv6 正常,IPv4 说不定被拦了」。

所以加一组对照:同一台 VPS、同一套矩阵、同一时间窗,目标是 CGNAT 出口 58.249.143.142。

探测报文(sport 41500-41511)  →  0/12 到达
同路径有状态回包(443、22222)  →  正常畅通

同一条物理路径上,有状态流量过、无状态主动发起不过。

这个对比把因果锁死了:丢包点在 NAT 映射查询,与报文标志无关。如果 BRAS 真有标志过滤,IPv6 侧(无 NAT、无法用映射缺失解释)必然会暴露它,而那里是 100%。


第四层:NAT 类型与端口控制协议

STUN 判定原理

从同一个已绑定的本地 UDP 端口,依次向多个不同地址的 STUN 服务器发 Binding Request,比对各自返回的 XOR-MAPPED-ADDRESS:

  • 不同目的地 → 映射不变 = 锥形 NAT
  • 不同目的地 → 映射变化 = 对称型 NAT(NAT4)

探测脚本里准备了四个 STUN 服务器和两个 PCP 目标:

nat_probe.py 脚本:STUN 服务器列表(miwifi、bilibili、hity、Google)与 PCP / NAT-PMP 探测目标

四组复现

序号本地端口→ miwifi→ bilibili→ Google
134788:6227:6228:6237
257181:6287:6288:6292
350051:6325:6326:6343
451305:6361:6363:6379

同一个本地端口,对着三个不同目的地,被分配了三个不同的公网映射端口。

四组独立复现,行为完全一致。确认对称型 NAT4。

这种 NAT 下,映射条目绑定四元组,第三方无法预测映射端口,因此 NAT 穿透(hole punching)在最常见实现下都会失败。这就是 P2P、游戏联机、视频通话受影响的根因。四种 NAT 类型的分法和判据,之前单独写过一篇:NAT 类型有哪些?对称型 NAT 是啥?

PCP 与 NAT-PMP

两者都用 UDP/5351。向运营商边界设备发标准请求:

协议目标结果
PCP (RFC 6887)10.1.128.1:5351无响应
NAT-PMP (RFC 6886)10.1.128.1:5351无响应
PCP(对照组)192.168.100.1:5351有响应(rc=12)
NAT-PMP(对照组)192.168.100.1:5351有响应

对照组是必须的。 同一探测端、同一方法,本地网关响应正常、运营商设备超时,这就把「你工具不行」这条抗辩堵死了。

结论:运营商部署了 CGNAT,但没提供 PCP。用户没有任何标准化途径申请端口映射。

第五层:IPv6 互通故障

一个意外的发现

测试过程中,用户报告:手机(联通流量卡)访问软路由的 IPv6 地址时浏览器一直转圈。

这个现象之前单独排过一轮,过程记在联通 IPv6 打不通,客服只会说「你家没有公网 IP」里。这次手上有抓包和对照节点,能把它钉到具体方向上。

于是做了四组两两测试:

源目的结果
腾讯云 VPS软路由 WAN v6通
腾讯云 VPS手机 v6通
软路由手机不通(0/3)
手机软路由不通(转圈)

三方节点,只有联通内部这两段互不可达。第三方反而是唯一能同时触达双方的。

traceroute 定位断点

VPS → 手机(通)
  5  2408:8001:3161:f72c::4
  9  2408:8142:6000:f101:2::1     <- 移动侧
 12  2408:8142:6000:f501::f       <- 到达

软路由 → 手机(不通)
  1  2408:8001:3010:db::          <- 有响应
  2..20  *                        <- 再无节点响应

同一目标,两条路径的入口不同。固网侧的流量卡在 2408:8001:3010:: 之后。

抓包把方向钉死

让手机再访问一次,同时在软路由抓 src net 2408:8459::/32:

17:48:50.966  ...3d0c.36368 > ...:1dae.80: Flags [S], seq 4210686661
17:48:51.983  ...3d0c.36368 > ...:1dae.80: Flags [S], seq 4210686661
17:48:53.006  ...3d0c.36368 > ...:1dae.80: Flags [S], seq 4210686661
... 共 8 次,间隔 0.3s → 0.75s → 1.5s → 3s

手机的 SYN 到达了软路由(去程通),而同一个 seq 重传 8 次、指数退避,这是 TCP 在完全没收到任何回应时的标准行为。

再用 VPS 对照验证软路由会不会回 RST:

VPS: connect to ...:80 failed: Connection refused, after 4 ms

Connection refused 就是收到 RST。4 毫秒往返,证明软路由确实回了 RST,且跨网到腾讯云正常送达。

所以是单向的:

方向状态
手机 → 软路由(去程)通,SYN 到达网卡
软路由 → 手机(回程)断,RST 送不回去

排除其他可能

假设排除依据
源地址问题强制 WAN / 强制 LAN / 默认三种源,全部 0/3
协议问题TCP 和 ICMPv6 都受影响
整体路径故障同一出口到腾讯云、阿里都正常
手机无 IPv6有全局地址,且 VPS 能 ping 通它
BRAS 标志过滤56/56 全到达,见第三层

定位:联通固网 → 移动方向的单向路由/转发缺失。

更早一次的投诉经过写在联通下发的 IPv6 地址无法被直接访问:投诉历程里。

至于具体是「路由表缺条目」还是「ACL 丢弃」——从外部无法判定。因为运营商会过滤 ICMPv6 unreachable,恰好把唯一的判别信号遮掉了。我试过抓 ICMPv6 错误消息,探测手机期间一个都没有回来。

结果总表

项目值等级
BRAS 厂商华为实测(CHAP name)
BRAS 集群b1-s-gdgz-gq / b2-s-gdgz-xt实测(PADO AC-Name)
BRAS 接入地址10.1.128.1/32实测
BRAS 端口策略22 恒定 DROP,其余 RST实测(5 样本 × 低速)
控制平面有 CoPP,连续扫描触发 DROP 漂移实测(3 次运行不一致)
认证协议CHAP-MD5实测
AAA 后端gz.cncnet.net实测
并发会话上限1实测(online count=1 >= Max num=1)
顶号策略拒绝新会话,非顶号实测(主会话未变)
IPv6 入向标志过滤不存在,56/56 到达实测
NAT 类型对称型 NAT4实测(4 组复现)
PCP (RFC 6887)无响应实测(含对照组)
NAT-PMP (RFC 6886)无响应实测(含对照组)
IPv4 出口58.249.141.202 / 58.249.143.142(轮换)实测
IPv6 互通固网→移动单向断实测(抓包定位)
BRAS 型号未知未验证

这对用联通宽带的人意味着什么

三条能直接用的结论:

  1. 别指望多拨叠带宽。限制不在路由器,在 AAA 的 Max num=1。换光猫、换软路由、换固件都没用,这个值在运营商侧。
  2. 想要公网端口,只剩 IPv6 或投诉两条路。IPv4 侧是对称型 NAT4,映射端口随目的地变化,IP 和端口都猜不到;PCP / NAT-PMP 又不响应,没有标准化申请通道。这也是为什么 UPnP 在这条线上是哑的。NAT 类型怎么影响联机,游戏老是掉线、联机卡顿?先查 NAT 类型和 UPnP这篇讲得更细。
  3. IPv6 有公网地址 ≠ IPv6 能用。2408:8256 开头的 /64 是真的全球可路由,但固网到移动方向的回程是断的,手机直连家里设备会一直转圈。真要远程访问,还是走中转或者 VPS,人在外面怎么进家里的网这篇里有现成的搭法。

至于这台 BRAS 的具体型号,从外部探不到,只能到此为止。

如果你觉得这篇文章不错,有用的话可以点击下面的“赞赏”支持作者,你的支持是我更新的动力(饿饿吃饭)。

  • reward_image1
  • reward_image2
欢迎来到XiaoZou123,这里是一个电脑极客、数码爱好者网站。我平时喜欢关注数码新闻,研究计算机技术。如果你看我头像觉得我是二次元,那我其实还算不上!
最后更新于 2026-10-18