summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorBug Magnet <marco.nikic@mullvad.net>2025-09-04 14:44:16 +0200
committerBug Magnet <marco.nikic@mullvad.net>2025-09-04 14:44:16 +0200
commite02425beead9652439193d672cd8ce58b52039f7 (patch)
tree0b898164a73e0cfacaa299fd117b9cab7b0ca834
parent3b6dc6cf42e53e775a7eb35682270ca2f81c7d64 (diff)
parentd4d8308a02f1673f5d96d13ebc416695b77d8ee6 (diff)
downloadmullvadvpn-e02425beead9652439193d672cd8ce58b52039f7.tar.xz
mullvadvpn-e02425beead9652439193d672cd8ce58b52039f7.zip
Merge branch 'figure-out-a-way-to-test-daita-fix-ios-1112'
-rw-r--r--ci/ios/test-router/raas/src/capture/parse.rs6
-rw-r--r--ios/MullvadVPNUITests/Networking/PacketCapture.swift8
-rw-r--r--ios/MullvadVPNUITests/Pages/TunnelControlPage.swift8
-rw-r--r--ios/MullvadVPNUITests/RelayTests.swift157
4 files changed, 145 insertions, 34 deletions
diff --git a/ci/ios/test-router/raas/src/capture/parse.rs b/ci/ios/test-router/raas/src/capture/parse.rs
index e8f96c280a..a72ccaf233 100644
--- a/ci/ios/test-router/raas/src/capture/parse.rs
+++ b/ci/ios/test-router/raas/src/capture/parse.rs
@@ -58,6 +58,7 @@ pub struct ConnectionId {
pub struct PacketTransmission {
from_peer: bool,
timestamp: u64,
+ size: u32,
}
#[derive(Default, Debug)]
@@ -108,8 +109,8 @@ impl ParsedConnections {
}
let transport_protocol = packet.transport_protocol();
- let Some((source_port, destination_port)) =
- packet_ports(packet.payload(), transport_protocol)
+ let payload = packet.payload();
+ let Some((source_port, destination_port)) = packet_ports(payload, transport_protocol)
else {
log::debug!("Failed to parse an IP packet from {source} to {destination}");
return;
@@ -137,6 +138,7 @@ impl ParsedConnections {
let packet_transmission = PacketTransmission {
from_peer: self.ip_matches_peer(source),
timestamp,
+ size: u32::try_from(payload.len()).unwrap(),
};
self.connections
diff --git a/ios/MullvadVPNUITests/Networking/PacketCapture.swift b/ios/MullvadVPNUITests/Networking/PacketCapture.swift
index 1b31af2d4d..6b7fd355bb 100644
--- a/ios/MullvadVPNUITests/Networking/PacketCapture.swift
+++ b/ios/MullvadVPNUITests/Networking/PacketCapture.swift
@@ -114,11 +114,15 @@ class Packet: Codable, Equatable {
/// Timestamp in microseconds
private var timestamp: Int64
+ // The size of the packet
+ public let size: Int32
+
public var date: Date
enum CodingKeys: String, CodingKey {
case fromPeer = "from_peer"
case timestamp
+ case size
}
required init(from decoder: Decoder) throws {
@@ -126,12 +130,14 @@ class Packet: Codable, Equatable {
fromPeer = try container.decode(Bool.self, forKey: .fromPeer)
timestamp = try container.decode(Int64.self, forKey: .timestamp) / 1000000
date = Date(timeIntervalSince1970: TimeInterval(timestamp))
+ size = try container.decode(Int32.self, forKey: .size)
}
static func == (lhs: Packet, rhs: Packet) -> Bool {
return lhs.fromPeer == rhs.fromPeer &&
lhs.timestamp == rhs.timestamp &&
- lhs.date == rhs.date
+ lhs.date == rhs.date &&
+ lhs.size == rhs.size
}
}
diff --git a/ios/MullvadVPNUITests/Pages/TunnelControlPage.swift b/ios/MullvadVPNUITests/Pages/TunnelControlPage.swift
index c591a39683..7f3d2295e6 100644
--- a/ios/MullvadVPNUITests/Pages/TunnelControlPage.swift
+++ b/ios/MullvadVPNUITests/Pages/TunnelControlPage.swift
@@ -216,9 +216,13 @@ class TunnelControlPage: Page {
return self
}
- func getInIPAddressFromConnectionStatus() -> String {
+ func getInIPAddressAndPortFromConnectionStatus() -> (String, Int) {
let inAddressRow = app.staticTexts[.connectionPanelInAddressRow]
- return inAddressRow.label.components(separatedBy: ":")[0]
+ // The row looks like this "85.203.53.145:43030 UDP"
+ let components = inAddressRow.label.components(separatedBy: ":")
+ let inIpAddress = components[0] // 85.203.53.145
+ let inPort = components[1].components(separatedBy: " ")[0] // 43030 UDP, take only the port part
+ return (inIpAddress, Int(inPort)!)
}
func getCurrentRelayName() -> String {
diff --git a/ios/MullvadVPNUITests/RelayTests.swift b/ios/MullvadVPNUITests/RelayTests.swift
index 1e790b93e9..13dc40fd94 100644
--- a/ios/MullvadVPNUITests/RelayTests.swift
+++ b/ios/MullvadVPNUITests/RelayTests.swift
@@ -177,13 +177,13 @@ class RelayTests: LoggedInWithTimeUITestCase {
TunnelControlPage(app)
.waitForConnectedLabel()
- let connectedToIPAddress = TunnelControlPage(app)
+ let (connectedToIPAddress, _) = TunnelControlPage(app)
.tapRelayStatusExpandCollapseButton()
- .getInIPAddressFromConnectionStatus()
+ .getInIPAddressAndPortFromConnectionStatus()
try Networking.verifyCanAccessInternet()
- try generateTraffic(to: connectedToIPAddress, on: 80, assertProtocol: .TCP)
+ try generateTrafficAndDisconnect(from: connectedToIPAddress, searchForPort: 80, assertProtocol: .TCP)
}
func testWireGuardOverShadowsocksCustomPort() throws {
@@ -233,13 +233,13 @@ class RelayTests: LoggedInWithTimeUITestCase {
TunnelControlPage(app)
.waitForConnectedLabel()
- let connectedToIPAddress = TunnelControlPage(app)
+ let (connectedToIPAddress, _) = TunnelControlPage(app)
.tapRelayStatusExpandCollapseButton()
- .getInIPAddressFromConnectionStatus()
+ .getInIPAddressAndPortFromConnectionStatus()
try Networking.verifyCanAccessInternet()
- try generateTraffic(to: connectedToIPAddress, on: 51900, assertProtocol: .UDP)
+ try generateTrafficAndDisconnect(from: connectedToIPAddress, searchForPort: 51900, assertProtocol: .UDP)
}
func testWireGuardOverTCPManually() throws {
@@ -368,12 +368,12 @@ class RelayTests: LoggedInWithTimeUITestCase {
TunnelControlPage(app)
.waitForConnectedLabel()
- let connectedToIPAddress = TunnelControlPage(app)
+ let (connectedToIPAddress, _) = TunnelControlPage(app)
.tapRelayStatusExpandCollapseButton()
- .getInIPAddressFromConnectionStatus()
+ .getInIPAddressAndPortFromConnectionStatus()
- let relayIPAddress = TunnelControlPage(app)
- .getInIPAddressFromConnectionStatus()
+ let (relayIPAddress, _) = TunnelControlPage(app)
+ .getInIPAddressAndPortFromConnectionStatus()
// Disconnect in order to create firewall rules, otherwise the test router cannot be reached
TunnelControlPage(app)
@@ -393,7 +393,7 @@ class RelayTests: LoggedInWithTimeUITestCase {
try Networking.verifyCanAccessInternet()
- try generateTraffic(to: connectedToIPAddress, on: 443, assertProtocol: .UDP)
+ try generateTrafficAndDisconnect(from: connectedToIPAddress, searchForPort: 443, assertProtocol: .UDP)
}
/// Test automatic switching to TCP is functioning when UDP traffic to relay is blocked. This test first connects to a realy to get the IP address of it, in order to block UDP traffic to this relay.
@@ -472,17 +472,7 @@ class RelayTests: LoggedInWithTimeUITestCase {
}
func testDAITASettings() throws {
- // Undo enabling DAITA in teardown
- addTeardownBlock {
- HeaderBar(self.app)
- .tapSettingsButton()
-
- SettingsPage(self.app)
- .tapDAITACell()
-
- DAITAPage(self.app)
- .tapEnableSwitchIfOn()
- }
+ try disableDaitaInTeardown()
HeaderBar(app)
.tapSettingsButton()
@@ -521,6 +511,81 @@ class RelayTests: LoggedInWithTimeUITestCase {
.tapDisconnectButton()
}
+ func testDaitaIncreasesAverageDataConsumption() throws {
+ // Verify daita is off
+ HeaderBar(app)
+ .tapSettingsButton()
+
+ SettingsPage(app)
+ .verifyDAITAOff()
+ .tapDoneButton()
+
+ // Get packet capture #1
+ let (firstIPAddress, firstPort, streamWithoutDaita) = try generateTrafficSample()
+
+ // Turn on daita
+ HeaderBar(app)
+ .tapSettingsButton()
+
+ SettingsPage(app)
+ .verifyDAITAOff()
+ .tapDAITACell()
+
+ DAITAPage(app)
+ .verifyTwoPages()
+ .verifyDirectOnlySwitchIsDisabled()
+ .tapEnableSwitch()
+ .tapBackButton()
+
+ SettingsPage(app)
+ .verifyDAITAOn()
+ .tapDoneButton()
+
+ try disableDaitaInTeardown()
+
+ // Get packet capture #2
+ let (secondIpAddress, secondPort, streamWithDaita) = try generateTrafficSample()
+
+ // Compare packet capture #1 and #2 mean packet size
+ let packetStreamWithoutDaita = try XCTUnwrap(streamWithoutDaita
+ .filter { $0.destinationAddress == firstIPAddress && $0.destinationPort == firstPort }
+ .first
+ )
+
+ let packetStreamWithDaita = try XCTUnwrap(streamWithDaita
+ .filter { $0.destinationAddress == secondIpAddress && $0.destinationPort == secondPort }
+ .first
+ )
+
+ let computeMeanPacketSize: (Stream, Int) -> Int32 = { stream, sampleSize in
+ stream.packets[..<sampleSize]
+ .map { $0.size }
+ .reduce(0, +) / Int32(sampleSize)
+ }
+
+ // Sample size might vary a lot, but DAITA is consistently padding enough that 100 samples or so should be good
+ // In this case, limit the total sample size to the smallest packet capture
+ let maximumSampleSize = min(packetStreamWithoutDaita.packets.count, packetStreamWithDaita.packets.count)
+ let meanPacketSizeWithoutDaita = computeMeanPacketSize(packetStreamWithoutDaita, maximumSampleSize)
+ let meanPacketSizeWithDaita = computeMeanPacketSize(packetStreamWithDaita, maximumSampleSize)
+
+ XCTAssertTrue(meanPacketSizeWithDaita > meanPacketSizeWithoutDaita)
+ }
+
+ private func disableDaitaInTeardown() throws {
+ // Undo enabling DAITA in teardown
+ addTeardownBlock {
+ HeaderBar(self.app)
+ .tapSettingsButton()
+
+ SettingsPage(self.app)
+ .tapDAITACell()
+
+ DAITAPage(self.app)
+ .tapEnableSwitchIfOn()
+ }
+ }
+
func testMultihopSettings() throws {
// Undo enabling Multihop in teardown
addTeardownBlock {
@@ -615,10 +680,10 @@ extension RelayTests {
allowAddVPNConfigurationsIfAsked()
- let relayIPAddress = TunnelControlPage(app)
+ let (relayIPAddress, _) = TunnelControlPage(app)
.waitForConnectedLabel()
.tapRelayStatusExpandCollapseButton()
- .getInIPAddressFromConnectionStatus()
+ .getInIPAddressAndPortFromConnectionStatus()
let relayName = TunnelControlPage(app).getCurrentRelayName()
@@ -628,16 +693,18 @@ extension RelayTests {
return RelayInfo(name: relayName, ipAddress: relayIPAddress)
}
- private func generateTraffic(
- to connectedToIPAddress: String,
- on port: Int,
+ @discardableResult
+ private func generateTrafficAndDisconnect(
+ from connectedToIPAddress: String,
+ searchForPort port: Int,
+ duration: TimeInterval = 1,
assertProtocol transportProtocol: NetworkTransportProtocol
- ) throws {
+ ) throws -> [Stream] {
let targetIPAddress = Networking.getAlwaysReachableIPAddress()
let trafficGenerator = TrafficGenerator(destinationHost: targetIPAddress, port: 80)
trafficGenerator.startGeneratingUDPTraffic(interval: 0.1)
- RunLoop.current.run(until: .now + 1)
+ RunLoop.current.run(until: .now + duration)
trafficGenerator.stopGeneratingUDPTraffic()
TunnelControlPage(app)
@@ -651,5 +718,37 @@ extension RelayTests {
)
XCTAssertTrue(streamFromPeerToRelay.transportProtocol == transportProtocol)
+ return capturedStreams
+ }
+
+ /// Starts a packet capture, connects to a relay, generates synthetic traffic,
+ /// disconnects from the relay, and gets a representation of the captured traffic
+ // swiftlint:disable:next large_tuple
+ private func generateTrafficSample() throws -> (String, Int, [Stream]) {
+ startPacketCapture()
+
+ // Connect
+ TunnelControlPage(app)
+ .tapSelectLocationButton()
+
+ SelectLocationPage(app)
+ .tapLocationCell(withName: BaseUITestCase.testsDefaultDAITACountryName)
+
+ allowAddVPNConfigurationsIfAsked()
+
+ // Generate traffic sample
+ let (IPAddress, port) = TunnelControlPage(app)
+ .waitForConnectedLabel()
+ .tapRelayStatusExpandCollapseButton()
+ .getInIPAddressAndPortFromConnectionStatus()
+
+ let stream = try generateTrafficAndDisconnect(
+ from: IPAddress,
+ searchForPort: port,
+ duration: 30,
+ assertProtocol: .UDP
+ )
+
+ return (IPAddress, port, stream)
}
} // swiftlint:disable:this file_length