-
Notifications
You must be signed in to change notification settings - Fork 384
/
Copy pathNetworking.swift
229 lines (192 loc) · 9.05 KB
/
Networking.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
//
// Networking.swift
// MullvadVPNUITests
//
// Created by Niklas Berglund on 2024-02-05.
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
//
import Foundation
import Network
import XCTest
enum NetworkingError: Error {
case notConfiguredError
case internalError(reason: String)
}
struct DNSServerEntry: Decodable {
let organization: String
let mullvad_dns: Bool
}
/// Class with methods for verifying network connectivity
class Networking {
/// Get IP address of the iOS device under test
static func getIPAddress() throws -> String {
var ipAddress: String
// Get list of all interfaces on the local machine:
var interfaceList: UnsafeMutablePointer<ifaddrs>?
guard getifaddrs(&interfaceList) == 0, let firstInterfaceAddress = interfaceList else {
throw NetworkingError.internalError(reason: "Failed to locate local networking interface")
}
// For each interface
for interfacePointer in sequence(first: firstInterfaceAddress, next: { $0.pointee.ifa_next }) {
let flags = Int32(interfacePointer.pointee.ifa_flags)
let interfaceAddress = interfacePointer.pointee.ifa_addr.pointee
// Check for running IPv4 interfaces. Skip the loopback interface.
if (
flags &
(IFF_UP | IFF_RUNNING | IFF_LOOPBACK)
) == (IFF_UP | IFF_RUNNING),
interfaceAddress.sa_family == UInt8(AF_INET) {
// Check if interface is en0 which is the WiFi connection on the iPhone
let name = String(cString: interfacePointer.pointee.ifa_name)
if name == "en0" {
// Convert interface address to a human readable string:
var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST))
if getnameinfo(
interfacePointer.pointee.ifa_addr,
socklen_t(interfaceAddress.sa_len),
&hostname,
socklen_t(hostname.count),
nil,
socklen_t(0),
NI_NUMERICHOST
) == 0 {
ipAddress = String(cString: hostname)
return ipAddress
}
}
}
}
freeifaddrs(interfaceList)
throw NetworkingError.internalError(reason: "Failed to determine device's IP address")
}
/// Get configured ad serving domain as URL object
private static func getAdServingDomainURL() -> URL? {
guard let adServingDomain = Bundle(for: BaseUITestCase.self)
.infoDictionary?["AdServingDomain"] as? String,
let adServingDomainURL = URL(string: adServingDomain) else {
XCTFail("Ad serving domain not configured")
return nil
}
return adServingDomainURL
}
/// Get configured ad serving domain
private static func getAdServingDomain() throws -> String {
guard let adServingDomain = Bundle(for: Networking.self)
.infoDictionary?["AdServingDomain"] as? String else {
throw NetworkingError.notConfiguredError
}
return adServingDomain
}
/// Get configured domain to use for Internet connectivity checks
private static func getAlwaysReachableDomain() throws -> String {
guard let shouldBeReachableDomain = Bundle(for: Networking.self)
.infoDictionary?["ShouldBeReachableDomain"] as? String else {
throw NetworkingError.notConfiguredError
}
return shouldBeReachableDomain
}
/// Check whether host and port is reachable by attempting to connect a socket
private static func canConnectSocket(host: String, port: String) throws -> Bool {
let socketHost = NWEndpoint.Host(host)
let socketPort = try XCTUnwrap(NWEndpoint.Port(port))
let connection = NWConnection(host: socketHost, port: socketPort, using: .tcp)
var connectionError: Error?
let connectionStateDeterminedExpectation = XCTestExpectation(
description: "Completion handler for the reach ad serving domain request is invoked"
)
connection.stateUpdateHandler = { state in
print("State: \(state)")
switch state {
case let .failed(error):
connection.cancel()
connectionError = error
connectionStateDeterminedExpectation.fulfill()
case .ready:
connection.cancel()
connectionStateDeterminedExpectation.fulfill()
default:
break
}
}
connection.start(queue: .global())
let waitResult = XCTWaiter.wait(for: [connectionStateDeterminedExpectation], timeout: 15)
if waitResult != .completed || connectionError != nil {
return false
}
return true
}
/// Verify API can be accessed by attempting to connect a socket to the configured API host and port
public static func verifyCanAccessAPI() throws {
let apiIPAddress = try MullvadAPIWrapper.getAPIIPAddress()
let apiPort = try MullvadAPIWrapper.getAPIPort()
XCTAssertTrue(try canConnectSocket(host: apiIPAddress, port: apiPort))
}
/// Verify API cannot be accessed by attempting to connect a socket to the configured API host and port
public static func verifyCannotAccessAPI() throws {
let apiIPAddress = try MullvadAPIWrapper.getAPIIPAddress()
let apiPort = try MullvadAPIWrapper.getAPIPort()
XCTAssertFalse(try canConnectSocket(host: apiIPAddress, port: apiPort))
}
/// Verify that the device has Internet connectivity
public static func verifyCanAccessInternet() throws {
XCTAssertTrue(try canConnectSocket(host: getAlwaysReachableDomain(), port: "80"))
}
/// Verify that the device does not have Internet connectivity
public static func verifyCannotAccessInternet() throws {
XCTAssertFalse(try canConnectSocket(host: getAlwaysReachableDomain(), port: "80"))
}
/// Verify that an ad serving domain is reachable by making sure a connection can be established on port 80
public static func verifyCanReachAdServingDomain() throws {
XCTAssertTrue(try Self.canConnectSocket(host: try Self.getAdServingDomain(), port: "80"))
}
/// Verify that an ad serving domain is NOT reachable by making sure a connection can not be established on port 80
public static func verifyCannotReachAdServingDomain() throws {
XCTAssertFalse(try Self.canConnectSocket(host: try Self.getAdServingDomain(), port: "80"))
}
/// Verify that the expected DNS server is used by verifying provider name and whether it is a Mullvad DNS server or not
public static func verifyDNSServerProvider(_ providerName: String, isMullvad: Bool) throws {
guard let mullvadDNSLeakURL = URL(string: "https://am.i.mullvad.net/dnsleak") else {
throw NetworkingError.internalError(reason: "Failed to create URL object")
}
var request = URLRequest(url: mullvadDNSLeakURL)
request.setValue("application/json", forHTTPHeaderField: "accept")
var requestData: Data?
var requestResponse: URLResponse?
var requestError: Error?
let completionHandlerInvokedExpectation = XCTestExpectation(
description: "Completion handler for the request is invoked"
)
do {
let dataTask = URLSession.shared.dataTask(with: request) { data, response, error in
requestData = data
requestResponse = response
requestError = error
completionHandlerInvokedExpectation.fulfill()
}
dataTask.resume()
let waitResult = XCTWaiter.wait(for: [completionHandlerInvokedExpectation], timeout: 30)
if waitResult != .completed {
XCTFail("Failed to verify DNS server provider - timeout")
} else {
if let response = requestResponse as? HTTPURLResponse {
if response.statusCode != 200 {
XCTFail("Failed to verify DNS server provider - unexpected server response")
}
}
if let error = requestError {
XCTFail("Failed to verify DNS server provider - encountered error \(error.localizedDescription)")
}
if let requestData = requestData {
let dnsServerEntries = try JSONDecoder().decode([DNSServerEntry].self, from: requestData)
XCTAssertGreaterThanOrEqual(dnsServerEntries.count, 1)
for dnsServerEntry in dnsServerEntries {
XCTAssertEqual(dnsServerEntry.organization, providerName)
XCTAssertEqual(dnsServerEntry.mullvad_dns, isMullvad)
}
}
}
} catch {
XCTFail("Failed to verify DNS server provider - couldn't serialize JSON")
}
}
}