[GH-ISSUE #2818] Amnezia Premium VLESS reports connected but passes no traffic; gateway can issue malformed or dead configs #1287

Open
opened 2026-10-13 20:39:28 +00:00 by bsakaguchi · 4 comments
Owner

Originally created by @ovkirillov on GitHub (Jul 8, 2026).
Original GitHub issue: https://github.com/amnezia-vpn/amnezia-client/issues/2818

Description

AmneziaVPN reports an Amnezia Premium VLESS connection as connected and creates tun2, but no traffic passes. Native traffic, an HTTP CONNECT proxy, and a SOCKS5 proxy all time out.

This appears related to #2750, but this report includes Linux reproduction, gateway-allocation validation, route/firewall analysis, and an independent Xray Core control test.

No subscription key, UUID, REALITY key, short ID, XHTTP path, or authentication data is included below.

Environment

  • AmneziaVPN: 4.8.19.0 (129ae44)
  • OS: Fedora 43 x86_64
  • Kernel: 6.19
  • Premium protocols advertised by the imported profile: awg, vless
  • Route mode: site-based compatibility mode, all public traffic through the tunnel with RFC1918 networks excluded
  • Self-hosted VLESS + REALITY works on the same host and network

Steps to reproduce

  1. Import an active Amnezia Premium subscription.
  2. Select Use VLESS protocol and reload/fetch the API configuration.
  3. Connect to the Premium profile.
  4. Observe that the UI/service reports connected and tun2 is created.
  5. Test DNS and HTTPS traffic.

Example checks:

ip -4 route get 1.1.1.1
curl -4 --max-time 15 https://api.ipify.org
curl -4 --max-time 15 --resolve api.ipify.org:443:104.26.12.205 https://api.ipify.org

Actual behavior

  • tun2 is UP,LOWER_UP and the public route selects it.
  • Amnezia reports the connection as active.
  • DNS requests time out.
  • A destination pinned with --resolve also times out, so this is not only DNS.
  • HTTP CONNECT and SOCKS5 traffic fail as well.

Expected behavior

The client should only report the Premium VLESS connection as usable after the Xray outbound can complete its handshake and pass traffic. The gateway should not issue structurally incomplete or unreachable configurations.

Findings

1. Endpoint route and kill-switch exception can target the wrong address

For one issued Premium XHTTP/TLS configuration:

  • Profile metadata hostName and Xray outbounds[].settings.vnext[].address were different.
  • Amnezia added a physical-interface exception for hostName.
  • The actual Xray endpoint was routed into tun2 itself.
  • The kill switch also allowed the metadata host but not the actual Xray endpoint.

This creates a recursive tunnel bootstrap failure. Adding a /32 physical route and amnvpn.110.allowNets exception for the actual Xray vnext.address fixes the bootstrap route, although the issued Premium sessions described below still fail.

Suggested client fix: derive the physical route and kill-switch exception from the active Xray outbound address, not only from profile hostName.

2. Gateway issued malformed or dead configurations

The encrypted Premium gateway API was queried repeatedly through a known-working self-hosted tunnel. Returned VLESS profiles were validated before use.

Observed allocations included:

  • XHTTP/TLS endpoints that immediately refused TCP/443.
  • A REALITY profile with a public key but an empty serverName and empty short ID. TCP connected, but the REALITY session never completed.
  • A structurally complete REALITY/Vision profile whose server accepted TCP but reset the VLESS session.

Multiple locations were tested. Invalid configurations should be rejected by the API or client before replacing the last known configuration.

3. Independent Xray Core control reproduces the reset

The complete REALITY/Vision profile was exported locally without exposing its secrets and tested with official Xray Core 26.5.9:

  • Local inbound: SOCKS5 on 127.0.0.1
  • Premium outbound: exact gateway-issued VLESS configuration
  • Outer route: a working self-hosted VLESS + REALITY tunnel
  • Fingerprints tested: provider-default Chrome and Firefox

Xray accepted the local SOCKS request, then the Premium session was reset:

accepted tcp:api.ipify.org:443 [premium-test-socks >> proxy]
curl: (35) Recv failure: Connection reset by peer

Because this also fails through a working alternate route with upstream Xray Core, the complete Premium allocation is failing independently of Fedora TUN routing, DNS, or the direct ISP path.

Control result

After restoring the self-hosted profile on the same host:

native egress:      working
HTTP CONNECT:       working
SOCKS5:             working
route:              dev tun2
tun2socks processes: 1

This confirms that the local proxy services, TUN path, and general Xray support are functional.

Suggested fixes

  1. Validate each Premium config before commit:
    • reachable vnext.address:port;
    • non-empty REALITY serverName and public key;
    • supported transport/security combination.
  2. Build the physical route and kill-switch exception from the actual Xray outbound address.
  3. Do not report Connected solely because tun2 exists; require an Xray handshake or a small connectivity probe.
  4. Keep the previous known-working configuration when a refreshed allocation is malformed or unreachable.
  5. Check the Premium backend pools that currently issue unreachable XHTTP endpoints, incomplete REALITY metadata, or sessions that reset after TCP establishment.

Additional notes

  • Both Chrome and Firefox uTLS fingerprints were tested.
  • Only one Amnezia client connection attempt and one tun2socks process were active during the final tests.
  • Subscription and transport secrets can be provided privately to maintainers if required.
Originally created by @ovkirillov on GitHub (Jul 8, 2026). Original GitHub issue: https://github.com/amnezia-vpn/amnezia-client/issues/2818 ## Description AmneziaVPN reports an Amnezia Premium VLESS connection as connected and creates `tun2`, but no traffic passes. Native traffic, an HTTP CONNECT proxy, and a SOCKS5 proxy all time out. This appears related to #2750, but this report includes Linux reproduction, gateway-allocation validation, route/firewall analysis, and an independent Xray Core control test. No subscription key, UUID, REALITY key, short ID, XHTTP path, or authentication data is included below. ## Environment - AmneziaVPN: `4.8.19.0` (`129ae44`) - OS: Fedora 43 x86_64 - Kernel: 6.19 - Premium protocols advertised by the imported profile: `awg`, `vless` - Route mode: site-based compatibility mode, all public traffic through the tunnel with RFC1918 networks excluded - Self-hosted VLESS + REALITY works on the same host and network ## Steps to reproduce 1. Import an active Amnezia Premium subscription. 2. Select `Use VLESS protocol` and reload/fetch the API configuration. 3. Connect to the Premium profile. 4. Observe that the UI/service reports connected and `tun2` is created. 5. Test DNS and HTTPS traffic. Example checks: ```console ip -4 route get 1.1.1.1 curl -4 --max-time 15 https://api.ipify.org curl -4 --max-time 15 --resolve api.ipify.org:443:104.26.12.205 https://api.ipify.org ``` ## Actual behavior - `tun2` is `UP,LOWER_UP` and the public route selects it. - Amnezia reports the connection as active. - DNS requests time out. - A destination pinned with `--resolve` also times out, so this is not only DNS. - HTTP CONNECT and SOCKS5 traffic fail as well. ## Expected behavior The client should only report the Premium VLESS connection as usable after the Xray outbound can complete its handshake and pass traffic. The gateway should not issue structurally incomplete or unreachable configurations. ## Findings ### 1. Endpoint route and kill-switch exception can target the wrong address For one issued Premium XHTTP/TLS configuration: - Profile metadata `hostName` and Xray `outbounds[].settings.vnext[].address` were different. - Amnezia added a physical-interface exception for `hostName`. - The actual Xray endpoint was routed into `tun2` itself. - The kill switch also allowed the metadata host but not the actual Xray endpoint. This creates a recursive tunnel bootstrap failure. Adding a `/32` physical route and `amnvpn.110.allowNets` exception for the actual Xray `vnext.address` fixes the bootstrap route, although the issued Premium sessions described below still fail. Suggested client fix: derive the physical route and kill-switch exception from the active Xray outbound address, not only from profile `hostName`. ### 2. Gateway issued malformed or dead configurations The encrypted Premium gateway API was queried repeatedly through a known-working self-hosted tunnel. Returned VLESS profiles were validated before use. Observed allocations included: - XHTTP/TLS endpoints that immediately refused TCP/443. - A REALITY profile with a public key but an empty `serverName` and empty short ID. TCP connected, but the REALITY session never completed. - A structurally complete REALITY/Vision profile whose server accepted TCP but reset the VLESS session. Multiple locations were tested. Invalid configurations should be rejected by the API or client before replacing the last known configuration. ### 3. Independent Xray Core control reproduces the reset The complete REALITY/Vision profile was exported locally without exposing its secrets and tested with official Xray Core 26.5.9: - Local inbound: SOCKS5 on `127.0.0.1` - Premium outbound: exact gateway-issued VLESS configuration - Outer route: a working self-hosted VLESS + REALITY tunnel - Fingerprints tested: provider-default Chrome and Firefox Xray accepted the local SOCKS request, then the Premium session was reset: ```text accepted tcp:api.ipify.org:443 [premium-test-socks >> proxy] curl: (35) Recv failure: Connection reset by peer ``` Because this also fails through a working alternate route with upstream Xray Core, the complete Premium allocation is failing independently of Fedora TUN routing, DNS, or the direct ISP path. ## Control result After restoring the self-hosted profile on the same host: ```text native egress: working HTTP CONNECT: working SOCKS5: working route: dev tun2 tun2socks processes: 1 ``` This confirms that the local proxy services, TUN path, and general Xray support are functional. ## Suggested fixes 1. Validate each Premium config before commit: - reachable `vnext.address:port`; - non-empty REALITY `serverName` and public key; - supported transport/security combination. 2. Build the physical route and kill-switch exception from the actual Xray outbound address. 3. Do not report `Connected` solely because `tun2` exists; require an Xray handshake or a small connectivity probe. 4. Keep the previous known-working configuration when a refreshed allocation is malformed or unreachable. 5. Check the Premium backend pools that currently issue unreachable XHTTP endpoints, incomplete REALITY metadata, or sessions that reset after TCP establishment. ## Additional notes - Both Chrome and Firefox uTLS fingerprints were tested. - Only one Amnezia client connection attempt and one `tun2socks` process were active during the final tests. - Subscription and transport secrets can be provided privately to maintainers if required.
Author
Owner

@Swiftkill commented on GitHub (Jul 10, 2026):

Same problem. At same time wole programs UI becomes unresponsible or coruped and reports 1100 API error

<!-- gh-comment-id:4937003791 --> @Swiftkill commented on GitHub (Jul 10, 2026): Same problem. At same time wole programs UI becomes unresponsible or coruped and reports 1100 API error
Author
Owner

@WAYuriyDmitriev commented on GitHub (Jul 12, 2026):

Some problem Macos 15.5

<!-- gh-comment-id:4951183941 --> @WAYuriyDmitriev commented on GitHub (Jul 12, 2026): Some problem Macos 15.5
Author
Owner

@zhghe77 commented on GitHub (Jul 21, 2026):

Same problem on Fedora 44, but works fine on Windows 11.

<!-- gh-comment-id:5032236768 --> @zhghe77 commented on GitHub (Jul 21, 2026): Same problem on Fedora 44, but works fine on Windows 11.
Author
Owner

@HeisLuka commented on GitHub (Sep 9, 2026):

I rechecked this against current dev (2a8242d3067d1fafcc5e17417c1eb98a430896d9), and the source still appears to permit the main failure modes described in this issue.

There seem to be three independent problems here.

1. XRay still has two possible endpoint identities

XrayProtocol loads the actual native XRay configuration into m_xrayConfig, but independently derives:

m_remoteAddress = NetworkUtilities::getIPAddress(
    m_rawConfig.value(configKey::hostName).toString());

The real VLESS transport endpoint, however, is encoded in the XRay outbound:

outbounds[].settings.vnext[].address
outbounds[].settings.vnext[].port

The project itself already treats vnext[0].address as the VLESS address in its serialization code.

But setupRouting() still uses the metadata-derived m_remoteAddress for control-plane decisions, including:

AdapterIndexTo(m_remoteAddress)
vpnServer = m_remoteAddress
enableKillSwitch(...)
enablePeerTraffic(...)

So a configuration with:

profile hostName = A
XRay vnext.address = B

can still result in XRay dialing B while the surrounding routing / firewall logic reasons about A.

That means the same VPN generation can still have two different notions of "the server endpoint".

I think this should be collapsed into one canonical transport identity, derived from the actual active XRay outbound:

TransportEndpointIdentity
= vnext.address + vnext.port

and that same identity should be used for endpoint escape, physical route lookup, kill-switch allowance, diagnostics and reachability checks.

hostName can remain profile/API metadata, but it should not independently define the control-plane endpoint when the native XRay config says something different.

2. Premium refresh still appears to trust a gateway candidate before proving it is usable

The current refresh path is still roughly:

/v1/config
-> decode/decompress JSON
-> configVersion check
-> ApiV2ServerConfig::fromJson(...)
-> preserve account/local fields
-> replace stored server configuration

ApiV2ServerConfig::fromJson() is a deserializer. It copies fields such as hostName, containers, DNS, API config, etc.; it does not validate VLESS/REALITY semantics.

Likewise, SecureServersRepository::editServer() validates the broad config type and then persists the replacement. It is not a protocol validator.

I do not see a validation barrier in this path that proves, before promotion, that the returned VLESS generation has for example:

usable outbound
non-empty address
valid port
UUID
valid transport/security combination

if security == reality:
    serverName
    publicKey
    shortId

and there is no last-known-good activation transaction around that replacement.

So the problematic state described in the report is still structurally possible:

working generation
-> gateway returns candidate
-> candidate is syntactically deserializable
-> candidate becomes authoritative
-> only afterwards connection proves it is dead

A safer contract would be:

fetch candidate
-> structural validation
-> protocol semantic validation
-> derive actual transport endpoint
-> bounded activation/probe
-> promote candidate

If validation or activation fails, the previous known-working generation should remain authoritative.

This is separate from the backend problem. The independent upstream-Xray test in the original report is important because it shows that some allocations can also be dead outside Amnezia's TUN/routing path. Client-side validation cannot repair a bad backend allocation, but it can prevent that allocation from replacing a working one.

3. Connected still does not mean the XRay outbound is healthy

This part is particularly clear in current XrayProtocol::startTun2Socks().

After tun2socks emits its local stack-ready message:

[STACK] tun://... <-> socks5://...

the client runs setupRouting() and then does:

setConnectionState(Vpn::ConnectionState::Connected);

There is no requirement here for:

successful remote XRay handshake
successful VLESS/REALITY session
received data
successful DNS/HTTP probe

So the state from this report remains representable by design:

TUN exists
tun2socks local stack ready
routes installed
UI = Connected

but

XRay outbound dead
traffic = 0

In other words:

LOCAL_STACK_READY != DATA_PLANE_HEALTHY

I would either introduce an intermediate state or make Connected depend on a bounded end-to-end health condition.

This does not need to be a heavy Internet speed test. Even a small controlled probe, or a protocol/data-plane readiness signal with a timeout, would distinguish "local plumbing is installed" from "the VPN can actually transport traffic".

Suggested regression cases

  1. hostName=A, vnext.address=B -> endpoint routing / firewall logic must target B.
  2. Changing metadata hostName alone must not change the actual XRay transport path.
  3. REALITY candidate with missing serverName, publicKey or required shortId -> reject before repository replacement.
  4. Candidate that cannot activate -> preserve the previous generation.
  5. tun2socks reports local stack ready but XRay outbound is dead -> must not report a healthy Connected state.
  6. Valid candidate passes activation/data-plane check -> atomically promote it.

So although this issue was originally reported against 4.8.19.0, the core client-side invariants behind it still do not appear to be enforced in current dev.

I would treat the backend allocation quality and these client-side lifecycle/control-plane bugs as related but separate issues rather than choosing one as the explanation for all failures.

<!-- gh-comment-id:5607347626 --> @HeisLuka commented on GitHub (Sep 9, 2026): I rechecked this against current `dev` (`2a8242d3067d1fafcc5e17417c1eb98a430896d9`), and the source still appears to permit the main failure modes described in this issue. There seem to be three independent problems here. ### 1. XRay still has two possible endpoint identities `XrayProtocol` loads the actual native XRay configuration into `m_xrayConfig`, but independently derives: ```cpp m_remoteAddress = NetworkUtilities::getIPAddress( m_rawConfig.value(configKey::hostName).toString()); ``` The real VLESS transport endpoint, however, is encoded in the XRay outbound: ```text outbounds[].settings.vnext[].address outbounds[].settings.vnext[].port ``` The project itself already treats `vnext[0].address` as the VLESS address in its serialization code. But `setupRouting()` still uses the metadata-derived `m_remoteAddress` for control-plane decisions, including: ```text AdapterIndexTo(m_remoteAddress) vpnServer = m_remoteAddress enableKillSwitch(...) enablePeerTraffic(...) ``` So a configuration with: ```text profile hostName = A XRay vnext.address = B ``` can still result in XRay dialing B while the surrounding routing / firewall logic reasons about A. That means the same VPN generation can still have two different notions of "the server endpoint". I think this should be collapsed into one canonical transport identity, derived from the actual active XRay outbound: ```text TransportEndpointIdentity = vnext.address + vnext.port ``` and that same identity should be used for endpoint escape, physical route lookup, kill-switch allowance, diagnostics and reachability checks. `hostName` can remain profile/API metadata, but it should not independently define the control-plane endpoint when the native XRay config says something different. ### 2. Premium refresh still appears to trust a gateway candidate before proving it is usable The current refresh path is still roughly: ```text /v1/config -> decode/decompress JSON -> configVersion check -> ApiV2ServerConfig::fromJson(...) -> preserve account/local fields -> replace stored server configuration ``` `ApiV2ServerConfig::fromJson()` is a deserializer. It copies fields such as `hostName`, containers, DNS, API config, etc.; it does not validate VLESS/REALITY semantics. Likewise, `SecureServersRepository::editServer()` validates the broad config type and then persists the replacement. It is not a protocol validator. I do not see a validation barrier in this path that proves, before promotion, that the returned VLESS generation has for example: ```text usable outbound non-empty address valid port UUID valid transport/security combination if security == reality: serverName publicKey shortId ``` and there is no last-known-good activation transaction around that replacement. So the problematic state described in the report is still structurally possible: ```text working generation -> gateway returns candidate -> candidate is syntactically deserializable -> candidate becomes authoritative -> only afterwards connection proves it is dead ``` A safer contract would be: ```text fetch candidate -> structural validation -> protocol semantic validation -> derive actual transport endpoint -> bounded activation/probe -> promote candidate ``` If validation or activation fails, the previous known-working generation should remain authoritative. This is separate from the backend problem. The independent upstream-Xray test in the original report is important because it shows that some allocations can also be dead outside Amnezia's TUN/routing path. Client-side validation cannot repair a bad backend allocation, but it can prevent that allocation from replacing a working one. ### 3. `Connected` still does not mean the XRay outbound is healthy This part is particularly clear in current `XrayProtocol::startTun2Socks()`. After tun2socks emits its local stack-ready message: ```text [STACK] tun://... <-> socks5://... ``` the client runs `setupRouting()` and then does: ```cpp setConnectionState(Vpn::ConnectionState::Connected); ``` There is no requirement here for: ```text successful remote XRay handshake successful VLESS/REALITY session received data successful DNS/HTTP probe ``` So the state from this report remains representable by design: ```text TUN exists tun2socks local stack ready routes installed UI = Connected but XRay outbound dead traffic = 0 ``` In other words: ```text LOCAL_STACK_READY != DATA_PLANE_HEALTHY ``` I would either introduce an intermediate state or make `Connected` depend on a bounded end-to-end health condition. This does not need to be a heavy Internet speed test. Even a small controlled probe, or a protocol/data-plane readiness signal with a timeout, would distinguish "local plumbing is installed" from "the VPN can actually transport traffic". ### Suggested regression cases 1. `hostName=A`, `vnext.address=B` -> endpoint routing / firewall logic must target B. 2. Changing metadata `hostName` alone must not change the actual XRay transport path. 3. REALITY candidate with missing `serverName`, `publicKey` or required `shortId` -> reject before repository replacement. 4. Candidate that cannot activate -> preserve the previous generation. 5. tun2socks reports local stack ready but XRay outbound is dead -> must not report a healthy `Connected` state. 6. Valid candidate passes activation/data-plane check -> atomically promote it. So although this issue was originally reported against `4.8.19.0`, the core client-side invariants behind it still do not appear to be enforced in current `dev`. I would treat the backend allocation quality and these client-side lifecycle/control-plane bugs as related but separate issues rather than choosing one as the explanation for all failures.
Sign in to join this conversation.
No labels
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
amnezia-vpn/amnezia-client#1287
No description provided.