Underlying VPN Logic

This commit is contained in:
GFWFighter
2023-10-17 03:15:15 +03:30
parent 7b2129ad6d
commit fe7cdc276e
12 changed files with 869 additions and 28 deletions

View File

@@ -0,0 +1,43 @@
//
// Extension+RunBlocking.swift
// SingBoxPacketTunnel
//
// Created by GFWFighter on 7/25/1402 AP.
//
import Foundation
import Libcore
import NetworkExtension
func runBlocking<T>(_ block: @escaping () async -> T) -> T {
let semaphore = DispatchSemaphore(value: 0)
let box = resultBox<T>()
Task.detached {
let value = await block()
box.result0 = value
semaphore.signal()
}
semaphore.wait()
return box.result0
}
func runBlocking<T>(_ tBlock: @escaping () async throws -> T) throws -> T {
let semaphore = DispatchSemaphore(value: 0)
let box = resultBox<T>()
Task.detached {
do {
let value = try await tBlock()
box.result = .success(value)
} catch {
box.result = .failure(error)
}
semaphore.signal()
}
semaphore.wait()
return try box.result.get()
}
private class resultBox<T> {
var result: Result<T, Error>!
var result0: T!
}

View File

@@ -0,0 +1,224 @@
//
// ExtensionPlatformInterface.swift
// SingBoxPacketTunnel
//
// Created by GFWFighter on 7/25/1402 AP.
//
import Foundation
import Libcore
import NetworkExtension
public class ExtensionPlatformInterface: NSObject, LibboxPlatformInterfaceProtocol, LibboxCommandServerHandlerProtocol {
private let tunnel: ExtensionProvider
private var networkSettings: NEPacketTunnelNetworkSettings?
init(_ tunnel: ExtensionProvider) {
self.tunnel = tunnel
}
public func openTun(_ options: LibboxTunOptionsProtocol?, ret0_: UnsafeMutablePointer<Int32>?) throws {
try runBlocking { [self] in
try await openTun0(options, ret0_)
}
}
private func openTun0(_ options: LibboxTunOptionsProtocol?, _ ret0_: UnsafeMutablePointer<Int32>?) async throws {
guard let options else {
throw NSError(domain: "nil options", code: 0)
}
guard let ret0_ else {
throw NSError(domain: "nil return pointer", code: 0)
}
let settings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: "127.0.0.1")
if options.getAutoRoute() {
settings.mtu = NSNumber(value: options.getMTU())
var error: NSError?
let dnsServer = options.getDNSServerAddress(&error)
if let error {
throw error
}
settings.dnsSettings = NEDNSSettings(servers: [dnsServer])
var ipv4Address: [String] = []
var ipv4Mask: [String] = []
let ipv4AddressIterator = options.getInet4Address()!
while ipv4AddressIterator.hasNext() {
let ipv4Prefix = ipv4AddressIterator.next()!
ipv4Address.append(ipv4Prefix.address)
ipv4Mask.append(ipv4Prefix.mask())
}
let ipv4Settings = NEIPv4Settings(addresses: ipv4Address, subnetMasks: ipv4Mask)
var ipv4Routes: [NEIPv4Route] = []
let inet4RouteAddressIterator = options.getInet4RouteAddress()!
if inet4RouteAddressIterator.hasNext() {
while inet4RouteAddressIterator.hasNext() {
let ipv4RoutePrefix = inet4RouteAddressIterator.next()!
ipv4Routes.append(NEIPv4Route(destinationAddress: ipv4RoutePrefix.address, subnetMask: ipv4RoutePrefix.mask()))
}
} else {
ipv4Routes.append(NEIPv4Route.default())
}
for (index, address) in ipv4Address.enumerated() {
ipv4Routes.append(NEIPv4Route(destinationAddress: address, subnetMask: ipv4Mask[index]))
}
ipv4Settings.includedRoutes = ipv4Routes
settings.ipv4Settings = ipv4Settings
var ipv6Address: [String] = []
var ipv6Prefixes: [NSNumber] = []
let ipv6AddressIterator = options.getInet6Address()!
while ipv6AddressIterator.hasNext() {
let ipv6Prefix = ipv6AddressIterator.next()!
ipv6Address.append(ipv6Prefix.address)
ipv6Prefixes.append(NSNumber(value: ipv6Prefix.prefix))
}
let ipv6Settings = NEIPv6Settings(addresses: ipv6Address, networkPrefixLengths: ipv6Prefixes)
var ipv6Routes: [NEIPv6Route] = []
let inet6RouteAddressIterator = options.getInet6RouteAddress()!
if inet6RouteAddressIterator.hasNext() {
while inet6RouteAddressIterator.hasNext() {
let ipv6RoutePrefix = inet4RouteAddressIterator.next()!
ipv6Routes.append(NEIPv6Route(destinationAddress: ipv6RoutePrefix.description, networkPrefixLength: NSNumber(value: ipv6RoutePrefix.prefix)))
}
} else {
ipv6Routes.append(NEIPv6Route.default())
}
ipv6Settings.includedRoutes = ipv6Routes
settings.ipv6Settings = ipv6Settings
}
if options.isHTTPProxyEnabled() {
let proxySettings = NEProxySettings()
let proxyServer = NEProxyServer(address: options.getHTTPProxyServer(), port: Int(options.getHTTPProxyServerPort()))
proxySettings.httpServer = proxyServer
proxySettings.httpsServer = proxyServer
settings.proxySettings = proxySettings
}
networkSettings = settings
try await tunnel.setTunnelNetworkSettings(settings)
if let tunFd = tunnel.packetFlow.value(forKeyPath: "socket.fileDescriptor") as? Int32 {
ret0_.pointee = tunFd
return
}
let tunFdFromLoop = LibboxGetTunnelFileDescriptor()
if tunFdFromLoop != -1 {
ret0_.pointee = tunFdFromLoop
} else {
throw NSError(domain: "missing file descriptor", code: 0)
}
}
public func usePlatformAutoDetectControl() -> Bool {
true
}
public func autoDetectControl(_: Int32) throws {}
public func findConnectionOwner(_: Int32, sourceAddress _: String?, sourcePort _: Int32, destinationAddress _: String?, destinationPort _: Int32, ret0_ _: UnsafeMutablePointer<Int32>?) throws {
throw NSError(domain: "not implemented", code: 0)
}
public func packageName(byUid _: Int32, error _: NSErrorPointer) -> String {
""
}
public func uid(byPackageName _: String?, ret0_ _: UnsafeMutablePointer<Int32>?) throws {
throw NSError(domain: "not implemented", code: 0)
}
public func useProcFS() -> Bool {
false
}
public func writeLog(_ message: String?) {
guard let message else {
return
}
tunnel.writeMessage(message)
}
public func usePlatformDefaultInterfaceMonitor() -> Bool {
false
}
public func startDefaultInterfaceMonitor(_: LibboxInterfaceUpdateListenerProtocol?) throws {}
public func closeDefaultInterfaceMonitor(_: LibboxInterfaceUpdateListenerProtocol?) throws {}
public func useGetter() -> Bool {
false
}
public func getInterfaces() throws -> LibboxNetworkInterfaceIteratorProtocol {
throw NSError(domain: "not implemented", code: 0)
}
public func underNetworkExtension() -> Bool {
true
}
public func clearDNSCache() {
guard let networkSettings else {
return
}
tunnel.reasserting = true
tunnel.setTunnelNetworkSettings(nil) { _ in
}
tunnel.setTunnelNetworkSettings(networkSettings) { _ in
}
tunnel.reasserting = false
}
public func serviceReload() throws {
runBlocking { [self] in
await tunnel.reloadService()
}
}
public func getSystemProxyStatus() -> LibboxSystemProxyStatus? {
let status = LibboxSystemProxyStatus()
guard let networkSettings else {
return status
}
guard let proxySettings = networkSettings.proxySettings else {
return status
}
if proxySettings.httpServer == nil {
return status
}
status.available = true
status.enabled = proxySettings.httpEnabled
return status
}
public func setSystemProxyEnabled(_ isEnabled: Bool) throws {
guard let networkSettings else {
return
}
guard let proxySettings = networkSettings.proxySettings else {
return
}
if proxySettings.httpServer == nil {
return
}
if proxySettings.httpEnabled == isEnabled {
return
}
proxySettings.httpEnabled = isEnabled
proxySettings.httpsEnabled = isEnabled
networkSettings.proxySettings = proxySettings
try runBlocking {
try await self.tunnel.setTunnelNetworkSettings(networkSettings)
}
}
func reset() {
networkSettings = nil
}
}

