ddn.net.ssh.session

SSH client session implementation.

Drives the SSH protocol over a caller-supplied transport: the identification exchange first, then the packet layer and key exchange phases. The session implements the public ddn.api.net.ssh.SshSession contract; pumping is explicit and every potentially-blocking step reports progress instead of blocking.

The session is single-threaded and non-reentrant, owns all protocol state for the connection, and references - but does not own - the transport.

Types 13

Session limits and defaults from the support matrix.

bytes = 1024UL * 1024 * 1024Rekey after this many bytes sent or received
packets = 1UL << 28Rekey after this many packets in one direction
intervalSeconds = 3600Rekey after this much time
identificationLineMax = 255Maximum identification line length including the terminator
preBannerLinesMax = 64Maximum lines before the identification string
preBannerBytesMax = 8192Maximum total pre-banner bytes
handshakeTimeoutSeconds = 30Default handshake deadline
writeQueueMax = 8 * 1024 * 1024Session write queue bound
stagingMax = 256 * 1024Maximum undecoded bytes staged between the transport and the packet layer; the packet layer re-validates its exact bounds
readBudgetPerPump = 128 * 1024Maximum transport bytes moved into staging per pump call
authAttemptsMax = 20Session-wide authentication attempt bound
keepAliveMaxUnanswered = 3Maximum keepalive requests allowed to stay unanswered

Configurable rekey thresholds (RFC 4253 section 9).

The defaults mirror SshRekeyLimits; every threshold applies to the bytes and packets moved under one installed direction-key set and resets at each NEWKEYS boundary. timeoutSeconds bounds a single rekey exchange against a stalled peer.

Fields
ulong bytesRekey after this many bytes sent or received under one key set
ulong packetsRekey after this many packets sent or received in one direction
long intervalSecondsRekey after this much time since the keys were installed
long timeoutSecondsFail the session when a rekey exchange takes longer than this
nameMax = 1024
instructionMax = 16384
promptMax = 1024
promptsMax = 64
answerMax = 4096
bannerMax = 16384
private enumSshKexPhase

Sub-phases of the key-exchange state.

sendKexInit
negotiate
awaitReply
awaitHostDecision
established
private structForwardBinding

SSH-2 client session over a caller-supplied transport. An established remote-forward listener binding.

Fields
string bindAddressBind address requested from the server.
ushort boundPortBound (possibly allocated) port.
private structPendingForwardAccept

An incoming forwarded channel waiting for its accept decision.

Fields
SshChannelImpl channelThe buffered, not-yet-accepted channel.
SshForwardedChannelInfo infoThe connection identity shown to the handler.

Global-request-operation implementation driven by session pumping.

Replies arrive in request order without identifiers; the session keeps the FIFO that pairs them.

