Merge pull request #2 in PEC/pepperdash-simplsharp-core from bugfix/fix-ssh-20161205 to master

* commit '422947559eee5739fea933cb3dcb7b4097bcfd73':
  Removed handler for stream error
  Ssh Connect() now spins up new clients.  Seems to be helping deal with connection failures
  Trying and failing lots of things to fix SshClient. About to try making and destroying the entire client
  Hunting down program crash when network cable is pulled from server. Client.ErrorOccurred is not catching this
  Working through elusive disconnect bugs on SSH, with Amazon on CP3N
This commit is contained in:
Heath Volmer
2016-12-07 10:02:38 -05:00
12 changed files with 151 additions and 92 deletions

View File

@@ -9,4 +9,21 @@ using Crestron.SimplSharp.CrestronSockets;
namespace PepperDash.Core namespace PepperDash.Core
{ {
public delegate void GenericSocketStatusChangeEventDelegate(ISocketStatus client); public delegate void GenericSocketStatusChangeEventDelegate(ISocketStatus client);
/// <summary>
///
/// </summary>
public class GenericSocketStatusChageEventArgs : EventArgs
{
public ISocketStatus Client { get; private set; }
public GenericSocketStatusChageEventArgs() { }
public GenericSocketStatusChageEventArgs(ISocketStatus client)
{
Client = client;
}
}
} }

View File

