博主头像

人間になりたい!!!!!


皖ICP备2025096275号

盛科 CTC7132 平台逆向: 绕过 group-speed, 解锁 SFP+ 的 2.5G 协商速率

前言

博主前段时间购入了一台基于 Centec CTC7132 平台的企业级交换机. 其拥有 48 个 2.5Gbe 电口, 4 个 SFP+ 光口和两个 40G QSFP 光口. 同时, 博主也购入了两条 2.5Gbe 的 SFP GPON 猫棒. 使用这类猫棒的一个硬性要求就是端口速率强制锁死在 2.5Gbe / 1Gbe, 否则无法使用. 当博主在其 WebUI 上尝试更改速率时, 出现了要求我使用 group-speed 命令更改的报错. 但使用 CLI 发现 group-speed 并没有可以将接口调制成 2.5Gbe 的命令, 遂深入研究.

CTC7132 平台

CTC7132(TsingMa)芯片是盛科面向云时代,边缘计算技术演进需求推出的第六代核心交换芯片。芯片集成ARM双核A53处理器,支持QSGMII和USXGMII等端口形态,提供从100M到100G的全速率端口能力。
芯片创新集成X-Engine™,包含了OAM/BFD Engine,Telemetry Engine,Security Engine和Wireless Engine等。提供线速的增强特性能力和更高的集成度,更好服务于边缘的多业务接入场景,满足网络未知,业务需求不断融合的网络发展趋势。

  • OAM/BFD Engine 提供高精度,多条目的OAM和BFD会话,并集成了RFC2544和TWAMP能力,提供IP RAN的接入解决方案。
  • Telemetry Engine 提供的NetFlow 功能,基于会话提供业务统计,丢包统计,提供最小和最大的时延抖动分析,为业务和流量优化提供数据支撑。
  • Wireless Engine 提供线速的CAPWAP加解封装以及分片重组能力,支撑1G/2.5G/5G多速率融合,有效支撑有线无线的融合。

作为盛科第六代芯片,CTC7132继承了丰富的特性,包括L2、L3、MPLS、VXLAN、MPLS SR和OAM/APS等特性,亮点特性包括可视化特性、可编程特性、安全和流量控制特性、高精度时钟特性。
CTC7132提供100M/1G/2.5G/5G/10G/25G/40G/50G/100G的端口速率。其SerDes设计满足多个端口协议:SGMII/QSGMII/USXGMII/XFI/SFI/XAUI/CAUI4/10G-KR/40G-KR4/100G-KR4/25G-KR/50G-KR2。

引用自 https://www.centec.com/silicon/Ethernet-Switch-Silicon/CTC7132

综上所述, 芯片侧是支持 2.5Gbe 速率的配置的.

研究目标

既然判断大概率不是 CPU 侧的限制, 那么研究方向主要集中在软件, 例如底层 SDK, 上层 CLI. 除此之外, 我们还需要探究为什么厂商要强制将四个 SFP+ 端口绑定在一个组内统一调整速率, 以及为什么不能调整 2.5Gbe 速率.

观前提示

阅读本文需要您具备一定的 Linux 基础知识, Linux 编程基础, 逆向基础, 计算机网络知识基础等. 如果您没有这些知识, 可能会阅读困难. 可以尝试借助 AI 来辅助理解.


开整

1. 固件分析

使用 binwalk 解包固件, 获取完整的 rootfs. 经过分析后可以得到以下信息

项值
内核版本4.9.62-tsingma (厂商自定义 LOCALVERSION)
架构AArch64 (ARM64), SMP
编译工具Linaro GCC 6.3.1 (2017.02)
构建时间2024-11-18 CST
平台Centec (盛科), CLDK v20.01

我们的核心目的是将端口速率更改至 2.5Gbe, 所以我们需要使得 group-speed 命令支持 2.5Gbe 速率的下发. 因此首要入手点是 group-speed 命令. 全盘搜索该字符串, 查找哪些二进制里包含这个字符串.

nanami114@NanamiWork-PC:~/firmware/moondon_webImage_v3.0.17.20250718/_uImage_v3.0.15.7.bin.extracted/_53E8E4.extracted/cpio-root$ grep -rl --binary-files=text "group-speed" usr/ lib/ sbin/ bin/
usr/sbin/imi
usr/sbin/nsm
usr/bin/imish
lib/libimish.so

在博主之前的分析中已经得出 imish 和 imi 均为用户态 OEM Shell, 只具备下发命令参数的功能, 不具备底层 SDK 实现. 因此, 这里我们需要检查的就是 usr/sbin/nsm.

2. 分析 nsm

2.1 group-speed 分析

在 IDA Pro 中对其进行静态逆向. 查找所有包含 group-speed 的函数

经过分析和查找, 发现在 00b9a090 处的 group-speed 指向了一个 sub_536744 函数, 对其进行分析.

完整的 sub_536744 伪代码如下

