summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj10
-rw-r--r--ios/MullvadVPN.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved2
-rw-r--r--ios/PacketTunnelCore/Pinger/Pinger.swift317
-rw-r--r--ios/PacketTunnelCore/Pinger/TunnelPinger.swift41
-rw-r--r--ios/PacketTunnelCoreTests/PingerTests.swift33
5 files changed, 23 insertions, 380 deletions
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj
index a2e87c4f98..82506832af 100644
--- a/ios/MullvadVPN.xcodeproj/project.pbxproj
+++ b/ios/MullvadVPN.xcodeproj/project.pbxproj
@@ -279,7 +279,6 @@
58C7A4492A863F490060C66F /* PacketTunnelCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 58C7A4362A863F440060C66F /* PacketTunnelCore.framework */; };
58C7A44A2A863F490060C66F /* PacketTunnelCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 58C7A4362A863F440060C66F /* PacketTunnelCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
58C7A4512A863FB50060C66F /* PingerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58799A352A84FC9F007BE51F /* PingerProtocol.swift */; };
- 58C7A4522A863FB50060C66F /* Pinger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5838318A27C40A3900000571 /* Pinger.swift */; };
58C7A4552A863FB90060C66F /* TunnelMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FC040927B3EE03001C21F0 /* TunnelMonitor.swift */; };
58C7A4562A863FB90060C66F /* DefaultPathObserverProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58225D252A84E8A10083D7F1 /* DefaultPathObserverProtocol.swift */; };
58C7A4572A863FB90060C66F /* TunnelDeviceInfoProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 582403162A821FD700163DE8 /* TunnelDeviceInfoProtocol.swift */; };
@@ -289,7 +288,6 @@
58C7A45C2A8640490060C66F /* MullvadLogging.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 58D223F3294C8FF00029F5F8 /* MullvadLogging.framework */; };
58C7A4692A8643A90060C66F /* IPv4Header.h in Headers */ = {isa = PBXBuildFile; fileRef = 58218E1428B65058000C624F /* IPv4Header.h */; settings = {ATTRIBUTES = (Public, ); }; };
58C7A46A2A8643A90060C66F /* ICMPHeader.h in Headers */ = {isa = PBXBuildFile; fileRef = 58218E1628B65396000C624F /* ICMPHeader.h */; settings = {ATTRIBUTES = (Public, ); }; };
- 58C7A4702A8649ED0060C66F /* PingerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58C7A46F2A8649ED0060C66F /* PingerTests.swift */; };
58C7AF112ABD8480007EDD7A /* TunnelProviderMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 585DA89226B0323E00B8C587 /* TunnelProviderMessage.swift */; };
58C7AF122ABD8480007EDD7A /* TunnelProviderReply.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5898D2A7290182B000EB5EBA /* TunnelProviderReply.swift */; };
58C7AF162ABD84A8007EDD7A /* URLRequestProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58D229B6298D1D5200BB5A2D /* URLRequestProxy.swift */; };
@@ -1476,7 +1474,6 @@
582FFA82290A84E700895745 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
58342C032AAB61FB003BA12D /* State+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "State+Extensions.swift"; sourceTree = "<group>"; };
5835B7CB233B76CB0096D79F /* TunnelManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelManager.swift; sourceTree = "<group>"; };
- 5838318A27C40A3900000571 /* Pinger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Pinger.swift; sourceTree = "<group>"; };
5838321A2AC1B18400EA2071 /* PacketTunnelActor+Mocks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PacketTunnelActor+Mocks.swift"; sourceTree = "<group>"; };
5838321C2AC1C54600EA2071 /* TaskSleepTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskSleepTests.swift; sourceTree = "<group>"; };
5838321E2AC3160A00EA2071 /* PacketTunnelActor+KeyPolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PacketTunnelActor+KeyPolicy.swift"; sourceTree = "<group>"; };
@@ -1673,7 +1670,6 @@
58C7A4362A863F440060C66F /* PacketTunnelCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = PacketTunnelCore.framework; sourceTree = BUILT_PRODUCTS_DIR; };
58C7A4382A863F450060C66F /* PacketTunnelCore.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PacketTunnelCore.h; sourceTree = "<group>"; };
58C7A43D2A863F460060C66F /* PacketTunnelCoreTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PacketTunnelCoreTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
- 58C7A46F2A8649ED0060C66F /* PingerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PingerTests.swift; sourceTree = "<group>"; };
58C8191729FAA2C400DEB1B4 /* NotificationConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationConfiguration.swift; sourceTree = "<group>"; };
58CAF9F72983D36800BE19F7 /* Coordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coordinator.swift; sourceTree = "<group>"; };
58CAF9FF2983FF0200BE19F7 /* LoginInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginInteractor.swift; sourceTree = "<group>"; };
@@ -3361,7 +3357,6 @@
children = (
58218E1628B65396000C624F /* ICMPHeader.h */,
58218E1428B65058000C624F /* IPv4Header.h */,
- 5838318A27C40A3900000571 /* Pinger.swift */,
58799A352A84FC9F007BE51F /* PingerProtocol.swift */,
449275412C3570CA000526DE /* ICMP.swift */,
449275432C3C3029000526DE /* TunnelPinger.swift */,
@@ -3392,7 +3387,6 @@
58EC067D2A8D2B0700BEB973 /* Mocks */,
F0C4C9BD2C49477B00A79006 /* MultiHopEphemeralPeerExchangerTests.swift */,
58FE25D32AA729B5003D1918 /* PacketTunnelActorTests.swift */,
- 58C7A46F2A8649ED0060C66F /* PingerTests.swift */,
A97D25B12B0CB02D00946B2D /* ProtocolObfuscatorTests.swift */,
F0A163882C47B46300592300 /* SingleHopEphemeralPeerExchangerTests.swift */,
5838321C2AC1C54600EA2071 /* TaskSleepTests.swift */,
@@ -5489,7 +5483,6 @@
files = (
58FE25F42AA9D730003D1918 /* PacketTunnelActor+Extensions.swift in Sources */,
58DDA18F2ABC32380039C360 /* Timings.swift in Sources */,
- 58C7A4522A863FB50060C66F /* Pinger.swift in Sources */,
580D6B8C2AB3369300B2D6E0 /* BlockedStateErrorMapperProtocol.swift in Sources */,
58C7AF172ABD84AA007EDD7A /* ProxyURLRequest.swift in Sources */,
5838321F2AC3160A00EA2071 /* PacketTunnelActor+KeyPolicy.swift in Sources */,
@@ -5576,7 +5569,6 @@
58FE25D42AA729B5003D1918 /* PacketTunnelActorTests.swift in Sources */,
F07751572C50F149006E6A12 /* EphemeralPeerExchangingPipelineTests.swift in Sources */,
7A3FD1B52AD4465A0042BEA6 /* AppMessageHandlerTests.swift in Sources */,
- 58C7A4702A8649ED0060C66F /* PingerTests.swift in Sources */,
A97D25B22B0CB02D00946B2D /* ProtocolObfuscatorTests.swift in Sources */,
F062B94D2C16E09700B6D47A /* TunnelSettingsManagerTests.swift in Sources */,
);
@@ -9232,7 +9224,7 @@
repositoryURL = "https://github.com/mullvad/wireguard-apple.git";
requirement = {
kind = revision;
- revision = 5d5fbf1af490c2ec893cae908f5a204ac6f0da46;
+ revision = f1401d43f9d03438a81ca806b9f0c20269b116cb;
};
};
/* End XCRemoteSwiftPackageReference section */
diff --git a/ios/MullvadVPN.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ios/MullvadVPN.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
index 0ded07e5b3..09aa5c9a58 100644
--- a/ios/MullvadVPN.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
+++ b/ios/MullvadVPN.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -14,7 +14,7 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/mullvad/wireguard-apple.git",
"state" : {
- "revision" : "5d5fbf1af490c2ec893cae908f5a204ac6f0da46"
+ "revision" : "f1401d43f9d03438a81ca806b9f0c20269b116cb"
}
}
],
diff --git a/ios/PacketTunnelCore/Pinger/Pinger.swift b/ios/PacketTunnelCore/Pinger/Pinger.swift
deleted file mode 100644
index 69ae1ab5af..0000000000
--- a/ios/PacketTunnelCore/Pinger/Pinger.swift
+++ /dev/null
@@ -1,317 +0,0 @@
-//
-// Pinger.swift
-// PacketTunnelCore
-//
-// Created by pronebird on 21/02/2022.
-// Copyright © 2022 Mullvad VPN AB. All rights reserved.
-//
-
-import Foundation
-import Network
-
-// This is the legacy Pinger using native TCP/IP networking.
-
-/// ICMP client.
-public final class Pinger: PingerProtocol {
- // Socket read buffer size.
- private static let bufferSize = 65535
-
- // Sender identifier passed along with ICMP packet.
- private let identifier: UInt16
-
- private var sequenceNumber: UInt16 = 0
- private var socket: CFSocket?
- private var readBuffer = [UInt8](repeating: 0, count: bufferSize)
- private let stateLock = NSRecursiveLock()
- private let replyQueue: DispatchQueue
- private var destAddress: IPv4Address?
-
- public var onReply: ((PingerReply) -> Void)? {
- get {
- stateLock.withLock {
- return _onReply
- }
- }
- set {
- stateLock.withLock {
- _onReply = newValue
- }
- }
- }
-
- private var _onReply: ((PingerReply) -> Void)?
-
- deinit {
- closeSocket()
- }
-
- public init(identifier: UInt16 = 757, replyQueue: DispatchQueue) {
- self.identifier = identifier
- self.replyQueue = replyQueue
- }
-
- /// Open socket and optionally bind it to the given interface.
- /// Automatically closes the previously opened socket when called multiple times in a row.
- public func openSocket(bindTo interfaceName: String?, destAddress: IPv4Address) throws {
- stateLock.lock()
- defer { stateLock.unlock() }
-
- closeSocket()
-
- self.destAddress = destAddress
-
- var context = CFSocketContext()
- context.info = Unmanaged.passUnretained(self).toOpaque()
-
- guard let newSocket = CFSocketCreate(
- kCFAllocatorDefault,
- AF_INET,
- SOCK_DGRAM,
- IPPROTO_ICMP,
- CFSocketCallBackType.readCallBack.rawValue,
- { socket, callbackType, _, _, info in
- guard let socket, let info, callbackType == .readCallBack else {
- return
- }
-
- let pinger = Unmanaged<Pinger>.fromOpaque(info).takeUnretainedValue()
-
- pinger.readSocket(socket)
- },
- &context
- ) else {
- throw Error.createSocket
- }
-
- let flags = CFSocketGetSocketFlags(newSocket)
- if (flags & kCFSocketCloseOnInvalidate) == 0 {
- CFSocketSetSocketFlags(newSocket, flags | kCFSocketCloseOnInvalidate)
- }
-
- if let interfaceName {
- try bindSocket(newSocket, to: interfaceName)
- }
-
- guard let runLoop = CFSocketCreateRunLoopSource(kCFAllocatorDefault, newSocket, 0) else {
- throw Error.createRunLoop
- }
-
- CFRunLoopAddSource(CFRunLoopGetMain(), runLoop, .commonModes)
-
- socket = newSocket
- }
-
- public func closeSocket() {
- stateLock.lock()
- defer { stateLock.unlock() }
-
- if let socket {
- CFSocketInvalidate(socket)
-
- self.socket = nil
- }
- }
-
- /// Send ping packet to the given address.
- /// Returns `PingerSendResult` on success, otherwise throws a `Pinger.Error`.
- public func send() throws -> PingerSendResult {
- stateLock.lock()
- defer { stateLock.unlock() }
-
- guard let socket else {
- throw Error.closedSocket
- }
-
- guard let destAddress else {
- throw Error.parseIPAddress
- }
-
- var sa = sockaddr_in()
- sa.sin_len = UInt8(MemoryLayout.size(ofValue: sa))
- sa.sin_family = sa_family_t(AF_INET)
- sa.sin_addr = destAddress.rawValue.withUnsafeBytes { buffer in
- buffer.bindMemory(to: in_addr.self).baseAddress!.pointee
- }
-
- let sequenceNumber = nextSequenceNumber()
- let packetData = ICMP.createICMPPacket(
- identifier: identifier,
- sequenceNumber: sequenceNumber
- )
-
- let bytesSent = packetData.withUnsafeBytes { dataBuffer -> Int in
- withUnsafeBytes(of: &sa) { bufferPointer in
- let sockaddrPointer = bufferPointer.bindMemory(to: sockaddr.self).baseAddress!
-
- return sendto(
- CFSocketGetNative(socket),
- dataBuffer.baseAddress!,
- dataBuffer.count,
- 0,
- sockaddrPointer,
- socklen_t(MemoryLayout<sockaddr_in>.size)
- )
- }
- }
-
- guard bytesSent >= 0 else {
- throw Error.sendPacket(errno)
- }
-
- return PingerSendResult(sequenceNumber: sequenceNumber)
- }
-
- private func nextSequenceNumber() -> UInt16 {
- stateLock.lock()
- let (nextValue, _) = sequenceNumber.addingReportingOverflow(1)
- sequenceNumber = nextValue
- stateLock.unlock()
-
- return nextValue
- }
-
- private func readSocket(_ socket: CFSocket) {
- var address = sockaddr()
- var addressLength = socklen_t(MemoryLayout.size(ofValue: address))
-
- let bytesRead = recvfrom(
- CFSocketGetNative(socket),
- &readBuffer,
- Self.bufferSize,
- 0,
- &address,
- &addressLength
- )
-
- do {
- guard bytesRead > 0 else { throw Error.receivePacket(errno) }
-
- let icmpHeader = try ICMP.parseICMPResponse(buffer: &readBuffer, length: bytesRead)
- guard icmpHeader.identifier == identifier else {
- throw Error.clientIdentifierMismatch
- }
- guard icmpHeader.type == ICMP_ECHOREPLY else {
- throw Error.invalidICMPType(icmpHeader.type)
- }
- guard let sender = Self.makeIPAddress(from: address) else { throw Error.parseIPAddress }
-
- replyQueue.async {
- self.onReply?(.success(sender, icmpHeader.sequenceNumber))
- }
- } catch Pinger.Error.clientIdentifierMismatch {
- // Ignore responses from other senders.
- } catch {
- replyQueue.async {
- self.onReply?(.parseError(error))
- }
- }
- }
-
- private func bindSocket(_ socket: CFSocket, to interfaceName: String) throws {
- var index = if_nametoindex(interfaceName)
- guard index > 0 else {
- throw Error.mapInterfaceNameToIndex(errno)
- }
-
- let result = setsockopt(
- CFSocketGetNative(socket),
- IPPROTO_IP,
- IP_BOUND_IF,
- &index,
- socklen_t(MemoryLayout.size(ofValue: index))
- )
-
- if result == -1 {
- throw Error.bindSocket(errno)
- }
- }
-
- private static func makeIPAddress(from sa: sockaddr) -> IPAddress? {
- if sa.sa_family == AF_INET {
- return withUnsafeBytes(of: sa) { buffer -> IPAddress? in
- buffer.bindMemory(to: sockaddr_in.self).baseAddress.flatMap { boundPointer in
- var saddr = boundPointer.pointee
- let data = Data(bytes: &saddr.sin_addr, count: MemoryLayout<in_addr>.size)
-
- return IPv4Address(data, nil)
- }
- }
- }
-
- if sa.sa_family == AF_INET6 {
- return withUnsafeBytes(of: sa) { buffer in
- buffer.bindMemory(to: sockaddr_in6.self).baseAddress
- .flatMap { boundPointer in
- var saddr6 = boundPointer.pointee
- let data = Data(
- bytes: &saddr6.sin6_addr,
- count: MemoryLayout<in6_addr>.size
- )
-
- return IPv6Address(data)
- }
- }
- }
-
- return nil
- }
-}
-
-extension Pinger {
- public enum Error: LocalizedError {
- /// Failure to create a socket.
- case createSocket
-
- /// Failure to map interface name to index.
- case mapInterfaceNameToIndex(Int32)
-
- /// Failure to bind socket to interface.
- case bindSocket(Int32)
-
- /// Failure to create a runloop for socket.
- case createRunLoop
-
- /// Failure to send a packet due to socket being closed.
- case closedSocket
-
- /// Failure to send packet. Contains the `errno`.
- case sendPacket(Int32)
-
- /// Failure to receive packet. Contains the `errno`.
- case receivePacket(Int32)
-
- /// Unexpected ICMP reply type
- case invalidICMPType(UInt8)
-
- /// Response identifier does not match the sender identifier.
- case clientIdentifierMismatch
-
- /// Failure to parse IP address.
- case parseIPAddress
-
- public var errorDescription: String? {
- switch self {
- case .createSocket:
- return "Failure to create socket."
- case .mapInterfaceNameToIndex:
- return "Failure to map interface name to index."
- case .bindSocket:
- return "Failure to bind socket to interface."
- case .createRunLoop:
- return "Failure to create run loop for socket."
- case .closedSocket:
- return "Socket is closed."
- case let .sendPacket(code):
- return "Failure to send packet (errno: \(code))."
- case let .receivePacket(code):
- return "Failure to receive packet (errno: \(code))."
- case let .invalidICMPType(type):
- return "Unexpected ICMP reply type: \(type)"
- case .clientIdentifierMismatch:
- return "Response identifier does not match the sender identifier."
- case .parseIPAddress:
- return "Failed to parse IP address."
- }
- }
- }
-}
diff --git a/ios/PacketTunnelCore/Pinger/TunnelPinger.swift b/ios/PacketTunnelCore/Pinger/TunnelPinger.swift
index f011fc9a01..9b9162f2e5 100644
--- a/ios/PacketTunnelCore/Pinger/TunnelPinger.swift
+++ b/ios/PacketTunnelCore/Pinger/TunnelPinger.swift
@@ -15,7 +15,7 @@ import WireGuardKit
public final class TunnelPinger: PingerProtocol {
private var sequenceNumber: UInt16 = 0
private let stateLock = NSRecursiveLock()
- private let pingQueue: DispatchQueue
+ private let pingReceiveQueue: DispatchQueue
private let replyQueue: DispatchQueue
private var destAddress: IPv4Address?
private var _onReply: ((PingerReply) -> Void)?
@@ -39,7 +39,7 @@ public final class TunnelPinger: PingerProtocol {
init(pingProvider: ICMPPingProvider, replyQueue: DispatchQueue) {
self.pingProvider = pingProvider
self.replyQueue = replyQueue
- self.pingQueue = DispatchQueue(label: "PacketTunnel.icmp")
+ self.pingReceiveQueue = DispatchQueue(label: "PacketTunnel.Receive.icmp")
self.logger = Logger(label: "TunnelPinger")
}
@@ -50,6 +50,22 @@ public final class TunnelPinger: PingerProtocol {
public func openSocket(bindTo interfaceName: String?, destAddress: IPv4Address) throws {
try pingProvider.openICMP(address: destAddress)
self.destAddress = destAddress
+ pingReceiveQueue.async { [weak self] in
+ while let self {
+ do {
+ let seq = try pingProvider.receiveICMP()
+
+ replyQueue.async { [weak self] in
+ self?.onReply?(PingerReply.success(destAddress, UInt16(seq)))
+ }
+ } catch {
+ replyQueue.async { [weak self] in
+ self?.onReply?(PingerReply.parseError(error))
+ }
+ return
+ }
+ }
+ }
}
public func closeSocket() {
@@ -59,25 +75,10 @@ public final class TunnelPinger: PingerProtocol {
public func send() throws -> PingerSendResult {
let sequenceNumber = nextSequenceNumber()
- logger.debug("*** sending ping \(sequenceNumber)")
- pingQueue.async { [weak self] in
- guard let self, let destAddress else { return }
- let reply: PingerReply
- do {
- try pingProvider.sendICMPPing(seqNumber: sequenceNumber)
- // NOTE: we cheat here by returning the destination address we were passed, rather than parsing it from the packet on the other side of the FFI boundary.
- reply = .success(destAddress, sequenceNumber)
- } catch {
- reply = .parseError(error)
- }
- self.logger.debug("--- Pinger reply: \(reply)")
-
- replyQueue.async { [weak self] in
- guard let self else { return }
- self.onReply?(reply)
- }
- }
+ guard let destAddress else { throw WireGuardAdapterError.invalidState }
+ // NOTE: we cheat here by returning the destination address we were passed, rather than parsing it from the packet on the other side of the FFI boundary.
+ try pingProvider.sendICMPPing(seqNumber: sequenceNumber)
return PingerSendResult(sequenceNumber: UInt16(sequenceNumber))
}
diff --git a/ios/PacketTunnelCoreTests/PingerTests.swift b/ios/PacketTunnelCoreTests/PingerTests.swift
deleted file mode 100644
index e849b0fda2..0000000000
--- a/ios/PacketTunnelCoreTests/PingerTests.swift
+++ /dev/null
@@ -1,33 +0,0 @@
-//
-// PingerTests.swift
-// PacketTunnelCoreTests
-//
-// Created by pronebird on 11/08/2023.
-// Copyright © 2023 Mullvad VPN AB. All rights reserved.
-//
-
-@testable import MullvadMockData
-import Network
-import PacketTunnelCore
-import XCTest
-
-final class PingerTests: XCTestCase {
- func testPingingLocalhost() throws {
- let expectation = self.expectation(description: "Wait for ping reply.")
- let pinger = Pinger(identifier: 1234, replyQueue: .main)
-
- var sendResult: PingerSendResult?
-
- pinger.onReply = { reply in
- if case let .success(sender, sequenceNumber) = reply, sendResult?.sequenceNumber == sequenceNumber {
- XCTAssertTrue(sender.isLoopback)
- expectation.fulfill()
- }
- }
-
- try pinger.openSocket(bindTo: "lo0", destAddress: .loopback)
- sendResult = try pinger.send()
-
- waitForExpectations(timeout: .UnitTest.timeout)
- }
-}