amnezia-client/client/platforms/ios/PacketTunnelProvider+Xray.swift
2024-07-22 14:40:24 +03:00

187 lines
6.5 KiB
Swift

import Foundation
import NetworkExtension
import WireGuardKitGo
enum XrayErrors: Error {
case noXrayConfig
case xrayConfigIsWrong
case cantSaveXrayConfig
case cantParseListenAndPort
case cantSaveHevSocksConfig
}
extension Constants {
static let cachesDirectory: URL = {
if let cachesDirectoryURL = FileManager.default.urls(for: .cachesDirectory,
in: .userDomainMask).first {
return cachesDirectoryURL
} else {
fatalError("Unable to retrieve caches directory.")
}
}()
}
extension PacketTunnelProvider {
func startXray(completionHandler: @escaping (Error?) -> Void) {
// Xray configuration
guard let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol,
let providerConfiguration = protocolConfiguration.providerConfiguration,
let configData = providerConfiguration[Constants.xrayConfigKey] as? Data else {
xrayLog(.error, message: "Can't get xray configuration")
completionHandler(XrayErrors.noXrayConfig)
return
}
// Tunnel settings
let ipv6Enabled = false
let hideVPNIcon = false
let settings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: "254.1.1.1")
settings.mtu = 9000
settings.ipv4Settings = {
let settings = NEIPv4Settings(addresses: ["198.18.0.1"], subnetMasks: ["255.255.0.0"])
settings.includedRoutes = [NEIPv4Route.default()]
return settings
}()
settings.ipv6Settings = {
guard ipv6Enabled else {
return nil
}
let settings = NEIPv6Settings(addresses: ["fd6e:a81b:704f:1211::1"], networkPrefixLengths: [64])
settings.includedRoutes = [NEIPv6Route.default()]
if hideVPNIcon {
settings.excludedRoutes = [NEIPv6Route(destinationAddress: "::", networkPrefixLength: 128)]
}
return settings
}()
do {
let xrayConfig = try JSONDecoder().decode(XrayConfig.self,
from: configData)
var dnsArray = [String]()
if let dns1 = xrayConfig.dns1 {
dnsArray.append(dns1)
}
if let dns2 = xrayConfig.dns2 {
dnsArray.append(dns2)
}
settings.dnsSettings = !dnsArray.isEmpty
? NEDNSSettings(servers: dnsArray)
: NEDNSSettings(servers: ["1.1.1.1"])
let xrayConfigData = xrayConfig.config.data(using: .utf8)
guard let xrayConfigData else {
xrayLog(.error, message: "Can't encode config to data")
completionHandler(XrayErrors.xrayConfigIsWrong)
return
}
let jsonDict = try JSONSerialization.jsonObject(with: xrayConfigData,
options: []) as? [String: Any]
guard var jsonDict else {
xrayLog(.error, message: "Can't parse address and port for hevSocks")
completionHandler(XrayErrors.cantParseListenAndPort)
return
}
let port = 10808
let address = "::1"
if var inboundsArray = jsonDict["inbounds"] as? [[String: Any]], !inboundsArray.isEmpty {
inboundsArray[0]["port"] = port
inboundsArray[0]["listen"] = address
jsonDict["inbounds"] = inboundsArray
}
let updatedData = try JSONSerialization.data(withJSONObject: jsonDict, options: [])
setTunnelNetworkSettings(settings) { [weak self] error in
if let error {
completionHandler(error)
return
}
// Launch xray
self?.setupAndStartXray(configData: updatedData) { xrayError in
if let xrayError {
completionHandler(xrayError)
return
}
// Launch hevSocks
self?.setupAndRunTun2socks(configData: updatedData,
address: address,
port: port,
completionHandler: completionHandler)
}
}
} catch {
completionHandler(error)
return
}
}
func stopXray(completionHandler: () -> Void) {
Socks5Tunnel.quit()
LibXrayStopXray()
completionHandler()
}
private func setupAndStartXray(configData: Data,
completionHandler: @escaping (Error?) -> Void) {
let path = Constants.cachesDirectory.appendingPathComponent("config.json", isDirectory: false).path
guard FileManager.default.createFile(atPath: path, contents: configData) else {
xrayLog(.error, message: "Can't save xray configuration")
completionHandler(XrayErrors.cantSaveXrayConfig)
return
}
LibXrayRunXray(nil,
path,
Int64.max)
completionHandler(nil)
xrayLog(.info, message: "Xray started")
}
private func setupAndRunTun2socks(configData: Data,
address: String,
port: Int,
completionHandler: @escaping (Error?) -> Void) {
let config = """
tunnel:
mtu: 9000
socks5:
port: \(port)
address: \(address)
udp: 'udp'
misc:
task-stack-size: 20480
connect-timeout: 5000
read-write-timeout: 60000
log-file: stderr
log-level: error
limit-nofile: 65535
"""
let configurationFilePath = Constants.cachesDirectory.appendingPathComponent("config.yml", isDirectory: false).path
guard FileManager.default.createFile(atPath: configurationFilePath, contents: config.data(using: .utf8)!) else {
xrayLog(.info, message: "Cant save hevSocks configuration")
completionHandler(XrayErrors.cantSaveHevSocksConfig)
return
}
DispatchQueue.global().async {
xrayLog(.info, message: "Hev socks started")
completionHandler(nil)
Socks5Tunnel.run(withConfig: configurationFilePath)
}
}
}