1/*** 2 This file is part of avahi. 3 4 avahi is free software; you can redistribute it and/or modify it 5 under the terms of the GNU Lesser General Public License as 6 published by the Free Software Foundation; either version 2.1 of the 7 License, or (at your option) any later version. 8 9 avahi is distributed in the hope that it will be useful, but WITHOUT 10 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 11 or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General 12 Public License for more details. 13 14 You should have received a copy of the GNU Lesser General Public 15 License along with avahi; if not, write to the Free Software 16 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 17 USA. 18***/ 19 20#ifdef HAVE_CONFIG_H 21#include <config.h> 22#endif 23 24#include <string.h> 25#include <stdio.h> 26#include <stdlib.h> 27 28#include <avahi-common/domain.h> 29#include <avahi-common/timeval.h> 30#include "avahi-common/avahi-malloc.h" 31#include <avahi-common/error.h> 32 33#include "browse.h" 34#include "log.h" 35 36#define TIMEOUT_MSEC 5000 37 38struct AvahiSServiceResolver { 39 AvahiServer *server; 40 char *service_name; 41 char *service_type; 42 char *domain_name; 43 AvahiProtocol address_protocol; 44 45 AvahiIfIndex interface; 46 AvahiProtocol protocol; 47 48 AvahiSRecordBrowser *record_browser_srv; 49 AvahiSRecordBrowser *record_browser_txt; 50 AvahiSRecordBrowser *record_browser_a; 51 AvahiSRecordBrowser *record_browser_aaaa; 52 53 AvahiRecord *srv_record, *txt_record, *address_record; 54 AvahiLookupResultFlags srv_flags, txt_flags, address_flags; 55 56 AvahiSServiceResolverCallback callback; 57 void* userdata; 58 AvahiLookupFlags user_flags; 59 60 AvahiTimeEvent *time_event; 61 62 AVAHI_LLIST_FIELDS(AvahiSServiceResolver, resolver); 63}; 64 65static void finish(AvahiSServiceResolver *r, AvahiResolverEvent event) { 66 AvahiLookupResultFlags flags; 67 68 assert(r); 69 70 if (r->time_event) { 71 avahi_time_event_free(r->time_event); 72 r->time_event = NULL; 73 } 74 75 flags = 76 r->txt_flags | 77 r->srv_flags | 78 r->address_flags; 79 80 switch (event) { 81 case AVAHI_RESOLVER_FAILURE: 82 83 r->callback( 84 r, 85 r->interface, 86 r->protocol, 87 event, 88 r->service_name, 89 r->service_type, 90 r->domain_name, 91 NULL, 92 NULL, 93 0, 94 NULL, 95 flags, 96 r->userdata); 97 98 break; 99 100 case AVAHI_RESOLVER_FOUND: { 101 AvahiAddress a; 102 103 assert(event == AVAHI_RESOLVER_FOUND); 104 105 assert(r->srv_record); 106 107 if (r->address_record) { 108 switch (r->address_record->key->type) { 109 case AVAHI_DNS_TYPE_A: 110 a.proto = AVAHI_PROTO_INET; 111 a.data.ipv4 = r->address_record->data.a.address; 112 break; 113 114 case AVAHI_DNS_TYPE_AAAA: 115 a.proto = AVAHI_PROTO_INET6; 116 a.data.ipv6 = r->address_record->data.aaaa.address; 117 break; 118 119 default: 120 assert(0); 121 } 122 } 123 124 r->callback( 125 r, 126 r->interface, 127 r->protocol, 128 event, 129 r->service_name, 130 r->service_type, 131 r->domain_name, 132 r->srv_record->data.srv.name, 133 r->address_record ? &a : NULL, 134 r->srv_record->data.srv.port, 135 r->txt_record ? r->txt_record->data.txt.string_list : NULL, 136 flags, 137 r->userdata); 138 139 break; 140 } 141 } 142} 143 144static void time_event_callback(AvahiTimeEvent *e, void *userdata) { 145 AvahiSServiceResolver *r = userdata; 146 147 assert(e); 148 assert(r); 149 150 avahi_server_set_errno(r->server, AVAHI_ERR_TIMEOUT); 151 finish(r, AVAHI_RESOLVER_FAILURE); 152} 153 154static void start_timeout(AvahiSServiceResolver *r) { 155 struct timeval tv; 156 assert(r); 157 158 if (r->time_event) 159 return; 160 161 avahi_elapse_time(&tv, TIMEOUT_MSEC, 0); 162 163 r->time_event = avahi_time_event_new(r->server->time_event_queue, &tv, time_event_callback, r); 164} 165 166static void record_browser_callback( 167 AvahiSRecordBrowser*rr, 168 AvahiIfIndex interface, 169 AvahiProtocol protocol, 170 AvahiBrowserEvent event, 171 AvahiRecord *record, 172 AvahiLookupResultFlags flags, 173 void* userdata) { 174 175 AvahiSServiceResolver *r = userdata; 176 177 assert(rr); 178 assert(r); 179 180 if (rr == r->record_browser_aaaa || rr == r->record_browser_a) 181 r->address_flags = flags; 182 else if (rr == r->record_browser_srv) 183 r->srv_flags = flags; 184 else if (rr == r->record_browser_txt) 185 r->txt_flags = flags; 186 187 switch (event) { 188 189 case AVAHI_BROWSER_NEW: { 190 int changed = 0; 191 assert(record); 192 193 if (r->interface > 0 && interface > 0 && interface != r->interface) 194 return; 195 196 if (r->protocol != AVAHI_PROTO_UNSPEC && protocol != AVAHI_PROTO_UNSPEC && protocol != r->protocol) 197 return; 198 199 if (r->interface <= 0) 200 r->interface = interface; 201 202 if (r->protocol == AVAHI_PROTO_UNSPEC) 203 r->protocol = protocol; 204 205 switch (record->key->type) { 206 case AVAHI_DNS_TYPE_SRV: 207 if (!r->srv_record) { 208 r->srv_record = avahi_record_ref(record); 209 changed = 1; 210 211 if (r->record_browser_a) { 212 avahi_s_record_browser_free(r->record_browser_a); 213 r->record_browser_a = NULL; 214 } 215 216 if (r->record_browser_aaaa) { 217 avahi_s_record_browser_free(r->record_browser_aaaa); 218 r->record_browser_aaaa = NULL; 219 } 220 221 if (!(r->user_flags & AVAHI_LOOKUP_NO_ADDRESS)) { 222 223 if (r->address_protocol == AVAHI_PROTO_INET || r->address_protocol == AVAHI_PROTO_UNSPEC) { 224 AvahiKey *k = avahi_key_new(r->srv_record->data.srv.name, AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_A); 225 r->record_browser_a = avahi_s_record_browser_new(r->server, r->interface, r->protocol, k, r->user_flags & ~(AVAHI_LOOKUP_NO_TXT|AVAHI_LOOKUP_NO_ADDRESS), record_browser_callback, r); 226 avahi_key_unref(k); 227 } 228 229 if (r->address_protocol == AVAHI_PROTO_INET6 || r->address_protocol == AVAHI_PROTO_UNSPEC) { 230 AvahiKey *k = avahi_key_new(r->srv_record->data.srv.name, AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_AAAA); 231 r->record_browser_aaaa = avahi_s_record_browser_new(r->server, r->interface, r->protocol, k, r->user_flags & ~(AVAHI_LOOKUP_NO_TXT|AVAHI_LOOKUP_NO_ADDRESS), record_browser_callback, r); 232 avahi_key_unref(k); 233 } 234 } 235 } 236 break; 237 238 case AVAHI_DNS_TYPE_TXT: 239 240 assert(!(r->user_flags & AVAHI_LOOKUP_NO_TXT)); 241 242 if (!r->txt_record) { 243 r->txt_record = avahi_record_ref(record); 244 changed = 1; 245 } 246 break; 247 248 case AVAHI_DNS_TYPE_A: 249 case AVAHI_DNS_TYPE_AAAA: 250 251 assert(!(r->user_flags & AVAHI_LOOKUP_NO_ADDRESS)); 252 253 if (!r->address_record) { 254 r->address_record = avahi_record_ref(record); 255 changed = 1; 256 } 257 break; 258 259 default: 260 abort(); 261 } 262 263 264 if (changed && 265 r->srv_record && 266 (r->txt_record || (r->user_flags & AVAHI_LOOKUP_NO_TXT)) && 267 (r->address_record || (r->user_flags & AVAHI_LOOKUP_NO_ADDRESS))) 268 finish(r, AVAHI_RESOLVER_FOUND); 269 270 break; 271 272 } 273 274 case AVAHI_BROWSER_REMOVE: 275 276 assert(record); 277 278 switch (record->key->type) { 279 case AVAHI_DNS_TYPE_SRV: 280 281 if (r->srv_record && avahi_record_equal_no_ttl(record, r->srv_record)) { 282 avahi_record_unref(r->srv_record); 283 r->srv_record = NULL; 284 285 if (r->record_browser_a) { 286 avahi_s_record_browser_free(r->record_browser_a); 287 r->record_browser_a = NULL; 288 } 289 290 if (r->record_browser_aaaa) { 291 avahi_s_record_browser_free(r->record_browser_aaaa); 292 r->record_browser_aaaa = NULL; 293 } 294 295 /** Look for a replacement */ 296 avahi_s_record_browser_restart(r->record_browser_srv); 297 start_timeout(r); 298 } 299 300 break; 301 302 case AVAHI_DNS_TYPE_TXT: 303 304 assert(!(r->user_flags & AVAHI_LOOKUP_NO_TXT)); 305 306 if (r->txt_record && avahi_record_equal_no_ttl(record, r->txt_record)) { 307 avahi_record_unref(r->txt_record); 308 r->txt_record = NULL; 309 310 /** Look for a replacement */ 311 avahi_s_record_browser_restart(r->record_browser_txt); 312 start_timeout(r); 313 } 314 break; 315 316 case AVAHI_DNS_TYPE_A: 317 case AVAHI_DNS_TYPE_AAAA: 318 319 assert(!(r->user_flags & AVAHI_LOOKUP_NO_ADDRESS)); 320 321 if (r->address_record && avahi_record_equal_no_ttl(record, r->address_record)) { 322 avahi_record_unref(r->address_record); 323 r->address_record = NULL; 324 325 /** Look for a replacement */ 326 if (r->record_browser_aaaa) 327 avahi_s_record_browser_restart(r->record_browser_aaaa); 328 if (r->record_browser_a) 329 avahi_s_record_browser_restart(r->record_browser_a); 330 start_timeout(r); 331 } 332 break; 333 334 default: 335 abort(); 336 } 337 338 break; 339 340 case AVAHI_BROWSER_CACHE_EXHAUSTED: 341 case AVAHI_BROWSER_ALL_FOR_NOW: 342 break; 343 344 case AVAHI_BROWSER_FAILURE: 345 346 if (rr == r->record_browser_a && r->record_browser_aaaa) { 347 /* We were looking for both AAAA and A, and the other query is still living, so we'll not die */ 348 avahi_s_record_browser_free(r->record_browser_a); 349 r->record_browser_a = NULL; 350 break; 351 } 352 353 if (rr == r->record_browser_aaaa && r->record_browser_a) { 354 /* We were looking for both AAAA and A, and the other query is still living, so we'll not die */ 355 avahi_s_record_browser_free(r->record_browser_aaaa); 356 r->record_browser_aaaa = NULL; 357 break; 358 } 359 360 /* Hmm, everything's lost, tell the user */ 361 362 if (r->record_browser_srv) 363 avahi_s_record_browser_free(r->record_browser_srv); 364 if (r->record_browser_txt) 365 avahi_s_record_browser_free(r->record_browser_txt); 366 if (r->record_browser_a) 367 avahi_s_record_browser_free(r->record_browser_a); 368 if (r->record_browser_aaaa) 369 avahi_s_record_browser_free(r->record_browser_aaaa); 370 371 r->record_browser_srv = r->record_browser_txt = r->record_browser_a = r->record_browser_aaaa = NULL; 372 373 finish(r, AVAHI_RESOLVER_FAILURE); 374 break; 375 } 376} 377 378AvahiSServiceResolver *avahi_s_service_resolver_new( 379 AvahiServer *server, 380 AvahiIfIndex interface, 381 AvahiProtocol protocol, 382 const char *name, 383 const char *type, 384 const char *domain, 385 AvahiProtocol aprotocol, 386 AvahiLookupFlags flags, 387 AvahiSServiceResolverCallback callback, 388 void* userdata) { 389 390 AvahiSServiceResolver *r; 391 AvahiKey *k; 392 char n[AVAHI_DOMAIN_NAME_MAX]; 393 int ret; 394 395 assert(server); 396 assert(type); 397 assert(callback); 398 399 AVAHI_CHECK_VALIDITY_RETURN_NULL(server, AVAHI_IF_VALID(interface), AVAHI_ERR_INVALID_INTERFACE); 400 AVAHI_CHECK_VALIDITY_RETURN_NULL(server, AVAHI_PROTO_VALID(protocol), AVAHI_ERR_INVALID_PROTOCOL); 401 AVAHI_CHECK_VALIDITY_RETURN_NULL(server, AVAHI_PROTO_VALID(aprotocol), AVAHI_ERR_INVALID_PROTOCOL); 402 AVAHI_CHECK_VALIDITY_RETURN_NULL(server, !domain || avahi_is_valid_domain_name(domain), AVAHI_ERR_INVALID_DOMAIN_NAME); 403 AVAHI_CHECK_VALIDITY_RETURN_NULL(server, !name || avahi_is_valid_service_name(name), AVAHI_ERR_INVALID_SERVICE_NAME); 404 AVAHI_CHECK_VALIDITY_RETURN_NULL(server, avahi_is_valid_service_type_strict(type), AVAHI_ERR_INVALID_SERVICE_TYPE); 405 AVAHI_CHECK_VALIDITY_RETURN_NULL(server, AVAHI_FLAGS_VALID(flags, AVAHI_LOOKUP_USE_WIDE_AREA|AVAHI_LOOKUP_USE_MULTICAST|AVAHI_LOOKUP_NO_TXT|AVAHI_LOOKUP_NO_ADDRESS), AVAHI_ERR_INVALID_FLAGS); 406 407 if (!domain) 408 domain = server->domain_name; 409 410 if ((ret = avahi_service_name_join(n, sizeof(n), name, type, domain)) < 0) { 411 avahi_server_set_errno(server, ret); 412 return NULL; 413 } 414 415 if (!(r = avahi_new(AvahiSServiceResolver, 1))) { 416 avahi_server_set_errno(server, AVAHI_ERR_NO_MEMORY); 417 return NULL; 418 } 419 420 r->server = server; 421 r->service_name = avahi_strdup(name); 422 r->service_type = avahi_normalize_name_strdup(type); 423 r->domain_name = avahi_normalize_name_strdup(domain); 424 r->callback = callback; 425 r->userdata = userdata; 426 r->address_protocol = aprotocol; 427 r->srv_record = r->txt_record = r->address_record = NULL; 428 r->srv_flags = r->txt_flags = r->address_flags = 0; 429 r->interface = interface; 430 r->protocol = protocol; 431 r->user_flags = flags; 432 r->record_browser_a = r->record_browser_aaaa = r->record_browser_srv = r->record_browser_txt = NULL; 433 r->time_event = NULL; 434 AVAHI_LLIST_PREPEND(AvahiSServiceResolver, resolver, server->service_resolvers, r); 435 436 k = avahi_key_new(n, AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_SRV); 437 r->record_browser_srv = avahi_s_record_browser_new(server, interface, protocol, k, flags & ~(AVAHI_LOOKUP_NO_TXT|AVAHI_LOOKUP_NO_ADDRESS), record_browser_callback, r); 438 avahi_key_unref(k); 439 440 if (!r->record_browser_srv) { 441 avahi_s_service_resolver_free(r); 442 return NULL; 443 } 444 445 if (!(flags & AVAHI_LOOKUP_NO_TXT)) { 446 k = avahi_key_new(n, AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_TXT); 447 r->record_browser_txt = avahi_s_record_browser_new(server, interface, protocol, k, flags & ~(AVAHI_LOOKUP_NO_TXT|AVAHI_LOOKUP_NO_ADDRESS), record_browser_callback, r); 448 avahi_key_unref(k); 449 450 if (!r->record_browser_txt) { 451 avahi_s_service_resolver_free(r); 452 return NULL; 453 } 454 } 455 456 start_timeout(r); 457 458 return r; 459} 460 461void avahi_s_service_resolver_free(AvahiSServiceResolver *r) { 462 assert(r); 463 464 AVAHI_LLIST_REMOVE(AvahiSServiceResolver, resolver, r->server->service_resolvers, r); 465 466 if (r->time_event) 467 avahi_time_event_free(r->time_event); 468 469 if (r->record_browser_srv) 470 avahi_s_record_browser_free(r->record_browser_srv); 471 if (r->record_browser_txt) 472 avahi_s_record_browser_free(r->record_browser_txt); 473 if (r->record_browser_a) 474 avahi_s_record_browser_free(r->record_browser_a); 475 if (r->record_browser_aaaa) 476 avahi_s_record_browser_free(r->record_browser_aaaa); 477 478 if (r->srv_record) 479 avahi_record_unref(r->srv_record); 480 if (r->txt_record) 481 avahi_record_unref(r->txt_record); 482 if (r->address_record) 483 avahi_record_unref(r->address_record); 484 485 avahi_free(r->service_name); 486 avahi_free(r->service_type); 487 avahi_free(r->domain_name); 488 avahi_free(r); 489} 490