1 /*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9 #include "ZeroconfMDNS.h"
10 #include <arpa/inet.h>
11
12 #include <string>
13 #include <sstream>
14 #include "threads/SingleLock.h"
15 #include "utils/log.h"
16 #include "dialogs/GUIDialogKaiToast.h"
17 #include "guilib/LocalizeStrings.h"
18 #if defined(TARGET_WINDOWS)
19 #include "platform/win32/WIN32Util.h"
20 #endif //TARGET_WINDOWS
21
22 #if defined(HAS_MDNS_EMBEDDED)
23 #include <mDnsEmbedded.h>
24 #endif //HAS_MDNS_EMBEDDED
25
26 extern HWND g_hWnd;
27
Process()28 void CZeroconfMDNS::Process()
29 {
30 #if defined(HAS_MDNS_EMBEDDED)
31 CLog::Log(LOGDEBUG, "ZeroconfEmbedded - processing...");
32 struct timeval timeout;
33 timeout.tv_sec = 1;
34 while (( !m_bStop ))
35 embedded_mDNSmainLoop(timeout);
36 #endif //HAS_MDNS_EMBEDDED
37
38 }
39
40
CZeroconfMDNS()41 CZeroconfMDNS::CZeroconfMDNS() : CThread("ZeroconfEmbedded")
42 {
43 m_service = NULL;
44 #if defined(HAS_MDNS_EMBEDDED)
45 embedded_mDNSInit();
46 Create();
47 #endif //HAS_MDNS_EMBEDDED
48 }
49
~CZeroconfMDNS()50 CZeroconfMDNS::~CZeroconfMDNS()
51 {
52 doStop();
53 #if defined(HAS_MDNS_EMBEDDED)
54 StopThread();
55 embedded_mDNSExit();
56 #endif //HAS_MDNS_EMBEDDED
57 }
58
IsZCdaemonRunning()59 bool CZeroconfMDNS::IsZCdaemonRunning()
60 {
61 #if !defined(HAS_MDNS_EMBEDDED)
62 uint32_t version;
63 uint32_t size = sizeof(version);
64 DNSServiceErrorType err = DNSServiceGetProperty(kDNSServiceProperty_DaemonVersion, &version, &size);
65 if(err != kDNSServiceErr_NoError)
66 {
67 CLog::Log(LOGERROR, "ZeroconfMDNS: Zeroconf can't be started probably because Apple's Bonjour Service isn't installed. You can get it by either installing Itunes or Apple's Bonjour Print Service for Windows (http://support.apple.com/kb/DL999)");
68 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error, g_localizeStrings.Get(34300), g_localizeStrings.Get(34301), 10000, true);
69 return false;
70 }
71 CLog::Log(LOGDEBUG, "ZeroconfMDNS:Bonjour version is %d.%d", version / 10000, version / 100 % 100);
72 #endif //!HAS_MDNS_EMBEDDED
73 return true;
74 }
75
76 //methods to implement for concrete implementations
doPublishService(const std::string & fcr_identifier,const std::string & fcr_type,const std::string & fcr_name,unsigned int f_port,const std::vector<std::pair<std::string,std::string>> & txt)77 bool CZeroconfMDNS::doPublishService(const std::string& fcr_identifier,
78 const std::string& fcr_type,
79 const std::string& fcr_name,
80 unsigned int f_port,
81 const std::vector<std::pair<std::string, std::string> >& txt)
82 {
83 DNSServiceRef netService = NULL;
84 TXTRecordRef txtRecord;
85 DNSServiceErrorType err;
86 TXTRecordCreate(&txtRecord, 0, NULL);
87
88 #if !defined(HAS_MDNS_EMBEDDED)
89 CSingleLock lock(m_data_guard);
90 if(m_service == NULL)
91 {
92 err = DNSServiceCreateConnection(&m_service);
93 if (err != kDNSServiceErr_NoError)
94 {
95 CLog::Log(LOGERROR, "ZeroconfMDNS: DNSServiceCreateConnection failed with error = %ld", (int) err);
96 return false;
97 }
98 #ifdef TARGET_WINDOWS_STORE
99 CLog::Log(LOGERROR, "ZeroconfMDNS: WSAAsyncSelect not yet supported for TARGET_WINDOWS_STORE");
100 #else
101 err = WSAAsyncSelect( (SOCKET) DNSServiceRefSockFD( m_service ), g_hWnd, BONJOUR_EVENT, FD_READ | FD_CLOSE );
102 if (err != kDNSServiceErr_NoError)
103 CLog::Log(LOGERROR, "ZeroconfMDNS: WSAAsyncSelect failed with error = %ld", (int) err);
104 #endif
105 }
106 #endif //!HAS_MDNS_EMBEDDED
107
108 CLog::Log(LOGDEBUG, "ZeroconfMDNS: identifier: %s type: %s name:%s port:%i", fcr_identifier.c_str(), fcr_type.c_str(), fcr_name.c_str(), f_port);
109
110 //add txt records
111 if(!txt.empty())
112 {
113 for (const auto& it : txt)
114 {
115 CLog::Log(LOGDEBUG, "ZeroconfMDNS: key:%s, value:%s", it.first.c_str(), it.second.c_str());
116 uint8_t txtLen = (uint8_t)strlen(it.second.c_str());
117 TXTRecordSetValue(&txtRecord, it.first.c_str(), txtLen, it.second.c_str());
118 }
119 }
120
121 {
122 CSingleLock lock(m_data_guard);
123 netService = m_service;
124 err = DNSServiceRegister(&netService, kDNSServiceFlagsShareConnection, 0, fcr_name.c_str(), fcr_type.c_str(), NULL, NULL, htons(f_port), TXTRecordGetLength(&txtRecord), TXTRecordGetBytesPtr(&txtRecord), registerCallback, NULL);
125 }
126
127 if (err != kDNSServiceErr_NoError)
128 {
129 // Something went wrong so lets clean up.
130 if (netService)
131 DNSServiceRefDeallocate(netService);
132
133 CLog::Log(LOGERROR, "ZeroconfMDNS: DNSServiceRegister returned (error = %ld)", (int) err);
134 }
135 else
136 {
137 CSingleLock lock(m_data_guard);
138 struct tServiceRef newService;
139 newService.serviceRef = netService;
140 newService.txtRecordRef = txtRecord;
141 newService.updateNumber = 0;
142 m_services.insert(make_pair(fcr_identifier, newService));
143 }
144
145 return err == kDNSServiceErr_NoError;
146 }
147
doForceReAnnounceService(const std::string & fcr_identifier)148 bool CZeroconfMDNS::doForceReAnnounceService(const std::string& fcr_identifier)
149 {
150 bool ret = false;
151 CSingleLock lock(m_data_guard);
152 tServiceMap::iterator it = m_services.find(fcr_identifier);
153 if(it != m_services.end())
154 {
155 // for force announcing a service with mdns we need
156 // to change a txt record - so we diddle between
157 // even and odd dummy records here
158 if ( (it->second.updateNumber % 2) == 0)
159 TXTRecordSetValue(&it->second.txtRecordRef, "xbmcdummy", strlen("evendummy"), "evendummy");
160 else
161 TXTRecordSetValue(&it->second.txtRecordRef, "xbmcdummy", strlen("odddummy"), "odddummy");
162 it->second.updateNumber++;
163
164 if (DNSServiceUpdateRecord(it->second.serviceRef, NULL, 0, TXTRecordGetLength(&it->second.txtRecordRef), TXTRecordGetBytesPtr(&it->second.txtRecordRef), 0) == kDNSServiceErr_NoError)
165 ret = true;
166 }
167 return ret;
168 }
169
doRemoveService(const std::string & fcr_ident)170 bool CZeroconfMDNS::doRemoveService(const std::string& fcr_ident)
171 {
172 CSingleLock lock(m_data_guard);
173 tServiceMap::iterator it = m_services.find(fcr_ident);
174 if(it != m_services.end())
175 {
176 DNSServiceRefDeallocate(it->second.serviceRef);
177 TXTRecordDeallocate(&it->second.txtRecordRef);
178 m_services.erase(it);
179 CLog::Log(LOGDEBUG, "ZeroconfMDNS: Removed service %s", fcr_ident.c_str());
180 return true;
181 }
182 else
183 return false;
184 }
185
doStop()186 void CZeroconfMDNS::doStop()
187 {
188 {
189 CSingleLock lock(m_data_guard);
190 CLog::Log(LOGDEBUG, "ZeroconfMDNS: Shutdown services");
191 for (auto& it : m_services)
192 {
193 DNSServiceRefDeallocate(it.second.serviceRef);
194 TXTRecordDeallocate(&it.second.txtRecordRef);
195 CLog::Log(LOGDEBUG, "ZeroconfMDNS: Removed service %s", it.first.c_str());
196 }
197 m_services.clear();
198 }
199 {
200 CSingleLock lock(m_data_guard);
201 #if defined(TARGET_WINDOWS_STORE)
202 CLog::Log(LOGERROR, "ZeroconfMDNS: WSAAsyncSelect not yet supported for TARGET_WINDOWS_STORE");
203 #else
204 WSAAsyncSelect( (SOCKET) DNSServiceRefSockFD( m_service ), g_hWnd, BONJOUR_EVENT, 0 );
205 #endif //TARGET_WINDOWS
206
207 if (m_service)
208 DNSServiceRefDeallocate(m_service);
209 m_service = NULL;
210 }
211 }
212
registerCallback(DNSServiceRef sdref,const DNSServiceFlags flags,DNSServiceErrorType errorCode,const char * name,const char * regtype,const char * domain,void * context)213 void DNSSD_API CZeroconfMDNS::registerCallback(DNSServiceRef sdref, const DNSServiceFlags flags, DNSServiceErrorType errorCode, const char *name, const char *regtype, const char *domain, void *context)
214 {
215 (void)sdref; // Unused
216 (void)flags; // Unused
217 (void)context; // Unused
218
219 if (errorCode == kDNSServiceErr_NoError)
220 {
221 if (flags & kDNSServiceFlagsAdd)
222 CLog::Log(LOGDEBUG, "ZeroconfMDNS: %s.%s%s now registered and active", name, regtype, domain);
223 else
224 CLog::Log(LOGDEBUG, "ZeroconfMDNS: %s.%s%s registration removed", name, regtype, domain);
225 }
226 else if (errorCode == kDNSServiceErr_NameConflict)
227 CLog::Log(LOGDEBUG, "ZeroconfMDNS: %s.%s%s Name in use, please choose another", name, regtype, domain);
228 else
229 CLog::Log(LOGDEBUG, "ZeroconfMDNS: %s.%s%s error code %d", name, regtype, domain, errorCode);
230 }
231
ProcessResults()232 void CZeroconfMDNS::ProcessResults()
233 {
234 CSingleLock lock(m_data_guard);
235 DNSServiceErrorType err = DNSServiceProcessResult(m_service);
236 if (err != kDNSServiceErr_NoError)
237 CLog::Log(LOGERROR, "ZeroconfMDNS: DNSServiceProcessResult returned (error = %ld)", (int) err);
238 }
239
240