Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions .github/scripts/get-CI-result.sh
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
#!/bin/bash

# 检查参数
if [ $# -ne 2 ]; then
echo "Usage: $0 <COMMIT_ID> <SECURITY>"
if [ $# -ne 3 ]; then
echo "Usage: $0 <COMMIT_ID> <SECURITY> <REPOSITORY>"
exit 1
fi

COMMIT_ID=$1
SECURITY=$2
REPOSITORY=$3

# 设置最大等待时间
MAX_WAIT_TIME=7200
Expand All @@ -18,7 +19,7 @@ while true; do

response=$(curl -s -H "Content-Type: application/json" \
-H "Authorization: Basic ${SECURITY}" \
-d "{\"type\": \"RETRIEVE-TASK-STATUS\", \"commitId\": \"${COMMIT_ID}\"}" "http://get-tasend-back-twkvcdsbpj.cn-hangzhou.fcapp.run")
-d "{\"type\": \"RETRIEVE-TASK-STATUS\", \"repositoryUrl\": \"${REPOSITORY}\", \"commitId\": \"${COMMIT_ID}\"}" "http://get-tasend-back-twkvcdsbpj.cn-hangzhou.fcapp.run")
echo "Response: $response"

# 检查curl是否成功
Expand Down
1 change: 1 addition & 0 deletions .github/scripts/trigger-CI.sh
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ curl -v -H "Content-Type: application/json" \
\"type\": \"CREATE-TASK\",
\"commitId\": \"${COMMIT_ID}\",
\"repositoryUrl\": \"${REPO_URL}\",
\"prId\": \"${GITHUB_PR_ID}\",
\"aone\": { \"projectId\": \"${PROJECT_ID}\", \"pipelineId\": \"${PIPELINE_ID}\"},
\"newBranch\": { \"name\": \"${BRANCH_NAME}\", \"ref\": \"${BRANCH_REF}\" },
\"params\": {\"cancel-in-progress\": \"${CANCEL_IN_PROGRESS}\", \"github_commit\":\"${GITHUB_COMMIT_ID}\", \"github_source_repo\": \"${GITHUB_SOURCE_REPO}\"}
Expand Down
1 change: 1 addition & 0 deletions .github/scripts/trigger-docs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ curl -v -H "Content-Type: application/json" \
\"type\": \"CREATE-TASK\",
\"commitId\": \"${COMMIT_ID}\",
\"repositoryUrl\": \"${REPO_URL}\",
\"prId\": \"${GITHUB_PR_ID}\",
\"aone\": { \"projectId\": \"${PROJECT_ID}\", \"pipelineId\": \"${PIPELINE_ID}\"},
\"newBranch\": { \"name\": \"${BRANCH_NAME}\", \"ref\": \"${BRANCH_REF}\" },
\"params\": {\"cancel-in-progress\": \"${CANCEL_IN_PROGRESS}\", \"github_commit\":\"${GITHUB_COMMIT_ID}\", \"github_source_repo\": \"${GITHUB_SOURCE_REPO}\", \"checkout_submodules\": \"${CHECKOUT_SUBMODULES}\", \"checkout_username\": \"${CHECK_USER_NAME}\", \"checkout_token\": \"${CHECK_TOKEN}\"}
Expand Down
49 changes: 0 additions & 49 deletions .github/workflows/CI-request-trigger.yml

This file was deleted.

118 changes: 118 additions & 0 deletions docs/_specs/websocket-tcp-proxy/plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# WebSocket TCP 端口代理服务 - 实现计划

## 实现步骤

### 步骤 1: 在 rocklet 中添加端口转发端点

**文件**: `rock/rocklet/local_api.py`

添加 WebSocket 端点:
```python
@sandbox_proxy_router.websocket("/portforward")
async def portforward(websocket: WebSocket, port: int):
"""
容器内 TCP 端口代理端点。

流程:
1. 接受 WebSocket 连接
2. 验证端口 (1024-65535, 排除 22)
3. 连接到 127.0.0.1:{port}
4. 双向转发二进制数据
"""
```

核心实现:
- 使用 `asyncio.open_connection("127.0.0.1", port)` 连接本地 TCP 端口
- 创建两个异步任务进行双向数据转发
- WebSocket 使用 `receive_bytes()` 和 `send_bytes()` 处理二进制数据

### 步骤 2: 在 SandboxProxyService 中添加辅助方法

**文件**: `rock/sandbox/service/sandbox_proxy_service.py`

添加方法:
```python
def _validate_port(self, port: int) -> tuple[bool, str | None]:
"""验证端口是否在允许范围内。"""

def _get_rocklet_portforward_url(self, sandbox_status_dict: dict, port: int) -> str:
"""获取 rocklet portforward 端点的 WebSocket URL。"""
```

URL 格式:`ws://{host_ip}:{mapped_port}/portforward?port={target_port}`

### 步骤 3: 在 SandboxProxyService 中添加代理方法

**文件**: `rock/sandbox/service/sandbox_proxy_service.py`

添加方法:
```python
async def websocket_to_tcp_proxy(
self,
client_websocket: WebSocket,
sandbox_id: str,
port: int,
tcp_connect_timeout: float = 10.0,
idle_timeout: float = 300.0,
) -> None:
"""
将客户端 WebSocket 连接代理到 rocklet 的 portforward 端点。

流程:
1. 验证端口
2. 获取沙箱状态
3. 构建 rocklet portforward URL
4. 连接到 rocklet WebSocket
5. 双向转发二进制数据
"""
```

核心实现:
- 使用 `websockets.connect()` 连接 rocklet
- 复用现有的 WebSocket 双向转发模式

### 步骤 4: 添加外部 WebSocket 路由端点

**文件**: `rock/admin/entrypoints/sandbox_proxy_api.py`

添加路由:
```python
@sandbox_proxy_router.websocket("/sandboxes/{id}/portforward")
async def portforward(websocket: WebSocket, id: str, port: int):
"""
外部 WebSocket TCP 端口代理端点。
"""
```

## 架构说明

```
客户端 ──WebSocket──▶ Proxy服务 ──WebSocket──▶ rocklet ──TCP──▶ 目标端口
│ │
外部代理层 内部代理层
(sandbox_proxy_api.py) (local_api.py)
```

**为什么需要两层?**

Docker 容器只暴露预定义端口(如 8080),无法直接访问容器内动态启动的服务端口。rocklet 运行在容器内,可以直接访问 `127.0.0.1:{任意端口}`。

## 依赖

无需新增外部依赖:
- `asyncio` - TCP 连接和异步任务
- `websockets` - WebSocket 客户端(已依赖)

## 测试计划

### 单元测试
1. 端口验证逻辑 (`_validate_port`)
2. rocklet portforward URL 构建 (`_get_rocklet_portforward_url`)
3. rocklet 端点存在性验证

### 集成测试
1. 正常连接和断开
2. 双向二进制数据传输
3. 端口限制验证
4. 超时处理
5. 多客户端并发连接
118 changes: 118 additions & 0 deletions docs/_specs/websocket-tcp-proxy/spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# WebSocket TCP 端口代理服务

## 1. 概述

在 ROCK 沙箱代理服务层实现 **WebSocket → TCP** 端口代理,允许客户端通过 WebSocket 连接访问沙箱容器内监听的 TCP 端口。

## 2. 架构设计

### 2.1 架构图

```
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────────────────┐
│ WebSocket 客户端 │────▶│ Proxy 服务 │────▶│ rocklet (容器内 8080 端口) │
└─────────────────┘ └─────────────────┘ └─────────────────────────────┘
/portforward?port=X │
│ TCP 连接
┌─────────────────┐
│ 沙箱内 TCP 端口 │
│ (如 3000, 8080) │
└─────────────────┘
```

### 2.2 两层代理架构

| 层级 | 组件 | 职责 |
|-----|------|------|
| **外部代理层** | `sandbox_proxy_service.py` | 接收客户端 WebSocket 连接,转发到 rocklet |
| **内部代理层** | `rocklet/local_api.py` | 在容器内连接目标 TCP 端口,实现实际代理 |

### 2.3 为什么需要两层?

Docker 容器默认只暴露预定义端口(如 8080),无法直接从外部访问容器内动态启动的服务端口。通过 rocklet 层代理,可以访问容器内任意 TCP 端口。

## 3. 功能规格

### 3.1 外部 API(客户端调用)

| 项目 | 规格 |
|-----|------|
| **端点路径** | `WS /sandboxes/{sandbox_id}/portforward?port={port}` |
| **sandbox_id** | 沙箱实例的唯一标识符 |
| **port** | Query 参数,指定要代理的目标 TCP 端口号 |

**示例:**
```
ws://localhost:8080/sandboxes/abc123/portforward?port=3000
```

### 3.2 内部 API(rocklet 端点)

| 项目 | 规格 |
|-----|------|
| **端点路径** | `WS /portforward?port={port}` |
| **port** | Query 参数,指定容器内的目标 TCP 端口号 |
| **监听端口** | rocklet 监听 `Port.PROXY` (22555),通过 Docker 映射到宿主机随机端口 |

### 3.3 数据传输

| 项目 | 规格 |
|-----|------|
| **协议** | WebSocket |
| **数据格式** | 二进制消息(Binary Frame) |
| **转发方式** | 原样透传,不修改数据内容 |

### 3.4 端口安全限制

| 项目 | 规格 |
|-----|------|
| **允许范围** | 1024-65535 |
| **排除端口** | 22 (SSH) |
| **拒绝行为** | WebSocket 关闭码 1008 (Policy Violation) |

### 3.5 连接生命周期

| 场景 | 行为 |
|-----|------|
| **连接建立** | 外部代理 → rocklet → TCP 端口,逐层建立连接 |
| **一对一映射** | 每个 WebSocket 连接对应一个独立的 TCP 连接 |
| **多客户端** | 多个 WebSocket 客户端连接同一端口,各自创建独立的 TCP 连接 |
| **TCP 断开** | 关闭 rocklet WebSocket → 关闭客户端 WebSocket |
| **客户端断开** | 关闭 rocklet WebSocket → 关闭 TCP 连接 |

### 3.6 超时配置

| 超时类型 | 时长 | 说明 |
|---------|------|------|
| **连接超时** | 10 秒 | 建立到 rocklet 或 TCP 端口的连接最大等待时间 |
| **空闲超时** | 300 秒 | 连接无数据传输后自动关闭 |

### 3.7 错误处理

| 错误场景 | 处理方式 |
|---------|---------|
| **端口不在允许范围** | WebSocket 关闭码 1008,提示端口不允许 |
| **端口被排除(如 22)** | WebSocket 关闭码 1008,提示端口不允许 |
| **沙箱不存在** | WebSocket 关闭码 1011,提示沙箱未启动 |
| **TCP 连接超时** | WebSocket 关闭码 1011,提示连接超时 |
| **TCP 连接被拒绝** | WebSocket 关闭码 1011,提示连接失败 |
| **传输中断** | WebSocket 关闭码 1011 |

### 3.8 认证授权

不需要额外的认证/授权机制。

## 4. 实现位置

| 文件 | 修改内容 |
|-----|---------|
| `rock/rocklet/local_api.py` | 添加 `/portforward` WebSocket 端点,实现容器内 TCP 代理 |
| `rock/sandbox/service/sandbox_proxy_service.py` | 添加 `websocket_to_tcp_proxy()` 方法和 `_get_rocklet_portforward_url()` 方法 |
| `rock/admin/entrypoints/sandbox_proxy_api.py` | 添加外部 WebSocket 路由端点 |

## 5. 参考设计

参考 Kubernetes port-forward 的 WebSocket 实现:
- URL: `/api/v1/namespaces/{namespace}/pods/{name}/portforward?ports={port}`
- 数据流:WebSocket 双向透传 TCP 数据
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
sidebar_position: 1
---
# 版本说明
* [release v1.2.0](v1.2.0.md)
* [release v1.3.0](v1.3.0.md)
Loading
Loading