View File

@@ -0,0 +1,160 @@
//
// ExtensionProvider.swift
// SingBoxPacketTunnel
//
// Created by GFWFighter on 7/25/1402 AP.
//
import Foundation
import Libcore
import NetworkExtension
open class ExtensionProvider: NEPacketTunnelProvider {
public static let errorFile = FilePath.workingDirectory.appendingPathComponent("network_extension_error")
private var commandServer: LibboxCommandServer!
private var boxService: LibboxBoxService!
private var systemProxyAvailable = false
private var systemProxyEnabled = false
private var platformInterface: ExtensionPlatformInterface!
private var config: String!
override open func startTunnel(options: [String: NSObject]?) async throws {
let disableMemoryLimit = (options?["DisableMemoryLimit"] as? NSString as? String ?? "NO") == "YES"
guard let config = options?["Config"] as? NSString as? String else {
writeFatalError("(packet-tunnel) error: config not provided")
return
}
guard let config = SingBox.setupConfig(config: config) else {
writeFatalError("(packet-tunnel) error: config is invalid")
return
}
self.config = config
try? FileManager.default.removeItem(at: ExtensionProvider.errorFile)
do {
try FileManager.default.createDirectory(at: FilePath.workingDirectory, withIntermediateDirectories: true)
} catch {
writeFatalError("(packet-tunnel) error: create working directory: \(error.localizedDescription)")
return
}
LibboxSetup(FilePath.sharedDirectory.relativePath, FilePath.workingDirectory.relativePath, FilePath.cacheDirectory.relativePath, false)
var error: NSError?
LibboxRedirectStderr(FilePath.cacheDirectory.appendingPathComponent("stderr.log").relativePath, &error)
if let error {
writeError("(packet-tunnel) redirect stderr error: \(error.localizedDescription)")
}
LibboxSetMemoryLimit(!disableMemoryLimit)
if platformInterface == nil {
platformInterface = ExtensionPlatformInterface(self)
}
commandServer = LibboxNewCommandServer(platformInterface, Int32(30))
do {
try commandServer.start()
} catch {
writeFatalError("(packet-tunnel): log server start error: \(error.localizedDescription)")
return
}
writeMessage("(packet-tunnel) log server started")
await startService()
}
func writeMessage(_ message: String) {
if let commandServer {
commandServer.writeMessage(message)
} else {
NSLog(message)
}
}
func writeError(_ message: String) {
writeMessage(message)
try? message.write(to: ExtensionProvider.errorFile, atomically: true, encoding: .utf8)
}
public func writeFatalError(_ message: String) {
#if DEBUG
NSLog(message)
#endif
writeError(message)
cancelTunnelWithError(NSError(domain: message, code: 0))
}
private func startService() async {
let configContent = config
var error: NSError?
let service = LibboxNewService(configContent, platformInterface, &error)
if let error {
writeError("(packet-tunnel) error: create service: \(error.localizedDescription)")
return
}
guard let service else {
return
}
do {
try service.start()
} catch {
writeError("(packet-tunnel) error: start service: \(error.localizedDescription)")
return
}
boxService = service
commandServer.setService(service)
}
private func stopService() {
if let service = boxService {
do {
try service.close()
} catch {
writeError("(packet-tunnel) error: stop service: \(error.localizedDescription)")
}
boxService = nil
commandServer.setService(nil)
}
if let platformInterface {
platformInterface.reset()
}
}
func reloadService() async {
writeMessage("(packet-tunnel) reloading service")
reasserting = true
defer {
reasserting = false
}
stopService()
await startService()
}
override open func stopTunnel(with reason: NEProviderStopReason) async {
writeMessage("(packet-tunnel) stopping, reason: \(reason)")
stopService()
if let server = commandServer {
try? await Task.sleep(nanoseconds: 100 * NSEC_PER_MSEC)
try? server.close()
commandServer = nil
}
}
override open func handleAppMessage(_ messageData: Data) async -> Data? {
messageData
}
override open func sleep() async {
if let boxService {
boxService.sleep()
}
}
override open func wake() {
if let boxService {
boxService.wake()
}
}
}

