--- /dev/null
+/* $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;
+}
#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
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);
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)));
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)
void ui_gnome_interactive(void)
{
gtk_main();
+ gtk_widget_destroy(GTK_WIDGET(App));
}