Skip to content

Commit

Permalink
Implement packet capture client and models
Browse files Browse the repository at this point in the history
  • Loading branch information
niklasberglund committed Jun 4, 2024
1 parent 5fc0b40 commit c49e7a7
Show file tree
Hide file tree
Showing 10 changed files with 373 additions and 31 deletions.
5 changes: 4 additions & 1 deletion ios/Configurations/UITests.xcconfig.template
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,8 @@ 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

// 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
28 changes: 28 additions & 0 deletions ios/MullvadVPN.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -652,13 +652,17 @@
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 */; };
85978A542BE0F10E00F999A7 /* PacketCaptureAPIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85978A532BE0F10E00F999A7 /* PacketCaptureAPIClient.swift */; };
85A42B862BB1D627007BABF7 /* XCUIElement+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85A42B852BB1D627007BABF7 /* XCUIElement+Extensions.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 */; };
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 @@ -1980,11 +1984,15 @@
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>"; };
85978A532BE0F10E00F999A7 /* PacketCaptureAPIClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PacketCaptureAPIClient.swift; sourceTree = "<group>"; };
85A42B852BB1D627007BABF7 /* XCUIElement+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCUIElement+Extensions.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>"; };
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 @@ -3874,6 +3882,13 @@
isa = PBXGroup;
children = (
8518F6392B601910009EB113 /* Base */,
85F1E17F2C0A29FA00DB8F55 /* External apps */,
852969272B4D9C1F007EAD4C /* AccountTests.swift */,
8556EB532B9A1D7100D26DD4 /* BridgingHeader.h */,
85557B112B594FC900795FE1 /* ConnectivityTests.swift */,
A9BFAFFE2BD004ED00F2BCA1 /* CustomListsTests.swift */,
852969372B4ED20E007EAD4C /* Info.plist */,
85B267602B849ADB0098E3CD /* mullvad-api.h */,
85557B0C2B591B0F00795FE1 /* Networking */,
852969312B4E9220007EAD4C /* Pages */,
7A45CFCD2C08697100D80B21 /* Screenshots */,
Expand All @@ -3888,6 +3903,7 @@
85C7A2E82B89024B00035D5A /* SettingsTests.swift */,
856952E12BD6B04C008C1F84 /* XCUIElement+Extensions.swift */,
85557B152B5ABBBE00795FE1 /* XCUIElementQuery+Extensions.swift */,
85F1E17D2C0A256200DB8F55 /* LeakTests.swift */,
);
path = MullvadVPNUITests;
sourceTree = "<group>";
Expand Down Expand Up @@ -3933,10 +3949,19 @@
85557B0F2B59215F00795FE1 /* FirewallRule.swift */,
85557B132B5983CF00795FE1 /* MullvadAPIWrapper.swift */,
85E3BDE42B70E18C00FA71FD /* Networking.swift */,
85978A532BE0F10E00F999A7 /* PacketCaptureAPIClient.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 @@ -6019,6 +6044,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 */,
850201DB2B503D7700EF8C96 /* RelayTests.swift in Sources */,
7A45CFC62C05FF6A00D80B21 /* ScreenshotTests.swift in Sources */,
Expand All @@ -6041,9 +6067,11 @@
8587A05D2B84D43100152938 /* ChangeLogAlert.swift in Sources */,
85FB5A102B6960A30015DCED /* AccountDeletionPage.swift in Sources */,
7A45CFC72C071DD400D80B21 /* SnapshotHelper.swift in Sources */,
85F1E17E2C0A256200DB8F55 /* LeakTests.swift in Sources */,
85557B162B5ABBBE00795FE1 /* XCUIElementQuery+Extensions.swift in Sources */,
855D9F5B2B63E56B00D7C64D /* ProblemReportPage.swift in Sources */,
8529693A2B4F0238007EAD4C /* TermsOfServicePage.swift in Sources */,
85978A542BE0F10E00F999A7 /* PacketCaptureAPIClient.swift in Sources */,
85A42B882BB44D31007BABF7 /* DeviceManagementPage.swift in Sources */,
8532E6872B8CCED600ACECD1 /* ProblemReportSubmittedPage.swift in Sources */,
85FB5A0C2B6903990015DCED /* WelcomePage.swift in Sources */,
Expand Down

