Skip to content

Commit

Permalink
iOS Xray support (#864)
Browse files Browse the repository at this point in the history
Xray for ios
  • Loading branch information
leninalive authored Jun 30, 2024
1 parent eeeb280 commit 760f935
Show file tree
Hide file tree
Showing 10 changed files with 285 additions and 3 deletions.
2 changes: 1 addition & 1 deletion client/3rd/amneziawg-apple
Submodule amneziawg-apple updated 135 files
1 change: 1 addition & 0 deletions client/containers/containers_defs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,7 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c)
case DockerContainer::WireGuard: return true;
case DockerContainer::OpenVpn: return true;
case DockerContainer::Awg: return true;
case DockerContainer::Xray: return true;
case DockerContainer::Cloak:
return true;
// case DockerContainer::ShadowSocks: return true;
Expand Down
6 changes: 6 additions & 0 deletions client/ios/networkextension/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,12 @@ set_target_properties("networkextension" PROPERTIES
find_library(FW_ASSETS_LIBRARY AssetsLibrary)
find_library(FW_MOBILE_CORE MobileCoreServices)
find_library(FW_UI_KIT UIKit)
find_library(FW_LIBRESOLV libresolv.9.tbd)

target_link_libraries(networkextension PRIVATE ${FW_ASSETS_LIBRARY})
target_link_libraries(networkextension PRIVATE ${FW_MOBILE_CORE})
target_link_libraries(networkextension PRIVATE ${FW_UI_KIT})
target_link_libraries(networkextension PRIVATE ${FW_LIBRESOLV})

target_compile_options(networkextension PRIVATE -DGROUP_ID=\"${BUILD_IOS_GROUP_IDENTIFIER}\")
target_compile_options(networkextension PRIVATE -DNETWORK_EXTENSION=1)
Expand All @@ -80,12 +82,14 @@ target_sources(networkextension PRIVATE
${WG_APPLE_SOURCE_DIR}/WireGuardKit/Array+ConcurrentMap.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/IPAddress+AddrInfo.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/PrivateKey.swift
${CLIENT_ROOT_DIR}/platforms/ios/HevSocksTunnel.swift
${CLIENT_ROOT_DIR}/platforms/ios/NELogController.swift
${CLIENT_ROOT_DIR}/platforms/ios/Log.swift
${CLIENT_ROOT_DIR}/platforms/ios/LogRecord.swift
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider.swift
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider+WireGuard.swift
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider+OpenVPN.swift
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider+Xray.swift
${CLIENT_ROOT_DIR}/platforms/ios/WGConfig.swift
${CLIENT_ROOT_DIR}/platforms/ios/iosglue.mm
)
Expand Down Expand Up @@ -114,3 +118,5 @@ target_include_directories(networkextension PRIVATE ${CLIENT_ROOT_DIR})
target_include_directories(networkextension PRIVATE ${CMAKE_CURRENT_BINARY_DIR})

target_link_libraries(networkextension PRIVATE ${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/wireguard/ios/arm64/libwg-go.a)

target_link_libraries(networkextension PRIVATE ${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/xray/HevSocks5Tunnel.xcframework)
73 changes: 73 additions & 0 deletions client/platforms/ios/HevSocksTunnel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import HevSocks5Tunnel

public enum Socks5Tunnel {

private static var tunnelFileDescriptor: Int32? {
var ctlInfo = ctl_info()
withUnsafeMutablePointer(to: &ctlInfo.ctl_name) {
$0.withMemoryRebound(to: CChar.self, capacity: MemoryLayout.size(ofValue: $0.pointee)) {
_ = strcpy($0, "com.apple.net.utun_control")
}
}
for fd: Int32 in 0...1024 {
var addr = sockaddr_ctl()
var ret: Int32 = -1
var len = socklen_t(MemoryLayout.size(ofValue: addr))
withUnsafeMutablePointer(to: &addr) {
$0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
ret = getpeername(fd, $0, &len)
}
}
if ret != 0 || addr.sc_family != AF_SYSTEM {
continue
}
if ctlInfo.ctl_id == 0 {
ret = ioctl(fd, CTLIOCGINFO, &ctlInfo)
if ret != 0 {
continue
}
}
if addr.sc_id == ctlInfo.ctl_id {
return fd
}
}
return nil
}

private static var interfaceName: String? {
guard let tunnelFileDescriptor = self.tunnelFileDescriptor else {
return nil
}
var buffer = [UInt8](repeating: 0, count: Int(IFNAMSIZ))
return buffer.withUnsafeMutableBufferPointer { mutableBufferPointer in
guard let baseAddress = mutableBufferPointer.baseAddress else {
return nil
}
var ifnameSize = socklen_t(IFNAMSIZ)
let result = getsockopt(
tunnelFileDescriptor,
2 /* SYSPROTO_CONTROL */,
2 /* UTUN_OPT_IFNAME */,
baseAddress,
&ifnameSize
)
if result == 0 {
return String(cString: baseAddress)
} else {
return nil
}
}
}

@discardableResult
public static func run(withConfig filePath: String) -> Int32 {
guard let fileDescriptor = self.tunnelFileDescriptor else {
fatalError("Get tunnel file descriptor failed.")
}
return hev_socks5_tunnel_main(filePath.cString(using: .utf8), fileDescriptor)
}

public static func quit() {
hev_socks5_tunnel_quit()
}
}
4 changes: 4 additions & 0 deletions client/platforms/ios/NELogController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ public func ovpnLog(_ type: OSLogType, title: String = "", message: String) {
neLog(type, title: "OVPN: \(title)", message: message)
}

public func xrayLog(_ type: OSLogType, title: String = "", message: String) {
neLog(type, title: "XRAY: \(title)", message: message)
}

public func neLog(_ type: OSLogType, title: String = "", message: String) {
Log.log(type, title: "NE: \(title)", message: message)
}
159 changes: 159 additions & 0 deletions client/platforms/ios/PacketTunnelProvider+Xray.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import Foundation
import NetworkExtension
import WireGuardKitGo

enum XrayErrors: Error {
case noXrayConfig
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 xrayConfigData = providerConfiguration[Constants.xrayConfigKey] as? Data else {
xrayLog(.error, message: "Can't get xray configuration")
completionHandler(XrayErrors.noXrayConfig)
return
}

// Tunnel settings
let ipv6Enabled = true
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
}()

let dns = ["8.8.4.4","1.1.1.1"]
settings.dnsSettings = NEDNSSettings(servers: dns)

setTunnelNetworkSettings(settings) { [weak self] error in
if let error {
completionHandler(error)
return
}

// Launch xray
self?.setupAndStartXray(configData: xrayConfigData) { xrayError in
if let xrayError {
completionHandler(xrayError)
return
}

// Launch hevSocks
self?.setupAndRunTun2socks(configData: xrayConfigData,
completionHandler: completionHandler)
}
}
}

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,
completionHandler: @escaping (Error?) -> Void) {
var port = 10808
var address = "::1"

let jsonDict = try? JSONSerialization.jsonObject(with: configData, options: []) as? [String: Any]

guard let jsonDict else {
xrayLog(.error, message: "Can't parse address and port for hevSocks")
completionHandler(XrayErrors.cantParseListenAndPort)
return
}

// Xray listen and port should be the same as port and address in hevSocks
if let inbounds = jsonDict["inbounds"] as? [[String: Any]], let inbound = inbounds.first {
if let listen = inbound["listen"] as? String {
address = listen
address.removeAll { $0 == "[" || $0 == "]" }
}
if let portFromConfig = inbound["port"] as? Int {
port = portFromConfig
}
}

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)
}
}
}
13 changes: 12 additions & 1 deletion client/platforms/ios/PacketTunnelProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@ import Darwin
import OpenVPNAdapter