Fields
private Kind kind_
private string bindAddress_
private ushort requestedPort_
private ushort boundPort_
private bool done_
private bool accepted_
private bool cancelled_
private SshError error_
Methods
Kind kind() @property constThe request flavour (package bookkeeping).
string bindAddress() @property constThe bind address sent on the wire.
ushort requestedPort() @property constThe requested bind port (0 when allocation was requested).
void completeGlobalReply(bool accepted, ushort allocatedPort)Completes the operation from the peer's reply.
void failLocal(SshError error)Completes the operation with a local failure before sending.
SshProgress progress() @property const
bool done() @property const
SshError error() @property const
bool accepted() @property const
ushort boundPort() @property const
void cancel()
Constructors
this(Kind kind, string bindAddress, ushort requestedPort)Constructs a global request operation.
Nested Templates
KindGlobal request flavour for reply bookkeeping.
Fields
private SshTransport transport_
private SshSessionState state_
private SshError sessionError_
private string ourIdentification_
private string peerIdentification_
private string peerSoftware_
private ubyte[] bannerBuffer_
private ubyte[] writeQueue_
private size_t writePos_
private size_t channelBacklogBytes_
private SshChannelImpl[] channels_
private size_t nextChannelPump_
private uint nextChannelId_
private SshAuthOperationImpl authOperation_
private SshGlobalRequestOp[] globalRequestQueue_
private SshForwardedChannelHandler forwardHandler_
private ForwardBinding[] activeForwards_
private PendingForwardAccept[] pendingForwardAccepts_
private string authUsername_
private bool serviceRequested_
private bool authServiceAccepted_
private uint authAttempts_
private ubyte[] pendingWire_
private SshKexPhase kexPhase_
private SshKexInit ourKexInit_
private SshKexInit peerKexInit_
private SshNegotiationResult negotiation_
private ubyte[32] exchangeHash_
private ubyte[] sharedSecretMpint_
private ubyte[32] sessionId_
private bool sessionIdValid_
private X25519 ecdh_
private ubyte[32] ecdhPublic_
private EcdhP256 ecdhP256_
private ubyte[] ecdhP256Public_
private DH dh_
private DHPrivateKey dhPrivate_
private ubyte[] dhPublic_
private SshHostKeyVerifier hostVerifier_
private SshHostKeyInfo hostKeyInfo_
private SshParsedPublicKey hostKeyParsed_
private SshParsedSignature hostSignature_
private bool ourKexValueSent_
private bool kexReplyAllowed_
private bool hostKeyChecked_
private bool newKeysSent_
private bool newKeysReceived_
private SshPacketReader clearReader_
private SshPacketWriter clearWriter_
private GcmPacketWriter gcmWriter_
private GcmPacketReader gcmReader_
private EtMPacketWriter etmWriter_
private EtMPacketReader etmReader_
private ChachaPolyPacketWriter chachaWriter_
private ChachaPolyPacketReader chachaReader_
private string serverSignatureAlgorithms_
private bool strictKexActive_
private bool initialKexDone_
private bool rekeying_
private bool rekeyRequested_
private bool ourKexInitSentThisRound_
private bool rekeyRoundFromPeer_
private ulong bytesSent_
private ulong bytesReceived_
private ulong packetsSent_
private ulong packetsReceived_
private SshRekeyConfig rekeyConfig_
private MonoTime rekeyStart_
private bool sawPreKexinitTraffic_
private bool discardNextKexPacket_
private uint extInfoCount_
private bool extInfoAfterNewKeysAllowed_
private bool extInfoRequiresAuthSuccess_
private Duration keepAliveInterval_
private MonoTime lastTraffic_
private MonoTime keepAliveSentAt_
private SshGlobalRequestOp keepAliveOp_
private uint keepAlivesUnanswered_
private CryptoRandom rng_
private MonoTime delegate() clock_
private MonoTime phaseStart_
private Duration handshakeTimeout_
private size_t preBannerLinesCount_
Methods
SshSessionImpl create(SshTransport transport, SshAlgorithmPreferences preferences = SshAlgorithmPreferences .recommended()) staticCreates a session over a transport.
SshSessionState state() @property const
SshError sessionError() @property const
void setAlgorithmPreferences(scope const SshAlgorithmPreferences prefs)Sets algorithm preferences before the handshake starts.
private void validateAlgorithmPreferences( scope const SshAlgorithmPreferences prefs) static
private void validateNames(string category, scope const string[] offered, scope const string[] supported) static
void setClock(MonoTime delegate() clock)Sets the monotonic clock used for handshake deadlines.
void setKeepAlive(Duration interval)Configures keepalive requests; the transport phase acts on them.
void setRekeyLimits(SshRekeyConfig config)Configures automatic rekey thresholds; non-positive members revert that threshold to the default.
SshProgress pump()Advances all session state machines by one step.
SshWaitState waitState() @property const
private bool nextDeadline(out MonoTime deadline) constComputes the nearest timer deadline the driver must observe.
private Duration keepAliveTimeout() constReturns: the configured keepalive timeout interval.
bool rekeying() @property const
void requestRekey()Requests a key re-exchange at the next legal opportunity.
string peerIdentification() @property const
void setHostKeyVerifier(SshHostKeyVerifier verifier)Installs the host-key verifier consulted during key exchange.
void setUsername(string username) @safe pureSets the user name presented during authentication.
SshAuthOperation authenticatePublicKey(SshSigner signer)Starts (or resumes) public-key authentication with an application-owned signer. While an attempt is pending the same operation is returned; protocol requests are never duplicated.
SshAuthOperation authenticatePassword(SshPasswordProvider provider)Starts (or resumes) password authentication with an application-owned credential provider. While an attempt is pending the same operation is returned; protocol requests are never duplicated.
SshAuthOperation authenticateKeyboardInteractive( SshKeyboardInteractiveProvider provider)Starts (or resumes) keyboard-interactive authentication with a non-blocking, application-owned challenge provider.
string username() const @safe pure nothrowReturns: the configured user name.
inout(ubyte[32]) sessionId() inout @safe pure nothrowReturns: the session identifier bytes for signed requests.
void queueAuthPacket(const(ubyte)[] payload) @trustedQueues an authentication packet; a null payload is a no-op used when the request awaits an asynchronous credential.
private bool pendingAuthMismatch(bool nullCredential)
private void startAuth(SshAuthOperationImpl operation)
SshChannelOpenOperation openSessionChannel( scope const SshChannelOpenOptions options = SshChannelOpenOptions .init)Opens a session channel for exec, shell, or subsystem requests.
SshChannelOpenOperation openDirectTcpIpChannel(string host, ushort port, string originatorAddress, ushort originatorPort, scope const SshChannelOpenOptions options = SshChannelOpenOptions .init)Opens a direct TCP/IP channel to a destination reachable from the server.
SshRemoteForwardOperation requestRemoteForward(string bindAddress, ushort bindPort)
SshGlobalRequestOperation cancelRemoteForward(string bindAddress, ushort bindPort)
size_t channelCount() @property const
void disconnect(ushort reason, string description)Disconnects: sends nothing until the packet layer exists, then flushes pending writes and closes the transport.
private SshProgress pumpKeyExchange()Drives the whole key-exchange phase: KEXINIT exchange, ECDH, host verification, NEWKEYS transitions, and extension info.
private void sendOurKexInit()
private void startKexExchange()Starts the negotiated key-exchange method.
private void sendKexExchangeInit()Sends the KEX init message of the negotiated method.
private void dispatchReadyPackets()Dispatches every packet the active reader has assembled.
private ubyte[] takePacket() @trusted
private void queuePacket(const(ubyte)[] payload)
private void queueClearPacket(const(ubyte)[] payload)
private SshProgress handleMessage(const(ubyte)[] payload)Handles one decrypted message payload.
private bool isKexMessage(ubyte message) static pure nothrow @safeReturns: whether a message belongs to key exchange.
private void handleDisconnect(SshWireReader reader) @safe
private void handlePeerKexInit(const(ubyte)[] payload)
private void handleKexReply(SshWireReader reader)Handles the KEX reply message of the negotiated method.
private void handleNewKeys()
private void beginRekey(bool fromPeer)Starts a rekey round, optionally triggered by the peer's KEXINIT.
private void checkRekeyLimits()Applies the configured rekey thresholds.
private void maybeFinishKeyExchange()Completes the exchange once both NEWKEYS directions happened.
private void enforceNegotiatedHostKey(string keyAlgorithm, string signatureAlgorithm)Binds the presented host key and signature to the negotiated host-key algorithm (RFC 4253 section 7.1).
private void wipeKexAgreements() @trustedErases the ephemeral key-exchange agreements.
private void wipeSharedSecret() @trustedErases the encoded shared secret after both directions derived their keys.
private void wipeBytes(ubyte[] bytes) static nothrow @nogc @trustedVolatile-wipes a secret byte array.
private void wipePacketProtection() nothrow @nogc @trustedWipes and releases every installed packet-protection object.
private void wipeWriteProtection() nothrow @nogc @trustedWipes and releases installed outbound packet protection.
private void wipeReadProtection() nothrow @nogc @trustedWipes and releases installed inbound packet protection.
private void cleanupSecrets() @trustedTerminal secret teardown: shared secrets, live agreement state, and any queued plaintext still holding credentials.
private void pumpKeepAlive()Drives the keepalive schedule.
private void handleExtInfo(SshWireReader reader)
private void installReadProtection()
private uint writeSequenceNumber() const
private uint readSequenceNumber() const
private bool isEtmCipher(string cipher) static pure nothrow @safeReturns: whether the cipher uses the encrypt-then-MAC layout.
private bool isChachaCipher(string cipher) static pure nothrow @safeReturns: whether the cipher is the OpenSSH AEAD construction.
private EtMMac etmMacFor(string macName) staticMaps an encrypt-then-MAC wire name to its implementation.
private size_t etmMacKeyLength(EtMMac mac) static pure nothrow @nogc @safeParams: mac = MAC selection.
private void sendNewKeys()
private size_t negotiatedCipherKeyLength(string cipher) static @safe pure
private SshChannelOpenOperation openChannel(SshChannelType type, scope const SshChannelOpenOptions options, SshDirectTcpIpTarget target = SshDirectTcpIpTarget.init)Opens a channel of the given type.
private SshChannelOpenOp failedOpen(SshError error) static
private SshGlobalRequestOp failedGlobal( SshGlobalRequestOp.Kind kind, string bindAddress, ushort bindPort, SshError error) static
private void queueGlobalForwardRequest(string name, string bindAddress, ushort bindPort)Queues a reply-bearing forwarding global request (RFC 4254 7.1).
private SshGlobalRequestOp takeGlobalRequest()Pops the oldest reply-bearing global request.
private bool isWildcardAddress(string address) static pure nothrow
private bool addressCompatible(string a, string b) static pure nothrow
private bool bindingMatches(string aAddress, ushort aPort, string bAddress, ushort bPort) static pure nothrow
private void refuseIncomingChannel(uint sender, uint reason, string description)Refuses a peer-opened channel with a typed failure reason (RFC 4254 section 5.1.1).
private void finalizeForwardDecision(SshChannelImpl channel, bool accept)Accepts or refuses an incoming forwarded channel once decided.
private void handleForwardedChannelOpen(uint sender, uint window, uint maxPacket, SshWireReader reader)Dispatches an incoming forwarded-tcpip open (RFC 4254 7.2) to the caller's policy. Unsolicited opens are refused before the handler is consulted.
private void pumpForwardedAccepts()Re-polls pending forward accept decisions once per pump.
void queueChannelPacket(const(ubyte)[] payload) @trustedQueues a channel message through the session packet path.
void channelConsumed(SshChannelImpl channel, size_t bytes)Records receive-window consumption by a channel read and sends WINDOW_ADJUST when the threshold is reached.
void grantReceiveCredit(SshChannelImpl channel)Sends an accumulated receive-window re-grant when the channel's pending consumption crossed the replenish threshold.
void channelProtocolViolation(string message)Reports a channel flow-control violation to the session.
bool canQueueChannelBytes(size_t n)Returns: whether a channel packet of n further bytes still fits into the session write-queue budget.
size_t channelBacklogRoom() constReturns: aggregate room available to channel application backlogs.
void reserveChannelBacklog(size_t bytes)Records bytes accepted into a channel-owned backlog.
void releaseChannelBacklog(size_t bytes)Records bytes moved out of a channel-owned backlog.
string serverSignatureAlgorithms() const @propertyReturns: the server's advertised server-sig-algs list, or an empty string when the server did not send EXT_INFO.
private void pumpChannels()Advances all open channels: flush write backlogs bounded by the remote windows, fair round-robin.
private void handleAuthenticationMessage(ubyte message, SshWireReader reader)Dispatches connection-protocol messages to their channels.
private void handleConnectionMessage(ubyte message, SshWireReader reader)
private SshChannelImpl findLocal(uint localId)
private void pumpAuthOperation()Drives the active authentication operation one step.
void ensureServiceRequested()Sends the ssh-userauth service request once, when authentication starts.
private void handleServiceAccept(SshWireReader reader) @trusted
private void handleUserauthFailure(SshWireReader reader) @trusted
private void handleUserauthSuccess(SshWireReader reader) @trusted
private void handleUserauthBanner(SshWireReader reader) @trusted
private void handleUserauthMethodSpecific(SshWireReader reader) @trusted
private SshProgress parseBanner()Parses buffered bytes for the peer identification string.
private LineEnd findLineEnd(const(ubyte)[] data) static
private bool lineStartsWithSsh(const(ubyte)[] line) static
private void enforceBannerBounds()
private void parseIdentificationLine(const(ubyte)[] line)Validates one identification line: protocol version, software version, character set, and length. Marks the session failed on any violation.
private void throwProtocol(string message)
private ptrdiff_t indexOfChar(string s, char needle, size_t start = 0) static pure nothrow
private void queueWrite(const(ubyte)[] data)Queues bytes for writing, enforcing the session queue bound.
private bool flushWrites()Flushes queued writes to the transport.
private bool pumpTransportReads()Reads available transport bytes into the phase buffer.
private void pumpPackets()Moves pending wire bytes into the active packet reader one byte at a time, dispatching completed packets between bytes so a NEWKEYS mid-batch switches readers exactly at the boundary.
private void failTransport(string message) @trustedMarks the session failed with a transport-flavoured message.
private bool writePending() const
private SshProgress fail(SshError error) @trustedMarks the session failed and returns the error progress.
private bool isTerminal(SshSessionState state) static pure nothrow
private bool established() const pure nothrow
Constructors
this(SshTransport transport, SshAlgorithmPreferences preferences = SshAlgorithmPreferences .recommended())Constructs a session over a transport.
Nested Templates
LineStatus
LineEnd
private classBannerException : Exception

