1 /*
2 AirSane Imaging Daemon
3 Copyright (C) 2018-2021 Simul Piscator
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include "mdnspublisher.h"
20
21 #include <avahi-client/client.h>
22 #include <avahi-client/publish.h>
23 #include <avahi-common/alternative.h>
24 #include <avahi-common/error.h>
25 #include <avahi-common/malloc.h>
26 #include <avahi-common/thread-watch.h>
27
28 #include <algorithm>
29 #include <cerrno>
30 #include <cstring>
31 #include <iostream>
32 #include <list>
33
34 namespace {
35
36 class AvahiThreadedPollGuard
37 {
38 public:
AvahiThreadedPollGuard(AvahiThreadedPoll * pThread)39 explicit AvahiThreadedPollGuard(AvahiThreadedPoll* pThread)
40 : mpThread(pThread)
41 {
42 ::avahi_threaded_poll_lock(mpThread);
43 }
~AvahiThreadedPollGuard()44 ~AvahiThreadedPollGuard() { ::avahi_threaded_poll_unlock(mpThread); }
45
46 private:
47 AvahiThreadedPoll* mpThread;
48 };
49
50 struct ServiceEntry
51 {
52 MdnsPublisher::Service* mpService;
53 AvahiEntryGroup* mpEntryGroup;
54
ServiceEntry__anon96e916750111::ServiceEntry55 ServiceEntry(MdnsPublisher::Service* p)
56 : mpService(p)
57 , mpEntryGroup(nullptr)
58 {}
59
~ServiceEntry__anon96e916750111::ServiceEntry60 ~ServiceEntry() { unannounce(); }
61
announce__anon96e916750111::ServiceEntry62 bool announce(AvahiClient* pClient)
63 {
64 unannounce();
65 int err;
66 do {
67 err = doAnnounce(pClient);
68 if (err == AVAHI_ERR_COLLISION)
69 renameService();
70 } while (err == AVAHI_ERR_COLLISION);
71 if (err) {
72 std::cerr << "Avahi error when adding service: " << ::avahi_strerror(err)
73 << " (" << err << ")" << std::endl;
74 }
75 return !err;
76 }
77
doAnnounce__anon96e916750111::ServiceEntry78 int doAnnounce(AvahiClient* pClient)
79 {
80 if (!pClient) {
81 std::cerr << "No avahi client instance available, not registering service"
82 << std::endl;
83 return AVAHI_ERR_FAILURE;
84 }
85 if (mpEntryGroup)
86 ::avahi_entry_group_free(mpEntryGroup);
87 mpEntryGroup = ::avahi_entry_group_new(pClient, &entryGroupCallback, this);
88 if (!mpEntryGroup)
89 return ::avahi_client_errno(pClient);
90 int err = ::avahi_entry_group_add_service(mpEntryGroup,
91 mpService->interfaceIndex(),
92 AVAHI_PROTO_UNSPEC,
93 AvahiPublishFlags(0),
94 mpService->name().c_str(),
95 mpService->type().c_str(),
96 nullptr,
97 nullptr,
98 mpService->port(),
99 nullptr);
100 if (!err)
101 err = ::avahi_entry_group_commit(mpEntryGroup);
102 if (!err) {
103 AvahiStringList* txt = nullptr;
104 for (const auto& entry : mpService->txtRecord())
105 txt = ::avahi_string_list_add_pair(
106 txt, entry.first.c_str(), entry.second.c_str());
107 err = ::avahi_entry_group_update_service_txt_strlst(
108 mpEntryGroup,
109 mpService->interfaceIndex(),
110 AVAHI_PROTO_UNSPEC,
111 AvahiPublishFlags(0),
112 mpService->name().c_str(),
113 mpService->type().c_str(),
114 nullptr,
115 txt);
116 ::avahi_string_list_free(txt);
117 }
118 return err;
119 }
120
unannounce__anon96e916750111::ServiceEntry121 void unannounce()
122 {
123 if (mpEntryGroup) {
124 ::avahi_entry_group_free(mpEntryGroup);
125 mpEntryGroup = nullptr;
126 }
127 }
128
renameService__anon96e916750111::ServiceEntry129 void renameService()
130 {
131 char* altname = ::avahi_alternative_service_name(mpService->name().c_str());
132 mpService->setName(altname);
133 ::avahi_free(altname);
134 }
135
onCollision__anon96e916750111::ServiceEntry136 void onCollision()
137 {
138 renameService();
139 announce(::avahi_entry_group_get_client(mpEntryGroup));
140 }
141
onError__anon96e916750111::ServiceEntry142 void onError() { unannounce(); }
143
entryGroupCallback__anon96e916750111::ServiceEntry144 static void entryGroupCallback(AvahiEntryGroup*,
145 AvahiEntryGroupState state,
146 void* instance)
147 {
148 auto p = static_cast<ServiceEntry*>(instance);
149 switch (state) {
150 case AVAHI_ENTRY_GROUP_COLLISION:
151 p->onCollision();
152 break;
153 case AVAHI_ENTRY_GROUP_FAILURE:
154 p->onError();
155 break;
156 case AVAHI_ENTRY_GROUP_UNCOMMITED:
157 case AVAHI_ENTRY_GROUP_REGISTERING:
158 case AVAHI_ENTRY_GROUP_ESTABLISHED:
159 break;
160 }
161 }
162 };
163
164 } // namespace
165
166 struct MdnsPublisher::Private
167 {
168 AvahiThreadedPoll* mpThread;
169 AvahiClient* mpClient;
170 AvahiClientState mState;
171
172 std::string mHostnameFqdn, mHostname;
173 std::list<ServiceEntry> mServices;
174
PrivateMdnsPublisher::Private175 Private()
176 : mpThread(nullptr)
177 , mpClient(nullptr)
178 , mState(AVAHI_CLIENT_CONNECTING)
179 {
180 mpThread = ::avahi_threaded_poll_new();
181 if (mpThread) {
182 createClient();
183 if (::avahi_threaded_poll_start(mpThread)) {
184 int err = errno;
185 std::cerr << ::strerror(err) << std::endl;
186 }
187 }
188 }
~PrivateMdnsPublisher::Private189 ~Private()
190 {
191 if (mpThread) {
192 ::avahi_threaded_poll_stop(mpThread);
193 destroyClient();
194 ::avahi_threaded_poll_free(mpThread);
195 }
196 }
197
clientCallbackMdnsPublisher::Private198 static void clientCallback(AvahiClient* client,
199 AvahiClientState state,
200 void* instance)
201 {
202 auto p = static_cast<Private*>(instance);
203 if (p->mState == AVAHI_CLIENT_CONNECTING)
204 switch (state) {
205 case AVAHI_CLIENT_S_COLLISION:
206 case AVAHI_CLIENT_S_REGISTERING:
207 case AVAHI_CLIENT_S_RUNNING:
208 p->onConnected();
209 break;
210 case AVAHI_CLIENT_FAILURE:
211 case AVAHI_CLIENT_CONNECTING:
212 break;
213 }
214 if (state == AVAHI_CLIENT_FAILURE) {
215 if (::avahi_client_errno(client) == AVAHI_ERR_DISCONNECTED)
216 p->onDisconnected();
217 else
218 p->onError(::avahi_client_errno(client));
219 }
220 p->mState = state;
221 }
222
createClientMdnsPublisher::Private223 void createClient()
224 {
225 destroyClient();
226 const AvahiPoll* pPoll = ::avahi_threaded_poll_get(mpThread);
227 int err = 0;
228 mpClient = ::avahi_client_new(
229 pPoll, AVAHI_CLIENT_NO_FAIL, &clientCallback, this, &err);
230 if (!mpClient) {
231 std::cerr << "Failed to create avahi client: " << ::avahi_strerror(err)
232 << " (" << err << ")" << std::endl;
233 return;
234 }
235 const char* name = ::avahi_client_get_host_name_fqdn(mpClient);
236 if (!name) {
237 int err = ::avahi_client_errno(mpClient);
238 std::cerr << "Failed to get host name: " << ::avahi_strerror(err) << " ("
239 << err << ")" << std::endl;
240 return;
241 }
242 mHostnameFqdn = name;
243 size_t pos = mHostnameFqdn.find('.');
244 mHostname = mHostnameFqdn.substr(0, pos);
245 }
246
destroyClientMdnsPublisher::Private247 void destroyClient()
248 {
249 if (mpClient) {
250 for (auto& entry : mServices)
251 entry.unannounce();
252 ::avahi_client_free(mpClient);
253 }
254 mpClient = nullptr;
255 }
256
onConnectedMdnsPublisher::Private257 void onConnected()
258 {
259 for (auto& entry : mServices)
260 entry.announce(mpClient);
261 }
262
onDisconnectedMdnsPublisher::Private263 void onDisconnected()
264 {
265 destroyClient();
266 createClient();
267 }
268
onErrorMdnsPublisher::Private269 void onError(int err)
270 {
271 std::clog << ::avahi_strerror(err) << std::endl;
272 destroyClient();
273 }
274
findServiceMdnsPublisher::Private275 std::list<ServiceEntry>::iterator findService(const Service* pService)
276 {
277 auto i = std::find_if(mServices.begin(),
278 mServices.end(),
279 [pService](const ServiceEntry& entry) {
280 return entry.mpService == pService;
281 });
282 return i;
283 }
284 };
285
MdnsPublisher()286 MdnsPublisher::MdnsPublisher()
287 : p(new Private)
288 {}
289
~MdnsPublisher()290 MdnsPublisher::~MdnsPublisher()
291 {
292 delete p;
293 }
294
295 const std::string&
hostname() const296 MdnsPublisher::hostname() const
297 {
298 return p->mHostname;
299 }
300
301 const std::string&
hostnameFqdn() const302 MdnsPublisher::hostnameFqdn() const
303 {
304 return p->mHostnameFqdn;
305 }
306
307 bool
announce(MdnsPublisher::Service * pService)308 MdnsPublisher::announce(MdnsPublisher::Service* pService)
309 {
310 AvahiThreadedPollGuard guard(p->mpThread);
311 bool ok = false;
312 auto i = p->findService(pService);
313 if (i != p->mServices.end()) {
314 ok = true;
315 } else {
316 p->mServices.push_back(ServiceEntry(pService));
317 ok = p->mServices.back().announce(p->mpClient);
318 }
319 return ok;
320 }
321
322 bool
unannounce(MdnsPublisher::Service * pService)323 MdnsPublisher::unannounce(MdnsPublisher::Service* pService)
324 {
325 AvahiThreadedPollGuard guard(p->mpThread);
326 bool ok = false;
327 auto i = p->findService(pService);
328 if (i != p->mServices.end()) {
329 ok = true;
330 i->unannounce();
331 p->mServices.erase(i);
332 }
333 return ok;
334 }
335