Fixed g_source_attach() error checking.
[udpgate.git] / src / network.c
1 /* $Id$
2  * UDP forwarding utility
3  * Copyright (C) 2004 Jan Kratochvil <project-udpforward@jankratochvil.net>
4  * 
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; exactly version 2 of June 1991 is required
8  * 
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  * 
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17  */
18
19
20 #include "config.h"
21
22 #include <glib/gmessages.h>
23 #include <glib/gmain.h>
24 #include <sys/socket.h>
25 #include <netinet/in.h>
26 #include <glib/glist.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <sys/types.h>
30 #include <signal.h>
31 #include <errno.h>
32 #include <glib/giochannel.h>
33 #include <sys/ioctl.h>
34 #include <string.h>
35 #include <glib/galloca.h>
36 #include <glib/gprintf.h>
37
38 #include "network.h"
39
40
41 /* Config: */
42 #define NETWORK_PATHNAME_PID "/var/run/udpforward.pid"
43 #define SOCK_SOURCE_CHECK_EVENTS (G_IO_IN)      /* |G_IO_PRI */
44 #define SOCK_SOURCE_CHECK_REVENTS (SOCK_SOURCE_CHECK_EVENTS)    /* |G_IO_ERR|G_IO_HUP|G_IO_NVAL */
45 #define SERVER_INADDR 0xEA021F51        /* paulina.vellum.cz = 81.31.2.234 */
46 #define SERVER_PORT   9201
47 #define CLIENT_TIMEOUT_SEC (5*60)
48
49
50 struct client {
51         GPollFD gpollfd;
52         struct sockaddr_in sockaddr_in_from;
53         guint timeout_id;
54         };
55
56 static GSource *sock_gsource;
57
58 static GList *sock_client_list; /* of 'struct client *' */
59 struct client *master;
60
61 static gboolean write_daemon_running(pid_t pid);
62
63 pid_t is_daemon_running(void)
64 {
65 FILE *f;
66 char buf[LINE_MAX],*got;
67 int pid_int;
68
69         if (sock_gsource)
70                 return getpid();
71
72         if (!(f=fopen(NETWORK_PATHNAME_PID,"r")))
73                 goto err;
74         got=fgets(buf,sizeof(buf),f);
75         fclose(f);      /* FIXME: error ignored */
76         if (got!=buf) {
77 err_unlink:
78                 write_daemon_running((pid_t)-1);        /* unlink */
79 err:
80                 return (pid_t)-1;
81                 }
82         pid_int=atoi(buf);
83         if (pid_int<=1)
84                 goto err_unlink;
85         if (kill((pid_t)pid_int,0)) {
86                 if (errno==ESRCH)
87                         goto err_unlink;
88                 goto err;
89                 }
90         return (pid_t)pid_int;
91 }
92
93 static gboolean write_daemon_running(pid_t pid)
94 {
95 FILE *f;
96
97         if (pid==(pid_t)-1) {
98                 if (unlink(NETWORK_PATHNAME_PID)) {
99                         g_warning(_("Error removing PID file \"%s\": %m"),NETWORK_PATHNAME_PID);
100                         return FALSE;
101                         }
102                 return TRUE;
103                 }
104         if (!(f=fopen(NETWORK_PATHNAME_PID,"w"))) {
105                 g_warning(_("Error writing PID %d to \"%s\": %m"),(int)pid,NETWORK_PATHNAME_PID);
106                 return FALSE;
107                 }
108         fprintf(f,"%d\n",(int)pid);     /* errors ignored */
109         fclose(f);      /* FIXME: error ignored */
110         return TRUE;
111 }
112
113 static void client_timeout_remove(struct client *client)
114 {
115         g_return_if_fail(client!=NULL);
116         g_return_if_fail(client!=master);
117
118         if (client->timeout_id) {
119 gboolean errgboolean;
120
121                 errgboolean=g_source_remove(client->timeout_id);
122                 g_assert(errgboolean==TRUE);
123                 client->timeout_id=0;
124                 }
125 }
126
127 static gboolean client_touch_timeout(struct client *client);
128
129 static void client_touch(struct client *client)
130 {
131         g_return_if_fail(client!=NULL);
132         g_return_if_fail(client!=master);
133
134         client_timeout_remove(client);
135         client->timeout_id=g_timeout_add(
136                         CLIENT_TIMEOUT_SEC*1000,        /* interval; msec */
137                         (GSourceFunc)client_touch_timeout,      /* function */
138                         client);        /* data */
139         g_assert(client->timeout_id!=0);
140 }
141
142 static void client_destroy(struct client *client);
143
144 static gboolean client_touch_timeout(struct client *client)
145 {
146         g_return_val_if_fail(client!=NULL,FALSE);       /* FALSE=>should be removed */
147         g_return_val_if_fail(client!=master,FALSE);     /* FALSE=>should be removed */
148
149         client_destroy(client);
150
151         return FALSE;   /* GSource should be removed */
152 }
153
154 static struct client *client_new(void);
155
156 static void handle_master(struct client *master)
157 {
158 ssize_t gotlen;
159 struct sockaddr_in sockaddr_in_from;
160 char packet[0x10000];
161 struct client *client;
162 struct sockaddr_in sockaddr_in_server;
163 socklen_t sockaddr_in_from_len;
164
165         g_return_if_fail(master!=NULL);
166
167         sockaddr_in_from_len=sizeof(sockaddr_in_from);
168         while (-1!=(gotlen=recvfrom(
169                         master->gpollfd.fd,     /* s */
170                         packet, /* buf */
171                         sizeof(packet), /* len */
172                         0,      /* flags */
173                         (struct sockaddr *)&sockaddr_in_from,   /* from */
174                         &sockaddr_in_from_len))) {      /* fromlen */
175 GList *clientl;
176
177                 if (sockaddr_in_from_len!=sizeof(sockaddr_in_from))     /* FIXME: errors reporting */
178                         continue;
179                 /* FIXME: Performance: Ugly search... */
180                 for (clientl=sock_client_list;clientl;clientl=clientl->next) {
181                         client=clientl->data;
182                         if (client==master)
183                                 continue;
184                         if (1
185                                         && client->sockaddr_in_from.sin_family     ==sockaddr_in_from.sin_family
186                                         && client->sockaddr_in_from.sin_port       ==sockaddr_in_from.sin_port
187                                         && client->sockaddr_in_from.sin_addr.s_addr==sockaddr_in_from.sin_addr.s_addr)
188                                 break;
189                         }
190                 if (!clientl) {
191                         client=client_new();
192                         client->sockaddr_in_from=sockaddr_in_from;
193                         }
194                 client_touch(client);
195                 UDPFORWARD_MEMZERO(&sockaddr_in_server);
196                 sockaddr_in_server.sin_family=AF_INET;
197                 sockaddr_in_server.sin_port=htons(SERVER_PORT);
198                 sockaddr_in_server.sin_addr.s_addr=htonl(SERVER_INADDR);
199                 /* FIXME: errors checking */
200                 sendto(
201                                 client->gpollfd.fd,     /* s */
202                                 packet, /* msg */
203                                 gotlen, /* len */
204                                 0,      /* flags */
205                                 (struct sockaddr *)&sockaddr_in_server, /* to */
206                                 sizeof(sockaddr_in_server));    /* tolen */
207                 }
208 }
209
210 static void handle_client(struct client *client)
211 {
212 ssize_t gotlen;
213 struct sockaddr_in sockaddr_in_from;
214 char packet [0x10000];
215 socklen_t sockaddr_in_from_len;
216
217         g_return_if_fail(client!=NULL);
218         g_return_if_fail(master!=NULL);
219
220         while (-1!=(gotlen=recvfrom(
221                         client->gpollfd.fd,     /* s */
222                         packet, /* buf */
223                         sizeof(packet), /* len */
224                         0,      /* flags */
225                         (struct sockaddr *)&sockaddr_in_from,   /* from */
226                         &sockaddr_in_from_len))) {      /* fromlen */
227                 if (sockaddr_in_from_len!=sizeof(sockaddr_in_from))     /* FIXME: errors reporting */
228                         continue;
229                 client_touch(client);
230                 /* FIXME: errors checking */
231                 sendto(
232                                 master->gpollfd.fd,     /* s */
233                                 packet, /* msg */
234                                 gotlen, /* len */
235                                 0,      /* flags */
236                                 (struct sockaddr *)&client->sockaddr_in_from,   /* to */
237                                 sizeof(client->sockaddr_in_from));      /* tolen */
238                 }
239 }
240
241 static gboolean sock_source_callback(gpointer data /* unused */)
242 {
243 GList *clientl;
244
245         for (clientl=sock_client_list;clientl;clientl=clientl->next) {
246 struct client *client=clientl->data;
247
248                 if (client->gpollfd.revents&SOCK_SOURCE_CHECK_REVENTS) {
249                         if (client==master)
250                                 handle_master(client);
251                         else
252                                 handle_client(client);
253                         }
254                 }
255
256         return TRUE; /* the source should be kept active */
257 }
258
259 static gboolean sock_source_prepare(GSource *source,gint *timeout)
260 {
261         *timeout=-1;
262
263         return FALSE;
264 }
265
266 static gboolean sock_source_check(GSource *source)
267 {
268 GList *clientl;
269
270         for (clientl=sock_client_list;clientl;clientl=clientl->next) {
271 struct client *client=clientl->data;
272
273                 if (client->gpollfd.revents&SOCK_SOURCE_CHECK_REVENTS)
274                         return TRUE;
275                 }
276         return FALSE;
277 }
278
279 static gboolean sock_source_dispatch(GSource *source,GSourceFunc callback,gpointer user_data)
280 {
281         g_assert(callback!=NULL);
282         return (*callback)(user_data);
283 }
284
285 static GSourceFuncs sock_source_watch_funcs={
286                 sock_source_prepare,
287                 sock_source_check,
288                 sock_source_dispatch,
289                 NULL, /* finalize */
290                 };
291
292 static void sock_gsource_destroy(void)
293 {
294         while (sock_client_list)
295                 client_destroy(sock_client_list->data);
296
297         g_assert(master==NULL);
298
299         if (sock_gsource) {
300                 g_source_destroy(sock_gsource);
301                 sock_gsource=NULL;
302                 }
303 }
304
305 static gboolean sock_gsource_new(void)
306 {
307         if (sock_gsource)
308                 return TRUE;
309
310         g_assert(sock_client_list==NULL);
311
312         /* attach sock_source_callback() to watch for any abnormalities
313          * on our open pipe 'parentheart_fds' and terminate the child if parent dies.
314          */
315         if (!(sock_gsource=g_source_new(&sock_source_watch_funcs,sizeof(GSource)))) {
316                 g_warning("g_source_new(): %m");
317                 return FALSE;
318                 }
319         g_source_set_callback(
320                         sock_gsource,  /* source */
321                         sock_source_callback,  /* func */
322                         NULL, /* data */
323                         NULL);  /* notify */
324         if (!g_source_attach(   /* returns 'guint' id */
325                         sock_gsource,  /* source */
326                         NULL)) {        /* context; NULL means 'default context' */
327                 g_warning("g_source_attach(gsource,NULL): %m");
328                 sock_gsource_destroy();
329                 return FALSE;
330                 }
331         return TRUE;
332 }
333
334 static struct client *client_new(void)
335 {
336 struct client *client;
337 static unsigned long oneul=1;
338 int sock;
339
340         if (!sock_gsource_new())
341                 return FALSE;
342
343         if (-1==(sock=socket(AF_INET,SOCK_DGRAM,0))) {
344                 g_warning("socket(AF_INET,SOCK_DGRAM): %m");
345                 return NULL;
346                 }
347         if (ioctl(sock,FIONBIO,&oneul)) {       /* non-blocking mode */
348                 g_warning("ioctl(sock,FIONBIO,&1): %m");
349                 close(sock);    /* errors ignored */
350                 return NULL;
351                 }
352
353         udpforward_new(client);
354         client->gpollfd.fd=sock;
355         client->gpollfd.events=SOCK_SOURCE_CHECK_EVENTS;
356         client->gpollfd.revents=0;
357         client->timeout_id=0;
358         sock_client_list=g_list_prepend(sock_client_list,client);
359         g_source_add_poll(sock_gsource,&client->gpollfd);
360
361         return client;
362 }
363
364 static void client_destroy(struct client *client)
365 {
366         g_return_if_fail(client!=NULL);
367
368         if (!sock_gsource_new())
369                 return;
370
371         if (client==master) {
372                 master=NULL;
373                 g_assert(client->timeout_id==0);
374                 }
375         else
376                 client_timeout_remove(client);
377
378         g_source_remove_poll(sock_gsource,&client->gpollfd);
379         sock_client_list=g_list_remove(sock_client_list,client);
380         close(client->gpollfd.fd);      /* errors ignored */
381         g_free(client);
382 }
383
384 gboolean network_start(gint port)
385 {
386 pid_t daemon_pid;
387 struct sockaddr_in sockaddr_in;
388
389         g_return_val_if_fail(port>=0,FALSE);
390
391         if ((pid_t)-1!=(daemon_pid=is_daemon_running())) {
392                 g_warning(_("Cannot start network daemon: Daemon is already running on PID %d"),(int)daemon_pid);
393                 return FALSE;
394                 }
395         g_assert(master==NULL);
396         if (!(master=client_new()))
397                 return FALSE;
398         UDPFORWARD_MEMZERO(&sockaddr_in);
399         sockaddr_in.sin_family=AF_INET;
400         sockaddr_in.sin_port=htons(port);
401         sockaddr_in.sin_addr.s_addr=INADDR_ANY;
402         if (bind(master->gpollfd.fd,(struct sockaddr *)&sockaddr_in,sizeof(sockaddr_in))) {
403                 g_warning("bind(sock,{AF_INET,INADDR_ANY:%d}): %m",(int)port);
404                 sock_gsource_destroy();
405                 return FALSE;
406                 }
407         write_daemon_running(getpid());
408         return TRUE;
409 }
410
411 gboolean network_stop(void)
412 {
413 pid_t daemon_pid;
414 int errno_save;
415
416         if ((pid_t)-1==(daemon_pid=is_daemon_running())) {
417                 g_warning(_("Cannot stop network daemon: Daemon is not running"));
418                 return FALSE;
419                 }
420         if (daemon_pid==getpid()) {
421                 sock_gsource_destroy();
422                 return TRUE;
423                 }
424         errno=0;
425         kill(daemon_pid,SIGKILL);
426         errno_save=errno;
427         if (errno_save)  {
428                 g_warning(udpforward_printf_alloca(_("Unable to stop the daemon at PID %d: %s"),
429                                 (int)daemon_pid,strerror(errno_save)));
430                 return FALSE;
431                 }
432         return TRUE;
433 }