[GH-ISSUE #2377] Windows Xray/REALITY breaks with domain endpoint while the same config works with IP and on Android #1051

Open
opened 2026-10-13 20:33:33 +00:00 by bsakaguchi · 0 comments
Owner

Originally created by @piterskikh on GitHub (Mar 20, 2026).
Original GitHub issue: https://github.com/amnezia-vpn/amnezia-client/issues/2377

Summary

On Windows desktop, an imported vless:// REALITY profile fails when the endpoint is a domain, but the same profile works when only the endpoint is changed to an IP.

The same domain-based profile also works on Android.

This points to a Windows desktop client bug, not a server-side REALITY issue.

Affected profile shape

Working on Android, failing on Windows:

vless://246471ee-941e-4e58-a0dd-b52a6adf9810@se.pit.com:443?encryption=none&flow=xtls-rprx-vision&security=reality&sni=www.teliacompany.com&fp=chrome&pbk=UOB8iyIoHQJazBTWXNaqX8pY7qHztqAI1thtAVj8VC4&sid=e250cfb41c45624b&type=tcp#amnezia-telia-vision-01

Working on Windows and Android:

vless://246471ee-941e-4e58-a0dd-b52a6adf9810@77.121.133.188:443?encryption=none&flow=xtls-rprx-vision&security=reality&sni=www.teliacompany.com&fp=chrome&pbk=UOB8iyIoHQJazBTWXNaqX8pY7qHztqAI1thtAVj8VC4&sid=e250cfb41c45624b&type=tcp#amnezia-telia-vision-01

The only difference is endpoint host = domain vs endpoint host = IP.

Observed behavior

  • Windows:
    • profile imports
    • connection starts
    • internet is effectively broken / no usable traffic
  • Android:
    • same domain-based profile works

Expected behavior

If the profile works with IP and the same REALITY tuple (sni, pbk, sid, flow) also works on Android, Windows should also work with the domain endpoint.

Why this does not look server-side

  • The same REALITY server works with the IP endpoint.
  • The same REALITY server works with the same domain endpoint from Android.
  • A pure Xray smoke-test against the same server works with both IP and domain endpoints.

Probable root cause

Windows desktop appears to keep a mismatch between:

  • the transport endpoint used by embedded Xray
  • the IP used for route / firewall / peer-traffic bypass rules

When importing a vless:// link, the deserializer keeps the endpoint hostname as-is in outbounds[].settings.vnext[].address:

But the Windows Xray protocol path resolves hostName separately and uses the resolved IP for routing-related rules:

That produces:

  • Xray outbound dialing domain:443
  • peer traffic / firewall bypass using resolved IP

This mismatch is consistent with the symptom that IP works while domain does not.

Relevant code paths

  • Imported VLESS keeps domain in outbound address:

    • client/core/serialization/vless.cpp
    • QJsonIO::SetValue(outbound, host, { "settings", "vnext", 0, "address" });
  • Windows resolves the host separately:

    • client/protocols/xrayprotocol.cpp
    • m_remoteAddress = NetworkUtilities::getIPAddress(m_rawConfig.value(amnezia::config_key::hostName).toString());
  • Windows uses resolved IP in routing / peer-traffic setup:

    • config.insert("vpnServer", m_remoteAddress);
  • Android already normalizes the Xray config differently:

    • client/android/xray/src/main/kotlin/Xray.kt
    • It replaces hostName with a resolved IP in the Xray JSON before start, while keeping REALITY serverName.

Candidate fix

Before calling xrayStart() on desktop, normalize outbounds[].settings.vnext[].address from the imported hostname to the same resolved IP already stored in m_remoteAddress.

This keeps:

  • REALITY serverName / decoy domain untouched
  • Windows routing / firewall / peer-traffic rules aligned with the actual Xray transport endpoint

Minimal patch idea

Add a small normalization step in client/protocols/xrayprotocol.cpp after loading m_xrayConfig:

normalizeXrayOutboundAddress(
    m_xrayConfig,
    m_rawConfig.value(amnezia::config_key::hostName).toString(),
    m_remoteAddress);

where normalizeXrayOutboundAddress() rewrites matching outbounds[].settings.vnext[].address.

Workarounds

  • Use IP in the endpoint instead of a domain on Windows.
  • Keep the REALITY sni / serverName domain unchanged.

Notes

This issue seems separate from Android split-tunneling problems. In this case, Android works with the same domain-based REALITY profile, while Windows does not.

Originally created by @piterskikh on GitHub (Mar 20, 2026). Original GitHub issue: https://github.com/amnezia-vpn/amnezia-client/issues/2377 ## Summary On Windows desktop, an imported `vless://` REALITY profile fails when the endpoint is a domain, but the same profile works when only the endpoint is changed to an IP. The same domain-based profile also works on Android. This points to a Windows desktop client bug, not a server-side REALITY issue. ## Affected profile shape Working on Android, failing on Windows: ```text vless://246471ee-941e-4e58-a0dd-b52a6adf9810@se.pit.com:443?encryption=none&flow=xtls-rprx-vision&security=reality&sni=www.teliacompany.com&fp=chrome&pbk=UOB8iyIoHQJazBTWXNaqX8pY7qHztqAI1thtAVj8VC4&sid=e250cfb41c45624b&type=tcp#amnezia-telia-vision-01 ``` Working on Windows and Android: ```text vless://246471ee-941e-4e58-a0dd-b52a6adf9810@77.121.133.188:443?encryption=none&flow=xtls-rprx-vision&security=reality&sni=www.teliacompany.com&fp=chrome&pbk=UOB8iyIoHQJazBTWXNaqX8pY7qHztqAI1thtAVj8VC4&sid=e250cfb41c45624b&type=tcp#amnezia-telia-vision-01 ``` The only difference is `endpoint host = domain` vs `endpoint host = IP`. ## Observed behavior - Windows: - profile imports - connection starts - internet is effectively broken / no usable traffic - Android: - same domain-based profile works ## Expected behavior If the profile works with `IP` and the same REALITY tuple (`sni`, `pbk`, `sid`, `flow`) also works on Android, Windows should also work with the domain endpoint. ## Why this does not look server-side - The same REALITY server works with the IP endpoint. - The same REALITY server works with the same domain endpoint from Android. - A pure Xray smoke-test against the same server works with both IP and domain endpoints. ## Probable root cause Windows desktop appears to keep a mismatch between: - the transport endpoint used by embedded Xray - the IP used for route / firewall / peer-traffic bypass rules When importing a `vless://` link, the deserializer keeps the endpoint hostname as-is in `outbounds[].settings.vnext[].address`: - [[client/core/serialization/vless.cpp](typora://app/client/core/serialization/vless.cpp)](../client/core/serialization/vless.cpp) But the Windows Xray protocol path resolves `hostName` separately and uses the resolved IP for routing-related rules: - [[client/protocols/xrayprotocol.cpp](typora://app/client/protocols/xrayprotocol.cpp)](../client/protocols/xrayprotocol.cpp) That produces: - Xray outbound dialing `domain:443` - peer traffic / firewall bypass using `resolved IP` This mismatch is consistent with the symptom that `IP` works while `domain` does not. ## Relevant code paths - Imported VLESS keeps domain in outbound address: - `client/core/serialization/vless.cpp` - `QJsonIO::SetValue(outbound, host, { "settings", "vnext", 0, "address" });` - Windows resolves the host separately: - `client/protocols/xrayprotocol.cpp` - `m_remoteAddress = NetworkUtilities::getIPAddress(m_rawConfig.value(amnezia::config_key::hostName).toString());` - Windows uses resolved IP in routing / peer-traffic setup: - `config.insert("vpnServer", m_remoteAddress);` - Android already normalizes the Xray config differently: - `client/android/xray/src/main/kotlin/Xray.kt` - It replaces `hostName` with a resolved IP in the Xray JSON before start, while keeping REALITY `serverName`. ## Candidate fix Before calling `xrayStart()` on desktop, normalize `outbounds[].settings.vnext[].address` from the imported hostname to the same resolved IP already stored in `m_remoteAddress`. This keeps: - REALITY `serverName` / decoy domain untouched - Windows routing / firewall / peer-traffic rules aligned with the actual Xray transport endpoint ## Minimal patch idea Add a small normalization step in `client/protocols/xrayprotocol.cpp` after loading `m_xrayConfig`: ```cpp normalizeXrayOutboundAddress( m_xrayConfig, m_rawConfig.value(amnezia::config_key::hostName).toString(), m_remoteAddress); ``` where `normalizeXrayOutboundAddress()` rewrites matching `outbounds[].settings.vnext[].address`. ## Workarounds - Use IP in the endpoint instead of a domain on Windows. - Keep the REALITY `sni` / `serverName` domain unchanged. ## Notes This issue seems separate from Android split-tunneling problems. In this case, Android works with the same domain-based REALITY profile, while Windows does not.
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#1051
No description provided.