diff options
Diffstat (limited to '')
-rw-r--r-- | netwerk/dns/mdns/libmdns/MDNSResponderOperator.cpp | 719 |
1 files changed, 719 insertions, 0 deletions
diff --git a/netwerk/dns/mdns/libmdns/MDNSResponderOperator.cpp b/netwerk/dns/mdns/libmdns/MDNSResponderOperator.cpp new file mode 100644 index 0000000000..ae780b614d --- /dev/null +++ b/netwerk/dns/mdns/libmdns/MDNSResponderOperator.cpp @@ -0,0 +1,719 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "MDNSResponderOperator.h" +#include "MDNSResponderReply.h" +#include "mozilla/EndianUtils.h" +#include "mozilla/Logging.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/Unused.h" +#include "nsComponentManagerUtils.h" +#include "nsCOMPtr.h" +#include "nsDebug.h" +#include "nsDNSServiceInfo.h" +#include "nsHashPropertyBag.h" +#include "nsIProperty.h" +#include "nsISimpleEnumerator.h" +#include "nsIVariant.h" +#include "nsServiceManagerUtils.h" +#include "nsNetAddr.h" +#include "nsNetCID.h" +#include "nsSocketTransportService2.h" +#include "nsThreadUtils.h" +#include "nsXPCOMCID.h" +#include "private/pprio.h" + +#include "nsASocketHandler.h" + +namespace mozilla { +namespace net { + +static LazyLogModule gMDNSLog("MDNSResponderOperator"); +#undef LOG_I +#define LOG_I(...) \ + MOZ_LOG(mozilla::net::gMDNSLog, mozilla::LogLevel::Debug, (__VA_ARGS__)) +#undef LOG_E +#define LOG_E(...) \ + MOZ_LOG(mozilla::net::gMDNSLog, mozilla::LogLevel::Error, (__VA_ARGS__)) + +class MDNSResponderOperator::ServiceWatcher final : public nsASocketHandler { + public: + NS_DECL_THREADSAFE_ISUPPORTS + + // nsASocketHandler methods + virtual void OnSocketReady(PRFileDesc* fd, int16_t outFlags) override { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + MOZ_ASSERT(fd == mFD); + + if (outFlags & (PR_POLL_ERR | PR_POLL_HUP | PR_POLL_NVAL)) { + LOG_E("error polling on listening socket (%p)", fd); + mCondition = NS_ERROR_UNEXPECTED; + } + + if (!(outFlags & PR_POLL_READ)) { + return; + } + + DNSServiceProcessResult(mService); + } + + virtual void OnSocketDetached(PRFileDesc* fd) override { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + MOZ_ASSERT(mThread); + MOZ_ASSERT(fd == mFD); + + if (!mFD) { + return; + } + + // Bug 1175387: do not double close the handle here. + PR_ChangeFileDescNativeHandle(mFD, -1); + PR_Close(mFD); + mFD = nullptr; + + mThread->Dispatch( + NewRunnableMethod("MDNSResponderOperator::ServiceWatcher::Deallocate", + this, &ServiceWatcher::Deallocate), + NS_DISPATCH_NORMAL); + } + + virtual void IsLocal(bool* aIsLocal) override { *aIsLocal = true; } + + virtual void KeepWhenOffline(bool* aKeepWhenOffline) override { + *aKeepWhenOffline = true; + } + + virtual uint64_t ByteCountSent() override { return 0; } + virtual uint64_t ByteCountReceived() override { return 0; } + + explicit ServiceWatcher(DNSServiceRef aService, + MDNSResponderOperator* aOperator) + : mThread(nullptr), + mSts(nullptr), + mOperatorHolder(aOperator), + mService(aService), + mFD(nullptr), + mAttached(false) { + if (!gSocketTransportService) { + nsCOMPtr<nsISocketTransportService> sts = + do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID); + } + } + + nsresult Init() { + MOZ_ASSERT(!OnSocketThread(), "on socket thread"); + mThread = NS_GetCurrentThread(); + + if (!mService) { + return NS_OK; + } + + if (!gSocketTransportService) { + return NS_ERROR_FAILURE; + } + mSts = gSocketTransportService; + + int osfd = DNSServiceRefSockFD(mService); + if (osfd == -1) { + return NS_ERROR_FAILURE; + } + + mFD = PR_ImportFile(osfd); + return PostEvent("MDNSResponderOperator::ServiceWatcher::OnMsgAttach", + &ServiceWatcher::OnMsgAttach); + } + + void Close() { + MOZ_ASSERT(!OnSocketThread(), "on socket thread"); + + if (!gSocketTransportService) { + Deallocate(); + return; + } + + PostEvent("MDNSResponderOperator::ServiceWatcher::OnMsgClose", + &ServiceWatcher::OnMsgClose); + } + + private: + ~ServiceWatcher() = default; + + void Deallocate() { + if (mService) { + DNSServiceRefDeallocate(mService); + mService = nullptr; + } + mOperatorHolder = nullptr; + } + + nsresult PostEvent(const char* aName, void (ServiceWatcher::*func)(void)) { + return gSocketTransportService->Dispatch( + NewRunnableMethod(aName, this, func), NS_DISPATCH_NORMAL); + } + + void OnMsgClose() { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + + if (NS_FAILED(mCondition)) { + return; + } + + // tear down socket. this signals the STS to detach our socket handler. + mCondition = NS_BINDING_ABORTED; + + // if we are attached, then socket transport service will call our + // OnSocketDetached method automatically. Otherwise, we have to call it + // (and thus close the socket) manually. + if (!mAttached) { + OnSocketDetached(mFD); + } + } + + void OnMsgAttach() { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + + if (NS_FAILED(mCondition)) { + return; + } + + mCondition = TryAttach(); + + // if we hit an error while trying to attach then bail... + if (NS_FAILED(mCondition)) { + NS_ASSERTION(!mAttached, "should not be attached already"); + OnSocketDetached(mFD); + } + } + + nsresult TryAttach() { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + + nsresult rv; + + if (!gSocketTransportService) { + return NS_ERROR_FAILURE; + } + + // + // find out if it is going to be ok to attach another socket to the STS. + // if not then we have to wait for the STS to tell us that it is ok. + // the notification is asynchronous, which means that when we could be + // in a race to call AttachSocket once notified. for this reason, when + // we get notified, we just re-enter this function. as a result, we are + // sure to ask again before calling AttachSocket. in this way we deal + // with the race condition. though it isn't the most elegant solution, + // it is far simpler than trying to build a system that would guarantee + // FIFO ordering (which wouldn't even be that valuable IMO). see bug + // 194402 for more info. + // + if (!gSocketTransportService->CanAttachSocket()) { + nsCOMPtr<nsIRunnable> event = NewRunnableMethod( + "MDNSResponderOperator::ServiceWatcher::OnMsgAttach", this, + &ServiceWatcher::OnMsgAttach); + + nsresult rv = gSocketTransportService->NotifyWhenCanAttachSocket(event); + if (NS_FAILED(rv)) { + return rv; + } + } + + // + // ok, we can now attach our socket to the STS for polling + // + rv = gSocketTransportService->AttachSocket(mFD, this); + if (NS_FAILED(rv)) { + return rv; + } + + mAttached = true; + + // + // now, configure our poll flags for listening... + // + mPollFlags = (PR_POLL_READ | PR_POLL_EXCEPT); + + return NS_OK; + } + + nsCOMPtr<nsIThread> mThread; + RefPtr<nsSocketTransportService> mSts; + RefPtr<MDNSResponderOperator> mOperatorHolder; + DNSServiceRef mService; + PRFileDesc* mFD; + bool mAttached; +}; + +NS_IMPL_ISUPPORTS(MDNSResponderOperator::ServiceWatcher, nsISupports) + +MDNSResponderOperator::MDNSResponderOperator() + : mService(nullptr), + mWatcher(nullptr), + mThread(NS_GetCurrentThread()), + mIsCancelled(false) {} + +MDNSResponderOperator::~MDNSResponderOperator() { Stop(); } + +nsresult MDNSResponderOperator::Start() { + if (mIsCancelled) { + return NS_OK; + } + + if (IsServing()) { + Stop(); + } + + return NS_OK; +} + +nsresult MDNSResponderOperator::Stop() { return ResetService(nullptr); } + +nsresult MDNSResponderOperator::ResetService(DNSServiceRef aService) { + nsresult rv; + + if (aService != mService) { + if (mWatcher) { + mWatcher->Close(); + mWatcher = nullptr; + } + + if (aService) { + RefPtr<ServiceWatcher> watcher = new ServiceWatcher(aService, this); + if (NS_WARN_IF(NS_FAILED(rv = watcher->Init()))) { + return rv; + } + mWatcher = watcher; + } + + mService = aService; + } + return NS_OK; +} + +BrowseOperator::BrowseOperator(const nsACString& aServiceType, + nsIDNSServiceDiscoveryListener* aListener) + : MDNSResponderOperator(), + mServiceType(aServiceType), + mListener(aListener) {} + +nsresult BrowseOperator::Start() { + nsresult rv; + if (NS_WARN_IF(NS_FAILED(rv = MDNSResponderOperator::Start()))) { + return rv; + } + + DNSServiceRef service = nullptr; + DNSServiceErrorType err = DNSServiceBrowse( + &service, 0, kDNSServiceInterfaceIndexAny, mServiceType.get(), nullptr, + &BrowseReplyRunnable::Reply, this); + NS_WARNING_ASSERTION(kDNSServiceErr_NoError == err, "DNSServiceBrowse fail"); + + if (mListener) { + if (kDNSServiceErr_NoError == err) { + mListener->OnDiscoveryStarted(mServiceType); + } else { + mListener->OnStartDiscoveryFailed(mServiceType, err); + } + } + + if (NS_WARN_IF(kDNSServiceErr_NoError != err)) { + return NS_ERROR_FAILURE; + } + + return ResetService(service); +} + +nsresult BrowseOperator::Stop() { + bool isServing = IsServing(); + nsresult rv = MDNSResponderOperator::Stop(); + + if (isServing && mListener) { + if (NS_SUCCEEDED(rv)) { + mListener->OnDiscoveryStopped(mServiceType); + } else { + mListener->OnStopDiscoveryFailed(mServiceType, static_cast<uint32_t>(rv)); + } + } + + return rv; +} + +void BrowseOperator::Reply(DNSServiceRef aSdRef, DNSServiceFlags aFlags, + uint32_t aInterfaceIndex, + DNSServiceErrorType aErrorCode, + const nsACString& aServiceName, + const nsACString& aRegType, + const nsACString& aReplyDomain) { + MOZ_ASSERT(GetThread() == NS_GetCurrentThread()); + + if (NS_WARN_IF(kDNSServiceErr_NoError != aErrorCode)) { + LOG_E("BrowseOperator::Reply (%d)", aErrorCode); + if (mListener) { + mListener->OnStartDiscoveryFailed(mServiceType, aErrorCode); + } + return; + } + + if (!mListener) { + return; + } + nsCOMPtr<nsIDNSServiceInfo> info = new nsDNSServiceInfo(); + + if (NS_WARN_IF(!info)) { + return; + } + if (NS_WARN_IF(NS_FAILED(info->SetServiceName(aServiceName)))) { + return; + } + if (NS_WARN_IF(NS_FAILED(info->SetServiceType(aRegType)))) { + return; + } + if (NS_WARN_IF(NS_FAILED(info->SetDomainName(aReplyDomain)))) { + return; + } + + if (aFlags & kDNSServiceFlagsAdd) { + mListener->OnServiceFound(info); + } else { + mListener->OnServiceLost(info); + } +} + +RegisterOperator::RegisterOperator(nsIDNSServiceInfo* aServiceInfo, + nsIDNSRegistrationListener* aListener) + : MDNSResponderOperator(), + mServiceInfo(aServiceInfo), + mListener(aListener) {} + +nsresult RegisterOperator::Start() { + nsresult rv; + + rv = MDNSResponderOperator::Start(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + uint16_t port; + if (NS_WARN_IF(NS_FAILED(rv = mServiceInfo->GetPort(&port)))) { + return rv; + } + nsAutoCString type; + if (NS_WARN_IF(NS_FAILED(rv = mServiceInfo->GetServiceType(type)))) { + return rv; + } + + TXTRecordRef txtRecord; + char buf[TXT_BUFFER_SIZE] = {0}; + TXTRecordCreate(&txtRecord, TXT_BUFFER_SIZE, buf); + + nsCOMPtr<nsIPropertyBag2> attributes; + if (NS_FAILED(rv = mServiceInfo->GetAttributes(getter_AddRefs(attributes)))) { + LOG_I("register: no attributes"); + } else { + nsCOMPtr<nsISimpleEnumerator> enumerator; + if (NS_WARN_IF(NS_FAILED( + rv = attributes->GetEnumerator(getter_AddRefs(enumerator))))) { + return rv; + } + + bool hasMoreElements; + while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMoreElements)) && + hasMoreElements) { + nsCOMPtr<nsISupports> element; + MOZ_ALWAYS_SUCCEEDS(enumerator->GetNext(getter_AddRefs(element))); + nsCOMPtr<nsIProperty> property = do_QueryInterface(element); + MOZ_ASSERT(property); + + nsAutoString name; + nsCOMPtr<nsIVariant> value; + MOZ_ALWAYS_SUCCEEDS(property->GetName(name)); + MOZ_ALWAYS_SUCCEEDS(property->GetValue(getter_AddRefs(value))); + + nsAutoCString str; + if (NS_WARN_IF(NS_FAILED(value->GetAsACString(str)))) { + continue; + } + + TXTRecordSetValue(&txtRecord, + /* it's safe because key name is ASCII only. */ + NS_LossyConvertUTF16toASCII(name).get(), str.Length(), + str.get()); + } + } + + nsAutoCString host; + nsAutoCString name; + nsAutoCString domain; + + DNSServiceRef service = nullptr; + DNSServiceErrorType err = DNSServiceRegister( + &service, 0, 0, + NS_SUCCEEDED(mServiceInfo->GetServiceName(name)) ? name.get() : nullptr, + type.get(), + NS_SUCCEEDED(mServiceInfo->GetDomainName(domain)) ? domain.get() + : nullptr, + NS_SUCCEEDED(mServiceInfo->GetHost(host)) ? host.get() : nullptr, + NativeEndian::swapToNetworkOrder(port), TXTRecordGetLength(&txtRecord), + TXTRecordGetBytesPtr(&txtRecord), &RegisterReplyRunnable::Reply, this); + NS_WARNING_ASSERTION(kDNSServiceErr_NoError == err, + "DNSServiceRegister fail"); + + TXTRecordDeallocate(&txtRecord); + + if (NS_WARN_IF(kDNSServiceErr_NoError != err)) { + if (mListener) { + mListener->OnRegistrationFailed(mServiceInfo, err); + } + return NS_ERROR_FAILURE; + } + + return ResetService(service); +} + +nsresult RegisterOperator::Stop() { + bool isServing = IsServing(); + nsresult rv = MDNSResponderOperator::Stop(); + + if (isServing && mListener) { + if (NS_SUCCEEDED(rv)) { + mListener->OnServiceUnregistered(mServiceInfo); + } else { + mListener->OnUnregistrationFailed(mServiceInfo, + static_cast<uint32_t>(rv)); + } + } + + return rv; +} + +void RegisterOperator::Reply(DNSServiceRef aSdRef, DNSServiceFlags aFlags, + DNSServiceErrorType aErrorCode, + const nsACString& aName, + const nsACString& aRegType, + const nsACString& aDomain) { + MOZ_ASSERT(GetThread() == NS_GetCurrentThread()); + + if (kDNSServiceErr_NoError != aErrorCode) { + LOG_E("RegisterOperator::Reply (%d)", aErrorCode); + } + + if (!mListener) { + return; + } + nsCOMPtr<nsIDNSServiceInfo> info = new nsDNSServiceInfo(mServiceInfo); + if (NS_WARN_IF(NS_FAILED(info->SetServiceName(aName)))) { + return; + } + if (NS_WARN_IF(NS_FAILED(info->SetServiceType(aRegType)))) { + return; + } + if (NS_WARN_IF(NS_FAILED(info->SetDomainName(aDomain)))) { + return; + } + + if (kDNSServiceErr_NoError == aErrorCode) { + if (aFlags & kDNSServiceFlagsAdd) { + mListener->OnServiceRegistered(info); + } else { + // If a successfully-registered name later suffers a name conflict + // or similar problem and has to be deregistered, the callback will + // be invoked with the kDNSServiceFlagsAdd flag not set. + LOG_E("RegisterOperator::Reply: deregister"); + if (NS_WARN_IF(NS_FAILED(Stop()))) { + return; + } + } + } else { + mListener->OnRegistrationFailed(info, aErrorCode); + } +} + +ResolveOperator::ResolveOperator(nsIDNSServiceInfo* aServiceInfo, + nsIDNSServiceResolveListener* aListener) + : MDNSResponderOperator(), + mServiceInfo(aServiceInfo), + mListener(aListener) {} + +nsresult ResolveOperator::Start() { + nsresult rv; + if (NS_WARN_IF(NS_FAILED(rv = MDNSResponderOperator::Start()))) { + return rv; + } + + nsAutoCString name; + mServiceInfo->GetServiceName(name); + nsAutoCString type; + mServiceInfo->GetServiceType(type); + nsAutoCString domain; + mServiceInfo->GetDomainName(domain); + + LOG_I("Resolve: (%s), (%s), (%s)", name.get(), type.get(), domain.get()); + + DNSServiceRef service = nullptr; + DNSServiceErrorType err = DNSServiceResolve( + &service, 0, kDNSServiceInterfaceIndexAny, name.get(), type.get(), + domain.get(), (DNSServiceResolveReply)&ResolveReplyRunnable::Reply, this); + + if (NS_WARN_IF(kDNSServiceErr_NoError != err)) { + if (mListener) { + mListener->OnResolveFailed(mServiceInfo, err); + } + return NS_ERROR_FAILURE; + } + + return ResetService(service); +} + +void ResolveOperator::Reply(DNSServiceRef aSdRef, DNSServiceFlags aFlags, + uint32_t aInterfaceIndex, + DNSServiceErrorType aErrorCode, + const nsACString& aFullName, + const nsACString& aHostTarget, uint16_t aPort, + uint16_t aTxtLen, const unsigned char* aTxtRecord) { + MOZ_ASSERT(GetThread() == NS_GetCurrentThread()); + + auto guard = MakeScopeExit([&] { Unused << NS_WARN_IF(NS_FAILED(Stop())); }); + + if (NS_WARN_IF(kDNSServiceErr_NoError != aErrorCode)) { + LOG_E("ResolveOperator::Reply (%d)", aErrorCode); + return; + } + + // Resolve TXT record + int count = TXTRecordGetCount(aTxtLen, aTxtRecord); + LOG_I("resolve: txt count = %d, len = %d", count, aTxtLen); + nsCOMPtr<nsIWritablePropertyBag2> attributes = new nsHashPropertyBag(); + if (NS_WARN_IF(!attributes)) { + return; + } + if (count) { + for (int i = 0; i < count; ++i) { + char key[TXT_BUFFER_SIZE] = {'\0'}; + uint8_t vSize = 0; + const void* value = nullptr; + if (kDNSServiceErr_NoError != + TXTRecordGetItemAtIndex(aTxtLen, aTxtRecord, i, TXT_BUFFER_SIZE, key, + &vSize, &value)) { + break; + } + + nsAutoCString str(reinterpret_cast<const char*>(value), vSize); + LOG_I("resolve TXT: (%d) %s=%s", vSize, key, str.get()); + + if (NS_WARN_IF(NS_FAILED(attributes->SetPropertyAsACString( + /* it's safe to convert because key name is ASCII only. */ + NS_ConvertASCIItoUTF16(key), str)))) { + break; + } + } + } + + if (!mListener) { + return; + } + nsCOMPtr<nsIDNSServiceInfo> info = new nsDNSServiceInfo(mServiceInfo); + if (NS_WARN_IF(NS_FAILED(info->SetHost(aHostTarget)))) { + return; + } + if (NS_WARN_IF(NS_FAILED(info->SetPort(aPort)))) { + return; + } + if (NS_WARN_IF(NS_FAILED(info->SetAttributes(attributes)))) { + return; + } + + if (kDNSServiceErr_NoError == aErrorCode) { + GetAddrInfor(info); + } else { + mListener->OnResolveFailed(info, aErrorCode); + Unused << NS_WARN_IF(NS_FAILED(Stop())); + } +} + +void ResolveOperator::GetAddrInfor(nsIDNSServiceInfo* aServiceInfo) { + RefPtr<GetAddrInfoOperator> getAddreOp = + new GetAddrInfoOperator(aServiceInfo, mListener); + Unused << NS_WARN_IF(NS_FAILED(getAddreOp->Start())); +} + +GetAddrInfoOperator::GetAddrInfoOperator( + nsIDNSServiceInfo* aServiceInfo, nsIDNSServiceResolveListener* aListener) + : MDNSResponderOperator(), + mServiceInfo(aServiceInfo), + mListener(aListener) {} + +nsresult GetAddrInfoOperator::Start() { + nsresult rv; + if (NS_WARN_IF(NS_FAILED(rv = MDNSResponderOperator::Start()))) { + return rv; + } + + nsAutoCString host; + mServiceInfo->GetHost(host); + + LOG_I("GetAddrInfo: (%s)", host.get()); + + DNSServiceRef service = nullptr; + DNSServiceErrorType err = DNSServiceGetAddrInfo( + &service, kDNSServiceFlagsForceMulticast, kDNSServiceInterfaceIndexAny, + kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6, host.get(), + (DNSServiceGetAddrInfoReply)&GetAddrInfoReplyRunnable::Reply, this); + + if (NS_WARN_IF(kDNSServiceErr_NoError != err)) { + if (mListener) { + mListener->OnResolveFailed(mServiceInfo, err); + } + return NS_ERROR_FAILURE; + } + + return ResetService(service); +} + +void GetAddrInfoOperator::Reply(DNSServiceRef aSdRef, DNSServiceFlags aFlags, + uint32_t aInterfaceIndex, + DNSServiceErrorType aErrorCode, + const nsACString& aHostName, + const NetAddr& aAddress, uint32_t aTTL) { + MOZ_ASSERT(GetThread() == NS_GetCurrentThread()); + + auto guard = MakeScopeExit([&] { Unused << NS_WARN_IF(NS_FAILED(Stop())); }); + + if (NS_WARN_IF(kDNSServiceErr_NoError != aErrorCode)) { + LOG_E("GetAddrInfoOperator::Reply (%d)", aErrorCode); + return; + } + + if (!mListener) { + return; + } + + NetAddr addr = aAddress; + nsCOMPtr<nsINetAddr> address = new nsNetAddr(&addr); + nsCString addressStr; + if (NS_WARN_IF(NS_FAILED(address->GetAddress(addressStr)))) { + return; + } + + nsCOMPtr<nsIDNSServiceInfo> info = new nsDNSServiceInfo(mServiceInfo); + if (NS_WARN_IF(NS_FAILED(info->SetAddress(addressStr)))) { + return; + } + + /** + * |kDNSServiceFlagsMoreComing| means this callback will be one or more + * callback events later, so this instance should be kept alive until all + * follow-up events are processed. + */ + if (aFlags & kDNSServiceFlagsMoreComing) { + guard.release(); + } + + if (kDNSServiceErr_NoError == aErrorCode) { + mListener->OnServiceResolved(info); + } else { + mListener->OnResolveFailed(info, aErrorCode); + } +} + +} // namespace net +} // namespace mozilla |