__int64 __fastcall sub_536744(__int64 a1, __int64 a2, const char **a3)
{
  unsigned __int8 v6; // [xsp+33h] [xbp+33h] BYREF
  int v7; // [xsp+34h] [xbp+34h] BYREF
  int v8; // [xsp+38h] [xbp+38h]
  char v9; // [xsp+3Fh] [xbp+3Fh]
  __int64 v10; // [xsp+40h] [xbp+40h]
  unsigned __int8 v11; // [xsp+4Fh] [xbp+4Fh]

  v10 = *(_QWORD *)(a1 + 64);
  v9 = 0;
  v11 = 0;
  v8 = 0;
  if ( *(_WORD *)(v10 + 302) != 2 )
  {
    (*(void (__fastcall **)(_QWORD, const char *))(a1 + 16))(
      *(_QWORD *)(a1 + 24),
      "%% Can only set speed on physical interface\n");
    return 1;
  }
  v8 = sub_50EB5C(v10, &v6, &v7);
  if ( v8 )
  {
    (*(void (__fastcall **)(_QWORD, const char *))(a1 + 16))(*(_QWORD *)(a1 + 24), "%% Get port group id fail \n");
    return 1;
  }
  if ( !v6 )
  {
    (*(void (**)(_QWORD, const char *, ...))(a1 + 16))(
      *(_QWORD *)(a1 + 24),
      "%% Unsupported CLI in present interface. Please refer to CLI \"speed %s\"\n",
      *a3);
    return 1;
  }
  if ( (((unsigned __int8)byte_B9B4E8 >> 7) & 1) != 0 )
  {
    if ( (unsigned int)sub_50F1B0(*(unsigned int *)(v10 + 80)) )
    {
      byte_B9B4E8 = 1;
    }
    else if ( (unsigned int)sub_50F184(*(unsigned int *)(v10 + 80)) )
    {
      byte_B9B4E8 = 2;
    }
    else if ( (unsigned int)sub_50F100(*(unsigned int *)(v10 + 80)) )
    {
      byte_B9B4E8 = 3;
    }
  }
  sub_49ABCC(v10);
  sub_49AC88(v10);
  sub_49ACFC(v10);
  v9 = (1 << (v6 - 1)) & byte_CAB190;
  if ( !strncmp(*a3, "1000", 4u) )
  {
    if ( v7 && (v7 & 2) == 0 )
      return sub_52C79C(a1, 4294967051LL);
    if ( (unsigned int)sub_50F1B0(*(unsigned int *)(v10 + 80)) && v9 )
    {
      (*(void (__fastcall **)(_QWORD, const char *))(a1 + 16))(
        *(_QWORD *)(a1 + 24),
        "speed has already been 1G, no need to config \n");
      return 20;
    }
    v11 = 1;
  }
  else if ( !strncmp(*a3, "10G", 3u) )
  {
    if ( v7 && (v7 & 4) == 0 )
      return sub_52C79C(a1, 4294967051LL);
    if ( (unsigned int)sub_50F184(*(unsigned int *)(v10 + 80)) && v9 )
    {
      (*(void (__fastcall **)(_QWORD, const char *))(a1 + 16))(
        *(_QWORD *)(a1 + 24),
        "speed has already been 10G, no need to config \n");
      return 20;
    }
    v11 = 2;
  }
  else if ( !strncmp(*a3, "25G", 3u) )
  {
    if ( v7 && (v7 & 0x80) == 0 )
      return sub_52C79C(a1, 4294967051LL);
    if ( (unsigned int)sub_50F100(*(unsigned int *)(v10 + 80)) && v9 )
    {
      (*(void (__fastcall **)(_QWORD, const char *))(a1 + 16))(
        *(_QWORD *)(a1 + 24),
        "speed has already been 25G, no need to config \n");
      return 20;
    }
    v11 = 3;
  }
  else if ( !strncmp(*a3, "default", 7u) )
  {
    if ( ((unsigned int)sub_50F1B0(*(unsigned int *)(v10 + 80)) && byte_B9B4E8 == 1
       || (unsigned int)sub_50F184(*(unsigned int *)(v10 + 80)) && byte_B9B4E8 == 2
       || (unsigned int)sub_50F100(*(unsigned int *)(v10 + 80)) && byte_B9B4E8 == 3)
      && v9 )
    {
      (*(void (__fastcall **)(_QWORD, const char *))(a1 + 16))(
        *(_QWORD *)(a1 + 24),
        "speed has already been default value, no need to config \n");
      return 20;
    }
    v11 = byte_B9B4E8;
  }
  v8 = sub_510E9C(v10, v11, v6);
  if ( v8 )
  {
    (*(void (__fastcall **)(_QWORD, const char *))(a1 + 16))(*(_QWORD *)(a1 + 24), "%% Set speed failed!\n");
    return 1;
  }
  else
  {
    if ( !strncmp(*a3, "default", 7u) || v11 == (unsigned __int8)byte_B9B4E8 )
    {
      byte_CAB191 &= ~(unsigned __int8)(1 << (v6 - 1));
    }
    else
    {
      byte_CAB191 |= 1 << (v6 - 1);
      dword_CAB194 &= ~(15 << (4 * (v6 - 1)));
      dword_CAB194 |= v11 << (4 * (v6 - 1));
    }
    byte_CAB190 |= 1 << (v6 - 1);
    return 0;
  }
}

可以明显看出, 这个函数就是 group-speed 的处理函数. 因此我们标记其为 0x536744.
在该函数中, 有几处明显和速率有关的 strncmp

strncmp(*a3, "1000", 4u)
strncmp(*a3, "10G", 3u)
strncmp(*a3, "25G", 3u)
strncmp(*a3, "default", 7u)

其中 1000, 10G, 25G, default 恰好对应了 group-speed 的四种速率状态

Switch(config-if)# group-speed ?
  1000     Switch speed of all interface in group to 1G
  10G      Switch speed of all interface in group to 10G
  25G      Switch speed of all interface in group to 25G
  default  Switch speed of all interface in group to default value

同时我们还可以看出, 所有用户输入处理完成后, 被统一交付给了一个 sub_510E9C 函数进行处理, 然后将返回值交给 v8 参数, 作为命令执行的结果. 因此我们这里定位到 sub_510E9C 进行研究

__int64 __fastcall sub_510E9C(__int64 a1, unsigned __int8 a2, unsigned __int8 a3)
{
  if ( !a1 )
    return 4294967287LL;
  if ( *(_WORD *)(a1 + 302) != 2 )
    return 4294967284LL;
  if ( (unsigned int)sub_4FC03C(a1, a2, a3) )
    return 0xFFFFFFFFLL;
  return 0;
}

该函数指向了 sub_4FC03C, 继续分析

__int64 __fastcall sub_4FC03C(__int64 a1, unsigned __int8 a2, unsigned __int8 a3)
{
  if ( *(_WORD *)(a1 + 302) == 2 )
    return (unsigned int)sub_9C0DC4(*(unsigned int *)(a1 + 24), a2, a3);
  else
    return 128;
}

该函数指向了 sub_9C0DC4, 继续分析

__int64 __fastcall sub_9C0DC4(int a1, char a2, char a3)
{
  int v4; // [xsp+28h] [xbp+28h] BYREF
  char v5; // [xsp+2Ch] [xbp+2Ch]
  char v6; // [xsp+2Dh] [xbp+2Dh]

  v4 = a1;
  v5 = a2;
  v6 = a3;
  return sub_9B4D70(929, &v4, 8, 0, 0, 0, 0);
}

该函数指向了 sub_9B4D70, 继续分析

