Cleanup getenv(3) by GLib compatible functions.
[udpgate.git] / src / network.c
index c902ce1..07a3061 100644 (file)
 #include <glib/galloca.h>
 #include <glib/gprintf.h>
 #include <glib/grand.h>
+#include <glib/gutils.h>
 
 #include "network.h"
 #include "packet.h"
 #include "main.h"      /* for optarg_verbose */
+#include "pathname.h"
 
 
 /* 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 0x7F000001
-#undef  SERVER_INADDR
-#define SERVER_INADDR 0x511F02EA       /* paulina.vellum.cz = 81.31.2.234; host order */
+#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 (5)
+#define PROBE_TIMEOUT_SEC_BASE 5
+#define PROBE_TIMEOUT_SEC_MAX 3500
 
 
 void (*network_notify_hostip)(guint32 hostip_guint32);
 
 
+static G_CONST_RETURN gchar *pid_pathname(void)
+{
+static const gchar *static_pathname;
+static const char *user_val;
+
+       if (!static_pathname)
+               user_val=g_get_user_name();
+       return pathname_find(&static_pathname,
+                       G_STRINGIFY(LOCALSTATEDIR) "/run",PACKAGE ".pid",
+                       (!user_val ? NULL : g_get_tmp_dir()),
+                                       (!user_val ? NULL : udpgate_printf_alloca(".%s-%s.pid",user_val,PACKAGE)),
+                       NULL);
+}
+
+
 struct client {
        GPollFD gpollfd;
        struct sockaddr_in sockaddr_in_from;
@@ -70,6 +85,9 @@ static GList *sock_client_list;       /* of 'struct client *', incl. 'master' and 'pro
 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);
 
@@ -82,7 +100,7 @@ int pid_int;
        if (sock_gsource)
                return getpid();
 
-       if (!(f=fopen(NETWORK_PATHNAME_PID,"r")))
+       if (!(f=fopen(pid_pathname(),"r")))
                goto err;
        got=fgets(buf,sizeof(buf),f);
        fclose(f);      /* FIXME: error ignored */
@@ -108,15 +126,20 @@ static gboolean write_daemon_running(pid_t pid)
 FILE *f;
 
        if (pid==(pid_t)-1) {
-               if (unlink(NETWORK_PATHNAME_PID)) {
+               if (unlink(pid_pathname())) {
                        if (errno!=ENOENT)
-                               g_warning(_("Error removing PID file \"%s\": %m"),NETWORK_PATHNAME_PID);
+                               g_warning(_("Error removing PID file \"%s\": %m"),pid_pathname());
                        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);
+       if (!(f=fopen(pid_pathname(),"w"))) {
+static gboolean once=TRUE;
+
+               if (once) {
+                       once=FALSE;
+                       g_warning(_("Error writing PID %d to \"%s\": %m"),(int)pid,pid_pathname());
+                       }
                return FALSE;
                }
        fprintf(f,"%d\n",(int)pid);     /* errors ignored */
@@ -142,14 +165,15 @@ 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==probe ? PROBE_TIMEOUT_SEC*1000 : CLIENT_TIMEOUT_SEC*1000),     /* interval; msec */
+                       timeout_sec*1000,       /* interval; msec */
                        (GSourceFunc)client_touch_timeout,      /* function */
                        client);        /* data */
        if (optarg_verbose)
@@ -158,6 +182,7 @@ static void client_touch(struct client *client)
 }
 
 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)
 {
@@ -174,6 +199,17 @@ static gboolean client_touch_timeout(struct client *client)
        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."));
@@ -260,6 +296,13 @@ GList *clientl;
                        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) {
@@ -276,7 +319,7 @@ GList *clientl;
                        client=client_new();
                        client->sockaddr_in_from=sockaddr_in_from;
                        }
-               client_touch(client);
+               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);
@@ -340,7 +383,7 @@ socklen_t sockaddr_in_from_length;
                        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 */
@@ -411,6 +454,7 @@ 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);
@@ -549,39 +593,70 @@ size_t packet_length;
        return TRUE;
 }
 
-gboolean network_start(gint port)
+static gboolean master_start(gint port)
 {
-pid_t daemon_pid;
 struct sockaddr_in sockaddr_in;
+uint16_t port_use;
 
        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);
+       port_use=port;
+       if (port < 0 || port_use != port) {
+               g_warning(_("Port value %d is not valid for IPv4!"),(int)port);
                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_port=htons(port_use);
        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();
+               g_warning("bind(sock,{AF_INET,INADDR_ANY:%u}): %m",(unsigned)port_use);
                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()))
-               goto err_sock_gsource_destroy;
-       probe_send(probe,port);
-       client_touch(probe);    /* timeout */
+               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;
+               }
+       if (!probe_start(port)) {
+               sock_gsource_destroy();
+               return FALSE;
+               }
        write_daemon_running(getpid()); /* errors ignored */
        if (network_notify_hostip)
                (*network_notify_hostip)(0);