-
Notifications
You must be signed in to change notification settings - Fork 136
/
Copy pathKronosInternetAddress.swift
163 lines (141 loc) · 6.62 KB
/
KronosInternetAddress.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
/*
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
* This product includes software developed at Datadog (https://www.datadoghq.com/).
* Copyright 2019-2020 Datadog, Inc.
*
* This file includes software developed by MobileNativeFoundation, https://mobilenativefoundation.org and altered by Datadog.
* Use of this source code is governed by Apache License 2.0 license: https://github.com/MobileNativeFoundation/Kronos/blob/main/LICENSE
*/
import Foundation
/// This enum represents an internet address that can either be IPv4 or IPv6.
///
/// - IPv6: An Internet Address of type IPv6 (e.g.: '::1').
/// - IPv4: An Internet Address of type IPv4 (e.g.: '127.0.0.1').
internal enum KronosInternetAddress: Hashable {
case ipv6(sockaddr_in6)
case ipv4(sockaddr_in)
/// Human readable host represetnation (e.g. '192.168.1.1' or 'ab:ab:ab:ab:ab:ab:ab:ab').
var host: String? {
switch self {
case .ipv6(var address):
var buffer = [CChar](repeating: 0, count: Int(INET6_ADDRSTRLEN))
inet_ntop(AF_INET6, &address.sin6_addr, &buffer, socklen_t(INET6_ADDRSTRLEN))
return String(cString: buffer)
case .ipv4(var address):
var buffer = [CChar](repeating: 0, count: Int(INET_ADDRSTRLEN))
inet_ntop(AF_INET, &address.sin_addr, &buffer, socklen_t(INET_ADDRSTRLEN))
return String(cString: buffer)
}
}
/// The protocol family that should be used on the socket creation for this address.
var family: Int32 {
switch self {
case .ipv4:
return PF_INET
case .ipv6:
return PF_INET6
}
}
/// If the address is reserved for private internets (local / private IP).
var isPrivate: Bool {
guard let host = host else {
return false
}
switch self {
case .ipv6:
// Ref.: https://datatracker.ietf.org/doc/html/rfc4193#section-3
// +--------+-+------------+-----------+----------------------------+
// | 7 bits |1| 40 bits | 16 bits | 64 bits |
// +--------+-+------------+-----------+----------------------------+
// | Prefix |L| Global ID | Subnet ID | Interface ID |
// +--------+-+------------+-----------+----------------------------+
//
// Local IP is expected to have FC00::/7 prefix (7 bits) and L byte set to 1,
// which effectively means `fd` prefix for local IPs.
let localPrefix = "fd"
// Ref.: https://datatracker.ietf.org/doc/html/rfc4291#section-2.4
let multicastPrefix = "ff"
let hostLowercased = host.lowercased()
return hostLowercased.starts(with: localPrefix)
|| hostLowercased.starts(with: multicastPrefix)
case .ipv4:
// Ref.: https://datatracker.ietf.org/doc/html/rfc1918#section-3
// Local IPs have predefined ranges:
// - class A: 10.0.0.0 — 10.255.255.255
// - class B: 172.16.0.0 — 172.31.255.255
// - class C: 192.168.0.0 — 192.168.255.255
let classABCregex = #"^((10\.)|(172\.1[6-9]\.)|(172\.2[0-9]\.)|(172\.3[0-1]\.)|(192\.168\.))"#
// Ref.: https://datatracker.ietf.org/doc/html/rfc5771#section-3
// Multicast address (range 224.0.0.0 - 239.255.255.255) are considered to be local network
// addresses too (https://developer.apple.com/forums/thread/663848)
//
let multicastRegex = #"^((22[4-9]\.)|(23[0-9]\.))"#
let broadcastIP = "255.255.255.255"
return host.range(of: classABCregex, options: .regularExpression) != nil
|| host.range(of: multicastRegex, options: .regularExpression) != nil
|| host == broadcastIP
}
}
func hash(into hasher: inout Hasher) {
hasher.combine(self.host)
}
init?(dataWithSockAddress data: NSData) {
let storage = sockaddr_storage.from(unsafeDataWithSockAddress: data)
switch Int32(storage.ss_family) {
case AF_INET:
self = storage.withUnsafeAddress { KronosInternetAddress.ipv4($0.pointee) }
case AF_INET6:
self = storage.withUnsafeAddress { KronosInternetAddress.ipv6($0.pointee) }
default:
return nil
}
}
/// Returns the address struct (either sockaddr_in or sockaddr_in6) represented as an CFData.
///
/// - parameter port: The port number to associate on the address struct.
///
/// - returns: An address struct wrapped into a CFData type.
func addressData(withPort port: Int) -> CFData {
switch self {
case .ipv6(var address):
address.sin6_port = in_port_t(port).bigEndian
return Data(bytes: &address, count: MemoryLayout<sockaddr_in6>.size) as CFData
case .ipv4(var address):
address.sin_port = in_port_t(port).bigEndian
return Data(bytes: &address, count: MemoryLayout<sockaddr_in>.size) as CFData
}
}
}
/// Compare InternetAddress(es) by making sure the host representation are equal.
internal func == (lhs: KronosInternetAddress, rhs: KronosInternetAddress) -> Bool {
return lhs.host == rhs.host
}
// MARK: - sockaddr_storage helpers
extension sockaddr_storage {
/// Creates a new storage value from a data type that contains the memory layout of a sockaddr_t. This
/// is used to create sockaddr_storage(s) from some of the CF C functions such as `CFHostGetAddressing`.
///
/// !!! WARNING: This method is unsafe and assumes the memory layout is of `sockaddr_t`. !!!
///
/// - parameter data: The data to be interpreted as sockaddr
/// - returns: The newly created sockaddr_storage value
fileprivate static func from(unsafeDataWithSockAddress data: NSData) -> sockaddr_storage {
var storage = sockaddr_storage()
data.getBytes(&storage, length: data.length)
return storage
}
/// Calls a closure with traditional BSD Sockets address parameters.
///
/// - parameter body: A closure to call with `self` referenced appropriately for calling
/// BSD Sockets APIs that take an address.
///
/// - throws: Any error thrown by `body`.
///
/// - returns: Any result returned by `body`.
fileprivate func withUnsafeAddress<T, U>(_ body: (_ address: UnsafePointer<U>) -> T) -> T {
var storage = self
return withUnsafePointer(to: &storage) {
$0.withMemoryRebound(to: U.self, capacity: 1) { address in body(address) }
}
}
}