diff --git a/client-gtk/Makefile.am b/client-gtk/Makefile.am index ce3bc27..b8a7f37 100644 --- a/client-gtk/Makefile.am +++ b/client-gtk/Makefile.am @@ -13,6 +13,8 @@ inador_gtk_client_SOURCES = main.c \ ni-ip-add-dialog.c ni-ip-add-dialog.h \ ni-interface-filter.c ni-interface-filter.h \ ni-interface-chooser-dialog.c ni-interface-chooser-dialog.h \ + ni-route.c ni-route.h \ + ni-window-route.c ni-window-route.h \ $(BUILT_SOURCES) #inador_gtk_client_CPPFLAGS = -DGAMEDATA_DIR=\"$(gamedatadir)/\" -DLOCALEDIR=\"$(localedir)\" $(AM_CPPFLAGS) diff --git a/client-gtk/main.c b/client-gtk/main.c index b6fa204..ea58eb5 100644 --- a/client-gtk/main.c +++ b/client-gtk/main.c @@ -5,6 +5,8 @@ #include "ni-client.h" #include "ni-interface.h" #include "ni-window-interface.h" +#include "ni-window-route.h" + #include "../src/link-types.h" enum { @@ -54,6 +56,8 @@ GtkWidget *main_vbox, *network_vbox; GtkWidget *empty_label = NULL; GHashTable *network_blocks_list; GtkWidget *create_bridge_menu; +GtkWidget *route_v4_menu, *route_v6_menu; +GtkWidget *route_v4_window = NULL, *route_v6_window = NULL; static void click_interface_cb (GtkIconView *iconview, GtkTreePath *path, gpointer data) { UINetworkType *network_block = (UINetworkType *) data; @@ -74,6 +78,22 @@ static void click_interface_cb (GtkIconView *iconview, GtkTreePath *path, gpoint gtk_widget_show (ventana); } +static void click_open_route_table_v4 (GtkWidget *widget, gpointer data) { + if (route_v4_window == NULL) { + route_v4_window = ni_window_route_new (ni_client, AF_INET); + } + + gtk_widget_show (route_v4_window); +} + +static void click_open_route_table_v6 (GtkWidget *widget, gpointer data) { + if (route_v6_window == NULL) { + route_v6_window = ni_window_route_new (ni_client, AF_INET6); + } + + gtk_widget_show (route_v6_window); +} + int guess_interface_type (NIInterface *ni_interface) { guint type; @@ -321,6 +341,32 @@ int main (int argc, char *argv[]) { gtk_widget_show_all (menu); + /* Menú de las tablas de ruteo */ + menu_item = gtk_menu_item_new_with_mnemonic ("Tablas de _ruteo"); + gtk_menu_shell_append (GTK_MENU_SHELL (menu_bar), menu_item); + + menu = gtk_menu_new (); + gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu_item), menu); + + /* La tabla de ruteo de IPv4 */ + route_v4_menu = gtk_image_menu_item_new_with_mnemonic ("Tabla de IPv_4"); + if (connected == FALSE) { + gtk_widget_set_sensitive (route_v4_menu, FALSE); + } + gtk_menu_shell_append (GTK_MENU_SHELL (menu), route_v4_menu); + g_signal_connect (route_v4_menu, "activate", G_CALLBACK (click_open_route_table_v4), NULL); + + /* La tabla de ruteo de IPv6 */ + route_v6_menu = gtk_image_menu_item_new_with_mnemonic ("Tabla de IPv_6"); + if (connected == FALSE) { + gtk_widget_set_sensitive (route_v6_menu, FALSE); + } + gtk_menu_shell_append (GTK_MENU_SHELL (menu), route_v6_menu); + g_signal_connect (route_v6_menu, "activate", G_CALLBACK (click_open_route_table_v6), NULL); + + gtk_widget_show_all (menu); + + /* El resto de la interfaz */ scrolled = gtk_scrolled_window_new (NULL, NULL); gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled), GTK_POLICY_AUTOMATIC, GTK_POLICY_NEVER); diff --git a/client-gtk/ni-client.c b/client-gtk/ni-client.c index 5856d17..3f48d7d 100644 --- a/client-gtk/ni-client.c +++ b/client-gtk/ni-client.c @@ -34,6 +34,8 @@ #include "ni-client.h" #include "ni-interface.h" #include "ni-marshal.h" +#include "ni-route.h" + #include "../src/network-inador-manager.h" #define COMMAND_SOCKET_PATH "/tmp/network-inador.socket" @@ -50,6 +52,8 @@ struct _NIClientPrivate { gchar *socket_path; GHashTable *interfaces; + + GList *routes_v4, *routes_v6; guint source; guint events; }; @@ -66,9 +70,11 @@ enum { DATA_READ, NEW_INTERFACE, - DELETE_INTERFACE, + NEW_ROUTE, + DELETE_ROUTE, + LAST_SIGNAL }; @@ -158,13 +164,36 @@ static void ni_client_class_init (NIClientClass *klass) { 1, NI_TYPE_INTERFACE); + signals[NEW_ROUTE] = g_signal_new ("new-route", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (NIClientClass, new_route), + NULL, + NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, + 1, + NI_TYPE_ROUTE); + + signals[DELETE_ROUTE] = g_signal_new ("delete-route", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (NIClientClass, delete_route), + NULL, + NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, + 1, + NI_TYPE_ROUTE); + klass->data_response = ni_client_data_read; klass->new_interface = NULL; klass->delete_interface = NULL; + klass->new_route = NULL; + klass->delete_route = NULL; } static void ni_client_process_interface_response (NIClient *ni_client, gpointer data, guint size) { - printf ("Interface response\n"); NIInterface *ni_interface; unsigned char *buffer = (unsigned char *) data; @@ -193,6 +222,163 @@ static void ni_client_process_interface_response (NIClient *ni_client, gpointer g_signal_emit (ni_client, signals[NEW_INTERFACE], 0, ni_interface); } +static NIRoute *_ni_client_search_route (GList *list_routes, sa_family_t family, uint8_t type, uint32_t table, void *dest, uint32_t prefix, uint32_t priority) { + GList *g; + NIRoute *ni_route; + int family_size = 0; + + if (family == AF_INET) { + family_size = sizeof (struct in_addr); + } else if (family == AF_INET6) { + family_size = sizeof (struct in6_addr); + } + + for (g = list_routes; g != NULL; g = g->next) { + ni_route = (NIRoute *) g->data; + + if (ni_route_get_family(ni_route) != family) continue; + if (ni_route_get_route_type(ni_route) != type) continue; + if (ni_route_get_table(ni_route) != table) continue; + if (ni_route_get_priority(ni_route) != priority) continue; + + if (memcmp (ni_route_get_dest (ni_route), dest, family_size) == 0 && ni_route_get_prefix(ni_route) == prefix) { + return ni_route; + } + } + + return NULL; +} + +static void ni_client_process_route_response (NIClient *ni_client, gpointer data, guint size) { + unsigned char *buffer = (unsigned char *) data; + uint8_t family, family_size; + uint8_t type, protocol, tos, scope, prefix; + uint32_t tabla, prioridad; + uint8_t num_nexthops; + gboolean has_prefsrc; + int pos, g; + NIRouteNH nexthops[16]; + NIRoute *ni_route; + struct_addr dest, prefsrc; + + memset (nexthops, 0, sizeof (nexthops)); + family = buffer[2]; + + if (family == AF_INET) { + family_size = sizeof (struct in_addr); + } else if (family == AF_INET6) { + family_size = sizeof (struct in6_addr); + } + + type = buffer[3]; + + memcpy (&tabla, &buffer[4], 4); + prefix = buffer[8]; + protocol = buffer[9]; + tos = buffer[10]; + scope = buffer[11]; + + has_prefsrc = FALSE; + if (buffer[12] & 0x01) { + has_prefsrc = TRUE; + } + + num_nexthops = buffer[13]; + + memcpy (&prioridad, &buffer[14], 4); + + memcpy (&dest, &buffer[18], family_size); + + pos = 18 + family_size; + if (has_prefsrc) { + memcpy (&prefsrc, &buffer[pos], family_size); + pos += family_size; + } + + if (num_nexthops > 16) { + num_nexthops = 16; + } + for (g = 0; g < num_nexthops; g++) { + nexthops[g].has_gw = FALSE; + if (buffer[pos] & 0x01) { + /* Tiene GW */ + nexthops[g].has_gw = TRUE; + } + pos++; + + nexthops[g].flags = buffer[pos]; + pos++; + + nexthops[g].weight = buffer[pos]; + pos++; + + pos++; /* El byte ignorado */ + + memcpy (&nexthops[g].out_index, &buffer[pos], 4); + pos +=4; + + if (nexthops[g].has_gw) { + memcpy (&nexthops[g].gw, &buffer[pos], family_size); + pos += family_size; + } + } + ni_route = ni_route_new (ni_client, family, type, tabla, &dest, prefix, protocol, tos, scope, (has_prefsrc ? &prefsrc : NULL), prioridad, num_nexthops, nexthops); + + if (family == AF_INET) { + ni_client->priv->routes_v4 = g_list_append (ni_client->priv->routes_v4, ni_route); + } else if (family == AF_INET6) { + ni_client->priv->routes_v6 = g_list_append (ni_client->priv->routes_v6, ni_route); + } + g_signal_emit (ni_client, signals[NEW_ROUTE], 0, ni_route); +} + +static void ni_client_process_route_del_response (NIClient *ni_client, gpointer data, guint size) { + unsigned char *buffer = (unsigned char *) data; + uint8_t family, family_size; + uint8_t type, prefix; + uint32_t tabla, prioridad; + NIRoute *ni_route; + struct_addr dest; + GList *list_routes; + + family = buffer[2]; + + if (family == AF_INET) { + family_size = sizeof (struct in_addr); + list_routes = ni_client->priv->routes_v4; + } else if (family == AF_INET6) { + family_size = sizeof (struct in6_addr); + list_routes = ni_client->priv->routes_v6; + } + + type = buffer[3]; + + memcpy (&tabla, &buffer[4], 4); + prefix = buffer[8]; + memcpy (&prioridad, &buffer[9], 4); + + memcpy (&dest, &buffer[13], family_size); + + ni_route = _ni_client_search_route (list_routes, family, type, tabla, &dest, prefix, prioridad); + + if (ni_route == NULL) { + /* ¿No hay ruta? Raro */ + return; + } + + /* Desligar la ruta */ + if (family == AF_INET) { + ni_client->priv->routes_v4 = g_list_remove (ni_client->priv->routes_v4, ni_route); + } else if (family == AF_INET6) { + ni_client->priv->routes_v6 = g_list_remove (ni_client->priv->routes_v6, ni_route); + } + + g_signal_emit (ni_client, signals[DELETE_ROUTE], 0, ni_route); + + /* Liberar el objeto */ + g_object_unref (ni_route); +} + static gboolean ni_client_data_read (NIClient *ni_client, gpointer data, guint size) { unsigned char *buffer = (unsigned char *) data; uint32_t index; @@ -243,6 +429,16 @@ static gboolean ni_client_data_read (NIClient *ni_client, gpointer data, guint s /* Dejar pasar el evento para que la propia interfaz lo procese */ return FALSE; break; + case NET_INADOR_EVENT_ROUTE_REMOVED: + /* Eliminar una ruta es ligeramente mas complejo porque necesitamos que haga match con varios valores para eliminarla */ + ni_client_process_route_del_response (ni_client, data, size); + return TRUE; + break; + case NET_INADOR_EVENT_ROUTE_ADDED: + /* TODO: Revisar si la ruta existe, entonces, en teoría, es una actualización */ + ni_client_process_route_response (ni_client, data, size); + return FALSE; + break; } } else if (buffer[0] == NET_INADOR_TYPE_RESPONSE) { switch (buffer[1]) { @@ -262,7 +458,6 @@ static gboolean ni_client_data_read (NIClient *ni_client, gpointer data, guint s case NET_INADOR_RESPONSE_DHCP_STATUS: memcpy (&index, &buffer[2], 4); - /* Si la interfaz no existe, proccesarla yo, para que se cree el objeto, si la interfaz ya existe, dejar pasar el mensaje */ ni_interface = (NIInterface *) g_hash_table_lookup (ni_client->priv->interfaces, GINT_TO_POINTER(index)); if (ni_interface == NULL) { /* Esta es una condición de error interesante. No debería ocurrir. Absorber el evento */ @@ -272,6 +467,11 @@ static gboolean ni_client_data_read (NIClient *ni_client, gpointer data, guint s /* Dejar pasar el evento para que la propia interfaz lo procese */ return FALSE; break; + case NET_INADOR_RESPONSE_ROUTE: + /* Cuando hacemos listado, no hay problemas de actualización */ + ni_client_process_route_response (ni_client, data, size); + return FALSE; + break; } } @@ -374,8 +574,10 @@ static void ni_client_init (NIClient *ni_client) { priv->client_socket = -1; priv->socket_path = NULL; + priv->routes_v4 = NULL; + priv->routes_v6 = NULL; priv->source = 0; - priv->events = NET_INADOR_EVENT_MASK_INTERFACES | NET_INADOR_EVENT_MASK_IP | NET_INADOR_EVENT_MASK_DHCP_STATUS; + priv->events = NET_INADOR_EVENT_MASK_INTERFACES | NET_INADOR_EVENT_MASK_IP | NET_INADOR_EVENT_MASK_DHCP_STATUS | NET_INADOR_EVENT_MASK_ROUTES; priv->interfaces = g_hash_table_new (g_direct_hash, g_direct_equal); } @@ -407,6 +609,21 @@ void ni_client_ask_interfaces (NIClient *ni_client) { send (ni_client->priv->client_socket, buffer, 2, 0); } +void ni_client_ask_routes (NIClient *ni_client) { + unsigned char buffer[8]; + + if (ni_client->priv->client_socket < 0) { + return; + } + + buffer[0] = NET_INADOR_TYPE_COMMAND; + buffer[1] = NET_INADOR_COMMAND_LIST_ROUTES; + memset (&buffer[2], 0, 4); + buffer[6] = AF_UNSPEC; + + send (ni_client->priv->client_socket, buffer, 7, 0); +} + void ni_client_ask_ip_new (NIClient *ni_client, NIInterface *ni_interface, int family, int prefix, struct_addr *addr, uint32_t flags, unsigned char scope, int has_local, struct_addr *local_addr, int has_brd, struct_addr *brd_addr, uint32_t *cacheinfo) { unsigned char buffer[80]; uint32_t index; @@ -713,6 +930,18 @@ NIInterface *ni_client_get_interface_by_index (NIClient *ni_client, guint index) return g_hash_table_lookup (ni_client->priv->interfaces, GINT_TO_POINTER(index)); } +GList *ni_client_get_routes_v4 (NIClient *ni_client) { + g_return_val_if_fail (NI_IS_CLIENT (ni_client), NULL); + + return ni_client->priv->routes_v4; +} + +GList *ni_client_get_routes_v6 (NIClient *ni_client) { + g_return_val_if_fail (NI_IS_CLIENT (ni_client), NULL); + + return ni_client->priv->routes_v6; +} + gboolean ni_client_connect (NIClient *ni_client) { int s, ret; struct sockaddr_un path_dest; @@ -749,6 +978,9 @@ gboolean ni_client_connect (NIClient *ni_client) { /* Pedir un listado de interfaces */ ni_client_ask_interfaces (ni_client); + + /* Pedir un listado de rutas */ + ni_client_ask_routes (ni_client); } void ni_client_disconnect (NIClient *ni_client) { diff --git a/client-gtk/ni-client.h b/client-gtk/ni-client.h index faab229..333bcff 100644 --- a/client-gtk/ni-client.h +++ b/client-gtk/ni-client.h @@ -60,6 +60,8 @@ struct _NIClientClass { /*gboolean (* data_response_error) (NiClient *ni_client, gpointer data, guint size);*/ void (*new_interface) (NIClient *ni_client, gpointer ni_interface); void (*delete_interface) (NIClient *ni_client, gpointer ni_interface); + void (*new_route) (NIClient *ni_client, gpointer ni_route); + void (*delete_route) (NIClient *ni_client, gpointer ni_route); }; struct _NIClient { @@ -75,7 +77,9 @@ typedef struct _NIInterface NIInterface; */ NIClient *ni_client_new (void); NIClient *ni_client_new_with_path (const char *path); + gboolean ni_client_connect (NIClient *ni_client); + void ni_client_ask_ip_interface (NIClient *ni_client, NIInterface *ni_interface, int family); void ni_client_ask_ip_new (NIClient *ni_client, NIInterface *ni_interface, int family, int prefix, struct_addr *addr, uint32_t flags, unsigned char scope, int has_local, struct_addr *local_addr, int has_brd, struct_addr *brd_addr, uint32_t *cacheinfo); void ni_client_ask_ip_delete (NIClient *ni_client, NIInterface *ni_interface, NIIP *ni_ip); @@ -83,9 +87,13 @@ void ni_client_ask_change_iface_name (NIClient *ni_client, NIInterface *ni_inter void ni_client_ask_change_iface_mtu (NIClient *ni_client, NIInterface *ni_interface, guint new_mtu); void ni_client_ask_up_down_interface (NIClient *ni_client, NIInterface *ni_interface, gboolean is_up); void ni_client_ask_create_bridge (NIClient *ni_client, const gchar *name); + GList *ni_client_get_list_interfaces (NIClient *ni_client); NIInterface *ni_client_get_interface_by_index (NIClient *ni_client, guint index); +GList *ni_client_get_routes_v4 (NIClient *ni_client); +GList *ni_client_get_routes_v6 (NIClient *ni_client); + void ni_client_ask_interface_clear_master (NIClient *ni_client, NIInterface *ni_interface); void ni_client_ask_interface_set_master (NIClient *ni_client, NIInterface *ni_interface, NIInterface *master); diff --git a/client-gtk/ni-ip.h b/client-gtk/ni-ip.h index 4371a9d..281e39a 100644 --- a/client-gtk/ni-ip.h +++ b/client-gtk/ni-ip.h @@ -1,5 +1,5 @@ /* - * ni-interface.h + * ni-ip.h * This file is part of NetworkInador * * Copyright (C) 2021 - Gatuno diff --git a/client-gtk/ni-route.c b/client-gtk/ni-route.c new file mode 100644 index 0000000..b485e5d --- /dev/null +++ b/client-gtk/ni-route.c @@ -0,0 +1,466 @@ +/* + * ni-route.c + * This file is part of NetworkInador + * + * Copyright (C) 2021 - Félix Arreola Rodríguez + * + * NetworkInador 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. + * + * NetworkInador is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with NetworkInador; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +#include +#include +#include + +#include "ni-client.h" +#include "ni-route.h" +#include "ni-ip.h" + +#include "../src/network-inador-manager.h" + +struct _NIRoutePrivate { + NIClient *ni_client; + + guint family; + gint family_size; + guint type; + uint32_t table; + struct_addr dest; + guint prefix; + + guint protocol, tos, scope; + + gboolean has_prefsrc; + struct_addr prefsrc; + guint priority; + + /* Múltiples GW */ + guint nh; + NIRouteNH *nexthops; +}; + +enum { + PROP_NI_CLIENT = 1, + + N_PROPERTIES +}; + +G_DEFINE_TYPE_WITH_PRIVATE (NIRoute, ni_route, G_TYPE_OBJECT) + +static GParamSpec *obj_properties[N_PROPERTIES] = { NULL, }; + +enum { + ROUTE_UPDATED, + + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL]; + +static gboolean ni_route_process_route_response (NIClient *ni_client, unsigned char *buffer, guint size, gpointer data); + +static void ni_route_constructed (GObject *obj) { + NIRoute *ni_route; + GObjectClass *parent_class = g_type_class_peek (G_TYPE_OBJECT); + + parent_class->constructed (obj); + + ni_route = NI_ROUTE (obj); + + if (ni_route->priv->ni_client != NULL) { + /* Conectar la señal de datos */ + g_signal_connect (ni_route->priv->ni_client, "packet-read", G_CALLBACK (ni_route_process_route_response), ni_route); + } +} + +static void ni_route_dispose (GObject *obj) { + NIRoute *ni_route; + GObjectClass *parent_class = g_type_class_peek (G_TYPE_OBJECT); + + ni_route = NI_ROUTE (obj); + + g_signal_handlers_disconnect_by_func (ni_route->priv->ni_client, G_CALLBACK (ni_route_process_route_response), ni_route); + + parent_class->dispose (obj); +} + +static void ni_route_finalize (GObject *obj) { + NIRoute *ni_route; + GObjectClass *parent_class = g_type_class_peek (G_TYPE_OBJECT); + + ni_route = NI_ROUTE (obj); + + /* Nada que hacer, por el momento */ + + parent_class->finalize (obj); +} + +static void ni_route_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { + NIRoute *ni_route = NI_ROUTE (object); + g_return_if_fail (NI_IS_ROUTE (object)); + + switch (prop_id) { + case PROP_NI_CLIENT: + ni_route->priv->ni_client = NI_CLIENT (g_value_get_object (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void ni_route_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { + NIRoute *ni_route = NI_ROUTE (object); + g_return_if_fail (NI_IS_ROUTE (object)); + + switch (prop_id) { + case PROP_NI_CLIENT: + g_value_set_object (value, ni_route->priv->ni_client); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void ni_route_class_init (NIRouteClass *klass) { + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->set_property = ni_route_set_property; + object_class->get_property = ni_route_get_property; + object_class->constructed = ni_route_constructed; + object_class->dispose = ni_route_dispose; + object_class->finalize = ni_route_finalize; + + obj_properties[PROP_NI_CLIENT] = g_param_spec_object ( + "ni-client", + "Network Inador Client", + "The client object", + NI_TYPE_CLIENT, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE); + + g_object_class_install_properties (object_class, N_PROPERTIES, obj_properties); + + signals[ROUTE_UPDATED] = g_signal_new ("updated", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, + NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0); +} + +static void ni_route_init (NIRoute *ni_route) { + NIRoutePrivate *priv = ni_route_get_instance_private (ni_route); + ni_route->priv = priv; + + /* initialize all public and private members to reasonable default values. + * They are all automatically initialized to 0 to begin with. */ + priv->ni_client = NULL; + priv->family = AF_UNSPEC; + priv->type = 0; + priv->table = 0; + priv->prefix = 0; + + priv->protocol = 0; + priv->tos = 0; + priv->scope = 0; + + priv->priority = 0; + + priv->has_prefsrc = FALSE; + priv->nh = 0; + priv->nexthops = NULL; + + memset (&priv->dest, 0, sizeof (priv->dest)); + memset (&priv->prefsrc, 0, sizeof (priv->prefsrc)); +} + +static gboolean ni_route_process_route_response (NIClient *ni_client, unsigned char *buffer, guint size, gpointer user_data) { + /* Tengo que revisar si los datos son de mi interés, para absorber el evento yo */ + NIRoute *ni_route = (NIRoute *) user_data; + uint8_t family, family_size; + uint8_t type, protocol, tos, scope, prefix; + uint32_t tabla, prioridad; + uint8_t num_nexthops; + gboolean has_prefsrc; + int pos, g; + NIRouteNH nexthops[16]; + struct_addr dest, prefsrc; + int was_update = 0; + + memset (nexthops, 0, sizeof (nexthops)); + family = buffer[2]; + + if (buffer[0] != NET_INADOR_TYPE_EVENT && buffer[1] != NET_INADOR_EVENT_ROUTE_ADDED) { + return FALSE; /* No es para mí */ + } + + if (family == AF_INET) { + family_size = sizeof (struct in_addr); + } else if (family == AF_INET6) { + family_size = sizeof (struct in6_addr); + } + + type = buffer[3]; + + memcpy (&tabla, &buffer[4], 4); + prefix = buffer[8]; + protocol = buffer[9]; + tos = buffer[10]; + scope = buffer[11]; + + has_prefsrc = FALSE; + if (buffer[12] & 0x01) { + has_prefsrc = TRUE; + } + + num_nexthops = buffer[13]; + + memcpy (&prioridad, &buffer[14], 4); + + memcpy (&dest, &buffer[18], family_size); + + pos = 18 + family_size; + if (has_prefsrc) { + memcpy (&prefsrc, &buffer[pos], family_size); + pos += family_size; + } + + /* Tengo suficientes datos, revisar si es mi ruta */ + if (ni_route->priv->family != family) return FALSE; + if (ni_route->priv->type != type) return FALSE; + if (ni_route->priv->table != tabla) return FALSE; + if (ni_route->priv->priority != prioridad) return FALSE; + + if (memcmp (&ni_route->priv->dest, &dest, family_size) != 0 || ni_route->priv->prefix != prefix) { + return FALSE; + } + + /* Terminar de leer los otros datos */ + if (num_nexthops > 16) { + num_nexthops = 16; + } + for (g = 0; g < num_nexthops; g++) { + nexthops[g].has_gw = FALSE; + if (buffer[pos] & 0x01) { + /* Tiene GW */ + nexthops[g].has_gw = TRUE; + } + pos++; + + nexthops[g].flags = buffer[pos]; + pos++; + + nexthops[g].weight = buffer[pos]; + pos++; + + pos++; /* El byte ignorado */ + + memcpy (&nexthops[g].out_index, &buffer[pos], 4); + pos +=4; + + if (nexthops[g].has_gw) { + memcpy (&nexthops[g].gw, &buffer[pos], family_size); + pos += family_size; + } + } + + /* Es mi ruta, procesar como actualización */ + if (ni_route->priv->protocol != protocol) { + was_update = 1; + ni_route->priv->protocol = protocol; + } + + if (ni_route->priv->tos != tos) { + was_update = 1; + ni_route->priv->tos = tos; + } + + if (ni_route->priv->scope != scope) { + was_update = 1; + ni_route->priv->scope = scope; + } + + if (ni_route->priv->has_prefsrc != has_prefsrc) { + ni_route->priv->has_prefsrc = has_prefsrc; + was_update = 1; + if (has_prefsrc == FALSE) { + memset (&ni_route->priv->prefsrc, 0, family_size); + } else { + memcpy (&ni_route->priv->prefsrc, &prefsrc, ni_route->priv->family_size); + } + } else if (ni_route->priv->has_prefsrc) { + was_update = 1; + memcpy (&ni_route->priv->prefsrc, &prefsrc, ni_route->priv->family_size); + } + + if (ni_route->priv->nh != num_nexthops) { + /* Liberar los next-hops anteriores y re-crear los nuevos */ + ni_route->priv->nh = num_nexthops; + + free (ni_route->priv->nexthops); + + ni_route->priv->nexthops = (NIRouteNH *) malloc (sizeof (NIRouteNH) * num_nexthops); + for (g = 0; g < num_nexthops; g++) { + /* Copiar cada next-hop */ + memcpy (&ni_route->priv->nexthops[g], &nexthops[g], sizeof (NIRouteNH)); + } + was_update = 1; + } else { + for (g = 0; g < num_nexthops; g++) { + /* Comparar brincos */ + if (memcmp (&ni_route->priv->nexthops[g], &nexthops[g], sizeof (NIRouteNH)) != 0) { + was_update = 1; + memcpy (&ni_route->priv->nexthops[g], &nexthops[g], sizeof (NIRouteNH)); + } + } + } + + /* Emitir mi señal de update */ + if (was_update) { + g_signal_emit (ni_route, signals[ROUTE_UPDATED], 0); + } + + /* Mi evento, ya lo procesé */ + return TRUE; +} + +/* Los métodos getters */ +NIClient *ni_route_get_client (NIRoute *ni_route) { + g_return_val_if_fail (NI_IS_ROUTE (ni_route), NULL); + + return ni_route->priv->ni_client; +} + +guint ni_route_get_route_type (NIRoute *ni_route) { + g_return_val_if_fail (NI_IS_ROUTE (ni_route), 0); + + return ni_route->priv->type; +} + +guint ni_route_get_family (NIRoute *ni_route) { + g_return_val_if_fail (NI_IS_ROUTE (ni_route), 0); + + return ni_route->priv->family; +} + +const struct_addr *ni_route_get_dest (NIRoute *ni_route) { + g_return_val_if_fail (NI_IS_ROUTE (ni_route), NULL); + + return &ni_route->priv->dest; +} + +guint ni_route_get_prefix (NIRoute *ni_route) { + g_return_val_if_fail (NI_IS_ROUTE (ni_route), 0); + + return ni_route->priv->prefix; +} + +uint32_t ni_route_get_table (NIRoute *ni_route) { + g_return_val_if_fail (NI_IS_ROUTE (ni_route), 0); + + return ni_route->priv->table; +} + +guint ni_route_get_num_nexthops (NIRoute *ni_route) { + g_return_val_if_fail (NI_IS_ROUTE (ni_route), 0); + + return ni_route->priv->nh; +} + +const NIRouteNH *ni_route_get_nexthops (NIRoute *ni_route) { + g_return_val_if_fail (NI_IS_ROUTE (ni_route), NULL); + + return ni_route->priv->nexthops; +} + +gboolean ni_route_has_prefsrc (NIRoute *ni_route) { + g_return_val_if_fail (NI_IS_ROUTE (ni_route), FALSE); + + return ni_route->priv->has_prefsrc; +} + +const struct_addr *ni_route_get_prefsrc (NIRoute *ni_route) { + g_return_val_if_fail (NI_IS_ROUTE (ni_route), NULL); + + return &ni_route->priv->prefsrc; +} + +guint ni_route_get_priority (NIRoute *ni_route) { + g_return_val_if_fail (NI_IS_ROUTE (ni_route), 0); + + return ni_route->priv->priority; +} + +/* +guint family; + guint type; + guint table; + struct_addr dest; + guint prefix; + + guint protocol, tos, scope; + + gboolean has_prefsrc; + struct_addr prefsrc; + guint priority; +*/ +NIRoute *ni_route_new (NIClient *ni_client, int family, int type, uint32_t table, struct_addr *dest, int prefix, unsigned char protocol, unsigned char tos, unsigned char scope, struct_addr *prefsrc, int priority, int num_nexthops, NIRouteNH *nexthops) { + NIRoute *ni_route; + int g; + + g_return_val_if_fail (NI_IS_CLIENT (ni_client), NULL); + g_return_val_if_fail (dest != NULL, NULL); + g_return_val_if_fail (num_nexthops > 0, NULL); + + ni_route = g_object_new (NI_TYPE_ROUTE, "ni-client", ni_client, NULL); + + if (family == AF_INET) { + ni_route->priv->family_size = sizeof (struct in_addr); + } else if (family == AF_INET6) { + ni_route->priv->family_size = sizeof (struct in6_addr); + } + + /* Establecer y copiar los valores */ + ni_route->priv->family = family; + ni_route->priv->type = type; + ni_route->priv->table = table; + ni_route->priv->prefix = prefix; + + memcpy (&ni_route->priv->dest, dest, ni_route->priv->family_size); + + ni_route->priv->protocol = protocol; + ni_route->priv->tos = tos; + ni_route->priv->scope = scope; + ni_route->priv->priority = priority; + + if (prefsrc != NULL) { + ni_route->priv->has_prefsrc = TRUE; + + memcpy (&ni_route->priv->prefsrc, prefsrc, ni_route->priv->family_size); + } + + ni_route->priv->nh = num_nexthops; + ni_route->priv->nexthops = (NIRouteNH *) malloc (sizeof (NIRouteNH) * num_nexthops); + for (g = 0; g < num_nexthops; g++) { + /* Copiar cada next-hop */ + memcpy (&ni_route->priv->nexthops[g], &nexthops[g], sizeof (NIRouteNH)); + } + + + return ni_route; +} + diff --git a/client-gtk/ni-route.h b/client-gtk/ni-route.h new file mode 100644 index 0000000..6e58cbc --- /dev/null +++ b/client-gtk/ni-route.h @@ -0,0 +1,91 @@ +/* + * ni-route.h + * This file is part of NetworkInador + * + * Copyright (C) 2021 - Gatuno + * + * NetworkInador 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. + * + * NetworkInador is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with NetworkInador; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +/* inclusion guard */ +#ifndef __NI_ROUTE_H__ +#define __NI_ROUTE_H__ + +#include + +#include + +#include +#include + +G_BEGIN_DECLS + +typedef struct _NIRouteClass NIRouteClass; +typedef struct _NIRoute NIRoute; +typedef struct _NIRoutePrivate NIRoutePrivate; + +/* + * Type declaration. + */ +#define NI_TYPE_ROUTE (ni_route_get_type ()) +#define NI_ROUTE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NI_TYPE_ROUTE, NIRoute)) +#define NI_ROUTE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), NI_TYPE_ROUTE, NIRouteClass)) +#define NI_IS_ROUTE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NI_TYPE_ROUTE)) +#define NI_IS_ROUTE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NI_TYPE_ROUTE)) +#define NI_ROUTE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), NI_TYPE_ROUTE, NIRouteClass)) + +GType ni_route_get_type (void); + +struct _NIRouteClass { + GObjectClass parent_class; + +}; + +struct _NIRoute { + GObject parent_instance; + + NIRoutePrivate *priv; +}; + +typedef struct _NIRouteNH { + gboolean has_gw; + struct_addr gw; + guint out_index; + guint weight; + guint flags; +} NIRouteNH; + +/* + * Method definitions. + */ + +NIRoute *ni_route_new (NIClient *ni_client, int family, int type, uint32_t table, struct_addr *dest, int prefix, unsigned char protocol, unsigned char tos, unsigned char scope, struct_addr *prefsrc, int priority, int num_nexthops, NIRouteNH *nexthops); + +NIClient *ni_route_get_client (NIRoute *ni_route); +guint ni_route_get_family (NIRoute *ni_route); +guint ni_route_get_route_type (NIRoute *ni_route); +const struct_addr *ni_route_get_dest (NIRoute *ni_route); +guint ni_route_get_prefix (NIRoute *ni_route); +uint32_t ni_route_get_table (NIRoute *ni_route); +guint ni_route_get_num_nexthops (NIRoute *ni_route); +const NIRouteNH *ni_route_get_nexthops (NIRoute *ni_route); +gboolean ni_route_has_prefsrc (NIRoute *ni_route); +const struct_addr *ni_route_get_prefsrc (NIRoute *ni_route); +guint ni_route_get_priority (NIRoute *ni_route); + +G_END_DECLS + +#endif /* __NI_ROUTE_H__ */ diff --git a/client-gtk/ni-window-interface.c b/client-gtk/ni-window-interface.c index 9a07e2d..b9195b3 100644 --- a/client-gtk/ni-window-interface.c +++ b/client-gtk/ni-window-interface.c @@ -482,7 +482,6 @@ static void ni_window_interface_clear_slave_click_cb (GtkWidget *widget, gpointe /* Eventos que vienen del network-inador */ static void ni_window_interface_addr_added_cb (NIWindowInterface *window_iface, NIIP *ni_ip) { - GtkTreeIter iter; char buffer[256], ip[128], local[128]; guint prefix, family, family_size; @@ -506,11 +505,9 @@ static void ni_window_interface_addr_added_cb (NIWindowInterface *window_iface, } if (family == AF_INET) { - gtk_list_store_insert (window_iface->priv->ipv4_store, &iter, -1); - gtk_list_store_set (window_iface->priv->ipv4_store, &iter, IP_STORE_COL_MAIN_IP, buffer, IP_STORE_COL_OBJECT, ni_ip, -1); + gtk_list_store_insert_with_values (window_iface->priv->ipv4_store, NULL, -1, IP_STORE_COL_MAIN_IP, buffer, IP_STORE_COL_OBJECT, ni_ip, -1); } else if (family == AF_INET6) { - gtk_list_store_insert (window_iface->priv->ipv6_store, &iter, -1); - gtk_list_store_set (window_iface->priv->ipv6_store, &iter, IP_STORE_COL_MAIN_IP, buffer, IP_STORE_COL_OBJECT, ni_ip, -1); + gtk_list_store_insert_with_values (window_iface->priv->ipv6_store, NULL, -1, IP_STORE_COL_MAIN_IP, buffer, IP_STORE_COL_OBJECT, ni_ip, -1); } } @@ -689,14 +686,12 @@ static void ni_window_interface_removed_from_bridge_cb (NIInterface *ni_interfac static void ni_window_interface_added_to_bridge_cb (NIInterface *ni_interface, guint new_master, gpointer data) { NIWindowInterface *window_iface = NI_WINDOW_INTERFACE (data); - GtkTreeIter iter; guint our_index; our_index = ni_interface_get_index (window_iface->priv->ni_interface); if (new_master == our_index) { /* Esta interfaz acaba de ser esclavizada a nuestra interfaz */ - gtk_list_store_insert (window_iface->priv->bridge_ports_store, &iter, -1); - gtk_list_store_set (window_iface->priv->bridge_ports_store, &iter, BRIDGE_PORT_STORE_COL_IFACE_NAME, ni_interface_get_name (ni_interface), BRIDGE_PORT_STORE_COL_OBJECT, ni_interface, -1); + gtk_list_store_insert_with_values (window_iface->priv->bridge_ports_store, NULL, -1, BRIDGE_PORT_STORE_COL_IFACE_NAME, ni_interface_get_name (ni_interface), BRIDGE_PORT_STORE_COL_OBJECT, ni_interface, -1); } } @@ -714,7 +709,6 @@ static void ni_window_interface_removed_from_bridge_cb (NIInterface *ni_interfac static void ni_window_interface_new_interface_cb (NIClient *ni_client, NIInterface *ni_interface, gpointer data) { NIWindowInterface *window_iface = NI_WINDOW_INTERFACE (data); guint our_index, master_other; - GtkTreeIter iter; our_index = ni_interface_get_index (window_iface->priv->ni_interface); @@ -725,8 +719,7 @@ static void ni_window_interface_new_interface_cb (NIClient *ni_client, NIInterfa master_other = ni_interface_get_master (ni_interface); if (master_other == our_index) { /* Esta interfaz está esclavizada a nuestra interfaz */ - gtk_list_store_insert (window_iface->priv->bridge_ports_store, &iter, -1); - gtk_list_store_set (window_iface->priv->bridge_ports_store, &iter, BRIDGE_PORT_STORE_COL_IFACE_NAME, ni_interface_get_name (ni_interface), BRIDGE_PORT_STORE_COL_OBJECT, ni_interface, -1); + gtk_list_store_insert_with_values (window_iface->priv->bridge_ports_store, NULL, -1, BRIDGE_PORT_STORE_COL_IFACE_NAME, ni_interface_get_name (ni_interface), BRIDGE_PORT_STORE_COL_OBJECT, ni_interface, -1); } } @@ -903,12 +896,21 @@ static void ni_window_interface_constructed (GObject *obj) { static void ni_window_interface_dispose (GObject *obj) { NIWindowInterface *window_iface; GObjectClass *parent_class = G_OBJECT_CLASS (g_type_class_peek (GTK_TYPE_WINDOW)); + NIClient *ni_client; window_iface = NI_WINDOW_INTERFACE (obj); g_object_unref (window_iface->priv->ni_interface); window_iface->priv->ni_interface = NULL; + /* Desconectar todos los manejadores de señal del ni_client */ + if (window_iface->priv->ni_interface != NULL) { + ni_client = ni_interface_get_client (window_iface->priv->ni_interface); + + g_signal_handlers_disconnect_by_data (window_iface->priv->ni_interface, window_iface); + g_signal_handlers_disconnect_by_data (ni_client, window_iface); + } + parent_class->dispose (obj); } diff --git a/client-gtk/ni-window-route.c b/client-gtk/ni-window-route.c new file mode 100644 index 0000000..65b9567 --- /dev/null +++ b/client-gtk/ni-window-route.c @@ -0,0 +1,642 @@ +/* + * ni-window-route.c + * This file is part of Network Inador + * + * Copyright (C) 2022 - Félix Arreola Rodríguez + * + * Network Inador 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. + * + * Network Inador is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Network Inador; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include "ni-window-route.h" + +#include "ni-client.h" +#include "ni-route.h" +#include "ni-interface.h" + +struct _NIWindowRoutePrivate { + NIClient *ni_client; + + int family; + + uint32_t table_filter; + GtkWidget *vbox, *tree_routes; + GtkListStore *routes_store; + GtkTreeModel *routes_filtered; + GtkListStore *tables_store; + + GtkWidget *del_route_button; + GtkWidget *combo_tables; +}; + +enum { + PROP_NI_CLIENT = 1, + + PROP_NI_FAMILY, + + PROP_NI_TABLE_FILTER, + + N_PROPERTIES +}; + +enum { + ROUTE_STORE_TABLE, + ROUTE_STORE_COL_DST_GW, + + ROUTE_STORE_COL_METRIC, + + ROUTE_STORE_COL_PREFSRC, + + // TODO: Agregar tipo de protocolo, tos, scope + ROUTE_STORE_COL_OBJECT, + + NUM_ROUTE_STORE_COLS +}; + +enum { + TABLE_STORE_TABLE, + TABLE_STORE_NAME, + + TABLE_STORE_IS_NEW, + + NUM_TABLE_STORE_COLS +}; + +struct _NIWindowRouteSearchTable { + gboolean found; + uint32_t tabla; + GtkTreeIter iter; +}; + +struct _NIWindowRouteSearchRoute { + gboolean found; + void *obj; + GtkTreeIter iter; +}; + +G_DEFINE_TYPE_WITH_PRIVATE (NIWindowRoute, ni_window_route, GTK_TYPE_WINDOW) + +static GParamSpec *obj_properties[N_PROPERTIES] = { NULL, }; + +static gboolean ni_window_route_foreach_tables_search_id (GtkTreeModel *tree_model, GtkTreePath *path, GtkTreeIter *iter, gpointer data); +static gboolean ni_window_route_foreach_routes_search_route (GtkTreeModel *tree_model, GtkTreePath *path, GtkTreeIter *iter, gpointer data); +static void ni_window_interface_new_ip_cb (NIClient *ni_client, NIRoute *ni_route, gpointer data); +static void ni_window_interface_delete_ip_cb (NIClient *ni_client, NIRoute *ni_route, gpointer data); + +static void ni_window_route_updated_route_cb (NIRoute *ni_route, gpointer data) { + NIWindowRoute *window_route = NI_WINDOW_ROUTE (data); + struct _NIWindowRouteSearchRoute for_search; + char buffer_ip[256]; + guint priority; + const struct_addr *dest; + int family; + + family = ni_route_get_family (ni_route); + + /* Preparar el pref-src, si lo tiene */ + buffer_ip[0] = 0; + if (ni_route_has_prefsrc (ni_route)) { + dest = ni_route_get_prefsrc (ni_route); + + inet_ntop (family, dest, buffer_ip, sizeof (buffer_ip)); + } + + priority = ni_route_get_priority (ni_route); + + for_search.found = FALSE; + for_search.obj = ni_route; + + gtk_tree_model_foreach (GTK_TREE_MODEL (window_route->priv->routes_store), ni_window_route_foreach_routes_search_route, &for_search); + + if (for_search.found == TRUE) { + /* Tengo fila, actualizar los valores */ + gtk_list_store_set (window_route->priv->routes_store, &for_search.iter, ROUTE_STORE_COL_METRIC, priority, ROUTE_STORE_COL_PREFSRC, buffer_ip, -1); + } +} + +static void ni_window_route_route_added_cb (NIWindowRoute *window_route, NIRoute *ni_route) { + int family, prefix; + char buffer_ip[256], buffer[512]; + const struct_addr *dest; + uint32_t table; + guint priority; + struct _NIWindowRouteSearchTable for_search; + + family = ni_route_get_family (ni_route); + dest = ni_route_get_dest (ni_route); + prefix = ni_route_get_prefix (ni_route); + table = ni_route_get_table (ni_route); + priority = ni_route_get_priority (ni_route); + + /* Preparar el dest-gw */ + inet_ntop (family, dest, buffer_ip, sizeof (buffer_ip)); + if (family == AF_INET && prefix == 32) { + snprintf (buffer, sizeof (buffer), "%s", buffer_ip); + } else if (family == AF_INET6 && prefix == 128) { + snprintf (buffer, sizeof (buffer), "%s", buffer_ip); + } else { + snprintf (buffer, sizeof (buffer), "%s/%i", buffer_ip, prefix); + } + + /* Preparar el pref-src, si lo tiene */ + buffer_ip[0] = 0; + if (ni_route_has_prefsrc (ni_route)) { + dest = ni_route_get_prefsrc (ni_route); + + inet_ntop (family, dest, buffer_ip, sizeof (buffer_ip)); + } + + /* Revisar que la tabla exista, si la tabla no existe, la creamos en el menú de la lista de tablas */ + for_search.found = FALSE; + for_search.tabla = table; + + gtk_tree_model_foreach (GTK_TREE_MODEL (window_route->priv->tables_store), ni_window_route_foreach_tables_search_id, &for_search); + + if (for_search.found == FALSE) { + /* Insertar el valor en el list store de las tablas */ + gtk_list_store_insert_with_values (window_route->priv->tables_store, NULL, -1, TABLE_STORE_TABLE, table, TABLE_STORE_NAME, NULL, TABLE_STORE_IS_NEW, FALSE, -1); + } + + gtk_list_store_insert_with_values (window_route->priv->routes_store, NULL, -1, ROUTE_STORE_TABLE, table, ROUTE_STORE_COL_DST_GW, buffer, ROUTE_STORE_COL_METRIC, priority, ROUTE_STORE_COL_PREFSRC, buffer_ip, ROUTE_STORE_COL_OBJECT, ni_route, -1); + + g_signal_connect (ni_route, "updated", G_CALLBACK (ni_window_route_updated_route_cb), window_route); +} + +/* Función para renderizar la tabla y el nombre con guion separado */ +void ni_window_route_cell_renderer_table_id (GtkCellLayout *cell_layout, GtkCellRenderer *cell, GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data) { + uint32_t tabla; + const gchar *name; + gboolean new; + char buffer[1024]; + + gtk_tree_model_get (tree_model, iter, TABLE_STORE_TABLE, &tabla, TABLE_STORE_NAME, &name, TABLE_STORE_IS_NEW, &new, -1); + + if (new == FALSE) { + if (name == NULL) { + g_snprintf (buffer, sizeof (buffer), "%i - (Sin nombre)", tabla); + } else { + g_snprintf (buffer, sizeof (buffer), "%i - %s", tabla, name); + } + g_object_set (cell, "text", buffer, NULL); + } else { + g_object_set (cell, "text", "Nueva tabla...", NULL); + } +} + +/* Función para renderizar la gateway */ +void ni_window_route_cell_renderer_gw_oiface (GtkTreeViewColumn *tree_column, GtkCellRenderer *cell, GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data) { + NIRoute *ni_route; + NIClient *ni_client; + NIInterface *ni_interface; + int family; + char buffer_ip[256], buffer[8192]; + int buffer_len; + int g, num_nexthops; + const NIRouteNH *nexthops; + + gtk_tree_model_get (tree_model, iter, ROUTE_STORE_COL_OBJECT, &ni_route, -1); + + family = ni_route_get_family (ni_route); + + ni_client = ni_route_get_client (ni_route); + num_nexthops = ni_route_get_num_nexthops (ni_route); + nexthops = ni_route_get_nexthops (ni_route); + buffer_len = 0; + for (g = 0; g < num_nexthops; g++) { + if (g != 0) { + /* Anexar la coma, */ + buffer_len += g_snprintf (&buffer[buffer_len], sizeof (buffer) - buffer_len, ", "); + } + + if (nexthops[g].has_gw) { + inet_ntop (family, &nexthops[g].gw, buffer_ip, sizeof (buffer_ip)); + + buffer_len += g_snprintf (&buffer[buffer_len], sizeof (buffer) - buffer_len, "%s ", buffer_ip); + } + + ni_interface = ni_client_get_interface_by_index (ni_client, nexthops[g].out_index); + if (ni_interface == NULL) { + buffer_len += g_snprintf (&buffer[buffer_len], sizeof (buffer) - buffer_len, "[¿?]"); + } else { + buffer_len += g_snprintf (&buffer[buffer_len], sizeof (buffer) - buffer_len, "[%s]", ni_interface_get_name (ni_interface)); + } + } + + g_object_set (cell, "text", buffer, NULL); +} + +static gboolean ni_window_route_delete_event (GtkWidget *widget, GdkEventAny *event) { + return gtk_widget_hide_on_delete (widget); +} + +static void ni_window_route_constructed (GObject *obj) { + GObjectClass *parent_class = G_OBJECT_CLASS (g_type_class_peek (GTK_TYPE_WINDOW)); + NIWindowRoute *window_route = NI_WINDOW_ROUTE (obj); + const GList *route_list = NULL, *g; + NIRoute *ni_route; + + parent_class->constructed (obj); + + if (window_route->priv->ni_client == NULL) return; + + /* Cambiar el título + name = g_strdup_printf ("Interfaz %s", ni_interface_get_name (window_iface->priv->ni_interface)); + gtk_window_set_title (GTK_WINDOW (obj), name); + g_free (name);*/ + + /* Conectar la señal de ruta agregada y ruta eliminada */ + g_signal_connect (window_route->priv->ni_client, "new-route", G_CALLBACK (ni_window_interface_new_ip_cb), window_route); + g_signal_connect (window_route->priv->ni_client, "delete-route", G_CALLBACK (ni_window_interface_delete_ip_cb), window_route); + + /* Recorrer las rutas, y procesar la información */ + if (window_route->priv->family == AF_INET) { + route_list = ni_client_get_routes_v4 (window_route->priv->ni_client); + } else if (window_route->priv->family == AF_INET6) { + route_list = ni_client_get_routes_v6 (window_route->priv->ni_client); + } + + for (g = route_list; g != NULL; g = g->next) { + ni_route = (NIRoute *) g->data; + + ni_window_route_route_added_cb (window_route, ni_route); + } +} + +static void ni_window_route_dispose (GObject *obj) { + NIWindowRoute *window_route; + GObjectClass *parent_class = G_OBJECT_CLASS (g_type_class_peek (GTK_TYPE_WINDOW)); + + window_route = NI_WINDOW_ROUTE (obj); + + g_object_unref (window_route->priv->ni_client); + window_route->priv->ni_client = NULL; + + parent_class->dispose (obj); +} + +static void ni_window_route_change_filter_table_id (NIWindowRoute *window_route, uint32_t new_table) { + /* Primero, guardar el valor nuevo */ + window_route->priv->table_filter = new_table; + + gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (window_route->priv->routes_filtered)); + + /* Notificar el cambio de propiedad */ + g_object_notify_by_pspec (G_OBJECT (window_route), obj_properties[PROP_NI_TABLE_FILTER]); +} + +static gboolean ni_window_route_foreach_tables_search_id (GtkTreeModel *tree_model, GtkTreePath *path, GtkTreeIter *iter, gpointer data) { + struct _NIWindowRouteSearchTable *for_search = (struct _NIWindowRouteSearchTable *) data; + uint32_t tabla; + gboolean is_new; + + gtk_tree_model_get (tree_model, iter, TABLE_STORE_TABLE, &tabla, TABLE_STORE_IS_NEW, &is_new, -1); + if (is_new) return FALSE; + + if (tabla == for_search->tabla) { + for_search->found = TRUE; + for_search->iter = *iter; + return TRUE; + } + + return FALSE; +} + +static gboolean ni_window_route_foreach_routes_search_route (GtkTreeModel *tree_model, GtkTreePath *path, GtkTreeIter *iter, gpointer data) { + struct _NIWindowRouteSearchRoute *for_search = (struct _NIWindowRouteSearchRoute *) data; + void *route; + + gtk_tree_model_get (tree_model, iter, ROUTE_STORE_COL_OBJECT, &route, -1); + + if (route == for_search->obj) { + for_search->found = TRUE; + for_search->iter = *iter; + return TRUE; + } + + return FALSE; +} + +static gint ni_window_route_sort_tables_func (GtkTreeModel *tree_model, GtkTreeIter *a, GtkTreeIter *b, gpointer user_data) { + uint32_t tabla_a, tabla_b; + gboolean is_new_a, is_new_b; + + gtk_tree_model_get (tree_model, a, TABLE_STORE_TABLE, &tabla_a, TABLE_STORE_IS_NEW, &is_new_a, -1); + gtk_tree_model_get (tree_model, b, TABLE_STORE_TABLE, &tabla_b, TABLE_STORE_IS_NEW, &is_new_b, -1); + + if (is_new_a) { + return 1; + } + + if (is_new_b) { + return -1; + } + + return tabla_a - tabla_b; +} + +static void ni_window_route_changed_combo_table_cb (GtkWidget *combo, gpointer data) { + NIWindowRoute *window_route = NI_WINDOW_ROUTE (data); + GtkTreeIter iter; + uint32_t tabla; + gboolean is_new; + struct _NIWindowRouteSearchTable for_search; + + gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo), &iter); + gtk_tree_model_get (GTK_TREE_MODEL (window_route->priv->tables_store), &iter, TABLE_STORE_TABLE, &tabla, TABLE_STORE_IS_NEW, &is_new, -1); + + if (is_new) { + /* TODO: Presentar el cuadro de dialogo de creación de nueva tabla */ + + /* Localizar el valor previo de la tabla, para regresar el combo */ + for_search.found = FALSE; + for_search.tabla = window_route->priv->table_filter; + + gtk_tree_model_foreach (GTK_TREE_MODEL (window_route->priv->tables_store), ni_window_route_foreach_tables_search_id, &for_search); + if (for_search.found) { + gtk_combo_box_set_active_iter (GTK_COMBO_BOX (combo), &for_search.iter); + } else { + gtk_combo_box_set_active_iter (GTK_COMBO_BOX (combo), NULL); + } + } else { + /* Aplicar el nuevo filtro */ + ni_window_route_change_filter_table_id (window_route, tabla); + } +} + +gboolean ni_window_route_tree_filter_routes_func (GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data) { + NIWindowRoute *window_route = NI_WINDOW_ROUTE (data); + uint32_t tabla; + + if (window_route->priv->table_filter == 0) return TRUE; + + gtk_tree_model_get (tree_model, iter, ROUTE_STORE_TABLE, &tabla, -1); + if (tabla == window_route->priv->table_filter) { + return TRUE; + } + + return FALSE; +} + +static void ni_window_interface_new_ip_cb (NIClient *ni_client, NIRoute *ni_route, gpointer data) { + NIWindowRoute *window_route = NI_WINDOW_ROUTE (data); + + ni_window_route_route_added_cb (window_route, ni_route); +} + +static void ni_window_interface_delete_ip_cb (NIClient *ni_client, NIRoute *ni_route, gpointer data) { + NIWindowRoute *window_route = NI_WINDOW_ROUTE (data); + struct _NIWindowRouteSearchRoute for_search; + + for_search.found = FALSE; + for_search.obj = ni_route; + + gtk_tree_model_foreach (GTK_TREE_MODEL (window_route->priv->routes_store), ni_window_route_foreach_routes_search_route, &for_search); + + if (for_search.found == TRUE) { + /* Tengo fila, actualizar los valores */ + gtk_list_store_remove (window_route->priv->routes_store, &for_search.iter); + } +} + +static void ni_window_route_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { + NIWindowRoute *window_route = NI_WINDOW_ROUTE (object); + g_return_if_fail (NI_IS_WINDOW_ROUTE (object)); + + switch (prop_id) { + case PROP_NI_CLIENT: + window_route->priv->ni_client = NI_CLIENT (g_value_get_object (value)); + g_object_ref (window_route->priv->ni_client); + break; + case PROP_NI_FAMILY: + window_route->priv->family = g_value_get_uint (value); + break; + case PROP_NI_TABLE_FILTER: + ni_window_route_change_filter_table_id (window_route, g_value_get_uint (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void ni_window_route_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { + NIWindowRoute *window_route = NI_WINDOW_ROUTE (object); + g_return_if_fail (NI_IS_WINDOW_ROUTE (object)); + + switch (prop_id) { + case PROP_NI_CLIENT: + g_value_set_object (value, window_route->priv->ni_client); + break; + case PROP_NI_FAMILY: + g_value_set_uint (value, window_route->priv->family); + break; + case PROP_NI_TABLE_FILTER: + g_value_set_uint (value, window_route->priv->table_filter); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void ni_window_route_class_init (NIWindowRouteClass *klass) { + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWindowClass *window_class = GTK_WINDOW_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->set_property = ni_window_route_set_property; + object_class->get_property = ni_window_route_get_property; + object_class->constructed = ni_window_route_constructed; + object_class->dispose = ni_window_route_dispose; + + widget_class->delete_event = ni_window_route_delete_event; + + obj_properties[PROP_NI_CLIENT] = g_param_spec_object ( + "ni-client", + "Network Inador Client", + "The client object", + NI_TYPE_CLIENT, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE); + + obj_properties[PROP_NI_FAMILY] = g_param_spec_uint ( + "family", + "Family", + "The routing family.", + 0, G_MAXUINT, 0, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE); + + obj_properties[PROP_NI_TABLE_FILTER] = g_param_spec_uint ( + "table-filter", + "Tabla filtrada", + "Tabla filtrada.", + 0, G_MAXUINT, 0, + G_PARAM_READWRITE); + + g_object_class_install_properties (object_class, N_PROPERTIES, obj_properties); +} + +GtkWidget *ni_window_route_create_tree_for_routes (GtkTreeModel *store, GtkWidget **tree) { + GtkWidget *scrolled; + GtkAdjustment *h, *v; + GtkTreeViewColumn *column; + GtkCellRenderer *renderer; + + *tree = gtk_tree_view_new_with_model (GTK_TREE_MODEL (store)); + + gtk_tree_view_set_grid_lines (GTK_TREE_VIEW (*tree), GTK_TREE_VIEW_GRID_LINES_HORIZONTAL); + h = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (*tree)); + v = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (*tree)); + + scrolled = gtk_scrolled_window_new (h, v); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); + + renderer = gtk_cell_renderer_text_new (); + column = gtk_tree_view_column_new_with_attributes ("Destino", renderer, "text", ROUTE_STORE_COL_DST_GW, NULL); + gtk_tree_view_column_set_resizable (GTK_TREE_VIEW_COLUMN (column), TRUE); + gtk_tree_view_append_column (GTK_TREE_VIEW (*tree), column); + + renderer = gtk_cell_renderer_text_new (); + column = gtk_tree_view_column_new (); + gtk_tree_view_column_set_resizable (GTK_TREE_VIEW_COLUMN (column), TRUE); + gtk_tree_view_column_set_title (GTK_TREE_VIEW_COLUMN (column), "Puerta de enlace"); + gtk_tree_view_column_pack_start (GTK_TREE_VIEW_COLUMN (column), renderer, TRUE); + gtk_tree_view_column_set_cell_data_func (GTK_TREE_VIEW_COLUMN (column), renderer, ni_window_route_cell_renderer_gw_oiface, NULL, NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW (*tree), column); + + renderer = gtk_cell_renderer_text_new (); + column = gtk_tree_view_column_new_with_attributes ("Métrica", renderer, "text", ROUTE_STORE_COL_METRIC, NULL); + gtk_tree_view_column_set_resizable (GTK_TREE_VIEW_COLUMN (column), TRUE); + gtk_tree_view_append_column (GTK_TREE_VIEW (*tree), column); + + renderer = gtk_cell_renderer_text_new (); + column = gtk_tree_view_column_new_with_attributes ("Dir. Origen", renderer, "text", ROUTE_STORE_COL_PREFSRC, NULL); + gtk_tree_view_column_set_resizable (GTK_TREE_VIEW_COLUMN (column), TRUE); + gtk_tree_view_append_column (GTK_TREE_VIEW (*tree), column); + + gtk_container_add (GTK_CONTAINER (scrolled), *tree); + + return scrolled; +} + +static void ni_window_route_init (NIWindowRoute *window_route) { + NIWindowRoutePrivate *priv = ni_window_route_get_instance_private (window_route); + window_route->priv = priv; + GtkWindow *window = GTK_WINDOW (window_route); + GtkWidget *hbox, *label, *button, *image; + GtkWidget *vbox, *scrolled, *vbox2; + GtkTreeSelection *selection; + GtkSizeGroup *size_l; + GtkTreeIter iter; + GtkCellRenderer *renderer; + + /* initialize all public and private members to reasonable default values. + * They are all automatically initialized to 0 to begin with. */ + priv->ni_client = NULL; + priv->family = 0; + gtk_window_set_title (window, "Tabla de ruteo"); + + priv->vbox = vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 5); + gtk_container_add (GTK_CONTAINER (window), priv->vbox); + + vbox2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 5); + gtk_box_pack_start (GTK_BOX (vbox), vbox2, FALSE, FALSE, 5); + + label = gtk_label_new ("Seleccione tabla de ruteo:"); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_box_pack_start (GTK_BOX (vbox2), label, FALSE, FALSE, 0); + + priv->tables_store = gtk_list_store_new (NUM_TABLE_STORE_COLS, G_TYPE_UINT, G_TYPE_STRING, G_TYPE_BOOLEAN); + gtk_list_store_insert_with_values (priv->tables_store, &iter, -1, TABLE_STORE_TABLE, 0, TABLE_STORE_NAME, "Todas", TABLE_STORE_IS_NEW, FALSE, -1); + gtk_list_store_insert_with_values (priv->tables_store, NULL, -1, TABLE_STORE_TABLE, 254, TABLE_STORE_NAME, "Main", TABLE_STORE_IS_NEW, FALSE, -1); + gtk_list_store_insert_with_values (priv->tables_store, NULL, -1, TABLE_STORE_TABLE, 255, TABLE_STORE_NAME, "Local", TABLE_STORE_IS_NEW, FALSE, -1); + gtk_list_store_insert_with_values (priv->tables_store, NULL, -1, TABLE_STORE_TABLE, 0, TABLE_STORE_NAME, NULL, TABLE_STORE_IS_NEW, TRUE, -1); + + /* Mantener ordenadas las tablas */ + gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE (priv->tables_store), ni_window_route_sort_tables_func, NULL, NULL); + gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (priv->tables_store), GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID, GTK_SORT_ASCENDING); + + priv->combo_tables = gtk_combo_box_new_with_model (GTK_TREE_MODEL (priv->tables_store)); + renderer = gtk_cell_renderer_text_new (); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (priv->combo_tables), renderer, TRUE); + gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (priv->combo_tables), renderer, ni_window_route_cell_renderer_table_id, NULL, NULL); + gtk_combo_box_set_active_iter (GTK_COMBO_BOX (priv->combo_tables), &iter); + gtk_box_pack_start (GTK_BOX (vbox2), priv->combo_tables, FALSE, FALSE, 0); + g_signal_connect (priv->combo_tables, "changed", G_CALLBACK (ni_window_route_changed_combo_table_cb), window_route); + + /* Preparar el list store */ + priv->routes_store = gtk_list_store_new (NUM_ROUTE_STORE_COLS, G_TYPE_UINT, G_TYPE_STRING, G_TYPE_UINT, G_TYPE_STRING, G_TYPE_POINTER); + priv->routes_filtered = gtk_tree_model_filter_new (GTK_TREE_MODEL (priv->routes_store), NULL); + gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (priv->routes_filtered), ni_window_route_tree_filter_routes_func, window_route, NULL); + + label = gtk_label_new ("Rutas:"); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 5); + gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, TRUE, 0); + + scrolled = ni_window_route_create_tree_for_routes (priv->routes_filtered, &priv->tree_routes); + gtk_box_pack_start (GTK_BOX (hbox), scrolled, TRUE, TRUE, 0); + + /* Botonera del tree view */ + vbox2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 5); + gtk_box_pack_start (GTK_BOX (hbox), vbox2, FALSE, FALSE, 0); + + button = gtk_button_new_from_icon_name ("list-add", GTK_ICON_SIZE_LARGE_TOOLBAR); + gtk_box_pack_start (GTK_BOX (vbox2), button, FALSE, FALSE, 0); + //g_signal_connect (button, "clicked", G_CALLBACK (ni_window_interface_addr_v6_button_add_cb), window_iface); + + priv->del_route_button = gtk_button_new_from_icon_name ("list-remove", GTK_ICON_SIZE_LARGE_TOOLBAR); + gtk_box_pack_start (GTK_BOX (vbox2), priv->del_route_button, FALSE, FALSE, 0); + //g_signal_connect (priv->del_route_button, "clicked", G_CALLBACK (ni_window_interface_addr_v6_button_del_cb), window_iface); + gtk_widget_set_sensitive (priv->del_route_button, FALSE); + + /* Conectar la señal de cambio de selección del tree view */ + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->tree_routes)); + //g_signal_connect (selection, "changed", G_CALLBACK (has_ip_selected_v6_cb), window_iface); + + /* La botonera inferior */ + hbox = gtk_button_box_new (GTK_ORIENTATION_HORIZONTAL); + gtk_button_box_set_layout (GTK_BUTTON_BOX (hbox), GTK_BUTTONBOX_END); + gtk_box_pack_start (GTK_BOX (priv->vbox), hbox, FALSE, FALSE, 0); + + /* El boton de cerrar */ + button = gtk_button_new_with_label ("Cerrar"); + g_signal_connect_swapped (button, "clicked", G_CALLBACK (gtk_widget_hide_on_delete), window); + image = gtk_image_new_from_icon_name ("window-close", GTK_ICON_SIZE_BUTTON); + gtk_button_set_image (GTK_BUTTON (button), image); + gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0); + + gtk_widget_show_all (priv->vbox); +} + +GtkWidget* ni_window_route_new (NIClient *ni_client, int family) { + NIWindowRoute *window_route; + + window_route = g_object_new (NI_TYPE_WINDOW_ROUTE, "type", GTK_WINDOW_TOPLEVEL, "ni-client", ni_client, "family", family, NULL); + + return GTK_WIDGET (window_route); +} + diff --git a/client-gtk/ni-window-route.h b/client-gtk/ni-window-route.h new file mode 100644 index 0000000..b837831 --- /dev/null +++ b/client-gtk/ni-window-route.h @@ -0,0 +1,72 @@ +/* + * ni-window-route.h + * This file is part of NetworkInador + * + * Copyright (C) 2021 - Gatuno + * + * NetworkInador 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. + * + * NetworkInador is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with NetworkInador; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +/* inclusion guard */ +#ifndef __NI_WINDOW_ROUTE_H__ +#define __NI_WINDOW_ROUTE_H__ + + +#include + +#include +#include + +#include "ni-client.h" +#include "ni-route.h" + +G_BEGIN_DECLS + +typedef struct _NIWindowRouteClass NIWindowRouteClass; +typedef struct _NIWindowRoute NIWindowRoute; +typedef struct _NIWindowRoutePrivate NIWindowRoutePrivate; + +/* + * Type declaration. + */ +#define NI_TYPE_WINDOW_ROUTE (ni_window_route_get_type ()) +#define NI_WINDOW_ROUTE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NI_TYPE_WINDOW_ROUTE, NIWindowRoute)) +#define NI_WINDOW_ROUTE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), NI_TYPE_WINDOW_ROUTE, NIWindowRouteClass)) +#define NI_IS_WINDOW_ROUTE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NI_TYPE_WINDOW_ROUTE)) +#define NI_IS_WINDOW_ROUTE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NI_TYPE_WINDOW_ROUTE)) +#define NI_WINDOW_ROUTE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), NI_TYPE_WINDOW_ROUTE, NIWindowRouteClass)) + +GType ni_window_route_get_type (void); + +struct _NIWindowRouteClass { + GtkWindowClass parent_class; + +}; + +struct _NIWindowRoute { + GtkWindow parent_instance; + + NIWindowRoutePrivate *priv; +}; + +/* + * Method definitions. + */ +GtkWidget* ni_window_route_new (NIClient *ni_client, int family); + +G_END_DECLS + +#endif /* __NI_WINDOW_ROUTE_H__ */