Always report only once per the session: Error writing PID ...
[udpgate.git] / src / network.c
index 8ca1ffa..59d3a9a 100644 (file)
@@ -1,6 +1,6 @@
 /* $Id$
- * UDP forwarding utility
- * Copyright (C) 2004 Jan Kratochvil <project-udpforward@jankratochvil.net>
+ * UDP Gateway utility network layer
+ * Copyright (C) 2004 Jan Kratochvil <project-udpgate@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
 #include <string.h>
 #include <glib/galloca.h>
 #include <glib/gprintf.h>
+#include <glib/grand.h>
 
 #include "network.h"
+#include "packet.h"
+#include "main.h"      /* for optarg_verbose */
 
 
 /* Config: */
-#define NETWORK_PATHNAME_PID "/var/run/udpforward.pid"
+#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 0xEA021F51       /* paulina.vellum.cz = 81.31.2.234 */
-#define SERVER_PORT   9201
+#define SERVER_INADDR 0xC37AD054       /* mms2.org = 195.122.208.84; host order */
+#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_BASE 5
+#define PROBE_TIMEOUT_SEC_MAX 3500
+
+
+void (*network_notify_hostip)(guint32 hostip_guint32);
 
 
 struct client {
@@ -55,8 +65,13 @@ struct client {
 
 static GSource *sock_gsource;
 
-static GList *sock_client_list;        /* of 'struct client *' */
-struct client *master;
+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 guint probe_timeout_sec_now;
+guint probe_timeout_sec_max=G_MAXUINT;
+static gint port_local;        /* for 'probe' resends */
 
 static gboolean write_daemon_running(pid_t pid);
 
@@ -96,13 +111,19 @@ FILE *f;
 
        if (pid==(pid_t)-1) {
                if (unlink(NETWORK_PATHNAME_PID)) {
-                       g_warning(_("Error removing PID file \"%s\": %m"),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);
+static gboolean once=TRUE;
+
+               if (once) {
+                       once=FALSE;
+                       g_warning(_("Error writing PID %d to \"%s\": %m"),(int)pid,NETWORK_PATHNAME_PID);
+                       }
                return FALSE;
                }
        fprintf(f,"%d\n",(int)pid);     /* errors ignored */
@@ -118,6 +139,8 @@ static void client_timeout_remove(struct client *client)
        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;
@@ -126,56 +149,145 @@ gboolean errgboolean;
 
 static gboolean client_touch_timeout(struct client *client);
 
-static void client_touch(struct client *client)
+static void client_touch(struct client *client,guint timeout_sec)
 {
        g_return_if_fail(client!=NULL);
        g_return_if_fail(client!=master);
+       g_return_if_fail(timeout_sec>0);
 
        client_timeout_remove(client);
        client->timeout_id=g_timeout_add(
-                       CLIENT_TIMEOUT_SEC*1000,        /* interval; msec */
+                       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 probe_send(struct client *probe,gint port_local);
 
 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);
+       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) {
+               if (probe_timeout_sec_now<probe_timeout_sec_max) {
+                       probe_timeout_sec_now*=PROBE_TIMEOUT_SEC_BASE;
+                       g_assert(probe_timeout_sec_max==G_MAXUINT || probe_timeout_sec_max<PROBE_TIMEOUT_SEC_MAX);
+                       probe_timeout_sec_now=MIN(probe_timeout_sec_now,probe_timeout_sec_max);
+                       probe_timeout_sec_now=MIN(probe_timeout_sec_now,PROBE_TIMEOUT_SEC_MAX);
+                       if (probe_send(probe,port_local)) {
+                               client_touch(probe,probe_timeout_sec_now);      /* timeout */
+                               return FALSE;   /* GSource should be removed */
+                               }
+                       /* failure FALLTHRU */
+                       }
+               network_stop();
+               /* Never destroy 'client' now - it has been destroyed by network_stop()! */
+               g_warning(_("No probe response from the server, stopping the daemon. Please check your public Internet connectivity."));
+               }
+       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)
 {
+       g_return_if_fail(master!=NULL);
+
+       for (;;) {
+char packet[0x10000];
 ssize_t gotlen;
 struct sockaddr_in sockaddr_in_from;
-char packet[0x10000];
-struct client *client;
+struct client *client=NULL     /* Prevent false positive: might be used uninitialized */;
 struct sockaddr_in sockaddr_in_server;
-socklen_t sockaddr_in_from_len;
+socklen_t sockaddr_in_from_length;
+GList *clientl;
 
-       g_return_if_fail(master!=NULL);
+               sockaddr_in_from_length=sizeof(sockaddr_in_from);
+               if (-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 */
+                       break;
 
-       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_length!=sizeof(sockaddr_in_from))  /* FIXME: errors reporting */
+                       continue;
 
-               if (sockaddr_in_from_len!=sizeof(sockaddr_in_from))     /* FIXME: errors reporting */
+               if (packet_recognized(packet,gotlen)) {
+                       handle_master_probe(packet,gotlen,&sockaddr_in_from);
+                       continue;
+                       }
+               /* Not yet initialized by 'probe' reply - drop it. */
+               if (probe) {
+                       if (optarg_verbose)
+                               g_message(_("Data packet received from %s but no probe reply yet; dropping packet."),
+                                               SOCKADDR_IN_TO_STRING(&sockaddr_in_from));
                        continue;
+                       }
+
                /* FIXME: Performance: Ugly search... */
                for (clientl=sock_client_list;clientl;clientl=clientl->next) {
                        client=clientl->data;
@@ -191,8 +303,8 @@ GList *clientl;
                        client=client_new();
                        client->sockaddr_in_from=sockaddr_in_from;
                        }
-               client_touch(client);
-               UDPFORWARD_MEMZERO(&sockaddr_in_server);
+               client_touch(client,CLIENT_TIMEOUT_SEC);
+               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);
@@ -207,26 +319,55 @@ GList *clientl;
                }
 }
 
-static void handle_client(struct client *client)
+static void handle_probe(struct client *probe)
 {
+       g_return_if_fail(probe!=NULL);
+
+       for (;;) {
 ssize_t gotlen;
 struct sockaddr_in sockaddr_in_from;
-char packet [0x10000];
-socklen_t sockaddr_in_from_len;
+char packet[0x10000];
+socklen_t sockaddr_in_from_length;
 
+               sockaddr_in_from_length=sizeof(sockaddr_in_from);
+               if (-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 */
+                       break;
+               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)
+{
        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 */
+       for (;;) {
+ssize_t gotlen;
+struct sockaddr_in sockaddr_in_from;
+char packet [0x10000];
+socklen_t sockaddr_in_from_length;
+
+               sockaddr_in_from_length=sizeof(sockaddr_in_from);
+               if (-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 */
+                       break;
+               if (sockaddr_in_from_length!=sizeof(sockaddr_in_from))  /* FIXME: errors reporting */
                        continue;
-               client_touch(client);
+               client_touch(client,CLIENT_TIMEOUT_SEC);
                /* FIXME: errors checking */
                sendto(
                                master->gpollfd.fd,     /* s */
@@ -246,8 +387,10 @@ GList *clientl;
 struct client *client=clientl->data;
 
                if (client->gpollfd.revents&SOCK_SOURCE_CHECK_REVENTS) {
-                       if (client==master)
+                       /**/ if (client==master)
                                handle_master(client);
+                       else if (client==probe)
+                               handle_probe(client);
                        else
                                handle_client(client);
                        }
@@ -295,13 +438,14 @@ static void sock_gsource_destroy(void)
                client_destroy(sock_client_list->data);
 
        g_assert(master==NULL);
+       g_assert(probe==NULL);
 
        if (sock_gsource) {
                g_source_destroy(sock_gsource);
                sock_gsource=NULL;
                }
 
-       write_daemon_running((pid_t)-1);        /* unlink */
+       write_daemon_running((pid_t)-1);        /* unlink; errors ignored */
 }
 
 static gboolean sock_gsource_new(void)
@@ -342,8 +486,8 @@ 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");
+       if (-1==(sock=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP))) {
+               g_warning("socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP): %m");
                return NULL;
                }
        if (ioctl(sock,FIONBIO,&oneul)) {       /* non-blocking mode */
@@ -352,7 +496,7 @@ int sock;
                return NULL;
                }
 
-       udpforward_new(client);
+       udpgate_new(client);
        client->gpollfd.fd=sock;
        client->gpollfd.events=SOCK_SOURCE_CHECK_EVENTS;
        client->gpollfd.revents=0;
@@ -360,6 +504,9 @@ int sock;
        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;
 }
 
@@ -370,43 +517,146 @@ static void client_destroy(struct client *client)
        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
+       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);
 }
 
-gboolean network_start(gint port)
+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;
+}
+
+static gboolean master_start(gint port)
 {
-pid_t daemon_pid;
 struct sockaddr_in sockaddr_in;
 
        g_return_val_if_fail(port>=0,FALSE);
+       g_return_val_if_fail(master==NULL,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);
+       /* Setup 'master': */
        if (!(master=client_new()))
                return FALSE;
-       UDPFORWARD_MEMZERO(&sockaddr_in);
+       UDPGATE_MEMZERO(&sockaddr_in);
        sockaddr_in.sin_family=AF_INET;
        sockaddr_in.sin_port=htons(port);
-       sockaddr_in.sin_addr.s_addr=INADDR_ANY;
+       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);
+               return FALSE;
+               }
+       return TRUE;
+}
+
+static gboolean probe_start(gint port)
+{
+       g_return_val_if_fail(port>=0,FALSE);
+       g_return_val_if_fail(probe==NULL,FALSE);
+
+       /* Setup 'probe': */
+       if (!(probe=client_new()))
+               return FALSE;
+       port_local=port;
+       if (!probe_send(probe,port)) {
+               client_destroy(probe);
+               return FALSE;
+               }
+       probe_timeout_sec_now=PROBE_TIMEOUT_SEC_BASE;
+       client_touch(probe,probe_timeout_sec_now);      /* timeout */
+       return TRUE;
+}
+
+gboolean network_start(gint port)
+{
+pid_t daemon_pid;
+
+       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;
+               }
+       if (!master_start(port)) {
                sock_gsource_destroy();
                return FALSE;
                }
