P9(3): per-inbound forwarding module (amo-style form, dynamic egress compile)

Design as decided 2026-10-21 (replaces the raw-JSON egress editor idea):
the forwarding form lives on the inbound, and the entry agent's egress
entries are compiled from it on every config pull — never snapshotted.

- model: Inbound.Forwarding (JSON text) via AutoMigrate; panel-side
  metadata only, never reaches xray itself
- service/forwarding.go: ForwardingSpec (mode direct/forward; landing
  managed|manual), save-time validation (managed target must exist and
  be a SOCKS-family landing — the fork's SK5 inbound is 'mixed'; no
  self-forward; manual endpoint/protocol sanity), live resolution
  (managed: concrete listen IP wins, else hosting node's address;
  credentials from the target's first account), and the compiler that
  appends one socks/http outbound (tag fwd-<inboundId>) plus a prepended
  inboundTag rule per forwarding inbound
- buildXrayConfig agent path: compile runs after applyAgentEgress, so
  entries ride on the live template (or stored egress override) and
  template edits keep flowing; unresolvable entries skip with a warning
  instead of failing the whole config
- UpdateInbound's field-merge now carries Forwarding (its absence was
  caught by the smoke: adds worked, updates silently kept the old spec)
- GET /panel/api/inbounds/forwardTargets: managed-landing candidates
  (id/remark/tag/listen/port/node name) — credentials never exposed
- frontend: new Forwarding tab in the inbound form (mode, landing type,
  managed picker, manual endpoint with link import incl. base64 form),
  adapter round-trips the spec as a JSON string like settings; en/zh
  strings; generated API types refreshed

Acceptance: unit tests for parse/validate/resolve/compile incl. the
dynamic-recompile proof (template edit + spec edit both reflected), and
an end-to-end smoke (real binary, two agent nodes, all inbound writes
in browser form-urlencoded format): 22/22 PASS — see P9-REPORT.md.
This commit is contained in:
Muse
2026-10-21 17:40:42 +00:00
parent ceca96571d
commit f6d652019d
16 changed files with 874 additions and 4 deletions
+22
View File
@@ -282,6 +282,28 @@
"streamTab": "Stream",
"securityTab": "Security",
"sniffingTab": "Sniffing",
"forwardingTab": "Forwarding",
"forwarding": {
"mode": "Forwarding",
"direct": "Direct",
"forward": "Forward (entry → exit)",
"landing": "Landing",
"managed": "Managed landing",
"manual": "Manual endpoint",
"target": "Landing inbound",
"targetPlaceholder": "Pick a SOCKS (mixed) inbound",
"targetDisabled": "disabled",
"address": "Landing address",
"port": "Exit port",
"protocol": "Entry → exit protocol",
"username": "SOCKS5 username",
"password": "Landing password",
"importLink": "Import from link",
"importPlaceholder": "socks5://user:pass@host:port",
"importApply": "Import",
"importFailed": "Could not parse that link",
"help": "Applies when this inbound is deployed to an agent node: on every config pull the panel compiles this forwarding into a dedicated outbound plus a routing rule for that agent. A managed landing's address and credentials are resolved from the chosen inbound at build time."
},
"sniffingMetadataOnly": "Metadata only",
"sniffingRouteOnly": "Route only",
"sniffingIpsExcluded": "IPs excluded",
+22
View File
@@ -282,6 +282,28 @@
"streamTab": "传输",
"securityTab": "安全",
"sniffingTab": "嗅探",
"forwardingTab": "转发",
"forwarding": {
"mode": "转发",
"direct": "直连",
"forward": "转发(入口→出口)",
"landing": "落地方式",
"managed": "受管落地",
"manual": "手动端点",
"target": "落地入站",
"targetPlaceholder": "选择一个 SK5(mixed)入站",
"targetDisabled": "已停用",
"address": "落地地址",
"port": "出口端口",
"protocol": "入口到出口协议",
"username": "SOCKS5 用户名",
"password": "落地密码",
"importLink": "从链接导入",
"importPlaceholder": "socks5://user:pass@host:port",
"importApply": "导入",
"importFailed": "链接解析失败",
"help": "仅当本入站部署到 agent 节点时生效:该 agent 每次拉取配置时,面板把这条转发编译成专属出站加一条路由规则拼进它的配置。受管落地的地址与账密在生成时从所选入站实时解析。"
},
"sniffingMetadataOnly": "仅元数据",
"sniffingRouteOnly": "仅路由",
"sniffingIpsExcluded": "排除的 IP",