/* -*- 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(PR_GetCurrentThread() == gSocketThread); 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(PR_GetCurrentThread() == gSocketThread); 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(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 sts = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID); } } nsresult Init() { MOZ_ASSERT(PR_GetCurrentThread() != gSocketThread); 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(&ServiceWatcher::OnMsgAttach); } void Close() { MOZ_ASSERT(PR_GetCurrentThread() != gSocketThread); if (!gSocketTransportService) { Deallocate(); return; } PostEvent(&ServiceWatcher::OnMsgClose); } private: ~ServiceWatcher() = default; void Deallocate() { if (mService) { DNSServiceRefDeallocate(mService); mService = nullptr; } mOperatorHolder = nullptr; } nsresult PostEvent(void(ServiceWatcher::*func)(void)) { return gSocketTransportService->Dispatch(NewRunnableMethod(this, func), NS_DISPATCH_NORMAL); } void OnMsgClose() { MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); 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(PR_GetCurrentThread() == gSocketThread); 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(PR_GetCurrentThread() == gSocketThread); 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 event = NewRunnableMethod(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 mThread; RefPtr mSts; RefPtr 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 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(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 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 attributes; if (NS_FAILED(rv = mServiceInfo->GetAttributes(getter_AddRefs(attributes)))) { LOG_I("register: no attributes"); } else { nsCOMPtr 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 element; MOZ_ALWAYS_SUCCEEDS(enumerator->GetNext(getter_AddRefs(element))); nsCOMPtr property = do_QueryInterface(element); MOZ_ASSERT(property); nsAutoString name; nsCOMPtr 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(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 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 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(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 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 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 address = new nsNetAddr(&addr); nsCString addressStr; if (NS_WARN_IF(NS_FAILED(address->GetAddress(addressStr)))) { return; } nsCOMPtr 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