-       write_daemon_running(getpid());
+       if (!probe_start(port)) {
+               sock_gsource_destroy();
+               return FALSE;
+               }
+       write_daemon_running(getpid()); /* errors ignored */
+       if (network_notify_hostip)
+               (*network_notify_hostip)(0);
+       return TRUE;
+}
+
+gboolean optarg_port_set_string(const gchar *port_string)
+{
+char *endp;
+long port_long;
+
+       g_return_val_if_fail(port_string!=NULL,FALSE);
+
+       port_long=strtol(port_string,&endp,0);
+       if (endp && *endp) {
+               g_warning(_("Invalid port specification, offending string: %s"),endp);
+               return FALSE;
+               }
+       if (port_long<1 || port_long>=G_MAXINT || (endp && *endp)) {
+               g_warning(_("Invalid port integer number specification (%ld)"),port_long);
+               return FALSE;
+               }
+       optarg_port=port_long;
        return TRUE;
 }
 
@@ -421,15 +671,61 @@ int errno_save;
                }
        if (daemon_pid==getpid()) {
                sock_gsource_destroy();
-               return TRUE;
+               goto ok;
                }
        errno=0;
        kill(daemon_pid,SIGKILL);
        errno_save=errno;
+       write_daemon_running((pid_t)-1);        /* unlink; errors ignored */
        if (errno_save)  {
-               g_warning(udpforward_printf_alloca(_("Unable to stop the daemon at PID %d: %s"),
+               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 */
+       /* Unable to contact the server, aborting. */
+       return FALSE;
+}