/src/swift-nio/Sources/NIOCore/Interfaces.swift
Line | Count | Source |
1 | | //===----------------------------------------------------------------------===// |
2 | | // |
3 | | // This source file is part of the SwiftNIO open source project |
4 | | // |
5 | | // Copyright (c) 2017-2021 Apple Inc. and the SwiftNIO project authors |
6 | | // Licensed under Apache License v2.0 |
7 | | // |
8 | | // See LICENSE.txt for license information |
9 | | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors |
10 | | // |
11 | | // SPDX-License-Identifier: Apache-2.0 |
12 | | // |
13 | | //===----------------------------------------------------------------------===// |
14 | | #if os(Linux) || os(FreeBSD) || os(Android) |
15 | | #if canImport(Glibc) |
16 | | @preconcurrency import Glibc |
17 | | #elseif canImport(Musl) |
18 | | @preconcurrency import Musl |
19 | | #elseif canImport(Bionic) |
20 | | @preconcurrency import Bionic |
21 | | #endif |
22 | | import CNIOLinux |
23 | | #elseif os(OpenBSD) |
24 | | import CNIOOpenBSD |
25 | | #elseif canImport(Darwin) |
26 | | import Darwin |
27 | | #elseif canImport(WASILibc) |
28 | | @preconcurrency import WASILibc |
29 | | #elseif os(Windows) |
30 | | import let WinSDK.AF_INET |
31 | | import let WinSDK.AF_INET6 |
32 | | |
33 | | import let WinSDK.INET_ADDRSTRLEN |
34 | | import let WinSDK.INET6_ADDRSTRLEN |
35 | | |
36 | | import struct WinSDK.ADDRESS_FAMILY |
37 | | import struct WinSDK.IP_ADAPTER_ADDRESSES |
38 | | import struct WinSDK.IP_ADAPTER_UNICAST_ADDRESS |
39 | | |
40 | | import struct WinSDK.sockaddr |
41 | | import struct WinSDK.sockaddr_in |
42 | | import struct WinSDK.sockaddr_in6 |
43 | | import struct WinSDK.sockaddr_storage |
44 | | import struct WinSDK.sockaddr_un |
45 | | |
46 | | import typealias WinSDK.UINT8 |
47 | | #else |
48 | | #error("The Core interfaces module was unable to identify your C library.") |
49 | | #endif |
50 | | |
51 | | #if !os(Windows) && !os(WASI) |
52 | | extension ifaddrs { |
53 | 0 | fileprivate var dstaddr: UnsafeMutablePointer<sockaddr>? { |
54 | | #if os(Linux) || os(Android) |
55 | 0 | return self.ifa_ifu.ifu_dstaddr |
56 | | #elseif canImport(Darwin) || os(OpenBSD) |
57 | | return self.ifa_dstaddr |
58 | | #endif |
59 | 0 | } |
60 | | |
61 | 0 | fileprivate var broadaddr: UnsafeMutablePointer<sockaddr>? { |
62 | | #if os(Linux) || os(Android) |
63 | 0 | return self.ifa_ifu.ifu_broadaddr |
64 | | #elseif canImport(Darwin) || os(OpenBSD) |
65 | | return self.ifa_dstaddr |
66 | | #endif |
67 | 0 | } |
68 | | } |
69 | | #endif |
70 | | |
71 | | /// A representation of a single network interface on a system. |
72 | | @available(*, deprecated, renamed: "NIONetworkDevice") |
73 | | public final class NIONetworkInterface: Sendable { |
74 | | // This is a class because in almost all cases this will carry |
75 | | // four structs that are backed by classes, and so will incur 4 |
76 | | // refcount operations each time it is copied. |
77 | | |
78 | | /// The name of the network interface. |
79 | | public let name: String |
80 | | |
81 | | /// The address associated with the given network interface. |
82 | | public let address: SocketAddress |
83 | | |
84 | | /// The netmask associated with this address, if any. |
85 | | public let netmask: SocketAddress? |
86 | | |
87 | | /// The broadcast address associated with this socket interface, if it has one. Some |
88 | | /// interfaces do not, especially those that have a `pointToPointDestinationAddress`. |
89 | | public let broadcastAddress: SocketAddress? |
90 | | |
91 | | /// The address of the peer on a point-to-point interface, if this is one. Some |
92 | | /// interfaces do not have such an address: most of those have a `broadcastAddress` |
93 | | /// instead. |
94 | | public let pointToPointDestinationAddress: SocketAddress? |
95 | | |
96 | | /// If the Interface supports Multicast |
97 | | public let multicastSupported: Bool |
98 | | |
99 | | /// The index of the interface, as provided by `if_nametoindex`. |
100 | | public let interfaceIndex: Int |
101 | | |
102 | | #if os(WASI) |
103 | | @available(*, unavailable) |
104 | | init() { fatalError() } |
105 | | #endif |
106 | | |
107 | | #if os(Windows) |
108 | | internal init?( |
109 | | _ pAdapter: UnsafeMutablePointer<IP_ADAPTER_ADDRESSES>, |
110 | | _ pAddress: UnsafeMutablePointer<IP_ADAPTER_UNICAST_ADDRESS> |
111 | | ) { |
112 | | self.name = String( |
113 | | decodingCString: pAdapter.pointee.FriendlyName, |
114 | | as: UTF16.self |
115 | | ) |
116 | | guard let address = pAddress.pointee.Address.lpSockaddr.convert() else { |
117 | | return nil |
118 | | } |
119 | | self.address = address |
120 | | |
121 | | switch pAddress.pointee.Address.lpSockaddr.pointee.sa_family { |
122 | | case ADDRESS_FAMILY(AF_INET): |
123 | | self.netmask = SocketAddress(ipv4MaskForPrefix: Int(pAddress.pointee.OnLinkPrefixLength)) |
124 | | self.interfaceIndex = Int(pAdapter.pointee.IfIndex) |
125 | | break |
126 | | case ADDRESS_FAMILY(AF_INET6): |
127 | | self.netmask = SocketAddress(ipv6MaskForPrefix: Int(pAddress.pointee.OnLinkPrefixLength)) |
128 | | self.interfaceIndex = Int(pAdapter.pointee.Ipv6IfIndex) |
129 | | break |
130 | | default: |
131 | | return nil |
132 | | } |
133 | | |
134 | | // TODO(compnerd) handle broadcast/ppp/multicast information |
135 | | self.broadcastAddress = nil |
136 | | self.pointToPointDestinationAddress = nil |
137 | | self.multicastSupported = false |
138 | | } |
139 | | #elseif !os(WASI) |
140 | 0 | internal init?(_ caddr: ifaddrs) { |
141 | 0 | self.name = String(cString: caddr.ifa_name!) |
142 | 0 |
|
143 | 0 | guard caddr.ifa_addr != nil else { |
144 | 0 | return nil |
145 | 0 | } |
146 | 0 |
|
147 | 0 | guard let address = caddr.ifa_addr!.convert() else { |
148 | 0 | return nil |
149 | 0 | } |
150 | 0 | self.address = address |
151 | 0 |
|
152 | 0 | if let netmask = caddr.ifa_netmask { |
153 | 0 | self.netmask = netmask.convert() |
154 | 0 | } else { |
155 | 0 | self.netmask = nil |
156 | 0 | } |
157 | 0 |
|
158 | 0 | if (caddr.ifa_flags & UInt32(IFF_BROADCAST)) != 0, let addr = caddr.broadaddr { |
159 | 0 | self.broadcastAddress = addr.convert() |
160 | 0 | self.pointToPointDestinationAddress = nil |
161 | 0 | } else if (caddr.ifa_flags & UInt32(IFF_POINTOPOINT)) != 0, let addr = caddr.dstaddr { |
162 | 0 | self.broadcastAddress = nil |
163 | 0 | self.pointToPointDestinationAddress = addr.convert() |
164 | 0 | } else { |
165 | 0 | self.broadcastAddress = nil |
166 | 0 | self.pointToPointDestinationAddress = nil |
167 | 0 | } |
168 | 0 |
|
169 | 0 | if (caddr.ifa_flags & UInt32(IFF_MULTICAST)) != 0 { |
170 | 0 | self.multicastSupported = true |
171 | 0 | } else { |
172 | 0 | self.multicastSupported = false |
173 | 0 | } |
174 | 0 |
|
175 | 0 | do { |
176 | 0 | self.interfaceIndex = Int(try SystemCalls.if_nametoindex(caddr.ifa_name)) |
177 | 0 | } catch { |
178 | 0 | return nil |
179 | 0 | } |
180 | 0 | } |
181 | | #endif |
182 | | } |
183 | | |
184 | | @available(*, deprecated, renamed: "NIONetworkDevice") |
185 | | extension NIONetworkInterface: CustomDebugStringConvertible { |
186 | 0 | public var debugDescription: String { |
187 | 0 | let baseString = "Interface \(self.name): address \(self.address)" |
188 | 0 | let maskString = self.netmask != nil ? " netmask \(self.netmask!)" : "" |
189 | 0 | return baseString + maskString |
190 | 0 | } |
191 | | } |
192 | | |
193 | | @available(*, deprecated, renamed: "NIONetworkDevice") |
194 | | extension NIONetworkInterface: Equatable { |
195 | 0 | public static func == (lhs: NIONetworkInterface, rhs: NIONetworkInterface) -> Bool { |
196 | 0 | lhs.name == rhs.name && lhs.address == rhs.address && lhs.netmask == rhs.netmask |
197 | 0 | && lhs.broadcastAddress == rhs.broadcastAddress |
198 | 0 | && lhs.pointToPointDestinationAddress == rhs.pointToPointDestinationAddress |
199 | 0 | && lhs.interfaceIndex == rhs.interfaceIndex |
200 | 0 | } |
201 | | } |
202 | | |
203 | | /// A helper extension for working with sockaddr pointers. |
204 | | extension UnsafeMutablePointer where Pointee == sockaddr { |
205 | | /// Converts the `sockaddr` to a `SocketAddress`. |
206 | 0 | fileprivate func convert() -> SocketAddress? { |
207 | 0 | let addressBytes = UnsafeRawPointer(self) |
208 | 0 | switch NIOBSDSocket.AddressFamily(rawValue: CInt(pointee.sa_family)) { |
209 | 0 | case .inet: |
210 | 0 | return SocketAddress(addressBytes.load(as: sockaddr_in.self)) |
211 | 0 | case .inet6: |
212 | 0 | return SocketAddress(addressBytes.load(as: sockaddr_in6.self)) |
213 | 0 | case .unix: |
214 | 0 | return SocketAddress(addressBytes.load(as: sockaddr_un.self)) |
215 | 0 | default: |
216 | 0 | return nil |
217 | 0 | } |
218 | 0 | } |
219 | | } |
220 | | |
221 | | /// A representation of a single network device on a system. |
222 | | public struct NIONetworkDevice { |
223 | | private var backing: Backing |
224 | | |
225 | | /// The name of the network device. |
226 | | public var name: String { |
227 | 0 | get { |
228 | 0 | self.backing.name |
229 | 0 | } |
230 | 0 | set { |
231 | 0 | self.uniquifyIfNeeded() |
232 | 0 | self.backing.name = newValue |
233 | 0 | } |
234 | | } |
235 | | |
236 | | /// The address associated with the given network device. |
237 | | public var address: SocketAddress? { |
238 | 0 | get { |
239 | 0 | self.backing.address |
240 | 0 | } |
241 | 0 | set { |
242 | 0 | self.uniquifyIfNeeded() |
243 | 0 | self.backing.address = newValue |
244 | 0 | } |
245 | | } |
246 | | |
247 | | /// The netmask associated with this address, if any. |
248 | | public var netmask: SocketAddress? { |
249 | 0 | get { |
250 | 0 | self.backing.netmask |
251 | 0 | } |
252 | 0 | set { |
253 | 0 | self.uniquifyIfNeeded() |
254 | 0 | self.backing.netmask = newValue |
255 | 0 | } |
256 | | } |
257 | | |
258 | | /// The broadcast address associated with this socket interface, if it has one. Some |
259 | | /// interfaces do not, especially those that have a `pointToPointDestinationAddress`. |
260 | | public var broadcastAddress: SocketAddress? { |
261 | 0 | get { |
262 | 0 | self.backing.broadcastAddress |
263 | 0 | } |
264 | 0 | set { |
265 | 0 | self.uniquifyIfNeeded() |
266 | 0 | self.backing.broadcastAddress = newValue |
267 | 0 | } |
268 | | } |
269 | | |
270 | | /// The address of the peer on a point-to-point interface, if this is one. Some |
271 | | /// interfaces do not have such an address: most of those have a `broadcastAddress` |
272 | | /// instead. |
273 | | public var pointToPointDestinationAddress: SocketAddress? { |
274 | 0 | get { |
275 | 0 | self.backing.pointToPointDestinationAddress |
276 | 0 | } |
277 | 0 | set { |
278 | 0 | self.uniquifyIfNeeded() |
279 | 0 | self.backing.pointToPointDestinationAddress = newValue |
280 | 0 | } |
281 | | } |
282 | | |
283 | | /// If the Interface supports Multicast |
284 | | public var multicastSupported: Bool { |
285 | 0 | get { |
286 | 0 | self.backing.multicastSupported |
287 | 0 | } |
288 | 0 | set { |
289 | 0 | self.uniquifyIfNeeded() |
290 | 0 | self.backing.multicastSupported = newValue |
291 | 0 | } |
292 | | } |
293 | | |
294 | | /// The index of the interface, as provided by `if_nametoindex`. |
295 | | public var interfaceIndex: Int { |
296 | 0 | get { |
297 | 0 | self.backing.interfaceIndex |
298 | 0 | } |
299 | 0 | set { |
300 | 0 | self.uniquifyIfNeeded() |
301 | 0 | self.backing.interfaceIndex = newValue |
302 | 0 | } |
303 | | } |
304 | | |
305 | | /// Create a brand new network interface. |
306 | | /// |
307 | | /// This constructor will fail if NIO does not understand the format of the underlying |
308 | | /// socket address family. This is quite common: for example, Linux will return AF_PACKET |
309 | | /// addressed interfaces on most platforms, which NIO does not currently understand. |
310 | | #if os(Windows) |
311 | | internal init?( |
312 | | _ pAdapter: UnsafeMutablePointer<IP_ADAPTER_ADDRESSES>, |
313 | | _ pAddress: UnsafeMutablePointer<IP_ADAPTER_UNICAST_ADDRESS> |
314 | | ) { |
315 | | guard let backing = Backing(pAdapter, pAddress) else { |
316 | | return nil |
317 | | } |
318 | | self.backing = backing |
319 | | } |
320 | | #elseif !os(WASI) |
321 | 0 | internal init?(_ caddr: ifaddrs) { |
322 | 0 | guard let backing = Backing(caddr) else { |
323 | 0 | return nil |
324 | 0 | } |
325 | 0 |
|
326 | 0 | self.backing = backing |
327 | 0 | } |
328 | | #endif |
329 | | |
330 | | #if !os(Windows) && !os(WASI) |
331 | | /// Convert a `NIONetworkInterface` to a `NIONetworkDevice`. As `NIONetworkDevice`s are a superset of `NIONetworkInterface`s, |
332 | | /// it is always possible to perform this conversion. |
333 | | @available(*, deprecated, message: "This is a compatibility helper, and will be removed in a future release") |
334 | 0 | public init(_ interface: NIONetworkInterface) { |
335 | 0 | self.backing = Backing( |
336 | 0 | name: interface.name, |
337 | 0 | address: interface.address, |
338 | 0 | netmask: interface.netmask, |
339 | 0 | broadcastAddress: interface.broadcastAddress, |
340 | 0 | pointToPointDestinationAddress: interface.pointToPointDestinationAddress, |
341 | 0 | multicastSupported: interface.multicastSupported, |
342 | 0 | interfaceIndex: interface.interfaceIndex |
343 | 0 | ) |
344 | 0 | } |
345 | | #endif |
346 | | |
347 | | public init( |
348 | | name: String, |
349 | | address: SocketAddress?, |
350 | | netmask: SocketAddress?, |
351 | | broadcastAddress: SocketAddress?, |
352 | | pointToPointDestinationAddress: SocketAddress, |
353 | | multicastSupported: Bool, |
354 | | interfaceIndex: Int |
355 | 0 | ) { |
356 | 0 | self.backing = Backing( |
357 | 0 | name: name, |
358 | 0 | address: address, |
359 | 0 | netmask: netmask, |
360 | 0 | broadcastAddress: broadcastAddress, |
361 | 0 | pointToPointDestinationAddress: pointToPointDestinationAddress, |
362 | 0 | multicastSupported: multicastSupported, |
363 | 0 | interfaceIndex: interfaceIndex |
364 | 0 | ) |
365 | 0 | } |
366 | | |
367 | 0 | private mutating func uniquifyIfNeeded() { |
368 | 0 | if !isKnownUniquelyReferenced(&self.backing) { |
369 | 0 | self.backing = Backing(copying: self.backing) |
370 | 0 | } |
371 | 0 | } |
372 | | } |
373 | | |
374 | | extension NIONetworkDevice: @unchecked Sendable {} |
375 | | |
376 | | extension NIONetworkDevice { |
377 | | fileprivate final class Backing { |
378 | | /// The name of the network interface. |
379 | | var name: String |
380 | | |
381 | | /// The address associated with the given network interface. |
382 | | var address: SocketAddress? |
383 | | |
384 | | /// The netmask associated with this address, if any. |
385 | | var netmask: SocketAddress? |
386 | | |
387 | | /// The broadcast address associated with this socket interface, if it has one. Some |
388 | | /// interfaces do not, especially those that have a `pointToPointDestinationAddress`. |
389 | | var broadcastAddress: SocketAddress? |
390 | | |
391 | | /// The address of the peer on a point-to-point interface, if this is one. Some |
392 | | /// interfaces do not have such an address: most of those have a `broadcastAddress` |
393 | | /// instead. |
394 | | var pointToPointDestinationAddress: SocketAddress? |
395 | | |
396 | | /// If the Interface supports Multicast |
397 | | var multicastSupported: Bool |
398 | | |
399 | | /// The index of the interface, as provided by `if_nametoindex`. |
400 | | var interfaceIndex: Int |
401 | | |
402 | | /// Create a brand new network interface. |
403 | | /// |
404 | | /// This constructor will fail if NIO does not understand the format of the underlying |
405 | | /// socket address family. This is quite common: for example, Linux will return AF_PACKET |
406 | | /// addressed interfaces on most platforms, which NIO does not currently understand. |
407 | | #if os(Windows) |
408 | | internal init?( |
409 | | _ pAdapter: UnsafeMutablePointer<IP_ADAPTER_ADDRESSES>, |
410 | | _ pAddress: UnsafeMutablePointer<IP_ADAPTER_UNICAST_ADDRESS> |
411 | | ) { |
412 | | self.name = String( |
413 | | decodingCString: pAdapter.pointee.FriendlyName, |
414 | | as: UTF16.self |
415 | | ) |
416 | | self.address = pAddress.pointee.Address.lpSockaddr.convert() |
417 | | |
418 | | switch pAddress.pointee.Address.lpSockaddr.pointee.sa_family { |
419 | | case ADDRESS_FAMILY(AF_INET): |
420 | | self.netmask = SocketAddress(ipv4MaskForPrefix: Int(pAddress.pointee.OnLinkPrefixLength)) |
421 | | self.interfaceIndex = Int(pAdapter.pointee.IfIndex) |
422 | | break |
423 | | case ADDRESS_FAMILY(AF_INET6): |
424 | | self.netmask = SocketAddress(ipv6MaskForPrefix: Int(pAddress.pointee.OnLinkPrefixLength)) |
425 | | self.interfaceIndex = Int(pAdapter.pointee.Ipv6IfIndex) |
426 | | break |
427 | | default: |
428 | | return nil |
429 | | } |
430 | | |
431 | | // TODO(compnerd) handle broadcast/ppp/multicast information |
432 | | self.broadcastAddress = nil |
433 | | self.pointToPointDestinationAddress = nil |
434 | | self.multicastSupported = false |
435 | | } |
436 | | #elseif !os(WASI) |
437 | 0 | internal init?(_ caddr: ifaddrs) { |
438 | 0 | self.name = String(cString: caddr.ifa_name!) |
439 | 0 | self.address = caddr.ifa_addr.flatMap { $0.convert() } |
440 | 0 | self.netmask = caddr.ifa_netmask.flatMap { $0.convert() } |
441 | 0 |
|
442 | 0 | if (caddr.ifa_flags & UInt32(IFF_BROADCAST)) != 0, let addr = caddr.broadaddr { |
443 | 0 | self.broadcastAddress = addr.convert() |
444 | 0 | self.pointToPointDestinationAddress = nil |
445 | 0 | } else if (caddr.ifa_flags & UInt32(IFF_POINTOPOINT)) != 0, let addr = caddr.dstaddr { |
446 | 0 | self.broadcastAddress = nil |
447 | 0 | self.pointToPointDestinationAddress = addr.convert() |
448 | 0 | } else { |
449 | 0 | self.broadcastAddress = nil |
450 | 0 | self.pointToPointDestinationAddress = nil |
451 | 0 | } |
452 | 0 |
|
453 | 0 | self.multicastSupported = (caddr.ifa_flags & UInt32(IFF_MULTICAST)) != 0 |
454 | 0 | do { |
455 | 0 | self.interfaceIndex = Int(try SystemCalls.if_nametoindex(caddr.ifa_name)) |
456 | 0 | } catch { |
457 | 0 | return nil |
458 | 0 | } |
459 | 0 | } |
460 | | #endif |
461 | | |
462 | 0 | init(copying original: Backing) { |
463 | 0 | self.name = original.name |
464 | 0 | self.address = original.address |
465 | 0 | self.netmask = original.netmask |
466 | 0 | self.broadcastAddress = original.broadcastAddress |
467 | 0 | self.pointToPointDestinationAddress = original.pointToPointDestinationAddress |
468 | 0 | self.multicastSupported = original.multicastSupported |
469 | 0 | self.interfaceIndex = original.interfaceIndex |
470 | 0 | } |
471 | | |
472 | | init( |
473 | | name: String, |
474 | | address: SocketAddress?, |
475 | | netmask: SocketAddress?, |
476 | | broadcastAddress: SocketAddress?, |
477 | | pointToPointDestinationAddress: SocketAddress?, |
478 | | multicastSupported: Bool, |
479 | | interfaceIndex: Int |
480 | 0 | ) { |
481 | 0 | self.name = name |
482 | 0 | self.address = address |
483 | 0 | self.netmask = netmask |
484 | 0 | self.broadcastAddress = broadcastAddress |
485 | 0 | self.pointToPointDestinationAddress = pointToPointDestinationAddress |
486 | 0 | self.multicastSupported = multicastSupported |
487 | 0 | self.interfaceIndex = interfaceIndex |
488 | 0 | } |
489 | | } |
490 | | } |
491 | | |
492 | | extension NIONetworkDevice: CustomDebugStringConvertible { |
493 | 0 | public var debugDescription: String { |
494 | 0 | let baseString = "Device \(self.name): address \(String(describing: self.address))" |
495 | 0 | let maskString = self.netmask != nil ? " netmask \(self.netmask!)" : "" |
496 | 0 | return baseString + maskString |
497 | 0 | } |
498 | | } |
499 | | |
500 | | // Sadly, as this is class-backed we cannot synthesise the implementation. |
501 | | extension NIONetworkDevice: Equatable { |
502 | 0 | public static func == (lhs: NIONetworkDevice, rhs: NIONetworkDevice) -> Bool { |
503 | 0 | lhs.name == rhs.name && lhs.address == rhs.address && lhs.netmask == rhs.netmask |
504 | 0 | && lhs.broadcastAddress == rhs.broadcastAddress |
505 | 0 | && lhs.pointToPointDestinationAddress == rhs.pointToPointDestinationAddress |
506 | 0 | && lhs.interfaceIndex == rhs.interfaceIndex |
507 | 0 | } |
508 | | } |
509 | | |
510 | | extension NIONetworkDevice: Hashable { |
511 | 0 | public func hash(into hasher: inout Hasher) { |
512 | 0 | hasher.combine(self.name) |
513 | 0 | hasher.combine(self.address) |
514 | 0 | hasher.combine(self.netmask) |
515 | 0 | hasher.combine(self.broadcastAddress) |
516 | 0 | hasher.combine(self.pointToPointDestinationAddress) |
517 | 0 | hasher.combine(self.interfaceIndex) |
518 | 0 | } |
519 | | } |