diff --git a/DesktopEdge/MainWindow.xaml.cs b/DesktopEdge/MainWindow.xaml.cs index 2116cb94..b66159dc 100644 --- a/DesktopEdge/MainWindow.xaml.cs +++ b/DesktopEdge/MainWindow.xaml.cs @@ -1,690 +1,690 @@ -using System; -using System.Collections.Generic; -using System.Windows; -using System.Windows.Input; -using System.IO; -using System.ServiceProcess; -using System.Linq; -using System.Diagnostics; -using System.Windows.Controls; -using System.Drawing; - -using ZitiDesktopEdge.Models; -using ZitiDesktopEdge.ServiceClient; - -namespace ZitiDesktopEdge { - - /// - /// Interaction logic for MainWindow.xaml - /// - public partial class MainWindow:Window { - - public System.Windows.Forms.NotifyIcon notifyIcon; - public string Position = "Bottom"; - private DateTime _startDate; - private System.Windows.Forms.Timer _timer; - private Client serviceClient = null; - private bool _isAttached = true; - private bool _isServiceInError = false; - private int _right = 75; - private int _left = 75; - private int _top = 30; - private double _maxHeight = 800d; - private string[] suffixes = { "Bps", "kBps", "mBps", "gBps", "tBps", "pBps" }; - - 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(); - - App.Current.MainWindow.WindowState = WindowState.Normal; - App.Current.MainWindow.Closing += MainWindow_Closing; - App.Current.MainWindow.Deactivated += MainWindow_Deactivated; - App.Current.MainWindow.Activated += MainWindow_Activated; - notifyIcon = new System.Windows.Forms.NotifyIcon(); - notifyIcon.Visible = true; - notifyIcon.Click += TargetNotifyIcon_Click; - notifyIcon.Visible = true; - IdentityMenu.OnDetach += OnDetach; - MainMenu.OnDetach += OnDetach; - - LaunchOrInstall(); - - SetNotifyIcon("white"); - } - - private void Window_MouseDown(object sender, MouseButtonEventArgs e) { - OnDetach(e); - } - - private void OnDetach(MouseButtonEventArgs e) { - if (e.ChangedButton == MouseButton.Left) { - _isAttached = false; - IdentityMenu.Arrow.Visibility = Visibility.Collapsed; - Arrow.Visibility = Visibility.Collapsed; - MainMenu.Detach(); - this.DragMove(); - } - } - - private void MainWindow_Activated(object sender, EventArgs e) { - this.Visibility = Visibility.Visible; - Placement(); - } - - private void MainWindow_Deactivated(object sender, EventArgs e) { - if (this._isAttached) { -#if DEBUG - Debug.WriteLine("debug is enabled - windows pinned"); -#else - this.Visibility = Visibility.Collapsed; -#endif - } - } - - private void MainWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e) { - notifyIcon.Visible = false; - notifyIcon.Icon.Dispose(); - notifyIcon.Dispose(); - } - - private void SetCantDisplay(string msg, string detailMessage) { - NoServiceView.Visibility = Visibility.Visible; - ErrorMsg.Content = msg; - 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 TargetNotifyIcon_Click(object sender, EventArgs e) { - this.Show(); - this.Activate(); - } - - private void UpdateServiceView() { - if (_isServiceInError) { - AddIdAreaButton.Opacity = 0.1; - AddIdAreaButton.IsEnabled = false; - AddIdButton.Opacity = 0.1; - AddIdButton.IsEnabled = false; - DisconnectButton.Visibility = Visibility.Collapsed; - ConnectButton.Visibility = Visibility.Visible; - ConnectButton.Opacity = 0.1; - StatArea.Opacity = 0.1; - } else { - AddIdAreaButton.Opacity = 1.0; - AddIdAreaButton.IsEnabled = true; - AddIdButton.Opacity = 1.0; - AddIdButton.IsEnabled = true; - ConnectButton.Visibility = Visibility.Collapsed; - DisconnectButton.Visibility = Visibility.Visible; - StatArea.Opacity = 1.0; - ConnectButton.Opacity = 1.0; - } - } - - private void MainWindow_Loaded(object sender, RoutedEventArgs e) { - // add a new service client - serviceClient = new Client(); - serviceClient.OnClientConnected += ServiceClient_OnClientConnected; - serviceClient.OnClientDisconnected += ServiceClient_OnClientDisconnected; - serviceClient.OnIdentityEvent += ServiceClient_OnIdentityEvent; - serviceClient.OnMetricsEvent += ServiceClient_OnMetricsEvent; - serviceClient.OnServiceEvent += ServiceClient_OnServiceEvent; - serviceClient.OnTunnelStatusEvent += ServiceClient_OnTunnelStatusEvent; - - Application.Current.Properties.Add("ServiceClient", serviceClient); - Application.Current.Properties.Add("Identities", new List()); - MainMenu.OnAttachmentChange += AttachmentChanged; - MainMenu.OnLogLevelChanged += LogLevelChanged; - IdentityMenu.OnError += IdentityMenu_OnError; - - try { - serviceClient.Connect(); - //var s = serviceClient.GetStatus(); - //LoadStatusFromService(s.Status); - } catch /*ignored for now (Exception ex) */{ - SetCantDisplay(); - serviceClient.Reconnect(); - } - IdentityMenu.OnForgot += IdentityForgotten; - Placement(); - } - - private void LogLevelChanged(string level) { - serviceClient.SetLogLevel(level); - } - - private void IdentityMenu_OnError(string message) { - ShowError("Identity Error", message); - } - - private void ServiceClient_OnClientConnected(object sender, object e) { - this.Dispatcher.Invoke(() => { - //e is _ALWAYS_ null at this time use this to display something if you want - NoServiceView.Visibility = Visibility.Collapsed; - _isServiceInError = false; - UpdateServiceView(); - SetNotifyIcon("white"); - LoadIdentities(true); - }); - } - - private void ServiceClient_OnClientDisconnected(object sender, object e) { - this.Dispatcher.Invoke(() => { - IdList.Children.Clear(); - SetCantDisplay(); - }); - } - - private void ServiceClient_OnIdentityEvent(object sender, IdentityEvent e) { - if (e == null) return; - - ZitiIdentity zid = ZitiIdentity.FromClient(e.Id); - Debug.WriteLine($"==== IdentityEvent : action:{e.Action} fingerprint:{e.Id.FingerPrint} name:{e.Id.Name} "); - - this.Dispatcher.Invoke(() => { - if (e.Action == "added") { - var found = identities.Find(i => i.Fingerprint == e.Id.FingerPrint); - if (found == null) { - identities.Add(zid); - LoadIdentities(true); - } else { - //if we get here exit out so that LoadIdentities() doesn't get called - found.IsEnabled = true; - return; - } - } else { - IdentityForgotten(ZitiIdentity.FromClient(e.Id)); - } - }); - Debug.WriteLine($"IDENTITY EVENT. Action: {e.Action} fingerprint: {zid.Fingerprint}"); - } - - private void ServiceClient_OnMetricsEvent(object sender, List ids) { - if (ids != null) { - long totalUp = 0; - long totalDown = 0; - foreach (var id in ids) { - //Debug.WriteLine($"==== MetricsEvent : id {id.Name} down: {id.Metrics.Down} up:{id.Metrics.Up}"); - if (id?.Metrics != null) { - totalDown += id.Metrics.Down; - totalUp += id.Metrics.Up; - } - } - this.Dispatcher.Invoke(() => { - SetSpeed(totalUp, UploadSpeed, UploadSpeedLabel); - SetSpeed(totalDown, DownloadSpeed, DownloadSpeedLabel); - }); - } - } - - public void SetSpeed(decimal bytes, Label speed, Label speedLabel) { - int counter = 0; - while (Math.Round(bytes / 1024) >= 1) { - bytes = bytes / 1024; - counter++; - } - speed.Content = bytes.ToString("0.0"); - speedLabel.Content = suffixes[counter]; - } - - 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); - - if (found == null) { - Debug.WriteLine($"{e.Action} service event for {e.Service.Name} but the provided identity fingerprint {e.Fingerprint} is not found!"); - return; - } - - 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."); - } else { - Debug.WriteLine("removing the service named: " + e.Service.Name); - found.Services.RemoveAll(s => s.Name == e.Service.Name); - } +using System; +using System.Collections.Generic; +using System.Windows; +using System.Windows.Input; +using System.IO; +using System.ServiceProcess; +using System.Linq; +using System.Diagnostics; +using System.Windows.Controls; +using System.Drawing; + +using ZitiDesktopEdge.Models; +using ZitiDesktopEdge.ServiceClient; + +namespace ZitiDesktopEdge { + + /// + /// Interaction logic for MainWindow.xaml + /// + public partial class MainWindow:Window { + + public System.Windows.Forms.NotifyIcon notifyIcon; + public string Position = "Bottom"; + private DateTime _startDate; + private System.Windows.Forms.Timer _timer; + private Client serviceClient = null; + private bool _isAttached = true; + private bool _isServiceInError = false; + private int _right = 75; + private int _left = 75; + private int _top = 30; + private double _maxHeight = 800d; + private string[] suffixes = { "Bps", "kBps", "mBps", "gBps", "tBps", "pBps" }; + + 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(); + + App.Current.MainWindow.WindowState = WindowState.Normal; + App.Current.MainWindow.Closing += MainWindow_Closing; + App.Current.MainWindow.Deactivated += MainWindow_Deactivated; + App.Current.MainWindow.Activated += MainWindow_Activated; + notifyIcon = new System.Windows.Forms.NotifyIcon(); + notifyIcon.Visible = true; + notifyIcon.Click += TargetNotifyIcon_Click; + notifyIcon.Visible = true; + IdentityMenu.OnDetach += OnDetach; + MainMenu.OnDetach += OnDetach; + + LaunchOrInstall(); + + SetNotifyIcon("white"); + } + + private void Window_MouseDown(object sender, MouseButtonEventArgs e) { + OnDetach(e); + } + + private void OnDetach(MouseButtonEventArgs e) { + if (e.ChangedButton == MouseButton.Left) { + _isAttached = false; + IdentityMenu.Arrow.Visibility = Visibility.Collapsed; + Arrow.Visibility = Visibility.Collapsed; + MainMenu.Detach(); + this.DragMove(); + } + } + + private void MainWindow_Activated(object sender, EventArgs e) { + this.Visibility = Visibility.Visible; + Placement(); + } + + private void MainWindow_Deactivated(object sender, EventArgs e) { + if (this._isAttached) { +#if DEBUG + Debug.WriteLine("debug is enabled - windows pinned"); +#else + this.Visibility = Visibility.Collapsed; +#endif + } + } + + private void MainWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e) { + notifyIcon.Visible = false; + notifyIcon.Icon.Dispose(); + notifyIcon.Dispose(); + } + + private void SetCantDisplay(string msg, string detailMessage) { + NoServiceView.Visibility = Visibility.Visible; + ErrorMsg.Content = msg; + 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 TargetNotifyIcon_Click(object sender, EventArgs e) { + this.Show(); + this.Activate(); + } + + private void UpdateServiceView() { + if (_isServiceInError) { + AddIdAreaButton.Opacity = 0.1; + AddIdAreaButton.IsEnabled = false; + AddIdButton.Opacity = 0.1; + AddIdButton.IsEnabled = false; + DisconnectButton.Visibility = Visibility.Collapsed; + ConnectButton.Visibility = Visibility.Visible; + ConnectButton.Opacity = 0.1; + StatArea.Opacity = 0.1; + } else { + AddIdAreaButton.Opacity = 1.0; + AddIdAreaButton.IsEnabled = true; + AddIdButton.Opacity = 1.0; + AddIdButton.IsEnabled = true; + ConnectButton.Visibility = Visibility.Collapsed; + DisconnectButton.Visibility = Visibility.Visible; + StatArea.Opacity = 1.0; + ConnectButton.Opacity = 1.0; + } + } + + private void MainWindow_Loaded(object sender, RoutedEventArgs e) { + // add a new service client + serviceClient = new Client(); + serviceClient.OnClientConnected += ServiceClient_OnClientConnected; + serviceClient.OnClientDisconnected += ServiceClient_OnClientDisconnected; + serviceClient.OnIdentityEvent += ServiceClient_OnIdentityEvent; + serviceClient.OnMetricsEvent += ServiceClient_OnMetricsEvent; + serviceClient.OnServiceEvent += ServiceClient_OnServiceEvent; + serviceClient.OnTunnelStatusEvent += ServiceClient_OnTunnelStatusEvent; + + Application.Current.Properties.Add("ServiceClient", serviceClient); + Application.Current.Properties.Add("Identities", new List()); + MainMenu.OnAttachmentChange += AttachmentChanged; + MainMenu.OnLogLevelChanged += LogLevelChanged; + IdentityMenu.OnError += IdentityMenu_OnError; + + try { + serviceClient.Connect(); + //var s = serviceClient.GetStatus(); + //LoadStatusFromService(s.Status); + } catch /*ignored for now (Exception ex) */{ + SetCantDisplay(); + serviceClient.Reconnect(); + } + IdentityMenu.OnForgot += IdentityForgotten; + Placement(); + } + + private void LogLevelChanged(string level) { + serviceClient.SetLogLevel(level); + } + + private void IdentityMenu_OnError(string message) { + ShowError("Identity Error", message); + } + + private void ServiceClient_OnClientConnected(object sender, object e) { + this.Dispatcher.Invoke(() => { + //e is _ALWAYS_ null at this time use this to display something if you want + NoServiceView.Visibility = Visibility.Collapsed; + _isServiceInError = false; + UpdateServiceView(); + SetNotifyIcon("white"); + LoadIdentities(true); + }); + } + + private void ServiceClient_OnClientDisconnected(object sender, object e) { + this.Dispatcher.Invoke(() => { + IdList.Children.Clear(); + SetCantDisplay(); + }); + } + + private void ServiceClient_OnIdentityEvent(object sender, IdentityEvent e) { + if (e == null) return; + + ZitiIdentity zid = ZitiIdentity.FromClient(e.Id); + Debug.WriteLine($"==== IdentityEvent : action:{e.Action} fingerprint:{e.Id.FingerPrint} name:{e.Id.Name} "); + + this.Dispatcher.Invoke(() => { + if (e.Action == "added") { + var found = identities.Find(i => i.Fingerprint == e.Id.FingerPrint); + if (found == null) { + identities.Add(zid); + LoadIdentities(true); + } else { + //if we get here exit out so that LoadIdentities() doesn't get called + found.IsEnabled = true; + return; + } + } else { + IdentityForgotten(ZitiIdentity.FromClient(e.Id)); + } + }); + Debug.WriteLine($"IDENTITY EVENT. Action: {e.Action} fingerprint: {zid.Fingerprint}"); + } + + private void ServiceClient_OnMetricsEvent(object sender, List ids) { + if (ids != null) { + long totalUp = 0; + long totalDown = 0; + foreach (var id in ids) { + //Debug.WriteLine($"==== MetricsEvent : id {id.Name} down: {id.Metrics.Down} up:{id.Metrics.Up}"); + if (id?.Metrics != null) { + totalDown += id.Metrics.Down; + totalUp += id.Metrics.Up; + } + } + this.Dispatcher.Invoke(() => { + SetSpeed(totalUp, UploadSpeed, UploadSpeedLabel); + SetSpeed(totalDown, DownloadSpeed, DownloadSpeedLabel); + }); + } + } + + public void SetSpeed(decimal bytes, Label speed, Label speedLabel) { + int counter = 0; + while (Math.Round(bytes / 1024) >= 1) { + bytes = bytes / 1024; + counter++; + } + speed.Content = bytes.ToString("0.0"); + speedLabel.Content = suffixes[counter]; + } + + 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); + + if (found == null) { + Debug.WriteLine($"{e.Action} service event for {e.Service.Name} but the provided identity fingerprint {e.Fingerprint} is not found!"); + return; + } + + 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."); + } else { + Debug.WriteLine("removing the service named: " + e.Service.Name); + found.Services.RemoveAll(s => s.Name == e.Service.Name); + } LoadIdentities(false); - IdentityDetails deets = ((MainWindow)Application.Current.MainWindow).IdentityMenu; + IdentityDetails deets = ((MainWindow)Application.Current.MainWindow).IdentityMenu; if (deets.IsVisible) { deets.UpdateView(); - } - }); - } - - private void ServiceClient_OnTunnelStatusEvent(object sender, TunnelStatusEvent e) { - if (e == null) return; //just skip it for now... - Debug.WriteLine($"==== TunnelStatusEvent: "); - 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"); - return; - } - this.MainMenu.LogLevel = e.Status.LogLevel; - InitializeTimer((int)e.Status.Duration); - LoadStatusFromService(e.Status); + } + }); + } + + private void ServiceClient_OnTunnelStatusEvent(object sender, TunnelStatusEvent e) { + if (e == null) return; //just skip it for now... + Debug.WriteLine($"==== TunnelStatusEvent: "); + 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"); + return; + } + this.MainMenu.LogLevel = e.Status.LogLevel; + InitializeTimer((int)e.Status.Duration); + LoadStatusFromService(e.Status); LoadIdentities(true); - IdentityDetails deets = ((MainWindow)Application.Current.MainWindow).IdentityMenu; + IdentityDetails deets = ((MainWindow)Application.Current.MainWindow).IdentityMenu; if (deets.IsVisible) { deets.UpdateView(); - } - }); - } - - private void IdentityForgotten(ZitiIdentity forgotten) { - ZitiIdentity idToRemove = null; - foreach (var id in identities) { - if (id.Fingerprint == forgotten.Fingerprint) { - idToRemove = id; - break; - } - } - identities.Remove(idToRemove); - LoadIdentities(false); - } - - private void AttachmentChanged(bool attached) { - _isAttached = attached; - if (!_isAttached) { - SetLocation(); - } - Placement(); - MainMenu.Visibility = Visibility.Collapsed; - } - - private void LoadStatusFromService(TunnelStatus status) { - //clear any identities - this.identities.Clear(); - - if (status != null) { - _isServiceInError = false; - UpdateServiceView(); - NoServiceView.Visibility = Visibility.Collapsed; - SetNotifyIcon("white"); - if (status.Active) { - InitializeTimer((int)status.Duration); - ConnectButton.Visibility = Visibility.Collapsed; - DisconnectButton.Visibility = Visibility.Visible; - SetNotifyIcon("green"); - } else { - ConnectButton.Visibility = Visibility.Visible; - DisconnectButton.Visibility = Visibility.Collapsed; - } - if (!Application.Current.Properties.Contains("ip")) { - Application.Current.Properties.Add("ip", status?.IpInfo?.Ip); - } - if (!Application.Current.Properties.Contains("subnet")) { - Application.Current.Properties.Add("subnet", status?.IpInfo?.Subnet); - } - if (!Application.Current.Properties.Contains("mtu")) { - Application.Current.Properties.Add("mtu", status?.IpInfo?.MTU); - } - if (!Application.Current.Properties.Contains("dns")) { - Application.Current.Properties.Add("dns", status?.IpInfo?.DNS); - } - - foreach (var id in status.Identities) { - updateViewWithIdentity(id); - } - LoadIdentities(true); - } else { - SetCantDisplay(); - } - } - - private void updateViewWithIdentity(Identity id) { - var zid = ZitiIdentity.FromClient(id); - foreach (var i in identities) { - if (i.Fingerprint == zid.Fingerprint) { - identities.Remove(i); - break; - } - } - identities.Add(zid); - } - private void SetNotifyIcon(string iconPrefix) { - var iconUri = new Uri("pack://application:,,/Assets/Images/ziti-" + iconPrefix + ".ico"); - System.IO.Stream iconStream = System.Windows.Application.GetResourceStream(iconUri).Stream; - notifyIcon.Icon = new System.Drawing.Icon(iconStream); - - Application.Current.MainWindow.Icon = System.Windows.Media.Imaging.BitmapFrame.Create(iconUri); - } - - private void LoadIdentities(Boolean repaint) { - IdList.Children.Clear(); - IdList.Height = 0; - IdList.MaxHeight = _maxHeight-520; - ZitiIdentity[] ids = identities.ToArray(); - double height = 490 + (ids.Length * 60); - if (height > _maxHeight) height = _maxHeight; - this.Height = height; - 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; - } - - 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) { - if (!_isServiceInError) { - ShowLoad(); - this.Dispatcher.Invoke(() => { - //Dispatcher.Invoke(new Action(() => { }), System.Windows.Threading.DispatcherPriority.ContextIdle); - DoConnect(); - HideLoad(); - }); - } - } - - private void DoConnect() { - try { - serviceClient.SetTunnelState(true); - SetNotifyIcon("green"); - ConnectButton.Visibility = Visibility.Collapsed; - DisconnectButton.Visibility = Visibility.Visible; - - for (int i = 0; i < identities.Count; i++) { - serviceClient.IdentityOnOff(identities[i].Fingerprint, true); - } - for (int i = 0; i < IdList.Children.Count; i++) { - IdentityItem item = IdList.Children[i] as IdentityItem; - item._identity.IsEnabled = true; - item.RefreshUI(); - } - } catch (ServiceException se) { - ShowError(se.AdditionalInfo, se.Message); - } 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(); - } - } - - private void ShowLoad() { - LoadProgress.IsIndeterminate = true; - LoadingScreen.Visibility = Visibility.Visible; - ((MainWindow)System.Windows.Application.Current.MainWindow).UpdateLayout(); - } - - private void HideLoad() { - LoadingScreen.Visibility = Visibility.Collapsed; - LoadProgress.IsIndeterminate = false; - } - - private void FormFadeOut_Completed(object sender, EventArgs e) { - closeCompleted = true; - //this.Close(); - } - private bool closeCompleted = false; - private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) { - if (!closeCompleted) { - FormFadeOut.Begin(); - e.Cancel = true; - } - } - - private void ShowError(String title, String message) { - ErrorTitle.Content = title; - ErrorDetails.Text = message; - ErrorView.Visibility = Visibility.Visible; - } - - private void CloseError(object sender, RoutedEventArgs e) { - ErrorView.Visibility = Visibility.Collapsed; - NoServiceView.Visibility = Visibility.Collapsed; - } - - private void CloseApp(object sender, RoutedEventArgs e) { - Application.Current.Shutdown(); - } - - private void MainUI_Deactivated(object sender, EventArgs e) { - if (this._isAttached) { -#if DEBUG - Debug.WriteLine("debug is enabled - windows pinned"); -#else - this.Visibility = Visibility.Collapsed; -#endif - } - } - - private void Label_MouseDoubleClick(object sender, MouseButtonEventArgs e) { - Placement(); - } - - private void Button_Click(object sender, RoutedEventArgs e) - { - serviceClient.SetLogLevel(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() - { - cur++; - if (cur > 6) - { - cur = 0; - } - return levels[cur]; - } - - private void IdList_LayoutUpdated(object sender, EventArgs e) { - Placement(); - } - } -} + } + }); + } + + private void IdentityForgotten(ZitiIdentity forgotten) { + ZitiIdentity idToRemove = null; + foreach (var id in identities) { + if (id.Fingerprint == forgotten.Fingerprint) { + idToRemove = id; + break; + } + } + identities.Remove(idToRemove); + LoadIdentities(false); + } + + private void AttachmentChanged(bool attached) { + _isAttached = attached; + if (!_isAttached) { + SetLocation(); + } + Placement(); + MainMenu.Visibility = Visibility.Collapsed; + } + + private void LoadStatusFromService(TunnelStatus status) { + //clear any identities + this.identities.Clear(); + + if (status != null) { + _isServiceInError = false; + UpdateServiceView(); + NoServiceView.Visibility = Visibility.Collapsed; + SetNotifyIcon("white"); + if (status.Active) { + InitializeTimer((int)status.Duration); + ConnectButton.Visibility = Visibility.Collapsed; + DisconnectButton.Visibility = Visibility.Visible; + SetNotifyIcon("green"); + } else { + ConnectButton.Visibility = Visibility.Visible; + DisconnectButton.Visibility = Visibility.Collapsed; + } + if (!Application.Current.Properties.Contains("ip")) { + Application.Current.Properties.Add("ip", status?.IpInfo?.Ip); + } + if (!Application.Current.Properties.Contains("subnet")) { + Application.Current.Properties.Add("subnet", status?.IpInfo?.Subnet); + } + if (!Application.Current.Properties.Contains("mtu")) { + Application.Current.Properties.Add("mtu", status?.IpInfo?.MTU); + } + if (!Application.Current.Properties.Contains("dns")) { + Application.Current.Properties.Add("dns", status?.IpInfo?.DNS); + } + + foreach (var id in status.Identities) { + updateViewWithIdentity(id); + } + LoadIdentities(true); + } else { + SetCantDisplay(); + } + } + + private void updateViewWithIdentity(Identity id) { + var zid = ZitiIdentity.FromClient(id); + foreach (var i in identities) { + if (i.Fingerprint == zid.Fingerprint) { + identities.Remove(i); + break; + } + } + identities.Add(zid); + } + private void SetNotifyIcon(string iconPrefix) { + var iconUri = new Uri("pack://application:,,/Assets/Images/ziti-" + iconPrefix + ".ico"); + System.IO.Stream iconStream = System.Windows.Application.GetResourceStream(iconUri).Stream; + notifyIcon.Icon = new System.Drawing.Icon(iconStream); + + Application.Current.MainWindow.Icon = System.Windows.Media.Imaging.BitmapFrame.Create(iconUri); + } + + private void LoadIdentities(Boolean repaint) { + IdList.Children.Clear(); + IdList.Height = 0; + IdList.MaxHeight = _maxHeight-520; + ZitiIdentity[] ids = identities.ToArray(); + double height = 490 + (ids.Length * 60); + if (height > _maxHeight) height = _maxHeight; + this.Height = height; + 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; + } + + 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) { + if (!_isServiceInError) { + ShowLoad(); + this.Dispatcher.Invoke(() => { + //Dispatcher.Invoke(new Action(() => { }), System.Windows.Threading.DispatcherPriority.ContextIdle); + DoConnect(); + HideLoad(); + }); + } + } + + private void DoConnect() { + try { + serviceClient.SetTunnelState(true); + SetNotifyIcon("green"); + ConnectButton.Visibility = Visibility.Collapsed; + DisconnectButton.Visibility = Visibility.Visible; + + for (int i = 0; i < identities.Count; i++) { + serviceClient.IdentityOnOff(identities[i].Fingerprint, true); + } + for (int i = 0; i < IdList.Children.Count; i++) { + IdentityItem item = IdList.Children[i] as IdentityItem; + item._identity.IsEnabled = true; + item.RefreshUI(); + } + } catch (ServiceException se) { + ShowError(se.AdditionalInfo, se.Message); + } 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(); + } + } + + private void ShowLoad() { + LoadProgress.IsIndeterminate = true; + LoadingScreen.Visibility = Visibility.Visible; + ((MainWindow)System.Windows.Application.Current.MainWindow).UpdateLayout(); + } + + private void HideLoad() { + LoadingScreen.Visibility = Visibility.Collapsed; + LoadProgress.IsIndeterminate = false; + } + + private void FormFadeOut_Completed(object sender, EventArgs e) { + closeCompleted = true; + //this.Close(); + } + private bool closeCompleted = false; + private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) { + if (!closeCompleted) { + FormFadeOut.Begin(); + e.Cancel = true; + } + } + + private void ShowError(String title, String message) { + ErrorTitle.Content = title; + ErrorDetails.Text = message; + ErrorView.Visibility = Visibility.Visible; + } + + private void CloseError(object sender, RoutedEventArgs e) { + ErrorView.Visibility = Visibility.Collapsed; + NoServiceView.Visibility = Visibility.Collapsed; + } + + private void CloseApp(object sender, RoutedEventArgs e) { + Application.Current.Shutdown(); + } + + private void MainUI_Deactivated(object sender, EventArgs e) { + if (this._isAttached) { +#if DEBUG + Debug.WriteLine("debug is enabled - windows pinned"); +#else + this.Visibility = Visibility.Collapsed; +#endif + } + } + + private void Label_MouseDoubleClick(object sender, MouseButtonEventArgs e) { + Placement(); + } + + private void Button_Click(object sender, RoutedEventArgs e) + { + serviceClient.SetLogLevel(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() + { + cur++; + if (cur > 6) + { + cur = 0; + } + return levels[cur]; + } + + private void IdList_LayoutUpdated(object sender, EventArgs e) { + Placement(); + } + } +} diff --git a/ZitiDesktopEdge.Client/ServiceClient/ClientImpl.cs b/ZitiDesktopEdge.Client/ServiceClient/ClientImpl.cs index d2266d11..9b761906 100644 --- a/ZitiDesktopEdge.Client/ServiceClient/ClientImpl.cs +++ b/ZitiDesktopEdge.Client/ServiceClient/ClientImpl.cs @@ -1,616 +1,616 @@ -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; - -/// -/// 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 - { - - 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) { - 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 = true; - 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) - { - Debug.WriteLine("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(); - } - +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; + +/// +/// 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 + { + + 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) { + 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 = true; + 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) + { + Debug.WriteLine("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) - { - Debug.WriteLine("Already in reconnect mode."); - return; - } - else - { - Reconnecting = true; - } - - Task.Run(() => - { - while (true) - { - try - { - Thread.Sleep(2500); //wait 500ms and try to reconnect... - Debug.WriteLine("Attempting to connect to service..."); - pipeClient?.Close(); - eventClient?.Close(); - setupPipe(); - - if (Connected) - { - Debug.WriteLine("Connected to the service - exiting reconect loop"); - Connected = true; - Reconnecting = false; - return; - } - else - { - //ClientDisconnected(null); - } - } - catch (Exception ex) { - Debug.WriteLine("Service error: " + ex.Message); - } - Debug.WriteLine("Reconnect failed. Trying again..."); - } - }); - } - - 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) - { - Debug.WriteLine("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); - Debug.WriteLine(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 - { - Debug.WriteLine("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: - Debug.WriteLine("unexpected operation! " + evt.Op); - break; - } - } catch (Exception e) - { - Debug.WriteLine(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) - { - Debug.WriteLine("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 - Debug.WriteLine("io error in read: " + ioe.Message); - ClientDisconnected(null); - throw ioe; - } - catch (Exception ee) - { - //almost certainly a problem with the pipe - Debug.WriteLine("unexpected error in read: " + ee.Message); - ClientDisconnected(null); - throw ee; - } - } - - private void debugServiceCommunication(string msg) - { - if (_extendedDebug) - { - Debug.WriteLine(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; - } - } - } + } + + public void Reconnect() + { + if (Reconnecting) + { + Debug.WriteLine("Already in reconnect mode."); + return; + } + else + { + Reconnecting = true; + } + + Task.Run(() => + { + while (true) + { + try + { + Thread.Sleep(2500); //wait 500ms and try to reconnect... + Debug.WriteLine("Attempting to connect to service..."); + pipeClient?.Close(); + eventClient?.Close(); + setupPipe(); + + if (Connected) + { + Debug.WriteLine("Connected to the service - exiting reconect loop"); + Connected = true; + Reconnecting = false; + return; + } + else + { + //ClientDisconnected(null); + } + } + catch (Exception ex) { + Debug.WriteLine("Service error: " + ex.Message); + } + Debug.WriteLine("Reconnect failed. Trying again..."); + } + }); + } + + 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) + { + Debug.WriteLine("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); + Debug.WriteLine(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 + { + Debug.WriteLine("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: + Debug.WriteLine("unexpected operation! " + evt.Op); + break; + } + } catch (Exception e) + { + Debug.WriteLine(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) + { + Debug.WriteLine("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 + Debug.WriteLine("io error in read: " + ioe.Message); + ClientDisconnected(null); + throw ioe; + } + catch (Exception ee) + { + //almost certainly a problem with the pipe + Debug.WriteLine("unexpected error in read: " + ee.Message); + ClientDisconnected(null); + throw ee; + } + } + + private void debugServiceCommunication(string msg) + { + if (_extendedDebug) + { + Debug.WriteLine(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