Skip to content

add xray-core tun mode#81

Open
dfliaoyue wants to merge 59 commits into
lhear:mainfrom
dfliaoyue:main
Open

add xray-core tun mode#81
dfliaoyue wants to merge 59 commits into
lhear:mainfrom
dfliaoyue:main

Conversation

@dfliaoyue

@dfliaoyue dfliaoyue commented Apr 13, 2026

Copy link
Copy Markdown
Contributor

It is completed with github copilot agent, with several tests and changes. It seems to work well on my phone.
As I'm an amateur, although I have found some reference sources and provided to copilot, I cannot check whether the codes by AI are clean and efficient enough.

  1. Add a toggle in setting screen to choose hev tun or xray tun.
  2. Add xray tun support. To use xray tun, tun outbound must be included in the config. It is architecture for xray tun mode (generated by copilot) below:
    Kotlin: nativeSpawnXray(xrayPath, filesDir.path, vpnFd)

    │ JNI 调用 → libxray-exec.so (xray_exec.c)

    C 代码内部:
    pipe(stdin_pipe) # 用于传 config JSON
    pipe(stdout_pipe) # 用于读日志
    fork()

    ├─ 父进程 → 返回 {pid, stdout_read_fd, stdin_write_fd} 给 Kotlin

    └─ 子进程(独立进程!)
    dup2(stdin_pipe[0], STDIN)
    dup2(stdout_pipe[1], STDOUT/STDERR)
    dup2(vpn_fd, 4) ← 关键!绕过 FD_CLOEXEC
    close_extra_fds(keep=4)
    execve(xray_path, ...) # Xray core 仍是独立子进程
  3. Fix connectivity check and app/geosite/geoip update for xray tun mode, as in xray mode socks inbound should not be configurated. After trying different ways, only the chosen way is somewhat avaliable and secure: restart xray core with additional socks inbound (random local address, port, user and password), complete the task, and if new task is required, use the same socks inbound, and after all tasks complete, restart xray core with no additional inbounds.

dfliaoyue and others added 15 commits April 6, 2026 11:41
Revoke the default metered connections on Android Q and above.
Comment out signingConfig for debug build type.
…I, and strings

Agent-Logs-Url: https://github.com/dfliaoyue/SimpleXray/sessions/3bd066db-cd7e-417d-8520-033c51f57684

Co-authored-by: dfliaoyue <7167345+dfliaoyue@users.noreply.github.com>
Add Xray built-in TUN inbound support as alternative to hev-socks5-tunnel
@dfliaoyue dfliaoyue marked this pull request as ready for review April 13, 2026 14:22
dfliaoyue and others added 14 commits April 14, 2026 10:12
Copilot AI and others added 8 commits April 14, 2026 13:38
…ead of Any.pack()

Agent-Logs-Url: https://github.com/dfliaoyue/SimpleXray/sessions/b4ebc246-8d5f-4cdf-99d9-49aab1297b81

Co-authored-by: dfliaoyue <7167345+dfliaoyue@users.noreply.github.com>
… setup overhead

Agent-Logs-Url: https://github.com/dfliaoyue/SimpleXray/sessions/cacc38c1-3f37-4952-b93f-c3f808d614d1

Co-authored-by: dfliaoyue <7167345+dfliaoyue@users.noreply.github.com>
…CKS_LIFETIME=10s

Agent-Logs-Url: https://github.com/dfliaoyue/SimpleXray/sessions/2ccc3582-412a-495f-8a96-9ed899319e00

Co-authored-by: dfliaoyue <7167345+dfliaoyue@users.noreply.github.com>
…onnectivity

Fix connectivity test in HEV TUN (with credentials) and Xray TUN modes
@maxtom25

Copy link
Copy Markdown

HandlerService API seems like a bad idea (security hole / backdoor). API port randomization and 127.x.x.x address is valid protection for "harmless" StatsService, but not for HandlerService.
HandlerService is used to create temporary SOCKS inbound for connection test only. If connection test from the App is required it is much easier to create (add in user config) a dedicated SOCKS inbound routed to a specific test address for example.

