374 lines
13 KiB
Markdown
374 lines
13 KiB
Markdown
# LayLink
|
||
|
||
LayLink 是一个基于 PHP Workerman 的策略控制型四层反向访问网关。
|
||
|
||
它不是 VPN。客户端连接 Client Agent,请求访问某个 TCP 目标;Client Agent 使用 LayLink Frame 协议连接 POP Server;POP Server 负责认证、策略判断、连接公网目标和审计。
|
||
|
||
## 当前节点类型
|
||
|
||
当前 MVP 分成 2 种核心类型:
|
||
|
||
1. `POP Server`
|
||
2. `Client Agent`
|
||
|
||
## 配置文件关系
|
||
|
||
`.env` 用来配置当前进程自己的运行参数。
|
||
|
||
`config/nodes.php` 用来声明 POP Server 认可哪些 Agent 节点,以及 Agent 的本地 allowlist。
|
||
|
||
`config/policies.php` 用来声明客户端访问策略。POP Server 根据这个文件决定某个用户是否允许访问某个目标,以及是否由 POP Server 直接连接公网目标。
|
||
|
||
`.env.example` 是示例模板。实际部署时建议复制为 `.env`,再按当前进程类型修改:
|
||
|
||
```bash
|
||
cp .env.example .env
|
||
```
|
||
|
||
`.env.example` 中的 `[config]`、`[client-agent]`、`[pop-server]` 是阅读分组标题,当前加载器会忽略这些标题,只读取 `KEY=value` 配置行。
|
||
|
||
## POP Server
|
||
|
||
POP Server 是控制面和转发入口。
|
||
|
||
它负责:
|
||
|
||
* 监听 Agent 长连接。
|
||
* 校验 Agent 的 `NODE_ID` 和 `NODE_TOKEN`。
|
||
* 校验客户端访问请求。
|
||
* 根据 `config/policies.php` 选择路由。
|
||
* 向 Agent 下发 `OPEN` 指令。
|
||
* 记录审计日志。
|
||
|
||
启动入口:
|
||
|
||
```bash
|
||
php bin/pop-server.php start
|
||
```
|
||
|
||
POP Server 需要配置这些 `.env`:
|
||
|
||
```env
|
||
APP_ENV=dev
|
||
POP_AGENT_LISTEN=0.0.0.0:9001
|
||
POP_ALLOWED_AGENT_TRANSPORTS=tcp
|
||
AUDIT_LOG=runtime/audit.log
|
||
LOG_LEVEL=debug
|
||
```
|
||
|
||
配置说明:
|
||
|
||
| 变量 | 作用 | 常见值 |
|
||
| --- | --- | --- |
|
||
| `APP_ENV` | 当前运行环境。开发时使用 `dev`,生产可使用 `prod`。 | `dev`、`test`、`prod` |
|
||
| `POP_AGENT_LISTEN` | POP Server 给 Client Agent 连接的监听地址。Agent 的 `POP_SERVER_ADDRESS` 应指向这里。 | `0.0.0.0:9001`、`127.0.0.1:9001` |
|
||
| `POP_ALLOWED_AGENT_TRANSPORTS` | POP Server 允许 Agent 使用的底层传输协议,逗号分隔。Agent 认证时会上报自己的选择,不在列表内会被拒绝。 | `tcp`、`tcp,kcp`、`tcp,udp,kcp` |
|
||
| `AUDIT_LOG` | 审计日志路径。MVP 使用 JSON Lines 追加写入。 | `runtime/audit.log` |
|
||
| `LOG_LEVEL` | 日志级别预留配置。当前 MVP 主要为后续日志工厂使用。 | `debug`、`info`、`warning`、`error` |
|
||
|
||
POP Server 通常不需要配置 `NODE_ID`、`NODE_TYPE`、`NODE_TOKEN`、`POP_SERVER_ADDRESS`。这些是 Agent 进程使用的。
|
||
|
||
## Client Agent
|
||
|
||
Client Agent 部署在客户端侧,作为本机或局域网入口。
|
||
|
||
它负责:
|
||
|
||
* 主动出站连接 POP Server。
|
||
* 使用 `NODE_ID`、`NODE_TYPE`、`NODE_TOKEN` 向 POP Server 认证。
|
||
* 维持心跳。
|
||
* 接收本地客户端连接。
|
||
* 将客户端请求和数据封装为 LayLink Frame。
|
||
* 通过选定的底层传输协议把 Frame 发送给 POP Server。
|
||
* 接收 POP Server 返回的目标数据并转发回本地客户端。
|
||
|
||
启动入口:
|
||
|
||
```bash
|
||
php bin/client-agent.php start
|
||
```
|
||
|
||
Client Agent 需要配置这些 `.env`:
|
||
|
||
```env
|
||
APP_ENV=dev
|
||
NODE_ID=client-01
|
||
NODE_TYPE=client
|
||
NODE_TOKEN=CHANGE_ME
|
||
AGENT_TRANSPORT_PROTOCOL=tcp
|
||
CLIENT_AGENT_AUTH_TOKEN=dev-token
|
||
CLIENT_AGENT_USER_ID=admin
|
||
CLIENT_AGENT_SOCKS5_ENABLED=true
|
||
CLIENT_AGENT_SOCKS5_LISTEN_IP=127.0.0.1
|
||
CLIENT_AGENT_SOCKS5_LISTEN_PORT=1080
|
||
CLIENT_AGENT_SOCKS5_UDP_LISTEN_IP=127.0.0.1
|
||
CLIENT_AGENT_SOCKS5_UDP_LISTEN_PORT=1081
|
||
CLIENT_AGENT_SOCKS5_UDP_ADVERTISE_IP=127.0.0.1
|
||
CLIENT_AGENT_SOCKS5_AUTH_MODE=no-auth
|
||
CLIENT_AGENT_SOCKS5_USERNAME=
|
||
CLIENT_AGENT_SOCKS5_PASSWORD=
|
||
CLIENT_AGENT_HTTP_PROXY_ENABLED=false
|
||
CLIENT_AGENT_HTTP_PROXY_LISTEN_IP=127.0.0.1
|
||
CLIENT_AGENT_HTTP_PROXY_LISTEN_PORT=8080
|
||
CLIENT_AGENT_RAW_JSON_ENABLED=false
|
||
CLIENT_AGENT_RAW_JSON_LISTEN_IP=127.0.0.1
|
||
CLIENT_AGENT_RAW_JSON_LISTEN_PORT=9000
|
||
POP_SERVER_ADDRESS=tcp://10.1.0.2:9001
|
||
LOG_LEVEL=debug
|
||
```
|
||
|
||
配置说明:
|
||
|
||
| 变量 | 作用 | 常见值 |
|
||
| --- | --- | --- |
|
||
| `APP_ENV` | 当前运行环境。 | `dev`、`test`、`prod` |
|
||
| `NODE_ID` | 当前 Client Agent 的节点 ID。必须存在于 `config/nodes.php`。 | `client-01` |
|
||
| `NODE_TYPE` | 当前节点类型。Client Agent 必须配置为 `client`。 | `client` |
|
||
| `NODE_TOKEN` | 当前节点认证密钥。必须和 `config/nodes.php` 中同一 `NODE_ID` 的 `token` 一致。 | 强随机字符串,开发时可临时用 `CHANGE_ME` |
|
||
| `AGENT_TRANSPORT_PROTOCOL` | 当前 Agent 到 POP Server 使用的底层传输协议。必须被 POP Server 的 `POP_ALLOWED_AGENT_TRANSPORTS` 允许。 | `tcp`、`udp`、`kcp` |
|
||
| `CLIENT_AGENT_AUTH_TOKEN` | SOCKS5/HTTP 代理入口生成 `OPEN` 帧时使用的客户端认证 token。 | `dev-token`,生产应替换 |
|
||
| `CLIENT_AGENT_USER_ID` | SOCKS5/HTTP 代理入口生成 `OPEN` 帧时使用的默认用户 ID。 | `admin`、`normal-user` |
|
||
| `CLIENT_AGENT_SOCKS5_ENABLED` | 是否启用 SOCKS5 本地入口。 | `true`、`false` |
|
||
| `CLIENT_AGENT_SOCKS5_LISTEN_IP` | SOCKS5 本地入口监听 IP,默认只允许本机访问。 | `127.0.0.1`、`0.0.0.0` |
|
||
| `CLIENT_AGENT_SOCKS5_LISTEN_PORT` | SOCKS5 本地入口监听端口。 | `1080` |
|
||
| `CLIENT_AGENT_SOCKS5_UDP_LISTEN_IP` | SOCKS5 UDP ASSOCIATE 本地 UDP relay 监听 IP。 | `127.0.0.1`、`0.0.0.0` |
|
||
| `CLIENT_AGENT_SOCKS5_UDP_LISTEN_PORT` | SOCKS5 UDP ASSOCIATE 本地 UDP relay 监听端口。 | `1081` |
|
||
| `CLIENT_AGENT_SOCKS5_UDP_ADVERTISE_IP` | UDP ASSOCIATE 回复给应用的 UDP relay IP。 | `127.0.0.1`、Client Agent 局域网 IP |
|
||
| `CLIENT_AGENT_SOCKS5_AUTH_MODE` | SOCKS5 认证模式。`no-auth` 使用无认证,`userpass` 使用 RFC1929 用户名/密码认证。 | `no-auth`、`userpass` |
|
||
| `CLIENT_AGENT_SOCKS5_USERNAME` | SOCKS5 用户名,仅 `userpass` 模式使用。 | 自定义用户名 |
|
||
| `CLIENT_AGENT_SOCKS5_PASSWORD` | SOCKS5 密码,仅 `userpass` 模式使用。 | 强随机密码 |
|
||
| `CLIENT_AGENT_HTTP_PROXY_ENABLED` | 是否启用 HTTP 代理本地入口,支持 `CONNECT` 和普通 HTTP 绝对 URL 请求。 | `true`、`false` |
|
||
| `CLIENT_AGENT_HTTP_PROXY_LISTEN_IP` | HTTP 代理本地入口监听 IP,默认只允许本机访问。 | `127.0.0.1`、`0.0.0.0` |
|
||
| `CLIENT_AGENT_HTTP_PROXY_LISTEN_PORT` | HTTP 代理本地入口监听端口。 | `8080`、`7890` |
|
||
| `CLIENT_AGENT_RAW_JSON_ENABLED` | 是否启用 raw-json 调试入口。 | `true`、`false` |
|
||
| `CLIENT_AGENT_RAW_JSON_LISTEN_IP` | raw-json 调试入口监听 IP。 | `127.0.0.1` |
|
||
| `CLIENT_AGENT_RAW_JSON_LISTEN_PORT` | raw-json 调试入口监听端口。 | `9000` |
|
||
| `POP_SERVER_ADDRESS` | POP Server 的 Agent 监听地址。必须带 `tcp://`。 | `tcp://10.1.0.2:9001`、`tcp://127.0.0.1:9001` |
|
||
| `LOG_LEVEL` | 日志级别预留配置。 | `debug`、`info`、`warning`、`error` |
|
||
|
||
Client Agent 的节点身份不是只写在 `.env` 中,POP Server 侧还必须在 `config/nodes.php` 中声明同名节点:
|
||
|
||
```php
|
||
'client-01' => [
|
||
'node_type' => 'client',
|
||
'token' => 'CHANGE_ME',
|
||
'allowed_cidrs' => [
|
||
'192.168.0.0/16',
|
||
'10.10.0.0/16',
|
||
],
|
||
'allowed_ports' => [22, 80, 443, 3306, 5432],
|
||
'enabled' => true,
|
||
],
|
||
```
|
||
|
||
当前 `allowed_cidrs` 和 `allowed_ports` 仍保留给后续 Agent 侧直连目标能力;新的最小路径会优先让 POP Server 直连公网目标。
|
||
|
||
当前 MVP 提供三种本地入口:
|
||
|
||
| 入口 | 默认状态 | 默认监听 | 适用场景 |
|
||
| --- | --- | --- | --- |
|
||
| SOCKS5 | 开启 | `127.0.0.1:1080` | 只能配置 SOCKS5 代理的应用。 |
|
||
| HTTP 代理 | 关闭 | `127.0.0.1:8080` | 支持 HTTP proxy 或 HTTP CONNECT 的应用。 |
|
||
| raw-json | 关闭 | `127.0.0.1:9000` | 开发调试,手工发送一行 JSON。 |
|
||
|
||
只能用 SOCKS5 的应用可直接配置:
|
||
|
||
```text
|
||
SOCKS5 Host: 127.0.0.1
|
||
SOCKS5 Port: 1080
|
||
```
|
||
|
||
SOCKS5 当前支持:
|
||
|
||
| 能力 | 状态 |
|
||
| --- | --- |
|
||
| 方法协商 | 支持 |
|
||
| 无认证 `0x00` | 支持 |
|
||
| 用户名/密码 `0x02`,RFC1929 | 支持 |
|
||
| IPv4 地址 | 支持 |
|
||
| 域名地址 | 支持 |
|
||
| IPv6 地址 | 支持 |
|
||
| `CONNECT` | 支持 |
|
||
| `BIND` | 按协议返回 command not supported |
|
||
| `UDP ASSOCIATE` | 支持,经 LayLink `UDP_DATA` Frame 转发到 POP Server |
|
||
|
||
SOCKS5 UDP 转发路径:
|
||
|
||
```text
|
||
App UDP
|
||
-> Client Agent UDP relay
|
||
-> UDP_DATA Frame over Agent transport
|
||
-> POP Server
|
||
-> Public UDP target
|
||
```
|
||
|
||
UDP 访问仍然由 POP Server 的 `config/policies.php` 控制。默认示例允许 `53`、`123`、`443`:
|
||
|
||
```php
|
||
[
|
||
'policy_id' => 'public-udp-egress',
|
||
'users' => ['normal-user', 'admin', 'devops'],
|
||
'target_hosts' => ['*'],
|
||
'target_ports' => [53, 123, 443],
|
||
'protocol' => 'udp',
|
||
'route_type' => 'direct',
|
||
'enabled' => true,
|
||
],
|
||
```
|
||
|
||
启用 SOCKS5 用户名密码认证:
|
||
|
||
```env
|
||
CLIENT_AGENT_SOCKS5_AUTH_MODE=userpass
|
||
CLIENT_AGENT_SOCKS5_USERNAME=alice
|
||
CLIENT_AGENT_SOCKS5_PASSWORD=change-this-password
|
||
```
|
||
|
||
如果启用 raw-json,客户端连接 raw-json 端口并发送一行 JSON:
|
||
|
||
```json
|
||
{"auth_token":"dev-token","user_id":"admin","target_host":"example.com","target_port":443,"protocol":"tcp"}
|
||
```
|
||
|
||
字段说明:
|
||
|
||
| 字段 | 作用 | 常见值 |
|
||
| --- | --- | --- |
|
||
| `auth_token` | 客户端认证 token。当前 MVP 固定为 `dev-token`。 | `dev-token` |
|
||
| `user_id` | 用户身份。POP Server 会用它匹配 `config/policies.php`。 | `admin`、`devops`、`normal-user` |
|
||
| `target_host` | 目标主机。 | `192.168.10.20`、`example.com` |
|
||
| `target_port` | 目标端口。 | `22`、`80`、`443`、`5432` |
|
||
| `protocol` | 目标协议。当前只支持 TCP。 | `tcp` |
|
||
| `route_hint` | 预留字段。新的最小路径由 POP Server 直连公网目标,通常不需要填写。 | `null` |
|
||
|
||
## 策略如何配置
|
||
|
||
客户端访问是否允许,由 `config/policies.php` 决定。
|
||
|
||
示例:
|
||
|
||
```php
|
||
[
|
||
'policy_id' => 'public-web-egress',
|
||
'users' => ['normal-user', 'admin', 'devops'],
|
||
'target_hosts' => ['*'],
|
||
'target_ports' => [80, 443],
|
||
'protocol' => 'tcp',
|
||
'route_type' => 'direct',
|
||
'enabled' => true,
|
||
],
|
||
```
|
||
|
||
这条策略表示:
|
||
|
||
* `normal-user`、`admin` 和 `devops` 可以访问任意主机的 `80`、`443` 端口。
|
||
* Client Agent 只负责把请求封装成 Frame 发到 POP Server。
|
||
* POP Server 校验策略后直接连接公网目标。
|
||
|
||
路由类型:
|
||
|
||
| `route_type` | 作用 |
|
||
| --- | --- |
|
||
| `direct` | POP Server 直接连接目标,适合公共互联网出口。 |
|
||
| `reject` | 拒绝访问。默认行为就是拒绝。 |
|
||
|
||
## 本地开发示例
|
||
|
||
一个最小本地开发 `.env` 可以这样写:
|
||
|
||
```env
|
||
APP_ENV=dev
|
||
POP_AGENT_LISTEN=127.0.0.1:9001
|
||
POP_ALLOWED_AGENT_TRANSPORTS=tcp
|
||
NODE_ID=client-01
|
||
NODE_TYPE=client
|
||
NODE_TOKEN=CHANGE_ME
|
||
AGENT_TRANSPORT_PROTOCOL=tcp
|
||
CLIENT_AGENT_AUTH_TOKEN=dev-token
|
||
CLIENT_AGENT_USER_ID=admin
|
||
CLIENT_AGENT_SOCKS5_ENABLED=true
|
||
CLIENT_AGENT_SOCKS5_LISTEN_IP=127.0.0.1
|
||
CLIENT_AGENT_SOCKS5_LISTEN_PORT=1080
|
||
CLIENT_AGENT_SOCKS5_UDP_LISTEN_IP=127.0.0.1
|
||
CLIENT_AGENT_SOCKS5_UDP_LISTEN_PORT=1081
|
||
CLIENT_AGENT_SOCKS5_UDP_ADVERTISE_IP=127.0.0.1
|
||
CLIENT_AGENT_SOCKS5_AUTH_MODE=no-auth
|
||
CLIENT_AGENT_SOCKS5_USERNAME=
|
||
CLIENT_AGENT_SOCKS5_PASSWORD=
|
||
CLIENT_AGENT_HTTP_PROXY_ENABLED=false
|
||
CLIENT_AGENT_HTTP_PROXY_LISTEN_IP=127.0.0.1
|
||
CLIENT_AGENT_HTTP_PROXY_LISTEN_PORT=8080
|
||
CLIENT_AGENT_RAW_JSON_ENABLED=false
|
||
CLIENT_AGENT_RAW_JSON_LISTEN_IP=127.0.0.1
|
||
CLIENT_AGENT_RAW_JSON_LISTEN_PORT=9000
|
||
POP_SERVER_ADDRESS=tcp://127.0.0.1:9001
|
||
AUDIT_LOG=runtime/audit.log
|
||
LOG_LEVEL=debug
|
||
```
|
||
|
||
## Agent 到 POP 的传输协议
|
||
|
||
Agent 到 POP Server 的业务数据始终使用 LayLink 自定义 Frame 协议封装。`AGENT_TRANSPORT_PROTOCOL` 只决定这些 Frame 运行在哪种底层传输上。
|
||
|
||
当前规划的传输类型:
|
||
|
||
| 值 | 含义 | 当前状态 |
|
||
| --- | --- | --- |
|
||
| `tcp` | Frame over TCP,最容易部署和调试。 | 已实现 |
|
||
| `udp` | Frame over UDP,需要额外处理可靠性、顺序和丢包。 | 已预留,未实现 |
|
||
| `kcp` | Frame over KCP/UDP,用 KCP 做可靠、低延迟传输。 | 已预留,未实现 |
|
||
|
||
POP Server 用 `POP_ALLOWED_AGENT_TRANSPORTS` 控制允许哪些传输协议。例如:
|
||
|
||
```env
|
||
POP_ALLOWED_AGENT_TRANSPORTS=tcp,kcp
|
||
```
|
||
|
||
Client Agent 用 `AGENT_TRANSPORT_PROTOCOL` 选择自己实际使用哪种协议。例如:
|
||
|
||
```env
|
||
AGENT_TRANSPORT_PROTOCOL=tcp
|
||
```
|
||
|
||
如果 Agent 选择的协议不在 POP 允许列表中,POP 会在认证阶段返回 `AUTH_FAIL`,原因是 `transport_not_allowed`。
|
||
|
||
当前代码只实现了 `tcp`。如果 Agent 配置为 `udp` 或 `kcp`,进程会启动失败并明确提示该传输尚未实现。
|
||
|
||
启动 POP Server:
|
||
|
||
```bash
|
||
php bin/pop-server.php start
|
||
```
|
||
|
||
另一个终端启动 Client Agent:
|
||
|
||
```bash
|
||
php bin/client-agent.php start
|
||
```
|
||
|
||
然后把应用的代理设置为 SOCKS5 `127.0.0.1:1080`。Client Agent 会解析 SOCKS5 `CONNECT`,封装成 `OPEN` 帧发给 POP Server;POP Server 校验通过后直连公网目标,随后通过 `DATA` 帧转发原始 TCP 数据。
|
||
|
||
验证 SOCKS5 HTTPS 联通性和出口 IP:
|
||
|
||
```bash
|
||
scripts/verify-socks5.sh
|
||
```
|
||
|
||
默认使用 `127.0.0.1:1080`。如果启用了 SOCKS5 用户名密码:
|
||
|
||
```bash
|
||
SOCKS5_USER=alice SOCKS5_PASSWORD=change-this-password scripts/verify-socks5.sh
|
||
```
|
||
|
||
## 部署检查清单
|
||
|
||
部署前至少确认:
|
||
|
||
* `NODE_TOKEN` 已替换为强随机密钥。
|
||
* `config/nodes.php` 中的 `token` 和 Agent `.env` 中的 `NODE_TOKEN` 一致。
|
||
* `NODE_TYPE` 和 `config/nodes.php` 中的 `node_type` 一致。
|
||
* Agent 的 `allowed_cidrs` 和 `allowed_ports` 足够窄。
|
||
* `config/policies.php` 不存在过宽的 `target_hosts` 和 `target_ports`。
|
||
* 生产环境不要继续使用固定的 `dev-token` 客户端认证。
|
||
* 生产环境应补充 TLS、JWT 或 mTLS、限流和更完整的审计存储。
|