Home | History | Annotate | Download | only in buffet
      1 /*
      2  * Copyright 2015 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 #include <vector>
     18 
     19 #include "buffet/avahi_mdns_client.h"
     20 
     21 #include <avahi-common/address.h>
     22 #include <avahi-common/defs.h>
     23 #include <avahi-common/error.h>
     24 
     25 #include <base/guid.h>
     26 #include <brillo/errors/error.h>
     27 
     28 using brillo::ErrorPtr;
     29 
     30 namespace buffet {
     31 
     32 std::unique_ptr<MdnsClient> MdnsClient::CreateInstance() {
     33   return std::unique_ptr<MdnsClient>{new AvahiMdnsClient()};
     34 
     35 }
     36 
     37 namespace {
     38 
     39 void HandleGroupStateChanged(AvahiEntryGroup* g,
     40                              AvahiEntryGroupState state,
     41                              AVAHI_GCC_UNUSED void* userdata) {
     42   if (state == AVAHI_ENTRY_GROUP_COLLISION ||
     43       state == AVAHI_ENTRY_GROUP_FAILURE) {
     44     LOG(ERROR) << "Avahi service group error: " << state;
     45   }
     46 }
     47 
     48 }  // namespace
     49 
     50 AvahiMdnsClient::AvahiMdnsClient()
     51     : service_name_(base::GenerateGUID()) {
     52   thread_pool_.reset(avahi_threaded_poll_new());
     53   CHECK(thread_pool_);
     54 
     55   int ret = 0;
     56 
     57   client_.reset(avahi_client_new(
     58       avahi_threaded_poll_get(thread_pool_.get()), {},
     59       &AvahiMdnsClient::OnAvahiClientStateUpdate, this, &ret));
     60   CHECK(client_) << avahi_strerror(ret);
     61 
     62   avahi_threaded_poll_start(thread_pool_.get());
     63 
     64   group_.reset(avahi_entry_group_new(client_.get(), HandleGroupStateChanged,
     65                                      nullptr));
     66   CHECK(group_) << avahi_strerror(avahi_client_errno(client_.get()))
     67                 << ". Check avahi-daemon configuration";
     68 }
     69 
     70 AvahiMdnsClient::~AvahiMdnsClient() {
     71   if (thread_pool_)
     72     avahi_threaded_poll_stop(thread_pool_.get());
     73 }
     74 
     75 void AvahiMdnsClient::PublishService(const std::string& service_type,
     76                                      uint16_t port,
     77                                      const std::vector<std::string>& txt) {
     78   CHECK(group_);
     79   CHECK_EQ("_privet._tcp", service_type);
     80 
     81   if (prev_port_ == port && prev_service_type_ == service_type &&
     82       txt_records_ == txt) {
     83     return;
     84   }
     85 
     86   // Create txt record.
     87   std::unique_ptr<AvahiStringList, decltype(&avahi_string_list_free)> txt_list{
     88       nullptr, &avahi_string_list_free};
     89 
     90   if (!txt.empty()) {
     91     std::vector<const char*> txt_vector_ptr;
     92 
     93     for (const auto& i : txt)
     94       txt_vector_ptr.push_back(i.c_str());
     95 
     96     txt_list.reset(avahi_string_list_new_from_array(txt_vector_ptr.data(),
     97                                                     txt_vector_ptr.size()));
     98     CHECK(txt_list);
     99   }
    100 
    101   int ret = 0;
    102   txt_records_ = txt;
    103 
    104   if (prev_port_ == port && prev_service_type_ == service_type) {
    105     ret = avahi_entry_group_update_service_txt_strlst(
    106         group_.get(), AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, {},
    107         service_name_.c_str(), service_type.c_str(), nullptr, txt_list.get());
    108 
    109     CHECK_GE(ret, 0) << avahi_strerror(ret);
    110   } else {
    111     prev_port_ = port;
    112     prev_service_type_ = service_type;
    113 
    114     avahi_entry_group_reset(group_.get());
    115     CHECK(avahi_entry_group_is_empty(group_.get()));
    116 
    117     ret = avahi_entry_group_add_service_strlst(
    118         group_.get(), AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, {},
    119         service_name_.c_str(), service_type.c_str(), nullptr, nullptr, port,
    120         txt_list.get());
    121     CHECK_GE(ret, 0) << avahi_strerror(ret);
    122 
    123     ret = avahi_entry_group_commit(group_.get());
    124     CHECK_GE(ret, 0) << avahi_strerror(ret);
    125   }
    126 }
    127 
    128 void AvahiMdnsClient::StopPublishing(const std::string& service_type) {
    129   CHECK(group_);
    130   avahi_entry_group_reset(group_.get());
    131   prev_service_type_.clear();
    132   prev_port_ = 0;
    133   txt_records_.clear();
    134 }
    135 
    136 void AvahiMdnsClient::OnAvahiClientStateUpdate(AvahiClient* s,
    137                                                AvahiClientState state,
    138                                                void* userdata) {
    139   // Avahi service has been re-initialized (probably due to host name conflict),
    140   // so we need to republish the service if it has been previously published.
    141   if (state == AVAHI_CLIENT_S_RUNNING) {
    142     AvahiMdnsClient* self = static_cast<AvahiMdnsClient*>(userdata);
    143     self->RepublishService();
    144   }
    145 }
    146 
    147 void AvahiMdnsClient::RepublishService() {
    148   // If we don't have a service to publish, there is nothing else to do here.
    149   if (prev_service_type_.empty())
    150     return;
    151 
    152   LOG(INFO) << "Republishing mDNS service";
    153   std::string service_type = std::move(prev_service_type_);
    154   uint16_t port = prev_port_;
    155   std::vector<std::string> txt = std::move(txt_records_);
    156   StopPublishing(service_type);
    157   PublishService(service_type, port, txt);
    158 }
    159 
    160 }  // namespace buffet
    161