Add real-traffic node testing, speed tests, VLESS+Reality support, and subscription by-name refresh
LatestAdded
- Real-traffic node testing by default. The
testcommand now starts a temporary xray instance per node (dedicated SOCKS port, bound to 127.0.0.1 only) and requestsgenerate_204through it, separating "TCP reachable" from "proxy actually usable." This fixes the long-standing issue where a TCP-only check marked a node as healthy even though proxy traffic never flowed (expired credentials, protocol handshake failure, no outbound connectivity on the peer). Output now shows separate TCP and proxy columns; pass--tcpto keep the fast TCP-only check. Addedcheck_real_traffic,batch_check_real,_curl_through_socks(usessocks5hso DNS resolves through the proxy, avoiding local DNS-pollution false negatives),_find_free_port, and_kill_procinxpilot/health_checker.py. auto_switchredesigned as active best-node selection. Everyintervalseconds it runs a real-traffic check across all nodes, picks the lowest-latency usable node as the best, and switches whenever the best is not the current node — no longer just a fallback that switches only when the current node fails.hysteresis(default 0) prevents flapping between nodes with similar latency; when the current node is unusable it ignores hysteresis and switches to the best; when every node is unreachable it refreshes the subscription by name (with backoff), re-checks, and switches. Thethresholdoption is replaced byhysteresis. Rewrote_check_and_switchinxpilot/auto_switch.pyand added_try_refresh_subscription,_switch_to(reused bymanual_switch), and_log_usable_status.- Subscription import with by-name refresh.
NodeManager.import_from_subscriptiongains anupdate_existingparameter; when true, it matches existing nodes by name and overwrites their connection fields (address, port, UUID, password, cipher, transport, TLS, SNI) while preserving id, group, and custom name — so when the provider rotates credentials or IPs, the same node is actually refreshed instead of a suffixed duplicate being created (jms-c56s1→jms-c56s1_1). Added theSUBSCRIPTION_REFRESH_FIELDSconstant and_refresh_node_fields. statuswarns whenauto_switchis off, reminding you that the proxy will not auto-switch if the current node fails.- VLESS+Reality support.
_parse_vless_linkparses Reality parameters (pbkpublic key,sidshort ID,fpfingerprint,flow) and URL-decodes the fragment node name (fixing%40/%3Agarble after import).ProxyManager._generate_outboundemitsrealitySettingsfor Reality nodes and writesflow(e.g.xtls-rprx-vision) into the VLESS user object. The storage field whitelist (add_node/update_node/ subscription refresh) now includes the Reality fields. - Automatic subscription cleanup with source tagging.
import_from_subscriptiongains asourceparameter that tags imported/refreshed nodes (e.g.source: 'JMS') and prunes nodes the subscription no longer returns. When a subscription renames a node (e.g.JMS-c56s1→JMS-1336028@c56s1) or drops one, the stale entry is removed so the local set always matches what the subscription returns. Includes cliff protection (skips pruning when the returned count drops below 50% of existing, to avoid mass deletion on a transient outage). Manually added nodes have nosourceand are never pruned. Bothsubscription updateand the auto_switch auto-refresh use this mode. - Usable-node anomaly logging. After each real-traffic check, if fewer nodes are usable than the total,
auto_switchlogs a warning listing the unreachable nodes and likely causes (written tolog_file, default/tmp/xpilot.log); after subscription import, if a source's node count does not match what the subscription returned, it logs the reason (with added/refreshed/failed/pruned counts) so you can tell why usable nodes dropped. xpilot test speed. Measures download speed via Cloudflare's__downendpoint (default 10 MB per node).--all-nodestests every node's proxy speed concurrently;--directtests direct (no-proxy) speed;--currenttests the current node;--sizesets the download size. Extracted_start_probe(shared temp-xray-probe logic reused by connectivity and speed) and addedcheck_speed,check_direct_speed,batch_check_speed, and_curl_downloadinxpilot/health_checker.py.testis now a command group (invoke_without_commandkeepsxpilot test --all-nodes/--tcp/--current/--groupworking) to host thespeedsubcommand. Breaking change: single-node testing moved from a positional argument (xpilot test my_node) to the--nodeoption (xpilot test --node my_node), because a group's positional would collide with the subcommand name.statusshows three live metrics by default: the current node's real-traffic latency, through-node download speed, and direct download speed (downloading 5 MB through the running proxy and directly).--no-speedskips the speed test and shows latency only.- Latency comparison everywhere. Wherever "through-node latency" is shown, "direct latency" now appears alongside —
statusshows them side by side;testshows the direct-latency baseline in its connectivity summary for comparison. AddedHealthChecker.check_direct_latency(a directgenerate_204request that auto-falls back to the Cloudflare endpoint behind the GFW). - New unit tests: subscription by-name refresh vs. default append, source auto-cleanup with cliff protection (
tests/test_node_manager.py); real-traffic check failure paths and port probing (tests/test_health_checker.py); auto_switch real-traffic switch decisions and subscription-refresh backoff (tests/test_auto_switch.py); VLESS+Reality parsing and outbound-config generation (tests/test_subscription.py,tests/test_proxy_manager.py).
Changed
subscription updatenow refreshes by name and imports new nodes instead of only appending new ones (matching the "update" semantics). Usenode importto append without refreshing existing nodes.- Default
auto_switchsettings inxpilot/config.pyaddauto_update_subscription(defaultTrue) andsubscription_refresh_cooldown(default3600). auto_switch.enableddefaults toTrue(wasFalse). A fresh install orxpilot initnow enables real-traffic auto-switching out of the box — no manualconfig setneeded.
Fixed
subscription.fetchnow uses atrust_env=Falsesession to force a direct connection, bypassing environment variables and the macOS system proxy. Previously,xpilot startpointed the system proxy at the local xray, so when the proxy itself was broken the subscription request was intercepted too — a "proxy down → can't fetch subscription → can't recover proxy" deadlock._parse_ss_linkrewritten to fix SIP002 (ss://base64(method:password)@host:port#name) parsing. Base64 characters (+/=) in the userinfo madeurlparsemistake the userinfo for hostname/port, so SS nodes failed to parse and were silently skipped during subscription updates, leaving stale nodes unrefreshed. Added_b64decode_loosefor websafe-base64 compatibility.
Full Changelog: v0.3.0...v0.4.0