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