From 22f7a698423902635411381febc7f760a7e2229e Mon Sep 17 00:00:00 2001 From: short <> Date: Wed, 12 May 2004 17:13:45 +0000 Subject: [PATCH] Untested network daemon code implemented. --- configure.ac | 36 +++++ src/Makefile.am | 2 + src/network.c | 433 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/network.h | 38 +++++ src/ui-gnome.c | 70 +++------ 5 files changed, 531 insertions(+), 48 deletions(-) create mode 100644 src/network.c create mode 100644 src/network.h diff --git a/configure.ac b/configure.ac index d6cc911..5fc889d 100644 --- a/configure.ac +++ b/configure.ac @@ -121,6 +121,42 @@ AH_BOTTOM([ #define G_LOG_DOMAIN ((const gchar *)"UDPForward") /** + * udpforward_newn: + * @objp: Variable with the pointer to the objects wished to be allocated. + * Original value is discarded. + * @n: Numbers of objects to be allocated. Value %0 is permitted (%NULL assignment effect). + * + * Macro to allocate @n objects of type *@objp and to assign the resulting pointer to @objp. + * Allocated memory may contain garbage. + * + * @Returns: Initialized @objp value as the memory of size #sizeof(typeof(*objp))*n. + * Value %NULL is returned iff @n==%0; + */ +#define udpforward_newn(objp,n) ((objp)=g_new(typeof(*(objp)),(n))) + +/** + * udpforward_new: + * @objp: Variable with the pointer to the object wished to be allocated. + * Original value is discarded. + * + * Macro to allocate one object of type *@objp and to assign the resulting pointer to @objp. + * Allocated memory may contain garbage. Equivalent to udpforward_newn(objp,1) call. + * + * @Returns: Initialized @objp value as the memory of size #sizeof(typeof(*objp)). + * Value %NULL is never returned. + */ +#define udpforward_new(objp) (udpforward_newn((objp),1)) + +/** + * UDPFORWARD_MEMZERO: + * @objp: Pointer to the variable to be cleared. + * + * Clears the sizeof(*@objp) bytes of the given pointer with memset(). + * Pass _pointer_ to the object to be cleared. + */ +#define UDPFORWARD_MEMZERO(objp) (memset((objp),0,sizeof(*(objp)))) + +/** * udpforward_printf_alloca: * @format: Format string. See the sprintf() documentation. * @args...: Arguments for @format. See the sprintf() documentation. diff --git a/src/Makefile.am b/src/Makefile.am index 9007261..93b4ca4 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -32,6 +32,8 @@ GLADE_OUT= \ udpforward_SOURCES= \ main.c \ main.h \ + network.c \ + network.h \ ui-gnome.c \ ui-gnome.h \ $(GLADE_OUT) diff --git a/src/network.c b/src/network.c new file mode 100644 index 0000000..39d8f8f --- /dev/null +++ b/src/network.c @@ -0,0 +1,433 @@ +/* $Id$ + * UDP forwarding utility + * Copyright (C) 2004 Jan Kratochvil + * + * 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; exactly version 2 of June 1991 is required + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "network.h" + + +/* Config: */ +#define NETWORK_PATHNAME_PID "/var/run/udpforward.pid" +#define SOCK_SOURCE_CHECK_EVENTS (G_IO_IN) /* |G_IO_PRI */ +#define SOCK_SOURCE_CHECK_REVENTS (SOCK_SOURCE_CHECK_EVENTS) /* |G_IO_ERR|G_IO_HUP|G_IO_NVAL */ +#define SERVER_INADDR 0xEA021F51 /* paulina.vellum.cz = 81.31.2.234 */ +#define SERVER_PORT 9201 +#define CLIENT_TIMEOUT_SEC (5*60) + + +struct client { + GPollFD gpollfd; + struct sockaddr_in sockaddr_in_from; + guint timeout_id; + }; + +static GSource *sock_gsource; + +static GList *sock_client_list; /* of 'struct client *' */ +struct client *master; + +static gboolean write_daemon_running(pid_t pid); + +pid_t is_daemon_running(void) +{ +FILE *f; +char buf[LINE_MAX],*got; +int pid_int; + + if (sock_gsource) + return getpid(); + + if (!(f=fopen(NETWORK_PATHNAME_PID,"r"))) + goto err; + got=fgets(buf,sizeof(buf),f); + fclose(f); /* FIXME: error ignored */ + if (got!=buf) { +err_unlink: + write_daemon_running((pid_t)-1); /* unlink */ +err: + return (pid_t)-1; + } + pid_int=atoi(buf); + if (pid_int<=1) + goto err_unlink; + if (kill((pid_t)pid_int,0)) { + if (errno==ESRCH) + goto err_unlink; + goto err; + } + return (pid_t)pid_int; +} + +static gboolean write_daemon_running(pid_t pid) +{ +FILE *f; + + if (pid==(pid_t)-1) { + if (unlink(NETWORK_PATHNAME_PID)) { + g_warning(_("Error removing PID file \"%s\": %m"),NETWORK_PATHNAME_PID); + return FALSE; + } + return TRUE; + } + if (!(f=fopen(NETWORK_PATHNAME_PID,"w"))) { + g_warning(_("Error writing PID %d to \"%s\": %m"),(int)pid,NETWORK_PATHNAME_PID); + return FALSE; + } + fprintf(f,"%d\n",(int)pid); /* errors ignored */ + fclose(f); /* FIXME: error ignored */ + return TRUE; +} + +static void client_timeout_remove(struct client *client) +{ + g_return_if_fail(client!=NULL); + g_return_if_fail(client!=master); + + if (client->timeout_id) { +gboolean errgboolean; + + errgboolean=g_source_remove(client->timeout_id); + g_assert(errgboolean==TRUE); + client->timeout_id=0; + } +} + +static gboolean client_touch_timeout(struct client *client); + +static void client_touch(struct client *client) +{ + g_return_if_fail(client!=NULL); + g_return_if_fail(client!=master); + + client_timeout_remove(client); + client->timeout_id=g_timeout_add( + CLIENT_TIMEOUT_SEC*1000, /* interval; msec */ + (GSourceFunc)client_touch_timeout, /* function */ + client); /* data */ + g_assert(client->timeout_id!=0); +} + +static void client_destroy(struct client *client); + +static gboolean client_touch_timeout(struct client *client) +{ + g_return_val_if_fail(client!=NULL,FALSE); /* FALSE=>should be removed */ + g_return_val_if_fail(client!=master,FALSE); /* FALSE=>should be removed */ + + client_destroy(client); + + return FALSE; /* GSource should be removed */ +} + +static struct client *client_new(void); + +static void handle_master(struct client *master) +{ +ssize_t gotlen; +struct sockaddr_in sockaddr_in_from; +char packet[0x10000]; +struct client *client; +struct sockaddr_in sockaddr_in_server; +socklen_t sockaddr_in_from_len; + + g_return_if_fail(master!=NULL); + + sockaddr_in_from_len=sizeof(sockaddr_in_from); + while (-1!=(gotlen=recvfrom( + master->gpollfd.fd, /* s */ + packet, /* buf */ + sizeof(packet), /* len */ + 0, /* flags */ + (struct sockaddr *)&sockaddr_in_from, /* from */ + &sockaddr_in_from_len))) { /* fromlen */ +GList *clientl; + + if (sockaddr_in_from_len!=sizeof(sockaddr_in_from)) /* FIXME: errors reporting */ + continue; + /* FIXME: Performance: Ugly search... */ + for (clientl=sock_client_list;clientl;clientl=clientl->next) { + client=clientl->data; + if (client==master) + continue; + if (1 + && client->sockaddr_in_from.sin_family ==sockaddr_in_from.sin_family + && client->sockaddr_in_from.sin_port ==sockaddr_in_from.sin_port + && client->sockaddr_in_from.sin_addr.s_addr==sockaddr_in_from.sin_addr.s_addr) + break; + } + if (!clientl) { + client=client_new(); + client->sockaddr_in_from=sockaddr_in_from; + } + client_touch(client); + UDPFORWARD_MEMZERO(&sockaddr_in_server); + sockaddr_in_server.sin_family=AF_INET; + sockaddr_in_server.sin_port=htons(SERVER_PORT); + sockaddr_in_server.sin_addr.s_addr=htonl(SERVER_INADDR); + /* FIXME: errors checking */ + sendto( + client->gpollfd.fd, /* s */ + packet, /* msg */ + gotlen, /* len */ + 0, /* flags */ + (struct sockaddr *)&sockaddr_in_server, /* to */ + sizeof(sockaddr_in_server)); /* tolen */ + } +} + +static void handle_client(struct client *client) +{ +ssize_t gotlen; +struct sockaddr_in sockaddr_in_from; +char packet [0x10000]; +socklen_t sockaddr_in_from_len; + + g_return_if_fail(client!=NULL); + g_return_if_fail(master!=NULL); + + while (-1!=(gotlen=recvfrom( + client->gpollfd.fd, /* s */ + packet, /* buf */ + sizeof(packet), /* len */ + 0, /* flags */ + (struct sockaddr *)&sockaddr_in_from, /* from */ + &sockaddr_in_from_len))) { /* fromlen */ + if (sockaddr_in_from_len!=sizeof(sockaddr_in_from)) /* FIXME: errors reporting */ + continue; + client_touch(client); + /* FIXME: errors checking */ + sendto( + master->gpollfd.fd, /* s */ + packet, /* msg */ + gotlen, /* len */ + 0, /* flags */ + (struct sockaddr *)&client->sockaddr_in_from, /* to */ + sizeof(client->sockaddr_in_from)); /* tolen */ + } +} + +static gboolean sock_source_callback(gpointer data /* unused */) +{ +GList *clientl; + + for (clientl=sock_client_list;clientl;clientl=clientl->next) { +struct client *client=clientl->data; + + if (client->gpollfd.revents&SOCK_SOURCE_CHECK_REVENTS) { + if (client==master) + handle_master(client); + else + handle_client(client); + } + } + + return TRUE; /* the source should be kept active */ +} + +static gboolean sock_source_prepare(GSource *source,gint *timeout) +{ + *timeout=-1; + + return FALSE; +} + +static gboolean sock_source_check(GSource *source) +{ +GList *clientl; + + for (clientl=sock_client_list;clientl;clientl=clientl->next) { +struct client *client=clientl->data; + + if (client->gpollfd.revents&SOCK_SOURCE_CHECK_REVENTS) + return TRUE; + } + return FALSE; +} + +static gboolean sock_source_dispatch(GSource *source,GSourceFunc callback,gpointer user_data) +{ + g_assert(callback!=NULL); + return (*callback)(user_data); +} + +static GSourceFuncs sock_source_watch_funcs={ + sock_source_prepare, + sock_source_check, + sock_source_dispatch, + NULL, /* finalize */ + }; + +static void sock_gsource_destroy(void) +{ + while (sock_client_list) + client_destroy(sock_client_list->data); + + g_assert(master==NULL); + + if (sock_gsource) { + g_source_destroy(sock_gsource); + sock_gsource=NULL; + } +} + +static gboolean sock_gsource_new(void) +{ + if (sock_gsource) + return TRUE; + + g_assert(sock_client_list==NULL); + + /* attach sock_source_callback() to watch for any abnormalities + * on our open pipe 'parentheart_fds' and terminate the child if parent dies. + */ + if (!(sock_gsource=g_source_new(&sock_source_watch_funcs,sizeof(GSource)))) { + g_warning("g_source_new(): %m"); + return FALSE; + } + g_source_set_callback( + sock_gsource, /* source */ + sock_source_callback, /* func */ + NULL, /* data */ + NULL); /* notify */ + if (g_source_attach( + sock_gsource, /* source */ + NULL)) { /* context; NULL means 'default context' */ + g_warning("g_source_attach(gsource,NULL): %m"); + sock_gsource_destroy(); + return FALSE; + } + return TRUE; +} + +static struct client *client_new(void) +{ +struct client *client; +static unsigned long oneul=1; +int sock; + + if (!sock_gsource_new()) + return FALSE; + + if (-1==(sock=socket(AF_INET,SOCK_DGRAM,0))) { + g_warning("socket(AF_INET,SOCK_DGRAM): %m"); + return NULL; + } + if (ioctl(sock,FIONBIO,&oneul)) { /* non-blocking mode */ + g_warning("ioctl(sock,FIONBIO,&1): %m"); + close(sock); /* errors ignored */ + return NULL; + } + + udpforward_new(client); + client->gpollfd.fd=sock; + client->gpollfd.events=SOCK_SOURCE_CHECK_EVENTS; + client->gpollfd.revents=0; + client->timeout_id=0; + sock_client_list=g_list_prepend(sock_client_list,client); + g_source_add_poll(sock_gsource,&client->gpollfd); + + return client; +} + +static void client_destroy(struct client *client) +{ + g_return_if_fail(client!=NULL); + + if (!sock_gsource_new()) + return; + + if (client==master) { + master=NULL; + g_assert(client->timeout_id==0); + } + else + client_timeout_remove(client); + + g_source_remove_poll(sock_gsource,&client->gpollfd); + sock_client_list=g_list_remove(sock_client_list,client); + close(client->gpollfd.fd); /* errors ignored */ + g_free(client); +} + +gboolean network_start(gint port) +{ +pid_t daemon_pid; +struct sockaddr_in sockaddr_in; + + g_return_val_if_fail(port>=0,FALSE); + + if ((pid_t)-1!=(daemon_pid=is_daemon_running())) { + g_warning(_("Cannot start network daemon: Daemon is already running on PID %d"),(int)daemon_pid); + return FALSE; + } + g_assert(master==NULL); + if (!(master=client_new())) + return FALSE; + UDPFORWARD_MEMZERO(&sockaddr_in); + sockaddr_in.sin_family=AF_INET; + sockaddr_in.sin_port=htons(port); + sockaddr_in.sin_addr.s_addr=INADDR_ANY; + if (bind(master->gpollfd.fd,(struct sockaddr *)&sockaddr_in,sizeof(sockaddr_in))) { + g_warning("bind(sock,{AF_INET,INADDR_ANY:%d}): %m",(int)port); + sock_gsource_destroy(); + return FALSE; + } + write_daemon_running(getpid()); + return TRUE; +} + +gboolean network_stop(void) +{ +pid_t daemon_pid; +int errno_save; + + if ((pid_t)-1==(daemon_pid=is_daemon_running())) { + g_warning(_("Cannot stop network daemon: Daemon is not running")); + return FALSE; + } + if (daemon_pid==getpid()) { + sock_gsource_destroy(); + return TRUE; + } + errno=0; + kill(daemon_pid,SIGKILL); + errno_save=errno; + if (errno_save) { + g_warning(udpforward_printf_alloca(_("Unable to stop the daemon at PID %d: %s"), + (int)daemon_pid,strerror(errno_save))); + return FALSE; + } + return TRUE; +} diff --git a/src/network.h b/src/network.h new file mode 100644 index 0000000..c4f4006 --- /dev/null +++ b/src/network.h @@ -0,0 +1,38 @@ +/* $Id$ + * Include file for the core network functionality of UDPForward + * Copyright (C) 2004 Jan Kratochvil + * + * 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; exactly version 2 of June 1991 is required + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#ifndef _UDPFORWARD_NETWORK_H +#define _UDPFORWARD_NETWORK_H 1 + + +#include +#include +#include + + +G_BEGIN_DECLS + +pid_t is_daemon_running(void); +gboolean network_start(gint port); +gboolean network_stop(void); + +G_END_DECLS + + +#endif /* _UDPFORWARD_NETWORK_H */ diff --git a/src/ui-gnome.c b/src/ui-gnome.c index 38a8786..7b2691d 100644 --- a/src/ui-gnome.c +++ b/src/ui-gnome.c @@ -36,9 +36,10 @@ #include #include +#include "network.h" + /* Config: */ -#define PATHNAME_PID "/var/run/udpforward.pid" #define DAEMON_CHECK_INTERVAL_MS 500 #define PORT_RANGE_BEGIN 2048 #define PORT_RANGE_END 10240 @@ -52,42 +53,12 @@ static GnomeAppBar *AppBar; static GtkEntry *PortEntry; -static pid_t daemon_pid; - -static gboolean is_daemon_running(void) -{ -FILE *f; -char buf[LINE_MAX],*got; -int pid_int; - - daemon_pid=(pid_t)-1; - if (!(f=fopen(PATHNAME_PID,"r"))) - goto err; - got=fgets(buf,sizeof(buf),f); - fclose(f); /* FIXME: error ignored */ - if (got!=buf) { -err_unlink: - unlink(PATHNAME_PID); -err: - return FALSE; - } - pid_int=atoi(buf); - if (pid_int<=1) - goto err_unlink; - if (kill((pid_t)pid_int,0)) { - if (errno==ESRCH) - goto err_unlink; - goto err; - } - daemon_pid=(pid_t)pid_int; - return TRUE; -} - static void state_start_stop(void) { -gboolean daemon_running; +pid_t daemon_pid; - daemon_running=is_daemon_running(); + daemon_pid=is_daemon_running(); + gboolean daemon_running=((pid_t)-1!=daemon_pid); gtk_widget_set_sensitive(GTK_WIDGET(ButtonStart),!daemon_running); gtk_widget_set_sensitive(GTK_WIDGET(ButtonStop) , daemon_running); gtk_widget_set_sensitive(GTK_WIDGET(PortHBox) ,!daemon_running); @@ -109,7 +80,7 @@ void on_PortButtonRandom_clicked(GtkButton *button,gpointer user_data) g_return_if_fail(GTK_IS_BUTTON(button)); state_start_stop(); - if ((pid_t)-1!=daemon_pid) + if ((pid_t)-1!=is_daemon_running()) return; gtk_entry_set_text(PortEntry, udpforward_printf_alloca("%d",(int)g_random_int_range(PORT_RANGE_BEGIN,PORT_RANGE_END))); @@ -122,28 +93,30 @@ void on_AutostartCheckButton_toggled(GtkToggleButton *togglebutton,gpointer user void on_ButtonStart_clicked(GtkButton *button,gpointer user_data) { +const gchar *port_string; +char *endp; +long port_long; + g_return_if_fail(GTK_IS_BUTTON(button)); - state_start_stop(); - if ((pid_t)-1!=daemon_pid) + port_string=gtk_entry_get_text(PortEntry); + port_long=strtol(port_string,&endp,0); + if (endp && *endp) { + g_warning(_("Invalid port specification, offending string: %s"),endp); + return; + } + if (port_long<1 || port_long>=G_MAXINT || (endp && *endp)) { + g_warning(_("Invalid port integer number specification (%ld)"),port_long); return; + } + network_start(port_long); } void on_ButtonStop_clicked(GtkButton *button,gpointer user_data) { -int errno_save; - g_return_if_fail(GTK_IS_BUTTON(button)); - state_start_stop(); - if ((pid_t)-1==daemon_pid) - return; - errno=0; - kill(daemon_pid,SIGKILL); - errno_save=errno; - if (errno_save) - g_warning(udpforward_printf_alloca(_("Unable to stop the daemon at PID %d: %s"), - (int)daemon_pid,strerror(errno_save))); + network_stop(); } void on_ButtonHide_clicked(GtkButton *button,gpointer user_data) @@ -179,4 +152,5 @@ gboolean ui_gnome_init(void) void ui_gnome_interactive(void) { gtk_main(); + gtk_widget_destroy(GTK_WIDGET(App)); } -- 1.8.3.1