Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add negative leak test for iOS #6449

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion ios/Configurations/UITests.xcconfig.template
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,14 @@ AD_SERVING_DOMAIN = vpnlist.to
// A domain which should be reachable. Used to verify Internet connectivity. Must be running a server on port 80.
SHOULD_BE_REACHABLE_DOMAIN = mullvad.net

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

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

// Specify whether app logs should be extracted and attached to test report for failing tests
ATTACH_APP_LOGS_ON_FAILURE = 0

// 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
PACKET_CAPTURE_BASE_URL = http:/${}/8.8.8.8
38 changes: 33 additions & 5 deletions ios/MullvadVPN.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -653,13 +653,18 @@
8585CBE32BC684180015B6A4 /* EditAccessMethodPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8585CBE22BC684180015B6A4 /* EditAccessMethodPage.swift */; };
8587A05D2B84D43100152938 /* ChangeLogAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8587A05C2B84D43100152938 /* ChangeLogAlert.swift */; };
8590896F2B61763B003AF5F5 /* LoggedOutUITestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8590896B2B61763B003AF5F5 /* LoggedOutUITestCase.swift */; };
8590A5442C2AF43400B9BF7B /* TrafficGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8590A5432C2AF43400B9BF7B /* TrafficGenerator.swift */; };
85978A542BE0F10E00F999A7 /* PacketCapture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85978A532BE0F10E00F999A7 /* PacketCapture.swift */; };
85A42B882BB44D31007BABF7 /* DeviceManagementPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85A42B872BB44D31007BABF7 /* DeviceManagementPage.swift */; };
85B267612B849ADB0098E3CD /* mullvad-api.h in Headers */ = {isa = PBXBuildFile; fileRef = 85B267602B849ADB0098E3CD /* mullvad-api.h */; };
85BEC23C2C29C41C0065A68D /* StreamCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85BEC23B2C29C41C0065A68D /* StreamCollection.swift */; };
85C7A2E92B89024B00035D5A /* SettingsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85C7A2E82B89024B00035D5A /* SettingsTests.swift */; };
85D039982BA4711800940E7F /* SettingsMigrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85D039972BA4711800940E7F /* SettingsMigrationTests.swift */; };
85D2B0B12B6BD32400DF9DA7 /* BaseUITestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8590896A2B61763B003AF5F5 /* BaseUITestCase.swift */; };
85E3BDE52B70E18C00FA71FD /* Networking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85E3BDE42B70E18C00FA71FD /* Networking.swift */; };
85EC620C2B838D10005AFFB5 /* MullvadAPIWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85557B132B5983CF00795FE1 /* MullvadAPIWrapper.swift */; };
85F1E17E2C0A256200DB8F55 /* LeakTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85F1E17D2C0A256200DB8F55 /* LeakTests.swift */; };
85F1E1812C0A2A0C00DB8F55 /* SafariApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85F1E1802C0A2A0C00DB8F55 /* SafariApp.swift */; };
85FB5A0C2B6903990015DCED /* WelcomePage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85FB5A0B2B6903990015DCED /* WelcomePage.swift */; };
85FB5A102B6960A30015DCED /* AccountDeletionPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85FB5A0F2B6960A30015DCED /* AccountDeletionPage.swift */; };
A90763B02B2857D50045ADF0 /* Socks5ConnectCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = A90763A02B2857D50045ADF0 /* Socks5ConnectCommand.swift */; };
Expand Down Expand Up @@ -2043,11 +2048,16 @@
859089692B61763B003AF5F5 /* LoggedInWithTimeUITestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoggedInWithTimeUITestCase.swift; sourceTree = "<group>"; };
8590896A2B61763B003AF5F5 /* BaseUITestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseUITestCase.swift; sourceTree = "<group>"; };
8590896B2B61763B003AF5F5 /* LoggedOutUITestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoggedOutUITestCase.swift; sourceTree = "<group>"; };
8590A5432C2AF43400B9BF7B /* TrafficGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrafficGenerator.swift; sourceTree = "<group>"; };
85978A532BE0F10E00F999A7 /* PacketCapture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PacketCapture.swift; sourceTree = "<group>"; };
85A42B872BB44D31007BABF7 /* DeviceManagementPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceManagementPage.swift; sourceTree = "<group>"; };
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>"; };
85BEC23B2C29C41C0065A68D /* StreamCollection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StreamCollection.swift; sourceTree = "<group>"; };
85C7A2E82B89024B00035D5A /* SettingsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsTests.swift; sourceTree = "<group>"; };
85D039972BA4711800940E7F /* SettingsMigrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsMigrationTests.swift; sourceTree = "<group>"; };
85E3BDE42B70E18C00FA71FD /* Networking.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Networking.swift; sourceTree = "<group>"; };
85F1E17D2C0A256200DB8F55 /* LeakTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LeakTests.swift; sourceTree = "<group>"; };
85F1E1802C0A2A0C00DB8F55 /* SafariApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariApp.swift; sourceTree = "<group>"; };
85FB5A0B2B6903990015DCED /* WelcomePage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomePage.swift; sourceTree = "<group>"; };
85FB5A0F2B6960A30015DCED /* AccountDeletionPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountDeletionPage.swift; sourceTree = "<group>"; };
A900E9B72ACC5C2B00C95F67 /* AccountsProxy+Stubs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AccountsProxy+Stubs.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -3981,21 +3991,23 @@
852969262B4D9C1F007EAD4C /* MullvadVPNUITests */ = {
isa = PBXGroup;
children = (
85557B0C2B591B0F00795FE1 /* Networking */,
852969312B4E9220007EAD4C /* Pages */,
7A45CFCD2C08697100D80B21 /* Screenshots */,
852969372B4ED20E007EAD4C /* Info.plist */,
8556EB532B9A1D7100D26DD4 /* BridgingHeader.h */,
85B267602B849ADB0098E3CD /* mullvad-api.h */,
852969372B4ED20E007EAD4C /* Info.plist */,
852969272B4D9C1F007EAD4C /* AccountTests.swift */,
85557B112B594FC900795FE1 /* ConnectivityTests.swift */,
A9BFAFFE2BD004ED00F2BCA1 /* CustomListsTests.swift */,
85F1E17D2C0A256200DB8F55 /* LeakTests.swift */,
850201DA2B503D7700EF8C96 /* RelayTests.swift */,
85D039972BA4711800940E7F /* SettingsMigrationTests.swift */,
85C7A2E82B89024B00035D5A /* SettingsTests.swift */,
8518F6392B601910009EB113 /* Base */,
856952E12BD6B04C008C1F84 /* XCUIElement+Extensions.swift */,
85557B152B5ABBBE00795FE1 /* XCUIElementQuery+Extensions.swift */,
8518F6392B601910009EB113 /* Base */,
85F1E17F2C0A29FA00DB8F55 /* External apps */,
85557B0C2B591B0F00795FE1 /* Networking */,
852969312B4E9220007EAD4C /* Pages */,
7A45CFCD2C08697100D80B21 /* Screenshots */,
);
path = MullvadVPNUITests;
sourceTree = "<group>";
Expand Down Expand Up @@ -4044,10 +4056,21 @@
85557B132B5983CF00795FE1 /* MullvadAPIWrapper.swift */,
85E3BDE42B70E18C00FA71FD /* Networking.swift */,
856952DB2BD2922A008C1F84 /* PartnerAPIClient.swift */,
85978A532BE0F10E00F999A7 /* PacketCapture.swift */,
85BEC23B2C29C41C0065A68D /* StreamCollection.swift */,
8590A5432C2AF43400B9BF7B /* TrafficGenerator.swift */,
);
path = Networking;
sourceTree = "<group>";
};
85F1E17F2C0A29FA00DB8F55 /* External apps */ = {
isa = PBXGroup;
children = (
85F1E1802C0A2A0C00DB8F55 /* SafariApp.swift */,
);
path = "External apps";
sourceTree = "<group>";
};
A907639F2B2857D50045ADF0 /* Socks5 */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -6203,6 +6226,7 @@
8529693C2B4F0257007EAD4C /* Alert.swift in Sources */,
8542F7532BCFBD050035C042 /* SelectLocationFilterPage.swift in Sources */,
850201DD2B503D8C00EF8C96 /* SelectLocationPage.swift in Sources */,
85F1E1812C0A2A0C00DB8F55 /* SafariApp.swift in Sources */,
85D039982BA4711800940E7F /* SettingsMigrationTests.swift in Sources */,
85021CAE2BDBC4290098B400 /* AppLogsPage.swift in Sources */,
850201DB2B503D7700EF8C96 /* RelayTests.swift in Sources */,
Expand All @@ -6217,6 +6241,7 @@
85557B202B5FBBD700795FE1 /* AccountPage.swift in Sources */,
852969352B4E9270007EAD4C /* LoginPage.swift in Sources */,
A998DA832BD2B055001D61A2 /* EditCustomListLocationsPage.swift in Sources */,
8590A5442C2AF43400B9BF7B /* TrafficGenerator.swift in Sources */,
7ACD79392C0DAADD00DBEE14 /* AddCustomListLocationsPage.swift in Sources */,
8556EB562B9B0AC500D26DD4 /* RevokedDevicePage.swift in Sources */,
A9BFAFFF2BD004ED00F2BCA1 /* CustomListsTests.swift in Sources */,
Expand All @@ -6227,10 +6252,12 @@
85FB5A102B6960A30015DCED /* AccountDeletionPage.swift in Sources */,
7A45CFC72C071DD400D80B21 /* SnapshotHelper.swift in Sources */,
856952DC2BD2922A008C1F84 /* PartnerAPIClient.swift in Sources */,
85F1E17E2C0A256200DB8F55 /* LeakTests.swift in Sources */,
85557B162B5ABBBE00795FE1 /* XCUIElementQuery+Extensions.swift in Sources */,
F0DAC8AF2C1712C300F80144 /* MultihopPromptAlert.swift in Sources */,
855D9F5B2B63E56B00D7C64D /* ProblemReportPage.swift in Sources */,
8529693A2B4F0238007EAD4C /* TermsOfServicePage.swift in Sources */,
85978A542BE0F10E00F999A7 /* PacketCapture.swift in Sources */,
85A42B882BB44D31007BABF7 /* DeviceManagementPage.swift in Sources */,
8532E6872B8CCED600ACECD1 /* ProblemReportSubmittedPage.swift in Sources */,
85FB5A0C2B6903990015DCED /* WelcomePage.swift in Sources */,
Expand All @@ -6244,6 +6271,7 @@
852969332B4E9232007EAD4C /* Page.swift in Sources */,
A9A557F32B7E19B10017ADA8 /* SettingsPage.swift in Sources */,
852D054F2BC43DF7008578D2 /* AddAccessMethodPage.swift in Sources */,
85BEC23C2C29C41C0065A68D /* StreamCollection.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down

This file was deleted.

63 changes: 62 additions & 1 deletion ios/MullvadVPNUITests/Base/BaseUITestCase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ class BaseUITestCase: XCTestCase {
/// Default relay to use in tests
static let testsDefaultRelayName = "se-got-wg-001"

/// True when the current test case is capturing packets
private var currentTestCaseShouldCapturePackets = false

/// True when a packet capture session is active
private var packetCaptureSessionIsActive = false
private var packetCaptureSession: PacketCaptureSession?

// swiftlint:disable force_cast
let displayName = Bundle(for: BaseUITestCase.self)
.infoDictionary?["DisplayName"] as! String
Expand Down Expand Up @@ -100,7 +107,7 @@ class BaseUITestCase: XCTestCase {
func allowAddVPNConfigurationsIfAsked() {
let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard")

if springboard.buttons["Allow"].waitForExistence(timeout: Self.shortTimeout) {
if springboard.buttons["Allowi"].waitForExistence(timeout: Self.shortTimeout) {
let alertAllowButton = springboard.buttons.element(boundBy: 0)
if alertAllowButton.waitForExistence(timeout: Self.defaultTimeout) {
alertAllowButton.tap()
Expand All @@ -125,6 +132,29 @@ class BaseUITestCase: XCTestCase {
}
}

/// Start packet capture for this test case
func startPacketCapture() {
currentTestCaseShouldCapturePackets = true
packetCaptureSessionIsActive = true
let packetCaptureClient = PacketCapture()
packetCaptureSession = packetCaptureClient.startCapture()
}

/// Stop the current packet capture and return captured data
func stopPacketCapture() -> StreamCollection {
packetCaptureSessionIsActive = false
guard let packetCaptureSession else {
XCTFail("Trying to stop capture when there is no active capture")
return StreamCollection(streams: [])
}

let packetCaptureAPIClient = PacketCapture()
packetCaptureAPIClient.stopCapture(session: packetCaptureSession)
let capturedData = packetCaptureAPIClient.getParsedCaptureObjects(session: packetCaptureSession)

return StreamCollection(streams: capturedData)
}

// MARK: - Setup & teardown

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

/// Test level setup
override func setUp() {
currentTestCaseShouldCapturePackets = false // Reset for each test case run
continueAfterFailure = false
app.launch()
}

/// Test level teardown
override func tearDown() {
if currentTestCaseShouldCapturePackets {
guard let packetCaptureSession = packetCaptureSession else {
XCTFail("Packet capture session unexpectedly not set up")
return
}

let packetCaptureClient = PacketCapture()

// If there's a an active session due to cancelled/failed test run make sure to end it
if packetCaptureSessionIsActive {
packetCaptureSessionIsActive = false
packetCaptureClient.stopCapture(session: packetCaptureSession)
}

packetCaptureClient.stopCapture(session: packetCaptureSession)
let pcap = packetCaptureClient.getPCAP(session: packetCaptureSession)
let parsedCapture = packetCaptureClient.getParsedCapture(session: packetCaptureSession)
self.packetCaptureSession = nil

let pcapAttachment = XCTAttachment(data: pcap)
pcapAttachment.name = self.name + ".pcap"
pcapAttachment.lifetime = .keepAlways
self.add(pcapAttachment)

let jsonAttachment = XCTAttachment(data: parsedCapture)
jsonAttachment.name = self.name + ".json"
jsonAttachment.lifetime = .keepAlways
self.add(jsonAttachment)
}

app.terminate()

if let testRun = self.testRun, testRun.failureCount > 0, attachAppLogsOnFailure == true {
Expand Down
27 changes: 27 additions & 0 deletions ios/MullvadVPNUITests/External apps/SafariApp.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//
// SafariApp.swift
// MullvadVPNUITests
//
// Created by Niklas Berglund on 2024-05-31.
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
//

import XCTest

class SafariApp {
let app = XCUIApplication(bundleIdentifier: "com.apple.mobilesafari")

func launch() {
app.launch()
}

@discardableResult func tapAddressBar() -> Self {
app.textFields.firstMatch.tap()
return self
}

@discardableResult func enterText(_ text: String) -> Self {
app.typeText(text)
return self
}
}
4 changes: 4 additions & 0 deletions ios/MullvadVPNUITests/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,14 @@
<string>$(IOS_DEVICE_PIN_CODE)</string>
<key>NoTimeAccountNumber</key>
<string>$(NO_TIME_ACCOUNT_NUMBER)</string>
<key>PacketCaptureAPIBaseURL</key>
<string>$(PACKET_CAPTURE_BASE_URL)</string>
<key>PartnerApiToken</key>
<string>$(PARTNER_API_TOKEN)</string>
<key>ShouldBeReachableDomain</key>
<string>$(SHOULD_BE_REACHABLE_DOMAIN)</string>
<key>ShouldBeReachableIPAddress</key>
<string>$(SHOULD_BE_REACHABLE_IP_ADDRESS)</string>
<key>TestDeviceIdentifier</key>
<string>$(TEST_DEVICE_IDENTIFIER_UUID</string>
</dict>
Expand Down
76 changes: 76 additions & 0 deletions ios/MullvadVPNUITests/LeakTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
//
// LeakTests.swift
// MullvadVPNUITests
//
// Created by Niklas Berglund on 2024-05-31.
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
//

import XCTest

class LeakTests: LoggedInWithTimeUITestCase {
override func tearDown() {
FirewallAPIClient().removeRules()
}

/// Send UDP traffic to a host, connect to relay and make sure while connected to relay no traffic leaked went directly to the host
func testNegativeLeaks() throws {
let testIpAddress = Networking.getAlwaysReachableIPAddress()
FirewallAPIClient().createRule(try FirewallRule.makeBlockAllTrafficRule(toIPAddress: testIpAddress))
startPacketCapture()
let trafficGenerator = TrafficGenerator(destinationHost: testIpAddress, port: 80)
trafficGenerator.startGeneratingUDPTraffic(interval: 1.0)

TunnelControlPage(app)
.tapSecureConnectionButton()

allowAddVPNConfigurationsIfAsked()

TunnelControlPage(app)
.waitForSecureConnectionLabel()
let connectedDate = Date()

let relayIPAddress = TunnelControlPage(app)
.getInIPAddressFromConnectionStatus()

// Keep the tunnel connection for a while
Thread.sleep(forTimeInterval: 5.0)

app.launch()
TunnelControlPage(app)
.tapDisconnectButton()
let disconnectedDate = Date()

// Keep the capture open for a while
Thread.sleep(forTimeInterval: 3.0)
trafficGenerator.stopGeneratingUDPTraffic()

let capturedStreamCollection = stopPacketCapture()

do {
let relayConnectionDateInterval = try capturedStreamCollection
.getConnectedThroughRelayDateInterval(
relayIPAddress: relayIPAddress
)

// Get traffic from time window of connection with some leeway
let secondsLeeway = 2.0
let connectedDateWithLeeway = relayConnectionDateInterval.start.addingTimeInterval(secondsLeeway)
let disconnectedDateWithLeeway = relayConnectionDateInterval.end.addingTimeInterval(-secondsLeeway)
let connectedToRelayDateIntervalWithLeeway = DateInterval(
start: connectedDateWithLeeway,
end: disconnectedDateWithLeeway
)
let connectedThroughRelayStreamCollection = capturedStreamCollection.extractStreamCollectionFrom(
connectedToRelayDateIntervalWithLeeway,
cutOffPacketsOverflow: true
)

// Treat any traffic to the test IP address during the connected time window as leak
connectedThroughRelayStreamCollection.dontAllowTrafficFromTestDevice(to: testIpAddress)
connectedThroughRelayStreamCollection.verifyDontHaveLeaks()
} catch {
XCTFail("Unexpectedly didn't find any traffic between test device and relay")
}
}
}
Loading
Loading