Local Network in iOS 14
In iOS14 if your app wants to use Bonjour service, mDNS service, or access a local network then the “Local Network” permission is required.
The below image shows services that require authorization. (WWDC Video).
Those apps requiring the permission need to describe which services it uses and its purpose in the “Info.plist”.
Apps that requested local network permission will be listed under Settings > Privacy > Local Network.
- If you revoke the permission of any app, manual triggering for request won’t allow the popup to appear.
- The triggered popup appears only at the app’s first launch.
- To consistently reshow the Local Network permission in the app without uninstalling it first, by going to Settings App -> General -> Reset -> Reset Location & Privacy.
Swift Source Code for Triggering the Local Network Privacy Alert
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
func triggerLocalNetworkPrivacyAlert() {
let sock4 = socket(AF_INET, SOCK_DGRAM, 0)
guard sock4 >= 0 else { return }
defer { close(sock4) }
let sock6 = socket(AF_INET6, SOCK_DGRAM, 0)
guard sock6 >= 0 else { return }
defer { close(sock6) }
let addresses = addressesOfDiscardServiceOnBroadcastCapableInterfaces()
var message = [UInt8]("!".utf8)
for address in addresses {
address.withUnsafeBytes { buf in
let sa = buf.baseAddress!.assumingMemoryBound(to: sockaddr.self)
let saLen = socklen_t(buf.count)
let sock = sa.pointee.sa_family == AF_INET ? sock4 : sock6
_ = sendto(sock, &message, message.count, MSG_DONTWAIT, sa, saLen)
}
}
}
/// Returns the addresses of the discard service (port 9) on every
/// broadcast-capable interface.
///
/// Each array entry is contains either a `sockaddr_in` or `sockaddr_in6`.
private func addressesOfDiscardServiceOnBroadcastCapableInterfaces() -> [Data] {
var addrList: UnsafeMutablePointer<ifaddrs>? = nil
let err = getifaddrs(&addrList)
guard err == 0, let start = addrList else { return [] }
defer { freeifaddrs(start) }
return sequence(first: start, next: { $0.pointee.ifa_next })
.compactMap { i -> Data? in
guard
(i.pointee.ifa_flags & UInt32(bitPattern: IFF_BROADCAST)) != 0,
let sa = i.pointee.ifa_addr
else { return nil }
var result = Data(UnsafeRawBufferPointer(start: sa, count: Int(sa.pointee.sa_len)))
switch CInt(sa.pointee.sa_family) {
case AF_INET:
result.withUnsafeMutableBytes { buf in
let sin = buf.baseAddress!.assumingMemoryBound(to: sockaddr_in.self)
sin.pointee.sin_port = UInt16(9).bigEndian
}
case AF_INET6:
result.withUnsafeMutableBytes { buf in
let sin6 = buf.baseAddress!.assumingMemoryBound(to: sockaddr_in6.self)
sin6.pointee.sin6_port = UInt16(9).bigEndian
}
default:
return nil
}
return result
}
}
You can use the unsatisfied reason property starting with iOS 14.2.
Source code below will cause a crash in lower versions.
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
func checkUnsatisfiedReason() {
let connection = NWConnection(host: "192.168.7.1", port: 80, using: .tcp)
connection.stateUpdateHandler = { latestState in
switch latestState {
case .setup:
print("setup")
case .waiting(_):
print("waiting")
if case .localNetworkDenied? = connection.currentPath?.unsatisfiedReason {
print("Local Network Denied")
}
case .preparing:
print("preparing")
case .ready:
print("Local Network Permission Granted")
case .failed(_):
print("failed")
case .cancelled:
print("cancelled")
@unknown default:
fatalError()
}
}
connection.start(queue: DispatchQueue(label: "monitor"))
}
An NWPath unsatisfiedReason may indicate a reason the path is unsatisfied. [Apple Documentation]
1
2
3
4
5
6
7
8
9
10
11
/// No reason is given
case notAvailable
/// The user has disabled cellular
case cellularDenied
/// The user has disabled Wi-Fi
case wifiDenied
/// The user has disabled local network access
case localNetworkDenied
References:
This post is licensed under CC BY 4.0 by the author.