Wanwire / FloxcoreXrayKit

XrayKit for Floxcore
14 stars 16 forks source link

Bug: Memory leak & slow work #4

Open blaxkdevios opened 5 months ago

blaxkdevios commented 5 months ago

I used Tun2SocksKit with the following code:

XrayCore.run(config: configFile, assets: geoPath) { error in
    if let error = error {
        print("VPN error=\(error)")
        return
    }

    print("success")
}
...
Socks5Tunnel.run(withConfig: configurationFilePath)

There are some problems:

  1. The completion handler of XrayCore.run is never called (not a big problem).
  2. In the console, I see the following errors (The internet is slow, but it works): failed to dial WebSocket > transport/internet/websocket: failed to dial to (ws://server.com/vless/): 404 Not Found > websocket: bad handshake transport/internet/websocket: failed to dial WebSocket > transport/internet/websocket: failed to dial to...
  3. After a few minutes, the application crashes due to exceeding the memory limit (50 MB).

Can someone help with problems 2 and 3? Does anyone have solutions?

tveerman commented 5 months ago
  1. The completion handler is called when XrayCore finishes running. When the exit code of XrayCore equals 0 the error will be nil. Else the error will be set to NEVPNError.configurationInvalid. However, XrayCore is running on a separate thread, so if you never see the completion handler being called then the app extension is possibly terminating too quickly.
  2. I suppose this means the configuration is invalid. XrayCore complains that it cannot create a WebSocket to the provided URL.
  3. I think your Xray configuration is causing XrayCore to recursively loop back to itself. At some point the amount of connections it has tried to open requires too much memory.

Make sure that the configuration you're feeding to XrayCore works on a standalone instance on a Mac or PC.

blaxkdevios commented 5 months ago

@tveerman Thanks for the answer!

If you don't mind, could you show an example configuration of XRay and Tun2SocksKit? (for vless)

blaxkdevios commented 5 months ago

@tveerman After hours of work I finally changed the config to Vless GRPC (with ws working badly) so now it works fine, no errors in logs, but crashing again when limiting memory.

Do you have any ideas why and how to fix it?

tveerman commented 5 months ago

As mentioned before I'd first try the configuration on a system where you can debug it more easily. That is, where you have access to the logs so you can see what it's doing. When you know the configuration should work, then feed the exact same configuration to XrayKit. XrayKit has no debugging facilities at the moment, so you have to capture the output (on stdout and stderr) generated by XrayCore yourself and store it somewhere.

One way to do that is to redirect stdout and stderr to a pipe and create a read handler that stores the output somewhere prior to launching XrayKit. For example, to a file that resides in a directory that's shared between the app extension and the main app:

private var relayFileHandle: FileHandle? = nil // global variable in your app extension

[...]

private func relayOutput() {
  var fd: [Int32] = [-1, -1]
  guard pipe(&fd) == 0 else {
    // handle pipe error
    return
  }
  // capture stdout
  guard dup2(fd[1], 1) != -1 else {
    // handle dup error
    return
  }
  // capture stderr
  guard dup2(fd[1], 2) != -1 else {
   // handle dup error
   return
  }

  relayFileHandle = FileHandle(fileDescriptor: fd[0], closeondealloc: true)
  relayFileHandle?.readabilityHandler = { fileHandle in
    if fileHandle.fileDescriptor >= 0 {
      let data = fileHandle.availableData
      // append data to shared file
    }
  }
}
blaxkdevios commented 5 months ago

@tveerman Thanks for the answer! By the way, is it possible to do a soft memory limit in go lang ? https://pkg.go.dev/runtime/debug#SetMemoryLimit

as i see in other apps that support xray there is an option to control memory limit

tveerman commented 5 months ago

I've dabbled with SetMemoryLimit and aggressive garbage collection, but they didn't seem to help at all. So I wonder how effective that setting is for the other apps you mention.

The only foolproof way to keep VPN working even in the face of an out-of-memory crash is to enable Connect on demand (set NETunnelProviderManager's isOnDemandEnabled to true).

blaxkdevios commented 5 months ago

i have tested the apps:

FoXray - It also drops the VPN connection if I overload the network with requests. I assume there’s a memory limit issue there as well. V2Box - There is a memory limit control in the settings, and it works well. The main screen provides statistics about memory usage.

I also thought about on-demand settings. Could you publish the changes with SetMemoryLimit?

Additionally, different libraries like libXRay (there are many such libraries) also have SetMemoryLimit (https://github.com/EbrahimTahernejad/libxray-apple).

tveerman commented 5 months ago

I doubt V2Box can effectively limit the memory usage.

SetMemoryLimit sets a soft limit, so the Go runtime is allowed to go over the limit (e.g., when there's no memory to garbage collect). Also, what should the Go runtime do when more memory is necessary to perform work? It either allocates the memory anyway or it kills the process with an out-of-memory error. In both cases you're not getting what you want.

Additionally, the runtime has to consider its own memory usage but also the memory usage of other threads that are not under its control. For example, the app extension's Swift runtime and the thread that converts IP packets to HTTP or SOCKS5 connections.

Finally, even if Go can effectively keep its memory usage under a certain limit, your app extension risks being killed due to excessive CPU usage when the Go garbage collector is constantly running because you're getting very close to the limit. iOS is not forgiving.

Considering the above I decided against using SetMemoryLimit. I tested SetMemoryLimit in a local branch that I no longer have. However, you can add your own by doing the following: 1) Make a git clone of this repository 2) cd into the git clone and run make 3) modify thirdparty/Xray-core-1.8.13/main/main.go to include a call to SetMemoryLimit in libxray_main or add a new exported function so you can call it on-the-fly. 4) rm -r thirdparty/Xray-core-1.8.13/.tmp 5) run make again. The resulting Swift Package is in build/XrayKit. Add this local Swift Package to your project instead of this remote github repo.