An SSH session progresses through distinct states from creation to disposal. Understanding these states is essential for proper session management and avoiding errors.
Session States
The SshSession has four possible states:
State
Description
What You Can Do
Disconnected
Initial state, no connection
Call Connect() or ConnectAsync()
Connected
TCP connected, SSH handshake complete
Call Authenticate() or disconnect
LoggedIn
Authenticated and ready to use
Execute commands, transfer files, configure settings
Disposed
Session terminated, resources released
Nothing - create a new session
State Transitions
Checking Current State
Access the session's current state via the ConnectionStatus property:
var session = new SshSession();
Console.WriteLine($"Current state: {session.ConnectionStatus}");
// Output: Current state: Disconnected
session.Connect("example.com", 22);
Console.WriteLine($"Current state: {session.ConnectionStatus}");
// Output: Current state: Connected
session.Authenticate(credential);
Console.WriteLine($"Current state: {session.ConnectionStatus}");
// Output: Current state: LoggedIn
session.Dispose();
Console.WriteLine($"Current state: {session.ConnectionStatus}");
// Output: Current state: Disposed
Valid Operations Per State
Disconnected State
Operations available immediately after creating a session:
var session = new SshSession();
// Valid operations:
session.SetSecureMethodPreferences(); // Configure algorithms
session.SetMethodPreferences(method, prefs); // Set algorithm preferences
session.Connect("example.com", 22); // Connect to server
await session.ConnectAsync("example.com", 22); // Connect asynchronously
// Invalid operations (will throw SshException with SshError.DevWrongUse):
session.Authenticate(credential); // ❌ Not connected yet
session.ExecuteCommand("ls"); // ❌ Not connected
session.SetSessionTimeout(TimeSpan.FromMinutes(5)); // ❌ Must connect first
Connected State
Operations available after successful connection:
session.Connect("example.com", 22);
// Now in Connected state
// Valid operations:
session.Authenticate(credential); // Authenticate
await session.AuthenticateAsync(credential); // Authenticate asynchronously
session.GetHostKey(); // Get server's host key
session.GetHostKeyHash(SshHashType.SHA256); // Get host key fingerprint
session.SetSessionTimeout(TimeSpan.FromMinutes(5)); // Set operation timeout
session.DisableSessionTimeout(); // Disable timeout
session.ConfigureKeepAlive(false, TimeSpan.FromSeconds(30)); // Configure keepalive
session.SendKeepAlive(); // Send keepalive
session.GetNegotiatedMethod(SshMethod.Kex); // Check algorithms
// Invalid operations:
session.ExecuteCommand("ls"); // ❌ Must authenticate first
session.ReadFile("/path/file", stream); // ❌ Must authenticate first
session.WriteFile("/path/file", stream); // ❌ Must authenticate first
LoggedIn State
Operations available after successful authentication:
session.Authenticate(credential);
// Now in LoggedIn state
// All operations from Connected state, plus:
session.ExecuteCommand("ls"); // Execute commands
await session.ExecuteCommandAsync("ls"); // Execute asynchronously
session.ReadFile("/path/file", stream); // Download files
await session.ReadFileAsync("/path/file", stream); // Download asynchronously
session.WriteFile("/path/file", stream); // Upload files
await session.WriteFileAsync("/path/file", stream); // Upload asynchronously
// Note: You can still call methods from Connected state:
session.SendKeepAlive(); // Send keepalive
session.SetSessionTimeout(TimeSpan.FromMinutes(10)); // Adjust timeout
session.GetHostKeyHash(SshHashType.SHA256); // Still works
Disposed State
Once disposed, the session is permanently unusable:
session.Dispose();
// Now in Disposed state
// All operations are invalid
session.Connect("example.com", 22); // ❌ Cannot reuse
session.ExecuteCommand("ls"); // ❌ Disposed
Proper Session Management
Using Statement (Recommended)
The best way to ensure proper cleanup:
using var session = new SshSession();
session.Connect("example.com", 22);
session.Authenticate(credential);
var result = session.ExecuteCommand("ls");
Console.WriteLine(result.Stdout);
// Automatically disposed when leaving scope
Try-Finally Pattern
Explicit disposal in exception scenarios:
var session = new SshSession();
try
{
session.Connect("example.com", 22);
session.Authenticate(credential);
var result = session.ExecuteCommand("ls");
Console.WriteLine(result.Stdout);
}
catch (SshException ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
finally
{
session.Dispose(); // Always cleanup
}
Manual Disposal
Explicit disposal when needed:
var session = new SshSession();
session.Connect("example.com", 22);
session.Authenticate(credential);
// Do work...
session.Dispose(); // Explicit cleanup
State Validation
The library automatically validates that operations are called in the correct state:
var session = new SshSession();
try
{
// Try to execute command before connecting
session.ExecuteCommand("ls");
}
catch (SshException ex) when (ex.Error == SshError.DevWrongUse)
{
Console.WriteLine("Operation not allowed in current state");
Console.WriteLine($"Current state: {session.ConnectionStatus}");
}
Complete Lifecycle Example
using NullOpsDevs.LibSsh;
using NullOpsDevs.LibSsh.Credentials;
using NullOpsDevs.LibSsh.Exceptions;
public class SessionLifecycleDemo
{
public void DemonstrateLifecycle()
{
// 1. Create session (Disconnected state)
var session = new SshSession();
Console.WriteLine($"1. Created: {session.ConnectionStatus}");
try
{
// 2. Configure before connecting (Disconnected state)
session.SetSecureMethodPreferences();
Console.WriteLine("2. Configured algorithm preferences");
// 3. Connect (Disconnected → Connected)
session.Connect("example.com", 22);
Console.WriteLine($"3. Connected: {session.ConnectionStatus}");
// 4. Post-connection configuration (Connected state)
session.SetSessionTimeout(TimeSpan.FromMinutes(5));
session.ConfigureKeepAlive(false, TimeSpan.FromSeconds(30));
Console.WriteLine("4. Configured session parameters");
// 5. Verify host key (Connected state)
var hostKey = session.GetHostKey();
Console.WriteLine($"5. Host key type: {hostKey.Type}");
// 6. Authenticate (Connected → LoggedIn)
var credential = SshCredential.FromPassword("user", "password");
bool authenticated = session.Authenticate(credential);
if (!authenticated)
{
Console.WriteLine("6. Authentication failed");
return;
}
Console.WriteLine($"6. Authenticated: {session.ConnectionStatus}");
// 7. Use the session (LoggedIn state)
var result = session.ExecuteCommand("whoami");
Console.WriteLine($"7. Executed command: {result.Stdout.Trim()}");
// 8. Transfer files (LoggedIn state)
using (var stream = File.OpenRead("local-file.txt"))
{
session.WriteFile("/tmp/uploaded-file.txt", stream);
}
Console.WriteLine("8. Uploaded file");
// 9. More commands (LoggedIn state)
result = session.ExecuteCommand("ls /tmp");
Console.WriteLine($"9. Listed directory:\n{result.Stdout}");
}
catch (SshException ex)
{
Console.WriteLine($"SSH Error: {ex.Message}");
Console.WriteLine($"Current state: {session.ConnectionStatus}");
}
finally
{
// 10. Cleanup (Any state → Disposed)
session.Dispose();
Console.WriteLine($"10. Disposed: {session.ConnectionStatus}");
}
}
}
Best Practices
Always dispose sessions:
using var session = new SshSession(); // Automatic disposal
Configure before connecting:
session.SetSecureMethodPreferences(); // Before Connect()
session.Connect("example.com", 22);
Check authentication result:
if (!session.Authenticate(credential))
{
Console.WriteLine("Authentication failed");
return; // Don't proceed
}
Solution: Authentication only happens once per connection.
Session Reuse Pattern
For multiple operations, reuse the session:
using var session = new SshSession();
session.Connect("example.com", 22);
session.Authenticate(credential);
// Reuse for multiple operations
for (int i = 0; i < 10; i++)
{
var result = session.ExecuteCommand($"echo {i}");
Console.WriteLine(result.Stdout);
// Session stays in LoggedIn state
Console.WriteLine($"State: {session.ConnectionStatus}");
}
// Still in LoggedIn state
session.WriteFile("/tmp/data.txt", stream);
State Persistence
State persists across operations:
session.Connect("example.com", 22);
// State: Connected
session.SetSessionTimeout(TimeSpan.FromMinutes(5));
// Still: Connected
session.Authenticate(credential);
// State: LoggedIn
session.ExecuteCommand("ls");
// Still: LoggedIn
session.SendKeepAlive();
// Still: LoggedIn
// State only changes with Connect(), Authenticate(), or Dispose()
Async State Management
Async methods follow the same state rules:
var session = new SshSession();
// Disconnected → Connected
await session.ConnectAsync("example.com", 22);
// Connected → LoggedIn
await session.AuthenticateAsync(credential);
// LoggedIn operations
var result = await session.ExecuteCommandAsync("ls");
var downloaded = await session.ReadFileAsync("/path", stream);
// Dispose (synchronous)
session.Dispose();
See Also
SshConnectionStatus enum (SshConnectionStatus.cs:6) - Session state enumeration
SshSession.ConnectionStatus property (SshSession.cs:36) - Current session state