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