187 lines
6.5 KiB
Swift
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)
|
|
}
|
|
}
|
|
}
|