swift-nio/Sources/NIOPosix/BSDSocketAPIPosix.swift

302 lines
12 KiB
Swift
Raw Normal View History

//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2020-2021 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
import NIOCore
#if os(Linux) || os(Android) || os(FreeBSD) || os(iOS) || os(macOS) || os(tvOS) || os(watchOS)
extension Shutdown {
internal var cValue: CInt {
switch self {
case .RD:
return CInt(Posix.SHUT_RD)
case .WR:
return CInt(Posix.SHUT_WR)
case .RDWR:
return CInt(Posix.SHUT_RDWR)
}
}
}
// MARK: Implementation of _BSDSocketProtocol for POSIX systems
extension NIOBSDSocket {
static func accept(socket s: NIOBSDSocket.Handle,
address addr: UnsafeMutablePointer<sockaddr>?,
address_len addrlen: UnsafeMutablePointer<socklen_t>?) throws -> NIOBSDSocket.Handle? {
return try Posix.accept(descriptor: s, addr: addr, len: addrlen)
}
static func bind(socket s: NIOBSDSocket.Handle,
address addr: UnsafePointer<sockaddr>,
address_len namelen: socklen_t) throws {
return try Posix.bind(descriptor: s, ptr: addr, bytes: Int(namelen))
}
static func close(socket s: NIOBSDSocket.Handle) throws {
return try Posix.close(descriptor: s)
}
static func connect(socket s: NIOBSDSocket.Handle,
address name: UnsafePointer<sockaddr>,
address_len namelen: socklen_t) throws -> Bool {
return try Posix.connect(descriptor: s, addr: name, size: namelen)
}
static func getpeername(socket s: NIOBSDSocket.Handle,
address name: UnsafeMutablePointer<sockaddr>,
address_len namelen: UnsafeMutablePointer<socklen_t>) throws {
return try Posix.getpeername(socket: s, address: name, addressLength: namelen)
}
static func getsockname(socket s: NIOBSDSocket.Handle,
address name: UnsafeMutablePointer<sockaddr>,
address_len namelen: UnsafeMutablePointer<socklen_t>) throws {
return try Posix.getsockname(socket: s, address: name, addressLength: namelen)
}
static func getsockopt(socket: NIOBSDSocket.Handle,
level: NIOBSDSocket.OptionLevel,
option_name optname: NIOBSDSocket.Option,
option_value optval: UnsafeMutableRawPointer,
option_len optlen: UnsafeMutablePointer<socklen_t>) throws {
return try Posix.getsockopt(socket: socket,
level: level.rawValue,
optionName: optname.rawValue,
optionValue: optval,
optionLen: optlen)
}
static func listen(socket s: NIOBSDSocket.Handle, backlog: CInt) throws {
return try Posix.listen(descriptor: s, backlog: backlog)
}
static func recv(socket s: NIOBSDSocket.Handle,
buffer buf: UnsafeMutableRawPointer,
length len: size_t) throws -> IOResult<size_t> {
return try Posix.read(descriptor: s, pointer: buf, size: len)
}
static func recvmsg(socket: NIOBSDSocket.Handle,
msgHdr: UnsafeMutablePointer<msghdr>, flags: CInt)
throws -> IOResult<size_t> {
return try Posix.recvmsg(descriptor: socket, msgHdr: msgHdr, flags: flags)
}
static func sendmsg(socket: NIOBSDSocket.Handle,
msgHdr: UnsafePointer<msghdr>, flags: CInt)
throws -> IOResult<size_t> {
return try Posix.sendmsg(descriptor: socket, msgHdr: msgHdr, flags: flags)
}
static func send(socket s: NIOBSDSocket.Handle,
buffer buf: UnsafeRawPointer,
length len: size_t) throws -> IOResult<size_t> {
return try Posix.write(descriptor: s, pointer: buf, size: len)
}
static func setsockopt(socket: NIOBSDSocket.Handle,
level: NIOBSDSocket.OptionLevel,
option_name optname: NIOBSDSocket.Option,
option_value optval: UnsafeRawPointer,
option_len optlen: socklen_t) throws {
return try Posix.setsockopt(socket: socket,
level: level.rawValue,
optionName: optname.rawValue,
optionValue: optval,
optionLen: optlen)
}
static func shutdown(socket: NIOBSDSocket.Handle, how: Shutdown) throws {
return try Posix.shutdown(descriptor: socket, how: how)
}
static func socket(domain af: NIOBSDSocket.ProtocolFamily,
type: NIOBSDSocket.SocketType,
protocolSubtype: NIOBSDSocket.ProtocolSubtype) throws -> NIOBSDSocket.Handle {
return try Posix.socket(domain: af, type: type, protocolSubtype: protocolSubtype)
}
static func recvmmsg(socket: NIOBSDSocket.Handle,
msgvec: UnsafeMutablePointer<MMsgHdr>,
vlen: CUnsignedInt,
flags: CInt,
timeout: UnsafeMutablePointer<timespec>?) throws -> IOResult<Int> {
return try Posix.recvmmsg(sockfd: socket,
msgvec: msgvec,
vlen: vlen,
flags: flags,
timeout: timeout)
}
static func sendmmsg(socket: NIOBSDSocket.Handle,
msgvec: UnsafeMutablePointer<MMsgHdr>,
vlen: CUnsignedInt,
flags: CInt) throws -> IOResult<Int> {
return try Posix.sendmmsg(sockfd: socket,
msgvec: msgvec,
vlen: vlen,
flags: flags)
}
// NOTE: this should return a `ssize_t`, however, that is not a standard
// type, and defining that type is difficult. Opt to return a `size_t`
// which is the same size, but is unsigned.
static func pread(socket: NIOBSDSocket.Handle,
pointer: UnsafeMutableRawPointer,
size: size_t,
offset: off_t) throws -> IOResult<size_t> {
return try Posix.pread(descriptor: socket,
pointer: pointer,
size: size,
offset: offset)
}
// NOTE: this should return a `ssize_t`, however, that is not a standard
// type, and defining that type is difficult. Opt to return a `size_t`
// which is the same size, but is unsigned.
static func pwrite(socket: NIOBSDSocket.Handle,
pointer: UnsafeRawPointer,
size: size_t,
offset: off_t) throws -> IOResult<size_t> {
return try Posix.pwrite(descriptor: socket, pointer: pointer, size: size, offset: offset)
}
static func poll(fds: UnsafeMutablePointer<pollfd>,
nfds: nfds_t,
timeout: CInt) throws -> CInt {
return try Posix.poll(fds: fds, nfds: nfds, timeout: timeout)
}
static func sendfile(socket s: NIOBSDSocket.Handle,
fd: CInt,
offset: off_t,
len: off_t) throws -> IOResult<Int> {
return try Posix.sendfile(descriptor: s, fd: fd, offset: offset, count: size_t(len))
}
static func setNonBlocking(socket: NIOBSDSocket.Handle) throws {
return try Posix.setNonBlocking(socket: socket)
}
NIO: implement `cleanupSocket(unixDomainSocketPath:)` for Windows (#1654) * NIO: implement `cleanupSocket(unixDomainSocketPath:)` for Windows This adds an initial implementation for the UDS clean up path on Windows. Unlike Unix, you cannot simply `stat` the destination to determine if it is a UDS endpoint. One must first create a file handle from the path. Assuming that we are able to create that, we should verify that the file that we are dealing with is a not a disk type as the subsequent checks will fail. Now, we can use the file handle to query the information of the file. This will allow us to determine if it is a reparse point. If not, it is impossible for it to be a UDS as a UDS endpoint is a reparse point with the tag IO_REPARSE_TAG_AF_UNIX. Once we know that we have a reparse point, we must query the kernel for information about it via the `DeviceIoControl` system call (perform an ioctl in Unix speak). The returned buffer's tag will identify if we have a UDS. For the next dance, we must now _close_ the handle as the handle being left open will cause the subsequent `DeleteFileW` to fail due to there being an open handle. Once we have cleaned up the file handle that we created, we can safely attempt to remove the file via `DeleteFileW`. This should hopefully be sufficiently robust from possible error scenarios for the removal of a UDS endpoint. * NIO: improve the UDS cleanup path on Windows Instead of doing the complex close-handle dance, instead prefer `SetFileInformationByHandle` which: 1. avoids a second UTF-8 -> UCS-2 conversion 2. avoids the need for the handle closing dance 3. avoids a third re-query of the handle 4. enables us to use "POSIX" deletion semantics (file is immediately removed from the namespace but data streams are left intact) This comes at the cost of being Windows 10 centric, which I find a reasonable trade-off as Windows 7 has been EOL'ed by Microsoft, and more importantly, Unix Domain Sockets are only available on Windows 10! Thanks to @adierking for the reminder about SetFileInformationByHandle!
2020-10-05 19:49:28 +08:00
static func cleanupUnixDomainSocket(atPath path: String) throws {
do {
var sb: stat = stat()
try withUnsafeMutablePointer(to: &sb) { sbPtr in
try Posix.stat(pathname: path, outStat: sbPtr)
}
// Only unlink the existing file if it is a socket
if sb.st_mode & S_IFSOCK == S_IFSOCK {
try Posix.unlink(pathname: path)
} else {
throw UnixDomainSocketPathWrongType()
}
} catch let err as IOError {
// If the filepath did not exist, we consider it cleaned up
if err.errnoCode == ENOENT {
return
}
throw err
}
}
}
#if os(iOS) || os(macOS) || os(tvOS) || os(watchOS)
import CNIODarwin
private let CMSG_FIRSTHDR = CNIODarwin_CMSG_FIRSTHDR
private let CMSG_NXTHDR = CNIODarwin_CMSG_NXTHDR
private let CMSG_DATA = CNIODarwin_CMSG_DATA
private let CMSG_DATA_MUTABLE = CNIODarwin_CMSG_DATA_MUTABLE
private let CMSG_SPACE = CNIODarwin_CMSG_SPACE
private let CMSG_LEN = CNIODarwin_CMSG_LEN
#else
import CNIOLinux
private let CMSG_FIRSTHDR = CNIOLinux_CMSG_FIRSTHDR
private let CMSG_NXTHDR = CNIOLinux_CMSG_NXTHDR
private let CMSG_DATA = CNIOLinux_CMSG_DATA
private let CMSG_DATA_MUTABLE = CNIOLinux_CMSG_DATA_MUTABLE
private let CMSG_SPACE = CNIOLinux_CMSG_SPACE
private let CMSG_LEN = CNIOLinux_CMSG_LEN
#endif
// MARK: _BSDSocketControlMessageProtocol implementation
extension NIOBSDSocketControlMessage {
static func firstHeader(inside msghdr: UnsafePointer<msghdr>)
-> UnsafeMutablePointer<cmsghdr>? {
return CMSG_FIRSTHDR(msghdr)
}
static func nextHeader(inside msghdr: UnsafeMutablePointer<msghdr>,
after: UnsafeMutablePointer<cmsghdr>)
-> UnsafeMutablePointer<cmsghdr>? {
return CMSG_NXTHDR(msghdr, after)
}
static func data(for header: UnsafePointer<cmsghdr>)
-> UnsafeRawBufferPointer? {
let data = CMSG_DATA(header)
let length =
size_t(header.pointee.cmsg_len) - NIOBSDSocketControlMessage.length(payloadSize: 0)
return UnsafeRawBufferPointer(start: data, count: Int(length))
}
static func data(for header: UnsafeMutablePointer<cmsghdr>)
-> UnsafeMutableRawBufferPointer? {
let data = CMSG_DATA_MUTABLE(header)
let length =
size_t(header.pointee.cmsg_len) - NIOBSDSocketControlMessage.length(payloadSize: 0)
return UnsafeMutableRawBufferPointer(start: data, count: Int(length))
}
static func length(payloadSize: size_t) -> size_t {
return CMSG_LEN(payloadSize)
}
static func space(payloadSize: size_t) -> size_t {
return CMSG_SPACE(payloadSize)
}
}
extension NIOBSDSocket {
static func setUDPSegmentSize(_ segmentSize: CInt, socket: NIOBSDSocket.Handle) throws {
#if os(Linux)
var segmentSize = segmentSize
try Self.setsockopt(socket: socket,
level: .udp,
option_name: .udp_segment,
option_value: &segmentSize,
option_len: socklen_t(MemoryLayout<CInt>.size))
#else
throw ChannelError.operationUnsupported
#endif
}
static func getUDPSegmentSize(socket: NIOBSDSocket.Handle) throws -> CInt {
#if os(Linux)
var segmentSize: CInt = 0
var optionLength = socklen_t(MemoryLayout<CInt>.size)
try withUnsafeMutablePointer(to: &segmentSize) { segmentSizeBytes in
try Self.getsockopt(socket: socket,
level: .udp,
option_name: .udp_segment,
option_value: segmentSizeBytes,
option_len: &optionLength)
}
return segmentSize
#else
throw ChannelError.operationUnsupported
#endif
}
}
#endif