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