diff options
| author | Andrew Guschin <guschin@altlinux.org> | 2026-10-30 01:59:28 +0400 |
|---|---|---|
| committer | Andrew Guschin <guschin@altlinux.org> | 2026-10-30 01:59:28 +0400 |
| commit | da2e0eef42d5d611b0125de17e467dbddf3d8d71 (patch) | |
| tree | bdbbf6b7190157fc2325ccb7dd42cf6bb62cdcd1 /docs/research/alt-runtime-integration.md | |
| parent | 170f05697812b2f8b29226b4aa07b1b8db38873e (diff) | |
Document subscription formats and ALT runtime integrationresearch
- Add the Roentgen domain glossary
- Record research on 3x-ui subscriptions, runtime integration, and defaults
Diffstat (limited to 'docs/research/alt-runtime-integration.md')
| -rw-r--r-- | docs/research/alt-runtime-integration.md | 152 |
1 files changed, 152 insertions, 0 deletions
diff --git a/docs/research/alt-runtime-integration.md b/docs/research/alt-runtime-integration.md new file mode 100644 index 0000000..6900ad8 --- /dev/null +++ b/docs/research/alt-runtime-integration.md @@ -0,0 +1,152 @@ +# System Xray, DNS, and polkit on ALT Linux + +Researched 2026-10-29 for Roentgen's Linux first release. This is a feasibility report and proposed design, not an implemented or tested network configuration. The scope is ALT Linux on the same host as the CLI/TUI, with a persistent system service and the distribution's `/usr/bin/xray`. No managed binary, binary download, container forwarding, or concurrent full-tunnel VPN is proposed. + +## Findings and evidence boundary + +The installed Xray can carry the required host TCP/UDP traffic over native TUN and Trojan TCP TLS. It needs Roentgen to configure all Linux routing and DNS capture. It does not have the automatic route, outbound-interface, or DNS takeover settings described in the earlier report about Xray 26.10.29. A kernel nftables guard independent of both Xray and the controller can retain blocking after either crashes. Protecting boot requires a separate early guard and explicit dependencies on the ALT network providers. Neither ordinary Xray startup nor `network-online.target` supplies that guarantee. These are design conclusions from the sources below, not successful live experiments. + +Read-only inspection confirmed ALT Regular x86-64 with `xray-core-26.3.27-alt2`, systemd `262-alt1`, polkit `127-alt1`, NetworkManager `1.58.1-alt2`, openresolv `3.14.1-alt1`, nftables `1.1.7-alt2`, and iproute2 `6.18.0-alt1`. `/usr/bin/xray version` reports `26.3.27`, build `v1.260327.0`. Resolved, NetworkManager, and polkit are active. `/etc/resolv.conf` is a regular resolvconf-generated file with a non-loopback LAN nameserver. The NSS hosts line includes `dns`, rather than `resolve`. Private names and resolver addresses are deliberately omitted. This establishes the conventional libc DNS path; it does not prove every application uses that path. + +Inspection used RPM metadata/file lists, installed unit files and manuals, service active-state queries, and sanitized resolver metadata. No service, package, interface, route, DNS setting, firewall object, or polkit rule was changed. No agent, authorization request, remote proxy, or speed test was run. Root-only polkit rule directories could not be read without elevation; their effective contents remain unknown. The earlier main-checkout research reports were read as context only. This checkout has no glossary or ADR directory. + +## Pin the installed release, not the current documentation + +Upstream tag `v26.3.27` resolves to `d2758a023cd7f4174a5a5fa4ff66e487d4342ba0`. ALT packaging tag `26.3.27-alt2` resolves to `dfce17e2794d0bc2bba421d8b3383a0ba6117af2`. Its `.gear/tags/list` names that exact upstream commit, and `.gear/rules` exports the upstream tag as the source tar. The spec builds `./main` with vendored dependencies and declares no source patch. `alt2` adds GeoIP/geosite files; it does not add newer TUN automation. This connects the installed package release to the inspected upstream source without assuming that today's branch head matches it. [Upstream tag API](https://api.github.com/repos/XTLS/Xray-core/git/ref/tags/v26.3.27), [ALT refs](https://git.altlinux.org/gears/x/xray-core.git/refs), [ALT source tag mapping][alt-tags], [ALT export rules][alt-rules], [ALT spec][alt-spec]. + +Native TUN first shipped in v26.1.23. At v26.3.27 its JSON settings are exactly `name`, `MTU`, and `userLevel`; omitted name and MTU become `xray0` and `1500`. Use the source spelling `MTU`. The Linux implementation opens `/dev/net/tun`, creates a nonpersistent TUN, sets its MTU, and brings it up. It does not assign addresses or install routes/rules. Its stack registers IPv4, IPv6, TCP, and UDP. It has no ICMP transport, unlike the newer report's description of local echo responses. A synthetic TCP SYN-ACK also does not demonstrate remote reachability. [First TUN release](https://github.com/XTLS/Xray-core/releases/tag/v26.1.23), [pinned settings][tun-conf], [pinned Linux implementation][tun-linux], [pinned stack][tun-stack], [pinned limitations][tun-readme]. + +Do not generate `gateway`, `dns`, `autoSystemRoutingTable`, `autoOutboundsInterface`, `autoSystemDnsToGateway`, or `desc` for this release. Its Linux implementation also has no `XRAY_TUN_FD` handling. The JSON decoder does not call `DisallowUnknownFields`, so a config containing newer fields can appear acceptable while those fields do nothing. Roentgen must validate its own narrow schema and use a version-specific generator. [Settings][tun-conf], [Linux source][tun-linux], [JSON loader][json-loader]. + +Recommend `26.3.27` as the first supported upstream baseline, with the observed `26.3.27-alt2` ALT build as the initial validation target. `26.1.23` is the historical TUN floor, not a claim that every release since then has passed this product's contract. Check RPM identity, absolute binary path and executable ownership, parsed upstream version, TUN device availability, kernel nftables/conntrack and IPv6 support, and required routing commands. Reject older or unknown incompatible versions before activation. Re-check after distribution upgrades and retain blocking if requested enabled. A version comparison alone is insufficient; use an explicit tested compatibility range and the generated-config checks. Nothing here requires a newer Xray or an automatic download. + +The ALT spec installs `/usr/share/xray/geoip.dat` and `geosite.dat` from third-party rule-list projects. They are not needed for the first release's explicit CIDR and DNS policy. Do not make their contents an implicit default routing policy. [ALT spec][alt-spec]. + +## Config generation and validation + +The installed help advertises `/usr/bin/xray run -test -format json -config <generated-file>` and `run -dump`. Source gives an important qualification. `-test` calls `core.New` before checking the test flag. TUN construction calls its `Init`, which creates the device and starts its stack/link. Thus testing a complete TUN config can alter the network when privileged, or fail on permissions when unprivileged. It is not a pure parser check. `-dump` exits through the config-merge path before core construction, but only checks merging/parsing and may print credentials. [Command implementation][run], [core construction][core], [TUN constructor][tun-handler]. + +Use Roentgen schema validation first. Validate the Trojan/TLS/routing portion with `run -test` on a generated copy without the TUN inbound, restricted to permitted settings, no file logging, no external config URLs, and no privileged capabilities. Validate a complete TUN config only in a disposable network namespace or ALT VM during development. Production activation itself must handle device/config failure under an already installed guard. A safe merge inspection is `run -dump -format json -config <generated-file>` with protected output, but it cannot certify TUN support or working routing. None of these validation commands was executed on a TUN config during this research. + +Import only the agreed plain or outer-Base64 subscription list of Trojan TCP TLS links. Normalize to a typed profile containing the remote address/port, password, TLS server name and supported TLS options. Require certificate verification. Do not pass provider JSON, command arguments, outbound tags, file paths, routing, DNS, or helper instructions into the privileged runtime. The pinned Trojan parser accepts one server, requires its address, nonzero port and password, and rejects Trojan `flow`. It emits a deprecation warning but still builds this protocol. The client implements UDP encapsulation as well as TCP over its server connection. Real server UDP and IPv6 reachability remain acceptance tests. [Pinned Trojan config][trojan-conf], [pinned client][trojan-client]. + +Custom IP/port/protocol routing and custom DNS are feasible through this core's router and outbounds. TUN initially supplies destination IPs, not hostnames. Domain routing needs deliberately configured DNS knowledge or sniffing and cannot promise domain recognition for arbitrary encrypted traffic. Limit the first release to product-defined rules. User direct-to-Internet rules would relax the stated enabled guarantee and need a separate decision. LAN and SSH exceptions already form explicit direct policy. [Pinned router][router], [TUN handler][tun-handler], [TUN DNS discussion][tun-readme]. + +## DNS on the observed ALT resolver arrangement + +Do not use `resolvectl dns` or `~.` as the first release's solution for this host. An active resolved does not make applications use its stub or its D-Bus resolver. NetworkManager distinguishes its DNS plugin from its `rc-manager`; `resolvconf` is a supported resolver-file manager. Resolved can also consume a resolver file owned elsewhere. The observed file and NSS arrangement therefore does not justify transferring DNS ownership to resolved. [NetworkManager DNS and rc-manager documentation](https://www.networkmanager.dev/docs/api/latest/NetworkManager.conf.html), [resolved resolver-file modes](https://www.freedesktop.org/software/systemd/man/latest/systemd-resolved.service.html). + +Recommend retaining resolvconf ownership and capturing ordinary host unicast DNS in the packet path. Mark TCP and UDP destination port 53 for TUN routing before LAN/direct exceptions, for both families and every ordinary service UID. In Xray, put a DNS rule before ordinary routing rules. Send those flows to a dedicated `freedom` outbound with `settings.redirect` set to an approved resolver IP and port 53, chained with `proxySettings.tag` to the selected Trojan outbound. This forwards whole DNS exchanges instead of resolving only address records. The pinned freedom code overrides the destination and carries TCP/UDP; its outbound dialer dispatches a chained destination through the tagged outbound. This is a source-supported proposed composition, not a tested config recipe. [Freedom config][freedom-conf], [freedom implementation][freedom], [outbound chaining][outbound-handler], [socket marking][sockopt]. + +A LAN-only nameserver often cannot be reached from the Trojan server, which is why simply proxying the original LAN destination is insufficient. Use a remote-reachable resolver chosen by product policy or the operator. Keep the original resolver file unchanged, so explicit disable restores its normal use by removing capture. DNS to hardcoded external servers is captured too. Application DoH, DoT, and DoQ traverse the general proxy policy; Roentgen does not decrypt them. Local loopback stub requests can stay local, but every stub's eventual upstream TCP/UDP query must enter capture. Do not broadly exempt resolved or NetworkManager UIDs. Enforce DNS precedence over the direct LAN exception in both routing and the final egress guard. The guard must drop physical TCP/UDP port-53 egress before any LAN accept, apart from the bounded bootstrap-worker exception. A routing failure must not turn LAN DNS into direct egress. + +This recommendation avoids a resolver-file rollback dependency on controller survival. It still requires testing policy-route relookup of locally originated marked packets, local stub upstream sockets bound to physical links, and queries to local non-loopback addresses that the kernel would deliver through its local table. Support that last case through explicit interception to an owned DNS listener or reject it during preflight. The inspected host's resolver is a separate LAN address, so that local-destination case is not established here. A physical-bound socket that cannot be captured must fail closed, never silently receive a direct exception. + +An alternative is an owned local DNS forwarder plus an exclusive openresolv entry. The installed manual supports `resolvconf -a <interface.protocol> -x` with input on stdin and deletion with `-d`; only the latest exclusive entry is used. Openresolv distributes entries to libc and configured resolver subscribers. This is supported integration machinery, but Roentgen would need to own the listener, stale-entry recovery and DNS during disabled mode. It would still need capture for applications that ignore the file. Prefer packet capture first; do not overwrite `/etc/resolv.conf` or edit NetworkManager connection profiles as a shortcut. No resolvconf mutation was performed. [Pinned openresolv manual](https://github.com/NetworkConfiguration/openresolv/blob/v3.14.1/resolvconf.8.in), [project resolver-manager explanation](https://roy.marples.name/projects/openresolv). + +The pinned Xray `dns` outbound is another option, but its default `nonIPQuery` behavior rejects non-A/AAAA queries. `nonIPQuery: "skip"` forwards those queries through its outbound connection, while A/AAAA use its built-in DNS client. That requires separate routing/bootstrap controls for both paths. Whole-message forwarding through the redirected, chained freedom outbound is a smaller initial policy and preserves MX/TXT/SRV/HTTPS queries for testing. [Pinned DNS config][dns-conf], [pinned DNS processing][dns-out]. + +Endpoint bootstrap is a separate infrastructure exception. Resolve only the selected proxy hostname through a dedicated service-controlled worker, with direct egress limited to approved bootstrap resolver tuples. Cache endpoint addresses, refresh them with bounded retries and TTL handling, and preserve the TLS server name when connecting to an IP. Update endpoint allowlists before switching addresses and remove old entries safely. Do not give every application a resolver escape, or give Xray unrestricted DNS/direct Internet access. DNS-independent bootstrap IPs avoid recursive dependency on the selected tunnel. Initial subscription retrieval while disabled can use ordinary networking. Enabled refresh uses the working proxy or waits if it is unavailable; it must not create a general direct fallback. + +Local DNS names may stop resolving under forced remote DNS. mDNS/LLMNR use other ports and multicast; define their LAN treatment explicitly. If LAN split DNS is desired, it is an explicit exception to universal ordinary DNS capture. The report recommends no silent LAN DNS exception. + +## Local service boundary and polkit + +Use a root-owned persistent Roentgen system daemon with a small local system-bus API. The CLI/TUI stays unprivileged. The service owns validated desired state, fixed helper invocations, configuration files, policy rules, routes, and its nftables table. Run Xray under a dedicated Roentgen runtime UID with only the capabilities and device access needed for TUN and socket marks. Keep route/firewall management in the controller. This release still needs `CAP_NET_ADMIN` in Xray to create its TUN, so that ownership split is not a security boundary against a compromised core. It is sufficient to separate lifecycle responsibility for crash handling. The observed package unit runs as `xray` with `CAP_NET_ADMIN` and `CAP_NET_BIND_SERVICE`, starts `/etc/xray/`, and uses `Restart=on-abort`. Use a separate product-owned unit/config path that invokes the same `/usr/bin/xray`; do not take over an unrelated packaged Xray instance. Detect competing TUN/firewall ownership. [ALT installed-unit source][alt-unit], [Linux TUN privilege requirements](https://docs.kernel.org/networking/tuntap.html), [socket options][sockopt]. + +For every mutation, derive the subject from the incoming D-Bus message's unique sender name and call polkit `CheckAuthorization` with a `system-bus-name` subject. The system bus owns sender identity and can supply connection credentials. Polkit 127 resolves that bus name to credentials, with pidfd support where available. Never accept a caller-supplied UID/PID/session/name as proof, authorize the daemon's own subject, or trust a client-side `pkcheck` success. Scope the pending operation to the caller connection, exact validated request and current state generation; cancel on disconnect and re-check state before applying it. [D-Bus sender/credentials specification](https://dbus.freedesktop.org/doc/dbus-specification.html), [polkit bus-subject implementation][polkit-bus], [Authority API][polkit-api]. + +Proposed actions, each mapped to fixed typed methods: + +- `org.roentgen.runtime.set-enabled` authorizes `SetEnabled(bool)` and the persisted boot intent, including explicit disable and recovery disable. +- `org.roentgen.runtime.select-config` authorizes selecting an existing stored profile by opaque ID, including a guarded switch while enabled. +- `org.roentgen.config.modify` authorizes bounded typed profile/subscription updates. It grants no arbitrary Xray JSON or filesystem access. +- `org.roentgen.policy.modify` authorizes validated LAN, SSH, resolver and supported routing policy changes. Disallow removing the active management path through an implicit profile update. +- `org.roentgen.diagnostics.download-test` authorizes a manual bounded speed test through the selected profile. Periodic delay checks need no recurring user authorization. + +Expose sanitized status without secrets. Protect credential/subscription export separately if added. Methods never accept executable paths, shell strings, arbitrary systemd units, arbitrary nftables scripts, route commands or privileged file destinations. A bus policy may admit method calls to this one service, but the service must still authorize them. Polkit approves an operation; it neither makes the CLI root nor makes untrusted input safe. Do not use a general `pkexec`, systemd unit-management permission, or blanket privileged arbitrary execution. [Polkit mechanism/action model][polkit-doc127]. + +Ship action XML with explicit `auth_admin` for `allow_any`, `allow_inactive`, and `allow_active`. SSH is not a local active-console authorization case, so setting only `allow_active` would exclude the intended workflow. `auth_admin` requires an administrator identity, not necessarily the caller's password. Avoid `auth_self` and `auth_admin_keep` initially. Cached action authorization does not bind future parameter values, and disabling capture is security-sensitive too. Administrator overrides remain the host administrator's policy. No Roentgen group or permissive JavaScript rule is needed. [Pinned policy semantics][polkit-doc127]. + +ALT's polkit spec splits `50-default.rules` into `polkit-default-rules`, whose description designates wheel members as administrators. That subpackage is absent on the inspected host. Upstream 127 falls back to UID 0 when admin rules yield no identities. Because the installed rule directories were unreadable, do not infer that this host actually uses wheel, root-only auth, a usable root password, or any particular administrator identity. Authentication policy and PAM availability need a real operator test later. This is compatible with choosing polkit instead of a product-specific group. [ALT polkit spec](https://packages.altlinux.org/en/sisyphus/srpms/polkit/specfiles/), [pinned administrator fallback][polkit-backend], [ALT rule template][polkit-alt-default]. + +For interactive SSH with a controlling terminal, use the installed `/usr/bin/pkttyagent` for the CLI's retained system-bus connection via `--system-bus-name <unique-name> --notify-fd <fd>`. Wait for the notification FD to close before making the authorized request. The alternative is `--process pid,start-time`; avoid bare PID reuse. Suspend the TUI's raw/full-screen mode while the agent owns the terminal, then restore it. `--fallback` preserves an existing agent and may therefore select an unsuitable GUI agent; do not rely on it to guarantee a terminal prompt. Agent registration can fail and must be reported. Stop the agent when the interaction ends. This path is supported by the installed manual and pinned program, but no agent was started here. [pkttyagent manual](https://www.freedesktop.org/software/polkit/docs/latest/pkttyagent.1.html), [pinned agent][polkit-agent]. + +Only explicit interactive requests set `AllowUserInteraction`. Noninteractive invocations use flags zero, start no agent, and return an authentication-required/denied error without mutating intent. A suitable authorization may already exist under site policy, so noninteractive does not necessarily mean deny. Handle `is_authorized`, challenge, errors, dismissal and cancellation separately; only an affirmative authorization permits mutation. Use a unique cancellation ID, cancel pending checks on user cancel, timeout or disconnect, and serialize the resulting transaction. Cancellation before commit changes nothing. Cancellation after the service has durably accepted a transaction reports its ID/state rather than claiming rollback. Background retries, boot restoration and the 30-second health job operate on previously authorized state and never prompt. [Authority flags/results/cancellation][polkit-api]. + +## Persistent enabled intent and the crash-safe guard + +The minimum process layout is an early one-shot guard, one persistent controller, and one Xray runtime. Xray can keep an isolated loopback probe inbound alive while capture is disabled. A distinct, short-lived bootstrap worker can narrow infrastructure DNS privileges. No separate tun2socks process is required. Polkit and the system bus remain system dependencies, not child processes. + +Store requested enabled/disabled, selected profile snapshot and exception policy in root-owned durable files with atomic replacement, file and directory fsync, strict schemas and permissions. Keep enough guard input on a local early-mounted filesystem, preferably alongside configuration on the root filesystem, rather than depending on late `/var` mounts. Record effective state separately, such as disabled, enabled/retrying, enabled/healthy, or error. Requested enabled must never turn into disabled because Xray cannot start. + +Use an owned `inet` nftables output filter with blocking behavior when enabled. Permit loopback and the owned TUN path, narrow link maintenance, direct LAN policy after DNS capture, recognized management replies, and restricted runtime/bootstrap infrastructure. The runtime escape should require its dedicated UID plus approved endpoint address/port/protocol, not UID alone. Never exempt all root traffic or accept all established connections before the guard, since ordinary direct connections established before enable would survive. Retain the main-table physical routes and use higher-priority UID/mark and management exceptions, followed by policy capture into a separate dual-stack TUN table. [nftables kernel/filter model](https://netfilter.org/projects/nftables/manpage.html), [policy-routing selectors](https://man7.org/linux/man-pages/man8/ip-rule.8.html), [pinned Xray loop warning][tun-readme]. + +Kernel rules outlive the userspace process that installed them. If the core exits, its nonpersistent TUN disappears and policy lookup may fall through to physical routes, but the independent output guard must still drop ordinary physical egress. Controller failure must also leave that guard intact. Do not remove it from `ExecStopPost`, a destructor, a restart hook or ordinary failure cleanup. Use interface-name matching so loss of the TUN does not invalidate the guard. Atomic nftables batches update only Roentgen-owned objects. The transition can be atomic within nftables, but persistence, routing and process startup are separate operations and require ordering and reconciliation. [Kernel TUN lifetime](https://docs.kernel.org/networking/tuntap.html), [atomic nftables replacement](https://wiki.nftables.org/wiki-nftables/index.php/Atomic_rule_replacement). + +On enable, validate input and discover exceptions, install blocking policy, then durably commit enabled intent before reporting acceptance. Start/retry Xray, configure owned routes and DNS capture, and report healthy only after an actual HTTPS exchange through the selected outbound. Failure at any stage leaves enabled/retrying and the guard intact. A crash before the durable commit may leave temporary blocking even though the previous intent was disabled; reconciliation follows the durable record. On explicit disable, prepare removal of capture while still guarded, durably commit disabled, then release the owned guard and stop unneeded TUN processing. Interrupted disable can remain temporarily blocked; reboot uses the committed disabled record. Never claim these multi-resource steps form a single atomic transaction. + +At boot, a dedicated guard unit must run before `network-pre.target`, read the persisted intent and load the guard without network access or authentication. Use deliberate early-unit dependencies, including `DefaultDependencies=no` with the required local mounts and shutdown ordering, and pull in `network-pre.target`. Ordering alone does not propagate startup failure. Add `Requires=` and `After=` dependencies from both ALT `network.service` and NetworkManager to the guard so a failed guard prevents these providers from bringing up ordinary networking. The observed providers already order after `network-pre.target`; relying only on their `Wants=` is insufficient. Controller startup occurs later and cannot release an enabled guard merely because Xray is unavailable. [systemd network-pre semantics](https://github.com/systemd/systemd/blob/v262/man/systemd.special.xml), [requirement/ordering semantics](https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html). + +Guard state corruption, missing required files after installation, or guard installation failure must produce a blocked boot/network-provider failure, not infer disabled. Define a distinct installation marker so an explicitly initialized disabled record is recognizable. Reserve an authenticated local recovery path. An authorized root operator can always change the host policy; that is outside the crash guarantee. + +The installed ALT nftables unit is itself a conflict requiring packaging attention. Its `ExecStop` flushes the entire ruleset, and `ExecReload` flushes then includes its configuration. An independent Roentgen table would disappear under those operations. Inspect enabled firewall owners and either integrate the guard into their supported lifecycle or reject that arrangement until coordinated. Never promise survival of arbitrary privileged firewall flushes. The report inspected `/usr/lib/systemd/system/nftables.service` without invoking it. Choosing a unique table name does not protect it from a global flush. [Inspected installed ALT unit](/usr/lib/systemd/system/nftables.service). + +Boot protection is conditional on verified local-root ALT boot ordering with networking initially down. Initramfs networking, network-root mounts, preconfigured interfaces, alternate network starters and late firewall flushers can create egress before the guard. Those need initramfs integration or must be unsupported. Ordering before `network-online.target` cannot repair an earlier leak. Verify the actual boot graph and packet trace in an ALT VM before claiming boot enforcement. systemd restart policy can recover crashes, but start-limit exhaustion needs a deliberate bounded retry strategy and a hung process needs health supervision. Neither may release the guard. [Service restart semantics](https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html). + +## LAN, SSH, and dual-stack exceptions + +Discover and preserve connected LAN routes and required DHCP, IPv6 neighbor discovery, router discovery and essential ICMP error traffic. The core has no general ICMP transport; useful link control must remain narrowly direct. Do not classify every RFC1918/ULA prefix as locally connected LAN. Expose the actual direct policy to the operator without treating a LAN nameserver as an automatic DNS exemption. + +Protect both existing and new inbound SSH sessions on actual listening addresses and ports, including custom ports. Mark recognized inbound management connections and restore the connection mark into output packet marks before route selection; route replies through the direct physical path. Replies target the client's ephemeral port, so a destination-port-22 output rule is wrong. Connection-tracking marks and route-output mark changes provide the needed mechanisms. Restrict classification to configured host SSH listeners and supported ingress paths. Do not bypass arbitrary outbound SSH merely because its destination port matches. [nftables mark handling](https://wiki.nftables.org/wiki-nftables/index.php/Setting_packet_metainformation), [routing marks](https://man7.org/linux/man-pages/man8/ip-rule.8.html). + +Seed already established SSH flows during enable. They may have missed the new inbound-mark rule, so preserve their exact connection tuples and initial reply path before capture. Read-only listener discovery cannot always distinguish sshd from another process or account for future socket-activated/custom listeners. Require explicit management port/address configuration when discovery is ambiguous. `SSH_CONNECTION` identifies one invoking session and is not sufficient for other sessions or future reconnects. Do not broadly exempt an entire remote client address unless the operator deliberately accepts that extra direct access. + +Apply capture and the guard to IPv4 and IPv6 together, or block ordinary egress for a family that cannot be carried. Ensure selected-server IPv6 destinations and UDP forwarding work, even when the outer Trojan connection uses IPv4. Test MTU, fragmentation, DNS TCP fallback, reverse-path filtering and reply source selection. Do not change global `rp_filter` as a generic workaround. Existing firewall drops can still break SSH; an accept in Roentgen's chain does not override drops in other base chains. Containers, forwarding and concurrent full-tunnel VPNs remain outside the first-release support contract. + +## Delay and manual speed checks + +Keep a service-only SOCKS inbound on loopback, with a separate probe tag forced to the selected Trojan outbound. Do not expose an unauthenticated local listener to every user; use core-supported authentication and protected credentials, or isolate the probe listener. When disabled, omit TUN and host capture but retain or start the selected-config probe runtime. A disabled host stays direct while this one probe still traverses the selected server. This can use the same Xray process with guarded restart/reconfiguration on state changes. [Pinned SOCKS implementation](https://github.com/XTLS/Xray-core/blob/v26.3.27/proxy/socks/server.go), [pinned inbound settings](https://github.com/XTLS/Xray-core/blob/v26.3.27/infra/conf/socks.go), [pinned routing][router]. + +Every 30 seconds, issue one small HTTPS check with a total five-second deadline covering name resolution, proxy/TLS connection and expected response. Resolve the check destination through the proxy, retain TLS verification, bound response bytes and redirects, and prohibit direct fallback. Prevent overlapping runs and tie results to the selected profile generation. Label the measurement as HTTPS delay rather than ICMP latency. While disabled, a failed check only updates health. While enabled, failure retains blocking and schedules retries; it never changes requested intent. Manual download speed uses an explicit request, a bounded duration/byte budget and the same selected path. Never start speed downloads from the periodic timer. + +## Remaining decisions and tests + +The platform, system binary, polkit choice, persistent requested state, enabled failure blocking, automatic retries, direct disabled behavior, and 30-second/five-second check schedule are settled requirements. They need no further approval as product choices. The following decisions remain: + +1. Choose the default remote DNS resolver and bootstrap resolver, whether operators may customize them, and whether LAN-only DNS names are deliberately unsupported or receive an explicit split-DNS exception. +2. Specify what counts as direct LAN, allowed management interfaces/addresses/ports, behavior when listener discovery is ambiguous, and how future SSH listener changes update persisted exceptions. +3. Choose the HTTPS check URL and expected response, the manual speed-test source and budget, retry backoff, and health thresholds. Decide whether failed TCP and UDP checks produce different degraded states. +4. Confirm the supported ALT boot/storage/firewall arrangements, the recovery command and console procedure, and whether initramfs networking is excluded. Decide how to coordinate the distribution nftables unit before installation. +5. Confirm a tested Xray compatibility range and the administrator-authentication UX under this host's effective polkit/PAM policy. Decide whether any later action may cache authorization. No product-specific group is proposed. +6. Define selected-profile removal/refresh behavior while enabled. Recommend retaining the last authorized usable snapshot until replacement is validated, or remaining enabled/blocked if it cannot be retained. Never silently disable or switch to direct traffic. + +Before implementation is considered safe, run destructive networking tests only in a disposable ALT VM with console access. Cover complete config validation, Trojan TCP/TLS and UDP, IPv4/IPv6, UDP/TCP DNS and non-address records, hardcoded/LAN/stub resolver capture, private-name behavior, new and established SSH on custom ports, LAN access, existing direct conntrack flows, endpoint DNS/address changes, controller/Xray SIGKILL, hangs, retry limits, power loss at each persistence boundary, malformed state, enabled and disabled reboot packet traces, firewall reload/stop conflicts, and polkit remote/noninteractive/cancel/agent-absent cases. Also verify no system binary replacement and that probes still use the selected profile while disabled. No live TUN, routing, DNS interception, nftables, boot or authentication acceptance test was performed for this report. + +[alt-tags]: https://git.altlinux.org/gears/x/xray-core.git?p=xray-core.git;a=blob_plain;f=.gear/tags/list;hb=dfce17e2794d0bc2bba421d8b3383a0ba6117af2 +[alt-rules]: https://git.altlinux.org/gears/x/xray-core.git?p=xray-core.git;a=blob_plain;f=.gear/rules;hb=dfce17e2794d0bc2bba421d8b3383a0ba6117af2 +[alt-spec]: https://git.altlinux.org/gears/x/xray-core.git?p=xray-core.git;a=blob_plain;f=xray-core.spec;hb=dfce17e2794d0bc2bba421d8b3383a0ba6117af2 +[alt-unit]: https://git.altlinux.org/gears/x/xray-core.git?p=xray-core.git;a=blob_plain;f=xray.service;hb=dfce17e2794d0bc2bba421d8b3383a0ba6117af2 +[tun-conf]: https://github.com/XTLS/Xray-core/blob/d2758a023cd7f4174a5a5fa4ff66e487d4342ba0/infra/conf/tun.go +[tun-linux]: https://github.com/XTLS/Xray-core/blob/d2758a023cd7f4174a5a5fa4ff66e487d4342ba0/proxy/tun/tun_linux.go +[tun-stack]: https://github.com/XTLS/Xray-core/blob/d2758a023cd7f4174a5a5fa4ff66e487d4342ba0/proxy/tun/stack_gvisor.go +[tun-handler]: https://github.com/XTLS/Xray-core/blob/d2758a023cd7f4174a5a5fa4ff66e487d4342ba0/proxy/tun/handler.go +[tun-readme]: https://github.com/XTLS/Xray-core/blob/d2758a023cd7f4174a5a5fa4ff66e487d4342ba0/proxy/tun/README.md +[json-loader]: https://github.com/XTLS/Xray-core/blob/d2758a023cd7f4174a5a5fa4ff66e487d4342ba0/infra/conf/serial/loader.go +[run]: https://github.com/XTLS/Xray-core/blob/d2758a023cd7f4174a5a5fa4ff66e487d4342ba0/main/run.go +[core]: https://github.com/XTLS/Xray-core/blob/d2758a023cd7f4174a5a5fa4ff66e487d4342ba0/core/xray.go +[trojan-conf]: https://github.com/XTLS/Xray-core/blob/d2758a023cd7f4174a5a5fa4ff66e487d4342ba0/infra/conf/trojan.go +[trojan-client]: https://github.com/XTLS/Xray-core/blob/d2758a023cd7f4174a5a5fa4ff66e487d4342ba0/proxy/trojan/client.go +[router]: https://github.com/XTLS/Xray-core/blob/d2758a023cd7f4174a5a5fa4ff66e487d4342ba0/infra/conf/router.go +[sockopt]: https://github.com/XTLS/Xray-core/blob/d2758a023cd7f4174a5a5fa4ff66e487d4342ba0/transport/internet/sockopt_linux.go +[freedom-conf]: https://github.com/XTLS/Xray-core/blob/d2758a023cd7f4174a5a5fa4ff66e487d4342ba0/infra/conf/freedom.go +[freedom]: https://github.com/XTLS/Xray-core/blob/d2758a023cd7f4174a5a5fa4ff66e487d4342ba0/proxy/freedom/freedom.go +[outbound-handler]: https://github.com/XTLS/Xray-core/blob/d2758a023cd7f4174a5a5fa4ff66e487d4342ba0/app/proxyman/outbound/handler.go +[dns-conf]: https://github.com/XTLS/Xray-core/blob/d2758a023cd7f4174a5a5fa4ff66e487d4342ba0/infra/conf/dns_proxy.go +[dns-out]: https://github.com/XTLS/Xray-core/blob/d2758a023cd7f4174a5a5fa4ff66e487d4342ba0/proxy/dns/dns.go +[polkit-doc127]: https://github.com/polkit-org/polkit/blob/127/docs/man/polkit.xml +[polkit-api]: https://github.com/polkit-org/polkit/blob/127/docs/polkit/docbook-interface-org.freedesktop.PolicyKit1.Authority.xml +[polkit-bus]: https://github.com/polkit-org/polkit/blob/127/src/polkit/polkitsystembusname.c +[polkit-agent]: https://github.com/polkit-org/polkit/blob/127/src/programs/pkttyagent.c +[polkit-backend]: https://github.com/polkit-org/polkit/blob/127/src/polkitbackend/polkitbackendduktapeauthority.c +[polkit-alt-default]: https://git.altlinux.org/gears/p/polkit.git?p=polkit.git;a=blob_plain;f=src/polkitbackend/50-default.rules.in;hb=6f4777cb31eb8b30f54bfe86624930f74fc0f2de |