Files
lisglosips/docs/S29_PERFORMANCE_FAULT_SECURITY_REPORT.md

6.2 KiB
Raw Permalink Blame History

S29 性能、故障与安全测试报告

测试范围

  • 环境:本地 KVM 开发环境,A/B/T 通过 Tailscale 联调。
  • 范围:小规模 CPS、并发、录音搬运、CDR/Recording Worker 恢复、Redis/MySQL 短故障、API 越权、重放与 SIP 非法来源/超 CPS 探针。
  • 约束:不做长时间满载压测,不删除 S28 验收数据,不禁用 SSH 密钥登录,不提前执行 S30。

执行记录

1. 基线健康检查

  • Blisglosips@apilisglosips@cdr-workerlisglosips@recording-workermysqlredis-serverheplify-servergrafana-serverlisglosips-prometheus.service 均 activeAPI /api/v2/health/ready 返回 okPrometheus 实际项目服务 lisglosips-prometheus.service 返回 ready。
  • Aopensipsrtpengine-daemonrtpengine-recording-daemonlisglosips-redis-auth-proxylisglosips-recording-finalize.timer 均 active。
  • Tlisglosips-s28-uasopensips 均 active。

2. CPS/并发/录音

  • 5 路并发呼叫:T 同时发起 5 路 S28 呼叫,均收到 100 Giving it a try200 OK、BYE 200 OK,用时约 11.92 秒;B 端生成 5 条 raw CDR、5 条 rated CDR、5 条 READY 录音,录音大小均为 55758 bytes。
  • 12 路短突发:T 同时发起 12 路短持话呼叫,均收到 100 Giving it a try200 OK、BYE 200 OK,用时约 2.13 秒;B 端核对 raws=12rated=12recordingsReady=12totalDuration=72、录音大小 55758-56078 bytes。
  • 修复项:T 呼叫脚本原默认固定 --local-port 50621,并发时出现 OSError: [Errno 98] Address already in use;已改为默认 --local-port 0 并在 socket bind 后用实际端口生成 Via/Contact。复测 5 路和 12 路并发均通过。

3. Worker 与数据服务故障

  • Recording Worker 故障:停止 B lisglosips@recording-worker 后发起呼叫,通话成功;恢复 worker 后自动搬运并入库录音,recordings.status=READY,大小 55758 bytes。
  • CDR Worker 故障:停止 B lisglosips@cdr-worker 后发起呼叫,Redis consumer group 出现 lag=1MySQL 无 raw CDR;恢复 worker 后 pending=0lag=0raw/rated CDR 正常落库。
  • Redis 故障:停止 B redis-server 后,A 返回 503 Redis Unavailable,符合保护预期;恢复 Redis 后 A 热路径仍持续返回 503,重启 A lisglosips-redis-auth-proxy 无效,重启 A opensips 后恢复,复测呼叫成功。
  • MySQL 故障:停止 B mysql 后发起呼叫,SIP 通话成功但 CDR 入库失败。修复后复测:MySQL 恢复并等待 pending idle 窗口后,CDR Worker 自动处理 pending Redis IDraw/rated CDR 正常入库,无新增 deadletter。

4. Web/API 安全

  • 未认证访问 /api/v2/users 返回 401 AUTH_REQUIRED
  • 质检角色测试用户 usr_s29_quality_only 访问 /api/v2/users 返回 403 RBAC_FORBIDDEN
  • 未认证访问录音播放 /api/v2/recordings/:id/play 返回 401 AUTH_REQUIRED
  • 伪造录音 ID ..etcpasswd 通过认证访问播放接口返回 404 RECORDING_NOT_READY,未泄露文件内容。
  • 充值重放:对 cus_s28_t 使用同一 idempotencyKey=s29-replay-cus-s28-000001 连续 POST 两次 0.000001 充值,两次返回同一充值 ID rch_da0bba1c7b1d44428ab92d77a55e503d;数据库核对 customer_recharges.rows=1idempotency_keys.rows=1、余额只从 99.748000 增加到 99.748001

5. SIP 安全探针

  • 从 B 100.90.90.91 向 A 100.90.90.90:15060/udp 发送非法来源 INVITECall-ID s29-illegal-1782094573-3991@lisglosips-b,客户端等待 3 秒无 SIP 响应;A OpenSIPS 最近日志未出现该 Call-ID。该结果说明非允许来源未进入当前业务路由;因无 OpenSIPS 层日志,保守记录为网络/主机边界静默丢弃证据。

缺陷与处理

编号 现象 处理 复测
S29-D1 T 呼叫脚本固定本地 UDP 端口,导致并发发起时端口冲突。 修改 infra/server-t/s28/lisglosips-s28-sip.py,默认 --local-port 0,bind 后写入实际端口到 Via/Contact,并部署到 T。 5 路并发和 12 路短突发均成功。
S29-D2 MySQL 短故障时 CDR Worker 将可恢复数据库连接错误 deadletter 并 ACK 原 Redis 消息,导致 CDR 不自动重试。 packages/redis 增加 CdrRetryableErrorretryable 错误不 ACK、不 deadletter,并释放幂等锁;worker-cdr 将数据库连接类错误标记为 retryable。 停 MySQL 后发起呼叫,恢复 MySQL 并等待 pending idle 窗口后 raw/rated CDR 自动入库,无新增 deadletter。
S29-D3 失败 CDR 的长 event_id 超过 raw_cdrs.event_id varchar(64),触发 Prisma 长度错误并 deadletter。 worker-cdr 入库前对超过 64 字符的 event_id 做稳定 SHA-256 后缀压缩,原始事件仍保留在 payload 中。 本地 Vitest 与 build 通过;B 部署后 MySQL 故障复测无新增长度类 deadletter。
S29-D4 Redis 恢复后 A 热路径仍返回 503 Redis Unavailable,仅重启 Redis proxy 无法恢复。 本次执行运行时恢复:重启 A opensips,恢复呼叫。根因需在后续 OpenSIPS Redis 连接池/故障重连策略中继续排查。 重启 A opensips 后 T->A 呼叫恢复成功。
S29-D5 S29 期间本地/T 到 B 的 Tailscale 管理链路出现短时高延迟/丢包,SSH 偶发超时。 未改网络配置;采用短命令、分步执行、服务健康复核。 后续 SSH 与 API 健康检查恢复;记录为开发环境网络风险。

结论

S29 在本地 KVM 开发环境完成。单 A/S28 最小闭环在 5 路并发和 12 路短突发下能完成 SIP、CDR、计费、录音入库;Recording Worker、CDR Worker、MySQL 短故障的可恢复路径已验证,其中 MySQL 可恢复缺陷已完成代码修复并部署到 B 当前 release。

API 鉴权、RBAC、录音播放未认证保护和充值幂等重放均通过。SIP 非法来源探针未进入业务路由,但缺少 OpenSIPS 层拒绝日志,建议后续补充明确的非法来源日志/指标。

遗留风险:Redis 恢复后 OpenSIPS 热路径仍需重启 opensips 才恢复,当前只有运行时恢复与复测记录,尚未完成根因级修复;多 A、完整生产路由、多 B 高可用不属于 S29 本次范围。