diff options
| author | Bug Magnet <marco.nikic@mullvad.net> | 2025-09-04 14:44:16 +0200 |
|---|---|---|
| committer | Bug Magnet <marco.nikic@mullvad.net> | 2025-09-04 14:44:16 +0200 |
| commit | e02425beead9652439193d672cd8ce58b52039f7 (patch) | |
| tree | 0b898164a73e0cfacaa299fd117b9cab7b0ca834 | |
| parent | 3b6dc6cf42e53e775a7eb35682270ca2f81c7d64 (diff) | |
| parent | d4d8308a02f1673f5d96d13ebc416695b77d8ee6 (diff) | |
| download | mullvadvpn-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.rs | 6 | ||||
| -rw-r--r-- | ios/MullvadVPNUITests/Networking/PacketCapture.swift | 8 | ||||
| -rw-r--r-- | ios/MullvadVPNUITests/Pages/TunnelControlPage.swift | 8 | ||||
| -rw-r--r-- | ios/MullvadVPNUITests/RelayTests.swift | 157 |
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 |