@dfliaoyue

Copy link
Copy Markdown
Contributor Author

HandlerService API seems like a bad idea (security hole / backdoor). API port randomization and 127.x.x.x address is valid protection for "harmless" StatsService, but not for HandlerService.
HandlerService is used to create temporary SOCKS inbound for connection test only. If connection test from the App is required it is much easier to create (add in user config) a dedicated SOCKS inbound routed to a specific test address for example.

I don't think a persistent socks inbound is secured either, which exposes socks proxy and port. May you find a cleaner and better way? Or maybe just disable the connectivity check in xray tun mode?

@dfliaoyue

Copy link
Copy Markdown
Contributor Author

HandlerService API seems like a bad idea (security hole / backdoor). API port randomization and 127.x.x.x address is valid protection for "harmless" StatsService, but not for HandlerService.
HandlerService is used to create temporary SOCKS inbound for connection test only. If connection test from the App is required it is much easier to create (add in user config) a dedicated SOCKS inbound routed to a specific test address for example.

I found dat download and update check are broken because of the same reason. Sing-box android can download new version and rule-set files, its method may be applied here.

…h random credentials (#4)

* feat: bypass VPN tunnel for downloads via ProtectedSocketFactory

- TProxyService: expose static protectSocket() via WeakReference to
  running instance; register in onCreate(), deregister in onDestroy()
- Add ProtectedSocketFactory: delegates to default SocketFactory and
  calls TProxyService.protectSocket() on every new socket so OkHttp
  connections bypass the active TUN tunnel
- MainViewModel: add buildHttpClient() helper – uses ProtectedSocketFactory
  in Xray TUN mode, SOCKS proxy in hev-socks5-tunnel mode, plain direct
  otherwise; wire it into downloadRuleFile() and checkForUpdates()
- getVpnBuilder: unconditionally exclude the app itself from VPN routing

Agent-Logs-Url: https://github.com/dfliaoyue/SimpleXray/sessions/5b5b5a4c-4128-4764-9bf4-e867a0da2718

Co-authored-by: dfliaoyue <7167345+dfliaoyue@users.noreply.github.com>

* refactor: address code review feedback

- Clarify protectSocket() KDoc: false = service not running (not a failure)
- Add Preferences.isXrayTunActive computed property so the TUN-mode
  condition is defined once; use it in MainViewModel.buildHttpClient()
- Add inline comment in ProtectedSocketFactory.protect() explaining
  exception-safety

Agent-Logs-Url: https://github.com/dfliaoyue/SimpleXray/sessions/5b5b5a4c-4128-4764-9bf4-e867a0da2718

Co-authored-by: dfliaoyue <7167345+dfliaoyue@users.noreply.github.com>

* fix: use explicit SocketFactory.getDefault() in ProtectedSocketFactory

Agent-Logs-Url: https://github.com/dfliaoyue/SimpleXray/sessions/5b5b5a4c-4128-4764-9bf4-e867a0da2718

Co-authored-by: dfliaoyue <7167345+dfliaoyue@users.noreply.github.com>

* feat: add withTempSocksProxiedClient for TUN-mode downloads and update checks

- New withTempSocksProxiedClient<T> suspend helper: picks a random high
  port (32768-60999), generates 8-byte random hex username/password
  (independent of user-configured SOCKS credentials), creates a
  temporary Xray SOCKS5 inbound via HandlerService gRPC API, waits for
  the port to bind via a plain TCP connect probe, installs a dedicated
  per-session java.net.Authenticator (restores globalSocksAuthenticator
  in finally), builds an OkHttpClient with 30 s connect / 5 min read /
  30 s write timeouts, runs the caller's block, and removes the inbound
  in finally.

- downloadRuleFile(): in Xray TUN mode call withTempSocksProxiedClient
  instead of buildHttpClient/ProtectedSocketFactory; non-TUN path
  unchanged.

- checkForUpdates(): same TUN-mode branching.

- buildHttpClient(): remove dead ProtectedSocketFactory TUN branch;
  update KDoc.  Remove ProtectedSocketFactory import; add
  java.util.concurrent.TimeUnit import.

Agent-Logs-Url: https://github.com/dfliaoyue/SimpleXray/sessions/3c4be36a-a687-4a79-a2b8-e794a4678fb9

Co-authored-by: dfliaoyue <7167345+dfliaoyue@users.noreply.github.com>

* refactor: address code review feedback on withTempSocksProxiedClient

- Extract magic port numbers into named constants EPHEMERAL_PORT_RANGE_START
  (32768) and EPHEMERAL_PORT_RANGE_SIZE (60999 - 32768 + 1 = 28232);
  use them in both testConnectivity() and withTempSocksProxiedClient().
- In withTempSocksProxiedClient() finally block, restore
  globalSocksAuthenticator directly instead of a saved getDefault()
  snapshot, eliminating any risk of restoring a stale reference.

Agent-Logs-Url: https://github.com/dfliaoyue/SimpleXray/sessions/3c4be36a-a687-4a79-a2b8-e794a4678fb9

Co-authored-by: dfliaoyue <7167345+dfliaoyue@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: dfliaoyue <7167345+dfliaoyue@users.noreply.github.com>
@maxtom25

Copy link
Copy Markdown

I don't think a persistent socks inbound is secured either, which exposes socks proxy and port.
Sing-box android can download new version and rule-set files, its method may be applied here.

Mihomo and probably sing-box include themself into addAllowedApplication and use protected sockets to communicate outside. With a native xray-core running there is a very limited control and almost no way to implement it this way.
But, same 127.x.x.x:rand trick can be used for a special SOCKS inbound with connections limited to a specific set of domains / ports / etc. SOCKS listening on 127.x.x.x:rand with almost all connections routed to direct is way less of a hole or a fingerprint in comparison to Xray API "hidden" the same.
As far as I can see SimpleXray concept is: it is up to me to configure, no hidden / inserted sub configs, etc. and HandlerService is a very different way to go.

"inbounds": [ { "tag": "con_test", "listen": "127.1.2.3", "port": 12345, "protocol": "socks", "settings": {"auth": "noauth"} } ]
"routing": { "domainStrategy": "AsIs", "rules": [
{ "type": "field", "inboundTag": ["con_test"], "domain": ["full:www.gstatic.com"], "outboundTag": "proxy" },
{ "type": "field", "inboundTag": ["con_test"], "outboundTag": "direct" } ] }

@dfliaoyue

Copy link
Copy Markdown
Contributor Author

I don't think a persistent socks inbound is secured either, which exposes socks proxy and port.
Sing-box android can download new version and rule-set files, its method may be applied here.

Mihomo and probably sing-box include themself into addAllowedApplication and use protected sockets to communicate outside. With a native xray-core running there is a very limited control and almost no way to implement it this way. But, same 127.x.x.x:rand trick can be used for a special SOCKS inbound with connections limited to a specific set of domains / ports / etc. SOCKS listening on 127.x.x.x:rand with almost all connections routed to direct is way less of a hole or a fingerprint in comparison to Xray API "hidden" the same. As far as I can see SimpleXray concept is: it is up to me to configure, no hidden / inserted sub configs, etc. and HandlerService is a very different way to go.

"inbounds": [ { "tag": "con_test", "listen": "127.1.2.3", "port": 12345, "protocol": "socks", "settings": {"auth": "noauth"} } ] "routing": { "domainStrategy": "AsIs", "rules": [ { "type": "field", "inboundTag": ["con_test"], "domain": ["full:www.gstatic.com"], "outboundTag": "proxy" }, { "type": "field", "inboundTag": ["con_test"], "outboundTag": "direct" } ] }

I shall abandon the handleAPI method, but still cannot accept a persistent socks even with random local address, port and credentials. I prefer using temporary socks to perform those tasks by restart xray core with temorarily changed config, which may cause some problems only when the tasks are running.

@maxtom25

Copy link
Copy Markdown

I shall abandon the handleAPI method, but still cannot accept a persistent socks even with random local address, port and credentials. I prefer using temporary socks to perform those tasks by restart xray core with temorarily changed config, which may cause some problems only when the tasks are running.

May I suggest: check for user defined (in prefs? or may be in config with predefined name), if none restart with temp inbound? TCP only inbound with just a few domains allowed seems harmless to me, but other might prefer short lived temp inbound.

@dfliaoyue

Copy link
Copy Markdown
Contributor Author

I shall abandon the handleAPI method, but still cannot accept a persistent socks even with random local address, port and credentials. I prefer using temporary socks to perform those tasks by restart xray core with temorarily changed config, which may cause some problems only when the tasks are running.

May I suggest: check for user defined (in prefs? or may be in config with predefined name), if none restart with temp inbound? TCP only inbound with just a few domains allowed seems harmless to me, but other might prefer short lived temp inbound.

I have considered it but abandoned the thought. Because user-defined inbound may be attached to complex route rules. Well too many methods may increase the complexity, I will make current way available now.

Copilot AI and others added 5 commits April 15, 2026 16:39
* security: disable connectivity test in xray TUN mode, replace HandlerService with multi-file SOCKS injection

Agent-Logs-Url: https://github.com/dfliaoyue/SimpleXray/sessions/cc9a2adb-eef3-4bf5-9e67-0de097312ae1

Co-authored-by: dfliaoyue <7167345+dfliaoyue@users.noreply.github.com>

* address code review: improve tag prefix, add port validation, improve state detection

Agent-Logs-Url: https://github.com/dfliaoyue/SimpleXray/sessions/cc9a2adb-eef3-4bf5-9e67-0de097312ae1

Co-authored-by: dfliaoyue <7167345+dfliaoyue@users.noreply.github.com>

* refactor: xray multi-file, random 127.x.x.x, min lifetime, re-enable connectivity test, 10s timeout, remove dead code

Agent-Logs-Url: https://github.com/dfliaoyue/SimpleXray/sessions/764f2a62-bd35-49ca-91a7-38989479523e

Co-authored-by: dfliaoyue <7167345+dfliaoyue@users.noreply.github.com>

* fix: address code review - cleaner random loopback range, Log.e for inconsistent state, add comment

Agent-Logs-Url: https://github.com/dfliaoyue/SimpleXray/sessions/764f2a62-bd35-49ca-91a7-38989479523e

Co-authored-by: dfliaoyue <7167345+dfliaoyue@users.noreply.github.com>

* fix: revert to stdin config inject for security, switch temp SOCKS5 to noauth to fix connectivity test

Agent-Logs-Url: https://github.com/dfliaoyue/SimpleXray/sessions/3140f723-857d-43fe-b37a-862d2122c9b9

Co-authored-by: dfliaoyue <7167345+dfliaoyue@users.noreply.github.com>

* docs: fix comments to accurately describe noauth temp SOCKS and stdin config flow

Agent-Logs-Url: https://github.com/dfliaoyue/SimpleXray/sessions/3140f723-857d-43fe-b37a-862d2122c9b9

Co-authored-by: dfliaoyue <7167345+dfliaoyue@users.noreply.github.com>

* fix: restore SOCKS5 password auth, pass temp config in-memory (never disk)

Agent-Logs-Url: https://github.com/dfliaoyue/SimpleXray/sessions/ed455583-d235-4c41-b2e3-98b1a3d220d4

Co-authored-by: dfliaoyue <7167345+dfliaoyue@users.noreply.github.com>

* fix: restore disk file for temp SOCKS config (TProxyService runs in :native process), clean up comments

Agent-Logs-Url: https://github.com/dfliaoyue/SimpleXray/sessions/255bfd61-cf5e-4b97-be9b-bec8a4674d6e

Co-authored-by: dfliaoyue <7167345+dfliaoyue@users.noreply.github.com>

* refactor: pass temp SOCKS config via intent extra instead of file

Agent-Logs-Url: https://github.com/dfliaoyue/SimpleXray/sessions/52f0d1d0-7763-4a3b-b690-913bb0b1d0ec

Co-authored-by: dfliaoyue <7167345+dfliaoyue@users.noreply.github.com>

* fix: restore missing java.io.File import in MainViewModel

Agent-Logs-Url: https://github.com/dfliaoyue/SimpleXray/sessions/725c31a0-2f20-4553-ae89-eefc3b799366

Co-authored-by: dfliaoyue <7167345+dfliaoyue@users.noreply.github.com>

* fix: use separate connect/read timeouts in connectivity check

Agent-Logs-Url: https://github.com/dfliaoyue/SimpleXray/sessions/eef5de99-de2c-4491-9a2a-99e00330d14e

Co-authored-by: dfliaoyue <7167345+dfliaoyue@users.noreply.github.com>

* fix: restore raw Socket approach for connectivity test (matches original lhear/SimpleXray)

Agent-Logs-Url: https://github.com/dfliaoyue/SimpleXray/sessions/482cadaa-470b-4c42-91ec-951df6b524f0

Co-authored-by: dfliaoyue <7167345+dfliaoyue@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: dfliaoyue <7167345+dfliaoyue@users.noreply.github.com>
* style: remove over-commented docs inconsistent with project style

Agent-Logs-Url: https://github.com/dfliaoyue/SimpleXray/sessions/4b916543-3f50-4398-8e1d-20810010f132

Co-authored-by: dfliaoyue <7167345+dfliaoyue@users.noreply.github.com>

* style: remove all comments added by this PR to match lhear/simpleXray zero-comment style

Agent-Logs-Url: https://github.com/dfliaoyue/SimpleXray/sessions/65099cfc-cde4-4a6b-bded-aa73afbd43a1

Co-authored-by: dfliaoyue <7167345+dfliaoyue@users.noreply.github.com>

* style: remove/condense dfliaoyue-introduced comments to match upstream zero-comment style

Agent-Logs-Url: https://github.com/dfliaoyue/SimpleXray/sessions/cf4133b3-3f37-4ffe-b284-b7fc49cb9be9

Co-authored-by: dfliaoyue <7167345+dfliaoyue@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: dfliaoyue <7167345+dfliaoyue@users.noreply.github.com>
Add a note indicating the README is for test purposes only.
@maxtom25

Copy link
Copy Markdown

I have considered it but abandoned the thought. Because user-defined inbound may be attached to complex route rules. Well too many methods may increase the complexity, I will make current way available now.

App compiled from your repository fails to start Xray (in any mode) on my devices (Android 13 and 11, unrooted). Seems like exception in xray-exec (tricky to catch as it is native code). Releases in your repository are based on week old source (no TUN, etc.), thus cannot check precompiled (just in case it is build issue somehow).

@dfliaoyue

Copy link
Copy Markdown
Contributor Author

I have considered it but abandoned the thought. Because user-defined inbound may be attached to complex route rules. Well too many methods may increase the complexity, I will make current way available now.

App compiled from your repository fails to start Xray (in any mode) on my devices (Android 13 and 11, unrooted). Seems like exception in xray-exec (tricky to catch as it is native code). Releases in your repository are based on week old source (no TUN, etc.), thus cannot check precompiled (just in case it is build issue somehow).

Well I may misunderstand the release build process...Try build #89 or #93 in action in my fork. Maybe some codes don't support older android? Lhear has set java to 21, is the recent build by lhear (only hev tun) works?

@dfliaoyue

dfliaoyue commented Apr 16, 2026

Copy link
Copy Markdown
Contributor Author

I have considered it but abandoned the thought. Because user-defined inbound may be attached to complex route rules. Well too many methods may increase the complexity, I will make current way available now.

App compiled from your repository fails to start Xray (in any mode) on my devices (Android 13 and 11, unrooted). Seems like exception in xray-exec (tricky to catch as it is native code). Releases in your repository are based on week old source (no TUN, etc.), thus cannot check precompiled (just in case it is build issue somehow).

Try build #97 in my fork too. #89 and #93 should be the same, and #95 has some changes (All work on my phone android 16). Tell me whether #95 works, and if not, please describe the bug and present log if possible. If #97 works and you are using per app proxy (allowed app mode, rather than disallowed mode), perhaps it's because a change causes conflict, which was intended to fix connectivity check problem using socket method (failed) but became unnecessary for current temp socks method.

…nt deadlock on Android 11/13 (#7)

* fix: remove redundant Q check, use ContextCompat.registerReceiver() for compatibility

Agent-Logs-Url: https://github.com/dfliaoyue/SimpleXray/sessions/aac58c99-1708-44c8-8dfe-8dd7fd314d12

Co-authored-by: dfliaoyue <7167345+dfliaoyue@users.noreply.github.com>

* Revert "fix: remove redundant Q check, use ContextCompat.registerReceiver() for compatibility"

This reverts commit 76b85fd.

Co-authored-by: dfliaoyue <7167345+dfliaoyue@users.noreply.github.com>

* fix: make close_extra_fds fork-safe, remove malloc/opendir in child process

Agent-Logs-Url: https://github.com/dfliaoyue/SimpleXray/sessions/9d5b0ada-b5cd-4948-a4b1-629dc79185df

Co-authored-by: dfliaoyue <7167345+dfliaoyue@users.noreply.github.com>

* fix: revert unconditional self-exclusion in getVpnBuilder, remove dead protectSocket machinery

Agent-Logs-Url: https://github.com/dfliaoyue/SimpleXray/sessions/12a808d0-2ea6-48d7-be8e-5b8df399c0bf

Co-authored-by: dfliaoyue <7167345+dfliaoyue@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: dfliaoyue <7167345+dfliaoyue@users.noreply.github.com>
@maxtom25

Copy link
Copy Markdown

Try build #97 in my fork too. #89 and #93 should be the same, and #95 has some changes (All work on my phone android 16). Tell me whether #95 works, and if not, please describe the bug and present log if possible. If #97 works and you are using per app proxy (allowed app mode, rather than disallowed mode), perhaps it's because a change causes conflict, which was intended to fix connectivity check problem using socket method (failed) but became unnecessary for current temp socks method.

Synced up to commit 58a5492 (fix in xray_exec), starts fine in both modes. Running in TUN mode without any issues so far.

Copilot AI and others added 3 commits April 16, 2026 21:43
Agent-Logs-Url: https://github.com/dfliaoyue/SimpleXray/sessions/917c623d-7ff4-4613-b1de-8bcf20471ce7

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: dfliaoyue <7167345+dfliaoyue@users.noreply.github.com>
Agent-Logs-Url: https://github.com/dfliaoyue/SimpleXray/sessions/a12e69ec-af96-4f51-a27b-ad2602fcffc3

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: dfliaoyue <7167345+dfliaoyue@users.noreply.github.com>
@dfliaoyue

Copy link
Copy Markdown
Contributor Author

Try build #97 in my fork too. #89 and #93 should be the same, and #95 has some changes (All work on my phone android 16). Tell me whether #95 works, and if not, please describe the bug and present log if possible. If #97 works and you are using per app proxy (allowed app mode, rather than disallowed mode), perhaps it's because a change causes conflict, which was intended to fix connectivity check problem using socket method (failed) but became unnecessary for current temp socks method.

Synced up to commit 58a5492 (fix in xray_exec), starts fine in both modes. Running in TUN mode without any issues so far.

Actually there is a cleaner way: unix domain socket for API, and http proxy on unix domain socket for delay test and asset download.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants