mirror of
https://github.com/outbackdingo/firezone.git
synced 2026-01-27 10:18:54 +00:00
fix(apple/ios): don't require hard link to __res_9_state (#10143)
In Xcode 16, how the compiler determines the size of C structs changed. In Xcode 15 and below, when the compiler saw `__res_9_state()`, it thought, "This is a C struct. I know its size and layout from the system's header files. I will generate code to allocate that much memory and zero it out." This was a type-based operation; it only needed the "blueprint" for the struct. In Xcode 16 and later, the compiler sees `__res_9_state()` and thinks, "This is a C struct. To initialize it, I need to link to the actual symbol named `___res_9_state` inside the libresolv library." This became a symbol-based operation, creating a direct dependency that didn't exist before. To fix this, we initialize a raw pointer with a manual type specification to the zeroed-out struct, which reverts to the prior behavior. Has been tested on iPhone 12, iOS 17. Fixes #10108
This commit is contained in:
@@ -495,7 +495,7 @@ extension Adapter: CallbackHandlerDelegate {
|
||||
// the system's resolver and we can grab the system resolvers directly.
|
||||
// If we try to continue below without valid tunnel addresses assigned
|
||||
// to the interface, we'll crash.
|
||||
return BindResolvers().getservers().map(BindResolvers.getnameinfo)
|
||||
return BindResolvers.getServers()
|
||||
}
|
||||
|
||||
var resolvers: [String] = []
|
||||
@@ -511,7 +511,7 @@ extension Adapter: CallbackHandlerDelegate {
|
||||
guard let networkSettings = self.networkSettings else { return }
|
||||
|
||||
// Only now can we get the system resolvers
|
||||
resolvers = BindResolvers().getservers().map(BindResolvers.getnameinfo)
|
||||
resolvers = BindResolvers.getServers()
|
||||
|
||||
// Restore connlib's DNS resolvers
|
||||
networkSettings.clearDummyMatchDomain()
|
||||
|
||||
@@ -1,38 +1,51 @@
|
||||
//
|
||||
// Resolv.swift
|
||||
// Firezone
|
||||
// BindResolvers.swift
|
||||
// (c) 2024 Firezone, Inc.
|
||||
// LICENSE: Apache-2.0
|
||||
//
|
||||
// Created by Jamil Bou Kheir on 12/22/23.
|
||||
//
|
||||
// Reads system resolvers from libresolv, similar to reading /etc/resolv.conf but this also works on iOS
|
||||
|
||||
public class BindResolvers {
|
||||
var state = __res_9_state()
|
||||
import FirezoneKit
|
||||
|
||||
public init() {
|
||||
res_9_ninit(state)
|
||||
enum BindResolvers {
|
||||
|
||||
static func getServers() -> [String] {
|
||||
// 1. Manually allocate memory for one __res_9_state struct. On iOS 17 and below, this prevents the linker
|
||||
// from attempting to link to libresolv9 which prevents an "Symbol not found" error.
|
||||
// See https://github.com/firezone/firezone/issues/10108
|
||||
let statePtr = UnsafeMutablePointer<__res_9_state>.allocate(capacity: 1)
|
||||
statePtr.initialize(to: __res_9_state()) // Zero-initialize the allocated memory.
|
||||
|
||||
// 2. Ensure memory is cleaned up.
|
||||
defer {
|
||||
res_9_ndestroy(statePtr)
|
||||
statePtr.deinitialize(count: 1)
|
||||
statePtr.deallocate()
|
||||
}
|
||||
|
||||
// 3. Initialize the resolver state by passing the pointer directly.
|
||||
guard res_9_ninit(statePtr) == 0 else {
|
||||
Log.warning("Failed to initialize resolver state")
|
||||
|
||||
// Cleanup will happen via defer.
|
||||
return []
|
||||
}
|
||||
|
||||
// 4. Get the servers.
|
||||
var servers = [res_9_sockaddr_union](repeating: res_9_sockaddr_union(), count: 10)
|
||||
let foundCount = Int(res_9_getservers(statePtr, &servers, Int32(servers.count)))
|
||||
|
||||
// 5. Process the results.
|
||||
let validServers = Array(servers[0..<foundCount]).filter { $0.sin.sin_len > 0 }
|
||||
return validServers.map { getnameinfo($0) }
|
||||
}
|
||||
|
||||
deinit {
|
||||
res_9_ndestroy(state)
|
||||
}
|
||||
|
||||
public final func getservers() -> [res_9_sockaddr_union] {
|
||||
let maxServers = 10
|
||||
var servers = [res_9_sockaddr_union](repeating: res_9_sockaddr_union(), count: maxServers)
|
||||
let found = Int(res_9_getservers(state, &servers, Int32(maxServers)))
|
||||
|
||||
// filter is to remove the erroneous empty entry when there's no real servers
|
||||
return Array(servers[0..<found]).filter { $0.sin.sin_len > 0 }
|
||||
}
|
||||
}
|
||||
|
||||
extension BindResolvers {
|
||||
public static func getnameinfo(_ sock: res_9_sockaddr_union) -> String {
|
||||
private static func getnameinfo(_ sock: res_9_sockaddr_union) -> String {
|
||||
var sockUnion = sock
|
||||
var hostBuffer = [CChar](repeating: 0, count: Int(NI_MAXHOST))
|
||||
|
||||
let sinlen = socklen_t(sockUnion.sin.sin_len)
|
||||
|
||||
_ = withUnsafePointer(to: &sockUnion) {
|
||||
$0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
|
||||
Darwin.getnameinfo(
|
||||
@@ -42,7 +55,6 @@ extension BindResolvers {
|
||||
NI_NUMERICHOST)
|
||||
}
|
||||
}
|
||||
|
||||
return String(cString: hostBuffer)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,14 @@ export default function Apple() {
|
||||
return (
|
||||
<Entries downloadLinks={downloadLinks} title="macOS / iOS">
|
||||
{/* When you cut a release, remove any solved issues from the "known issues" lists over in `client-apps`. This must not be done when the issue's PR merges. */}
|
||||
<Unreleased></Unreleased>
|
||||
<Unreleased>
|
||||
<ChangeItem pull="10143">
|
||||
Fixes an issue on iOS 17 and below that caused the tunnel to crash
|
||||
after signing in. This was due to a change in how newer versions of
|
||||
Xcode handle linking against referenced libraries. iOS 18 and higher
|
||||
is unaffected.
|
||||
</ChangeItem>
|
||||
</Unreleased>
|
||||
<Entry version="1.5.6" date={new Date("2025-08-02")}>
|
||||
<ChangeItem pull="10075">
|
||||
Fixes an issue on iOS where the tunnel may never fully come up after
|
||||
|
||||
Reference in New Issue
Block a user