Skip to content

Commit 01f98a5

Browse files
Implement packet capture client and models
1 parent e3dd3d0 commit 01f98a5

File tree

10 files changed

+383
-37
lines changed

10 files changed

+383
-37
lines changed

ios/Configurations/UITests.xcconfig.template

+4-1
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,14 @@ AD_SERVING_DOMAIN = vpnlist.to
1919
// A domain which should be reachable. Used to verify Internet connectivity. Must be running a server on port 80.
2020
SHOULD_BE_REACHABLE_DOMAIN = mullvad.net
2121

22-
// Base URL for the firewall API, Note that // will be treated as a comment, therefor you need to insert a ${} between the slashes for example http:/${}/8.8.8.8
22+
// Base URL for the firewall API. Note that // will be treated as a comment, therefor you need to insert a ${} between the slashes for example http:/${}/8.8.8.8
2323
FIREWALL_API_BASE_URL = http:/${}/8.8.8.8
2424

2525
// URL for Mullvad provided JSON data with information about the connection. https://am.i.mullvad.net/json for production, https://am.i.stagemole.eu/json for staging.
2626
AM_I_JSON_URL = https:/${}/am.i.stagemole.eu/json
2727

2828
// Specify whether app logs should be extracted and attached to test report for failing tests
2929
ATTACH_APP_LOGS_ON_FAILURE = 0
30+
31+
// Base URL for the packet capture API. Note that // will be treated as a comment, therefor you need to insert a ${} between the slashes for example http:/${}/8.8.8.8
32+
PACKET_CAPTURE_BASE_URL = http:/${}/8.8.8.8

ios/MullvadVPN.xcodeproj/project.pbxproj

