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 
CreateInstance()32 std::unique_ptr<MdnsClient> MdnsClient::CreateInstance() {
33   return std::unique_ptr<MdnsClient>{new AvahiMdnsClient()};
34 
35 }
36 
37 namespace {
38 
HandleGroupStateChanged(AvahiEntryGroup * g,AvahiEntryGroupState state,AVAHI_GCC_UNUSED void * userdata)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 
AvahiMdnsClient()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 
~AvahiMdnsClient()70 AvahiMdnsClient::~AvahiMdnsClient() {
71   if (thread_pool_)
72     avahi_threaded_poll_stop(thread_pool_.get());
73 }
74 
PublishService(const std::string & service_type,uint16_t port,const std::vector<std::string> & txt)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 
StopPublishing(const std::string & service_type)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 
OnAvahiClientStateUpdate(AvahiClient * s,AvahiClientState state,void * userdata)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 
RepublishService()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