__int64 __fastcall sub_9B4D70(
        unsigned int a1,
        const void *a2,
        size_t a3,
        __int64 a4,
        __int64 a5,
        __int64 a6,
        __int64 a7)
{
  int v8; // w0
  _DWORD s[5]; // [xsp+58h] [xbp+58h] BYREF
  int v14; // [xsp+6Ch] [xbp+6Ch]
  __int64 v15; // [xsp+70h] [xbp+70h]
  void *dest; // [xsp+78h] [xbp+78h]

  dest = 0;
  v15 = 0;
  v14 = 0;
  memset(s, 0, 0x10u);
  if ( dword_E51FE0 )
  {
    if ( a1 <= 0x479 )
    {
      if ( !a3 || a2 )
      {
        if ( sock_shal >= 0 )
        {
          if ( qword_E4FF98 )
          {
            log_sys(158, 1, "hal socket not connect, send msg(%d) fail", a1);
            return 0xFFFFFFFFLL;
          }
          else if ( a3 <= 0xFF4 )
          {
            if ( qword_E51FF0 && *(unsigned __int16 *)(qword_E51FF0 + 16) + a3 + 12 > 0x1000 )
              sub_9B4B60();
            v8 = dword_E4FF84++;
            s[2] = v8;
            s[1] = a3 + 12;
            s[0] = a1;
            v15 = sub_9B3F1C(&sock_shal, a3 + 12);
            if ( v15 )
            {
              dest = (void *)(v15 + 18 + *(unsigned __int16 *)(v15 + 16));
              memcpy(dest, s, 0xCu);
              *(_WORD *)(v15 + 16) += 12;
              dest = (char *)dest + 12;
              memcpy(dest, a2, a3);
              *(_WORD *)(v15 + 16) += a3;
              if ( !dword_E51FA4 || a7 )
              {
                while ( qword_E51FE8 )
                {
                  v14 = sub_9B4B60();
                  if ( v14 == -2 )
                    pal_delay(1000);
                }
              }
              return 0;
            }
            else
            {
              log_sys(158, 1, "hal talk: no memory");
              return 0xFFFFFFFFLL;
            }
          }
          else
          {
            log_sys(158, 1, "hal talk: req_len is too longer");
            return 0xFFFFFFFFLL;
          }
        }
        else
        {
          return 0xFFFFFFFFLL;
        }
      }
      else
      {
        log_sys(158, 3, "req == NULL, but req_len != 0");
        return 0xFFFFFFFFLL;
      }
    }
    else
    {
      log_sys(158, 3, "Msg type error, 0x%x", a1);
      return 0;
    }
  }
  else
  {
    log_sys(158, 1, "hal socket not init");
    return 0xFFFFFFFFLL;
  }
}

不难看出, 函数 sub_9B4D70 就是用户 CLI 与板层 HAL 通讯的函数.
纵观整个 group-speed 实现, 我们不难发现, 该命令根本没有 2.5Gbe 速率的分支, 从底层也根本没实现 2.5Gbe 速率的下发. 因此 group-speed 对于我们的目的而言已经没有研究的价值了. 现在的方向应该是检查在什么情况下 speed 命令才会拒绝下发命令, 转而要求使用 group-speed.

2.2 speed 分析

查找所有包含 speed 的函数

在 sub_534568 函数中发现了关于配置速率报错的字符串输出, 我们这里重点分析该函数

