Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Sources/NIOCore/BSDSocketAPI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,13 @@ extension NIOBSDSocket.Option {
/// Control multicast time-to-live.
public static let ip_multicast_ttl: NIOBSDSocket.Option =
NIOBSDSocket.Option(rawValue: IP_MULTICAST_TTL)

/// The IPv4 layer generates an IP header when sending a packet
/// unless the ``ip_hdrincl`` socket option is enabled on the socket.
/// When it is enabled, the packet must contain an IP header. For
/// receiving, the IP header is always included in the packet.
public static let ip_hdrincl: NIOBSDSocket.Option =
NIOBSDSocket.Option(rawValue: IP_HDRINCL)
}

// IPv6 Options
Expand Down
5 changes: 5 additions & 0 deletions Sources/NIOCore/ChannelOption.swift
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,11 @@ public struct ChannelOptions {
public static let socketOption = { (name: NIOBSDSocket.Option) -> Types.SocketOption in
.init(level: .socket, name: name)
}

/// - seealso: `SocketOption`.
public static let ipOption = { (name: NIOBSDSocket.Option) -> Types.SocketOption in
.init(level: .ip, name: name)
}

/// - seealso: `SocketOption`.
public static let tcpOption = { (name: NIOBSDSocket.Option) -> Types.SocketOption in
Expand Down
8 changes: 8 additions & 0 deletions Sources/NIOPosix/BSDSocketAPICommon.swift
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,14 @@ extension NIOBSDSocket.SocketType {
internal static let stream: NIOBSDSocket.SocketType =
NIOBSDSocket.SocketType(rawValue: SOCK_STREAM)
#endif

#if os(Linux)
internal static let raw: NIOBSDSocket.SocketType =
NIOBSDSocket.SocketType(rawValue: CInt(SOCK_RAW.rawValue))
#else
internal static let raw: NIOBSDSocket.SocketType =
NIOBSDSocket.SocketType(rawValue: SOCK_RAW)
#endif
}

// IPv4 Options
Expand Down
197 changes: 197 additions & 0 deletions Sources/NIOPosix/RawSocketBootstrap.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2022 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

/// A `RawSocketBootstrap` is an easy way to interact with IP based protocols other then TCP and UDP.
///
/// Example:
///
/// ```swift
/// let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
/// defer {
/// try! group.syncShutdownGracefully()
/// }
/// let bootstrap = RawSocketBootstrap(group: group)
/// .channelInitializer { channel in
/// channel.pipeline.addHandler(MyChannelHandler())
/// }
/// let channel = try! bootstrap.bind(host: "127.0.0.1", ipProtocol: .icmp).wait()
/// /* the Channel is now ready to send/receive IP packets */
///
/// try channel.closeFuture.wait() // Wait until the channel un-binds.
/// ```
///
/// The `Channel` will operate on `AddressedEnvelope<ByteBuffer>` as inbound and outbound messages.
public final class NIORawSocketBootstrap {

private let group: EventLoopGroup
private var channelInitializer: Optional<ChannelInitializerCallback>
@usableFromInline
internal var _channelOptions: ChannelOptions.Storage

/// Create a `RawSocketBootstrap` on the `EventLoopGroup` `group`.
///
/// The `EventLoopGroup` `group` must be compatible, otherwise the program will crash. `RawSocketBootstrap` is
/// compatible only with `MultiThreadedEventLoopGroup` as well as the `EventLoop`s returned by
/// `MultiThreadedEventLoopGroup.next`. See `init(validatingGroup:)` for a fallible initializer for
/// situations where it's impossible to tell ahead of time if the `EventLoopGroup` is compatible or not.
///
/// - parameters:
/// - group: The `EventLoopGroup` to use.
public convenience init(group: EventLoopGroup) {
guard NIOOnSocketsBootstraps.isCompatible(group: group) else {
preconditionFailure("RawSocketBootstrap is only compatible with MultiThreadedEventLoopGroup and " +
"SelectableEventLoop. You tried constructing one with \(group) which is incompatible.")
}
self.init(validatingGroup: group)!
}

/// Create a `RawSocketBootstrap` on the `EventLoopGroup` `group`, validating that `group` is compatible.
///
/// - parameters:
/// - group: The `EventLoopGroup` to use.
public init?(validatingGroup group: EventLoopGroup) {
guard NIOOnSocketsBootstraps.isCompatible(group: group) else {
return nil
}
self._channelOptions = ChannelOptions.Storage()
self.group = group
self.channelInitializer = nil
}

/// Initialize the bound `Channel` with `initializer`. The most common task in initializer is to add
/// `ChannelHandler`s to the `ChannelPipeline`.
///
/// - parameters:
/// - handler: A closure that initializes the provided `Channel`.
public func channelInitializer(_ handler: @escaping @Sendable (Channel) -> EventLoopFuture<Void>) -> Self {
self.channelInitializer = handler
return self
}

/// Specifies a `ChannelOption` to be applied to the `Channel`.
///
/// - parameters:
/// - option: The option to be applied.
/// - value: The value for the option.
@inlinable
public func channelOption<Option: ChannelOption>(_ option: Option, value: Option.Value) -> Self {
self._channelOptions.append(key: option, value: value)
return self
}

/// Bind the `Channel` to `host`.
/// All packets or errors matching the `ipProtocol` specified are passed to the resulting `Channel`.
///
/// - parameters:
/// - host: The host to bind on.
/// - ipProtocol: The IP protocol used in the IP protocol/nextHeader field.
public func bind(host: String, ipProtocol: NIOIPProtocol) -> EventLoopFuture<Channel> {
return bind0(ipProtocol: ipProtocol) {
return try SocketAddress.makeAddressResolvingHost(host, port: 0)
}
}

private func bind0(ipProtocol: NIOIPProtocol, _ makeSocketAddress: () throws -> SocketAddress) -> EventLoopFuture<Channel> {
let address: SocketAddress
do {
address = try makeSocketAddress()
} catch {
return group.next().makeFailedFuture(error)
}
precondition(address.port == nil || address.port == 0, "port must be 0 or not set")
func makeChannel(_ eventLoop: SelectableEventLoop) throws -> DatagramChannel {
return try DatagramChannel(eventLoop: eventLoop,
protocolFamily: address.protocol,
protocolSubtype: .init(ipProtocol),
socketType: .raw)
}
return withNewChannel(makeChannel: makeChannel) { (eventLoop, channel) in
channel.register().flatMap {
channel.bind(to: address)
}
}
}

/// Connect the `Channel` to `host`.
///
/// - parameters:
/// - host: The host to connect to.
/// - ipProtocol: The IP protocol used in the IP protocol/nextHeader field.
public func connect(host: String, ipProtocol: NIOIPProtocol) -> EventLoopFuture<Channel> {
return connect0(ipProtocol: ipProtocol) {
return try SocketAddress.makeAddressResolvingHost(host, port: 0)
}
}

private func connect0(ipProtocol: NIOIPProtocol, _ makeSocketAddress: () throws -> SocketAddress) -> EventLoopFuture<Channel> {
let address: SocketAddress
do {
address = try makeSocketAddress()
} catch {
return group.next().makeFailedFuture(error)
}
func makeChannel(_ eventLoop: SelectableEventLoop) throws -> DatagramChannel {
return try DatagramChannel(eventLoop: eventLoop,
protocolFamily: address.protocol,
protocolSubtype: .init(ipProtocol),
socketType: .raw)
}
return withNewChannel(makeChannel: makeChannel) { (eventLoop, channel) in
channel.register().flatMap {
channel.connect(to: address)
}
}
}

private func withNewChannel(makeChannel: (_ eventLoop: SelectableEventLoop) throws -> DatagramChannel, _ bringup: @escaping (EventLoop, DatagramChannel) -> EventLoopFuture<Void>) -> EventLoopFuture<Channel> {
let eventLoop = self.group.next()
let channelInitializer = self.channelInitializer ?? { _ in eventLoop.makeSucceededFuture(()) }
let channelOptions = self._channelOptions

let channel: DatagramChannel
do {
channel = try makeChannel(eventLoop as! SelectableEventLoop)
} catch {
return eventLoop.makeFailedFuture(error)
}

func setupChannel() -> EventLoopFuture<Channel> {
eventLoop.assertInEventLoop()
return channelOptions.applyAllChannelOptions(to: channel).flatMap {
channelInitializer(channel)
}.flatMap {
eventLoop.assertInEventLoop()
return bringup(eventLoop, channel)
}.map {
channel
}.flatMapError { error in
eventLoop.makeFailedFuture(error)
}
}

if eventLoop.inEventLoop {
return setupChannel()
} else {
return eventLoop.flatSubmit {
setupChannel()
}
}
}
}

#if swift(>=5.6)
@available(*, unavailable)
extension NIORawSocketBootstrap: Sendable {}
#endif
2 changes: 1 addition & 1 deletion Sources/NIOPosix/Socket.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ typealias IOVector = iovec
/// - parameters:
/// - protocolFamily: The protocol family to use (usually `AF_INET6` or `AF_INET`).
/// - type: The type of the socket to create.
/// - protocolSubtype: The subtype of the protocol, corresponding to the `protocol`
/// - protocolSubtype: The subtype of the protocol, corresponding to the `protocolSubtype`
/// argument to the socket syscall. Defaults to 0.
/// - setNonBlocking: Set non-blocking mode on the socket.
/// - throws: An `IOError` if creation of the socket failed.
Expand Down
1 change: 1 addition & 0 deletions Tests/LinuxMain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ class LinuxMainRunner {
testCase(PendingDatagramWritesManagerTests.allTests),
testCase(PipeChannelTest.allTests),
testCase(PriorityQueueTest.allTests),
testCase(RawSocketBootstrapTests.allTests),
testCase(SALChannelTest.allTests),
testCase(SALEventLoopTests.allTests),
testCase(SNIHandlerTest.allTests),
Expand Down
4 changes: 2 additions & 2 deletions Tests/NIOPosixTests/DatagramChannelTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import NIOCore
@testable import NIOPosix
import XCTest

private extension Channel {
extension Channel {
func waitForDatagrams(count: Int) throws -> [AddressedEnvelope<ByteBuffer>] {
return try self.pipeline.context(name: "ByteReadRecorder").flatMap { context in
if let future = (context.handler as? DatagramReadRecorder<ByteBuffer>)?.notifyForDatagrams(count) {
Expand Down Expand Up @@ -47,7 +47,7 @@ private extension Channel {
/// A class that records datagrams received and forwards them on.
///
/// Used extensively in tests to validate messaging expectations.
private class DatagramReadRecorder<DataType>: ChannelInboundHandler {
final class DatagramReadRecorder<DataType>: ChannelInboundHandler {
typealias InboundIn = AddressedEnvelope<DataType>
typealias InboundOut = AddressedEnvelope<DataType>

Expand Down
Loading