Coverage Report

Created: 2025-07-23 06:54

/src/swift-nio/Sources/NIOCore/BSDSocketAPI.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) 2020-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
15
#if os(Windows)
16
import ucrt
17
18
import let WinSDK.IPPROTO_IP
19
import let WinSDK.IPPROTO_IPV6
20
import let WinSDK.IPPROTO_TCP
21
import let WinSDK.IPPROTO_UDP
22
23
import let WinSDK.IP_ADD_MEMBERSHIP
24
import let WinSDK.IP_DROP_MEMBERSHIP
25
import let WinSDK.IP_HDRINCL
26
import let WinSDK.IP_MULTICAST_IF
27
import let WinSDK.IP_MULTICAST_LOOP
28
import let WinSDK.IP_MULTICAST_TTL
29
import let WinSDK.IPV6_JOIN_GROUP
30
import let WinSDK.IPV6_LEAVE_GROUP
31
import let WinSDK.IPV6_MULTICAST_HOPS
32
import let WinSDK.IPV6_MULTICAST_IF
33
import let WinSDK.IPV6_MULTICAST_LOOP
34
import let WinSDK.IPV6_V6ONLY
35
36
import let WinSDK.AF_INET
37
import let WinSDK.AF_INET6
38
import let WinSDK.AF_UNIX
39
40
import let WinSDK.PF_INET
41
import let WinSDK.PF_INET6
42
import let WinSDK.PF_UNIX
43
44
import let WinSDK.SO_BROADCAST
45
import let WinSDK.SO_ERROR
46
import let WinSDK.SO_KEEPALIVE
47
import let WinSDK.SO_LINGER
48
import let WinSDK.SO_RCVBUF
49
import let WinSDK.SO_RCVTIMEO
50
import let WinSDK.SO_REUSEADDR
51
import let WinSDK.SO_SNDBUF
52
53
import let WinSDK.SOL_SOCKET
54
55
import let WinSDK.TCP_NODELAY
56
57
import struct WinSDK.SOCKET
58
59
import func WinSDK.inet_ntop
60
import func WinSDK.inet_pton
61
62
import func WinSDK.GetLastError
63
import func WinSDK.WSAGetLastError
64
65
internal typealias socklen_t = ucrt.size_t
66
#elseif os(Linux) || os(Android)
67
#if canImport(Glibc)
68
@preconcurrency import Glibc
69
#elseif canImport(Musl)
70
@preconcurrency import Musl
71
#elseif canImport(Android)
72
@preconcurrency import Android
73
#endif
74
import CNIOLinux
75
76
#if os(Android)
77
private let sysInet_ntop:
78
    @convention(c) (CInt, UnsafeRawPointer, UnsafeMutablePointer<CChar>, socklen_t) -> UnsafePointer<CChar>? = inet_ntop
79
private let sysInet_pton: @convention(c) (CInt, UnsafePointer<CChar>, UnsafeMutableRawPointer) -> CInt = inet_pton
80
#else
81
private let sysInet_ntop:
82
    @convention(c) (CInt, UnsafeRawPointer?, UnsafeMutablePointer<CChar>?, socklen_t) -> UnsafePointer<CChar>? =
83
        inet_ntop
84
private let sysInet_pton: @convention(c) (CInt, UnsafePointer<CChar>?, UnsafeMutableRawPointer?) -> CInt = inet_pton
85
#endif
86
#elseif canImport(Darwin)
87
import Darwin
88
89
private let sysInet_ntop:
90
    @convention(c) (CInt, UnsafeRawPointer?, UnsafeMutablePointer<CChar>?, socklen_t) -> UnsafePointer<CChar>? =
91
        inet_ntop
92
private let sysInet_pton: @convention(c) (CInt, UnsafePointer<CChar>?, UnsafeMutableRawPointer?) -> CInt = inet_pton
93
#elseif canImport(WASILibc)
94
@preconcurrency import WASILibc
95
96
private let sysInet_ntop:
97
    @convention(c) (CInt, UnsafeRawPointer?, UnsafeMutablePointer<CChar>?, socklen_t) -> UnsafePointer<CChar>? =
98
        inet_ntop