@@ -26,13 +26,12 @@ namespace PepperDash.Core
/// <summary> /// <summary>
/// Event when the connection status changes. /// Event when the connection status changes.
/// </summary> /// </summary>
//[Obsolete("Use SocketStatusChange instead")] public event EventHandler<GenericSocketStatusChageEventArgs> ConnectionChange;
//public event EventHandler<SshConnectionChangeEventArgs> ConnectionChange;
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public event GenericSocketStatusChangeEventDelegate SocketStatusChange; //public event GenericSocketStatusChangeEventDelegate SocketStatusChange;
/// <summary> /// <summary>
/// Address of server /// Address of server
@@ -63,6 +62,14 @@ namespace PepperDash.Core
get { return ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; } get { return ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; }
} }
/// <summary>
/// S+ helper for IsConnected
/// </summary>
public ushort UIsConnected
{
get { return (ushort)(IsConnected ? 1 : 0); }
}
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
@@ -93,6 +100,11 @@ namespace PepperDash.Core
/// </summary> /// </summary>
public bool AutoReconnect { get; set; } public bool AutoReconnect { get; set; }
/// <summary>
/// Will be set and unset by connect and disconnect only
/// </summary>
public bool ConnectEnabled { get; private set; }
/// <summary> /// <summary>
/// S+ helper for AutoReconnect /// S+ helper for AutoReconnect
/// </summary> /// </summary>
@@ -109,13 +121,15 @@ namespace PepperDash.Core
public int AutoReconnectIntervalMs { get; set; } public int AutoReconnectIntervalMs { get; set; }
SshClient Client; SshClient Client;
ShellStream TheStream; ShellStream TheStream;
CTimer ReconnectTimer; CTimer ReconnectTimer;
string PreviousHostname; //string PreviousHostname;
int PreviousPort; //int PreviousPort;
string PreviousUsername; //string PreviousUsername;
string PreviousPassword; //string PreviousPassword;
/// <summary> /// <summary>
/// Typical constructor. /// Typical constructor.
@@ -171,7 +185,8 @@ namespace PepperDash.Core
/// </summary> /// </summary>
public void Connect() public void Connect()
{ {
Debug.Console(1, this, "attempting connect, IsConnected={0}", Client != null ? Client.IsConnected : false); ConnectEnabled = true;
Debug.Console(1, this, "attempting connect");
// Cancel reconnect if running. // Cancel reconnect if running.
if (ReconnectTimer != null) if (ReconnectTimer != null)
@@ -197,55 +212,56 @@ namespace PepperDash.Core
kauth.AuthenticationPrompt += new EventHandler<AuthenticationPromptEventArgs>(kauth_AuthenticationPrompt); kauth.AuthenticationPrompt += new EventHandler<AuthenticationPromptEventArgs>(kauth_AuthenticationPrompt);
PasswordAuthenticationMethod pauth = new PasswordAuthenticationMethod(Username, Password); PasswordAuthenticationMethod pauth = new PasswordAuthenticationMethod(Username, Password);
// always spin up new client in case parameters have changed
// **** MAY WANT TO CHANGE THIS BECAUSE OF SOCKET LEAKS ****
//if (Client != null)
//{
// Client.Disconnect();
// Client = null;
//}
// Make a new client if we need it or things have changed // Make a new client if we need it or things have changed
if (Client == null || PropertiesHaveChanged()) //if (Client == null || PropertiesHaveChanged())
{ //{
if (Client != null) if (Client != null)
{ {
Debug.Console(2, this, "Cleaning up disconnected client"); Debug.Console(2, this, "Cleaning up disconnected client");
Client.ErrorOccurred -= Client_ErrorOccurred; Client.ErrorOccurred -= Client_ErrorOccurred;
if(TheStream != null) KillStream();
TheStream.DataReceived -= Stream_DataReceived;
TheStream = null; //if (TheStream != null)
//{
// TheStream.DataReceived -= Stream_DataReceived;
// TheStream.ErrorOccurred -= TheStream_ErrorOccurred;
//}
//TheStream = null;
} }
Debug.Console(2, this, "Creating new SshClient"); Debug.Console(2, this, "Creating new SshClient");
ConnectionInfo connectionInfo = new ConnectionInfo(Hostname, Port, Username, pauth, kauth); ConnectionInfo connectionInfo = new ConnectionInfo(Hostname, Port, Username, pauth, kauth);
Client = new SshClient(connectionInfo); Client = new SshClient(connectionInfo);
Client.ErrorOccurred += Client_ErrorOccurred; Client.ErrorOccurred += Client_ErrorOccurred;
} //}
PreviousHostname = Hostname; //PreviousHostname = Hostname;
PreviousPassword = Password; //PreviousPassword = Password;
PreviousPort = Port; //PreviousPort = Port;
PreviousUsername = Username; //PreviousUsername = Username;
//You can do it! //You can do it!
ClientStatus = SocketStatus.SOCKET_STATUS_WAITING; ClientStatus = SocketStatus.SOCKET_STATUS_WAITING;
try try
{ {
Client.Connect(); Client.Connect();
if (Client.IsConnected)
{ // Have to assume client is connected cause Client.IsConnected is busted in some cases
Client.KeepAliveInterval = TimeSpan.FromSeconds(2); // All other conditions *should* error out...
Client.SendKeepAlive(); //if (Client.IsConnected)
//{
//Client.KeepAliveInterval = TimeSpan.FromSeconds(2);
//Client.SendKeepAlive();
TheStream = Client.CreateShellStream("PDTShell", 100, 80, 100, 200, 65534); TheStream = Client.CreateShellStream("PDTShell", 100, 80, 100, 200, 65534);
TheStream.DataReceived += Stream_DataReceived; TheStream.DataReceived += Stream_DataReceived;
//TheStream.ErrorOccurred += TheStream_ErrorOccurred;
Debug.Console(1, this, "Connected"); Debug.Console(1, this, "Connected");
ClientStatus = SocketStatus.SOCKET_STATUS_CONNECTED; ClientStatus = SocketStatus.SOCKET_STATUS_CONNECTED;
PreviousHostname = Hostname; //PreviousHostname = Hostname;
PreviousPassword = Password; //PreviousPassword = Password;
PreviousPort = Port; //PreviousPort = Port;
PreviousUsername = Username; //PreviousUsername = Username;
} //}
return; return; // Success will not pass here
} }
catch (SshConnectionException e) catch (SshConnectionException e)
{ {
@@ -269,54 +285,44 @@ namespace PepperDash.Core
} }
// Sucess will not make it this far // Sucess will not make it this far
Client.Disconnect();
ClientStatus = SocketStatus.SOCKET_STATUS_CONNECT_FAILED; ClientStatus = SocketStatus.SOCKET_STATUS_CONNECT_FAILED;
HandleConnectionFailure(); HandleConnectionFailure();
} }
/// <summary> /// <summary>
/// Disconnect the clients and put away it's resources. /// Disconnect the clients and put away it's resources.
/// </summary> /// </summary>
public void Disconnect() public void Disconnect()
{ {
ConnectEnabled = false;
// Stop trying reconnects, if we are // Stop trying reconnects, if we are
if (ReconnectTimer != null) if (ReconnectTimer != null)
{ {
ReconnectTimer.Stop(); ReconnectTimer.Stop();
ReconnectTimer = null; ReconnectTimer = null;
} }
if(TheStream != null) KillStream();
TheStream.DataReceived -= Stream_DataReceived;
Client.Disconnect(); Client.Disconnect();
Client = null;
ClientStatus = SocketStatus.SOCKET_STATUS_BROKEN_LOCALLY; ClientStatus = SocketStatus.SOCKET_STATUS_BROKEN_LOCALLY;
Debug.Console(1, this, "Disconnected"); Debug.Console(1, this, "Disconnected");
} }
/// <summary>
///
/// </summary>
//void DiscoAndCleanup()
//{
// if (Client != null)
// {
// Client.ErrorOccurred -= Client_ErrorOccurred;
// TheStream.DataReceived -= Stream_DataReceived;
// Debug.Console(2, this, "Cleaning up disconnected client");
// Client.Disconnect();
// Client.Dispose();
// Client = null;
// }
//}
/// <summary> /// <summary>
/// Anything to do with reestablishing connection on failures /// Anything to do with reestablishing connection on failures
/// </summary> /// </summary>
void HandleConnectionFailure() void HandleConnectionFailure()
{ {
Debug.Console(2, this, "Checking autoreconnect: {0}, {1}ms", if (Client != null)
AutoReconnect, AutoReconnectIntervalMs); Client.Disconnect();
if (AutoReconnect) KillStream();
if (AutoReconnect && ConnectEnabled)
{ {
Debug.Console(2, this, "Checking autoreconnect: {0}, {1}ms",
AutoReconnect, AutoReconnectIntervalMs);
if (ReconnectTimer == null)// || !ReconnectTimerRunning) if (ReconnectTimer == null)// || !ReconnectTimerRunning)
{ {
ReconnectTimer = new CTimer(o => ReconnectTimer = new CTimer(o =>
@@ -335,12 +341,24 @@ namespace PepperDash.Core
} }
} }
bool PropertiesHaveChanged() void KillStream()
{ {
return Hostname != PreviousHostname || Port != PreviousPort if (TheStream != null)
|| Username != PreviousUsername || Password != PreviousPassword; {
TheStream.DataReceived -= Stream_DataReceived;
//TheStream.ErrorOccurred -= TheStream_ErrorOccurred;
TheStream.Close();
TheStream.Dispose();
TheStream = null;
}
} }
//bool PropertiesHaveChanged()
//{
// return Hostname != PreviousHostname || Port != PreviousPort
// || Username != PreviousUsername || Password != PreviousPassword;
//}
/// <summary> /// <summary>
/// Handles the keyboard interactive authentication, should it be required. /// Handles the keyboard interactive authentication, should it be required.
/// </summary> /// </summary>
@@ -371,23 +389,29 @@ namespace PepperDash.Core
} }
} }
///// <summary>
///// Handler for errors on the stream...
///// </summary>
///// <param name="sender"></param>
///// <param name="e"></param>
//void TheStream_ErrorOccurred(object sender, ExceptionEventArgs e)
//{
// Debug.Console(1, this, "Unhandled SSH STREAM error: {0}", e.Exception);
// ClientStatus = SocketStatus.SOCKET_STATUS_BROKEN_REMOTELY;
// HandleConnectionFailure();
//}
/// <summary> /// <summary>
/// Error event handler for client events - disconnect, etc. Will forward those events via ConnectionChange /// Error event handler for client events - disconnect, etc. Will forward those events via ConnectionChange
/// event /// event
/// </summary> /// </summary>
void Client_ErrorOccurred(object sender, Crestron.SimplSharp.Ssh.Common.ExceptionEventArgs e) void Client_ErrorOccurred(object sender, Crestron.SimplSharp.Ssh.Common.ExceptionEventArgs e)
{ {
Debug.Console(1, this, "SSH client error: {0}", e.Exception); if (e.Exception is SshConnectionException || e.Exception is System.Net.Sockets.SocketException)
if (!(e.Exception is SshConnectionException))
{
Debug.Console(1, this, "Disconnected by remote"); Debug.Console(1, this, "Disconnected by remote");
} else
if (Client != null) Debug.Console(1, this, "Unhandled SSH client error: {0}", e.Exception);
{
Client.Disconnect();
//Client.Dispose();
//Client = null;
}
ClientStatus = SocketStatus.SOCKET_STATUS_BROKEN_REMOTELY; ClientStatus = SocketStatus.SOCKET_STATUS_BROKEN_REMOTELY;
HandleConnectionFailure(); HandleConnectionFailure();
} }
@@ -397,12 +421,8 @@ namespace PepperDash.Core
/// </summary> /// </summary>
void OnConnectionChange() void OnConnectionChange()
{ {
//if(ConnectionChange != null) if (ConnectionChange != null)
// ConnectionChange(this, new SshConnectionChangeEventArgs(IsConnected, this)); ConnectionChange(this, new GenericSocketStatusChageEventArgs(this));
var handler = SocketStatusChange;
if (handler != null)
SocketStatusChange(this);
} }
#region IBasicCommunication Members #region IBasicCommunication Members

