@mentra/glasses-media 3.3.0-dev.819 → 3.3.0-dev.821
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -18,10 +18,14 @@ public struct HotspotInterfaceAddress: Equatable {
|
|
|
18
18
|
public struct HotspotInterfaceSnapshot: Equatable {
|
|
19
19
|
public let links: [String: Int]
|
|
20
20
|
public let addresses: [HotspotInterfaceAddress]
|
|
21
|
+
/// Attached interfaces whose `AF_LINK` type is `IFT_ETHER`. Apple platforms report Wi-Fi and
|
|
22
|
+
/// wired Ethernet this way; cellular, VPN tunnel and loopback links have other types.
|
|
23
|
+
public let ethernetLinks: Set<String>
|
|
21
24
|
|
|
22
|
-
public init(links: [String: Int], addresses: [HotspotInterfaceAddress]) {
|
|
25
|
+
public init(links: [String: Int], addresses: [HotspotInterfaceAddress], ethernetLinks: Set<String> = []) {
|
|
23
26
|
self.links = links
|
|
24
27
|
self.addresses = addresses
|
|
28
|
+
self.ethernetLinks = ethernetLinks
|
|
25
29
|
}
|
|
26
30
|
}
|
|
27
31
|
|
|
@@ -87,6 +91,13 @@ public struct HotspotWifiIdentity: Equatable {
|
|
|
87
91
|
detached.insert(name)
|
|
88
92
|
}
|
|
89
93
|
}
|
|
94
|
+
|
|
95
|
+
/// Pin a binding chosen from hotspot client evidence (see `HotspotInterfacePolicy.select`)
|
|
96
|
+
/// as this session's Wi-Fi interface. It then ends exactly like a reported one.
|
|
97
|
+
public mutating func adopt(_ binding: HotspotInterfaceBinding) {
|
|
98
|
+
guard !rejected.contains(binding.interface), !detached.contains(binding.interface) else { return }
|
|
99
|
+
trusted[binding.interface] = binding.index
|
|
100
|
+
}
|
|
90
101
|
}
|
|
91
102
|
|
|
92
103
|
/// Wi-Fi is not always `en0`: a Mac can have built-in Ethernet on `en0` and Wi-Fi on `en1`.
|
|
@@ -96,11 +107,33 @@ public enum HotspotInterfacePolicy {
|
|
|
96
107
|
/// The unique up IPv4 address, on an interface this session trusts as Wi-Fi and that is still
|
|
97
108
|
/// attached with the same index, that can be the hotspot client. With a BLE-reported gateway
|
|
98
109
|
/// it must be a valid client on that gateway's /24. Missing or ambiguous candidates return nil.
|
|
110
|
+
///
|
|
111
|
+
/// A phone on cellular has no Wi-Fi path when the join starts, and after association iOS
|
|
112
|
+
/// leaves the internet-less glasses AP out of both paths, so no Wi-Fi report ever arrives.
|
|
113
|
+
/// Only when the session has no trusted Wi-Fi interface, the hotspot client address is the
|
|
114
|
+
/// evidence instead: the unique valid client of the BLE gateway's /24 on an attached
|
|
115
|
+
/// Ethernet-type link that no path has reported as non-Wi-Fi. Callers bind it only after the
|
|
116
|
+
/// glasses SSID matched, then require a connection from it to reach the gateway, and must
|
|
117
|
+
/// `adopt` it so the session pins that interface.
|
|
99
118
|
public static func select(snapshot: HotspotInterfaceSnapshot, identity: HotspotWifiIdentity,
|
|
100
119
|
gateway: String?) -> HotspotInterfaceBinding?
|
|
120
|
+
{
|
|
121
|
+
if !identity.trusted.isEmpty {
|
|
122
|
+
return unique(snapshot, gateway: gateway) { identity.trusted[$0] }
|
|
123
|
+
}
|
|
124
|
+
guard let gateway else { return nil }
|
|
125
|
+
return unique(snapshot, gateway: gateway) { name in
|
|
126
|
+
guard snapshot.ethernetLinks.contains(name), !identity.rejected.contains(name),
|
|
127
|
+
!identity.detached.contains(name) else { return nil }
|
|
128
|
+
return snapshot.links[name]
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
private static func unique(_ snapshot: HotspotInterfaceSnapshot, gateway: String?,
|
|
133
|
+
index eligibleIndex: (String) -> Int?) -> HotspotInterfaceBinding?
|
|
101
134
|
{
|
|
102
135
|
let bindings = Set(snapshot.addresses.compactMap { row -> HotspotInterfaceBinding? in
|
|
103
|
-
guard let index =
|
|
136
|
+
guard let index = eligibleIndex(row.name), snapshot.links[row.name] == index,
|
|
104
137
|
row.isUp, LocalMediaPolicy.isPrivate(row.ipv4) else { return nil }
|
|
105
138
|
if let gateway, !LocalMediaPolicy.isHotspotClientAddress(row.ipv4, gateway: gateway) { return nil }
|
|
106
139
|
return HotspotInterfaceBinding(interface: row.name, index: index, address: row.ipv4)
|
|
@@ -257,6 +257,8 @@ public final class GlassesHotspotNetwork {
|
|
|
257
257
|
/// a Wi-Fi type constraint on a usable hotspot route. Both paths' interface types feed one
|
|
258
258
|
/// session identity (HotspotWifiIdentity): a positive Wi-Fi report outlives later omission
|
|
259
259
|
/// only while the same interface stays attached; contrary reports and replacement fail closed.
|
|
260
|
+
/// A phone that starts on cellular never gets a report; HotspotInterfacePolicy then uses
|
|
261
|
+
/// the hotspot client address as the evidence.
|
|
260
262
|
private func startInterfaceMonitors(generation gen: Int) {
|
|
261
263
|
interfaceMonitors.forEach { $0.cancel() }
|
|
262
264
|
let monitors = [NWPathMonitor(requiredInterfaceType: .wifi), NWPathMonitor()]
|
|
@@ -318,8 +320,10 @@ public final class GlassesHotspotNetwork {
|
|
|
318
320
|
}
|
|
319
321
|
|
|
320
322
|
private func bind(_ selected: HotspotInterfaceBinding) {
|
|
323
|
+
let evidence = wifiIdentity.trusted[selected.interface] == selected.index ? "wifi_report" : "hotspot_client"
|
|
324
|
+
wifiIdentity.adopt(selected)
|
|
321
325
|
binding = selected
|
|
322
|
-
logger.info("HOTSPOT_JOIN address_verified interface=\(selected.interface, privacy: .public)")
|
|
326
|
+
logger.info("HOTSPOT_JOIN address_verified interface=\(selected.interface, privacy: .public) evidence=\(evidence, privacy: .public)")
|
|
323
327
|
}
|
|
324
328
|
|
|
325
329
|
/// Interface names and their IPv4 addresses only; never credentials or SSIDs.
|
|
@@ -327,8 +331,11 @@ public final class GlassesHotspotNetwork {
|
|
|
327
331
|
let snapshot = observeInterfaces()
|
|
328
332
|
let wifi = Set(wifiIdentity.trusted.keys)
|
|
329
333
|
let addresses = snapshot.addresses.filter { wifi.contains($0.name) }.map { "\($0.name)=\($0.ipv4)" }
|
|
334
|
+
let other = snapshot.addresses.filter { !wifi.contains($0.name) && LocalMediaPolicy.isPrivate($0.ipv4) }
|
|
335
|
+
.map { "\($0.name)\(snapshot.ethernetLinks.contains($0.name) ? "" : "(non-ethernet)")=\($0.ipv4)" }
|
|
330
336
|
return "Wi-Fi interfaces=\(wifi.isEmpty ? "none" : wifi.sorted().joined(separator: ",")), " +
|
|
331
|
-
"Wi-Fi IPv4=\(addresses.isEmpty ? "none" : addresses.sorted().joined(separator: ","))"
|
|
337
|
+
"Wi-Fi IPv4=\(addresses.isEmpty ? "none" : addresses.sorted().joined(separator: ",")), " +
|
|
338
|
+
"other private IPv4=\(other.isEmpty ? "none" : other.sorted().joined(separator: ","))"
|
|
332
339
|
}
|
|
333
340
|
|
|
334
341
|
private func waitForLocalAccess(address: String, generation gen: Int) {
|
|
@@ -376,14 +383,16 @@ public final class GlassesHotspotNetwork {
|
|
|
376
383
|
replies.forEach { $0() }
|
|
377
384
|
}
|
|
378
385
|
|
|
379
|
-
/// Every attached interface's kernel index (its AF_LINK row) and every IPv4
|
|
380
|
-
/// interface name and up state. Selection belongs to HotspotInterfacePolicy;
|
|
381
|
-
/// name alone never identifies Wi-Fi. A failed read yields an empty sample, which
|
|
386
|
+
/// Every attached interface's kernel index and link type (its AF_LINK row) and every IPv4
|
|
387
|
+
/// address with its interface name and up state. Selection belongs to HotspotInterfacePolicy;
|
|
388
|
+
/// an interface name alone never identifies Wi-Fi. A failed read yields an empty sample, which
|
|
389
|
+
/// fails closed.
|
|
382
390
|
static func interfaceSnapshot() -> HotspotInterfaceSnapshot {
|
|
383
391
|
var interfaces: UnsafeMutablePointer<ifaddrs>?
|
|
384
392
|
guard getifaddrs(&interfaces) == 0 else { return HotspotInterfaceSnapshot(links: [:], addresses: []) }
|
|
385
393
|
defer { freeifaddrs(interfaces) }
|
|
386
394
|
var links: [String: Int] = [:]
|
|
395
|
+
var ethernetLinks: Set<String> = []
|
|
387
396
|
var rows: [HotspotInterfaceAddress] = []
|
|
388
397
|
var cursor = interfaces
|
|
389
398
|
while let item = cursor {
|
|
@@ -392,8 +401,13 @@ public final class GlassesHotspotNetwork {
|
|
|
392
401
|
guard let address = value.ifa_addr else { continue }
|
|
393
402
|
let name = String(cString: value.ifa_name)
|
|
394
403
|
if address.pointee.sa_family == UInt8(AF_LINK) {
|
|
395
|
-
let index = address.withMemoryRebound(to: sockaddr_dl.self, capacity: 1) {
|
|
396
|
-
|
|
404
|
+
let (index, type) = address.withMemoryRebound(to: sockaddr_dl.self, capacity: 1) {
|
|
405
|
+
(Int($0.pointee.sdl_index), Int32($0.pointee.sdl_type))
|
|
406
|
+
}
|
|
407
|
+
if index > 0 {
|
|
408
|
+
links[name] = index
|
|
409
|
+
if type == IFT_ETHER { ethernetLinks.insert(name) }
|
|
410
|
+
}
|
|
397
411
|
continue
|
|
398
412
|
}
|
|
399
413
|
guard address.pointee.sa_family == UInt8(AF_INET) else { continue }
|
|
@@ -401,6 +415,6 @@ public final class GlassesHotspotNetwork {
|
|
|
401
415
|
guard getnameinfo(address, socklen_t(address.pointee.sa_len), &host, socklen_t(host.count), nil, 0, NI_NUMERICHOST) == 0 else { continue }
|
|
402
416
|
rows.append(HotspotInterfaceAddress(name: name, isUp: value.ifa_flags & UInt32(IFF_UP) != 0, ipv4: String(cString: host)))
|
|
403
417
|
}
|
|
404
|
-
return HotspotInterfaceSnapshot(links: links, addresses: rows)
|
|
418
|
+
return HotspotInterfaceSnapshot(links: links, addresses: rows, ethernetLinks: ethernetLinks)
|
|
405
419
|
}
|
|
406
420
|
}
|