]> git.karo-electronics.de Git - mdnsd.git/blob - mhttp.c
make IP handling a little more flexible
[mdnsd.git] / mhttp.c
1 #define _GNU_SOURCE
2
3 #include <sys/types.h>
4 #include <sys/socket.h>
5 #include <netinet/in.h>
6 #include <arpa/inet.h>
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <unistd.h>
10 #include <string.h>
11 #include <fcntl.h>
12 #include <errno.h>
13 #include <signal.h>
14 #include <poll.h>
15 #include <sys/time.h>
16
17 #include "mdnsd.h"
18 #include "sdtxt.h"
19 #include "netwatch.h"
20
21 #define HOSTNAMESIZE 64
22 #define FIFO_PATH "/tmp/mdns-fifo"
23
24 #define MAX_ANNOUNCE_IP 2
25
26 enum {
27    MDNSD_STARTUP,
28    MDNSD_PROBE,
29    MDNSD_ANNOUNCE,
30    MDNSD_RUN,
31    MDNSD_SHUTDOWN
32 };
33
34 typedef struct _ipcam_ip_info
35 {
36   char          *label;
37   char          *ip;
38   int            link_id;
39
40   /* service-discovery records */
41   mdnsdr         host_to_ip;
42   mdnsdr         ip_to_host;
43 } IpcamIPInfo;
44
45 typedef struct _ipcam_service_info
46 {
47   mdnsd          dnsd;
48   char           hostname[HOSTNAMESIZE];
49   char          *servicename;
50
51   int            port;
52
53   IpcamIPInfo    ipinfos[MAX_ANNOUNCE_IP];
54
55   xht            metadata;
56
57   /* service-discovery records */
58   mdnsdr         srv_to_host;
59   mdnsdr         txt_for_srv;
60
61   mdnsdr         ptr_to_srv;
62
63   int            state;
64 } IpcamServiceInfo;
65
66 static IpcamServiceInfo ipcam_info;
67 static int signal_pipe[2];
68 static int fifo_fd;
69
70 void     request_service (IpcamServiceInfo *info, int stage);
71
72 char *
73 increment_name (char *name)
74 {
75   int   id = 1;
76   char *pos, *end = NULL;
77   char *ret = NULL;
78
79   pos = strrchr (name, '-');
80
81   if (pos)
82     {
83       id = strtol (pos + 1, &end, 10);
84       if (*end == '\0')
85         *pos = '\0';
86       else
87         id = 1;
88     }
89
90   id += 1;
91
92   asprintf (&ret, "%s-%d", name, id);
93
94   return ret;
95 }
96
97
98 /* conflict handling */
99 void
100 handle_conflict (mdnsdr r, char *name, int type, void *arg)
101 {
102   IpcamServiceInfo *info = (IpcamServiceInfo *) arg;
103   char *newname;
104   int i;
105
106   for (i = 0; i < MAX_ANNOUNCE_IP; i++)
107     {
108       if (r == info->ipinfos[i].ip_to_host)
109         {
110           /* can't do anything about a reverse lookup conflict. Just stop
111            * announcing it. */
112           info->ipinfos[i].ip_to_host = NULL;
113           fprintf (stderr, "zeroconf reverse lookup conflict for %s!\n", info->ipinfos[i].label);
114           return;
115         }
116       if (r == info->ipinfos[i].host_to_ip)
117         {
118           info->ipinfos[i].host_to_ip = NULL;
119         }
120     }
121
122   if (info->servicename == NULL)
123     {
124       newname = increment_name (info->hostname);
125     }
126   else
127     {
128       newname = increment_name (info->servicename);
129       free (info->servicename);
130     }
131
132   info->servicename = newname;
133
134   if (r == info->srv_to_host)
135     info->srv_to_host = NULL;
136   if (r == info->txt_for_srv)
137     info->txt_for_srv = NULL;
138
139   fprintf (stderr, "conflicting name \"%s\". trying %s\n",
140            name, info->servicename);
141
142   info->state = MDNSD_PROBE;
143   write (signal_pipe[1], " ", 1);
144 }
145
146
147 /* quit and updates */
148 void sighandler (int sig)
149 {
150   if (sig != SIGHUP)
151     {
152       ipcam_info.state = MDNSD_SHUTDOWN;
153     }
154
155   write (signal_pipe[1], " ", 1);
156 }
157
158
159 /* create multicast 224.0.0.251:5353 socket */
160 int
161 msock ()
162 {
163   int s, flag = 1, ittl = 255;
164   struct sockaddr_in in;
165   struct ip_mreq mc;
166   char ttl = 255;
167
168   bzero (&in, sizeof (in));
169   in.sin_family = AF_INET;
170   in.sin_port = htons (5353);
171   in.sin_addr.s_addr = 0;
172
173   if ((s = socket (AF_INET,SOCK_DGRAM,0)) < 0)
174     return 0;
175
176   setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*) &flag, sizeof (flag));
177   if (bind (s, (struct sockaddr*) &in, sizeof (in)))
178     {
179       close(s);
180       return 0;
181     }
182
183   mc.imr_multiaddr.s_addr = inet_addr ("224.0.0.251");
184   mc.imr_interface.s_addr = htonl (INADDR_ANY);
185   setsockopt (s, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mc,   sizeof (mc));
186   setsockopt (s, IPPROTO_IP, IP_MULTICAST_TTL,  &ttl,  sizeof (ttl));
187   setsockopt (s, IPPROTO_IP, IP_MULTICAST_TTL,  &ittl, sizeof (ittl));
188
189   flag =  fcntl (s, F_GETFL, 0);
190   flag |= O_NONBLOCK;
191   fcntl (s, F_SETFL, flag);
192
193   return s;
194 }
195
196 void
197 request_ip_addresses (IpcamServiceInfo *info, char *hostlocal)
198 {
199   char revlookup[256];
200   int i;
201   long int ip;
202
203   for (i = 0; i < MAX_ANNOUNCE_IP; i++)
204     {
205       if (info->ipinfos[i].ip)
206         {
207           ip = inet_addr (info->ipinfos[i].ip);
208           snprintf (revlookup, 256, "%ld.%ld.%ld.%ld.in-addr.arpa.",
209                     (ip >> 24) & 0xff, (ip >> 16) & 0xff, (ip >> 8) & 0xff, (ip >> 0) & 0xff);
210
211           if (!info->ipinfos[i].host_to_ip)
212             {
213               info->ipinfos[i].host_to_ip  = mdnsd_unique (info->dnsd, hostlocal,
214                                                            QTYPE_A, 120, handle_conflict, info);
215             }
216           mdnsd_set_raw (info->dnsd, info->ipinfos[i].host_to_ip, (unsigned char *) &ip, 4);
217          
218           if (!info->ipinfos[i].ip_to_host)
219             {
220               info->ipinfos[i].ip_to_host  = mdnsd_unique (info->dnsd, revlookup,
221                                                            QTYPE_PTR, 120, handle_conflict, info);
222             }
223           mdnsd_set_host (info->dnsd, info->ipinfos[i].ip_to_host, hostlocal);
224         }
225     }
226 }
227
228 void
229 request_service (IpcamServiceInfo *info, int stage)
230 {
231   unsigned char *packet, servlocal[256], hostlocal[256];
232   int i, len = 0;
233
234   sprintf (servlocal, "%s._http._tcp.local.",
235            info->servicename ? info->servicename : info->hostname);
236   sprintf (hostlocal, "%s.local.",
237            info->servicename ? info->servicename : info->hostname);
238
239   /*
240    * Timeouts according to
241    *   http://files.multicastdns.org/draft-cheshire-dnsext-multicastdns.txt
242    *
243    * As a general rule, the recommended TTL value for Multicast DNS
244    * resource records with a host name as the resource record's name
245    * (e.g. A, AAAA, HINFO, etc.) or contained within the resource record's
246    * rdata (e.g. SRV, reverse mapping PTR record, etc.) is 120 seconds.
247    *
248    * The recommended TTL value for other Multicast DNS resource records
249    * is 75 minutes.
250    */
251
252   switch (stage)
253     {
254       case 0:
255         request_ip_addresses (info, hostlocal);
256
257         break;
258
259       case 1:
260         if (!info->srv_to_host)
261           {
262             info->srv_to_host = mdnsd_unique (info->dnsd, servlocal,
263                                               QTYPE_SRV, 120, handle_conflict, info);
264           }
265         mdnsd_set_srv (info->dnsd, info->srv_to_host, 0, 0,
266                        info->port, hostlocal);
267
268         if (!info->txt_for_srv)
269           {
270             info->txt_for_srv = mdnsd_unique (info->dnsd, servlocal,
271                                               QTYPE_TXT, 4500, handle_conflict, info);
272           }
273         packet = sd2txt (info->metadata, &len);
274         mdnsd_set_raw (info->dnsd, info->txt_for_srv, packet, len);
275         free(packet);
276         break;
277
278       case 2:
279         if (!info->ptr_to_srv)
280           {
281             info->ptr_to_srv  = mdnsd_shared (info->dnsd, "_http._tcp.local.",
282                                               QTYPE_PTR, 4500);
283           }
284         mdnsd_set_host (info->dnsd, info->ptr_to_srv, servlocal);
285
286         for (i = 0; i < MAX_ANNOUNCE_IP; i++)
287           {
288             if (info->ipinfos[i].ip)
289               fprintf (stderr, "Announcing .local site named '%s' to %s:%d (%s)\n", hostlocal,
290                        info->ipinfos[i].ip, info->port, servlocal);
291           }
292         break;
293
294       default:
295         fprintf (stderr, "announce stage %d is invalid\n", stage);
296         break;
297     }
298 }
299
300 void
301 update_port_info (IpcamServiceInfo *info, int port)
302 {
303   unsigned char hostlocal[256];
304
305   if (port == info->port)
306     return;
307
308   info->port = port;
309
310   if (!info->srv_to_host)
311     return;
312
313   sprintf (hostlocal, "%s.local.",
314            info->servicename ? info->servicename : info->hostname);
315
316   fprintf (stderr, "mhttp: updating port info to port %d\n", info->port);
317   mdnsd_set_srv (info->dnsd, info->srv_to_host, 0, 0,
318                  info->port, hostlocal);
319 }
320
321 void
322 iface_change_callback (int   link_index,
323                        char *label,
324                        char *ipaddr,
325                        int   add,
326                        void *user_data)
327 {
328   IpcamServiceInfo *info = (IpcamServiceInfo *) user_data;
329   int i;
330
331   for (i = 0; i < MAX_ANNOUNCE_IP; i++)
332     {
333       if (strcmp (info->ipinfos[i].label, label) != 0)
334         continue;
335
336       if (add && (!info->ipinfos[i].ip ||
337                   strcmp (info->ipinfos[i].ip, ipaddr) != 0 ||
338                   info->ipinfos[i].link_id != link_index))
339         {
340           if (info->ipinfos[i].ip)
341             free (info->ipinfos[i].ip);
342           info->ipinfos[i].ip = strdup (ipaddr);
343           info->ipinfos[i].link_id = link_index;
344         }
345      
346       if (!add && info->ipinfos[i].ip)
347         {
348           free (info->ipinfos[i].ip);
349           info->ipinfos[i].ip = NULL;
350           info->ipinfos[i].link_id = -1;
351         }
352     }
353
354   info->state = MDNSD_PROBE;
355   write (signal_pipe[1], " ", 1);
356 }
357
358
359 void
360 iface_link_callback (int   link_index,
361                      int   running,
362                      void *user_data)
363 {
364   IpcamServiceInfo *info = (IpcamServiceInfo *) user_data;
365   int i;
366   int link_changed = 0;
367
368   for (i = 0; i < MAX_ANNOUNCE_IP; i++)
369     if (link_index == info->ipinfos[i].link_id)
370       link_changed = 1;
371
372   if (!link_changed)
373     return;
374
375   info->state = running ? MDNSD_PROBE : MDNSD_STARTUP;
376   write (signal_pipe[1], " ", 1);
377 }
378
379
380 int main(int argc, char *argv[])
381 {
382   struct message msg;
383   unsigned short int port;
384   struct timeval tv;
385   int bsize, ssize = sizeof(struct sockaddr_in);
386   unsigned char buf[MAX_PACKET_LEN];
387   struct sockaddr_in from, to;
388   int i, s;
389   int nlink;
390   unsigned long remote_ip;
391   char *value;
392   int polltime = 0;
393   int announce_stage = 0;
394   struct pollfd fds[4];
395
396   if(argc < 3)
397     {
398       fprintf (stderr, "usage: mhttp <label1> <label2> <port> <key1>=<value1> <key2>=<value2> ...\n");
399       fprintf (stderr, "   <label1>, <label2> are the labels of the network interface to be watched\n");
400       fprintf (stderr, "   <port> is the port number of the service to be advertized\n");
401       fprintf (stderr, "   <key>=<value> are the keys that get embedded into the TXT record.\n");
402       fprintf (stderr, "\n   The port later can be changed by writing \"port:8080\" to " FIFO_PATH ".\n");
403       return -1;
404     }
405
406   ipcam_info.dnsd = mdnsd_new (1, 1000);
407
408   ipcam_info.state = MDNSD_STARTUP;
409
410   gethostname (ipcam_info.hostname, HOSTNAMESIZE);
411   ipcam_info.hostname[HOSTNAMESIZE-1] = '\0';
412   if (strchr (ipcam_info.hostname, '.'))
413     strchr (ipcam_info.hostname, '.')[0] = '\0';
414
415   ipcam_info.servicename = NULL;
416   
417   for (i = 0; i < MAX_ANNOUNCE_IP; i++)
418     {
419       ipcam_info.ipinfos[i].label      = argv[i+1];
420       ipcam_info.ipinfos[i].ip         = NULL;
421       ipcam_info.ipinfos[i].link_id    = -1;
422       ipcam_info.ipinfos[i].host_to_ip = NULL;
423       ipcam_info.ipinfos[i].ip_to_host = NULL;
424     }
425
426   ipcam_info.port = atoi(argv[3]);
427
428   ipcam_info.metadata = xht_new (11);
429   for (i = 4; i < argc; i++)
430     {
431       value = index (argv[i], '=');
432       if (value)
433         {
434           value[0] = '\0';
435           value++;
436           xht_set (ipcam_info.metadata, argv[i], value);
437         }
438     }
439
440   ipcam_info.ptr_to_srv     = NULL;
441   ipcam_info.srv_to_host    = NULL;
442   ipcam_info.txt_for_srv    = NULL;
443
444   pipe (signal_pipe);
445   signal(SIGHUP,  sighandler);
446   signal(SIGINT,  sighandler);
447   signal(SIGQUIT, sighandler);
448   signal(SIGTERM, sighandler);
449
450   if ((s = msock()) == 0)
451     {
452       fprintf (stderr, "can't create socket: %s\n", strerror(errno));
453       return -1;
454     }
455
456   if ((nlink = netwatch_open ()) < 0)
457     {
458       fprintf (stderr, "can't connect to netlink: %s\n", strerror(errno));
459       return -1;
460     }
461
462   netwatch_register_callbacks (iface_change_callback,
463                                iface_link_callback,
464                                &ipcam_info);
465   netwatch_queue_inforequest (nlink);
466
467
468   if (mkfifo (FIFO_PATH, S_IRWXU) < 0)
469     {
470       if (errno != EEXIST)
471         {
472           fprintf (stderr, "can't create named pipe: %s\n", strerror(errno));
473           return -1;
474         }
475     }
476
477   if ((fifo_fd = open (FIFO_PATH, O_RDONLY | O_NONBLOCK)) < 0)
478     {
479       fprintf (stderr, "can't open named pipe: %s\n", strerror(errno));
480       return -1;
481     }
482
483   /* we need to open the fifo for writing as well (although we'll never
484    * use it for this) to avoid POLLHUP to happen when no client wants
485    * something from us. Ugh. */
486
487   if ((i = open (FIFO_PATH, O_WRONLY)) < 0)
488     {
489       fprintf (stderr, "can't dummy-open write end of pipe: %s\n",
490                strerror(errno));
491       return -1;
492     }
493
494   while(1)
495     {
496       fds[0].fd      = signal_pipe[0];
497       fds[0].events  = POLLIN;
498       fds[0].revents = 0;
499       fds[1].fd      = s;
500       fds[1].events  = POLLIN;
501       fds[1].revents = 0;
502       fds[2].fd      = nlink;
503       fds[2].events  = POLLIN;
504       fds[2].revents = 0;
505       fds[3].fd      = fifo_fd;
506       fds[3].events  = POLLIN;
507       fds[3].revents = 0;
508
509       poll (fds, 4, polltime);
510
511       /* only used when we wake-up from a signal */
512       if (fds[0].revents)
513         {
514           char hostname[HOSTNAMESIZE];
515
516           read (signal_pipe[0], buf, MAX_PACKET_LEN);
517
518           gethostname (hostname, HOSTNAMESIZE);
519           hostname[HOSTNAMESIZE-1] = '\0';
520           if (strchr (hostname, '.'))
521             strchr (hostname, '.')[0] = '\0';
522           if (strcmp (hostname, ipcam_info.hostname))
523             {
524               /* hostname changed */
525               strcpy (ipcam_info.hostname, hostname);
526               free (ipcam_info.servicename);
527               ipcam_info.servicename = NULL;
528
529               ipcam_info.state = MDNSD_PROBE;
530             }
531         }
532
533       if (fds[2].revents)
534         {
535           netwatch_dispatch (nlink);
536         }
537
538       if (fds[3].revents)
539         {
540           char message[1024];
541           int ret;
542
543           ret = read (fifo_fd, message, 1023);
544
545           if (ret > 0)
546             {
547               message[ret] = '\0';
548
549               if (!strncmp ("port:", message, 5))
550                 {
551                   int port = atoi (message + 5);
552                   if (port > 0 && port < 65536)
553                     update_port_info (&ipcam_info, port);
554                 }
555               else
556                 {
557                   fprintf (stderr, "mdnsd: got unknown fifo message: %s", message);
558                 }
559             }
560           else if (ret < 0)
561             {
562               fprintf (stderr, "mdnsd: can't read from pipe: %s\n", strerror (errno));
563             }
564         }
565
566       switch (ipcam_info.state)
567         {
568           case MDNSD_STARTUP:
569             /* we're waiting for a netwatch based statechange */
570             /* fprintf (stderr, "in STARTUP\n"); */
571             polltime = 5000;
572             break;
573
574           case MDNSD_PROBE:
575             /* fprintf (stderr, "in PROBE\n"); */
576             if (ipcam_info.ptr_to_srv)
577               mdnsd_done (ipcam_info.dnsd, ipcam_info.ptr_to_srv);
578             if (ipcam_info.srv_to_host)
579               mdnsd_done (ipcam_info.dnsd, ipcam_info.srv_to_host);
580             if (ipcam_info.txt_for_srv)
581               mdnsd_done (ipcam_info.dnsd, ipcam_info.txt_for_srv);
582
583             ipcam_info.ptr_to_srv     = NULL;
584             ipcam_info.srv_to_host    = NULL;
585             ipcam_info.txt_for_srv    = NULL;
586
587             for (i = 0; i < MAX_ANNOUNCE_IP; i++)
588               {
589                 if (ipcam_info.ipinfos[i].host_to_ip)
590                   mdnsd_done (ipcam_info.dnsd, ipcam_info.ipinfos[i].host_to_ip);
591                 if (ipcam_info.ipinfos[i].ip_to_host)
592                   mdnsd_done (ipcam_info.dnsd, ipcam_info.ipinfos[i].ip_to_host);
593                 ipcam_info.ipinfos[i].host_to_ip = NULL;
594                 ipcam_info.ipinfos[i].ip_to_host = NULL;
595               }
596
597             ipcam_info.state = MDNSD_ANNOUNCE;
598             announce_stage = 0;
599             tv.tv_sec = 0;
600             tv.tv_usec = 0;
601             break;
602
603           case MDNSD_ANNOUNCE:
604             /* fprintf (stderr, "in ANNOUNCE\n"); */
605             if (announce_stage < 3)
606               {
607                 struct timeval cur_tv;
608                 long msecs;
609                 gettimeofday (&cur_tv, NULL);
610                 msecs = (cur_tv.tv_sec - tv.tv_sec) * 1000 + cur_tv.tv_usec / 1000 - tv.tv_usec / 1000;
611
612                 if (tv.tv_sec == 0 || msecs > 755)
613                   {
614                     request_service (&ipcam_info, announce_stage);
615                     announce_stage ++;
616                     tv = cur_tv;
617                     cur_tv = *mdnsd_sleep (ipcam_info.dnsd);
618                     polltime = cur_tv.tv_sec * 1000 + cur_tv.tv_usec / 1000;
619                     if (polltime >= 756)
620                       polltime = 756;
621                   }
622                 else
623                   {
624                     cur_tv = *mdnsd_sleep (ipcam_info.dnsd);
625                     polltime = cur_tv.tv_sec * 1000 + cur_tv.tv_usec / 1000;
626                     if (polltime >= 756 - msecs)
627                       polltime = 756 - msecs;
628                   }
629               }
630             else
631               {
632                 tv = *mdnsd_sleep (ipcam_info.dnsd);
633                 polltime = tv.tv_sec * 1000 + tv.tv_usec / 1000;
634
635                 ipcam_info.state = MDNSD_RUN;
636               }
637             break;
638
639           case MDNSD_RUN:
640             tv = *mdnsd_sleep (ipcam_info.dnsd);
641             polltime = tv.tv_sec * 1000 + tv.tv_usec / 1000;
642             break;
643
644           case MDNSD_SHUTDOWN:
645             mdnsd_shutdown (ipcam_info.dnsd);
646             break;
647
648           default:
649             fprintf (stderr, "in default???\n");
650             break;
651         }
652
653       if (fds[1].revents)
654         {
655           while ((bsize = recvfrom (s, buf, MAX_PACKET_LEN, 0,
656                                     (struct sockaddr*) &from, &ssize)) > 0)
657             {
658               bzero (&msg, sizeof (struct message));
659               message_parse (&msg, buf);
660               mdnsd_in (ipcam_info.dnsd, &msg,
661                         (unsigned long int) from.sin_addr.s_addr,
662                         from.sin_port);
663             }
664           if (bsize < 0 && errno != EAGAIN)
665             {
666               fprintf (stderr, "can't read from socket: %s\n", strerror (errno));
667             }
668         }
669
670       while (mdnsd_out (ipcam_info.dnsd, &msg, &remote_ip, &port))
671         {
672           bzero (&to, sizeof (to));
673           to.sin_family = AF_INET;
674           to.sin_port = port;
675           to.sin_addr.s_addr = remote_ip;
676           if (sendto (s, message_packet (&msg), message_packet_len (&msg),
677                       0, (struct sockaddr *) &to,
678                       sizeof (struct sockaddr_in)) != message_packet_len (&msg))
679             {
680               fprintf (stderr, "can't write to socket: %s\n", strerror(errno));
681             }
682         }
683
684       if (ipcam_info.state == MDNSD_SHUTDOWN)
685         break;
686     }
687
688   mdnsd_shutdown (ipcam_info.dnsd);
689   mdnsd_free (ipcam_info.dnsd);
690   return 0;
691 }
692