15 KiB
LayLink
LayLink 是一个基于 PHP Workerman 的策略控制型四层反向访问网关。
它不是 VPN。客户端连接 Client Agent,请求访问某个 TCP 目标;Client Agent 使用 LayLink Frame 协议连接 POP Server;POP Server 负责认证、策略判断、连接公网目标和审计。
当前节点类型
当前 MVP 分成 2 种核心类型:
POP ServerClient Agent
配置文件关系
.env 用来配置当前进程自己的运行参数。
config/nodes.php 用来声明 POP Server 认可哪些 Agent 节点,以及 Agent 的本地 allowlist。
config/policies.php 用来声明客户端访问策略。POP Server 根据这个文件决定某个用户是否允许访问某个目标,以及是否由 POP Server 直接连接公网目标。
.env.example 是示例模板。实际部署时建议复制为 .env,再按当前进程类型修改:
cp .env.example .env
.env.example 中的 [config]、[client-agent]、[pop-server] 是阅读分组标题,当前加载器会忽略这些标题,只读取 KEY=value 配置行。
Agent 与 POP Server 之间的 LayLink Frame 支持加密:
LAYLINK_FRAME_ENCRYPTION=none
LAYLINK_FRAME_ENCRYPTION_KEY=
可选值:
| 值 | 作用 |
|---|---|
none |
不加密,开发调试默认值。 |
chacha20 |
使用 libsodium XChaCha20 stream 对 Frame body 加密。 |
启用 chacha20 时,POP Server 和 Client Agent 必须配置完全相同的加密方式和密钥:
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指令。 - 记录审计日志。
启动入口:
php bin/pop-server.php start
POP Server 需要配置这些 .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 返回的目标数据并转发回本地客户端。
启动入口:
php bin/client-agent.php start
Client Agent 需要配置这些 .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 中声明同名节点:
'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, '8080-10080', 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 的应用可直接配置:
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 转发路径:
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:
[
'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 用户名密码认证:
CLIENT_AGENT_SOCKS5_AUTH_MODE=userpass
CLIENT_AGENT_SOCKS5_USERNAME=alice
CLIENT_AGENT_SOCKS5_PASSWORD=change-this-password
如果启用 raw-json,客户端连接 raw-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、8080、5432 |
protocol |
目标协议。当前只支持 TCP。 | tcp |
route_hint |
预留字段。新的最小路径由 POP Server 直连公网目标,通常不需要填写。 | null |
策略如何配置
客户端访问是否允许,由 config/policies.php 决定。
示例:
[
'policy_id' => 'public-web-egress',
'users' => ['normal-user', 'admin', 'devops'],
'target_hosts' => ['*'],
'target_ports' => [80, 443, '8080-10080'],
'protocol' => 'tcp',
'route_type' => 'direct',
'enabled' => true,
],
这条策略表示:
normal-user、admin和devops可以访问任意主机的80、443,以及8080到10080端口。- Client Agent 只负责把请求封装成 Frame 发到 POP Server。
- POP Server 校验策略后直接连接公网目标。
target_ports 和 allowed_ports 都支持两种写法:
- 单端口:
80 - 端口范围:
'8080-10080'
路由类型:
route_type |
作用 |
|---|---|
direct |
POP Server 直接连接目标,适合公共互联网出口。 |
reject |
拒绝访问。默认行为就是拒绝。 |
本地开发示例
一个最小本地开发 .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 控制允许哪些传输协议。例如:
POP_ALLOWED_AGENT_TRANSPORTS=tcp,kcp
Client Agent 用 AGENT_TRANSPORT_PROTOCOL 选择自己实际使用哪种协议。例如:
AGENT_TRANSPORT_PROTOCOL=tcp
如果 Agent 选择的协议不在 POP 允许列表中,POP 会在认证阶段返回 AUTH_FAIL,原因是 transport_not_allowed。
当前代码只实现了 tcp。如果 Agent 配置为 udp 或 kcp,进程会启动失败并明确提示该传输尚未实现。
启动 POP Server:
php bin/pop-server.php start
另一个终端启动 Client Agent:
php bin/client-agent.php start
然后把应用的代理设置为 SOCKS5 127.0.0.1:1080。Client Agent 会解析 SOCKS5 CONNECT,封装成 OPEN 帧发给 POP Server;POP Server 校验通过后直连公网目标,随后通过 DATA 帧转发原始 TCP 数据。
TCP 大流量 DATA 帧使用二进制帧编码;AUTH、OPEN、CLOSE、ERROR 等控制帧仍使用 JSON 编码。启用 chacha20 时,二进制和 JSON Frame body 都会被加密。
大文件下载时,LayLink 会使用 Workerman 的 pauseRecv() / resumeRecv() 做背压:当下游发送缓冲区过高时暂停上游读取,缓冲排空后继续读取。这可以避免单个慢连接无限堆积内存或因为发送缓冲区满而断联。
当 POP 收到目标站关闭连接时,Client Agent 会先等待本地客户端发送缓冲区排空,再关闭本地 socket,避免大文件尾部数据还在缓冲区里时被提前截断。TCP DATA 会按 256 KiB 分片发送,以避免巨帧并改善多会话公平性。
当前每个 Client Agent worker 仍然通过单条 Agent-to-POP TCP 长连接承载多个会话。背压可以保护进程不堵死,但单条 TCP 长连接仍可能产生队头阻塞;多 worker、多 POP 长连接、KCP 或 per-session window 是后续性能优化方向。
验证 SOCKS5 HTTPS 联通性和出口 IP:
scripts/verify-socks5.sh
默认使用 127.0.0.1:1080。如果启用了 SOCKS5 用户名密码:
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、限流和更完整的审计存储。