cache: make record/hostname lookup case-insensitive
[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_MAX,
43 };
44
45 struct service {
46 struct vlist_node node;
47
48 time_t t;
49
50 const char *id;
51 const char *instance;
52 const char *service;
53 const uint8_t *txt;
54 int txt_len;
55 int port;
56 int active;
57 };
58
59 static const struct blobmsg_policy service_policy[__SERVICE_MAX] = {
60 [SERVICE_INSTANCE] = { .name = "instance", .type = BLOBMSG_TYPE_STRING },
61 [SERVICE_SERVICE] = { .name = "service", .type = BLOBMSG_TYPE_STRING },
62 [SERVICE_PORT] = { .name = "port", .type = BLOBMSG_TYPE_INT32 },
63 [SERVICE_TXT] = { .name = "txt", .type = BLOBMSG_TYPE_ARRAY },
64 };
65
66 static void
67 service_update(struct vlist_tree *tree, struct vlist_node *node_new,
68 struct vlist_node *node_old);
69
70 static struct blob_buf b;
71 static VLIST_TREE(services, avl_strcmp, service_update, false, false);
72 static int service_init_announce;
73
74 /**
75 * service_instance_name - construct Service Instance Name as in RFC 6763
76 *
77 * RFC 6763 specifies Service Instance Names in the following way:
78 *
79 * Service Instance Name = <Instance> . <Service> . <Domain>
80 *
81 * @s: service to generate service instance name for
82 */
83 static const char *
84 service_instance_name(struct service *s)
85 {
86 static char buffer[256];
87
88 snprintf(buffer, sizeof(buffer), "%s.%s", s->instance, s->service);
89
90 return buffer;
91 }
92
93 static void
94 service_add_ptr(const char *host, int ttl)
95 {
96 int len = dn_comp(host, mdns_buf, sizeof(mdns_buf), NULL, NULL);
97
98 if (len < 1)
99 return;
100
101 dns_add_answer(TYPE_PTR, mdns_buf, len, ttl);
102 }
103
104 static void
105 service_add_srv(struct service *s, int ttl)
106 {
107 struct dns_srv_data *sd = (struct dns_srv_data *) mdns_buf;
108 int len = sizeof(*sd);
109
110 len += dn_comp(mdns_hostname_local, mdns_buf + len, sizeof(mdns_buf) - len, NULL, NULL);
111 if (len <= sizeof(*sd))
112 return;
113
114 sd->port = cpu_to_be16(s->port);
115 dns_add_answer(TYPE_SRV, mdns_buf, len, ttl);
116 }
117
118 #define TOUT_LOOKUP 60
119
120 static time_t
121 service_timeout(struct service *s)
122 {
123 time_t t = monotonic_time();
124
125 if (t - s->t <= TOUT_LOOKUP) {
126 DBG(2, "t=%" PRId64 ", s->t=%" PRId64 ", t - s->t = %" PRId64 "\n", (int64_t)t, (int64_t)s->t, (int64_t)(t - s->t));
127 return 0;
128 }
129
130 return t;
131 }
132
133 static void
134 service_reply_single(struct interface *iface, struct sockaddr *to, struct service *s, int ttl, int force)
135 {
136 const char *host = service_instance_name(s);
137 char *service = strstr(host, "._");
138 time_t t = service_timeout(s);
139
140
141 if (!force && (!s->active || !service || !t))
142 return;
143
144 service++;
145
146 s->t = t;
147
148 dns_init_answer();
149 service_add_ptr(service_instance_name(s), ttl);
150 dns_send_answer(iface, to, service);
151
152 dns_init_answer();
153 service_add_srv(s, ttl);
154 if (s->txt && s->txt_len)
155 dns_add_answer(TYPE_TXT, (uint8_t *) s->txt, s->txt_len, ttl);
156 dns_send_answer(iface, to, host);
157 }
158
159 void
160 service_reply(struct interface *iface, struct sockaddr *to, const char *instance, const char *service_domain, int ttl)
161 {
162 struct service *s;
163
164 vlist_for_each_element(&services, s, node) {
165 if (instance && strcmp(s->instance, instance))
166 continue;
167 if (service_domain && strcmp(s->service, service_domain))
168 continue;
169 service_reply_single(iface, to, s, ttl, 0);
170 }
171 }
172
173 void
174 service_announce_services(struct interface *iface, struct sockaddr *to, int ttl)
175 {
176 struct service *s;
177
178 vlist_for_each_element(&services, s, node) {
179 s->t = 0;
180 if (ttl) {
181 dns_init_answer();
182 service_add_ptr(s->service, ttl);
183 dns_send_answer(iface, to, C_DNS_SD);
184 }
185 service_reply_single(iface, to, s, ttl, 0);
186 }
187 }
188
189 static void
190 service_update(struct vlist_tree *tree, struct vlist_node *node_new,
191 struct vlist_node *node_old)
192 {
193 struct interface *iface;
194 struct service *s;
195
196 if (!node_old) {
197 s = container_of(node_new, struct service, node);
198 if (service_init_announce)
199 vlist_for_each_element(&interfaces, iface, node) {
200 s->t = 0;
201 service_reply_single(iface, NULL, s, announce_ttl, 1);
202 }
203 return;
204 }
205
206 s = container_of(node_old, struct service, node);
207 if (!node_new && service_init_announce)
208 vlist_for_each_element(&interfaces, iface, node)
209 service_reply_single(iface, NULL, s, 0, 1);
210 free(s);
211 }
212
213 static void
214 service_load_blob(struct blob_attr *b)
215 {
216 struct blob_attr *txt, *_tb[__SERVICE_MAX];
217 struct service *s;
218 char *d_instance, *d_service, *d_id;
219 uint8_t *d_txt;
220 int rem2;
221 int txt_len = 0;
222 unsigned int n;
223
224 blobmsg_parse(service_policy, ARRAY_SIZE(service_policy),
225 _tb, blobmsg_data(b), blobmsg_data_len(b));
226 if (!_tb[SERVICE_PORT] || !_tb[SERVICE_SERVICE])
227 return;
228
229 if (_tb[SERVICE_TXT])
230 blobmsg_for_each_attr(txt, _tb[SERVICE_TXT], rem2)
231 txt_len += 1 + strlen(blobmsg_get_string(txt));
232
233 n = strlen(blobmsg_name(b));
234 s = calloc_a(sizeof(*s),
235 &d_id, n + 1,
236 &d_instance, _tb[SERVICE_INSTANCE] ? strlen(blobmsg_get_string(_tb[SERVICE_INSTANCE])) + 1 : 0,
237 &d_service, strlen(blobmsg_get_string(_tb[SERVICE_SERVICE])) + 1,
238 &d_txt, txt_len);
239 if (!s)
240 return;
241
242 s->port = blobmsg_get_u32(_tb[SERVICE_PORT]);
243 s->id = strncpy(d_id, blobmsg_name(b), n);
244 if (_tb[SERVICE_INSTANCE])
245 s->instance = strcpy(d_instance, blobmsg_get_string(_tb[SERVICE_INSTANCE]));
246 else
247 s->instance = umdns_host_label;
248 s->service = strcpy(d_service, blobmsg_get_string(_tb[SERVICE_SERVICE]));
249 s->active = 1;
250 s->t = 0;
251 s->txt_len = txt_len;
252 s->txt = d_txt;
253
254 if (_tb[SERVICE_TXT])
255 blobmsg_for_each_attr(txt, _tb[SERVICE_TXT], rem2) {
256 int len = strlen(blobmsg_get_string(txt));
257 if (!len)
258 return;
259 if (len > 0xff)
260 len = 0xff;
261 *d_txt = len;
262 d_txt++;
263 memcpy(d_txt, blobmsg_get_string(txt), len);
264 d_txt += len;
265 }
266
267 vlist_add(&services, &s->node, s->id);
268 }
269
270 static void
271 service_load(char *path)
272 {
273 struct blob_attr *cur;
274 glob_t gl;
275 int i, rem;
276
277 if (glob(path, GLOB_NOESCAPE | GLOB_MARK, NULL, &gl))
278 return;
279
280 for (i = 0; i < gl.gl_pathc; i++) {
281 blob_buf_init(&b, 0);
282 if (blobmsg_add_json_from_file(&b, gl.gl_pathv[i])) {
283 blob_for_each_attr(cur, b.head, rem)
284 service_load_blob(cur);
285 } else {
286 fprintf(stderr, "Error reading %s JSON\n", gl.gl_pathv[i]);
287 }
288 }
289 globfree(&gl);
290 }
291
292 static void
293 service_init_cb(struct ubus_request *req, int type, struct blob_attr *msg)
294 {
295 struct blob_attr *cur;
296 int rem;
297
298 get_hostname();
299
300 vlist_update(&services);
301 service_load("/etc/umdns/*");
302
303 blob_for_each_attr(cur, msg, rem) {
304 struct blob_attr *cur2;
305 int rem2;
306
307 blobmsg_for_each_attr(cur2, cur, rem2) {
308 struct blob_attr *cur3;
309 int rem3;
310
311 if (strcmp(blobmsg_name(cur2), "instances"))
312 continue;
313
314 blobmsg_for_each_attr(cur3, cur2, rem3) {
315 struct blob_attr *cur4;
316 int rem4;
317 int running = 0;
318
319 blobmsg_for_each_attr(cur4, cur3, rem4) {
320 const char *name = blobmsg_name(cur4);
321
322 if (!strcmp(name, "running")) {
323 running = blobmsg_get_bool(cur4);
324 } else if (running && !strcmp(name, "data")) {
325 struct blob_attr *cur5;
326 int rem5;
327
328 blobmsg_for_each_attr(cur5, cur4, rem5) {
329 struct blob_attr *cur6;
330 int rem6;
331
332 if (strcmp(blobmsg_name(cur5), "mdns"))
333 continue;
334
335 blobmsg_for_each_attr(cur6, cur5, rem6)
336 service_load_blob(cur6);
337 }
338 break;
339 }
340 }
341 }
342 }
343 }
344 vlist_flush(&services);
345 }
346
347 void
348 service_init(int announce)
349 {
350 get_hostname();
351
352 service_init_announce = announce;
353 ubus_service_list(service_init_cb);
354 }
355
356 void
357 service_cleanup(void)
358 {
359 vlist_flush(&services);
360 blob_buf_free(&b);
361 }