Skip to content

dima-u/SwiftyXrayKit

Repository files navigation

SwiftyXrayKit

A Swift package for running Xray-core inside a NEPacketTunnelProvider on iOS and macOS.

SPM Swift 6.0 Build

Overview

SwiftyXrayKit wraps libXray-apple — a custom Apple-platform build of XTLS/libXray using a patched Xray-core. The key patch makes Xray's TUN inbound work over a SOCK_STREAM socketpair, which is required inside the iOS Network Extension sandbox where SOCK_SEQPACKET is forbidden.

No SOCKS5 proxy or tun2socks layer needed — packets flow directly between NEPacketTunnelFlow and Xray's gVisor TUN stack.

What's included

XrayBridge Bridges NEPacketTunnelFlow ↔ Xray TUN inbound via a SOCK_STREAM socketpair
SwiftyXray Low-level libXray wrapper: run, stop, share-link conversion, port allocation
XrayTuningPreset Tuning parameters with .mobile, .desktop, and .default presets
GeoFilesLoader Downloads geoip.dat / geosite.dat with progress tracking

Features

  • ✅ Based on Xray-core v26.3.27
  • ✅ iOS 15+ and macOS 13+
  • ✅ Direct TUN inbound — no SOCKS5 or tun2socks
  • ✅ Share link input (VMess, VLESS, and others)
  • ✅ Tuning presets for mobile and desktop
  • ✅ Config transform hook — mutate the final JSON before run
  • ✅ Raw config file support — bypass kit patching entirely
  • ✅ Geo-site and geo-ip downloader

Installation

dependencies: [
    .package(url: "https://github.com/dima-u/SwiftyXrayKit.git", from: "1.1.0")
]

Usage

Minimal setup

import NetworkExtension
import SwiftyXrayKit

class PacketTunnelProvider: NEPacketTunnelProvider {

    var bridge: XrayBridge?

    override func startTunnel(options: [String: NSObject]?, completionHandler: @escaping (Error?) -> Void) {
        setTunnelNetworkSettings(makeNetworkSettings()) { [weak self] error in
            guard let self, error == nil else { completionHandler(error); return }
            do {
                try self.startXray()
                completionHandler(nil)
            } catch {
                completionHandler(error)
            }
        }
    }

    private func startXray() throws {
        let config = try String(contentsOf: configFileURL, encoding: .utf8)
        let bridge = XrayBridge(packetFlow: packetFlow)
        try bridge.start(
            config: .json(config),
            dataDir: geoDataDir,
            finalConfigPath: finalConfigURL
        )
        self.bridge = bridge
    }

    override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
        bridge?.stop()
        bridge = nil
        completionHandler()
    }
}

Share link input

Pass a VMess / VLESS / etc. share link directly — the kit converts it to JSON:

try bridge.start(config: .url("vless://..."), dataDir: geoDir, finalConfigPath: finalPath)

Tuning presets

.default resolves to .mobile on iOS and .desktop on macOS:

try bridge.start(config: .json(config), dataDir: geoDir, finalConfigPath: finalPath,
                 preset: .mobile)

Customise individual fields:

var preset = XrayTuningPreset.mobile
preset.memoryLimitMB = 40
preset.tcpMaxInFlight = 256
try bridge.start(config: .json(config), dataDir: geoDir, finalConfigPath: finalPath,
                 preset: preset)
Preset memoryLimitMB tcpBufMaxKB tcpMaxInFlight udpMaxConns idleTimeoutSec
.mobile 30 1024 512 256 120
.desktop 50 4096 8192 4096 300

Config transform

Intercept the kit-built config dictionary (TUN inbound already injected) and return a modified copy:

try bridge.start(config: .json(config), dataDir: geoDir, finalConfigPath: finalPath,
                 configTransform: { config in
                     var c = config
                     c["log"] = ["loglevel": "warning"]
                     c["dns"] = ["servers": ["1.1.1.1", "8.8.8.8"]]
                     return c
                 })

Raw config file

Skip all kit patching and run a fully pre-built Xray JSON:

try bridge.startWithRawConfig(rawConfigPath: myConfigURL, dataDir: geoDir)

Geo files

let loader = GeoFilesLoader()
try await loader.download(to: geoDataDir) { progress in
    print("Downloading: \(Int(progress * 100))%")
}

Bytes transferred

// Call periodically to get stats since last call
let stats = bridge.getAndClearStats()
print("\(stats.sent)\(stats.received)")

Backend

The xcframework is built from dima-u/libXray-apple with patches on top of dima-u/Xray-core-apple:

  • tun_darwin.goSOCK_STREAM socketpair support in the TUN inbound (iOS NE sandbox doesn't allow SOCK_SEQPACKET)
  • stack_gvisor.goSetTCPBufMaxKB, SetTCPMaxInFlight tuning; reduced TIME_WAIT (60s → 15s)
  • udp_fullcone.goSetMaxUDPConns with enforcement
  • condition_geoip.goClearGeoIPCache for clean restart
  • tls/config.goResetSessionCache for clean restart
  • distro/all/all.go — slimmed to: VLESS/VMess outbound, REALITY, WebSocket, xHTTP, splitHTTP

License

Apache 2.0. See LICENSE.

About

A Swift wrapper and ready to use PacketTunnelProvider primitives for Xray-core functionality, providing easy-to-use APIs for iOS and macOS applications.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages