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