A Swift package for running Xray-core inside a NEPacketTunnelProvider on iOS and macOS.
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.
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 |
- ✅ 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
dependencies: [
.package(url: "https://github.com/dima-u/SwiftyXrayKit.git", from: "1.1.0")
]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()
}
}Pass a VMess / VLESS / etc. share link directly — the kit converts it to JSON:
try bridge.start(config: .url("vless://..."), dataDir: geoDir, finalConfigPath: finalPath).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 |
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
})Skip all kit patching and run a fully pre-built Xray JSON:
try bridge.startWithRawConfig(rawConfigPath: myConfigURL, dataDir: geoDir)let loader = GeoFilesLoader()
try await loader.download(to: geoDataDir) { progress in
print("Downloading: \(Int(progress * 100))%")
}// Call periodically to get stats since last call
let stats = bridge.getAndClearStats()
print("↑ \(stats.sent) ↓ \(stats.received)")The xcframework is built from dima-u/libXray-apple with patches on top of dima-u/Xray-core-apple:
tun_darwin.go—SOCK_STREAMsocketpair support in the TUN inbound (iOS NE sandbox doesn't allowSOCK_SEQPACKET)stack_gvisor.go—SetTCPBufMaxKB,SetTCPMaxInFlighttuning; reduced TIME_WAIT (60s → 15s)udp_fullcone.go—SetMaxUDPConnswith enforcementcondition_geoip.go—ClearGeoIPCachefor clean restarttls/config.go—ResetSessionCachefor clean restartdistro/all/all.go— slimmed to: VLESS/VMess outbound, REALITY, WebSocket, xHTTP, splitHTTP
Apache 2.0. See LICENSE.