SFP 猫棒接管运营商光猫,外加 igmpproxy 跑通 IPTV

缘起

家里那台中国移动给配的 ZTE F663N 光猫,已经服役 6 年多了。

这玩意儿当年我就吐槽过——只有 LAN1 一个千兆口、限制 4 个终端、Wi-Fi 还停留在 2.4GHz 802.11n。当时实在受不了,就有了那篇 《解包和打包中兴光猫配置文件》,写了个工具把 cfg 解包改桥接,套自己的路由器和 AC Wi-Fi,总算把 100M 跑满了。

带宽从最早的 50Mbps 一路升到现在的 500Mbps,光猫这台硬件却没换过,一直在原地扛着。年头一长,老化的迹象就藏不住了:莫名其妙断流、PPPoE 重拨不上来、TR-069 后台时不时被推一份「看似为你好」的配置回来、最近半年甚至到了三五天就得手动重启一次的程度。修一台疲态尽显的运营商光猫,还要继续替它擦屁股,实在没意思。

正好赶上换路由器,挑了一台 BeeconMini SEED AC5——MT7987 SoC、2.5G 双 RJ45、自带 SFP 槽、带 PoE 输出。SFP 槽这个配置就很有意思:插一根 SFP 光猫棒,光纤直接进路由器,运营商那台砖头就可以扔抽屉里了。PoE 输出留着以后扩 AP 不用走砖头电源,这条也是加分项。

顺手再吐槽一句:现在所有运营商的 IPTV 都还在死磕「魔百盒 + 多播 VLAN」这套上古架构。智能电视、Apple TV、安卓盒子早就什么都能装,节目源完全可以做成一个 App,账号鉴权 + DRM 在云端搞定就行——非要塞一个独立硬件 + 多播 VLAN,本质是业务捆绑、硬件 KPI、加上多一道防盗看的物理壁垒。这套配下来网络拓扑直接复杂 N 倍,也是为什么大量用户最后还是认栽用回运营商光猫。这篇就把怎么绕开它记一下。

整体拓扑

flowchart TD
    ISP[ISP 光纤]
    SFP["HSGQ DFP-34X-2C2
(SFP 光猫棒,烧入运营商克隆 profile)"] AC5["BeeconMini SEED AC5
SFP 槽 + 2×2.5G + PoE"] LAN["br-lan
VLAN 6 untagged"] WAN["pppoe-wan
VLAN 41"] TR069["tr069
VLAN 46, DHCP"] IPTV["br-iptv + iptv_mcast
+ igmpproxy"] AP[AP / 客户端] NET[公网] ACS[运营商 ACS] MBOX["魔百盒
(移动 IPTV)"] TV[电视] ISP --> SFP --> AC5 AC5 --> LAN --> AP AC5 --> WAN --> NET AC5 --> TR069 --> ACS AC5 --> IPTV --> MBOX --> TV

SFP 光猫棒:选什么、怎么进

DFP-34X-2C2 SFP 光猫棒

这次用的是 HSGQ DFP-34X-2C2,Realtek RTL960x 方案。选它有两个理由:

  1. 能刷 ODI 固件,telnet 进去用 flash get / flash set 直接读写 boot 参数,比改运营商光猫的固件友好十倍。
  2. 社区资料齐全,OMCI 字段克隆有现成参考。

猫棒的进入方式:

Item Value
Default IP 192.168.1.1(无 DHCP,本机配静态 192.168.1.2
默认账号 admin / admin
Web UI http://192.168.1.1
Telnet telnet 192.168.1.1

固件用 Anime4000/RTL960x 仓库里的 ODI 系列。我实际刷的是 M114_sfp_ODI_Vlan_220414.tar(2022-04-14 的 VLAN 构建)。

扒运营商参数(克隆原光猫)

要让 OLT 接受这根猫棒,得让它自报家门跟原光猫一模一样。每家运营商的注册逻辑不太一样:

  • 广东移动:PLOAM 密码 = LOID(ASCII 转 hex)
  • 上海移动:取原光猫的 LOID 当 PLOAM 密码(再加 SN 和 VLAN ID)

需要的字段(下面用 <placeholder> 表示需要替换为实际值):

  • GPON_SN(如 CMHIxxxxxxxx,从原光猫超管页或 OMCI 抓出)
  • GPON_PLOAM_PASSWD(hex 形式,下面会讲怎么算)
  • ELAN_MAC_ADDR(原光猫 LAN MAC)
  • OUI(取 MAC 前 3 字节)
  • OMCI 相关:HW_CWMP_MANUFACTURER, HW_CWMP_PRODUCTCLASS, HW_HWVER, OMCI_SW_VER1/2, GPON_ONU_MODEL, OMCI_VEIP_SLOT_ID, OMCI_OLT_MODE

⚠️ 以下命令里所有具体值都是示例占位。实际克隆时请用你自己原光猫的真实参数,不要照抄博客里的字符串——那既不会工作,也不该公开。

最关键的一步是 OMCI 字段——OLT 在 OMCI 协商阶段会校验厂商和型号,对不上就拒绝注册。OLT 看的不是光猫外壳上印的型号,而是它在协商里报上来的那一串字段,所以克隆要从 OMCI 视角抄,不能从用户视角抄。从原光猫的 ctromfile.cfg(解码后的 OMCI/管理配置 XML)能看到 OLT 实际看到的是什么字段,照抄就行。

我家原光猫是 ZTE F663N,但它 OMCI 上 ONU Vendor ID 报的是 MTKc(MediaTek 芯片),ONU ModelEPONONU Hardware VersionPT632V1.0——这些都不能写错。F663N 内部 cfg 怎么解出来的,可以参考我之前那篇 解包打包工具

MAC_KEY 重生成

HSGQ 固件 V1.0-220304+ 引入了 MAC_KEY,每次改 ELAN_MAC_ADDR 都要重新生成:

1
2
echo -n "hsgq1.9aMAC_ADDR_UPPERCASE" | md5sum
# 例: hsgq1.9aAABBCCDDEEFF → <对应的 32 位 md5>

烧入 profile

最后用一组 flash set 把克隆参数刷进去:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
flash set PON_VENDOR_ID ZTEG
flash set HW_CWMP_MANUFACTURER ZTEG
flash set HW_CWMP_PRODUCTCLASS F663N
flash set HW_HWVER PT632V1.0
flash set OMCI_VEIP_SLOT_ID 255
flash set OMCI_OLT_MODE 3
flash set OMCI_FAKE_OK 1
flash set OMCI_SW_VER1 V1.1.0P1T9
flash set OMCI_SW_VER2 V1.1.0P1T9
flash set GPON_ONU_MODEL EPON
flash set GPON_SN <你的_GPON_SN>
flash set GPON_PLOAM_PASSWD <LOID_的_hex>
flash set ELAN_MAC_ADDR <原光猫_LAN_MAC>
flash set MAC_KEY <md5_of_hsgq1.9a+大写MAC>
flash set OUI <MAC前3字节用逗号分隔>
flash set HW_SERIAL_NO <原光猫_HW_serial>
reboot

⚠️ PLOAM 错了 ONU 会被拒,可恢复;但 SN 克隆错了可能被运营商风控标记。先在小范围验证再上线。

另外提一句合规:克隆 SN/MAC 是灰色地带,本文记的是自家电路上的折腾过程,不是教你去蹭别人的宽带。运营商真要查,PON 口下挂的设备都是看得见的。

猫棒重启后插回 SFP 槽,OLT 收到注册请求,验证通过会下发 PPPoE/IPTV/TR-069 的 service profile。

路由器配 VLAN:先把容易的搞定

BeeconMini SEED AC5

下游路由器用的是 SEED AC5(iStoreOS,MT7987),SFP 槽里就是上面那根猫棒,所以从猫棒出来的所有 VLAN 都直接落在 eth1 上。需要识别四个 VLAN:

VID 用途
41 互联网(PPPoE 拨号)
46 TR-069 管理
48 IPTV 单播控制(DHCP / 鉴权 / EPG / 回看 / 时移)
900 IPTV 多播视频流(直播)

**互联网 / TR-069 这部分是常规操作。**PPPoE 在 OpenWrt 里就是标准配置:

1
2
3
4
5
6
uci set network.wan.proto='pppoe'
uci set network.wan.device='eth1.41'
uci set network.wan.username='<你的宽带账号>@139.gd'
uci set network.wan.password='<密码>'
uci commit network
ifup wan

TR-069 类似,DHCP 拿地址但关掉 default route 和 peer DNS,避免污染主路由表:

1
2
3
4
uci set network.tr069.proto='dhcp'
uci set network.tr069.device='eth1.46'
uci set network.tr069.defaultroute='0'
uci set network.tr069.peerdns='0'

到这里互联网部分跑通——PPPoE 拨号、IPv6、CGNAT 全都正常,2.5G 物理口直接打满。

难的是 IPTV。这才是这篇文章的重头戏。

IPTV:以为很简单,结果……

家里有长辈,他们不像年轻人都看网络点播,习惯坐沙发上看电视直播——这也是为什么必须把 IPTV 跑通,不能「算了用网络版凑合」。

最初看这事是真不当回事的。原来那台 F663N 光猫上有一个独立的 IPTV LAN 口,运营商出厂时就把对应的 VLAN、PPPoE/IPoE、组播参数都在光猫内部配好了,魔百盒直接插那个口就能开机看直播,用户根本不需要知道 VLAN 48、VLAN 900 这些东西的存在。所以我换猫棒的时候很自然地想:那不就是把这一个口的逻辑挪到自己路由器上吗?给机顶盒拉一个 VLAN,配下 DHCP,应该半小时搞完。

结果整整折腾了一天。

第一阶段:只配 VLAN 48,发现少了点东西

最初的想法很朴素:VLAN 48 是单播控制,VLAN 900 是多播视频,先把 VLAN 48 弄通看看魔百盒能不能开机。

eth1.48 起一个接口、放一个 br-iptv、给魔百盒一个 DHCP——很顺利就跑起来了:

  • ✅ 魔百盒能开机、能登录、能鉴权
  • ✅ 频道列表、EPG 正常加载
  • 回看正常、时移正常(这俩走 HTTP 单播,VLAN 48 就够了)
  • 直播没画面

奇了怪了,回看都能看,怎么直播反而不行?看了一下才反应过来:直播走的是组播,封装在 VLAN 900 里,单纯把 VLAN 48 通了根本没把直播流喂下来。

试过的弯路(避坑)

在搞清楚要上 igmpproxy 之前,我先试了两条看起来更省事的路子,都不通,记下来给后人避坑:

弯路 A:VLAN 48 + VLAN 900 都做 untagged 扔同一个桥

直觉上是把所有 IPTV 流量都堆给魔百盒,让它自己处理。结果魔百盒同时从 VLAN 48 和 VLAN 900 收到 DHCP 响应,拿到两个 IP,状态机直接卡住——注册不上 IPTV 平台。

弯路 B:在 AC5 的交换芯片上把 LAN6 配两个 untagged VLAN

打算让交换机硬件层面就把两个 VLAN 都送到魔百盒口。结果发现 AC5 的 eth1 交换芯片不支持同一个端口配置两个 untagged VLAN——这是硬件限制,VLAN ingress 表里一个 port 只能落到一个 PVID。换句话说,想「魔百盒口同时收 VLAN 48 和 VLAN 900 的 untagged 帧」在 MT7987 这套交换芯片上根本配不出来。

走完这两条死路之后才确认:多播必须在路由器上做 L3 处理,不能指望硬件 L2 解决。

第二阶段:L2 桥跨 VLAN flood——还是不行

下一个直觉:把 eth1.48(控制 VLAN)和 eth1.900(多播 VLAN)扔进同一个 bridge br-iptv,开 IGMP snooping,让多播在桥里 flood 给魔百盒——这跟原 F663N 内部 IGMP-Proxy 的逻辑大致是一样的。

这套在 MT7987 上根本不工作。魔百盒能拿单播 IP,能登录,但选频道之后还是没画面。

抓包发现:

  • eth1.900 RX 哗哗地涌进多播包(一个频道几百 kB/s)
  • eth1.48 TX 几乎纹丝不动
  • bridge mdb show dev br-iptv 全空——即使魔百盒在发合法的 IGMP v2 report

三个原因,每个都能让你怀疑人生:

  1. MTK PPE / hwnat 在劫持流量。iStoreOS 默认 flow_offloading_hw=1,硬件加速会在 Linux bridge 之前就把多播包消化掉。即便 multicast_snooping=0 让桥强制 flood,硬件这层也拦着不放。

  2. 桥的 IGMP snooping mdb 表始终空的。桥本身没 IP,querier 源 IP 是 0.0.0.0,snooping 模块在这种状态下不会正确学习魔百盒的 join report。

  3. 同 trunk 的 split-horizon:魔百盒和 carrier 都通过同一根物理 eth1 走 VLAN 48 上来,Linux bridge 默认不会把流量送回入口端口(除非开 hairpin),所以即便桥成功转发,魔百盒也收不到 carrier 的回包。

结论:这套必须上 L3,让内核多播路由(MFC)来干这事,绕开桥和硬件加速

第三阶段:igmpproxy L3 方案

整体思路:

flowchart TD
    MBOX["魔百盒
(VLAN 48 untagged, 接交换机 LAN6)"] BR["br-iptv
192.168.99.1/30, 仅含 eth1.48
(downstream)"] PROXY(["igmpproxy
(kernel MFC)"]) UP["eth1.900
10.0.0.2/24, 独立 L3 接口
(upstream)"] CARRIER[Carrier 多播头端] MBOX --> BR BR --> PROXY PROXY --> UP UP --> CARRIER

/etc/config/network 关键部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# br-iptv 只放魔百盒这一侧
config device
option name 'br-iptv'
option type 'bridge'
list ports 'eth1.48'
option igmp_snooping '1'
option multicast_querier '0' # 让 igmpproxy 当 querier

config interface 'iptv'
option proto 'static'
option device 'br-iptv'
option ipaddr '192.168.99.1'
option netmask '255.255.255.252'
option defaultroute '0'

# eth1.900 独立做 upstream
config interface 'iptv_mcast'
option proto 'static'
option device 'eth1.900'
option ipaddr '10.0.0.2'
option netmask '255.255.255.0'
option defaultroute '0'
option peerdns '0'

防火墙加一个区让两边互通:

1
2
3
4
5
6
7
config zone
option name 'iptv'
option input 'ACCEPT'
option output 'ACCEPT'
option forward 'ACCEPT'
list network 'iptv'
list network 'iptv_mcast'

最重要:硬件 flow offload 必须关掉

1
2
uci set firewall.@defaults[0].flow_offloading_hw='0'
uci set firewall.@defaults[0].flow_offloading='1' # 软件 offload 留着

/etc/config/igmpproxy

1
2
3
4
5
6
7
8
9
10
11
12
config igmpproxy
option quickleave 1

config phyint iptv_mcast
option network 'iptv_mcast'
option direction 'upstream'
list altnet '183.235.0.0/16'

config phyint iptv
option network 'iptv'
option direction 'downstream'
list altnet '100.64.0.0/10'
1
2
/etc/init.d/igmpproxy enable
/etc/init.d/igmpproxy restart

收尾的坑:altnet 不能写 0.0.0.0/0

第一遍配的时候我图省事在 upstream 和 downstream 两边都写了 altnet 0.0.0.0/0igmpproxy 死活不工作,日志一直在刷:

1
warn igmpproxy: No interfaces found for source 100.98.x.y

ip mroute show 永远是 Iif: unresolved

原因在 igmpproxy 0.4getIfByAddress() 实现:它用最长前缀匹配来决定 IGMP 报文是从哪个 phyint 进来的。两边都是 0.0.0.0/0(mask = 0),两个接口都「匹配」但优先级一致,函数返回 NULL,IGMP 报文直接被拒收。

修法是给两边各自一个能区分来源 IP 的具体段

  • upstream eth1.900altnet 183.235.0.0/16 —— carrier 多播头端的源段(用 tcpdump -i eth1.900 -nn 'multicast' 实测确认)
  • downstream iptv (br-iptv):altnet 100.64.0.0/10 —— 运营商 CGNAT 大段(实测魔百盒拿到的就是 100.98.x.x 之类的 IP,全在这段里)

改完重启 igmpproxy,立即就工作了。

验证

1
2
3
4
5
6
7
8
9
10
11
12
13
# 内核 MFC 已建立路由
$ ip mroute show
(183.235.0.17,239.10.0.114) Iif: eth1.900 Oifs: br-iptv State: resolved

# br-iptv 出口数据流飙升(一个 HD 频道大约 3-4 MB / 12s)
$ ip -s link show br-iptv | grep -A1 TX
TX: bytes packets ...
3418950 2589

# 魔百盒 IGMP join 在 eth1.48 入口可见
$ tcpdump -i eth1.48 -nn -e 'igmp'
<MBOX-MAC> > 01:00:5e:0b:00:44, vlan 48, p 0,
<MBOX-IP> > 239.11.0.68: igmp v2 report 239.11.0.68

电视画面瞬间出来,遥控切台也丝滑。

三层心法

把这件事拆成三个层次记住:

  1. 运营商 PON 鉴权 = 克隆 OMCI 字段。SN / PLOAM 是公开的克隆要点,OMCI 那一堆 HW_CWMP_*OMCI_SW_VER*GPON_ONU_MODEL 才是 OLT 真正用来识别「你是不是合法设备」的地方。

  2. PPPoE / TR-069 / IPTV 单播 = L2 透传就行。VLAN 41/46/48 在 OpenWrt 上当成普通 8021q sub-interface 处理,连标准 NAT/DHCP 流程都能跑。注意 IPTV 的回看和时移都走单播 HTTP,只配 VLAN 48 就够;唯独直播是多播,必须额外处理 VLAN 900。

  3. 多播 IPTV ≠ L2 转发。在 MT7987 这类带 PPE 硬件加速的平台上,L2 桥跨 VLAN flood 多播是不工作的。正解是 L3:igmpproxy + 关 hwnat + altnet 写具体段

附带踩出来的几条经验:

  • getIfByAddress 的最长前缀匹配会让 0.0.0.0/0 成为陷阱,写 altnet 一定要用能区分来源的具体段。
  • flow_offloading_hw 默认开但对多播路由有害,软件 flow offload (flow_offloading=1) 是安全的——这个组合是 MT7987 这一代 SoC 上多播工作的硬条件。
  • multicast_querier=1 在桥没 IP 的情况下源会变成 0.0.0.0,许多机顶盒和 snooping 模块对这种 query 处理有问题——交给 igmpproxy 做 querier 反而更可靠。
  • 别试图在交换芯片上让一个端口同时 untagged 两个 VLAN,至少 MT7987 这套硬件不支持。

后记

跑了一段时间,记录一下实际效果:

  • 那台 ZTE F663N 已经收进抽屉吃灰,光纤直接进 AC5 的 SFP 槽。
  • PPPoE 的 500M 带宽轻轻松松跑满,2.5G 内网也能稳定 280MB/s+。
  • 直播 4K 频道流畅、回看时移都正常,切台没感觉跟原来有什么不同——这条最关键,验收通过。
  • 不再需要每隔几天去重启那台砖头光猫,省心。

回头看,这次折腾的本质是把光猫这个最后的「运营商飞地」也收进自己的网络管理体系里。六年前那次解 cfg 改桥接是软改,治标;这次直接把硬件也换掉,治本。下一步可能是 PoE 那条 LAN 出去带个吸顶 AP,全屋 Wi-Fi 6E 也升一升,不过那是另一篇文章了。

参考