View File

@@ -26,7 +26,8 @@ namespace PepperDash.Core
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public event GenericSocketStatusChangeEventDelegate SocketStatusChange; //public event GenericSocketStatusChangeEventDelegate SocketStatusChange;
public event EventHandler<GenericSocketStatusChageEventArgs> ConnectionChange;
/// <summary> /// <summary>
/// ///
@@ -207,10 +208,14 @@ namespace PepperDash.Core
break; break;
} }
// Relay the event var handler = ConnectionChange;
var handler = SocketStatusChange;
if (handler != null) if (handler != null)
SocketStatusChange(this); ConnectionChange(this, new GenericSocketStatusChageEventArgs(this));
// Relay the event
//var handler = SocketStatusChange;
//if (handler != null)
// SocketStatusChange(this);
} }
} }

View File

@@ -31,7 +31,7 @@ namespace PepperDash.Core
/// </summary> /// </summary>
public interface ISocketStatus : IBasicCommunication public interface ISocketStatus : IBasicCommunication
{ {
event GenericSocketStatusChangeEventDelegate SocketStatusChange; event EventHandler<GenericSocketStatusChageEventArgs> ConnectionChange;
SocketStatus ClientStatus { get; } SocketStatus ClientStatus { get; }
} }

View File

@@ -32,11 +32,7 @@ namespace PepperDash.Core
static CTimer SaveTimer; static CTimer SaveTimer;
/// <summary> static Debug()
/// This should called from the ControlSystem Initiailize method. Will attempt to call
/// CrestronDataStoreStatic.InitCrestronDataStore which may have been called elsewhere.
/// </summary>
public static void Initialize()
{ {
CrestronDataStoreStatic.InitCrestronDataStore(); CrestronDataStoreStatic.InitCrestronDataStore();
if (CrestronEnvironment.RuntimeEnvironment == eRuntimeEnvironment.SimplSharpPro) if (CrestronEnvironment.RuntimeEnvironment == eRuntimeEnvironment.SimplSharpPro)
@@ -53,6 +49,27 @@ namespace PepperDash.Core
Level = Contexts.GetOrCreateItem("DEFAULT").Level; Level = Contexts.GetOrCreateItem("DEFAULT").Level;
} }
/// <summary>
/// This should called from the ControlSystem Initiailize method. Will attempt to call
/// CrestronDataStoreStatic.InitCrestronDataStore which may have been called elsewhere.
/// </summary>
public static void Initialize()
{
//CrestronDataStoreStatic.InitCrestronDataStore();
//if (CrestronEnvironment.RuntimeEnvironment == eRuntimeEnvironment.SimplSharpPro)
//{
// // Add command to console
// CrestronConsole.AddNewConsoleCommand(SetDebugFromConsole, "appdebug",
// "appdebug:P [0-2]: Sets the application's console debug message level",
// ConsoleAccessLevelEnum.AccessOperator);
//}
//CrestronEnvironment.ProgramStatusEventHandler += CrestronEnvironment_ProgramStatusEventHandler;
//LoadMemory();
//Level = Contexts.GetOrCreateItem("DEFAULT").Level;
}
/// <summary> /// <summary>
/// Used to save memory when shutting down /// Used to save memory when shutting down
/// </summary> /// </summary>

