ubus: assume that the service iface can be NULL
[project/mdnsd.git] / service.c
1 /*
2 * Copyright (C) 2014 John Crispin <blogic@openwrt.org>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License version 2.1
6 * as published by the Free Software Foundation
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 */
13
14 #include <sys/types.h>
15 #include <arpa/nameser.h>
16 #include <sys/socket.h>
17
18 #include <resolv.h>
19 #include <glob.h>
20 #include <inttypes.h>
21 #include <stdio.h>
22 #include <time.h>
23
24 #include <libubus.h>
25 #include <libubox/vlist.h>
26 #include <libubox/uloop.h>
27 #include <libubox/avl-cmp.h>
28 #include <libubox/blobmsg_json.h>
29
30 #include "ubus.h"
31 #include "dns.h"
32 #include "service.h"
33 #include "util.h"
34 #include "interface.h"
35 #include "announce.h"
36
37 enum {
38 SERVICE_INSTANCE,
39 SERVICE_SERVICE,
40 SERVICE_PORT,
41 SERVICE_TXT,
42 SERVICE_HOSTNAME,
43 __SERVICE_MAX,
44 };
45
46 struct service {
47 struct vlist_node node;
48
49 time_t t;
50
51 const char *id;
52 const char *instance;
53 const char *service;
54 const uint8_t *txt;
55 int txt_len;
56 int port;
57 int active;
58 };
59
60 static const struct blobmsg_policy service_policy[__SERVICE_MAX] = {
61 [SERVICE_INSTANCE] = { .name = "instance", .type = BLOBMSG_TYPE_STRING },
62 [SERVICE_SERVICE] = { .name = "service", .type = BLOBMSG_TYPE_STRING },
63 [SERVICE_PORT] = { .name = "port", .type = BLOBMSG_TYPE_INT32 },
64 [SERVICE_TXT] = { .name = "txt", .type = BLOBMSG_TYPE_ARRAY },
65 [SERVICE_HOSTNAME] = { .name = "hostname", .type = BLOBMSG_TYPE_STRING },
66 };
67
68 static void
69 service_update(struct vlist_tree *tree, struct vlist_node *node_new,
70 struct vlist_node *node_old);
71
72 static void
73 hostname_update(struct vlist_tree *tree, struct vlist_node *node_new,
74 struct vlist_node *node_old);
75
76 static struct blob_buf b;
77 static VLIST_TREE(services, avl_strcmp, service_update, false, false);
78 VLIST_TREE(hostnames, avl_strcmp, hostname_update, false, false);
79 static int service_init_announce;
80
81 /**
82 * service_instance_name - construct Service Instance Name as in RFC 6763
83 *
84 * RFC 6763 specifies Service Instance Names in the following way:
85 *
86 * Service Instance Name = <Instance> . <Service> . <Domain>
87 *
88 * @s: service to generate service instance name for
89 */
90 static const char *
91 service_instance_name(struct service *s)
92 {
93 static char buffer[256];
94
95 snprintf(buffer, sizeof(buffer), "%s.%s", s->instance, s->service);
96
97 return buffer;
98 }
99
100 static void
101 service_add_ptr(const char *host, int ttl)
102 {
103 int len = dn_comp(host, mdns_buf, sizeof(mdns_buf), NULL, NULL);
104
105 if (len < 1)
106 return;
107
108 dns_add_answer(TYPE_PTR, mdns_buf, len, ttl);
109 }
110
111 static void
112 service_add_srv(struct service *s, int ttl)
113 {
114 struct dns_srv_data *sd = (struct dns_srv_data *) mdns_buf;
115 int len = sizeof(*sd);
116
117 len += dn_comp(mdns_hostname_local, mdns_buf + len, sizeof(mdns_buf) - len, NULL, NULL);
118 if (len <= sizeof(*sd))
119 return;
120
121 sd->port = cpu_to_be16(s->port);
122 dns_add_answer(TYPE_SRV, mdns_buf, len, ttl);
123 }
124
125 #define TOUT_LOOKUP 60
126
127 static time_t
128 service_timeout(struct service *s)
129 {
130 time_t t = monotonic_time();
131
132 if (t - s->t <= TOUT_LOOKUP) {
133 DBG(2, "t=%" PRId64 ", s->t=%" PRId64 ", t - s->t = %" PRId64 "\n", (int64_t)t, (int64_t)s->t, (int64_t)(t - s->t));
134 return 0;
135 }
136
137 return t;
138 }
139
140 static void
141 service_reply_single(struct interface *iface, struct sockaddr *to, struct service *s, int ttl, int force)
142 {
143 const char *host = service_instance_name(s);
144 char *service = strstr(host, "._");
145 time_t t = service_timeout(s);
146
147
148 if (!force && (!s->active || !service || !t))
149 return;
150
151 service++;
152
153 s->t = t;
154
155 dns_init_answer();
156 service_add_ptr(service_instance_name(s), ttl);
157 dns_send_answer(iface, to, service);
158
159 dns_init_answer();
160 service_add_srv(s, ttl);
161 if (s->txt && s->txt_len)
162 dns_add_answer(TYPE_TXT, (uint8_t *) s->txt, s->txt_len, ttl);
163 dns_send_answer(iface, to, host);
164 }
165
166 void
167 service_reply(struct interface *iface, struct sockaddr *to, const char *instance, const char *service_domain, int ttl)
168 {
169 struct service *s;
170
171 vlist_for_each_element(&services, s, node) {
172 if (instance && strcmp(s->instance, instance))
173 continue;
174 if (service_domain && strcmp(s->service, service_domain))
175 continue;
176 service_reply_single(iface, to, s, ttl, 0);
177 }
178 }
179
180 void
181 service_announce_services(struct interface *iface, struct sockaddr *to, int ttl)
182 {
183 struct service *s;
184
185 vlist_for_each_element(&services, s, node) {
186 s->t = 0;
187 if (ttl) {
188 dns_init_answer();
189 service_add_ptr(s->service, ttl);
190 dns_send_answer(iface, to, C_DNS_SD);
191 }
192 service_reply_single(iface, to, s, ttl, 0);
193 }
194 }
195
196 static void
197 service_update(struct vlist_tree *tree, struct vlist_node *node_new,
198 struct vlist_node *node_old)
199 {
200 struct interface *iface;
201 struct service *s;
202
203 if (!node_old) {
204 s = container_of(node_new, struct service, node);
205 if (service_init_announce)
206 vlist_for_each_element(&interfaces, iface, node) {
207 s->t = 0;
208 service_reply_single(iface, NULL, s, announce_ttl, 1);
209 }
210 return;
211 }
212
213 s = container_of(node_old, struct service, node);
214 if (!node_new && service_init_announce)
215 vlist_for_each_element(&interfaces, iface, node)
216 service_reply_single(iface, NULL, s, 0, 1);
217 free(s);
218 }
219
220 static void
221 hostname_update(struct vlist_tree *tree, struct vlist_node *node_new,
222 struct vlist_node *node_old)
223 {
224 struct interface *iface;
225 struct hostname *h;
226
227 if (!node_old) {
228 h = container_of(node_new, struct hostname, node);
229 vlist_for_each_element(&interfaces, iface, node)
230 dns_reply_a(iface, NULL, announce_ttl, h->hostname);
231 return;
232 }
233
234 h = container_of(node_old, struct hostname, node);
235 if (!node_new)
236 vlist_for_each_element(&interfaces, iface, node)
237 dns_reply_a(iface, NULL, 0, h->hostname);
238
239 free(h);
240 }
241
242 static void
243 service_load_hostname(struct blob_attr *b)
244 {
245 struct hostname *h;
246 char *hostname, *d_hostname;
247
248 hostname = blobmsg_get_string(b);
249 h = calloc_a(sizeof(*h), &d_hostname, strlen(hostname) + 1);
250 if (!h)
251 return;
252
253 h->hostname = strcpy(d_hostname, hostname);
254
255 vlist_add(&hostnames, &h->node, h->hostname);
256 }
257
258 static void
259 service_load_blob(struct blob_attr *b)
260 {
261 struct blob_attr *txt, *_tb[__SERVICE_MAX];
262 struct service *s;
263 char *d_instance, *d_service, *d_id;
264 uint8_t *d_txt;
265 int rem2;
266 int txt_len = 0;
267 unsigned int n;
268
269 blobmsg_parse(service_policy, ARRAY_SIZE(service_policy),
270 _tb, blobmsg_data(b), blobmsg_data_len(b));
271
272 if (_tb[SERVICE_HOSTNAME]) {
273 service_load_hostname(_tb[SERVICE_HOSTNAME]);
274 return;
275 }
276
277 if (!_tb[SERVICE_PORT] || !_tb[SERVICE_SERVICE])
278 return;
279
280 if (_tb[SERVICE_TXT])
281 blobmsg_for_each_attr(txt, _tb[SERVICE_TXT], rem2)
282 txt_len += 1 + strlen(blobmsg_get_string(txt));
283
284 n = strlen(blobmsg_name(b));
285 s = calloc_a(sizeof(*s),
286 &d_id, n + 1,
287 &d_instance, _tb[SERVICE_INSTANCE] ? strlen(blobmsg_get_string(_tb[SERVICE_INSTANCE])) + 1 : 0,
288 &d_service, strlen(blobmsg_get_string(_tb[SERVICE_SERVICE])) + 1,
289 &d_txt, txt_len);
290 if (!s)
291 return;
292
293 s->port = blobmsg_get_u32(_tb[SERVICE_PORT]);
294 s->id = strncpy(d_id, blobmsg_name(b), n);
295 if (_tb[SERVICE_INSTANCE])
296 s->instance = strcpy(d_instance, blobmsg_get_string(_tb[SERVICE_INSTANCE]));
297 else
298 s->instance = umdns_host_label;
299 s->service = strcpy(d_service, blobmsg_get_string(_tb[SERVICE_SERVICE]));
300 s->active = 1;
301 s->t = 0;
302 s->txt_len = txt_len;
303 s->txt = d_txt;
304
305 if (_tb[SERVICE_TXT])
306 blobmsg_for_each_attr(txt, _tb[SERVICE_TXT], rem2) {
307 int len = strlen(blobmsg_get_string(txt));
308 if (!len)
309 return;
310 if (len > 0xff)
311 len = 0xff;
312 *d_txt = len;
313 d_txt++;
314 memcpy(d_txt, blobmsg_get_string(txt), len);
315 d_txt += len;
316 }
317
318 vlist_add(&services, &s->node, s->id);
319 }
320
321 static void
322 service_load(char *path)
323 {
324 struct blob_attr *cur;
325 glob_t gl;
326 int i, rem;
327
328 if (glob(path, GLOB_NOESCAPE | GLOB_MARK, NULL, &gl))
329 return;
330
331 for (i = 0; i < gl.gl_pathc; i++) {
332 blob_buf_init(&b, 0);
333 if (blobmsg_add_json_from_file(&b, gl.gl_pathv[i])) {
334 blob_for_each_attr(cur, b.head, rem)
335 service_load_blob(cur);
336 } else {
337 fprintf(stderr, "Error reading %s JSON\n", gl.gl_pathv[i]);
338 }
339 }
340 globfree(&gl);
341 }
342
343 static void
344 service_init_cb(struct ubus_request *req, int type, struct blob_attr *msg)
345 {
346 struct blob_attr *cur;
347 int rem;
348
349 get_hostname();
350
351 vlist_update(&services);
352 vlist_update(&hostnames);
353 service_load("/etc/umdns/*");
354
355 blob_for_each_attr(cur, msg, rem) {
356 struct blob_attr *cur2;
357 int rem2;
358
359 blobmsg_for_each_attr(cur2, cur, rem2) {
360 struct blob_attr *cur3;
361 int rem3;
362
363 if (strcmp(blobmsg_name(cur2), "instances"))
364 continue;
365
366 blobmsg_for_each_attr(cur3, cur2, rem3) {
367 struct blob_attr *cur4;
368 int rem4;
369 int running = 0;
370
371 blobmsg_for_each_attr(cur4, cur3, rem4) {
372 const char *name = blobmsg_name(cur4);
373
374 if (!strcmp(name, "running")) {
375 running = blobmsg_get_bool(cur4);
376 } else if (running && !strcmp(name, "data")) {
377 struct blob_attr *cur5;
378 int rem5;
379
380 blobmsg_for_each_attr(cur5, cur4, rem5) {
381 struct blob_attr *cur6;
382 int rem6;
383
384 if (strcmp(blobmsg_name(cur5), "mdns"))
385 continue;
386
387 blobmsg_for_each_attr(cur6, cur5, rem6)
388 service_load_blob(cur6);
389 }
390 break;
391 }
392 }
393 }
394 }
395 }
396 vlist_flush(&services);
397 vlist_flush(&hostnames);
398 }
399
400 void
401 service_init(int announce)
402 {
403 get_hostname();
404
405 service_init_announce = announce;
406 ubus_service_list(service_init_cb);
407 }
408
409 void
410 service_cleanup(void)
411 {
412 vlist_flush(&services);
413 blob_buf_free(&b);
414 }