From ee05d7096678e9391903ce02910f981f0e45fe62 Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Sat, 5 Oct 2024 10:29:43 +0200 Subject: [PATCH] Add TcpKeepAlive option and enable it by default. (#235) --- README.md | 4 +++- src/Tmds.Ssh/SshClientSettings.Defaults.cs | 2 ++ src/Tmds.Ssh/SshClientSettings.SshConfig.cs | 1 + src/Tmds.Ssh/SshClientSettings.cs | 2 ++ src/Tmds.Ssh/SshConfig.cs | 6 +++++- src/Tmds.Ssh/SshSession.cs | 5 +++++ 6 files changed, 18 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 74db69e..9fb6458 100644 --- a/README.md +++ b/README.md @@ -221,11 +221,13 @@ class SshClientSettings string HostName { get; set; } = ""; int Port { get; set; } = 22; - IReadOnlyList Credentials { get; set; } = DefaultCredentials; + List Credentials { get; set; } = DefaultCredentials; bool AutoConnect { get; set; } = true; bool AutoReconnect { get; set; } = false; + bool TcpKeepAlive { get; set; } = true; + List GlobalKnownHostsFilePaths { get; set; } = DefaultGlobalKnownHostsFilePaths; List UserKnownHostsFilePaths { get; set; } = DefaultUserKnownHostsFilePaths; HostAuthentication? HostAuthentication { get; set; } // not called when known to be trusted/revoked. diff --git a/src/Tmds.Ssh/SshClientSettings.Defaults.cs b/src/Tmds.Ssh/SshClientSettings.Defaults.cs index 36947b5..6c05c82 100644 --- a/src/Tmds.Ssh/SshClientSettings.Defaults.cs +++ b/src/Tmds.Ssh/SshClientSettings.Defaults.cs @@ -33,6 +33,8 @@ partial class SshClientSettings private static bool DefaultHashKnownHosts => false; + private static bool DefaultTcpKeepAlive => true; + // Algorithms are in **order of preference**. private readonly static List EmptyList = []; internal readonly static List SupportedKeyExchangeAlgorithms = [ AlgorithmNames.EcdhSha2Nistp256, AlgorithmNames.EcdhSha2Nistp384, AlgorithmNames.EcdhSha2Nistp521 ]; diff --git a/src/Tmds.Ssh/SshClientSettings.SshConfig.cs b/src/Tmds.Ssh/SshClientSettings.SshConfig.cs index a5d9af1..cffa3be 100644 --- a/src/Tmds.Ssh/SshClientSettings.SshConfig.cs +++ b/src/Tmds.Ssh/SshClientSettings.SshConfig.cs @@ -45,6 +45,7 @@ internal static async ValueTask LoadFromConfigAsync(string? u MinimumRSAKeySize = sshConfig.RequiredRSASize ?? DefaultMinimumRSAKeySize, Credentials = DetermineCredentials(sshConfig), HashKnownHosts = sshConfig.HashKnownHosts ?? DefaultHashKnownHosts, + TcpKeepAlive = sshConfig.TcpKeepAlive ?? DefaultTcpKeepAlive }; if (sshConfig.UserKnownHostsFiles is not null) { diff --git a/src/Tmds.Ssh/SshClientSettings.cs b/src/Tmds.Ssh/SshClientSettings.cs index e32c46d..2f5a3ee 100644 --- a/src/Tmds.Ssh/SshClientSettings.cs +++ b/src/Tmds.Ssh/SshClientSettings.cs @@ -208,6 +208,8 @@ public Dictionary? EnvironmentVariables } } + public bool TcpKeepAlive { get; set; } = DefaultTcpKeepAlive; + public int MinimumRSAKeySize { get; set; } = DefaultMinimumRSAKeySize; // TODO throw if <0. // Currently these settings are not exposed. diff --git a/src/Tmds.Ssh/SshConfig.cs b/src/Tmds.Ssh/SshConfig.cs index 30f4f51..1cddb99 100644 --- a/src/Tmds.Ssh/SshConfig.cs +++ b/src/Tmds.Ssh/SshConfig.cs @@ -59,6 +59,7 @@ public struct AlgorithmList public StrictHostKeyChecking? HostKeyChecking { get; set; } public bool? HashKnownHosts { get; set; } public List? SendEnv { get; set; } + public bool? TcpKeepAlive { get; set; } internal static ValueTask DetermineConfigForHost(string? userName, string host, int? port, IReadOnlyDictionary? options, IReadOnlyList configFiles, CancellationToken cancellationToken) { @@ -442,6 +443,10 @@ private static void HandleMatchedKeyword(SshConfig config, ReadOnlySpan ke config.Compression ??= ParseYesNoKeywordValue(keyword, ref remainder); break; + case "tcpkeepalive": + config.TcpKeepAlive ??= ParseYesNoKeywordValue(keyword, ref remainder); + break; + /* The following options are unsupported, we have some basic handling that checks the option value indicates the feature is disabled */ case "permitlocalcommand": @@ -536,7 +541,6 @@ we have some basic handling that checks the option value indicates the feature i // case "streamlocalbindunlink": // case "serveralivecountmax": // case "serveraliveinterval": - // case "tcpkeepalive": // case "setenv": // case "tag": // case "proxycommand": diff --git a/src/Tmds.Ssh/SshSession.cs b/src/Tmds.Ssh/SshSession.cs index 3ffaaf0..21b6b53 100644 --- a/src/Tmds.Ssh/SshSession.cs +++ b/src/Tmds.Ssh/SshSession.cs @@ -102,6 +102,11 @@ private async Task EstablishConnectionAsync(CancellationToken ct) ConnectionInfo.IPAddress = (socket.RemoteEndPoint as IPEndPoint)?.Address; socket.NoDelay = true; + if (_settings.TcpKeepAlive) + { + socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true); + } + Logger.ConnectionEstablished(); return new SocketSshConnection(Logger, _sequencePool, socket);