From c333f350d703997b67db67b8a578ea4bc70089fa Mon Sep 17 00:00:00 2001 From: Mohammed Morsi Date: Wed, 20 May 2009 10:49:06 -0400 Subject: [PATCH] connect to the ovirt-vnc-proxy server to access a vm's vnc --- Makefile.am | 2 +- internal.h | 30 ++++++ main.c | 24 ++++- tunnel.c | 319 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 370 insertions(+), 5 deletions(-) create mode 100644 tunnel.c diff --git a/Makefile.am b/Makefile.am index 1247729..b5a3e01 100644 --- a/Makefile.am +++ b/Makefile.am @@ -18,7 +18,7 @@ bin_PROGRAMS = ovirt-viewer -ovirt_viewer_SOURCES = main.c wui_thread.c internal.h +ovirt_viewer_SOURCES = main.c wui_thread.c tunnel.c internal.h ovirt_viewer_CFLAGS = $(OVIRT_VIEWER_CFLAGS) -DCAINFO='"$(CAINFO)"' ovirt_viewer_LDADD = $(OVIRT_VIEWER_LIBS) diff --git a/internal.h b/internal.h index 04512f9..80e675e 100644 --- a/internal.h +++ b/internal.h @@ -39,6 +39,21 @@ extern gboolean debug; } \ } while (0) +/* Verbose messages are always compiled in, but have to + * be turned on using the --verbose command line switch. + */ +extern gboolean verbose; + +#define VERBOSE(fs,...) \ + do { \ + if (verbose) { \ + fprintf (stderr, "%s:%d: [thread %p] ", __FILE__, __LINE__, \ + g_thread_self ()); \ + fprintf (stderr, (fs), ## __VA_ARGS__); \ + fprintf (stderr, "\n"); \ + } \ + } while (0) + /* String equality tests, suggested by Jim Meyering. */ #define STREQ(a,b) (strcmp((a),(b)) == 0) #define STRCASEEQ(a,b) (strcasecmp((a),(b)) == 0) @@ -53,6 +68,12 @@ extern gboolean debug; extern const char *cainfo; extern gboolean check_cert; +/* server we're connecting to */ +extern const char* hostname; + +/* vm currently in focus */ +extern struct vm* vm_in_focus; + /* Communications between the main thread and the WUI thread. For * an explanation of the threading model, please see the comment in * main(). @@ -144,6 +165,15 @@ extern gboolean wui_thread_has_valid_vmlist (void); */ extern gboolean wui_thread_is_busy (void); + +/* Communications between the main thread and the tunnel thread.*/ +extern void start_tunnel (void); +extern void stop_tunnel (void); + +/* port which local tunnel is listening on */ +extern int tunnel_port; + + /* Returns true if the main vm list contains a * running vm w/ the same name as specified one */ diff --git a/main.c b/main.c index 356d379..2962f3f 100644 --- a/main.c +++ b/main.c @@ -55,6 +55,8 @@ gboolean debug = 0; +gboolean verbose = 0; + /* Usually /etc/pki/tls/certs/ca-bundle.crt unless overridden during * configure or on the command line. */ @@ -68,6 +70,10 @@ gboolean check_cert = FALSE; // do we want this enabled by default ? */ static GSList *vmlist = NULL; +/* internal.h shared constructs */ +const char* hostname; +struct vm* vm_in_focus; + /* Private functions. */ static void start_ui (void); static GtkWidget *menu_item_new (int which_menu); @@ -198,6 +204,8 @@ static const GOptionEntry options[] = { "check the SSL certificate of the server", NULL }, { "debug", 'd', 0, G_OPTION_ARG_NONE, &debug, "turn on debugging messages", NULL }, + { "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, + "turn on verbose messages", NULL }, { "version", 'V', 0, G_OPTION_ARG_NONE, &print_version, "display version and exit", NULL }, { NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL } @@ -225,6 +233,10 @@ main (int argc, char *argv[]) * processed in the main thread - see: * http://mail.gnome.org/archives/gtk-app-devel-list/2007-March/msg00232.html * + * A tunnel thread is also started to locally listen for vnc packets + * and make them proxyable, adding the vm name, before forwarding onto + * the server + * * Note that under Win32 you must confine all Gtk/Gdk interactions * to a single thread - see: * http://developer.gimp.org/api/2.0/gdk/gdk-Threads.html @@ -267,6 +279,7 @@ main (int argc, char *argv[]) gtk_main (); stop_wui_thread (); + stop_tunnel(); exit (0); } @@ -510,6 +523,7 @@ help_about (GtkWidget *menu) const char *authors[] = { "Richard W.M. Jones ", "Daniel P. Berrange ", + "Mohammed Morsi ", NULL }; @@ -557,7 +571,6 @@ connect_to_wui_on_enter (GtkWidget *widget, gpointer data) static void connect_to_wui (GtkWidget *widget, gpointer data) { - const char *hostname; char *uri; int len; @@ -570,6 +583,7 @@ connect_to_wui (GtkWidget *widget, gpointer data) snprintf (uri, len, HTTPS "://%s/ovirt", hostname); wui_thread_send_connect (uri); + start_tunnel(); } static void @@ -606,7 +620,6 @@ connect_to_vm (GtkWidget *widget, gpointer _vm) int i, uuidlen, len, fd; GtkWidget *child; const char *label; - const char* hostname; char *label2; char new_title[97]; // 47 chars for title + 50 for vm name @@ -637,9 +650,12 @@ connect_to_vm (GtkWidget *widget, gpointer _vm) return; } + // FIXME on notebook tab switch, change vm_in_focus + vm_in_focus = vm; + /* This VM isn't in the notebook already, so create a new console. */ - hostname = gtk_entry_get_text (GTK_ENTRY (ca_hostname)); - fd = viewer_open_vnc_socket(hostname, vm->forward_vnc_port); + DEBUG ("connecting to local tunnel on port %i", tunnel_port); + fd = viewer_open_vnc_socket("127.0.0.1", tunnel_port); if (fd == -1) return; /* We've already given an error. */ child = vnc_display_new (); diff --git a/tunnel.c b/tunnel.c new file mode 100644 index 0000000..a03ef0b --- /dev/null +++ b/tunnel.c @@ -0,0 +1,319 @@ +/* ovirt viewer console application + * Copyright (C) 2008 Red Hat Inc. + * Written by Mohammed Morsi + * + * 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* ovirt-viewer starts listening on network port to + * encapsulate vnc packets including the vm's name + * so as to be able to be proxied. + * + * This operation takes place in another thread + * which can be started/stopped by calling + * start_tunnel / stop_tunnel. + * + * An additional connection thread is created and maintained + * internally for each vm / vnc connection open in ovirt-viewer + * establishing a connection w/ the ovirt server. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "internal.h" + +/* constants */ + +// port to try to listen on, if we can't, increment until we find one we can +const int PORT_RANGE_START = 5600; + +// max length of a vm name +const int VM_NAME_MAX_LEN = 250; + +// max length of vnc data +const int VNC_DATA_MAX_LEN = 800000; + +// port which to connect to on ovirt server +const int OVIRT_SERVER_PORT = 5900; + +/* Private thread functions */ +static gpointer tunnel_thread(gpointer data); +static gpointer client_server_thread(gpointer data); +static gpointer server_client_thread(gpointer data); + +/* Other private functions */ +static void close_socket(gpointer _socket, gpointer data); +static void wait_for_thread(gpointer _thread, gpointer data); + +/* tunnel and main threads */ +static GThread *tunnel_gthread = NULL; +static GThread *main_gthread = NULL; + +/* list of communication threads */ +static GSList *communication_threads = NULL; + +/* list of sockets */ +static GSList *sockets = NULL; + +/* thread termination flag */ +static gboolean run_tunnel = FALSE; + +/* internal.h shared constructs */ +int tunnel_port; + +///////////////// + +/** public implementations **/ + +/* start tunnel thread */ +void +start_tunnel(void) +{ + GError *error = NULL; + + DEBUG ("starting the tunnel thread"); + + assert (tunnel_gthread == NULL); + + run_tunnel = TRUE; + + main_gthread = g_thread_self (); + + tunnel_gthread = g_thread_create (tunnel_thread, NULL, TRUE, &error); + if (error) { + g_print ("%s\n", error->message); + g_error_free (error); + exit (1); + } +}; + +/* stop tunnel thread */ +void +stop_tunnel(void) +{ + if(!run_tunnel) + return; + + DEBUG ("stopping the tunnel thread"); + + assert (tunnel_gthread != NULL); + ASSERT_IS_MAIN_THREAD (); + + run_tunnel = FALSE; + + g_slist_foreach(sockets, close_socket, NULL); + + (void) g_thread_join (tunnel_gthread); + tunnel_gthread = NULL; +}; + +///////////////// + +/** private implementations **/ + +/* the tunnel thread */ +static gpointer +tunnel_thread (gpointer _data) +{ + //char vm_data[VM_NAME_MAX_LEN]; + int local_server_socketfd, ovirt_server_socket, client_socketfd; + unsigned int local_server_len, client_len, ovirt_server_len; + + struct sockaddr_in local_server_address; + struct sockaddr_in ovirt_server_address; + struct sockaddr_in client_address; + + GThread *client_server_gthread = NULL; + GThread *server_client_gthread = NULL; + + int sockets_param[2]; + int * c_socket; + + DEBUG ("tunnel thread starting up"); + + // ovirt server address + ovirt_server_address.sin_family = PF_INET; + ovirt_server_address.sin_addr.s_addr = inet_addr(hostname); + ovirt_server_address.sin_port = htons(OVIRT_SERVER_PORT); + ovirt_server_len = sizeof(ovirt_server_address); + + // create local net socket + local_server_socketfd = socket(PF_INET, SOCK_STREAM, 0); + c_socket = malloc(sizeof(int)); *c_socket = local_server_socketfd; + sockets = g_slist_prepend(sockets, c_socket); + + // local server address + tunnel_port = PORT_RANGE_START; + local_server_address.sin_family = PF_INET; + local_server_address.sin_addr.s_addr = inet_addr("127.0.0.1"); + local_server_address.sin_port = htons(tunnel_port); + local_server_len = sizeof(local_server_address); + + // increment ports until one is available + while(bind(local_server_socketfd, (struct sockaddr*)&local_server_address, local_server_len) < 0){ + tunnel_port += 1; + local_server_address.sin_port += htons(tunnel_port); + } + + DEBUG ("tunnel bound to local port %i", tunnel_port); + + // increase client buffer size? + listen(local_server_socketfd, 5); + + while(run_tunnel) { + // accept a client connection + DEBUG("tunnel accepting"); + client_len = sizeof(client_address); + client_socketfd = accept(local_server_socketfd, (struct sockaddr*)&client_address, &client_len); + if(client_socketfd < 0){ + DEBUG("tunnel accept failed"); + break; + } + // TODO check accept return value for err + c_socket = malloc(sizeof(int)); *c_socket = client_socketfd; + sockets = g_slist_prepend(sockets, c_socket); + + DEBUG ("client connected to tunnel"); + + // establish connection w/ ovirt server + ovirt_server_socket = socket(PF_INET, SOCK_STREAM, 0); + c_socket = malloc(sizeof(int)); *c_socket = ovirt_server_socket; + sockets = g_slist_prepend(sockets, c_socket); + DEBUG ("connecting to ovirt server %s on %i", hostname, OVIRT_SERVER_PORT); + if(connect(ovirt_server_socket, (struct sockaddr*)&ovirt_server_address, ovirt_server_len) < 0){ + DEBUG ("could not connect to ovirt server"); + break; + //return NULL; + } + DEBUG ("connected to ovirt server"); + + sockets_param[0] = ovirt_server_socket; + sockets_param[1] = client_socketfd; + + // launch thread for client -> server traffic + client_server_gthread = g_thread_create (client_server_thread, + &sockets_param, TRUE, NULL); + + // launch thread for server -> client traffic + server_client_gthread = g_thread_create (server_client_thread, + &sockets_param, TRUE, NULL); + + communication_threads = g_slist_prepend(communication_threads, client_server_gthread); + communication_threads = g_slist_prepend(communication_threads, server_client_gthread); + + // send target vm for this session + //strcpy(vm_data, vm_in_focus->description); + DEBUG ("sending vm %s", vm_in_focus->description); + write(ovirt_server_socket, vm_in_focus->description, strlen(vm_in_focus->description)); + + } + + DEBUG("terminating tunnel thread"); + + // wait for connection threads to finish + g_slist_foreach(communication_threads, wait_for_thread, NULL); + + DEBUG ("tunnel thread completed"); + return NULL; +}; + +/* the tunnel thread */ +static gpointer +client_server_thread (gpointer _data){ + int nbytes; + char vnc_data[VNC_DATA_MAX_LEN]; + + int ovirt_server_socket = ((int*)_data)[0], + client_socket = ((int*)_data)[1]; + + DEBUG ("client/server thread starting up"); + + while(run_tunnel){ + VERBOSE( "accepting client data"); + + // grab vnc data + nbytes = read(client_socket, vnc_data, VNC_DATA_MAX_LEN); + if(nbytes <= 0){ + DEBUG ( "error reading data from client" ); + break; + } + VERBOSE ("read %i bytes from client", nbytes); + + // send network_data onto server + nbytes = write(ovirt_server_socket, vnc_data, nbytes); + if(nbytes <= 0){ + DEBUG ( "error writing data to server" ); + break; + } + VERBOSE ("wrote %i bytes to server", nbytes); + } + + DEBUG ("client/server thread completed"); + return NULL; +}; + +/* the server thread */ +static gpointer +server_client_thread (gpointer _data){ + char vnc_data[VNC_DATA_MAX_LEN]; + + int ovirt_server_socket = ((int*)_data)[0], + client_socket = ((int*)_data)[1]; + + int nbytes; + + DEBUG ("server/client thread starting up"); + + while(run_tunnel){ + // grab vnc data + nbytes = read(ovirt_server_socket, vnc_data, VNC_DATA_MAX_LEN); + if(nbytes <= 0){ + DEBUG ( "error reading data from server" ); + break; + } + VERBOSE ("read %i bytes from server", nbytes); + + // send network_data onto client + nbytes = write(client_socket, vnc_data, nbytes); + if(nbytes <= 0){ + DEBUG ( "error writing data to client" ); + break; + } + VERBOSE ("wrote %i bytes to client", nbytes); + } + + DEBUG ("server/client thread completed"); + return NULL; +}; + +static void close_socket(gpointer _socket, gpointer data){ + shutdown(*(int*) _socket, 2); + close(*(int*) _socket); + free((int*) _socket); +}; + +static void wait_for_thread(gpointer _thread, gpointer data){ + g_thread_join((GThread*)_thread); +}; -- 1.8.3.1