This file was deleted.

56 changes: 56 additions & 0 deletions ios/MullvadVPNUITests/Base/BaseUITestCase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ class BaseUITestCase: XCTestCase {
static let veryLongTimeout = 60.0
static let shortTimeout = 1.0

/// 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 @@ -56,6 +63,29 @@ class BaseUITestCase: XCTestCase {
}
}

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

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

let packetCaptureAPIClient = PacketCaptureAPIClient()
packetCaptureAPIClient.stopCapture(session: packetCaptureSession)
let capturedData = packetCaptureAPIClient.getParsedCapture(session: packetCaptureSession)

return capturedData
}

// MARK: - Setup & teardown

/// Override this class function to change the uninstall behaviour in suite level teardown
Expand All @@ -72,12 +102,38 @@ 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 = PacketCaptureAPIClient()

// 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)
}

app.terminate()
}

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
}
}
2 changes: 2 additions & 0 deletions ios/MullvadVPNUITests/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
<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>ShouldBeReachableDomain</key>
<string>$(SHOULD_BE_REACHABLE_DOMAIN)</string>
<key>TestDeviceIdentifier</key>
Expand Down
40 changes: 40 additions & 0 deletions ios/MullvadVPNUITests/LeakTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//
// LeakTests.swift
// MullvadVPNUITests
//
// Created by Niklas Berglund on 2024-05-31.
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
//

import XCTest

class LeakTests: LoggedInWithTimeUITestCase {
/// For now just the skeleton of a leak test - traffic is captured and parsed, but not analyzed
func testLeaks() throws {
startPacketCapture()

TunnelControlPage(app)
.tapSecureConnectionButton()

allowAddVPNConfigurationsIfAsked()

TunnelControlPage(app)
.waitForSecureConnectionLabel()

// Trigger traffic by navigating to website in Safari
let safariApp = SafariApp()
safariApp.launch()
safariApp.tapAddressBar()
safariApp.enterText("mullvad.net\n")

app.launch()
TunnelControlPage(app)
.tapDisconnectButton()

// Keep the capture open for a while
Thread.sleep(forTimeInterval: 5.0)
let capturedTraffic = stopPacketCapture()

// Analyze captured traffic
}
}
10 changes: 2 additions & 8 deletions ios/MullvadVPNUITests/Networking/FirewallRule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,16 @@
import Foundation
import XCTest

enum NetworkingProtocol: String {
case TCP = "tcp"
case UDP = "udp"
case ICMP = "icmp"
}

struct FirewallRule {
let fromIPAddress: String
let toIPAddress: String
let protocols: [NetworkingProtocol]
let protocols: [NetworkTransportProtocol]

/// - Parameters:
/// - fromIPAddress: Block traffic originating from this source IP address.
/// - toIPAddress: Block traffic to this destination IP address.
/// - protocols: Protocols which should be blocked. If none is specified all will be blocked.
private init(fromIPAddress: String, toIPAddress: String, protocols: [NetworkingProtocol]) {
private init(fromIPAddress: String, toIPAddress: String, protocols: [NetworkTransportProtocol]) {
self.fromIPAddress = fromIPAddress
self.toIPAddress = toIPAddress
self.protocols = protocols
Expand Down
6 changes: 6 additions & 0 deletions ios/MullvadVPNUITests/Networking/Networking.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ import Foundation
import Network
import XCTest

enum NetworkTransportProtocol: String, Codable {
case TCP = "tcp"
case UDP = "udp"
case ICMP = "icmp"
}

enum NetworkingError: Error {
case notConfiguredError
case internalError(reason: String)
Expand Down
Loading

0 comments on commit c49e7a7

Please sign in to comment.