What is SwiftNIO SSH?
mainlibssh2, providing the networking and cryptographic layers necessary to drive SSH sessions directly from within Swift services.repository·main·Indexed 19 days ago
https://github.com/apple/swift-nio-sshA programmatic implementation of the SSHv2 protocol built on SwiftNIO. It provides building blocks for developers to create custom SSH clients and servers, supporting session channels, direct and reverse TCP port forwarding, and password or public key authentication. It utilizes modern cryptographic primitives including Ed25519, ECDSA, AES-GCM, and x25519.
libssh2, providing the networking and cryptographic layers necessary to drive SSH sessions directly from within Swift services.SwiftNIO SSH provides a set of abstractions for managing SSH connections, port forwarding, user authentication, host key verification, and child channels. The library is organized into several functional areas:
NIOSSHHandler and configuring clients or servers via SSHClientConfiguration and SSHServerConfiguration.GlobalRequest and GlobalRequestDelegate.NIOSSHClientUserAuthenticationDelegate and NIOSSHServerUserAuthenticationDelegate.NIOSSHClientServerAuthenticationDelegate.SSHChannelType, SSHChildChannelOptions, and handling channel-specific events.NIOSSHPublicKey, NIOSSHPrivateKey, and NIOSSHSignature.NIOSSHError.SwiftNIO SSH is a programmatic implementation of the SSHv2 protocol, designed as a collection of building blocks rather than a ready-to-use client or server. It is more akin to libssh2 than openssh, allowing developers to drive SSH sessions directly from within Swift services.
Key Features:
session, directTCPIP, and forwardedTCPIP channel types.Minimum Swift Versions:
0.9.0 and newer: Swift 5.8+Remote port forwarding allows a client to request that the server listen on a specific address/port and forward inbound connections back to the client.
Clients use NIOSSHHandler.sendGlobalRequest to send global requests. Supported requests are:
GlobalRequest.TCPForwardingRequest.listen(host:port:): Tells the server to start listening.GlobalRequest.TCPForwardingRequest.cancel(host:port:): Tells the server to stop listening.Servers implement GlobalRequestDelegate to receive and respond to these requests. The method tcpForwardingRequest(_:handler:promise:) is invoked when a request is received. Once the server handles the request, the result is passed into the promise.
Forwarded channels are sent from the server to the client using the .forwardedTCPIP channel type.
All data transmitted over an SSH channel is encapsulated in the SSHChannelData structure. This structure handles both regular and extended data types via SSHChannelData.DataType:
.channel: Used for the vast majority of core data. In session channels, this represents standard input/output. In TCP forwarding channels, this represents the forwarded data..stdErr: Used in session channels to represent standard error.SwiftNIO SSH uses a multiplexed architecture. The core protocol logic is handled by a NIOSSHHandler (a SwiftNIO ChannelHandler) within a primary NIO Channel.
Because SSH is multiplexed, the connection is subdivided into multiple bidirectional communication channels. SwiftNIO SSH represents each SSH channel as a new, independent NIO Channel (a "child channel").
Channel Types:
session: Used for invoking programs or shells (the most common type).directTCPIP: Used for direct TCP port forwarding (client to server).forwardedTCPIP: Used for remote TCP port forwarding (server to client).Data Handling:
Channels communicate using SSHChannelData.
session channels: SSHChannelData/DataType/channel is used for stdin and stdout, while SSHChannelData/DataType/stdErr is used for stderr.SSHChannelData/DataType/channel is used for all forwarded data.Remote port forwarding (client asking the server to listen on a port) is handled via Global Requests.
Clients initiate these requests using NIOSSHHandler/sendTCPForwardingRequest(_:promise:). Supported requests include:
GlobalRequest/TCPForwardingRequest/listen(host:port:): Tells the server to listen on a specific host/port.GlobalRequest/TCPForwardingRequest/cancel(host:port:): Tells the server to stop listening.Servers must implement a GlobalRequestDelegate to respond to these requests. The primary method is:
tcpForwardingRequest(_:handler:promise:).
When a request is accepted, the server sends the resulting forwarded channels to the client using the SSHChannelType/forwardedTCPIP(_:) channel type.
SwiftNIO SSH supports the SSHv2 protocol with the following feature set:
shell and exec channel requests.password and public key authentication..directTCPIP channel type.SwiftNIO SSH implements the SSH protocol via the NIOSSHHandler, which is a SwiftNIO ChannelHandler. Because SSH is a multiplexed protocol, SwiftNIO SSH uses a "child channel" abstraction.
When a peer creates a new SSH channel, the library creates a new NIO Channel to represent that specific channel's traffic. This allows you to interact with different SSH streams (like a shell session or a port forward) using standard NIO Channel patterns. Events within a single child channel are strictly ordered, but events across different channels may be interleaved.
Supported channel types include:
session: Used for invoking programs or shells (the most common type).directTCPIP: Used for client-to-server TCP port forwarding.forwardedTCPIP: Used for server-to-client TCP port forwarding.┌ ─ NIO Channel ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐
│ ┌────────────────────────────────────────────────┐ │
│ │ NIOSSHHandler │ │
│ └────────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────┐ │
│ │ SSH Child Channel │ │
│ │ ┌────────────────────────────────────────┐ │ │
│ │ │ User Handler │ │ │
│ │ └────────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────────┘ │
└ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘The SSH protocol relies heavily on half-closure in child channels. By default, NIO Channels have half-closure disabled, which causes unexpected behavior in SwiftNIO SSH child channels.
Requirement: You must explicitly enable half-closure support for all child channels:
channel.setOption(ChannelOptions.allowRemoteHalfClosure, true)Behavior with half-closure enabled:
ChannelEvent.inputClosed.close(mode: .output).