99
private let sysInet_pton: @convention(c) (CInt, UnsafePointer<CChar>?, UnsafeMutableRawPointer?) -> CInt = inet_pton
100
#else
101
#error("The BSD Socket module was unable to identify your C library.")
102
#endif
103
104
#if os(Android)
105
#if compiler(>=6.0)
106
@usableFromInline let IFF_BROADCAST: CUnsignedInt = numericCast(Android.IFF_BROADCAST.rawValue)
107
@usableFromInline let IFF_POINTOPOINT: CUnsignedInt = numericCast(Android.IFF_POINTOPOINT.rawValue)
108
@usableFromInline let IFF_MULTICAST: CUnsignedInt = numericCast(Android.IFF_MULTICAST.rawValue)
109
#else
110
@usableFromInline let IFF_BROADCAST: CUnsignedInt = numericCast(SwiftGlibc.IFF_BROADCAST.rawValue)
111
@usableFromInline let IFF_POINTOPOINT: CUnsignedInt = numericCast(SwiftGlibc.IFF_POINTOPOINT.rawValue)
112
@usableFromInline let IFF_MULTICAST: CUnsignedInt = numericCast(SwiftGlibc.IFF_MULTICAST.rawValue)
113
#endif
114
#if arch(arm)
115
@usableFromInline let SO_RCVTIMEO = SO_RCVTIMEO_OLD
116
@usableFromInline let SO_TIMESTAMP = SO_TIMESTAMP_OLD
117
#endif
118
#elseif os(Linux)
119
// Work around SO_TIMESTAMP/SO_RCVTIMEO being awkwardly defined in glibc.
120
@usableFromInline let SO_TIMESTAMP = CNIOLinux_SO_TIMESTAMP
121
@usableFromInline let SO_RCVTIMEO = CNIOLinux_SO_RCVTIMEO
122
#endif
123
124
public enum NIOBSDSocket: Sendable {
125
    #if os(Windows)
126
    public typealias Handle = SOCKET
127
    #else
128
    public typealias Handle = CInt
129
    #endif
130
}
131
132
extension NIOBSDSocket {
133
    /// Specifies the addressing scheme that the socket can use.
134
    public struct AddressFamily: RawRepresentable, Sendable {
135
        public typealias RawValue = CInt
136
        public var rawValue: RawValue
137
138
        @inlinable
139
0
        public init(rawValue: RawValue) {
140
0
            self.rawValue = rawValue
141
0
        }
142
    }
143
}
144
145
extension NIOBSDSocket.AddressFamily: Equatable {
146
}
147
148
extension NIOBSDSocket.AddressFamily: Hashable {
149
}
150
151
extension NIOBSDSocket {
152
    /// Specifies the type of protocol that the socket can use.
153
    public struct ProtocolFamily: RawRepresentable, Sendable {
154
        public typealias RawValue = CInt
155
        public var rawValue: RawValue
156
157
        @inlinable
158
0
        public init(rawValue: RawValue) {
159
0
            self.rawValue = rawValue
160
0
        }
161
    }
162
}
163
164
extension NIOBSDSocket.ProtocolFamily: Equatable {
165
}
166
167
extension NIOBSDSocket.ProtocolFamily: Hashable {
168
}
169
170
extension NIOBSDSocket {
171
    /// Defines socket option levels.
172
    public struct OptionLevel: RawRepresentable, Sendable {
173
        public typealias RawValue = CInt
174
        public var rawValue: RawValue
175
176
        @inlinable
177
0
        public init(rawValue: RawValue) {
178
0
            self.rawValue = rawValue
179
0
        }
180
    }
181
}
182
183
extension NIOBSDSocket.OptionLevel: Equatable {
184
}
185
186
extension NIOBSDSocket.OptionLevel: Hashable {
187
}
188
189
extension NIOBSDSocket {
190
    /// Defines configuration option names.
191
    public struct Option: RawRepresentable, Sendable {
192
        public typealias RawValue = CInt
193
        public var rawValue: RawValue
194
195
        @inlinable
196
0
        public init(rawValue: RawValue) {
197
0
            self.rawValue = rawValue
198
0
        }
199
    }
200
}
201
202
extension NIOBSDSocket.Option: Equatable {
203
}
204
205
extension NIOBSDSocket.Option: Hashable {
206
}
207
208
// Address Family
209
extension NIOBSDSocket.AddressFamily {
210
    /// Address for IP version 4.
211
    @inlinable
212
0
    public static var inet: NIOBSDSocket.AddressFamily {
213
0
        NIOBSDSocket.AddressFamily(rawValue: AF_INET)
214
0
    }
215
216
    /// Address for IP version 6.
217
    @inlinable
218
0
    public static var inet6: NIOBSDSocket.AddressFamily {
219
0
        NIOBSDSocket.AddressFamily(rawValue: AF_INET6)
220
0
    }
221
222
    /// Unix local to host address.
223
    @inlinable
224
0
    public static var unix: NIOBSDSocket.AddressFamily {
225
0
        NIOBSDSocket.AddressFamily(rawValue: AF_UNIX)
226
0
    }
227
}
228
229
// Protocol Family
230
extension NIOBSDSocket.ProtocolFamily {
231
    /// IP network 4 protocol.
232
    @inlinable
233
0
    public static var inet: NIOBSDSocket.ProtocolFamily {
234
0
        NIOBSDSocket.ProtocolFamily(rawValue: PF_INET)
235
0
    }
236
237
    /// IP network 6 protocol.
238
    @inlinable
239
0
    public static var inet6: NIOBSDSocket.ProtocolFamily {
240
0
        NIOBSDSocket.ProtocolFamily(rawValue: PF_INET6)
241
0
    }
242
243
    #if !os(WASI)
244
    /// UNIX local to the host.
245
    @inlinable
246
0
    public static var unix: NIOBSDSocket.ProtocolFamily {
247
0
        NIOBSDSocket.ProtocolFamily(rawValue: PF_UNIX)
248
0
    }
249
    #endif
250
}
251
252
#if !os(Windows) && !os(WASI)
253
extension NIOBSDSocket.ProtocolFamily {
254
    /// UNIX local to the host, alias for `PF_UNIX` (`.unix`)
255
    @inlinable
256
0
    public static var local: NIOBSDSocket.ProtocolFamily {
257
0
        NIOBSDSocket.ProtocolFamily(rawValue: PF_LOCAL)
258
0
    }
259
}
260
#endif
261
262
// Option Level
263
extension NIOBSDSocket.OptionLevel {
264
    /// Socket options that apply only to IP sockets.
265
    #if os(Linux) || os(Android)
266
    @inlinable
267
0
    public static var ip: NIOBSDSocket.OptionLevel {
268
0
        NIOBSDSocket.OptionLevel(rawValue: CInt(IPPROTO_IP))
269
0
    }
270
    #else
271
    @inlinable
272
    public static var ip: NIOBSDSocket.OptionLevel {
273
        NIOBSDSocket.OptionLevel(rawValue: IPPROTO_IP)
274
    }
275
    #endif
276
277
    /// Socket options that apply only to IPv6 sockets.
278
    #if os(Linux) || os(Android)
279
    @inlinable
280
0
    public static var ipv6: NIOBSDSocket.OptionLevel {
281
0
        NIOBSDSocket.OptionLevel(rawValue: CInt(IPPROTO_IPV6))
282
0
    }
283
    #elseif os(Windows)
284
    @inlinable
285
    public static var ipv6: NIOBSDSocket.OptionLevel {
286
        NIOBSDSocket.OptionLevel(rawValue: IPPROTO_IPV6.rawValue)
287
    }
288
    #else
289
    @inlinable
290
    public static var ipv6: NIOBSDSocket.OptionLevel {
291
        NIOBSDSocket.OptionLevel(rawValue: IPPROTO_IPV6)
292
    }
293
    #endif
294
295
    /// Socket options that apply only to TCP sockets.
296
    #if os(Linux) || os(Android)
297
    @inlinable
298
0
    public static var tcp: NIOBSDSocket.OptionLevel {
299
0
        NIOBSDSocket.OptionLevel(rawValue: CInt(IPPROTO_TCP))
300
0
    }
301
    #elseif os(Windows)
302
    @inlinable
303
    public static var tcp: NIOBSDSocket.OptionLevel {
304
        NIOBSDSocket.OptionLevel(rawValue: IPPROTO_TCP.rawValue)
305
    }
306
    #else
307
    @inlinable
308
    public static var tcp: NIOBSDSocket.OptionLevel {
309
        NIOBSDSocket.OptionLevel(rawValue: IPPROTO_TCP)
310
    }
311
    #endif
312
313
    /// Socket options that apply to MPTCP sockets.
314
    ///
315
    /// These only work on Linux currently.
316
    @inlinable
317
0
    public static var mptcp: NIOBSDSocket.OptionLevel {
318
0
        NIOBSDSocket.OptionLevel(rawValue: 284)
319
0
    }
320
321
    /// Socket options that apply to all sockets.
322
    @inlinable
323
0
    public static var socket: NIOBSDSocket.OptionLevel {
324
0
        NIOBSDSocket.OptionLevel(rawValue: SOL_SOCKET)
325
0
    }
326
327
    /// Socket options that apply only to UDP sockets.
328
    #if os(Linux) || os(Android)
329
    @inlinable
330
0
    public static var udp: NIOBSDSocket.OptionLevel {
331
0
        NIOBSDSocket.OptionLevel(rawValue: CInt(IPPROTO_UDP))
332
0
    }
333
    #elseif os(Windows)
334
    @inlinable
335
    public static var udp: NIOBSDSocket.OptionLevel {
336
        NIOBSDSocket.OptionLevel(rawValue: IPPROTO_UDP.rawValue)
337
    }
338
    #else
339
    @inlinable
340
    public static var udp: NIOBSDSocket.OptionLevel {
341
        NIOBSDSocket.OptionLevel(rawValue: IPPROTO_UDP)
342
    }
343
    #endif
344
}
345
346
// IPv4 Options
347
extension NIOBSDSocket.Option {
348
    /// Add a multicast group membership.
349
    @inlinable
350
0
    public static var ip_add_membership: NIOBSDSocket.Option {
351
0
        NIOBSDSocket.Option(rawValue: IP_ADD_MEMBERSHIP)
352
0
    }
353
354
    /// Drop a multicast group membership.
355
    @inlinable
356
0
    public static var ip_drop_membership: NIOBSDSocket.Option {
357
0
        NIOBSDSocket.Option(rawValue: IP_DROP_MEMBERSHIP)
358
0
    }
359
360
    /// Set the interface for outgoing multicast packets.
361
    @inlinable
362
0
    public static var ip_multicast_if: NIOBSDSocket.Option {
363
0
        NIOBSDSocket.Option(rawValue: IP_MULTICAST_IF)
364
0
    }
365
366
    /// Control multicast loopback.
367
    @inlinable
368
0
    public static var ip_multicast_loop: NIOBSDSocket.Option {
369
0
        NIOBSDSocket.Option(rawValue: IP_MULTICAST_LOOP)
370
0
    }
371
372
    /// Control multicast time-to-live.
373
    @inlinable
374
0
    public static var ip_multicast_ttl: NIOBSDSocket.Option {
375
0
        NIOBSDSocket.Option(rawValue: IP_MULTICAST_TTL)
376
0
    }
377
378
    /// The IPv4 layer generates an IP header when sending a packet
379
    /// unless the ``ip_hdrincl`` socket option is enabled on the socket.
380
    /// When it is enabled, the packet must contain an IP header.  For
381
    /// receiving, the IP header is always included in the packet.
382
    @inlinable
383
0
    public static var ip_hdrincl: NIOBSDSocket.Option {
384
0
        NIOBSDSocket.Option(rawValue: IP_HDRINCL)
385
0
    }
386
}
387
388
// IPv6 Options
389
extension NIOBSDSocket.Option {
390
    /// Add an IPv6 group membership.
391
    @inlinable
392
0
    public static var ipv6_join_group: NIOBSDSocket.Option {
393
0
        NIOBSDSocket.Option(rawValue: IPV6_JOIN_GROUP)
394
0
    }
395
396
    /// Drop an IPv6 group membership.
397
    @inlinable
398
0
    public static var ipv6_leave_group: NIOBSDSocket.Option {
399
0
        NIOBSDSocket.Option(rawValue: IPV6_LEAVE_GROUP)
400
0
    }
401
402
    /// Specify the maximum number of router hops for an IPv6 packet.
403
    @inlinable
404
0
    public static var ipv6_multicast_hops: NIOBSDSocket.Option {
405
0
        NIOBSDSocket.Option(rawValue: IPV6_MULTICAST_HOPS)
406
0
    }
407
408
    /// Set the interface for outgoing multicast packets.
409
    @inlinable
410
0
    public static var ipv6_multicast_if: NIOBSDSocket.Option {
411
0
        NIOBSDSocket.Option(rawValue: IPV6_MULTICAST_IF)
412
0
    }
413
414
    /// Control multicast loopback.
415
    @inlinable
416
0
    public static var ipv6_multicast_loop: NIOBSDSocket.Option {
417
0
        NIOBSDSocket.Option(rawValue: IPV6_MULTICAST_LOOP)
418
0
    }
419
420
    /// Indicates if a socket created for the `AF_INET6` address family is
421
    /// restricted to IPv6 only.
422
    @inlinable
423
0
    public static var ipv6_v6only: NIOBSDSocket.Option {
424
0
        NIOBSDSocket.Option(rawValue: IPV6_V6ONLY)
425
0
    }
426
}
427
428
// TCP Options
429
extension NIOBSDSocket.Option {
430
    /// Disables the Nagle algorithm for send coalescing.
431
    @inlinable
432
0
    public static var tcp_nodelay: NIOBSDSocket.Option {
433
0
        NIOBSDSocket.Option(rawValue: TCP_NODELAY)
434
0
    }
435
}
436
437
#if os(Linux) || os(FreeBSD) || os(Android)
438
extension NIOBSDSocket.Option {
439
    /// Get information about the TCP connection.
440
    @inlinable
441
0
    public static var tcp_info: NIOBSDSocket.Option {
442
0
        NIOBSDSocket.Option(rawValue: TCP_INFO)
443
0
    }
444
}
445
#endif
446
447
#if canImport(Darwin)
448
extension NIOBSDSocket.Option {
449
    /// Get information about the TCP connection.
450
    @inlinable
451
    public static var tcp_connection_info: NIOBSDSocket.Option {
452
        NIOBSDSocket.Option(rawValue: TCP_CONNECTION_INFO)
453
    }
454
}
455
#endif
456
457
#if os(Linux)
458
extension NIOBSDSocket.Option {
459
    // Note: UDP_SEGMENT and UDP_GRO are not available on all Linux platforms so values are
460
    // hardcoded.
461
462
    /// Use UDP segmentation offload (UDP_SEGMENT, or 'GSO'). Only available on Linux.
463
    @inlinable
464
0
    public static var udp_segment: NIOBSDSocket.Option {
465
0
        NIOBSDSocket.Option(rawValue: 103)
466
0
    }
467
468
    /// Use UDP generic receive offload (GRO). Only available on Linux.
469
    @inlinable
470
0
    public static var udp_gro: NIOBSDSocket.Option {
471
0
        NIOBSDSocket.Option(rawValue: 104)
472
0
    }
473
}
474
#endif
475
476
// MPTCP options
477
//
478
// These values are hardcoded as they're fairly new, and not available in all
479
// header files yet.
480
extension NIOBSDSocket.Option {
481
    /// Get info about an MPTCP connection
482
    @inlinable
483
0
    public static var mptcp_info: NIOBSDSocket.Option {
484
0
        NIOBSDSocket.Option(rawValue: 1)
485
0
    }
486
}
487
488
#if !os(WASI)
489
// Socket Options
490
extension NIOBSDSocket.Option {
491
    /// Get the error status and clear.
492
    @inlinable
493
0
    public static var so_error: NIOBSDSocket.Option {
494
0
        Self(rawValue: SO_ERROR)
495
0
    }
496
497
    /// Use keep-alives.
498
    @inlinable
499
0
    public static var so_keepalive: NIOBSDSocket.Option {
500
0
        Self(rawValue: SO_KEEPALIVE)
501
0
    }
502
503
    /// Linger on close if unsent data is present.
504
    @inlinable
505
0
    public static var so_linger: NIOBSDSocket.Option {
506
0
        Self(rawValue: SO_LINGER)
507
0
    }
508
509
    /// Specifies the total per-socket buffer space reserved for receives.
510
    @inlinable
511
0
    public static var so_rcvbuf: NIOBSDSocket.Option {
512
0
        Self(rawValue: SO_RCVBUF)
513
0
    }
514
515
    /// Specifies the total per-socket buffer space reserved for sends.
516
    @inlinable
517
0
    public static var so_sndbuf: NIOBSDSocket.Option {
518
0
        Self(rawValue: SO_SNDBUF)
519
0
    }
520
521
    /// Specifies the receive timeout.
522
    @inlinable
523
0
    public static var so_rcvtimeo: NIOBSDSocket.Option {
524
0
        Self(rawValue: SO_RCVTIMEO)
525
0
    }
526
527
    /// Allows the socket to be bound to an address that is already in use.
528
    @inlinable
529
0
    public static var so_reuseaddr: NIOBSDSocket.Option {
530
0
        Self(rawValue: SO_REUSEADDR)
531
0
    }
532
533
    /// Allows the socket to send broadcast messages.
534
    @inlinable
535
0
    public static var so_broadcast: NIOBSDSocket.Option {
536
0
        Self(rawValue: SO_BROADCAST)
537
0
    }
538
}
539
#endif
540
541
#if !os(Windows) && !os(WASI)
542
extension NIOBSDSocket.Option {
543
    /// Indicate when to generate timestamps.
544
    @inlinable
545
0
    public static var so_timestamp: NIOBSDSocket.Option {
546
0
        NIOBSDSocket.Option(rawValue: SO_TIMESTAMP)
547
0
    }
548
}
549
#endif
550
551
extension NIOBSDSocket {
552
    // Sadly this was defined on BSDSocket, and we need it for SocketAddress.
553
    @inline(never)
554
    internal static func inet_pton(
555
        addressFamily: NIOBSDSocket.AddressFamily,
556
        addressDescription: UnsafePointer<CChar>,
557
        address: UnsafeMutableRawPointer
558
0
    ) throws {
559
0
        #if os(Windows)
560
0
        // TODO(compnerd) use `InetPtonW` to ensure that we handle unicode properly
561
0
        switch WinSDK.inet_pton(addressFamily.rawValue, addressDescription, address) {
562
0
        case 0: throw IOError(errnoCode: EINVAL, reason: "inet_pton")
563
0
        case 1: return
564
0
        default: throw IOError(winsock: WSAGetLastError(), reason: "inet_pton")
565
0
        }
566
0
        #else
567
0
        switch sysInet_pton(CInt(addressFamily.rawValue), addressDescription, address) {
568
0
        case 0: throw IOError(errnoCode: EINVAL, reason: #function)
569
0
        case 1: return
570
0
        default: throw IOError(errnoCode: errno, reason: #function)
571
0
        }
572
0
        #endif
573
0
    }
574
575
    @discardableResult
576
    @inline(never)
577
    internal static func inet_ntop(
578
        addressFamily: NIOBSDSocket.AddressFamily,
579
        addressBytes: UnsafeRawPointer,
580
        addressDescription: UnsafeMutablePointer<CChar>,
581
        addressDescriptionLength: socklen_t
582
0
    ) throws -> UnsafePointer<CChar> {
583
0
        #if os(Windows)
584
0
        // TODO(compnerd) use `InetNtopW` to ensure that we handle unicode properly
585
0
        guard
586
0
            let result = WinSDK.inet_ntop(
587
0
                addressFamily.rawValue,
588
0
                addressBytes,
589
0
                addressDescription,
590
0
                Int(addressDescriptionLength)
591
0
            )
592
0
        else {
593
0
            throw IOError(windows: GetLastError(), reason: "inet_ntop")
594
0
        }
595
0
        return result
596
0
        #else
597
0
        switch sysInet_ntop(CInt(addressFamily.rawValue), addressBytes, addressDescription, addressDescriptionLength) {
598
0
        case .none: throw IOError(errnoCode: errno, reason: #function)
599
0
        case .some(let ptr): return ptr
600
0
        }
601
0
        #endif
602
0
    }
603
}