swift-nio/Sources/NIO/SocketChannel.swift

851 lines
33 KiB
Swift

//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2017-2018 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
//
//===----------------------------------------------------------------------===//
private extension ByteBuffer {
mutating func withMutableWritePointer(body: (UnsafeMutableRawBufferPointer) throws -> IOResult<Int>) rethrows -> IOResult<Int> {
var singleResult: IOResult<Int>!
_ = try self.writeWithUnsafeMutableBytes { ptr in
let localWriteResult = try body(ptr)
singleResult = localWriteResult
switch localWriteResult {
case .processed(let written):
return written
case .wouldBlock(let written):
return written
}
}
return singleResult
}
}
/// A `Channel` for a client socket.
///
/// - note: All operations on `SocketChannel` are thread-safe.
final class SocketChannel: BaseSocketChannel<Socket> {
private var connectTimeout: TimeAmount? = nil
private var connectTimeoutScheduled: Scheduled<Void>?
private var allowRemoteHalfClosure: Bool = false
private var inputShutdown: Bool = false
private var outputShutdown: Bool = false
private let pendingWrites: PendingStreamWritesManager
// This is `Channel` API so must be thread-safe.
override public var isWritable: Bool {
return pendingWrites.isWritable
}
override var isOpen: Bool {
self.eventLoop.assertInEventLoop()
assert(super.isOpen == self.pendingWrites.isOpen)
return super.isOpen
}
init(eventLoop: SelectableEventLoop, protocolFamily: Int32) throws {
let socket = try Socket(protocolFamily: protocolFamily, type: Posix.SOCK_STREAM, setNonBlocking: true)
self.pendingWrites = PendingStreamWritesManager(iovecs: eventLoop.iovecs, storageRefs: eventLoop.storageRefs)
try super.init(socket: socket, eventLoop: eventLoop, recvAllocator: AdaptiveRecvByteBufferAllocator())
}
init(eventLoop: SelectableEventLoop, descriptor: CInt) throws {
let socket = try Socket(descriptor: descriptor, setNonBlocking: true)
self.pendingWrites = PendingStreamWritesManager(iovecs: eventLoop.iovecs, storageRefs: eventLoop.storageRefs)
try super.init(socket: socket, eventLoop: eventLoop, recvAllocator: AdaptiveRecvByteBufferAllocator())
}
deinit {
// We should never have any pending writes left as otherwise we may leak callbacks
assert(pendingWrites.isEmpty)
}
override func setOption0<T: ChannelOption>(option: T, value: T.OptionType) throws {
self.eventLoop.assertInEventLoop()
guard isOpen else {
throw ChannelError.ioOnClosedChannel
}
switch option {
case _ as ConnectTimeoutOption:
connectTimeout = value as? TimeAmount
case _ as AllowRemoteHalfClosureOption:
allowRemoteHalfClosure = value as! Bool
case _ as WriteSpinOption:
pendingWrites.writeSpinCount = value as! UInt
case _ as WriteBufferWaterMarkOption:
pendingWrites.waterMark = value as! WriteBufferWaterMark
default:
try super.setOption0(option: option, value: value)
}
}
override func getOption0<T: ChannelOption>(option: T) throws -> T.OptionType {
self.eventLoop.assertInEventLoop()
guard isOpen else {
throw ChannelError.ioOnClosedChannel
}
switch option {
case _ as ConnectTimeoutOption:
return connectTimeout as! T.OptionType
case _ as AllowRemoteHalfClosureOption:
return allowRemoteHalfClosure as! T.OptionType
case _ as WriteSpinOption:
return pendingWrites.writeSpinCount as! T.OptionType
case _ as WriteBufferWaterMarkOption:
return pendingWrites.waterMark as! T.OptionType
default:
return try super.getOption0(option: option)
}
}
override func registrationFor(interested: SelectorEventSet) -> NIORegistration {
return .socketChannel(self, interested)
}
init(socket: Socket, parent: Channel? = nil, eventLoop: SelectableEventLoop) throws {
self.pendingWrites = PendingStreamWritesManager(iovecs: eventLoop.iovecs, storageRefs: eventLoop.storageRefs)
try super.init(socket: socket, parent: parent, eventLoop: eventLoop, recvAllocator: AdaptiveRecvByteBufferAllocator())
}
override func readFromSocket() throws -> ReadResult {
self.eventLoop.assertInEventLoop()
// Just allocate one time for the while read loop. This is fine as ByteBuffer is a struct and uses COW.
var buffer = recvAllocator.buffer(allocator: allocator)
var result = ReadResult.none
for i in 1...maxMessagesPerRead {
guard self.isOpen && !self.inputShutdown else {
throw ChannelError.eof
}
// Reset reader and writerIndex and so allow to have the buffer filled again. This is better here than at
// the end of the loop to not do an allocation when the loop exits.
buffer.clear()
switch try buffer.withMutableWritePointer(body: self.socket.read(pointer:)) {
case .processed(let bytesRead):
if bytesRead > 0 {
let mayGrow = recvAllocator.record(actualReadBytes: bytesRead)
readPending = false
assert(self.isActive)
pipeline.fireChannelRead0(NIOAny(buffer))
result = .some
if buffer.writableBytes > 0 {
// If we did not fill the whole buffer with read(...) we should stop reading and wait until we get notified again.
// Otherwise chances are good that the next read(...) call will either read nothing or only a very small amount of data.
// Also this will allow us to call fireChannelReadComplete() which may give the user the chance to flush out all pending
// writes.
return result
} else if mayGrow && i < maxMessagesPerRead {
// if the ByteBuffer may grow on the next allocation due we used all the writable bytes we should allocate a new `ByteBuffer` to allow ramping up how much data
// we are able to read on the next read operation.
buffer = recvAllocator.buffer(allocator: allocator)
}
} else {
if inputShutdown {
// We received a EOF because we called shutdown on the fd by ourself, unregister from the Selector and return
readPending = false
unregisterForReadable()
return result
}
// end-of-file
throw ChannelError.eof
}
case .wouldBlock(let bytesRead):
assert(bytesRead == 0)
return result
}
}
return result
}
override func writeToSocket() throws -> OverallWriteResult {
let result = try self.pendingWrites.triggerAppropriateWriteOperations(scalarBufferWriteOperation: { ptr in
guard ptr.count > 0 else {
// No need to call write if the buffer is empty.
return .processed(0)
}
// normal write
return try self.socket.write(pointer: ptr)
}, vectorBufferWriteOperation: { ptrs in
// Gathering write
try self.socket.writev(iovecs: ptrs)
}, scalarFileWriteOperation: { descriptor, index, endIndex in
try self.socket.sendFile(fd: descriptor, offset: index, count: endIndex - index)
})
if result.writable {
// writable again
self.pipeline.fireChannelWritabilityChanged0()
}
return result.writeResult
}
override func connectSocket(to address: SocketAddress) throws -> Bool {
if try self.socket.connect(to: address) {
return true
}
if let timeout = connectTimeout {
connectTimeoutScheduled = eventLoop.scheduleTask(in: timeout) { () -> Void in
if self.pendingConnect != nil {
// The connection was still not established, close the Channel which will also fail the pending promise.
self.close0(error: ChannelError.connectTimeout(timeout), mode: .all, promise: nil)
}
}
}
return false
}
override func finishConnectSocket() throws {
if let scheduled = self.connectTimeoutScheduled {
// Connection established so cancel the previous scheduled timeout.
self.connectTimeoutScheduled = nil
scheduled.cancel()
}
try self.socket.finishConnect()
}
override func close0(error: Error, mode: CloseMode, promise: EventLoopPromise<Void>?) {
do {
switch mode {
case .output:
if outputShutdown {
promise?.fail(ChannelError.outputClosed)
return
}
try socket.shutdown(how: .WR)
outputShutdown = true
// Fail all pending writes and so ensure all pending promises are notified
pendingWrites.failAll(error: error, close: false)
unregisterForWritable()
promise?.succeed(())
pipeline.fireUserInboundEventTriggered(ChannelEvent.outputClosed)
case .input:
if inputShutdown {
promise?.fail(ChannelError.inputClosed)
return
}
switch error {
case ChannelError.eof:
// No need to explicit call socket.shutdown(...) as we received an EOF and the call would only cause
// ENOTCON
break
default:
try socket.shutdown(how: .RD)
}
inputShutdown = true
unregisterForReadable()
promise?.succeed(())
pipeline.fireUserInboundEventTriggered(ChannelEvent.inputClosed)
case .all:
if let timeout = connectTimeoutScheduled {
connectTimeoutScheduled = nil
timeout.cancel()
}
super.close0(error: error, mode: mode, promise: promise)
}
} catch let err {
promise?.fail(err)
}
}
override func markFlushPoint() {
// Even if writable() will be called later by the EventLoop we still need to mark the flush checkpoint so we are sure all the flushed messages
// are actually written once writable() is called.
self.pendingWrites.markFlushCheckpoint()
}
override func cancelWritesOnClose(error: Error) {
self.pendingWrites.failAll(error: error, close: true)
}
@discardableResult override func readIfNeeded0() -> Bool {
if inputShutdown {
return false
}
return super.readIfNeeded0()
}
override public func read0() {
if inputShutdown {
return
}
super.read0()
}
override func bufferPendingWrite(data: NIOAny, promise: EventLoopPromise<Void>?) {
if outputShutdown {
promise?.fail(ChannelError.outputClosed)
return
}
guard let data = data.tryAsIOData() else {
promise?.fail(ChannelError.writeDataUnsupported)
return
}
if !self.pendingWrites.add(data: data, promise: promise) {
pipeline.fireChannelWritabilityChanged0()
}
}
}
/// A `Channel` for a server socket.
///
/// - note: All operations on `ServerSocketChannel` are thread-safe.
final class ServerSocketChannel: BaseSocketChannel<ServerSocket> {
private var backlog: Int32 = 128
private let group: EventLoopGroup
/// The server socket channel is never writable.
// This is `Channel` API so must be thread-safe.
override public var isWritable: Bool { return false }
convenience init(eventLoop: SelectableEventLoop, group: EventLoopGroup, protocolFamily: Int32) throws {
try self.init(serverSocket: try ServerSocket(protocolFamily: protocolFamily, setNonBlocking: true), eventLoop: eventLoop, group: group)
}
init(serverSocket: ServerSocket, eventLoop: SelectableEventLoop, group: EventLoopGroup) throws {
self.group = group
try super.init(socket: serverSocket, eventLoop: eventLoop, recvAllocator: AdaptiveRecvByteBufferAllocator())
}
convenience init(descriptor: CInt, eventLoop: SelectableEventLoop, group: EventLoopGroup) throws {
let socket = try ServerSocket(descriptor: descriptor, setNonBlocking: true)
try self.init(serverSocket: socket, eventLoop: eventLoop, group: group)
try self.socket.listen(backlog: backlog)
}
override func registrationFor(interested: SelectorEventSet) -> NIORegistration {
return .serverSocketChannel(self, interested)
}
override func setOption0<T: ChannelOption>(option: T, value: T.OptionType) throws {
self.eventLoop.assertInEventLoop()
guard isOpen else {
throw ChannelError.ioOnClosedChannel
}
switch option {
case _ as BacklogOption:
backlog = value as! Int32
default:
try super.setOption0(option: option, value: value)
}
}
override func getOption0<T: ChannelOption>(option: T) throws -> T.OptionType {
self.eventLoop.assertInEventLoop()
guard isOpen else {
throw ChannelError.ioOnClosedChannel
}
switch option {
case _ as BacklogOption:
return backlog as! T.OptionType
default:
return try super.getOption0(option: option)
}
}
override public func bind0(to address: SocketAddress, promise: EventLoopPromise<Void>?) {
self.eventLoop.assertInEventLoop()
guard self.isOpen else {
promise?.fail(ChannelError.ioOnClosedChannel)
return
}
guard self.isRegistered else {
promise?.fail(ChannelError.inappropriateOperationForState)
return
}
let p = eventLoop.makePromise(of: Void.self)
p.futureResult.map {
// It's important to call the methods before we actually notify the original promise for ordering reasons.
self.becomeActive0(promise: promise)
}.whenFailure{ error in
promise?.fail(error)
}
executeAndComplete(p) {
try socket.bind(to: address)
self.updateCachedAddressesFromSocket(updateRemote: false)
try self.socket.listen(backlog: backlog)
}
}
override func connectSocket(to address: SocketAddress) throws -> Bool {
throw ChannelError.operationUnsupported
}
override func finishConnectSocket() throws {
throw ChannelError.operationUnsupported
}
override func readFromSocket() throws -> ReadResult {
var result = ReadResult.none
for _ in 1...maxMessagesPerRead {
guard self.isOpen else {
throw ChannelError.eof
}
if let accepted = try self.socket.accept(setNonBlocking: true) {
readPending = false
result = .some
do {
let chan = try SocketChannel(socket: accepted, parent: self, eventLoop: group.next() as! SelectableEventLoop)
assert(self.isActive)
pipeline.fireChannelRead0(NIOAny(chan))
} catch let err {
try? accepted.close()
throw err
}
} else {
break
}
}
return result
}
override func shouldCloseOnReadError(_ err: Error) -> Bool {
guard let err = err as? IOError else { return true }
switch err.errnoCode {
case ECONNABORTED,
EMFILE,
ENFILE,
ENOBUFS,
ENOMEM:
// These are errors we may be able to recover from. The user may just want to stop accepting connections for example
// or provide some other means of back-pressure. This could be achieved by a custom ChannelDuplexHandler.
return false
default:
return true
}
}
override func cancelWritesOnClose(error: Error) {
// No writes to cancel.
return
}
override public func channelRead0(_ data: NIOAny) {
self.eventLoop.assertInEventLoop()
let ch = data.forceAsOther() as SocketChannel
ch.eventLoop.execute {
ch.register().flatMapThrowing {
guard ch.isOpen else {
throw ChannelError.ioOnClosedChannel
}
ch.becomeActive0(promise: nil)
}.whenFailure { error in
ch.close(promise: nil)
}
}
}
override func bufferPendingWrite(data: NIOAny, promise: EventLoopPromise<Void>?) {
promise?.fail(ChannelError.operationUnsupported)
}
override func markFlushPoint() {
// We do nothing here: flushes are no-ops.
}
override func flushNow() -> IONotificationState {
return IONotificationState.unregister
}
}
/// A channel used with datagram sockets.
///
/// Currently this channel is in an early stage. It supports only unconnected
/// datagram sockets, and does not currently support either multicast or
/// broadcast send or receive.
///
/// The following features are well worth adding:
///
/// - Multicast support
/// - Broadcast support
/// - Connected mode
final class DatagramChannel: BaseSocketChannel<Socket> {
// Guard against re-entrance of flushNow() method.
private let pendingWrites: PendingDatagramWritesManager
// This is `Channel` API so must be thread-safe.
override public var isWritable: Bool {
return pendingWrites.isWritable
}
override var isOpen: Bool {
self.eventLoop.assertInEventLoop()
assert(super.isOpen == self.pendingWrites.isOpen)
return super.isOpen
}
convenience init(eventLoop: SelectableEventLoop, descriptor: CInt) throws {
let socket = Socket(descriptor: descriptor)
do {
try self.init(socket: socket, eventLoop: eventLoop)
} catch {
try? socket.close()
throw error
}
}
init(eventLoop: SelectableEventLoop, protocolFamily: Int32) throws {
let socket = try Socket(protocolFamily: protocolFamily, type: Posix.SOCK_DGRAM)
do {
try socket.setNonBlocking()
} catch let err {
try? socket.close()
throw err
}
self.pendingWrites = PendingDatagramWritesManager(msgs: eventLoop.msgs,
iovecs: eventLoop.iovecs,
addresses: eventLoop.addresses,
storageRefs: eventLoop.storageRefs)
try super.init(socket: socket, eventLoop: eventLoop, recvAllocator: FixedSizeRecvByteBufferAllocator(capacity: 2048))
}
init(socket: Socket, parent: Channel? = nil, eventLoop: SelectableEventLoop) throws {
try socket.setNonBlocking()
self.pendingWrites = PendingDatagramWritesManager(msgs: eventLoop.msgs,
iovecs: eventLoop.iovecs,
addresses: eventLoop.addresses,
storageRefs: eventLoop.storageRefs)
try super.init(socket: socket, parent: parent, eventLoop: eventLoop, recvAllocator: FixedSizeRecvByteBufferAllocator(capacity: 2048))
}
// MARK: Datagram Channel overrides required by BaseSocketChannel
override func setOption0<T: ChannelOption>(option: T, value: T.OptionType) throws {
self.eventLoop.assertInEventLoop()
guard isOpen else {
throw ChannelError.ioOnClosedChannel
}
switch option {
case _ as WriteSpinOption:
pendingWrites.writeSpinCount = value as! UInt
case _ as WriteBufferWaterMarkOption:
pendingWrites.waterMark = value as! WriteBufferWaterMark
default:
try super.setOption0(option: option, value: value)
}
}
override func getOption0<T: ChannelOption>(option: T) throws -> T.OptionType {
self.eventLoop.assertInEventLoop()
guard isOpen else {
throw ChannelError.ioOnClosedChannel
}
switch option {
case _ as WriteSpinOption:
return pendingWrites.writeSpinCount as! T.OptionType
case _ as WriteBufferWaterMarkOption:
return pendingWrites.waterMark as! T.OptionType
default:
return try super.getOption0(option: option)
}
}
override func registrationFor(interested: SelectorEventSet) -> NIORegistration {
return .datagramChannel(self, interested)
}
override func connectSocket(to address: SocketAddress) throws -> Bool {
// For now we don't support operating in connected mode for datagram channels.
throw ChannelError.operationUnsupported
}
override func finishConnectSocket() throws {
// For now we don't support operating in connected mode for datagram channels.
throw ChannelError.operationUnsupported
}
override func readFromSocket() throws -> ReadResult {
var rawAddress = sockaddr_storage()
var rawAddressLength = socklen_t(MemoryLayout<sockaddr_storage>.size)
var buffer = self.recvAllocator.buffer(allocator: self.allocator)
var readResult = ReadResult.none
for i in 1...self.maxMessagesPerRead {
guard self.isOpen else {
throw ChannelError.eof
}
buffer.clear()
let result = try buffer.withMutableWritePointer {
try self.socket.recvfrom(pointer: $0, storage: &rawAddress, storageLen: &rawAddressLength)
}
switch result {
case .processed(let bytesRead):
assert(bytesRead > 0)
assert(self.isOpen)
let mayGrow = recvAllocator.record(actualReadBytes: bytesRead)
readPending = false
let msg = AddressedEnvelope(remoteAddress: rawAddress.convert(), data: buffer)
assert(self.isActive)
pipeline.fireChannelRead0(NIOAny(msg))
if mayGrow && i < maxMessagesPerRead {
buffer = recvAllocator.buffer(allocator: allocator)
}
readResult = .some
case .wouldBlock(let bytesRead):
assert(bytesRead == 0)
return readResult
}
}
return readResult
}
override func shouldCloseOnReadError(_ err: Error) -> Bool {
guard let err = err as? IOError else { return true }
switch err.errnoCode {
// ECONNREFUSED can happen on linux if the previous sendto(...) failed.
// See also:
// - https://bugzilla.redhat.com/show_bug.cgi?id=1375
// - https://lists.gt.net/linux/kernel/39575
case ECONNREFUSED,
ENOMEM:
// These are errors we may be able to recover from.
return false
default:
return true
}
}
/// Buffer a write in preparation for a flush.
override func bufferPendingWrite(data: NIOAny, promise: EventLoopPromise<Void>?) {
guard let data = data.tryAsByteEnvelope() else {
promise?.fail(ChannelError.writeDataUnsupported)
return
}
if !self.pendingWrites.add(envelope: data, promise: promise) {
assert(self.isActive)
pipeline.fireChannelWritabilityChanged0()
}
}
/// Mark a flush point. This is called when flush is received, and instructs
/// the implementation to record the flush.
override func markFlushPoint() {
// Even if writable() will be called later by the EventLoop we still need to mark the flush checkpoint so we are sure all the flushed messages
// are actually written once writable() is called.
self.pendingWrites.markFlushCheckpoint()
}
/// Called when closing, to instruct the specific implementation to discard all pending
/// writes.
override func cancelWritesOnClose(error: Error) {
self.pendingWrites.failAll(error: error, close: true)
}
override func writeToSocket() throws -> OverallWriteResult {
let result = try self.pendingWrites.triggerAppropriateWriteOperations(scalarWriteOperation: { (ptr, destinationPtr, destinationSize) in
guard ptr.count > 0 else {
// No need to call write if the buffer is empty.
return .processed(0)
}
// normal write
return try self.socket.sendto(pointer: ptr,
destinationPtr: destinationPtr,
destinationSize: destinationSize)
}, vectorWriteOperation: { msgs in
try self.socket.sendmmsg(msgs: msgs)
})
if result.writable {
// writable again
assert(self.isActive)
self.pipeline.fireChannelWritabilityChanged0()
}
return result.writeResult
}
// MARK: Datagram Channel overrides not required by BaseSocketChannel
override func bind0(to address: SocketAddress, promise: EventLoopPromise<Void>?) {
self.eventLoop.assertInEventLoop()
guard self.isRegistered else {
promise?.fail(ChannelError.inappropriateOperationForState)
return
}
do {
try socket.bind(to: address)
self.updateCachedAddressesFromSocket(updateRemote: false)
becomeActive0(promise: promise)
} catch let err {
promise?.fail(err)
}
}
}
extension SocketChannel: CustomStringConvertible {
var description: String {
return "SocketChannel { selectable = \(self.selectable), localAddress = \(self.localAddress.debugDescription), remoteAddress = \(self.remoteAddress.debugDescription) }"
}
}
extension ServerSocketChannel: CustomStringConvertible {
var description: String {
return "ServerSocketChannel { selectable = \(self.selectable), localAddress = \(self.localAddress.debugDescription), remoteAddress = \(self.remoteAddress.debugDescription) }"
}
}
extension DatagramChannel: CustomStringConvertible {
var description: String {
return "DatagramChannel { selectable = \(self.selectable), localAddress = \(self.localAddress.debugDescription), remoteAddress = \(self.remoteAddress.debugDescription) }"
}
}
extension DatagramChannel: MulticastChannel {
/// The socket options for joining and leaving multicast groups are very similar.
/// This enum allows us to write a single function to do all the work, and then
/// at the last second pull out the correct socket option name.
private enum GroupOperation {
/// Join a multicast group.
case join
/// Leave a multicast group.
case leave
/// Given a socket option level, returns the appropriate socket option name for
/// this group operation.
///
/// - parameters:
/// - level: The socket option level. Must be one of `IPPROTO_IP` or
/// `IPPROTO_IPV6`. Will trap if an invalid value is provided.
/// - returns: The socket option name to use for this group operation.
func optionName(level: CInt) -> CInt {
switch (self, level) {
case (.join, CInt(IPPROTO_IP)):
return CInt(IP_ADD_MEMBERSHIP)
case (.leave, CInt(IPPROTO_IP)):
return CInt(IP_DROP_MEMBERSHIP)
case (.join, CInt(IPPROTO_IPV6)):
return CInt(IPV6_JOIN_GROUP)
case (.leave, CInt(IPPROTO_IPV6)):
return CInt(IPV6_LEAVE_GROUP)
default:
preconditionFailure("Unexpected socket option level: \(level)")
}
}
}
public func joinGroup(_ group: SocketAddress, interface: NIONetworkInterface?, promise: EventLoopPromise<Void>?) {
if eventLoop.inEventLoop {
self.performGroupOperation0(group, interface: interface, promise: promise, operation: .join)
} else {
eventLoop.execute {
self.performGroupOperation0(group, interface: interface, promise: promise, operation: .join)
}
}
}
public func leaveGroup(_ group: SocketAddress, interface: NIONetworkInterface?, promise: EventLoopPromise<Void>?) {
if eventLoop.inEventLoop {
self.performGroupOperation0(group, interface: interface, promise: promise, operation: .leave)
} else {
eventLoop.execute {
self.performGroupOperation0(group, interface: interface, promise: promise, operation: .leave)
}
}
}
/// The implementation of `joinGroup` and `leaveGroup`.
///
/// Joining and leaving a multicast group ultimately corresponds to a single, carefully crafted, socket option.
private func performGroupOperation0(_ group: SocketAddress,
interface: NIONetworkInterface?,
promise: EventLoopPromise<Void>?,
operation: GroupOperation) {
self.eventLoop.assertInEventLoop()
guard self.isActive else {
promise?.fail(ChannelError.inappropriateOperationForState)
return
}
// We need to check that we have the appropriate address types in all cases. They all need to overlap with
// the address type of this channel, or this cannot work.
guard let localAddress = self.localAddress else {
promise?.fail(ChannelError.unknownLocalAddress)
return
}
guard localAddress.protocolFamily == group.protocolFamily else {
promise?.fail(ChannelError.badMulticastGroupAddressFamily)
return
}
// Ok, now we need to check that the group we've been asked to join is actually a multicast group.
guard group.isMulticast else {
promise?.fail(ChannelError.illegalMulticastAddress(group))
return
}
// Ok, we now have reason to believe this will actually work. We need to pass this on to the socket.
do {
switch (group, interface?.address) {
case (.unixDomainSocket, _):
preconditionFailure("Should not be reachable, UNIX sockets are never multicast addresses")
case (.v4(let groupAddress), .some(.v4(let interfaceAddress))):
// IPv4Binding with specific target interface.
let multicastRequest = ip_mreq(imr_multiaddr: groupAddress.address.sin_addr, imr_interface: interfaceAddress.address.sin_addr)
try self.socket.setOption(level: CInt(IPPROTO_IP), name: operation.optionName(level: CInt(IPPROTO_IP)), value: multicastRequest)
case (.v4(let groupAddress), .none):
// IPv4 binding without target interface.
let multicastRequest = ip_mreq(imr_multiaddr: groupAddress.address.sin_addr, imr_interface: in_addr(s_addr: INADDR_ANY))
try self.socket.setOption(level: CInt(IPPROTO_IP), name: operation.optionName(level: CInt(IPPROTO_IP)), value: multicastRequest)
case (.v6(let groupAddress), .some(.v6)):
// IPv6 binding with specific target interface.
let multicastRequest = ipv6_mreq(ipv6mr_multiaddr: groupAddress.address.sin6_addr, ipv6mr_interface: UInt32(interface!.interfaceIndex))
try self.socket.setOption(level: CInt(IPPROTO_IPV6), name: operation.optionName(level: CInt(IPPROTO_IPV6)), value: multicastRequest)
case (.v6(let groupAddress), .none):
// IPv6 binding with no specific interface requested.
let multicastRequest = ipv6_mreq(ipv6mr_multiaddr: groupAddress.address.sin6_addr, ipv6mr_interface: 0)
try self.socket.setOption(level: CInt(IPPROTO_IPV6), name: operation.optionName(level: CInt(IPPROTO_IPV6)), value: multicastRequest)
case (.v4, .some(.v6)), (.v6, .some(.v4)), (.v4, .some(.unixDomainSocket)), (.v6, .some(.unixDomainSocket)):
// Mismatched group and interface address: this is an error.
throw ChannelError.badInterfaceAddressFamily
}
promise?.succeed(())
} catch {
promise?.fail(error)
return
}
}
}