__int64 __fastcall sub_534568(__int64 a1, __int64 a2, const char **a3)
{
  void (*v4)(__int64, const char *, ...); // x19
  __int64 v5; // x20
  const char *v6; // x0
  void (*v7)(__int64, const char *, ...); // x19
  __int64 v8; // x20
  const char *v9; // x21
  const char *v10; // x0
  char dest[2]; // [xsp+50h] [xbp+50h] BYREF
  __int64 v14; // [xsp+52h] [xbp+52h]
  char v15; // [xsp+5Bh] [xbp+5Bh] BYREF
  unsigned int v16; // [xsp+5Ch] [xbp+5Ch]
  __int64 v17; // [xsp+60h] [xbp+60h]
  __int64 v18; // [xsp+68h] [xbp+68h]
  int v19; // [xsp+74h] [xbp+74h]
  __int64 v20; // [xsp+78h] [xbp+78h]
  unsigned int v21; // [xsp+84h] [xbp+84h]
  char v22; // [xsp+8Bh] [xbp+8Bh]
  unsigned int v23; // [xsp+8Ch] [xbp+8Ch]

  v23 = 0;
  v20 = *(_QWORD *)(a1 + 64);
  v22 = 1;
  v21 = 0;
  if ( *(_WORD *)(v20 + 302) == 2 )
  {
    v15 = 0;
    v19 = 0;
    v19 = sub_50EB5C(v20, &v15, 0);
    if ( !v19 )
    {
      if ( v15 )
      {
        *(_WORD *)dest = 0;
        v14 = 0;
        strcpy(dest, *a3);
        (*(void (**)(_QWORD, const char *, ...))(a1 + 16))(
          *(_QWORD *)(a1 + 24),
          "%% Unsupported CLI in present interface. Please refer to CLI \"group-speed %s\"\n",
          *a3);
        return 1;
      }
    }
  }
  if ( *(int *)(v20 + 76) < 0 && !*(_DWORD *)(v20 + 84) )
  {
    (*(void (__fastcall **)(_QWORD, const char *))(a1 + 16))(
      *(_QWORD *)(a1 + 24),
      "%% Can not set speed with interface media type auto select\n");
    return 1;
  }
  v18 = *(_QWORD *)(v20 + 448);
  if ( v18 && *(_BYTE *)(v18 + 125) && *(_BYTE *)(v18 + 96) != 1 )
  {
    (*(void (__fastcall **)(_QWORD, const char *))(a1 + 16))(
      *(_QWORD *)(a1 + 24),
      "%% Cannot configure aggregator member\n");
    return 1;
  }
  v17 = *(_QWORD *)(v20 + 448);
  if ( v17 && *(_BYTE *)(v17 + 125) && *(_BYTE *)(v17 + 96) != 2 )
  {
    (*(void (__fastcall **)(_QWORD, const char *))(a1 + 16))(*(_QWORD *)(a1 + 24), "%% Cannot configure aggregator\n");
    return 1;
  }
  if ( *(_WORD *)(v20 + 302) != 2 && *(_WORD *)(v20 + 302) != 9 )
  {
    v4 = *(void (**)(__int64, const char *, ...))(a1 + 16);
    v5 = *(_QWORD *)(a1 + 24);
    v6 = (const char *)sub_49EB94(*(unsigned __int16 *)(v20 + 302));
    v4(v5, "%% Can't set speed on %s interface\n", v6);
    return 1;
  }
  if ( !strncmp(*a3, "400G", 4u) )
  {
    v23 = 12;
  }
  else if ( !strncmp(*a3, "200G", 4u) )
  {
    v23 = 11;
  }
  else if ( !strncmp(*a3, "100G", 4u) )
  {
    v23 = 6;
  }
  else if ( !strncmp(*a3, "40G", 3u) )
  {
    v23 = 5;
  }
  else if ( !strncmp(*a3, "50G", 3u) )
  {
    v23 = 10;
  }
  else if ( !strncmp(*a3, "25G", 3u) )
  {
    v23 = 9;
  }
  else if ( !strncmp(*a3, "10G", 3u) )
  {
    v23 = 4;
  }
  else if ( !strncmp(*a3, "5G", 2u) )
  {
    v23 = 8;
  }
  else if ( !strncmp(*a3, "2G5", 3u) )
  {
    v23 = 7;
  }
  else if ( !strncmp(*a3, "1000", 4u) )
  {
    v23 = 3;
  }
  else if ( !strncmp(*a3, "100", 3u) )
  {
    v23 = 2;
  }
  else if ( !strncmp(*a3, "10", 2u) )
  {
    v23 = 1;
  }
  else
  {
    if ( strncmp(*a3, "auto", 4u) )
    {
      (*(void (**)(_QWORD, const char *, ...))(a1 + 16))(*(_QWORD *)(a1 + 24), "%% Invalid speed : %s\n", *a3);
      return 1;
    }
    v23 = 0;
  }
  if ( v23 && *(_DWORD *)(v20 + 268) && *(_DWORD *)(v20 + 820) == 1 )
  {
    (*(void (__fastcall **)(_QWORD, const char *))(a1 + 16))(
      *(_QWORD *)(a1 + 24),
      "%% Please disable eee function first\n");
    return 1;
  }
  else
  {
    if ( *(_WORD *)(v20 + 302) != 2
      || !(unsigned int)sub_50F024(*(unsigned int *)(v20 + 80))
      && !(unsigned int)sub_50F050(*(unsigned int *)(v20 + 80))
      && !(unsigned int)sub_50F07C(*(unsigned int *)(v20 + 80))
      && !(unsigned int)sub_50F100(*(unsigned int *)(v20 + 80))
      && !(unsigned int)sub_50F0D4(*(unsigned int *)(v20 + 80))
      && !(unsigned int)sub_50F0A8(*(unsigned int *)(v20 + 80))
      && !(unsigned int)sub_50F184(*(unsigned int *)(v20 + 80)) )
    {
      v22 = 0;
    }
    if ( v22 && (*(_WORD *)(v20 + 836) & 0x80) == 0 )
    {
      if ( v23 && v23 != 12 && v23 != 11 && v23 != 6 && v23 != 10 && v23 != 9 && v23 != 4 && v23 != 5 )
        v21 = 20;
      if ( (*(_WORD *)(v20 + 836) & 0x3F) == 2 )
      {
        if ( v23 == 12 )
          v21 = 20;
        if ( v23 == 11 )
          v21 = 20;
        if ( v23 == 6 )
          v21 = 20;
        if ( v23 == 10 )
          v21 = 20;
      }
      if ( (*(_WORD *)(v20 + 836) & 0x3F) == 1 )
      {
        if ( v23 == 12 )
          v21 = 20;
        if ( v23 == 11 )
          v21 = 20;
        if ( v23 == 6
          && (unsigned int)sub_50F0D4(*(unsigned int *)(v20 + 80))
          && (unsigned int)sub_50F07C(*(unsigned int *)(v20 + 80)) )
        {
          v21 = 20;
        }
        if ( v23 == 10
          && (unsigned int)sub_50F0D4(*(unsigned int *)(v20 + 80))
          && (unsigned int)sub_50F100(*(unsigned int *)(v20 + 80)) )
        {
          v21 = 20;
        }
        if ( v23 == 5 || v23 == 4 )
          v21 = 20;
      }
      if ( (*(_WORD *)(v20 + 836) & 0x3F) == 4 || (*(_WORD *)(v20 + 836) & 0x3F) == 8 )
      {
        if ( v23 == 5 || v23 == 9 || v23 == 4 )
          v21 = 20;
        if ( v23 == 10 )
        {
          if ( (unsigned int)sub_50F0D4(*(unsigned int *)(v20 + 80))
            && (unsigned int)sub_50F07C(*(unsigned int *)(v20 + 80)) )
          {
            v21 = 20;
          }
          if ( (unsigned int)sub_50F0D4(*(unsigned int *)(v20 + 80))
            && !(unsigned int)sub_50F100(*(unsigned int *)(v20 + 80))
            && !(unsigned int)sub_50F07C(*(unsigned int *)(v20 + 80)) )
          {
            v21 = 20;
          }
        }
        if ( v23 == 6 )
        {
          if ( (unsigned int)sub_50F07C(*(unsigned int *)(v20 + 80))
            && (unsigned int)sub_50F050(*(unsigned int *)(v20 + 80)) )
          {
            v21 = 20;
          }
          if ( (unsigned int)sub_50F0A8(*(unsigned int *)(v20 + 80))
            && (unsigned int)sub_50F07C(*(unsigned int *)(v20 + 80))
            && !(unsigned int)sub_50F050(*(unsigned int *)(v20 + 80)) )
          {
            v21 = 20;
          }
        }
      }
      if ( (*(_WORD *)(v20 + 836) & 0x3F) == 0 )
      {
        if ( v23 == 11 || v23 == 12 )
          v21 = 20;
        if ( v23 == 6
          && (unsigned int)sub_50F0D4(*(unsigned int *)(v20 + 80))
          && (unsigned int)sub_50F07C(*(unsigned int *)(v20 + 80)) )
        {
          v21 = 20;
        }
        if ( v23 == 10
          && (unsigned int)sub_50F0D4(*(unsigned int *)(v20 + 80))
          && (unsigned int)sub_50F100(*(unsigned int *)(v20 + 80)) )
        {
          v21 = 20;
        }
      }
    }
    if ( v23 == 8 && (*(_BYTE *)(v20 + 249) || *(_BYTE *)(v20 + 248)) )
    {
      (*(void (__fastcall **)(_QWORD, const char *))(a1 + 16))(
        *(_QWORD *)(a1 + 24),
        "%% Please disable flowcontrol send and receive function first\n");
      return 1;
    }
    else
    {
      v16 = sub_510A48(*(unsigned int *)(*(_QWORD *)(a1 + 136) + 16LL), v20, v23);
      if ( !v16 )
        *(_BYTE *)(v20 + 401) |= 4u;
      if ( (unsigned int)sub_52C79C(a1, v16) )
      {
        return 1;
      }
      else
      {
        v7 = *(void (**)(__int64, const char *, ...))(a1 + 16);
        v8 = *(_QWORD *)(a1 + 24);
        v9 = (const char *)sub_49FF90(*(unsigned __int16 *)(v20 + 836));
        v10 = (const char *)sub_49ED5C(*(unsigned int *)(v20 + 68));
        v7(
          v8,
          "%% Warning: FEC %s can be configured but not in effect on %s port\n"
          "%% Please use show interface cmd to confirm FEC status details\n",
          v9,
          v10);
        return v21;
      }
    }
  }
}

