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