Files
Archive/sing-box/clients/apple/Library/Network/ExtensionPlatformInterface.swift
2025-01-27 19:35:24 +01:00

427 lines
17 KiB
Swift

import Foundation
import Libbox
import NetworkExtension
import UserNotifications
#if canImport(CoreWLAN)
import CoreWLAN
#endif
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 autoRouteUseSubRangesByDefault = await SharedPreferences.autoRouteUseSubRangesByDefault.get()
let excludeAPNs = await SharedPreferences.excludeAPNsRoute.get()
let settings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: "127.0.0.1")
if options.getAutoRoute() {
settings.mtu = NSNumber(value: options.getMTU())
let dnsServer = try options.getDNSServerAddress()
let dnsSettings = NEDNSSettings(servers: [dnsServer.value])
dnsSettings.matchDomains = [""]
dnsSettings.matchDomainsNoSearch = true
settings.dnsSettings = dnsSettings
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] = []
var ipv4ExcludeRoutes: [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 if autoRouteUseSubRangesByDefault {
ipv4Routes.append(NEIPv4Route(destinationAddress: "1.0.0.0", subnetMask: "255.0.0.0"))
ipv4Routes.append(NEIPv4Route(destinationAddress: "2.0.0.0", subnetMask: "254.0.0.0"))
ipv4Routes.append(NEIPv4Route(destinationAddress: "4.0.0.0", subnetMask: "252.0.0.0"))
ipv4Routes.append(NEIPv4Route(destinationAddress: "8.0.0.0", subnetMask: "248.0.0.0"))
ipv4Routes.append(NEIPv4Route(destinationAddress: "16.0.0.0", subnetMask: "240.0.0.0"))
ipv4Routes.append(NEIPv4Route(destinationAddress: "32.0.0.0", subnetMask: "224.0.0.0"))
ipv4Routes.append(NEIPv4Route(destinationAddress: "64.0.0.0", subnetMask: "192.0.0.0"))
ipv4Routes.append(NEIPv4Route(destinationAddress: "128.0.0.0", subnetMask: "128.0.0.0"))
} else {
ipv4Routes.append(NEIPv4Route.default())
}
let inet4RouteExcludeAddressIterator = options.getInet4RouteExcludeAddress()!
while inet4RouteExcludeAddressIterator.hasNext() {
let ipv4RoutePrefix = inet4RouteExcludeAddressIterator.next()!
ipv4ExcludeRoutes.append(NEIPv4Route(destinationAddress: ipv4RoutePrefix.address(), subnetMask: ipv4RoutePrefix.mask()))
}
if await SharedPreferences.excludeDefaultRoute.get(), !ipv4Routes.isEmpty {
if !ipv4ExcludeRoutes.contains(where: { it in
it.destinationAddress == "0.0.0.0" && it.destinationSubnetMask == "255.255.255.254"
}) {
ipv4ExcludeRoutes.append(NEIPv4Route(destinationAddress: "0.0.0.0", subnetMask: "255.255.255.254"))
}
}
if excludeAPNs, !ipv4Routes.isEmpty {
if !ipv4ExcludeRoutes.contains(where: { it in
it.destinationAddress == "17.0.0.0" && it.destinationSubnetMask == "255.0.0.0"
}) {
ipv4ExcludeRoutes.append(NEIPv4Route(destinationAddress: "17.0.0.0", subnetMask: "255.0.0.0"))
}
}
ipv4Settings.includedRoutes = ipv4Routes
ipv4Settings.excludedRoutes = ipv4ExcludeRoutes
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] = []
var ipv6ExcludeRoutes: [NEIPv6Route] = []
let inet6RouteAddressIterator = options.getInet6RouteAddress()!
if inet6RouteAddressIterator.hasNext() {
while inet6RouteAddressIterator.hasNext() {
let ipv6RoutePrefix = inet6RouteAddressIterator.next()!
ipv6Routes.append(NEIPv6Route(destinationAddress: ipv6RoutePrefix.address(), networkPrefixLength: NSNumber(value: ipv6RoutePrefix.prefix())))
}
} else if autoRouteUseSubRangesByDefault {
ipv6Routes.append(NEIPv6Route(destinationAddress: "100::", networkPrefixLength: 8))
ipv6Routes.append(NEIPv6Route(destinationAddress: "200::", networkPrefixLength: 7))
ipv6Routes.append(NEIPv6Route(destinationAddress: "400::", networkPrefixLength: 6))
ipv6Routes.append(NEIPv6Route(destinationAddress: "800::", networkPrefixLength: 5))
ipv6Routes.append(NEIPv6Route(destinationAddress: "1000::", networkPrefixLength: 4))
ipv6Routes.append(NEIPv6Route(destinationAddress: "2000::", networkPrefixLength: 3))
ipv6Routes.append(NEIPv6Route(destinationAddress: "4000::", networkPrefixLength: 2))
ipv6Routes.append(NEIPv6Route(destinationAddress: "8000::", networkPrefixLength: 1))
} else {
ipv6Routes.append(NEIPv6Route.default())
}
let inet6RouteExcludeAddressIterator = options.getInet6RouteExcludeAddress()!
while inet6RouteExcludeAddressIterator.hasNext() {
let ipv6RoutePrefix = inet6RouteExcludeAddressIterator.next()!
ipv6ExcludeRoutes.append(NEIPv6Route(destinationAddress: ipv6RoutePrefix.address(), networkPrefixLength: NSNumber(value: ipv6RoutePrefix.prefix())))
}
ipv6Settings.includedRoutes = ipv6Routes
ipv6Settings.excludedRoutes = ipv6ExcludeRoutes
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
if await SharedPreferences.systemProxyEnabled.get() {
proxySettings.httpEnabled = true
proxySettings.httpsEnabled = true
}
var bypassDomains: [String] = []
let bypassDomainIterator = options.getHTTPProxyBypassDomain()!
while bypassDomainIterator.hasNext() {
bypassDomains.append(bypassDomainIterator.next())
}
if excludeAPNs {
if !bypassDomains.contains(where: { it in
it == "push.apple.com"
}) {
bypassDomains.append("push.apple.com")
}
}
if !bypassDomains.isEmpty {
proxySettings.exceptionList = bypassDomains
}
var matchDomains: [String] = []
let matchDomainIterator = options.getHTTPProxyMatchDomain()!
while matchDomainIterator.hasNext() {
matchDomains.append(matchDomainIterator.next())
}
if !matchDomains.isEmpty {
proxySettings.matchDomains = matchDomains
}
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 {
false
}
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)
}
private var nwMonitor: NWPathMonitor? = nil
public func startDefaultInterfaceMonitor(_ listener: LibboxInterfaceUpdateListenerProtocol?) throws {
guard let listener else {
return
}
let monitor = NWPathMonitor()
nwMonitor = monitor
let semaphore = DispatchSemaphore(value: 0)
monitor.pathUpdateHandler = { path in
self.onUpdateDefaultInterface(listener, path)
semaphore.signal()
monitor.pathUpdateHandler = { path in
self.onUpdateDefaultInterface(listener, path)
}
}
monitor.start(queue: DispatchQueue.global())
semaphore.wait()
}
private func onUpdateDefaultInterface(_ listener: LibboxInterfaceUpdateListenerProtocol, _ path: Network.NWPath) {
if path.status == .unsatisfied {
listener.updateDefaultInterface("", interfaceIndex: -1, isExpensive: false, isConstrained: false)
} else {
let defaultInterface = path.availableInterfaces.first!
listener.updateDefaultInterface(defaultInterface.name, interfaceIndex: Int32(defaultInterface.index), isExpensive: path.isExpensive, isConstrained: path.isConstrained)
}
}
public func closeDefaultInterfaceMonitor(_: LibboxInterfaceUpdateListenerProtocol?) throws {
nwMonitor?.cancel()
nwMonitor = nil
}
public func getInterfaces() throws -> LibboxNetworkInterfaceIteratorProtocol {
guard let nwMonitor else {
throw NSError(domain: "NWMonitor not started", code: 0)
}
let path = nwMonitor.currentPath
if path.status == .unsatisfied {
return networkInterfaceArray([])
}
var interfaces: [LibboxNetworkInterface] = []
for it in path.availableInterfaces {
let interface = LibboxNetworkInterface()
interface.name = it.name
interface.index = Int32(it.index)
switch it.type {
case .wifi:
interface.type = LibboxInterfaceTypeWIFI
case .cellular:
interface.type = LibboxInterfaceTypeCellular
case .wiredEthernet:
interface.type = LibboxInterfaceTypeEthernet
default:
interface.type = LibboxInterfaceTypeOther
}
interfaces.append(interface)
}
return networkInterfaceArray(interfaces)
}
class networkInterfaceArray: NSObject, LibboxNetworkInterfaceIteratorProtocol {
private var iterator: IndexingIterator<[LibboxNetworkInterface]>
init(_ array: [LibboxNetworkInterface]) {
iterator = array.makeIterator()
}
private var nextValue: LibboxNetworkInterface? = nil
func hasNext() -> Bool {
nextValue = iterator.next()
return nextValue != nil
}
func next() -> LibboxNetworkInterface? {
nextValue
}
}
public func underNetworkExtension() -> Bool {
true
}
public func includeAllNetworks() -> Bool {
#if !os(tvOS)
return SharedPreferences.includeAllNetworks.getBlocking()
#else
return false
#endif
}
public func clearDNSCache() {
guard let networkSettings else {
return
}
tunnel.reasserting = true
tunnel.setTunnelNetworkSettings(nil) { _ in
}
tunnel.setTunnelNetworkSettings(networkSettings) { _ in
}
tunnel.reasserting = false
}
public func readWIFIState() -> LibboxWIFIState? {
#if os(iOS)
let network = runBlocking {
await NEHotspotNetwork.fetchCurrent()
}
guard let network else {
return nil
}
return LibboxWIFIState(network.ssid, wifiBSSID: network.bssid)!
#elseif os(macOS)
guard let interface = CWWiFiClient.shared().interface() else {
return nil
}
guard let ssid = interface.ssid() else {
return nil
}
guard let bssid = interface.bssid() else {
return nil
}
return LibboxWIFIState(ssid, wifiBSSID: bssid)!
#else
return nil
#endif
}
public func serviceReload() throws {
runBlocking { [self] in
await tunnel.reloadService()
}
}
public func postServiceClose() {
reset()
tunnel.postServiceClose()
}
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
}
public func send(_ notification: LibboxNotification?) throws {
#if !os(tvOS)
guard let notification else {
return
}
let center = UNUserNotificationCenter.current()
let content = UNMutableNotificationContent()
content.title = notification.title
content.subtitle = notification.subtitle
content.body = notification.body
if !notification.openURL.isEmpty {
content.userInfo["OPEN_URL"] = notification.openURL
content.categoryIdentifier = "OPEN_URL"
}
content.interruptionLevel = .active
let request = UNNotificationRequest(identifier: notification.identifier, content: content, trigger: nil)
try runBlocking {
try await center.requestAuthorization(options: [.alert])
try await center.add(request)
}
#endif
}
}