diff --git a/configure.ac b/configure.ac index a338377..7b77330 100644 --- a/configure.ac +++ b/configure.ac @@ -1,6 +1,7 @@ dnl dnl xfce4-taskmanager - A small taskmanager based on the Xfce 4 libraries. dnl +dnl 2024-2024 Jehan-Antoine Vayssade dnl 2014-2021 Simon Steinbess dnl 2018-2019 Rozhuk Ivan dnl 2014 Landry Breuil @@ -81,6 +82,9 @@ dnl *********************************** XDT_CHECK_OPTIONAL_PACKAGE([LIBX11], [x11], [1.6.7], [libx11], [Libx11 support]) XDT_CHECK_OPTIONAL_PACKAGE([WNCK], [libwnck-3.0], [3.2], [wnck3], [building with libwnck3 for window icons/names], [yes]) +AC_CHECK_LIB(pcap, [pcap_open_live]) +AC_CHECK_HEADERS([pcap.h]) + dnl *********************************** dnl ********** Check for skel ********* dnl *********************************** @@ -104,12 +108,14 @@ else ;; dragonfly*|netbsd*|openbsd*|darwin*) ac_os_implementation="bsd" + AC_CHECK_LIB([kvm], [kvm_openfiles]) AC_CHECK_HEADERS([err.h pwd.h stdlib.h string.h sys/param.h sys/sched.h \ sys/swap.h sys/sysctl.h sys/types.h unistd.h]) ;; solaris*) ac_os_implementation="solaris" AC_CHECK_LIB([kstat], [kstat_open]) + AC_CHECK_LIB([socket], [freeifaddrs]) AC_CHECK_HEADERS([fcntl.h kstat.h procfs.h pwd.h stdlib.h string.h \ sys/procfs.h sys/stat.h sys/swap.h sys/types.h]) ;; diff --git a/screenshot.png b/screenshot.png new file mode 100644 index 0000000..2af5915 Binary files /dev/null and b/screenshot.png differ diff --git a/src/Makefile.am b/src/Makefile.am index be34016..f97e672 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -8,6 +8,7 @@ bin_PROGRAMS = \ xfce4_taskmanager_CFLAGS = \ $(CAIRO_CFLAGS) \ + $(LIBPCAP_CFLAGS) \ $(LIBX11_CFLAGS) \ $(LIBXMU_CFLAGS) \ $(GTK3_CFLAGS) \ @@ -18,6 +19,7 @@ xfce4_taskmanager_CFLAGS = \ xfce4_taskmanager_LDADD = \ $(CAIRO_LIBS) \ + $(LIBPCAP_LIBS) \ $(LIBX11_LIBS) \ $(LIBXMU_LIBS) \ $(GTK3_LIBS) \ @@ -30,6 +32,8 @@ xfce4_taskmanager_SOURCES = \ main.c \ process-window_ui.h \ process-window.c process-window.h \ + inode-to-sock.c inode-to-sock.h \ + network-analyzer.c network-analyzer.h \ process-monitor.c process-monitor.h \ process-tree-model.c process-tree-model.h \ process-tree-view.c process-tree-view.h \ diff --git a/src/app-manager.c b/src/app-manager.c index d1b1cc2..193d043 100644 --- a/src/app-manager.c +++ b/src/app-manager.c @@ -44,7 +44,7 @@ static App *apps_lookup_app (GArray *apps, WnckApplication *application); static void application_opened (WnckScreen *screen, WnckApplication *application, XtmAppManager *manager); static void application_closed (WnckScreen *screen, WnckApplication *application, XtmAppManager *manager); static void scale_factor_changed (GdkMonitor *monitor); - +// static WnckHandle *handle = NULL; static void @@ -59,10 +59,18 @@ static void xtm_app_manager_init (XtmAppManager *manager) { WnckApplication *application; - WnckScreen *screen = wnck_screen_get_default (); - GdkMonitor *monitor = gdk_display_get_monitor (gdk_display_get_default (), 0); + WnckScreen *screen; + GdkMonitor *monitor; GList *windows, *l; + // #if WNCK_CHECK_VERSION(43, 0, 0) + // handle = wnck_handle_new (WNCK_CLIENT_TYPE_APPLICATION); + // screen = wnck_handle_get_default_screen (handle); + // #else + screen = wnck_screen_get_default (); + // #endif + monitor = gdk_display_get_monitor (gdk_display_get_default (), 0); + /* Retrieve initial applications */ while (gtk_events_pending ()) gtk_main_iteration (); @@ -210,7 +218,11 @@ application_closed (WnckScreen *screen __unused, WnckApplication *application, X static void scale_factor_changed (GdkMonitor *monitor) { + // #if WNCK_CHECK_VERSION(43, 0, 0) + // wnck_handle_set_default_mini_icon_size (handle, WNCK_DEFAULT_MINI_ICON_SIZE * gdk_monitor_get_scale_factor (monitor)); + // #else wnck_set_default_mini_icon_size (WNCK_DEFAULT_MINI_ICON_SIZE * gdk_monitor_get_scale_factor (monitor)); + // #endif } diff --git a/src/inode-to-sock.c b/src/inode-to-sock.c new file mode 100644 index 0000000..425c7a2 --- /dev/null +++ b/src/inode-to-sock.c @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2024 Jehan-Antoine Vayssade, + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "inode-to-sock.h" +#include "stdio.h" + +XtmInodeToSock * +xtm_create_inode_to_sock (void) +{ + XtmInodeToSock *its = g_new (XtmInodeToSock, 1); + its->hash = g_hash_table_new_full (g_int_hash, g_int_equal, g_free, NULL); + // freebsd only + its->pid = g_hash_table_new_full (g_int_hash, g_int_equal, g_free, NULL); + return its; +} + +void +xtm_destroy_inode_to_sock (XtmInodeToSock *its) +{ + if (its == NULL) + return; + + g_hash_table_destroy (its->hash); + g_hash_table_destroy (its->pid); + + free (its); +} + +XtmInodeToSock * +xtm_inode_to_sock_get_default (void) +{ + static XtmInodeToSock *its = NULL; + if (its == NULL) + its = xtm_create_inode_to_sock (); + return its; +} diff --git a/src/inode-to-sock.h b/src/inode-to-sock.h new file mode 100644 index 0000000..822b4c3 --- /dev/null +++ b/src/inode-to-sock.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2024 Jehan-Antoine Vayssade, + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef INMODE_TO_SOCK_H +#define INMODE_TO_SOCK_H + +#include + +typedef struct _XtmInodeToSock XtmInodeToSock; +struct _XtmInodeToSock +{ + // inode to port + GHashTable *hash; + // inode to pid (freebsd) + GHashTable *pid; +}; + +XtmInodeToSock *xtm_create_inode_to_sock (void); +void xtm_refresh_inode_to_sock (XtmInodeToSock *); +void xtm_destroy_inode_to_sock (XtmInodeToSock *); +XtmInodeToSock *xtm_inode_to_sock_get_default (void); + +#endif diff --git a/src/main.c b/src/main.c index bf967b0..fdedc24 100644 --- a/src/main.c +++ b/src/main.c @@ -12,6 +12,7 @@ #include "config.h" #endif +#include "network-analyzer.h" #include "process-window.h" #include "settings.h" #include "task-manager.h" @@ -28,6 +29,7 @@ static XtmTaskManager *task_manager; static guint timer_id; static gboolean start_hidden = FALSE; static gboolean standalone = FALSE; +static XtmNetworkAnalyzer *analyzer = NULL; static GOptionEntry main_entries[] = { { "start-hidden", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_NONE, &start_hidden, "Don't open a task manager window", NULL }, @@ -121,15 +123,14 @@ destroy_window (void) } static gboolean -delete_window (void) +delete_window (GtkWidget *widget, GdkEvent *event, gpointer user_data) { if (!status_icon_get_visible ()) { - if (timer_id > 0) - g_source_remove (timer_id); - gtk_main_quit (); + destroy_window (); return FALSE; } + gtk_widget_hide (window); return TRUE; } @@ -140,8 +141,10 @@ collect_data (void) guint num_processes; gfloat cpu, memory_percent, swap_percent; guint64 swap_used, swap_free, swap_total, memory_used, memory_total; + guint64 tcp_rx, tcp_tx, tcp_error; gchar *used, *total, tooltip[1024], memory_info[64], swap_info[64]; + xtm_task_manager_get_network_info (task_manager, &tcp_rx, &tcp_tx, &tcp_error); xtm_task_manager_get_system_info (task_manager, &num_processes, &cpu, &memory_used, &memory_total, &swap_used, &swap_total); memory_percent = (memory_total != 0) ? ((memory_used * 100.0f) / (float)memory_total) : 0.0f; @@ -159,19 +162,23 @@ collect_data (void) g_free (used); g_free (total); - xtm_process_window_set_system_info (XTM_PROCESS_WINDOW (window), num_processes, cpu, memory_percent, memory_info, swap_percent, swap_info); + xtm_process_window_set_system_info (XTM_PROCESS_WINDOW (window), num_processes, cpu, memory_percent, memory_info, swap_percent, swap_info, tcp_rx, tcp_tx, tcp_error); xtm_task_manager_get_swap_usage (task_manager, &swap_free, &swap_total); xtm_process_window_show_swap_usage (XTM_PROCESS_WINDOW (window), (swap_total > 0)); if (status_icon_get_visible ()) { - g_snprintf (tooltip, sizeof (tooltip), + g_snprintf ( + tooltip, sizeof (tooltip), _("Processes: %u\n" "CPU: %.0f%%\n" "Memory: %s\n" - "Swap: %s"), - num_processes, cpu, memory_info, swap_info); + "Swap: %s\n" + "Network: %.2f / %.2f MB/s"), + num_processes, cpu, memory_info, swap_info, + tcp_rx * 1e-5, tcp_tx * 1e-5); + G_GNUC_BEGIN_IGNORE_DEPRECATIONS gtk_status_icon_set_tooltip_markup (GTK_STATUS_ICON (status_icon_or_null), tooltip); G_GNUC_END_IGNORE_DEPRECATIONS @@ -253,8 +260,7 @@ main (int argc, char *argv[]) if (!xfconf_init (&error)) { xfce_message_dialog (NULL, _("Xfce Notify Daemon"), - "dialog-error", - _("Settings daemon is unavailable"), + "dialog-error", _("Settings daemon is unavailable"), error->message, "application-exit", GTK_RESPONSE_ACCEPT, NULL); @@ -273,6 +279,8 @@ main (int argc, char *argv[]) g_signal_connect_swapped (app, "activate", G_CALLBACK (xtm_process_window_show), window); + //! create net + analyzer = xtm_network_analyzer_get_default (); task_manager = xtm_task_manager_new (xtm_process_window_get_model (XTM_PROCESS_WINDOW (window))); collect_data (); @@ -282,8 +290,11 @@ main (int argc, char *argv[]) g_signal_connect_after (settings, "notify::full-command-line", G_CALLBACK (collect_data), NULL); g_signal_connect (settings, "notify::show-status-icon", G_CALLBACK (show_hide_status_icon), NULL); - g_signal_connect (window, "destroy", G_CALLBACK (destroy_window), NULL); + g_signal_connect (xtm_process_window_get (XTM_PROCESS_WINDOW (window)), "destroy", G_CALLBACK (destroy_window), NULL); + g_signal_connect (xtm_process_window_get (XTM_PROCESS_WINDOW (window)), "delete-event", G_CALLBACK (delete_window), NULL); g_signal_connect (window, "delete-event", G_CALLBACK (delete_window), NULL); + g_signal_connect (window, "destroy", G_CALLBACK (destroy_window), NULL); + if (gtk_widget_get_visible (window) || status_icon_get_visible ()) gtk_main (); @@ -297,5 +308,7 @@ main (int argc, char *argv[]) g_object_unref (status_icon_or_null); xfconf_shutdown (); + xtm_destroy_network_analyzer (analyzer); + return 0; } diff --git a/src/network-analyzer.c b/src/network-analyzer.c new file mode 100644 index 0000000..2238e26 --- /dev/null +++ b/src/network-analyzer.c @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2024 Jehan-Antoine Vayssade, + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "network-analyzer.h" +#include "task-manager.h" + +#include +#include +#include +#include +#include +#include +#include + +void *network_analyzer_thread (void *ptr); + +void +increament_packet_count (char *mac, char *direction, GHashTable *hash_table, long int port) +{ + gint64 *key = g_new0 (gint64, 1); + *key = port; + gpointer value = g_hash_table_lookup (hash_table, key); + g_hash_table_replace (hash_table, key, (gpointer)(((guint64)value) + 1)); + // printf ("%s -> %s %ld: %ld\n", mac, direction, port, ((guint64)value) + 1); +} + +void * +network_analyzer_thread (void *ptr) +{ +#ifdef HAVE_LIBPCAP + XtmNetworkAnalyzer *analyzer = (XtmNetworkAnalyzer *)ptr; + pcap_loop (analyzer->handle, -1, packet_callback, (void *)analyzer); +#endif + return NULL; +} + +XtmNetworkAnalyzer * +xtm_create_network_analyzer (void) +{ + XtmNetworkAnalyzer *analyzer = NULL; + +#ifdef HAVE_LIBPCAP + XtmNetworkAnalyzer *current = NULL; + char errbuf[PCAP_ERRBUF_SIZE]; + pcap_if_t *it = NULL; + + if (pcap_findalldevs (&it, errbuf) != 0) + { + fprintf (stderr, "Error finding device: %s\n", errbuf); + return NULL; + } + + + while (it) + { + guint8 mac[6]; + char *device = it->name; + + //! todo check pcap lib ? + if (get_mac_address (device, mac) == 0) + { + printf ( + "%s, MAC address: %02X:%02X:%02X:%02X:%02X:%02X\n", + device, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + } + else + { + fprintf (stderr, "No mac adresse for %s\n", device); + it = it->next; + continue; + } + + // Open a pcap session + pcap_t *handle = pcap_open_live (device, BUFSIZ, 1, 1000, errbuf); + + if (handle == NULL) + { + fprintf (stderr, "Could not open device %s: %s\n", device, errbuf); + it = it->next; + continue; + } + + if (analyzer == NULL) + { + analyzer = (XtmNetworkAnalyzer *)malloc (sizeof (XtmNetworkAnalyzer)); + analyzer->next = NULL; + current = analyzer; + } + else + { + current->next = (XtmNetworkAnalyzer *)malloc (sizeof (XtmNetworkAnalyzer)); + current = current->next; + current->next = NULL; + } + + current->handle = handle; + current->iface = it; + current->packetin = g_hash_table_new_full (g_int_hash, g_int_equal, g_free, NULL); + current->packetout = g_hash_table_new_full (g_int_hash, g_int_equal, g_free, NULL); + + memcpy (current->mac, mac, sizeof (uint8_t) * 6); + + pthread_mutex_init (¤t->lock, NULL); + pthread_create (¤t->thread, NULL, network_analyzer_thread, (void *)current); + + it = it->next; + } + + if (analyzer == NULL) + { + fprintf (stderr, "Could not open any device %s\n", errbuf); + pcap_freealldevs (it); + } +#endif + + return analyzer; +} + +XtmNetworkAnalyzer * +xtm_network_analyzer_get_default (void) +{ + static int initialized = FALSE; + static XtmNetworkAnalyzer *analyzer = NULL; + + if (initialized == FALSE) + { + initialized = TRUE; + // analyzer may be NULL if no device can be opened + analyzer = xtm_create_network_analyzer (); + } + + return analyzer; +} + +void +xtm_destroy_network_analyzer (XtmNetworkAnalyzer *analyzer) +{ +#ifdef HAVE_LIBPCAP + if (analyzer == NULL) + return; + + XtmNetworkAnalyzer *previous = analyzer; + pcap_if_t *it = analyzer->iface; + + while (analyzer) + { + pthread_cancel (analyzer->thread); + pcap_close (analyzer->handle); + g_hash_table_destroy (analyzer->packetin); + g_hash_table_destroy (analyzer->packetout); + pthread_mutex_destroy (&analyzer->lock); + analyzer = analyzer->next; + free (previous); + previous = analyzer; + } + + pcap_freealldevs (it); +#endif +} diff --git a/src/network-analyzer.h b/src/network-analyzer.h new file mode 100644 index 0000000..1b21347 --- /dev/null +++ b/src/network-analyzer.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2024 Jehan-Antoine Vayssade, + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef NETWORK_ANALYZER_H +#define NETWORK_ANALYZER_H + +#include + +#ifdef HAVE_LIBPCAP +#include +#include +#endif + +typedef struct _XtmNetworkAnalyzer XtmNetworkAnalyzer; +struct _XtmNetworkAnalyzer +{ + // interface mac adress + guint8 mac[6]; + + void *iface; // pcap_if_t + void *handle; // pcap_t + + pthread_t thread; + pthread_mutex_t lock; + + // map port and number of packets + GHashTable *packetin; + GHashTable *packetout; + + // next interface + XtmNetworkAnalyzer *next; +}; + +XtmNetworkAnalyzer *xtm_create_network_analyzer (void); +void increament_packet_count (char *mac, char *direction, GHashTable *hash_table, long int port); +void xtm_destroy_network_analyzer (XtmNetworkAnalyzer *analyzer); +XtmNetworkAnalyzer *xtm_network_analyzer_get_default (void); + +#endif diff --git a/src/process-monitor.c b/src/process-monitor.c index 89d3f1c..3576d46 100644 --- a/src/process-monitor.c +++ b/src/process-monitor.c @@ -16,7 +16,7 @@ #include - +#define MAX_GRAPH 3 enum { @@ -34,8 +34,8 @@ struct _XtmProcessMonitor /**/ gfloat step_size; gint type; - GArray *history; - GArray *history_swap; + GArray *history[MAX_GRAPH]; + gfloat max[MAX_GRAPH]; gboolean dark_mode; }; G_DEFINE_TYPE (XtmProcessMonitor, xtm_process_monitor, GTK_TYPE_DRAWING_AREA) @@ -76,8 +76,12 @@ xtm_process_monitor_init (XtmProcessMonitor *monitor) { GtkSettings *settings = gtk_settings_get_default (); - monitor->history = g_array_new (FALSE, TRUE, sizeof (gfloat)); - monitor->history_swap = g_array_new (FALSE, TRUE, sizeof (gfloat)); + for (int graph = 0; graph < MAX_GRAPH; ++graph) + { + monitor->history[graph] = g_array_new (FALSE, TRUE, sizeof (gfloat)); + monitor->max[graph] = 1.0; + } + monitor->dark_mode = xtm_gtk_widget_is_dark_mode (GTK_WIDGET (monitor)); g_signal_connect_swapped (settings, "notify::gtk-theme-name", G_CALLBACK (xtm_process_monitor_on_notify_theme_name), monitor); @@ -89,8 +93,8 @@ xtm_process_monitor_finalize (GObject *object) { XtmProcessMonitor *monitor = XTM_PROCESS_MONITOR (object); - g_array_free (monitor->history, TRUE); - g_array_free (monitor->history_swap, TRUE); + for (int graph = 0; graph < MAX_GRAPH; ++graph) + g_array_free (monitor->history[graph], TRUE); G_OBJECT_CLASS (xtm_process_monitor_parent_class)->finalize (object); } @@ -142,13 +146,10 @@ xtm_process_monitor_draw (GtkWidget *widget, cairo_t *cr) guint minimum_history_length; minimum_history_length = (guint)(gtk_widget_get_allocated_width (widget) / monitor->step_size); - if (monitor->history->len < minimum_history_length) - { - g_array_set_size (monitor->history, minimum_history_length + 1); - if (monitor->type == 1) - g_array_set_size (monitor->history_swap, minimum_history_length + 1); - } + for (int graph = 0; graph < MAX_GRAPH; ++graph) + if (monitor->history[graph]->len < minimum_history_length) + g_array_set_size (monitor->history[graph], minimum_history_length + 1); xtm_process_monitor_paint (monitor, cr); return FALSE; @@ -172,6 +173,30 @@ xtm_process_monitor_cairo_set_source_rgb (cairo_t *cr, double red, double green, cairo_set_source_rgb (cr, red, green, blue); } +static void +xtm_process_monitor_cairo_set_color (XtmProcessMonitor *monitor, cairo_t *cr, gfloat alpha, guint variant) +{ + if (monitor->type == 0) + xtm_process_monitor_cairo_set_source_rgba (cr, 1.0, 0.43, 0.0, alpha, monitor->dark_mode); + else if (monitor->type == 1) + { + // xtm_process_monitor_cairo_set_source_rgba (cr, 0.67, 0.09, 0.32, alpha, monitor->dark_mode); + if (variant == 0) + xtm_process_monitor_cairo_set_source_rgba (cr, 0.80, 0.22, 0.42, alpha, monitor->dark_mode); + else + xtm_process_monitor_cairo_set_source_rgba (cr, 0.50, 0.50, 0.50, alpha, monitor->dark_mode); + } + else + { + if (variant == 0) + xtm_process_monitor_cairo_set_source_rgba (cr, 0.00, 0.42, 0.64, alpha, monitor->dark_mode); + else if (variant == 1) + xtm_process_monitor_cairo_set_source_rgba (cr, 0.02, 0.71, 0.81, alpha, monitor->dark_mode); + else + xtm_process_monitor_cairo_set_source_rgba (cr, 0.00, 1.00, 1.00, alpha, monitor->dark_mode); + } +} + static cairo_surface_t * xtm_process_monitor_graph_surface_create (XtmProcessMonitor *monitor, gint width, gint height) { @@ -180,69 +205,39 @@ xtm_process_monitor_graph_surface_create (XtmProcessMonitor *monitor, gint width gdouble peak, step_size; gint i; - if (monitor->history->len <= 1) - { - g_warning ("Cannot paint graph with n_peak <= 1"); - return NULL; - } step_size = (gdouble)monitor->step_size; graph_surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height); cr = cairo_create (graph_surface); /* Draw line and area below the line, distinguish between CPU (0) and Mem (1) color-wise */ - cairo_translate (cr, step_size, 0); cairo_set_line_width (cr, 0.85); cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND); cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND); cairo_set_antialias (cr, CAIRO_ANTIALIAS_DEFAULT); - cairo_move_to (cr, width, height); - /* Create a line before the call to cairo_translate, - * to avoid creating a downward sloping line going off the graph */ - peak = g_array_index (monitor->history, gfloat, 0); - cairo_line_to (cr, width, (1.0 - peak) * height); - - for (i = 0; (step_size * (i - 1)) <= width; i++) + for (int graph = 0; graph < MAX_GRAPH; ++graph) { - peak = g_array_index (monitor->history, gfloat, i); - cairo_translate (cr, -step_size, 0); - cairo_line_to (cr, width, (1.0 - peak) * height); - } + if (monitor->history[graph]->len <= 1) + continue; - if (monitor->type == 0) - xtm_process_monitor_cairo_set_source_rgba (cr, 1.0, 0.43, 0.0, 0.3, monitor->dark_mode); - else - xtm_process_monitor_cairo_set_source_rgba (cr, 0.67, 0.09, 0.32, 0.3, monitor->dark_mode); - cairo_line_to (cr, width, height); - cairo_fill_preserve (cr); - - if (monitor->type == 0) - xtm_process_monitor_cairo_set_source_rgba (cr, 1.0, 0.43, 0.0, 1.0, monitor->dark_mode); - else - xtm_process_monitor_cairo_set_source_rgba (cr, 0.67, 0.09, 0.32, 1.0, monitor->dark_mode); - cairo_stroke (cr); - - /* Draw Swap graph */ - if (monitor->type == 1) - { - cairo_translate (cr, step_size * i, 0); cairo_move_to (cr, width, height); - - peak = g_array_index (monitor->history_swap, gfloat, 0); - cairo_line_to (cr, width, (1.0 - peak) * height); + cairo_translate (cr, step_size, 0); + xtm_process_monitor_cairo_set_color (monitor, cr, 0.3, graph); for (i = 0; (step_size * (i - 1)) <= width; i++) { - peak = g_array_index (monitor->history_swap, gfloat, i); + peak = g_array_index (monitor->history[graph], gfloat, i) / monitor->max[graph]; cairo_translate (cr, -step_size, 0); cairo_line_to (cr, width, (1.0 - peak) * height); } - xtm_process_monitor_cairo_set_source_rgba (cr, 0.33, 0.04, 0.16, 0.3, monitor->dark_mode); + cairo_line_to (cr, width, height); cairo_fill_preserve (cr); - xtm_process_monitor_cairo_set_source_rgba (cr, 0.33, 0.04, 0.16, 1.0, monitor->dark_mode); + xtm_process_monitor_cairo_set_color (monitor, cr, 1.0, graph); cairo_stroke (cr); + + cairo_translate (cr, width, 0); } cairo_destroy (cr); @@ -300,21 +295,17 @@ xtm_process_monitor_new (void) } void -xtm_process_monitor_add_peak (XtmProcessMonitor *monitor, gfloat peak, gfloat peak_swap) +xtm_process_monitor_add_peak (XtmProcessMonitor *monitor, gfloat peak, gint index) { g_return_if_fail (XTM_IS_PROCESS_MONITOR (monitor)); - g_return_if_fail (peak >= 0.0f && peak <= 1.0f); + // g_return_if_fail (peak >= 0.0f && peak <= 1.0f); - g_array_prepend_val (monitor->history, peak); - if (monitor->history->len > 1) - g_array_remove_index (monitor->history, monitor->history->len - 1); + if (peak > monitor->max[index]) + monitor->max[index] = peak; - if (monitor->type == 1) - { - g_array_prepend_val (monitor->history_swap, peak_swap); - if (monitor->history_swap->len > 1) - g_array_remove_index (monitor->history_swap, monitor->history_swap->len - 1); - } + g_array_prepend_val (monitor->history[index], peak); + if (monitor->history[index]->len > 1) + g_array_remove_index (monitor->history[index], monitor->history[index]->len - 1); if (GDK_IS_WINDOW (gtk_widget_get_window (GTK_WIDGET (monitor)))) gdk_window_invalidate_rect (gtk_widget_get_window (GTK_WIDGET (monitor)), NULL, FALSE); @@ -340,8 +331,8 @@ void xtm_process_monitor_clear (XtmProcessMonitor *monitor) { g_return_if_fail (XTM_IS_PROCESS_MONITOR (monitor)); - g_array_set_size (monitor->history, 0); - g_array_set_size (monitor->history_swap, 0); + for (int graph = 0; graph < MAX_GRAPH; ++graph) + g_array_set_size (monitor->history[graph], 0); if (GDK_IS_WINDOW (gtk_widget_get_window (GTK_WIDGET (monitor)))) gdk_window_invalidate_rect (gtk_widget_get_window (GTK_WIDGET (monitor)), NULL, FALSE); } diff --git a/src/process-monitor.h b/src/process-monitor.h index 6755a56..1714899 100644 --- a/src/process-monitor.h +++ b/src/process-monitor.h @@ -1,4 +1,5 @@ /* + * Copyright (c) 2024 Jehan-Antoine Vayssade, * Copyright (c) 2010 Mike Massonnet, * * This program is free software; you can redistribute it and/or modify @@ -24,7 +25,7 @@ typedef struct _XtmProcessMonitor XtmProcessMonitor; GType xtm_process_monitor_get_type (void); GtkWidget *xtm_process_monitor_new (void); -void xtm_process_monitor_add_peak (XtmProcessMonitor *monitor, gfloat peak, gfloat peak_swap); +void xtm_process_monitor_add_peak (XtmProcessMonitor *monitor, gfloat peak, gint index); void xtm_process_monitor_set_step_size (XtmProcessMonitor *monitor, gfloat step_size); void xtm_process_monitor_set_type (XtmProcessMonitor *monitor, gint type); void xtm_process_monitor_clear (XtmProcessMonitor *monitor); diff --git a/src/process-statusbar.c b/src/process-statusbar.c index 484d7d3..d545d92 100644 --- a/src/process-statusbar.c +++ b/src/process-statusbar.c @@ -26,7 +26,11 @@ enum PROP_SWAP, PROP_SHOW_SWAP, PROP_NUM_PROCESSES, + PROP_NETWORK_RX, + PROP_NETWORK_TX, + PROP_NETWORK_ERROR, }; + typedef struct _XtmProcessStatusbarClass XtmProcessStatusbarClass; struct _XtmProcessStatusbarClass { @@ -43,11 +47,19 @@ struct _XtmProcessStatusbar GtkWidget *label_memory; GtkWidget *label_swap; + GtkWidget *label_net_rx; + GtkWidget *label_net_tx; + GtkWidget *label_net_error; + gfloat cpu; gchar memory[64]; gchar swap[64]; guint num_processes; + gfloat tcp_rx; + gfloat tcp_tx; + guint64 tcp_error; + gboolean dark_mode; }; G_DEFINE_TYPE (XtmProcessStatusbar, xtm_process_statusbar, GTK_TYPE_BOX) @@ -74,6 +86,12 @@ xtm_process_statusbar_class_init (XtmProcessStatusbarClass *klass) g_param_spec_boolean ("show-swap", "ShowSwap", "Show or hide swap usage", TRUE, G_PARAM_WRITABLE)); g_object_class_install_property (class, PROP_NUM_PROCESSES, g_param_spec_uint ("num-processes", "NumProcesses", "Number of processes", 0, G_MAXUINT, 0, G_PARAM_CONSTRUCT | G_PARAM_WRITABLE)); + g_object_class_install_property (class, PROP_NETWORK_RX, + g_param_spec_float ("network-rx", "RX", "Net rx", 0, 100, 0, G_PARAM_CONSTRUCT | G_PARAM_WRITABLE)); + g_object_class_install_property (class, PROP_NETWORK_TX, + g_param_spec_float ("network-tx", "TX", "Net tx", 0, 100, 0, G_PARAM_CONSTRUCT | G_PARAM_WRITABLE)); + g_object_class_install_property (class, PROP_NETWORK_ERROR, + g_param_spec_uint64 ("network-error", "NetEror", "Number of error since last update", 0, G_MAXUINT64, 0, G_PARAM_CONSTRUCT | G_PARAM_WRITABLE)); } static void @@ -99,13 +117,16 @@ xtm_process_statusbar_on_notify_theme_name (XtmProcessStatusbar *statusbar) xtm_process_statusbar_set_label_style (statusbar, statusbar->label_num_processes); xtm_process_statusbar_set_label_style (statusbar, statusbar->label_memory); xtm_process_statusbar_set_label_style (statusbar, statusbar->label_swap); + xtm_process_statusbar_set_label_style (statusbar, statusbar->label_net_rx); + xtm_process_statusbar_set_label_style (statusbar, statusbar->label_net_tx); + xtm_process_statusbar_set_label_style (statusbar, statusbar->label_net_error); } static void xtm_process_statusbar_init (XtmProcessStatusbar *statusbar) { GtkSettings *settings = gtk_settings_get_default (); - GtkWidget *hbox, *hbox_cpu, *hbox_mem; + GtkWidget *hbox, *hbox_cpu, *hbox_net, *hbox_mem; GtkStyleContext *context; GtkCssProvider *provider; statusbar->settings = xtm_settings_get_default (); @@ -116,6 +137,7 @@ xtm_process_statusbar_init (XtmProcessStatusbar *statusbar) hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 16); hbox_cpu = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 16); + hbox_net = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 16); hbox_mem = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 16); statusbar->label_cpu = gtk_label_new (NULL); @@ -143,11 +165,39 @@ xtm_process_statusbar_init (XtmProcessStatusbar *statusbar) gtk_box_pack_start (GTK_BOX (hbox_mem), statusbar->label_swap, TRUE, FALSE, 0); context = gtk_widget_get_style_context (statusbar->label_swap); provider = gtk_css_provider_new (); - gtk_css_provider_load_from_data (provider, "* { color: #75324d; } .dark { color: #8acdb2; }", -1, NULL); + gtk_css_provider_load_from_data (provider, "* { color: #808080; } .dark { color: #808080; }", -1, NULL); + gtk_style_context_add_provider (context, GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + g_object_unref (provider); + + statusbar->label_net_rx = gtk_label_new (NULL); + gtk_label_set_ellipsize (GTK_LABEL (statusbar->label_net_rx), PANGO_ELLIPSIZE_END); + gtk_box_pack_start (GTK_BOX (hbox_net), statusbar->label_net_rx, TRUE, FALSE, 0); + context = gtk_widget_get_style_context (statusbar->label_net_rx); + provider = gtk_css_provider_new (); + gtk_css_provider_load_from_data (provider, "* { color: #006ca2; } .dark { color: #ff935d; }", -1, NULL); + gtk_style_context_add_provider (context, GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + g_object_unref (provider); + + statusbar->label_net_tx = gtk_label_new (NULL); + gtk_label_set_ellipsize (GTK_LABEL (statusbar->label_net_tx), PANGO_ELLIPSIZE_END); + gtk_box_pack_start (GTK_BOX (hbox_net), statusbar->label_net_tx, TRUE, FALSE, 0); + context = gtk_widget_get_style_context (statusbar->label_net_tx); + provider = gtk_css_provider_new (); + gtk_css_provider_load_from_data (provider, "* { color: #05b6ce; } .dark { color: #fa4931; }", -1, NULL); + gtk_style_context_add_provider (context, GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + g_object_unref (provider); + + statusbar->label_net_error = gtk_label_new (NULL); + gtk_label_set_ellipsize (GTK_LABEL (statusbar->label_net_error), PANGO_ELLIPSIZE_END); + gtk_box_pack_start (GTK_BOX (hbox_net), statusbar->label_net_error, TRUE, FALSE, 0); + context = gtk_widget_get_style_context (statusbar->label_net_error); + provider = gtk_css_provider_new (); + gtk_css_provider_load_from_data (provider, "* { color: #008080; } .dark { color: #FF0000; }", -1, NULL); gtk_style_context_add_provider (context, GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); g_object_unref (provider); gtk_box_pack_start (GTK_BOX (hbox), hbox_cpu, TRUE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (hbox), hbox_net, TRUE, TRUE, 0); gtk_box_pack_start (GTK_BOX (hbox), hbox_mem, TRUE, TRUE, 0); gtk_box_set_homogeneous (GTK_BOX (hbox), TRUE); @@ -157,6 +207,9 @@ xtm_process_statusbar_init (XtmProcessStatusbar *statusbar) xtm_process_statusbar_set_label_style (statusbar, statusbar->label_num_processes); xtm_process_statusbar_set_label_style (statusbar, statusbar->label_memory); xtm_process_statusbar_set_label_style (statusbar, statusbar->label_swap); + xtm_process_statusbar_set_label_style (statusbar, statusbar->label_net_rx); + xtm_process_statusbar_set_label_style (statusbar, statusbar->label_net_tx); + xtm_process_statusbar_set_label_style (statusbar, statusbar->label_net_error); gtk_widget_show_all (hbox); } @@ -224,6 +277,31 @@ xtm_process_statusbar_set_property (GObject *object, guint property_id, const GV g_free (text); break; + case PROP_NETWORK_RX: + statusbar->tcp_rx = g_value_get_float (value); + // statusbar->tcp_rx = interval_to_second(statusbar->tcp_rx, statusbar->settings); + float_value = rounded_float_value (statusbar->tcp_rx, statusbar->settings); + text = g_strdup_printf (_("RX: %s MB/s"), float_value); + gtk_label_set_text (GTK_LABEL (statusbar->label_net_rx), text); + g_free (text); + break; + + case PROP_NETWORK_TX: + statusbar->tcp_tx = g_value_get_float (value); + // statusbar->tcp_tx = interval_to_second(statusbar->tcp_tx, statusbar->settings); + float_value = rounded_float_value (statusbar->tcp_tx, statusbar->settings); + text = g_strdup_printf (_("TX: %s MB/s"), float_value); + gtk_label_set_text (GTK_LABEL (statusbar->label_net_tx), text); + g_free (text); + break; + + case PROP_NETWORK_ERROR: + statusbar->tcp_error = g_value_get_uint64 (value); + text = g_strdup_printf (_("Error: %lu"), statusbar->tcp_error); + gtk_label_set_text (GTK_LABEL (statusbar->label_net_error), text); + g_free (text); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; diff --git a/src/process-tree-view.c b/src/process-tree-view.c index ec86fbb..73c981a 100644 --- a/src/process-tree-view.c +++ b/src/process-tree-view.c @@ -11,6 +11,15 @@ #include "config.h" #endif +#include +#include +#include +#include +#include +#include +#include + +#include "network-analyzer.h" #include "process-tree-model.h" #include "process-tree-view.h" #include "settings.h" @@ -37,6 +46,9 @@ enum COLUMN_UID, COLUMN_CPU, COLUMN_GROUP_CPU, + COLUMN_PACKET_IN, + COLUMN_PACKET_OUT, + COLUMN_ACTIVE_SOCKET, COLUMN_PRIORITY, N_COLUMNS, }; @@ -88,6 +100,7 @@ xtm_process_tree_view_class_init (XtmProcessTreeViewClass *klass) static void xtm_process_tree_view_init (XtmProcessTreeView *treeview) { + XtmNetworkAnalyzer *network; GtkCellRenderer *cell_text, *cell_right_aligned; GtkTreeViewColumn *column; gboolean visible; @@ -122,6 +135,9 @@ xtm_process_tree_view_init (XtmProcessTreeView *treeview) G_TYPE_STRING, /* XTM_PTV_COLUMN_CPU_STR */ G_TYPE_FLOAT, /* XTM_PTV_COLUMN_GROUP_CPU */ G_TYPE_STRING, /* XTM_PTV_COLUMN_GROUP_CPU_STR */ + G_TYPE_UINT64, /* XTM_PTV_COLUMN_PACKET_IN */ + G_TYPE_UINT64, /* XTM_PTV_COLUMN_PACKET_OUT */ + G_TYPE_UINT64, /* XTM_PTV_COLUMN_ACTIVE_SOCKET */ G_TYPE_INT, /* XTM_PTV_COLUMN_PRIORITY */ G_TYPE_STRING, /* XTM_PTV_COLUMN_BACKGROUND */ G_TYPE_STRING, /* XTM_PTV_COLUMN_FOREGROUND */ @@ -245,6 +261,43 @@ xtm_process_tree_view_init (XtmProcessTreeView *treeview) g_signal_connect (column, "clicked", G_CALLBACK (column_clicked), treeview); gtk_tree_view_insert_column (GTK_TREE_VIEW (treeview), column, treeview->columns_positions[COLUMN_GROUP_CPU]); + /*************/ + + g_object_get (treeview->settings, "column-packet-in", &visible, NULL); + column = gtk_tree_view_column_new_with_attributes (_("PacketIn"), cell_right_aligned, "text", XTM_PTV_COLUMN_PACKET_IN, "cell-background", XTM_PTV_COLUMN_BACKGROUND, "foreground", XTM_PTV_COLUMN_FOREGROUND, NULL); + g_object_set (column, COLUMN_PROPERTIES, NULL); + g_object_set_data (G_OBJECT (column), "sort-column-id", GINT_TO_POINTER (XTM_PTV_COLUMN_PACKET_IN)); + g_object_set_data (G_OBJECT (column), "column-id", GINT_TO_POINTER (COLUMN_PACKET_IN)); + g_signal_connect (column, "clicked", G_CALLBACK (column_clicked), treeview); + gtk_tree_view_insert_column (GTK_TREE_VIEW (treeview), column, treeview->columns_positions[COLUMN_PACKET_IN]); + + g_object_get (treeview->settings, "column-packet-out", &visible, NULL); + column = gtk_tree_view_column_new_with_attributes (_("PacketOut"), cell_right_aligned, "text", XTM_PTV_COLUMN_PACKET_OUT, "cell-background", XTM_PTV_COLUMN_BACKGROUND, "foreground", XTM_PTV_COLUMN_FOREGROUND, NULL); + g_object_set (column, COLUMN_PROPERTIES, NULL); + g_object_set_data (G_OBJECT (column), "sort-column-id", GINT_TO_POINTER (XTM_PTV_COLUMN_PACKET_OUT)); + g_object_set_data (G_OBJECT (column), "column-id", GINT_TO_POINTER (COLUMN_PACKET_OUT)); + g_signal_connect (column, "clicked", G_CALLBACK (column_clicked), treeview); + gtk_tree_view_insert_column (GTK_TREE_VIEW (treeview), column, treeview->columns_positions[COLUMN_PACKET_OUT]); + + g_object_get (treeview->settings, "column-active-socket", &visible, NULL); + column = gtk_tree_view_column_new_with_attributes (_("ActiveSocket"), cell_right_aligned, "text", XTM_PTV_COLUMN_ACTIVE_SOCKET, "cell-background", XTM_PTV_COLUMN_BACKGROUND, "foreground", XTM_PTV_COLUMN_FOREGROUND, NULL); + g_object_set (column, COLUMN_PROPERTIES, NULL); + g_object_set_data (G_OBJECT (column), "sort-column-id", GINT_TO_POINTER (XTM_PTV_COLUMN_ACTIVE_SOCKET)); + g_object_set_data (G_OBJECT (column), "column-id", GINT_TO_POINTER (COLUMN_ACTIVE_SOCKET)); + g_signal_connect (column, "clicked", G_CALLBACK (column_clicked), treeview); + gtk_tree_view_insert_column (GTK_TREE_VIEW (treeview), column, treeview->columns_positions[COLUMN_ACTIVE_SOCKET]); + + // insufficient permission + network = xtm_network_analyzer_get_default (); + if (network == NULL) + { + gtk_tree_view_column_set_visible (gtk_tree_view_get_column (GTK_TREE_VIEW (treeview), treeview->columns_positions[COLUMN_PACKET_IN]), FALSE); + gtk_tree_view_column_set_visible (gtk_tree_view_get_column (GTK_TREE_VIEW (treeview), treeview->columns_positions[COLUMN_PACKET_OUT]), FALSE); + gtk_tree_view_column_set_visible (gtk_tree_view_get_column (GTK_TREE_VIEW (treeview), treeview->columns_positions[COLUMN_ACTIVE_SOCKET]), FALSE); + } + + /*******************/ + g_object_get (treeview->settings, "column-priority", &visible, NULL); /* TRANSLATORS: “Prio.” is short for Priority, it appears in the tree view header. */ column = gtk_tree_view_column_new_with_attributes (_("Prio."), cell_right_aligned, "text", XTM_PTV_COLUMN_PRIORITY, "cell-background", XTM_PTV_COLUMN_BACKGROUND, "foreground", XTM_PTV_COLUMN_FOREGROUND, NULL); @@ -842,6 +895,12 @@ settings_changed (GObject *object, GParamSpec *pspec, XtmProcessTreeView *treevi column_id = COLUMN_GROUP_CPU; else if (!g_strcmp0 (pspec->name, "column-priority")) column_id = COLUMN_PRIORITY; + else if (!g_strcmp0 (pspec->name, "column-packet-in")) + column_id = COLUMN_PACKET_IN; + else if (!g_strcmp0 (pspec->name, "column-packet-out")) + column_id = COLUMN_PACKET_OUT; + else if (!g_strcmp0 (pspec->name, "column-active-socket")) + column_id = COLUMN_ACTIVE_SOCKET; g_object_get (object, pspec->name, &visible, NULL); gtk_tree_view_column_set_visible (gtk_tree_view_get_column (GTK_TREE_VIEW (treeview), treeview->columns_positions[column_id]), visible); diff --git a/src/process-tree-view.h b/src/process-tree-view.h index f8950b9..62dcb0d 100644 --- a/src/process-tree-view.h +++ b/src/process-tree-view.h @@ -39,6 +39,9 @@ enum XTM_PTV_COLUMN_CPU_STR, XTM_PTV_COLUMN_GROUP_CPU, XTM_PTV_COLUMN_GROUP_CPU_STR, + XTM_PTV_COLUMN_PACKET_IN, + XTM_PTV_COLUMN_PACKET_OUT, + XTM_PTV_COLUMN_ACTIVE_SOCKET, XTM_PTV_COLUMN_PRIORITY, XTM_PTV_COLUMN_BACKGROUND, XTM_PTV_COLUMN_FOREGROUND, diff --git a/src/process-window.c b/src/process-window.c index eaa020b..2602773 100644 --- a/src/process-window.c +++ b/src/process-window.c @@ -52,6 +52,7 @@ struct _XtmProcessWindow GtkWidget *filter_searchbar; GtkWidget *cpu_monitor; GtkWidget *mem_monitor; + GtkWidget *net_monitor; GtkWidget *vpaned; GtkWidget *treeview; GtkWidget *statusbar; @@ -347,6 +348,13 @@ xtm_process_window_init (XtmProcessWindow *window) gtk_widget_show (window->mem_monitor); gtk_container_add (GTK_CONTAINER (toolitem), window->mem_monitor); + toolitem = GTK_WIDGET (gtk_builder_get_object (window->builder, "graph-net")); + window->net_monitor = xtm_process_monitor_new (); + xtm_process_monitor_set_step_size (XTM_PROCESS_MONITOR (window->net_monitor), refresh_rate / 1000.0f); + xtm_process_monitor_set_type (XTM_PROCESS_MONITOR (window->net_monitor), 2); + gtk_widget_show (window->net_monitor); + gtk_container_add (GTK_CONTAINER (toolitem), window->net_monitor); + g_signal_connect_swapped (window->settings, "notify::refresh-rate", G_CALLBACK (monitor_update_step_size), window); } @@ -489,6 +497,7 @@ monitor_update_step_size (XtmProcessWindow *window) g_object_get (window->settings, "refresh-rate", &refresh_rate, NULL); g_object_set (window->cpu_monitor, "step-size", refresh_rate / 1000.0, NULL); g_object_set (window->mem_monitor, "step-size", refresh_rate / 1000.0, NULL); + g_object_set (window->net_monitor, "step-size", refresh_rate / 1000.0, NULL); } /** @@ -511,6 +520,12 @@ xtm_process_window_show (GtkWidget *widget) GTK_WIDGET_CLASS (xtm_process_window_parent_class)->show (widget); } +GtkWindow * +xtm_process_window_get (XtmProcessWindow *window) +{ + return GTK_WINDOW (window->window); +} + static void xtm_process_window_hide (GtkWidget *widget) { @@ -533,24 +548,40 @@ xtm_process_window_get_model (XtmProcessWindow *window) } void -xtm_process_window_set_system_info (XtmProcessWindow *window, guint num_processes, gfloat cpu, gfloat memory, gchar *memory_str, gfloat swap, gchar *swap_str) +xtm_process_window_set_system_info (XtmProcessWindow *window, guint num_processes, gfloat cpu, gfloat memory, gchar *memory_str, gfloat swap, gchar *swap_str, guint64 tcp_rx, guint64 tcp_tx, guint64 tcp_error) { - gchar text[100]; + gchar text[200]; gchar value[4]; g_return_if_fail (XTM_IS_PROCESS_WINDOW (window)); g_return_if_fail (GTK_IS_BOX (window->statusbar)); - g_object_set (window->statusbar, "num-processes", num_processes, "cpu", cpu, "memory", memory_str, "swap", swap_str, NULL); + g_object_set ( + window->statusbar, + "num-processes", num_processes, + "cpu", cpu, + "memory", memory_str, + "swap", swap_str, + "network-rx", tcp_rx * 1e-5, + "network-tx", tcp_tx * 1e-5, + "network-error", tcp_error, + NULL); - xtm_process_monitor_add_peak (XTM_PROCESS_MONITOR (window->cpu_monitor), cpu / 100.0f, -1.0); + xtm_process_monitor_add_peak (XTM_PROCESS_MONITOR (window->cpu_monitor), cpu / 100.0f, 0); g_snprintf (value, sizeof (value), "%.0f", cpu); g_snprintf (text, sizeof (text), _("CPU: %s%%"), value); gtk_widget_set_tooltip_text (window->cpu_monitor, text); - xtm_process_monitor_add_peak (XTM_PROCESS_MONITOR (window->mem_monitor), memory / 100.0f, swap / 100.0f); + xtm_process_monitor_add_peak (XTM_PROCESS_MONITOR (window->mem_monitor), memory / 100.0f, 0); + xtm_process_monitor_add_peak (XTM_PROCESS_MONITOR (window->mem_monitor), swap / 100.0f, 1); g_snprintf (text, sizeof (text), _("Memory: %s"), memory_str); gtk_widget_set_tooltip_text (window->mem_monitor, text); + + xtm_process_monitor_add_peak (XTM_PROCESS_MONITOR (window->net_monitor), tcp_rx * 1e-5, 0); + xtm_process_monitor_add_peak (XTM_PROCESS_MONITOR (window->net_monitor), tcp_tx * 1e-5, 1); + xtm_process_monitor_add_peak (XTM_PROCESS_MONITOR (window->net_monitor), tcp_error, 2); + g_snprintf (text, sizeof (text), _("Network rx=%0.2f MB/s\nNetwork tx=%0.2f MB/s \nNetwork error=%lu error/s"), tcp_rx * 1e-5, tcp_tx * 1e-5, tcp_error); + gtk_widget_set_tooltip_text (window->net_monitor, text); } void diff --git a/src/process-window.h b/src/process-window.h index 3f552a3..5d30681 100644 --- a/src/process-window.h +++ b/src/process-window.h @@ -34,10 +34,11 @@ typedef struct _XtmProcessWindow XtmProcessWindow; GType xtm_process_window_get_type (void); GtkWidget *xtm_process_window_new (void); +GtkWindow *xtm_process_window_get (XtmProcessWindow *window); void xtm_process_window_settings_init (XtmProcessWindow *window, XfconfChannel *channel); void xtm_process_window_show (GtkWidget *widget); GtkTreeModel *xtm_process_window_get_model (XtmProcessWindow *window); -void xtm_process_window_set_system_info (XtmProcessWindow *window, guint num_processes, gfloat cpu, gfloat memory, gchar *memory_str, gfloat swap, gchar *swap_str); +void xtm_process_window_set_system_info (XtmProcessWindow *window, guint num_processes, gfloat cpu, gfloat memory, gchar *memory_str, gfloat swap, gchar *swap_str, guint64 tcp_rx, guint64 tcp_tx, guint64 tcp_error); void xtm_process_window_show_swap_usage (XtmProcessWindow *window, gboolean show_swap_usage); #endif /* !PROCESS_WINDOW_H */ diff --git a/src/process-window.ui b/src/process-window.ui index 896d832..88d4adb 100644 --- a/src/process-window.ui +++ b/src/process-window.ui @@ -198,6 +198,27 @@ 1 + + + True + False + True + False + 0 + none + + + + + + + + + True + True + 1 + + True diff --git a/src/settings-dialog.c b/src/settings-dialog.c index 18f3ea7..6f7e5ea 100644 --- a/src/settings-dialog.c +++ b/src/settings-dialog.c @@ -11,6 +11,9 @@ #include "config.h" #endif +#include + +#include "network-analyzer.h" #include "settings-dialog.h" #include "settings-dialog_ui.h" #include "settings.h" @@ -19,8 +22,6 @@ #include #endif -#include - static void show_about_dialog (GtkWidget *widget, gpointer user_data); static GtkWidget *xtm_settings_dialog_new (GtkBuilder *builder, GtkWidget *parent_window); @@ -117,18 +118,22 @@ show_about_dialog (GtkWidget *widget, gpointer user_data) "(c) 2005-2008 Johannes Zellner", "", "FreeBSD", + " \342\200\242 Jehan-Antoine Vayssade", " \342\200\242 Rozhuk Ivan", " \342\200\242 Mike Massonnet", " \342\200\242 Oliver Lehmann", "", "OpenBSD", + " \342\200\242 Jehan-Antoine Vayssade", " \342\200\242 Landry Breuil", "", "Linux", + " \342\200\242 Jehan-Antoine Vayssade", " \342\200\242 Johannes Zellner", " \342\200\242 Mike Massonnet", "", "OpenSolaris", + " \342\200\242 Jehan-Antoine Vayssade", " \342\200\242 Mike Massonnet", " \342\200\242 Peter Tribble", NULL @@ -174,6 +179,7 @@ xtm_settings_dialog_new (GtkBuilder *builder, GtkWidget *parent_window) GtkWidget *dialog; GtkWidget *button; XtmSettings *settings; + XtmNetworkAnalyzer *network; settings = xtm_settings_get_default (); dialog = GTK_WIDGET (gtk_builder_get_object (builder, "settings-dialog")); @@ -214,8 +220,20 @@ xtm_settings_dialog_new (GtkBuilder *builder, GtkWidget *parent_window) builder_bind_toggle_button (builder, "uid", settings, "column-uid"); builder_bind_toggle_button (builder, "cpu", settings, "column-cpu"); builder_bind_toggle_button (builder, "group-cpu", settings, "column-group-cpu"); + builder_bind_toggle_button (builder, "packet-in", settings, "column-packet-in"); + builder_bind_toggle_button (builder, "packet-out", settings, "column-packet-out"); + builder_bind_toggle_button (builder, "active-socket", settings, "column-active-socket"); builder_bind_toggle_button (builder, "priority", settings, "column-priority"); + // insufficient permission + network = xtm_network_analyzer_get_default (); + + if (network == NULL) + { + gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (builder, "packet-in"))); + gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (builder, "packet-out"))); + gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (builder, "active-socket"))); + } button = GTK_WIDGET (gtk_builder_get_object (builder, "button-about")); g_signal_connect (button, "clicked", G_CALLBACK (show_about_dialog), dialog); diff --git a/src/settings-dialog.ui b/src/settings-dialog.ui index cd5879f..9fa785c 100644 --- a/src/settings-dialog.ui +++ b/src/settings-dialog.ui @@ -508,6 +508,48 @@ 10 + + + Packet in + True + True + False + True + + + False + True + 10 + + + + + Packet out + True + True + False + True + + + False + True + 10 + + + + + Active Socket + True + True + False + True + + + False + True + 10 + + 1 diff --git a/src/settings.c b/src/settings.c index 2693e22..f226356 100644 --- a/src/settings.c +++ b/src/settings.c @@ -47,6 +47,9 @@ enum PROP_COLUMN_CPU, PROP_COLUMN_GROUP_CPU, PROP_COLUMN_PRIORITY, + PROP_COLUMN_PACKET_IN, + PROP_COLUMN_PACKET_OUT, + PROP_COLUMN_ACTIVE_SOCKET, PROP_SORT_COLUMN_ID, PROP_SORT_TYPE, PROP_HANDLE_POSITION, @@ -116,6 +119,14 @@ xtm_settings_class_init (XtmSettingsClass *klass) g_param_spec_boolean ("column-cpu", "ColumnCPU", "Show column CPU", TRUE, G_PARAM_READWRITE)); g_object_class_install_property (class, PROP_COLUMN_GROUP_CPU, g_param_spec_boolean ("column-group-cpu", "ColumnGroupCPU", "Show column Group CPU", TRUE, G_PARAM_READWRITE)); + + g_object_class_install_property (class, PROP_COLUMN_PACKET_IN, + g_param_spec_boolean ("column-packet-in", "ColumnPacketIn", "Show column packet in", TRUE, G_PARAM_READWRITE)); + g_object_class_install_property (class, PROP_COLUMN_PACKET_OUT, + g_param_spec_boolean ("column-packet-out", "ColumnPacketOut", "Show column packet out", TRUE, G_PARAM_READWRITE)); + g_object_class_install_property (class, PROP_COLUMN_PACKET_OUT, + g_param_spec_boolean ("column-active-socket", "ColumnActiveSocket", "Show number of used socket descriptor", TRUE, G_PARAM_READWRITE)); + g_object_class_install_property (class, PROP_COLUMN_PRIORITY, g_param_spec_boolean ("column-priority", "ColumnPriority", "Show column priority", FALSE, G_PARAM_READWRITE)); g_object_class_install_property (class, PROP_SORT_COLUMN_ID, @@ -219,6 +230,15 @@ xtm_settings_bind_xfconf (XtmSettings *settings, XfconfChannel *channel) G_OBJECT (settings), "column-cpu"); xfconf_g_property_bind (channel, SETTING_COLUMN_GROUP_CPU, G_TYPE_BOOLEAN, G_OBJECT (settings), "column-group-cpu"); + + + xfconf_g_property_bind (channel, SETTING_COLUMN_PACKET_IN, G_TYPE_BOOLEAN, + G_OBJECT (settings), "column-packet-in"); + xfconf_g_property_bind (channel, SETTING_COLUMN_PACKET_OUT, G_TYPE_BOOLEAN, + G_OBJECT (settings), "column-packet-out"); + xfconf_g_property_bind (channel, SETTING_COLUMN_ACTIVE_SOCKET, G_TYPE_BOOLEAN, + G_OBJECT (settings), "column-active-socket"); + xfconf_g_property_bind (channel, SETTING_COLUMN_PRIORITY, G_TYPE_BOOLEAN, G_OBJECT (settings), "column-priority"); diff --git a/src/settings.h b/src/settings.h index 3ed0eea..630e307 100644 --- a/src/settings.h +++ b/src/settings.h @@ -48,6 +48,9 @@ #define SETTING_COLUMN_UID "/columns/column-uid" #define SETTING_COLUMN_CPU "/columns/column-cpu" #define SETTING_COLUMN_GROUP_CPU "/columns/column-group-cpu" +#define SETTING_COLUMN_PACKET_IN "/columns/column-packet-in" +#define SETTING_COLUMN_PACKET_OUT "/columns/column-packet-out" +#define SETTING_COLUMN_ACTIVE_SOCKET "/columns/column-active-socket" #define SETTING_COLUMN_PRIORITY "/columns/column-priority" #define SETTING_COLUMN_SORT_ID "/columns/sort-id" #define SETTING_COLUMN_SORT_TYPE "/columns/sort-type" diff --git a/src/task-manager-bsd.c b/src/task-manager-bsd.c index b0f03b0..cbe6377 100644 --- a/src/task-manager-bsd.c +++ b/src/task-manager-bsd.c @@ -20,6 +20,8 @@ #include "config.h" #endif +#include "inode-to-sock.h" +#include "network-analyzer.h" #include "task-manager.h" #include @@ -40,14 +42,413 @@ /* for struct vmtotal */ #include +// clang-format off +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +// clang-format on + /* errno */ #include + +#ifdef __OpenBSD__ extern int errno; +#else +// struct file in NetBSD +// ugly ! where is defined this ? +// plateform dependent +typedef long register_t; +typedef unsigned long paddr_t; +#include +#include +#include +#include +#include +#include +#include +#endif + +#define _KERNEL +#include +#include +#undef _KERNEL char *state_abbrev[] = { "", "start", "run", "sleep", "stop", "zomb", "dead", "onproc" }; +static XtmInodeToSock *inode_to_sock = NULL; +static XtmNetworkAnalyzer *analyzer = NULL; + +gboolean get_if_count (int *data); +gboolean get_network_usage_if (int interface, guint64 *tcp_rx, guint64 *tcp_tx, guint64 *tcp_error); +struct kinfo_file *get_process_fds (int *nfiles, int kern, int arg); + + +#ifdef __OpenBSD__ +void list_process_fds (Task *task, struct kinfo_proc *kp); +#else +void list_process_fds (Task *task, struct kinfo_proc2 *kp); +#endif + +#ifdef HAVE_LIBPCAP +void +packet_callback (u_char *args, const struct pcap_pkthdr *header, const u_char *packet) +{ + // Extract source and destination IP addresses and ports from the packet + struct ether_header eth_header; + struct ip ip_header; + struct tcphdr tcp_header; + XtmNetworkAnalyzer *iface; + long int src_port, dst_port; + char local_mac[18]; + char src_mac[18]; + char dst_mac[18]; + + memcpy (ð_header, packet, sizeof (struct ether_header)); + memcpy (&ip_header, packet + sizeof (struct ether_header), sizeof (struct ip)); + memcpy (&tcp_header, packet + sizeof (struct ether_header) + sizeof (struct ip), sizeof (struct ip)); + + // cast -> increases required alignment from 1 to 2 [-Wcast-align] + // const struct ether_header *eth_header = (const struct ether_header *)packet; + // struct ip *ip_header = (struct ip *)(packet + sizeof (struct ether_header)); + // struct tcphdr *tcp_header = (struct tcphdr *)(packet + sizeof (struct ether_header) + sizeof (struct ip)); + // printf("%d, %d \n", eth_heade.ether_type , ip_header.ip_p); + + // Dropped non-ip packet + if (eth_header.ether_type != 8 || ip_header.ip_p != 6) + return; + + iface = (XtmNetworkAnalyzer *)args; + + src_port = ntohs (tcp_header.th_sport); + dst_port = ntohs (tcp_header.th_dport); + + // directly use strcmp on analyzer->mac, eth_header->ether_shost doesnt work + + snprintf (local_mac, sizeof (local_mac), + "%02X:%02X:%02X:%02X:%02X:%02X", + iface->mac[0], iface->mac[1], + iface->mac[2], iface->mac[3], + iface->mac[4], iface->mac[5]); + + snprintf (src_mac, sizeof (local_mac), + "%02X:%02X:%02X:%02X:%02X:%02X", + eth_header.ether_shost[0], eth_header.ether_shost[1], + eth_header.ether_shost[2], eth_header.ether_shost[3], + eth_header.ether_shost[4], eth_header.ether_shost[5]); + + snprintf (dst_mac, sizeof (local_mac), + "%02X:%02X:%02X:%02X:%02X:%02X", + eth_header.ether_dhost[0], eth_header.ether_dhost[1], + eth_header.ether_dhost[2], eth_header.ether_dhost[3], + eth_header.ether_dhost[4], eth_header.ether_dhost[5]); + + // Debug + // pthread_mutex_lock(&iface->lock); + + if (strcmp (local_mac, src_mac) == 0) + increament_packet_count (local_mac, "in ", iface->packetin, src_port); + + if (strcmp (local_mac, dst_mac) == 0) + increament_packet_count (local_mac, "out", iface->packetout, dst_port); + + // pthread_mutex_unlock(&iface->lock); +} +#endif + +gboolean +get_network_usage (guint64 *tcp_rx, guint64 *tcp_tx, guint64 *tcp_error) +{ + // can also be get trough tcpstat struct + // int mib[] = { CTL_NET, PF_INET, IPPROTO_TCP, TCPCTL_STATS } + // sysctl(mib, sizeof(mib) / sizeof(mib[0]), %tcpstat, &len, NULL, 0) + // repeat for IPPROTO_UDP or use IPPROTO_ETHERIP + struct ifaddrs *ifaddr, *ifa; + + *tcp_error = 0; + *tcp_rx = 0; + *tcp_tx = 0; + + + if (getifaddrs (&ifaddr) == -1) + return -1; + + for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) + { + if (ifa->ifa_addr == NULL) + continue; + + // Check if the interface is a network interface (AF_PACKET for Linux) + if (ifa->ifa_addr->sa_family == AF_LINK) + { + struct if_data *ifdata = (struct if_data *)ifa->ifa_data; + // Check if the interface has a hardware address (MAC address) + if (ifdata != NULL) + { + *tcp_error += ifdata->ifi_oerrors + ifdata->ifi_ierrors; + ; + *tcp_rx += ifdata->ifi_ibytes; + *tcp_tx += ifdata->ifi_obytes; + } + // printf("%d, %d \n", *tcp_rx, *tcp_tx); + } + } + + freeifaddrs (ifaddr); + + return 0; +} + +int +get_mac_address (const char *device, uint8_t mac[6]) +{ + struct ifaddrs *ifaddr, *ifa; + + if (getifaddrs (&ifaddr) == -1) + return -1; + + for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) + { + if (ifa->ifa_addr == NULL) + continue; + + // Check if the interface is a network interface (AF_PACKET for Linux) + if (ifa->ifa_addr->sa_family == AF_LINK && strcmp (device, ifa->ifa_name) == 0) + { + // Check if the interface has a hardware address (MAC address) + if (ifa->ifa_data != NULL) + { + // warning: cast from 'struct sockaddr *' to 'struct sockaddr_dl *' increases required alignment from 1 to 2 + // struct sockaddr_dl *sdl = (struct sockaddr_dl *)ifa->ifa_addr; + struct sockaddr_dl sdl; + memcpy (&sdl, ifa->ifa_addr, sizeof (struct sockaddr_dl)); + memcpy (mac, sdl.sdl_data + sdl.sdl_nlen, sizeof (uint8_t) * 6); + freeifaddrs (ifaddr); + return 0; + } + } + } + + freeifaddrs (ifaddr); + return -1; +} + +struct kinfo_file * +get_process_fds (int *nfiles, int kern, int arg) +{ + // inspired by NetBSD pstat.c + int mib[6]; + size_t len; + struct kinfo_file *kf; + + // Set the MIB (Management Information Base) for the sysctl call + mib[0] = CTL_KERN; + +#ifdef __OpenBSD__ + mib[1] = KERN_FILE; +#else + mib[1] = KERN_FILE2; +#endif + + mib[2] = kern; + mib[3] = arg; + mib[4] = sizeof (struct kinfo_file); + mib[5] = 0; + + // Get the number of open files + if (sysctl (mib, 6, NULL, &len, NULL, 0) < 0) + { + printf ("sysctl Get the number of opened fd"); + return NULL; + } + + *nfiles = len / sizeof (struct kinfo_file); + + // Allocate memory for the kinfo_file structures + kf = malloc (len); + if (kf == NULL) + { + printf ("malloc"); + return NULL; + } + + mib[5] = len / sizeof (struct kinfo_file); + + // Get the list of open files + if (sysctl (mib, 6, kf, &len, NULL, 0) < 0) + { + printf ("sysctl, enable to get kinfo_file *"); + free (kf); + return NULL; + } + + return kf; +} + +void +xtm_refresh_inode_to_sock (XtmInodeToSock *its) +{ +#ifdef __OpenBSD__ + // unneeded +#else + // unneeded +#endif +} + +void +#ifdef __OpenBSD__ +list_process_fds (Task *task, struct kinfo_proc *kp) +#else +list_process_fds (Task *task, struct kinfo_proc2 *kp) +#endif +{ +#ifdef __OpenBSD__ + // inspired by openbsd netstat/inet.c + // inspired by openbsd fstat/fstat.c + + long int nfiles, port; + XtmNetworkAnalyzer *current; + struct kinfo_file *kf = get_process_fds (&nfiles, KERN_FILE_BYPID, task->pid); + + if (kf == NULL) + return; + + // Parse the kinfo_file structures + for (int i = 0; i < nfiles; i++) + { + if (kf[i].f_type == DTYPE_SOCKET) + { + // interesting properties + // task->packet_in += kf[i].f_rxfer; + // task->packet_in += kf[i].f_rbytes; + // task->packet_out += kf[i].f_rwfer; + // task->packet_out += kf[i].f_wbytes;netstat + + current = analyzer; + while (current != NULL) + { + port = ntohs (kf[i].inp_lport); + task->packet_in += (guint64)g_hash_table_lookup (current->packetin, &port); + task->packet_out += (guint64)g_hash_table_lookup (current->packetout, &port); + current = current->next; + } + + task->active_socket += 1; + } + } + + free (kf); +#else + // inspired by netbsd fstat/fstat.c + XtmNetworkAnalyzer *current; + char *memf, *nlistf; + char buf[_POSIX2_LINE_MAX]; + kvm_t *kd; + struct filedesc filed; + struct fdtab dt; + size_t len; + + fdfile_t **ofiles; + fdfile_t *fp; + struct socket *sock; + struct socket so; + struct file file; + fdfile_t fdfile; + struct protosw proto; + struct domain dom; + struct in4pcb in4pcb; + struct in6pcb in6pcb; + struct inpcb *inp; + long int port; + + nlistf = memf = NULL; + kd = kvm_openfiles (nlistf, memf, NULL, O_RDONLY, buf); + + kvm_read (kd, kp->p_fd, &filed, sizeof (filed)); + kvm_read (kd, (u_long)filed.fd_dt, &dt, sizeof (dt)); + + len = (filed.fd_lastfile + 1) * sizeof (fdfile_t *); + ofiles = malloc (len); + kvm_read (kd, (u_long)&filed.fd_dt->dt_ff, ofiles, (filed.fd_lastfile + 1) * (sizeof (fdfile_t *))); + + for (int i = 0; i <= filed.fd_lastfile; i++) + { + if (ofiles[i] == NULL) + continue; + + fp = ofiles[i]; + + kvm_read (kd, (u_long)fp, &fdfile, sizeof (fdfile)); + + if (fdfile.ff_file == NULL) + continue; + + kvm_read (kd, (u_long)fdfile.ff_file, &file, sizeof (file)); + + if (file.f_type != DTYPE_SOCKET) + continue; + + sock = (struct socket *)file.f_data; + kvm_read (kd, (u_long)sock, &so, sizeof (struct socket)); + kvm_read (kd, (u_long)so.so_proto, &proto, sizeof (struct protosw)); + kvm_read (kd, (u_long)proto.pr_domain, &dom, sizeof (struct domain)); + + port = 0; + + if (dom.dom_family == AF_INET) + { + if (proto.pr_protocol == IPPROTO_TCP) + { + if (so.so_pcb == NULL) + continue; + + kvm_read (kd, (u_long)so.so_pcb, (char *)&in4pcb, sizeof (in4pcb)); + inp = (struct inpcb *)&in4pcb; + port = ntohs (inp->inp_lport); + } + } + + if (dom.dom_family == AF_INET6) + { + if (proto.pr_protocol == IPPROTO_TCP) + { + if (so.so_pcb == NULL) + continue; + + kvm_read (kd, (u_long)so.so_pcb, (char *)&in6pcb, sizeof (in6pcb)); + inp = (struct inpcb *)&in6pcb; + port = ntohs (inp->inp_lport); + } + } + + if (port == 0) + continue; + + task->active_socket += 1; + + current = analyzer; + if (current != NULL) + { + task->packet_in += (guint64)g_hash_table_lookup (current->packetin, &port); + task->packet_out += (guint64)g_hash_table_lookup (current->packetout, &port); + current = current->next; + } + } + + kvm_close (kd); + free (ofiles); + +#endif +} + gboolean get_task_list (GArray *task_list) { @@ -57,11 +458,22 @@ get_task_list (GArray *task_list) struct kinfo_proc *kp; #else struct kinfo_proc2 *kp; + char errbuf[_POSIX2_LINE_MAX]; + kvm_t *kdp; #endif Task t; char **args; - gchar *buf; int nproc, i; + gchar *buf; + +#ifdef __OpenBSD__ +#else + kdp = kvm_open (NULL, NULL, NULL, KVM_NO_FILES, errbuf); +#endif + + analyzer = xtm_network_analyzer_get_default (); + inode_to_sock = xtm_inode_to_sock_get_default (); + xtm_refresh_inode_to_sock (inode_to_sock); mib[0] = CTL_KERN; #ifdef __OpenBSD__ @@ -116,6 +528,8 @@ get_task_list (GArray *task_list) t.rss = p.p_vm_rssize * getpagesize (); g_snprintf (t.state, sizeof t.state, "%s", state_abbrev[p.p_stat]); g_strlcpy (t.name, p.p_comm, strlen (p.p_comm) + 1); + +#ifdef __OpenBSD__ /* shamelessly stolen from top/machine.c */ if (!P_ZOMBIE (&p)) { @@ -132,8 +546,10 @@ get_task_list (GArray *task_list) mib[1] = KERN_PROC_ARGS; mib[2] = t.pid; mib[3] = KERN_PROC_ARGV; + if (sysctl (mib, 4, args, &size, NULL, 0) == 0) break; + if (errno != ENOMEM) { /* ESRCH: process disappeared */ /* printf ("process with pid %d disappeared, errno=%d\n", t.pid, errno); */ @@ -142,15 +558,40 @@ get_task_list (GArray *task_list) break; } } + buf = g_strjoinv (" ", args); g_assert (g_utf8_validate (buf, -1, NULL)); g_strlcpy (t.cmdline, buf, sizeof t.cmdline); g_free (buf); free (args); } +#else + // assuming NetBSD 10.0 + // https://github.com/NetBSD/src/blob/trunk/lib/libkvm/kvm_proc.c#L1116 + // https://github.com/NetBSD/pkgsrc/blob/trunk/sysutils/xfce4-taskmanager/files/task-manager-netbsd.c + // fixing code used in __OpenBSD__ crash at g_strjoinv due to strlen + + if (!(kp[i].p_stat == SDEAD)) + { + args = kvm_getargv2 (kdp, &kp[i], BUFSIZ); + if (args != NULL) + { + buf = g_strjoinv (" ", args); + g_strlcpy (t.cmdline, buf, sizeof (t.cmdline)); + g_free (buf); + // Memory seem stable without that + // otherwise i get a segfault + // free (args); + } + } +#endif t.cpu_user = (100.0f * ((gfloat)p.p_pctcpu / FSCALE)); t.cpu_system = 0.0f; /* TODO ? */ + + if (analyzer != NULL) + list_process_fds (&t, &p); + g_array_append_val (task_list, t); } free (kp); @@ -201,7 +642,15 @@ get_cpu_usage (gushort *cpu_count, gfloat *cpu_user, gfloat *cpu_system) static gulong cur_user = 0, cur_system = 0, cur_total = 0; static gulong old_user = 0, old_system = 0, old_total = 0; +#ifdef KERN_CPTIME int mib[] = { CTL_KERN, KERN_CPTIME }; +#elif defined KERN_CPTIME2 + int mib[] = { CTL_KERN, KERN_CPTIME2 }; +#else + // NetBSD 10.0 + int mib[] = { CTL_KERN, KERN_CP_TIME }; +#endif + glong cp_time[CPUSTATES]; gsize size = sizeof (cp_time); if (sysctl (mib, 2, &cp_time, &size, NULL, 0) < 0) diff --git a/src/task-manager-freebsd.c b/src/task-manager-freebsd.c index 9983f1b..0fd2e58 100644 --- a/src/task-manager-freebsd.c +++ b/src/task-manager-freebsd.c @@ -15,21 +15,40 @@ #include "task-manager.h" +// clang-format off #include #include #include #include + #include #include +#include #include #include #include -#include #if defined(__FreeBSD_version) && __FreeBSD_version >= 900044 #include #endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +// clang-format on + +#include + +#include "inode-to-sock.h" +#include "network-analyzer.h" +#include "task-manager.h" + static const gchar ki_stat2state[] = { ' ', /* - */ 'R', /* SIDL */ @@ -41,6 +60,152 @@ static const gchar ki_stat2state[] = { 'L' /* SLOCK */ }; +static XtmInodeToSock *inode_to_sock = NULL; +static XtmNetworkAnalyzer *analyzer = NULL; + +void list_process_fds (Task *task); +gboolean get_if_count (int *data); +gboolean get_network_usage_if (int interface, guint64 *tcp_rx, guint64 *tcp_tx, guint64 *tcp_error); + +#ifdef HAVE_LIBPCAP +void +packet_callback (u_char *args, const struct pcap_pkthdr *header, const u_char *packet) +{ + // Extract source and destination IP addresses and ports from the packet + struct ether_header *eth_header = (struct ether_header *)packet; + struct ip *ip_header = (struct ip *)(packet + sizeof (struct ether_header)); + struct tcphdr *tcp_header = (struct tcphdr *)(packet + sizeof (struct ether_header) + sizeof (struct ip)); + XtmNetworkAnalyzer *iface = (XtmNetworkAnalyzer *)args; + + // Dropped non-ip packet + if (eth_header->ether_type != 8 || ip_header->ip_p != 6) + return; + + long int src_port = ntohs (tcp_header->th_sport); + long int dst_port = ntohs (tcp_header->th_dport); + + // directly use strcmp on analyzer->mac, eth_header->ether_shost doesnt work + + char local_mac[18]; + char src_mac[18]; + char dst_mac[18]; + + sprintf (local_mac, + "%02X:%02X:%02X:%02X:%02X:%02X", + iface->mac[0], iface->mac[1], + iface->mac[2], iface->mac[3], + iface->mac[4], iface->mac[5]); + + sprintf (src_mac, + "%02X:%02X:%02X:%02X:%02X:%02X", + eth_header->ether_shost[0], eth_header->ether_shost[1], + eth_header->ether_shost[2], eth_header->ether_shost[3], + eth_header->ether_shost[4], eth_header->ether_shost[5]); + + sprintf (dst_mac, + "%02X:%02X:%02X:%02X:%02X:%02X", + eth_header->ether_dhost[0], eth_header->ether_dhost[1], + eth_header->ether_dhost[2], eth_header->ether_dhost[3], + eth_header->ether_dhost[4], eth_header->ether_dhost[5]); + + // Debug + // pthread_mutex_lock(&iface->lock); + + if (strcmp (local_mac, src_mac) == 0) + increament_packet_count (local_mac, "in ", iface->packetin, src_port); + + if (strcmp (local_mac, dst_mac) == 0) + increament_packet_count (local_mac, "out", iface->packetout, dst_port); + + // pthread_mutex_unlock(&iface->lock); +} +#endif + + +gboolean +get_if_count (int *data) +{ + size_t len = sizeof (*data); + + static int32_t name[] = { CTL_NET, PF_LINK, NETLINK_GENERIC, IFMIB_SYSTEM, IFMIB_IFCOUNT }; + name[0] = CTL_NET; + name[1] = PF_LINK; + name[2] = NETLINK_GENERIC; + name[3] = IFMIB_SYSTEM; + name[4] = IFMIB_IFCOUNT; + + return sysctl (name, 5, data, &len, 0, 0) < 0; +} + +gboolean +get_network_usage_if (int interface, guint64 *tcp_rx, guint64 *tcp_tx, guint64 *tcp_error) +{ + struct ifmibdata data; + size_t len = sizeof (data); + + static int32_t name[] = { CTL_NET, PF_LINK, NETLINK_GENERIC, IFMIB_IFDATA, 0, IFDATA_GENERAL }; + name[4] = interface; + + if (sysctl (name, 6, &data, &len, 0, 0) < 0) + return 1; + + //*tcp_error = data.ifmd_data.ifi_oerrors + data.ifmd_data.ifi_ierrors; + *tcp_rx += data.ifmd_data.ifi_ibytes; + *tcp_tx += data.ifmd_data.ifi_obytes; + + return 0; +} + +gboolean +get_network_usage (guint64 *tcp_rx, guint64 *tcp_tx, guint64 *tcp_error) +{ + int ifcount = 0; + + if (get_if_count (&ifcount)) + return 1; + + *tcp_error = 0; + *tcp_rx = 0; + *tcp_tx = 0; + + for (int i = 0; i < ifcount; ++i) + get_network_usage_if (i, tcp_rx, tcp_tx, tcp_error); + + return 0; +} + +int +get_mac_address (const char *device, uint8_t mac[6]) +{ + struct ifaddrs *ifaddr, *ifa; + + if (getifaddrs (&ifaddr) == -1) + return -1; + + for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) + { + if (ifa->ifa_addr == NULL) + continue; + + int family = ifa->ifa_addr->sa_family; + + // Check if the interface is a network interface (AF_PACKET for Linux) + if (family == AF_LINK && strcmp (device, ifa->ifa_name) == 0) + { + // Check if the interface has a hardware address (MAC address) + if (ifa->ifa_data != NULL) + { + struct sockaddr_dl *sdl = (struct sockaddr_dl *)ifa->ifa_addr; + memcpy (mac, sdl->sdl_data + sdl->sdl_nlen, sizeof (uint8_t) * 6); + freeifaddrs (ifaddr); + return 0; + } + } + } + + freeifaddrs (ifaddr); + return -1; +} static guint64 get_mem_by_bytes (const gchar *name) @@ -132,6 +297,114 @@ get_cpu_usage (gushort *cpu_count, gfloat *cpu_user, gfloat *cpu_system) return TRUE; } +// TODO check the OpenBSD version here +// (struct sockaddr_in*)kinfo_file.kf_sa_local + +void +xtm_refresh_inode_to_sock (XtmInodeToSock *its) +{ + FILE *fp; + char line[2048]; + char *token; + char *delim = " "; + + if (analyzer == NULL) + return; + + // Execute the sockstat command and get the output + fp = popen ("sockstat -L -P tcp,udp", "r"); + if (fp == NULL) + return; + + g_hash_table_remove_all (its->hash); + g_hash_table_remove_all (its->pid); + + // Skip the header line + fgets (line, 2048, fp); + + // Parse the output line by line + while (fgets (line, 2048, fp) != NULL) + { + // Remove the newline character + line[strcspn (line, "\n")] = '\0'; + + // Split the line into tokens + token = strtok (line, delim); + + // Parse the columns + char user[50]; + char command[50]; + char pid[10]; + char fd[10]; + char proto[10]; + char local_address[50]; + char foreign_address[50]; + + strcpy (user, token); + token = strtok (NULL, delim); + strcpy (command, token); + token = strtok (NULL, delim); + strcpy (pid, token); + token = strtok (NULL, delim); + strcpy (fd, token); + token = strtok (NULL, delim); + strcpy (proto, token); + token = strtok (NULL, delim); + strcpy (local_address, token); + token = strtok (NULL, delim); + strcpy (foreign_address, token); + + long int local_port, assos; + gint64 *inode1 = g_new0 (gint64, 1); + gint64 *inode2 = g_new0 (gint64, 1); + + *inode1 = atoi (fd); + *inode2 = atoi (fd); + assos = atoi (pid); + + sscanf (local_address, "%*[^:]:%d", &local_port); + // (intptr_t) -> cast to pointer from integer of different size [-Wint-to-pointer-cast] + g_hash_table_replace (its->hash, inode1, (gpointer)(intptr_t)local_port); + g_hash_table_replace (its->pid, inode2, (gpointer)(intptr_t)assos); + + // Print the parsed values + // printf("%d -> %d\n", *inode, local_port); + } + + // Close the pipe + pclose (fp); +} + +void +list_process_fds (Task *task) +{ + XtmNetworkAnalyzer *current; + GHashTableIter iter; + gpointer key, value; + gint64 key_int, value_int; + long int port; + + g_hash_table_iter_init (&iter, inode_to_sock->pid); + while (g_hash_table_iter_next (&iter, &key, &value)) + { + key_int = *(gint64 *)(key); + value_int = GPOINTER_TO_INT (value); + if (task->pid == value_int) + { + port = (long int)g_hash_table_lookup (inode_to_sock->hash, &key_int); + task->active_socket += 1; + + current = analyzer; + while (current) + { + task->packet_in += (guint64)g_hash_table_lookup (current->packetin, &port); + task->packet_out += (guint64)g_hash_table_lookup (current->packetout, &port); + current = current->next; + } + } + } +} + static gboolean get_task_details (struct kinfo_proc *kp, Task *task) { @@ -233,17 +506,25 @@ get_task_details (struct kinfo_proc *kp, Task *task) if (kp->ki_flag & P_JAILED) task->state[i++] = 'J'; + if (analyzer != NULL) + list_process_fds (task); + return TRUE; } gboolean get_task_list (GArray *task_list) { + analyzer = xtm_network_analyzer_get_default (); + kvm_t *kd; struct kinfo_proc *kp; int cnt = 0, i; Task task; + inode_to_sock = xtm_inode_to_sock_get_default (); + xtm_refresh_inode_to_sock (inode_to_sock); + if ((kd = kvm_openfiles (_PATH_DEVNULL, _PATH_DEVNULL, NULL, O_RDONLY, NULL)) == NULL) return FALSE; diff --git a/src/task-manager-linux.c b/src/task-manager-linux.c index aeef8fc..5ef4427 100644 --- a/src/task-manager-linux.c +++ b/src/task-manager-linux.c @@ -1,4 +1,5 @@ /* + * Copyright (c) 2024 Jehan-Antoine Vayssade, * Copyright (c) 2008-2010 Mike Massonnet * Copyright (c) 2006 Johannes Zellner * @@ -12,11 +13,368 @@ #include "config.h" #endif +#include "inode-to-sock.h" +#include "network-analyzer.h" #include "task-manager.h" +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +static XtmNetworkAnalyzer *analyzer = NULL; +static XtmInodeToSock *inode_to_sock = NULL; static gushort _cpu_count = 0; static gulong jiffies_total_delta = 0; +void list_process_fds (Task *task); +void xtm_refresh_inode_to_sock_protocol (XtmInodeToSock *its, char *filename); + +void +xtm_refresh_inode_to_sock_protocol (XtmInodeToSock *its, char *filename) +{ + char buffer[8192]; + char rem_addr[128], local_addr[128]; + int local_port, rem_port, inode, count; + gint64 *key; + + FILE *procinfo = fopen (filename, "r"); + + if (procinfo == 0) + { + perror (filename); + return; + } + + // skip header + if (fgets (buffer, sizeof (buffer), procinfo) == 0) + { + printf ("%s no header\n", filename); + fclose (procinfo); + return; + } + + while (fgets (buffer, sizeof (buffer), procinfo)) + { + count = sscanf ( + buffer, + "%*d: %64[0-9A-Fa-f]:%X %64[0-9A-Fa-f]:%X %*X %*X:%*X %*X:%*X %*X %*d %*d %d", + local_addr, &local_port, rem_addr, &rem_port, &inode); + + if (count != 5) + continue; + + key = g_new0 (gint64, 1); + *key = inode; + g_hash_table_replace (its->hash, key, (gpointer)(intptr_t)local_port); + } + + fclose (procinfo); +} + +void +xtm_refresh_inode_to_sock (XtmInodeToSock *its) +{ + xtm_refresh_inode_to_sock_protocol (its, "/proc/net/tcp"); + xtm_refresh_inode_to_sock_protocol (its, "/proc/net/tcp6"); + + xtm_refresh_inode_to_sock_protocol (its, "/proc/net/udp"); + xtm_refresh_inode_to_sock_protocol (its, "/proc/net/udp6"); + + xtm_refresh_inode_to_sock_protocol (its, "/proc/net/udplite"); + xtm_refresh_inode_to_sock_protocol (its, "/proc/net/udplite6"); + + xtm_refresh_inode_to_sock_protocol (its, "/proc/net/raw"); + xtm_refresh_inode_to_sock_protocol (its, "/proc/net/raw6"); + + xtm_refresh_inode_to_sock_protocol (its, "/proc/net/icmp"); + xtm_refresh_inode_to_sock_protocol (its, "/proc/net/icmp6"); +} + +#ifdef HAVE_LIBPCAP +void +packet_callback (u_char *args, const struct pcap_pkthdr *header, const u_char *packet) +{ + struct ether_header *eth_header = (struct ether_header *)packet; + XtmNetworkAnalyzer *iface = (XtmNetworkAnalyzer *)args; + + char local_mac[18]; + char src_mac[18]; + char dst_mac[18]; + + int32_t src_port = -1; + int32_t dst_port = -1; + + sprintf (local_mac, "%02X:%02X:%02X:%02X:%02X:%02X", + iface->mac[0], iface->mac[1], iface->mac[2], + iface->mac[3], iface->mac[4], iface->mac[5]); + + sprintf (src_mac, "%02X:%02X:%02X:%02X:%02X:%02X", + eth_header->ether_shost[0], eth_header->ether_shost[1], + eth_header->ether_shost[2], eth_header->ether_shost[3], + eth_header->ether_shost[4], eth_header->ether_shost[5]); + + sprintf (dst_mac, "%02X:%02X:%02X:%02X:%02X:%02X", + eth_header->ether_dhost[0], eth_header->ether_dhost[1], + eth_header->ether_dhost[2], eth_header->ether_dhost[3], + eth_header->ether_dhost[4], eth_header->ether_dhost[5]); + + // IPv4 handling + if (ntohs (eth_header->ether_type) == ETHERTYPE_IP) + { + struct ip *ip_header = (struct ip *)(packet + sizeof (struct ether_header)); + + // TCP handling + if (ip_header->ip_p == IPPROTO_TCP) + { + struct tcphdr *tcp_header = (struct tcphdr *)(packet + sizeof (struct ether_header) + sizeof (struct ip)); + src_port = ntohs (tcp_header->source); + dst_port = ntohs (tcp_header->dest); + } + // UDP handling + else if (ip_header->ip_p == IPPROTO_UDP) + { + struct udphdr *udp_header = (struct udphdr *)(packet + sizeof (struct ether_header) + sizeof (struct ip)); + src_port = ntohs (udp_header->source); + dst_port = ntohs (udp_header->dest); + } + // ICMP handling + else if (ip_header->ip_p == IPPROTO_ICMP) + { + // The Internet Control Message Protocol (ICMP) does not use ports like TCP and UDP + // But the ICMP packet is encapsulated in an IPv4 packet + // 0x1 Reported by /proc/net/raw using ping + src_port = 1; + dst_port = 1; + } + } + // IPv6 handling + else if (ntohs (eth_header->ether_type) == ETHERTYPE_IPV6) + { + struct ip6_hdr *ip6_header = (struct ip6_hdr *)(packet + sizeof (struct ether_header)); + + // TCP handling + if (ip6_header->ip6_nxt == IPPROTO_TCP) + { + struct tcphdr *tcp_header = (struct tcphdr *)(packet + sizeof (struct ether_header) + sizeof (struct ip6_hdr)); + src_port = ntohs (tcp_header->source); + dst_port = ntohs (tcp_header->dest); + } + // UDP handling + else if (ip6_header->ip6_nxt == IPPROTO_UDP) + { + struct udphdr *udp_header = (struct udphdr *)(packet + sizeof (struct ether_header) + sizeof (struct ip6_hdr)); + src_port = ntohs (udp_header->source); + dst_port = ntohs (udp_header->dest); + } + // ICMP handling + else if (ip6_header->ip6_nxt == IPPROTO_ICMPV6) + { + // The Internet Control Message Protocol (ICMP) does not use ports like TCP and UDP + // But the ICMP packet is encapsulated in an IPv6 packet + // 0x3A Reported by /proc/net/raw6 + src_port = 0x3A; + dst_port = 0x3A; + } + } + // Raw packet handling + else + { + src_port = 1; + dst_port = 1; + } + + //! ICMP and RAW packet share the same local port + //! thus the link port -> count is broken in this case + //! since local port become non unique + //! However it still allow to see some unexpected program + + if (src_port != -1 && dst_port != -1) + { + if (strcmp (local_mac, src_mac) == 0) + increament_packet_count (local_mac, "in ", iface->packetin, src_port); + + if (strcmp (local_mac, dst_mac) == 0) + increament_packet_count (local_mac, "out", iface->packetout, dst_port); + } +} +#endif + +int +get_mac_address (const char *device, uint8_t mac[6]) +{ + struct ifaddrs *ifaddr, *ifa; + char device_path[512]; + char link_path[512]; + + snprintf (device_path, 512, "/sys/class/net/%s", device); + ssize_t len = readlink (device_path, link_path, 511); + + // disable localhost, docker, vpn, and other virtual device + // only physical device should remain + if (len == -1 || strstr (link_path, "virtual") != NULL) + return -1; + + if (getifaddrs (&ifaddr) == -1) + return -1; + + for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) + { + if (ifa->ifa_addr == NULL) + continue; + + int family = ifa->ifa_addr->sa_family; + + // Check if the interface is a network interface (AF_PACKET for Linux) + if (family == AF_PACKET && strcmp (device, ifa->ifa_name) == 0) + { + // Check if the interface has a hardware address (MAC address) + if (ifa->ifa_data != NULL) + { + struct sockaddr_ll *sll = (struct sockaddr_ll *)ifa->ifa_addr; + memcpy (mac, sll->sll_addr, sizeof (uint8_t) * 6); + freeifaddrs (ifaddr); + return 0; + } + } + } + + freeifaddrs (ifaddr); + return -1; +} + +gboolean +get_network_usage (guint64 *tcp_rx, guint64 *tcp_tx, guint64 *tcp_error) +{ + FILE *file; + gchar buffer[256]; + char *out; + + *tcp_rx = 0; + *tcp_tx = 0; + *tcp_error = 0; + + if ((file = fopen ("/proc/net/dev", "r")) == NULL) + return FALSE; + + out = fgets (buffer, sizeof (buffer), file); + + if (!out) + { + fclose (file); + return FALSE; + } + + out = fgets (buffer, sizeof (buffer), file); + + if (!out) + { + fclose (file); + return FALSE; + } + + while (fgets (buffer, sizeof (buffer), file)) + { + unsigned long int dummy = 0; + unsigned long int r_bytes = 0; + unsigned long int t_bytes = 0; + unsigned long int r_packets = 0; + unsigned long int t_packets = 0; + unsigned long int error = 0; + gchar ifname[256]; + + int count = sscanf ( + buffer, "%[^:]: %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu", + ifname, &r_bytes, &r_packets, &error, + &dummy, &dummy, &dummy, &dummy, &dummy, + &t_bytes, &t_packets); + + if (count != 11) + { + printf ("Something went wrong while reading /proc/net/dev -> expected %d\n", count); + break; + } + + *tcp_rx += r_bytes; + *tcp_tx += t_bytes; + *tcp_error += error; + } + + fclose (file); + + return TRUE; +} + +void +list_process_fds (Task *task) +{ + XtmNetworkAnalyzer *current; + char path[1024]; + char link[2048]; + char target[2048]; + struct dirent *entry; + ssize_t len; + long int port; + DIR *dir; + + task->packet_in = 0; + task->packet_out = 0; + + snprintf (path, sizeof (path), "/proc/%d/fd", (int)task->pid); + + dir = opendir (path); + + if (dir == 0) + return; + + while ((entry = readdir (dir)) != NULL) + { + if (entry->d_type != DT_LNK) + continue; + + snprintf (link, sizeof (link), "%s/%s", path, entry->d_name); + len = readlink (link, target, sizeof (target) - 1); + + if (len == -1) + continue; + + target[len] = '\0'; + if (strncmp (target, "socket:", 7) != 0) + continue; + + int inode; + if (sscanf (target, "socket:[%d]", &inode) == 1) + { + task->active_socket += 1; + port = (long int)g_hash_table_lookup (inode_to_sock->hash, &inode); + + current = analyzer; + while (current) + { + // pthread_mutex_lock(&analyzer->lock); + task->packet_in += (guint64)g_hash_table_lookup (current->packetin, &port); + task->packet_out += (guint64)g_hash_table_lookup (current->packetout, &port); + // pthread_mutex_lock(&analyzer->lock); + current = current->next; + } + } + } + closedir (dir); +} + gboolean get_memory_usage (guint64 *memory_total, guint64 *memory_available, guint64 *memory_free, guint64 *memory_cache, guint64 *memory_buffers, guint64 *swap_total, guint64 *swap_free) { @@ -304,6 +662,8 @@ get_task_details (GPid pid, Task *task) fclose (file); } + list_process_fds (task); + /* Read the full command line */ if (!get_task_cmdline (task)) return FALSE; @@ -319,6 +679,10 @@ get_task_list (GArray *task_list) GPid pid; Task task; + analyzer = xtm_network_analyzer_get_default (); + inode_to_sock = xtm_inode_to_sock_get_default (); + xtm_refresh_inode_to_sock (inode_to_sock); + if ((dir = g_dir_open ("/proc", 0, NULL)) == NULL) return FALSE; diff --git a/src/task-manager-skel.c b/src/task-manager-skel.c index b9b4045..dd404cd 100644 --- a/src/task-manager-skel.c +++ b/src/task-manager-skel.c @@ -26,6 +26,29 @@ static gushort _cpu_count = 0; */ +int +get_mac_address (const char *device, uint8_t mac[6]) +{ + memset (mac, 0, sizeof (uint8_t) * 6); + return FALSE; +} + +gboolean +get_network_usage (guint64 *tcp_rx, guint64 *tcp_tx, guint64 *tcp_error) +{ + *tcp_rx = 0; + *tcp_tx = 0; + *tcp_error = 0; + return TRUE; +} + +#ifdef HAVE_LIBPCAP +void +packet_callback (u_char *args, const struct pcap_pkthdr *header, const u_char *packet) +{ +} +#endif + gboolean get_memory_usage (guint64 *memory_total, guint64 *memory_available, guint64 *memory_free, guint64 *memory_cache, guint64 *memory_buffers, guint64 *swap_total, guint64 *swap_free) { diff --git a/src/task-manager-solaris.c b/src/task-manager-solaris.c index 4f4c1df..83c8f99 100644 --- a/src/task-manager-solaris.c +++ b/src/task-manager-solaris.c @@ -1,4 +1,5 @@ /* + * Copyright (c) 2024 Jehan-Antoine Vayssade, * Copyright (c) 2010 Mike Massonnet * Copyright (c) 2009 Peter Tribble * @@ -12,29 +13,456 @@ #include "config.h" #endif +#include "inode-to-sock.h" +#include "network-analyzer.h" #include "task-manager.h" +#include #include +#include /* typedef int (*pfi_t)() for inet/optcom.h */ +#include #include #include #include #include #include +#include #include #include #include #include +// clang-format off +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +// clang-format on + +static XtmInodeToSock *inode_to_sock = NULL; +static XtmNetworkAnalyzer *analyzer = NULL; + static kstat_ctl_t *kc; static gushort _cpu_count = 0; static gulong ticks_total_delta = 0; +void addtoconninode (XtmInodeToSock *its, gint64 pid, char *ip, guint64 port); + static void init_stats (void) { kc = kstat_open (); } +int +get_mac_address (const char *device, uint8_t mac[6]) +{ +#ifdef HAVE_LIBSOCKET + struct ifaddrs *ifaddr, *ifa; + + if (getifaddrs (&ifaddr) == -1) + return -1; + + for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) + { + if (ifa->ifa_addr == NULL) + continue; + + // Check if the interface is a network interface (AF_PACKET for Linux) + if (ifa->ifa_addr->sa_family == AF_LINK && strcmp (device, ifa->ifa_name) == 0) + { + // Check if the interface has a hardware address (MAC address) + if (ifa->ifa_data != NULL) + { + // warning: cast from 'struct sockaddr *' to 'struct sockaddr_dl *' increases required alignment from 1 to 2 + // struct sockaddr_dl *sdl = (struct sockaddr_dl *)ifa->ifa_addr; + struct sockaddr_dl sdl; + memcpy (&sdl, ifa->ifa_addr, sizeof (struct sockaddr_dl)); + memcpy (mac, sdl.sdl_data + sdl.sdl_nlen, sizeof (uint8_t) * 6); + freeifaddrs (ifaddr); + return 0; + } + } + } + + freeifaddrs (ifaddr); + return -1; +#else + memset (mac, 0, sizeof (uint8_t) * 6); + return FALSE; +#endif +} + +gboolean +get_network_usage (guint64 *tcp_rx, guint64 *tcp_tx, guint64 *tcp_error) +{ + kstat_named_t *knp; + kstat_t *ksp; + + if (!kc) + init_stats (); + + if (!(ksp = kstat_lookup (kc, "link", -1, NULL))) + { + printf ("kstat_lookup failed\n"); + return FALSE; + } + + kstat_read (kc, ksp, NULL); + *tcp_error = 0; + + if ((knp = kstat_data_lookup (ksp, "rbytes64")) != NULL) + *tcp_rx = knp->value.ui64; + + if ((knp = kstat_data_lookup (ksp, "obytes64")) != NULL) + *tcp_tx = knp->value.ui64; + + if ((knp = kstat_data_lookup (ksp, "ierrors")) != NULL) + *tcp_error += knp->value.ui32; + + if ((knp = kstat_data_lookup (ksp, "oerrors")) != NULL) + *tcp_error += knp->value.ui32; + + return TRUE; +} + +#ifdef HAVE_LIBPCAP +void +packet_callback (u_char *args, const struct pcap_pkthdr *header, const u_char *packet) +{ + // Extract source and destination IP addresses and ports from the packet + struct ether_header eth_header; + struct ip ip_header; + struct tcphdr tcp_header; + XtmNetworkAnalyzer *iface; + long int src_port, dst_port; + char local_mac[18]; + char src_mac[18]; + char dst_mac[18]; + + memcpy (ð_header, packet, sizeof (struct ether_header)); + memcpy (&ip_header, packet + sizeof (struct ether_header), sizeof (struct ip)); + memcpy (&tcp_header, packet + sizeof (struct ether_header) + sizeof (struct ip), sizeof (struct ip)); + + // cast -> increases required alignment from 1 to 2 [-Wcast-align] + // const struct ether_header *eth_header = (const struct ether_header *)packet; + // struct ip *ip_header = (struct ip *)(packet + sizeof (struct ether_header)); + // struct tcphdr *tcp_header = (struct tcphdr *)(packet + sizeof (struct ether_header) + sizeof (struct ip)); + // printf("%d, %d \n", eth_heade.ether_type , ip_header.ip_p); + + // Dropped non-ip packet + if (eth_header.ether_type != 8 || ip_header.ip_p != 6) + return; + + iface = (XtmNetworkAnalyzer *)args; + + src_port = ntohs (tcp_header.th_sport); + dst_port = ntohs (tcp_header.th_dport); + + // directly use strcmp on analyzer->mac, eth_header->ether_shost doesnt work + + snprintf (local_mac, sizeof (local_mac), + "%02X:%02X:%02X:%02X:%02X:%02X", + iface->mac[0], iface->mac[1], + iface->mac[2], iface->mac[3], + iface->mac[4], iface->mac[5]); + + snprintf (src_mac, sizeof (local_mac), + "%02X:%02X:%02X:%02X:%02X:%02X", + eth_header.ether_shost.ether_addr_octet[0], eth_header.ether_shost.ether_addr_octet[1], + eth_header.ether_shost.ether_addr_octet[2], eth_header.ether_shost.ether_addr_octet[3], + eth_header.ether_shost.ether_addr_octet[4], eth_header.ether_shost.ether_addr_octet[5]); + + snprintf (dst_mac, sizeof (local_mac), + "%02X:%02X:%02X:%02X:%02X:%02X", + eth_header.ether_dhost.ether_addr_octet[0], eth_header.ether_dhost.ether_addr_octet[1], + eth_header.ether_dhost.ether_addr_octet[2], eth_header.ether_dhost.ether_addr_octet[3], + eth_header.ether_dhost.ether_addr_octet[4], eth_header.ether_dhost.ether_addr_octet[5]); + + // Debug + // pthread_mutex_lock(&iface->lock); + + if (strcmp (local_mac, src_mac) == 0) + increament_packet_count (local_mac, "in ", iface->packetin, src_port); + + if (strcmp (local_mac, dst_mac) == 0) + increament_packet_count (local_mac, "out", iface->packetout, dst_port); + + // pthread_mutex_unlock(&iface->lock); +} +#endif + +void +set_task_network_data (Task *task, guint64 port) +{ + XtmNetworkAnalyzer *current; + task->active_socket += 1; + + current = analyzer; + while (current) + { + task->packet_in += (guint64)g_hash_table_lookup (current->packetin, &port); + task->packet_out += (guint64)g_hash_table_lookup (current->packetout, &port); + current = current->next; + } +} + +void +list_process_fds (Task *task) +{ + XtmNetworkAnalyzer *current; + GHashTableIter iter; + gpointer key, value; + gint64 key_int, value_int; + + g_hash_table_iter_init (&iter, inode_to_sock->pid); + while (g_hash_table_iter_next (&iter, &key, &value)) + { + key_int = *(gint64 *)(key); + value_int = GPOINTER_TO_INT (value); + + if (task->pid == value_int) + set_task_network_data (task, key_int); + } +} + +void +addtoconninode (XtmInodeToSock *its, gint64 pid, char *ip, guint64 port) +{ + gint64 *inode; + + if (its == NULL) + return; + + // seem like idle socket are given back to pid 0 (kernel ?) + if (pid <= 0) + return; + + if (strcmp (ip, "0.0.0.0") == 0) + return; + + if (strcmp (ip, "::") == 0) + return; + + if (strcmp (ip, "127.0.0.1") == 0) + return; + + inode = g_new0 (gint64, 1); + *inode = port; + + g_hash_table_replace (inode_to_sock->pid, inode, (gpointer)(intptr_t)pid); + + printf ("PID %ld: Local Address: [%s]:%ld\n", pid, ip, port); +} + +void +xtm_refresh_inode_to_sock (XtmInodeToSock *its) +{ + // inspired by : + // nxsensor/src/sysdeps/solaris.c + // net-snmp/agent/mibgroup/mibII/tcpTable.c + // net-snmp/agent/mibgroup/mibgroup/kernel_sunos5.c + // psutil/psutil/_psutil_sunos.c + // illumos-joyent/master/usr/src/uts/common/io/tl.c + // nicstat/nicstat.c + + int sd, ret, flags, getcode, num_ent, i; + char buf[4096]; + char lip[INET6_ADDRSTRLEN]; + + mib2_tcpConnEntry_t tp; + mib2_udpEntry_t ude; + +#if defined(AF_INET6) + mib2_tcp6ConnEntry_t tp6; + mib2_udp6Entry_t ude6; +#endif + + struct strbuf ctlbuf, databuf; + struct T_optmgmt_req tor = { 0 }; + struct T_optmgmt_ack toa = { 0 }; + struct T_error_ack tea = { 0 }; + struct opthdr mibhdr = { 0 }; + + sd = open ("/dev/arp", O_RDWR); + if (sd == -1) + { + perror ("open"); + return; + } + + ret = ioctl (sd, I_PUSH, "tcp"); + if (ret == -1) + { + perror ("ioctl"); + close (sd); + return; + } + + ret = ioctl (sd, I_PUSH, "udp"); + if (ret == -1) + { + perror ("ioctl"); + close (sd); + return; + } + + // g_hash_table_remove_all (its->pid); + // g_hash_table_remove_all (its->hash); + + // Set up the request + tor.PRIM_type = T_SVR4_OPTMGMT_REQ; + tor.OPT_offset = sizeof (struct T_optmgmt_req); + tor.OPT_length = sizeof (struct opthdr); + tor.MGMT_flags = T_CURRENT; + mibhdr.level = MIB2_IP; + mibhdr.name = 0; +#ifdef NEW_MIB_COMPLIANT + mibhdr.len = 1; +#else + mibhdr.len = 0; +#endif + memcpy (buf, &tor, sizeof (tor)); + memcpy (buf + tor.OPT_offset, &mibhdr, sizeof (mibhdr)); + ctlbuf.buf = buf; + ctlbuf.len = tor.OPT_offset + tor.OPT_length; + flags = 0; + + // Send the request + if (putmsg (sd, &ctlbuf, NULL, flags) == -1) + { + perror ("putmsg"); + close (sd); + return; + } + + ctlbuf.maxlen = sizeof (buf); + + for (;;) + { + getcode = getmsg (sd, &ctlbuf, NULL, &flags); + memcpy (&toa, buf, sizeof (toa)); + memcpy (&tea, buf, sizeof (tea)); + + if (getcode != MOREDATA || ctlbuf.len < (int)sizeof (struct T_optmgmt_ack) || + toa.PRIM_type != T_OPTMGMT_ACK || toa.MGMT_flags != T_SUCCESS) + { + break; + } + + if (ctlbuf.len >= (int)sizeof (struct T_error_ack) && tea.PRIM_type == T_ERROR_ACK) + { + fprintf (stderr, "ERROR_ACK\n"); + close (sd); + return; + } + + if (getcode == 0 && ctlbuf.len >= (int)sizeof (struct T_optmgmt_ack) && + toa.PRIM_type == T_OPTMGMT_ACK && toa.MGMT_flags == T_SUCCESS) + { + fprintf (stderr, "ERROR_T_OPTMGMT_ACK\n"); + close (sd); + return; + } + + memset (&mibhdr, 0x0, sizeof (mibhdr)); + memcpy (&mibhdr, buf + toa.OPT_offset, toa.OPT_length); + databuf.maxlen = mibhdr.len; + databuf.len = 0; + databuf.buf = (char *)malloc ((int)mibhdr.len); + if (!databuf.buf) + { + fprintf (stderr, "Out of memory\n"); + close (sd); + return; + } + + flags = 0; + getcode = getmsg (sd, NULL, &databuf, &flags); + + if (getcode < 0) + { + perror ("getmsg"); + free (databuf.buf); + close (sd); + return; + } + + // TCPv4 + if (mibhdr.level == MIB2_TCP && mibhdr.name == MIB2_TCP_13) + { + num_ent = mibhdr.len / sizeof (mib2_tcpConnEntry_t); + for (i = 0; i < num_ent; i++) + { + memcpy (&tp, databuf.buf + i * sizeof (tp), sizeof (tp)); + inet_ntop (AF_INET, &tp.tcpConnLocalAddress, lip, sizeof (lip)); + addtoconninode (inode_to_sock, tp.tcpConnCreationProcess, lip, tp.tcpConnLocalPort); + // interesting properties, allowing to remove pcap + // tp.tcpConnEntryInfo.ce_in_data_inorder_segs + // tp.tcpConnEntryInfo.ce_in_data_unorder_segs + // tp.tcpConnEntryInfo.ce_out_data_segs + // tp.tcpConnEntryInfo.ce_out_retrans_segs + } + } +#if defined(AF_INET6) + // TCPv6 + else if (mibhdr.level == MIB2_TCP6 && mibhdr.name == MIB2_TCP6_CONN) + { + num_ent = mibhdr.len / sizeof (mib2_tcp6ConnEntry_t); + for (i = 0; i < num_ent; i++) + { + memcpy (&tp6, databuf.buf + i * sizeof (tp6), sizeof (tp6)); + inet_ntop (AF_INET6, &tp6.tcp6ConnLocalAddress, lip, sizeof (lip)); + addtoconninode (inode_to_sock, tp6.tcp6ConnCreationProcess, lip, tp6.tcp6ConnLocalPort); + // interesting properties, allowing to remove pcap + // tp.tcpConnEntryInfo.ce_in_data_inorder_segs + // tp.tcpConnEntryInfo.ce_in_data_unorder_segs + // tp.tcpConnEntryInfo.ce_out_data_segs + // tp.tcpConnEntryInfo.ce_out_retrans_segs + } + } +#endif + // UDPv4 + else if (mibhdr.level == MIB2_UDP || mibhdr.level == MIB2_UDP_ENTRY) + { + num_ent = mibhdr.len / sizeof (mib2_udpEntry_t); + for (i = 0; i < num_ent; i++) + { + memcpy (&ude, databuf.buf + i * sizeof (ude), sizeof (ude)); + inet_ntop (AF_INET, &ude.udpLocalAddress, lip, sizeof (lip)); + addtoconninode (inode_to_sock, ude.udpCreationProcess, lip, ude.udpLocalPort); + } + } +#if defined(AF_INET6) + // UDPv6 + else if (mibhdr.level == MIB2_UDP6 || mibhdr.level == MIB2_UDP6_ENTRY) + { + num_ent = mibhdr.len / sizeof (mib2_udp6Entry_t); + for (i = 0; i < num_ent; i++) + { + memcpy (&ude6, databuf.buf + i * sizeof (ude6), sizeof (ude6)); + inet_ntop (AF_INET6, &ude6.udp6LocalAddress, lip, sizeof (lip)); + addtoconninode (inode_to_sock, ude6.udp6CreationProcess, lip, ude6.udp6LocalPort); + } + } +#endif + + free (databuf.buf); + } + + close (sd); +} + gboolean get_memory_usage (guint64 *memory_total, guint64 *memory_available, guint64 *memory_free, guint64 *memory_cache, guint64 *memory_buffers, guint64 *swap_total, guint64 *swap_free) { @@ -201,6 +629,8 @@ get_task_details (GPid pid, Task *task) fclose (file); + list_process_fds (task); + return TRUE; } @@ -212,6 +642,11 @@ get_task_list (GArray *task_list) GPid pid; Task task; + printf ("------------\n"); + analyzer = xtm_network_analyzer_get_default (); + inode_to_sock = xtm_inode_to_sock_get_default (); + xtm_refresh_inode_to_sock (inode_to_sock); + if ((dir = g_dir_open ("/proc", 0, NULL)) == NULL) return FALSE; diff --git a/src/task-manager.c b/src/task-manager.c index 090e37c..38cfdb6 100644 --- a/src/task-manager.c +++ b/src/task-manager.c @@ -1,4 +1,5 @@ /* + * Copyright (c) 2024 Jehan-Antoine Vayssade, * Copyright (c) 2010 Mike Massonnet, * Copyright (c) 2018 Rozhuk Ivan * @@ -12,6 +13,7 @@ #include "config.h" #endif +#include "network-analyzer.h" #include "process-tree-view.h" /* for the columns of the model */ #include "settings.h" #include "task-manager.h" @@ -22,6 +24,10 @@ #include #endif +#ifdef HAVE_LIBPCAP +#include +#endif + #include #include #include @@ -42,6 +48,7 @@ struct _XtmTaskManagerClass { GObjectClass parent_class; }; + struct _XtmTaskManager { GObject parent; @@ -61,6 +68,12 @@ struct _XtmTaskManager guint64 memory_buffers; guint64 swap_total; guint64 swap_free; + guint64 tcp_rx; + guint64 tcp_tx; + guint64 tcp_error; + guint64 old_tcp_rx; + guint64 old_tcp_tx; + guint64 old_tcp_error; }; G_DEFINE_TYPE (XtmTaskManager, xtm_task_manager, G_TYPE_OBJECT) @@ -101,6 +114,9 @@ xtm_task_manager_init (XtmTaskManager *manager) g_object_get (settings, "full-command-line", &full_cmdline, NULL); g_signal_connect (settings, "notify::more-precision", G_CALLBACK (setting_changed), manager); g_signal_connect (settings, "notify::full-command-line", G_CALLBACK (setting_changed), manager); + manager->old_tcp_rx = 0; + manager->old_tcp_tx = 0; + manager->old_tcp_error = 0; } static void @@ -108,6 +124,7 @@ xtm_task_manager_finalize (GObject *object) { XtmTaskManager *manager = XTM_TASK_MANAGER (object); g_array_free (manager->tasks, TRUE); + #ifdef HAVE_WNCK if (manager->app_manager != NULL) { @@ -334,6 +351,9 @@ model_update_tree_iter (XtmTaskManager *manager, GtkTreeIter *iter, glong timest XTM_PTV_COLUMN_CPU_STR, cpu, XTM_PTV_COLUMN_GROUP_CPU, (task->group_cpu_user + task->group_cpu_system), XTM_PTV_COLUMN_GROUP_CPU_STR, group_cpu, + XTM_PTV_COLUMN_PACKET_IN, task->packet_in, + XTM_PTV_COLUMN_PACKET_OUT, task->packet_out, + XTM_PTV_COLUMN_ACTIVE_SOCKET, task->active_socket, XTM_PTV_COLUMN_PRIORITY, task->prio, XTM_PTV_COLUMN_BACKGROUND, background, XTM_PTV_COLUMN_FOREGROUND, foreground, @@ -393,6 +413,33 @@ xtm_task_manager_new (GtkTreeModel *model) return manager; } +void +xtm_task_manager_get_network_info (XtmTaskManager *manager, guint64 *tcp_rx, guint64 *tcp_tx, guint64 *tcp_error) +{ + g_return_if_fail (XTM_IS_TASK_MANAGER (manager)); + get_network_usage (&manager->tcp_rx, &manager->tcp_tx, &manager->tcp_error); + + if (manager->old_tcp_rx == 0 && manager->old_tcp_tx == 0 && manager->old_tcp_error == 0) + { + *tcp_rx = 0; + *tcp_tx = 0; + *tcp_error = 0; + } + else + { + gint ms; + g_object_get (settings, "refresh-rate", &ms, NULL); + // ugly approximation in guint64 + *tcp_rx = (manager->tcp_rx - manager->old_tcp_rx) / ms * 1000; + *tcp_tx = (manager->tcp_tx - manager->old_tcp_tx) / ms * 1000; + *tcp_error = (manager->tcp_error - manager->old_tcp_error) / ms * 1000; + } + + manager->old_tcp_rx = manager->tcp_rx; + manager->old_tcp_tx = manager->tcp_tx; + manager->old_tcp_error = manager->tcp_error; +} + void xtm_task_manager_get_system_info (XtmTaskManager *manager, guint *num_processes, gfloat *cpu, guint64 *memory_used, guint64 *memory_total, diff --git a/src/task-manager.h b/src/task-manager.h index 0fc7a45..b20e9eb 100644 --- a/src/task-manager.h +++ b/src/task-manager.h @@ -10,9 +10,17 @@ #ifndef TASK_MANAGER_H #define TASK_MANAGER_H +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + #include #include +#ifdef HAVE_LIBPCAP +#include +#endif + /** * Legend colors */ @@ -45,6 +53,10 @@ struct _Task gfloat group_cpu_system; guint64 group_vsz; guint64 group_rss; + + guint64 packet_in; + guint64 packet_out; + guint64 active_socket; }; /** @@ -53,6 +65,12 @@ struct _Task * memory_available = free + cache + buffers + an-OS-specific-value */ +#ifdef HAVE_LIBPCAP +void packet_callback (u_char *args, const struct pcap_pkthdr *header, const u_char *packet); +#endif + +int get_mac_address (const char *device, uint8_t mac[6]); +gboolean get_network_usage (guint64 *tcp_rx, guint64 *tcp_tx, guint64 *tcp_error); gboolean get_memory_usage (guint64 *memory_total, guint64 *memory_available, guint64 *memory_free, guint64 *memory_cache, guint64 *memory_buffers, guint64 *swap_total, guint64 *swap_free); gboolean get_cpu_usage (gushort *cpu_count, gfloat *cpu_user, gfloat *cpu_system); gboolean get_task_list (GArray *task_list); @@ -73,6 +91,7 @@ typedef struct _XtmTaskManager XtmTaskManager; GType xtm_task_manager_get_type (void); XtmTaskManager *xtm_task_manager_new (GtkTreeModel *model); +void xtm_task_manager_get_network_info (XtmTaskManager *manager, guint64 *tcp_rx, guint64 *tcp_tx, guint64 *tcp_error); void xtm_task_manager_get_system_info (XtmTaskManager *manager, guint *num_processes, gfloat *cpu, guint64 *memory_used, guint64 *memory_total, guint64 *swap_used, guint64 *swap_total);