Carries a typed identification failure out of the banner parser.

Fields
SshError error
Constructors

Concrete authentication operation driven by session pumping.

Implements the public SshAuthOperation contract plus internal callbacks invoked by the session's message dispatch.

Fields
SshSigner signer_
SshPasswordProvider passwordProvider_
private SshSessionImpl session_
private SshAuthPhase phase_
private SshError error_
private bool succeeded_
private bool partialSuccess_
private bool cancelled_
private bool passwordChangeRequired_
private string[] availableMethods_
private string banner_
private bool serviceAccepted_
private bool requestSent_
private bool wireRequestPending_
private bool publicKeyProbeAccepted_
private bool discovered_
private uint attempts_
private SshSignResult signResult_
private SshPasswordResult passwordResult_
private const(ubyte)[] pendingSignedData_
private SshKeyboardInteractiveChallenge keyboardChallenge_
private SshKeyboardInteractiveResult keyboardResult_
private uint keyboardRounds_
Methods
SshProgress progress() @property const
bool done() @property const
bool succeeded() @property const
SshError error() @property const
bool partialSuccess() @property const
SshAuthMethod[] availableMethods() @property const
string banner() @property const
bool passwordChangeRequired() @property const
void cancel()
bool waitingCredential() constReturns: whether the attempt currently waits for an application credential (password, prompt answer, or signer transport).
bool awaitingServerReply() constReturns: whether a sent request currently owns the next auth reply.
bool wireReplyOutstanding() constReturns: whether this operation still owns an untagged wire reply.
void retireWireReply()Retires the reply slot of a cancelled operation.
void failPending(SshError error)Fails the attempt from session-level teardown.
void pump()Advances one step: request the service, submit the method request, or poll asynchronous credentials.
void onServiceAccepted()Marks the ssh-userauth service as accepted.
void onFailure(string[] methods, bool partial)Handles a USERAUTH_FAILURE: record methods and finish the attempt, surfacing partial success distinctly.
void onSuccess()Handles USERAUTH_SUCCESS.
void onBanner(string text) @safeRecords a bounded authentication banner.
void onMethodSpecific(ref SshWireReader reader)Handles message 60 according to the active authentication method.
private void sendMethodRequest()
private void pollCredential()
private void onKeyboardChallenge(ref SshWireReader reader)
private void pollKeyboardProvider()
private void sendKeyboardAnswers()
private void clearKeyboardResult() @safe nothrow
private void failAuth(SshError error)
private string rejectionMessage() const @safe pure nothrow
private string signatureAlgorithmFor(string keyAlgorithm)
string selectRsaSignature(string offered) staticSelects the RSA SHA-2 signature algorithm for a server's advertised capability list.
private bool listsAlgorithm(string list, string candidate) static pure @safeWhether a comma-separated algorithm list names the candidate.
private bool validSignatureAlgorithm(string returned, string requested) static pure nothrow @safeWhether a signer's returned algorithm satisfies the request.
private bool verifySignerResult(scope const SshPublicKeyInfo keyInfo, scope const(ubyte)[] signedData, scope const SshSignResult result, string requestedAlgorithm)Verifies a signer result against the public key before transmission.
Constructors
this(SshSessionImpl session, SshAuthMethod method, Object credential)Constructs an operation for one authentication method.
private enumSshAuthPhase

Internal phases of an authentication attempt.

idle
waitCredential
private classFakeClockSession

Verifies the FakeClockSession helper advances deterministically.

Fields
private long ticks_
Methods
MonoTime now() @property const
void advance(Duration by)

Functions 1

private fnstring driveToEstablished(SshSessionImpl session, int maxPumps = 64)