diff --git a/DesktopEdge/App.config b/DesktopEdge/App.config index 1451e8e03..5ffd8f8ee 100644 --- a/DesktopEdge/App.config +++ b/DesktopEdge/App.config @@ -1,6 +1,6 @@ - - - - - - \ No newline at end of file + + + + + + diff --git a/DesktopEdge/IdentityDetails.xaml.cs b/DesktopEdge/IdentityDetails.xaml.cs index 68635e7d4..e18d255f3 100644 --- a/DesktopEdge/IdentityDetails.xaml.cs +++ b/DesktopEdge/IdentityDetails.xaml.cs @@ -1,166 +1,162 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Data; -using System.Windows.Documents; -using System.Windows.Input; -using System.Windows.Media; -using System.Windows.Media.Imaging; -using System.Windows.Navigation; -using System.Windows.Shapes; -using ZitiDesktopEdge.Models; - -namespace ZitiDesktopEdge { - /// - /// Interaction logic for IdentityDetails.xaml - /// - public partial class IdentityDetails:UserControl { - - private bool _isAttached = true; - public delegate void Forgot(ZitiIdentity forgotten); - public event Forgot OnForgot; - public delegate void ErrorOccurred(string message); - public event ErrorOccurred OnError; - public delegate void Detched(MouseButtonEventArgs e); - public event Detched OnDetach; - public double MainHeight = 500; - - private List identities { - get { - return (List)Application.Current.Properties["Identities"]; - } - } - - private ZitiIdentity _identity; - - public ZitiIdentity Identity { - get { - return _identity; - } - set { - _identity = value; - this.IdDetailToggle.Enabled = _identity.IsEnabled; - UpdateView(); - IdentityArea.Opacity = 1.0; - IdentityArea.Visibility = Visibility.Visible; - this.Visibility = Visibility.Visible; - } - } - - public IdentityItem SelectedIdentity { get; set; } - - private void Window_MouseDown(object sender, MouseButtonEventArgs e) { - if (e.ChangedButton == MouseButton.Left) { - _isAttached = false; - OnDetach(e); - } - } - - - public bool IsAttached { - get { - return _isAttached; - } - set { - _isAttached = value; - if (_isAttached) { - Arrow.Visibility = Visibility.Visible; - ConfirmArrow.Visibility = Visibility.Visible; - } else { - Arrow.Visibility = Visibility.Collapsed; - ConfirmArrow.Visibility = Visibility.Collapsed; - } - } - } - - public void UpdateView() { - IdDetailName.Text = _identity.Name; - IdDetailName.ToolTip = _identity.Name; - IdDetailToggle.Enabled = _identity.IsEnabled; - IdentityName.Value = _identity.Name; - IdentityNetwork.Value = _identity.ControllerUrl; - IdentityEnrollment.Value = _identity.Status; - IdentityStatus.Value = _identity.IsEnabled ? "active" : "disabled"; - ServiceList.Children.Clear(); - if (_identity.Services.Count>0) { - for (int i = 0; i < _identity.Services.Count; i++) { - ServiceInfo editor = new ServiceInfo(); - editor.Label = _identity.Services[i].Name; - editor.Value = _identity.Services[i].Url; - editor.Warning = _identity.Services[i].Warning; - editor.IsLocked = true; - ServiceList.Children.Add(editor); - } - double newHeight = MainHeight - 300; - ServiceRow.Height = new GridLength((double)newHeight); - MainDetailScroll.MaxHeight = newHeight; - MainDetailScroll.Height = newHeight; - MainDetailScroll.Visibility = Visibility.Visible; - ServiceTitle.Content = _identity.Services.Count + " SERVICES"; - } else { - ServiceRow.Height = new GridLength((double)0.0); - MainDetailScroll.Visibility = Visibility.Collapsed; - ServiceTitle.Content = "NO SERVICES AVAILABLE"; - } - } - - private void IdToggle(bool on) { - ServiceClient.Client client = (ServiceClient.Client)Application.Current.Properties["ServiceClient"]; - client.IdentityOnOff(_identity.Fingerprint, on); - SelectedIdentity.ToggleSwitch.Enabled = on; - _identity.IsEnabled = on; - IdentityStatus.Value = _identity.IsEnabled ? "active" : "disabled"; - } - - public IdentityDetails() { - InitializeComponent(); - } - private void HideMenu(object sender, MouseButtonEventArgs e) { - this.Visibility = Visibility.Collapsed; - } - - public void SetHeight(double height) { - MainDetailScroll.Height = height; - } - - private void ForgetIdentity(object sender, MouseButtonEventArgs e) { - if (this.Visibility==Visibility.Visible) { - ConfirmView.Visibility = Visibility.Visible; - } - } - - private void CancelConfirmButton_Click(object sender, RoutedEventArgs e) { - ConfirmView.Visibility = Visibility.Collapsed; - } - - private void ConfirmButton_Click(object sender, RoutedEventArgs e) { - this.Visibility = Visibility.Collapsed; - ServiceClient.Client client = (ServiceClient.Client)Application.Current.Properties["ServiceClient"]; - try { - client.RemoveIdentity(_identity.Fingerprint); - - ZitiIdentity forgotten = new ZitiIdentity(); - foreach (var id in identities) { - if (id.Fingerprint == _identity.Fingerprint) { - forgotten = id; - identities.Remove(id); - break; - } - } - - if (OnForgot != null) { - OnForgot(forgotten); - } - } catch (ServiceClient.ServiceException se) { - OnError(se.Message); - } catch (Exception ex) { - OnError(ex.Message); - } - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using ZitiDesktopEdge.Models; +using ZitiDesktopEdge.ServiceClient; + +using NLog; + +namespace ZitiDesktopEdge { + /// + /// Interaction logic for IdentityDetails.xaml + /// + public partial class IdentityDetails:UserControl { + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + + private bool _isAttached = true; + public delegate void Forgot(ZitiIdentity forgotten); + public event Forgot OnForgot; + public delegate void ErrorOccurred(string message); + public event ErrorOccurred OnError; + public delegate void Detched(MouseButtonEventArgs e); + public event Detched OnDetach; + public double MainHeight = 500; + + private List identities { + get { + return (List)Application.Current.Properties["Identities"]; + } + } + + private ZitiIdentity _identity; + + public ZitiIdentity Identity { + get { + return _identity; + } + set { + _identity = value; + this.IdDetailToggle.Enabled = _identity.IsEnabled; + UpdateView(); + IdentityArea.Opacity = 1.0; + IdentityArea.Visibility = Visibility.Visible; + this.Visibility = Visibility.Visible; + } + } + + public IdentityItem SelectedIdentity { get; set; } + + private void Window_MouseDown(object sender, MouseButtonEventArgs e) { + if (e.ChangedButton == MouseButton.Left) { + _isAttached = false; + OnDetach(e); + } + } + + + public bool IsAttached { + get { + return _isAttached; + } + set { + _isAttached = value; + if (_isAttached) { + Arrow.Visibility = Visibility.Visible; + ConfirmArrow.Visibility = Visibility.Visible; + } else { + Arrow.Visibility = Visibility.Collapsed; + ConfirmArrow.Visibility = Visibility.Collapsed; + } + } + } + + public void UpdateView() { + IdDetailName.Text = _identity.Name; + IdDetailName.ToolTip = _identity.Name; + IdDetailToggle.Enabled = _identity.IsEnabled; + IdentityName.Value = _identity.Name; + IdentityNetwork.Value = _identity.ControllerUrl; + IdentityEnrollment.Value = _identity.Status; + IdentityStatus.Value = _identity.IsEnabled ? "active" : "disabled"; + ServiceList.Children.Clear(); + if (_identity.Services.Count>0) { + foreach(var zitiSvc in _identity.Services.OrderBy(s => s.Name.ToLower())) { + Logger.Debug("painting: " + zitiSvc.Name); + ServiceInfo editor = new ServiceInfo(); + editor.Label = zitiSvc.Name; + editor.Value = zitiSvc.Url; + editor.Warning = zitiSvc.Warning; + editor.IsLocked = true; + ServiceList.Children.Add(editor); + } + double newHeight = MainHeight - 300; + ServiceRow.Height = new GridLength((double)newHeight); + MainDetailScroll.MaxHeight = newHeight; + MainDetailScroll.Height = newHeight; + MainDetailScroll.Visibility = Visibility.Visible; + ServiceTitle.Content = _identity.Services.Count + " SERVICES"; + } else { + ServiceRow.Height = new GridLength((double)0.0); + MainDetailScroll.Visibility = Visibility.Collapsed; + ServiceTitle.Content = "NO SERVICES AVAILABLE"; + } + } + + async private void IdToggle(bool on) { + DataClient client = (DataClient)Application.Current.Properties["ServiceClient"]; + await client.IdentityOnOffAsync(_identity.Fingerprint, on); + SelectedIdentity.ToggleSwitch.Enabled = on; + _identity.IsEnabled = on; + IdentityStatus.Value = _identity.IsEnabled ? "active" : "disabled"; + } + + public IdentityDetails() { + InitializeComponent(); + } + private void HideMenu(object sender, MouseButtonEventArgs e) { + this.Visibility = Visibility.Collapsed; + } + + public void SetHeight(double height) { + MainDetailScroll.Height = height; + } + + private void ForgetIdentity(object sender, MouseButtonEventArgs e) { + if (this.Visibility==Visibility.Visible) { + ConfirmView.Visibility = Visibility.Visible; + } + } + + private void CancelConfirmButton_Click(object sender, RoutedEventArgs e) { + ConfirmView.Visibility = Visibility.Collapsed; + } + + async private void ConfirmButton_Click(object sender, RoutedEventArgs e) { + this.Visibility = Visibility.Collapsed; + DataClient client = (DataClient)Application.Current.Properties["ServiceClient"]; + try { + await client.RemoveIdentityAsync(_identity.Fingerprint); + + ZitiIdentity forgotten = new ZitiIdentity(); + foreach (var id in identities) { + if (id.Fingerprint == _identity.Fingerprint) { + forgotten = id; + identities.Remove(id); + break; + } + } + + if (OnForgot != null) { + OnForgot(forgotten); + } + } catch (DataStructures.ServiceException se) { + OnError(se.Message); + } catch (Exception ex) { + OnError(ex.Message); + } + } + } +} diff --git a/DesktopEdge/MainMenu.xaml.cs b/DesktopEdge/MainMenu.xaml.cs index 678c29e00..e9ebbd83e 100644 --- a/DesktopEdge/MainMenu.xaml.cs +++ b/DesktopEdge/MainMenu.xaml.cs @@ -1,323 +1,328 @@ -using System.Windows; -using System.Windows.Controls; -using System.Windows.Input; -using System.Diagnostics; -using System; -using System.Threading; -using System.Management.Automation; -using ZitiDesktopEdge.Models; -using System.Reflection; -using System.Web; -using System.Net.Mail; -using System.IO; -using System.Net; -using Newtonsoft.Json.Linq; -using System.Threading.Tasks; - -namespace ZitiDesktopEdge -{ - /// - /// Interaction logic for MainMenu.xaml - /// - public partial class MainMenu : UserControl { - - - public delegate void AttachementChanged(bool attached); - public event AttachementChanged OnAttachmentChange; - public delegate void LogLevelChanged(string level); - public event LogLevelChanged OnLogLevelChanged; - public delegate void Detched(MouseButtonEventArgs e); - public event Detched OnDetach; - public string menuState = "Main"; - public string licenseData = "it's open source."; - public string LogLevel = ""; - private string _updateUrl = "https://api.github.com/repos/openziti/desktop-edge-win/releases/latest"; - private string _downloadUrl = ""; - - public MainMenu() { - InitializeComponent(); - LicensesItems.Text = licenseData; - CheckUpdates(); - } - - private void HideMenu(object sender, MouseButtonEventArgs e) { - menuState = "Menu"; - UpdateState(); - MainMenuArea.Visibility = Visibility.Collapsed; - } - private void Window_MouseDown(object sender, MouseButtonEventArgs e) { - if (e.ChangedButton == MouseButton.Left) { - OnDetach(e); - } - } - - private void CloseApp(object sender, MouseButtonEventArgs e) { - Application.Current.Shutdown(); - } - - private void DoUpdate(object sender, MouseButtonEventArgs e) { - System.Diagnostics.Process.Start(_downloadUrl); - } - - private void ShowAbout(object sender, MouseButtonEventArgs e) { - menuState = "About"; - UpdateState(); - } - - private void ShowAdvanced(object sender, MouseButtonEventArgs e) { - menuState = "Advanced"; - UpdateState(); - } - private void ShowLicenses(object sender, MouseButtonEventArgs e) { - menuState = "Licenses"; - UpdateState(); - } - private void ShowConfig(object sender, MouseButtonEventArgs e) { - menuState = "Config"; - UpdateState(); - } - private void ShowLogs(object sender, MouseButtonEventArgs e) { - menuState = "Logs"; - UpdateState(); - } - private void ShowUILogs(object sender, MouseButtonEventArgs e) { - menuState = "UILogs"; - UpdateState(); - } - private void SetLogLevel(object sender, MouseButtonEventArgs e) { - menuState = "LogLevel"; - UpdateState(); - } - - private void CheckUpdates() { - try - { - HttpWebRequest httpWebRequest = WebRequest.CreateHttp(_updateUrl); - httpWebRequest.Method = "GET"; - httpWebRequest.ContentType = "application/json"; - httpWebRequest.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36"; - HttpWebResponse httpResponse = (HttpWebResponse)httpWebRequest.GetResponse(); - StreamReader streamReader = new StreamReader(httpResponse.GetResponseStream()); - string result = streamReader.ReadToEnd(); - JObject json = JObject.Parse(result); - string currentVersion = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString(); - string serverVersion = json.Property("tag_name").Value.ToString() + ".0"; - - Version installed = new Version(currentVersion); - Version published = new Version(serverVersion); - int compare = installed.CompareTo(published); - if (compare < 0) - { - UpdateAvailable.Content = "An Upgrade is available, click to download"; - UpdateAvailable.Visibility = Visibility.Visible; - } - else if (compare > 0) - { - UpdateAvailable.Content = "Your version is newer than the released version"; - UpdateAvailable.Visibility = Visibility.Visible; - } - JArray assets = JArray.Parse(json.Property("assets").Value.ToString()); - foreach (JObject asset in assets.Children()) - { - _downloadUrl = asset.Property("browser_download_url").Value.ToString(); - break; - } - } catch(Exception ex) - { - UpdateAvailable.Content = "An exception occurred while performing upgrade check"; - Debug.WriteLine("Error when checking for version: " + ex.Message); - UpdateAvailable.Visibility = Visibility.Visible; - } - } - - private void UpdateState() { - MainItems.Visibility = Visibility.Collapsed; - AboutItems.Visibility = Visibility.Collapsed; - MainItemsButton.Visibility = Visibility.Collapsed; - AboutItemsArea.Visibility = Visibility.Collapsed; - BackArrow.Visibility = Visibility.Collapsed; - AdvancedItems.Visibility = Visibility.Collapsed; - LicensesItems.Visibility = Visibility.Collapsed; - LogsItems.Visibility = Visibility.Collapsed; - ConfigItems.Visibility = Visibility.Collapsed; - LogLevelItems.Visibility = Visibility.Collapsed; - - if (menuState == "About") { - MenuTitle.Content = "About"; - AboutItemsArea.Visibility = Visibility.Visible; - AboutItems.Visibility = Visibility.Visible; - BackArrow.Visibility = Visibility.Visible; - - string version = ""; - try { - ServiceClient.TunnelStatus s = (ServiceClient.TunnelStatus)Application.Current.Properties["CurrentTunnelStatus"]; - version = $"{s.ServiceVersion.Version}@{s.ServiceVersion.Revision}"; - } catch (Exception e) { -#if DEBUG - Debug.WriteLine(e.ToString()); -#endif - } - - // Interface Version - VersionInfo.Content = "App: " + System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString()+" Service: "+ version; - - } else if (menuState=="Advanced") { - MenuTitle.Content = "Advanced Settings"; - AdvancedItems.Visibility = Visibility.Visible; - BackArrow.Visibility = Visibility.Visible; - } else if (menuState=="Licenses") { - MenuTitle.Content = "Third Party Licenses"; - LicensesItems.Visibility = Visibility.Visible; - BackArrow.Visibility = Visibility.Visible; - } else if (menuState=="Logs") { - ServiceClient.Client client = (ServiceClient.Client)Application.Current.Properties["ServiceClient"]; - MenuTitle.Content = "Service Logs"; - LogsItems.Text = client.GetLogs(); - LogsItems.Visibility = Visibility.Visible; - BackArrow.Visibility = Visibility.Visible; - } else if (menuState == "UILogs") { - MenuTitle.Content = "Application Logs"; - LogsItems.Text = UILog.GetLogs(); - LogsItems.Visibility = Visibility.Visible; - BackArrow.Visibility = Visibility.Visible; - } else if (menuState == "LogLevel") { - ResetLevels(); - - MenuTitle.Content = "Set Log Level"; - LogLevelItems.Visibility = Visibility.Visible; - BackArrow.Visibility = Visibility.Visible; - } else if (menuState=="Config") { - MenuTitle.Content = "Tunnel Configuration"; - ConfigItems.Visibility = Visibility.Visible; - BackArrow.Visibility = Visibility.Visible; - - ConfigIp.Value = Application.Current.Properties["ip"]?.ToString(); - ConfigSubnet.Value = Application.Current.Properties["subnet"]?.ToString(); - ConfigMtu.Value = Application.Current.Properties["mtu"]?.ToString(); - ConfigDns.Value = Application.Current.Properties["dns"]?.ToString(); - } else { - MenuTitle.Content = "Main Menu"; - MainItems.Visibility = Visibility.Visible; - MainItemsButton.Visibility = Visibility.Visible; - } - } - - private void GoBack(object sender, MouseButtonEventArgs e) { - if (menuState=="Config"||menuState=="Logs"||menuState=="UILogs") { - menuState = "Advanced"; - } else if (menuState=="Licenses") { - menuState = "About"; - } else { - menuState = "Menu"; - } - UpdateState(); - } - private void ShowPrivacy(object sender, MouseButtonEventArgs e) { - Process.Start(new ProcessStartInfo("https://netfoundry.io/privacy") { UseShellExecute = true }); - } - private void ShowTerms(object sender, MouseButtonEventArgs e) { - Process.Start(new ProcessStartInfo("https://netfoundry.io/terms") { UseShellExecute = true }); - } - private void ShowFeedback(object sender, MouseButtonEventArgs e) { - ServiceClient.Client client = (ServiceClient.Client)Application.Current.Properties["ServiceClient"]; - var mailMessage = new MailMessage(); - mailMessage.From = new MailAddress("ziti-support@netfoundry.io"); - mailMessage.Subject = "Ziti Support"; - mailMessage.IsBodyHtml = true; - mailMessage.Body = ""; - - string timestamp = DateTime.Now.ToFileTime().ToString(); - string serviceLogTempFile = Path.Combine(Path.GetTempPath(), timestamp+"-Ziti-Service.log"); - using (StreamWriter sw = new StreamWriter(serviceLogTempFile)) { - sw.WriteLine(client.GetLogs()); - } - - string uiLogTempFile = Path.Combine(Path.GetTempPath(), timestamp+"-Ziti-Application.log"); - using (StreamWriter sw = new StreamWriter(uiLogTempFile)) { - sw.WriteLine(UILog.GetLogs()); - } - - mailMessage.Attachments.Add(new Attachment(serviceLogTempFile)); - mailMessage.Attachments.Add(new Attachment(uiLogTempFile)); - - string emlFile = Path.Combine(Path.GetTempPath(), timestamp+"-ziti.eml"); - - using (var filestream = File.Open(emlFile, FileMode.Create)) { - var binaryWriter = new BinaryWriter(filestream); - binaryWriter.Write(System.Text.Encoding.UTF8.GetBytes("X-Unsent: 1" + Environment.NewLine)); - var assembly = typeof(SmtpClient).Assembly; - var mailWriterType = assembly.GetType("System.Net.Mail.MailWriter"); - var mailWriterContructor = mailWriterType.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, new[] { typeof(Stream) }, null); - var mailWriter = mailWriterContructor.Invoke(new object[] { filestream }); - var sendMethod = typeof(MailMessage).GetMethod("Send", BindingFlags.Instance | BindingFlags.NonPublic); - sendMethod.Invoke(mailMessage, BindingFlags.Instance | BindingFlags.NonPublic, null, new object[] { mailWriter, true, true }, null); - var closeMethod = mailWriter.GetType().GetMethod("Close", BindingFlags.Instance | BindingFlags.NonPublic); - closeMethod.Invoke(mailWriter, BindingFlags.Instance | BindingFlags.NonPublic, null, new object[] { }, null); - } - - Process.Start(emlFile); - - - - //string body = HttpUtility.UrlEncode("\n\nService Logs\n\n" + client.GetLogs());// + "\n\nApplication Logs\n\n" + UILog.GetLogs()); - //Process.Start(new ProcessStartInfo("mailto:ziti-support@netfoundry.io?subject=Ziti%20Support&body="+body) { UseShellExecute = true }); - } - private void ShowSupport(object sender, MouseButtonEventArgs e) { - Process.Start(new ProcessStartInfo("https://openziti.discourse.group/") { UseShellExecute = true }); - } - - private void DetachWindow(object sender, MouseButtonEventArgs e) { - Application.Current.MainWindow.ShowInTaskbar = true; - DetachButton.Visibility = Visibility.Collapsed; - AttachButton.Visibility = Visibility.Visible; - Arrow.Visibility = Visibility.Collapsed; - if (OnAttachmentChange != null) { - OnAttachmentChange(false); - } - MainMenuArea.Visibility = Visibility.Collapsed; - } - - public void Detach() { - Application.Current.MainWindow.ShowInTaskbar = true; - DetachButton.Visibility = Visibility.Collapsed; - AttachButton.Visibility = Visibility.Visible; - Arrow.Visibility = Visibility.Collapsed; - } - private void RetachWindow(object sender, MouseButtonEventArgs e) { - Application.Current.MainWindow.ShowInTaskbar = false; - DetachButton.Visibility = Visibility.Visible; - AttachButton.Visibility = Visibility.Collapsed; - Arrow.Visibility = Visibility.Visible; - if (OnAttachmentChange != null) { - OnAttachmentChange(true); - } - } - - private void ResetLevels() { - if (this.LogLevel == "") this.LogLevel = "error"; - LogVerbose.IsSelected = false; - LogDebug.IsSelected = false; - LogInfo.IsSelected = false; - LogError.IsSelected = false; - LogFatal.IsSelected = false; - LogWarn.IsSelected = false; - LogTrace.IsSelected = false; - if (this.LogLevel == "verbose") LogVerbose.IsSelected = true; - else if (this.LogLevel == "debug") LogDebug.IsSelected = true; - else if (this.LogLevel == "info") LogInfo.IsSelected = true; - else if (this.LogLevel == "error") LogError.IsSelected = true; - else if (this.LogLevel == "fatal") LogFatal.IsSelected = true; - else if (this.LogLevel == "warn") LogWarn.IsSelected = true; - else if (this.LogLevel == "trace") LogTrace.IsSelected = true; - } - - private void SetLevel(object sender, MouseButtonEventArgs e) { - SubOptionItem item = (SubOptionItem)sender; - this.LogLevel = item.Label.ToLower(); - if (OnLogLevelChanged != null) { - OnLogLevelChanged(this.LogLevel); - } - ResetLevels(); - } - } -} +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Diagnostics; +using System; +using System.Threading; +using ZitiDesktopEdge.Models; +using System.Reflection; +using System.Web; +using System.Net.Mail; +using System.IO; +using System.Net; +using Newtonsoft.Json.Linq; +using ZitiDesktopEdge.ServiceClient; + +using NLog; +using NLog.Config; +using NLog.Targets; + +using ZitiDesktopEdge.Server; + +namespace ZitiDesktopEdge +{ + /// + /// Interaction logic for MainMenu.xaml + /// + public partial class MainMenu : UserControl { + + + public delegate void AttachementChanged(bool attached); + public event AttachementChanged OnAttachmentChange; + public delegate void LogLevelChanged(string level); + public event LogLevelChanged OnLogLevelChanged; + public delegate void Detched(MouseButtonEventArgs e); + public event Detched OnDetach; + public string menuState = "Main"; + public string licenseData = "it's open source."; + public string LogLevel = ""; + private string _updateUrl = "https://api.github.com/repos/openziti/desktop-edge-win/releases/latest"; + private string _downloadUrl = ""; + + public MainMenu() { + InitializeComponent(); + LicensesItems.Text = licenseData; + CheckUpdates(); + } + + private void HideMenu(object sender, MouseButtonEventArgs e) { + menuState = "Menu"; + UpdateState(); + MainMenuArea.Visibility = Visibility.Collapsed; + } + private void Window_MouseDown(object sender, MouseButtonEventArgs e) { + if (e.ChangedButton == MouseButton.Left) { + OnDetach(e); + } + } + + private void CloseApp(object sender, MouseButtonEventArgs e) { + Application.Current.Shutdown(); + } + + private void DoUpdate(object sender, MouseButtonEventArgs e) { + System.Diagnostics.Process.Start(_downloadUrl); + } + + private void ShowAbout(object sender, MouseButtonEventArgs e) { + menuState = "About"; + UpdateState(); + } + + private void ShowAdvanced(object sender, MouseButtonEventArgs e) { + menuState = "Advanced"; + UpdateState(); + } + private void ShowLicenses(object sender, MouseButtonEventArgs e) { + menuState = "Licenses"; + UpdateState(); + } + private void ShowConfig(object sender, MouseButtonEventArgs e) { + menuState = "Config"; + UpdateState(); + } + private void ShowLogs(object sender, MouseButtonEventArgs e) { + menuState = "Logs"; + UpdateState(); + } + private void ShowUILogs(object sender, MouseButtonEventArgs e) { + menuState = "UILogs"; + UpdateState(); + } + private void SetLogLevel(object sender, MouseButtonEventArgs e) { + menuState = "LogLevel"; + UpdateState(); + } + + private void CheckUpdates() { + try + { + HttpWebRequest httpWebRequest = WebRequest.CreateHttp(_updateUrl); + httpWebRequest.Method = "GET"; + httpWebRequest.ContentType = "application/json"; + httpWebRequest.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36"; + HttpWebResponse httpResponse = (HttpWebResponse)httpWebRequest.GetResponse(); + StreamReader streamReader = new StreamReader(httpResponse.GetResponseStream()); + string result = streamReader.ReadToEnd(); + JObject json = JObject.Parse(result); + string currentVersion = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString(); + string serverVersion = json.Property("tag_name").Value.ToString() + ".0"; + + Version installed = new Version(currentVersion); + Version published = new Version(serverVersion); + int compare = installed.CompareTo(published); + if (compare < 0) + { + UpdateAvailable.Content = "An Upgrade is available, click to download"; + UpdateAvailable.Visibility = Visibility.Visible; + } + else if (compare > 0) + { + UpdateAvailable.Content = "Your version is newer than the released version"; + UpdateAvailable.Visibility = Visibility.Visible; + } + JArray assets = JArray.Parse(json.Property("assets").Value.ToString()); + foreach (JObject asset in assets.Children()) + { + _downloadUrl = asset.Property("browser_download_url").Value.ToString(); + break; + } + } catch(Exception ex) + { + UpdateAvailable.Content = "An exception occurred while performing upgrade check"; + Debug.WriteLine("Error when checking for version: " + ex.Message); + UpdateAvailable.Visibility = Visibility.Visible; + } + } + + private void UpdateState() { + MainItems.Visibility = Visibility.Collapsed; + AboutItems.Visibility = Visibility.Collapsed; + MainItemsButton.Visibility = Visibility.Collapsed; + AboutItemsArea.Visibility = Visibility.Collapsed; + BackArrow.Visibility = Visibility.Collapsed; + AdvancedItems.Visibility = Visibility.Collapsed; + LicensesItems.Visibility = Visibility.Collapsed; + LogsItems.Visibility = Visibility.Collapsed; + ConfigItems.Visibility = Visibility.Collapsed; + LogLevelItems.Visibility = Visibility.Collapsed; + + if (menuState == "About") { + MenuTitle.Content = "About"; + AboutItemsArea.Visibility = Visibility.Visible; + AboutItems.Visibility = Visibility.Visible; + BackArrow.Visibility = Visibility.Visible; + + string version = ""; + try { + DataStructures.TunnelStatus s = (DataStructures.TunnelStatus)Application.Current.Properties["CurrentTunnelStatus"]; + version = $"{s.ServiceVersion.Version}@{s.ServiceVersion.Revision}"; + } catch (Exception e) { +#if DEBUG + Debug.WriteLine(e.ToString()); +#endif + } + + // Interface Version + VersionInfo.Content = "App: " + System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString()+" Service: "+ version; + + } else if (menuState=="Advanced") { + MenuTitle.Content = "Advanced Settings"; + AdvancedItems.Visibility = Visibility.Visible; + BackArrow.Visibility = Visibility.Visible; + } else if (menuState=="Licenses") { + MenuTitle.Content = "Third Party Licenses"; + LicensesItems.Visibility = Visibility.Visible; + BackArrow.Visibility = Visibility.Visible; + } else if (menuState=="Logs") { + DataClient client = (DataClient)Application.Current.Properties["ServiceClient"]; + MenuTitle.Content = "Service Logs"; + LogsItems.Text = client.GetLogs(); + LogsItems.Visibility = Visibility.Visible; + BackArrow.Visibility = Visibility.Visible; + } else if (menuState == "UILogs") { + MenuTitle.Content = "Application Logs"; + LogsItems.Text = UILog.GetLogs(); + LogsItems.Visibility = Visibility.Visible; + BackArrow.Visibility = Visibility.Visible; + } else if (menuState == "LogLevel") { + ResetLevels(); + + MenuTitle.Content = "Set Log Level"; + LogLevelItems.Visibility = Visibility.Visible; + BackArrow.Visibility = Visibility.Visible; + } else if (menuState=="Config") { + MenuTitle.Content = "Tunnel Configuration"; + ConfigItems.Visibility = Visibility.Visible; + BackArrow.Visibility = Visibility.Visible; + + ConfigIp.Value = Application.Current.Properties["ip"]?.ToString(); + ConfigSubnet.Value = Application.Current.Properties["subnet"]?.ToString(); + ConfigMtu.Value = Application.Current.Properties["mtu"]?.ToString(); + ConfigDns.Value = Application.Current.Properties["dns"]?.ToString(); + } else { + MenuTitle.Content = "Main Menu"; + MainItems.Visibility = Visibility.Visible; + MainItemsButton.Visibility = Visibility.Visible; + } + } + + private void GoBack(object sender, MouseButtonEventArgs e) { + if (menuState=="Config"||menuState=="Logs"||menuState=="UILogs") { + menuState = "Advanced"; + } else if (menuState=="Licenses") { + menuState = "About"; + } else { + menuState = "Menu"; + } + UpdateState(); + } + private void ShowPrivacy(object sender, MouseButtonEventArgs e) { + Process.Start(new ProcessStartInfo("https://netfoundry.io/privacy") { UseShellExecute = true }); + } + private void ShowTerms(object sender, MouseButtonEventArgs e) { + Process.Start(new ProcessStartInfo("https://netfoundry.io/terms") { UseShellExecute = true }); + } + private void ShowFeedback(object sender, MouseButtonEventArgs e) { + DataClient client = (DataClient)Application.Current.Properties["ServiceClient"]; + var mailMessage = new MailMessage(); + mailMessage.From = new MailAddress("ziti-support@netfoundry.io"); + mailMessage.Subject = "Ziti Support"; + mailMessage.IsBodyHtml = true; + mailMessage.Body = ""; + + string timestamp = DateTime.Now.ToFileTime().ToString(); + string serviceLogTempFile = Path.Combine(Path.GetTempPath(), timestamp+"-Ziti-Service.log"); + using (StreamWriter sw = new StreamWriter(serviceLogTempFile)) { + sw.WriteLine(client.GetLogs()); + } + + string uiLogTempFile = Path.Combine(Path.GetTempPath(), timestamp+"-Ziti-Application.log"); + using (StreamWriter sw = new StreamWriter(uiLogTempFile)) { + sw.WriteLine(UILog.GetLogs()); + } + + mailMessage.Attachments.Add(new Attachment(serviceLogTempFile)); + mailMessage.Attachments.Add(new Attachment(uiLogTempFile)); + + string emlFile = Path.Combine(Path.GetTempPath(), timestamp+"-ziti.eml"); + + using (var filestream = File.Open(emlFile, FileMode.Create)) { + var binaryWriter = new BinaryWriter(filestream); + binaryWriter.Write(System.Text.Encoding.UTF8.GetBytes("X-Unsent: 1" + Environment.NewLine)); + var assembly = typeof(SmtpClient).Assembly; + var mailWriterType = assembly.GetType("System.Net.Mail.MailWriter"); + var mailWriterContructor = mailWriterType.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, new[] { typeof(Stream) }, null); + var mailWriter = mailWriterContructor.Invoke(new object[] { filestream }); + var sendMethod = typeof(MailMessage).GetMethod("Send", BindingFlags.Instance | BindingFlags.NonPublic); + sendMethod.Invoke(mailMessage, BindingFlags.Instance | BindingFlags.NonPublic, null, new object[] { mailWriter, true, true }, null); + var closeMethod = mailWriter.GetType().GetMethod("Close", BindingFlags.Instance | BindingFlags.NonPublic); + closeMethod.Invoke(mailWriter, BindingFlags.Instance | BindingFlags.NonPublic, null, new object[] { }, null); + } + + Process.Start(emlFile); + + + + //string body = HttpUtility.UrlEncode("\n\nService Logs\n\n" + client.GetLogs());// + "\n\nApplication Logs\n\n" + UILog.GetLogs()); + //Process.Start(new ProcessStartInfo("mailto:ziti-support@netfoundry.io?subject=Ziti%20Support&body="+body) { UseShellExecute = true }); + } + private void ShowSupport(object sender, MouseButtonEventArgs e) { + Process.Start(new ProcessStartInfo("https://openziti.discourse.group/") { UseShellExecute = true }); + } + + private void DetachWindow(object sender, MouseButtonEventArgs e) { + Application.Current.MainWindow.ShowInTaskbar = true; + DetachButton.Visibility = Visibility.Collapsed; + AttachButton.Visibility = Visibility.Visible; + Arrow.Visibility = Visibility.Collapsed; + if (OnAttachmentChange != null) { + OnAttachmentChange(false); + } + MainMenuArea.Visibility = Visibility.Collapsed; + } + + public void Detach() { + Application.Current.MainWindow.ShowInTaskbar = true; + DetachButton.Visibility = Visibility.Collapsed; + AttachButton.Visibility = Visibility.Visible; + Arrow.Visibility = Visibility.Collapsed; + } + private void RetachWindow(object sender, MouseButtonEventArgs e) { + Application.Current.MainWindow.ShowInTaskbar = false; + DetachButton.Visibility = Visibility.Visible; + AttachButton.Visibility = Visibility.Collapsed; + Arrow.Visibility = Visibility.Visible; + if (OnAttachmentChange != null) { + OnAttachmentChange(true); + } + } + + private void ResetLevels() { + if (this.LogLevel == "") this.LogLevel = "error"; + LogVerbose.IsSelected = false; + LogDebug.IsSelected = false; + LogInfo.IsSelected = false; + LogError.IsSelected = false; + LogFatal.IsSelected = false; + LogWarn.IsSelected = false; + LogTrace.IsSelected = false; + if (this.LogLevel == "verbose") LogVerbose.IsSelected = true; + else if (this.LogLevel == "debug") LogDebug.IsSelected = true; + else if (this.LogLevel == "info") LogInfo.IsSelected = true; + else if (this.LogLevel == "error") LogError.IsSelected = true; + else if (this.LogLevel == "fatal") LogFatal.IsSelected = true; + else if (this.LogLevel == "warn") LogWarn.IsSelected = true; + else if (this.LogLevel == "trace") LogTrace.IsSelected = true; + } + + private void SetLevel(object sender, MouseButtonEventArgs e) { + SubOptionItem item = (SubOptionItem)sender; + this.LogLevel = item.Label.ToLower(); + if (OnLogLevelChanged != null) { + OnLogLevelChanged(this.LogLevel); + } + ResetLevels(); + } + } +} diff --git a/DesktopEdge/MainWindow.xaml b/DesktopEdge/MainWindow.xaml index 42c7e442e..8225e8575 100644 --- a/DesktopEdge/MainWindow.xaml +++ b/DesktopEdge/MainWindow.xaml @@ -1,323 +1,323 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DesktopEdge/MainWindow.xaml.cs b/DesktopEdge/MainWindow.xaml.cs index 07387ee49..a87807ab9 100644 --- a/DesktopEdge/MainWindow.xaml.cs +++ b/DesktopEdge/MainWindow.xaml.cs @@ -3,27 +3,36 @@ using System.Windows; using System.Windows.Input; using System.IO; -using ZitiDesktopEdge.Models; -using ZitiDesktopEdge.ServiceClient; using System.ServiceProcess; using System.Linq; using System.Diagnostics; using System.Windows.Controls; using System.Drawing; +using System.Threading; +using System.Threading.Tasks; + +using ZitiDesktopEdge.Models; +using ZitiDesktopEdge.DataStructures; +using ZitiDesktopEdge.ServiceClient; +using NLog; +using NLog.Config; +using NLog.Targets; namespace ZitiDesktopEdge { /// /// Interaction logic for MainWindow.xaml /// - public partial class MainWindow:Window { + public partial class MainWindow : Window { + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); public System.Windows.Forms.NotifyIcon notifyIcon; public string Position = "Bottom"; private DateTime _startDate; - private System.Windows.Forms.Timer _timer; - private Client serviceClient = null; + private System.Windows.Forms.Timer _tunnelUptimeTimer; + private DataClient serviceClient = null; + MonitorClient monitorClient = null; private bool _isAttached = true; private bool _isServiceInError = false; private int _right = 75; @@ -32,32 +41,47 @@ public partial class MainWindow:Window { private double _maxHeight = 800d; private string[] suffixes = { "Bps", "kBps", "mBps", "gBps", "tBps", "pBps" }; + private static SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1, 1); + private List identities { get { return (List)Application.Current.Properties["Identities"]; } } - private void LaunchOrInstall() { - ServiceController ctl = ServiceController.GetServices().FirstOrDefault(s => s.ServiceName=="ziti"); - if (ctl==null) { - SetCantDisplay(); - } else { - if (ctl.Status!=ServiceControllerStatus.Running) { - try { - ctl.Start(); - } catch (Exception e) { - UILog.Log(e.Message); - SetCantDisplay(); - } - } - } - } - - private List services = new List(); public MainWindow() { InitializeComponent(); + var asm = System.Reflection.Assembly.GetExecutingAssembly(); + var logname = asm.GetName().Name; + + var curdir = Path.GetDirectoryName(asm.Location); + string nlogFile = Path.Combine(curdir, logname + ".log.config"); + + if (File.Exists(nlogFile)) { + LogManager.Configuration = new XmlLoggingConfiguration(nlogFile); + } else { + var config = new LoggingConfiguration(); + // Targets where to log to: File and Console + var logfile = new FileTarget("logfile") { + FileName = $"logs\\UI\\{logname}.log", + ArchiveEvery = FileArchivePeriod.Day, + ArchiveNumbering = ArchiveNumberingMode.Rolling, + MaxArchiveFiles = 7, + Layout = "${longdate}|${level:uppercase=true:padding=5}|${logger}|${message}", + //ArchiveAboveSize = 10000, + }; + var logconsole = new ConsoleTarget("logconsole"); + + // Rules for mapping loggers to targets + config.AddRule(LogLevel.Debug, LogLevel.Fatal, logconsole); + config.AddRule(LogLevel.Debug, LogLevel.Fatal, logfile); + + // Apply config + LogManager.Configuration = config; + } + Logger.Info("service started - logger initialized"); + App.Current.MainWindow.WindowState = WindowState.Normal; App.Current.MainWindow.Closing += MainWindow_Closing; App.Current.MainWindow.Deactivated += MainWindow_Deactivated; @@ -69,8 +93,6 @@ public MainWindow() { IdentityMenu.OnDetach += OnDetach; MainMenu.OnDetach += OnDetach; - LaunchOrInstall(); - SetNotifyIcon("white"); } @@ -105,24 +127,33 @@ private void MainWindow_Deactivated(object sender, EventArgs e) { private void MainWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e) { notifyIcon.Visible = false; - notifyIcon.Icon.Dispose(); - notifyIcon.Dispose(); + //notifyIcon.Icon.Dispose(); + //notifyIcon.Dispose(); } - - private void SetCantDisplay(string msg, string detailMessage) { + + private void SetCantDisplay(string title, string detailMessage, Visibility closeButtonVisibility) { NoServiceView.Visibility = Visibility.Visible; - ErrorMsg.Content = msg; + CloseErrorButton.IsEnabled = true; + CloseErrorButton.Visibility = closeButtonVisibility; + ErrorMsg.Content = title; ErrorMsgDetail.Content = detailMessage; SetNotifyIcon("red"); _isServiceInError = true; UpdateServiceView(); } - private void SetCantDisplay() { - SetCantDisplay("Service Not Started", "Start the Ziti Tunnel Service to get started"); - } + +// private void SetCantDisplay(string msg) { +// //SetCantDisplay("Service Not Started", msg, Visibility.Visible); +// ShowServiceNotStarted(); +// } private void TargetNotifyIcon_Click(object sender, EventArgs e) { this.Show(); + System.Windows.Forms.MouseEventArgs mea = (System.Windows.Forms.MouseEventArgs)e; + /*if (mea.cli mea.RightButton) { + } else { + + }*/ this.Activate(); } @@ -148,9 +179,9 @@ private void UpdateServiceView() { } } - private void MainWindow_Loaded(object sender, RoutedEventArgs e) { + async private void MainWindow_Loaded(object sender, RoutedEventArgs e) { // add a new service client - serviceClient = new Client(); + serviceClient = new DataClient(); serviceClient.OnClientConnected += ServiceClient_OnClientConnected; serviceClient.OnClientDisconnected += ServiceClient_OnClientDisconnected; serviceClient.OnIdentityEvent += ServiceClient_OnIdentityEvent; @@ -158,6 +189,10 @@ private void MainWindow_Loaded(object sender, RoutedEventArgs e) { serviceClient.OnServiceEvent += ServiceClient_OnServiceEvent; serviceClient.OnTunnelStatusEvent += ServiceClient_OnTunnelStatusEvent; + monitorClient = new MonitorClient(); + monitorClient.OnClientConnected += MonitorClient_OnClientConnected; + monitorClient.OnServiceStatusEvent += MonitorClient_OnServiceStatusEvent; + Application.Current.Properties.Add("ServiceClient", serviceClient); Application.Current.Properties.Add("Identities", new List()); MainMenu.OnAttachmentChange += AttachmentChanged; @@ -165,19 +200,135 @@ private void MainWindow_Loaded(object sender, RoutedEventArgs e) { IdentityMenu.OnError += IdentityMenu_OnError; try { - serviceClient.Connect(); - //var s = serviceClient.GetStatus(); - //LoadStatusFromService(s.Status); + await serviceClient.ConnectAsync(); + await serviceClient.WaitForConnectionAsync(); } catch /*ignored for now (Exception ex) */{ - SetCantDisplay(); + ShowServiceNotStarted(); serviceClient.Reconnect(); } + + try { + await monitorClient.ConnectAsync(); + await monitorClient.WaitForConnectionAsync(); + } catch /*ignored for now (Exception ex) */{ + monitorClient.Reconnect(); + } + IdentityMenu.OnForgot += IdentityForgotten; Placement(); } + private void MonitorClient_OnServiceStatusEvent(object sender, ServiceStatusEvent evt) { + Debug.WriteLine("MonitorClient_OnServiceStatusEvent"); + ServiceControllerStatus status = (ServiceControllerStatus)Enum.Parse(typeof(ServiceControllerStatus), evt.Status); + + switch (status) { + case ServiceControllerStatus.Running: + Logger.Info("Service is started"); + break; + case ServiceControllerStatus.Stopped: + Logger.Info("Service is stopped"); + ShowServiceNotStarted(); + break; + case ServiceControllerStatus.StopPending: + Logger.Info("Service is stopping..."); + this.Dispatcher.Invoke(async () => { + SetCantDisplay("The Service is Stopping", "Please wait while the service stops", Visibility.Hidden); + await WaitForServiceToStop(DateTime.Now + TimeSpan.FromSeconds(3)); + }); + break; + case ServiceControllerStatus.StartPending: + Logger.Info("Service is starting..."); + break; + case ServiceControllerStatus.PausePending: + Logger.Warn("UNEXPECTED STATUS: PausePending"); + break; + case ServiceControllerStatus.Paused: + Logger.Warn("UNEXPECTED STATUS: Paused"); + break; + default: + Logger.Warn("UNEXPECTED STATUS: {0}", evt.Status); + break; + } + } + + async private Task WaitForServiceToStop(DateTime until) { + //continually poll for the service to stop. If it is stuck - ask the user if they want to try to force + //close the service + while (DateTime.Now < until) { + await Task.Delay(2000); + ServiceStatusEvent resp = await monitorClient.Status(); + if (resp.IsStopped()) { + // good - that's what we are waiting for... + return; + } else { + // bad - not stopped yet... + Logger.Debug("Waiting for service to stop... Still not stopped yet"); + } + } + // real bad - means it's stuck probably. Ask the user if they want to try to force it... + Logger.Warn("Waiting for service to stop... Service did not reach stopped state in the expected amount of time."); + SetCantDisplay("The Service Appears Stuck", "Would you like to try to force close the service?", Visibility.Visible); + CloseErrorButton.Content = "Force Quit"; + CloseErrorButton.Click -= CloseError; + CloseErrorButton.Click += ForceQuitButtonClick; + } + + async private void ForceQuitButtonClick(object sender, RoutedEventArgs e) { + ServiceStatusEvent status = await monitorClient.ForceTerminate(); + if(status.IsStopped()) { + //good + CloseErrorButton.Click += CloseError; //reset the close button... + CloseErrorButton.Click -= ForceQuitButtonClick; + } else { + //bad... + SetCantDisplay("The Service Is Still Running", "Current status is: " + status.Status, Visibility.Visible); + } + } + + async private void StartZitiService(object sender, RoutedEventArgs e) { + try { + ShowLoad("Starting", "Staring the data service"); + Logger.Info("StartZitiService"); + var r = await monitorClient.StartServiceAsync(); + if (r.Code != 0) { + Logger.Debug("ERROR: {0} : {1}", r.Message, r.Error); + } else { + Logger.Info("Service started!"); + startZitiButtonVisible = false; + CloseErrorButton.Click -= StartZitiService; + CloseError(null, null); + } + } catch(Exception ex){ + Logger.Info(ex, "UNEXPECTED ERROR!"); + startZitiButtonVisible = false; + CloseErrorButton.Click += StartZitiService; + CloseErrorButton.IsEnabled = true; + } + CloseErrorButton.IsEnabled = true; + HideLoad(); + } + + bool startZitiButtonVisible = false; + private void ShowServiceNotStarted() { + semaphoreSlim.Wait(); //make sure the event is only added to the button once + CloseErrorButton.Click -= CloseError; + if (!startZitiButtonVisible) { + CloseErrorButton.Content = "Start Service"; + startZitiButtonVisible = true; + CloseErrorButton.Click += StartZitiService; + } + semaphoreSlim.Release(); + SetCantDisplay("Service Not Started", "Do you want to start the data service now?", Visibility.Visible); + } + + + private void MonitorClient_OnClientConnected(object sender, object e) { + Debug.WriteLine("MonitorClient_OnClientConnected"); + } + private void LogLevelChanged(string level) { - serviceClient.SetLogLevel(level); + serviceClient.SetLogLevelAsync(level).Wait(); } private void IdentityMenu_OnError(string message) { @@ -198,7 +349,11 @@ private void ServiceClient_OnClientConnected(object sender, object e) { private void ServiceClient_OnClientDisconnected(object sender, object e) { this.Dispatcher.Invoke(() => { IdList.Children.Clear(); - SetCantDisplay(); + if (e != null) { + Logger.Debug(e.ToString()); + } + //SetCantDisplay("Start the Ziti Tunnel Service to continue"); + ShowServiceNotStarted(); }); } @@ -256,7 +411,7 @@ public void SetSpeed(decimal bytes, Label speed, Label speedLabel) { private void ServiceClient_OnServiceEvent(object sender, ServiceEvent e) { if (e == null) return; - + Debug.WriteLine($"==== ServiceEvent : action:{e.Action} fingerprint:{e.Fingerprint} name:{e.Service.Name} "); this.Dispatcher.Invoke(() => { var found = identities.Find(id => id.Fingerprint == e.Fingerprint); @@ -269,8 +424,11 @@ private void ServiceClient_OnServiceEvent(object sender, ServiceEvent e) { if (e.Action == "added") { ZitiService zs = new ZitiService(e.Service); var svc = found.Services.Find(s => s.Name == zs.Name); - if (svc == null) found.Services.Add(zs); - else Debug.WriteLine("the service named " + zs.Name + " is already accounted for on this identity."); + if (svc == null) { + found.Services.Add(zs); + } else { + Debug.WriteLine("the service named " + zs.Name + " is already accounted for on this identity."); + } } else { Debug.WriteLine("removing the service named: " + e.Service.Name); found.Services.RemoveAll(s => s.Name == e.Service.Name); @@ -286,13 +444,14 @@ private void ServiceClient_OnServiceEvent(object sender, ServiceEvent e) { private void ServiceClient_OnTunnelStatusEvent(object sender, TunnelStatusEvent e) { if (e == null) return; //just skip it for now... Debug.WriteLine($"==== TunnelStatusEvent: "); + Application.Current.Properties.Remove("CurrentTunnelStatus"); Application.Current.Properties.Add("CurrentTunnelStatus", e.Status); e.Status.Dump(Console.Out); this.Dispatcher.Invoke(() => { - if(e.ApiVersion != Client.EXPECTED_API_VERSION) { - SetCantDisplay("Version mismatch!", "The version of the Service is not compatible"); + if (e.ApiVersion != DataClient.EXPECTED_API_VERSION) { + SetCantDisplay("Version mismatch!", "The version of the Service is not compatible", Visibility.Visible); return; - } + } this.MainMenu.LogLevel = e.Status.LogLevel; InitializeTimer((int)e.Status.Duration); LoadStatusFromService(e.Status); @@ -362,7 +521,8 @@ private void LoadStatusFromService(TunnelStatus status) { } LoadIdentities(true); } else { - SetCantDisplay(); + //SetCantDisplay("Start the Ziti Tunnel Service to continue"); + ShowServiceNotStarted(); } } @@ -384,17 +544,17 @@ private void SetNotifyIcon(string iconPrefix) { Application.Current.MainWindow.Icon = System.Windows.Media.Imaging.BitmapFrame.Create(iconUri); } - private void LoadIdentities(Boolean repaint) { + private void LoadIdentities(Boolean repaint) { IdList.Children.Clear(); IdList.Height = 0; - IdList.MaxHeight = _maxHeight-520; - ZitiIdentity[] ids = identities.ToArray(); + IdList.MaxHeight = _maxHeight - 520; + ZitiIdentity[] ids = identities.OrderBy(i => i.Name.ToLower()).ToArray(); double height = 490 + (ids.Length * 60); if (height > _maxHeight) height = _maxHeight; this.Height = height; - IdentityMenu.SetHeight(this.Height-160); + IdentityMenu.SetHeight(this.Height - 160); bool isActive = false; - for (int i=0; i9)?hours.ToString():"0"+hours; - var minutesString = (minutes>9)? minutes.ToString():"0"+minutes; - var secondsString = (seconds>9) ? seconds.ToString() : "0"+seconds; - ConnectedTime.Content = hoursString+":"+minutesString+":"+secondsString; + var hoursString = (hours > 9) ? hours.ToString() : "0" + hours; + var minutesString = (minutes > 9) ? minutes.ToString() : "0" + minutes; + var secondsString = (seconds > 9) ? seconds.ToString() : "0" + seconds; + ConnectedTime.Content = hoursString + ":" + minutesString + ":" + secondsString; } private void InitializeTimer(int millisAgoStarted) { - _startDate = DateTime.Now.Subtract(new TimeSpan(0,0,0,0, millisAgoStarted)); - _timer = new System.Windows.Forms.Timer(); - _timer.Interval = 100; - _timer.Tick += OnTimedEvent; - _timer.Enabled = true; - _timer.Start(); - } - private void Connect(object sender, RoutedEventArgs e) { + _startDate = DateTime.Now.Subtract(new TimeSpan(0, 0, 0, 0, millisAgoStarted)); + _tunnelUptimeTimer = new System.Windows.Forms.Timer(); + _tunnelUptimeTimer.Interval = 100; + _tunnelUptimeTimer.Tick += OnTimedEvent; + _tunnelUptimeTimer.Enabled = true; + _tunnelUptimeTimer.Start(); + } + private void ConnectButtonClick(object sender, RoutedEventArgs e) { if (!_isServiceInError) { - ShowLoad(); - this.Dispatcher.Invoke(() => { + ShowLoad("Starting Service", "Please wait while the service is started..."); + this.Dispatcher.Invoke(async () => { //Dispatcher.Invoke(new Action(() => { }), System.Windows.Threading.DispatcherPriority.ContextIdle); - DoConnect(); + await DoConnectAsync(); HideLoad(); }); } } - private void DoConnect() { + async private Task DoConnectAsync() { try { serviceClient.SetTunnelState(true); SetNotifyIcon("green"); @@ -574,7 +733,7 @@ private void DoConnect() { DisconnectButton.Visibility = Visibility.Visible; for (int i = 0; i < identities.Count; i++) { - serviceClient.IdentityOnOff(identities[i].Fingerprint, true); + await serviceClient.IdentityOnOffAsync(identities[i].Fingerprint, true); } for (int i = 0; i < IdList.Children.Count; i++) { IdentityItem item = IdList.Children[i] as IdentityItem; @@ -582,39 +741,52 @@ private void DoConnect() { item.RefreshUI(); } } catch (ServiceException se) { - ShowError("Error Occurred", se.Message+" "+se.AdditionalInfo); + ShowError("Error Occurred", se.Message + " " + se.AdditionalInfo); } catch (Exception ex) { ShowError("Unexpected Error", "Code 3:" + ex.Message); } } - private void Disconnect(object sender, RoutedEventArgs e) { - if (!_isServiceInError) { - ShowLoad(); - try { - ConnectedTime.Content = "00:00:00"; - _timer.Stop(); - serviceClient.SetTunnelState(false); - SetNotifyIcon("white"); - ConnectButton.Visibility = Visibility.Visible; - DisconnectButton.Visibility = Visibility.Collapsed; - for (int i = 0; i < identities.Count; i++) { - serviceClient.IdentityOnOff(identities[i].Fingerprint, false); - } - for (int i = 0; i < IdList.Children.Count; i++) { - IdentityItem item = IdList.Children[i] as IdentityItem; - item._identity.IsEnabled = false; - item.RefreshUI(); - } - } catch (ServiceException se) { - ShowError(se.AdditionalInfo, se.Message); - } catch (Exception ex) { - ShowError("Unexpected Error", "Code 4:" + ex.Message); - } - HideLoad(); + async private void Disconnect(object sender, RoutedEventArgs e) { + + ShowLoad("Disabling Service", "Please wait for the service to stop."); + var r = await monitorClient.StopServiceAsync(); + if (r.Error != null && int.Parse(r.Error) != 0) { + Logger.Debug("ERROR: {0}", r.Message); + } else { + Logger.Info("Service stopped!"); } - } - private void ShowLoad() { + /* + if (!_isServiceInError) { + try { + ShowLoad(); + ConnectedTime.Content = "00:00:00"; + _tunnelUptimeTimer.Stop(); + serviceClient.SetTunnelState(false); + SetNotifyIcon("white"); + ConnectButton.Visibility = Visibility.Visible; + DisconnectButton.Visibility = Visibility.Collapsed; + for (int i = 0; i < identities.Count; i++) { + await serviceClient.IdentityOnOffAsync(identities[i].Fingerprint, false); + } + for (int i = 0; i < IdList.Children.Count; i++) { + IdentityItem item = IdList.Children[i] as IdentityItem; + item._identity.IsEnabled = false; + item.RefreshUI(); + } + } catch (ServiceException se) { + ShowError(se.AdditionalInfo, se.Message); + } catch (Exception ex) { + ShowError("Unexpected Error", "Code 4:" + ex.Message); + } + HideLoad(); + }*/ + HideLoad(); + } + + private void ShowLoad(string title, string msg) { + LoadingDetails.Text = msg; + LoadingTitle.Content = title; LoadProgress.IsIndeterminate = true; LoadingScreen.Visibility = Visibility.Visible; ((MainWindow)System.Windows.Application.Current.MainWindow).UpdateLayout(); @@ -646,6 +818,7 @@ private void ShowError(String title, String message) { private void CloseError(object sender, RoutedEventArgs e) { ErrorView.Visibility = Visibility.Collapsed; NoServiceView.Visibility = Visibility.Collapsed; + CloseErrorButton.IsEnabled = true; } private void CloseApp(object sender, RoutedEventArgs e) { @@ -666,18 +839,15 @@ private void Label_MouseDoubleClick(object sender, MouseButtonEventArgs e) { Placement(); } - private void Button_Click(object sender, RoutedEventArgs e) - { - serviceClient.SetLogLevel(NextLevel()); + async private void Button_Click(object sender, RoutedEventArgs e) { + await serviceClient.SetLogLevelAsync(NextLevel()); } int cur = 0; LogLevelEnum[] levels = new LogLevelEnum[] { LogLevelEnum.FATAL, LogLevelEnum.ERROR, LogLevelEnum.WARN, LogLevelEnum.INFO, LogLevelEnum.DEBUG, LogLevelEnum.TRACE, LogLevelEnum.VERBOSE }; - public LogLevelEnum NextLevel() - { + public LogLevelEnum NextLevel() { cur++; - if (cur > 6) - { + if (cur > 6) { cur = 0; } return levels[cur]; @@ -686,5 +856,20 @@ public LogLevelEnum NextLevel() private void IdList_LayoutUpdated(object sender, EventArgs e) { Placement(); } + /* + string sstatus = "stop"; + async private void Button_Click_1(object sender, RoutedEventArgs e) { + ServiceStatusEvent r = null; + if (sstatus == "Stopped") { + r = await monitorClient.StartServiceAsync(); + } else { + r = await monitorClient.StopServiceAsync(); + } + sstatus = r.Status; + } + async private void Button_Click_2(object sender, RoutedEventArgs e) { + Logger.Info("button 2!"); + await Task.Delay(10); + }*/ } } diff --git a/DesktopEdge/Models/ZitiIdentity.cs b/DesktopEdge/Models/ZitiIdentity.cs index 187066f86..350b4d526 100644 --- a/DesktopEdge/Models/ZitiIdentity.cs +++ b/DesktopEdge/Models/ZitiIdentity.cs @@ -30,7 +30,7 @@ public ZitiIdentity(string Name, string ControllerUrl, bool IsEnabled, List -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Ziti.Desktop.Edge.Properties { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Ziti.Desktop.Edge.Properties.Resources", typeof(Resources).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - } -} +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Ziti.Desktop.Edge.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Ziti.Desktop.Edge.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/DesktopEdge/Properties/Settings.Designer.cs b/DesktopEdge/Properties/Settings.Designer.cs index bd88d5245..c0fab6e41 100644 --- a/DesktopEdge/Properties/Settings.Designer.cs +++ b/DesktopEdge/Properties/Settings.Designer.cs @@ -1,26 +1,26 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Ziti.Desktop.Edge.Properties { - - - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.6.0.0")] - internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { - - private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); - - public static Settings Default { - get { - return defaultInstance; - } - } - } -} +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Ziti.Desktop.Edge.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.8.1.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + } +} diff --git a/DesktopEdge/Views/ItemRenderers/IdentityItem.xaml.cs b/DesktopEdge/Views/ItemRenderers/IdentityItem.xaml.cs index eeda5c57d..38e736102 100644 --- a/DesktopEdge/Views/ItemRenderers/IdentityItem.xaml.cs +++ b/DesktopEdge/Views/ItemRenderers/IdentityItem.xaml.cs @@ -1,89 +1,90 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Data; -using System.Windows.Documents; -using System.Windows.Input; -using System.Windows.Media; -using System.Windows.Media.Imaging; -using System.Windows.Navigation; -using System.Windows.Shapes; -using ZitiDesktopEdge.Models; - -namespace ZitiDesktopEdge { - /// - /// User Control to list Identities and give status - /// - public partial class IdentityItem:UserControl { - - public delegate void StatusChanged(bool attached); - public event StatusChanged OnStatusChanged; - - public ZitiIdentity _identity; - public ZitiIdentity Identity { - get { - return _identity; - } - set { - _identity = value; - this.RefreshUI(); - } - } - - public void RefreshUI () { - ToggleSwitch.Enabled = _identity.IsEnabled; - IdName.Content = _identity.Name; - IdUrl.Content = _identity.ControllerUrl; - ServiceCount.Content = _identity.Services.Count.ToString(); - if (ToggleSwitch.Enabled) { - ToggleStatus.Content = "ENABLED"; - } else { - ToggleStatus.Content = "DISABLED"; - } - } - - public IdentityItem() { - InitializeComponent(); - ToggleSwitch.OnToggled += ToggleIdentity; - } - - private void ToggleIdentity(bool on) { - try { - if (OnStatusChanged != null) { - OnStatusChanged(on); - } - ServiceClient.Client client = (ServiceClient.Client)Application.Current.Properties["ServiceClient"]; - ServiceClient.Identity id = client.IdentityOnOff(_identity.Fingerprint, on); - this.Identity.IsEnabled = on; - if (on) { - ToggleStatus.Content = "ENABLED"; - } else { - ToggleStatus.Content = "DISABLED"; - } - } catch (ServiceClient.ServiceException se) { - MessageBox.Show(se.AdditionalInfo, se.Message); - } catch (Exception ex) { - MessageBox.Show("Error", ex.Message); - } - } - - private void Canvas_MouseEnter(object sender, MouseEventArgs e) { - OverState.Opacity = 0.2; - } - - private void Canvas_MouseLeave(object sender, MouseEventArgs e) { - OverState.Opacity = 0; - } - - private void OpenDetails(object sender, MouseButtonEventArgs e) { - IdentityDetails deets = ((MainWindow)Application.Current.MainWindow).IdentityMenu; - deets.SelectedIdentity = this; - deets.IdDetailToggle.Enabled = this.Identity.IsEnabled; - deets.Identity = this.Identity; - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using ZitiDesktopEdge.Models; +using ZitiDesktopEdge.ServiceClient; + +namespace ZitiDesktopEdge { + /// + /// User Control to list Identities and give status + /// + public partial class IdentityItem:UserControl { + + public delegate void StatusChanged(bool attached); + public event StatusChanged OnStatusChanged; + + public ZitiIdentity _identity; + public ZitiIdentity Identity { + get { + return _identity; + } + set { + _identity = value; + this.RefreshUI(); + } + } + + public void RefreshUI () { + ToggleSwitch.Enabled = _identity.IsEnabled; + IdName.Content = _identity.Name; + IdUrl.Content = _identity.ControllerUrl; + ServiceCount.Content = _identity.Services.Count.ToString(); + if (ToggleSwitch.Enabled) { + ToggleStatus.Content = "ENABLED"; + } else { + ToggleStatus.Content = "DISABLED"; + } + } + + public IdentityItem() { + InitializeComponent(); + ToggleSwitch.OnToggled += ToggleIdentity; + } + + async private void ToggleIdentity(bool on) { + try { + if (OnStatusChanged != null) { + OnStatusChanged(on); + } + DataClient client = (DataClient)Application.Current.Properties["ServiceClient"]; + DataStructures.Identity id = await client.IdentityOnOffAsync(_identity.Fingerprint, on); + this.Identity.IsEnabled = on; + if (on) { + ToggleStatus.Content = "ENABLED"; + } else { + ToggleStatus.Content = "DISABLED"; + } + } catch (DataStructures.ServiceException se) { + MessageBox.Show(se.AdditionalInfo, se.Message); + } catch (Exception ex) { + MessageBox.Show("Error", ex.Message); + } + } + + private void Canvas_MouseEnter(object sender, MouseEventArgs e) { + OverState.Opacity = 0.2; + } + + private void Canvas_MouseLeave(object sender, MouseEventArgs e) { + OverState.Opacity = 0; + } + + private void OpenDetails(object sender, MouseButtonEventArgs e) { + IdentityDetails deets = ((MainWindow)Application.Current.MainWindow).IdentityMenu; + deets.SelectedIdentity = this; + deets.IdDetailToggle.Enabled = this.Identity.IsEnabled; + deets.Identity = this.Identity; + } + } +} diff --git a/DesktopEdge/ZitiDesktopEdge.csproj b/DesktopEdge/ZitiDesktopEdge.csproj index 88fec2a3d..b80fa0eb1 100644 --- a/DesktopEdge/ZitiDesktopEdge.csproj +++ b/DesktopEdge/ZitiDesktopEdge.csproj @@ -88,7 +88,11 @@ ..\packages\Newtonsoft.Json.12.0.3\lib\net45\Newtonsoft.Json.dll + + ..\packages\NLog.4.7.5\lib\net45\NLog.dll + + @@ -104,7 +108,10 @@ True True + + + @@ -243,6 +250,9 @@ Resources.Designer.cs + + Always + Always diff --git a/DesktopEdge/ZitiDesktopEdge.log.config b/DesktopEdge/ZitiDesktopEdge.log.config new file mode 100644 index 000000000..26bdc30d1 --- /dev/null +++ b/DesktopEdge/ZitiDesktopEdge.log.config @@ -0,0 +1,20 @@ + + + + + + + + + + + + + diff --git a/DesktopEdge/packages.config b/DesktopEdge/packages.config index d4d1038f5..9001a3120 100644 --- a/DesktopEdge/packages.config +++ b/DesktopEdge/packages.config @@ -1,6 +1,5 @@ - - - - - + + + + \ No newline at end of file diff --git a/ZitiDesktopEdge.Client/ServiceClient/DataStructures.cs b/ZitiDesktopEdge.Client/DataStructures/DataStructures.cs similarity index 91% rename from ZitiDesktopEdge.Client/ServiceClient/DataStructures.cs rename to ZitiDesktopEdge.Client/DataStructures/DataStructures.cs index bc2b7e1a5..ad98ea70a 100644 --- a/ZitiDesktopEdge.Client/ServiceClient/DataStructures.cs +++ b/ZitiDesktopEdge.Client/DataStructures/DataStructures.cs @@ -5,8 +5,17 @@ /// These classes represent the data structures that are passed back and forth /// between the service and the client. /// -namespace ZitiDesktopEdge.ServiceClient -{ +namespace ZitiDesktopEdge.DataStructures { + public enum LogLevelEnum { + FATAL = 0, + ERROR = 1, + WARN = 2, + INFO = 3, + DEBUG = 4, + TRACE = 5, + VERBOSE = 6, + } + public class SvcResponse { public int Code { get; set; } @@ -282,4 +291,12 @@ public class IdentityEvent : ActionEvent { public Identity Id { get; set; } } + + public class ServiceStatusEvent : SvcResponse { + public string Status { get; set; } + + public bool IsStopped() { + return "Stopped" == this.Status; + } + } } \ No newline at end of file diff --git a/ZitiDesktopEdge.Client/Server/EventRegistry.cs b/ZitiDesktopEdge.Client/Server/EventRegistry.cs new file mode 100644 index 000000000..e3b2ad543 --- /dev/null +++ b/ZitiDesktopEdge.Client/Server/EventRegistry.cs @@ -0,0 +1,14 @@ +using System; +using System.Threading; + +namespace ZitiDesktopEdge.Server { + public class EventRegistry { + protected static SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1, 1); + + public static event EventHandler MyEvent; + + public static void SendEventToConsumers(object objToSend) { + MyEvent(objToSend, null); + } + } +} diff --git a/ZitiDesktopEdge.Client/Server/IPCServer.cs b/ZitiDesktopEdge.Client/Server/IPCServer.cs new file mode 100644 index 000000000..0bad502d2 --- /dev/null +++ b/ZitiDesktopEdge.Client/Server/IPCServer.cs @@ -0,0 +1,216 @@ +using System; +using System.Threading.Tasks; +using System.IO; +using System.IO.Pipes; +using System.Security.Principal; +using System.Security.AccessControl; + +using Newtonsoft.Json; +using NLog; + +using ZitiDesktopEdge.DataStructures; + +namespace ZitiDesktopEdge.Server { + public class IPCServer { + public const string PipeName = @"OpenZiti\ziti-monitor\ipc"; + public const string EventPipeName = @"OpenZiti\ziti-monitor\events"; + + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + private static int BUFFER_SIZE = 16 * 1024; + + private JsonSerializer serializer = new JsonSerializer() { Formatting = Formatting.None }; + private string ipcPipeName; + private string eventPipeName; + + public IPCServer() { + this.ipcPipeName = IPCServer.PipeName; + this.eventPipeName = IPCServer.EventPipeName; + } + + async public Task startIpcServer() { + int idx = 0; + + // Allow AuthenticatedUserSid read and write access to the pipe. + PipeSecurity pipeSecurity = new PipeSecurity(); + var id = new SecurityIdentifier(WellKnownSidType.AuthenticatedUserSid, null); + pipeSecurity.SetAccessRule(new PipeAccessRule(id, PipeAccessRights.CreateNewInstance | PipeAccessRights.ReadWrite, AccessControlType.Allow)); + + while (true) { + try { + var ipcPipeServer = new NamedPipeServerStream( + ipcPipeName, + PipeDirection.InOut, + NamedPipeServerStream.MaxAllowedServerInstances, + PipeTransmissionMode.Byte, + PipeOptions.Asynchronous | PipeOptions.WriteThrough, + BUFFER_SIZE, + BUFFER_SIZE, + pipeSecurity); + + await ipcPipeServer.WaitForConnectionAsync(); + Logger.Debug("Total ipc clients now at: {0}", ++idx); + _ = Task.Run(async () => { + try { + await handleIpcClientAsync(ipcPipeServer); + } catch(Exception icpe) { + Logger.Error(icpe, "Unexpected erorr in handleIpcClientAsync"); + } + idx--; + Logger.Debug("Total ipc clients now at: {0}", idx); + }); + } catch (Exception pe) { + Logger.Error(pe, "Unexpected erorr when connecting a client pipe."); + } + } + } + async public Task startEventsServer() { + int idx = 0; + + // Allow AuthenticatedUserSid read and write access to the pipe. + PipeSecurity pipeSecurity = new PipeSecurity(); + var id = new SecurityIdentifier(WellKnownSidType.AuthenticatedUserSid, null); + pipeSecurity.SetAccessRule(new PipeAccessRule(id, PipeAccessRights.CreateNewInstance | PipeAccessRights.ReadWrite, AccessControlType.Allow)); + + while (true) { + try { + var eventPipeServer = new NamedPipeServerStream( + eventPipeName, + PipeDirection.InOut, + NamedPipeServerStream.MaxAllowedServerInstances, + PipeTransmissionMode.Byte, + PipeOptions.Asynchronous | PipeOptions.WriteThrough, + BUFFER_SIZE, + BUFFER_SIZE, + pipeSecurity); + + await eventPipeServer.WaitForConnectionAsync(); + Logger.Debug("Total event clients now at: {0}", ++idx); + _ = Task.Run(async () => { + try { + await handleEventClientAsync(eventPipeServer); + } catch (Exception icpe) { + Logger.Error(icpe, "Unexpected erorr in handleEventClientAsync"); + } + idx--; + Logger.Debug("Total event clients now at: {0}", idx); + }); + } catch (Exception pe) { + Logger.Error(pe, "Unexpected erorr when connecting a client pipe."); + } + } + } + + async public Task handleIpcClientAsync(NamedPipeServerStream ss) { + using (ss) { + try { + StreamReader reader = new StreamReader(ss); + StreamWriter writer = new StreamWriter(ss); + + string line = await reader.ReadLineAsync(); + + while (line != null) { + await processMessageAsync(line, writer); + line = await reader.ReadLineAsync(); + } + + Logger.Debug("handleIpcClientAsync is complete"); + } catch (Exception e) { + Logger.Error(e, "Unexpected erorr when reading from or writing to a client pipe."); + } + } + } + + async public Task handleEventClientAsync(NamedPipeServerStream ss) { + using (ss) { + + StreamWriter writer = new StreamWriter(ss); + EventHandler eh = async (object sender, EventArgs e) => { + await writer.WriteLineAsync(sender.ToString()); + await writer.FlushAsync(); + }; + + ServiceStatusEvent status = new ServiceStatusEvent() { + //Op="status", Status = ServiceActions.ServiceStatus() + Code = 0, + Error = null, + Message = "Success", + Status = ServiceActions.ServiceStatus() + }; + await writer.WriteLineAsync(JsonConvert.SerializeObject(status)); + await writer.FlushAsync(); + + EventRegistry.MyEvent += eh; + try { + StreamReader reader = new StreamReader(ss); + + string line = await reader.ReadLineAsync(); + while (line != null) { + await processMessageAsync(line, writer); + line = await reader.ReadLineAsync(); + } + + Logger.Debug("handleEventClientAsync is complete"); + } catch (Exception e) { + Logger.Error(e, "Unexpected erorr when reading from or writing to a client pipe."); + } + EventRegistry.MyEvent -= eh; + } + } + + async public Task processMessageAsync(string msg, StreamWriter writer) { + Logger.Debug("message received: {0}", msg); + var r = new SvcResponse(); + var rr = new ServiceStatusEvent(); + try { + ActionEvent ae = serializer.Deserialize(new JsonTextReader(new StringReader(msg))); + Logger.Info("Op: {0}", ae.Op); + switch (ae.Op.ToLower()) { + case "stop": + if (ae.Action == "Force") { + // attempt to forcefully find the process and terminate it... + Logger.Warn("User has requested a FORCEFUL termination of the service. It must be stuck. Current status: {0}", ServiceActions.ServiceStatus()); + var procs = System.Diagnostics.Process.GetProcessesByName("ziti-tunnel"); + if (procs == null || procs.Length == 0) { + Logger.Error("Process not found! Cannot terminate!"); + rr.Code = -20; + rr.Error = "Process not found! Cannot terminate!"; + rr.Message = "Could not terminate the service forcefully"; + break; + } + + foreach(var p in procs) { + Logger.Warn("Forcefully terminating process: {0}", p.Id); + p.Kill(); + } + rr.Message = "Service has been terminated"; + rr.Status = ServiceActions.ServiceStatus(); + r = rr; + } else { + r.Message = ServiceActions.StopService(); + } + break; + case "start": + r.Message = ServiceActions.StartService(); + break; + case "status": + rr.Status = ServiceActions.ServiceStatus(); + r = rr; + break; + default: + msg = string.Format("UNKNOWN ACTION received: {0}", ae.Op); + Logger.Error(msg); + r.Code = -3; + r.Error = msg; + break; + } + } catch (Exception e) { + Logger.Error(e, "Unexpected erorr in processMessage!"); + r.Code = -2; + r.Error = e.Message + ":" + e?.InnerException?.Message; + } + Logger.Info("Returning status: {0}", r.Message); + await writer.WriteLineAsync(JsonConvert.SerializeObject(r)); + await writer.FlushAsync(); + } + } +} diff --git a/ZitiDesktopEdge.Client/Server/ServiceActions.cs b/ZitiDesktopEdge.Client/Server/ServiceActions.cs new file mode 100644 index 000000000..3a6bd159d --- /dev/null +++ b/ZitiDesktopEdge.Client/Server/ServiceActions.cs @@ -0,0 +1,31 @@ +using System.ServiceProcess; + +using NLog; + +namespace ZitiDesktopEdge.Server { + public static class ServiceActions { + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + + private static ServiceController sc = new ServiceController("ziti"); + public static string ServiceStatus() { + var status = sc.Status; + return status.ToString(); + } + + public static string StartService() { + Logger.Info("request to start ziti service received... processing..."); + sc.Start(); + sc.WaitForStatus(ServiceControllerStatus.Running, new System.TimeSpan(0,0,30)); + Logger.Info("request to start ziti service received... complete..."); + return ServiceStatus(); + } + + public static string StopService() { + Logger.Info("request to stop ziti service received... processing..."); + sc.Stop(); + sc.WaitForStatus(ServiceControllerStatus.Stopped, new System.TimeSpan(0, 0, 30)); + Logger.Info("request to stop ziti service received... complete..."); + return ServiceStatus(); + } + } +} diff --git a/ZitiDesktopEdge.Client/ServiceClient/AbstractClient.cs b/ZitiDesktopEdge.Client/ServiceClient/AbstractClient.cs new file mode 100644 index 000000000..4e47f1158 --- /dev/null +++ b/ZitiDesktopEdge.Client/ServiceClient/AbstractClient.cs @@ -0,0 +1,258 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Pipes; +using System.Security.Principal; +using System.Security.AccessControl; +using System.Threading; +using System.Threading.Tasks; + +using Newtonsoft.Json; +using NLog; + + +using ZitiDesktopEdge.DataStructures; + +namespace ZitiDesktopEdge.ServiceClient { + public abstract class AbstractClient { + + private bool _extendedDebug = false; //set ZITI_EXTENDED_DEBUG env var to true if you want to diagnose issues with the service comms + + protected NamedPipeClientStream pipeClient = null; + protected NamedPipeClientStream eventClient = null; + protected StreamWriter ipcWriter = null; + protected StreamReader ipcReader = null; + protected abstract Task ConnectPipesAsync(); + protected abstract void ProcessLine(string line); + protected abstract Logger Logger { get; } + + protected const string localPipeServer = "."; + protected const int ServiceConnectTimeout = 500; + + //protected object namedPipeSyncLock = new object(); + protected static SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1, 1); + + protected JsonSerializer serializer = new JsonSerializer() { Formatting = Formatting.None }; + + protected virtual void ClientConnected(object e) { + Connected = true; + Reconnecting = false; + CleanShutdown = false; + Logger.Debug("Client connected successfully. Setting CleanShutdown set to false."); + + ipcWriter = new StreamWriter(pipeClient); + ipcReader = new StreamReader(pipeClient); + Task.Run(async () => { //hack for now until it's async... + try { + StreamReader eventReader = new StreamReader(eventClient); + while (true) { + if (eventReader.EndOfStream) { + break; + } + + try { + string respAsString = await readMessageAsync(eventReader, "ClientConnected"); + ProcessLine(respAsString); + } catch (Exception ex) { + Logger.Warn(ex, "ERROR caught"); + } + } + } catch (Exception ex) { + Logger.Debug("unepxected error: " + ex.ToString()); + } + + // since this thread is always sitting waiting to read + // it should be the only one triggering this event + ClientDisconnected(null); + }); + OnClientConnected?.Invoke(this, e); + } + + protected virtual void ClientDisconnected(object e) { + Reconnect(); + Connected = false; + OnClientDisconnected?.Invoke(this, e); + } + + protected virtual void ShutdownEvent(StatusEvent e) { + Logger.Debug("Clean shutdown detected from ziti"); + CleanShutdown = true; + OnShutdownEvent?.Invoke(this, e); + } + + async protected Task sendAsync(object objToSend) { + bool retried = false; + while (true) { + try { + string toSend = JsonConvert.SerializeObject(objToSend, Formatting.None); + + if (toSend?.Trim() != null) { + debugServiceCommunication("=============== sending message =============== "); + debugServiceCommunication(toSend); + await ipcWriter.WriteAsync(toSend); + await ipcWriter.WriteAsync('\n'); + debugServiceCommunication("=============== flushing message =============== "); + await ipcWriter.FlushAsync(); + debugServiceCommunication("=============== sent message =============== "); + debugServiceCommunication(""); + debugServiceCommunication(""); + } else { + Logger.Debug("NOT sending empty object??? " + objToSend?.ToString()); + } + break; + } catch (IOException ioe) { + //almost certainly a problem with the pipe - recreate the pipe... try one more time. + //setupPipe(); + if (retried) { + //we tried - throw the error... + throw ioe; + } else { + retried = true; //fall back through to the while and try again + } + } catch (Exception ex) { + //if this fails it's usually because the writer is null/invalid. throwing IOException + //will trigger the pipe to rebuild + throw new IOException("Unexpected error when sending data to service. " + ex.Message); + } + } + } + + public event EventHandler OnClientConnected = null; + public event EventHandler OnClientDisconnected; + public event EventHandler OnShutdownEvent; + + public bool Reconnecting { get; set; } + public bool Connected { get; set; } + public bool CleanShutdown { get; set; } + + public AbstractClient() { + try { + string extDebugEnv = Environment.GetEnvironmentVariable("ZITI_EXTENDED_DEBUG"); + if (extDebugEnv != null) { + if (bool.Parse(extDebugEnv)) { + _extendedDebug = true; + } + } + } catch (Exception ex) { + Logger.Debug("EXCEPTION IN CLIENT CONNECT: " + ex.Message); + //if this happens - enter retry mode... + Reconnect(); + } + } + + async public Task ConnectAsync() { + //establish the named pipe to the service + await ConnectPipesAsync(); + } + + public void Reconnect() { + if (Reconnecting) { + Logger.Debug("Already in reconnect mode."); + return; + } else { + Reconnecting = true; + } + + Task.Run(async () => { + Logger.Info("service is down. attempting to connect to service..."); + + DateTime reconnectStart = DateTime.Now; + DateTime logAgainAfter = reconnectStart + TimeSpan.FromSeconds(1); + + while (true) { + try { + await Task.Delay(2500); + await ConnectPipesAsync(); + + if (Connected) { + Logger.Debug("Connected to the service - exiting reconect loop"); + Connected = true; + Reconnecting = false; + return; + } else { + //ClientDisconnected(null); + } + } catch { + var now = DateTime.Now; + if (now > logAgainAfter) { + Logger.Debug("Reconnect failed. Trying again..."); + var duration = now - reconnectStart; + if (duration > TimeSpan.FromHours(1)) { + Logger.Info("reconnect has not completed and has been running for {0} hours", duration.TotalHours); + logAgainAfter += TimeSpan.FromHours(1); + } else if (duration > TimeSpan.FromMinutes(1)) { + Logger.Info("reconnect has not completed and has been running for {0} minutes", duration.TotalMinutes); + logAgainAfter += TimeSpan.FromMinutes(1); + } else { + logAgainAfter += TimeSpan.FromSeconds(1); + } + } + } + } + }); + } + + protected void debugServiceCommunication(string msg) { + if (_extendedDebug) { + Logger.Debug(msg); + } + } + + async protected Task readAsync(StreamReader reader, string where) where T : SvcResponse { + string respAsString = await readMessageAsync(reader, where); + T resp = (T)serializer.Deserialize(new StringReader(respAsString), typeof(T)); + return resp; + } + + async public Task readMessageAsync(StreamReader reader, string where) { + try {/* + if (reader.EndOfStream) { + throw new ServiceException("the pipe has closed", 0, "end of stream reached"); + }*/ + int emptyCount = 1; //just a stop gap in case something crazy happens in the communication + + debugServiceCommunication("==============a reading message =============== " + where); + string respAsString = await reader.ReadLineAsync(); + debugServiceCommunication(respAsString); + debugServiceCommunication("=============== read message =============== " + where); + while (string.IsNullOrEmpty(respAsString?.Trim())) { + /*if (reader.EndOfStream) { + throw new Exception("the pipe has closed"); + }*/ + debugServiceCommunication("Received empty payload - continuing to read until a payload is received"); + //now how'd that happen... + debugServiceCommunication("==============b reading message =============== " + where); + respAsString = await reader.ReadLineAsync(); + debugServiceCommunication(respAsString); + debugServiceCommunication("=============== read message =============== " + where); + emptyCount++; + if (emptyCount > 5) { + Logger.Debug("are we there yet? " + reader.EndOfStream); + //that's just too many... + //setupPipe(); + return null; + } + } + debugServiceCommunication(""); + debugServiceCommunication(""); + return respAsString; + } catch (IOException ioe) { + //almost certainly a problem with the pipe + Logger.Debug("io error in read: " + ioe.Message); + ClientDisconnected(null); + throw ioe; + } catch (Exception ee) { + //almost certainly a problem with the pipe + Logger.Debug("unexpected error in read: " + ee.Message); + ClientDisconnected(null); + throw ee; + } + } + + async public Task WaitForConnectionAsync() { + while (Reconnecting || !Connected) { + await Task.Delay(100); + } + } + } +} diff --git a/ZitiDesktopEdge.Client/ServiceClient/ClientImpl.cs b/ZitiDesktopEdge.Client/ServiceClient/ClientImpl.cs deleted file mode 100644 index 38ecef697..000000000 --- a/ZitiDesktopEdge.Client/ServiceClient/ClientImpl.cs +++ /dev/null @@ -1,629 +0,0 @@ -using System; -using System.Diagnostics; -using System.Collections.Generic; -using System.IO; -using System.IO.Pipes; -using System.Security.Principal; -using System.Security.AccessControl; - -using Newtonsoft.Json; -using System.Threading; -using System.Threading.Tasks; - -using NLog; - -/// -/// The implementation will abstract away the setup of the communication to -/// the service. This implementation will communicate to the service over a -/// a NamedPipe. -/// -/// All communication is effectively serial - one or more messages sent and -/// one or more messages returned. -/// -/// -namespace ZitiDesktopEdge.ServiceClient -{ - public enum LogLevelEnum - { - FATAL = 0, - ERROR = 1, - WARN = 2, - INFO = 3, - DEBUG = 4, - TRACE = 5, - VERBOSE = 6, - } - - public class Client { - private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); - - public const int EXPECTED_API_VERSION = 1; - - public event EventHandler OnTunnelStatusEvent; - public event EventHandler> OnMetricsEvent; - public event EventHandler OnIdentityEvent; - public event EventHandler OnServiceEvent; - public event EventHandler OnClientConnected; - public event EventHandler OnClientDisconnected; - public event EventHandler OnShutdownEvent; - - public bool CleanShutdown { get; set; } - - protected virtual void ShutdownEvent(StatusEvent e) { - Logger.Debug("Clean shutdown detected from ziti"); - CleanShutdown = true; - OnShutdownEvent?.Invoke(this, e); - } - - protected virtual void TunnelStatusEvent(TunnelStatusEvent e) - { - OnTunnelStatusEvent?.Invoke(this, e); - } - - protected virtual void MetricsEvent(List e) - { - OnMetricsEvent?.Invoke(this, e); - } - - protected virtual void IdentityEvent(IdentityEvent e) - { - OnIdentityEvent?.Invoke(this, e); - } - - protected virtual void ServiceEvent(ServiceEvent e) - { - OnServiceEvent?.Invoke(this, e); - } - - protected virtual void ClientConnected(object e) - { - Connected = true; - Reconnecting = false; - CleanShutdown = false; - Logger.Debug("CleanShutdown set to false"); - OnClientConnected?.Invoke(this, e); - } - - protected virtual void ClientDisconnected(object e) - { - Reconnect(); - Connected = false; - OnClientDisconnected?.Invoke(this, e); - } - - JsonSerializer serializer = new JsonSerializer(); - - private object namedPipeSyncLock = new object(); - const string ipcPipe = @"NetFoundry\tunneler\ipc"; - const string logPipe = @"NetFoundry\tunneler\logs"; - const string eventPipe = @"NetFoundry\tunneler\events"; - const string localPipeServer = "."; - const PipeDirection inOut = PipeDirection.InOut; - const int ServiceConnectTimeout = 500; - - NamedPipeClientStream pipeClient = null; - StreamWriter ipcWriter = null; - StreamReader ipcReader = null; - - NamedPipeClientStream eventClient = null; -#if DEBUG - bool _extendedDebug = false; //set ZITI_EXTENDED_DEBUG env var to true if you want to diagnose issues with the service comms -#else - bool _extendedDebug = false; -#endif - - public Client() - { - try - { - string extDebugEnv = Environment.GetEnvironmentVariable("ZITI_EXTENDED_DEBUG"); - if (extDebugEnv != null) - { - if (bool.Parse(extDebugEnv)) - { - _extendedDebug = true; - } - } - } - catch(Exception ex) - { - Logger.Debug("EXCEPTION IN CLIENT CONNECT: " + ex.Message); - //if this happens - enter retry mode... - Reconnect(); - } - } - - public bool Reconnecting { get; set; } - public bool Connected { get; set; } - - public void Connect() - { - //establish the named pipe to the service - setupPipe(); - } - - public void WaitForConnection() { - while(Reconnecting || !Connected) { - Task.Delay(100).Wait(); - } - } - - public void Reconnect() - { - if (Reconnecting) - { - Logger.Debug("Already in reconnect mode."); - return; - } - else - { - Reconnecting = true; - } - - Task.Run(() => { - Logger.Info("service is down. attempting to connect to service..."); - - DateTime reconnectStart = DateTime.Now; - DateTime logAgainAfter = reconnectStart + TimeSpan.FromSeconds(1); - - while (true) { - try { - Thread.Sleep(2500); - pipeClient?.Close(); - eventClient?.Close(); - setupPipe(); - - if (Connected) { - Logger.Debug("Connected to the service - exiting reconect loop"); - Connected = true; - Reconnecting = false; - return; - } else { - //ClientDisconnected(null); - } - } catch { - var now = DateTime.Now; - if (now > logAgainAfter) { - Logger.Debug("Reconnect failed. Trying again..."); - var duration = now - reconnectStart; - if (duration > TimeSpan.FromHours(1)) { - Logger.Info("reconnect has not completed and has been running for {0} hours", duration.TotalHours); - logAgainAfter += TimeSpan.FromHours(1); - } else if (duration > TimeSpan.FromMinutes(1)) { - Logger.Info("reconnect has not completed and has been running for {0} minutes", duration.TotalMinutes); - logAgainAfter += TimeSpan.FromMinutes(1); - } else { - logAgainAfter += TimeSpan.FromSeconds(1); - } - } - } - } - }); - } - - PipeSecurity CreateSystemIOPipeSecurity() - { - PipeSecurity pipeSecurity = new PipeSecurity(); - - var id = new SecurityIdentifier(WellKnownSidType.AuthenticatedUserSid, null); - - // Allow Everyone read and write access to the pipe. - pipeSecurity.SetAccessRule(new PipeAccessRule(id, PipeAccessRights.ReadWrite, AccessControlType.Allow)); - - return pipeSecurity; - } - - private void setupPipe() - { - lock (namedPipeSyncLock) - { - pipeClient = new NamedPipeClientStream(localPipeServer, ipcPipe, inOut); - eventClient = new NamedPipeClientStream(localPipeServer, eventPipe, PipeDirection.In); - - try - { - eventClient.Connect(ServiceConnectTimeout); - pipeClient.Connect(ServiceConnectTimeout); - ipcWriter = new StreamWriter(pipeClient); - ipcReader = new StreamReader(pipeClient); - ClientConnected(null); - } - catch(Exception ex) - { - throw new ServiceException("Could not connect to the service.", 1, ex.Message); - } - - Task.Run(() => { //hack for now until it's async... - try - { - StreamReader eventReader = new StreamReader(eventClient); - while (true) - { - if (eventReader.EndOfStream) - { - break; - } - - processEvent(eventReader); - } - } - catch(Exception ex) - { - Logger.Debug("unepxected error: " + ex.ToString()); - } - - // since this thread is always sitting waiting to read - // it should be the only one triggering this event - ClientDisconnected(null); - }); - } - } - - public ZitiTunnelStatus GetStatus() - { - try - { - send(new ServiceFunction() { Function = "Status" }); - var rtn = read(ipcReader); - return rtn; - } - catch (IOException ioe) - { - //almost certainly a problem with the pipe - recreate the pipe... - //setupPipe(); - throw ioe; - } - } - - ServiceFunction AddIdentityFunction = new ServiceFunction() { Function = "AddIdentity" }; - - public Identity AddIdentity(string identityName, bool activate, string jwt) - { - try - { - Identity id = new Identity - { - Active = activate, - Name = identityName - }; - - NewIdentity newId = new NewIdentity() - { - Id = id, - Flags = new EnrollmentFlags() - { - JwtString = jwt - } - }; - - send(AddIdentityFunction); - send(newId); - var resp = read(ipcReader); - Logger.Debug(resp.ToString()); - if(resp.Code != 0) - { - throw new ServiceException(resp.Message, resp.Code, resp.Error); - } - return resp.Payload; - } - catch (IOException) - { - //almost certainly a problem with the pipe - recreate the pipe... - //setupPipe(); - throw; - } - } - - - public void RemoveIdentity(string fingerPrint) - { - if (string.IsNullOrEmpty(fingerPrint)) - { - //nothing to do... - return; - } - - try - { - FingerprintFunction removeFunction = new FingerprintFunction() - { - Function = "RemoveIdentity", - Payload = new FingerprintPayload() { Fingerprint = fingerPrint } - }; - send(removeFunction); - var r = read(ipcReader); - } - catch (IOException ioe) - { - //almost certainly a problem with the pipe - recreate the pipe... - //setupPipe(); - throw ioe; - } - } - - private void checkConnected() - { - if (Reconnecting) - { - throw new ServiceException("Client is not connected", 2, "Cannot use the client at this time, it is reconnecting"); - } - if (!Connected) - { - throw new ServiceException("Client is not connected", 2, "Cannot use the client at this time, it is not connected"); - } - } - - public void SetTunnelState(bool onOff) - { - /* - checkConnected(); - try - { - send(new BooleanFunction("TunnelState", onOff)); - read(ipcReader); - } - catch (IOException ioe) - { - //almost certainly a problem with the pipe - recreate the pipe... - //setupPipe(); - throw ioe; - } - */ - } - - public string GetLogs() - { - try - { - NamedPipeClientStream logClient = new NamedPipeClientStream(localPipeServer, logPipe, PipeDirection.In); - StreamReader logReader = new StreamReader(logClient); - logClient.Connect(ServiceConnectTimeout); - - string content = logReader.ReadToEnd(); - - //ugly hack to turn ansi escaping to not... _bleck_ - //todo: fix this :point_up: - content = new System.Text.RegularExpressions.Regex(@"\x1B\[[^@-~]*[@-~]").Replace(content, ""); - return content; - } - catch - { - //almost certainly a problem with the pipe - probably means the service is NOT running - return "Error fetching logs from service. Is it running?"; - } - } - - public void SetLogLevel(string level) { - try { - send(new SetLogLevelFunction(level)); - SvcResponse resp = read(ipcReader); - return; - } catch (IOException ioe) { - //almost certainly a problem with the pipe - recreate the pipe... - //setupPipe(); - throw ioe; - } - } - - public void SetLogLevel(LogLevelEnum level) - { - try - { - send(new SetLogLevelFunction(Enum.GetName(level.GetType(), level))); - SvcResponse resp = read(ipcReader); - return; - } - catch (IOException ioe) - { - //almost certainly a problem with the pipe - recreate the pipe... - //setupPipe(); - throw ioe; - } - } - - public Identity IdentityOnOff(string fingerprint, bool onOff) - { - try - { - send(new IdentityToggleFunction(fingerprint, onOff)); - IdentityResponse idr = read(ipcReader); - return idr.Payload; - } - catch (IOException ioe) - { - //almost certainly a problem with the pipe - recreate the pipe... - //setupPipe(); - throw ioe; - } - } - - private void send(object objToSend) - { - bool retried = false; - while (true) - { - try - { - string toSend = JsonConvert.SerializeObject(objToSend, Formatting.None); - - if (toSend?.Trim() != null) - { - debugServiceCommunication("=============== sending message =============== "); - debugServiceCommunication(toSend); - ipcWriter.Write(toSend); - ipcWriter.Write('\n'); - debugServiceCommunication("=============== flushing message =============== "); - ipcWriter.Flush(); - debugServiceCommunication("=============== sent message =============== "); - debugServiceCommunication(""); - debugServiceCommunication(""); - } - else - { - Logger.Debug("NOT sending empty object??? " + objToSend?.ToString()); - } - break; - } - catch (IOException ioe) - { - //almost certainly a problem with the pipe - recreate the pipe... try one more time. - //setupPipe(); - if (retried) - { - //we tried - throw the error... - throw ioe; - } - else - { - retried = true; //fall back through to the while and try again - } - } - catch (Exception ex) - { - //if this fails it's usually because the writer is null/invalid. throwing IOException - //will trigger the pipe to rebuild - throw new IOException("Unexpected error when sending data to service. " + ex.Message); - } - } - } - - private T read(StreamReader reader) where T : SvcResponse - { - string respAsString = readMessage(reader); - T resp = (T)serializer.Deserialize(new StringReader(respAsString), typeof(T)); - return resp; - } - - private void processEvent(StreamReader reader) - { - try - { - string respAsString = readMessage(reader); - StatusEvent evt = (StatusEvent)serializer.Deserialize(new StringReader(respAsString), typeof(StatusEvent)); - - switch (evt.Op) - { - case "metrics": - MetricsEvent m = (MetricsEvent)serializer.Deserialize(new StringReader(respAsString), typeof(MetricsEvent)); - - if (m != null) - { - MetricsEvent(m.Identities); - } - break; - case "status": - TunnelStatusEvent se = (TunnelStatusEvent)serializer.Deserialize(new StringReader(respAsString), typeof(TunnelStatusEvent)); - - if (se != null) - { - TunnelStatusEvent(se); - } - break; - case "identity": - IdentityEvent id = (IdentityEvent)serializer.Deserialize(new StringReader(respAsString), typeof(IdentityEvent)); - - if (id != null) - { - IdentityEvent(id); - } - break; - case "service": - ServiceEvent svc = (ServiceEvent)serializer.Deserialize(new StringReader(respAsString), typeof(ServiceEvent)); - - if (svc != null) - { - ServiceEvent(svc); - } - break; - case "shutdown": - - break; - default: - Logger.Debug("unexpected operation! " + evt.Op); - break; - } - } catch (Exception e) - { - Logger.Debug(e.Message); - } - } - - public string readMessage(StreamReader reader) - { - try - { - if (reader.EndOfStream) - { - throw new ServiceException("the pipe has closed", 0, "end of stream reached"); - } - int emptyCount = 1; //just a stop gap in case something crazy happens in the communication - - debugServiceCommunication( "=============== reading message =============== " + emptyCount); - string respAsString = reader.ReadLine(); - debugServiceCommunication(respAsString); - debugServiceCommunication("=============== read message =============== " + emptyCount); - while (string.IsNullOrEmpty(respAsString?.Trim())) - { - if (reader.EndOfStream) - { - throw new Exception("the pipe has closed"); - } - debugServiceCommunication("Received empty payload - continuing to read until a payload is received"); - //now how'd that happen... - debugServiceCommunication("=============== reading message =============== " + emptyCount); - respAsString = reader.ReadLine(); - debugServiceCommunication(respAsString); - debugServiceCommunication("=============== read message =============== " + emptyCount); - emptyCount++; - if (emptyCount > 5) - { - Logger.Debug("are we there yet? " + reader.EndOfStream); - //that's just too many... - //setupPipe(); - return null; - } - } - debugServiceCommunication(""); - debugServiceCommunication(""); - return respAsString; - } - catch (IOException ioe) - { - //almost certainly a problem with the pipe - Logger.Debug("io error in read: " + ioe.Message); - ClientDisconnected(null); - throw ioe; - } - catch (Exception ee) - { - //almost certainly a problem with the pipe - Logger.Debug("unexpected error in read: " + ee.Message); - ClientDisconnected(null); - throw ee; - } - } - - private void debugServiceCommunication(string msg) - { - if (_extendedDebug) - { - Logger.Debug(msg); - } - } - public ZitiTunnelStatus debug() - { - try - { - send(new ServiceFunction() { Function = "Debug" }); - var rtn = read(ipcReader); - return rtn; - } - catch (IOException ioe) - { - //almost certainly a problem with the pipe - recreate the pipe... - //setupPipe(); - throw ioe; - } - } - } -} \ No newline at end of file diff --git a/ZitiDesktopEdge.Client/ServiceClient/DataClient.cs b/ZitiDesktopEdge.Client/ServiceClient/DataClient.cs new file mode 100644 index 000000000..5f028af35 --- /dev/null +++ b/ZitiDesktopEdge.Client/ServiceClient/DataClient.cs @@ -0,0 +1,307 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Pipes; +using System.Security.Principal; +using System.Security.AccessControl; +using System.Threading; +using System.Threading.Tasks; + +using Newtonsoft.Json; +using NLog; + +using ZitiDesktopEdge.DataStructures; +using ZitiDesktopEdge.Server; + +/// +/// The implementation will abstract away the setup of the communication to +/// the service. This implementation will communicate to the service over a +/// a NamedPipe. +/// +/// All communication is effectively serial - one or more messages sent and +/// one or more messages returned. +/// +/// +namespace ZitiDesktopEdge.ServiceClient { + public class DataClient : AbstractClient { + private static readonly Logger _logger = LogManager.GetCurrentClassLogger(); + protected override Logger Logger { get { return _logger; } } + + public const int EXPECTED_API_VERSION = 1; + + public event EventHandler OnTunnelStatusEvent; + public event EventHandler> OnMetricsEvent; + public event EventHandler OnIdentityEvent; + public event EventHandler OnServiceEvent; + + protected override void ShutdownEvent(StatusEvent e) { + Logger.Debug("Clean shutdown detected from ziti"); + CleanShutdown = true; + base.ShutdownEvent(e); + } + + protected virtual void TunnelStatusEvent(TunnelStatusEvent e) { + OnTunnelStatusEvent?.Invoke(this, e); + } + + protected virtual void MetricsEvent(List e) { + OnMetricsEvent?.Invoke(this, e); + } + + protected virtual void IdentityEvent(IdentityEvent e) { + OnIdentityEvent?.Invoke(this, e); + } + + protected virtual void ServiceEvent(ServiceEvent e) { + OnServiceEvent?.Invoke(this, e); + } + + protected override void ClientConnected(object e) { + base.ClientConnected(e); + } + + protected override void ClientDisconnected(object e) { + Reconnect(); + Connected = false; + base.ClientDisconnected(e); + } + + const string ipcPipe = @"OpenZiti\ziti\ipc"; + const string logPipe = @"OpenZiti\ziti\logs"; + const string eventPipe = @"OpenZiti\ziti\events"; + + public DataClient() : base() { + } + + PipeSecurity CreateSystemIOPipeSecurity() { + PipeSecurity pipeSecurity = new PipeSecurity(); + + var id = new SecurityIdentifier(WellKnownSidType.AuthenticatedUserSid, null); + + // Allow Everyone read and write access to the pipe. + pipeSecurity.SetAccessRule(new PipeAccessRule(id, PipeAccessRights.ReadWrite, AccessControlType.Allow)); + + return pipeSecurity; + } + + async protected override Task ConnectPipesAsync() { + await semaphoreSlim.WaitAsync(); + try { + pipeClient = new NamedPipeClientStream(localPipeServer, ipcPipe, PipeDirection.InOut); + eventClient = new NamedPipeClientStream(localPipeServer, eventPipe, PipeDirection.In); + await eventClient.ConnectAsync(ServiceConnectTimeout); + await pipeClient.ConnectAsync(ServiceConnectTimeout); + ClientConnected(null); + } catch (Exception ex) { + semaphoreSlim.Release(); + throw new ServiceException("Could not connect to the service.", 1, ex.Message); + } + semaphoreSlim.Release(); + } + + async public Task GetStatusAsync() { + try { + await sendAsync(new ServiceFunction() { Function = "Status" }); + var rtn = await readAsync(ipcReader, "GetStatusAsync"); + return rtn; + } catch (IOException ioe) { + //almost certainly a problem with the pipe - recreate the pipe... + //setupPipe(); + throw ioe; + } + } + + ServiceFunction AddIdentityFunction = new ServiceFunction() { Function = "AddIdentity" }; + + async public Task AddIdentityAsync(string identityName, bool activate, string jwt) { + try { + Identity id = new Identity { + Active = activate, + Name = identityName + }; + + NewIdentity newId = new NewIdentity() { + Id = id, + Flags = new EnrollmentFlags() { + JwtString = jwt + } + }; + + await sendAsync(AddIdentityFunction); + await sendAsync(newId); + var resp = await readAsync(ipcReader, "AddIdentityAsync"); + Logger.Debug(resp.ToString()); + if (resp.Code != 0) { + throw new ServiceException(resp.Message, resp.Code, resp.Error); + } + return resp.Payload; + } catch (IOException) { + //almost certainly a problem with the pipe - recreate the pipe... + //setupPipe(); + throw; + } + } + + + async public Task RemoveIdentityAsync(string fingerPrint) { + if (string.IsNullOrEmpty(fingerPrint)) { + //nothing to do... + return; + } + + try { + FingerprintFunction removeFunction = new FingerprintFunction() { + Function = "RemoveIdentity", + Payload = new FingerprintPayload() { Fingerprint = fingerPrint } + }; + await sendAsync(removeFunction); + var r = await readAsync(ipcReader, "RemoveIdentityAsync"); + } catch (IOException ioe) { + //almost certainly a problem with the pipe - recreate the pipe... + //setupPipe(); + throw ioe; + } + } + + private void checkConnected() { + if (Reconnecting) { + throw new ServiceException("Client is not connected", 2, "Cannot use the client at this time, it is reconnecting"); + } + if (!Connected) { + throw new ServiceException("Client is not connected", 2, "Cannot use the client at this time, it is not connected"); + } + } + + public void SetTunnelState(bool onOff) { + /* + checkConnected(); + try + { + send(new BooleanFunction("TunnelState", onOff)); + read(ipcReader); + } + catch (IOException ioe) + { + //almost certainly a problem with the pipe - recreate the pipe... + //setupPipe(); + throw ioe; + } + */ + } + + public string GetLogs() { + try { + NamedPipeClientStream logClient = new NamedPipeClientStream(localPipeServer, logPipe, PipeDirection.In); + StreamReader logReader = new StreamReader(logClient); + logClient.Connect(ServiceConnectTimeout); + + string content = logReader.ReadToEnd(); + + //ugly hack to turn ansi escaping to not... _bleck_ + //todo: fix this :point_up: + content = new System.Text.RegularExpressions.Regex(@"\x1B\[[^@-~]*[@-~]").Replace(content, ""); + return content; + } catch { + //almost certainly a problem with the pipe - probably means the service is NOT running + return "Error fetching logs from service. Is it running?"; + } + } + + async public Task SetLogLevelAsync(string level) { + try { + await sendAsync(new SetLogLevelFunction(level)); + SvcResponse resp = await readAsync(ipcReader, "SetLogLevelAsync"); + return; + } catch (IOException ioe) { + //almost certainly a problem with the pipe - recreate the pipe... + //setupPipe(); + throw ioe; + } + } + + async public Task SetLogLevelAsync(LogLevelEnum level) { + try { + await sendAsync(new SetLogLevelFunction(Enum.GetName(level.GetType(), level))); + SvcResponse resp = await readAsync(ipcReader, "SetLogLevelAsync"); + return; + } catch (IOException ioe) { + //almost certainly a problem with the pipe - recreate the pipe... + //setupPipe(); + throw ioe; + } + } + + async public Task IdentityOnOffAsync(string fingerprint, bool onOff) { + try { + await sendAsync(new IdentityToggleFunction(fingerprint, onOff)); + IdentityResponse idr = await readAsync(ipcReader, "IdentityOnOffAsync"); + return idr.Payload; + } catch (IOException ioe) { + //almost certainly a problem with the pipe - recreate the pipe... + //setupPipe(); + throw ioe; + } + } + + protected override void ProcessLine(string line) { + try { + string respAsString = line; + var jsonReaderEvt = new JsonTextReader(new StringReader(respAsString)); + StatusEvent evt = serializer.Deserialize(jsonReaderEvt); + var jsonReader = new JsonTextReader(new StringReader(respAsString)); + + switch (evt.Op) { + case "metrics": + MetricsEvent m = serializer.Deserialize(jsonReader); + + if (m != null) { + MetricsEvent(m.Identities); + } + break; + case "status": + TunnelStatusEvent se = serializer.Deserialize(jsonReader); + + if (se != null) { + TunnelStatusEvent(se); + } + break; + case "identity": + IdentityEvent id = serializer.Deserialize(jsonReader); + + if (id != null) { + IdentityEvent(id); + } + break; + case "service": + ServiceEvent svc = serializer.Deserialize(jsonReader); + + if (svc != null) { + ServiceEvent(svc); + } + break; + case "shutdown": + Logger.Debug("Service shutdown has been requested! " + evt.Op); + ClientDisconnected("true"); + break; + default: + Logger.Debug("unexpected operation! " + evt.Op); + break; + } + } catch (Exception e) { + Logger.Debug(e.Message); + } + } + + async public Task debugAsync() { + try { + await sendAsync(new ServiceFunction() { Function = "Debug" }); + var rtn = await readAsync(ipcReader, "debugAsync"); + return rtn; + } catch (IOException ioe) { + //almost certainly a problem with the pipe - recreate the pipe... + //setupPipe(); + throw ioe; + } + } + } +} \ No newline at end of file diff --git a/ZitiDesktopEdge.Client/ServiceClient/MonitorClient.cs b/ZitiDesktopEdge.Client/ServiceClient/MonitorClient.cs new file mode 100644 index 000000000..75cd4a7ce --- /dev/null +++ b/ZitiDesktopEdge.Client/ServiceClient/MonitorClient.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Pipes; +using System.Security.Principal; +using System.Security.AccessControl; +using System.Threading; +using System.Threading.Tasks; + +using Newtonsoft.Json; +using NLog; + +using ZitiDesktopEdge.DataStructures; +using ZitiDesktopEdge.Server; + +/// +/// The implementation will abstract away the setup of the communication to +/// the monitor service. This implementation will communicate to the service over a +/// a NamedPipe. +/// +/// All communication is effectively serial - one or more messages sent and +/// one or more messages returned. +/// +/// +namespace ZitiDesktopEdge.ServiceClient { + public class MonitorClient : AbstractClient { + private static readonly Logger _logger = LogManager.GetCurrentClassLogger(); + protected override Logger Logger { get { return _logger; } } + + public const int EXPECTED_API_VERSION = 1; + + public event EventHandler OnServiceStatusEvent; + + protected virtual void ServiceStatusEvent(ServiceStatusEvent e) { + OnServiceStatusEvent?.Invoke(this, e); + } + + public MonitorClient() : base() { + } + + async protected override Task ConnectPipesAsync() { + await semaphoreSlim.WaitAsync(); + try { + pipeClient = new NamedPipeClientStream(localPipeServer, IPCServer.PipeName, PipeDirection.InOut); + eventClient = new NamedPipeClientStream(localPipeServer, IPCServer.EventPipeName, PipeDirection.In); + await eventClient.ConnectAsync(ServiceConnectTimeout); + await pipeClient.ConnectAsync(ServiceConnectTimeout); + ClientConnected(null); + } catch (Exception ex) { + semaphoreSlim.Release(); + throw new ServiceException("Could not connect to the service.", 1, ex.Message); + } + semaphoreSlim.Release(); + } + + protected override void ProcessLine(string line) { + var jsonReader = new JsonTextReader(new StringReader(line)); + ServiceStatusEvent evt = serializer.Deserialize(jsonReader); + ServiceStatusEvent(evt); + } + + async internal Task SendServiceFunctionAsync(object toSend) { + await sendAsync(toSend); + + var resp = await readMessageAsync(ipcReader, "SendServiceFunctionAsync"); + Logger.Info("RESPONSE: {0}", resp); + return resp; + } + + async public Task StopServiceAsync() { + ActionEvent action = new ActionEvent() { Action = "Normal", Op = "Stop" }; + //string result = await SendServiceFunctionAsync(action); + await sendAsync(action); + return await readAsync(ipcReader, "StopServiceAsync"); + } + async public Task StartServiceAsync() { + ActionEvent action = new ActionEvent() { Action = "Normal", Op = "Start" }; + //string result = await SendServiceFunctionAsync(action); + await sendAsync(action); + return await readAsync(ipcReader, "StartServiceAsync"); + } + + async public Task ForceTerminate() { + ActionEvent action = new ActionEvent() { Action = "Force", Op = "Stop" }; + await sendAsync(action); + return await readAsync(ipcReader, "ForceTerminate"); + } + + async public Task Status() { + ActionEvent action = new ActionEvent() { Action = "", Op = "Status" }; + await sendAsync(action); + return await readAsync(ipcReader, "Status"); + } + } +} \ No newline at end of file diff --git a/ZitiDesktopEdge.Client/ZitiDesktopEdge.Client.csproj b/ZitiDesktopEdge.Client/ZitiDesktopEdge.Client.csproj index 19741a345..76f0bd162 100644 --- a/ZitiDesktopEdge.Client/ZitiDesktopEdge.Client.csproj +++ b/ZitiDesktopEdge.Client/ZitiDesktopEdge.Client.csproj @@ -7,11 +7,12 @@ {26B30979-99B5-4102-BD0B-129BEDFF0057} Library Properties - ZitiDesktopEdge.Client + ZitiDesktopEdge ZitiDesktopEdge.Client - v4.7.2 + v4.8 512 true + true @@ -33,7 +34,6 @@ ..\packages\Newtonsoft.Json.12.0.3\lib\net45\Newtonsoft.Json.dll - True ..\packages\NLog.4.7.5\lib\net45\NLog.dll @@ -44,6 +44,7 @@ + @@ -54,8 +55,13 @@ - - + + + + + + + diff --git a/ZitiDesktopEdge.sln b/ZitiDesktopEdge.sln index 3edaa021d..8167e22a5 100644 --- a/ZitiDesktopEdge.sln +++ b/ZitiDesktopEdge.sln @@ -11,10 +11,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ZitiDesktopEdge", "DesktopEdge\ZitiDesktopEdge.csproj", "{FA4291EA-A97E-4854-959F-91EE7B330B37}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ZitiUpdateService", "ZitiUpdateService\ZitiUpdateService.csproj", "{9AB81609-F5DD-40A6-87B5-F3279D48514D}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ZitiDesktopEdge.Client", "ZitiDesktopEdge.Client\ZitiDesktopEdge.Client.csproj", "{26B30979-99B5-4102-BD0B-129BEDFF0057}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ZitiUpdateService", "ZitiUpdateService\ZitiUpdateService.csproj", "{9AB81609-F5DD-40A6-87B5-F3279D48514D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -27,6 +27,11 @@ Global Release|ARM64 = Release|ARM64 Release|x64 = Release|x64 Release|x86 = Release|x86 + Releases|Any CPU = Releases|Any CPU + Releases|ARM = Releases|ARM + Releases|ARM64 = Releases|ARM64 + Releases|x64 = Releases|x64 + Releases|x86 = Releases|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {FA4291EA-A97E-4854-959F-91EE7B330B37}.Debug|Any CPU.ActiveCfg = Debug|Any CPU @@ -49,26 +54,16 @@ Global {FA4291EA-A97E-4854-959F-91EE7B330B37}.Release|x64.Build.0 = Release|Any CPU {FA4291EA-A97E-4854-959F-91EE7B330B37}.Release|x86.ActiveCfg = Release|Any CPU {FA4291EA-A97E-4854-959F-91EE7B330B37}.Release|x86.Build.0 = Release|Any CPU - {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Debug|ARM.ActiveCfg = Debug|Any CPU - {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Debug|ARM.Build.0 = Debug|Any CPU - {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Debug|ARM64.ActiveCfg = Debug|Any CPU - {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Debug|ARM64.Build.0 = Debug|Any CPU - {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Debug|x64.ActiveCfg = Debug|Any CPU - {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Debug|x64.Build.0 = Debug|Any CPU - {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Debug|x86.ActiveCfg = Debug|Any CPU - {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Debug|x86.Build.0 = Debug|Any CPU - {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Release|Any CPU.Build.0 = Release|Any CPU - {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Release|ARM.ActiveCfg = Release|Any CPU - {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Release|ARM.Build.0 = Release|Any CPU - {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Release|ARM64.ActiveCfg = Release|Any CPU - {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Release|ARM64.Build.0 = Release|Any CPU - {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Release|x64.ActiveCfg = Release|Any CPU - {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Release|x64.Build.0 = Release|Any CPU - {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Release|x86.ActiveCfg = Release|Any CPU - {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Release|x86.Build.0 = Release|Any CPU + {FA4291EA-A97E-4854-959F-91EE7B330B37}.Releases|Any CPU.ActiveCfg = Releases|Any CPU + {FA4291EA-A97E-4854-959F-91EE7B330B37}.Releases|Any CPU.Build.0 = Releases|Any CPU + {FA4291EA-A97E-4854-959F-91EE7B330B37}.Releases|ARM.ActiveCfg = Releases|Any CPU + {FA4291EA-A97E-4854-959F-91EE7B330B37}.Releases|ARM.Build.0 = Releases|Any CPU + {FA4291EA-A97E-4854-959F-91EE7B330B37}.Releases|ARM64.ActiveCfg = Releases|Any CPU + {FA4291EA-A97E-4854-959F-91EE7B330B37}.Releases|ARM64.Build.0 = Releases|Any CPU + {FA4291EA-A97E-4854-959F-91EE7B330B37}.Releases|x64.ActiveCfg = Releases|Any CPU + {FA4291EA-A97E-4854-959F-91EE7B330B37}.Releases|x64.Build.0 = Releases|Any CPU + {FA4291EA-A97E-4854-959F-91EE7B330B37}.Releases|x86.ActiveCfg = Releases|Any CPU + {FA4291EA-A97E-4854-959F-91EE7B330B37}.Releases|x86.Build.0 = Releases|Any CPU {26B30979-99B5-4102-BD0B-129BEDFF0057}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {26B30979-99B5-4102-BD0B-129BEDFF0057}.Debug|Any CPU.Build.0 = Debug|Any CPU {26B30979-99B5-4102-BD0B-129BEDFF0057}.Debug|ARM.ActiveCfg = Debug|Any CPU @@ -89,6 +84,46 @@ Global {26B30979-99B5-4102-BD0B-129BEDFF0057}.Release|x64.Build.0 = Release|Any CPU {26B30979-99B5-4102-BD0B-129BEDFF0057}.Release|x86.ActiveCfg = Release|Any CPU {26B30979-99B5-4102-BD0B-129BEDFF0057}.Release|x86.Build.0 = Release|Any CPU + {26B30979-99B5-4102-BD0B-129BEDFF0057}.Releases|Any CPU.ActiveCfg = Release|Any CPU + {26B30979-99B5-4102-BD0B-129BEDFF0057}.Releases|Any CPU.Build.0 = Release|Any CPU + {26B30979-99B5-4102-BD0B-129BEDFF0057}.Releases|ARM.ActiveCfg = Release|Any CPU + {26B30979-99B5-4102-BD0B-129BEDFF0057}.Releases|ARM.Build.0 = Release|Any CPU + {26B30979-99B5-4102-BD0B-129BEDFF0057}.Releases|ARM64.ActiveCfg = Release|Any CPU + {26B30979-99B5-4102-BD0B-129BEDFF0057}.Releases|ARM64.Build.0 = Release|Any CPU + {26B30979-99B5-4102-BD0B-129BEDFF0057}.Releases|x64.ActiveCfg = Release|Any CPU + {26B30979-99B5-4102-BD0B-129BEDFF0057}.Releases|x64.Build.0 = Release|Any CPU + {26B30979-99B5-4102-BD0B-129BEDFF0057}.Releases|x86.ActiveCfg = Release|Any CPU + {26B30979-99B5-4102-BD0B-129BEDFF0057}.Releases|x86.Build.0 = Release|Any CPU + {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Debug|ARM.ActiveCfg = Debug|Any CPU + {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Debug|ARM.Build.0 = Debug|Any CPU + {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Debug|ARM64.Build.0 = Debug|Any CPU + {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Debug|x64.ActiveCfg = Debug|Any CPU + {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Debug|x64.Build.0 = Debug|Any CPU + {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Debug|x86.ActiveCfg = Debug|Any CPU + {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Debug|x86.Build.0 = Debug|Any CPU + {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Release|Any CPU.Build.0 = Release|Any CPU + {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Release|ARM.ActiveCfg = Release|Any CPU + {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Release|ARM.Build.0 = Release|Any CPU + {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Release|ARM64.ActiveCfg = Release|Any CPU + {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Release|ARM64.Build.0 = Release|Any CPU + {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Release|x64.ActiveCfg = Release|Any CPU + {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Release|x64.Build.0 = Release|Any CPU + {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Release|x86.ActiveCfg = Release|Any CPU + {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Release|x86.Build.0 = Release|Any CPU + {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Releases|Any CPU.ActiveCfg = Releases|Any CPU + {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Releases|Any CPU.Build.0 = Releases|Any CPU + {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Releases|ARM.ActiveCfg = Releases|Any CPU + {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Releases|ARM.Build.0 = Releases|Any CPU + {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Releases|ARM64.ActiveCfg = Releases|Any CPU + {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Releases|ARM64.Build.0 = Releases|Any CPU + {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Releases|x64.ActiveCfg = Releases|Any CPU + {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Releases|x64.Build.0 = Releases|Any CPU + {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Releases|x86.ActiveCfg = Releases|Any CPU + {9AB81609-F5DD-40A6-87B5-F3279D48514D}.Releases|x86.Build.0 = Releases|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/ZitiUpdateService/App.config b/ZitiUpdateService/App.config index 29658eee5..1c4c262e9 100644 --- a/ZitiUpdateService/App.config +++ b/ZitiUpdateService/App.config @@ -1,7 +1,7 @@ - + - + - + - - + + - + - + - + - \ No newline at end of file + + + + + + + + + diff --git a/ZitiUpdateService/IUpdateCheck.cs b/ZitiUpdateService/IUpdateCheck.cs index fb0a472d7..6e64e91e2 100644 --- a/ZitiUpdateService/IUpdateCheck.cs +++ b/ZitiUpdateService/IUpdateCheck.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace ZitiUpdateService { interface IUpdateCheck { diff --git a/ZitiUpdateService/Program.cs b/ZitiUpdateService/Program.cs index 47b17a830..2e66652bd 100644 --- a/ZitiUpdateService/Program.cs +++ b/ZitiUpdateService/Program.cs @@ -1,60 +1,60 @@ -using System; -using System.IO; -using System.Reflection; -using System.ServiceProcess; -using NLog; +using NLog; using NLog.Config; using NLog.Targets; +using System.IO; +using System.Reflection; + namespace ZitiUpdateService { - static class Program { - private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); - - /// - /// The main entry point for the application. - /// - static void Main() { - //Environment.SetEnvironmentVariable("ZITI_EXTENDED_DEBUG", "true"); - var curdir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); - string nlogFile = Path.Combine(curdir, "ziti-monitor-log.config"); - - if (File.Exists(nlogFile)) { - LogManager.Configuration = new XmlLoggingConfiguration(nlogFile); - } else { - var config = new LoggingConfiguration(); - var logname = "ziti-montior"; - // Targets where to log to: File and Console - var logfile = new FileTarget("logfile") { - FileName = $"{logname}.log", - ArchiveEvery = FileArchivePeriod.Day, - ArchiveNumbering = ArchiveNumberingMode.Rolling, - MaxArchiveFiles = 7, - Layout = "${longdate}|${level:uppercase=true:padding=5}|${logger}|${message}", - //ArchiveAboveSize = 10000, - }; - var logconsole = new ConsoleTarget("logconsole"); + static class Program { - // Rules for mapping loggers to targets - config.AddRule(LogLevel.Info, LogLevel.Fatal, logconsole); - config.AddRule(LogLevel.Debug, LogLevel.Fatal, logfile); + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); - // Apply config - LogManager.Configuration = config; - } - Logger.Info("service started - logger initialized"); - UpdateService updateSvc = new UpdateService(); -#if DEBUG + /// + /// The main entry point for the application. + /// + static void Main() { + var asm = System.Reflection.Assembly.GetExecutingAssembly(); + var logname = asm.GetName().Name; + + var curdir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + string nlogFile = Path.Combine(curdir, "ziti-monitor-log.config"); + + if (File.Exists(nlogFile)) { + LogManager.Configuration = new XmlLoggingConfiguration(nlogFile); + } else { + var config = new LoggingConfiguration(); + // Targets where to log to: File and Console + var logfile = new FileTarget("logfile") { + FileName = $"logs\\ZitiMonitorService\\{logname}.log", + ArchiveEvery = FileArchivePeriod.Day, + ArchiveNumbering = ArchiveNumberingMode.Rolling, + MaxArchiveFiles = 7, + Layout = "${longdate}|${level:uppercase=true:padding=5}|${logger}|${message}", + }; + var logconsole = new ConsoleTarget("logconsole"); - updateSvc.Debug(); - System.Threading.Thread.Sleep(System.Threading.Timeout.Infinite); + // Rules for mapping loggers to targets + config.AddRule(LogLevel.Debug, LogLevel.Fatal, logconsole); + config.AddRule(LogLevel.Debug, LogLevel.Fatal, logfile); + + // Apply config + LogManager.Configuration = config; + } + Logger.Info("service started - logger initialized"); + + UpdateService updateSvc = new UpdateService(); + updateSvc.AutoLog = true; +#if DEBUG + updateSvc.Debug(); + updateSvc.WaitForCompletion(); #else - ServiceBase[] ServicesToRun; - ServicesToRun = new ServiceBase[] + ServiceBase[] ServicesToRun = new ServiceBase[] { updateSvc }; ServiceBase.Run(ServicesToRun); #endif - } + } } } diff --git a/ZitiUpdateService/ProjectInstaller.cs b/ZitiUpdateService/ProjectInstaller.cs index 3720deaa9..2ee16e83f 100644 --- a/ZitiUpdateService/ProjectInstaller.cs +++ b/ZitiUpdateService/ProjectInstaller.cs @@ -1,20 +1,15 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.ComponentModel; -using System.Configuration.Install; -using System.Linq; -using System.Threading.Tasks; - -namespace ZitiUpdateService { - [RunInstaller(true)] - public partial class ProjectInstaller : System.Configuration.Install.Installer { - public ProjectInstaller() { - InitializeComponent(); - } - - private void ZitiUpdateServiceInstaller_AfterInstall(object sender, InstallEventArgs e) { - - } - } -} +using System.ComponentModel; +using System.Configuration.Install; + +namespace ZitiUpdateService { + [RunInstaller(true)] + public partial class ProjectInstaller : System.Configuration.Install.Installer { + public ProjectInstaller() { + InitializeComponent(); + } + + private void ZitiUpdateServiceInstaller_AfterInstall(object sender, InstallEventArgs e) { + + } + } +} diff --git a/ZitiUpdateService/Properties/AssemblyInfo.cs b/ZitiUpdateService/Properties/AssemblyInfo.cs index 54b4e41bc..9d780e463 100644 --- a/ZitiUpdateService/Properties/AssemblyInfo.cs +++ b/ZitiUpdateService/Properties/AssemblyInfo.cs @@ -1,4 +1,4 @@ -using System.Reflection; +using System.Reflection; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following diff --git a/ZitiUpdateService/ServiceMonitor.cs b/ZitiUpdateService/ServiceMonitor.cs deleted file mode 100644 index 8d02bb038..000000000 --- a/ZitiUpdateService/ServiceMonitor.cs +++ /dev/null @@ -1,27 +0,0 @@ - -namespace ZitiUpdateService -{ - public class ServiceMonitor - { - public void MonitorSrevice() - { - //a simple method that opens a pipe to the data service and tries to read data - //from the pipe. If the pipe doesn't respond - } - - public void ResetNetwork() - { - - } - - public void ResetRouting() - { - - } - - public void ResetDNS() - { - - } - } -} \ No newline at end of file diff --git a/ZitiUpdateService/ServiceUpdater.cs b/ZitiUpdateService/ServiceUpdater.cs deleted file mode 100644 index 4853fccc8..000000000 --- a/ZitiUpdateService/ServiceUpdater.cs +++ /dev/null @@ -1,8 +0,0 @@ - -namespace ZitiUpdateService -{ - public class ServiceUpdater - { - - } -} \ No newline at end of file diff --git a/ZitiUpdateService/UpdateChecker.cs b/ZitiUpdateService/UpdateChecker.cs deleted file mode 100644 index 220d04758..000000000 --- a/ZitiUpdateService/UpdateChecker.cs +++ /dev/null @@ -1,8 +0,0 @@ - -namespace ZitiUpdateService -{ - public class UpdateChecker - { - - } -} \ No newline at end of file diff --git a/ZitiUpdateService/UpdateCheckers.cs b/ZitiUpdateService/UpdateCheckers.cs index 34c265b12..564b15ab9 100644 --- a/ZitiUpdateService/UpdateCheckers.cs +++ b/ZitiUpdateService/UpdateCheckers.cs @@ -1,93 +1,100 @@ -using System; +using Newtonsoft.Json.Linq; +using NLog; +using System; using System.IO; using System.Net; -using NLog; -using Newtonsoft.Json.Linq; - namespace ZitiUpdateService { - internal class GithubCheck : IUpdateCheck { - private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); - string url; - string downloadUrl = null; - string downloadFileName = null; + internal class GithubCheck : IUpdateCheck { + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + string url; + string downloadUrl = null; + string downloadFileName = null; + + public GithubCheck(string url) { + this.url = url; + } + + public bool AlreadyDownloaded(string destinationFolder, string destinationName) { + return File.Exists(Path.Combine(destinationFolder, destinationName)); + } + + public void CopyUpdatePackage(string destinationFolder, string destinationName) { + WebClient webClient = new WebClient(); + string dest = Path.Combine(destinationFolder, destinationName); + Logger.Info("download started: {0}", downloadUrl); + webClient.DownloadFile(downloadUrl, dest); + Logger.Info("download complete. file at {0}", dest); + } + + public string FileName() { + return downloadFileName; + } - public GithubCheck(string url) { - this.url = url; - } + public bool IsUpdateAvailable(Version current) { + HttpWebRequest httpWebRequest = WebRequest.CreateHttp(url); + httpWebRequest.Method = "GET"; + httpWebRequest.ContentType = "application/json"; + httpWebRequest.UserAgent = "OpenZiti UpdateService"; + HttpWebResponse httpResponse = (HttpWebResponse)httpWebRequest.GetResponse(); + StreamReader streamReader = new StreamReader(httpResponse.GetResponseStream()); + string result = streamReader.ReadToEnd(); + JObject json = JObject.Parse(result); + string serverVersion = json.Property("tag_name").Value.ToString() + ".0"; + + Version published = new Version(serverVersion); + int compare = current.CompareTo(published); + if (compare < 0) { + Logger.Info("an upgrade is available."); + } else if (compare > 0) { + Logger.Info("the version installed is newer than the released version"); + return false; + } else { + Logger.Debug("current version {0} is the same as the latest release {0}", current, published); + return false; + } + + JArray assets = JArray.Parse(json.Property("assets").Value.ToString()); + foreach (JObject asset in assets.Children()) { + downloadUrl = asset.Property("browser_download_url").Value.ToString(); + break; + } + + if (downloadUrl == null) { + Logger.Error("DOWNLOAD URL not found at: {0}", url); + return false; + } + downloadFileName = downloadUrl.Substring(downloadUrl.LastIndexOf('/') + 1); + + return true; + } + } + + internal class FilesystemCheck : IUpdateCheck { + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + string dest = null; + + bool isUpdateAvailable = false; + public FilesystemCheck(bool updateAvailable) { + this.isUpdateAvailable = updateAvailable; + + } public bool AlreadyDownloaded(string destinationFolder, string destinationName) { - return File.Exists(Path.Combine(destinationFolder, destinationName)); - } + return File.Exists(Path.Combine(destinationFolder, destinationName)); + } public void CopyUpdatePackage(string destinationFolder, string destinationName) { - WebClient webClient = new WebClient(); - string dest = Path.Combine(destinationFolder, destinationName); - Logger.Info("download started: {0}", downloadUrl); - webClient.DownloadFile(downloadUrl, dest); - Logger.Info("download complete. file at {0}", dest); - } - - public string FileName() { - return downloadFileName; - } - - public bool IsUpdateAvailable(Version current) { - HttpWebRequest httpWebRequest = WebRequest.CreateHttp(url); - httpWebRequest.Method = "GET"; - httpWebRequest.ContentType = "application/json"; - httpWebRequest.UserAgent = "OpenZiti UpdateService"; - HttpWebResponse httpResponse = (HttpWebResponse)httpWebRequest.GetResponse(); - StreamReader streamReader = new StreamReader(httpResponse.GetResponseStream()); - string result = streamReader.ReadToEnd(); - JObject json = JObject.Parse(result); - string serverVersion = json.Property("tag_name").Value.ToString() + ".0"; - - Version published = new Version(serverVersion); - int compare = current.CompareTo(published); - if (compare < 0) { - Logger.Info("an upgrade is available."); - } else if (compare > 0) { - Logger.Info("the version installed is newer than the released version"); - return false; - } else { - Logger.Debug("current version {0} is the same as the latest release {0}", current, published); - return false; - } - JArray assets = JArray.Parse(json.Property("assets").Value.ToString()); - foreach (JObject asset in assets.Children()) { - downloadUrl = asset.Property("browser_download_url").Value.ToString(); - break; - } - - if (downloadUrl == null) { - Logger.Error("DOWNLOAD URL not found at: {0}", url); - return false; - } - downloadFileName = downloadUrl.Substring(downloadUrl.LastIndexOf('/') + 1); - - return true; - } - } - - internal class FilesystemCheck : IUpdateCheck { - private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); - string dest = null; - public bool AlreadyDownloaded(string destinationFolder, string destinationName) { - return File.Exists(Path.Combine(destinationFolder, destinationName)); - } - - public void CopyUpdatePackage(string destinationFolder, string destinationName) { - dest = Path.Combine(destinationFolder, destinationName); - File.Copy(@"C:\git\github\openziti\desktop-edge-win\Installer\Output\" + FileName(), dest); - } - - public string FileName() { - return "Ziti Desktop Edge Client-1.3.0.exe"; - } - - public bool IsUpdateAvailable(Version current) { - return true; - } - } + dest = Path.Combine(destinationFolder, destinationName); + File.Copy(@"C:\git\github\openziti\desktop-edge-win\Installer\Output\" + FileName(), dest); + } + + public string FileName() { + return "Ziti Desktop Edge Client-1.3.0.exe"; + } + + public bool IsUpdateAvailable(Version current) { + return isUpdateAvailable; + } + } } diff --git a/ZitiUpdateService/UpdateService.cs b/ZitiUpdateService/UpdateService.cs index 96c43d7ef..ad652d4f8 100644 --- a/ZitiUpdateService/UpdateService.cs +++ b/ZitiUpdateService/UpdateService.cs @@ -1,216 +1,300 @@ -using System; +using NLog; +using System; +using System.Configuration; using System.Diagnostics; +using System.IO; using System.Linq; +using System.Reflection; +using System.Security.Cryptography.X509Certificates; using System.ServiceProcess; -using System.IO; -using System.Timers; -using System.Configuration; using System.Threading.Tasks; - +using System.Timers; +using ZitiDesktopEdge.DataStructures; +using ZitiDesktopEdge.Server; using ZitiDesktopEdge.ServiceClient; -using NLog; -using System.Reflection; namespace ZitiUpdateService { - public partial class UpdateService : ServiceBase { - private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); - - private Timer _updateTimer = new Timer(); - private bool inUpdateCheck = false; - private string _rootDirectory = ""; - private string _logDirectory = ""; - private string _versionType = "latest"; - - private Client svc = new Client(); - private bool running = false; - - ServiceController controller; - public UpdateService() { - InitializeComponent(); - - Logger.Info("Initializing"); - svc.OnClientConnected += Svc_OnClientConnected; - svc.OnTunnelStatusEvent += Svc_OnTunnelStatusEvent; - svc.OnClientDisconnected += Svc_OnClientDisconnected; - svc.OnShutdownEvent += Svc_OnShutdownEvent; - } - - public void Debug() { - OnStart(null); - } - - protected override void OnStart(string[] args) { - Logger.Info("ziti-monitor service is starting"); - try { - if (ConfigurationManager.AppSettings.Get("Version") != null) _versionType = ConfigurationManager.AppSettings.Get("Version"); - } catch (Exception e) { - Logger.Info(e.ToString()); - } - _rootDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "OpenZiti"); - if (!Directory.Exists(_rootDirectory)) Directory.CreateDirectory(_rootDirectory); - _logDirectory = Path.Combine(_rootDirectory, "Logs"); - if (!Directory.Exists(_logDirectory)) Directory.CreateDirectory(_logDirectory); - if (!running) { - running = true; - Task.Run(() => { - SetupServiceWatchers(); - }); - } - Logger.Info("ziti-monitor service is initialized and running"); - } - - protected override void OnStop() { - Logger.Info("ziti-monitor service is stopping"); - } - - private void SetupServiceWatchers() { - - var updateTimerInterval = ConfigurationManager.AppSettings.Get("UpdateTimer"); - var upInt = TimeSpan.Zero; - if (!TimeSpan.TryParse(updateTimerInterval, out upInt)) { - upInt = new TimeSpan(0, 1, 0); - } - - _updateTimer = new Timer(); - _updateTimer.Elapsed += CheckUpdate; - _updateTimer.Interval = upInt.TotalMilliseconds; - _updateTimer.Enabled = true; - _updateTimer.Start(); - Logger.Info("Version Checker is running"); - CheckUpdate(null, null); //check immediately - - try { - svc.Connect(); - } catch { - svc.Reconnect(); - } - - svc.WaitForConnection(); - } - - private void CheckUpdate(object sender, ElapsedEventArgs e) { - if (inUpdateCheck) return; - inUpdateCheck = true; //simple semaphone - try { - Logger.Debug("checking for update"); - var updateUrl = ConfigurationManager.AppSettings.Get("UpdateUrl"); - if (string.IsNullOrEmpty(updateUrl)) { - updateUrl = "https://api.github.com/repos/openziti/desktop-edge-win/releases/latest"; - } - IUpdateCheck check = new GithubCheck(updateUrl); - //IUpdateCheck check = new FilesystemCheck(); - - string currentVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString(); //fetch from ziti? - Version installed = new Version(currentVersion); - if (!check.IsUpdateAvailable(installed)) { - Logger.Debug("update check complete. no update available"); - inUpdateCheck = false; - return; - } - - Logger.Info("update is available."); - - var curdir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); - - var updateFolder = $"{curdir}{Path.DirectorySeparatorChar}updates"; - Directory.CreateDirectory(updateFolder); - - Logger.Info("copying update package"); - string filename = check.FileName(); - - if (check.AlreadyDownloaded(updateFolder, filename)) { - Logger.Info("package has already been downloaded - moving to install phase"); - } else { - Logger.Info("copying update package begins"); - check.CopyUpdatePackage(updateFolder, filename); - Logger.Info("copying update package complete"); - } - - StopZiti(); - - // shell out to a new process and run the uninstall, reinstall steps which SHOULD stop this current process as well - string fileDestination = Path.Combine(updateFolder, filename); - Process.Start(fileDestination, "/passive"); - } catch (Exception ex) { - Logger.Error(ex, "Unexpected error has occurred"); - } - inUpdateCheck = false; - } - - private void StartZiti() { - controller = ServiceController.GetServices().FirstOrDefault(s => s.ServiceName == "ziti"); - if (controller != null && controller.Status != ServiceControllerStatus.Running && controller.Status != ServiceControllerStatus.StartPending && controller.Status != ServiceControllerStatus.ContinuePending) { - try { - Logger.Info("Starting Service"); - controller.Start(); - controller.WaitForStatus(ServiceControllerStatus.Running, TimeSpan.FromSeconds(30)); - SetupServiceWatchers(); - } catch (Exception e) { - Logger.Info("Cannot Start Service - " + e.ToString()); - } - } - } - - private void StopZiti() { - Logger.Info("Stopping the ziti service..."); - controller = ServiceController.GetServices().FirstOrDefault(s => s.ServiceName == "ziti"); - if (controller != null && controller.Status != ServiceControllerStatus.Stopped) { - try { - controller.Stop(); - controller.WaitForStatus(ServiceControllerStatus.Stopped, TimeSpan.FromSeconds(30)); - } catch (Exception e) { - Logger.Error(e, "Timout while trying to stop service!"); - } - } - } - - private static void Svc_OnShutdownEvent(object sender, StatusEvent e) { - Logger.Info("the service is shutting down normally..."); - } - - private static void Svc_OnTunnelStatusEvent(object sender, TunnelStatusEvent e) { - string dns = e?.Status?.IpInfo?.DNS; - string version = e?.Status?.ServiceVersion.Version; - string op = e?.Op; - Logger.Info($"Operation {op}. running dns: {dns} at version {version}"); - } - - private static void Svc_OnClientConnected(object sender, object e) { - Logger.Info("successfully connected to service"); - } - - private static void Svc_OnClientDisconnected(object sender, object e) { - Client svc = (Client)sender; - if (svc.CleanShutdown) { - //then this is fine and expected - the service is shutting down - Logger.Info("client disconnected due to clean service shutdown"); - } else { - Logger.Error("SERVICE IS DOWN and did not exit cleanly. initiating DNS cleanup"); - //EnumerateDNS(); - var ps = System.Management.Automation.PowerShell.Create(); - string script = "Get-NetIPInterface | ForEach-Object { Set-DnsClientServerAddress -InterfaceIndex $_.ifIndex -ResetServerAddresses }"; - ps.Commands.AddScript(script); - Logger.Info("No longer connected to the service. Resetting the network by executing reset script."); - Task.Delay(1000).Wait(); - ps.Invoke(); - Logger.Info("Reset script executed."); - //EnumerateDNS(); - } - } - - private static void EnumerateDNS() { - var ps = System.Management.Automation.PowerShell.Create(); - ps.AddScript("Get-DnsClientServerAddress"); - var results = ps.Invoke(); - - using (StringWriter sw = new StringWriter()) { - foreach (var r in results) { - string name = (string)r.Properties["InterfaceAlias"].Value; - string[] dnses = (string[])r.Properties["ServerAddresses"].Value; - sw.WriteLine($"Interface: {name}. DNS: {string.Join(",", dnses)}"); - } - Logger.Info("DNS RESULTS:\n{0}", sw.ToString()); - } - } - } + public partial class UpdateService : ServiceBase { + private static string[] expected_hashes = new string[] { "39636E9F5E80308DE370C914CE8112876ECF4E0C" }; + private static string[] expected_subject = new string[] { @"CN=""NetFoundry, Inc."", O=""NetFoundry, Inc."", L=Herndon, S=Virginia, C=US" }; + + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + + private Timer _updateTimer = new Timer(); + private bool inUpdateCheck = false; + private string _rootDirectory = ""; + private string _logDirectory = ""; + private string _versionType = "latest"; + + private DataClient svc = new DataClient(); + private bool running = false; + private string asmDir = null; + private string updateFolder = null; + private string filePrefix = "Ziti.Desktop.Edge.Client-"; + Version assemblyVersion = null; + + ServiceController controller; + ZitiDesktopEdge.Server.IPCServer svr = new ZitiDesktopEdge.Server.IPCServer(); + Task ipcServer = null; + Task eventServer = null; + + public UpdateService() { + InitializeComponent(); + + Logger.Info("Initializing"); + svc.OnClientConnected += Svc_OnClientConnected; + svc.OnTunnelStatusEvent += Svc_OnTunnelStatusEvent; + svc.OnClientDisconnected += Svc_OnClientDisconnected; + svc.OnShutdownEvent += Svc_OnShutdownEvent; + } + + public void Debug() { + OnStart(new string[] { "FilesystemCheck" }); + } + + protected override void OnStart(string[] args) { + Logger.Info("ziti-monitor service is starting"); + try { + if (ConfigurationManager.AppSettings.Get("Version") != null) _versionType = ConfigurationManager.AppSettings.Get("Version"); + } catch (Exception e) { + Logger.Info(e.ToString()); + } + _rootDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "OpenZiti"); + if (!Directory.Exists(_rootDirectory)) Directory.CreateDirectory(_rootDirectory); + _logDirectory = Path.Combine(_rootDirectory, "Logs"); + if (!Directory.Exists(_logDirectory)) Directory.CreateDirectory(_logDirectory); + if (!running) { + running = true; + Task.Run(() => { + SetupServiceWatchers(args); + }); + } + + ipcServer = svr.startIpcServer(); + eventServer = svr.startEventsServer(); + + Logger.Info("ziti-monitor service is initialized and running"); + } + + public void WaitForCompletion() { + Task.WaitAll(ipcServer, eventServer); + } + + protected override void OnStop() { + Logger.Info("ziti-monitor service is stopping"); + } + + private void SetupServiceWatchers(string[] args) { + + var updateTimerInterval = ConfigurationManager.AppSettings.Get("UpdateTimer"); + var upInt = TimeSpan.Zero; + if (!TimeSpan.TryParse(updateTimerInterval, out upInt)) { + upInt = new TimeSpan(0, 1, 0); + } + + _updateTimer = new Timer(); + _updateTimer.Elapsed += CheckUpdate; + _updateTimer.Interval = upInt.TotalMilliseconds; + _updateTimer.Enabled = true; + _updateTimer.Start(); + Logger.Info("Version Checker is running"); + + + string assemblyVersionStr = Assembly.GetExecutingAssembly().GetName().Version.ToString(); //fetch from ziti? + assemblyVersion = new Version(assemblyVersionStr); + asmDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + updateFolder = $"{asmDir}{Path.DirectorySeparatorChar}updates"; + scanForStaleDownloads(updateFolder); + CheckUpdate(args, null); //check immediately + + try { + svc.ConnectAsync().Wait(); + } catch { + svc.Reconnect(); + } + + svc.WaitForConnectionAsync().Wait(); + } + + private void CheckUpdate(object sender, ElapsedEventArgs e) { + if (inUpdateCheck) return; + inUpdateCheck = true; //simple semaphone + try { + Logger.Debug("checking for update"); + string updateUrl = "https://api.github.com/repos/openziti/desktop-edge-win/releases/latest"; //hardcoded on purpose + string[] senderAsArgs = (string[])sender; + IUpdateCheck check = null; + if (sender == null || senderAsArgs.Length < 1 || !senderAsArgs[0].Equals("FilesystemCheck")) { + check = new GithubCheck(updateUrl); + } else { + check = new FilesystemCheck(false); + } + + if (!check.IsUpdateAvailable(assemblyVersion)) { + Logger.Debug("update check complete. no update available"); + inUpdateCheck = false; + return; + } + + Logger.Info("update is available."); + Directory.CreateDirectory(updateFolder); + + Logger.Info("copying update package"); + string filename = check.FileName(); + + if (check.AlreadyDownloaded(updateFolder, filename)) { + Logger.Info("package has already been downloaded - moving to install phase"); + } else { + Logger.Info("copying update package begins"); + check.CopyUpdatePackage(updateFolder, filename); + Logger.Info("copying update package complete"); + } + + string fileDestination = Path.Combine(updateFolder, filename); + + // check digital signature + var signer = X509Certificate.CreateFromSignedFile(fileDestination); + /* keep these commented out lines - just in case we need all the certs from the file use this + var coll = new X509Certificate2Collection(); + coll.Import(filePath); + */ + + var subject = signer.Subject; + if (!expected_subject.Contains(subject)) { + Logger.Error("the file downloaded uses a subject that is unknown! the installation will not proceed. [subject:{0}]", subject); + return; + + } else { + Logger.Info("the file downloaded uses a known subject. installation and can proceed. [subject:{0}]", subject); + } + + var hash = signer.GetCertHashString(); + if (!expected_hashes.Contains(hash)) { + Logger.Error("the file downloaded is signed by an unknown certificate! the installation will not proceed. [hash:{0}]", hash); + return; + + } else { + Logger.Info("the file downloaded is signed by a known certificate. installation and can proceed. [subject:{0}]", subject); + } + + StopZiti(); + + // shell out to a new process and run the uninstall, reinstall steps which SHOULD stop this current process as well + Process.Start(fileDestination, "/passive"); + } catch (Exception ex) { + Logger.Error(ex, "Unexpected error has occurred"); + } + inUpdateCheck = false; + } + + private bool isOlder(Version current) { + int compare = current.CompareTo(assemblyVersion); + if (compare < 0) { + return true; + } else if (compare > 0) { + return false; + } else { + return false; + } + } + + private void scanForStaleDownloads(string folder) { + try { + Logger.Info("Scanning for stale downloads"); + foreach (var f in Directory.EnumerateFiles(folder)) { + FileInfo fi = new FileInfo(f); + if (fi.Exists) { + if (fi.Name.StartsWith(filePrefix)) { + Logger.Debug("scanning for staleness: " + f); + string ver = Path.GetFileNameWithoutExtension(f).Substring(filePrefix.Length); + Version fileVersion = new Version(ver); + if (isOlder(fileVersion)) { + Logger.Info("Removing old download: " + fi.Name); + fi.Delete(); + } else { + Logger.Debug("Retaining file. {1} is the same or newer than {1}", fi.Name, assemblyVersion); + } + } else { + Logger.Debug("skipping file named {0}", f); + } + } else { + Logger.Debug("file named {0} did not exist?", f); + } + } + } catch (Exception ex) { + Logger.Error(ex, "Unexpected exception"); + } + } + + private void StopZiti() { + Logger.Info("Stopping the ziti service..."); + controller = ServiceController.GetServices().FirstOrDefault(s => s.ServiceName == "ziti"); + if (controller != null && controller.Status != ServiceControllerStatus.Stopped) { + try { + controller.Stop(); + controller.WaitForStatus(ServiceControllerStatus.Stopped, TimeSpan.FromSeconds(30)); + } catch (Exception e) { + Logger.Error(e, "Timout while trying to stop service!"); + } + } + } + + private static void Svc_OnShutdownEvent(object sender, StatusEvent e) { + Logger.Info("the service is shutting down normally..."); + } + + private static void Svc_OnTunnelStatusEvent(object sender, TunnelStatusEvent e) { + string dns = e?.Status?.IpInfo?.DNS; + string version = e?.Status?.ServiceVersion.Version; + string op = e?.Op; + Logger.Info($"Operation {op}. running dns: {dns} at version {version}"); + } + + private static void Svc_OnClientConnected(object sender, object e) { + Logger.Info("successfully connected to service"); + } + + private static void Svc_OnClientDisconnected(object sender, object e) { + DataClient svc = (DataClient)sender; + if (svc.CleanShutdown) { + //then this is fine and expected - the service is shutting down + Logger.Info("client disconnected due to clean service shutdown"); + } else { + Logger.Error("SERVICE IS DOWN and did not exit cleanly. initiating DNS cleanup"); + + ServiceStatusEvent status = new ServiceStatusEvent() { + Code = 10, + Error = "SERVICE DOWN", + Message = "SERVICE DOWN", + Status = ServiceActions.ServiceStatus() + }; + EventRegistry.SendEventToConsumers(status); + + //EnumerateDNS(); + var ps = System.Management.Automation.PowerShell.Create(); + string script = "Get-NetIPInterface | ForEach-Object { Set-DnsClientServerAddress -InterfaceIndex $_.ifIndex -ResetServerAddresses }"; + ps.Commands.AddScript(script); + Logger.Info("No longer connected to the service. Resetting the network by executing reset script."); + Task.Delay(1000).Wait(); + ps.Invoke(); + Logger.Info("Reset script executed."); + //EnumerateDNS(); + } + } + + private static void EnumerateDNS() { + var ps = System.Management.Automation.PowerShell.Create(); + ps.AddScript("Get-DnsClientServerAddress"); + var results = ps.Invoke(); + + using (StringWriter sw = new StringWriter()) { + foreach (var r in results) { + string name = (string)r.Properties["InterfaceAlias"].Value; + string[] dnses = (string[])r.Properties["ServerAddresses"].Value; + sw.WriteLine($"Interface: {name}. DNS: {string.Join(",", dnses)}"); + } + Logger.Info("DNS RESULTS:\n{0}", sw.ToString()); + } + } + } } diff --git a/ZitiUpdateService/ZitiUpdateService.csproj b/ZitiUpdateService/ZitiUpdateService.csproj index 52a43b791..cbc070cea 100644 --- a/ZitiUpdateService/ZitiUpdateService.csproj +++ b/ZitiUpdateService/ZitiUpdateService.csproj @@ -28,6 +28,7 @@ false true true + AnyCPU diff --git a/ZitiUpdateService/ziti-monitor-log.config b/ZitiUpdateService/ziti-monitor-log.config index 23485ec72..974322e1b 100644 --- a/ZitiUpdateService/ziti-monitor-log.config +++ b/ZitiUpdateService/ziti-monitor-log.config @@ -6,15 +6,15 @@ + maxArchiveFiles="7" /> - + diff --git a/release-notes.md b/release-notes.md index d3d549f24..09954ed02 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,4 +1,24 @@ -# Release 1.4.3 +# Release 1.4.5 + +## What's New +* closes #216 - The big change is that the big button now will send a message to the monitor service which will have the proper rights to stop and start the data service (`ziti`). + +## Other changes: +* Changed the default mask to /10 as to not be different +* Changed the minimum allowable mask to be /16 +* Migrate any masks > /16 to /16 +* fixes #220 - Alphabetize the service list + +## Bugs fixed: +* fixes #221 - Cleanup previous update files +* fixes #218 - 0 length config cause panic +* fixes #211 - segv on hosted service + +## Dependency Updates +* update to github.com/openziti/sdk-golang v0.14.12 + +# Release 1.4.4 +(skipped 1.4.3 by mistake) ## What's New diff --git a/service/build.bat b/service/build.bat index 29ff94ed7..7e2237957 100644 --- a/service/build.bat +++ b/service/build.bat @@ -14,7 +14,7 @@ REM See the License for the specific language governing permissions and REM limitations under the License. REM SET REPO_URL=https://github.com/openziti/ziti-tunnel-sdk-c.git -SET ZITI_TUNNEL_REPO_BRANCH=v0.7.12 +SET ZITI_TUNNEL_REPO_BRANCH=v0.7.14 REM override the c sdk used in the build - leave blank for the same as specified in the tunneler sdk SET ZITI_SDK_C_BRANCH= REM the number of TCP connections the tunneler sdk can have at any one time diff --git a/service/ziti-tunnel/config/config.go b/service/ziti-tunnel/config/config.go index 9b19c1505..58de2012b 100644 --- a/service/ziti-tunnel/config/config.go +++ b/service/ziti-tunnel/config/config.go @@ -31,3 +31,7 @@ func Path() string { func LogFile() string { return Path() + "ziti-tunneler.log" } + +func BackupFile() string { + return File() + ".backup" +} diff --git a/service/ziti-tunnel/constants/consts.go b/service/ziti-tunnel/constants/consts.go index f15043f73..029611028 100644 --- a/service/ziti-tunnel/constants/consts.go +++ b/service/ziti-tunnel/constants/consts.go @@ -20,6 +20,6 @@ package constants const( Ipv4ip = "100.64.0.1" Ipv4MaxMask = 8 - Ipv4MinMask = 28 - Ipv4DefaultMask = 16 + Ipv4MinMask = 16 + Ipv4DefaultMask = 10 ) \ No newline at end of file diff --git a/service/ziti-tunnel/service/ipc.go b/service/ziti-tunnel/service/ipc.go index ab8c97adb..5d75df948 100644 --- a/service/ziti-tunnel/service/ipc.go +++ b/service/ziti-tunnel/service/ipc.go @@ -104,7 +104,7 @@ func SubMain(ops chan string, changes chan<- svc.Status) error { _ = logging.Elog.Info(InformationEvent, SvcName+" status set to running") log.Info(SvcName + " status set to running. starting cancel loop") - rts.SaveState() + rts.SaveState() //if we get this far it means things seem to be working. backup the config waitForStopRequest(ops) diff --git a/service/ziti-tunnel/service/pkg-vars.go b/service/ziti-tunnel/service/pkg-vars.go index 039dc0e2c..4d915515f 100644 --- a/service/ziti-tunnel/service/pkg-vars.go +++ b/service/ziti-tunnel/service/pkg-vars.go @@ -25,7 +25,7 @@ import ( "time" ) var Version dto.ServiceVersion -var pipeBase = `\\.\pipe\NetFoundry\tunneler\` +var pipeBase = `\\.\pipe\OpenZiti\ziti\` var rts = RuntimeState{ ids: make(map[string]*Id), diff --git a/service/ziti-tunnel/service/state.go b/service/ziti-tunnel/service/state.go index 166a57333..bc4cf6a4a 100644 --- a/service/ziti-tunnel/service/state.go +++ b/service/ziti-tunnel/service/state.go @@ -54,7 +54,7 @@ func (t *RuntimeState) SaveState() { // overwrite file if it exists _ = os.MkdirAll(config.Path(), 0644) - log.Debugf("copying original config onto config.json.backup") + log.Debugf("backing up config") backup,err := backupConfig() if err != nil { log.Warnf("could not backup config file! %v", err) @@ -252,29 +252,50 @@ func (t *RuntimeState) LoadIdentity(id *Id) { } func (t *RuntimeState) LoadConfig() { - log.Infof("reading config file located at: %s", config.File()) - file, err := os.OpenFile(config.File(), os.O_RDONLY, 0644) + err := readConfig(t, config.File()) if err != nil { - t.state = &dto.TunnelStatus{} - return + err = readConfig(t, config.BackupFile()) + if err != nil { + log.Panicf("config file is not valid nor is backup file!") + } } - r := bufio.NewReader(file) - dec := json.NewDecoder(r) + //any specific code needed when starting the process. some values need to be cleared + TunStarted = time.Now() //reset the time on startup - err = dec.Decode(&rts.state) - if err != nil { - log.Panicf("unexpected error reading config file. %v", err) + if rts.state.TunIpv4Mask > constants.Ipv4MinMask { + log.Warnf("provided mask: [%d] is smaller than the minimum permitted: [%d] and will be changed", rts.state.TunIpv4Mask, constants.Ipv4MinMask) + rts.UpdateIpv4Mask(constants.Ipv4MinMask) + } +} + +func readConfig(t *RuntimeState, filename string) error { + log.Infof("reading config file located at: %s", filename) + info, err := os.Stat(filename) + if os.IsNotExist(err) { + log.Infof("the config file does not exist. this is normal if this is a new install or if the config file was removed manually") + return nil } - err = file.Close() + if info.Size() == 0 { + return fmt.Errorf("the config file at contains no bytes and is considered invalid: %s", filename) + } + + file, err := os.OpenFile(filename, os.O_RDONLY, 0644) if err != nil { - log.Errorf("could not close configuration file. this is not normal! %v", err) + return fmt.Errorf("unexpected error opening config file: %v", err) } - for _, id := range t.ids { - id.Active = false + r := bufio.NewReader(file) + dec := json.NewDecoder(r) + + err = dec.Decode(&t.state) + defer file.Close() + + if err != nil { + return fmt.Errorf("unexpected error reading config file: %v", err) } + return nil } func (t *RuntimeState) UpdateIpv4Mask(ipv4mask int){ @@ -312,14 +333,14 @@ func iPv6Disabled() bool { } } -func (r *RuntimeState) AddRoute(destination net.IPNet, nextHop net.IP, metric uint32) error { - nativeTunDevice := (*r.tun).(*tun.NativeTun) +func (t *RuntimeState) AddRoute(destination net.IPNet, nextHop net.IP, metric uint32) error { + nativeTunDevice := (*t.tun).(*tun.NativeTun) luid := winipcfg.LUID(nativeTunDevice.LUID()) return luid.AddRoute(destination, nextHop, metric) } -func (r *RuntimeState) RemoveRoute(destination net.IPNet, nextHop net.IP) error { - nativeTunDevice := (*r.tun).(*tun.NativeTun) +func (t *RuntimeState) RemoveRoute(destination net.IPNet, nextHop net.IP) error { + nativeTunDevice := (*t.tun).(*tun.NativeTun) luid := winipcfg.LUID(nativeTunDevice.LUID()) return luid.DeleteRoute(destination, nextHop) }