Coverage Report

Created: 2026-02-11 06:21

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}