diff --git a/ios/ProxyPin/PacketTunnelProvider.swift b/ios/ProxyPin/PacketTunnelProvider.swift index 875638e..303fc51 100644 --- a/ios/ProxyPin/PacketTunnelProvider.swift +++ b/ios/ProxyPin/PacketTunnelProvider.swift @@ -21,12 +21,36 @@ class PacketTunnelProvider: NEPacketTunnelProvider { } let host = conf["proxyHost"] as! String - let proxyPort = conf["proxyPort"] as! Int - let ipProxy = conf["ipProxy"] as! Bool? ?? false + let proxyPort = conf["proxyPort"] as! Int + let ipProxy = conf["ipProxy"] as! Bool? ?? false +// let networkSettings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: "127.0.0.1") let networkSettings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: host) -// let networkSettings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: host) NSLog(conf.debugDescription) + + networkSettings.mtu = 9000 + + let ipv4Settings = NEIPv4Settings(addresses: ["10.0.0.2"], subnetMasks: ["255.255.255.255"]) + + if (ipProxy){ + ipv4Settings.includedRoutes = [NEIPv4Route.default()] + ipv4Settings.excludedRoutes = [ + NEIPv4Route(destinationAddress: "10.0.0.0", subnetMask: "255.0.0.0"), + NEIPv4Route(destinationAddress: "100.64.0.0", subnetMask: "255.192.0.0"), +// NEIPv4Route(destinationAddress: "127.0.0.0", subnetMask: "255.0.0.0"), + NEIPv4Route(destinationAddress: "169.254.0.0", subnetMask: "255.255.0.0"), + NEIPv4Route(destinationAddress: "172.16.0.0", subnetMask: "255.240.0.0"), + NEIPv4Route(destinationAddress: "192.168.0.0", subnetMask: "255.255.0.0"), + NEIPv4Route(destinationAddress: "17.0.0.0", subnetMask: "255.0.0.0"), + ] + + let dns = "223.5.5.5,8.8.8.8" + let dnsSettings = NEDNSSettings(servers: dns.components(separatedBy: ",")) + dnsSettings.matchDomains = [""] + dnsSettings.matchDomainsNoSearch = true + networkSettings.dnsSettings = dnsSettings + } + //http代理 let proxySettings = NEProxySettings() proxySettings.httpEnabled = true @@ -34,30 +58,9 @@ class PacketTunnelProvider: NEPacketTunnelProvider { proxySettings.httpsEnabled = true proxySettings.httpsServer = NEProxyServer(address: host, port: proxyPort) proxySettings.matchDomains = [""] - - networkSettings.proxySettings = proxySettings - networkSettings.mtu = 1480 - let ipv4Settings = NEIPv4Settings(addresses: ["10.0.0.2"], subnetMasks: ["255.255.255.255"]) - - if (ipProxy){ - ipv4Settings.includedRoutes = [NEIPv4Route.default()] -// ipv4Settings.excludedRoutes = [ -// NEIPv4Route(destinationAddress: "10.0.0.0", subnetMask: "255.0.0.0"), -// NEIPv4Route(destinationAddress: "100.64.0.0", subnetMask: "255.192.0.0"), -// NEIPv4Route(destinationAddress: "127.0.0.0", subnetMask: "255.0.0.0"), -// NEIPv4Route(destinationAddress: "169.254.0.0", subnetMask: "255.255.0.0"), -// NEIPv4Route(destinationAddress: "172.16.0.0", subnetMask: "255.240.0.0"), -// NEIPv4Route(destinationAddress: "192.168.0.0", subnetMask: "255.255.0.0"), -// NEIPv4Route(destinationAddress: "17.0.0.0", subnetMask: "255.0.0.0"), -// ] - - - let dns = "114.114.114.114,8.8.8.8" - let dnsSettings = NEDNSSettings(servers: dns.components(separatedBy: ",")) - dnsSettings.matchDomains = [""] - networkSettings.dnsSettings = dnsSettings - } + networkSettings.proxySettings = proxySettings + networkSettings.ipv4Settings = ipv4Settings diff --git a/ios/ProxyPin/ProxyPin-Bridging-Header.h b/ios/ProxyPin/ProxyPin-Bridging-Header.h new file mode 100644 index 0000000..6d1ffba --- /dev/null +++ b/ios/ProxyPin/ProxyPin-Bridging-Header.h @@ -0,0 +1,8 @@ +// +// ProxyPin-Bridging-Header.h +// Runner +// +// Created by wanghongen on 2025/5/28. +// + +#import "GBPing.h" diff --git a/ios/ProxyPin/vpn/Connection.swift b/ios/ProxyPin/vpn/Connection.swift index a982005..ee16ead 100644 --- a/ios/ProxyPin/vpn/Connection.swift +++ b/ios/ProxyPin/vpn/Connection.swift @@ -71,37 +71,34 @@ class Connection{ func closeConnection() { connectionCloser.closeConnection(connection: self) } - + func addSendData(data: Data) { + self.sendBuffer.append(data) - QueueFactory.instance.getQueue().async(flags: .barrier) { - self.sendBuffer.append(data) + if (self.channel?.state != .ready) { + os_log("Connection %{public}@ is not ready, cannot send data", log: OSLog.default, type: .debug, self.description) + return + } - if (self.nwProtocol == .TCP && self.channel?.state != .ready) { - return - } - self.sendToDestination() - } + self.sendToDestination() } //发送到目标服务器的数据 func sendToDestination() { - QueueFactory.instance.getQueue().async(flags: .barrier) { - os_log("Sending data to destination key %{public}@", log: OSLog.default, type: .debug, self.description) - if (self.sendBuffer.count == 0) { - return +// os_log("Sending data to destination key %{public}@", log: OSLog.default, type: .debug, self.description) + if (self.sendBuffer.count == 0) { + return + } + + let data = self.sendBuffer + self.sendBuffer.removeAll() + + self.channel?.send(content: data, completion: .contentProcessed({ error in + if let error = error { + os_log("Failed to send data to destination key %{public}@ error: %{public}@", log: OSLog.default, type: .error, self.description, error.localizedDescription) + self.closeConnection() } - - let data = self.sendBuffer - self.sendBuffer.removeAll() - - self.channel?.send(content: data, completion: .contentProcessed({ error in - if let error = error { - os_log("Failed to send data to destination key %{public}@ error: %{public}@", log: OSLog.default, type: .error, self.description, error.localizedDescription) - self.closeConnection() - } - })) - } + })) } var description: String { diff --git a/ios/ProxyPin/vpn/ConnectionHandler.swift b/ios/ProxyPin/vpn/ConnectionHandler.swift index 9e40e45..42f54ec 100644 --- a/ios/ProxyPin/vpn/ConnectionHandler.swift +++ b/ios/ProxyPin/vpn/ConnectionHandler.swift @@ -45,10 +45,13 @@ class ConnectionHandler { switch ipHeader.protocolNumber { case ProtocolType.tcp.rawValue: handleTCPPacket(packet: clientPacketData, ipHeader: ipHeader) - case ProtocolType.udp.rawValue: - handleUDPPacket(clientPacketData: clientPacketData, ipHeader: ipHeader) + break + case ProtocolType.udp.rawValue: + handleUDPPacket(clientPacketData: clientPacketData, ipHeader: ipHeader) + break case ProtocolType.icmp.rawValue: handleICMPPacket(clientPacketData: &clientPacketData, ipHeader: ipHeader) + break default: os_log("Unsupported IP protocol: %d", log: OSLog.default, type: .error, ipHeader.protocolNumber) } @@ -91,13 +94,17 @@ class ConnectionHandler { } synchronized(connection) { -// os_log("Received UDP packet", log: OSLog.default, type: .default) + os_log("handle UDP Packet %{public}@", log: OSLog.default, type: .default, connection.description) if newSession { ioService.registerSession(connection: connection) } let payload = clientPacketData.subdata(in: UDPPacketFactory.UDP_HEADER_LENGTH.. 0 { -// initProxyConnect(packet: packet, destinationIP: destinationIP, destinationPort: destinationPort, connection: connection) -// os_log("Received data packet %{public}@ length:%d seq:%u", log: OSLog.default, type: .default, connection.description, dataLength, tcpHeader.sequenceNumber) - //accumulate data from client +// os_log("Received data packet %{public}@ length:%d seq:%u, ack:%u", log: OSLog.default, type: .default, connection.description, dataLength, tcpHeader.sequenceNumber, tcpHeader.ackNumber) manager.addClientData(data: tcpHeader.payload!, connection: connection) - - //send ack to client only if new data was added sendAck(ipHeader: ipHeader, tcpHeader: tcpHeader, acceptedDataLength: dataLength, connection: connection) - } else { -// os_log("Received ACK packet %{public}@ seq:%u", log: OSLog.default, type: .default, connection.description, tcpHeader.sequenceNumber) - //an ack from client for previously sent data - acceptAck(tcpHeader: tcpHeader, connection: connection) - if connection.isClosingConnection { - sendFinAck(ipHeader: ipHeader, tcpHeader: tcpHeader, connection: connection) - } else if connection.isAckedToFin && !tcpHeader.isFIN() { - //the last ACK from client after FIN-ACK flag was sent - manager.closeConnection(nwProtocol: .TCP, ip: destinationIP, port: destinationPort, srcIp: sourceIP, srcPort: sourcePort) - } + os_log("Received ACK packet %{public}@ seq:%u, ack:%u", log: OSLog.default, type: .default, connection.description, tcpHeader.sequenceNumber, tcpHeader.ackNumber) + } + + acceptAck(tcpHeader: tcpHeader, connection: connection) + + if connection.isClosingConnection { + sendFinAck(ipHeader: ipHeader, tcpHeader: tcpHeader, connection: connection) + } else if connection.isAckedToFin && !tcpHeader.isFIN() { + manager.closeConnection(nwProtocol: .TCP, ip: destinationIP, port: destinationPort, srcIp: sourceIP, srcPort: sourcePort) } //received the last segment of data from vpn client @@ -274,7 +272,7 @@ class ConnectionHandler { let ackData = TCPPacketFactory.createResponseAckData(ipHeader: ipHeader, tcpHeader: tcpHeader, ackToClient: ackNumber) self.write(data: ackData) -// os_log("Sent ACK packet to client %{public}@:%{public}d ack# %{public}d", log: OSLog.default, type: .debug, PacketUtil.intToIPAddress(ipHeader.destinationIP), tcpHeader.destinationPort, ackNumber) +// os_log("Sent ACK packet to client %{public}@ ack: %u", log: OSLog.default, type: .default, connection.description, ackNumber) } } @@ -325,7 +323,7 @@ class ConnectionHandler { self.ioService.registerSession(connection: connection) } self.write(data: packet.buffer) -// os_log("SYN-ACK packet length:%d sent", log: OSLog.default, type: .default, packet.buffer.count) +// os_log("SYN-ACK %{public}@ packet length:%d sent ack:%u", log: OSLog.default, type: .default, connection.description, packet.buffer.count, tcpTransport.ackNumber) } } diff --git a/ios/ProxyPin/vpn/ConnectionManager.swift b/ios/ProxyPin/vpn/ConnectionManager.swift index 09cf094..bedb2c9 100644 --- a/ios/ProxyPin/vpn/ConnectionManager.swift +++ b/ios/ProxyPin/vpn/ConnectionManager.swift @@ -14,11 +14,10 @@ class ConnectionManager : CloseableConnection{ //static let instance = ConnectionManager() private var table: [String: Connection] = [:] - private let tableQueue = DispatchQueue(label: "ProxyPin.ConnectionManager") public var proxyAddress: NWEndpoint? - private let defaultPorts: [UInt16] = [80, 8080, 8888, 443] + private let defaultPorts: [UInt16] = [80, 443] func getConnection(nwProtocol: NWProtocol, ip: UInt32, port: UInt16, srcIp: UInt32, srcPort: UInt16) -> Connection? { @@ -27,87 +26,65 @@ class ConnectionManager : CloseableConnection{ } func getConnectionByKey(key: String) -> Connection? { - return tableQueue.sync { - return table[key] - } + return table[key] } func createTCPConnection(ip: UInt32, port: UInt16, srcIp: UInt32, srcPort: UInt16) -> Connection { let key = Connection.getConnectionKey(nwProtocol: .TCP, destIp: ip, destPort: port, sourceIp: srcIp, sourcePort: srcPort) - return tableQueue.sync { - if let existingConnection = table[key] { - return existingConnection - } - - let connection = Connection(nwProtocol: .TCP, sourceIp: srcIp, sourcePort: srcPort, destinationIp: ip, destinationPort: port, connectionCloser: self) - - let parameters = NWParameters.tcp - parameters.allowLocalEndpointReuse = true - parameters.includePeerToPeer = true - - let endpoint: NWEndpoint - if defaultPorts.contains(port) { - endpoint = proxyAddress! - } else { - let ipString = PacketUtil.intToIPAddress(ip) - endpoint = NWEndpoint.hostPort(host: NWEndpoint.Host(ipString), port: NWEndpoint.Port(rawValue: port)!) - } - - let nwConnection = NWConnection(to: endpoint, using: parameters) - - connection.channel = nwConnection - connection.isInitConnect = true - - tableQueue.async(flags: .barrier) { - self.table[key] = connection - } - os_log("Created TCP connection %{public}@", log: OSLog.default, type: .default, key) - - return connection + if let existingConnection = table[key] { + return existingConnection } -// nwConnection.stateUpdateHandler = { state in -// switch state { -// case .ready: -// connection.isConnected = true -// os_log("Connected to %{public}@", log: OSLog.default, type: .debug, endpoint.debugDescription) -// case .failed(let error): -// connection.isConnected = false -// os_log("Failed to connect: %{public}@", log: OSLog.default, type: .error, error.localizedDescription) -// -// default: -// break -// } -// } + let connection = Connection(nwProtocol: .TCP, sourceIp: srcIp, sourcePort: srcPort, destinationIp: ip, destinationPort: port, connectionCloser: self) + let ipString = PacketUtil.intToIPAddress(ip) + + let endpoint: NWEndpoint + if (defaultPorts.contains(port) && !isPrivateIP(ipString)) { + endpoint = proxyAddress! + } else { + endpoint = NWEndpoint.hostPort(host: NWEndpoint.Host(ipString), port: NWEndpoint.Port(rawValue: port)!) + } + + // 使用 TCP 协议 + let parameters = NWParameters.tcp + + let nwConnection = NWConnection(to: endpoint, using: parameters) + + connection.channel = nwConnection + connection.isInitConnect = true + + self.table[key] = connection + os_log("Created TCP connection %{public}@", log: OSLog.default, type: .default, key) + + return connection } - + + private func isPrivateIP(_ ip: String) -> Bool { + return ip.hasPrefix("10.") || + ip.hasPrefix("172.") && (16...31).contains(Int(ip.split(separator: ".")[1]) ?? -1) || + ip.hasPrefix("192.168.") + } + func createUDPConnection(ip: UInt32, port: UInt16, srcIp: UInt32, srcPort: UInt16) -> Connection { let key = Connection.getConnectionKey(nwProtocol: .UDP, destIp: ip, destPort: port, sourceIp: srcIp, sourcePort: srcPort) - return tableQueue.sync { - if let existingConnection = table[key] { - return existingConnection - } - - let connection = Connection(nwProtocol: .UDP, sourceIp: srcIp, sourcePort: srcPort, destinationIp: ip, destinationPort: port, connectionCloser: self) - - - let endpoint = NWEndpoint.hostPort(host: NWEndpoint.Host((PacketUtil.intToIPAddress(ip))), port: NWEndpoint.Port(rawValue: port)!) - - let nwConnection = NWConnection(to: endpoint, using: .udp) - connection.channel = nwConnection - - os_log("Created UDP connection %{public}@", log: OSLog.default, type: .default, key) - - connection.isConnected = true - tableQueue.async(flags: .barrier) { - self.table[key] = connection - } - - return connection + if let existingConnection = table[key] { + return existingConnection } + + let connection = Connection(nwProtocol: .UDP, sourceIp: srcIp, sourcePort: srcPort, destinationIp: ip, destinationPort: port, connectionCloser: self) + + let endpoint = NWEndpoint.hostPort(host: NWEndpoint.Host((PacketUtil.intToIPAddress(ip))), port: NWEndpoint.Port(rawValue: port)!) + + let nwConnection = NWConnection(to: endpoint, using: .udp) + connection.channel = nwConnection + + os_log("Created UDP connection %{public}@", log: OSLog.default, type: .default, key) + self.table[key] = connection + + return connection } func closeConnection(connection: Connection) { @@ -120,14 +97,13 @@ class ConnectionManager : CloseableConnection{ // 从内存中删除连接,然后关闭套接字。 func closeConnection(nwProtocol: NWProtocol, ip: UInt32, port: UInt16, srcIp: UInt32, srcPort: UInt16) { let key = Connection.getConnectionKey(nwProtocol: nwProtocol, destIp: ip, destPort: port, sourceIp: srcIp, sourcePort: srcPort) - tableQueue.async(flags: .barrier) { - if let connection = self.table.removeValue(forKey: key) { - if connection.channel?.state != .cancelled { - connection.channel?.cancel() - os_log("Closed connection %{public}@", log: OSLog.default, type: .debug, key) - } else { - os_log("Connection %{public}@ is already cancelled", log: OSLog.default, type: .debug, key) - } + + if let connection = self.table.removeValue(forKey: key) { + if connection.channel?.state != .cancelled { + connection.channel?.cancel() + os_log("Closed connection %{public}@", log: OSLog.default, type: .debug, key) + } else { + os_log("Connection %{public}@ is already cancelled", log: OSLog.default, type: .debug, key) } } } @@ -140,8 +116,7 @@ class ConnectionManager : CloseableConnection{ connection.addSendData(data: data) } - - //阻止java垃圾收集器收集会话 + func keepSessionAlive(connection: Connection) { let key = Connection.getConnectionKey( nwProtocol: connection.nwProtocol, @@ -150,8 +125,7 @@ class ConnectionManager : CloseableConnection{ sourceIp: connection.sourceIp, sourcePort: connection.sourcePort ) - tableQueue.async(flags: .barrier) { - self.table[key] = connection - } + + self.table[key] = connection } } diff --git a/ios/ProxyPin/vpn/ProxyVpnService.swift b/ios/ProxyPin/vpn/ProxyVpnService.swift index 5f3b0c5..ef70891 100644 --- a/ios/ProxyPin/vpn/ProxyVpnService.swift +++ b/ios/ProxyPin/vpn/ProxyVpnService.swift @@ -11,13 +11,13 @@ import Network import os.log class ProxyVpnService { - private let queue: DispatchQueue = DispatchQueue(label: "ProxyPin.ProxyVpnService") private var packetFlow: NEPacketTunnelFlow private var connectionHandler: ConnectionHandler private var socketIOService: SocketIOService + private var isRunning = true; - init(packetFlow: NEPacketTunnelFlow, proxyAddress: Network.NWEndpoint?) { + init(packetFlow: NEPacketTunnelFlow, proxyAddress: Network.NWEndpoint?) { self.packetFlow = packetFlow self.socketIOService = SocketIOService(clientPacketWriter: packetFlow) let manager = ConnectionManager() @@ -32,20 +32,25 @@ class ProxyVpnService { A stopped interface should never start again. Create a new interface instead. */ func start() { + isRunning = true; self.readPackets() } func stop() { + isRunning = false; self.socketIOService.stop() - queue.suspend() } func readPackets() -> Void { + if (!isRunning) { + return + } + self.packetFlow.readPackets { (packets, protocols) in - + // os_log("Read %d packets", packets.count) for (i, packet) in packets.enumerated() { - self.connectionHandler.handlePacket(packet: packet, version: protocols[i]) + self.connectionHandler.handlePacket(packet: packet, version: protocols[i]) } self.readPackets() } diff --git a/ios/ProxyPin/vpn/ping/GBPing.h b/ios/ProxyPin/vpn/ping/GBPing.h new file mode 100644 index 0000000..ba97eba --- /dev/null +++ b/ios/ProxyPin/vpn/ping/GBPing.h @@ -0,0 +1,58 @@ +// +// GBPing.h +// GBPing +// +// Created by Luka Mirosevic on 05/11/2012. +// Copyright (c) 2012 Goonbee. All rights reserved. +// + +#import + +#import "GBPingSummary.h" + +@class GBPingSummary; +@protocol GBPingDelegate; + +NS_ASSUME_NONNULL_BEGIN + +typedef void(^StartupCallback)(BOOL success, NSError * _Nullable error); + +@interface GBPing : NSObject + +@property (weak, nonatomic, nullable) id delegate; + +@property (copy, nonatomic, nullable) NSString *host; +@property (assign, atomic) NSTimeInterval pingPeriod; +@property (assign, atomic) NSTimeInterval timeout; +@property (assign, atomic) NSUInteger payloadSize; +@property (assign, atomic) NSUInteger ttl; +@property (assign, atomic) NSUInteger count; +@property (assign, atomic, readonly) BOOL isPinging; +@property (assign, atomic, readonly) BOOL isReady; +@property (assign, atomic) BOOL useIpv4; +@property (assign, atomic) BOOL useIpv6; + +@property (assign, atomic) BOOL debug; + +-(void)setupWithBlock:(StartupCallback)callback; +-(void)startPinging; +-(void)stop; + +@end + +@protocol GBPingDelegate + +@optional + +-(void)ping:(GBPing *)pinger didFinishWithTime:(NSTimeInterval)time; +-(void)ping:(GBPing *)pinger didFailWithError:(NSError *)error; + +-(void)ping:(GBPing *)pinger didSendPingWithSummary:(GBPingSummary *)summary; +-(void)ping:(GBPing *)pinger didFailToSendPingWithSummary:(GBPingSummary *)summary error:(NSError *)error; +-(void)ping:(GBPing *)pinger didTimeoutWithSummary:(GBPingSummary *)summary; +-(void)ping:(GBPing *)pinger didReceiveReplyWithSummary:(GBPingSummary *)summary; +-(void)ping:(GBPing *)pinger didReceiveUnexpectedReplyWithSummary:(GBPingSummary *)summary; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/ProxyPin/vpn/ping/GBPing.m b/ios/ProxyPin/vpn/ping/GBPing.m new file mode 100644 index 0000000..4381600 --- /dev/null +++ b/ios/ProxyPin/vpn/ping/GBPing.m @@ -0,0 +1,1000 @@ +// +// GBPing.m +// GBPing +// +// Created by Luka Mirosevic on 05/11/2012. +// Copyright (c) 2012 Goonbee. All rights reserved. +// + +#import "GBPing.h" + +#if TARGET_OS_EMBEDDED || TARGET_IPHONE_SIMULATOR + #import +#else + #import +#endif + +#import "ICMPHeader.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +static NSTimeInterval const kPendingPingsCleanupGrace = 1.0; + +static NSUInteger const kDefaultPayloadSize = 56; +static NSUInteger const kDefaultTTL = 49; +static NSTimeInterval const kDefaultPingPeriod = 1.0; +static NSTimeInterval const kDefaultTimeout = 2.0; + +@interface GBPing () + +@property (assign, atomic) int socket; +@property (strong, nonatomic) NSData *hostAddress; +@property (strong, nonatomic) NSString *hostAddressString; +@property (assign, nonatomic) uint16_t identifier; +@property (assign, nonatomic) NSUInteger counter; + +@property (assign, atomic, readwrite) BOOL isPinging; +@property (assign, atomic, readwrite) BOOL isReady; +@property (assign, nonatomic) NSUInteger nextSequenceNumber; +@property (strong, atomic) NSMutableDictionary *pendingPings; +@property (strong, nonatomic) NSMutableDictionary *timeoutTimers; + +@property (strong, nonatomic) dispatch_queue_t setupQueue; + +@property (assign, atomic) BOOL isStopped; + +@end + +@implementation GBPing { + NSUInteger _payloadSize; + NSUInteger _ttl; + NSUInteger _count; + NSTimeInterval _timeout; + NSTimeInterval _pingPeriod; + NSTimeInterval _endTime; +} + +#pragma mark - custom acc + +-(void)setTimeout:(NSTimeInterval)timeout { + @synchronized(self) { + if (self.isPinging) { + if (self.debug) { + NSLog(@"GBPing: can't set timeout while pinger is running."); + } + } + else { + _timeout = timeout; + } + } +} + +-(NSTimeInterval)timeout { + @synchronized(self) { + if (!_timeout) { + return kDefaultTimeout; + } + else { + return _timeout; + } + } +} + +-(void)setTtl:(NSUInteger)ttl { + @synchronized(self) { + if (self.isPinging) { + if (self.debug) { + NSLog(@"GBPing: can't set ttl while pinger is running."); + } + } + else { + _ttl = ttl; + } + } +} + +-(NSUInteger)ttl { + @synchronized(self) { + if (!_ttl) { + return kDefaultTTL; + } + else { + return _ttl; + } + } +} + +-(void)setCount:(NSUInteger)count { + @synchronized(self) { + if (self.isPinging) { + if (self.debug) { + NSLog(@"GBPing: can't set count while pinger is running."); + } + } + else { + _count = count; + } + } +} + +-(NSUInteger)count { + @synchronized(self) { + if (!_count) { + return 0; + } + else { + return _count; + } + } +} + +-(void)setPayloadSize:(NSUInteger)payloadSize { + @synchronized(self) { + if (self.isPinging) { + if (self.debug) { + NSLog(@"GBPing: can't set payload size while pinger is running."); + } + } + else { + _payloadSize = payloadSize; + } + } +} + +-(NSUInteger)payloadSize { + @synchronized(self) { + if (!_payloadSize) { + return kDefaultPayloadSize; + } + else { + return _payloadSize; + } + } +} + +-(void)setPingPeriod:(NSTimeInterval)pingPeriod { + @synchronized(self) { + if (self.isPinging) { + if (self.debug) { + NSLog(@"GBPing: can't set pingPeriod while pinger is running."); + } + } + else { + _pingPeriod = pingPeriod; + } + } +} + +-(NSTimeInterval)pingPeriod { + @synchronized(self) { + if (!_pingPeriod) { + return (NSTimeInterval)kDefaultPingPeriod; + } + else { + return _pingPeriod; + } + } +} + +#pragma mark - core pinging methods + +-(void)setupWithBlock:(StartupCallback)callback { + //error out of its already setup + if (self.isReady) { + if (self.debug) { + NSLog(@"GBPing: Can't setup, already setup."); + } + + //notify about error and return + dispatch_async(dispatch_get_main_queue(), ^{ + callback(NO, nil); + }); + return; + } + + //error out if no host is set + if (!self.host) { + if (self.debug) { + NSLog(@"GBPing: set host before attempting to start."); + } + + //notify about error and return + dispatch_async(dispatch_get_main_queue(), ^{ + callback(NO, nil); + }); + return; + } + + //set up data structs + self.nextSequenceNumber = 0; + @synchronized (self) { + self.pendingPings = [[NSMutableDictionary alloc] init]; + self.timeoutTimers = [[NSMutableDictionary alloc] init]; + } + + dispatch_async(self.setupQueue, ^{ + CFStreamError streamError; + BOOL success; + + CFHostRef hostRef = CFHostCreateWithName(NULL, (__bridge CFStringRef)self.host); + + /* + * CFHostCreateWithName will return a null result in certain cases. + * CFHostStartInfoResolution will return YES if _hostRef is null. + */ + if (hostRef) { + success = CFHostStartInfoResolution(hostRef, kCFHostAddresses, &streamError); + } else { + success = NO; + } + + if (!success) { + //construct an error + NSDictionary *userInfo; + NSError *error; + + if (hostRef && streamError.domain == kCFStreamErrorDomainNetDB) { + userInfo = [NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInteger:streamError.error], kCFGetAddrInfoFailureKey, + nil + ]; + } + else { + userInfo = nil; + } + error = [NSError errorWithDomain:(NSString *)kCFErrorDomainCFNetwork code:kCFHostErrorUnknown userInfo:userInfo]; + + //clean up so far + [self stop]; + + //notify about error and return + dispatch_async(dispatch_get_main_queue(), ^{ + callback(NO, error); + }); + + //just incase + if (hostRef) { + CFRelease(hostRef); + } + return; + } + + //get the first IPv4 or IPv6 address + Boolean resolved; + NSArray *addresses = (__bridge NSArray *)CFHostGetAddressing(hostRef, &resolved); + if (resolved && (addresses != nil)) { + resolved = false; + for (NSData *address in addresses) { + const struct sockaddr *anAddrPtr = (const struct sockaddr *)[address bytes]; + + if ([address length] >= sizeof(struct sockaddr) && + ((self.useIpv4 && anAddrPtr->sa_family == AF_INET) || + (self.useIpv6 && anAddrPtr->sa_family == AF_INET6)) ) { + resolved = true; + self.hostAddress = address; + self.hostAddressString = [self ntop:(struct sockaddr *)anAddrPtr len:(socklen_t)address.length]; + break; + } + } + } + + //we can stop host resolution now + if (hostRef) { + CFRelease(hostRef); + } + + //if an error occurred during resolution + if (!resolved) { + //stop + [self stop]; + + //notify about error and return + dispatch_async(dispatch_get_main_queue(), ^{ + callback(NO, [NSError errorWithDomain:(NSString *)kCFErrorDomainCFNetwork code:kCFHostErrorHostNotFound userInfo:nil]); + }); + return; + } + + //set up socket + int err = 0; + switch (self.hostAddressFamily) { + case AF_INET: { + self.socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP); + if (self.socket < 0) { + err = errno; + } + } break; + case AF_INET6: { + self.socket = socket(AF_INET6, SOCK_DGRAM, IPPROTO_ICMPV6); + if (self.socket < 0) { + err = errno; + } + } break; + default: { + err = EPROTONOSUPPORT; + } break; + } + + //couldnt setup socket + if (err) { + //clean up so far + [self stop]; + + //notify about error and close + dispatch_async(dispatch_get_main_queue(), ^{ + callback(NO, [NSError errorWithDomain:NSPOSIXErrorDomain code:err userInfo:nil]); + }); + return; + } + + //set ttl on the socket + if (self.ttl) { + u_char ttlForSockOpt = (u_char)self.ttl; + setsockopt(self.socket, IPPROTO_IP, IP_TTL, &ttlForSockOpt, sizeof(NSUInteger)); + } + + //we are ready now + self.isReady = YES; + + //notify that we are ready + dispatch_async(dispatch_get_main_queue(), ^{ + callback(YES, nil); + }); + }); + + self.isStopped = NO; +} + + +-(void)startPinging { + if (self.isReady && !self.isPinging) { + //go into infinite listenloop on a new thread (listenThread) + NSThread *listenThread = [[NSThread alloc] initWithTarget:self selector:@selector(listenLoop) object:nil]; + listenThread.name = @"listenThread"; + + //set up loop that sends packets on a new thread (sendThread) + NSThread *sendThread = [[NSThread alloc] initWithTarget:self selector:@selector(sendLoop) object:nil]; + sendThread.name = @"sendThread"; + + //we're pinging now + self.isPinging = YES; + [listenThread start]; + [sendThread start]; + } +} + +-(void)listenLoop { + @autoreleasepool { + while (self.isPinging) { + [self listenOnce]; + } + } +} + +-(void)listenOnce { + int err; + struct sockaddr_storage addr; + socklen_t addrLen; + ssize_t bytesRead; + void * buffer; + enum { kBufferSize = 65535 }; + + buffer = malloc(kBufferSize); + + if (buffer == nil) { + err = errno; + return; + } + + //read the data. + addrLen = sizeof(addr); + bytesRead = recvfrom(self.socket, buffer, kBufferSize, 0, (struct sockaddr *)&addr, &addrLen); + err = 0; + if (bytesRead < 0) { + err = errno; + } + + //process the data we read. + if (bytesRead > 0) { + _endTime = CFAbsoluteTimeGetCurrent(); + struct sockaddr_in *sin = (struct sockaddr_in *)&addr; + NSString *host = [self ntop:(struct sockaddr *)&addr len:addrLen]; + + if([host isEqualToString:self.hostAddressString]) { // only make sense where received packet comes from expected source + NSDate *receiveDate = [NSDate date]; + NSMutableData *packet; + + packet = [NSMutableData dataWithBytes:buffer length:(NSUInteger) bytesRead]; + + if (packet == nil) { + err = errno; + return; + } + + //complete the ping summary + const struct ICMPHeader *headerPointer; + + if (sin->sin_family == AF_INET) { + headerPointer = [[self class] icmp4InPacket:packet]; + } else { + headerPointer = (const struct ICMPHeader *)[packet bytes]; + } + + NSUInteger seqNo = (NSUInteger)OSSwapBigToHostInt16(headerPointer->sequenceNumber); + NSNumber *key = @(seqNo); + GBPingSummary *pingSummary; + @synchronized (self) { + pingSummary = [(GBPingSummary *)self.pendingPings[key] copy]; + } + + if (pingSummary) { + if ([self isValidPingResponsePacket:packet]) { + pingSummary.receiveDate = receiveDate; + if (sin->sin_family == AF_INET) { + //set ttl from response (different servers may respond with different ttls) + const struct IPHeader *ipPtr; + if ([packet length] >= sizeof(IPHeader)) { + ipPtr = (const IPHeader *)[packet bytes]; + pingSummary.ttl = ipPtr->timeToLive; + } + } + + pingSummary.status = GBPingStatusSuccess; + + //invalidate the timeouttimer + @synchronized (self) { + NSTimer *timer = self.timeoutTimers[key]; + [timer invalidate]; + [self.timeoutTimers removeObjectForKey:key]; + } + + dispatch_async(dispatch_get_main_queue(), ^{ + //notify delegate + if (self.delegate && [self.delegate respondsToSelector:@selector(ping:didReceiveReplyWithSummary:)] ) { + [self.delegate ping:self didReceiveReplyWithSummary:[pingSummary copy]]; + } + }); + } + else { + pingSummary.status = GBPingStatusFail; + + dispatch_async(dispatch_get_main_queue(), ^{ + if (self.delegate && [self.delegate respondsToSelector:@selector(ping:didReceiveUnexpectedReplyWithSummary:)] ) { + [self.delegate ping:self didReceiveUnexpectedReplyWithSummary:[pingSummary copy]]; + } + }); + } + } + } + } + else { + + //we failed to read the data, so shut everything down. + if (err == 0) { + err = EPIPE; + } + + @synchronized(self) { + if (!self.isStopped) { + dispatch_async(dispatch_get_main_queue(), ^{ + if (self.delegate && [self.delegate respondsToSelector:@selector(ping:didFailWithError:)] ) { + [self.delegate ping:self didFailWithError:[NSError errorWithDomain:NSPOSIXErrorDomain code:err userInfo:nil]]; + } + }); + } + } + + //stop the whole thing + [self stop]; + } + + free(buffer); +} + +-(void)sendLoop { + @autoreleasepool { + self.counter = _count; + BOOL stopping = NO; + NSTimeInterval startTime = CFAbsoluteTimeGetCurrent(); + _endTime = 0; + while (self.isPinging) { + [self sendPing]; + + if (_count > 0) { + self.counter -= 1; + if (self.counter == 0) { + stopping = YES; + } + } + + NSTimeInterval runUntil = CFAbsoluteTimeGetCurrent() + (stopping ? self.timeout : self.pingPeriod); + NSTimeInterval time = 0; + while (runUntil > time) { + NSDate *runUntilDate = [NSDate dateWithTimeIntervalSinceReferenceDate:runUntil]; + [[NSRunLoop currentRunLoop] runUntilDate:runUntilDate]; + + time = CFAbsoluteTimeGetCurrent(); + } + if (stopping) { + break; + } + } + [self stop]; + dispatch_async(dispatch_get_main_queue(), ^{ + if (self.delegate && [self.delegate respondsToSelector:@selector(ping:didFinishWithTime:)] ) { + NSTimeInterval interval = 0; + if (self->_endTime > 0) { + interval = self->_endTime - startTime; + } + [self.delegate ping:self didFinishWithTime:interval]; + } + }); + } +} + +-(void)sendPing { + if (self.isPinging) { + + int err; + NSData *packet; + ssize_t bytesSent; + + // Construct the ping packet. + NSData *payload = [self generateDataWithLength:(self.payloadSize)]; + + switch (self.hostAddressFamily) { + case AF_INET: { + packet = [self pingPacketWithType:kICMPv4TypeEchoRequest payload:payload requiresChecksum:YES]; + } break; + case AF_INET6: { + packet = [self pingPacketWithType:kICMPv6TypeEchoRequest payload:payload requiresChecksum:NO]; + } break; + default: { + err = errno; + return; + } break; + } + + // this is our ping summary + GBPingSummary *newPingSummary = [GBPingSummary new]; + + // Send the packet. + if (self.socket == 0) { + bytesSent = -1; + err = EBADF; + } + else { + + //record the send date + NSDate *sendDate = [NSDate date]; + + //construct ping summary, as much as it can + newPingSummary.sequenceNumber = self.nextSequenceNumber; + newPingSummary.host = self.host; + newPingSummary.ip = self.hostAddressString; + newPingSummary.sendDate = sendDate; + newPingSummary.ttl = self.ttl; + newPingSummary.payloadSize = self.payloadSize; + newPingSummary.status = GBPingStatusPending; + + //add it to pending pings + NSNumber *key = @(self.nextSequenceNumber); + @synchronized (self) { + self.pendingPings[key] = newPingSummary; + } + + //increment sequence number + self.nextSequenceNumber += 1; + + //we create a copy, this one will be passed out to other threads + GBPingSummary *pingSummaryCopy = [newPingSummary copy]; + + //we need to clean up our list of pending pings, and we do that after the timeout has elapsed (+ some grace period) + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)((self.timeout + kPendingPingsCleanupGrace) * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + //remove the ping from the pending list + @synchronized (self) { + [self.pendingPings removeObjectForKey:key]; + } + }); + + //add a timeout timer + //add a timeout timer + NSTimer *timeoutTimer = [NSTimer scheduledTimerWithTimeInterval:self.timeout + target:[NSBlockOperation blockOperationWithBlock:^{ + + newPingSummary.status = GBPingStatusFail; + self->_endTime = CFAbsoluteTimeGetCurrent(); + + //notify about the failure + dispatch_async(dispatch_get_main_queue(), ^{ + if (self.delegate && [self.delegate respondsToSelector:@selector(ping:didTimeoutWithSummary:)]) { + [self.delegate ping:self didTimeoutWithSummary:pingSummaryCopy]; + } + }); + + //remove the timer itself from the timers list + //lm make sure that the timer list doesnt grow and these removals actually work... try logging the count of the timeoutTimers when stopping the pinger + @synchronized (self) { + [self.timeoutTimers removeObjectForKey:key]; + } + }] + selector:@selector(main) + userInfo:nil + repeats:NO]; + [[NSRunLoop mainRunLoop] addTimer:timeoutTimer forMode:NSRunLoopCommonModes]; + + //keep a local ref to it + if (self.timeoutTimers) { + @synchronized (self) { + self.timeoutTimers[key] = timeoutTimer; + } + } + + //notify delegate about this + dispatch_async(dispatch_get_main_queue(), ^{ + if (self.delegate && [self.delegate respondsToSelector:@selector(ping:didSendPingWithSummary:)]) { + [self.delegate ping:self didSendPingWithSummary:pingSummaryCopy]; + } + }); + + bytesSent = sendto( + self.socket, + [packet bytes], + [packet length], + 0, + (struct sockaddr *) [self.hostAddress bytes], + (socklen_t) [self.hostAddress length] + ); + err = 0; + if (bytesSent < 0) { + err = errno; + } + } + + // This is after the sending + + //successfully sent + if ((bytesSent > 0) && (((NSUInteger) bytesSent) == [packet length])) { + //noop, we already notified delegate about sending of the ping + } + //failed to send + else { + //complete the error + if (err == 0) { + err = ENOBUFS; // This is not a hugely descriptor error, alas. + } + + //little log + if (self.debug) { + NSLog(@"GBPing: failed to send packet with error code: %d", err); + } + + //change status + newPingSummary.status = GBPingStatusFail; + + GBPingSummary *pingSummaryCopyAfterFailure = [newPingSummary copy]; + + //notify delegate + dispatch_async(dispatch_get_main_queue(), ^{ + if (self.delegate && [self.delegate respondsToSelector:@selector(ping:didFailToSendPingWithSummary:error:)]) { + [self.delegate ping:self didFailToSendPingWithSummary:pingSummaryCopyAfterFailure error:[NSError errorWithDomain:NSPOSIXErrorDomain code:err userInfo:nil]]; + } + }); + } + } +} + +-(void)stop { + @synchronized(self) { + if (!self.isStopped) { + self.isPinging = NO; + + self.isReady = NO; + + //destroy listenThread by closing socket (listenThread) + if (self.socket) { + close(self.socket); + self.socket = 0; + } + + //destroy host + self.hostAddress = nil; + + //clean up pendingpings + [self.pendingPings removeAllObjects]; + self.pendingPings = nil; + for (NSNumber *key in [self.timeoutTimers copy]) { + NSTimer *timer = self.timeoutTimers[key]; + [timer invalidate]; + } + + //clean up timeouttimers + [self.timeoutTimers removeAllObjects]; + self.timeoutTimers = nil; + + //reset seq number + self.nextSequenceNumber = 0; + + self.isStopped = YES; + } + } +} + +#pragma mark - util + +static uint16_t in_cksum(const void *buffer, size_t bufferLen) +// This is the standard BSD checksum code, modified to use modern types. +{ + size_t bytesLeft; + int32_t sum; + const uint16_t * cursor; + union { + uint16_t us; + uint8_t uc[2]; + } last; + uint16_t answer; + + bytesLeft = bufferLen; + sum = 0; + cursor = buffer; + + /* + * Our algorithm is simple, using a 32 bit accumulator (sum), we add + * sequential 16 bit words to it, and at the end, fold back all the + * carry bits from the top 16 bits into the lower 16 bits. + */ + while (bytesLeft > 1) { + sum += *cursor; + cursor += 1; + bytesLeft -= 2; + } + + /* mop up an odd byte, if necessary */ + if (bytesLeft == 1) { + last.uc[0] = * (const uint8_t *) cursor; + last.uc[1] = 0; + sum += last.us; + } + + /* add back carry outs from top 16 bits to low 16 bits */ + sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */ + sum += (sum >> 16); /* add carry */ + answer = (uint16_t) ~sum; /* truncate to 16 bits */ + + return answer; +} + ++(NSString *)sourceAddressInPacket:(NSData *)packet { + // Returns the source address of the IP packet + + const struct IPHeader *ipPtr; + const uint8_t *sourceAddress; + + if ([packet length] >= sizeof(IPHeader)) { + ipPtr = (const IPHeader *)[packet bytes]; + + sourceAddress = ipPtr->sourceAddress;//dont need to swap byte order those cuz theyre the smallest atomic unit (1 byte) + NSString *ipString = [NSString stringWithFormat:@"%d.%d.%d.%d", sourceAddress[0], sourceAddress[1], sourceAddress[2], sourceAddress[3]]; + + return ipString; + } + else return nil; +} + ++ (NSUInteger)icmp4HeaderOffsetInPacket:(NSData *)packet +// Returns the offset of the ICMPHeader within an IP packet. +{ + NSUInteger result; + const struct IPHeader * ipPtr; + size_t ipHeaderLength; + + result = NSNotFound; + if ([packet length] >= (sizeof(IPHeader) + sizeof(ICMPHeader))) { + ipPtr = (const IPHeader *) [packet bytes]; + assert((ipPtr->versionAndHeaderLength & 0xF0) == 0x40); // IPv4 + assert(ipPtr->protocol == 1); // ICMP + ipHeaderLength = (ipPtr->versionAndHeaderLength & 0x0F) * sizeof(uint32_t); + if ([packet length] >= (ipHeaderLength + sizeof(ICMPHeader))) { + result = ipHeaderLength; + } + } + return result; +} + ++ (const struct ICMPHeader *)icmp4InPacket:(NSData *)packet +// See comment in header. +{ + const struct ICMPHeader * result; + NSUInteger icmpHeaderOffset; + + result = nil; + icmpHeaderOffset = [self icmp4HeaderOffsetInPacket:packet]; + if (icmpHeaderOffset != NSNotFound) { + result = (const struct ICMPHeader *) (((const uint8_t *)[packet bytes]) + icmpHeaderOffset); + } + return result; +} + +- (BOOL)isValidPingResponsePacket:(NSMutableData *)packet +{ + BOOL result; + + switch (self.hostAddressFamily) { + case AF_INET: { + result = [self isValidPing4ResponsePacket:packet]; + } break; + case AF_INET6: { + result = [self isValidPing6ResponsePacket:packet]; + } break; + default: { + result = NO; + } break; + } + return result; +} + +- (BOOL)isValidPing4ResponsePacket:(NSMutableData *)packet +// Returns true if the packet looks like a valid ping response packet destined +// for us. +{ + BOOL result; + NSUInteger icmpHeaderOffset; + ICMPHeader * icmpPtr; + uint16_t receivedChecksum; + uint16_t calculatedChecksum; + + result = NO; + + icmpHeaderOffset = [[self class] icmp4HeaderOffsetInPacket:packet]; + if (icmpHeaderOffset != NSNotFound) { + icmpPtr = (struct ICMPHeader *) (((uint8_t *)[packet mutableBytes]) + icmpHeaderOffset); + + receivedChecksum = icmpPtr->checksum; + icmpPtr->checksum = 0; + calculatedChecksum = in_cksum(icmpPtr, [packet length] - icmpHeaderOffset); + icmpPtr->checksum = receivedChecksum; + + if (receivedChecksum == calculatedChecksum) { + if ( (icmpPtr->type == kICMPv4TypeEchoReply) && (icmpPtr->code == 0) ) { + if ( OSSwapBigToHostInt16(icmpPtr->identifier) == self.identifier ) { + if ( OSSwapBigToHostInt16(icmpPtr->sequenceNumber) < self.nextSequenceNumber ) { + result = YES; + } + } + } + } + } + + // NSLog(@"valid: %@, type: %d", _b(result), icmpPtr->type); + + return result; +} + +- (BOOL)isValidPing6ResponsePacket:(NSMutableData *)packet +// Returns true if the IPv6 packet looks like a valid ping response packet destined +// for us. +{ + BOOL result; + const ICMPHeader * icmpPtr; + + result = NO; + + if (packet.length >= sizeof(*icmpPtr)) { + icmpPtr = packet.bytes; + + if ( (icmpPtr->type == kICMPv6TypeEchoReply) && (icmpPtr->code == 0) ) { + if ( OSSwapBigToHostInt16(icmpPtr->identifier) == self.identifier ) { + if ( OSSwapBigToHostInt16(icmpPtr->sequenceNumber) < self.nextSequenceNumber ) { + result = YES; + } + } + } + + } + + return result; +} + +-(NSData *)generateDataWithLength:(NSUInteger)length { + //create a buffer full of 7's of specified length + char tempBuffer[length]; + memset(tempBuffer, 7, length); + + return [[NSData alloc] initWithBytes:tempBuffer length:length]; +} + +- (void)_invokeTimeoutCallback:(NSTimer *)timer +{ + dispatch_block_t callback = timer.userInfo; + if (callback) { + callback(); + } +} + +- (NSData *)pingPacketWithType:(uint8_t)type payload:(NSData *)payload requiresChecksum:(BOOL)requiresChecksum { + NSMutableData * packet; + ICMPHeader * icmpPtr; + + packet = [NSMutableData dataWithLength:sizeof(*icmpPtr) + payload.length]; + if (packet == nil) { return nil; } + + icmpPtr = packet.mutableBytes; + icmpPtr->type = type; + icmpPtr->code = 0; + icmpPtr->checksum = 0; + icmpPtr->identifier = OSSwapHostToBigInt16(self.identifier); + icmpPtr->sequenceNumber = OSSwapHostToBigInt16(self.nextSequenceNumber); + memcpy(&icmpPtr[1], [payload bytes], [payload length]); + + if (requiresChecksum) { + // The IP checksum routine returns a 16-bit number that's already in correct byte order + // (due to wacky 1's complement maths), so we just put it into the packet as a 16-bit unit. + + icmpPtr->checksum = in_cksum(packet.bytes, packet.length); + } + + return packet; +} + +- (sa_family_t)hostAddressFamily { + sa_family_t result = AF_UNSPEC; + // Save a reference to a local variable, avoid crash when hostAddress is release by other thread. + NSData *hostAddress = self.hostAddress; + if (hostAddress != nil && hostAddress.length >= sizeof(struct sockaddr)) { + result = ((const struct sockaddr *)hostAddress.bytes)->sa_family; + } + + return result; +} + +- (NSString*)ntop:(struct sockaddr *)sa len:(socklen_t)len { + char ntop[NI_MAXHOST] = { 0 }; + int ecode = getnameinfo(sa, len, ntop, sizeof(ntop), NULL, 0, NI_NUMERICHOST); + if (ecode == 0) { + return [[NSString alloc] initWithUTF8String:ntop]; + } else { + return nil; + } +} + +#pragma mark - memory + +-(id)init { + if (self = [super init]) { + self.setupQueue = dispatch_queue_create("GBPing setup queue", 0); + self.isStopped = YES; + self.identifier = arc4random(); + self.useIpv4 = YES; + self.useIpv6 = YES; + } + + return self; +} + +-(void)dealloc { + self.delegate = nil; + self.host = nil; + @synchronized (self) { + self.timeoutTimers = nil; + self.pendingPings = nil; + } + self.hostAddress = nil; + + //clean up socket to be sure + if (self.socket) { + close(self.socket); + self.socket = 0; + } +} + +@end diff --git a/ios/ProxyPin/vpn/ping/GBPingHelper.swift b/ios/ProxyPin/vpn/ping/GBPingHelper.swift new file mode 100644 index 0000000..fcdf972 --- /dev/null +++ b/ios/ProxyPin/vpn/ping/GBPingHelper.swift @@ -0,0 +1,95 @@ +// +// GBPingHelper.swift +// + +import Foundation + +public typealias Handler = ((_ response: [String: Any]) -> Void) + +public class GBPingHelper: NSObject { + private var ping: GBPing? + private let delegate = PingDelegate() + + func start(withHost host: String, ipv4: Bool, ipv6: Bool, count: UInt, interval: TimeInterval, timeout: TimeInterval, ttl: UInt, handler: @escaping Handler) { + ping?.stop() + ping = GBPing() + guard let ping = ping else { + return + } + ping.host = host + ping.useIpv4 = ipv4 + ping.useIpv6 = ipv6 + ping.count = count + ping.pingPeriod = interval + ping.timeout = timeout + if ttl > 0 { + ping.ttl = ttl + } + + delegate.handler = handler + ping.delegate = delegate + ping.setup { success, err in + if let err = err as NSError? { + if err.domain == kCFErrorDomainCFNetwork as String { + handler(["error": "UnknownHost"]) + } else { + handler(["error": "UnknownError"]) + } + return + } + if success { + self.delegate.transmitted = 0 + self.delegate.received = 0 + ping.startPinging() + } + } + } + + func stop() { + ping?.stop() + } +} + +private class PingDelegate: NSObject, GBPingDelegate { + public var handler: Handler? + public var transmitted = 0 + public var received = 0 + + func handle(_ summary: GBPingSummary, error: String? = nil) { + guard let handler = handler else { + return + } + var ret: [String: Any] = [:] + ret["seq"] = summary.sequenceNumber + ret["host"] = summary.host + ret["ip"] = summary.ip + ret["ttl"] = summary.ttl + ret["time"] = summary.rtt + ret["error"] = error + handler(ret) + } + + func ping(_ pinger: GBPing, didSendPingWith summary: GBPingSummary) { + transmitted += 1 + } + + func ping(_ pinger: GBPing, didTimeoutWith summary: GBPingSummary) { + handle(summary, error: "RequestTimedOut") + } + + func ping(_ pinger: GBPing, didReceiveReplyWith summary: GBPingSummary) { + received += 1 + handle(summary) + } + + func ping(_ pinger: GBPing, didFinishWithTime time: TimeInterval) { + guard let handler = handler else { + return + } + var ret: [String: Any] = [:] + ret["time"] = time + ret["received"] = received + ret["transmitted"] = transmitted + handler(ret) + } +} diff --git a/ios/ProxyPin/vpn/ping/GBPingSummary.h b/ios/ProxyPin/vpn/ping/GBPingSummary.h new file mode 100644 index 0000000..d05dc96 --- /dev/null +++ b/ios/ProxyPin/vpn/ping/GBPingSummary.h @@ -0,0 +1,29 @@ +// +// GBPingSummary.h +// GBPing +// +// Created by Luka Mirosevic on 05/11/2012. +// Copyright (c) 2012 Goonbee. All rights reserved. +// + +#import + +@interface GBPingSummary : NSObject + +typedef enum { + GBPingStatusPending, + GBPingStatusSuccess, + GBPingStatusFail, +} GBPingStatus; + +@property (assign, nonatomic) NSUInteger sequenceNumber; +@property (assign, nonatomic) NSUInteger payloadSize; +@property (assign, nonatomic) NSUInteger ttl; +@property (strong, nonatomic, nullable) NSString *host; +@property (strong, nonatomic, nullable) NSString *ip; +@property (strong, nonatomic, nullable) NSDate *sendDate; +@property (strong, nonatomic, nullable) NSDate *receiveDate; +@property (assign, nonatomic) NSTimeInterval rtt; +@property (assign, nonatomic) GBPingStatus status; + +@end diff --git a/ios/ProxyPin/vpn/ping/GBPingSummary.m b/ios/ProxyPin/vpn/ping/GBPingSummary.m new file mode 100644 index 0000000..a0f0ace --- /dev/null +++ b/ios/ProxyPin/vpn/ping/GBPingSummary.m @@ -0,0 +1,68 @@ +// +// GBPingSummary.m +// GBPing +// +// Created by Luka Mirosevic on 05/11/2012. +// Copyright (c) 2012 Goonbee. All rights reserved. +// + +#import "GBPingSummary.h" + +@implementation GBPingSummary + +#pragma mark - custom acc + +-(void)setHost:(NSString *)host { + _host = host; +} + +-(NSTimeInterval)rtt { + if (self.sendDate) { + return [self.receiveDate timeIntervalSinceDate:self.sendDate]; + } + else { + return 0; + } +} + +#pragma mark - copying + +-(id)copyWithZone:(NSZone *)zone { + GBPingSummary *copy = [[[self class] allocWithZone:zone] init]; + + copy.sequenceNumber = self.sequenceNumber; + copy.payloadSize = self.payloadSize; + copy.ttl = self.ttl; + copy.host = [self.host copy]; + copy.ip = [self.ip copy]; + copy.sendDate = [self.sendDate copy]; + copy.receiveDate = [self.receiveDate copy]; + copy.status = self.status; + + return copy; +} + +#pragma mark - memory + +-(id)init { + if (self = [super init]) { + self.status = GBPingStatusPending; + } + + return self; +} + +-(void)dealloc { + self.host = nil; + self.ip = nil; + self.sendDate = nil; + self.receiveDate = nil; +} + +#pragma mark - description + +-(NSString *)description { + return [NSString stringWithFormat:@"host: %@, ip:%@, seq: %lu, status: %d, ttl: %lu, payloadSize: %lu, sendDate: %@, receiveDate: %@, rtt: %f", self.host, self.ip, (unsigned long)self.sequenceNumber, self.status, (unsigned long)self.ttl, (unsigned long)self.payloadSize, self.sendDate, self.receiveDate, self.rtt]; +} + +@end diff --git a/ios/ProxyPin/vpn/ping/ICMPHeader.h b/ios/ProxyPin/vpn/ping/ICMPHeader.h new file mode 100644 index 0000000..78ce033 --- /dev/null +++ b/ios/ProxyPin/vpn/ping/ICMPHeader.h @@ -0,0 +1,80 @@ +// +// ICMPHeader.h +// GBPing +// +// Created by Luka Mirosevic on 15/11/2012. +// Copyright (c) 2012 Goonbee. All rights reserved. +// + +#ifndef GBPing_ICMPHeader_h +#define GBPing_ICMPHeader_h + +#include + +#pragma mark - IP and ICMP On-The-Wire Format + +// The following declarations specify the structure of ping packets on the wire. + +// IP header structure: + +struct IPHeader { + uint8_t versionAndHeaderLength; + uint8_t differentiatedServices; + uint16_t totalLength; + uint16_t identification; + uint16_t flagsAndFragmentOffset; + uint8_t timeToLive; + uint8_t protocol; + uint16_t headerChecksum; + uint8_t sourceAddress[4]; + uint8_t destinationAddress[4]; + // options... + // data... +}; +typedef struct IPHeader IPHeader; + +__Check_Compile_Time(sizeof(IPHeader) == 20); +__Check_Compile_Time(offsetof(IPHeader, versionAndHeaderLength) == 0); +__Check_Compile_Time(offsetof(IPHeader, differentiatedServices) == 1); +__Check_Compile_Time(offsetof(IPHeader, totalLength) == 2); +__Check_Compile_Time(offsetof(IPHeader, identification) == 4); +__Check_Compile_Time(offsetof(IPHeader, flagsAndFragmentOffset) == 6); +__Check_Compile_Time(offsetof(IPHeader, timeToLive) == 8); +__Check_Compile_Time(offsetof(IPHeader, protocol) == 9); +__Check_Compile_Time(offsetof(IPHeader, headerChecksum) == 10); +__Check_Compile_Time(offsetof(IPHeader, sourceAddress) == 12); +__Check_Compile_Time(offsetof(IPHeader, destinationAddress) == 16); + +// ICMP type and code combinations: + +enum { + kICMPv4TypeEchoRequest = 8, + kICMPv4TypeEchoReply = 0 +}; + +enum { + kICMPv6TypeEchoRequest = 128, + kICMPv6TypeEchoReply = 129 +}; + +// ICMP header structure: + +struct ICMPHeader { + uint8_t type; + uint8_t code; + uint16_t checksum; + uint16_t identifier; + uint16_t sequenceNumber; + // data... +}; +typedef struct ICMPHeader ICMPHeader; + +__Check_Compile_Time(sizeof(ICMPHeader) == 8); +__Check_Compile_Time(offsetof(ICMPHeader, type) == 0); +__Check_Compile_Time(offsetof(ICMPHeader, code) == 1); +__Check_Compile_Time(offsetof(ICMPHeader, checksum) == 2); +__Check_Compile_Time(offsetof(ICMPHeader, identifier) == 4); +__Check_Compile_Time(offsetof(ICMPHeader, sequenceNumber) == 6); + + +#endif diff --git a/ios/ProxyPin/vpn/socket/ClientPacketWriter.swift b/ios/ProxyPin/vpn/socket/ClientPacketWriter.swift index de627c8..acbfb64 100644 --- a/ios/ProxyPin/vpn/socket/ClientPacketWriter.swift +++ b/ios/ProxyPin/vpn/socket/ClientPacketWriter.swift @@ -9,7 +9,6 @@ import NetworkExtension class ClientPacketWriter: NSObject { private var packetFlow: NEPacketTunnelFlow - private let packetQueue = DispatchQueue(label: "packetQueue", attributes: .concurrent) private var isShutdown = false init(packetFlow: NEPacketTunnelFlow) { @@ -17,17 +16,11 @@ class ClientPacketWriter: NSObject { } func write(data: Data) { - if !self.isShutdown { - packetQueue.async { - self.packetFlow.writePackets([data], withProtocols: [NSNumber(value: AF_INET)]) - } - } + self.packetFlow.writePackets([data], withProtocols: [NSNumber(value: AF_INET)]) } func shutdown() { - packetQueue.async(flags: .barrier) { - self.isShutdown = true - } + self.isShutdown = true } } diff --git a/ios/ProxyPin/vpn/socket/SocketIOService.swift b/ios/ProxyPin/vpn/socket/SocketIOService.swift index 1a3c3e0..de21ac3 100644 --- a/ios/ProxyPin/vpn/socket/SocketIOService.swift +++ b/ios/ProxyPin/vpn/socket/SocketIOService.swift @@ -28,29 +28,35 @@ class SocketIOService { queue.async(flags: .barrier) { self.shutdown = true } - queue.suspend() +// queue.suspend() } //从connection接受数据 写到client public func registerSession(connection: Connection) { + connection.channel!.stateUpdateHandler = { state in +// os_log("Connection %{public}@ state changed to %{public}@", log: OSLog.default, type: .default, connection.description, String(describing: state)) switch state { case .ready: connection.isConnected = true os_log("Connected to %{public}@ on receiveMessage", log: OSLog.default, type: .default, connection.description) + //接受远程服务器的数据 connection.sendToDestination() self.receiveMessage(connection: connection) case .cancelled: connection.isConnected = false -// os_log("Connection cancelled", log: OSLog.default, type: .default) + os_log("Connection cancelled %{public}@", log: OSLog.default, type: .default, connection.description) connection.closeConnection() + self.sendFin(connection: connection) case .failed(let error): connection.isConnected = false - os_log("Failed to connect: %{public}@", log: OSLog.default, type: .error, error.localizedDescription) + os_log("Failed to connect: %{public}@ %{public}@", log: OSLog.default, type: .error,connection.description, error.localizedDescription) connection.closeConnection() + self.sendFin(connection: connection) default: + os_log("Connection %{public}@ entered unhandled state: %{public}@", log: OSLog.default, type: .default, connection.description, String(describing: state)) break } } @@ -84,14 +90,14 @@ class SocketIOService { return } - queue.async { - guard let channel = connection.channel else { - os_log("Invalid channel type", log: OSLog.default, type: .error) - return - } - - channel.receive(minimumIncompleteLength: 0, maximumLength: Self.maxReceiveBufferSize) { (data, context, isComplete, error) in -// os_log("Received TCP data packet %{public}@ length %d", log: OSLog.default, type: .default, connection.description, data?.count ?? 0) + guard let channel = connection.channel else { + os_log("Invalid channel type", log: OSLog.default, type: .error) + return + } + + channel.receive(minimumIncompleteLength: 1, maximumLength: Self.maxReceiveBufferSize) { (data, context, isComplete, error) in + self.queue.async(flags: .barrier) { + os_log("Received TCP data packet %{public}@ length %d", log: OSLog.default, type: .default, connection.description, data?.count ?? 0) if let error = error { os_log("Failed to read from TCP socket: %@", log: OSLog.default, type: .error, error as CVarArg) self.sendFin(connection: connection) @@ -109,7 +115,6 @@ class SocketIOService { self.receiveMessage(connection: connection) if (isComplete) { - self.sendFin(connection: connection) connection.isAbortingConnection = true return } @@ -158,6 +163,10 @@ class SocketIOService { } private func sendFin(connection: Connection) { + if (connection.nwProtocol != .TCP) { + return + } + guard let ipHeader = connection.lastIpHeader, let tcpHeader = connection.lastTcpHeader else { os_log("Invalid ipHeader or tcpHeader", log: OSLog.default, type: .error) return @@ -177,13 +186,14 @@ class SocketIOService { } func readUDP(connection: Connection) { - queue.async { - guard let channel = connection.channel else { - os_log("Invalid channel type", log: OSLog.default, type: .error) - return - } + + guard let channel = connection.channel else { + os_log("Invalid channel type", log: OSLog.default, type: .error) + return + } - channel.receive(minimumIncompleteLength: 1, maximumLength: 4196) { (data, context, isComplete, error) in + channel.receive(minimumIncompleteLength: 1, maximumLength: 65507) { (data, context, isComplete, error) in + self.queue.async(flags: .barrier) { if let error = error { os_log("Failed to read from UDP socket: %@", log: OSLog.default, type: .error, error as CVarArg) connection.isAbortingConnection = true @@ -196,13 +206,16 @@ class SocketIOService { return } + guard let ipHeader = connection.lastIpHeader, let udpHeader = connection.lastUdpHeader else { + os_log("Missing IP or UDP header for connection %{public}@", log: OSLog.default, type: .error, connection.description) + return + } let packetData = UDPPacketFactory.createResponsePacket( - ip: connection.lastIpHeader!, - udp: connection.lastUdpHeader!, + ip: ipHeader, + udp: udpHeader, packetData: data ) -// os_log("Sending UDP data packet to client", log: OSLog.default, type: .default) self.clientPacketWriter.writePackets([packetData], withProtocols: [NSNumber(value: AF_INET)]) @@ -210,5 +223,5 @@ class SocketIOService { self.receiveMessage(connection: connection) } } - } + } } diff --git a/ios/ProxyPin/vpn/transport/protocol/UDPHeader.swift b/ios/ProxyPin/vpn/transport/protocol/UDPHeader.swift index 4423b56..31e4592 100644 --- a/ios/ProxyPin/vpn/transport/protocol/UDPHeader.swift +++ b/ios/ProxyPin/vpn/transport/protocol/UDPHeader.swift @@ -23,7 +23,6 @@ struct UDPHeader { } } - class UDPPacketFactory { static let UDP_HEADER_LENGTH = 8 @@ -40,15 +39,17 @@ class UDPPacketFactory { return UDPHeader(sourcePort: srcPort, destinationPort: destPort, length: length, checksum: checksum) } - + + // static func createResponsePacket(ip: IP4Header, udp: UDPHeader, packetData: Data?) -> Data { var udpLen = 8 if let packetData = packetData { udpLen += packetData.count } + let srcPort = udp.destinationPort let destPort = udp.sourcePort - + let ipHeader = ip.copy() let srcIp = ip.destinationIP let destIp = ip.sourceIP @@ -63,15 +64,13 @@ class UDPPacketFactory { ipHeader.totalLength = UInt16(totalLength) var ipData = ipHeader.toBytes() - + // clear IP checksum ipData.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer) in bytes[10] = 0 bytes[11] = 0 } - //os_log("Create UDP response packet from %{public}@:%{public}d to %{public}@:%{public}d totalLength:%{public}d", log: OSLog.default, type: .default, PacketUtil.intToIPAddress(srcIp), srcPort, PacketUtil.intToIPAddress(destIp), destPort, totalLength) - // calculate checksum for IP header let ipChecksum = PacketUtil.calculateChecksum(data: ipData, offset: 0, length: ipData.count) @@ -89,24 +88,16 @@ class UDPPacketFactory { // copy UDP header to buffer buffer.append(contentsOf: srcPort.bytes) buffer.append(contentsOf: destPort.bytes) - buffer.append(contentsOf: UInt16(udpLen).bytes) - let checksum: UInt16 = 0 - buffer.append(contentsOf: checksum.bytes) + // 计算UDP校验和 + let udpChecksum: UInt16 = 0 + buffer.append(contentsOf: udpChecksum.bytes) if let packetData = packetData { - buffer.append(packetData) + buffer.append(packetData) } return buffer } - //打印数据包 - public static func printPacket(data: Data) { - guard let udpHeader = createUDPHeader(from: data) else { - return - } - os_log("UDP Header: sourcePort: %{public}d, destinationPort: %{public}d, length: %{public}d, checksum: %{public}d", log: OSLog.default, type: .default, udpHeader.sourcePort, udpHeader.destinationPort, udpHeader.length, udpHeader.checksum) - } - } diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 766c348..1cc12a2 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 54; + objectVersion = 55; objects = { /* Begin PBXBuildFile section */ @@ -25,6 +25,9 @@ 9B70772D2A5718FB00F184A9 /* AudioManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B70772C2A5718FB00F184A9 /* AudioManager.swift */; }; 9B7077362A5728B900F184A9 /* silence.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 9B7077352A5728B900F184A9 /* silence.mp3 */; }; 9B90F5802C183CDE007D7A81 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 9B90F5822C183CDE007D7A81 /* InfoPlist.strings */; }; + 9BAB4FC02DE75CFE0093BFBA /* GBPing.m in Sources */ = {isa = PBXBuildFile; fileRef = 9BAB4FBC2DE75CFE0093BFBA /* GBPing.m */; }; + 9BAB4FC12DE75CFE0093BFBA /* GBPingSummary.m in Sources */ = {isa = PBXBuildFile; fileRef = 9BAB4FBE2DE75CFE0093BFBA /* GBPingSummary.m */; }; + 9BAB4FC32DE75D220093BFBA /* GBPingHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BAB4FC22DE75D220093BFBA /* GBPingHelper.swift */; }; 9BC4B8CC2B4B48710047DBDD /* PictureInPictureManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BC4B8CB2B4B48710047DBDD /* PictureInPictureManager.swift */; }; 9BCA28662C9772DD00C2B46C /* ConnectionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BCA28652C9772DD00C2B46C /* ConnectionHandler.swift */; }; 9BCA286A2C97748100C2B46C /* IP4Header.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BCA28692C97748100C2B46C /* IP4Header.swift */; }; @@ -124,6 +127,13 @@ 9B90F57D2C183C7E007D7A81 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/LaunchScreen.strings"; sourceTree = ""; }; 9B90F5812C183CDE007D7A81 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 9B90F5832C183CE0007D7A81 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/InfoPlist.strings"; sourceTree = ""; }; + 9BAB4FBB2DE75CFE0093BFBA /* GBPing.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GBPing.h; sourceTree = ""; }; + 9BAB4FBC2DE75CFE0093BFBA /* GBPing.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GBPing.m; sourceTree = ""; }; + 9BAB4FBD2DE75CFE0093BFBA /* GBPingSummary.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GBPingSummary.h; sourceTree = ""; }; + 9BAB4FBE2DE75CFE0093BFBA /* GBPingSummary.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GBPingSummary.m; sourceTree = ""; }; + 9BAB4FBF2DE75CFE0093BFBA /* ICMPHeader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ICMPHeader.h; sourceTree = ""; }; + 9BAB4FC22DE75D220093BFBA /* GBPingHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GBPingHelper.swift; sourceTree = ""; }; + 9BAB4FC42DE75E9A0093BFBA /* ProxyPin-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ProxyPin-Bridging-Header.h"; sourceTree = ""; }; 9BC4B8CB2B4B48710047DBDD /* PictureInPictureManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PictureInPictureManager.swift; sourceTree = ""; }; 9BCA28652C9772DD00C2B46C /* ConnectionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionHandler.swift; sourceTree = ""; }; 9BCA28692C97748100C2B46C /* IP4Header.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IP4Header.swift; sourceTree = ""; }; @@ -266,10 +276,24 @@ 9B0912242A54593A001108B7 /* PacketTunnelProvider.swift */, 9B0912262A54593A001108B7 /* Info.plist */, 9B0912272A54593A001108B7 /* ProxyPin.entitlements */, + 9BAB4FC42DE75E9A0093BFBA /* ProxyPin-Bridging-Header.h */, ); path = ProxyPin; sourceTree = ""; }; + 9BAB4FB12DE74F570093BFBA /* ping */ = { + isa = PBXGroup; + children = ( + 9BAB4FC22DE75D220093BFBA /* GBPingHelper.swift */, + 9BAB4FBB2DE75CFE0093BFBA /* GBPing.h */, + 9BAB4FBC2DE75CFE0093BFBA /* GBPing.m */, + 9BAB4FBD2DE75CFE0093BFBA /* GBPingSummary.h */, + 9BAB4FBE2DE75CFE0093BFBA /* GBPingSummary.m */, + 9BAB4FBF2DE75CFE0093BFBA /* ICMPHeader.h */, + ); + path = ping; + sourceTree = ""; + }; 9BC4B8D12B4C19ED0047DBDD /* pip */ = { isa = PBXGroup; children = ( @@ -283,6 +307,7 @@ 9BCA28642C97729000C2B46C /* vpn */ = { isa = PBXGroup; children = ( + 9BAB4FB12DE74F570093BFBA /* ping */, 9BCA287B2C989A8700C2B46C /* socket */, 9BCA28762C98901800C2B46C /* utils */, 9BCA28672C97746200C2B46C /* transport */, @@ -420,6 +445,7 @@ }; 9B09121F2A54593A001108B7 = { CreatedOnToolsVersion = 14.2; + LastSwiftMigration = 1630; }; }; }; @@ -484,10 +510,14 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", ); + inputPaths = ( + ); name = "[CP] Copy Pods Resources"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", ); + outputPaths = ( + ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; @@ -539,10 +569,14 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); + inputPaths = ( + ); name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); + outputPaths = ( + ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; @@ -625,9 +659,12 @@ 9BCA288C2C995B3700C2B46C /* UDPHeader.swift in Sources */, 9BCA28732C988E9D00C2B46C /* Packet.swift in Sources */, 9BCA288A2C98C82000C2B46C /* SocketIOService.swift in Sources */, + 9BAB4FC02DE75CFE0093BFBA /* GBPing.m in Sources */, + 9BAB4FC12DE75CFE0093BFBA /* GBPingSummary.m in Sources */, 9BCA28852C98C6B300C2B46C /* QueueFactory.swift in Sources */, 9BCA286A2C97748100C2B46C /* IP4Header.swift in Sources */, 9BCA287A2C989A7200C2B46C /* Connection.swift in Sources */, + 9BAB4FC32DE75D220093BFBA /* GBPingHelper.swift in Sources */, 9BCA28752C988EC400C2B46C /* TransportHeader.swift in Sources */, 9BCA287F2C989AF300C2B46C /* NWProtocol.swift in Sources */, 9BCA286F2C977E4C00C2B46C /* TCPPacketFactory.swift in Sources */, @@ -971,6 +1008,7 @@ buildSettings = { CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; @@ -998,6 +1036,7 @@ SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OBJC_BRIDGING_HEADER = "ProxyPin/ProxyPin-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -1009,6 +1048,7 @@ buildSettings = { CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; @@ -1034,6 +1074,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OBJC_BRIDGING_HEADER = "ProxyPin/ProxyPin-Bridging-Header.h"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; @@ -1044,6 +1085,7 @@ buildSettings = { CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; @@ -1069,6 +1111,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OBJC_BRIDGING_HEADER = "ProxyPin/ProxyPin-Bridging-Header.h"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 25cc178..e24fb59 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -74,5 +74,7 @@ NSAllowsArbitraryLoads + NSLocalNetworkUsageDescription + Remote Device Connect diff --git a/ios/Runner/VpnManager.swift b/ios/Runner/VpnManager.swift index 4ba427c..a3c89d0 100755 --- a/ios/Runner/VpnManager.swift +++ b/ios/Runner/VpnManager.swift @@ -48,8 +48,13 @@ class VpnManager{ if let manager = manager { self.observerAdded = true NotificationCenter.default.addObserver(forName: NSNotification.Name.NEVPNStatusDidChange, object: manager.connection, queue: OperationQueue.main, using: { [unowned self] (notification) -> Void in + self.updateVPNStatus(manager) - }) + + if (manager.connection.status == .invalid || manager.connection.status == .disconnected){ + print("VPN断开: \(String(describing: manager.debugDescription))") + } + }) } } } diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 367f019..867d212 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -252,7 +252,7 @@ "pullConfigFail": "Failed to pull configuration, please check the network connection", "sync": "Sync", "invalidQRCode": "Unrecognized QR code", - "remoteConnectFail": "connection failed,Please check if it is allowed on the same LAN and firewall", + "remoteConnectFail": "Connection failed,Please check if it is allowed on the same LAN and firewall", "remoteConnectSuccessTips": "Your phone needs to enable packet capture in order to capture requests", "windowMode": "Window Mode", diff --git a/lib/ui/component/app_dialog.dart b/lib/ui/component/app_dialog.dart index e378750..b7100d4 100644 --- a/lib/ui/component/app_dialog.dart +++ b/lib/ui/component/app_dialog.dart @@ -105,13 +105,13 @@ class CustomToast extends StatelessWidget { ); } - void show(BuildContext context) { + void show(BuildContext context, {Alignment alignment = Alignment.bottomLeft}) { toastification.show( context: context, title: Text(message), icon: icon == null ? null : Icon(icon), type: type._toastificationType, - alignment: Alignment.bottomLeft, + alignment: alignment, autoCloseDuration: duration, style: ToastificationStyle.flat, pauseOnHover: true, diff --git a/lib/ui/mobile/widgets/remote_device.dart b/lib/ui/mobile/widgets/remote_device.dart index bd234dd..dbf23b6 100644 --- a/lib/ui/mobile/widgets/remote_device.dart +++ b/lib/ui/mobile/widgets/remote_device.dart @@ -28,6 +28,7 @@ import 'package:proxypin/network/components/manager/request_rewrite_manager.dart import 'package:proxypin/network/components/manager/script_manager.dart'; import 'package:proxypin/network/http/http_client.dart'; import 'package:proxypin/network/util/logger.dart'; +import 'package:proxypin/ui/component/app_dialog.dart'; import 'package:proxypin/ui/component/qrcode/qr_scan_view.dart'; import 'package:proxypin/ui/component/utils.dart'; import 'package:proxypin/ui/component/widgets.dart'; @@ -58,7 +59,12 @@ class RemoteModel { factory RemoteModel.fromJson(Map json) { return RemoteModel( - connect: json['connect'], host: json['host'], port: json['port'], os: json['os'], hostname: json['hostname']); + connect: json['connect'], + host: json['host'], + port: json['port'], + os: json['os'], + hostname: json['hostname'], + ipProxy: json['ipProxy'] == true); } RemoteModel copyWith({ @@ -87,7 +93,7 @@ class RemoteModel { } Map toJson() { - return {'connect': connect, 'host': host, 'port': port, 'os': os, 'hostname': hostname}; + return {'connect': connect, 'host': host, 'port': port, 'os': os, 'hostname': hostname, 'ipProxy': ipProxy}; } } @@ -390,6 +396,7 @@ class _RemoteDevicePageState extends State { Future doConnect(String host, int port, {bool? ipProxy}) async { if (doConnecting) return false; doConnecting = true; + try { var response = await HttpClients.get("http://$host:$port/ping", timeout: const Duration(milliseconds: 3000)); if (response.bodyAsString == "pong") { @@ -424,11 +431,9 @@ class _RemoteDevicePageState extends State { } catch (e) { logger.e(e); if (mounted) { - showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog(content: Text(localizations.remoteConnectFail)); - }); + if (mounted) { + CustomToast.error(localizations.remoteConnectFail).show(context, alignment: Alignment.topCenter); + } } return false; } finally {