Coverage Report

Created: 2025-07-23 06:54

/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
}