enum TunnelProtoType: String {
case wireguard, openvpn
case wireguard, openvpn, xray

}

struct Constants {
static let kDefaultPathKey = "defaultPath"
static let processQueueName = "org.amnezia.process-packets"
static let kActivationAttemptId = "activationAttemptId"
static let ovpnConfigKey = "ovpn"
static let xrayConfigKey = "xray"
static let wireGuardConfigKey = "wireguard"
static let loggerTag = "NET"

Expand Down Expand Up @@ -91,6 +93,8 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
protoType = .openvpn
} else if (providerConfiguration?[Constants.wireGuardConfigKey] as? Data) != nil {
protoType = .wireguard
} else if (providerConfiguration?[Constants.xrayConfigKey] as? Data) != nil {
protoType = .xray
}
}

Expand All @@ -107,6 +111,9 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
completionHandler: completionHandler)
case .openvpn:
startOpenVPN(completionHandler: completionHandler)
case .xray:
startXray(completionHandler: completionHandler)

}
}

Expand All @@ -124,6 +131,8 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
case .openvpn:
stopOpenVPN(with: reason,
completionHandler: completionHandler)
case .xray:
stopXray(completionHandler: completionHandler)
}
}

Expand All @@ -138,6 +147,8 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
handleWireguardStatusMessage(messageData, completionHandler: completionHandler)
case .openvpn:
handleOpenVPNStatusMessage(messageData, completionHandler: completionHandler)
case .xray:
break;
}
}

Expand Down
2 changes: 2 additions & 0 deletions client/platforms/ios/ios_controller.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,11 @@ protected slots:
bool setupCloak();
bool setupWireGuard();
bool setupAwg();
bool setupXray();

bool startOpenVPN(const QString &config);
bool startWireGuard(const QString &jsonConfig);
bool startXray(const QString &jsonConfig);

void startTunnel();

Expand Down
Loading

0 comments on commit 760f935

Please sign in to comment.