summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAndrej Mihajlov <and@mullvad.net>2023-08-16 13:08:43 +0200
committerAndrej Mihajlov <and@mullvad.net>2023-08-16 13:08:43 +0200
commit436a87c0f0bd91db940643cf5f72f066243ae49e (patch)
tree4bb7469fe157639cf1877672d824830f14175445
parentc06d09878954bc775727ed34d2ccaff30037c7cd (diff)
parent75391574abec9dcf5460c96e32069751c9644ec3 (diff)
downloadmullvadvpn-436a87c0f0bd91db940643cf5f72f066243ae49e.tar.xz
mullvadvpn-436a87c0f0bd91db940643cf5f72f066243ae49e.zip
Merge branch 'add-packet-tunnel-core'
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj394
-rw-r--r--ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadVPN.xcscheme41
-rw-r--r--ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/PacketTunnelCore.xcscheme66
-rw-r--r--ios/PacketTunnel/ObjCBridgingHeader.h15
-rw-r--r--ios/PacketTunnel/PacketTunnelPathObserver.swift48
-rw-r--r--ios/PacketTunnel/PacketTunnelProvider.swift38
-rw-r--r--ios/PacketTunnel/TunnelMonitor/TunnelMonitorDelegate.swift23
-rw-r--r--ios/PacketTunnel/TunnelMonitor/WgStats.swift51
-rw-r--r--ios/PacketTunnel/WgAdapterDeviceInfo.swift84
-rw-r--r--ios/PacketTunnelCore/PacketTunnelCore.h19
-rw-r--r--ios/PacketTunnelCore/Pinger/ICMPHeader.h (renamed from ios/PacketTunnel/TunnelMonitor/ICMPHeader.h)4
-rw-r--r--ios/PacketTunnelCore/Pinger/IPv4Header.h (renamed from ios/PacketTunnel/TunnelMonitor/IPv4Header.h)2
-rw-r--r--ios/PacketTunnelCore/Pinger/Pinger.swift (renamed from ios/PacketTunnel/TunnelMonitor/Pinger.swift)76
-rw-r--r--ios/PacketTunnelCore/Pinger/PingerProtocol.swift28
-rw-r--r--ios/PacketTunnelCore/TunnelMonitor/DefaultPathObserverProtocol.swift25
-rw-r--r--ios/PacketTunnelCore/TunnelMonitor/TunnelDeviceInfoProtocol.swift17
-rw-r--r--ios/PacketTunnelCore/TunnelMonitor/TunnelMonitor.swift (renamed from ios/PacketTunnel/TunnelMonitor/TunnelMonitor.swift)191
-rw-r--r--ios/PacketTunnelCore/TunnelMonitor/TunnelMonitorProtocol.swift43
-rw-r--r--ios/PacketTunnelCore/TunnelMonitor/WgStats.swift19
-rw-r--r--ios/PacketTunnelCoreTests/PingerTests.swift32
-rw-r--r--ios/TestPlans/MullvadVPNApp.xctestplan7
21 files changed, 939 insertions, 284 deletions
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj
index 6614c287fc..33ac96d9fd 100644
--- a/ios/MullvadVPN.xcodeproj/project.pbxproj
+++ b/ios/MullvadVPN.xcodeproj/project.pbxproj
@@ -88,7 +88,6 @@
582BB1AF229566420055B6EF /* SettingsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 582BB1AE229566420055B6EF /* SettingsCell.swift */; };
582BB1B1229569620055B6EF /* UINavigationBar+Appearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 582BB1B0229569620055B6EF /* UINavigationBar+Appearance.swift */; };
5835B7CC233B76CB0096D79F /* TunnelManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5835B7CB233B76CB0096D79F /* TunnelManager.swift */; };
- 5838318B27C40A3900000571 /* Pinger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5838318A27C40A3900000571 /* Pinger.swift */; };
583D86482A2678DC0060D63B /* DeviceStateAccessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 583D86472A2678DC0060D63B /* DeviceStateAccessor.swift */; };
583DA21425FA4B5C00318683 /* LocationDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 583DA21325FA4B5C00318683 /* LocationDataSource.swift */; };
583FE00C29C0C7FD006E85F9 /* ModalPresentationConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 583FE00B29C0C7FD006E85F9 /* ModalPresentationConfiguration.swift */; };
@@ -227,7 +226,6 @@
589C6A7C2A45AE0100DAD3EF /* TunnelObfuscation.h in Headers */ = {isa = PBXBuildFile; fileRef = 589C6A7B2A45AE0100DAD3EF /* TunnelObfuscation.h */; settings = {ATTRIBUTES = (Public, ); }; };
589C6A7D2A45B06800DAD3EF /* TunnelObfuscation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5840231F2A406BF5007B27AC /* TunnelObfuscation.framework */; };
58A1AA8C23F5584C009F7EA6 /* ConnectionPanelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A1AA8B23F5584B009F7EA6 /* ConnectionPanelView.swift */; };
- 58A3BDB028A1821A00C8C2C6 /* WgStats.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A3BDAF28A1821A00C8C2C6 /* WgStats.swift */; };
58A8EE5A2976BFBB009C0F8D /* SKError+Localized.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A8EE592976BFBB009C0F8D /* SKError+Localized.swift */; };
58A8EE5E2976DB00009C0F8D /* StorePaymentManagerError+Display.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A8EE5D2976DB00009C0F8D /* StorePaymentManagerError+Display.swift */; };
58A99ED3240014A0006599E9 /* TermsOfServiceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A99ED2240014A0006599E9 /* TermsOfServiceViewController.swift */; };
@@ -262,6 +260,24 @@
58C76A092A33850E00100D75 /* ApplicationTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58C76A072A33850E00100D75 /* ApplicationTarget.swift */; };
58C76A0B2A338E4300100D75 /* BackgroundTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58C76A0A2A338E4300100D75 /* BackgroundTask.swift */; };
58C774BE29A7A249003A1A56 /* CustomNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58C774BD29A7A249003A1A56 /* CustomNavigationController.swift */; };
+ 58C7A43E2A863F470060C66F /* PacketTunnelCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 58C7A4362A863F440060C66F /* PacketTunnelCore.framework */; };
+ 58C7A4462A863F490060C66F /* PacketTunnelCore.h in Headers */ = {isa = PBXBuildFile; fileRef = 58C7A4382A863F450060C66F /* PacketTunnelCore.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 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 */; };
+ 58C7A4582A863FB90060C66F /* TunnelMonitorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58C7A42C2A85067A0060C66F /* TunnelMonitorProtocol.swift */; };
+ 58C7A4592A863FB90060C66F /* WgStats.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A3BDAF28A1821A00C8C2C6 /* WgStats.swift */; };
+ 58C7A45A2A863FDD0060C66F /* WgAdapterDeviceInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 582403142A821FB000163DE8 /* WgAdapterDeviceInfo.swift */; };
+ 58C7A45B2A8640030060C66F /* PacketTunnelPathObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58225D272A84F23B0083D7F1 /* PacketTunnelPathObserver.swift */; };
+ 58C7A45C2A8640490060C66F /* MullvadLogging.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 58D223F3294C8FF00029F5F8 /* MullvadLogging.framework */; platformFilter = ios; };
+ 58C7A45D2A8640490060C66F /* MullvadLogging.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 58D223F3294C8FF00029F5F8 /* MullvadLogging.framework */; platformFilter = ios; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+ 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 */; };
58C8191829FAA2C400DEB1B4 /* NotificationConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58C8191729FAA2C400DEB1B4 /* NotificationConfiguration.swift */; };
58CAF9F82983D36800BE19F7 /* Coordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58CAF9F72983D36800BE19F7 /* Coordinator.swift */; };
58CAF9FA2983E0C600BE19F7 /* LoginCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58CAF9F92983E0C600BE19F7 /* LoginCoordinator.swift */; };
@@ -274,7 +290,6 @@
58CCA01822426713004F3011 /* AccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58CCA01722426713004F3011 /* AccountViewController.swift */; };
58CCA01E2242787B004F3011 /* AccountTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58CCA01D2242787B004F3011 /* AccountTextField.swift */; };
58CE38C728992C8700A6D6E5 /* WireGuardAdapterError+Localization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58E07298288031D5008902F8 /* WireGuardAdapterError+Localization.swift */; };
- 58CE38C828992C9200A6D6E5 /* TunnelMonitorDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58E072A428814C28008902F8 /* TunnelMonitorDelegate.swift */; };
58CE5E64224146200008646E /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58CE5E63224146200008646E /* AppDelegate.swift */; };
58CE5E66224146200008646E /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58CE5E65224146200008646E /* LoginViewController.swift */; };
58CE5E6B224146210008646E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 58CE5E6A224146210008646E /* Assets.xcassets */; };
@@ -375,7 +390,6 @@
58FBFBE9291622580020E046 /* ExponentialBackoffTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FBFBE8291622580020E046 /* ExponentialBackoffTests.swift */; };
58FBFBEA291622580020E046 /* MullvadREST.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 06799ABC28F98E1D00ACD94E /* MullvadREST.framework */; };
58FBFBF1291630700020E046 /* DurationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FBFBF0291630700020E046 /* DurationTests.swift */; };
- 58FC040A27B3EE03001C21F0 /* TunnelMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FC040927B3EE03001C21F0 /* TunnelMonitor.swift */; };
58FD5BF024238EB300112C88 /* SKProduct+Formatting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FD5BEF24238EB300112C88 /* SKProduct+Formatting.swift */; };
58FD5BF42428C67600112C88 /* InAppPurchaseButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FD5BF32428C67600112C88 /* InAppPurchaseButton.swift */; };
58FDF2D92A0BA11A00C2B061 /* DeviceCheckOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FDF2D82A0BA11900C2B061 /* DeviceCheckOperation.swift */; };
@@ -526,6 +540,34 @@
remoteGlobalIDString = 06799ABB28F98E1D00ACD94E;
remoteInfo = MullvadREST;
};
+ 58C7A43F2A863F470060C66F /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 58CE5E58224146200008646E /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 58C7A4352A863F440060C66F;
+ remoteInfo = PacketTunnelCore;
+ };
+ 58C7A4472A863F490060C66F /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 58CE5E58224146200008646E /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 58C7A4352A863F440060C66F;
+ remoteInfo = PacketTunnelCore;
+ };
+ 58C7A45E2A8640490060C66F /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 58CE5E58224146200008646E /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 58D223F2294C8FF00029F5F8;
+ remoteInfo = MullvadLogging;
+ };
+ 58C7A4712A864B860060C66F /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 58CE5E58224146200008646E /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 58CE5E5F224146200008646E;
+ remoteInfo = MullvadVPN;
+ };
58CE5E7F224146470008646E /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 58CE5E58224146200008646E /* Project object */;
@@ -758,6 +800,7 @@
06799AD228F98E1D00ACD94E /* MullvadREST.framework in Embed Frameworks */,
58D223CD294C8BCB0029F5F8 /* Operations.framework in Embed Frameworks */,
A97F1F482A1F4E1A00ECEFDE /* MullvadTransport.framework in Embed Frameworks */,
+ 58C7A44A2A863F490060C66F /* PacketTunnelCore.framework in Embed Frameworks */,
58F0974F2A20C31100DA2DAD /* WireGuardKitTypes in Embed Frameworks */,
063F027A2902B63F001FA09F /* RelayCache.framework in Embed Frameworks */,
);
@@ -811,6 +854,17 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ 58C7A4602A8640490060C66F /* Embed Frameworks */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 2147483647;
+ dstPath = "";
+ dstSubfolderSpec = 10;
+ files = (
+ 58C7A45D2A8640490060C66F /* MullvadLogging.framework in Embed Frameworks */,
+ );
+ name = "Embed Frameworks";
+ runOnlyForDeploymentPostprocessing = 0;
+ };
58CE5E85224146470008646E /* Embed Foundation Extensions */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
@@ -918,9 +972,12 @@
5820EDA8288FE064006BF4E4 /* DeviceManagementInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceManagementInteractor.swift; sourceTree = "<group>"; };
5820EDAA288FF0D2006BF4E4 /* DeviceRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceRowView.swift; sourceTree = "<group>"; };
58218E1428B65058000C624F /* IPv4Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = IPv4Header.h; sourceTree = "<group>"; };
- 58218E1528B650C1000C624F /* ObjCBridgingHeader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ObjCBridgingHeader.h; sourceTree = "<group>"; };
58218E1628B65396000C624F /* ICMPHeader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ICMPHeader.h; sourceTree = "<group>"; };
+ 58225D252A84E8A10083D7F1 /* DefaultPathObserverProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultPathObserverProtocol.swift; sourceTree = "<group>"; };
+ 58225D272A84F23B0083D7F1 /* PacketTunnelPathObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PacketTunnelPathObserver.swift; sourceTree = "<group>"; };
5823FA5326CE49F600283BF8 /* TunnelObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelObserver.swift; sourceTree = "<group>"; };
+ 582403142A821FB000163DE8 /* WgAdapterDeviceInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WgAdapterDeviceInfo.swift; sourceTree = "<group>"; };
+ 582403162A821FD700163DE8 /* TunnelDeviceInfoProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelDeviceInfoProtocol.swift; sourceTree = "<group>"; };
58293FAC2510CA58005D0BB5 /* ProblemReportViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProblemReportViewController.swift; sourceTree = "<group>"; };
58293FB025124117005D0BB5 /* CustomTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTextField.swift; sourceTree = "<group>"; };
58293FB2251241B3005D0BB5 /* CustomTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTextView.swift; sourceTree = "<group>"; };
@@ -1014,6 +1071,7 @@
5878F4FF29CDA742003D4BE2 /* UIView+AutoLayoutBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+AutoLayoutBuilder.swift"; sourceTree = "<group>"; };
5878F50129CDB989003D4BE2 /* ChangeLogCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChangeLogCoordinator.swift; sourceTree = "<group>"; };
587988C628A2A01F00E3DF54 /* AccountDataThrottling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountDataThrottling.swift; sourceTree = "<group>"; };
+ 58799A352A84FC9F007BE51F /* PingerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PingerProtocol.swift; sourceTree = "<group>"; };
587A01FB23F1F0BE00B68763 /* SimulatorTunnelProviderHost.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimulatorTunnelProviderHost.swift; sourceTree = "<group>"; };
587AD7C523421D7000E93A53 /* TunnelSettingsV1.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TunnelSettingsV1.swift; sourceTree = "<group>"; };
587B7535266528A200DEF7E9 /* NotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationManager.swift; sourceTree = "<group>"; };
@@ -1106,6 +1164,11 @@
58C76A072A33850E00100D75 /* ApplicationTarget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationTarget.swift; sourceTree = "<group>"; };
58C76A0A2A338E4300100D75 /* BackgroundTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundTask.swift; sourceTree = "<group>"; };
58C774BD29A7A249003A1A56 /* CustomNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomNavigationController.swift; sourceTree = "<group>"; };
+ 58C7A42C2A85067A0060C66F /* TunnelMonitorProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelMonitorProtocol.swift; sourceTree = "<group>"; };
+ 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>"; };
58CAF9F92983E0C600BE19F7 /* LoginCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginCoordinator.swift; sourceTree = "<group>"; };
@@ -1143,7 +1206,6 @@
58E0729C28814AAE008902F8 /* PacketTunnelConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PacketTunnelConfiguration.swift; sourceTree = "<group>"; };
58E0729E28814ACC008902F8 /* WireGuardLogLevel+Logging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WireGuardLogLevel+Logging.swift"; sourceTree = "<group>"; };
58E072A028814B0E008902F8 /* MullvadEndpoint+WgEndpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MullvadEndpoint+WgEndpoint.swift"; sourceTree = "<group>"; };
- 58E072A428814C28008902F8 /* TunnelMonitorDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelMonitorDelegate.swift; sourceTree = "<group>"; };
58E0A98727C8F46300FE6BDD /* Tunnel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tunnel.swift; sourceTree = "<group>"; };
58E0E2832A3718CE002E3420 /* URLSessionShadowsocksTransport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionShadowsocksTransport.swift; sourceTree = "<group>"; };
58E11187292FA11F009FCA84 /* SettingsMigrationUIHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsMigrationUIHandler.swift; sourceTree = "<group>"; };
@@ -1316,11 +1378,28 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ 58C7A4332A863F440060C66F /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 58C7A45C2A8640490060C66F /* MullvadLogging.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 58C7A43A2A863F450060C66F /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 58C7A43E2A863F470060C66F /* PacketTunnelCore.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
58CE5E5D224146200008646E /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
58F0974E2A20C31100DA2DAD /* WireGuardKitTypes in Frameworks */,
+ 58C7A4492A863F490060C66F /* PacketTunnelCore.framework in Frameworks */,
5898D2A92901844E00EB5EBA /* libRelaySelector.a in Frameworks */,
58D223F9294C8FF00029F5F8 /* MullvadLogging.framework in Frameworks */,
58D223E6294C8F120029F5F8 /* MullvadTypes.framework in Frameworks */,
@@ -2077,6 +2156,35 @@
path = Containers;
sourceTree = "<group>";
};
+ 58C7A42E2A85091B0060C66F /* Pinger */ = {
+ isa = PBXGroup;
+ children = (
+ 58218E1628B65396000C624F /* ICMPHeader.h */,
+ 58218E1428B65058000C624F /* IPv4Header.h */,
+ 5838318A27C40A3900000571 /* Pinger.swift */,
+ 58799A352A84FC9F007BE51F /* PingerProtocol.swift */,
+ );
+ path = Pinger;
+ sourceTree = "<group>";
+ };
+ 58C7A4372A863F450060C66F /* PacketTunnelCore */ = {
+ isa = PBXGroup;
+ children = (
+ 58C7A4382A863F450060C66F /* PacketTunnelCore.h */,
+ 58C7A42E2A85091B0060C66F /* Pinger */,
+ 58E072A228814B96008902F8 /* TunnelMonitor */,
+ );
+ path = PacketTunnelCore;
+ sourceTree = "<group>";
+ };
+ 58C7A4432A863F490060C66F /* PacketTunnelCoreTests */ = {
+ isa = PBXGroup;
+ children = (
+ 58C7A46F2A8649ED0060C66F /* PingerTests.swift */,
+ );
+ path = PacketTunnelCoreTests;
+ sourceTree = "<group>";
+ };
58CAF9F22983D32200BE19F7 /* Coordinators */ = {
isa = PBXGroup;
children = (
@@ -2110,6 +2218,8 @@
584023202A406BF5007B27AC /* TunnelObfuscation */,
58695A9E2A4ADA9200328DB3 /* TunnelObfuscationTests */,
7A83C3FC2A55B39500DFB83A /* TestPlans */,
+ 58C7A4372A863F450060C66F /* PacketTunnelCore */,
+ 58C7A4432A863F490060C66F /* PacketTunnelCoreTests */,
58CE5E61224146200008646E /* Products */,
584F991F2902CBDD001F858D /* Frameworks */,
);
@@ -2134,6 +2244,8 @@
A97F1F412A1F4E1A00ECEFDE /* MullvadTransport.framework */,
5840231F2A406BF5007B27AC /* TunnelObfuscation.framework */,
58695A9D2A4ADA9100328DB3 /* TunnelObfuscationTests.xctest */,
+ 58C7A4362A863F440060C66F /* PacketTunnelCore.framework */,
+ 58C7A43D2A863F460060C66F /* PacketTunnelCoreTests.xctest */,
);
name = Products;
sourceTree = "<group>";
@@ -2172,15 +2284,15 @@
isa = PBXGroup;
children = (
58CE5E7D224146470008646E /* Info.plist */,
- 58218E1528B650C1000C624F /* ObjCBridgingHeader.h */,
58CE5E7E224146470008646E /* PacketTunnel.entitlements */,
58E0729C28814AAE008902F8 /* PacketTunnelConfiguration.swift */,
58CE5E7B224146470008646E /* PacketTunnelProvider.swift */,
58E072A028814B0E008902F8 /* MullvadEndpoint+WgEndpoint.swift */,
- 58E072A228814B96008902F8 /* TunnelMonitor */,
58E07298288031D5008902F8 /* WireGuardAdapterError+Localization.swift */,
58E0729E28814ACC008902F8 /* WireGuardLogLevel+Logging.swift */,
58906DDF2445C7A5002F0673 /* NEProviderStopReason+Debug.swift */,
+ 582403142A821FB000163DE8 /* WgAdapterDeviceInfo.swift */,
+ 58225D272A84F23B0083D7F1 /* PacketTunnelPathObserver.swift */,
58915D662A25F9F20066445B /* DeviceCheck */,
);
path = PacketTunnel;
@@ -2256,12 +2368,11 @@
58E072A228814B96008902F8 /* TunnelMonitor */ = {
isa = PBXGroup;
children = (
- 5838318A27C40A3900000571 /* Pinger.swift */,
+ 58225D252A84E8A10083D7F1 /* DefaultPathObserverProtocol.swift */,
+ 582403162A821FD700163DE8 /* TunnelDeviceInfoProtocol.swift */,
58FC040927B3EE03001C21F0 /* TunnelMonitor.swift */,
- 58E072A428814C28008902F8 /* TunnelMonitorDelegate.swift */,
+ 58C7A42C2A85067A0060C66F /* TunnelMonitorProtocol.swift */,
58A3BDAF28A1821A00C8C2C6 /* WgStats.swift */,
- 58218E1428B65058000C624F /* IPv4Header.h */,
- 58218E1628B65396000C624F /* ICMPHeader.h */,
);
path = TunnelMonitor;
sourceTree = "<group>";
@@ -2408,6 +2519,16 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ 58C7A4312A863F440060C66F /* Headers */ = {
+ isa = PBXHeadersBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 58C7A46A2A8643A90060C66F /* ICMPHeader.h in Headers */,
+ 58C7A4692A8643A90060C66F /* IPv4Header.h in Headers */,
+ 58C7A4462A863F490060C66F /* PacketTunnelCore.h in Headers */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
58D223A0294C8A480029F5F8 /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
@@ -2625,6 +2746,45 @@
productReference = 58B0A2A0238EE67E00BC001D /* MullvadVPNTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
+ 58C7A4352A863F440060C66F /* PacketTunnelCore */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 58C7A44B2A863F4A0060C66F /* Build configuration list for PBXNativeTarget "PacketTunnelCore" */;
+ buildPhases = (
+ 58C7A4312A863F440060C66F /* Headers */,
+ 58C7A4322A863F440060C66F /* Sources */,
+ 58C7A4332A863F440060C66F /* Frameworks */,
+ 58C7A4342A863F440060C66F /* Resources */,
+ 58C7A4602A8640490060C66F /* Embed Frameworks */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ 58C7A45F2A8640490060C66F /* PBXTargetDependency */,
+ );
+ name = PacketTunnelCore;
+ productName = PacketTunnelCore;
+ productReference = 58C7A4362A863F440060C66F /* PacketTunnelCore.framework */;
+ productType = "com.apple.product-type.framework";
+ };
+ 58C7A43C2A863F450060C66F /* PacketTunnelCoreTests */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 58C7A44E2A863F4A0060C66F /* Build configuration list for PBXNativeTarget "PacketTunnelCoreTests" */;
+ buildPhases = (
+ 58C7A4392A863F450060C66F /* Sources */,
+ 58C7A43A2A863F450060C66F /* Frameworks */,
+ 58C7A43B2A863F450060C66F /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ 58C7A4402A863F470060C66F /* PBXTargetDependency */,
+ 58C7A4722A864B860060C66F /* PBXTargetDependency */,
+ );
+ name = PacketTunnelCoreTests;
+ productName = PacketTunnelCoreTests;
+ productReference = 58C7A43D2A863F460060C66F /* PacketTunnelCoreTests.xctest */;
+ productType = "com.apple.product-type.bundle.unit-test";
+ };
58CE5E5F224146200008646E /* MullvadVPN */ = {
isa = PBXNativeTarget;
buildConfigurationList = 58CE5E72224146210008646E /* Build configuration list for PBXNativeTarget "MullvadVPN" */;
@@ -2648,6 +2808,7 @@
58CE5E80224146470008646E /* PBXTargetDependency */,
A97F1F462A1F4E1A00ECEFDE /* PBXTargetDependency */,
A9EC20F22A5D79ED0040D56E /* PBXTargetDependency */,
+ 58C7A4482A863F490060C66F /* PBXTargetDependency */,
);
name = MullvadVPN;
packageProductDependencies = (
@@ -2845,6 +3006,12 @@
58B0A29F238EE67E00BC001D = {
CreatedOnToolsVersion = 11.2.1;
};
+ 58C7A4352A863F440060C66F = {
+ CreatedOnToolsVersion = 14.3.1;
+ };
+ 58C7A43C2A863F450060C66F = {
+ CreatedOnToolsVersion = 14.3.1;
+ };
58CE5E5F224146200008646E = {
CreatedOnToolsVersion = 10.0;
LastSwiftMigration = 1020;
@@ -2924,6 +3091,8 @@
A97F1F402A1F4E1A00ECEFDE /* MullvadTransport */,
5840231E2A406BF5007B27AC /* TunnelObfuscation */,
58695A9C2A4ADA9100328DB3 /* TunnelObfuscationTests */,
+ 58C7A4352A863F440060C66F /* PacketTunnelCore */,
+ 58C7A43C2A863F450060C66F /* PacketTunnelCoreTests */,
);
};
/* End PBXProject section */
@@ -2966,6 +3135,20 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ 58C7A4342A863F440060C66F /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 58C7A43B2A863F450060C66F /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
58CE5E5E224146200008646E /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
@@ -3217,6 +3400,28 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ 58C7A4322A863F440060C66F /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 58C7A4522A863FB50060C66F /* Pinger.swift in Sources */,
+ 58C7A4572A863FB90060C66F /* TunnelDeviceInfoProtocol.swift in Sources */,
+ 58C7A4562A863FB90060C66F /* DefaultPathObserverProtocol.swift in Sources */,
+ 58C7A4552A863FB90060C66F /* TunnelMonitor.swift in Sources */,
+ 58C7A4512A863FB50060C66F /* PingerProtocol.swift in Sources */,
+ 58C7A4592A863FB90060C66F /* WgStats.swift in Sources */,
+ 58C7A4582A863FB90060C66F /* TunnelMonitorProtocol.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 58C7A4392A863F450060C66F /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 58C7A4702A8649ED0060C66F /* PingerTests.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
58CE5E5C224146200008646E /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
@@ -3456,12 +3661,10 @@
587AD7C723421D8600E93A53 /* TunnelSettingsV1.swift in Sources */,
5893C6FA29C1B481009090D1 /* DNSSettings.swift in Sources */,
580810E52A30E13A00B74552 /* DeviceStateAccessorProtocol.swift in Sources */,
- 58CE38C828992C9200A6D6E5 /* TunnelMonitorDelegate.swift in Sources */,
+ 58C7A45A2A863FDD0060C66F /* WgAdapterDeviceInfo.swift in Sources */,
580810E82A30E15500B74552 /* DeviceCheckRemoteServiceProtocol.swift in Sources */,
068CE5782927BE4800A068BB /* Migration.swift in Sources */,
58915D682A25FA080066445B /* DeviceCheckRemoteService.swift in Sources */,
- 58FC040A27B3EE03001C21F0 /* TunnelMonitor.swift in Sources */,
- 5838318B27C40A3900000571 /* Pinger.swift in Sources */,
06410E05292D0FC000AFC18C /* SettingsParser.swift in Sources */,
A92ECC292A7802AB0052F1B1 /* StoredDeviceData.swift in Sources */,
A9D96B1B2A8248F200A5C673 /* MigrationManager.swift in Sources */,
@@ -3475,10 +3678,10 @@
58906DE02445C7A5002F0673 /* NEProviderStopReason+Debug.swift in Sources */,
06410DFF292CF16C00AFC18C /* KeychainSettingsStore.swift in Sources */,
58E072A128814B0E008902F8 /* MullvadEndpoint+WgEndpoint.swift in Sources */,
+ 58C7A45B2A8640030060C66F /* PacketTunnelPathObserver.swift in Sources */,
06AC116228F94C450037AF9A /* ApplicationConfiguration.swift in Sources */,
A92ECC252A7802520052F1B1 /* StoredAccountData.swift in Sources */,
A92ECC2D2A7803A50052F1B1 /* DeviceState.swift in Sources */,
- 58A3BDB028A1821A00C8C2C6 /* WgStats.swift in Sources */,
583FE02429C1ACB3006E85F9 /* RESTCreateApplePaymentResponse+Localization.swift in Sources */,
5877D70F282137E8002FCFC7 /* SettingsManager.swift in Sources */,
58CE38C728992C8700A6D6E5 /* WireGuardAdapterError+Localization.swift in Sources */,
@@ -3655,6 +3858,27 @@
isa = PBXTargetDependency;
productRef = 58915D6B2A2603700066445B /* WireGuardKitTypes */;
};
+ 58C7A4402A863F470060C66F /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 58C7A4352A863F440060C66F /* PacketTunnelCore */;
+ targetProxy = 58C7A43F2A863F470060C66F /* PBXContainerItemProxy */;
+ };
+ 58C7A4482A863F490060C66F /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 58C7A4352A863F440060C66F /* PacketTunnelCore */;
+ targetProxy = 58C7A4472A863F490060C66F /* PBXContainerItemProxy */;
+ };
+ 58C7A45F2A8640490060C66F /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ platformFilter = ios;
+ target = 58D223F2294C8FF00029F5F8 /* MullvadLogging */;
+ targetProxy = 58C7A45E2A8640490060C66F /* PBXContainerItemProxy */;
+ };
+ 58C7A4722A864B860060C66F /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 58CE5E5F224146200008646E /* MullvadVPN */;
+ targetProxy = 58C7A4712A864B860060C66F /* PBXContainerItemProxy */;
+ };
58CE5E80224146470008646E /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 58CE5E78224146470008646E /* PacketTunnel */;
@@ -4181,6 +4405,124 @@
};
name = Release;
};
+ 58C7A44C2A863F4A0060C66F /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 5808273928487E3E006B77A4 /* Base.xcconfig */;
+ buildSettings = {
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CURRENT_PROJECT_VERSION = 1;
+ DEFINES_MODULE = YES;
+ DYLIB_COMPATIBILITY_VERSION = 1;
+ DYLIB_CURRENT_VERSION = 1;
+ DYLIB_INSTALL_NAME_BASE = "@rpath";
+ ENABLE_MODULE_VERIFIER = YES;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 Mullvad VPN AB. All rights reserved.";
+ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@loader_path/Frameworks",
+ );
+ MARKETING_VERSION = 1.0;
+ MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++";
+ MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++20";
+ PRODUCT_BUNDLE_IDENTIFIER = "$(APPLICATION_IDENTIFIER).PacketTunnelCore";
+ PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
+ SKIP_INSTALL = YES;
+ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
+ SUPPORTS_MACCATALYST = NO;
+ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ VERSIONING_SYSTEM = "apple-generic";
+ VERSION_INFO_PREFIX = "";
+ };
+ name = Debug;
+ };
+ 58C7A44D2A863F4A0060C66F /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 5808273928487E3E006B77A4 /* Base.xcconfig */;
+ buildSettings = {
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CURRENT_PROJECT_VERSION = 1;
+ DEFINES_MODULE = YES;
+ DYLIB_COMPATIBILITY_VERSION = 1;
+ DYLIB_CURRENT_VERSION = 1;
+ DYLIB_INSTALL_NAME_BASE = "@rpath";
+ ENABLE_MODULE_VERIFIER = YES;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 Mullvad VPN AB. All rights reserved.";
+ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@loader_path/Frameworks",
+ );
+ MARKETING_VERSION = 1.0;
+ MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++";
+ MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++20";
+ PRODUCT_BUNDLE_IDENTIFIER = "$(APPLICATION_IDENTIFIER).PacketTunnelCore";
+ PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
+ SKIP_INSTALL = YES;
+ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
+ SUPPORTS_MACCATALYST = NO;
+ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ VERSIONING_SYSTEM = "apple-generic";
+ VERSION_INFO_PREFIX = "";
+ };
+ name = Release;
+ };
+ 58C7A44F2A863F4A0060C66F /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_TEAM = CKG9MXH72F;
+ GENERATE_INFOPLIST_FILE = YES;
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = net.mullvad.MullvadVPN.PacketTunnelCoreTests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
+ SUPPORTS_MACCATALYST = NO;
+ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
+ SWIFT_EMIT_LOC_STRINGS = NO;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Debug;
+ };
+ 58C7A4502A863F4A0060C66F /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_TEAM = CKG9MXH72F;
+ GENERATE_INFOPLIST_FILE = YES;
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = net.mullvad.MullvadVPN.PacketTunnelCoreTests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
+ SUPPORTS_MACCATALYST = NO;
+ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
+ SWIFT_EMIT_LOC_STRINGS = NO;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Release;
+ };
58CE5E70224146210008646E /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@@ -4355,7 +4697,6 @@
);
PRODUCT_BUNDLE_IDENTIFIER = "$(APPLICATION_IDENTIFIER).PacketTunnel";
PRODUCT_NAME = "$(TARGET_NAME)";
- SWIFT_OBJC_BRIDGING_HEADER = "$(SRCROOT)/PacketTunnel/ObjCBridgingHeader.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
};
@@ -4376,7 +4717,6 @@
);
PRODUCT_BUNDLE_IDENTIFIER = "$(APPLICATION_IDENTIFIER).PacketTunnel";
PRODUCT_NAME = "$(TARGET_NAME)";
- SWIFT_OBJC_BRIDGING_HEADER = "$(SRCROOT)/PacketTunnel/ObjCBridgingHeader.h";
SWIFT_VERSION = 5.0;
};
name = Release;
@@ -4827,6 +5167,24 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
+ 58C7A44B2A863F4A0060C66F /* Build configuration list for PBXNativeTarget "PacketTunnelCore" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 58C7A44C2A863F4A0060C66F /* Debug */,
+ 58C7A44D2A863F4A0060C66F /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 58C7A44E2A863F4A0060C66F /* Build configuration list for PBXNativeTarget "PacketTunnelCoreTests" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 58C7A44F2A863F4A0060C66F /* Debug */,
+ 58C7A4502A863F4A0060C66F /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
58CE5E5B224146200008646E /* Build configuration list for PBXProject "MullvadVPN" */ = {
isa = XCConfigurationList;
buildConfigurations = (
diff --git a/ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadVPN.xcscheme b/ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadVPN.xcscheme
index ecfd93db2a..2757b5fb64 100644
--- a/ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadVPN.xcscheme
+++ b/ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadVPN.xcscheme
@@ -213,6 +213,47 @@
ReferencedContainer = "container:MullvadVPN.xcodeproj">
</BuildableReference>
</TestableReference>
+ <TestableReference
+ skipped = "NO"
+ parallelizable = "YES">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "58C7A43C2A863F450060C66F"
+ BuildableName = "PacketTunnelCoreTests.xctest"
+ BlueprintName = "PacketTunnelCoreTests"
+ ReferencedContainer = "container:MullvadVPN.xcodeproj">
+ </BuildableReference>
+ </TestableReference>
+ <TestableReference
+ skipped = "NO">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "58C7A43C2A863F450060C66F"
+ BuildableName = "PacketTunnelCoreTests.xctest"
+ BlueprintName = "PacketTunnelCoreTests"
+ ReferencedContainer = "container:MullvadVPN.xcodeproj">
+ </BuildableReference>
+ </TestableReference>
+ <TestableReference
+ skipped = "NO">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "58C7A43C2A863F450060C66F"
+ BuildableName = "PacketTunnelCoreTests.xctest"
+ BlueprintName = "PacketTunnelCoreTests"
+ ReferencedContainer = "container:MullvadVPN.xcodeproj">
+ </BuildableReference>
+ </TestableReference>
+ <TestableReference
+ skipped = "NO">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "58C7A43C2A863F450060C66F"
+ BuildableName = "PacketTunnelCoreTests.xctest"
+ BlueprintName = "PacketTunnelCoreTests"
+ ReferencedContainer = "container:MullvadVPN.xcodeproj">
+ </BuildableReference>
+ </TestableReference>
</Testables>
</TestAction>
<LaunchAction
diff --git a/ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/PacketTunnelCore.xcscheme b/ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/PacketTunnelCore.xcscheme
new file mode 100644
index 0000000000..b00fd5b4fd
--- /dev/null
+++ b/ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/PacketTunnelCore.xcscheme
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+ LastUpgradeVersion = "1430"
+ version = "1.7">
+ <BuildAction
+ parallelizeBuildables = "YES"
+ buildImplicitDependencies = "YES">
+ <BuildActionEntries>
+ <BuildActionEntry
+ buildForTesting = "YES"
+ buildForRunning = "YES"
+ buildForProfiling = "YES"
+ buildForArchiving = "YES"
+ buildForAnalyzing = "YES">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "58C7A4352A863F440060C66F"
+ BuildableName = "PacketTunnelCore.framework"
+ BlueprintName = "PacketTunnelCore"
+ ReferencedContainer = "container:MullvadVPN.xcodeproj">
+ </BuildableReference>
+ </BuildActionEntry>
+ </BuildActionEntries>
+ </BuildAction>
+ <TestAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ shouldUseLaunchSchemeArgsEnv = "YES"
+ shouldAutocreateTestPlan = "YES">
+ </TestAction>
+ <LaunchAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ launchStyle = "0"
+ useCustomWorkingDirectory = "NO"
+ ignoresPersistentStateOnLaunch = "NO"
+ debugDocumentVersioning = "YES"
+ debugServiceExtension = "internal"
+ allowLocationSimulation = "YES">
+ </LaunchAction>
+ <ProfileAction
+ buildConfiguration = "Release"
+ shouldUseLaunchSchemeArgsEnv = "YES"
+ savedToolIdentifier = ""
+ useCustomWorkingDirectory = "NO"
+ debugDocumentVersioning = "YES">
+ <MacroExpansion>
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "58C7A4352A863F440060C66F"
+ BuildableName = "PacketTunnelCore.framework"
+ BlueprintName = "PacketTunnelCore"
+ ReferencedContainer = "container:MullvadVPN.xcodeproj">
+ </BuildableReference>
+ </MacroExpansion>
+ </ProfileAction>
+ <AnalyzeAction
+ buildConfiguration = "Debug">
+ </AnalyzeAction>
+ <ArchiveAction
+ buildConfiguration = "Release"
+ revealArchiveInOrganizer = "YES">
+ </ArchiveAction>
+</Scheme>
diff --git a/ios/PacketTunnel/ObjCBridgingHeader.h b/ios/PacketTunnel/ObjCBridgingHeader.h
deleted file mode 100644
index 4e57f0de38..0000000000
--- a/ios/PacketTunnel/ObjCBridgingHeader.h
+++ /dev/null
@@ -1,15 +0,0 @@
-//
-// ObjCBridgingHeader.h
-// MullvadVPN
-//
-// Created by pronebird on 24/08/2022.
-// Copyright © 2022 Mullvad VPN AB. All rights reserved.
-//
-
-#ifndef OBJCBRIDGINGHEADER_H
-#define OBJCBRIDGINGHEADER_H
-
-#include "IPv4Header.h"
-#include "ICMPHeader.h"
-
-#endif /* OBJCBRIDGINGHEADER_H */
diff --git a/ios/PacketTunnel/PacketTunnelPathObserver.swift b/ios/PacketTunnel/PacketTunnelPathObserver.swift
new file mode 100644
index 0000000000..b16c62f705
--- /dev/null
+++ b/ios/PacketTunnel/PacketTunnelPathObserver.swift
@@ -0,0 +1,48 @@
+//
+// PacketTunnelPathObserver.swift
+// PacketTunnel
+//
+// Created by pronebird on 10/08/2023.
+// Copyright © 2023 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+import NetworkExtension
+import PacketTunnelCore
+
+final class PacketTunnelPathObserver: DefaultPathObserverProtocol {
+ private weak var packetTunnelProvider: NEPacketTunnelProvider?
+ private let stateLock = NSLock()
+ private var observationToken: NSKeyValueObservation?
+
+ init(packetTunnelProvider: NEPacketTunnelProvider) {
+ self.packetTunnelProvider = packetTunnelProvider
+ }
+
+ var defaultPath: NetworkPath? {
+ return packetTunnelProvider?.defaultPath
+ }
+
+ func start(_ body: @escaping (NetworkPath) -> Void) {
+ stateLock.withLock {
+ observationToken?.invalidate()
+
+ // Normally packet tunnel provider should exist throughout the network extension lifetime.
+ observationToken = packetTunnelProvider?.observe(\.defaultPath, options: [.new]) { _, change in
+ let nwPath = change.newValue.flatMap { $0 }
+ if let nwPath {
+ body(nwPath)
+ }
+ }
+ }
+ }
+
+ func stop() {
+ stateLock.withLock {
+ observationToken?.invalidate()
+ observationToken = nil
+ }
+ }
+}
+
+extension NetworkExtension.NWPath: NetworkPath {}
diff --git a/ios/PacketTunnel/PacketTunnelProvider.swift b/ios/PacketTunnel/PacketTunnelProvider.swift
index a964215771..5b7bdd3013 100644
--- a/ios/PacketTunnel/PacketTunnelProvider.swift
+++ b/ios/PacketTunnel/PacketTunnelProvider.swift
@@ -14,6 +14,7 @@ import MullvadTypes
import Network
import NetworkExtension
import Operations
+import PacketTunnelCore
import RelayCache
import RelaySelector
import TunnelProviderMessaging
@@ -25,7 +26,7 @@ private let tunnelStartupFailureRestartInterval: TimeInterval = 2
/// Delay before trying to reconnect tunnel after private key rotation.
private let keyRotationTunnelReconnectionDelay = 60 * 2
-class PacketTunnelProvider: NEPacketTunnelProvider, TunnelMonitorDelegate {
+class PacketTunnelProvider: NEPacketTunnelProvider {
/// Tunnel provider logger.
private let providerLogger: Logger
@@ -178,11 +179,14 @@ class PacketTunnelProvider: NEPacketTunnelProvider, TunnelMonitorDelegate {
)
tunnelMonitor = TunnelMonitor(
- delegateQueue: dispatchQueue,
- packetTunnelProvider: self,
- adapter: adapter
+ eventQueue: dispatchQueue,
+ pinger: Pinger(replyQueue: dispatchQueue),
+ tunnelDeviceInfo: WgAdapterDeviceInfo(adapter: adapter),
+ defaultPathObserver: PacketTunnelPathObserver(packetTunnelProvider: self)
)
- tunnelMonitor.delegate = self
+ tunnelMonitor.onEvent = { [weak self] event in
+ self?.handleTunnelMonitorEvent(event)
+ }
}
override func startTunnel(options: [String: NSObject]?, completionHandler: @escaping (Error?) -> Void) {
@@ -381,9 +385,22 @@ class PacketTunnelProvider: NEPacketTunnelProvider, TunnelMonitorDelegate {
tunnelMonitor.onWake()
}
- // MARK: - TunnelMonitorDelegate
+ // MARK: - Private: Tunnel monitoring
+
+ private func handleTunnelMonitorEvent(_ event: TunnelMonitorEvent) {
+ switch event {
+ case .connectionEstablished:
+ tunnelConnectionEstablished()
- func tunnelMonitorDidDetermineConnectionEstablished(_ tunnelMonitor: TunnelMonitor) {
+ case .connectionLost:
+ tunnelConnectionLost()
+
+ case let .networkReachabilityChanged(isReachable):
+ tunnelReachabilityChanged(isReachable)
+ }
+ }
+
+ private func tunnelConnectionEstablished() {
dispatchPrecondition(condition: .onQueue(dispatchQueue))
providerLogger.debug("Connection established.")
@@ -399,7 +416,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider, TunnelMonitorDelegate {
setReconnecting(false)
}
- func tunnelMonitorDelegateShouldHandleConnectionRecovery(_ tunnelMonitor: TunnelMonitor) {
+ private func tunnelConnectionLost() {
dispatchPrecondition(condition: .onQueue(dispatchQueue))
let (value, isOverflow) = numberOfFailedAttempts.addingReportingOverflow(1)
@@ -414,10 +431,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider, TunnelMonitorDelegate {
reconnectTunnel(to: .automatic, shouldStopTunnelMonitor: false)
}
- func tunnelMonitor(
- _ tunnelMonitor: TunnelMonitor,
- networkReachabilityStatusDidChange isNetworkReachable: Bool
- ) {
+ private func tunnelReachabilityChanged(_ isNetworkReachable: Bool) {
dispatchPrecondition(condition: .onQueue(dispatchQueue))
guard self.isNetworkReachable != isNetworkReachable else { return }
diff --git a/ios/PacketTunnel/TunnelMonitor/TunnelMonitorDelegate.swift b/ios/PacketTunnel/TunnelMonitor/TunnelMonitorDelegate.swift
deleted file mode 100644
index 9839faf3e5..0000000000
--- a/ios/PacketTunnel/TunnelMonitor/TunnelMonitorDelegate.swift
+++ /dev/null
@@ -1,23 +0,0 @@
-//
-// TunnelMonitorDelegate.swift
-// PacketTunnel
-//
-// Created by pronebird on 15/07/2022.
-// Copyright © 2022 Mullvad VPN AB. All rights reserved.
-//
-
-import Foundation
-
-protocol TunnelMonitorDelegate: AnyObject {
- /// Invoked when tunnel monitor determined that connection is established.
- func tunnelMonitorDidDetermineConnectionEstablished(_ tunnelMonitor: TunnelMonitor)
-
- /// Invoked when tunnel monitor determined that connection attempt has failed.
- func tunnelMonitorDelegateShouldHandleConnectionRecovery(_ tunnelMonitor: TunnelMonitor)
-
- /// Invoked when network reachability status changes.
- func tunnelMonitor(
- _ tunnelMonitor: TunnelMonitor,
- networkReachabilityStatusDidChange isNetworkReachable: Bool
- )
-}
diff --git a/ios/PacketTunnel/TunnelMonitor/WgStats.swift b/ios/PacketTunnel/TunnelMonitor/WgStats.swift
deleted file mode 100644
index d2bec85d2d..0000000000
--- a/ios/PacketTunnel/TunnelMonitor/WgStats.swift
+++ /dev/null
@@ -1,51 +0,0 @@
-//
-// WgStats.swift
-// PacketTunnel
-//
-// Created by pronebird on 08/08/2022.
-// Copyright © 2022 Mullvad VPN AB. All rights reserved.
-//
-
-import Foundation
-
-struct WgStats {
- let bytesReceived: UInt64
- let bytesSent: UInt64
-
- init() {
- bytesReceived = 0
- bytesSent = 0
- }
-
- init?(from string: String) {
- var _bytesReceived: UInt64?
- var _bytesSent: UInt64?
-
- string.enumerateLines { line, stop in
- if _bytesReceived == nil, let value = parseValue("rx_bytes=", in: line) {
- _bytesReceived = value
- } else if _bytesSent == nil, let value = parseValue("tx_bytes=", in: line) {
- _bytesSent = value
- }
-
- if _bytesReceived != nil, _bytesSent != nil {
- stop = true
- }
- }
-
- guard let _bytesReceived, let _bytesSent else {
- return nil
- }
-
- bytesReceived = _bytesReceived
- bytesSent = _bytesSent
- }
-}
-
-@inline(__always) private func parseValue(_ prefixKey: String, in line: String) -> UInt64? {
- guard line.hasPrefix(prefixKey) else { return nil }
-
- let value = line.dropFirst(prefixKey.count)
-
- return UInt64(value)
-}
diff --git a/ios/PacketTunnel/WgAdapterDeviceInfo.swift b/ios/PacketTunnel/WgAdapterDeviceInfo.swift
new file mode 100644
index 0000000000..c32a37b111
--- /dev/null
+++ b/ios/PacketTunnel/WgAdapterDeviceInfo.swift
@@ -0,0 +1,84 @@
+//
+// WgAdapterInfoProvider.swift
+// PacketTunnel
+//
+// Created by pronebird on 08/08/2023.
+// Copyright © 2023 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+import PacketTunnelCore
+import WireGuardKit
+
+struct WgAdapterDeviceInfo: TunnelDeviceInfoProtocol {
+ let adapter: WireGuardAdapter
+
+ var interfaceName: String? {
+ return adapter.interfaceName
+ }
+
+ func getStats() throws -> WgStats {
+ var result: String?
+
+ let dispatchGroup = DispatchGroup()
+ dispatchGroup.enter()
+ adapter.getRuntimeConfiguration { string in
+ result = string
+ dispatchGroup.leave()
+ }
+
+ guard case .success = dispatchGroup.wait(wallTimeout: .now() + .seconds(1)) else { throw StatsError.timeout }
+ guard let result else { throw StatsError.nilValue }
+ guard let newStats = WgStats(from: result) else { throw StatsError.parse }
+
+ return newStats
+ }
+
+ enum StatsError: LocalizedError {
+ case timeout, nilValue, parse
+
+ var errorDescription: String? {
+ switch self {
+ case .timeout:
+ return "adapter.getRuntimeConfiguration timeout."
+ case .nilValue:
+ return "Received nil string for stats."
+ case .parse:
+ return "Couldn't parse stats."
+ }
+ }
+ }
+}
+
+private extension WgStats {
+ init?(from string: String) {
+ var bytesReceived: UInt64?
+ var bytesSent: UInt64?
+
+ string.enumerateLines { line, stop in
+ if bytesReceived == nil, let value = parseValue("rx_bytes=", in: line) {
+ bytesReceived = value
+ } else if bytesSent == nil, let value = parseValue("tx_bytes=", in: line) {
+ bytesSent = value
+ }
+
+ if bytesReceived != nil, bytesSent != nil {
+ stop = true
+ }
+ }
+
+ guard let bytesReceived, let bytesSent else {
+ return nil
+ }
+
+ self.init(bytesReceived: bytesReceived, bytesSent: bytesSent)
+ }
+}
+
+@inline(__always) private func parseValue(_ prefixKey: String, in line: String) -> UInt64? {
+ guard line.hasPrefix(prefixKey) else { return nil }
+
+ let value = line.dropFirst(prefixKey.count)
+
+ return UInt64(value)
+}
diff --git a/ios/PacketTunnelCore/PacketTunnelCore.h b/ios/PacketTunnelCore/PacketTunnelCore.h
new file mode 100644
index 0000000000..09a65f16c7
--- /dev/null
+++ b/ios/PacketTunnelCore/PacketTunnelCore.h
@@ -0,0 +1,19 @@
+//
+// PacketTunnelCore.h
+// PacketTunnelCore
+//
+// Created by pronebird on 11/08/2023.
+// Copyright © 2023 Mullvad VPN AB. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+//! Project version number for PacketTunnelCore.
+FOUNDATION_EXPORT double PacketTunnelCoreVersionNumber;
+
+//! Project version string for PacketTunnelCore.
+FOUNDATION_EXPORT const unsigned char PacketTunnelCoreVersionString[];
+
+// In this header, you should import all the public headers of your framework using statements like #import <PacketTunnelCore/PublicHeader.h>
+#import <PacketTunnelCore/ICMPHeader.h>
+#import <PacketTunnelCore/IPv4Header.h>
diff --git a/ios/PacketTunnel/TunnelMonitor/ICMPHeader.h b/ios/PacketTunnelCore/Pinger/ICMPHeader.h
index 7e91f576d2..a5458b53b9 100644
--- a/ios/PacketTunnel/TunnelMonitor/ICMPHeader.h
+++ b/ios/PacketTunnelCore/Pinger/ICMPHeader.h
@@ -1,6 +1,6 @@
//
// ICMPHeader.h
-// MullvadVPN
+// PacketTunnelCore
//
// Created by pronebird on 24/08/2022.
// Copyright © 2022 Mullvad VPN AB. All rights reserved.
@@ -9,6 +9,8 @@
#ifndef ICMPHEADER_H
#define ICMPHEADER_H
+#include <AssertMacros.h>
+
struct ICMPHeader {
uint8_t type;
uint8_t code;
diff --git a/ios/PacketTunnel/TunnelMonitor/IPv4Header.h b/ios/PacketTunnelCore/Pinger/IPv4Header.h
index bcb0b8ecc9..9024ce8208 100644
--- a/ios/PacketTunnel/TunnelMonitor/IPv4Header.h
+++ b/ios/PacketTunnelCore/Pinger/IPv4Header.h
@@ -1,6 +1,6 @@
//
// IPv4Header.h
-// MullvadVPN
+// PacketTunnelCore
//
// Created by pronebird on 24/08/2022.
// Copyright © 2022 Mullvad VPN AB. All rights reserved.
diff --git a/ios/PacketTunnel/TunnelMonitor/Pinger.swift b/ios/PacketTunnelCore/Pinger/Pinger.swift
index f5872bb590..c312f822a4 100644
--- a/ios/PacketTunnel/TunnelMonitor/Pinger.swift
+++ b/ios/PacketTunnelCore/Pinger/Pinger.swift
@@ -1,6 +1,6 @@
//
// Pinger.swift
-// PacketTunnel
+// PacketTunnelCore
//
// Created by pronebird on 21/02/2022.
// Copyright © 2022 Mullvad VPN AB. All rights reserved.
@@ -11,25 +11,7 @@ import protocol Network.IPAddress
import struct Network.IPv4Address
import struct Network.IPv6Address
-protocol PingerDelegate: AnyObject {
- func pinger(
- _ pinger: Pinger,
- didReceiveResponseFromSender senderAddress: IPAddress,
- icmpHeader: ICMPHeader
- )
-
- func pinger(
- _ pinger: Pinger,
- didFailWithError error: Error
- )
-}
-
-final class Pinger {
- struct SendResult {
- var sequenceNumber: UInt16
- var bytesSent: UInt16
- }
-
+public final class Pinger: PingerProtocol {
// Socket read buffer size.
private static let bufferSize = 65535
@@ -40,37 +22,35 @@ final class Pinger {
private var socket: CFSocket?
private var readBuffer = [UInt8](repeating: 0, count: bufferSize)
private let stateLock = NSRecursiveLock()
+ private let replyQueue: DispatchQueue
- private weak var _delegate: PingerDelegate?
- private let delegateQueue: DispatchQueue
-
- var delegate: PingerDelegate? {
+ public var onReply: ((PingerReply) -> Void)? {
get {
- stateLock.lock()
- defer { stateLock.unlock() }
-
- return _delegate
+ stateLock.withLock {
+ return _onReply
+ }
}
set {
- stateLock.lock()
- defer { stateLock.unlock() }
-
- _delegate = newValue
+ stateLock.withLock {
+ _onReply = newValue
+ }
}
}
+ private var _onReply: ((PingerReply) -> Void)?
+
deinit {
closeSocket()
}
- init(identifier: UInt16 = 757, delegateQueue: DispatchQueue) {
+ public init(identifier: UInt16 = 757, replyQueue: DispatchQueue) {
self.identifier = identifier
- self.delegateQueue = delegateQueue
+ 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.
- func openSocket(bindTo interfaceName: String?) throws {
+ public func openSocket(bindTo interfaceName: String?) throws {
stateLock.lock()
defer { stateLock.unlock() }
@@ -117,7 +97,7 @@ final class Pinger {
socket = newSocket
}
- func closeSocket() {
+ public func closeSocket() {
stateLock.lock()
defer { stateLock.unlock() }
@@ -129,8 +109,8 @@ final class Pinger {
}
/// Send ping packet to the given address.
- /// Returns `SendResult` on success, otherwise throws a `Pinger.Error`.
- func send(to address: IPv4Address) throws -> SendResult {
+ /// Returns `PingerSendResult` on success, otherwise throws a `Pinger.Error`.
+ public func send(to address: IPv4Address) throws -> PingerSendResult {
stateLock.lock()
defer { stateLock.unlock() }
@@ -170,7 +150,7 @@ final class Pinger {
throw Error.sendPacket(errno)
}
- return SendResult(sequenceNumber: sequenceNumber, bytesSent: UInt16(bytesSent))
+ return PingerSendResult(sequenceNumber: sequenceNumber, bytesSent: UInt16(bytesSent))
}
private func nextSequenceNumber() -> UInt16 {
@@ -201,18 +181,14 @@ final class Pinger {
let icmpHeader = try parseICMPResponse(buffer: &readBuffer, length: bytesRead)
guard let sender = Self.makeIPAddress(from: address) else { throw Error.parseIPAddress }
- delegateQueue.async {
- self.delegate?.pinger(
- self,
- didReceiveResponseFromSender: sender,
- icmpHeader: icmpHeader
- )
+ replyQueue.async {
+ self.onReply?(.success(sender, icmpHeader.sequenceNumber))
}
} catch Pinger.Error.clientIdentifierMismatch {
// Ignore responses from other senders.
} catch {
- delegateQueue.async {
- self.delegate?.pinger(self, didFailWithError: error)
+ replyQueue.async {
+ self.onReply?(.parseError(error))
}
}
}
@@ -340,7 +316,7 @@ final class Pinger {
}
extension Pinger {
- enum Error: LocalizedError {
+ public enum Error: LocalizedError {
/// Failure to create a socket.
case createSocket
@@ -371,7 +347,7 @@ extension Pinger {
/// Failure to parse IP address.
case parseIPAddress
- var errorDescription: String? {
+ public var errorDescription: String? {
switch self {
case .createSocket:
return "Failure to create socket."
@@ -397,7 +373,7 @@ extension Pinger {
}
}
- enum MalformedResponseReason {
+ public enum MalformedResponseReason {
case ipv4PacketTooSmall
case icmpHeaderTooSmall
case invalidIPVersion
diff --git a/ios/PacketTunnelCore/Pinger/PingerProtocol.swift b/ios/PacketTunnelCore/Pinger/PingerProtocol.swift
new file mode 100644
index 0000000000..6b7a4f9764
--- /dev/null
+++ b/ios/PacketTunnelCore/Pinger/PingerProtocol.swift
@@ -0,0 +1,28 @@
+//
+// PingerProtocol.swift
+// PacketTunnelCore
+//
+// Created by pronebird on 10/08/2023.
+// Copyright © 2023 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+import Network
+
+public enum PingerReply {
+ case success(_ sender: IPAddress, _ sequenceNumber: UInt16)
+ case parseError(Error)
+}
+
+public struct PingerSendResult {
+ public var sequenceNumber: UInt16
+ public var bytesSent: UInt16
+}
+
+public protocol PingerProtocol {
+ var onReply: ((PingerReply) -> Void)? { get set }
+
+ func openSocket(bindTo interfaceName: String?) throws
+ func closeSocket()
+ func send(to address: IPv4Address) throws -> PingerSendResult
+}
diff --git a/ios/PacketTunnelCore/TunnelMonitor/DefaultPathObserverProtocol.swift b/ios/PacketTunnelCore/TunnelMonitor/DefaultPathObserverProtocol.swift
new file mode 100644
index 0000000000..97b31a1683
--- /dev/null
+++ b/ios/PacketTunnelCore/TunnelMonitor/DefaultPathObserverProtocol.swift
@@ -0,0 +1,25 @@
+//
+// DefaultPathObserverProtocol.swift
+// PacketTunnelCore
+//
+// Created by pronebird on 10/08/2023.
+// Copyright © 2023 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+import NetworkExtension
+
+public protocol DefaultPathObserverProtocol {
+ /// Returns current default path or `nil` if unknown yet.
+ var defaultPath: NetworkPath? { get }
+
+ /// Start observing changes to `defaultPath`.
+ func start(_ body: @escaping (NetworkPath) -> Void)
+
+ /// Stop observing changes to `defaultPath`.
+ func stop()
+}
+
+public protocol NetworkPath {
+ var status: NetworkExtension.NWPathStatus { get }
+}
diff --git a/ios/PacketTunnelCore/TunnelMonitor/TunnelDeviceInfoProtocol.swift b/ios/PacketTunnelCore/TunnelMonitor/TunnelDeviceInfoProtocol.swift
new file mode 100644
index 0000000000..fcd1ff88c3
--- /dev/null
+++ b/ios/PacketTunnelCore/TunnelMonitor/TunnelDeviceInfoProtocol.swift
@@ -0,0 +1,17 @@
+//
+// TunnelDeviceInfoProtocol.swift
+// PacketTunnelCore
+//
+// Created by pronebird on 08/08/2023.
+// Copyright © 2023 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+
+public protocol TunnelDeviceInfoProtocol {
+ /// Returns tunnel interface name (i.e utun0) if available.
+ var interfaceName: String? { get }
+
+ /// Returns tunnel statistics.
+ func getStats() throws -> WgStats
+}
diff --git a/ios/PacketTunnel/TunnelMonitor/TunnelMonitor.swift b/ios/PacketTunnelCore/TunnelMonitor/TunnelMonitor.swift
index 8448a89991..9c88b9158c 100644
--- a/ios/PacketTunnel/TunnelMonitor/TunnelMonitor.swift
+++ b/ios/PacketTunnelCore/TunnelMonitor/TunnelMonitor.swift
@@ -1,6 +1,6 @@
//
// TunnelMonitor.swift
-// PacketTunnel
+// PacketTunnelCore
//
// Created by pronebird on 09/02/2022.
// Copyright © 2022 Mullvad VPN AB. All rights reserved.
@@ -8,9 +8,8 @@
import Foundation
import MullvadLogging
-import MullvadTypes
-import NetworkExtension
-import WireGuardKit
+import protocol Network.IPAddress
+import struct Network.IPv4Address
/// Interval for periodic heartbeat ping issued when traffic is flowing.
/// Should help to detect connectivity issues on networks that drop traffic in one of directions,
@@ -48,7 +47,7 @@ private let inboundTrafficTimeout: TimeInterval = 5
/// Ping is issued after that timeout is exceeded.s
private let trafficTimeout: TimeInterval = 120
-final class TunnelMonitor: PingerDelegate {
+public final class TunnelMonitor: TunnelMonitorProtocol {
/// Connection state.
private enum ConnectionState {
/// Initialized and doing nothing.
@@ -191,7 +190,7 @@ final class TunnelMonitor: PingerDelegate {
netStats = newStats
}
- mutating func updatePingStats(sendResult: Pinger.SendResult, now: Date) {
+ mutating func updatePingStats(sendResult: PingerSendResult, now: Date) {
pingStats.requests.updateValue(now, forKey: sendResult.sequenceNumber)
pingStats.lastRequestDate = now
}
@@ -220,15 +219,15 @@ final class TunnelMonitor: PingerDelegate {
var lastReplyDate: Date?
}
- private let adapter: WireGuardAdapter
+ private let tunnelDeviceInfo: TunnelDeviceInfoProtocol
private let nslock = NSLock()
- private let eventQueue = DispatchQueue(label: "TunnelMonitor-eventQueue")
- private let delegateQueue: DispatchQueue
+ private let timerQueue = DispatchQueue(label: "TunnelMonitor-timerQueue")
+ private let eventQueue: DispatchQueue
- private let pinger: Pinger
- private weak var packetTunnelProvider: NEPacketTunnelProvider?
- private var defaultPathObserver: NSKeyValueObservation?
+ private var pinger: PingerProtocol
+ private var defaultPathObserver: DefaultPathObserverProtocol
+ private var isObservingDefaultPath = false
private var timer: DispatchSourceTimer?
private var state = State()
@@ -236,40 +235,49 @@ final class TunnelMonitor: PingerDelegate {
private let logger = Logger(label: "TunnelMonitor")
- private weak var _delegate: TunnelMonitorDelegate?
- weak var delegate: TunnelMonitorDelegate? {
+ private var _onEvent: ((TunnelMonitorEvent) -> Void)?
+ public var onEvent: ((TunnelMonitorEvent) -> Void)? {
set {
- nslock.lock()
- defer { nslock.unlock() }
-
- _delegate = newValue
+ nslock.withLock {
+ _onEvent = newValue
+ }
}
get {
- nslock.lock()
- defer { nslock.unlock() }
-
- return _delegate
+ nslock.withLock {
+ return _onEvent
+ }
}
}
- init(
- delegateQueue: DispatchQueue,
- packetTunnelProvider: NEPacketTunnelProvider,
- adapter: WireGuardAdapter
+ public init(
+ eventQueue: DispatchQueue,
+ pinger: PingerProtocol,
+ tunnelDeviceInfo: TunnelDeviceInfoProtocol,
+ defaultPathObserver: DefaultPathObserverProtocol
) {
- self.delegateQueue = delegateQueue
- self.packetTunnelProvider = packetTunnelProvider
- self.adapter = adapter
+ self.eventQueue = eventQueue
+ self.tunnelDeviceInfo = tunnelDeviceInfo
+ self.defaultPathObserver = defaultPathObserver
- pinger = Pinger(delegateQueue: eventQueue)
- pinger.delegate = self
+ self.pinger = pinger
+ self.pinger.onReply = { [weak self] reply in
+ guard let self else { return }
+
+ switch reply {
+ case let .success(sender, sequenceNumber):
+ didReceivePing(from: sender, sequenceNumber: sequenceNumber)
+
+ case let .parseError(error):
+ logger.error(error: error, message: "Failed to parse ICMP response.")
+ }
+ }
}
deinit {
stop()
}
- func start(probeAddress: IPv4Address) {
+ public func start(probeAddress: IPv4Address) {
nslock.lock()
defer { nslock.unlock() }
@@ -286,14 +294,14 @@ final class TunnelMonitor: PingerDelegate {
addDefaultPathObserver()
}
- func stop() {
+ public func stop() {
nslock.lock()
defer { nslock.unlock() }
_stop()
}
- func onWake() {
+ public func onWake() {
nslock.lock()
defer { nslock.unlock() }
@@ -312,7 +320,7 @@ final class TunnelMonitor: PingerDelegate {
}
}
- func onSleep() {
+ public func onSleep() {
nslock.lock()
defer { nslock.unlock() }
@@ -322,23 +330,6 @@ final class TunnelMonitor: PingerDelegate {
removeDefaultPathObserver()
}
- // MARK: - PingerDelegate
-
- func pinger(
- _ pinger: Pinger,
- didReceiveResponseFromSender senderAddress: IPAddress,
- icmpHeader: ICMPHeader
- ) {
- didReceivePing(from: senderAddress, icmpHeader: icmpHeader)
- }
-
- func pinger(_ pinger: Pinger, didFailWithError error: Error) {
- logger.error(
- error: error,
- message: "Failed to parse ICMP response."
- )
- }
-
// MARK: - Private
private func _stop(forRestart: Bool = false) {
@@ -359,37 +350,33 @@ final class TunnelMonitor: PingerDelegate {
}
private func addDefaultPathObserver() {
- guard let packetTunnelProvider else { return }
-
- defaultPathObserver?.invalidate()
+ defaultPathObserver.stop()
logger.trace("Add default path observer.")
- defaultPathObserver = packetTunnelProvider
- .observe(\.defaultPath, options: [.new]) { [weak self] _, change in
- guard let self else { return }
+ isObservingDefaultPath = true
- nslock.lock()
- defer { self.nslock.unlock() }
+ defaultPathObserver.start { [weak self] nwPath in
+ guard let self else { return }
- let newValue = change.newValue.flatMap { $0 }
- if let newPath = newValue {
- handleNetworkPathUpdate(newPath)
- }
+ nslock.withLock {
+ self.handleNetworkPathUpdate(nwPath)
}
+ }
- if let currentPath = packetTunnelProvider.defaultPath {
+ if let currentPath = defaultPathObserver.defaultPath {
handleNetworkPathUpdate(currentPath)
}
}
private func removeDefaultPathObserver() {
- guard let defaultPathObserver else { return }
+ guard isObservingDefaultPath else { return }
logger.trace("Remove default path observer.")
- defaultPathObserver.invalidate()
- self.defaultPathObserver = nil
+ defaultPathObserver.stop()
+
+ isObservingDefaultPath = false
}
private func checkConnectivity() {
@@ -468,7 +455,7 @@ final class TunnelMonitor: PingerDelegate {
state.connectionState = .recovering
probeAddress = nil
- sendDelegateShouldHandleConnectionRecovery()
+ sendConnectionLostEvent()
}
private func sendPing(to receiver: IPv4Address, now: Date) {
@@ -482,7 +469,7 @@ final class TunnelMonitor: PingerDelegate {
}
}
- private func handleNetworkPathUpdate(_ networkPath: NetworkExtension.NWPath) {
+ private func handleNetworkPathUpdate(_ networkPath: NetworkPath) {
let pathStatus = networkPath.status
let isReachable = pathStatus == .satisfiable || pathStatus == .satisfied
@@ -491,11 +478,11 @@ final class TunnelMonitor: PingerDelegate {
if isReachable {
logger.debug("Start monitoring connection.")
startMonitoring()
- sendDelegateNetworkStatusChange(true)
+ sendNetworkStatusChangeEvent(true)
} else {
logger.debug("Wait for network to become reachable before starting monitoring.")
state.connectionState = .waitingConnectivity
- sendDelegateNetworkStatusChange(false)
+ sendNetworkStatusChangeEvent(false)
}
case .waitingConnectivity:
@@ -503,7 +490,7 @@ final class TunnelMonitor: PingerDelegate {
logger.debug("Network is reachable. Resume monitoring.")
startMonitoring()
- sendDelegateNetworkStatusChange(true)
+ sendNetworkStatusChangeEvent(true)
case .connecting, .connected:
guard !isReachable else { return }
@@ -511,14 +498,14 @@ final class TunnelMonitor: PingerDelegate {
logger.debug("Network is unreachable. Pause monitoring.")
state.connectionState = .waitingConnectivity
stopMonitoring(resetRetryAttempt: true)
- sendDelegateNetworkStatusChange(false)
+ sendNetworkStatusChangeEvent(false)
case .stopped, .recovering:
break
}
}
- private func didReceivePing(from sender: IPAddress, icmpHeader: ICMPHeader) {
+ private func didReceivePing(from sender: IPAddress, sequenceNumber: UInt16) {
nslock.lock()
defer { nslock.unlock() }
@@ -529,7 +516,6 @@ final class TunnelMonitor: PingerDelegate {
}
let now = Date()
- let sequenceNumber = icmpHeader.sequenceNumber
guard let pingTimestamp = state.setPingReplyReceived(sequenceNumber, now: now) else {
logger.trace("Got unknown ping sequence: \(sequenceNumber).")
return
@@ -548,13 +534,13 @@ final class TunnelMonitor: PingerDelegate {
if case .connecting = state.connectionState {
state.connectionState = .connected
state.retryAttempt = 0
- sendDelegateConnectionEstablished()
+ sendConnectionEstablishedEvent()
}
}
private func startMonitoring() {
do {
- guard let interfaceName = adapter.interfaceName else {
+ guard let interfaceName = tunnelDeviceInfo.interfaceName else {
logger.debug("Failed to obtain utun interface name.")
return
}
@@ -585,7 +571,7 @@ final class TunnelMonitor: PingerDelegate {
}
private func startConnectivityCheckTimer() {
- let timer = DispatchSource.makeTimerSource(queue: eventQueue)
+ let timer = DispatchSource.makeTimerSource(queue: timerQueue)
timer.setEventHandler { [weak self] in
self?.checkConnectivity()
}
@@ -609,24 +595,21 @@ final class TunnelMonitor: PingerDelegate {
self.timer = nil
}
- private func sendDelegateConnectionEstablished() {
- delegateQueue.async {
- self.delegate?.tunnelMonitorDidDetermineConnectionEstablished(self)
+ private func sendConnectionEstablishedEvent() {
+ eventQueue.async {
+ self.onEvent?(.connectionEstablished)
}
}
- private func sendDelegateShouldHandleConnectionRecovery() {
- delegateQueue.async {
- self.delegate?.tunnelMonitorDelegateShouldHandleConnectionRecovery(self)
+ private func sendConnectionLostEvent() {
+ eventQueue.async {
+ self.onEvent?(.connectionLost)
}
}
- private func sendDelegateNetworkStatusChange(_ isNetworkReachable: Bool) {
- delegateQueue.async {
- self.delegate?.tunnelMonitor(
- self,
- networkReachabilityStatusDidChange: isNetworkReachable
- )
+ private func sendNetworkStatusChangeEvent(_ isNetworkReachable: Bool) {
+ eventQueue.async {
+ self.onEvent?(.networkReachabilityChanged(isNetworkReachable))
}
}
@@ -643,30 +626,12 @@ final class TunnelMonitor: PingerDelegate {
}
private func getStats() -> WgStats? {
- var result: String?
-
- let dispatchGroup = DispatchGroup()
- dispatchGroup.enter()
- adapter.getRuntimeConfiguration { string in
- result = string
- dispatchGroup.leave()
- }
-
- guard case .success = dispatchGroup.wait(wallTimeout: .now() + .seconds(1)) else {
- logger.debug("adapter.getRuntimeConfiguration timeout.")
- return nil
- }
-
- guard let result else {
- logger.debug("Received nil string for stats.")
- return nil
- }
+ do {
+ return try tunnelDeviceInfo.getStats()
+ } catch {
+ logger.error(error: error, message: "Failed to obtain adapter stats.")
- guard let newStats = WgStats(from: result) else {
- logger.debug("Couldn't parse stats.")
return nil
}
-
- return newStats
}
}
diff --git a/ios/PacketTunnelCore/TunnelMonitor/TunnelMonitorProtocol.swift b/ios/PacketTunnelCore/TunnelMonitor/TunnelMonitorProtocol.swift
new file mode 100644
index 0000000000..3bee394242
--- /dev/null
+++ b/ios/PacketTunnelCore/TunnelMonitor/TunnelMonitorProtocol.swift
@@ -0,0 +1,43 @@
+//
+// TunnelMonitorProtocol.swift
+// PacketTunnelCore
+//
+// Created by pronebird on 10/08/2023.
+// Copyright © 2023 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+import Network
+
+/// Tunnel monitor event.
+public enum TunnelMonitorEvent {
+ /// Dispatched after receiving the first ping response
+ case connectionEstablished
+
+ /// Dispatched when connection stops receiving ping responses.
+ /// The handler is responsible to reconfigure the tunnel and call `TunnelMonitorProtocol.start(probeAddress:)` to resume connection monitoring.
+ case connectionLost
+
+ /// Dispatched when network reachability changes.
+ case networkReachabilityChanged(_ isNetworkReachable: Bool)
+}
+
+public protocol TunnelMonitorProtocol {
+ /// Event handler that starts receiving events after the call to `start(probeAddress:)`.
+ var onEvent: ((TunnelMonitorEvent) -> Void)? { get set }
+
+ /// Start monitoring connection by pinging the given IP address.
+ /// Normally we should only give an address of a tunnel gateway here which is reachable over tunnel interface.
+ func start(probeAddress: IPv4Address)
+
+ /// Stop monitoring connection.
+ func stop()
+
+ /// Restarts internal timers and gracefully handles transition from sleep to awake device state.
+ /// Call this method when packet tunnel provider receives a wake event.
+ func onWake()
+
+ /// Cancels internal timers and time dependent data in preparation for device sleep.
+ /// Call this method when packet tunnel provider receives a sleep event.
+ func onSleep()
+}
diff --git a/ios/PacketTunnelCore/TunnelMonitor/WgStats.swift b/ios/PacketTunnelCore/TunnelMonitor/WgStats.swift
new file mode 100644
index 0000000000..ac343bba32
--- /dev/null
+++ b/ios/PacketTunnelCore/TunnelMonitor/WgStats.swift
@@ -0,0 +1,19 @@
+//
+// WgStats.swift
+// PacketTunnelCore
+//
+// Created by pronebird on 08/08/2022.
+// Copyright © 2022 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+
+public struct WgStats {
+ public let bytesReceived: UInt64
+ public let bytesSent: UInt64
+
+ public init(bytesReceived: UInt64 = 0, bytesSent: UInt64 = 0) {
+ self.bytesReceived = bytesReceived
+ self.bytesSent = bytesSent
+ }
+}
diff --git a/ios/PacketTunnelCoreTests/PingerTests.swift b/ios/PacketTunnelCoreTests/PingerTests.swift
new file mode 100644
index 0000000000..d77e9c4e5d
--- /dev/null
+++ b/ios/PacketTunnelCoreTests/PingerTests.swift
@@ -0,0 +1,32 @@
+//
+// PingerTests.swift
+// PacketTunnelCoreTests
+//
+// Created by pronebird on 11/08/2023.
+// Copyright © 2023 Mullvad VPN AB. All rights reserved.
+//
+
+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")
+ sendResult = try pinger.send(to: .loopback)
+
+ waitForExpectations(timeout: 1)
+ }
+}
diff --git a/ios/TestPlans/MullvadVPNApp.xctestplan b/ios/TestPlans/MullvadVPNApp.xctestplan
index 14e6322dd2..72cc666962 100644
--- a/ios/TestPlans/MullvadVPNApp.xctestplan
+++ b/ios/TestPlans/MullvadVPNApp.xctestplan
@@ -48,6 +48,13 @@
"identifier" : "589A455128E094B300565204",
"name" : "OperationsTests"
}
+ },
+ {
+ "target" : {
+ "containerPath" : "container:MullvadVPN.xcodeproj",
+ "identifier" : "58C7A43C2A863F450060C66F",
+ "name" : "PacketTunnelCoreTests"
+ }
}
],
"version" : 1