Go to file
2026-05-29 00:08:15 +08:00
bin 优化3 2026-05-29 00:08:15 +08:00
config add more 2026-05-28 23:41:40 +08:00
scripts add more 2026-05-28 23:41:40 +08:00
src 优化3 2026-05-29 00:08:15 +08:00
vendor init 2026-05-28 20:19:28 +08:00
.env.example 优化3 2026-05-29 00:08:15 +08:00
.gitignore chacha20 added 2026-05-28 21:07:28 +08:00
composer.json init 2026-05-28 20:19:28 +08:00
composer.lock init 2026-05-28 20:19:28 +08:00
contract.md 优化3 2026-05-29 00:08:15 +08:00
problems.md add more 2026-05-28 23:41:40 +08:00
readme.md 优化3 2026-05-29 00:08:15 +08:00

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,再按当前进程类型修改:

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 加密。

启用 chacha20POP 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_IDNODE_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 devtestprod
LAYLINK_FRAME_ENCRYPTION Agent 与 POP Server 之间 Frame 加密方式,两端必须一致。 nonechacha20
LAYLINK_FRAME_ENCRYPTION_KEY Frame 加密密钥,启用 chacha20 时必填。 普通口令、hex:...base64:...
POP_AGENT_LISTEN POP Server 给 Client Agent 连接的监听地址。Agent 的 POP_SERVER_ADDRESS 应指向这里。 0.0.0.0:9001127.0.0.1:9001
POP_ALLOWED_AGENT_TRANSPORTS POP Server 允许 Agent 使用的底层传输协议逗号分隔。Agent 认证时会上报自己的选择,不在列表内会被拒绝。 tcptcp,kcptcp,udp,kcp
AUDIT_LOG 审计日志路径。MVP 使用 JSON Lines 追加写入。 runtime/audit.log
LOG_LEVEL 日志级别预留配置。当前 MVP 主要为后续日志工厂使用。 debuginfowarningerror

POP Server 通常不需要配置 NODE_IDNODE_TYPENODE_TOKENPOP_SERVER_ADDRESS。这些是 Agent 进程使用的。

Client Agent

Client Agent 部署在客户端侧,作为本机或局域网入口。

它负责:

  • 主动出站连接 POP Server。
  • 使用 NODE_IDNODE_TYPENODE_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 当前运行环境。 devtestprod
NODE_ID 当前 Client Agent 的节点 ID。必须存在于 config/nodes.php client-01
NODE_TYPE 当前节点类型。Client Agent 必须配置为 client client
NODE_TOKEN 当前节点认证密钥。必须和 config/nodes.php 中同一 NODE_IDtoken 一致。 强随机字符串,开发时可临时用 CHANGE_ME
AGENT_TRANSPORT_PROTOCOL 当前 Agent 到 POP Server 使用的底层传输协议。必须被 POP Server 的 POP_ALLOWED_AGENT_TRANSPORTS 允许。 tcpudpkcp
CLIENT_AGENT_AUTH_TOKEN SOCKS5/HTTP 代理入口生成 OPEN 帧时使用的客户端认证 token。 dev-token,生产应替换
CLIENT_AGENT_USER_ID SOCKS5/HTTP 代理入口生成 OPEN 帧时使用的默认用户 ID。 adminnormal-user
CLIENT_AGENT_SOCKS5_ENABLED 是否启用 SOCKS5 本地入口。 truefalse
CLIENT_AGENT_SOCKS5_LISTEN_IP SOCKS5 本地入口监听 IP默认只允许本机访问。 127.0.0.10.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.10.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-authuserpass
CLIENT_AGENT_SOCKS5_USERNAME SOCKS5 用户名,仅 userpass 模式使用。 自定义用户名
CLIENT_AGENT_SOCKS5_PASSWORD SOCKS5 密码,仅 userpass 模式使用。 强随机密码
CLIENT_AGENT_HTTP_PROXY_ENABLED 是否启用 HTTP 代理本地入口,支持 CONNECT 和普通 HTTP 绝对 URL 请求。 truefalse
CLIENT_AGENT_HTTP_PROXY_LISTEN_IP HTTP 代理本地入口监听 IP默认只允许本机访问。 127.0.0.10.0.0.0
CLIENT_AGENT_HTTP_PROXY_LISTEN_PORT HTTP 代理本地入口监听端口。 80807890
CLIENT_AGENT_RAW_JSON_ENABLED 是否启用 raw-json 调试入口。 truefalse
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:9001tcp://127.0.0.1:9001
LOG_LEVEL 日志级别预留配置。 debuginfowarningerror

Client Agent 的节点身份不是只写在 .envPOP 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_cidrsallowed_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 支持
用户名/密码 0x02RFC1929 支持
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 控制。默认示例允许 53123443

[
    '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 admindevopsnormal-user
target_host 目标主机。 192.168.10.20example.com
target_port 目标端口。 228044380805432
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-useradmindevops 可以访问任意主机的 80443,以及 808010080 端口。
  • Client Agent 只负责把请求封装成 Frame 发到 POP Server。
  • POP Server 校验策略后直接连接公网目标。

target_portsallowed_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 配置为 udpkcp,进程会启动失败并明确提示该传输尚未实现。

启动 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 ServerPOP Server 校验通过后直连公网目标,随后通过 DATA 帧转发原始 TCP 数据。

TCP 大流量 DATA 帧使用二进制帧编码;AUTHOPENCLOSEERROR 等控制帧仍使用 JSON 编码。启用 chacha20 时,二进制和 JSON Frame body 都会被加密。

吞吐相关参数由两端共用POP Server 和 Client Agent 建议保持一致:

LAYLINK_DATA_CHUNK_BYTES=1048576
LAYLINK_MAX_SEND_BUFFER_BYTES=67108864
LAYLINK_BACKPRESSURE_HIGH_WATERMARK_BYTES=33554432

LAYLINK_DATA_CHUNK_BYTES 越大,每 MB 需要处理的 Frame 越少,单连接下载通常越快;如果多会话公平性变差,可以降到 262144524288LAYLINK_BACKPRESSURE_HIGH_WATERMARK_BYTES 必须小于 LAYLINK_MAX_SEND_BUFFER_BYTES

大文件下载时LayLink 会使用 Workerman 的 pauseRecv() / resumeRecv() 做背压:当下游发送缓冲区过高时暂停上游读取,缓冲排空后继续读取。这可以避免单个慢连接无限堆积内存或因为发送缓冲区满而断联。

当 POP 收到目标站关闭连接时Client Agent 会先等待本地客户端发送缓冲区排空,再关闭本地 socket避免大文件尾部数据还在缓冲区里时被提前截断。TCP DATA 默认按 1 MiB 分片发送,以减少帧开销;可通过 LAYLINK_DATA_CHUNK_BYTES 调整。

当前每个 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_TYPEconfig/nodes.php 中的 node_type 一致。
  • Agent 的 allowed_cidrsallowed_ports 足够窄。
  • config/policies.php 不存在过宽的 target_hoststarget_ports
  • 生产环境不要继续使用固定的 dev-token 客户端认证。
  • 生产环境应补充 TLS、JWT 或 mTLS、限流和更完整的审计存储。