+25-5
Original file line numberDiff line numberDiff line change
@@ -653,13 +653,16 @@
653653
8585CBE32BC684180015B6A4 /* EditAccessMethodPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8585CBE22BC684180015B6A4 /* EditAccessMethodPage.swift */; };
654654
8587A05D2B84D43100152938 /* ChangeLogAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8587A05C2B84D43100152938 /* ChangeLogAlert.swift */; };
655655
8590896F2B61763B003AF5F5 /* LoggedOutUITestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8590896B2B61763B003AF5F5 /* LoggedOutUITestCase.swift */; };
656+
85978A542BE0F10E00F999A7 /* PacketCaptureAPIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85978A532BE0F10E00F999A7 /* PacketCaptureAPIClient.swift */; };
656657
85A42B882BB44D31007BABF7 /* DeviceManagementPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85A42B872BB44D31007BABF7 /* DeviceManagementPage.swift */; };
657658
85B267612B849ADB0098E3CD /* mullvad-api.h in Headers */ = {isa = PBXBuildFile; fileRef = 85B267602B849ADB0098E3CD /* mullvad-api.h */; };
658659
85C7A2E92B89024B00035D5A /* SettingsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85C7A2E82B89024B00035D5A /* SettingsTests.swift */; };
659660
85D039982BA4711800940E7F /* SettingsMigrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85D039972BA4711800940E7F /* SettingsMigrationTests.swift */; };
660661
85D2B0B12B6BD32400DF9DA7 /* BaseUITestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8590896A2B61763B003AF5F5 /* BaseUITestCase.swift */; };
661662
85E3BDE52B70E18C00FA71FD /* Networking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85E3BDE42B70E18C00FA71FD /* Networking.swift */; };
662663
85EC620C2B838D10005AFFB5 /* MullvadAPIWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85557B132B5983CF00795FE1 /* MullvadAPIWrapper.swift */; };
664+
85F1E17E2C0A256200DB8F55 /* LeakTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85F1E17D2C0A256200DB8F55 /* LeakTests.swift */; };
665+
85F1E1812C0A2A0C00DB8F55 /* SafariApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85F1E1802C0A2A0C00DB8F55 /* SafariApp.swift */; };
663666
85FB5A0C2B6903990015DCED /* WelcomePage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85FB5A0B2B6903990015DCED /* WelcomePage.swift */; };
664667
85FB5A102B6960A30015DCED /* AccountDeletionPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85FB5A0F2B6960A30015DCED /* AccountDeletionPage.swift */; };
665668
A90763B02B2857D50045ADF0 /* Socks5ConnectCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = A90763A02B2857D50045ADF0 /* Socks5ConnectCommand.swift */; };
@@ -1995,11 +1998,14 @@
19951998
859089692B61763B003AF5F5 /* LoggedInWithTimeUITestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoggedInWithTimeUITestCase.swift; sourceTree = "<group>"; };
19961999
8590896A2B61763B003AF5F5 /* BaseUITestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseUITestCase.swift; sourceTree = "<group>"; };
19972000
8590896B2B61763B003AF5F5 /* LoggedOutUITestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoggedOutUITestCase.swift; sourceTree = "<group>"; };
2001+
85978A532BE0F10E00F999A7 /* PacketCaptureAPIClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PacketCaptureAPIClient.swift; sourceTree = "<group>"; };
19982002
85A42B872BB44D31007BABF7 /* DeviceManagementPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceManagementPage.swift; sourceTree = "<group>"; };
19992003
85B267602B849ADB0098E3CD /* mullvad-api.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "mullvad-api.h"; path = "../../mullvad-api/include/mullvad-api.h"; sourceTree = "<group>"; };
20002004
85C7A2E82B89024B00035D5A /* SettingsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsTests.swift; sourceTree = "<group>"; };
20012005
85D039972BA4711800940E7F /* SettingsMigrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsMigrationTests.swift; sourceTree = "<group>"; };
20022006
85E3BDE42B70E18C00FA71FD /* Networking.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Networking.swift; sourceTree = "<group>"; };
2007+
85F1E17D2C0A256200DB8F55 /* LeakTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LeakTests.swift; sourceTree = "<group>"; };
2008+
85F1E1802C0A2A0C00DB8F55 /* SafariApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariApp.swift; sourceTree = "<group>"; };
20032009
85FB5A0B2B6903990015DCED /* WelcomePage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomePage.swift; sourceTree = "<group>"; };
20042010
85FB5A0F2B6960A30015DCED /* AccountDeletionPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountDeletionPage.swift; sourceTree = "<group>"; };
20052011
A900E9B72ACC5C2B00C95F67 /* AccountsProxy+Stubs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AccountsProxy+Stubs.swift"; sourceTree = "<group>"; };
@@ -3905,22 +3911,24 @@
39053911
852969262B4D9C1F007EAD4C /* MullvadVPNUITests */ = {
39063912
isa = PBXGroup;
39073913
children = (
3908-
8518F6392B601910009EB113 /* Base */,
3909-
85557B0C2B591B0F00795FE1 /* Networking */,
3910-
852969312B4E9220007EAD4C /* Pages */,
3911-
7A45CFCD2C08697100D80B21 /* Screenshots */,
3912-
852969372B4ED20E007EAD4C /* Info.plist */,
39133914
8556EB532B9A1D7100D26DD4 /* BridgingHeader.h */,
39143915
85B267602B849ADB0098E3CD /* mullvad-api.h */,
3916+
852969372B4ED20E007EAD4C /* Info.plist */,
39153917
852969272B4D9C1F007EAD4C /* AccountTests.swift */,
39163918
85557B112B594FC900795FE1 /* ConnectivityTests.swift */,
39173919
A9BFAFFE2BD004ED00F2BCA1 /* CustomListsTests.swift */,
3920+
85F1E17D2C0A256200DB8F55 /* LeakTests.swift */,
39183921
850201DA2B503D7700EF8C96 /* RelayTests.swift */,
39193922
85D039972BA4711800940E7F /* SettingsMigrationTests.swift */,
39203923
85C7A2E82B89024B00035D5A /* SettingsTests.swift */,
39213924
8518F6392B601910009EB113 /* Test base classes */,
39223925
856952E12BD6B04C008C1F84 /* XCUIElement+Extensions.swift */,
39233926
85557B152B5ABBBE00795FE1 /* XCUIElementQuery+Extensions.swift */,
3927+
8518F6392B601910009EB113 /* Base */,
3928+
85F1E17F2C0A29FA00DB8F55 /* External apps */,
3929+
85557B0C2B591B0F00795FE1 /* Networking */,
3930+
852969312B4E9220007EAD4C /* Pages */,
3931+
7A45CFCD2C08697100D80B21 /* Screenshots */,
39243932
);
39253933
path = MullvadVPNUITests;
39263934
sourceTree = "<group>";
@@ -3972,10 +3980,19 @@
39723980
85557B132B5983CF00795FE1 /* MullvadAPIWrapper.swift */,
39733981
85E3BDE42B70E18C00FA71FD /* Networking.swift */,
39743982
856952DB2BD2922A008C1F84 /* PartnerAPIClient.swift */,
3983+
85978A532BE0F10E00F999A7 /* PacketCaptureAPIClient.swift */,
39753984
);
39763985
path = Networking;
39773986
sourceTree = "<group>";
39783987
};
3988+
85F1E17F2C0A29FA00DB8F55 /* External apps */ = {
3989+
isa = PBXGroup;
3990+
children = (
3991+
85F1E1802C0A2A0C00DB8F55 /* SafariApp.swift */,
3992+
);
3993+
path = "External apps";
3994+
sourceTree = "<group>";
3995+
};
39793996
A907639F2B2857D50045ADF0 /* Socks5 */ = {
39803997
isa = PBXGroup;
39813998
children = (
@@ -6084,6 +6101,7 @@
60846101
8529693C2B4F0257007EAD4C /* Alert.swift in Sources */,
60856102
8542F7532BCFBD050035C042 /* SelectLocationFilterPage.swift in Sources */,
60866103
850201DD2B503D8C00EF8C96 /* SelectLocationPage.swift in Sources */,
6104+
85F1E1812C0A2A0C00DB8F55 /* SafariApp.swift in Sources */,
60876105
85D039982BA4711800940E7F /* SettingsMigrationTests.swift in Sources */,
60886106
85021CAE2BDBC4290098B400 /* AppLogsPage.swift in Sources */,
60896107
850201DB2B503D7700EF8C96 /* RelayTests.swift in Sources */,
@@ -6108,9 +6126,11 @@
61086126
85FB5A102B6960A30015DCED /* AccountDeletionPage.swift in Sources */,
61096127
7A45CFC72C071DD400D80B21 /* SnapshotHelper.swift in Sources */,
61106128
856952DC2BD2922A008C1F84 /* PartnerAPIClient.swift in Sources */,
6129+
85F1E17E2C0A256200DB8F55 /* LeakTests.swift in Sources */,
61116130
85557B162B5ABBBE00795FE1 /* XCUIElementQuery+Extensions.swift in Sources */,
61126131
855D9F5B2B63E56B00D7C64D /* ProblemReportPage.swift in Sources */,
61136132
8529693A2B4F0238007EAD4C /* TermsOfServicePage.swift in Sources */,
6133+
85978A542BE0F10E00F999A7 /* PacketCaptureAPIClient.swift in Sources */,
61146134
85A42B882BB44D31007BABF7 /* DeviceManagementPage.swift in Sources */,
61156135
8532E6872B8CCED600ACECD1 /* ProblemReportSubmittedPage.swift in Sources */,
61166136
85FB5A0C2B6903990015DCED /* WelcomePage.swift in Sources */,

ios/MullvadVPN.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved

-22
This file was deleted.

ios/MullvadVPNUITests/Base/BaseUITestCase.swift

+61
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,13 @@ class BaseUITestCase: XCTestCase {
3131
/// Default relay to use in tests
3232
static let testsDefaultRelayName = "se-got-wg-001"
3333

34+
/// True when the current test case is capturing packets
35+
private var currentTestCaseShouldCapturePackets = false
36+
37+
/// True when a packet capture session is active
38+
private var packetCaptureSessionIsActive = false
39+
private var packetCaptureSession: PacketCaptureSession?
40+
3441
// swiftlint:disable force_cast
3542
let displayName = Bundle(for: BaseUITestCase.self)
3643
.infoDictionary?["DisplayName"] as! String
@@ -125,6 +132,29 @@ class BaseUITestCase: XCTestCase {
125132
}
126133
}
127134

135+
/// Start packet capture for this test case
136+
func startPacketCapture() {
137+
currentTestCaseShouldCapturePackets = true
138+
packetCaptureSessionIsActive = true
139+
let packetCaptureClient = PacketCaptureAPIClient()
140+
packetCaptureSession = packetCaptureClient.startCapture()
141+
}
142+
143+
/// Stop the current packet capture and return captured data
144+
func stopPacketCapture() -> [Stream] {
145+
packetCaptureSessionIsActive = false
146+
guard let packetCaptureSession else {
147+
XCTFail("Trying to stop capture when there is no active capture")
148+
return []
149+
}
150+
151+
let packetCaptureAPIClient = PacketCaptureAPIClient()
152+
packetCaptureAPIClient.stopCapture(session: packetCaptureSession)
153+
let capturedData = packetCaptureAPIClient.getParsedCaptureObjects(session: packetCaptureSession)
154+
155+
return capturedData
156+
}
157+
128158
// MARK: - Setup & teardown
129159

130160
/// Override this class function to change the uninstall behaviour in suite level teardown
@@ -141,12 +171,43 @@ class BaseUITestCase: XCTestCase {
141171

142172
/// Test level setup
143173
override func setUp() {
174+
currentTestCaseShouldCapturePackets = false // Reset for each test case run
144175
continueAfterFailure = false
145176
app.launch()
146177
}
147178

148179
/// Test level teardown
149180
override func tearDown() {
181+
if currentTestCaseShouldCapturePackets {
182+
guard let packetCaptureSession = packetCaptureSession else {
183+
XCTFail("Packet capture session unexpectedly not set up")
184+
return
185+
}
186+
187+
let packetCaptureClient = PacketCaptureAPIClient()
188+
189+
// If there's a an active session due to cancelled/failed test run make sure to end it
190+
if packetCaptureSessionIsActive {
191+
packetCaptureSessionIsActive = false
192+
packetCaptureClient.stopCapture(session: packetCaptureSession)
193+
}
194+
195+
packetCaptureClient.stopCapture(session: packetCaptureSession)
196+
let pcap = packetCaptureClient.getPCAP(session: packetCaptureSession)
197+
let parsedCapture = packetCaptureClient.getParsedCapture(session: packetCaptureSession)
198+
self.packetCaptureSession = nil
199+
200+
let pcapAttachment = XCTAttachment(data: pcap)
201+
pcapAttachment.name = self.name + ".pcap"
202+
pcapAttachment.lifetime = .keepAlways
203+
self.add(pcapAttachment)
204+
205+
let jsonAttachment = XCTAttachment(data: parsedCapture)
206+
jsonAttachment.name = self.name + ".json"
207+
jsonAttachment.lifetime = .keepAlways
208+
self.add(jsonAttachment)
209+
}
210+
150211
app.terminate()
151212

152213
if let testRun = self.testRun, testRun.failureCount > 0, attachAppLogsOnFailure == true {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
//
2+
// SafariApp.swift
3+
// MullvadVPNUITests
4+
//
5+
// Created by Niklas Berglund on 2024-05-31.
6+
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
7+
//
8+
9+
import XCTest
10+
11+
class SafariApp {
12+
let app = XCUIApplication(bundleIdentifier: "com.apple.mobilesafari")
13+
14+
func launch() {
15+
app.launch()
16+
}
17+
18+
@discardableResult func tapAddressBar() -> Self {
19+
app.textFields.firstMatch.tap()
20+
return self
21+
}
22+
23+
@discardableResult func enterText(_ text: String) -> Self {
24+
app.typeText(text)
25+
return self
26+
}
27+
}

ios/MullvadVPNUITests/Info.plist

+2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
<string>$(NO_TIME_ACCOUNT_NUMBER)</string>
2525
<key>PartnerApiToken</key>
2626
<string>$(PARTNER_API_TOKEN)</string>
27+
<key>PacketCaptureAPIBaseURL</key>
28+
<string>$(PACKET_CAPTURE_BASE_URL)</string>
2729
<key>ShouldBeReachableDomain</key>
2830
<string>$(SHOULD_BE_REACHABLE_DOMAIN)</string>
2931
<key>TestDeviceIdentifier</key>

ios/MullvadVPNUITests/LeakTests.swift

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
//
2+
// LeakTests.swift
3+
// MullvadVPNUITests
4+
//
5+
// Created by Niklas Berglund on 2024-05-31.
6+
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
7+
//
8+
9+
import XCTest
10+
11+
class LeakTests: LoggedInWithTimeUITestCase {
12+
/// For now just the skeleton of a leak test - traffic is captured and parsed, but not analyzed
13+
func testLeaks() throws {
14+
startPacketCapture()
15+
16+
TunnelControlPage(app)
17+
.tapSecureConnectionButton()
18+
19+
allowAddVPNConfigurationsIfAsked()
20+
21+
TunnelControlPage(app)
22+
.waitForSecureConnectionLabel()
23+
24+
// Trigger traffic by navigating to website in Safari
25+
let safariApp = SafariApp()
26+
safariApp.launch()
27+
safariApp.tapAddressBar()
28+
safariApp.enterText("mullvad.net\n")
29+
30+
app.launch()
31+
TunnelControlPage(app)
32+
.tapDisconnectButton()
33+
34+
// Keep the capture open for a while
35+
Thread.sleep(forTimeInterval: 5.0)
36+
let capturedTraffic = stopPacketCapture()
37+
38+
// Analyze captured traffic
39+
}
40+
}

ios/MullvadVPNUITests/Networking/FirewallRule.swift

+2-8
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,16 @@
99
import Foundation
1010
import XCTest
1111

12-
enum NetworkingProtocol: String {
13-
case TCP = "tcp"
14-
case UDP = "udp"
15-
case ICMP = "icmp"
16-
}
17-
1812
struct FirewallRule {
1913
let fromIPAddress: String
2014
let toIPAddress: String
21-
let protocols: [NetworkingProtocol]
15+
let protocols: [NetworkTransportProtocol]
2216

2317
/// - Parameters:
2418
/// - fromIPAddress: Block traffic originating from this source IP address.
2519
/// - toIPAddress: Block traffic to this destination IP address.
2620
/// - protocols: Protocols which should be blocked. If none is specified all will be blocked.
27-
private init(fromIPAddress: String, toIPAddress: String, protocols: [NetworkingProtocol]) {
21+
private init(fromIPAddress: String, toIPAddress: String, protocols: [NetworkTransportProtocol]) {
2822
self.fromIPAddress = fromIPAddress
2923
self.toIPAddress = toIPAddress
3024
self.protocols = protocols

ios/MullvadVPNUITests/Networking/Networking.swift

+7-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ import Foundation
1010
import Network
1111
import XCTest
1212

13+
enum NetworkTransportProtocol: String, Codable {
14+
case TCP = "tcp"
15+
case UDP = "udp"
16+
case ICMP = "icmp"
17+
}
18+
1319
enum NetworkingError: Error {
1420
case notConfiguredError
1521
case internalError(reason: String)
@@ -44,7 +50,7 @@ class Networking {
4450
interfaceAddress.sa_family == UInt8(AF_INET) {
4551
// Check if interface is en0 which is the WiFi connection on the iPhone
4652
let name = String(cString: interfacePointer.pointee.ifa_name)
47-
if name == "en0" {
53+
if name.hasPrefix("en") {
4854
// Convert interface address to a human readable string:
4955
var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST))
5056
if getnameinfo(

0 commit comments

Comments
 (0)