service: convert to vlist
[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 <netinet/in.h>
16 #include <arpa/nameser.h>
17 #include <sys/socket.h>
18 #include <netinet/in.h>
19 #include <arpa/inet.h>
20
21 #include <resolv.h>
22 #include <glob.h>
23 #include <stdio.h>
24 #include <time.h>
25
26 #include <uci.h>
27 #include <uci_blob.h>
28
29 #include <libubox/vlist.h>
30 #include <libubox/uloop.h>
31 #include <libubox/avl-cmp.h>
32 #include <libubox/blobmsg_json.h>
33
34 #include "dns.h"
35 #include "service.h"
36 #include "util.h"
37
38 enum {
39         SERVICE_PORT,
40         SERVICE_TXT,
41         __SERVICE_MAX,
42 };
43
44 struct service {
45         struct vlist_node node;
46
47         time_t t;
48
49         const char *service;
50         const char *daemon;
51         const uint8_t *txt;
52         int txt_len;
53         int port;
54         int active;
55 };
56
57 static const struct blobmsg_policy service_policy[__SERVICE_MAX] = {
58         [SERVICE_PORT] = { .name = "port", .type = BLOBMSG_TYPE_INT32 },
59         [SERVICE_TXT] = { .name = "txt", .type = BLOBMSG_TYPE_ARRAY },
60 };
61
62 static const struct uci_blob_param_list service_attr_list = {
63         .n_params = __SERVICE_MAX,
64         .params = service_policy,
65 };
66
67 static void
68 service_update(struct vlist_tree *tree, struct vlist_node *node_new,
69                struct vlist_node *node_old);
70
71 static struct blob_buf b;
72 static VLIST_TREE(services, avl_strcmp, service_update, false, false);
73 char *hostname = NULL;
74 static char *sdudp =  "_services._dns-sd._udp.local";
75 static char *sdtcp =  "_services._dns-sd._tcp.local";
76
77 char *
78 service_name(const char *domain)
79 {
80         static char buffer[256];
81
82         snprintf(buffer, sizeof(buffer), "%s.%s", hostname, domain);
83
84         return buffer;
85 }
86
87 static void
88 service_send_ptr(struct uloop_fd *u, struct service *s)
89 {
90         unsigned char buffer[MAX_NAME_LEN];
91         const char *host = service_name(s->service);
92         int len = dn_comp(host, buffer, MAX_NAME_LEN, NULL, NULL);
93
94         if (len < 1)
95                 return;
96
97         dns_add_answer(TYPE_PTR, buffer, len);
98 }
99
100 static void
101 service_send_ptr_c(struct uloop_fd *u, const char *host)
102 {
103         unsigned char buffer[MAX_NAME_LEN];
104         int len = dn_comp(host, buffer, MAX_NAME_LEN, NULL, NULL);
105
106         if (len < 1)
107                 return;
108
109         dns_add_answer(TYPE_PTR, buffer, len);
110 }
111
112 static void
113 service_send_a(struct uloop_fd *u)
114 {
115         unsigned char buffer[MAX_NAME_LEN];
116         char *host = service_name("local");
117         int len = dn_comp(host, buffer, MAX_NAME_LEN, NULL, NULL);
118         struct in_addr in;
119
120         if (!inet_aton(iface_ip, &in)) {
121                 fprintf(stderr, "%s is not valid\n", iface_ip);
122                 return;
123         }
124
125         if (len < 1)
126                 return;
127
128         dns_add_answer(TYPE_A, (uint8_t *) &in.s_addr, 4);
129 }
130
131 static void
132 service_send_srv(struct uloop_fd *u, struct service *s)
133 {
134         unsigned char buffer[MAX_NAME_LEN];
135         struct dns_srv_data *sd;
136         char *host = service_name("local");
137         int len = dn_comp(host, buffer, MAX_NAME_LEN, NULL, NULL);
138
139         if (len < 1)
140                 return;
141
142         sd = calloc(1, len + sizeof(struct dns_srv_data));
143         if (!sd)
144                 return;
145
146         sd->port = cpu_to_be16(s->port);
147         memcpy(&sd[1], buffer, len);
148         host = service_name(s->service);
149         dns_add_answer(TYPE_SRV, (uint8_t *) sd, len + sizeof(struct dns_srv_data));
150         free(sd);
151 }
152
153 #define TOUT_LOOKUP     60
154
155 static int
156 service_timeout(struct service *s)
157 {
158         time_t t = time(NULL);
159
160         if (t - s->t <= TOUT_LOOKUP)
161                 return 0;
162
163         s->t = t;
164
165         return 1;
166 }
167
168 void
169 service_reply_a(struct uloop_fd *u, int type)
170 {
171         if (type != TYPE_A)
172                 return;
173
174         dns_init_answer();
175         service_send_a(u);
176         dns_send_answer(u, service_name("local"));
177 }
178
179 void
180 service_reply(struct uloop_fd *u, const char *match)
181 {
182         struct service *s;
183
184         vlist_for_each_element(&services, s, node) {
185                 char *host = service_name(s->service);
186                 char *service = strstr(host, "._");
187
188                 if (!s->active || !service || !service_timeout(s))
189                         continue;
190
191                 service++;
192
193                 if (match && strcmp(match, s->service))
194                         continue;
195
196                 dns_init_answer();
197                 service_send_ptr(u, s);
198                 dns_send_answer(u, service);
199
200                 dns_init_answer();
201                 service_send_srv(u, s);
202                 if (s->txt && s->txt_len)
203                         dns_add_answer(TYPE_TXT, (uint8_t *) s->txt, s->txt_len);
204                 dns_send_answer(u, host);
205         }
206
207         if (match)
208                 return;
209
210         dns_init_answer();
211         service_send_a(u);
212         dns_send_answer(u, service_name("local"));
213 }
214
215 void
216 service_announce_services(struct uloop_fd *u, const char *service)
217 {
218         struct service *s;
219         int tcp = 1;
220
221         if (!strcmp(service, sdudp))
222                 tcp = 0;
223         else if (strcmp(service, sdtcp))
224                 return;
225
226         vlist_for_each_element(&services, s, node) {
227                 if (!strstr(s->service, "._tcp") && tcp)
228                         continue;
229                 if (!strstr(s->service, "._udp") && !tcp)
230                         continue;
231                 s->t = 0;
232                 dns_init_answer();
233                 service_send_ptr_c(u, s->service);
234                 if (tcp)
235                         dns_send_answer(u, sdtcp);
236                 else
237                         dns_send_answer(u, sdudp);
238                 service_reply(u, s->service);
239         }
240 }
241
242 void
243 service_announce(struct uloop_fd *u)
244 {
245         service_announce_services(u, sdudp);
246         service_announce_services(u, sdtcp);
247 }
248
249 static void
250 service_update(struct vlist_tree *tree, struct vlist_node *node_new,
251                struct vlist_node *node_old)
252 {
253         struct service *s;
254
255         if (!node_old)
256                 return;
257
258         s = container_of(node_old, struct service, node);
259         free(s);
260 }
261
262 static void
263 service_load(char *path)
264 {
265         struct blob_attr *txt, *cur, *_tb[__SERVICE_MAX];
266         int rem, i;
267         glob_t gl;
268
269         if (glob(path, GLOB_NOESCAPE | GLOB_MARK, NULL, &gl))
270                 return;
271
272         for (i = 0; i < gl.gl_pathc; i++) {
273                 blob_buf_init(&b, 0);
274
275                 if (!blobmsg_add_json_from_file(&b, gl.gl_pathv[i]))
276                         continue;
277                 blob_for_each_attr(cur, b.head, rem) {
278                         struct service *s;
279                         char *d_service, *d_daemon;
280                         uint8_t *d_txt;
281                         int rem2;
282                         int txt_len = 0;
283
284                         blobmsg_parse(service_policy, ARRAY_SIZE(service_policy),
285                                 _tb, blobmsg_data(cur), blobmsg_data_len(cur));
286                         if (!_tb[SERVICE_PORT] || !_tb[SERVICE_TXT])
287                                 continue;
288
289                         blobmsg_for_each_attr(txt, _tb[SERVICE_TXT], rem2)
290                                 txt_len += 1 + strlen(blobmsg_get_string(txt));
291
292                         s = calloc_a(sizeof(*s),
293                                 &d_daemon, strlen(gl.gl_pathv[i]) + 1,
294                                 &d_service, strlen(blobmsg_name(cur)) + 1,
295                                 &d_txt, txt_len);
296                         if (!s)
297                                 continue;
298
299                         s->port = blobmsg_get_u32(_tb[SERVICE_PORT]);
300                         s->service = strcpy(d_service, blobmsg_name(cur));
301                         s->daemon = strcpy(d_daemon, gl.gl_pathv[i]);
302                         s->active = 1;
303                         s->t = 0;
304                         s->txt_len = txt_len;
305                         s->txt = d_txt;
306
307                         blobmsg_for_each_attr(txt, _tb[SERVICE_TXT], rem2) {
308                                 int len = strlen(blobmsg_get_string(txt));
309                                 if (!len)
310                                         continue;
311                                 if (len > 0xff)
312                                         len = 0xff;
313                                 *d_txt = len;
314                                 d_txt++;
315                                 memcpy(d_txt, blobmsg_get_string(txt), len);
316                                 d_txt += len;
317                         }
318
319                         vlist_add(&services, &s->node, s->service);
320                 }
321         }
322 }
323
324 void
325 service_init(void)
326 {
327         if (!hostname)
328                 hostname = get_hostname();
329
330         vlist_update(&services);
331         service_load("/tmp/run/mdnsd/*");
332         vlist_flush(&services);
333 }
334
335 void
336 service_cleanup(void)
337 {
338         vlist_flush(&services);
339 }