/* $Id$ * UDP Gateway utility network layer * 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 #include "network.h" #include "packet.h" #include "main.h" /* for optarg_verbose */ /* Config: */ #define NETWORK_PATHNAME_PID "/var/run/udpgate.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 0x511F02EA /* paulina.vellum.cz = 81.31.2.234; host order */ #undef SERVER_INADDR #define SERVER_INADDR 0x7F000001 #define SERVER_PORT 9201 /* host order */ #define PROBE_INADDR SERVER_INADDR /* host order */ #define PROBE_PORT 8201 /* host order */ #define CLIENT_TIMEOUT_SEC (5*60) #define PROBE_TIMEOUT_SEC (5) void (*network_notify_hostip)(guint32 hostip_guint32); 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 *', incl. 'master' and 'probe' */ static struct client *master; /* no 'timeout' permitted */ static struct client *probe; /* 'timeout' permitted */ static guint32 probe_unique; 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)) { if (errno!=ENOENT) 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; if (optarg_verbose) g_message(_("Client fd %d removed timeout id %d"),client->gpollfd.fd,client->timeout_id); 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==probe ? PROBE_TIMEOUT_SEC*1000 : CLIENT_TIMEOUT_SEC*1000), /* interval; msec */ (GSourceFunc)client_touch_timeout, /* function */ client); /* data */ if (optarg_verbose) g_message(_("Client fd %d new timeout id %d"),client->gpollfd.fd,client->timeout_id); 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 */ if (optarg_verbose) g_message(_("Client fd %d timeout id %d occured/entered"),client->gpollfd.fd,client->timeout_id); /* Do not destroy the timeout in client_destroy(). * It would crash GLib - we remove it be returning FALSE from here. */ g_assert(client->timeout_id!=0); client->timeout_id=0; if (client==probe) { network_stop(); /* Never destroy 'client' now - it has been destroyed by network_stop()! */ } else { client_destroy(client); } if (optarg_verbose) g_message(_("Client timeout occurance finish")); return FALSE; /* GSource should be removed */ } static void handle_master_probe(const void *packet,size_t gotlen,const struct sockaddr_in *sockaddr_in_from) { GHashTable *got_hash; gpointer got_unique_gpointer; gpointer hostip_gpointer; guint32 hostip_guint32; g_return_if_fail(packet!=NULL); g_return_if_fail(sockaddr_in_from!=NULL); if (!probe) return; if (!(got_hash=packet_disassembly(packet,gotlen))) return; if (!(g_hash_table_lookup_extended( got_hash, /* hash_table */ GUINT_TO_POINTER(PACKET_ELEM_TYPE_DATA_GUINT32), /* lookup_key */ NULL, /* orig_key */ &got_unique_gpointer))) { err_packet_disassembly_destroy_got_hash: packet_disassembly_destroy(got_hash); return; } if (GPOINTER_TO_UINT(got_unique_gpointer)!=probe_unique) goto err_packet_disassembly_destroy_got_hash; if (!(g_hash_table_lookup_extended( got_hash, /* hash_table */ GUINT_TO_POINTER(PACKET_ELEM_TYPE_CLIENT_INADDR), /* lookup_key */ NULL, /* orig_key */ &hostip_gpointer))) goto err_packet_disassembly_destroy_got_hash; hostip_guint32=GPOINTER_TO_UINT(hostip_gpointer); packet_disassembly_destroy(got_hash); client_destroy(probe); if (network_notify_hostip) (*network_notify_hostip)(hostip_guint32); } 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_length; g_return_if_fail(master!=NULL); sockaddr_in_from_length=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_length))) { /* fromlen */ GList *clientl; if (sockaddr_in_from_length!=sizeof(sockaddr_in_from)) /* FIXME: errors reporting */ continue; if (packet_recognized(packet,gotlen)) { handle_master_probe(packet,gotlen,&sockaddr_in_from); 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); UDPGATE_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_probe(struct client *probe) { ssize_t gotlen; struct sockaddr_in sockaddr_in_from; char packet[0x10000]; socklen_t sockaddr_in_from_length; g_return_if_fail(probe!=NULL); sockaddr_in_from_length=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_length))) { /* fromlen */ if (sockaddr_in_from_length!=sizeof(sockaddr_in_from)) /* FIXME: errors reporting */ continue; /* Probe socket should have no response; maybe some ICMP errors - ignored. */ } } static void handle_client(struct client *client) { ssize_t gotlen; struct sockaddr_in sockaddr_in_from; char packet [0x10000]; socklen_t sockaddr_in_from_length; 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_length))) { /* fromlen */ if (sockaddr_in_from_length!=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 if (client==probe) handle_probe(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; } write_daemon_running((pid_t)-1); /* unlink; errors ignored */ } 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; } udpgate_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); if (optarg_verbose) g_message(_("Client fd %d created"),client->gpollfd.fd); return client; } static void client_destroy(struct client *client) { g_return_if_fail(client!=NULL); if (!sock_gsource_new()) return; if (optarg_verbose) g_message(_("Client fd %d timeout id %d destroy enter"),client->gpollfd.fd,client->timeout_id); if (client==master) { master=NULL; g_assert(client->timeout_id==0); } else { if (client==probe) probe=NULL; 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 */ if (optarg_verbose) g_message(_("Client fd %d timeout id %d destroy finish"),client->gpollfd.fd,client->timeout_id); g_free(client); } static gboolean probe_send(struct client *probe,gint port_local) { struct sockaddr_in sockaddr_in_server; GHashTable *probe_hash; gpointer packet; size_t packet_length; g_return_val_if_fail(probe!=NULL,FALSE); probe_unique=g_random_int(); probe_hash=g_hash_table_new( g_direct_hash, /* hash_func */ g_direct_equal); /* key_equal_func */ g_hash_table_insert(probe_hash,GUINT_TO_POINTER(PACKET_ELEM_TYPE_CLIENT_PORT) ,GUINT_TO_POINTER(port_local)); g_hash_table_insert(probe_hash,GUINT_TO_POINTER(PACKET_ELEM_TYPE_DATA_GUINT32),GUINT_TO_POINTER(probe_unique)); packet=packet_assembly(&packet_length,probe_hash); g_hash_table_destroy(probe_hash); if (!packet) return FALSE; UDPGATE_MEMZERO(&sockaddr_in_server); sockaddr_in_server.sin_family=AF_INET; sockaddr_in_server.sin_port=htons(PROBE_PORT); sockaddr_in_server.sin_addr.s_addr=htonl(PROBE_INADDR); /* FIXME: errors checking */ sendto( probe->gpollfd.fd, /* s */ packet, /* msg */ packet_length, /* len */ 0, /* flags */ (struct sockaddr *)&sockaddr_in_server, /* to */ sizeof(sockaddr_in_server)); /* tolen */ return TRUE; } 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; } /* Setup 'master': */ g_assert(master==NULL); if (!(master=client_new())) return FALSE; UDPGATE_MEMZERO(&sockaddr_in); sockaddr_in.sin_family=AF_INET; sockaddr_in.sin_port=htons(port); sockaddr_in.sin_addr.s_addr=htonl(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); err_sock_gsource_destroy: sock_gsource_destroy(); return FALSE; } /* Setup 'probe': */ if (!(probe=client_new())) goto err_sock_gsource_destroy; probe_send(probe,port); client_touch(probe); /* timeout */ write_daemon_running(getpid()); /* errors ignored */ if (network_notify_hostip) (*network_notify_hostip)(0); 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(); goto ok; } errno=0; kill(daemon_pid,SIGKILL); errno_save=errno; if (errno_save) { g_warning(udpgate_printf_alloca(_("Unable to stop the daemon at PID %d: %s"), (int)daemon_pid,strerror(errno_save))); return FALSE; } ok: if (network_notify_hostip) (*network_notify_hostip)(0); return TRUE; } static GMainLoop *gmainloop; static void network_detach_network_notify_hostip(guint32 hostip_guint32) { if (!hostip_guint32) g_main_loop_quit(gmainloop); } gboolean network_detach(void) { pid_t daemon_pid,forked_pid; if ((pid_t)-1==(daemon_pid=is_daemon_running())) return TRUE; if (getpid()!=daemon_pid) return TRUE; if (!optarg_no_fork) { if ((pid_t)-1==(forked_pid=fork())) { g_warning("fork(2): %m"); return FALSE; } if (forked_pid) { /* parent */ return TRUE; } write_daemon_running(getpid()); /* errors ignored */ optarg_verbose=0; close(STDIN_FILENO); close(STDOUT_FILENO); close(STDERR_FILENO); setpgrp(); setsid(); } network_notify_hostip=network_detach_network_notify_hostip; gmainloop=g_main_loop_new( NULL, /* context */ TRUE); /* is_running; ignored */ g_main_loop_run(gmainloop); /* loop */ g_warning(_("Unable to contact the server, aborting")); return FALSE; }