1 /**
2  * @file avahi.c Avahi Zeroconf Module
3  *
4  * Copyright (C) 2010 Creytiv.com
5  * Copyright (C) 2017 Jonathan Sieber
6  */
7 
8 /**
9  * @defgroup avahi avahi
10  *
11  * This module implements DNS Service Discovery via Avahi Client API
12  * It does 2 things:
13  * 1) Announce _sipuri._udp resource for the main UA (under the local IP)
14  * 2) Fills contact list with discovered hosts
15  *
16  * NOTE: This module is experimental.
17  *
18  */
19 
20 #include <re.h>
21 #include <baresip.h>
22 #include <stdlib.h>
23 #include <stdio.h>
24 #include <string.h>
25 
26 #include <avahi-common/simple-watch.h>
27 #include <avahi-client/lookup.h>
28 #include <avahi-client/publish.h>
29 #include <avahi-common/error.h>
30 
31 /* for if_nametoindex */
32 #include <net/if.h>
33 
34 /* gethostname, getaddrinfo */
35 #define __USE_XOPEN2K
36 #include <unistd.h>
37 #include <netdb.h>
38 
39 
40 struct avahi_st {
41 	AvahiSimplePoll* poll;
42 	AvahiClient* client;
43 	AvahiEntryGroup* group;
44 	AvahiServiceBrowser* browser;
45 	struct ua* local_ua;
46 	struct tmr poll_timer;
47 };
48 
49 static struct avahi_st* avahi = NULL;
50 
51 
group_callback(AvahiEntryGroup * group,AvahiEntryGroupState state,void * userdata)52 static void group_callback(AvahiEntryGroup* group,
53 	AvahiEntryGroupState state, void* userdata)
54 {
55 	(void)group;
56 	(void)userdata;
57 
58 	switch (state) {
59 
60 	case AVAHI_ENTRY_GROUP_ESTABLISHED:
61 		info ("avahi: Service Registration completed\n");
62 		break;
63 	case AVAHI_ENTRY_GROUP_FAILURE:
64 	case AVAHI_ENTRY_GROUP_COLLISION:
65 		warning("avahi: Service Registration failed\n");
66 		/* TODO: Think of smart way to handle collision? */
67 		break;
68 	case AVAHI_ENTRY_GROUP_UNCOMMITED:
69 	case AVAHI_ENTRY_GROUP_REGISTERING:
70 		/* Do nothing */
71 		break;
72 	}
73 }
74 
75 
create_services(AvahiClient * client)76 static void create_services(AvahiClient *client)
77 {
78 	int err;
79 	char buf[128] = "";
80 	char hostname[128] = "";
81 
82 	int if_idx = AVAHI_IF_UNSPEC;
83 	int af = AVAHI_PROTO_INET;
84 
85 	struct sa laddr;
86 
87 	/* Build announced sipuri as username@hostname */
88 	strncpy(hostname, avahi_client_get_host_name_fqdn(client),
89 		sizeof (hostname));
90 	re_snprintf(buf, sizeof(buf), "<sip:%s@%s>;regint=0",
91 				sys_username(),
92 				hostname);
93 
94 	info("avahi: Creating local UA %s\n", buf);
95 	err = ua_alloc(&avahi->local_ua, buf);
96 
97 	if (err) {
98 		warning("avahi: Could not create UA %s: %m\n", buf, err);
99 		return;
100 	}
101 
102 	re_snprintf(buf, sizeof(buf), "sip:%s@%s",
103 				sys_username(),
104 				hostname);
105 
106 	debug("avahi: Announcing URI: %s\n", buf);
107 
108 	/* Get interface number of baresip interface */
109 	if (str_isset(conf_config()->net.ifname)) {
110 		if_idx = if_nametoindex(conf_config()->net.ifname);
111 	}
112 
113 	if (net_af(baresip_network()) == AF_INET6) {
114 		af = AVAHI_PROTO_INET6;
115 	}
116 
117 	err |= sip_transp_laddr(uag_sip(), &laddr, SIP_TRANSP_UDP, 0);
118 	if (err) {
119 		warning("avahi: Can not find local SIP address\n");
120 	}
121 
122 	/* TODO: Query enabled transports and register these */
123 	avahi->group = avahi_entry_group_new(client, group_callback, NULL);
124 	err = avahi_entry_group_add_service(avahi->group,
125 		if_idx, af, 0,
126 		buf, "_sipuri._udp",
127 		NULL, NULL,
128 		ntohs(laddr.u.in.sin_port), NULL);
129 	err |= avahi_entry_group_commit(avahi->group);
130 
131 	if (err) {
132 		warning("avahi: Error in registering service");
133 	}
134 }
135 
136 
client_callback(AvahiClient * c,AvahiClientState state,AVAHI_GCC_UNUSED void * userdata)137 static void client_callback(AvahiClient *c, AvahiClientState state,
138 			    AVAHI_GCC_UNUSED void * userdata)
139 {
140 	(void)c;
141 
142 	switch (state) {
143 
144 	case AVAHI_CLIENT_S_RUNNING:
145 		info("avahi: Avahi Daemon running\n", state);
146 		break;
147 	default:
148 		warning("avahi: unknown client_callback: %d\n", state);
149 		break;
150 	}
151 }
152 
153 
add_contact(const char * uri,const AvahiAddress * address,uint16_t port)154 static void add_contact(const char* uri,
155 	const AvahiAddress *address, uint16_t port)
156 {
157 	int err;
158 	struct pl addr;
159 	char buf[128];
160 	struct contact *c;
161 	struct sa sa;
162 	struct sip_addr sipaddr;
163 
164 	/* Parse SIPURI to get username and stuff... */
165 	pl_set_str(&addr, uri);
166 	if (sip_addr_decode(&sipaddr, &addr)) {
167 		warning("avahi: could not decode sipuri %s\n", uri);
168 		return;
169 	}
170 
171 	if (address->proto == AVAHI_PROTO_INET6) {;
172 		sa_set_in6(&sa, address->data.ipv6.address, port);
173 	}
174 	else {
175 		sa_set_in(&sa, htonl(address->data.ipv4.address), port);
176 	}
177 
178 	re_snprintf(buf, sizeof(buf),
179 		"\"%r@%r\" <sip:%r@%J>;presence=p2p",
180 		&sipaddr.uri.user, &sipaddr.uri.host,
181 		&sipaddr.uri.user, &sa);
182 	pl_set_str(&addr, buf);
183 
184 	err = contact_add(baresip_contacts(), &c, &addr);
185 	if (err) {
186 		warning("Could not add contact %s: %m\n", buf, err);
187 	}
188 }
189 
190 
remove_contact_by_dname(const char * dname)191 static void remove_contact_by_dname(const char* dname)
192 {
193 	const struct list *lst;
194 	struct le *le;
195 
196 	/* remove sip: scheme for comparison */
197 	if (0 != re_regex(dname, str_len(dname), "^sip:")) {
198 		dname += 4;
199 	}
200 
201 	lst = contact_list(baresip_contacts());
202 
203 	for (le = list_head(lst); le; le = le->next) {
204 		struct contact *c = le->data;
205 		const struct sip_addr* addr = contact_addr(c);
206 
207 		if (pl_strcmp(&addr->dname, dname) == 0) {
208 			contact_remove(baresip_contacts(), c);
209 			return;
210 		}
211 	}
212 
213 	warning("avahi: Could not remove contact %s\n", dname);
214 }
215 
216 
resolve_callback(AvahiServiceResolver * r,AvahiIfIndex interface,AvahiProtocol protocol,AvahiResolverEvent event,const char * name,const char * type,const char * domain,const char * hostname,const AvahiAddress * address,uint16_t port,AvahiStringList * txt,AvahiLookupResultFlags flags,void * userdata)217 static void resolve_callback(
218 	AvahiServiceResolver *r,
219 	AvahiIfIndex interface,
220 	AvahiProtocol protocol,
221 	AvahiResolverEvent event,
222 	const char *name,
223 	const char *type,
224 	const char *domain,
225 	const char *hostname,
226 	const AvahiAddress *address,
227 	uint16_t port,
228 	AvahiStringList *txt,
229 	AvahiLookupResultFlags flags,
230 	void *userdata)
231 {
232 	(void)r;
233 	(void)interface;
234 	(void)txt;
235 	(void)userdata;
236 
237 	info("avahi: resolve %s %s %s %s\n", name, type, domain, hostname);
238 
239 	if (event == AVAHI_RESOLVER_FOUND) {
240 		if (protocol != address->proto) {
241 			warning("avahi: Resolved address type ambiguous\n");
242 		}
243 
244 		/* TODO: Process TXT field */
245 		if (!(flags & AVAHI_LOOKUP_RESULT_OUR_OWN)) {
246 			add_contact(name, address, port);
247 		}
248 	}
249 	else {
250 		warning("avahi: Resolver Error on %s: %s\n", name,
251 			avahi_strerror(avahi_client_errno(avahi->client)));
252 	}
253 
254 	avahi_service_resolver_free(r);
255 }
256 
257 
browse_callback(AvahiServiceBrowser * b,AvahiIfIndex interface,AvahiProtocol protocol,AvahiBrowserEvent event,const char * name,const char * type,const char * domain,AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,void * userdata)258 static void browse_callback(
259 	AvahiServiceBrowser *b,
260 	AvahiIfIndex interface,
261 	AvahiProtocol protocol,
262 	AvahiBrowserEvent event,
263 	const char *name,
264 	const char *type,
265 	const char *domain,
266 	AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
267 	void* userdata)
268 {
269 	int proto = AVAHI_PROTO_INET;
270 	(void)b;
271 	(void)userdata;
272 
273 	switch (event) {
274 	case AVAHI_BROWSER_NEW:
275 		debug("avahi: browse_callback if=%d proto=%d %s\n",
276 			interface, protocol, name);
277 		if (net_af(baresip_network()) == AF_INET6) {
278 			proto = AVAHI_PROTO_INET6;
279 		}
280 
281 		if (!(avahi_service_resolver_new(avahi->client,
282 				interface, protocol,
283 				name, type, domain,
284 				proto, 0, resolve_callback,
285 				avahi->client))) {
286 			warning("avahi: Error resolving %s\n", name);
287 		}
288 	break;
289 
290 	case AVAHI_BROWSER_REMOVE:
291 		remove_contact_by_dname(name);
292 		break;
293 	case AVAHI_BROWSER_ALL_FOR_NOW:
294 	case AVAHI_BROWSER_CACHE_EXHAUSTED:
295 		debug("avahi: (Browser) %s\n",
296 			event == AVAHI_BROWSER_CACHE_EXHAUSTED ?
297 			"CACHE_EXHAUSTED" : "ALL_FOR_NOW");
298 		break;
299 	default:
300 		warning("avahi: browse_callback %d %s\n", event, name);
301 		break;
302 	}
303 }
304 
305 
avahi_update(void * arg)306 static void avahi_update(void* arg)
307 {
308 	(void)arg;
309 
310 	avahi_simple_poll_iterate(avahi->poll, 0);
311 	tmr_start(&avahi->poll_timer, 250, avahi_update, 0);
312 }
313 
314 
destructor(void * arg)315 static void destructor(void* arg)
316 {
317 	struct avahi_st* a = arg;
318 
319 	tmr_cancel(&a->poll_timer);
320 
321 	mem_deref(a->local_ua);
322 
323 	/* Calling these destructor commands would be correct, but they
324 	 * spew out a lot of ugly D-Bus warning */
325 	if (a->browser) {
326 		avahi_service_browser_free(avahi->browser);
327 	}
328 
329 	if (a->group) {
330 		avahi_entry_group_free(avahi->group);
331 	}
332 
333 	if (a->client) {
334 		avahi_client_free(avahi->client);
335 	}
336 }
337 
338 
module_init(void)339 static int module_init(void)
340 {
341 	int err;
342 	avahi = mem_zalloc(sizeof(struct avahi_st), destructor);
343 	if (!avahi) {
344 		return ENOMEM;
345 	}
346 
347 	avahi->poll = avahi_simple_poll_new();
348 	avahi->client = avahi_client_new(
349 		avahi_simple_poll_get(avahi->poll),
350 		0, client_callback, NULL, &err);
351 
352 	/* Check wether creating the client object succeeded */
353 	if (!avahi->client) {
354 		warning("Failed to create client: %s\n", avahi_strerror(err));
355 		return err;
356 	}
357 
358 	avahi->browser = avahi_service_browser_new(avahi->client,
359 		AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, "_sipuri._udp", NULL,
360 		0, browse_callback, 0);
361 
362 	tmr_init(&avahi->poll_timer);
363 	avahi_update(0);
364 
365 	/* Register services when UA is ready */
366 	if (!avahi->group) {
367 		create_services(avahi->client);
368 	}
369 
370 	return 0;
371 }
372 
373 
module_close(void)374 static int module_close(void)
375 {
376 	debug("avahi: module_close\n");
377 
378 	avahi = mem_deref(avahi);
379 
380 	return 0;
381 }
382 
383 
384 const struct mod_export DECL_EXPORTS(avahi) = {
385 	"avahi",
386 	"application",
387 	module_init,
388 	module_close
389 };
390