-
Notifications
You must be signed in to change notification settings - Fork 80
/
service_advertiser_impl.cpp
390 lines (322 loc) · 14.1 KB
/
service_advertiser_impl.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
#include "mdns/service_advertiser_impl.h"
#include <boost/asio/ip/address.hpp>
#include "mdns/dns_sd_impl.h"
#include "slog/all_in_one.h"
#ifdef _WIN32
// winsock2.h provides htons
#else
// POSIX
#include <arpa/inet.h> // for htons
#endif
namespace mdns_details
{
using namespace mdns;
static std::string make_full_name(const std::string& host_name, const std::string& domain)
{
return !host_name.empty() ? host_name + "." + (!domain.empty() ? domain : "local.") : "";
}
static std::vector<unsigned char> make_txt_records(const txt_records& records)
{
std::vector<unsigned char> txt;
// create the txt record string in the correct format
for (const auto& record : records)
{
size_t len = record.size();
if (len > 255)
{
// txt record is too long
continue;
}
txt.push_back((unsigned char)len);
txt.insert(txt.end(), record.begin(), record.end());
}
// although a completely empty txt record is invalid, DNSServiceRegister handles this case
return txt;
}
struct register_address_context
{
slog::base_gate& gate;
};
static void DNSSD_API register_address_reply(
DNSServiceRef sdRef,
DNSRecordRef recordRef,
DNSServiceFlags flags,
DNSServiceErrorType errorCode,
void* context)
{
register_address_context* impl = (register_address_context*)context;
if (errorCode == kDNSServiceErr_NoError)
{
slog::log<slog::severities::more_info>(impl->gate, SLOG_FLF) << "After DNSServiceRegisterRecord, DNSServiceRegisterRecordReply received no error";
}
else
{
slog::log<slog::severities::error>(impl->gate, SLOG_FLF) << "After DNSServiceRegisterRecord, DNSServiceRegisterRecordReply received error: " << errorCode;
// possible errors include kDNSServiceErr_NameConflict, kDNSServiceErr_AlreadyRegistered
}
}
static bool register_address(DNSServiceRef& client, const std::string& host_name, const std::string& ip_address_, const std::string& domain, std::uint32_t interface_id, slog::base_gate& gate)
{
// since empty host_name is valid for other functions, check that logic error here
if (host_name.empty()) return false;
#if BOOST_VERSION >= 106600
const auto ip_address = boost::asio::ip::make_address(ip_address_);
#else
const auto ip_address = boost::asio::ip::address::from_string(ip_address_);
#endif
if (ip_address.is_unspecified()) return false;
// for now, limited to IPv4
if (!ip_address.is_v4()) return false;
bool result = false;
// the Avahi compatibility layer implementations of DNSServiceCreateConnection and DNSServiceRegisterRecord
// just return kDNSServiceErr_Unsupported
// see https://github.com/lathiat/avahi/blob/master/avahi-compat-libdns_sd/unsupported.c
// an alternative may be to use avahi-publish -a -R {host_name} {ip_address}
// see https://linux.die.net/man/1/avahi-publish-address
// lazily create the connection
DNSServiceErrorType errorCode = nullptr == client ? DNSServiceCreateConnection(&client) : kDNSServiceErr_NoError;
if (errorCode == kDNSServiceErr_NoError)
{
// so far as I can tell, attempting to register a host name in a domain other than the multicast .local domain always fails
// which may be the expected behaviour?
const auto fullname = make_full_name(host_name, domain);
#if BOOST_VERSION >= 106600
const auto rdata = htonl(ip_address.to_v4().to_uint());
#else
const auto rdata = htonl(ip_address.to_v4().to_ulong());
#endif
// so far as I can tell, this call always returns kDNSServiceErr_BadParam in my Windows environment
// with a message in the event log for the Bonjour Service:
// mDNS_Register_internal: TTL CCCC0000 should be 1 - 0x7FFFFFFF 4 ...
// which looks like a bug in how the ttl value is marshalled between the client stub library and the service?
DNSRecordRef recordRef;
register_address_context context{ gate };
errorCode = DNSServiceRegisterRecord(
(DNSServiceRef)client,
&recordRef,
kDNSServiceFlagsShared,
interface_id, // 0 == kDNSServiceInterfaceIndexAny
fullname.c_str(),
kDNSServiceType_A,
kDNSServiceClass_IN,
sizeof(rdata),
&rdata,
0,
®ister_address_reply,
&context);
if (errorCode == kDNSServiceErr_NoError)
{
// process the response (should this be in a loop, like for browse?)
errorCode = DNSServiceProcessResult((DNSServiceRef)client, 1);
if (errorCode == kDNSServiceErr_NoError)
{
slog::log<slog::severities::too_much_info>(gate, SLOG_FLF) << "After DNSServiceRegisterRecord, DNSServiceProcessResult succeeded";
result = true;
slog::log<slog::severities::info>(gate, SLOG_FLF) << "Registered address: " << ip_address.to_string() << " for hostname: " << fullname;
}
else
{
slog::log<slog::severities::error>(gate, SLOG_FLF) << "After DNSServiceRegisterRecord, DNSServiceProcessResult reported error: " << errorCode;
}
}
else
{
slog::log<slog::severities::error>(gate, SLOG_FLF) << "DNSServiceRegisterRecord reported error: " << errorCode;
}
}
else
{
slog::log<slog::severities::error>(gate, SLOG_FLF) << "DNSServiceCreateConnection reported error: " << errorCode;
}
return result;
}
struct service
{
service() : sdRef(nullptr) {}
service(const std::string& name, const std::string& type, const std::string& domain, DNSServiceRef sdRef)
: name(name)
, type(type)
, domain(domain)
, sdRef(sdRef)
{}
std::string name;
std::string type;
std::string domain;
DNSServiceRef sdRef;
};
static bool register_service(std::vector<service>& services, const std::string& name, const std::string& type, std::uint16_t port, const std::string& domain, const std::string& host_name, const txt_records& records, slog::base_gate& gate)
{
bool result = false;
DNSServiceRef sdRef;
const auto fullname = make_full_name(host_name, domain);
const std::vector<unsigned char> txt_records = make_txt_records(records);
DNSServiceErrorType errorCode = DNSServiceRegister(
&sdRef,
0,
kDNSServiceInterfaceIndexAny,
!name.empty() ? name.c_str() : NULL,
type.c_str(),
!domain.empty() ? domain.c_str() : NULL,
!fullname.empty() ? fullname.c_str() : NULL,
htons(port),
(std::uint16_t)txt_records.size(),
!txt_records.empty() ? &txt_records[0] : NULL,
NULL,
NULL);
if (errorCode == kDNSServiceErr_NoError)
{
result = true;
// if name were empty, the callback would indicate what name was automatically chosen (and likewise for domain)
services.push_back({ name, type, domain, sdRef });
slog::log<slog::severities::info>(gate, SLOG_FLF) << "Registered advertisement for: " << name << "." << type << (domain.empty() ? "" : "." + domain);
}
else
{
slog::log<slog::severities::error>(gate, SLOG_FLF) << "DNSServiceRegister reported error: " << errorCode << " while registering advertisement for: " << name << "." << type << (domain.empty() ? "" : "." + domain);
}
return result;
}
static bool update_record(const std::vector<service>& services, const std::string& name, const std::string& type, const std::string& domain, const txt_records& records, slog::base_gate& gate)
{
bool result = false;
// try to find a record that matches
for (const auto& service : services)
{
if (service.name == name && service.type == type && service.domain == domain)
{
const std::vector<unsigned char> txt_records = make_txt_records(records);
DNSServiceErrorType errorCode = DNSServiceUpdateRecord(service.sdRef, NULL, 0, (std::uint16_t)txt_records.size(), &txt_records[0], 0);
if (errorCode == kDNSServiceErr_NoError)
{
result = true;
slog::log<slog::severities::more_info>(gate, SLOG_FLF) << "Updated advertisement for: " << name << "." << type << (domain.empty() ? "" : "." + domain);
}
break;
}
}
return result;
}
}
namespace mdns
{
namespace details
{
// hm, 'final' may be appropriate here rather than 'override'?
class service_advertiser_impl_ : public service_advertiser_impl
{
public:
explicit service_advertiser_impl_(slog::base_gate& gate)
: client(nullptr)
, gate(gate)
{
}
~service_advertiser_impl_() override
{
try
{
close().wait();
}
catch (...) {}
}
pplx::task<void> open() override
{
// this might be the right place to create the client connection, rather than doing it lazily in register_address?
return pplx::task_from_result();
}
pplx::task<void> close() override
{
std::lock_guard<std::mutex> lock(mutex);
// De-register anything that is registered
for (const auto& service : services)
{
DNSServiceRefDeallocate(service.sdRef);
slog::log<slog::severities::too_much_info>(gate, SLOG_FLF) << "Advertisement stopped for: " << service.name;
}
services.clear();
if (nullptr != client)
{
DNSServiceRefDeallocate(client);
client = nullptr;
}
return pplx::task_from_result();
}
pplx::task<bool> register_address(const std::string& host_name, const std::string& ip_address, const std::string& domain, std::uint32_t interface_id) override
{
return pplx::create_task([=]
{
// "Note: client is responsible for serializing access to these structures if
// they are shared between concurrent threads."
// See dns_sd.h
std::lock_guard<std::mutex> lock(mutex);
return mdns_details::register_address(client, host_name, ip_address, domain, interface_id, gate);
});
}
pplx::task<bool> register_service(const std::string& name, const std::string& type, std::uint16_t port, const std::string& domain, const std::string& host_name, const txt_records& txt_records) override
{
return pplx::create_task([=]
{
// Lock also required here, for services
std::lock_guard<std::mutex> lock(mutex);
return mdns_details::register_service(services, name, type, port, domain, host_name, txt_records, gate);
});
}
pplx::task<bool> update_record(const std::string& name, const std::string& type, const std::string& domain, const txt_records& txt_records) override
{
return pplx::create_task([=]
{
// Lock also required here, for services
std::lock_guard<std::mutex> lock(mutex);
return mdns_details::update_record(services, name, type, domain, txt_records, gate);
});
}
private:
DNSServiceRef client;
std::vector<mdns_details::service> services;
std::mutex mutex;
slog::base_gate& gate;
};
}
service_advertiser::service_advertiser(std::unique_ptr<details::service_advertiser_impl> impl)
: impl(std::move(impl))
{
}
service_advertiser::service_advertiser(slog::base_gate& gate)
: impl(new details::service_advertiser_impl_(gate))
{
}
service_advertiser::service_advertiser(service_advertiser&& other)
: impl(std::move(other.impl))
{
}
service_advertiser& service_advertiser::operator=(service_advertiser&& other)
{
if (this != &other)
{
impl = std::move(other.impl);
}
return *this;
}
service_advertiser::~service_advertiser()
{
}
pplx::task<void> service_advertiser::open()
{
return impl->open();
}
pplx::task<void> service_advertiser::close()
{
return impl->close();
}
pplx::task<bool> service_advertiser::register_address(const std::string& host_name, const std::string& ip_address, const std::string& domain, std::uint32_t interface_id)
{
return impl->register_address(host_name, ip_address, domain, interface_id);
}
pplx::task<bool> service_advertiser::register_service(const std::string& name, const std::string& type, std::uint16_t port, const std::string& domain, const std::string& host_name, const txt_records& txt_records)
{
return impl->register_service(name, type, port, domain, host_name, txt_records);
}
pplx::task<bool> service_advertiser::update_record(const std::string& name, const std::string& type, const std::string& domain, const txt_records& txt_records)
{
return impl->update_record(name, type, domain, txt_records);
}
}