59 lines
6.2 KiB
Markdown
59 lines
6.2 KiB
Markdown
# S29 性能、故障与安全测试报告
|
||
|
||
## 测试范围
|
||
|
||
- 环境:本地 KVM 开发环境,A/B/T 通过 Tailscale 联调。
|
||
- 范围:小规模 CPS、并发、录音搬运、CDR/Recording Worker 恢复、Redis/MySQL 短故障、API 越权、重放与 SIP 非法来源/超 CPS 探针。
|
||
- 约束:不做长时间满载压测,不删除 S28 验收数据,不禁用 SSH 密钥登录,不提前执行 S30。
|
||
|
||
## 执行记录
|
||
|
||
### 1. 基线健康检查
|
||
|
||
- B:`lisglosips@api`、`lisglosips@cdr-worker`、`lisglosips@recording-worker`、`mysql`、`redis-server`、`heplify-server`、`grafana-server`、`lisglosips-prometheus.service` 均 active;API `/api/v2/health/ready` 返回 `ok`;Prometheus 实际项目服务 `lisglosips-prometheus.service` 返回 ready。
|
||
- A:`opensips`、`rtpengine-daemon`、`rtpengine-recording-daemon`、`lisglosips-redis-auth-proxy`、`lisglosips-recording-finalize.timer` 均 active。
|
||
- T:`lisglosips-s28-uas`、`opensips` 均 active。
|
||
|
||
### 2. CPS/并发/录音
|
||
|
||
- 5 路并发呼叫:T 同时发起 5 路 S28 呼叫,均收到 `100 Giving it a try`、`200 OK`、BYE `200 OK`,用时约 11.92 秒;B 端生成 5 条 raw CDR、5 条 rated CDR、5 条 READY 录音,录音大小均为 55758 bytes。
|
||
- 12 路短突发:T 同时发起 12 路短持话呼叫,均收到 `100 Giving it a try`、`200 OK`、BYE `200 OK`,用时约 2.13 秒;B 端核对 `raws=12`、`rated=12`、`recordingsReady=12`、`totalDuration=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=1`,MySQL 无 raw CDR;恢复 worker 后 `pending=0`、`lag=0`,raw/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 ID,raw/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=1`、`idempotency_keys.rows=1`、余额只从 `99.748000` 增加到 `99.748001`。
|
||
|
||
### 5. SIP 安全探针
|
||
|
||
- 从 B `100.90.90.91` 向 A `100.90.90.90:15060/udp` 发送非法来源 INVITE,Call-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` 增加 `CdrRetryableError`,retryable 错误不 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 本次范围。
|