blob: 6bf8c5dba81fbcf31d7706c22b0a9108c5cccc76 [file] [log] [blame]
Alex Vakulenkof0f55342015-08-18 15:51:40 -07001/*
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
Robert Gindacf92c662015-08-20 09:30:11 -070017#include <vector>
Alex Vakulenkof0f55342015-08-18 15:51:40 -070018
Robert Gindacf92c662015-08-20 09:30:11 -070019#include "buffet/avahi_mdns_client.h"
20#include "buffet/dbus_constants.h"
21
22#include <avahi-common/defs.h>
23#include <avahi-common/address.h>
Alex Vakulenkof0f55342015-08-18 15:51:40 -070024#include <base/guid.h>
Alex Vakulenko41705852015-10-13 10:12:06 -070025#include <brillo/dbus/async_event_sequencer.h>
26#include <brillo/dbus/dbus_signal_handler.h>
27#include <brillo/dbus/dbus_method_invoker.h>
28#include <brillo/errors/error.h>
Robert Gindacf92c662015-08-20 09:30:11 -070029#include <dbus/object_path.h>
30#include <dbus/object_proxy.h>
31
Alex Vakulenko41705852015-10-13 10:12:06 -070032using brillo::ErrorPtr;
33using brillo::dbus_utils::AsyncEventSequencer;
34using brillo::dbus_utils::CallMethodAndBlock;
35using brillo::dbus_utils::ExtractMethodCallResults;
Robert Gindacf92c662015-08-20 09:30:11 -070036using CompletionAction =
Alex Vakulenko41705852015-10-13 10:12:06 -070037 brillo::dbus_utils::AsyncEventSequencer::CompletionAction;
Alex Vakulenkof0f55342015-08-18 15:51:40 -070038
39namespace buffet {
40
Alex Vakulenko7f019f82015-08-21 19:30:36 -070041AvahiMdnsClient::AvahiMdnsClient(const scoped_refptr<dbus::Bus> &bus)
Alex Vakulenko0022b752015-10-02 11:09:59 -070042 : bus_(bus), service_name_(base::GenerateGUID()) {
Alex Vakulenkof0f55342015-08-18 15:51:40 -070043}
44
45AvahiMdnsClient::~AvahiMdnsClient() {
46}
47
Robert Gindacf92c662015-08-20 09:30:11 -070048// NB: This should be the one-and-only definition of this MdnsClient static
49// method.
50std::unique_ptr<MdnsClient> MdnsClient::CreateInstance(
51 const scoped_refptr<dbus::Bus> &bus) {
52 return std::unique_ptr<MdnsClient>{new AvahiMdnsClient(bus)};
53}
54
55// TODO(rginda): Report errors back to the caller.
56// TODO(rginda): Support publishing more than one service.
Alex Vakulenkof0f55342015-08-18 15:51:40 -070057void AvahiMdnsClient::PublishService(
Alex Vakulenko35d119a2015-09-10 16:16:27 -070058 const std::string& service_type, uint16_t port,
59 const std::vector<std::string>& txt) {
Robert Gindacf92c662015-08-20 09:30:11 -070060
Alex Vakulenko35d119a2015-09-10 16:16:27 -070061 CHECK_EQ("_privet._tcp", service_type);
Robert Gindab50bf542015-08-24 15:27:26 -070062
Robert Gindacf92c662015-08-20 09:30:11 -070063 if (service_state_ == READY) {
Robert Gindab50bf542015-08-24 15:27:26 -070064 if (service_type_ != service_type || port_ != port) {
65 // If the type or port of a service changes we have to re-publish
66 // rather than just update the txt record.
67 StopPublishing(service_type_);
68 if (service_state_ != UNDEF) {
69 LOG(ERROR) << "Failed to disable existing service.";
70 return;
71 }
Robert Gindacf92c662015-08-20 09:30:11 -070072 }
73 }
74
Robert Gindacf92c662015-08-20 09:30:11 -070075 service_type_ = service_type;
76 port_ = port;
77 txt_ = GetTxtRecord(txt);
78
79 if (avahi_state_ == UNDEF || avahi_state_ == ERROR) {
80 ConnectToAvahi();
Robert Gindab50bf542015-08-24 15:27:26 -070081 } else if (service_state_ == READY) {
82 UpdateServiceTxt();
Robert Gindacf92c662015-08-20 09:30:11 -070083 } else if (avahi_state_ == READY) {
Alex Vakulenko7f019f82015-08-21 19:30:36 -070084 CreateEntryGroup();
Robert Gindacf92c662015-08-20 09:30:11 -070085 } else {
86 CHECK(avahi_state_ == PENDING);
87 }
Alex Vakulenkof0f55342015-08-18 15:51:40 -070088}
89
Robert Gindacf92c662015-08-20 09:30:11 -070090// TODO(rginda): If we support publishing more than one service then we
91// may need a less ambiguous way of unpublishing them.
92void AvahiMdnsClient::StopPublishing(const std::string& service_type) {
Robert Gindab50bf542015-08-24 15:27:26 -070093 if (service_type_ != service_type) {
Robert Gindacf92c662015-08-20 09:30:11 -070094 LOG(ERROR) << "Unknown service type: " << service_type;
95 return;
96 }
97
98 if (service_state_ != READY) {
99 LOG(ERROR) << "Service is not published.";
100 }
101
Robert Gindacf92c662015-08-20 09:30:11 -0700102 service_type_.clear();
103 port_ = 0;
104
105 FreeEntryGroup();
Alex Vakulenkof0f55342015-08-18 15:51:40 -0700106}
107
Robert Gindacf92c662015-08-20 09:30:11 -0700108// Transform a service_info to a mDNS compatible TXT record value.
109// Concretely, a TXT record consists of a list of strings in the format
110// "key=value". Each string must be less than 256 bytes long, since they are
111// length/value encoded. Keys may not contain '=' characters, but are
112// otherwise unconstrained.
113//
114// We need a DBus type of "aay", which is a vector<vector<uint8_t>> in our
115// bindings.
116AvahiMdnsClient::TxtRecord AvahiMdnsClient::GetTxtRecord(
Alex Vakulenko35d119a2015-09-10 16:16:27 -0700117 const std::vector<std::string>& txt) {
Robert Gindacf92c662015-08-20 09:30:11 -0700118 TxtRecord result;
119 result.reserve(txt.size());
Alex Vakulenko35d119a2015-09-10 16:16:27 -0700120 for (const std::string& s : txt) {
Robert Gindacf92c662015-08-20 09:30:11 -0700121 result.emplace_back();
122 std::vector<uint8_t>& record = result.back();
Alex Vakulenko35d119a2015-09-10 16:16:27 -0700123 record.insert(record.end(), s.begin(), s.end());
Robert Gindacf92c662015-08-20 09:30:11 -0700124 }
125 return result;
126}
127
128void AvahiMdnsClient::ConnectToAvahi() {
129 avahi_state_ = PENDING;
130
131 avahi_ = bus_->GetObjectProxy(
132 dbus_constants::avahi::kServiceName,
133 dbus::ObjectPath(dbus_constants::avahi::kServerPath));
134
135 // This callback lives for the lifetime of the ObjectProxy.
136 avahi_->SetNameOwnerChangedCallback(
137 base::Bind(&AvahiMdnsClient::OnAvahiOwnerChanged,
138 weak_ptr_factory_.GetWeakPtr()));
139
140 // Reconnect to our signals on a new Avahi instance.
141 scoped_refptr<AsyncEventSequencer> sequencer(new AsyncEventSequencer());
Alex Vakulenko41705852015-10-13 10:12:06 -0700142 brillo::dbus_utils::ConnectToSignal(
Robert Gindacf92c662015-08-20 09:30:11 -0700143 avahi_,
144 dbus_constants::avahi::kServerInterface,
145 dbus_constants::avahi::kServerSignalStateChanged,
146 base::Bind(&AvahiMdnsClient::OnAvahiStateChanged,
147 weak_ptr_factory_.GetWeakPtr()),
148 sequencer->GetExportHandler(
149 dbus_constants::avahi::kServerInterface,
150 dbus_constants::avahi::kServerSignalStateChanged,
151 "Failed to subscribe to Avahi state change.",
152 true));
153 sequencer->OnAllTasksCompletedCall(
154 {// Get a onetime callback with the initial state of Avahi.
155 AsyncEventSequencer::WrapCompletionTask(
156 base::Bind(&dbus::ObjectProxy::WaitForServiceToBeAvailable,
157 avahi_,
158 base::Bind(&AvahiMdnsClient::OnAvahiAvailable,
159 weak_ptr_factory_.GetWeakPtr()))),
160 });
161}
162
163void AvahiMdnsClient::CreateEntryGroup() {
164 ErrorPtr error;
165
166 service_state_ = PENDING;
167
168 auto resp = CallMethodAndBlock(
169 avahi_, dbus_constants::avahi::kServerInterface,
170 dbus_constants::avahi::kServerMethodEntryGroupNew,
171 &error);
172
173 dbus::ObjectPath group_path;
174 if (!resp || !ExtractMethodCallResults(resp.get(), &error, &group_path)) {
175 service_state_ = ERROR;
176 LOG(ERROR) << "Error creating group.";
177 return;
178 }
179 entry_group_ = bus_->GetObjectProxy(dbus_constants::avahi::kServiceName,
180 group_path);
181
Alex Vakulenko7f019f82015-08-21 19:30:36 -0700182 // If we fail to connect to the StateChange signal for this group, just
Robert Gindacf92c662015-08-20 09:30:11 -0700183 // report that the whole thing has failed.
184 auto on_connect_cb = [](const std::string& interface_name,
185 const std::string& signal_name,
186 bool success) {
187 if (!success) {
188 LOG(ERROR) << "Failed to connect to StateChange signal "
189 "from EntryGroup.";
190 return;
191 }
192 };
193
Alex Vakulenko41705852015-10-13 10:12:06 -0700194 brillo::dbus_utils::ConnectToSignal(
Robert Gindacf92c662015-08-20 09:30:11 -0700195 entry_group_,
196 dbus_constants::avahi::kGroupInterface,
197 dbus_constants::avahi::kGroupSignalStateChanged,
198 base::Bind(&AvahiMdnsClient::HandleGroupStateChanged,
199 weak_ptr_factory_.GetWeakPtr()),
200 base::Bind(on_connect_cb));
201
Alex Vakulenko7f019f82015-08-21 19:30:36 -0700202 CreateService();
Robert Gindacf92c662015-08-20 09:30:11 -0700203}
204
205void AvahiMdnsClient::FreeEntryGroup() {
206 if (!entry_group_) {
207 LOG(ERROR) << "No group to free.";
208 return;
209 }
210
211 ErrorPtr error;
212 auto resp = CallMethodAndBlock(entry_group_,
213 dbus_constants::avahi::kGroupInterface,
214 dbus_constants::avahi::kGroupMethodFree,
215 &error);
216 // Extract and log relevant errors.
217 bool success = resp && ExtractMethodCallResults(resp.get(), &error);
218 if (!success) {
219 LOG(ERROR) << "Error freeing service group";
220 }
221
222 // Ignore any signals we may have registered for from this proxy.
223 entry_group_->Detach();
224 entry_group_ = nullptr;
225
226 service_state_ = UNDEF;
227}
228
229void AvahiMdnsClient::CreateService() {
230 ErrorPtr error;
231
Alex Vakulenko0022b752015-10-02 11:09:59 -0700232 VLOG(1) << "CreateService: name: " << service_name_ << ", type: " <<
Robert Gindacf92c662015-08-20 09:30:11 -0700233 service_type_ << ", port: " << port_;
Robert Gindacf92c662015-08-20 09:30:11 -0700234 auto resp = CallMethodAndBlock(
235 entry_group_,
236 dbus_constants::avahi::kGroupInterface,
237 dbus_constants::avahi::kGroupMethodAddService,
238 &error,
239 int32_t{AVAHI_IF_UNSPEC},
240 int32_t{AVAHI_PROTO_UNSPEC},
241 uint32_t{0}, // No flags.
Alex Vakulenko0022b752015-10-02 11:09:59 -0700242 service_name_,
Robert Gindab50bf542015-08-24 15:27:26 -0700243 std::string{"_privet._tcp"},
Robert Gindacf92c662015-08-20 09:30:11 -0700244 std::string{}, // domain.
245 std::string{}, // hostname
246 port_,
247 txt_);
248 if (!resp || !ExtractMethodCallResults(resp.get(), &error)) {
249 LOG(ERROR) << "Error creating service";
250 service_state_ = ERROR;
251 return;
252 }
253
254 resp = CallMethodAndBlock(entry_group_,
255 dbus_constants::avahi::kGroupInterface,
256 dbus_constants::avahi::kGroupMethodCommit,
257 &error);
258 if (!resp || !ExtractMethodCallResults(resp.get(), &error)) {
259 LOG(ERROR) << "Error committing service.";
260 service_state_ = ERROR;
261 return;
262 }
263
264 service_state_ = READY;
265}
266
Robert Gindab50bf542015-08-24 15:27:26 -0700267void AvahiMdnsClient::UpdateServiceTxt() {
268 ErrorPtr error;
269
270 CHECK_EQ(READY, service_state_);
271
Alex Vakulenko0022b752015-10-02 11:09:59 -0700272 VLOG(1) << "UpdateServiceTxt: name " << service_name_ << ", type: " <<
Robert Gindab50bf542015-08-24 15:27:26 -0700273 service_type_ << ", port: " << port_;
274 auto resp = CallMethodAndBlock(
275 entry_group_,
276 dbus_constants::avahi::kGroupInterface,
277 dbus_constants::avahi::kGroupMethodUpdateServiceTxt,
278 &error,
279 int32_t{AVAHI_IF_UNSPEC},
280 int32_t{AVAHI_PROTO_UNSPEC},
281 uint32_t{0}, // No flags.
Alex Vakulenko0022b752015-10-02 11:09:59 -0700282 service_name_,
Robert Gindab50bf542015-08-24 15:27:26 -0700283 std::string{"_privet._tcp"},
284 std::string{}, // domain.
285 txt_);
286 if (!resp || !ExtractMethodCallResults(resp.get(), &error)) {
287 LOG(ERROR) << "Error creating service";
288 service_state_ = ERROR;
289 return;
290 }
291};
292
Robert Gindacf92c662015-08-20 09:30:11 -0700293void AvahiMdnsClient::OnAvahiOwnerChanged(const std::string& old_owner,
294 const std::string& new_owner) {
295 if (new_owner.empty()) {
296 OnAvahiAvailable(false);
297 return;
298 }
299 OnAvahiAvailable(true);
300}
301
302void AvahiMdnsClient::OnAvahiStateChanged(int32_t state,
303 const std::string& error) {
304 HandleAvahiStateChange(state);
305}
306
307void AvahiMdnsClient::OnAvahiAvailable(bool avahi_is_on_dbus) {
308 VLOG(1) << "Avahi is " << (avahi_is_on_dbus ? "up." : "down.");
309 int32_t state = AVAHI_SERVER_FAILURE;
310 if (avahi_is_on_dbus) {
311 auto resp = CallMethodAndBlock(
312 avahi_, dbus_constants::avahi::kServerInterface,
313 dbus_constants::avahi::kServerMethodGetState,
314 nullptr);
315 if (!resp || !ExtractMethodCallResults(resp.get(), nullptr, &state)) {
316 LOG(WARNING) << "Failed to get avahi initial state. Relying on signal.";
317 }
318 }
319 VLOG(1) << "Initial Avahi state=" << state << ".";
320 HandleAvahiStateChange(state);
321}
322
323void AvahiMdnsClient::HandleAvahiStateChange(int32_t state) {
324 switch (state) {
325 case AVAHI_SERVER_RUNNING: {
326 // All host RRs have been established.
327 VLOG(1) << "Avahi ready for action.";
328 if (avahi_state_ == READY) {
329 LOG(INFO) << "Ignoring redundant Avahi up event.";
330 return;
331 }
332 avahi_state_ = READY;
Alex Vakulenko7f019f82015-08-21 19:30:36 -0700333 CreateEntryGroup();
Robert Gindacf92c662015-08-20 09:30:11 -0700334 } break;
335 case AVAHI_SERVER_INVALID:
336 // Invalid state (initial).
337 case AVAHI_SERVER_REGISTERING:
338 // Host RRs are being registered.
339 case AVAHI_SERVER_COLLISION:
340 // There is a collision with a host RR. All host RRs have been withdrawn,
341 // the user should set a new host name via avahi_server_set_host_name().
342 case AVAHI_SERVER_FAILURE:
343 // Some fatal failure happened, the server is unable to proceed.
344 avahi_state_ = ERROR;
Robert Gindab50bf542015-08-24 15:27:26 -0700345 if (service_state_ != UNDEF)
346 service_state_ = ERROR;
347
Robert Gindacf92c662015-08-20 09:30:11 -0700348 LOG(ERROR) << "Avahi changed to error state: " << state;
349 break;
350 default:
351 LOG(ERROR) << "Unknown Avahi server state change to " << state;
352 break;
353 }
354}
355
356void AvahiMdnsClient::HandleGroupStateChanged(
357 int32_t state,
358 const std::string& error_message) {
359 if (state == AVAHI_ENTRY_GROUP_COLLISION ||
360 state == AVAHI_ENTRY_GROUP_FAILURE) {
361 LOG(ERROR) << "Avahi service group error: " << state;
362 }
Alex Vakulenkof0f55342015-08-18 15:51:40 -0700363}
364
365} // namespace buffet