laylink/readme.md
2026-05-28 21:07:28 +08:00

399 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# LayLink
LayLink 是一个基于 PHP Workerman 的策略控制型四层反向访问网关。
它不是 VPN。客户端连接 Client Agent请求访问某个 TCP 目标Client Agent 使用 LayLink Frame 协议连接 POP ServerPOP 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` 配置行。
Agent 与 POP Server 之间的 LayLink Frame 支持加密:
```env
LAYLINK_FRAME_ENCRYPTION=none
LAYLINK_FRAME_ENCRYPTION_KEY=
```
可选值:
| 值 | 作用 |
| --- | --- |
| `none` | 不加密,开发调试默认值。 |
| `chacha20` | 使用 libsodium XChaCha20 stream 对 Frame body 加密。 |
启用 `chacha20`POP Server 和 Client Agent 必须配置完全相同的加密方式和密钥:
```env
LAYLINK_FRAME_ENCRYPTION=chacha20
LAYLINK_FRAME_ENCRYPTION_KEY=change-this-long-random-secret
```
密钥支持普通口令,也支持 `hex:``base64:` 前缀的 32 字节原始密钥。
## 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` |
| `LAYLINK_FRAME_ENCRYPTION` | Agent 与 POP Server 之间 Frame 加密方式,两端必须一致。 | `none`、`chacha20` |
| `LAYLINK_FRAME_ENCRYPTION_KEY` | Frame 加密密钥,启用 `chacha20` 时必填。 | 普通口令、`hex:...`、`base64:...` |
| `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 ServerPOP 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、限流和更完整的审计存储。