Home | History | Annotate | Download | only in avahi-core
      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 
     38 struct 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 
     65 static 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 
    144 static 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 
    154 static 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 
    166 static 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 
    378 AvahiSServiceResolver *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 
    461 void 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