/* $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( /* returns 'guint' id */ 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; }