View File

@@ -0,0 +1,59 @@
//
// SingBox.swift
// SingBoxPacketTunnel
//
// Created by GFWFighter on 7/25/1402 AP.
//
import Foundation
class SingBox {
static func setupConfig(config: String, mtu: Int = 9000) -> String? {
guard
let config = config.data(using: .utf8),
var json = try? JSONSerialization
.jsonObject(
with: config,
options: [.mutableLeaves, .mutableContainers]
) as? [String:Any]
else {
return nil
}
json["log"] = [
"disabled": false,
"level": "info",
"output": "log",
"timestamp": true
] as [String:Any]
json["experimental"] = [
"clash_api": [
"external_controller": "127.0.0.1:10864"
]
]
json["inbounds"] = [
[
"type": "tun",
"inet4_address": "172.19.0.1/30",
"auto_route": true,
"mtu": mtu,
"sniff": true
] as [String:Any]
]
var routing = (json["route"] as? [String:Any]) ?? [
"rules": [Any](),
"auto_detect_interface": true,
"final": (json["inbounds"] as? [[String:Any]])?.first?["tag"] ?? "proxy"
]
routing["geoip"] = [
"path": FilePath.assetsDirectory.appendingPathComponent("geoip.db"),
]
routing["geosite"] = [
"path": FilePath.assetsDirectory.appendingPathComponent("geosite.db"),
]
json["route"] = routing
guard let data = try? JSONSerialization.data(withJSONObject: json) else {
return nil
}
return String(data: data, encoding: .utf8)
}
}