View File

@@ -2,6 +2,6 @@
<ControlSystem> <ControlSystem>
<Name>MC3 SSH</Name> <Name>MC3 SSH</Name>
<Address>ssh 10.0.0.15</Address> <Address>ssh 10.0.0.15</Address>
<ProgramSlot /> <ProgramSlot>Program01</ProgramSlot>
<Storage /> <Storage>Internal Flash</Storage>
</ControlSystem> </ControlSystem>

View File

@@ -10,8 +10,8 @@
<ArchiveName /> <ArchiveName />
</RequiredInfo> </RequiredInfo>
<OptionalInfo> <OptionalInfo>
<CompiledOn>11/28/2016 1:59:52 PM</CompiledOn> <CompiledOn>12/6/2016 4:09:28 PM</CompiledOn>
<CompilerRev>1.0.0.25195</CompilerRev> <CompilerRev>1.0.0.29083</CompilerRev>
</OptionalInfo> </OptionalInfo>
<Plugin> <Plugin>
<Version>Crestron.SIMPLSharp, Version=2.0.48.0, Culture=neutral, PublicKeyToken=812d080f93e2de10</Version> <Version>Crestron.SIMPLSharp, Version=2.0.48.0, Culture=neutral, PublicKeyToken=812d080f93e2de10</Version>

View File

@@ -1,4 +1,4 @@
MainAssembly=PepperDash_Core.dll:91882e81937f6bb6c3592368dedeea09 MainAssembly=PepperDash_Core.dll:987a92dfd5c1b46ff22b2570acef6436
MainAssemblyMinFirmwareVersion=1.007.0017 MainAssemblyMinFirmwareVersion=1.007.0017
MainAssemblyResource=SimplSharpData.dat:315526abf906cded47fb0c7510266a7e MainAssemblyResource=SimplSharpData.dat:315526abf906cded47fb0c7510266a7e
ü ü