1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "osp/impl/discovery/mdns/mdns_responder_adapter_impl.h"
6
7 #include <algorithm>
8 #include <cctype>
9 #include <cstring>
10 #include <iostream>
11 #include <memory>
12 #include <string>
13 #include <utility>
14
15 #include "util/osp_logging.h"
16 #include "util/trace_logging.h"
17
18 namespace openscreen {
19 namespace osp {
20 namespace {
21
22 // RFC 1035 specifies a max string length of 256, including the leading length
23 // octet.
24 constexpr size_t kMaxDnsStringLength = 255;
25
26 // RFC 6763 recommends a maximum key length of 9 characters.
27 constexpr size_t kMaxTxtKeyLength = 9;
28
29 constexpr size_t kMaxStaticTxtDataSize = 256;
30
31 static_assert(sizeof(std::declval<RData>().u.txt) == kMaxStaticTxtDataSize,
32 "mDNSResponder static TXT data size expected to be 256 bytes");
33
34 static_assert(sizeof(mDNSAddr::ip.v4.b) == 4u,
35 "mDNSResponder IPv4 address must be 4 bytes");
36 static_assert(sizeof(mDNSAddr::ip.v6.b) == 16u,
37 "mDNSResponder IPv6 address must be 16 bytes");
38
AssignMdnsPort(mDNSIPPort * mdns_port,uint16_t port)39 void AssignMdnsPort(mDNSIPPort* mdns_port, uint16_t port) {
40 mdns_port->b[0] = (port >> 8) & 0xff;
41 mdns_port->b[1] = port & 0xff;
42 }
43
GetNetworkOrderPort(const mDNSOpaque16 & port)44 uint16_t GetNetworkOrderPort(const mDNSOpaque16& port) {
45 return port.b[0] << 8 | port.b[1];
46 }
47
IsValidServiceName(const std::string & service_name)48 bool IsValidServiceName(const std::string& service_name) {
49 // Service name requirements come from RFC 6335:
50 // - No more than 16 characters.
51 // - Begin with '_'.
52 // - Next is a letter or digit and end with a letter or digit.
53 // - May contain hyphens, but no consecutive hyphens.
54 // - Must contain at least one letter.
55 if (service_name.size() <= 1 || service_name.size() > 16)
56 return false;
57
58 if (service_name[0] != '_' || !std::isalnum(service_name[1]) ||
59 !std::isalnum(service_name.back())) {
60 return false;
61 }
62 bool has_alpha = false;
63 bool previous_hyphen = false;
64 for (auto it = service_name.begin() + 1; it != service_name.end(); ++it) {
65 if (*it == '-' && previous_hyphen)
66 return false;
67
68 previous_hyphen = *it == '-';
69 has_alpha = has_alpha || std::isalpha(*it);
70 }
71 return has_alpha && !previous_hyphen;
72 }
73
IsValidServiceProtocol(const std::string & protocol)74 bool IsValidServiceProtocol(const std::string& protocol) {
75 // RFC 6763 requires _tcp be used for TCP services and _udp for all others.
76 return protocol == "_tcp" || protocol == "_udp";
77 }
78
MakeLocalServiceNameParts(const std::string & service_instance,const std::string & service_name,const std::string & service_protocol,domainlabel * instance,domainlabel * name,domainlabel * protocol,domainname * type,domainname * domain)79 void MakeLocalServiceNameParts(const std::string& service_instance,
80 const std::string& service_name,
81 const std::string& service_protocol,
82 domainlabel* instance,
83 domainlabel* name,
84 domainlabel* protocol,
85 domainname* type,
86 domainname* domain) {
87 MakeDomainLabelFromLiteralString(instance, service_instance.c_str());
88 MakeDomainLabelFromLiteralString(name, service_name.c_str());
89 MakeDomainLabelFromLiteralString(protocol, service_protocol.c_str());
90 type->c[0] = 0;
91 AppendDomainLabel(type, name);
92 AppendDomainLabel(type, protocol);
93 const DomainName local_domain = DomainName::GetLocalDomain();
94 std::copy(local_domain.domain_name().begin(),
95 local_domain.domain_name().end(), domain->c);
96 }
97
MakeSubnetMaskFromPrefixLengthV4(uint8_t mask[4],uint8_t prefix_length)98 void MakeSubnetMaskFromPrefixLengthV4(uint8_t mask[4], uint8_t prefix_length) {
99 for (int i = 0; i < 4; prefix_length -= 8, ++i) {
100 if (prefix_length >= 8) {
101 mask[i] = 0xff;
102 } else if (prefix_length > 0) {
103 mask[i] = 0xff << (8 - prefix_length);
104 } else {
105 mask[i] = 0;
106 }
107 }
108 }
109
MakeSubnetMaskFromPrefixLengthV6(uint8_t mask[16],uint8_t prefix_length)110 void MakeSubnetMaskFromPrefixLengthV6(uint8_t mask[16], uint8_t prefix_length) {
111 for (int i = 0; i < 16; prefix_length -= 8, ++i) {
112 if (prefix_length >= 8) {
113 mask[i] = 0xff;
114 } else if (prefix_length > 0) {
115 mask[i] = 0xff << (8 - prefix_length);
116 } else {
117 mask[i] = 0;
118 }
119 }
120 }
121
IsValidTxtDataKey(const std::string & s)122 bool IsValidTxtDataKey(const std::string& s) {
123 if (s.size() > kMaxTxtKeyLength)
124 return false;
125 for (unsigned char c : s)
126 if (c < 0x20 || c > 0x7e || c == '=')
127 return false;
128 return true;
129 }
130
MakeTxtData(const std::map<std::string,std::string> & txt_data)131 std::string MakeTxtData(const std::map<std::string, std::string>& txt_data) {
132 std::string txt;
133 txt.reserve(kMaxStaticTxtDataSize);
134 for (const auto& line : txt_data) {
135 const auto key_size = line.first.size();
136 const auto value_size = line.second.size();
137 const auto line_size = value_size ? (key_size + 1 + value_size) : key_size;
138 if (!IsValidTxtDataKey(line.first) || line_size > kMaxDnsStringLength ||
139 (txt.size() + 1 + line_size) > kMaxStaticTxtDataSize) {
140 return {};
141 }
142 txt.push_back(line_size);
143 txt += line.first;
144 if (value_size) {
145 txt.push_back('=');
146 txt += line.second;
147 }
148 }
149 return txt;
150 }
151
MapMdnsError(int err)152 MdnsResponderErrorCode MapMdnsError(int err) {
153 switch (err) {
154 case mStatus_NoError:
155 return MdnsResponderErrorCode::kNoError;
156 case mStatus_UnsupportedErr:
157 return MdnsResponderErrorCode::kUnsupportedError;
158 case mStatus_UnknownErr:
159 return MdnsResponderErrorCode::kUnknownError;
160 default:
161 break;
162 }
163 OSP_DLOG_WARN << "unmapped mDNSResponder error: " << err;
164 return MdnsResponderErrorCode::kUnknownError;
165 }
166
ParseTxtResponse(const uint8_t data[kMaxStaticTxtDataSize],uint16_t length)167 std::vector<std::string> ParseTxtResponse(
168 const uint8_t data[kMaxStaticTxtDataSize],
169 uint16_t length) {
170 OSP_DCHECK(length <= kMaxStaticTxtDataSize);
171 if (length == 0)
172 return {};
173
174 std::vector<std::string> lines;
175 int total_pos = 0;
176 while (total_pos < length) {
177 uint8_t line_length = data[total_pos];
178 if ((line_length > kMaxDnsStringLength) ||
179 (total_pos + line_length >= length)) {
180 return {};
181 }
182 lines.emplace_back(&data[total_pos + 1],
183 &data[total_pos + line_length + 1]);
184 total_pos += line_length + 1;
185 }
186 return lines;
187 }
188
MdnsStatusCallback(mDNS * mdns,mStatus result)189 void MdnsStatusCallback(mDNS* mdns, mStatus result) {
190 OSP_LOG_INFO << "status good? " << (result == mStatus_NoError);
191 }
192
193 } // namespace
194
195 MdnsResponderAdapterImpl::MdnsResponderAdapterImpl() = default;
196 MdnsResponderAdapterImpl::~MdnsResponderAdapterImpl() = default;
197
Init()198 Error MdnsResponderAdapterImpl::Init() {
199 const auto err =
200 mDNS_Init(&mdns_, &platform_storage_, rr_cache_, kRrCacheSize,
201 mDNS_Init_DontAdvertiseLocalAddresses, &MdnsStatusCallback,
202 mDNS_Init_NoInitCallbackContext);
203
204 return (err == mStatus_NoError) ? Error::None()
205 : Error::Code::kInitializationFailure;
206 }
207
Close()208 void MdnsResponderAdapterImpl::Close() {
209 TRACE_SCOPED(TraceCategory::kMdns, "MdnsResponderAdapterImpl::Close");
210 mDNS_StartExit(&mdns_);
211 // Let all services send goodbyes.
212 while (!service_records_.empty()) {
213 RunTasks();
214 }
215 mDNS_FinalExit(&mdns_);
216
217 socket_to_questions_.clear();
218
219 responder_interface_info_.clear();
220
221 a_responses_.clear();
222 aaaa_responses_.clear();
223 ptr_responses_.clear();
224 srv_responses_.clear();
225 txt_responses_.clear();
226
227 service_records_.clear();
228 }
229
SetHostLabel(const std::string & host_label)230 Error MdnsResponderAdapterImpl::SetHostLabel(const std::string& host_label) {
231 TRACE_SCOPED(TraceCategory::kMdns, "MdnsResponderAdapterImpl::SetHostLabel");
232 if (host_label.size() > DomainName::kDomainNameMaxLabelLength)
233 return Error::Code::kDomainNameTooLong;
234
235 MakeDomainLabelFromLiteralString(&mdns_.hostlabel, host_label.c_str());
236 mDNS_SetFQDN(&mdns_);
237 if (!service_records_.empty()) {
238 DeadvertiseInterfaces();
239 AdvertiseInterfaces();
240 }
241 return Error::None();
242 }
243
RegisterInterface(const InterfaceInfo & interface_info,const IPSubnet & interface_address,UdpSocket * socket)244 Error MdnsResponderAdapterImpl::RegisterInterface(
245 const InterfaceInfo& interface_info,
246 const IPSubnet& interface_address,
247 UdpSocket* socket) {
248 TRACE_SCOPED(TraceCategory::kMdns,
249 "MdnsResponderAdapterImpl::RegisterInterface");
250 OSP_DCHECK(socket);
251
252 const auto info_it = responder_interface_info_.find(socket);
253 if (info_it != responder_interface_info_.end())
254 return Error::None();
255
256 NetworkInterfaceInfo& info = responder_interface_info_[socket];
257 std::memset(&info, 0, sizeof(NetworkInterfaceInfo));
258 info.InterfaceID = reinterpret_cast<decltype(info.InterfaceID)>(socket);
259 info.Advertise = mDNSfalse;
260 if (interface_address.address.IsV4()) {
261 info.ip.type = mDNSAddrType_IPv4;
262 interface_address.address.CopyToV4(info.ip.ip.v4.b);
263 info.mask.type = mDNSAddrType_IPv4;
264 MakeSubnetMaskFromPrefixLengthV4(info.mask.ip.v4.b,
265 interface_address.prefix_length);
266 } else {
267 info.ip.type = mDNSAddrType_IPv6;
268 interface_address.address.CopyToV6(info.ip.ip.v6.b);
269 info.mask.type = mDNSAddrType_IPv6;
270 MakeSubnetMaskFromPrefixLengthV6(info.mask.ip.v6.b,
271 interface_address.prefix_length);
272 }
273
274 static_assert(sizeof(info.MAC.b) == sizeof(interface_info.hardware_address),
275 "MAC address size mismatch.");
276 memcpy(info.MAC.b, interface_info.hardware_address.data(),
277 sizeof(info.MAC.b));
278 info.McastTxRx = 1;
279 platform_storage_.sockets.push_back(socket);
280 auto result = mDNS_RegisterInterface(&mdns_, &info, mDNSfalse);
281 OSP_LOG_IF(WARN, result != mStatus_NoError)
282 << "mDNS_RegisterInterface failed: " << result;
283
284 return (result == mStatus_NoError) ? Error::None()
285 : Error::Code::kMdnsRegisterFailure;
286 }
287
DeregisterInterface(UdpSocket * socket)288 Error MdnsResponderAdapterImpl::DeregisterInterface(UdpSocket* socket) {
289 TRACE_SCOPED(TraceCategory::kMdns,
290 "MdnsResponderAdapterImpl::DeregisterInterface");
291 const auto info_it = responder_interface_info_.find(socket);
292 if (info_it == responder_interface_info_.end())
293 return Error::Code::kItemNotFound;
294
295 const auto it = std::find(platform_storage_.sockets.begin(),
296 platform_storage_.sockets.end(), socket);
297 OSP_DCHECK(it != platform_storage_.sockets.end());
298 platform_storage_.sockets.erase(it);
299 if (info_it->second.RR_A.namestorage.c[0]) {
300 mDNS_Deregister(&mdns_, &info_it->second.RR_A);
301 info_it->second.RR_A.namestorage.c[0] = 0;
302 }
303 mDNS_DeregisterInterface(&mdns_, &info_it->second, mDNSfalse);
304 responder_interface_info_.erase(info_it);
305 return Error::None();
306 }
OnRead(UdpSocket * socket,ErrorOr<UdpPacket> packet_or_error)307 void MdnsResponderAdapterImpl::OnRead(UdpSocket* socket,
308 ErrorOr<UdpPacket> packet_or_error) {
309 if (packet_or_error.is_error()) {
310 return;
311 }
312
313 UdpPacket packet = std::move(packet_or_error.value());
314 TRACE_SCOPED(TraceCategory::kMdns, "MdnsResponderAdapterImpl::OnRead");
315 mDNSAddr src;
316 if (packet.source().address.IsV4()) {
317 src.type = mDNSAddrType_IPv4;
318 packet.source().address.CopyToV4(src.ip.v4.b);
319 } else {
320 src.type = mDNSAddrType_IPv6;
321 packet.source().address.CopyToV6(src.ip.v6.b);
322 }
323 mDNSIPPort srcport;
324 AssignMdnsPort(&srcport, packet.source().port);
325
326 mDNSAddr dst;
327 if (packet.source().address.IsV4()) {
328 dst.type = mDNSAddrType_IPv4;
329 packet.destination().address.CopyToV4(dst.ip.v4.b);
330 } else {
331 dst.type = mDNSAddrType_IPv6;
332 packet.destination().address.CopyToV6(dst.ip.v6.b);
333 }
334 mDNSIPPort dstport;
335 AssignMdnsPort(&dstport, packet.destination().port);
336
337 auto* packet_data = packet.data();
338 mDNSCoreReceive(&mdns_, const_cast<uint8_t*>(packet_data),
339 packet_data + packet.size(), &src, srcport, &dst, dstport,
340 reinterpret_cast<mDNSInterfaceID>(packet.socket()));
341 }
342
OnSendError(UdpSocket * socket,Error error)343 void MdnsResponderAdapterImpl::OnSendError(UdpSocket* socket, Error error) {
344 // TODO(crbug.com/openscreen/67): Implement this method.
345 OSP_UNIMPLEMENTED();
346 }
347
OnError(UdpSocket * socket,Error error)348 void MdnsResponderAdapterImpl::OnError(UdpSocket* socket, Error error) {
349 // TODO(crbug.com/openscreen/67): Implement this method.
350 OSP_UNIMPLEMENTED();
351 }
352
OnBound(UdpSocket * socket)353 void MdnsResponderAdapterImpl::OnBound(UdpSocket* socket) {
354 // TODO(crbug.com/openscreen/67): Implement this method.
355 OSP_UNIMPLEMENTED();
356 }
357
RunTasks()358 Clock::duration MdnsResponderAdapterImpl::RunTasks() {
359 TRACE_SCOPED(TraceCategory::kMdns, "MdnsResponderAdapterImpl::RunTasks");
360
361 mDNS_Execute(&mdns_);
362
363 // Using mDNS_Execute's response to determine the correct timespan before
364 // re-running this method doesn't work as expected. In the demo, under some
365 // cases (about 25% of demo runs), the response is set to an unreasonably
366 // large number (in the order of multiple days).
367 //
368 // From the mDNS documentation: "it is the responsibility [...] to set the
369 // timer according to the m->NextScheduledEvent value, and then when the timer
370 // fires, the timer callback function should call mDNS_Execute()" - for more
371 // details see third_party/mDNSResponder/src/mDNSCore/mDNS.c : 3390
372 //
373 // Together, I understand these to mean that the mdns library code doesn't
374 // expect we need mDNS_Execute called again by the task runner, only in the
375 // other special cases it calls out in documentation (which we currently do
376 // correctly). In our code, when we call mDNS_Execute again outside of the
377 // task runner, the result is currently discarded. What we would need to do is
378 // reach into the Task Runner's task and update how long before the task runs
379 // again. That would require some large refactoring and changes.
380 //
381 // Additionally, beyond this, the mDNS code documents that there are cases
382 // where the return value for mDNS_Execute should be ignored because it may be
383 // stale.
384 //
385 // TODO(rwkeane): More accurately determine when the next run of this method
386 // should be.
387 constexpr auto seconds_before_next_run = 1;
388
389 // Return as a duration.
390 return std::chrono::seconds(seconds_before_next_run);
391 }
392
TakePtrResponses()393 std::vector<PtrEvent> MdnsResponderAdapterImpl::TakePtrResponses() {
394 return std::move(ptr_responses_);
395 }
396
TakeSrvResponses()397 std::vector<SrvEvent> MdnsResponderAdapterImpl::TakeSrvResponses() {
398 return std::move(srv_responses_);
399 }
400
TakeTxtResponses()401 std::vector<TxtEvent> MdnsResponderAdapterImpl::TakeTxtResponses() {
402 return std::move(txt_responses_);
403 }
404
TakeAResponses()405 std::vector<AEvent> MdnsResponderAdapterImpl::TakeAResponses() {
406 return std::move(a_responses_);
407 }
408
TakeAaaaResponses()409 std::vector<AaaaEvent> MdnsResponderAdapterImpl::TakeAaaaResponses() {
410 return std::move(aaaa_responses_);
411 }
412
StartPtrQuery(UdpSocket * socket,const DomainName & service_type)413 MdnsResponderErrorCode MdnsResponderAdapterImpl::StartPtrQuery(
414 UdpSocket* socket,
415 const DomainName& service_type) {
416 TRACE_SCOPED(TraceCategory::kMdns, "MdnsResponderAdapterImpl::StartPtrQuery");
417 auto& ptr_questions = socket_to_questions_[socket].ptr;
418 if (ptr_questions.find(service_type) != ptr_questions.end())
419 return MdnsResponderErrorCode::kNoError;
420
421 auto& question = ptr_questions[service_type];
422
423 question.InterfaceID = reinterpret_cast<mDNSInterfaceID>(socket);
424 question.Target = {0};
425 if (service_type.EndsWithLocalDomain()) {
426 std::copy(service_type.domain_name().begin(),
427 service_type.domain_name().end(), question.qname.c);
428 } else {
429 const DomainName local_domain = DomainName::GetLocalDomain();
430 ErrorOr<DomainName> service_type_with_local =
431 DomainName::Append(service_type, local_domain);
432 if (!service_type_with_local) {
433 return MdnsResponderErrorCode::kDomainOverflowError;
434 }
435 std::copy(service_type_with_local.value().domain_name().begin(),
436 service_type_with_local.value().domain_name().end(),
437 question.qname.c);
438 }
439 question.qtype = kDNSType_PTR;
440 question.qclass = kDNSClass_IN;
441 question.LongLived = mDNStrue;
442 question.ExpectUnique = mDNSfalse;
443 question.ForceMCast = mDNStrue;
444 question.ReturnIntermed = mDNSfalse;
445 question.SuppressUnusable = mDNSfalse;
446 question.RetryWithSearchDomains = mDNSfalse;
447 question.TimeoutQuestion = 0;
448 question.WakeOnResolve = 0;
449 question.SearchListIndex = 0;
450 question.AppendSearchDomains = 0;
451 question.AppendLocalSearchDomains = 0;
452 question.qnameOrig = nullptr;
453 question.QuestionCallback = &MdnsResponderAdapterImpl::PtrQueryCallback;
454 question.QuestionContext = this;
455 const auto err = mDNS_StartQuery(&mdns_, &question);
456 OSP_LOG_IF(WARN, err != mStatus_NoError) << "mDNS_StartQuery failed: " << err;
457 return MapMdnsError(err);
458 }
459
StartSrvQuery(UdpSocket * socket,const DomainName & service_instance)460 MdnsResponderErrorCode MdnsResponderAdapterImpl::StartSrvQuery(
461 UdpSocket* socket,
462 const DomainName& service_instance) {
463 TRACE_SCOPED(TraceCategory::kMdns, "MdnsResponderAdapterImpl::StartSrvQuery");
464 if (!service_instance.EndsWithLocalDomain())
465 return MdnsResponderErrorCode::kInvalidParameters;
466
467 auto& srv_questions = socket_to_questions_[socket].srv;
468 if (srv_questions.find(service_instance) != srv_questions.end())
469 return MdnsResponderErrorCode::kNoError;
470
471 auto& question = srv_questions[service_instance];
472
473 question.InterfaceID = reinterpret_cast<mDNSInterfaceID>(socket);
474 question.Target = {0};
475 std::copy(service_instance.domain_name().begin(),
476 service_instance.domain_name().end(), question.qname.c);
477 question.qtype = kDNSType_SRV;
478 question.qclass = kDNSClass_IN;
479 question.LongLived = mDNStrue;
480 question.ExpectUnique = mDNSfalse;
481 question.ForceMCast = mDNStrue;
482 question.ReturnIntermed = mDNSfalse;
483 question.SuppressUnusable = mDNSfalse;
484 question.RetryWithSearchDomains = mDNSfalse;
485 question.TimeoutQuestion = 0;
486 question.WakeOnResolve = 0;
487 question.SearchListIndex = 0;
488 question.AppendSearchDomains = 0;
489 question.AppendLocalSearchDomains = 0;
490 question.qnameOrig = nullptr;
491 question.QuestionCallback = &MdnsResponderAdapterImpl::SrvQueryCallback;
492 question.QuestionContext = this;
493 const auto err = mDNS_StartQuery(&mdns_, &question);
494 OSP_LOG_IF(WARN, err != mStatus_NoError) << "mDNS_StartQuery failed: " << err;
495 return MapMdnsError(err);
496 }
497
StartTxtQuery(UdpSocket * socket,const DomainName & service_instance)498 MdnsResponderErrorCode MdnsResponderAdapterImpl::StartTxtQuery(
499 UdpSocket* socket,
500 const DomainName& service_instance) {
501 TRACE_SCOPED(TraceCategory::kMdns, "MdnsResponderAdapterImpl::StartTxtQuery");
502 if (!service_instance.EndsWithLocalDomain())
503 return MdnsResponderErrorCode::kInvalidParameters;
504
505 auto& txt_questions = socket_to_questions_[socket].txt;
506 if (txt_questions.find(service_instance) != txt_questions.end())
507 return MdnsResponderErrorCode::kNoError;
508
509 auto& question = txt_questions[service_instance];
510
511 question.InterfaceID = reinterpret_cast<mDNSInterfaceID>(socket);
512 question.Target = {0};
513 std::copy(service_instance.domain_name().begin(),
514 service_instance.domain_name().end(), question.qname.c);
515 question.qtype = kDNSType_TXT;
516 question.qclass = kDNSClass_IN;
517 question.LongLived = mDNStrue;
518 question.ExpectUnique = mDNSfalse;
519 question.ForceMCast = mDNStrue;
520 question.ReturnIntermed = mDNSfalse;
521 question.SuppressUnusable = mDNSfalse;
522 question.RetryWithSearchDomains = mDNSfalse;
523 question.TimeoutQuestion = 0;
524 question.WakeOnResolve = 0;
525 question.SearchListIndex = 0;
526 question.AppendSearchDomains = 0;
527 question.AppendLocalSearchDomains = 0;
528 question.qnameOrig = nullptr;
529 question.QuestionCallback = &MdnsResponderAdapterImpl::TxtQueryCallback;
530 question.QuestionContext = this;
531 const auto err = mDNS_StartQuery(&mdns_, &question);
532 OSP_LOG_IF(WARN, err != mStatus_NoError) << "mDNS_StartQuery failed: " << err;
533 return MapMdnsError(err);
534 }
535
StartAQuery(UdpSocket * socket,const DomainName & domain_name)536 MdnsResponderErrorCode MdnsResponderAdapterImpl::StartAQuery(
537 UdpSocket* socket,
538 const DomainName& domain_name) {
539 TRACE_SCOPED(TraceCategory::kMdns, "MdnsResponderAdapterImpl::StartAQuery");
540 if (!domain_name.EndsWithLocalDomain())
541 return MdnsResponderErrorCode::kInvalidParameters;
542
543 auto& a_questions = socket_to_questions_[socket].a;
544 if (a_questions.find(domain_name) != a_questions.end())
545 return MdnsResponderErrorCode::kNoError;
546
547 auto& question = a_questions[domain_name];
548 std::copy(domain_name.domain_name().begin(), domain_name.domain_name().end(),
549 question.qname.c);
550
551 question.InterfaceID = reinterpret_cast<mDNSInterfaceID>(socket);
552 question.Target = {0};
553 question.qtype = kDNSType_A;
554 question.qclass = kDNSClass_IN;
555 question.LongLived = mDNStrue;
556 question.ExpectUnique = mDNSfalse;
557 question.ForceMCast = mDNStrue;
558 question.ReturnIntermed = mDNSfalse;
559 question.SuppressUnusable = mDNSfalse;
560 question.RetryWithSearchDomains = mDNSfalse;
561 question.TimeoutQuestion = 0;
562 question.WakeOnResolve = 0;
563 question.SearchListIndex = 0;
564 question.AppendSearchDomains = 0;
565 question.AppendLocalSearchDomains = 0;
566 question.qnameOrig = nullptr;
567 question.QuestionCallback = &MdnsResponderAdapterImpl::AQueryCallback;
568 question.QuestionContext = this;
569 const auto err = mDNS_StartQuery(&mdns_, &question);
570 OSP_LOG_IF(WARN, err != mStatus_NoError) << "mDNS_StartQuery failed: " << err;
571 return MapMdnsError(err);
572 }
573
StartAaaaQuery(UdpSocket * socket,const DomainName & domain_name)574 MdnsResponderErrorCode MdnsResponderAdapterImpl::StartAaaaQuery(
575 UdpSocket* socket,
576 const DomainName& domain_name) {
577 TRACE_SCOPED(TraceCategory::kMdns,
578 "MdnsResponderAdapterImpl::StartAaaaQuery");
579 if (!domain_name.EndsWithLocalDomain())
580 return MdnsResponderErrorCode::kInvalidParameters;
581
582 auto& aaaa_questions = socket_to_questions_[socket].aaaa;
583 if (aaaa_questions.find(domain_name) != aaaa_questions.end())
584 return MdnsResponderErrorCode::kNoError;
585
586 auto& question = aaaa_questions[domain_name];
587 std::copy(domain_name.domain_name().begin(), domain_name.domain_name().end(),
588 question.qname.c);
589
590 question.InterfaceID = reinterpret_cast<mDNSInterfaceID>(socket);
591 question.Target = {0};
592 question.qtype = kDNSType_AAAA;
593 question.qclass = kDNSClass_IN;
594 question.LongLived = mDNStrue;
595 question.ExpectUnique = mDNSfalse;
596 question.ForceMCast = mDNStrue;
597 question.ReturnIntermed = mDNSfalse;
598 question.SuppressUnusable = mDNSfalse;
599 question.RetryWithSearchDomains = mDNSfalse;
600 question.TimeoutQuestion = 0;
601 question.WakeOnResolve = 0;
602 question.SearchListIndex = 0;
603 question.AppendSearchDomains = 0;
604 question.AppendLocalSearchDomains = 0;
605 question.qnameOrig = nullptr;
606 question.QuestionCallback = &MdnsResponderAdapterImpl::AaaaQueryCallback;
607 question.QuestionContext = this;
608 const auto err = mDNS_StartQuery(&mdns_, &question);
609 OSP_LOG_IF(WARN, err != mStatus_NoError) << "mDNS_StartQuery failed: " << err;
610 return MapMdnsError(err);
611 }
612
StopPtrQuery(UdpSocket * socket,const DomainName & service_type)613 MdnsResponderErrorCode MdnsResponderAdapterImpl::StopPtrQuery(
614 UdpSocket* socket,
615 const DomainName& service_type) {
616 TRACE_SCOPED(TraceCategory::kMdns, "MdnsResponderAdapterImpl::StopPtrQuery");
617 auto interface_entry = socket_to_questions_.find(socket);
618 if (interface_entry == socket_to_questions_.end())
619 return MdnsResponderErrorCode::kNoError;
620 auto entry = interface_entry->second.ptr.find(service_type);
621 if (entry == interface_entry->second.ptr.end())
622 return MdnsResponderErrorCode::kNoError;
623
624 const auto err = mDNS_StopQuery(&mdns_, &entry->second);
625 interface_entry->second.ptr.erase(entry);
626 OSP_LOG_IF(WARN, err != mStatus_NoError) << "mDNS_StopQuery failed: " << err;
627 RemoveQuestionsIfEmpty(socket);
628 return MapMdnsError(err);
629 }
630
StopSrvQuery(UdpSocket * socket,const DomainName & service_instance)631 MdnsResponderErrorCode MdnsResponderAdapterImpl::StopSrvQuery(
632 UdpSocket* socket,
633 const DomainName& service_instance) {
634 TRACE_SCOPED(TraceCategory::kMdns, "MdnsResponderAdapterImpl::StopSrvQuery");
635 auto interface_entry = socket_to_questions_.find(socket);
636 if (interface_entry == socket_to_questions_.end())
637 return MdnsResponderErrorCode::kNoError;
638 auto entry = interface_entry->second.srv.find(service_instance);
639 if (entry == interface_entry->second.srv.end())
640 return MdnsResponderErrorCode::kNoError;
641
642 const auto err = mDNS_StopQuery(&mdns_, &entry->second);
643 interface_entry->second.srv.erase(entry);
644 OSP_LOG_IF(WARN, err != mStatus_NoError) << "mDNS_StopQuery failed: " << err;
645 RemoveQuestionsIfEmpty(socket);
646 return MapMdnsError(err);
647 }
648
StopTxtQuery(UdpSocket * socket,const DomainName & service_instance)649 MdnsResponderErrorCode MdnsResponderAdapterImpl::StopTxtQuery(
650 UdpSocket* socket,
651 const DomainName& service_instance) {
652 TRACE_SCOPED(TraceCategory::kMdns, "MdnsResponderAdapterImpl::StopTxtQuery");
653 auto interface_entry = socket_to_questions_.find(socket);
654 if (interface_entry == socket_to_questions_.end())
655 return MdnsResponderErrorCode::kNoError;
656 auto entry = interface_entry->second.txt.find(service_instance);
657 if (entry == interface_entry->second.txt.end())
658 return MdnsResponderErrorCode::kNoError;
659
660 const auto err = mDNS_StopQuery(&mdns_, &entry->second);
661 interface_entry->second.txt.erase(entry);
662 OSP_LOG_IF(WARN, err != mStatus_NoError) << "mDNS_StopQuery failed: " << err;
663 RemoveQuestionsIfEmpty(socket);
664 return MapMdnsError(err);
665 }
666
StopAQuery(UdpSocket * socket,const DomainName & domain_name)667 MdnsResponderErrorCode MdnsResponderAdapterImpl::StopAQuery(
668 UdpSocket* socket,
669 const DomainName& domain_name) {
670 TRACE_SCOPED(TraceCategory::kMdns, "MdnsResponderAdapterImpl::StopAQuery");
671 auto interface_entry = socket_to_questions_.find(socket);
672 if (interface_entry == socket_to_questions_.end())
673 return MdnsResponderErrorCode::kNoError;
674 auto entry = interface_entry->second.a.find(domain_name);
675 if (entry == interface_entry->second.a.end())
676 return MdnsResponderErrorCode::kNoError;
677
678 const auto err = mDNS_StopQuery(&mdns_, &entry->second);
679 interface_entry->second.a.erase(entry);
680 OSP_LOG_IF(WARN, err != mStatus_NoError) << "mDNS_StopQuery failed: " << err;
681 RemoveQuestionsIfEmpty(socket);
682 return MapMdnsError(err);
683 }
684
StopAaaaQuery(UdpSocket * socket,const DomainName & domain_name)685 MdnsResponderErrorCode MdnsResponderAdapterImpl::StopAaaaQuery(
686 UdpSocket* socket,
687 const DomainName& domain_name) {
688 TRACE_SCOPED(TraceCategory::kMdns, "MdnsResponderAdapterImpl::StopAaaaQuery");
689 auto interface_entry = socket_to_questions_.find(socket);
690 if (interface_entry == socket_to_questions_.end())
691 return MdnsResponderErrorCode::kNoError;
692 auto entry = interface_entry->second.aaaa.find(domain_name);
693 if (entry == interface_entry->second.aaaa.end())
694 return MdnsResponderErrorCode::kNoError;
695
696 const auto err = mDNS_StopQuery(&mdns_, &entry->second);
697 interface_entry->second.aaaa.erase(entry);
698 OSP_LOG_IF(WARN, err != mStatus_NoError) << "mDNS_StopQuery failed: " << err;
699 RemoveQuestionsIfEmpty(socket);
700 return MapMdnsError(err);
701 }
702
RegisterService(const std::string & service_instance,const std::string & service_name,const std::string & service_protocol,const DomainName & target_host,uint16_t target_port,const std::map<std::string,std::string> & txt_data)703 MdnsResponderErrorCode MdnsResponderAdapterImpl::RegisterService(
704 const std::string& service_instance,
705 const std::string& service_name,
706 const std::string& service_protocol,
707 const DomainName& target_host,
708 uint16_t target_port,
709 const std::map<std::string, std::string>& txt_data) {
710 TRACE_SCOPED(TraceCategory::kMdns,
711 "MdnsResponderAdapterImpl::RegisterService");
712 OSP_DCHECK(IsValidServiceName(service_name));
713 OSP_DCHECK(IsValidServiceProtocol(service_protocol));
714 service_records_.push_back(std::make_unique<ServiceRecordSet>());
715 auto* service_record = service_records_.back().get();
716 domainlabel instance;
717 domainlabel name;
718 domainlabel protocol;
719 domainname type;
720 domainname domain;
721 domainname host;
722 mDNSIPPort port;
723
724 MakeLocalServiceNameParts(service_instance, service_name, service_protocol,
725 &instance, &name, &protocol, &type, &domain);
726 std::copy(target_host.domain_name().begin(), target_host.domain_name().end(),
727 host.c);
728 AssignMdnsPort(&port, target_port);
729 auto txt = MakeTxtData(txt_data);
730 if (txt.size() > kMaxStaticTxtDataSize) {
731 // Not handling oversized TXT records.
732 return MdnsResponderErrorCode::kUnsupportedError;
733 }
734
735 if (service_records_.size() == 1)
736 AdvertiseInterfaces();
737
738 auto result = mDNS_RegisterService(
739 &mdns_, service_record, &instance, &type, &domain, &host, port,
740 reinterpret_cast<const uint8_t*>(txt.data()), txt.size(), nullptr, 0,
741 mDNSInterface_Any, &MdnsResponderAdapterImpl::ServiceCallback, this, 0);
742
743 if (result != mStatus_NoError) {
744 service_records_.pop_back();
745 if (service_records_.empty())
746 DeadvertiseInterfaces();
747 }
748 return MapMdnsError(result);
749 }
750
DeregisterService(const std::string & service_instance,const std::string & service_name,const std::string & service_protocol)751 MdnsResponderErrorCode MdnsResponderAdapterImpl::DeregisterService(
752 const std::string& service_instance,
753 const std::string& service_name,
754 const std::string& service_protocol) {
755 TRACE_SCOPED(TraceCategory::kMdns,
756 "MdnsResponderAdapterImpl::DeregisterService");
757 domainlabel instance;
758 domainlabel name;
759 domainlabel protocol;
760 domainname type;
761 domainname domain;
762 domainname full_instance_name;
763
764 MakeLocalServiceNameParts(service_instance, service_name, service_protocol,
765 &instance, &name, &protocol, &type, &domain);
766 if (!ConstructServiceName(&full_instance_name, &instance, &type, &domain))
767 return MdnsResponderErrorCode::kInvalidParameters;
768
769 for (auto it = service_records_.begin(); it != service_records_.end(); ++it) {
770 if (SameDomainName(&full_instance_name, &(*it)->RR_SRV.namestorage)) {
771 // |it| will be removed from |service_records_| in ServiceCallback, when
772 // mDNSResponder is done with the memory.
773 mDNS_DeregisterService(&mdns_, it->get());
774 return MdnsResponderErrorCode::kNoError;
775 }
776 }
777 return MdnsResponderErrorCode::kNoError;
778 }
779
UpdateTxtData(const std::string & service_instance,const std::string & service_name,const std::string & service_protocol,const std::map<std::string,std::string> & txt_data)780 MdnsResponderErrorCode MdnsResponderAdapterImpl::UpdateTxtData(
781 const std::string& service_instance,
782 const std::string& service_name,
783 const std::string& service_protocol,
784 const std::map<std::string, std::string>& txt_data) {
785 TRACE_SCOPED(TraceCategory::kMdns, "MdnsResponderAdapterImpl::UpdateTxtData");
786 domainlabel instance;
787 domainlabel name;
788 domainlabel protocol;
789 domainname type;
790 domainname domain;
791 domainname full_instance_name;
792
793 MakeLocalServiceNameParts(service_instance, service_name, service_protocol,
794 &instance, &name, &protocol, &type, &domain);
795 if (!ConstructServiceName(&full_instance_name, &instance, &type, &domain))
796 return MdnsResponderErrorCode::kInvalidParameters;
797 std::string txt = MakeTxtData(txt_data);
798 if (txt.size() > kMaxStaticTxtDataSize) {
799 // Not handling oversized TXT records.
800 return MdnsResponderErrorCode::kUnsupportedError;
801 }
802
803 for (std::unique_ptr<ServiceRecordSet>& record : service_records_) {
804 if (SameDomainName(&full_instance_name, &record->RR_SRV.namestorage)) {
805 std::copy(txt.begin(), txt.end(), record->RR_TXT.rdatastorage.u.txt.c);
806 mDNS_Update(&mdns_, &record->RR_TXT, 0, txt.size(),
807 &record->RR_TXT.rdatastorage, nullptr);
808 return MdnsResponderErrorCode::kNoError;
809 }
810 }
811 return MdnsResponderErrorCode::kNoError;
812 }
813
814 // static
AQueryCallback(mDNS * m,DNSQuestion * question,const ResourceRecord * answer,QC_result added)815 void MdnsResponderAdapterImpl::AQueryCallback(mDNS* m,
816 DNSQuestion* question,
817 const ResourceRecord* answer,
818 QC_result added) {
819 TRACE_SCOPED(TraceCategory::kMdns,
820 "MdnsResponderAdapterImpl::AQueryCallback");
821 OSP_DCHECK(question);
822 OSP_DCHECK(answer);
823 OSP_DCHECK_EQ(answer->rrtype, kDNSType_A);
824 DomainName domain(std::vector<uint8_t>(
825 question->qname.c,
826 question->qname.c + DomainNameLength(&question->qname)));
827 IPAddress address(answer->rdata->u.ipv4.b);
828
829 auto* adapter =
830 reinterpret_cast<MdnsResponderAdapterImpl*>(question->QuestionContext);
831 OSP_DCHECK(adapter);
832 auto event_type = QueryEventHeader::Type::kAddedNoCache;
833 if (added == QC_add) {
834 event_type = QueryEventHeader::Type::kAdded;
835 } else if (added == QC_rmv) {
836 event_type = QueryEventHeader::Type::kRemoved;
837 } else {
838 OSP_DCHECK_EQ(added, QC_addnocache);
839 }
840 adapter->a_responses_.emplace_back(
841 QueryEventHeader{event_type,
842 reinterpret_cast<UdpSocket*>(answer->InterfaceID)},
843 std::move(domain), address);
844 }
845
846 // static
AaaaQueryCallback(mDNS * m,DNSQuestion * question,const ResourceRecord * answer,QC_result added)847 void MdnsResponderAdapterImpl::AaaaQueryCallback(mDNS* m,
848 DNSQuestion* question,
849 const ResourceRecord* answer,
850 QC_result added) {
851 TRACE_SCOPED(TraceCategory::kMdns,
852 "MdnsResponderAdapterImpl::AaaaQueryCallback");
853 OSP_DCHECK(question);
854 OSP_DCHECK(answer);
855 OSP_DCHECK_EQ(answer->rrtype, kDNSType_A);
856 DomainName domain(std::vector<uint8_t>(
857 question->qname.c,
858 question->qname.c + DomainNameLength(&question->qname)));
859 IPAddress address(IPAddress::Version::kV6, answer->rdata->u.ipv6.b);
860
861 auto* adapter =
862 reinterpret_cast<MdnsResponderAdapterImpl*>(question->QuestionContext);
863 OSP_DCHECK(adapter);
864 auto event_type = QueryEventHeader::Type::kAddedNoCache;
865 if (added == QC_add) {
866 event_type = QueryEventHeader::Type::kAdded;
867 } else if (added == QC_rmv) {
868 event_type = QueryEventHeader::Type::kRemoved;
869 } else {
870 OSP_DCHECK_EQ(added, QC_addnocache);
871 }
872 adapter->aaaa_responses_.emplace_back(
873 QueryEventHeader{event_type,
874 reinterpret_cast<UdpSocket*>(answer->InterfaceID)},
875 std::move(domain), address);
876 }
877
878 // static
PtrQueryCallback(mDNS * m,DNSQuestion * question,const ResourceRecord * answer,QC_result added)879 void MdnsResponderAdapterImpl::PtrQueryCallback(mDNS* m,
880 DNSQuestion* question,
881 const ResourceRecord* answer,
882 QC_result added) {
883 TRACE_SCOPED(TraceCategory::kMdns,
884 "MdnsResponderAdapterImpl::PtrQueryCallback");
885 OSP_DCHECK(question);
886 OSP_DCHECK(answer);
887 OSP_DCHECK_EQ(answer->rrtype, kDNSType_PTR);
888 DomainName result(std::vector<uint8_t>(
889 answer->rdata->u.name.c,
890 answer->rdata->u.name.c + DomainNameLength(&answer->rdata->u.name)));
891
892 auto* adapter =
893 reinterpret_cast<MdnsResponderAdapterImpl*>(question->QuestionContext);
894 OSP_DCHECK(adapter);
895 auto event_type = QueryEventHeader::Type::kAddedNoCache;
896 if (added == QC_add) {
897 event_type = QueryEventHeader::Type::kAdded;
898 } else if (added == QC_rmv) {
899 event_type = QueryEventHeader::Type::kRemoved;
900 } else {
901 OSP_DCHECK_EQ(added, QC_addnocache);
902 }
903 adapter->ptr_responses_.emplace_back(
904 QueryEventHeader{event_type,
905 reinterpret_cast<UdpSocket*>(answer->InterfaceID)},
906 std::move(result));
907 }
908
909 // static
SrvQueryCallback(mDNS * m,DNSQuestion * question,const ResourceRecord * answer,QC_result added)910 void MdnsResponderAdapterImpl::SrvQueryCallback(mDNS* m,
911 DNSQuestion* question,
912 const ResourceRecord* answer,
913 QC_result added) {
914 TRACE_SCOPED(TraceCategory::kMdns,
915 "MdnsResponderAdapterImpl::SrvQueryCallback");
916 OSP_DCHECK(question);
917 OSP_DCHECK(answer);
918 OSP_DCHECK_EQ(answer->rrtype, kDNSType_SRV);
919 DomainName service(std::vector<uint8_t>(
920 question->qname.c,
921 question->qname.c + DomainNameLength(&question->qname)));
922 DomainName result(
923 std::vector<uint8_t>(answer->rdata->u.srv.target.c,
924 answer->rdata->u.srv.target.c +
925 DomainNameLength(&answer->rdata->u.srv.target)));
926
927 auto* adapter =
928 reinterpret_cast<MdnsResponderAdapterImpl*>(question->QuestionContext);
929 OSP_DCHECK(adapter);
930 auto event_type = QueryEventHeader::Type::kAddedNoCache;
931 if (added == QC_add) {
932 event_type = QueryEventHeader::Type::kAdded;
933 } else if (added == QC_rmv) {
934 event_type = QueryEventHeader::Type::kRemoved;
935 } else {
936 OSP_DCHECK_EQ(added, QC_addnocache);
937 }
938 adapter->srv_responses_.emplace_back(
939 QueryEventHeader{event_type,
940 reinterpret_cast<UdpSocket*>(answer->InterfaceID)},
941 std::move(service), std::move(result),
942 GetNetworkOrderPort(answer->rdata->u.srv.port));
943 }
944
945 // static
TxtQueryCallback(mDNS * m,DNSQuestion * question,const ResourceRecord * answer,QC_result added)946 void MdnsResponderAdapterImpl::TxtQueryCallback(mDNS* m,
947 DNSQuestion* question,
948 const ResourceRecord* answer,
949 QC_result added) {
950 OSP_DCHECK(question);
951 OSP_DCHECK(answer);
952 OSP_DCHECK_EQ(answer->rrtype, kDNSType_TXT);
953 DomainName service(std::vector<uint8_t>(
954 question->qname.c,
955 question->qname.c + DomainNameLength(&question->qname)));
956 auto lines = ParseTxtResponse(answer->rdata->u.txt.c, answer->rdlength);
957
958 auto* adapter =
959 reinterpret_cast<MdnsResponderAdapterImpl*>(question->QuestionContext);
960 OSP_DCHECK(adapter);
961 auto event_type = QueryEventHeader::Type::kAddedNoCache;
962 if (added == QC_add) {
963 event_type = QueryEventHeader::Type::kAdded;
964 } else if (added == QC_rmv) {
965 event_type = QueryEventHeader::Type::kRemoved;
966 } else {
967 OSP_DCHECK_EQ(added, QC_addnocache);
968 }
969 adapter->txt_responses_.emplace_back(
970 QueryEventHeader{event_type,
971 reinterpret_cast<UdpSocket*>(answer->InterfaceID)},
972 std::move(service), std::move(lines));
973 }
974
975 // static
ServiceCallback(mDNS * m,ServiceRecordSet * service_record,mStatus result)976 void MdnsResponderAdapterImpl::ServiceCallback(mDNS* m,
977 ServiceRecordSet* service_record,
978 mStatus result) {
979 // TODO(btolsch): Handle mStatus_NameConflict.
980 if (result == mStatus_MemFree) {
981 OSP_DLOG_INFO << "free service record";
982 auto* adapter = reinterpret_cast<MdnsResponderAdapterImpl*>(
983 service_record->ServiceContext);
984 auto& service_records = adapter->service_records_;
985 service_records.erase(
986 std::remove_if(
987 service_records.begin(), service_records.end(),
988 [service_record](const std::unique_ptr<ServiceRecordSet>& sr) {
989 return sr.get() == service_record;
990 }),
991 service_records.end());
992
993 if (service_records.empty())
994 adapter->DeadvertiseInterfaces();
995 }
996 }
997
AdvertiseInterfaces()998 void MdnsResponderAdapterImpl::AdvertiseInterfaces() {
999 TRACE_SCOPED(TraceCategory::kMdns,
1000 "MdnsResponderAdapterImpl::AdvertiseInterfaces");
1001 for (auto& info : responder_interface_info_) {
1002 UdpSocket* socket = info.first;
1003 NetworkInterfaceInfo& interface_info = info.second;
1004 mDNS_SetupResourceRecord(&interface_info.RR_A, /** RDataStorage */ nullptr,
1005 reinterpret_cast<mDNSInterfaceID>(socket),
1006 kDNSType_A, kHostNameTTL, kDNSRecordTypeUnique,
1007 AuthRecordAny,
1008 /** Callback */ nullptr, /** Context */ nullptr);
1009 AssignDomainName(&interface_info.RR_A.namestorage,
1010 &mdns_.MulticastHostname);
1011 if (interface_info.ip.type == mDNSAddrType_IPv4) {
1012 interface_info.RR_A.resrec.rdata->u.ipv4 = interface_info.ip.ip.v4;
1013 } else {
1014 interface_info.RR_A.resrec.rdata->u.ipv6 = interface_info.ip.ip.v6;
1015 }
1016 mDNS_Register(&mdns_, &interface_info.RR_A);
1017 }
1018 }
1019
DeadvertiseInterfaces()1020 void MdnsResponderAdapterImpl::DeadvertiseInterfaces() {
1021 // Both loops below use the A resource record's domain name to determine
1022 // whether the record was advertised. AdvertiseInterfaces sets the domain
1023 // name before registering the A record, and this clears it after
1024 // deregistering.
1025 for (auto& info : responder_interface_info_) {
1026 NetworkInterfaceInfo& interface_info = info.second;
1027 if (interface_info.RR_A.namestorage.c[0]) {
1028 mDNS_Deregister(&mdns_, &interface_info.RR_A);
1029 interface_info.RR_A.namestorage.c[0] = 0;
1030 }
1031 }
1032 }
1033
RemoveQuestionsIfEmpty(UdpSocket * socket)1034 void MdnsResponderAdapterImpl::RemoveQuestionsIfEmpty(UdpSocket* socket) {
1035 auto entry = socket_to_questions_.find(socket);
1036 bool empty = entry->second.a.empty() || entry->second.aaaa.empty() ||
1037 entry->second.ptr.empty() || entry->second.srv.empty() ||
1038 entry->second.txt.empty();
1039 if (empty)
1040 socket_to_questions_.erase(entry);
1041 }
1042
1043 } // namespace osp
1044 } // namespace openscreen
1045