Untested network daemon code implemented.
authorshort <>
Wed, 12 May 2004 17:13:45 +0000 (17:13 +0000)
committershort <>
Wed, 12 May 2004 17:13:45 +0000 (17:13 +0000)
configure.ac
src/Makefile.am
src/network.c [new file with mode: 0644]
src/network.h [new file with mode: 0644]
src/ui-gnome.c

index d6cc911..5fc889d 100644 (file)
@@ -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.
index 9007261..93b4ca4 100644 (file)
@@ -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 (file)
index 0000000..39d8f8f
--- /dev/null
@@ -0,0 +1,433 @@
+/* $Id$
+ * UDP forwarding utility
+ * Copyright (C) 2004 Jan Kratochvil <project-udpforward@jankratochvil.net>
+ * 
+ * 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 <glib/gmessages.h>
+#include <glib/gmain.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <glib/glist.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <signal.h>
+#include <errno.h>
+#include <glib/giochannel.h>
+#include <sys/ioctl.h>
+#include <string.h>
+#include <glib/galloca.h>
+#include <glib/gprintf.h>
+
+#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 (file)
index 0000000..c4f4006
--- /dev/null
@@ -0,0 +1,38 @@
+/* $Id$
+ * Include file for the core network functionality of UDPForward
+ * Copyright (C) 2004 Jan Kratochvil <project-udpforward@jankratochvil.net>
+ * 
+ * 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 <glib/gtypes.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+
+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 */
index 38a8786..7b2691d 100644 (file)
 #include <string.h>
 #include <gtk/gtkmain.h>
 
+#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));
 }