]> git.karo-electronics.de Git - mdnsd.git/blob - mhttp.c
24d5e5af0c4e2e5741342f7bbf02faf1b6636702
[mdnsd.git] / mhttp.c
1 #include <sys/types.h>
2 #include <sys/socket.h>
3 #include <netinet/in.h>
4 #include <arpa/inet.h>
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <unistd.h>
8 #include <string.h>
9 #include <fcntl.h>
10 #include <errno.h>
11 #include <signal.h>
12 #include <poll.h>
13 #include <sys/time.h>
14 #include <sys/stat.h>
15
16 #include "mdnsd.h"
17 #include "dns_sd_txt.h"
18
19 #ifdef UNUSED
20 #elif defined(__GNUC__)
21 # define UNUSED(x) UNUSED_ ## x __attribute__((unused))
22 #elif defined(__LCLINT__)
23 # define UNUSED(x) /*@unused@*/ x
24 #else
25 # define UNUSED(x) x
26 #endif
27
28 #define HOSTNAMESIZE 64
29
30 typedef enum {
31     MDNSD_PROBE,
32     MDNSD_ANNOUNCE,
33     MDNSD_RUN,
34     MDNSD_SHUTDOWN
35 } TMdnsdState;
36
37 typedef struct _service_info
38 {
39     TMdnsd        *mdnsd;
40     char           hostname[HOSTNAMESIZE];
41     char          *servicename;
42
43     char          *ip;
44     int            port;
45
46     /* service-discovery records */
47     TMdnsdRecord  *host_to_ip;
48     TMdnsdRecord  *ip_to_host;
49     struct in_addr announce_ip;
50
51     SHASH          metadata;
52
53     /* service-discovery records */
54     TMdnsdRecord  *srv_to_host;
55     TMdnsdRecord  *txt_for_srv;
56
57     TMdnsdRecord  *ptr_to_srv;
58
59     TMdnsdState    state;
60 } ServiceInfo;
61
62 static ServiceInfo service_info;
63
64 void     request_service      (ServiceInfo *info, int stage);
65 void     request_ip_addresses (ServiceInfo *info);
66
67 char *increment_name (char *name)
68 {
69     int   id = 1;
70     char *pos;
71         char *end = NULL;
72     char *ret = NULL;
73
74     pos = strrchr (name, '-');
75
76     if (pos) {
77         id = strtol (pos + 1, &end, 10);
78         if (*end == '\0') {
79             *pos = '\0';
80                 } else {
81             id = 1;
82                 }
83     }
84
85     id += 1;
86
87     asprintf (&ret, "%s-%d", name, id);
88
89     return ret;
90 }
91
92
93 /* conflict handling */
94 void handle_conflict (TMdnsdRecord *record, uint8_t *name, int UNUSED(type), void *arg)
95 {
96     ServiceInfo *info = (ServiceInfo *) arg;
97     char *newname;
98
99     if (record == info->ip_to_host) {
100         /* can't do anything about a reverse lookup conflict. Just stop
101          * announcing it. */
102         info->ip_to_host = NULL;
103         fprintf (stderr, "zeroconf reverse lookup conflict for %s!\n", info->ip);
104         return;
105     }
106
107     if (record == info->host_to_ip) {
108         info->host_to_ip = NULL;
109         info->announce_ip.s_addr = 0;
110     }
111
112     if (info->servicename == NULL) {
113         newname = increment_name (info->hostname);
114     } else {
115         newname = increment_name (info->servicename);
116         free (info->servicename);
117     }
118
119     info->servicename = newname;
120
121     if (record == info->srv_to_host) {
122         info->srv_to_host = NULL;
123         }
124
125     if (record == info->txt_for_srv) {
126         info->txt_for_srv = NULL;
127         }
128
129     fprintf (stderr, "conflicting name \"%s\". trying %s\n",
130              name, info->servicename);
131
132         /* The hostname was changed, so go back to probe state */
133     info->state = MDNSD_PROBE;
134 }
135
136
137 /* quit and updates */
138 void sighandler (int sig)
139 {
140     if (sig != SIGHUP) {
141         service_info.state = MDNSD_SHUTDOWN;
142     }
143 }
144
145
146 /* create multicast 224.0.0.251:5353 socket */
147 int msock ()
148 {
149     int    sock_fd;
150         int    flag = 1;
151         int    ittl = 255;
152     char   ttl = 255;
153     struct sockaddr_in in;
154     struct ip_mreq mc;
155
156     bzero (&in, sizeof (in));
157     in.sin_family = AF_INET;
158     in.sin_port = htons (MDNS_PORT);
159     in.sin_addr.s_addr = 0;
160
161     if ((sock_fd = socket (AF_INET, SOCK_DGRAM, 0)) < 0) {
162         return 0;
163         }
164
165     setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, (char*) &flag, sizeof (flag));
166     if (bind (sock_fd, (struct sockaddr*) &in, sizeof (in))) {
167         close(sock_fd);
168         return 0;
169     }
170
171     mc.imr_multiaddr.s_addr = inet_addr ("224.0.0.251");
172     mc.imr_interface.s_addr = htonl (INADDR_ANY);
173     setsockopt (sock_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mc,   sizeof (mc));
174     setsockopt (sock_fd, IPPROTO_IP, IP_MULTICAST_TTL,  &ttl,  sizeof (ttl));
175     setsockopt (sock_fd, IPPROTO_IP, IP_MULTICAST_TTL,  &ittl, sizeof (ittl));
176
177     flag =  fcntl (sock_fd, F_GETFL, 0);
178     flag |= O_NONBLOCK;
179     fcntl (sock_fd, F_SETFL, flag);
180
181     return sock_fd;
182 }
183
184 void request_ip_addresses (ServiceInfo *info)
185 {
186     char revlookup[256];
187         char hostlocal[256];
188     struct in_addr ip;
189     int  num_ips = 0;
190
191     sprintf (hostlocal, "%s.local.", info->servicename ? info->servicename : info->hostname);
192
193     if (info->ip) {
194         ip.s_addr = inet_addr (info->ip);
195
196         if (ip.s_addr != info->announce_ip.s_addr) {
197             snprintf (revlookup, 256, "%d.%d.%d.%d.in-addr.arpa.",
198                       (ip.s_addr >> 24) & 0xff, (ip.s_addr >> 16) & 0xff,
199                       (ip.s_addr >> 8) & 0xff, (ip.s_addr >> 0) & 0xff);
200
201             if (!info->host_to_ip) {
202                 info->host_to_ip  = MdnsdAllocUnique(info->mdnsd, hostlocal,
203                                                              QTYPE_A, 120, handle_conflict, info);
204             }
205             MdnsdSetRaw (info->mdnsd, info->host_to_ip, (uint8_t *) &ip, 4);
206
207             if (!info->ip_to_host) {
208                 info->ip_to_host  = MdnsdAllocUnique(info->mdnsd, revlookup,
209                                                              QTYPE_PTR, 120, handle_conflict, info);
210             }
211             MdnsdSetHost (info->mdnsd, info->ip_to_host, hostlocal);
212
213             info->announce_ip = ip;
214         }
215
216         num_ips++;
217     } else {
218         if (info->host_to_ip) {
219             MdnsdDone (service_info.mdnsd, info->host_to_ip);
220                 }
221         if (info->ip_to_host) {
222             MdnsdDone (service_info.mdnsd, info->ip_to_host);
223                 }
224
225         info->host_to_ip = NULL;
226         info->ip_to_host = NULL;
227         info->announce_ip.s_addr = 0;
228     }
229 }
230
231 void request_service (ServiceInfo *info, int stage)
232 {
233     uint8_t *packet;
234         char     servlocal[256];
235         char     hostlocal[256];
236     int      len = 0;
237
238     sprintf (servlocal, "%s._http._tcp.local.",
239              info->servicename ? info->servicename : info->hostname);
240
241     /*
242      * Timeouts according to
243      *   http://files.multicastdns.org/draft-cheshire-dnsext-multicastdns.txt
244      *
245      * As a general rule, the recommended TTL value for Multicast DNS
246      * resource records with a host name as the resource record's name
247      * (e.g. A, AAAA, HINFO, etc.) or contained within the resource record's
248          * rdata (e.g. SRV, reverse mapping PTR record, etc.) is 120 seconds.
249              *
250      * The recommended TTL value for other Multicast DNS resource records
251      * is 75 minutes.
252      */
253
254     switch (stage)
255     {
256         case 0:
257             request_ip_addresses (info);
258
259             break;
260
261         case 1:
262             sprintf (hostlocal, "%s.local.",
263                      info->servicename ? info->servicename : info->hostname);
264
265             if (!info->srv_to_host) {
266                 info->srv_to_host = MdnsdAllocUnique (info->mdnsd, servlocal,
267                                                       QTYPE_SRV, 120, handle_conflict, info);
268             }
269
270             MdnsdSetSrv (info->mdnsd, info->srv_to_host, 0, 0, info->port, hostlocal);
271
272             if (!info->txt_for_srv) {
273                 info->txt_for_srv = MdnsdAllocUnique (info->mdnsd, servlocal,
274                                                       QTYPE_TXT, 4500, handle_conflict, info);
275             }
276
277             packet = DnsSd2Txt (info->metadata, &len);
278             MdnsdSetRaw (info->mdnsd, info->txt_for_srv, packet, len);
279             free(packet);
280             break;
281
282         case 2:
283             if (!info->ptr_to_srv) {
284                 info->ptr_to_srv  = MdnsdAllocShared (info->mdnsd, "_http._tcp.local.",
285                                                   QTYPE_PTR, 4500);
286             }
287             MdnsdSetHost (info->mdnsd, info->ptr_to_srv, servlocal);
288
289             if (info->ip) {
290                 fprintf (stderr, "Announcing \"%s.local\" to %s:%d\n",
291                          info->servicename ? info->servicename : info->hostname,
292                          info->ip, info->port);
293                         }
294             break;
295
296         default:
297             fprintf (stderr, "announce stage %d is invalid\n", stage);
298             break;
299     }
300 }
301
302 int main(int argc, char *argv[])
303 {
304     DNSMESSAGE msg;
305     uint16_t   port;
306     struct timeval tv;
307     int        bsize;
308         int        ssize = sizeof(struct sockaddr_in);
309     uint8_t    buf[MAX_PACKET_LEN];
310     struct sockaddr_in from;
311         struct sockaddr_in to;
312     int        idx;
313         int        s;
314     struct in_addr remote_ip;
315     char      *value;
316     int        polltime = 0;
317     int        announce_stage = 0;
318     struct pollfd fds[1];
319
320     if(argc < 4)
321     {
322         fprintf (stderr, "usage: mhttp <ip> <port> <key1>=<value1> <key2>=<value2> ...\n");
323         fprintf (stderr, "   <ip>  The IP address to promote\n");
324         fprintf (stderr, "   <port> is the port number of the service to be advertized\n");
325         fprintf (stderr, "   <key>=<value> are the keys that get embedded into the TXT record.\n");
326         return -1;
327     }
328
329     service_info.mdnsd = MdnsdNew (1, 1000);
330
331         //gethostname (service_info.hostname, HOSTNAMESIZE);
332         sprintf(service_info.hostname, "reinhardt");
333     service_info.hostname[HOSTNAMESIZE-1] = '\0';
334     if (strchr (service_info.hostname, '.'))
335         strchr (service_info.hostname, '.')[0] = '\0';
336
337     service_info.servicename = NULL;
338
339     service_info.ip          = strdup(argv[1]);
340     service_info.announce_ip.s_addr = inet_addr(service_info.ip);
341     service_info.host_to_ip  = NULL;
342     service_info.ip_to_host  = NULL;
343
344     service_info.port = atoi(argv[2]);
345
346     service_info.metadata = SHashInit (11);
347     for (idx = 2; idx < argc; idx++) {
348         value = index (argv[idx], '=');
349         if (value) {
350             value[0] = '\0';
351             value++;
352             SHashSet (service_info.metadata, argv[idx], value);
353         }
354     }
355
356     service_info.ptr_to_srv     = NULL;
357     service_info.srv_to_host    = NULL;
358     service_info.txt_for_srv    = NULL;
359
360     signal(SIGHUP,  sighandler);
361     signal(SIGINT,  sighandler);
362     signal(SIGQUIT, sighandler);
363     signal(SIGTERM, sighandler);
364
365     if ((s = msock()) == 0)
366     {
367         fprintf (stderr, "can't create socket: %s\n", strerror(errno));
368         return -1;
369     }
370
371     request_ip_addresses (&service_info);
372
373     service_info.state = MDNSD_PROBE;
374
375     while(1) {
376         fds[0].fd      = s;
377         fds[0].events  = POLLIN;
378         fds[0].revents = 0;
379
380         poll (fds, 1, polltime);
381
382         switch (service_info.state)
383         {
384             case MDNSD_PROBE:
385
386                                 if (service_info.ptr_to_srv) {
387                     MdnsdDone (service_info.mdnsd, service_info.ptr_to_srv);
388                                 }
389
390                                 if (service_info.srv_to_host) {
391                     MdnsdDone (service_info.mdnsd, service_info.srv_to_host);
392                                 }
393
394                 if (service_info.txt_for_srv) {
395                     MdnsdDone (service_info.mdnsd, service_info.txt_for_srv);
396                                 }
397
398                 service_info.ptr_to_srv     = NULL;
399                 service_info.srv_to_host    = NULL;
400                 service_info.txt_for_srv    = NULL;
401
402                 if (service_info.host_to_ip) {
403                     MdnsdDone (service_info.mdnsd, service_info.host_to_ip);
404                                 }
405
406                 if (service_info.ip_to_host) {
407                     MdnsdDone (service_info.mdnsd, service_info.ip_to_host);
408                                 }
409
410                 service_info.host_to_ip  = NULL;
411                 service_info.ip_to_host  = NULL;
412                 service_info.announce_ip.s_addr = 0;
413
414                 service_info.state = MDNSD_ANNOUNCE;
415                 announce_stage = 0;
416                 tv.tv_sec = 0;
417                 tv.tv_usec = 0;
418                 break;
419
420             case MDNSD_ANNOUNCE:
421                 if (announce_stage < 3) {
422                     struct timeval cur_tv;
423                     long msecs;
424
425                     gettimeofday (&cur_tv, NULL);
426                     msecs = (cur_tv.tv_sec - tv.tv_sec) * 1000 + cur_tv.tv_usec / 1000 - tv.tv_usec / 1000;
427
428                     if ((tv.tv_sec == 0) || (msecs > 755)) {
429                         request_service (&service_info, announce_stage);
430                         announce_stage ++;
431                         tv = cur_tv;
432                         cur_tv = *MdnsdGetMaxSleepTime (service_info.mdnsd);
433                         polltime = cur_tv.tv_sec * 1000 + cur_tv.tv_usec / 1000;
434                         if (polltime >= 756) {
435                             polltime = 756;
436                                                 }
437                     } else {
438                         cur_tv = *MdnsdGetMaxSleepTime (service_info.mdnsd);
439                         polltime = cur_tv.tv_sec * 1000 + cur_tv.tv_usec / 1000;
440                         if (polltime >= 756 - msecs) {
441                             polltime = 756 - msecs;
442                                                 }
443                     }
444                 } else {
445                     tv = *MdnsdGetMaxSleepTime (service_info.mdnsd);
446                     polltime = tv.tv_sec * 1000 + tv.tv_usec / 1000;
447
448                     service_info.state = MDNSD_RUN;
449                 }
450                 break;
451
452             case MDNSD_RUN:
453                 tv = *MdnsdGetMaxSleepTime (service_info.mdnsd);
454                 polltime = tv.tv_sec * 1000 + tv.tv_usec / 1000;
455                 break;
456
457             case MDNSD_SHUTDOWN:
458                 MdnsdShutdown (service_info.mdnsd);
459                 break;
460
461             default:
462                 fprintf (stderr, "in default???\n");
463                 break;
464         }
465
466         if (fds[0].revents) {
467             while ((bsize = recvfrom (s, buf, MAX_PACKET_LEN, 0, (struct sockaddr*) &from, &ssize)) > 0)
468             {
469                 bzero (&msg, sizeof (DNSMESSAGE));
470                 DnsParseMsg (&msg, buf);
471                 MdnsdInput(service_info.mdnsd, &msg,
472                            from.sin_addr,
473                            from.sin_port);
474             }
475
476             if (bsize < 0 && errno != EAGAIN) {
477                 fprintf (stderr, "can't read from socket: %s\n", strerror (errno));
478             }
479         }
480
481         while (MdnsdOutput (service_info.mdnsd, &msg, &remote_ip, &port)) {
482             bzero (&to, sizeof (to));
483             to.sin_family = AF_INET;
484             to.sin_port = port;
485             to.sin_addr.s_addr = remote_ip.s_addr;
486
487             if (sendto (s, DnsMsg2Pkt (&msg), DnsMsgLen(&msg), 0, (struct sockaddr *) &to, sizeof (struct sockaddr_in)) != DnsMsgLen(&msg)) {
488                 fprintf (stderr, "can't write to socket: %s\n", strerror(errno));
489             }
490         }
491
492         if (service_info.state == MDNSD_SHUTDOWN) {
493             break;
494                 }
495     }
496
497     MdnsdShutdown (service_info.mdnsd);
498     MdnsdFree (service_info.mdnsd);
499     return 0;
500 }
501