可以看出, 该函数中包含了 speed 命令的多个速率模式 (auto, 10, 100, 1000, 10G, 40G, 100G), 以及关键用户侧报错 %% Unsupported CLI in present interface. Please refer to CLI \"group-speed %s\"\n. 定位到报错位置具体分析代码.

  if ( *(_WORD *)(v20 + 302) == 2 )
  {
    v15 = 0;
    v19 = 0;
    v19 = sub_50EB5C(v20, &v15, 0);
    if ( !v19 )
    {
      if ( v15 )
      {
        *(_WORD *)dest = 0;
        v14 = 0;
        strcpy(dest, *a3);
        (*(void (**)(_QWORD, const char *, ...))(a1 + 16))(
          *(_QWORD *)(a1 + 24),
          "%% Unsupported CLI in present interface. Please refer to CLI \"group-speed %s\"\n",
          *a3);
        return 1;
      }
    }
  }

从代码中我们可以看出, 只有当 sub_50EB5C 返回非零值时, 才会触发相应检测. 这里我们查看其汇编继续分析

.text:00000000005367AC
.text:00000000005367AC loc_5367AC                              ; CODE XREF: sub_536744+38↑j
.text:00000000005367AC                 ADD             X1, X29, #0x50+var_1C
.text:00000000005367B0                 ADD             X0, X29, #0x50+var_1D
.text:00000000005367B4                 MOV             X2, X1
.text:00000000005367B8                 MOV             X1, X0
.text:00000000005367BC                 LDR             X0, [X29,#0x50+var_10]
.text:00000000005367C0                 BL              sub_50EB5C
.text:00000000005367C4                 STR             W0, [X29,#0x50+var_18]
.text:00000000005367C8                 LDR             W0, [X29,#0x50+var_18]
.text:00000000005367CC                 CMP             W0, #0
.text:00000000005367D0                 B.EQ            loc_536800
.text:00000000005367D4                 LDR             X0, [X29,#0x50+var_28]
.text:00000000005367D8                 LDR             X2, [X0,#0x10]
.text:00000000005367DC                 LDR             X0, [X29,#0x50+var_28]
.text:00000000005367E0                 LDR             X3, [X0,#0x18]
.text:00000000005367E4                 ADRL            X0, aGetPortGroupId ; "%% Get port group id fail \n"
.text:00000000005367EC                 MOV             X1, X0
.text:00000000005367F0                 MOV             X0, X3
.text:00000000005367F4                 BLR             X2
.text:00000000005367F8                 MOV             W0, #1
.text:00000000005367FC                 B               loc_536DCC

这里我们可以看到, 该函数执行后调用了 sub_50EB5C, 然后读取其返回值, 当其返回值为 0 时, 继续执行 loc_536800 处代码, 当其返回值非 0 时, 输出 %% Get port group id fail \n. 因此这里我们可以判断, sub_50EB5C 的语义正为用户态的 get port group id.
所以这里得出的结论是: 当 speed 检测到 group-id 非 0 时触发告警, 并让用户使用 group-speed 指定速率.

3. 研究判断逻辑

既然我们现在已经知道 speed 命令检测的是 group-id, 那么我们就从这里下手. 现在我们就来研究怎么样让 speed 命令绕过 group-id 的检测, 直接向板卡下发命令.
在上面的分析中我们可以得知, sub_536744 函数是 group-speed 命令的处理函数. 我们现在要找的是 speed, 所以肯定还有别的函数是包含 Unsupported CLI in present interface 报错的. 经过全局搜索后, 发现除了 sub_536744 以外, 只有 sub_534568 是包含这个报错的.

因此我们这里分析该函数.

if ( *(_WORD *)(v20 + 302) == 2 )
  {
    v15 = 0;
    v19 = 0;
    v19 = sub_50EB5C(v20, &v15, 0);
    if ( !v19 )
    {
      if ( v15 )
      {
        *(_WORD *)dest = 0;
        v14 = 0;
        strcpy(dest, *a3);
        (*(void (**)(_QWORD, const char *, ...))(a1 + 16))(
          *(_QWORD *)(a1 + 24),
          "%% Unsupported CLI in present interface. Please refer to CLI \"group-speed %s\"\n",
          *a3);
        return 1;
      }
    }
  }

从该函数中得知, 当 v15 非 0 时, 会触发报错. 而修改 v15 的函数为 sub_50EB5C, 也就是上面所说的 get port group id. 所以我们这里直接查看汇编, 看看哪个地址调用了该函数.

.text:00000000005345AC                 B.NE            loc_534644
.text:00000000005345B0                 STRB            WZR, [X29,#0x90+var_35]
.text:00000000005345B4                 STR             WZR, [X29,#0x90+var_1C]
.text:00000000005345B8                 ADD             X0, X29, #0x90+var_35
.text:00000000005345BC                 MOV             X2, #0
.text:00000000005345C0                 MOV             X1, X0
.text:00000000005345C4                 LDR             X0, [X29,#0x90+var_18]
.text:00000000005345C8                 BL              sub_50EB5C
.text:00000000005345CC                 STR             W0, [X29,#0x90+var_1C]
.text:00000000005345D0                 LDR             W0, [X29,#0x90+var_1C]
.text:00000000005345D4                 CMP             W0, #0
.text:00000000005345D8                 B.NE            loc_534644
.text:00000000005345DC                 LDRB            W0, [X29,#0x90+var_35]
.text:00000000005345E0                 CMP             W0, #0
.text:00000000005345E4                 B.EQ            loc_534644
.text:00000000005345E8                 ADRL            X0, word_AAD088
.text:00000000005345F0                 LDRH            W0, [X0]
.text:00000000005345F4                 STRH            W0, [X29,#0x90+dest]
.text:00000000005345F8                 STUR            XZR, [X29,#0x90+var_3E]
.text:00000000005345FC                 LDR             X0, [X29,#0x90+var_58]
.text:0000000000534600                 LDR             X1, [X0] ; src
.text:0000000000534604                 ADD             X0, X29, #0x90+dest ; dest
.text:0000000000534608                 BL              .strcpy
.text:000000000053460C                 LDR             X0, [X29,#0x90+var_48]
.text:0000000000534610                 LDR             X3, [X0,#0x10]
.text:0000000000534614                 LDR             X0, [X29,#0x90+var_48]
.text:0000000000534618                 LDR             X4, [X0,#0x18]
.text:000000000053461C                 LDR             X0, [X29,#0x90+var_58]
.text:0000000000534620                 LDR             X1, [X0]
.text:0000000000534624                 ADRL            X0, aUnsupportedCli ; "%% Unsupported CLI in present interface"...
.text:000000000053462C                 MOV             X2, X1
.text:0000000000534630                 MOV             X1, X0
.text:0000000000534634                 MOV             X0, X4
.text:0000000000534638                 BLR             X3
.text:000000000053463C                 MOV             W0, #1
.text:0000000000534640                 B               loc_53504C

从汇编码中可以看到, 在 0x5345C8 位置调用了该函数. 查看上下文, 可以发现:

  • .text:00000000005345C4 LDR X0, [X29,#0x90+var_18] 为 ifentry
  • sub_50EB5C 获取 group-id
  • .text:00000000005345CC STR W0, [X29,#0x90+var_1C] 保存返回值
  • .text:00000000005345D8 B.NE loc_534644 取失败, 当作无组接口继续
  • .text:00000000005345DC LDRB W0, [X29,#0x90+var_35] 取上文获取的返回值
  • .text:00000000005345E4 B.EQ loc_534644 group==0, 跳至正常 speed 设置速率.

现在已经分析的很明确了: 0x5345E4 就是判断 group-id 为 0 则正常下发速率的命令. 到了这一步我们就可以做一个尝试: 将这一块的 B.EQ 改为 B, 即无论如何都走正常速率下发流程.

4. 第一次 Patch

知道了手段, 那么我们就尝试写一份内存 Patch 脚本, 然后在设备上运行.

注: 该设备的 Linux Root Shell 已被博主破解, 目前已和厂商反馈并申请相关 CVE-ID. 在厂商明确修复完成并下放补丁前不做技术细节公开.

已知设备上有 Python 2.7 环境, 所以我们这里用 Python 写内存补丁.

GPATCH = 0x5345e4
wr(ABIL, struct.pack('<I', cur | 0x20))      # 给能力位图补 0x20
wr(GPATCH, binascii.unhexlify('18000014'))   # b.eq -> b

应用内存补丁后, 尝试使用 speed 命令进行修改. 虽然没有报错使用 group-speed, 但是出现了新的报错: % Can not set speed with interface media type auto select.

5. 定位首次 Patch 报错点

在 0x534674 处发现该报错的字符串

.text:0000000000534674                 ADRL            X0, aCanNotSetSpeed ; "%% Can not set speed with interface med"...

查看具体汇编码

.text:0000000000534644 loc_534644                              ; CODE XREF: sub_534568+44↑j
.text:0000000000534644                                         ; sub_534568+70↑j ...
.text:0000000000534644                 LDR             X0, [X29,#0x90+var_18]
.text:0000000000534648                 LDR             W0, [X0,#0x4C]
.text:000000000053464C                 CMP             W0, #0
.text:0000000000534650                 B.GE            loc_534690
.text:0000000000534654                 LDR             X0, [X29,#0x90+var_18]
.text:0000000000534658                 LDR             W0, [X0,#0x54]
.text:000000000053465C                 CMP             W0, #0
.text:0000000000534660                 B.NE            loc_534690
.text:0000000000534664                 LDR             X0, [X29,#0x90+var_48]
.text:0000000000534668                 LDR             X2, [X0,#0x10]
.text:000000000053466C                 LDR             X0, [X29,#0x90+var_48]
.text:0000000000534670                 LDR             X3, [X0,#0x18]
.text:0000000000534674                 ADRL            X0, aCanNotSetSpeed ; "%% Can not set speed with interface med"...
.text:000000000053467C                 MOV             X1, X0
.text:0000000000534680                 MOV             X0, X3
.text:0000000000534684                 BLR             X2
.text:0000000000534688                 MOV             W0, #1
.text:000000000053468C                 B               loc_53504C

这里可以看到, 该报错的执行条件是 [ifentry+0x4c] < 0 && [ifentry+0x54] == 0. 读取各口字段发现厂商做了如下处理

端口[+0x4c][+0x54]结果
电口 / QSFP正数0通过
SFP+ 51~54负数0报错

也就是说当 media-type 为 电口 / QSFP 时, 可以通过检查. 厂商 CLI 中有直接修改 media-type 的相关命令, 所以这里尝试直接在 CLI 中修改. 修改完成后再次使用 speed 命令下发指令, 随即被设备接受.

6. 第一次踩坑

应用速率后, 接入设备查看连接. 发现等了好一会并没有成功协商 2.5Gbe 速率. 再次下发指令发现出现新的报错: % Unsupport port speed ability. 查看设备日志发现, 手动更改的 media-type 被守卫进程 hsrvd 从 HAL 层重新拉回了 0x04. 因此这里得出了一个结论:
数据补丁不可靠, 必须直接改代码.

7. 尝试更改代码

经过上面的步骤, 博主发现: 能力限制的真正源头在 HAL 层, nsm 只是通讯中间件.
经过一番查找, 发现类型门校验在 0x50f920 处. 查看附近汇编码

.text:000000000050F750                 ADD             X0, X29, #0x30+var_10
.text:000000000050F754                 MOV             X1, X0
.text:000000000050F758                 LDR             X0, [X29,#0x30+var_18]
.text:000000000050F75C                 BL              sub_50EAAC
.text:000000000050F760                 LDR             W0, [X29,#0x30+var_10]
.text:000000000050F764                 AND             W0, W0, #0x1FFF
.text:000000000050F768                 STR             W0, [X29,#0x30+var_4]
.text:000000000050F76C                 LDR             W0, [X29,#0x30+var_8]
.text:000000000050F770                 AND             W0, W0, #0xFFFFFF
.text:000000000050F774                 STR             W0, [X29,#0x30+var_C]
...
.text:000000000050F910 loc_50F910                              ; CODE XREF: sub_50F728+1D4↑j
.text:000000000050F910                 LDR             W0, [X29,#0x30+var_1C]
.text:000000000050F914                 CMP             W0, #7
.text:000000000050F918                 B.NE            loc_50F92C
.text:000000000050F91C                 LDR             W0, [X29,#0x30+var_C]
.text:000000000050F920                 AND             W0, W0, #0x20 ; ' '
.text:000000000050F924                 CMP             W0, #0
.text:000000000050F928                 B.NE            loc_50F970

逐一解读逻辑:

  • .text:000000000050F758 LDR X0, [X29,#0x30+var_18] - ifentry
  • .text:000000000050F75C BL sub_50EAAC - 计算端口类型
  • .text:000000000050F768 STR W0, [X29,#0x30+var_4] - *type
  • .text:000000000050F76C LDR W0, [X29,#0x30+var_8] - ifentry + 0x50
  • .text:000000000050F774 STR W0, [X29,#0x30+var_C] - ability = 能力位图

接着查找能力位图

.text:000000000050E3BC                 AND             W0, W0, #0x1FFF
.text:000000000050E3C0                 CMP             W0, #0x63 ; switch 100 cases
.text:000000000050E3C4                 B.HI            def_50E3DC ; jumptable 000000000050E3DC default case, case 54
.text:000000000050E3C8                 ADRL            X1, jpt_50E3DC
.text:000000000050E3D0                 LDR             W0, [X1,W0,UXTW#2]
.text:000000000050E3D4                 ADR             X1, loc_50E3E0 ; jumptable 000000000050E3DC case 0
.text:000000000050E3D8                 ADD             X0, X1, W0,SXTW#2
.text:000000000050E3DC                 BR              X0      ; switch jump
50e3fc: ... 50e404: bl strncmp        ; 与 0xaa3d48 = 'agg'   (len 3) → 返回 20
50e430: ... bl strncmp                ; 与 0xaa3d50 = 'vlan'  (len 4) → 返回 21
50e45c: ... bl strncmp                ; 与 0xaa3d58 = 'tunnel'(len 6) → 返回 22
50e488: ... bl strncmp                ; 与 0xaa3d60 = 'loopback'(len 8) → 返回 23
50e4b4: ... bl strncmp                ; 与 0xaa3d70 = 'seth'  → 返回 ...

即 type 是按接口名(agg/vlan/tunnel/loopback/seth/eth…)分出来的类别码.
最终解出来的能力位图如下

分支允许的速率码说明
type==1{0,1,2,3}[if+0x10c]!=0 时禁 3(1G),返回 -246
type==3{0,1,2}
type==45{0,2,3,7,8}[if+0x10c]!=0 时禁 3/7/8;再查能力位
type==43 (0x2b){0,2,3,4,7,8}[if+0x10c]!=0 时禁 3/7/8/4;再查能力位
其余5/6/4/3/2/10/9/7/11/12 …按大速率集查能力位

因此速率码对照表即为: 0=auto 1=10M 2=100M 3=1G 4=10G 5=40G 6=100G 7=2.5G 8=5G 9=25G 10=50G 11=200G 12=400G
所以这里我们需要给校验码打补丁:

地址原值新值
nsm VA 0x50f728fd 7b bd a9 (stp x29,x30,[sp,#-48]!)00 00 80 52 c0 03 5f d6 (mov w0,#0; ret)

8. 第二次 Patch

使用 Python 编写补丁

import sys, binascii
pid = int(sys.argv[1]); act = sys.argv[2]
BK = '/tmp/mpatch_bk.txt'
# (label, nsm VA, original bytes hex, new bytes hex)  -- lengths may be 4 or 8 bytes
EDITS = [
    # 0x50f728 = port-speed ability validator: "stp x29,x30,[sp,#-48]!" -> "mov w0,#0; ret"
    ('valid_entry', 0x50f728, 'fd7bbda9', '00008052c0035fd6'),
]

def rd(off, n):
    f = open('/proc/%d/mem' % pid, 'rb'); f.seek(off); return f.read(n)

def wr(off, data):
    f = open('/proc/%d/mem' % pid, 'r+b'); f.seek(off); f.write(data); f.flush()

def hx(b):
    return binascii.hexlify(b)

if act == 'read':
    for (lab, a, o, n) in EDITS:
        print("%-14s @0x%x  now=%s" % (lab, a, hx(rd(a, len(o) // 2))))
    sys.exit(0)

if act == 'patch':
    lines = []
    for (lab, a, o, n) in EDITS:
        cur = hx(rd(a, len(o) // 2))
        lines.append("%s %x %s" % (lab, a, cur))
        if cur == o:
            wr(a, binascii.unhexlify(n))
        else:
            print("WARN %s current=%s expected=%s" % (lab, cur, o))
    open(BK, 'w').write("\n".join(lines) + "\n")
    for (lab, a, o, n) in EDITS:
        print("%-14s @0x%x -> %s" % (lab, a, hx(rd(a, len(o) // 2))))

if act == 'restore':
    for line in open(BK):
            wr(a, binascii.unhexlify(n))
        else:
            print("WARN %s current=%s expected=%s" % (lab, cur, o))
    open(BK, 'w').write("\n".join(lines) + "\n")
    for (lab, a, o, n) in EDITS:
        print("%-14s @0x%x -> %s" % (lab, a, hx(rd(a, len(o) // 2))))

if act == 'restore':
    for line in open(BK):
        lab, a, cur = line.split()
        wr(int(a, 16), binascii.unhexlify(cur))
        print("restored %s @%s -> %s" % (lab, a, hx(rd(int(a, 16), len(cur) // 2))))

在设备上应用补丁. 然后再次 speed 下发速率.

Switch(config-if)# speed 2G5

Switch# show running-config interface eth-0-51
interface eth-0-51
 speed 2G5

Switch# show interface eth-0-51
  Interface current state: UP
  Bandwidth 2500000 kbits
  Speed - 2.5Gb/s , Duplex - Full

可以看到, 此时端口速率已经成功的应用为 2.5Gbe. 且测试后接入设备也没有重置速率. 至此, Patch 完成.

9. 持久化

该交换机的 rootfs 文件系统为 ramfs, 所以没法做持久化. 但是 flash 挂载为 ext4. 因此我们可以写个 patch 脚本在设备上, 如果设备重启就手动应用一遍脚本. 交换机这种设备只要不是设备断电, 几乎不会重启. 所以这种方法暂且也是可行的. 之后或许会研究真正持久化的做法. 这里把完整补丁内容贴出来

#!/usr/bin/python2.7
# -*- coding: utf-8 -*-
import os
import sys
import glob
import time

TARGET = '/usr/sbin/nsm'                     # 目标进程
WAIT_SECS = 30                               # 等待 nsm 上线的秒数

# (标签, 运行时地址 == 文件内 VA(nsm 为非 PIE), 原始字节, 补丁字节)
# 注意:字节串均为小端(ARM64 指令在内存中的实际字节序)
EDITS = [
    ('group_check      ', 0x5345e4, '00030054',         '18000014'),
    ('ability_validator', 0x50f728, 'fd7bbda9fd030091', '00008052c0035fd6'),
]

PATCHDIR = os.path.dirname(os.path.abspath(__file__))
BACKUP = os.path.join(PATCHDIR, 'patch-sfp+.bak')


def find_pid():
    """按 /proc/<pid>/exe 指向找到 nsm 的 pid(不依赖 pidof 是否可用)。"""
    for p in glob.glob('/proc/[0-9]*/exe'):
        try:
            if os.readlink(p) == TARGET:
                return int(p.split('/')[2])
        except OSError:
            pass
    return None


def wait_pid():
    for _ in range(WAIT_SECS):
        pid = find_pid()
        if pid is not None:
            return pid
        time.sleep(1)
    return None


def hx(b):
    return ''.join('%02x' % ord(c) for c in b)


def mem_rd(fd, off, n):
    os.lseek(fd, off, os.SEEK_SET)
    return os.read(fd, n)


def mem_wr(fd, off, data):
    os.lseek(fd, off, os.SEEK_SET)
    return os.write(fd, data)


def do_status(fd):
    rc = 0
    for (lab, addr, orig, patched) in EDITS:
        n = len(orig) // 2
        cur = hx(mem_rd(fd, addr, n))
        if cur == patched:
            state = 'PATCHED'
        elif cur == orig:
            state = 'original'
        else:
            state = 'UNKNOWN'
            rc = 2
        print("  %s @0x%06x = %-20s [%s]" % (lab, addr, cur, state))
    return rc


def do_restore(fd):
    if not os.path.exists(BACKUP):
        print("ERROR: 找不到备份 %s,无法还原" % BACKUP)
        return 1
    for line in open(BACKUP):
        lab, addr, orig = line.split()
        mem_wr(fd, int(addr, 16), orig.decode('hex'))
        print("  restored %s @0x%s -> %s" % (lab, addr, hx(mem_rd(fd, int(addr, 16), len(orig) // 2))))
    return 0


def do_patch(fd):
    rc = 0
    bk = []
    for (lab, addr, orig, patched) in EDITS:
        n = len(orig) // 2
        cur = hx(mem_rd(fd, addr, n))
        bk.append("%s %x %s\n" % (lab.strip(), addr, orig))   # 备份一律记原始字节
        if cur == patched:
            print("  SKIP  %s @0x%06x 已经打过补丁" % (lab, addr))
        elif cur == orig:
            mem_wr(fd, addr, patched.decode('hex'))
            print("  PATCH %s @0x%06x  %s -> %s" % (lab, addr, cur, hx(mem_rd(fd, addr, n))))
        else:
            print("  WARN  %s @0x%06x 未知内容 %s(期望 %s),已跳过" % (lab, addr, cur, orig))
            rc = 2
    try:
        f = open(BACKUP, 'w')
        f.writelines(bk)
        f.close()
        print("  备份已写入 %s" % BACKUP)
    except IOError, e:
        print("  WARN 备份写入失败: %s" % e)
    return rc


def main():
    mode = 'patch'
    if '--restore' in sys.argv:
        mode = 'restore'
    elif '--status' in sys.argv:
        mode = 'status'

    print("=== patch-sfp+.py  mode=%s ===" % mode)
    pid = wait_pid()
    if pid is None:
        print("ERROR: 等待 %ds 仍未找到 nsm 进程 (%s)" % (WAIT_SECS, TARGET))
        return 1
    print("  nsm pid = %d" % pid)

    fd = os.open('/proc/%d/mem' % pid, os.O_RDWR)
    try:
        if mode == 'status':
            rc = do_status(fd)
        elif mode == 'restore':
            rc = do_restore(fd)
        else:
            rc = do_patch(fd)
    finally:
        os.close(fd)

    if rc == 0:
        print("=== DONE ===")
    else:
        print("=== DONE (rc=%d,请检查上面的 WARN) ===" % rc)
    return rc


if __name__ == '__main__':
    sys.exit(main())

10. 关于开头的问题

10.1 为什么厂商要将这四个端口绑定到一个组内?

芯片侧存在 PLL 域 (CMU) 的概念 (sys_tsingma_datapath_12g_is_pll_equal、sys_tsingma_datapath_28g_is_pll_equal、_sys_tsingma_datapath_12g_load_cmu_val), 按速率分 12G 域 (≤12.5G,容纳 1G/2.5G/10G) 与 28G 域 (25G/50G).
但是博主手上的这台设备端口规格为 SFP+, 并非 SFP28. 因此也就不支持 25G/50G 速率, 也就不会出现 28G 时钟速率的情况. 厂家将其绑定为一个组归根结底还是避免速率调整导致的设备故障, 这种做法也可以理解. 但由于固件过于公版, 并没有针对每个设备做十分细致的调整, 也就导致了这台设备非必要的功能缺失.

10.2 为什么 group-speed 没有设计 2.5Gbe 速率的下发实现?

关于这个疑问, 其实分析完之后也没法做出一个很准确的回答. 但分析过程中也确实从侧面获取到了一些厂家可能的意图.

  1. 产品定位: SFP+ 是上联口, 而 2.5G 是接入侧的定位
    整机是 48×2.5G 电口 + 4×10G + 2×40G:
  2. 2.5Gbe 是接入侧的能力, 由 48 个电口的 PHY 提供;
  3. SFP+ 是上联 / 级联用途, 目标速率是 1G/10G.
    在这种分工下, 让上联口跑 2.5Gbe 不是产品设计里的应用场景, 厂商自然不会把它做进上联专用的 group-speed.
  4. 2.5Gbe 在 SFP+ 上不是标准速率
    SFP+ MSA 定义的是 1G / 10G. 2.5G 跑 SFP+ 属于非标准用法, 能用它的模块很少.
    厂商为上联口提供非标准速率意味着: 额外的模块兼容性验证、DDM/CDR 行为的未知风险、以及售后支持成本.
  5. 代码结构原因:通用表 vs 特化逻辑
    这是最直接的工程解释:
  6. 每端口 speed 是全产品线共用的代码路径, 它的 token 表要覆盖所有 SKU 的所有口类型 (10M…400G), 所以 2G5 天然在里面;
  7. group-speed 是针对 上联端口组 特化的逻辑, 厂家只枚举了该场景真正要用的速率 (default|1000|10G|25G).
  8. 厂家其实是有意关掉, 不是漏做
    这是最有力的一条: speed-mode (1-10G | 2G5-10G | 1-25G | 10-25G) 这个命令已经把 2G5-10G 写好了, 包括写 EEPROM/sysenv, 重启生效, show speed-mode 全套. 但本板没有任何端口带上启用所需的标志, 导致该命令直接回 Do not support speed mode config!
    也就是说: 能力是现成的, 开关是故意焊死的. 这不太像是忘了做, 而是更像做了但按板型分产品或是客户配置关闭.很可能是按订单 / SKU 分级的产品策略 (要 2.5G 就得买对应型号).

总结

本分并非专业人士, 如有错误或疏漏请指出! 感谢您的阅读.

盛科 CTC7132 平台逆向: 绕过 group-speed, 解锁 SFP+ 的 2.5G 协商速率
https://blog.nanami.tech/archives/387/
本文作者 Madobi Nanami
发布时间 2026-10-07
许可协议 CC BY-NC-SA 4.0
发表新评论