Files
cms_thermo_active/Thermo.Active.CmsConnectGateway/GatewayAdapter.cs
T
2020-04-09 13:15:01 +02:00

760 lines
29 KiB
C#

using Renci.SshNet;
using Renci.SshNet.Common;
using Termo.Active.CmsConnectGateway.Events;
using Termo.Active.CmsConnectGateway.Exceptions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
namespace Termo.Active.CmsConnectGateway
{
public class GatewayAdapter : IDisposable
{
//Internal Constant
private const string SSH_SET_PROXY_COMMAND = "sudo ./setProxy.sh ";
private const string SSH_SET_DNSIP_COMMAND = "sudo ./setDnsIp.sh ";
private const string SSH_SET_DNSSUFFIX_COMMAND = "sudo ./setDnsSuffix.sh ";
private const string SSH_SET_NETWORK_COMMAND = "sudo ./setNetwork.sh ";
private const string SSH_GET_NETWORK_COMMAND = "sudo ./getNetworkConfiguration.sh ";
private const string SSH_GET_PROXY_COMMAND = "sudo ./getProxyConfiguration.sh ";
private const string SSH_TEST_CONNECTION_COMMAND = "sudo ./testConnection.sh ";
private const string SSH_GW_REBOOT_COMMAND = "sudo ./gatewayReboot.sh ";
const string IP_ADDR_LABEL = "IP_ADDRESS=";
const string GATEWAY_LABEL = "DEFAULT_GATEWAY=";
const string DNSIP_LABEL = "DNS_IP=";
const string DNSPREFIX_LABEL = "DNS_SUFFIX=";
const string PROXY_ADDR_LABEL = "PROXY=";
const string NO_PROXY_LABEL = "NO_PROXY=";
const string UNDEF_VALUE = "none";
const string CONNECTION_OK_VALUE = "OK";
const string CONNECTION_NOWEB_VALUE = "NO_WEB";
const string CONNECTION_NOPORT_VALUE = "NO_PORTS";
const int REBOOT_MINUTES_MAX = 2;
const int REBOOT_MSWAIT_BETWEEN_OP = 500;
//Internal Private Variable
private string host;
private string username;
private string password;
//---------------------------------------------------------------------------------------------------
#region Public_methods
/// <summary>
/// Simple constructor.
/// Create an instance with this configuration: hostname: localhost, username: root, password: root.
/// </summary>
public GatewayAdapter()
{
this.host = "localhost";
this.username = "root";
this.password = "root";
}
/// <summary>
/// Advanced constructor.
/// Create an instance with the configuration passed by arguments
/// </summary>
/// <param name="hostname">Name (or IP-Address) of the gateway</param>
/// <param name="username">Username for Gateway Login</param>
/// <param name="password">Password for Gateway Login</param>
/// <exception cref="System.NullReferenceException">Thrown when one parameter is null</exception>
public GatewayAdapter(string hostname, string username, string password)
{
Setup(hostname, username, password);
}
/// <summary>
/// Setup parameters Method.
/// </summary>
/// <param name="hostname">Name (or IP-Address) of the gateway</param>
/// <param name="username">Username for Gateway Login</param>
/// <param name="password">Password for Gateway Login</param>
/// <exception cref="System.NullReferenceException">Thrown when one parameter is null</exception>
public void Setup(string hostname, string username, string password)
{
this.host = hostname ?? throw new NullReferenceException("hostname is mandatory");
this.username = username ?? throw new NullReferenceException("username is mandatory");
this.password = password ?? throw new NullReferenceException("password is mandatory");
}
/// <summary>
/// Write Proxy-Configuration on Gateway.
/// <para />See <see cref="GatewayProxyConfigurationBuilder"/> for generate the configuration.
/// </summary>
/// <param name="proxyConfiguration">Configuration of the proxy.</param>
/// <exception cref="GatewayException"></exception>
public void WriteGatewayProxyConfiguration(GatewayProxyConfiguration proxyConfiguration)
{
String Command = GenerateSshProxyCommand(proxyConfiguration);
SendSSHCommand(Command);
}
/// <summary>
/// Write Newtork-Configuration on Gateway.
/// <para />See <see cref="GatewayNetworkConfigurationBuilder"/> for generate the configuration.
/// </summary>
/// <param name="networkConfiguration">Configuration of the network.</param>
/// <exception cref="GatewayException"></exception>
public void WriteGatewayNetworkConfiguration(GatewayNetworkConfiguration networkConfiguration)
{
String NetworkCommand = GenerateSshNetworkCommand(networkConfiguration);
String DnsIpCommand = GenerateSshDnsCommand(networkConfiguration);
String DnsSuffixCommand = GenerateSshDnsSuffxCommand(networkConfiguration);
SendSSHCommand(new List<string>() { NetworkCommand, DnsIpCommand, DnsSuffixCommand });
}
/// <summary>
/// Read Newtork-Configuration saved on Gateway.
/// </summary>
/// <returns>See <see cref="GatewayNetworkConfiguration"/>
/// </returns>
/// <exception cref="GatewayException"></exception>
public GatewayNetworkConfiguration ReadGatewayNetworkConfiguration()
{
return ElaborateConfigFromSshNetworkCommand( SendSSHCommand(SSH_GET_NETWORK_COMMAND) );
}
/// <summary>
/// Read Proxy-Configuration saved on Gateway.
/// </summary>
/// <returns>See <see cref="GatewayProxyConfiguration"/>
/// </returns>
/// <exception cref="GatewayException"></exception>
public GatewayProxyConfiguration ReadGatewayProxyConfiguration()
{
return ElaborateConfigFromSshProxyCommand(SendSSHCommand(SSH_GET_PROXY_COMMAND));
}
/// <summary>
/// Test connection to SCM/CMS Server, on Gateway.
/// </summary>
/// <param name="timeout">Seconds timeout for the request</param>
/// <exception cref="GatewayException"></exception>
public GatewayConnectionStatus TestGatewayConnection(int timeout)
{
if (timeout < 0)
throw new ArgumentOutOfRangeException("Timeout must be > 0");
return ElaborateTestConnectionCommand(SendSSHCommand(SSH_TEST_CONNECTION_COMMAND + timeout));
}
/// <summary>
/// Reboot asynchronously the Gateway.
/// </summary>
/// <param name="delay">Serconds to delay before reboot</param>
/// <param name="handler">Handler Callback when the operation is finished or an error occours.
/// See <see cref="GatewayRebootEventHandlerArgs"/>
/// </param>
public void RebootGatewayAsync(int delay, EventHandler<GatewayRebootEventHandlerArgs> handler)
{
if (delay < 0)
throw new ArgumentOutOfRangeException("Delay must be > 0");
//Create the Action
Action<int, EventHandler<GatewayRebootEventHandlerArgs>> action = (int del, EventHandler<GatewayRebootEventHandlerArgs> hand) =>
{
GatewayRebootEventHandlerArgs ev = new GatewayRebootEventHandlerArgs();
try
{
this.SendSSHReboot(del);
}
catch (Exception e)
{
ev.errorStatus = true;
ev.errorMessage = e.Message;
}
hand(this, ev);
};
// Create a task and not start it.
Task t = new Task (() => action(delay, handler));
t.Start();
}
/// <summary>
/// Reboot and wait a new session of the Gateway.
/// </summary>
/// <param name="delay">Seconds to delay before reboot</param>
/// <exception cref="GatewayException"></exception>
public void RebootGateway(int delay)
{
if (delay < 0)
throw new ArgumentOutOfRangeException("Delay must be > 0");
//Send Command
this.SendSSHReboot(delay);
}
public void Dispose()
{
}
public override string ToString()
{
return "CmsConnectAdapter - Host:" + this.host;
}
#endregion
//---------------------------------------------------------------------------------------------------
#region SSH_commands_preparations_sub_methods
//SSH command generator for Proxy setting
private string GenerateSshProxyCommand(GatewayProxyConfiguration configuration)
{
StringBuilder command = new StringBuilder(SSH_SET_PROXY_COMMAND);
//Call the script without an argument will disable the Proxy
if (configuration.hasProxy)
{
/* 1St parameter. Must be one of these:
* - Address:Port
* - User:Password@Address:Port
*/
//Setup Username & password if needed
if (!String.IsNullOrWhiteSpace(configuration.username) && !String.IsNullOrWhiteSpace(configuration.password))
command.Append(configuration.username).Append(":").Append(configuration.password).Append("@");
//Setup Address & Port
command.Append(configuration.address).Append(":").Append(configuration.port).Append(" ");
/* 2nd parameter. Must be:
* "no_proxy1,no_proxy2,…,no_proxyN"
*/
if (configuration.noproxyAddresses != null)
{
command.Append("\"");
command.Append(string.Join(",", configuration.noproxyAddresses));
command.Append("\"");
}
}
return command.ToString();
}
//SSH command generator for Network setting
private string GenerateSshNetworkCommand(GatewayNetworkConfiguration configuration)
{
StringBuilder command = new StringBuilder(SSH_SET_NETWORK_COMMAND);
if (configuration.hasDhcp)
{
command.Append("DHCP");
}
else
{
//Set IP address / Netmask
IPNetwork tempIpNetw = IPNetwork.Parse(configuration.ipAddress, configuration.netMaskAddress);
command.Append(configuration.ipAddress).Append("/").Append(tempIpNetw.Cidr).Append(" ");
if(configuration.defaultGatewayAddress != null)
command.Append(configuration.defaultGatewayAddress);
}
return command.ToString();
}
//SSH command generator for Dns setting
private string GenerateSshDnsCommand(GatewayNetworkConfiguration configuration)
{
StringBuilder command = new StringBuilder(SSH_SET_DNSIP_COMMAND);
if (configuration.dnsAddresses != null)
command.Append(string.Join(",", configuration.dnsAddresses));
return command.ToString();
}
//SSH command generator for Dns-Suffix setting
private string GenerateSshDnsSuffxCommand(GatewayNetworkConfiguration configuration)
{
StringBuilder command = new StringBuilder(SSH_SET_DNSSUFFIX_COMMAND);
if(configuration.dnsPrefixes != null)
command.Append(string.Join(",", configuration.dnsPrefixes));
return command.ToString();
}
#endregion
//---------------------------------------------------------------------------------------------------
#region elaborate_SSH_values_sub_methods
private GatewayNetworkConfiguration ElaborateConfigFromSshNetworkCommand(List<string> lines)
{
GatewayNetworkConfiguration configuration = new GatewayNetworkConfiguration();
foreach (string line in lines)
{
if (line.StartsWith(IP_ADDR_LABEL))
DecodeNetworkIPAddress(line.Remove(0, IP_ADDR_LABEL.Length).Trim(), ref configuration);
else if (line.StartsWith(GATEWAY_LABEL))
DecodeNetworkDefGateway(line.Remove(0, GATEWAY_LABEL.Length).Trim(), ref configuration);
else if (line.StartsWith(DNSIP_LABEL))
DecodeNetworkDnsIp(line.Remove(0, DNSIP_LABEL.Length).Trim(), ref configuration);
else if (line.StartsWith(DNSPREFIX_LABEL))
DecodeNetworkDnsPrefix(line.Remove(0, DNSPREFIX_LABEL.Length).Trim(), ref configuration);
}
return configuration;
}
private GatewayProxyConfiguration ElaborateConfigFromSshProxyCommand(List<string> lines)
{
GatewayProxyConfiguration configuration = new GatewayProxyConfiguration();
foreach (string line in lines)
{
if (line.StartsWith(PROXY_ADDR_LABEL))
DecodeProxyAddress(line.Remove(0, PROXY_ADDR_LABEL.Length).Trim(), ref configuration);
else if (line.StartsWith(NO_PROXY_LABEL))
DecodeNoProxyUrls(line.Remove(0, NO_PROXY_LABEL.Length).Trim(), ref configuration);
}
return configuration;
}
private GatewayConnectionStatus ElaborateTestConnectionCommand(List<string> lines)
{
GatewayConnectionStatus status = new GatewayConnectionStatus();
foreach (string line in lines)
{
if (!String.IsNullOrWhiteSpace(line))
DecodeConnectionStatus(line.Trim(), ref status);
}
return status;
}
#endregion
//---------------------------------------------------------------------------------------------------
#region decode_SSH_values_sub_methods
private void DecodeNetworkIPAddress(string stringValue, ref GatewayNetworkConfiguration configuration)
{
//Check if has DHCP
if (stringValue == "DHCP")
configuration.hasDhcp = true;
else
{
configuration.hasDhcp = false;
//Check if has / for netmask
if (!stringValue.Contains('/'))
throw new GatewayException("Internal Gateway Error during read (bad format): " + IP_ADDR_LABEL + stringValue);
string [] tempAddr = stringValue.Split('/');
//Check if has lenght == 2
if (tempAddr.Length != 2)
throw new GatewayException("Internal Gateway Error during read (bad format): " + IP_ADDR_LABEL + stringValue);
//Set the IPAddress / Netmask
IPAddress tempIp;
if (!EvaluateIPV4(tempAddr[0], out tempIp))
throw new GatewayException("Internal Gateway Error during read (bad format): " + IP_ADDR_LABEL + stringValue);
IPNetwork tempIpNetw;
if (!IPNetwork.TryParse(stringValue, out tempIpNetw))
throw new GatewayException("Internal Gateway Error during read (bad format): " + IP_ADDR_LABEL + stringValue);
configuration.ipAddress = tempIp;
configuration.netMaskAddress = tempIpNetw.Netmask;
}
}
private void DecodeNetworkDefGateway(string stringValue, ref GatewayNetworkConfiguration configuration)
{
//check if is defined
if (stringValue == UNDEF_VALUE)
configuration.defaultGatewayAddress = null;
else
{
IPAddress tempIp;
if (!EvaluateIPV4(stringValue, out tempIp))
throw new GatewayException("Internal Gateway Error during read (bad format): " + GATEWAY_LABEL + stringValue);
configuration.defaultGatewayAddress = tempIp;
}
}
private void DecodeNetworkDnsIp(string stringValue, ref GatewayNetworkConfiguration configuration)
{
//check if is defined
if (stringValue == UNDEF_VALUE)
configuration.dnsAddresses = null;
else
{
string[] tempStr = stringValue.Split(' ');
if (tempStr.Length == 0)
configuration.dnsAddresses = null;
else
{
IPAddress tempIp;
List<IPAddress> tempDns = new List<IPAddress>();
foreach (string str in tempStr)
{
if (string.IsNullOrEmpty(str))
continue;
if (!EvaluateIPV4(str, out tempIp))
throw new GatewayException("Internal Gateway Error during read (bad format): " + DNSIP_LABEL + stringValue);
tempDns.Add(tempIp);
}
configuration.dnsAddresses = tempDns;
}
}
}
private void DecodeNetworkDnsPrefix(string stringValue, ref GatewayNetworkConfiguration configuration)
{
//check if is defined
if (stringValue == UNDEF_VALUE)
configuration.dnsPrefixes = null;
else
{
string[] tempStr = stringValue.Split(' ');
if (tempStr.Length == 0)
configuration.dnsPrefixes = null;
else
configuration.dnsPrefixes = tempStr.Where(X=> !String.IsNullOrEmpty(X)).ToList();
}
}
private void DecodeProxyAddress(string stringValue, ref GatewayProxyConfiguration configuration)
{
//check if is defined
if (stringValue == UNDEF_VALUE)
configuration.hasProxy = false;
else
{
configuration.hasProxy = true;
/* Must be one of these:
* - Address:Port
* - User:Password@Address:Port
*/
if (!stringValue.Contains("@"))
{
//Set username & password
configuration.username = null;
configuration.password = null;
//Check if has :
if (!stringValue.Contains(':'))
throw new GatewayException("Internal Gateway Error during read (bad format): " + PROXY_ADDR_LABEL + stringValue);
string[] tempAddr = stringValue.Split(':');
//Check if has lenght == 2
if (tempAddr.Length != 2)
throw new GatewayException("Internal Gateway Error during read (bad format): " + PROXY_ADDR_LABEL + stringValue);
//Set the address
configuration.address = tempAddr[0];
//Set the port
UInt32 tempPort;
if (!UInt32.TryParse(tempAddr[1], out tempPort))
throw new GatewayException("Internal Gateway Error during read (bad format): " + PROXY_ADDR_LABEL + stringValue);
configuration.port = tempPort;
}
else
{
string[] splitted = stringValue.Split('@');
//Check if has lenght == 2
if (splitted.Length != 2)
throw new GatewayException("Internal Gateway Error during read (bad format): " + PROXY_ADDR_LABEL + stringValue);
//Check if has :
if (!splitted[0].Contains(':'))
throw new GatewayException("Internal Gateway Error during read (bad format): " + PROXY_ADDR_LABEL + stringValue);
if (!splitted[1].Contains(':'))
throw new GatewayException("Internal Gateway Error during read (bad format): " + PROXY_ADDR_LABEL + stringValue);
string[] tempLogin = splitted[0].Split(':');
string[] tempAddr = splitted[1].Split(':');
//Check if has lenght == 2
if (tempLogin.Length != 2)
throw new GatewayException("Internal Gateway Error during read (bad format): " + PROXY_ADDR_LABEL + stringValue);
if (tempAddr.Length != 2)
throw new GatewayException("Internal Gateway Error during read (bad format): " + PROXY_ADDR_LABEL + stringValue);
//Set the username
configuration.username = tempLogin[0];
//Set the password
configuration.password = tempLogin[1];
//Set the address
configuration.address = tempAddr[0];
//Set the port
UInt32 tempPort;
if (!UInt32.TryParse(tempAddr[1], out tempPort))
throw new GatewayException("Internal Gateway Error during read (bad format): " + PROXY_ADDR_LABEL + stringValue);
configuration.port = tempPort;
}
}
}
private void DecodeNoProxyUrls(string stringValue, ref GatewayProxyConfiguration configuration)
{
//check if is defined
if (stringValue == UNDEF_VALUE)
configuration.noproxyAddresses = null;
else
{
string[] tempStr = stringValue.Trim('"').Split(',');
for (int i=0;i<tempStr.Length;i++)
tempStr[i] = tempStr[i].Trim();
if (tempStr.Length == 0)
configuration.noproxyAddresses = null;
else
configuration.noproxyAddresses = tempStr.ToList();
}
}
private void DecodeConnectionStatus(string stringValue, ref GatewayConnectionStatus status)
{
if (stringValue == CONNECTION_OK_VALUE)
status = GatewayConnectionStatus.OK;
else if (stringValue == CONNECTION_NOWEB_VALUE)
status = GatewayConnectionStatus.WEB_ERROR;
else if (stringValue == CONNECTION_NOPORT_VALUE)
status = GatewayConnectionStatus.PORT_ERROR;
else
throw new GatewayException("Internal Gateway Error during Connection-Test (bad format): " + stringValue);
}
private bool EvaluateIPV4(string stringValue,out IPAddress address)
{
Regex rgx = new Regex(@"^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$");
if (rgx.IsMatch(stringValue))
{
IPAddress.TryParse(stringValue, out address);
return true;
}
address = new IPAddress(0);
return false;
}
#endregion
//---------------------------------------------------------------------------------------------------
#region ssh_sub_methods
//Override sendCommand for single command
private List<string> SendSSHCommand(string command)
{
return SendSSHCommand(new List<string> {command});
}
//Send multiple SSH Command
private List<string> SendSSHCommand(IEnumerable<string> command)
{
SshCommand sshCmd;
List<string> returnedValues = new List<string>();
//Create the SSH Gateway
SshClient _sshGateway = new SshClient(this.host, this.username, this.password);
try
{
//Connect
_sshGateway.Connect();
foreach(string cmd in command)
{
//Run command
sshCmd = _sshGateway.RunCommand(cmd);
//Add return values if esists
returnedValues.AddRange(sshCmd.Result.Split(new[] { "\n" }, StringSplitOptions.None));
//Check for errors
if (sshCmd.ExitStatus != 0)
{
_sshGateway.Disconnect();
throw new GatewayException("Internal Gateway Error: " + sshCmd.Error);
}
}
//Disconnect
_sshGateway.Disconnect();
}
catch (SocketException e)
{
throw new GatewayException("Connection Error: - Host:" + this.host);
}
catch (SshConnectionException e)
{
throw new GatewayException("Connection Error: - Host:" + this.host);
}
catch (ProxyException e)
{
throw new GatewayException("Proxy Error: - Host:" + this.host);
}
catch (SshAuthenticationException e)
{
throw new GatewayException("Authentication Error: - Host:" + this.host);
}
catch (SshException e)
{
throw new GatewayException("Internal command Error: - Host:" + this.host);
}
catch (GatewayException e)
{
throw new GatewayException(e.Message);
}
catch (Exception e)
{
throw new GatewayException("Unknown Error: " + e);
}
return returnedValues;
}
//Send REBOOT Command
private void SendSSHReboot(int seconds)
{
//Send SSH Command
SendSSHCommand(SSH_GW_REBOOT_COMMAND + seconds);
//Wait the time for reboot
Thread.Sleep((seconds + 5) * 1000);
//Save actual timestamp
DateTime nowAfterReboot = DateTime.Now;
//Create the Instance
SshClient _sshGateway = new SshClient(this.host, this.username, this.password);
//Phase 1 Wait Disconnection Cycle
bool disconnected = false;
do
{
try
{
//If TimeSpan > TOT MIN -> Exception
if ((DateTime.Now - nowAfterReboot) > TimeSpan.FromMinutes(REBOOT_MINUTES_MAX))
throw new GatewayException("Timeout Error during reboot - Gateway has never been rebooted");
//Try Connection
_sshGateway.Connect();
Thread.Sleep(REBOOT_MSWAIT_BETWEEN_OP);
_sshGateway.Disconnect();
}
catch (SocketException e)
{
//Not Connected
disconnected = true;
}
catch (SshConnectionException e)
{
//Not Connected
disconnected = true;
}
catch (SshException e)
{
//Not Connected
disconnected = true;
}
catch (GatewayException e)
{
//Error
throw new GatewayException(e.Message);
}
catch (Exception e)
{
//Error
throw new GatewayException("Unknown Error during reboot: " + e);
}
} while (!disconnected);
//Phase 2 Wait Re-connection Cycle
bool connected = false;
do
{
try
{
//If TimeSpan > TOT MIN -> Exception
if((DateTime.Now - nowAfterReboot) > TimeSpan.FromMinutes(REBOOT_MINUTES_MAX))
throw new GatewayException("Timeout Error during reboot - Gateway not found during reboot");
//Try Connection
_sshGateway.Connect();
connected = true;
}
catch (SocketException e)
{
//Not Connected
Thread.Sleep(REBOOT_MSWAIT_BETWEEN_OP);
}
catch (SshConnectionException e)
{
//Not Connected
Thread.Sleep(REBOOT_MSWAIT_BETWEEN_OP);
}
catch (SshException e)
{
//Not Connected
Thread.Sleep(REBOOT_MSWAIT_BETWEEN_OP);
}
catch (GatewayException e)
{
//Error
throw new GatewayException(e.Message);
}
catch (Exception e)
{
//Error
throw new GatewayException("Unknown Error during reboot: " + e);
}
} while (!connected);
_sshGateway.Disconnect();
}
#endregion
}
}