Skip to content

Commit 89e9074

Browse files
niklasberglundbuggmagnet
authored andcommitted
Implement packet capture client and models
1 parent 9c8c76f commit 89e9074

File tree

10 files changed

+380
-11
lines changed

10 files changed

+380
-11
lines changed

ios/Configurations/UITests.xcconfig.template

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

27-
// 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
27+
// 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
2828
FIREWALL_API_BASE_URL = http:/${}/8.8.8.8
2929

3030
// 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.
3131
AM_I_JSON_URL = https:/${}/am.i.stagemole.eu/json
3232

3333
// Specify whether app logs should be extracted and attached to test report for failing tests
3434
ATTACH_APP_LOGS_ON_FAILURE = 0
35+
36+
// 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
37+
PACKET_CAPTURE_BASE_URL = http:/${}/8.8.8.8

ios/MullvadVPN.xcodeproj/project.pbxproj

+21
Original file line numberDiff line numberDiff line change
@@ -647,13 +647,16 @@
647647
8585CBE32BC684180015B6A4 /* EditAccessMethodPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8585CBE22BC684180015B6A4 /* EditAccessMethodPage.swift */; };
648648
8587A05D2B84D43100152938 /* ChangeLogAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8587A05C2B84D43100152938 /* ChangeLogAlert.swift */; };
649649
8590896F2B61763B003AF5F5 /* LoggedOutUITestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8590896B2B61763B003AF5F5 /* LoggedOutUITestCase.swift */; };
650+
85978A542BE0F10E00F999A7 /* PacketCaptureAPIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85978A532BE0F10E00F999A7 /* PacketCaptureAPIClient.swift */; };
650651
85A42B882BB44D31007BABF7 /* DeviceManagementPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85A42B872BB44D31007BABF7 /* DeviceManagementPage.swift */; };
651652
85B267612B849ADB0098E3CD /* mullvad-api.h in Headers */ = {isa = PBXBuildFile; fileRef = 85B267602B849ADB0098E3CD /* mullvad-api.h */; };
652653
85C7A2E92B89024B00035D5A /* SettingsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85C7A2E82B89024B00035D5A /* SettingsTests.swift */; };
653654
85D039982BA4711800940E7F /* SettingsMigrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85D039972BA4711800940E7F /* SettingsMigrationTests.swift */; };
654655
85D2B0B12B6BD32400DF9DA7 /* BaseUITestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8590896A2B61763B003AF5F5 /* BaseUITestCase.swift */; };
655656
85E3BDE52B70E18C00FA71FD /* Networking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85E3BDE42B70E18C00FA71FD /* Networking.swift */; };
656657
85EC620C2B838D10005AFFB5 /* MullvadAPIWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85557B132B5983CF00795FE1 /* MullvadAPIWrapper.swift */; };
658+
85F1E17E2C0A256200DB8F55 /* LeakTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85F1E17D2C0A256200DB8F55 /* LeakTests.swift */; };
659+
85F1E1812C0A2A0C00DB8F55 /* SafariApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85F1E1802C0A2A0C00DB8F55 /* SafariApp.swift */; };
657660
85FB5A0C2B6903990015DCED /* WelcomePage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85FB5A0B2B6903990015DCED /* WelcomePage.swift */; };
658661
85FB5A102B6960A30015DCED /* AccountDeletionPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85FB5A0F2B6960A30015DCED /* AccountDeletionPage.swift */; };
659662
A90763B02B2857D50045ADF0 /* Socks5ConnectCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = A90763A02B2857D50045ADF0 /* Socks5ConnectCommand.swift */; };
@@ -1953,11 +1956,14 @@
19531956
859089692B61763B003AF5F5 /* LoggedInWithTimeUITestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoggedInWithTimeUITestCase.swift; sourceTree = "<group>"; };
19541957
8590896A2B61763B003AF5F5 /* BaseUITestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseUITestCase.swift; sourceTree = "<group>"; };
19551958
8590896B2B61763B003AF5F5 /* LoggedOutUITestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoggedOutUITestCase.swift; sourceTree = "<group>"; };
1959+
85978A532BE0F10E00F999A7 /* PacketCaptureAPIClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PacketCaptureAPIClient.swift; sourceTree = "<group>"; };
19561960
85A42B872BB44D31007BABF7 /* DeviceManagementPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceManagementPage.swift; sourceTree = "<group>"; };
19571961
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>"; };
19581962
85C7A2E82B89024B00035D5A /* SettingsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsTests.swift; sourceTree = "<group>"; };
19591963
85D039972BA4711800940E7F /* SettingsMigrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsMigrationTests.swift; sourceTree = "<group>"; };
19601964
85E3BDE42B70E18C00FA71FD /* Networking.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Networking.swift; sourceTree = "<group>"; };
1965+
85F1E17D2C0A256200DB8F55 /* LeakTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LeakTests.swift; sourceTree = "<group>"; };
1966+
85F1E1802C0A2A0C00DB8F55 /* SafariApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariApp.swift; sourceTree = "<group>"; };
19611967
85FB5A0B2B6903990015DCED /* WelcomePage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomePage.swift; sourceTree = "<group>"; };
19621968
85FB5A0F2B6960A30015DCED /* AccountDeletionPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountDeletionPage.swift; sourceTree = "<group>"; };
19631969
A900E9B72ACC5C2B00C95F67 /* AccountsProxy+Stubs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AccountsProxy+Stubs.swift"; sourceTree = "<group>"; };
@@ -3894,12 +3900,15 @@
38943900
852969272B4D9C1F007EAD4C /* AccountTests.swift */,
38953901
85557B112B594FC900795FE1 /* ConnectivityTests.swift */,
38963902
A9BFAFFE2BD004ED00F2BCA1 /* CustomListsTests.swift */,
3903+
85F1E17D2C0A256200DB8F55 /* LeakTests.swift */,
38973904
850201DA2B503D7700EF8C96 /* RelayTests.swift */,
38983905
85D039972BA4711800940E7F /* SettingsMigrationTests.swift */,
38993906
85C7A2E82B89024B00035D5A /* SettingsTests.swift */,
39003907
8518F6392B601910009EB113 /* Base */,
39013908
856952E12BD6B04C008C1F84 /* XCUIElement+Extensions.swift */,
39023909
85557B152B5ABBBE00795FE1 /* XCUIElementQuery+Extensions.swift */,
3910+
8518F6392B601910009EB113 /* Base */,
3911+
85F1E17F2C0A29FA00DB8F55 /* External apps */,
39033912
);
39043913
path = MullvadVPNUITests;
39053914
sourceTree = "<group>";
@@ -3947,10 +3956,19 @@
39473956
85557B132B5983CF00795FE1 /* MullvadAPIWrapper.swift */,
39483957
85E3BDE42B70E18C00FA71FD /* Networking.swift */,
39493958
856952DB2BD2922A008C1F84 /* PartnerAPIClient.swift */,
3959+
85978A532BE0F10E00F999A7 /* PacketCaptureAPIClient.swift */,
39503960
);
39513961
path = Networking;
39523962
sourceTree = "<group>";
39533963
};
3964+
85F1E17F2C0A29FA00DB8F55 /* External apps */ = {
3965+
isa = PBXGroup;
3966+
children = (
3967+
85F1E1802C0A2A0C00DB8F55 /* SafariApp.swift */,
3968+
);
3969+
path = "External apps";
3970+
sourceTree = "<group>";
3971+
};
39543972
A907639F2B2857D50045ADF0 /* Socks5 */ = {
39553973
isa = PBXGroup;
39563974
children = (
@@ -6021,6 +6039,7 @@
60216039
8529693C2B4F0257007EAD4C /* Alert.swift in Sources */,
60226040
8542F7532BCFBD050035C042 /* SelectLocationFilterPage.swift in Sources */,
60236041
850201DD2B503D8C00EF8C96 /* SelectLocationPage.swift in Sources */,
6042+
85F1E1812C0A2A0C00DB8F55 /* SafariApp.swift in Sources */,
60246043
85D039982BA4711800940E7F /* SettingsMigrationTests.swift in Sources */,
60256044
85021CAE2BDBC4290098B400 /* AppLogsPage.swift in Sources */,
60266045
850201DB2B503D7700EF8C96 /* RelayTests.swift in Sources */,
@@ -6045,9 +6064,11 @@
60456064
85FB5A102B6960A30015DCED /* AccountDeletionPage.swift in Sources */,
60466065
7A45CFC72C071DD400D80B21 /* SnapshotHelper.swift in Sources */,
60476066
856952DC2BD2922A008C1F84 /* PartnerAPIClient.swift in Sources */,
6067+
85F1E17E2C0A256200DB8F55 /* LeakTests.swift in Sources */,
60486068
85557B162B5ABBBE00795FE1 /* XCUIElementQuery+Extensions.swift in Sources */,
60496069
855D9F5B2B63E56B00D7C64D /* ProblemReportPage.swift in Sources */,
60506070
8529693A2B4F0238007EAD4C /* TermsOfServicePage.swift in Sources */,
6071+
85978A542BE0F10E00F999A7 /* PacketCaptureAPIClient.swift in Sources */,
60516072
85A42B882BB44D31007BABF7 /* DeviceManagementPage.swift in Sources */,
60526073
8532E6872B8CCED600ACECD1 /* ProblemReportSubmittedPage.swift in Sources */,
60536074
85FB5A0C2B6903990015DCED /* WelcomePage.swift in Sources */,

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
@@ -142,6 +149,29 @@ class BaseUITestCase: XCTestCase {
142149
}
143150
}
144151

152+
/// Start packet capture for this test case
153+
func startPacketCapture() {
154+
currentTestCaseShouldCapturePackets = true
155+
packetCaptureSessionIsActive = true
156+
let packetCaptureClient = PacketCaptureAPIClient()
157+
packetCaptureSession = packetCaptureClient.startCapture()
158+
}
159+
160+
/// Stop the current packet capture and return captured data
161+
func stopPacketCapture() -> [Stream] {
162+
packetCaptureSessionIsActive = false
163+
guard let packetCaptureSession else {
164+
XCTFail("Trying to stop capture when there is no active capture")
165+
return []
166+
}
167+
168+
let packetCaptureAPIClient = PacketCaptureAPIClient()
169+
packetCaptureAPIClient.stopCapture(session: packetCaptureSession)
170+
let capturedData = packetCaptureAPIClient.getParsedCaptureObjects(session: packetCaptureSession)
171+
172+
return capturedData
173+
}
174+
145175
// MARK: - Setup & teardown
146176

147177
/// Override this class function to change the uninstall behaviour in suite level teardown
@@ -158,12 +188,43 @@ class BaseUITestCase: XCTestCase {
158188

159189
/// Test level setup
160190
override func setUp() {
191+
currentTestCaseShouldCapturePackets = false // Reset for each test case run
161192
continueAfterFailure = false
162193
app.launch()
163194
}
164195

165196
/// Test level teardown
166197
override func tearDown() {
198+
if currentTestCaseShouldCapturePackets {
199+
guard let packetCaptureSession = packetCaptureSession else {
200+
XCTFail("Packet capture session unexpectedly not set up")
201+
return
202+
}
203+
204+
let packetCaptureClient = PacketCaptureAPIClient()
205+
206+
// If there's a an active session due to cancelled/failed test run make sure to end it
207+
if packetCaptureSessionIsActive {
208+
packetCaptureSessionIsActive = false
209+
packetCaptureClient.stopCapture(session: packetCaptureSession)
210+
}
211+
212+
packetCaptureClient.stopCapture(session: packetCaptureSession)
213+
let pcap = packetCaptureClient.getPCAP(session: packetCaptureSession)
214+
let parsedCapture = packetCaptureClient.getParsedCapture(session: packetCaptureSession)
215+
self.packetCaptureSession = nil
216+
217+
let pcapAttachment = XCTAttachment(data: pcap)
218+
pcapAttachment.name = self.name + ".pcap"
219+
pcapAttachment.lifetime = .keepAlways
220+
self.add(pcapAttachment)
221+
222+
let jsonAttachment = XCTAttachment(data: parsedCapture)
223+
jsonAttachment.name = self.name + ".json"
224+
jsonAttachment.lifetime = .keepAlways
225+
self.add(jsonAttachment)
226+
}
227+
167228
app.terminate()
168229

169230
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)