using EonaCat.Json; using System.Diagnostics; using System.IO; using System.Windows; using System.Windows.Controls; using System.Xml.Serialization; using System.Collections.ObjectModel; using System.Text.RegularExpressions; using System.ComponentModel; using System.Windows.Input; using EonaCat.Helpers.Commands; using System.Text; using MessageBox = System.Windows.MessageBox; using System.Net.Http; using System.Windows.Media; using Application = System.Windows.Application; using Brushes = System.Drawing.Brushes; using System.Windows.Threading; using Color = System.Drawing.Color; namespace EonaCat.PortMonitor { public partial class MainWindow : Window { public ObservableCollection Connections { get; set; } = new ObservableCollection(); private List _previousConnections = new List(); private BackgroundWorker _monitorWorker; private int _interval = 1 * 1000; WPF.Tray.TrayIcon _trayIcon = new WPF.Tray.TrayIcon(); public bool MinimizeToTray { get; private set; } public bool MinimizeOnStartup { get; private set; } public bool ShowPopups { get; private set; } = true; public bool FirstRun { get; private set; } = true; public MainWindow() { InitializeComponent(); DataContext = this; NetworkDataGrid.Items.Clear(); NetworkDataGrid.ItemsSource = Connections; this.StateChanged += MainWindow_StateChanged; SetupTrayIcon(); LoadSettings(); _monitorWorker = new BackgroundWorker(); _monitorWorker.DoWork += MonitorWorker_DoWork; _monitorWorker.RunWorkerAsync(); } protected override void OnActivated(EventArgs e) { base.OnActivated(e); if (FirstRun) { if (MinimizeToTray || MinimizeOnStartup) { // Minimize to tray on first run Hide(); FirstRun = false; } } } private void LoadSettings() { if (File.Exists("settings.json")) { var json = File.ReadAllText("settings.json"); var settings = JsonHelper.ToObject(json); if (settings != null) { if (settings.MinimizeToTray != null) { MinimizeToTray = (bool)settings.MinimizeToTray; } if (settings.MinimizeOnStartup != null) { MinimizeOnStartup = (bool)settings.MinimizeOnStartup; chkMinimize.IsChecked = MinimizeOnStartup; } if (settings.ShowPopups != null) { ShowPopups = (bool)settings.ShowPopups; chkShowPopups.IsChecked = ShowPopups; } } } } private void MainWindow_StateChanged(object? sender, EventArgs e) { if (WindowState == WindowState.Minimized) { // Minimize to tray and hide the window this.Hide(); } } private void SetupTrayIcon() { // Load the icon from embedded resource var icon = System.Drawing.Icon.ExtractAssociatedIcon(System.Reflection.Assembly.GetExecutingAssembly().Location); _trayIcon.Icon = icon; _trayIcon.TrayLeftMouseUp += (sender, args) => { // Show the main window when the tray icon is clicked WindowState = WindowState.Normal; Show(); Activate(); }; _trayIcon.TrayRightMouseUp += (sender, args) => { // Generate a context menu for the tray icon var contextMenu = new System.Windows.Controls.ContextMenu(); contextMenu.Items.Add(new MenuItem { Header = "Show", Command = new GiveCommand(() => { Show(); WindowState = WindowState.Normal; }) }); contextMenu.Items.Add(new MenuItem { Header = "Exit", Command = new GiveCommand(() => { Close(); }) }); _trayIcon.ContextMenu = contextMenu; }; } protected override void OnClosing(CancelEventArgs e) { // Save settings before closing var settings = new { MinimizeToTray = this.WindowState == WindowState.Minimized, MinimizeOnStartup = chkMinimize.IsChecked == true, ShowPopups = chkShowPopups.IsChecked == true }; File.WriteAllBytes("settings.json", Encoding.UTF8.GetBytes(JsonHelper.ToJson(settings))); base.OnClosing(e); } private void MonitorWorker_DoWork(object sender, DoWorkEventArgs e) { while (true) { MonitorConnections(null); Thread.Sleep(_interval); } } private async Task NotifyNewConnectionAsync(ConnectionInfo conn) { if (!ShowPopups) { return; } // Create a new notification manager var notificationManager = new WPF.Popup.NotificationManager(); // Set up the popup's title and content string popupTitle = $"New Connection: {conn.Protocol} {conn.LocalAddress}:{conn.LocalPort} -> {conn.RemoteAddress}:{conn.RemotePort}"; string popupContent = $"Process: {conn.ProcessName}\nState: {conn.State}"; // Set up the popup var notification = new WPF.Popup.NotificationContent { Title = popupTitle, Message = popupContent, Type = WPF.Popup.NotificationType.Information, }; // Show the popup await notificationManager.ShowAsync(notification, expirationTime: TimeSpan.FromSeconds(3), onClick: new Action(() => { ShowConnectionDetails(conn); })); } private void ShowConnectionDetails(ConnectionInfo conn) { // Play a sound when the notification is clicked System.Media.SystemSounds.Asterisk.Play(); var detailsWindow = new ConnectionDetailsWindow(conn); detailsWindow.ShowDialog(); } private void DataGrid_PreviewMouseDoubleClick(object sender, MouseButtonEventArgs e) { // Get the DataGrid that triggered the event DataGrid dataGrid = sender as DataGrid; if (dataGrid.SelectedItem != null) { ShowConnectionDetails(dataGrid.SelectedItem as ConnectionInfo); } } private List GetActiveConnections() { var connectionList = new List(); foreach (var conn in RunNetstatCommand()) { if (string.IsNullOrEmpty(conn)) continue; var match = ParseNetstatLine(conn); if (match.Success) { var connInfo = new ConnectionInfo { Protocol = match.Groups["protocol"].Value, LocalAddress = match.Groups["localAddress"].Value, LocalPort = int.Parse(match.Groups["localPort"].Value), RemoteAddress = match.Groups["remoteAddress"].Value, RemotePort = int.Parse(match.Groups["remotePort"].Value), State = string.IsNullOrEmpty(match.Groups["state"].Value) ? string.Empty : match.Groups["state"].Value, // Handle missing state for UDP ProcessId = string.IsNullOrEmpty(match.Groups["pid"].Value) ? 0 : int.Parse(match.Groups["pid"].Value), // Default PID to 0 if missing ProcessName = string.IsNullOrEmpty(match.Groups["pid"].Value) ? string.Empty : GetProcessName(int.Parse(match.Groups["pid"].Value)), ConnectionTime = DateTime.Now, ConnectionStatus = string.Empty }; connectionList.Add(connInfo); } } return connectionList; } private async void MonitorConnections(object state) { try { ConnectionInfo selectedConnection = null; // Store the currently selected connection Dispatcher.Invoke(() => { selectedConnection = NetworkDataGrid.SelectedItem as ConnectionInfo; }); var currentConnections = await Task.Run(GetActiveConnections); var inboundConnections = currentConnections.Where(IsInboundConnection).ToList(); var outboundConnections = currentConnections.Where(IsOutboundConnection).ToList(); int totalConnections = currentConnections.Count; // Identify new connections var newConnections = currentConnections .Where(c => !_previousConnections.Any(p => p.LocalAddress == c.LocalAddress && p.LocalPort == c.LocalPort)) .ToList(); // Identify closed connections var closedConnections = _previousConnections .Where(p => !currentConnections.Any(c => c.LocalAddress == p.LocalAddress && c.LocalPort == p.LocalPort)) .ToList(); // Identify state-changed connections var stateChangedConnections = currentConnections .Where(c => _previousConnections.Any(p => p.LocalAddress == c.LocalAddress && p.LocalPort == c.LocalPort && p.State != c.State)) .ToList(); await Dispatcher.InvokeAsync(() => { foreach (var newConn in newConnections) { var existingConn = Connections.FirstOrDefault(c => c.LocalAddress == newConn.LocalAddress && c.LocalPort == newConn.LocalPort); if (existingConn == null) { if (newConn.FirstConnectionTime == DateTime.MinValue) newConn.FirstConnectionTime = DateTime.Now; newConn.ConnectionStatus = "New"; newConn.ConnectionColor = new SolidColorBrush(AdjustColorBrightness(Colors.LightGreen)); // Softer green for new connections Connections.Add(newConn); } else { existingConn.ConnectionStatus = "Updated"; existingConn.ConnectionColor = new SolidColorBrush(AdjustColorBrightness(Colors.LightGreen)); // Softer green for updated connections } } foreach (var closedConn in closedConnections) { var conn = Connections.FirstOrDefault(c => c.LocalAddress == closedConn.LocalAddress && c.LocalPort == closedConn.LocalPort); if (conn != null) { conn.ConnectionStatus = "Closed"; conn.ConnectionColor = new SolidColorBrush(AdjustColorBrightness(Colors.Gray)); // Softer gray for closed connections Task.Delay(2000).ContinueWith(_ => { Dispatcher.Invoke(() => Connections.Remove(conn)); }); } } foreach (var stateChangedConn in stateChangedConnections) { var conn = Connections.FirstOrDefault(c => c.LocalAddress == stateChangedConn.LocalAddress && c.LocalPort == stateChangedConn.LocalPort); if (conn != null) { conn.ConnectionStatus = "State Changed"; conn.ConnectionColor = new SolidColorBrush(AdjustColorBrightness(Colors.Yellow)); // Softer yellow for state changes var timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) }; timer.Tick += (sender, args) => { conn.ConnectionColor = new SolidColorBrush(Colors.Transparent); timer.Stop(); }; timer.Start(); } } // Apply colors based on connection state with softer variants foreach (var conn in Connections) { switch (conn.State) { case "IDLE": conn.ConnectionColor = new SolidColorBrush(AdjustColorBrightness(Colors.Red)); // Softer red break; case "LISTENING": conn.ConnectionColor = new SolidColorBrush(AdjustColorBrightness(Colors.Blue)); // Softer blue break; case "SYN_SENT": conn.ConnectionColor = new SolidColorBrush(AdjustColorBrightness(Colors.Orange)); // Softer orange break; case "SYN_RECEIVED": conn.ConnectionColor = new SolidColorBrush(AdjustColorBrightness(Colors.Purple)); // Softer purple break; case "ESTABLISHED": conn.ConnectionColor = new SolidColorBrush(AdjustColorBrightness(Colors.DarkGreen)); // Softer dark green break; case "CLOSE_WAIT": conn.ConnectionColor = new SolidColorBrush(AdjustColorBrightness(Colors.Brown)); // Softer brown break; case "TIME_WAIT": conn.ConnectionColor = new SolidColorBrush(AdjustColorBrightness(Colors.DarkGray)); // Softer dark gray break; case "FIN_WAIT_1": case "FIN_WAIT_2": conn.ConnectionColor = new SolidColorBrush(AdjustColorBrightness(Colors.LightYellow)); // Softer light yellow break; case "LAST_ACK": conn.ConnectionColor = new SolidColorBrush(AdjustColorBrightness(Colors.DarkOrange)); // Softer dark orange break; default: conn.ConnectionColor = new SolidColorBrush(Colors.Transparent); // Default (no color) break; } } // Update DataGrids InboundDataGrid.ItemsSource = Connections.Where(IsInboundConnection).ToList(); OutboundDataGrid.ItemsSource = Connections.Where(IsOutboundConnection).ToList(); NetworkDataGrid.ItemsSource = Connections; FilterConnections_KeyUp(null, null); UpdateConnectionStats(totalConnections, inboundConnections.Count, outboundConnections.Count); }); } catch (Exception ex) { // Handle any exceptions appropriately Console.WriteLine($"Error monitoring connections: {ex.Message}"); } } // Method to adjust the color brightness and make it softer private System.Windows.Media.Color AdjustColorBrightness(System.Windows.Media.Color color) { byte r = (byte)(color.R + (255 - color.R) * 0.5); // Lighten the red component byte g = (byte)(color.G + (255 - color.G) * 0.5); // Lighten the green component byte b = (byte)(color.B + (255 - color.B) * 0.5); // Lighten the blue component return System.Windows.Media.Color.FromArgb(180, r, g, b); // alpha = 180 for a softer color } private void UpdateConnectionStats(int total, int inbound, int outbound) { var tcpConnections = Connections.Count(c => c.Protocol == "TCP"); var udpConnections = Connections.Count(c => c.Protocol == "UDP"); var avgConnectionTime = Connections.Average(c => (DateTime.Now - c.ConnectionTime).TotalSeconds); TcpUdpStatsText.Text = $"TCP Connections: {tcpConnections}\nUDP Connections: {udpConnections}\nAverage Connection Time: {avgConnectionTime:F2} seconds\nTotal: {total}\nInbound: {inbound}\nOutbound: {outbound}"; } private bool IsInboundConnection(ConnectionInfo connection) { // Inbound: LocalAddress is valid, and the machine is accepting the connection (server-side). return IsValidIpAddress(connection.LocalAddress) && connection.LocalAddress != "0.0.0.0" && connection.LocalAddress != "::" && !string.IsNullOrEmpty(connection.RemoteAddress) && connection.RemoteAddress != "0.0.0.0" && connection.RemoteAddress != "::" && connection.RemoteAddress != "N/A" && connection.RemotePort != 0; } private bool IsOutboundConnection(ConnectionInfo connection) { // Outbound: LocalAddress is valid, and the machine is initiating the connection (client-side). return IsValidIpAddress(connection.LocalAddress) && connection.LocalAddress != "0.0.0.0" && connection.LocalAddress != "::" && IsValidIpAddress(connection.RemoteAddress) && connection.RemoteAddress != "0.0.0.0" && connection.RemoteAddress != "::" && connection.RemoteAddress != "N/A" && connection.LocalPort != 0; } private bool IsValidIpAddress(string ipAddress) { return !string.IsNullOrEmpty(ipAddress) && (IsValidIPv4(ipAddress) || IsValidIPv6(ipAddress)); } private bool IsValidIPv4(string ipAddress) { return System.Net.IPAddress.TryParse(ipAddress, out var ip) && ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork; } private bool IsValidIPv6(string ipAddress) { return System.Net.IPAddress.TryParse(ipAddress, out var ip) && ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6; } private List RunNetstatCommand() { var result = new List(); try { ProcessStartInfo startInfo = new ProcessStartInfo("netstat", "-ano") { RedirectStandardOutput = true, UseShellExecute = false, CreateNoWindow = true }; using (Process process = Process.Start(startInfo)) { using (var reader = process.StandardOutput) { string line; while ((line = reader.ReadLine()) != null) { result.Add(line); } } } } catch (Exception ex) { Console.WriteLine($"Error running netstat: {ex.Message}"); } return result; } private Match ParseNetstatLine(string line) { // Regular expression for both TCP and UDP connections, IPv4 and IPv6. var regex = new Regex(@"(?TCP|UDP)\s+(?[\d\.]+|\[?[A-F0-9:]+\]?):(?\d+)\s+(?[\d\.]+|\[?[A-F0-9:]+\]?):(?\d+)\s*(?\S*)?\s*(?\d+)?"); var match = regex.Match(line); return match; } private string GetProcessName(int pid) { try { var process = Process.GetProcessById(pid); return process.ProcessName; } catch { return "N/A"; } } private void FilterConnections_KeyUp(object sender, System.Windows.Input.KeyEventArgs e) { // Get the filter keyword from the input box var keywordFilter = KeywordFilter.Text.ToLower(); // Filter the connections based on the keyword var filteredConnections = Connections?.Where(c => (string.IsNullOrEmpty(keywordFilter) || c.ProcessName.ToLower().Contains(keywordFilter) || c.RemoteAddress.ToLower().Contains(keywordFilter) || c.LocalAddress.ToLower().Contains(keywordFilter) || c.State.ToLower().Contains(keywordFilter) || c.Protocol.ToLower().Contains(keywordFilter) || c.LocalPort.ToString().Contains(keywordFilter) || c.RemotePort.ToString().Contains(keywordFilter) ) ).ToList(); // Clear the existing items and add filtered ones to the ObservableCollection Connections.Clear(); if (filteredConnections != null) { foreach (var conn in filteredConnections) { Connections.Add(conn); } } } private void FilterConnections_SelectionChanged(object sender, SelectionChangedEventArgs e) { FilterConnections_KeyUp(sender, null); } private void ExportToJsonButton_Click(object sender, RoutedEventArgs e) { var json = JsonHelper.ToJson(Connections, Json.Formatting.Indented); File.WriteAllText("connections.json", json); MessageBox.Show("Connections exported to connections.json", "Export Successful", MessageBoxButton.OK, MessageBoxImage.Information); } private void ExportToXmlButton_Click(object sender, RoutedEventArgs e) { var xmlSerializer = new XmlSerializer(typeof(List)); using (var writer = new StreamWriter("connections.xml")) { xmlSerializer.Serialize(writer, Connections); } MessageBox.Show("Connections exported to connections.xml", "Export Successful", MessageBoxButton.OK, MessageBoxImage.Information); } private void ExportToCsvButton_Click(object sender, RoutedEventArgs e) { var csvLines = new List { "Protocol,LocalAddress,LocalPort,RemoteAddress,RemotePort,State,ProcessName,ConnectionTime" }; csvLines.AddRange(Connections.Select(c => $"{c.Protocol},{c.LocalAddress},{c.LocalPort},{c.RemoteAddress},{c.RemotePort},{c.State},{c.ProcessName},{c.ConnectionTime}")); File.WriteAllLines("connections.csv", csvLines); MessageBox.Show("Connections exported to connections.csv", "Export Successful", MessageBoxButton.OK, MessageBoxImage.Information); } private void SaveFilterPresetButton_Click(object sender, RoutedEventArgs e) { var filters = new { Keywords = KeywordFilter.Text, }; var json = JsonHelper.ToJson(filters); File.WriteAllText("filterPreset.json", json); } private void LoadFilterPresetButton_Click(object sender, RoutedEventArgs e) { if (File.Exists("filterPreset.json")) { var json = File.ReadAllText("filterPreset.json"); var filters = JsonHelper.ToObject(json); KeywordFilter.Text = filters.Keywords; FilterConnections_KeyUp(null, null); } } private void ExportToHtmlButton_Click(object sender, RoutedEventArgs e) { var htmlContent = ""; foreach (var conn in Connections) { htmlContent += $""; } htmlContent += "
ProtocolLocalAddressLocalPortRemoteAddressRemotePortStateProcessNameConnectionTime
{conn.Protocol}{conn.LocalAddress}{conn.LocalPort}{conn.RemoteAddress}{conn.RemotePort}{conn.State}{conn.ProcessName}{conn.ConnectionTime}
"; File.WriteAllText("connections.html", htmlContent); MessageBox.Show("Connections exported to connections.html", "Export Successful", MessageBoxButton.OK, MessageBoxImage.Information); } private void chkMinimize_Checked(object sender, RoutedEventArgs e) { MinimizeOnStartup = chkMinimize.IsChecked == true; } private void chkShowPopups_Checked(object sender, RoutedEventArgs e) { ShowPopups = chkShowPopups.IsChecked == true; } private void KillProcessButton_Click(object sender, RoutedEventArgs e) { if (NetworkDataGrid.SelectedItem is ConnectionInfo selectedConnection) { try { var process = Process.GetProcessById(selectedConnection.ProcessId); process.Kill(); MessageBox.Show($"Process {selectedConnection.ProcessName} (PID: {selectedConnection.ProcessId}) terminated.", "Process Killed", MessageBoxButton.OK, MessageBoxImage.Warning); } catch (Exception ex) { MessageBox.Show($"Failed to terminate process: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error); } } } private async void IpLookupButton_Click(object sender, RoutedEventArgs e) { if (NetworkDataGrid.SelectedItem is ConnectionInfo selectedConnection) { string apiUrl = $"https://reallyfreegeoip.org/json/{selectedConnection.RemoteAddress}"; using (HttpClient client = new HttpClient()) { var response = await client.GetStringAsync(apiUrl); MessageBox.Show(response, "IP Lookup", MessageBoxButton.OK, MessageBoxImage.Information); } } } private void BlockIpButton_Click(object sender, RoutedEventArgs e) { // Check if an item is selected in the DataGrid if (NetworkDataGrid.SelectedItem is ConnectionInfo selectedConnection) { // Ask the user for confirmation var result = MessageBox.Show( $"Are you sure you want to block the IP address: {selectedConnection.RemoteAddress}?", "Confirm Block", MessageBoxButton.YesNo, MessageBoxImage.Question ); // If the user clicks 'Yes', proceed with blocking the IP if (result == MessageBoxResult.Yes) { string command = $"netsh advfirewall firewall add rule name=\"Blocked {selectedConnection.RemoteAddress}\" dir=in action=block remoteip={selectedConnection.RemoteAddress}"; Process.Start(new ProcessStartInfo("cmd.exe", "/C " + command) { CreateNoWindow = true }); MessageBox.Show($"Blocked IP: {selectedConnection.RemoteAddress}", "Firewall Rule Added", MessageBoxButton.OK, MessageBoxImage.Information); } } } private void btnTheme_Click(object sender, RoutedEventArgs e) { // Ensure the resources exist if (!Application.Current.Resources.Contains("DynamicBackgroundColor") || !Application.Current.Resources.Contains("DynamicTextColor")) { MessageBox.Show("Theme resources are missing!", "Error", MessageBoxButton.OK, MessageBoxImage.Error); return; } // Retrieve current background color System.Windows.Media.SolidColorBrush currentBg = (System.Windows.Media.SolidColorBrush)Application.Current.Resources["DynamicBackgroundColor"]; if (currentBg.Color == System.Windows.Media.Colors.Black) // Dark Mode → Light Mode { Application.Current.Resources["DynamicBackgroundColor"] = new System.Windows.Media.SolidColorBrush(Colors.White); Application.Current.Resources["DynamicTextColor"] = new System.Windows.Media.SolidColorBrush(Colors.Black); btnTheme.Content = "🌙"; // Set icon to moon for dark mode } else // Light Mode → Dark Mode { Application.Current.Resources["DynamicBackgroundColor"] = new System.Windows.Media.SolidColorBrush(Colors.Black); Application.Current.Resources["DynamicTextColor"] = new System.Windows.Media.SolidColorBrush(Colors.White); btnTheme.Content = "🌞"; // Set icon to sun for light mode } } private void ProcessPathButton_Click(object sender, RoutedEventArgs e) { // Check if an item is selected in the DataGrid if (NetworkDataGrid.SelectedItem is ConnectionInfo selectedConnection) { try { var process = Process.GetProcessById(selectedConnection.ProcessId); if (process != null && process.MainModule != null) { Process.Start("explorer.exe", $"/select, \"{process.MainModule.FileName}\""); } } catch (Exception ex) { MessageBox.Show($"Failed to get process path: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error); } } } } }