Coverage Report

Created: 2026-07-29 06:47

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/swift-nio/Sources/NIOPosix/Socket.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
15
#if !os(WASI)
16
17
import CNIOLinux
18
import CNIOOpenBSD
19
import NIOCore
20
21
#if canImport(WinSDK)
22
import WinSDK
23
#endif
24
25
/// The container used for writing multiple buffers via `writev`.
26
#if canImport(WinSDK)
27
typealias IOVector = WSABUF
28
#else
29
typealias IOVector = iovec
30
#endif
31
32
// TODO: scattering support
33
class Socket: BaseSocket, SocketProtocol {
34
    typealias SocketType = Socket
35
36
    /// The maximum number of bytes to write per `writev` call.
37
    static let writevLimitBytes = Int(Int32.max)
38
39
    /// The maximum number of `IOVector`s to write per `writev` call.
40
    static let writevLimitIOVectors: Int = Posix.UIO_MAXIOV
41
42
    /// Create a new instance.
43
    ///
44
    /// - Parameters:
45
    ///   - protocolFamily: The protocol family to use (usually `AF_INET6` or `AF_INET`).
46
    ///   - type: The type of the socket to create.
47
    ///   - protocolSubtype: The subtype of the protocol, corresponding to the `protocolSubtype`
48
    ///         argument to the socket syscall. Defaults to 0.
49
    ///   - setNonBlocking: Set non-blocking mode on the socket.
50
    /// - Throws: An `IOError` if creation of the socket failed.
51
    init(
52
        protocolFamily: NIOBSDSocket.ProtocolFamily,
53
        type: NIOBSDSocket.SocketType,
54
        protocolSubtype: NIOBSDSocket.ProtocolSubtype = .default,
55
        setNonBlocking: Bool = false
56
0
    ) throws {
57
0
        let sock = try BaseSocket.makeSocket(
58
0
            protocolFamily: protocolFamily,
59
0
            type: type,
60
0
            protocolSubtype: protocolSubtype,
61
0
            setNonBlocking: setNonBlocking
62
0
        )
63
0
        try super.init(socket: sock)
64
0
    }
65
66
    /// Create a new instance out of an already established socket.
67
    ///
68
    /// - Parameters:
69
    ///   - descriptor: The existing socket descriptor.
70
    ///   - setNonBlocking: Set non-blocking mode on the socket.
71
    /// - Throws: An `IOError` if could not change the socket into non-blocking
72
    #if !os(Windows)
73
    @available(*, deprecated, renamed: "init(socket:setNonBlocking:)")
74
0
    convenience init(descriptor: CInt, setNonBlocking: Bool) throws {
75
0
        try self.init(socket: descriptor, setNonBlocking: setNonBlocking)
76
0
    }
77
    #endif
78
79
    /// Create a new instance out of an already established socket.
80
    ///
81
    /// - Parameters:
82
    ///   - descriptor: The existing socket descriptor.
83
    ///   - setNonBlocking: Set non-blocking mode on the socket.
84
    /// - Throws: An `IOError` if could not change the socket into non-blocking
85
0
    init(socket: NIOBSDSocket.Handle, setNonBlocking: Bool) throws {
86
0
        try super.init(socket: socket)
87
0
        if setNonBlocking {
88
0
            try self.setNonBlocking()
89
0
        }
90
0
    }
91
92
    /// Create a new instance.
93
    ///
94
    /// The ownership of the passed in descriptor is transferred to this class. A user must call `close` to close the underlying
95
    /// file descriptor once it's not needed / used anymore.
96
    ///
97
    /// - Parameters:
98
    ///   - descriptor: The file descriptor to wrap.
99
    #if !os(Windows)
100
    @available(*, deprecated, renamed: "init(socket:)")
101
0
    convenience init(descriptor: CInt) throws {
102
0
        try self.init(socket: descriptor)
103
0
    }
104
    #endif
105
106
    /// Create a new instance.
107
    ///
108
    /// The ownership of the passed in descriptor is transferred to this class. A user must call `close` to close the underlying
109
    /// file descriptor once it's not needed / used anymore.
110
    ///
111
    /// - Parameters:
112
    ///   - descriptor: The file descriptor to wrap.
113
0
    override init(socket: NIOBSDSocket.Handle) throws {
114
0
        try super.init(socket: socket)
115
0
    }
116
117
    /// Connect to the `SocketAddress`.
118
    ///
119
    /// - Parameters:
120
    ///   - address: The `SocketAddress` to which the connection should be established.
121
    /// - Returns: `true` if the connection attempt completes, `false` if `finishConnect` must be called later to complete the connection attempt.
122
    /// - Throws: An `IOError` if the operation failed.
123
0
    func connect(to address: SocketAddress) throws -> Bool {
124
0
        try withUnsafeHandle { fd in
125
0
            try address.withSockAddr { (ptr, size) in
126
0
                try NIOBSDSocket.connect(
127
0
                    socket: fd,
128
0
                    address: ptr,
129
0
                    address_len: socklen_t(size)
130
0
                )
131
0
            }
132
0
        }
133
0
    }
134
135
0
    func connect(to address: VsockAddress) throws -> Bool {
136
0
        try withUnsafeHandle { fd in
137
0
            try address.withSockAddr { (ptr, size) in
138
0
                try NIOBSDSocket.connect(
139
0
                    socket: fd,
140
0
                    address: ptr,
141
0
                    address_len: socklen_t(size)
142
0
                )
143
0
            }
144
0
        }
145
0
    }
146
147
    /// Finish a previous non-blocking `connect` operation.
148
    ///
149
    /// - Throws: An `IOError` if the operation failed.
150
0
    func finishConnect() throws {
151
0
        let result: Int32 = try getOption(level: .socket, name: .so_error)
152
0
        if result != 0 {
153
0
            throw IOError(errnoCode: result, reason: "finishing a non-blocking connect failed")
154
0
        }
155
0
    }
156
157
    /// Write data to the remote peer.
158
    ///
159
    /// - Parameters:
160
    ///   - pointer: Pointer (and size) to data to write.
161
    /// - Returns: The `IOResult` which indicates how much data could be written and if the operation returned before all could be written (because the socket is in non-blocking mode).
162
    /// - Throws: An `IOError` if the operation failed.
163
0
    func write(pointer: UnsafeRawBufferPointer) throws -> IOResult<Int> {
164
0
        try withUnsafeHandle {
165
0
            try NIOBSDSocket.send(
166
0
                socket: $0,
167
0
                buffer: pointer.baseAddress!,
168
0
                length: pointer.count
169
0
            )
170
0
        }
171
0
    }
172
173
    /// Write data to the remote peer (gathering writes).
174
    ///
175
    /// - Parameters:
176
    ///   - iovecs: The `IOVector`s to write.
177
    /// - Returns: The `IOResult` which indicates how much data could be written and if the operation returned before all could be written (because the socket is in non-blocking mode).
178
    /// - Throws: An `IOError` if the operation failed.
179
0
    func writev(iovecs: UnsafeBufferPointer<IOVector>) throws -> IOResult<Int> {
180
0
        try withUnsafeHandle {
181
0
            try NIOBSDSocket.writev(socket: $0, iovecs: iovecs)
182
0
        }
183
0
    }
184
185
    /// Send data to a destination.
186
    ///
187
    /// - Parameters:
188
    ///   - pointer: Pointer (and size) to the data to send.
189
    ///   - destinationPtr: The destination to which the data should be sent.
190
    ///   - destinationSize: The size of the destination address given.
191
    ///   - controlBytes: Extra `cmsghdr` information.
192
    /// - Returns: The `IOResult` which indicates how much data could be written and if the operation returned before all could be written
193
    /// (because the socket is in non-blocking mode).
194
    /// - Throws: An `IOError` if the operation failed.
195
    func sendmsg(
196
        pointer: UnsafeRawBufferPointer,
197
        destinationPtr: UnsafePointer<sockaddr>?,
198
        destinationSize: socklen_t,
199
        controlBytes: UnsafeMutableRawBufferPointer
200
0
    ) throws -> IOResult<Int> {
201
0
        // Dubious const casts - it should be OK as there is no reason why this should get mutated
202
0
        // just bad const declaration below us.
203
0
        var vec = IOVector(
204
0
            iov_base: UnsafeMutableRawPointer(mutating: pointer.baseAddress!),
205
0
            iov_len: numericCast(pointer.count)
206
0
        )
207
0
        let notConstCorrectDestinationPtr = UnsafeMutableRawPointer(mutating: destinationPtr)
208
0
209
0
        return try self.withUnsafeHandle { handle in
210
0
            try withUnsafeMutablePointer(to: &vec) { vecPtr in
211
0
                var messageHeader = msghdr()
212
0
                messageHeader.msg_name = notConstCorrectDestinationPtr
213
0
                messageHeader.msg_namelen = destinationSize
214
0
                messageHeader.msg_iov = vecPtr
215
0
                messageHeader.msg_iovlen = 1
216
0
                messageHeader.control_ptr = controlBytes
217
0
                messageHeader.msg_flags = 0
218
0
                return try NIOBSDSocket.sendmsg(socket: handle, msgHdr: &messageHeader, flags: 0)
219
0
            }
220
0
        }
221
0
    }
222
223
    /// Read data from the socket.
224
    ///
225
    /// - Parameters:
226
    ///   - pointer: The pointer (and size) to the storage into which the data should be read.
227
    /// - Returns: The `IOResult` which indicates how much data could be read and if the operation returned before all could be read (because the socket is in non-blocking mode).
228
    /// - Throws: An `IOError` if the operation failed.
229
0
    func read(pointer: UnsafeMutableRawBufferPointer) throws -> IOResult<Int> {
230
0
        try withUnsafeHandle { (handle) -> IOResult<Int> in
231
            #if os(Windows)
232
            try Windows.recv(socket: handle, pointer: pointer.baseAddress!, size: Int32(pointer.count), flags: 0)
233
            #else
234
0
            try Posix.read(descriptor: handle, pointer: pointer.baseAddress!, size: pointer.count)
235
            #endif
236
0
        }
237
0
    }
238
239
    /// Receive data from the socket, along with aditional control information.
240
    ///
241
    /// - Parameters:
242
    ///   - pointer: The pointer (and size) to the storage into which the data should be read.
243
    ///   - storage: The address from which the data was received
244
    ///   - storageLen: The size of the storage itself.
245
    ///   - controlBytes: A buffer in memory for use receiving control bytes. This parameter will be modified to hold any data actually received.
246
    /// - Returns: The `IOResult` which indicates how much data could be received and if the operation returned before all the data could be received
247
    ///     (because the socket is in non-blocking mode)
248
    /// - Throws: An `IOError` if the operation failed.
249
    func recvmsg(
250
        pointer: UnsafeMutableRawBufferPointer,
251
        storage: inout sockaddr_storage,
252
        storageLen: inout socklen_t,
253
        controlBytes: inout UnsafeReceivedControlBytes
254
0
    ) throws -> IOResult<Int> {
255
0
        var vec = IOVector(iov_base: pointer.baseAddress, iov_len: numericCast(pointer.count))
256
0
257
0
        return try withUnsafeMutablePointer(to: &vec) { vecPtr in
258
0
            try storage.withMutableSockAddr { (sockaddrPtr, _) in
259
0
                var messageHeader = msghdr()
260
0
                messageHeader.msg_name = .init(sockaddrPtr)
261
0
                messageHeader.msg_namelen = storageLen
262
0
                messageHeader.msg_iov = vecPtr
263
0
                messageHeader.msg_iovlen = 1
264
0
                messageHeader.control_ptr = controlBytes.controlBytesBuffer
265
0
                messageHeader.msg_flags = 0
266
0
                defer {
267
0
                    // We need to write back the length of the message.
268
0
                    storageLen = messageHeader.msg_namelen
269
0
                }
270
0
271
0
                let result = try withUnsafeMutablePointer(to: &messageHeader) { messageHeader in
272
0
                    try withUnsafeHandle { fd in
273
0
                        try NIOBSDSocket.recvmsg(socket: fd, msgHdr: messageHeader, flags: 0)
274
0
                    }
275
0
                }
276
0
277
0
                // Only look at the control bytes if all is good.
278
0
                if case .processed = result {
279
0
                    controlBytes.receivedControlMessages = UnsafeControlMessageCollection(messageHeader: messageHeader)
280
0
                }
281
0
282
0
                return result
283
0
            }
284
0
        }
285
0
    }
286
287
    /// Send the content of a file descriptor to the remote peer (if possible a zero-copy strategy is applied).
288
    ///
289
    /// - Parameters:
290
    ///   - fd: The file descriptor of the file to send.
291
    ///   - offset: The offset in the file.
292
    ///   - count: The number of bytes to send.
293
    /// - Returns: The `IOResult` which indicates how much data could be send and if the operation returned before all could be send (because the socket is in non-blocking mode).
294
    /// - Throws: An `IOError` if the operation failed.
295
0
    func sendFile(fd: CInt, offset: Int, count: Int) throws -> IOResult<Int> {
296
0
        try withUnsafeHandle {
297
0
            try NIOBSDSocket.sendfile(
298
0
                socket: $0,
299
0
                fd: fd,
300
0
                offset: off_t(offset),
301
0
                len: off_t(count)
302
0
            )
303
0
        }
304
0
    }
305
306
    /// Receive `MMsgHdr`s.
307
    ///
308
    /// - Parameters:
309
    ///   - msgs: The pointer to the `MMsgHdr`s into which the received message will be stored.
310
    /// - Returns: The `IOResult` which indicates how many messages could be received and if the operation returned before all messages could be received (because the socket is in non-blocking mode).
311
    /// - Throws: An `IOError` if the operation failed.
312
0
    func recvmmsg(msgs: UnsafeMutableBufferPointer<MMsgHdr>) throws -> IOResult<Int> {
313
0
        try withUnsafeHandle {
314
0
            try NIOBSDSocket.recvmmsg(
315
0
                socket: $0,
316
0
                msgvec: msgs.baseAddress!,
317
0
                vlen: CUnsignedInt(msgs.count),
318
0
                flags: 0,
319
0
                timeout: nil
320
0
            )
321
0
        }
322
0
    }
323
324
    /// Send `MMsgHdr`s.
325
    ///
326
    /// - Parameters:
327
    ///   - msgs: The pointer to the `MMsgHdr`s which will be send.
328
    /// - Returns: The `IOResult` which indicates how many messages could be send and if the operation returned before all messages could be send (because the socket is in non-blocking mode).
329
    /// - Throws: An `IOError` if the operation failed.
330
0
    func sendmmsg(msgs: UnsafeMutableBufferPointer<MMsgHdr>) throws -> IOResult<Int> {
331
0
        try withUnsafeHandle {
332
0
            try NIOBSDSocket.sendmmsg(
333
0
                socket: $0,
334
0
                msgvec: msgs.baseAddress!,
335
0
                vlen: CUnsignedInt(msgs.count),
336
0
                flags: 0
337
0
            )
338
0
        }
339
0
    }
340
341
    /// Shutdown the socket.
342
    ///
343
    /// - Parameters:
344
    ///   - how: the mode of `Shutdown`.
345
    /// - Throws: An `IOError` if the operation failed.
346
0
    func shutdown(how: Shutdown) throws {
347
0
        try withUnsafeHandle {
348
0
            try NIOBSDSocket.shutdown(socket: $0, how: how)
349
0
        }
350
0
    }
351
352
    /// Sets the value for the 'UDP_SEGMENT' socket option.
353
0
    func setUDPSegmentSize(_ segmentSize: CInt) throws {
354
0
        try self.withUnsafeHandle {
355
0
            try NIOBSDSocket.setUDPSegmentSize(segmentSize, socket: $0)
356
0
        }
357
0
    }
358
359
    /// Returns the value of the 'UDP_SEGMENT' socket option.
360
0
    func getUDPSegmentSize() throws -> CInt {
361
0
        try self.withUnsafeHandle {
362
0
            try NIOBSDSocket.getUDPSegmentSize(socket: $0)
363
0
        }
364
0
    }
365
366
    /// Sets the value for the 'UDP_GRO' socket option.
367
0
    func setUDPReceiveOffload(_ enabled: Bool) throws {
368
0
        try self.withUnsafeHandle {
369
0
            try NIOBSDSocket.setUDPReceiveOffload(enabled, socket: $0)
370
0
        }
371
0
    }
372
373
    /// Returns the value of the 'UDP_GRO' socket option.
374
0
    func getUDPReceiveOffload() throws -> Bool {
375
0
        try self.withUnsafeHandle {
376
0
            try NIOBSDSocket.getUDPReceiveOffload(socket: $0)
377
0
        }
378
0
    }
379
}
380
#endif  // !os(WASI)