diff options
Diffstat (limited to 'netwerk/dns/mdns')
22 files changed, 4580 insertions, 0 deletions
diff --git a/netwerk/dns/mdns/libmdns/MDNSResponderOperator.cpp b/netwerk/dns/mdns/libmdns/MDNSResponderOperator.cpp new file mode 100644 index 000000000..72b557774 --- /dev/null +++ b/netwerk/dns/mdns/libmdns/MDNSResponderOperator.cpp @@ -0,0 +1,779 @@ +/* -*- 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<nsISocketTransportService> 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<nsIRunnable> 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<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 diff --git a/netwerk/dns/mdns/libmdns/MDNSResponderOperator.h b/netwerk/dns/mdns/libmdns/MDNSResponderOperator.h new file mode 100644 index 000000000..a932baa7c --- /dev/null +++ b/netwerk/dns/mdns/libmdns/MDNSResponderOperator.h @@ -0,0 +1,152 @@ +/* -*- 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/. */ + +#ifndef mozilla_netwerk_dns_mdns_libmdns_MDNSResponderOperator_h +#define mozilla_netwerk_dns_mdns_libmdns_MDNSResponderOperator_h + +#include "dns_sd.h" +#include "mozilla/Atomics.h" +#include "mozilla/RefPtr.h" +#include "nsCOMPtr.h" +#include "nsIDNSServiceDiscovery.h" +#include "nsIThread.h" +#include "nsString.h" + +namespace mozilla { +namespace net { + +class MDNSResponderOperator +{ + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MDNSResponderOperator) + +public: + MDNSResponderOperator(); + + virtual nsresult Start(); + virtual nsresult Stop(); + void Cancel() { mIsCancelled = true; } + nsIThread* GetThread() const { return mThread; } + +protected: + virtual ~MDNSResponderOperator(); + + bool IsServing() const { return mService; } + nsresult ResetService(DNSServiceRef aService); + +private: + class ServiceWatcher; + + DNSServiceRef mService; + RefPtr<ServiceWatcher> mWatcher; + nsCOMPtr<nsIThread> mThread; // remember caller thread for callback + Atomic<bool> mIsCancelled; +}; + +class BrowseOperator final : public MDNSResponderOperator +{ +public: + BrowseOperator(const nsACString& aServiceType, + nsIDNSServiceDiscoveryListener* aListener); + + nsresult Start() override; + nsresult Stop() override; + + void Reply(DNSServiceRef aSdRef, + DNSServiceFlags aFlags, + uint32_t aInterfaceIndex, + DNSServiceErrorType aErrorCode, + const nsACString& aServiceName, + const nsACString& aRegType, + const nsACString& aReplyDomain); + +private: + ~BrowseOperator() = default; + + nsCString mServiceType; + nsCOMPtr<nsIDNSServiceDiscoveryListener> mListener; +}; + +class RegisterOperator final : public MDNSResponderOperator +{ + enum { TXT_BUFFER_SIZE = 256 }; + +public: + RegisterOperator(nsIDNSServiceInfo* aServiceInfo, + nsIDNSRegistrationListener* aListener); + + nsresult Start() override; + nsresult Stop() override; + + void Reply(DNSServiceRef aSdRef, + DNSServiceFlags aFlags, + DNSServiceErrorType aErrorCode, + const nsACString& aName, + const nsACString& aRegType, + const nsACString& aDomain); + +private: + ~RegisterOperator() = default; + + nsCOMPtr<nsIDNSServiceInfo> mServiceInfo; + nsCOMPtr<nsIDNSRegistrationListener> mListener; +}; + +class ResolveOperator final : public MDNSResponderOperator +{ + enum { TXT_BUFFER_SIZE = 256 }; + +public: + ResolveOperator(nsIDNSServiceInfo* aServiceInfo, + nsIDNSServiceResolveListener* aListener); + + nsresult Start() override; + + void 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); + +private: + ~ResolveOperator() = default; + void GetAddrInfor(nsIDNSServiceInfo* aServiceInfo); + + nsCOMPtr<nsIDNSServiceInfo> mServiceInfo; + nsCOMPtr<nsIDNSServiceResolveListener> mListener; +}; + +union NetAddr; + +class GetAddrInfoOperator final : public MDNSResponderOperator +{ +public: + GetAddrInfoOperator(nsIDNSServiceInfo* aServiceInfo, + nsIDNSServiceResolveListener* aListener); + + nsresult Start() override; + + void Reply(DNSServiceRef aSdRef, + DNSServiceFlags aFlags, + uint32_t aInterfaceIndex, + DNSServiceErrorType aErrorCode, + const nsACString& aHostName, + const NetAddr& aAddress, + uint32_t aTTL); + +private: + ~GetAddrInfoOperator() = default; + + nsCOMPtr<nsIDNSServiceInfo> mServiceInfo; + nsCOMPtr<nsIDNSServiceResolveListener> mListener; +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_netwerk_dns_mdns_libmdns_MDNSResponderOperator_h diff --git a/netwerk/dns/mdns/libmdns/MDNSResponderReply.cpp b/netwerk/dns/mdns/libmdns/MDNSResponderReply.cpp new file mode 100644 index 000000000..7aa5b3759 --- /dev/null +++ b/netwerk/dns/mdns/libmdns/MDNSResponderReply.cpp @@ -0,0 +1,302 @@ +/* -*- 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 "MDNSResponderReply.h" +#include "mozilla/EndianUtils.h" +#include "private/pprio.h" + +namespace mozilla { +namespace net { + +BrowseReplyRunnable::BrowseReplyRunnable(DNSServiceRef aSdRef, + DNSServiceFlags aFlags, + uint32_t aInterfaceIndex, + DNSServiceErrorType aErrorCode, + const nsACString& aServiceName, + const nsACString& aRegType, + const nsACString& aReplyDomain, + BrowseOperator* aContext) + : mSdRef(aSdRef) + , mFlags(aFlags) + , mInterfaceIndex(aInterfaceIndex) + , mErrorCode(aErrorCode) + , mServiceName(aServiceName) + , mRegType(aRegType) + , mReplyDomain(aReplyDomain) + , mContext(aContext) +{ +} + +NS_IMETHODIMP +BrowseReplyRunnable::Run() +{ + MOZ_ASSERT(mContext); + mContext->Reply(mSdRef, + mFlags, + mInterfaceIndex, + mErrorCode, + mServiceName, + mRegType, + mReplyDomain); + return NS_OK; +} + +void +BrowseReplyRunnable::Reply(DNSServiceRef aSdRef, + DNSServiceFlags aFlags, + uint32_t aInterfaceIndex, + DNSServiceErrorType aErrorCode, + const char* aServiceName, + const char* aRegType, + const char* aReplyDomain, + void* aContext) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + + BrowseOperator* obj(reinterpret_cast<BrowseOperator*>(aContext)); + if (!obj) { + return; + } + + nsCOMPtr<nsIThread> thread(obj->GetThread()); + if (!thread) { + return; + } + + thread->Dispatch(new BrowseReplyRunnable(aSdRef, + aFlags, + aInterfaceIndex, + aErrorCode, + nsCString(aServiceName), + nsCString(aRegType), + nsCString(aReplyDomain), + obj), + NS_DISPATCH_NORMAL); +} + +RegisterReplyRunnable::RegisterReplyRunnable(DNSServiceRef aSdRef, + DNSServiceFlags aFlags, + DNSServiceErrorType aErrorCode, + const nsACString& aName, + const nsACString& aRegType, + const nsACString& domain, + RegisterOperator* aContext) + : mSdRef(aSdRef) + , mFlags(aFlags) + , mErrorCode(aErrorCode) + , mName(aName) + , mRegType(aRegType) + , mDomain(domain) + , mContext(aContext) +{ +} + +NS_IMETHODIMP +RegisterReplyRunnable::Run() +{ + MOZ_ASSERT(mContext); + + mContext->Reply(mSdRef, + mFlags, + mErrorCode, + mName, + mRegType, + mDomain); + return NS_OK; +} + +void +RegisterReplyRunnable::Reply(DNSServiceRef aSdRef, + DNSServiceFlags aFlags, + DNSServiceErrorType aErrorCode, + const char* aName, + const char* aRegType, + const char* domain, + void* aContext) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + + RegisterOperator* obj(reinterpret_cast<RegisterOperator*>(aContext)); + if (!obj) { + return; + } + + nsCOMPtr<nsIThread> thread(obj->GetThread()); + if (!thread) { + return; + } + + thread->Dispatch(new RegisterReplyRunnable(aSdRef, + aFlags, + aErrorCode, + nsCString(aName), + nsCString(aRegType), + nsCString(domain), + obj), + NS_DISPATCH_NORMAL); +} + +ResolveReplyRunnable::ResolveReplyRunnable(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, + ResolveOperator* aContext) + : mSdRef(aSdRef) + , mFlags(aFlags) + , mInterfaceIndex(aInterfaceIndex) + , mErrorCode(aErrorCode) + , mFullname(aFullName) + , mHosttarget(aHostTarget) + , mPort(aPort) + , mTxtLen(aTxtLen) + , mTxtRecord(new unsigned char[aTxtLen]) + , mContext(aContext) +{ + if (mTxtRecord) { + memcpy(mTxtRecord.get(), aTxtRecord, aTxtLen); + } +} + +ResolveReplyRunnable::~ResolveReplyRunnable() +{ +} + +NS_IMETHODIMP +ResolveReplyRunnable::Run() +{ + MOZ_ASSERT(mContext); + mContext->Reply(mSdRef, + mFlags, + mInterfaceIndex, + mErrorCode, + mFullname, + mHosttarget, + mPort, + mTxtLen, + mTxtRecord.get()); + return NS_OK; +} + +void +ResolveReplyRunnable::Reply(DNSServiceRef aSdRef, + DNSServiceFlags aFlags, + uint32_t aInterfaceIndex, + DNSServiceErrorType aErrorCode, + const char* aFullName, + const char* aHostTarget, + uint16_t aPort, + uint16_t aTxtLen, + const unsigned char* aTxtRecord, + void* aContext) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + + ResolveOperator* obj(reinterpret_cast<ResolveOperator*>(aContext)); + if (!obj) { + return; + } + + nsCOMPtr<nsIThread> thread(obj->GetThread()); + if (!thread) { + return; + } + + thread->Dispatch(new ResolveReplyRunnable(aSdRef, + aFlags, + aInterfaceIndex, + aErrorCode, + nsCString(aFullName), + nsCString(aHostTarget), + NativeEndian::swapFromNetworkOrder(aPort), + aTxtLen, + aTxtRecord, + obj), + NS_DISPATCH_NORMAL); +} + +GetAddrInfoReplyRunnable::GetAddrInfoReplyRunnable(DNSServiceRef aSdRef, + DNSServiceFlags aFlags, + uint32_t aInterfaceIndex, + DNSServiceErrorType aErrorCode, + const nsACString& aHostName, + const mozilla::net::NetAddr& aAddress, + uint32_t aTTL, + GetAddrInfoOperator* aContext) + : mSdRef(aSdRef) + , mFlags(aFlags) + , mInterfaceIndex(aInterfaceIndex) + , mErrorCode(aErrorCode) + , mHostName(aHostName) + , mAddress(aAddress) + , mTTL(aTTL) + , mContext(aContext) +{ +} + +GetAddrInfoReplyRunnable::~GetAddrInfoReplyRunnable() +{ +} + +NS_IMETHODIMP +GetAddrInfoReplyRunnable::Run() +{ + MOZ_ASSERT(mContext); + mContext->Reply(mSdRef, + mFlags, + mInterfaceIndex, + mErrorCode, + mHostName, + mAddress, + mTTL); + return NS_OK; +} + +void +GetAddrInfoReplyRunnable::Reply(DNSServiceRef aSdRef, + DNSServiceFlags aFlags, + uint32_t aInterfaceIndex, + DNSServiceErrorType aErrorCode, + const char* aHostName, + const struct sockaddr* aAddress, + uint32_t aTTL, + void* aContext) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + + GetAddrInfoOperator* obj(reinterpret_cast<GetAddrInfoOperator*>(aContext)); + if (!obj) { + return; + } + + nsCOMPtr<nsIThread> thread(obj->GetThread()); + if (!thread) { + return; + } + + NetAddr address; + address.raw.family = aAddress->sa_family; + + static_assert(sizeof(address.raw.data) >= sizeof(aAddress->sa_data), + "size of sockaddr.sa_data is too big"); + memcpy(&address.raw.data, aAddress->sa_data, sizeof(aAddress->sa_data)); + + thread->Dispatch(new GetAddrInfoReplyRunnable(aSdRef, + aFlags, + aInterfaceIndex, + aErrorCode, + nsCString(aHostName), + address, + aTTL, + obj), + NS_DISPATCH_NORMAL); +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/dns/mdns/libmdns/MDNSResponderReply.h b/netwerk/dns/mdns/libmdns/MDNSResponderReply.h new file mode 100644 index 000000000..794a585f8 --- /dev/null +++ b/netwerk/dns/mdns/libmdns/MDNSResponderReply.h @@ -0,0 +1,164 @@ +/* -*- 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/. */ + +#ifndef mozilla_netwerk_dns_mdns_libmdns_MDNSResponderReply_h +#define mozilla_netwerk_dns_mdns_libmdns_MDNSResponderReply_h + +#include "dns_sd.h" +#include "MDNSResponderOperator.h" +#include "mozilla/UniquePtr.h" +#include "nsIThread.h" +#include "mozilla/net/DNS.h" +#include "mozilla/RefPtr.h" +#include "nsThreadUtils.h" + +namespace mozilla { +namespace net { + +class BrowseReplyRunnable final : public Runnable +{ +public: + BrowseReplyRunnable(DNSServiceRef aSdRef, + DNSServiceFlags aFlags, + uint32_t aInterfaceIndex, + DNSServiceErrorType aErrorCode, + const nsACString& aServiceName, + const nsACString& aRegType, + const nsACString& aReplyDomain, + BrowseOperator* aContext); + + NS_IMETHOD Run() override; + + static void Reply(DNSServiceRef aSdRef, + DNSServiceFlags aFlags, + uint32_t aInterfaceIndex, + DNSServiceErrorType aErrorCode, + const char* aServiceName, + const char* aRegType, + const char* aReplyDomain, + void* aContext); + +private: + DNSServiceRef mSdRef; + DNSServiceFlags mFlags; + uint32_t mInterfaceIndex; + DNSServiceErrorType mErrorCode; + nsCString mServiceName; + nsCString mRegType; + nsCString mReplyDomain; + RefPtr<BrowseOperator> mContext; +}; + +class RegisterReplyRunnable final : public Runnable +{ +public: + RegisterReplyRunnable(DNSServiceRef aSdRef, + DNSServiceFlags aFlags, + DNSServiceErrorType aErrorCode, + const nsACString& aName, + const nsACString& aRegType, + const nsACString& aDomain, + RegisterOperator* aContext); + + NS_IMETHOD Run() override; + + static void Reply(DNSServiceRef aSdRef, + DNSServiceFlags aFlags, + DNSServiceErrorType aErrorCode, + const char* aName, + const char* aRegType, + const char* aDomain, + void* aContext); + +private: + DNSServiceRef mSdRef; + DNSServiceFlags mFlags; + DNSServiceErrorType mErrorCode; + nsCString mName; + nsCString mRegType; + nsCString mDomain; + RefPtr<RegisterOperator> mContext; +}; + +class ResolveReplyRunnable final : public Runnable +{ +public: + ResolveReplyRunnable(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, + ResolveOperator* aContext); + ~ResolveReplyRunnable(); + + NS_IMETHOD Run() override; + + static void Reply(DNSServiceRef aSdRef, + DNSServiceFlags aFlags, + uint32_t aInterfaceIndex, + DNSServiceErrorType aErrorCode, + const char* aFullName, + const char* aHostTarget, + uint16_t aPort, + uint16_t aTxtLen, + const unsigned char* aTxtRecord, + void* aContext); + +private: + DNSServiceRef mSdRef; + DNSServiceFlags mFlags; + uint32_t mInterfaceIndex; + DNSServiceErrorType mErrorCode; + nsCString mFullname; + nsCString mHosttarget; + uint16_t mPort; + uint16_t mTxtLen; + UniquePtr<unsigned char> mTxtRecord; + RefPtr<ResolveOperator> mContext; +}; + +class GetAddrInfoReplyRunnable final : public Runnable +{ +public: + GetAddrInfoReplyRunnable(DNSServiceRef aSdRef, + DNSServiceFlags aFlags, + uint32_t aInterfaceIndex, + DNSServiceErrorType aErrorCode, + const nsACString& aHostName, + const mozilla::net::NetAddr& aAddress, + uint32_t aTTL, + GetAddrInfoOperator* aContext); + ~GetAddrInfoReplyRunnable(); + + NS_IMETHOD Run() override; + + static void Reply(DNSServiceRef aSdRef, + DNSServiceFlags aFlags, + uint32_t aInterfaceIndex, + DNSServiceErrorType aErrorCode, + const char* aHostName, + const struct sockaddr* aAddress, + uint32_t aTTL, + void* aContext); + +private: + DNSServiceRef mSdRef; + DNSServiceFlags mFlags; + uint32_t mInterfaceIndex; + DNSServiceErrorType mErrorCode; + nsCString mHostName; + mozilla::net::NetAddr mAddress; + uint32_t mTTL; + RefPtr<GetAddrInfoOperator> mContext; +}; + +} // namespace net +} // namespace mozilla + + #endif // mozilla_netwerk_dns_mdns_libmdns_MDNSResponderReply_h diff --git a/netwerk/dns/mdns/libmdns/MulticastDNSAndroid.jsm b/netwerk/dns/mdns/libmdns/MulticastDNSAndroid.jsm new file mode 100644 index 000000000..771f9a794 --- /dev/null +++ b/netwerk/dns/mdns/libmdns/MulticastDNSAndroid.jsm @@ -0,0 +1,244 @@ +// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*- +/* 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/. */ + +"use strict"; + +this.EXPORTED_SYMBOLS = ["MulticastDNS"]; + +const { classes: Cc, interfaces: Ci, utils: Cu } = Components; + +Cu.import("resource://gre/modules/Messaging.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); +var log = Cu.import("resource://gre/modules/AndroidLog.jsm", {}).AndroidLog.d.bind(null, "MulticastDNS"); + +const FAILURE_INTERNAL_ERROR = -65537; + +// Helper function for sending commands to Java. +function send(type, data, callback) { + let msg = { + type: type + }; + + for (let i in data) { + try { + msg[i] = data[i]; + } catch (e) { + } + } + + Messaging.sendRequestForResult(msg) + .then(result => callback(result, null), + err => callback(null, typeof err === "number" ? err : FAILURE_INTERNAL_ERROR)); +} + +// Receives service found/lost event from NsdManager +function ServiceManager() { +} + +ServiceManager.prototype = { + listeners: {}, + numListeners: 0, + + registerEvent: function() { + log("registerEvent"); + Messaging.addListener(this.onServiceFound.bind(this), "NsdManager:ServiceFound"); + Messaging.addListener(this.onServiceLost.bind(this), "NsdManager:ServiceLost"); + }, + + unregisterEvent: function() { + log("unregisterEvent"); + Messaging.removeListener("NsdManager:ServiceFound"); + Messaging.removeListener("NsdManager:ServiceLost"); + }, + + addListener: function(aServiceType, aListener) { + log("addListener: " + aServiceType + ", " + aListener); + + if (!this.listeners[aServiceType]) { + this.listeners[aServiceType] = []; + } + if (this.listeners[aServiceType].includes(aListener)) { + log("listener already exists"); + return; + } + + this.listeners[aServiceType].push(aListener); + ++this.numListeners; + + if (this.numListeners === 1) { + this.registerEvent(); + } + + log("listener added: " + this); + }, + + removeListener: function(aServiceType, aListener) { + log("removeListener: " + aServiceType + ", " + aListener); + + if (!this.listeners[aServiceType]) { + log("listener doesn't exist"); + return; + } + let index = this.listeners[aServiceType].indexOf(aListener); + if (index < 0) { + log("listener doesn't exist"); + return; + } + + this.listeners[aServiceType].splice(index, 1); + --this.numListeners; + + if (this.numListeners === 0) { + this.unregisterEvent(); + } + + log("listener removed" + this); + }, + + onServiceFound: function(aServiceInfo) { + let listeners = this.listeners[aServiceInfo.serviceType]; + if (listeners) { + for (let listener of listeners) { + listener.onServiceFound(aServiceInfo); + } + } else { + log("no listener"); + } + return {}; + }, + + onServiceLost: function(aServiceInfo) { + let listeners = this.listeners[aServiceInfo.serviceType]; + if (listeners) { + for (let listener of listeners) { + listener.onServiceLost(aServiceInfo); + } + } else { + log("no listener"); + } + return {}; + } +}; + +// make an object from nsIPropertyBag2 +function parsePropertyBag2(bag) { + if (!bag || !(bag instanceof Ci.nsIPropertyBag2)) { + throw new TypeError("Not a property bag"); + } + + let attributes = []; + let enumerator = bag.enumerator; + while (enumerator.hasMoreElements()) { + let name = enumerator.getNext().QueryInterface(Ci.nsIProperty).name; + let value = bag.getPropertyAsACString(name); + attributes.push({ + "name": name, + "value": value + }); + } + + return attributes; +} + +function MulticastDNS() { + this.serviceManager = new ServiceManager(); +} + +MulticastDNS.prototype = { + startDiscovery: function(aServiceType, aListener) { + this.serviceManager.addListener(aServiceType, aListener); + + let serviceInfo = { + serviceType: aServiceType, + uniqueId: aListener.uuid + }; + + send("NsdManager:DiscoverServices", serviceInfo, (result, err) => { + if (err) { + log("onStartDiscoveryFailed: " + aServiceType + " (" + err + ")"); + this.serviceManager.removeListener(aServiceType, aListener); + aListener.onStartDiscoveryFailed(aServiceType, err); + } else { + aListener.onDiscoveryStarted(result); + } + }); + }, + + stopDiscovery: function(aServiceType, aListener) { + this.serviceManager.removeListener(aServiceType, aListener); + + let serviceInfo = { + uniqueId: aListener.uuid + }; + + send("NsdManager:StopServiceDiscovery", serviceInfo, (result, err) => { + if (err) { + log("onStopDiscoveryFailed: " + aServiceType + " (" + err + ")"); + aListener.onStopDiscoveryFailed(aServiceType, err); + } else { + aListener.onDiscoveryStopped(aServiceType); + } + }); + }, + + registerService: function(aServiceInfo, aListener) { + let serviceInfo = { + port: aServiceInfo.port, + serviceType: aServiceInfo.serviceType, + uniqueId: aListener.uuid + }; + + try { + serviceInfo.host = aServiceInfo.host; + } catch(e) { + // host unspecified + } + try { + serviceInfo.serviceName = aServiceInfo.serviceName; + } catch(e) { + // serviceName unspecified + } + try { + serviceInfo.attributes = parsePropertyBag2(aServiceInfo.attributes); + } catch(e) { + // attributes unspecified + } + + send("NsdManager:RegisterService", serviceInfo, (result, err) => { + if (err) { + log("onRegistrationFailed: (" + err + ")"); + aListener.onRegistrationFailed(aServiceInfo, err); + } else { + aListener.onServiceRegistered(result); + } + }); + }, + + unregisterService: function(aServiceInfo, aListener) { + let serviceInfo = { + uniqueId: aListener.uuid + }; + + send("NsdManager:UnregisterService", serviceInfo, (result, err) => { + if (err) { + log("onUnregistrationFailed: (" + err + ")"); + aListener.onUnregistrationFailed(aServiceInfo, err); + } else { + aListener.onServiceUnregistered(aServiceInfo); + } + }); + }, + + resolveService: function(aServiceInfo, aListener) { + send("NsdManager:ResolveService", aServiceInfo, (result, err) => { + if (err) { + log("onResolveFailed: (" + err + ")"); + aListener.onResolveFailed(aServiceInfo, err); + } else { + aListener.onServiceResolved(result); + } + }); + } +}; diff --git a/netwerk/dns/mdns/libmdns/fallback/DNSPacket.jsm b/netwerk/dns/mdns/libmdns/fallback/DNSPacket.jsm new file mode 100644 index 000000000..f0539fccb --- /dev/null +++ b/netwerk/dns/mdns/libmdns/fallback/DNSPacket.jsm @@ -0,0 +1,297 @@ +/* -*- Mode: js; js-indent-level: 2; indent-tabs-mode: nil; tab-width: 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/. */ +/* jshint esnext: true, moz: true */ + +'use strict'; + +this.EXPORTED_SYMBOLS = ['DNSPacket']; + +const { utils: Cu } = Components; + +Cu.import('resource://gre/modules/Services.jsm'); + +Cu.import('resource://gre/modules/DataReader.jsm'); +Cu.import('resource://gre/modules/DataWriter.jsm'); +Cu.import('resource://gre/modules/DNSRecord.jsm'); +Cu.import('resource://gre/modules/DNSResourceRecord.jsm'); + +const DEBUG = true; + +function debug(msg) { + Services.console.logStringMessage('DNSPacket: ' + msg); +} + +let DNS_PACKET_SECTION_TYPES = [ + 'QD', // Question + 'AN', // Answer + 'NS', // Authority + 'AR' // Additional +]; + +/** + * DNS Packet Structure + * ************************************************* + * + * Header + * ====== + * + * 00 2-Bytes 15 + * ------------------------------------------------- + * |00|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15| + * ------------------------------------------------- + * |<==================== ID =====================>| + * |QR|<== OP ===>|AA|TC|RD|RA|UN|AD|CD|<== RC ===>| + * |<================== QDCOUNT ==================>| + * |<================== ANCOUNT ==================>| + * |<================== NSCOUNT ==================>| + * |<================== ARCOUNT ==================>| + * ------------------------------------------------- + * + * ID: 2-Bytes + * FLAGS: 2-Bytes + * - QR: 1-Bit + * - OP: 4-Bits + * - AA: 1-Bit + * - TC: 1-Bit + * - RD: 1-Bit + * - RA: 1-Bit + * - UN: 1-Bit + * - AD: 1-Bit + * - CD: 1-Bit + * - RC: 4-Bits + * QDCOUNT: 2-Bytes + * ANCOUNT: 2-Bytes + * NSCOUNT: 2-Bytes + * ARCOUNT: 2-Bytes + * + * + * Data + * ==== + * + * 00 2-Bytes 15 + * ------------------------------------------------- + * |00|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15| + * ------------------------------------------------- + * |<???=============== QD[...] ===============???>| + * |<???=============== AN[...] ===============???>| + * |<???=============== NS[...] ===============???>| + * |<???=============== AR[...] ===============???>| + * ------------------------------------------------- + * + * QD: ??-Bytes + * AN: ??-Bytes + * NS: ??-Bytes + * AR: ??-Bytes + * + * + * Question Record + * =============== + * + * 00 2-Bytes 15 + * ------------------------------------------------- + * |00|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15| + * ------------------------------------------------- + * |<???================ NAME =================???>| + * |<=================== TYPE ====================>| + * |<=================== CLASS ===================>| + * ------------------------------------------------- + * + * NAME: ??-Bytes + * TYPE: 2-Bytes + * CLASS: 2-Bytes + * + * + * Resource Record + * =============== + * + * 00 4-Bytes 31 + * ------------------------------------------------- + * |00|02|04|06|08|10|12|14|16|18|20|22|24|26|28|30| + * ------------------------------------------------- + * |<???================ NAME =================???>| + * |<======= TYPE ========>|<======= CLASS =======>| + * |<==================== TTL ====================>| + * |<====== DATALEN ======>|<???==== DATA =====???>| + * ------------------------------------------------- + * + * NAME: ??-Bytes + * TYPE: 2-Bytes + * CLASS: 2-Bytes + * DATALEN: 2-Bytes + * DATA: ??-Bytes (Specified By DATALEN) + */ +class DNSPacket { + constructor() { + this._flags = _valueToFlags(0x0000); + this._records = {}; + + DNS_PACKET_SECTION_TYPES.forEach((sectionType) => { + this._records[sectionType] = []; + }); + } + + static parse(data) { + let reader = new DataReader(data); + if (reader.getValue(2) !== 0x0000) { + throw new Error('Packet must start with 0x0000'); + } + + let packet = new DNSPacket(); + packet._flags = _valueToFlags(reader.getValue(2)); + + let recordCounts = {}; + + // Parse the record counts. + DNS_PACKET_SECTION_TYPES.forEach((sectionType) => { + recordCounts[sectionType] = reader.getValue(2); + }); + + // Parse the actual records. + DNS_PACKET_SECTION_TYPES.forEach((sectionType) => { + let recordCount = recordCounts[sectionType]; + for (let i = 0; i < recordCount; i++) { + if (sectionType === 'QD') { + packet.addRecord(sectionType, + DNSRecord.parseFromPacketReader(reader)); + } + + else { + packet.addRecord(sectionType, + DNSResourceRecord.parseFromPacketReader(reader)); + } + } + }); + + if (!reader.eof) { + DEBUG && debug('Did not complete parsing packet data'); + } + + return packet; + } + + getFlag(flag) { + return this._flags[flag]; + } + + setFlag(flag, value) { + this._flags[flag] = value; + } + + addRecord(sectionType, record) { + this._records[sectionType].push(record); + } + + getRecords(sectionTypes, recordType) { + let records = []; + + sectionTypes.forEach((sectionType) => { + records = records.concat(this._records[sectionType]); + }); + + if (!recordType) { + return records; + } + + return records.filter(r => r.recordType === recordType); + } + + serialize() { + let writer = new DataWriter(); + + // Write leading 0x0000 (2 bytes) + writer.putValue(0x0000, 2); + + // Write `flags` (2 bytes) + writer.putValue(_flagsToValue(this._flags), 2); + + // Write lengths of record sections (2 bytes each) + DNS_PACKET_SECTION_TYPES.forEach((sectionType) => { + writer.putValue(this._records[sectionType].length, 2); + }); + + // Write records + DNS_PACKET_SECTION_TYPES.forEach((sectionType) => { + this._records[sectionType].forEach((record) => { + writer.putBytes(record.serialize()); + }); + }); + + return writer.data; + } + + toJSON() { + return JSON.stringify(this.toJSONObject()); + } + + toJSONObject() { + let result = {flags: this._flags}; + DNS_PACKET_SECTION_TYPES.forEach((sectionType) => { + result[sectionType] = []; + + let records = this._records[sectionType]; + records.forEach((record) => { + result[sectionType].push(record.toJSONObject()); + }); + }); + + return result; + } +} + +/** + * @private + */ +function _valueToFlags(value) { + return { + QR: (value & 0x8000) >> 15, + OP: (value & 0x7800) >> 11, + AA: (value & 0x0400) >> 10, + TC: (value & 0x0200) >> 9, + RD: (value & 0x0100) >> 8, + RA: (value & 0x0080) >> 7, + UN: (value & 0x0040) >> 6, + AD: (value & 0x0020) >> 5, + CD: (value & 0x0010) >> 4, + RC: (value & 0x000f) >> 0 + }; +} + +/** + * @private + */ +function _flagsToValue(flags) { + let value = 0x0000; + + value += flags.QR & 0x01; + + value <<= 4; + value += flags.OP & 0x0f; + + value <<= 1; + value += flags.AA & 0x01; + + value <<= 1; + value += flags.TC & 0x01; + + value <<= 1; + value += flags.RD & 0x01; + + value <<= 1; + value += flags.RA & 0x01; + + value <<= 1; + value += flags.UN & 0x01; + + value <<= 1; + value += flags.AD & 0x01; + + value <<= 1; + value += flags.CD & 0x01; + + value <<= 4; + value += flags.RC & 0x0f; + + return value; +} diff --git a/netwerk/dns/mdns/libmdns/fallback/DNSRecord.jsm b/netwerk/dns/mdns/libmdns/fallback/DNSRecord.jsm new file mode 100644 index 000000000..f5d48731f --- /dev/null +++ b/netwerk/dns/mdns/libmdns/fallback/DNSRecord.jsm @@ -0,0 +1,70 @@ +/* -*- Mode: js; js-indent-level: 2; indent-tabs-mode: nil; tab-width: 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/. */ +/* jshint esnext: true, moz: true */ + +'use strict'; + +this.EXPORTED_SYMBOLS = ['DNSRecord']; + +const { utils: Cu } = Components; + +Cu.import('resource://gre/modules/DataWriter.jsm'); +Cu.import('resource://gre/modules/DNSTypes.jsm'); + +class DNSRecord { + constructor(properties = {}) { + this.name = properties.name || ''; + this.recordType = properties.recordType || DNS_RECORD_TYPES.ANY; + this.classCode = properties.classCode || DNS_CLASS_CODES.IN; + this.cacheFlush = properties.cacheFlush || false; + } + + static parseFromPacketReader(reader) { + let name = reader.getLabel(); + let recordType = reader.getValue(2); + let classCode = reader.getValue(2); + let cacheFlush = (classCode & 0x8000) ? true : false; + classCode &= 0xff; + + return new this({ + name: name, + recordType: recordType, + classCode: classCode, + cacheFlush: cacheFlush + }); + } + + serialize() { + let writer = new DataWriter(); + + // Write `name` (ends with trailing 0x00 byte) + writer.putLabel(this.name); + + // Write `recordType` (2 bytes) + writer.putValue(this.recordType, 2); + + // Write `classCode` (2 bytes) + let classCode = this.classCode; + if (this.cacheFlush) { + classCode |= 0x8000; + } + writer.putValue(classCode, 2); + + return writer.data; + } + + toJSON() { + return JSON.stringify(this.toJSONObject()); + } + + toJSONObject() { + return { + name: this.name, + recordType: this.recordType, + classCode: this.classCode, + cacheFlush: this.cacheFlush + }; + } +} diff --git a/netwerk/dns/mdns/libmdns/fallback/DNSResourceRecord.jsm b/netwerk/dns/mdns/libmdns/fallback/DNSResourceRecord.jsm new file mode 100644 index 000000000..ba0072a50 --- /dev/null +++ b/netwerk/dns/mdns/libmdns/fallback/DNSResourceRecord.jsm @@ -0,0 +1,221 @@ +/* -*- Mode: js; js-indent-level: 2; indent-tabs-mode: nil; tab-width: 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/. */ +/* jshint esnext: true, moz: true */ + +'use strict'; + +this.EXPORTED_SYMBOLS = ['DNSResourceRecord']; + +const { utils: Cu } = Components; + +Cu.import('resource://gre/modules/Services.jsm'); +Cu.import('resource://gre/modules/DataReader.jsm'); +Cu.import('resource://gre/modules/DataWriter.jsm'); +Cu.import('resource://gre/modules/DNSRecord.jsm'); +Cu.import('resource://gre/modules/DNSTypes.jsm'); + +function debug(msg) { + Services.console.logStringMessage('MulticastDNS: ' + msg); +} + +const DNS_RESOURCE_RECORD_DEFAULT_TTL = 120; // 120 seconds + +class DNSResourceRecord extends DNSRecord { + constructor(properties = {}) { + super(properties); + + this.ttl = properties.ttl || DNS_RESOURCE_RECORD_DEFAULT_TTL; + this.data = properties.data || {}; + } + + static parseFromPacketReader(reader) { + let record = super.parseFromPacketReader(reader); + + let ttl = reader.getValue(4); + let recordData = reader.getBytes(reader.getValue(2)); + let packetData = reader.data; + + let data; + + switch (record.recordType) { + case DNS_RECORD_TYPES.A: + data = _parseA(recordData, packetData); + break; + case DNS_RECORD_TYPES.PTR: + data = _parsePTR(recordData, packetData); + break; + case DNS_RECORD_TYPES.TXT: + data = _parseTXT(recordData, packetData); + break; + case DNS_RECORD_TYPES.SRV: + data = _parseSRV(recordData, packetData); + break; + default: + data = null; + break; + } + + record.ttl = ttl; + record.data = data; + + return record; + } + + serialize() { + let writer = new DataWriter(super.serialize()); + + // Write `ttl` (4 bytes) + writer.putValue(this.ttl, 4); + + let data; + + switch (this.recordType) { + case DNS_RECORD_TYPES.A: + data = _serializeA(this.data); + break; + case DNS_RECORD_TYPES.PTR: + data = _serializePTR(this.data); + break; + case DNS_RECORD_TYPES.TXT: + data = _serializeTXT(this.data); + break; + case DNS_RECORD_TYPES.SRV: + data = _serializeSRV(this.data); + break; + default: + data = new Uint8Array(); + break; + } + + // Write `data` length. + writer.putValue(data.length, 2); + + // Write `data` (ends with trailing 0x00 byte) + writer.putBytes(data); + + return writer.data; + } + + toJSON() { + return JSON.stringify(this.toJSONObject()); + } + + toJSONObject() { + let result = super.toJSONObject(); + result.ttl = this.ttl; + result.data = this.data; + return result; + } +} + +/** + * @private + */ +function _parseA(recordData, packetData) { + let reader = new DataReader(recordData); + + let parts = []; + for (let i = 0; i < 4; i++) { + parts.push(reader.getValue(1)); + } + + return parts.join('.'); +} + +/** + * @private + */ +function _parsePTR(recordData, packetData) { + let reader = new DataReader(recordData); + + return reader.getLabel(packetData); +} + +/** + * @private + */ +function _parseTXT(recordData, packetData) { + let reader = new DataReader(recordData); + + let result = {}; + + let label = reader.getLabel(packetData); + if (label.length > 0) { + let parts = label.split('.'); + parts.forEach((part) => { + let [name] = part.split('=', 1); + let value = part.substr(name.length + 1); + result[name] = value; + }); + } + + return result; +} + +/** + * @private + */ +function _parseSRV(recordData, packetData) { + let reader = new DataReader(recordData); + + let priority = reader.getValue(2); + let weight = reader.getValue(2); + let port = reader.getValue(2); + let target = reader.getLabel(packetData); + + return { priority, weight, port, target }; +} + +/** + * @private + */ +function _serializeA(data) { + let writer = new DataWriter(); + + let parts = data.split('.'); + for (let i = 0; i < 4; i++) { + writer.putValue(parseInt(parts[i], 10) || 0); + } + + return writer.data; +} + +/** + * @private + */ +function _serializePTR(data) { + let writer = new DataWriter(); + + writer.putLabel(data); + + return writer.data; +} + +/** + * @private + */ +function _serializeTXT(data) { + let writer = new DataWriter(); + + for (let name in data) { + writer.putLengthString(name + '=' + data[name]); + } + + return writer.data; +} + +/** + * @private + */ +function _serializeSRV(data) { + let writer = new DataWriter(); + + writer.putValue(data.priority || 0, 2); + writer.putValue(data.weight || 0, 2); + writer.putValue(data.port || 0, 2); + writer.putLabel(data.target); + + return writer.data; +} diff --git a/netwerk/dns/mdns/libmdns/fallback/DNSTypes.jsm b/netwerk/dns/mdns/libmdns/fallback/DNSTypes.jsm new file mode 100644 index 000000000..8c5470639 --- /dev/null +++ b/netwerk/dns/mdns/libmdns/fallback/DNSTypes.jsm @@ -0,0 +1,100 @@ +/* -*- Mode: js; js-indent-level: 2; indent-tabs-mode: nil; tab-width: 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/. */ +/* jshint esnext: true, moz: true */ + +'use strict'; + +this.EXPORTED_SYMBOLS = [ + 'DNS_QUERY_RESPONSE_CODES', + 'DNS_AUTHORITATIVE_ANSWER_CODES', + 'DNS_CLASS_CODES', + 'DNS_RECORD_TYPES' +]; + +let DNS_QUERY_RESPONSE_CODES = { + QUERY : 0, // RFC 1035 - Query + RESPONSE : 1 // RFC 1035 - Reponse +}; + +let DNS_AUTHORITATIVE_ANSWER_CODES = { + NO : 0, // RFC 1035 - Not Authoritative + YES : 1 // RFC 1035 - Is Authoritative +}; + +let DNS_CLASS_CODES = { + IN : 0x01, // RFC 1035 - Internet + CS : 0x02, // RFC 1035 - CSNET + CH : 0x03, // RFC 1035 - CHAOS + HS : 0x04, // RFC 1035 - Hesiod + NONE : 0xfe, // RFC 2136 - None + ANY : 0xff, // RFC 1035 - Any +}; + +let DNS_RECORD_TYPES = { + SIGZERO : 0, // RFC 2931 + A : 1, // RFC 1035 + NS : 2, // RFC 1035 + MD : 3, // RFC 1035 + MF : 4, // RFC 1035 + CNAME : 5, // RFC 1035 + SOA : 6, // RFC 1035 + MB : 7, // RFC 1035 + MG : 8, // RFC 1035 + MR : 9, // RFC 1035 + NULL : 10, // RFC 1035 + WKS : 11, // RFC 1035 + PTR : 12, // RFC 1035 + HINFO : 13, // RFC 1035 + MINFO : 14, // RFC 1035 + MX : 15, // RFC 1035 + TXT : 16, // RFC 1035 + RP : 17, // RFC 1183 + AFSDB : 18, // RFC 1183 + X25 : 19, // RFC 1183 + ISDN : 20, // RFC 1183 + RT : 21, // RFC 1183 + NSAP : 22, // RFC 1706 + NSAP_PTR : 23, // RFC 1348 + SIG : 24, // RFC 2535 + KEY : 25, // RFC 2535 + PX : 26, // RFC 2163 + GPOS : 27, // RFC 1712 + AAAA : 28, // RFC 1886 + LOC : 29, // RFC 1876 + NXT : 30, // RFC 2535 + EID : 31, // RFC ???? + NIMLOC : 32, // RFC ???? + SRV : 33, // RFC 2052 + ATMA : 34, // RFC ???? + NAPTR : 35, // RFC 2168 + KX : 36, // RFC 2230 + CERT : 37, // RFC 2538 + DNAME : 39, // RFC 2672 + OPT : 41, // RFC 2671 + APL : 42, // RFC 3123 + DS : 43, // RFC 4034 + SSHFP : 44, // RFC 4255 + IPSECKEY : 45, // RFC 4025 + RRSIG : 46, // RFC 4034 + NSEC : 47, // RFC 4034 + DNSKEY : 48, // RFC 4034 + DHCID : 49, // RFC 4701 + NSEC3 : 50, // RFC ???? + NSEC3PARAM : 51, // RFC ???? + HIP : 55, // RFC 5205 + SPF : 99, // RFC 4408 + UINFO : 100, // RFC ???? + UID : 101, // RFC ???? + GID : 102, // RFC ???? + UNSPEC : 103, // RFC ???? + TKEY : 249, // RFC 2930 + TSIG : 250, // RFC 2931 + IXFR : 251, // RFC 1995 + AXFR : 252, // RFC 1035 + MAILB : 253, // RFC 1035 + MAILA : 254, // RFC 1035 + ANY : 255, // RFC 1035 + DLV : 32769 // RFC 4431 +}; diff --git a/netwerk/dns/mdns/libmdns/fallback/DataReader.jsm b/netwerk/dns/mdns/libmdns/fallback/DataReader.jsm new file mode 100644 index 000000000..a20c1dc32 --- /dev/null +++ b/netwerk/dns/mdns/libmdns/fallback/DataReader.jsm @@ -0,0 +1,133 @@ +/* -*- Mode: js; js-indent-level: 2; indent-tabs-mode: nil; tab-width: 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/. */ +/* jshint esnext: true, moz: true */ + +'use strict'; + +this.EXPORTED_SYMBOLS = ['DataReader']; + +class DataReader { + // `data` is `Uint8Array` + constructor(data, startByte = 0) { + this._data = data; + this._cursor = startByte; + } + + get buffer() { + return this._data.buffer; + } + + get data() { + return this._data; + } + + get eof() { + return this._cursor >= this._data.length; + } + + getBytes(length = 1) { + if (!length) { + return new Uint8Array(); + } + + let end = this._cursor + length; + if (end > this._data.length) { + return new Uint8Array(); + } + + let uint8Array = new Uint8Array(this.buffer.slice(this._cursor, end)); + this._cursor += length; + + return uint8Array; + } + + getString(length) { + let uint8Array = this.getBytes(length); + return _uint8ArrayToString(uint8Array); + } + + getValue(length) { + let uint8Array = this.getBytes(length); + return _uint8ArrayToValue(uint8Array); + } + + getLabel(decompressData) { + let parts = []; + let partLength; + + while ((partLength = this.getValue(1))) { + // If a length has been specified instead of a pointer, + // read the string of the specified length. + if (partLength !== 0xc0) { + parts.push(this.getString(partLength)); + continue; + } + + // TODO: Handle case where we have a pointer to the label + parts.push(String.fromCharCode(0xc0) + this.getString(1)); + break; + } + + let label = parts.join('.'); + + return _decompressLabel(label, decompressData || this._data); + } +} + +/** + * @private + */ +function _uint8ArrayToValue(uint8Array) { + let length = uint8Array.length; + if (length === 0) { + return null; + } + + let value = 0; + for (let i = 0; i < length; i++) { + value = value << 8; + value += uint8Array[i]; + } + + return value; +} + +/** + * @private + */ +function _uint8ArrayToString(uint8Array) { + let length = uint8Array.length; + if (length === 0) { + return ''; + } + + let results = []; + for (let i = 0; i < length; i += 1024) { + results.push(String.fromCharCode.apply(null, uint8Array.subarray(i, i + 1024))); + } + + return results.join(''); +} + +/** + * @private + */ +function _decompressLabel(label, decompressData) { + let result = ''; + + for (let i = 0, length = label.length; i < length; i++) { + if (label.charCodeAt(i) !== 0xc0) { + result += label.charAt(i); + continue; + } + + i++; + + let reader = new DataReader(decompressData, label.charCodeAt(i)); + result += _decompressLabel(reader.getLabel(), decompressData); + } + + return result; +} diff --git a/netwerk/dns/mdns/libmdns/fallback/DataWriter.jsm b/netwerk/dns/mdns/libmdns/fallback/DataWriter.jsm new file mode 100644 index 000000000..af20d65f5 --- /dev/null +++ b/netwerk/dns/mdns/libmdns/fallback/DataWriter.jsm @@ -0,0 +1,98 @@ +/* -*- Mode: js; js-indent-level: 2; indent-tabs-mode: nil; tab-width: 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/. */ +/* jshint esnext: true, moz: true */ + +'use strict'; + +this.EXPORTED_SYMBOLS = ['DataWriter']; + +class DataWriter { + constructor(data, maxBytes = 512) { + if (typeof data === 'number') { + maxBytes = data; + data = undefined; + } + + this._buffer = new ArrayBuffer(maxBytes); + this._data = new Uint8Array(this._buffer); + this._cursor = 0; + + if (data) { + this.putBytes(data); + } + } + + get buffer() { + return this._buffer.slice(0, this._cursor); + } + + get data() { + return new Uint8Array(this.buffer); + } + + // `data` is `Uint8Array` + putBytes(data) { + if (this._cursor + data.length > this._data.length) { + throw new Error('DataWriter buffer is exceeded'); + } + + for (let i = 0, length = data.length; i < length; i++) { + this._data[this._cursor] = data[i]; + this._cursor++; + } + } + + putByte(byte) { + if (this._cursor + 1 > this._data.length) { + throw new Error('DataWriter buffer is exceeded'); + } + + this._data[this._cursor] = byte + this._cursor++; + } + + putValue(value, length) { + length = length || 1; + if (length == 1) { + this.putByte(value); + } else { + this.putBytes(_valueToUint8Array(value, length)); + } + } + + putLabel(label) { + // Eliminate any trailing '.'s in the label (valid in text representation). + label = label.replace(/\.$/, ''); + let parts = label.split('.'); + parts.forEach((part) => { + this.putLengthString(part); + }); + this.putValue(0); + } + + putLengthString(string) { + if (string.length > 0xff) { + throw new Error("String too long."); + } + this.putValue(string.length); + for (let i = 0; i < string.length; i++) { + this.putValue(string.charCodeAt(i)); + } + } +} + +/** + * @private + */ +function _valueToUint8Array(value, length) { + let arrayBuffer = new ArrayBuffer(length); + let uint8Array = new Uint8Array(arrayBuffer); + for (let i = length - 1; i >= 0; i--) { + uint8Array[i] = value & 0xff; + value = value >> 8; + } + + return uint8Array; +} diff --git a/netwerk/dns/mdns/libmdns/fallback/MulticastDNS.jsm b/netwerk/dns/mdns/libmdns/fallback/MulticastDNS.jsm new file mode 100644 index 000000000..f43dfd5f8 --- /dev/null +++ b/netwerk/dns/mdns/libmdns/fallback/MulticastDNS.jsm @@ -0,0 +1,875 @@ +/* -*- Mode: js; js-indent-level: 2; indent-tabs-mode: nil; tab-width: 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/. */ +/* jshint esnext: true, moz: true */ + +'use strict'; + +this.EXPORTED_SYMBOLS = ['MulticastDNS']; + +const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; + +Cu.import('resource://gre/modules/Services.jsm'); +Cu.import('resource://gre/modules/Timer.jsm'); +Cu.import('resource://gre/modules/XPCOMUtils.jsm'); + +Cu.import('resource://gre/modules/DNSPacket.jsm'); +Cu.import('resource://gre/modules/DNSRecord.jsm'); +Cu.import('resource://gre/modules/DNSResourceRecord.jsm'); +Cu.import('resource://gre/modules/DNSTypes.jsm'); + +const NS_NETWORK_LINK_TOPIC = 'network:link-status-changed'; + +let observerService = Cc["@mozilla.org/observer-service;1"] + .getService(Components.interfaces.nsIObserverService); +let networkInfoService = Cc['@mozilla.org/network-info-service;1'] + .createInstance(Ci.nsINetworkInfoService); + +const DEBUG = true; + +const MDNS_MULTICAST_GROUP = '224.0.0.251'; +const MDNS_PORT = 5353; +const DEFAULT_TTL = 120; + +function debug(msg) { + dump('MulticastDNS: ' + msg + '\n'); +} + +function ServiceKey(svc) { + return "" + svc.serviceType.length + "/" + svc.serviceType + "|" + + svc.serviceName.length + "/" + svc.serviceName + "|" + + svc.port; +} + +function TryGet(obj, name) { + try { + return obj[name]; + } catch (err) { + return undefined; + } +} + +function IsIpv4Address(addr) { + let parts = addr.split('.'); + if (parts.length != 4) { + return false; + } + for (let part of parts) { + let partInt = Number.parseInt(part, 10); + if (partInt.toString() != part) { + return false; + } + if (partInt < 0 || partInt >= 256) { + return false; + } + } + return true; +} + +class PublishedService { + constructor(attrs) { + this.serviceType = attrs.serviceType.replace(/\.$/, ''); + this.serviceName = attrs.serviceName; + this.domainName = TryGet(attrs, 'domainName') || "local"; + this.address = TryGet(attrs, 'address') || "0.0.0.0"; + this.port = attrs.port; + this.serviceAttrs = _propertyBagToObject(TryGet(attrs, 'attributes') || {}); + this.host = TryGet(attrs, 'host'); + this.key = this.generateKey(); + this.lastAdvertised = undefined; + this.advertiseTimer = undefined; + } + + equals(svc) { + return (this.port == svc.port) && + (this.serviceName == svc.serviceName) && + (this.serviceType == svc.serviceType); + } + + generateKey() { + return ServiceKey(this); + } + + ptrMatch(name) { + return name == (this.serviceType + "." + this.domainName); + } + + clearAdvertiseTimer() { + if (!this.advertiseTimer) { + return; + } + clearTimeout(this.advertiseTimer); + this.advertiseTimer = undefined; + } +} + +class MulticastDNS { + constructor() { + this._listeners = new Map(); + this._sockets = new Map(); + this._services = new Map(); + this._discovered = new Map(); + this._querySocket = undefined; + this._broadcastReceiverSocket = undefined; + this._broadcastTimer = undefined; + + this._networkLinkObserver = { + observe: (subject, topic, data) => { + DEBUG && debug(NS_NETWORK_LINK_TOPIC + '(' + data + '); Clearing list of previously discovered services'); + this._discovered.clear(); + } + }; + } + + _attachNetworkLinkObserver() { + if (this._networkLinkObserverTimeout) { + clearTimeout(this._networkLinkObserverTimeout); + } + + if (!this._isNetworkLinkObserverAttached) { + DEBUG && debug('Attaching observer ' + NS_NETWORK_LINK_TOPIC); + observerService.addObserver(this._networkLinkObserver, NS_NETWORK_LINK_TOPIC, false); + this._isNetworkLinkObserverAttached = true; + } + } + + _detachNetworkLinkObserver() { + if (this._isNetworkLinkObserverAttached) { + if (this._networkLinkObserverTimeout) { + clearTimeout(this._networkLinkObserverTimeout); + } + + this._networkLinkObserverTimeout = setTimeout(() => { + DEBUG && debug('Detaching observer ' + NS_NETWORK_LINK_TOPIC); + observerService.removeObserver(this._networkLinkObserver, NS_NETWORK_LINK_TOPIC); + this._isNetworkLinkObserverAttached = false; + this._networkLinkObserverTimeout = null; + }, 5000); + } + } + + startDiscovery(aServiceType, aListener) { + DEBUG && debug('startDiscovery("' + aServiceType + '")'); + let { serviceType } = _parseServiceDomainName(aServiceType); + + this._attachNetworkLinkObserver(); + this._addServiceListener(serviceType, aListener); + + try { + this._query(serviceType + '.local'); + aListener.onDiscoveryStarted(serviceType); + } catch (e) { + DEBUG && debug('startDiscovery("' + serviceType + '") FAILED: ' + e); + this._removeServiceListener(serviceType, aListener); + aListener.onStartDiscoveryFailed(serviceType, Cr.NS_ERROR_FAILURE); + } + } + + stopDiscovery(aServiceType, aListener) { + DEBUG && debug('stopDiscovery("' + aServiceType + '")'); + let { serviceType } = _parseServiceDomainName(aServiceType); + + this._detachNetworkLinkObserver(); + this._removeServiceListener(serviceType, aListener); + + aListener.onDiscoveryStopped(serviceType); + + this._checkCloseSockets(); + } + + resolveService(aServiceInfo, aListener) { + DEBUG && debug('resolveService(): ' + aServiceInfo.serviceName); + + // Address info is already resolved during discovery + setTimeout(() => aListener.onServiceResolved(aServiceInfo)); + } + + registerService(aServiceInfo, aListener) { + DEBUG && debug('registerService(): ' + aServiceInfo.serviceName); + + // Initialize the broadcast receiver socket in case it + // hasn't already been started so we can listen for + // multicast queries/announcements on all interfaces. + this._getBroadcastReceiverSocket(); + + for (let name of ['port', 'serviceName', 'serviceType']) { + if (!TryGet(aServiceInfo, name)) { + aListener.onRegistrationFailed(aServiceInfo, Cr.NS_ERROR_FAILURE); + throw new Error('Invalid nsIDNSServiceInfo; Missing "' + name + '"'); + } + } + + let publishedService; + try { + publishedService = new PublishedService(aServiceInfo); + } catch (e) { + DEBUG && debug("Error constructing PublishedService: " + e + " - " + e.stack); + setTimeout(() => aListener.onRegistrationFailed(aServiceInfo, Cr.NS_ERROR_FAILURE)); + return; + } + + // Ensure such a service does not already exist. + if (this._services.get(publishedService.key)) { + setTimeout(() => aListener.onRegistrationFailed(aServiceInfo, Cr.NS_ERROR_FAILURE)); + return; + } + + // Make sure that the service addr is '0.0.0.0', or there is at least one + // socket open on the address the service is open on. + this._getSockets().then((sockets) => { + if (publishedService.address != '0.0.0.0' && !sockets.get(publishedService.address)) { + setTimeout(() => aListener.onRegistrationFailed(aServiceInfo, Cr.NS_ERROR_FAILURE)); + return; + } + + this._services.set(publishedService.key, publishedService); + + // Service registered.. call onServiceRegistered on next tick. + setTimeout(() => aListener.onServiceRegistered(aServiceInfo)); + + // Set a timeout to start advertising the service too. + publishedService.advertiseTimer = setTimeout(() => { + this._advertiseService(publishedService.key, /* firstAdv = */ true); + }); + }); + } + + unregisterService(aServiceInfo, aListener) { + DEBUG && debug('unregisterService(): ' + aServiceInfo.serviceName); + + let serviceKey; + try { + serviceKey = ServiceKey(aServiceInfo); + } catch (e) { + setTimeout(() => aListener.onUnregistrationFailed(aServiceInfo, Cr.NS_ERROR_FAILURE)); + return; + } + + let publishedService = this._services.get(serviceKey); + if (!publishedService) { + setTimeout(() => aListener.onUnregistrationFailed(aServiceInfo, Cr.NS_ERROR_FAILURE)); + return; + } + + // Clear any advertise timeout for this published service. + publishedService.clearAdvertiseTimer(); + + // Delete the service from the service map. + if (!this._services.delete(serviceKey)) { + setTimeout(() => aListener.onUnregistrationFailed(aServiceInfo, Cr.NS_ERROR_FAILURE)); + return; + } + + // Check the broadcast timer again to rejig when it should run next. + this._checkStartBroadcastTimer(); + + // Check to see if sockets should be closed, and if so close them. + this._checkCloseSockets(); + + aListener.onServiceUnregistered(aServiceInfo); + } + + _respondToQuery(serviceKey, message) { + let address = message.fromAddr.address; + let port = message.fromAddr.port; + DEBUG && debug('_respondToQuery(): key=' + serviceKey + ', fromAddr=' + + address + ":" + port); + + let publishedService = this._services.get(serviceKey); + if (!publishedService) { + debug("_respondToQuery Could not find service (key=" + serviceKey + ")"); + return; + } + + DEBUG && debug('_respondToQuery(): key=' + serviceKey + ': SENDING RESPONSE'); + this._advertiseServiceHelper(publishedService, {address,port}); + } + + _advertiseService(serviceKey, firstAdv) { + DEBUG && debug('_advertiseService(): key=' + serviceKey); + let publishedService = this._services.get(serviceKey); + if (!publishedService) { + debug("_advertiseService Could not find service to advertise (key=" + serviceKey + ")"); + return; + } + + publishedService.advertiseTimer = undefined; + + this._advertiseServiceHelper(publishedService, null).then(() => { + // If first advertisement, re-advertise in 1 second. + // Otherwise, set the lastAdvertised time. + if (firstAdv) { + publishedService.advertiseTimer = setTimeout(() => { + this._advertiseService(serviceKey) + }, 1000); + } else { + publishedService.lastAdvertised = Date.now(); + this._checkStartBroadcastTimer(); + } + }); + } + + _advertiseServiceHelper(svc, target) { + if (!target) { + target = {address:MDNS_MULTICAST_GROUP, port:MDNS_PORT}; + } + + return this._getSockets().then((sockets) => { + sockets.forEach((socket, address) => { + if (svc.address == "0.0.0.0" || address == svc.address) + { + let packet = this._makeServicePacket(svc, [address]); + let data = packet.serialize(); + try { + socket.send(target.address, target.port, data, data.length); + } catch (err) { + DEBUG && debug("Failed to send packet to " + + target.address + ":" + target.port); + } + } + }); + }); + } + + _cancelBroadcastTimer() { + if (!this._broadcastTimer) { + return; + } + clearTimeout(this._broadcastTimer); + this._broadcastTimer = undefined; + } + + _checkStartBroadcastTimer() { + DEBUG && debug("_checkStartBroadcastTimer()"); + // Cancel any existing broadcasting timer. + this._cancelBroadcastTimer(); + + let now = Date.now(); + + // Go through services and find services to broadcast. + let bcastServices = []; + let nextBcastWait = undefined; + for (let [serviceKey, publishedService] of this._services) { + // if lastAdvertised is undefined, service hasn't finished it's initial + // two broadcasts. + if (publishedService.lastAdvertised === undefined) { + continue; + } + + // Otherwise, check lastAdvertised against now. + let msSinceAdv = now - publishedService.lastAdvertised; + + // If msSinceAdv is more than 90% of the way to the TTL, advertise now. + if (msSinceAdv > (DEFAULT_TTL * 1000 * 0.9)) { + bcastServices.push(publishedService); + continue; + } + + // Otherwise, calculate the next time to advertise for this service. + // We set that at 95% of the time to the TTL expiry. + let nextAdvWait = (DEFAULT_TTL * 1000 * 0.95) - msSinceAdv; + if (nextBcastWait === undefined || nextBcastWait > nextAdvWait) { + nextBcastWait = nextAdvWait; + } + } + + // Schedule an immediate advertisement of all services to be advertised now. + for (let svc of bcastServices) { + svc.advertiseTimer = setTimeout(() => this._advertiseService(svc.key)); + } + + // Schedule next broadcast check for the next bcast time. + if (nextBcastWait !== undefined) { + DEBUG && debug("_checkStartBroadcastTimer(): Scheduling next check in " + nextBcastWait + "ms"); + this._broadcastTimer = setTimeout(() => this._checkStartBroadcastTimer(), nextBcastWait); + } + } + + _query(name) { + DEBUG && debug('query("' + name + '")'); + let packet = new DNSPacket(); + packet.setFlag('QR', DNS_QUERY_RESPONSE_CODES.QUERY); + + // PTR Record + packet.addRecord('QD', new DNSRecord({ + name: name, + recordType: DNS_RECORD_TYPES.PTR, + classCode: DNS_CLASS_CODES.IN, + cacheFlush: true + })); + + let data = packet.serialize(); + + // Initialize the broadcast receiver socket in case it + // hasn't already been started so we can listen for + // multicast queries/announcements on all interfaces. + this._getBroadcastReceiverSocket(); + + this._getQuerySocket().then((querySocket) => { + DEBUG && debug('sending query on query socket ("' + name + '")'); + querySocket.send(MDNS_MULTICAST_GROUP, MDNS_PORT, data, data.length); + }); + + // Automatically announce previously-discovered + // services that match and haven't expired yet. + setTimeout(() => { + DEBUG && debug('announcing previously discovered services ("' + name + '")'); + let { serviceType } = _parseServiceDomainName(name); + + this._clearExpiredDiscoveries(); + this._discovered.forEach((discovery, key) => { + let serviceInfo = discovery.serviceInfo; + if (serviceInfo.serviceType !== serviceType) { + return; + } + + let listeners = this._listeners.get(serviceInfo.serviceType) || []; + listeners.forEach((listener) => { + listener.onServiceFound(serviceInfo); + }); + }); + }); + } + + _clearExpiredDiscoveries() { + this._discovered.forEach((discovery, key) => { + if (discovery.expireTime < Date.now()) { + this._discovered.delete(key); + return; + } + }); + } + + _handleQueryPacket(packet, message) { + packet.getRecords(['QD']).forEach((record) => { + // Don't respond if the query's class code is not IN or ANY. + if (record.classCode !== DNS_CLASS_CODES.IN && + record.classCode !== DNS_CLASS_CODES.ANY) { + return; + } + + // Don't respond if the query's record type is not PTR or ANY. + if (record.recordType !== DNS_RECORD_TYPES.PTR && + record.recordType !== DNS_RECORD_TYPES.ANY) { + return; + } + + for (let [serviceKey, publishedService] of this._services) { + DEBUG && debug("_handleQueryPacket: " + packet.toJSON()); + if (publishedService.ptrMatch(record.name)) { + this._respondToQuery(serviceKey, message); + } + } + }); + } + + _makeServicePacket(service, addresses) { + let packet = new DNSPacket(); + packet.setFlag('QR', DNS_QUERY_RESPONSE_CODES.RESPONSE); + packet.setFlag('AA', DNS_AUTHORITATIVE_ANSWER_CODES.YES); + + let host = service.host || _hostname; + + // e.g.: foo-bar-service._http._tcp.local + let serviceDomainName = service.serviceName + '.' + service.serviceType + '.local'; + + // PTR Record + packet.addRecord('AN', new DNSResourceRecord({ + name: service.serviceType + '.local', // e.g.: _http._tcp.local + recordType: DNS_RECORD_TYPES.PTR, + data: serviceDomainName + })); + + // SRV Record + packet.addRecord('AR', new DNSResourceRecord({ + name: serviceDomainName, + recordType: DNS_RECORD_TYPES.SRV, + classCode: DNS_CLASS_CODES.IN, + cacheFlush: true, + data: { + priority: 0, + weight: 0, + port: service.port, + target: host // e.g.: My-Android-Phone.local + } + })); + + // A Records + for (let address of addresses) { + packet.addRecord('AR', new DNSResourceRecord({ + name: host, + recordType: DNS_RECORD_TYPES.A, + data: address + })); + } + + // TXT Record + packet.addRecord('AR', new DNSResourceRecord({ + name: serviceDomainName, + recordType: DNS_RECORD_TYPES.TXT, + classCode: DNS_CLASS_CODES.IN, + cacheFlush: true, + data: service.serviceAttrs || {} + })); + + return packet; + } + + _handleResponsePacket(packet, message) { + let services = {}; + let hosts = {}; + + let srvRecords = packet.getRecords(['AN', 'AR'], DNS_RECORD_TYPES.SRV); + let txtRecords = packet.getRecords(['AN', 'AR'], DNS_RECORD_TYPES.TXT); + let ptrRecords = packet.getRecords(['AN', 'AR'], DNS_RECORD_TYPES.PTR); + let aRecords = packet.getRecords(['AN', 'AR'], DNS_RECORD_TYPES.A); + + srvRecords.forEach((record) => { + let data = record.data || {}; + + services[record.name] = { + host: data.target, + port: data.port, + ttl: record.ttl + }; + }); + + txtRecords.forEach((record) => { + if (!services[record.name]) { + return; + } + + services[record.name].attributes = record.data; + }); + + aRecords.forEach((record) => { + if (IsIpv4Address(record.data)) { + hosts[record.name] = record.data; + } + }); + + ptrRecords.forEach((record) => { + let name = record.data; + if (!services[name]) { + return; + } + + let {host, port} = services[name]; + if (!host || !port) { + return; + } + + let { serviceName, serviceType, domainName } = _parseServiceDomainName(name); + if (!serviceName || !serviceType || !domainName) { + return; + } + + let address = hosts[host]; + if (!address) { + return; + } + + let ttl = services[name].ttl || 0; + let serviceInfo = { + serviceName: serviceName, + serviceType: serviceType, + host: host, + address: address, + port: port, + domainName: domainName, + attributes: services[name].attributes || {} + }; + + this._onServiceFound(serviceInfo, ttl); + }); + } + + _onServiceFound(serviceInfo, ttl = 0) { + let expireTime = Date.now() + (ttl * 1000); + let key = serviceInfo.serviceName + '.' + + serviceInfo.serviceType + '.' + + serviceInfo.domainName + ' @' + + serviceInfo.address + ':' + + serviceInfo.port; + + // If this service was already discovered, just update + // its expiration time and don't re-emit it. + if (this._discovered.has(key)) { + this._discovered.get(key).expireTime = expireTime; + return; + } + + this._discovered.set(key, { + serviceInfo: serviceInfo, + expireTime: expireTime + }); + + let listeners = this._listeners.get(serviceInfo.serviceType) || []; + listeners.forEach((listener) => { + listener.onServiceFound(serviceInfo); + }); + + DEBUG && debug('_onServiceFound()' + serviceInfo.serviceName); + } + + /** + * Gets a non-exclusive socket on 0.0.0.0:{random} to send + * multicast queries on all interfaces. This socket does + * not need to join a multicast group since it is still + * able to *send* multicast queries, but it does not need + * to *listen* for multicast queries/announcements since + * the `_broadcastReceiverSocket` is already handling them. + */ + _getQuerySocket() { + return new Promise((resolve, reject) => { + if (!this._querySocket) { + this._querySocket = _openSocket('0.0.0.0', 0, { + onPacketReceived: this._onPacketReceived.bind(this), + onStopListening: this._onStopListening.bind(this) + }); + } + resolve(this._querySocket); + }); + } + + /** + * Gets a non-exclusive socket on 0.0.0.0:5353 to listen + * for multicast queries/announcements on all interfaces. + * Since this socket needs to listen for multicast queries + * and announcements, this socket joins the multicast + * group on *all* interfaces (0.0.0.0). + */ + _getBroadcastReceiverSocket() { + return new Promise((resolve, reject) => { + if (!this._broadcastReceiverSocket) { + this._broadcastReceiverSocket = _openSocket('0.0.0.0', MDNS_PORT, { + onPacketReceived: this._onPacketReceived.bind(this), + onStopListening: this._onStopListening.bind(this) + }, /* multicastInterface = */ '0.0.0.0'); + } + resolve(this._broadcastReceiverSocket); + }); + } + + /** + * Gets a non-exclusive socket for each interface on + * {iface-ip}:5353 for sending query responses as + * well as for listening for unicast queries. These + * sockets do not need to join a multicast group + * since they are still able to *send* multicast + * query responses, but they do not need to *listen* + * for multicast queries since the `_querySocket` is + * already handling them. + */ + _getSockets() { + return new Promise((resolve) => { + if (this._sockets.size > 0) { + resolve(this._sockets); + return; + } + + Promise.all([getAddresses(), getHostname()]).then(() => { + _addresses.forEach((address) => { + let socket = _openSocket(address, MDNS_PORT, null); + this._sockets.set(address, socket); + }); + + resolve(this._sockets); + }); + }); + } + + _checkCloseSockets() { + // Nothing to do if no sockets to close. + if (this._sockets.size == 0) + return; + + // Don't close sockets if discovery listeners are still present. + if (this._listeners.size > 0) + return; + + // Don't close sockets if advertised services are present. + // Since we need to listen for service queries and respond to them. + if (this._services.size > 0) + return; + + this._closeSockets(); + } + + _closeSockets() { + this._sockets.forEach(socket => socket.close()); + this._sockets.clear(); + } + + _onPacketReceived(socket, message) { + let packet = DNSPacket.parse(message.rawData); + + switch (packet.getFlag('QR')) { + case DNS_QUERY_RESPONSE_CODES.QUERY: + this._handleQueryPacket(packet, message); + break; + case DNS_QUERY_RESPONSE_CODES.RESPONSE: + this._handleResponsePacket(packet, message); + break; + default: + break; + } + } + + _onStopListening(socket, status) { + DEBUG && debug('_onStopListening() ' + status); + } + + _addServiceListener(serviceType, listener) { + let listeners = this._listeners.get(serviceType); + if (!listeners) { + listeners = []; + this._listeners.set(serviceType, listeners); + } + + if (!listeners.find(l => l === listener)) { + listeners.push(listener); + } + } + + _removeServiceListener(serviceType, listener) { + let listeners = this._listeners.get(serviceType); + if (!listeners) { + return; + } + + let index = listeners.findIndex(l => l === listener); + if (index >= 0) { + listeners.splice(index, 1); + } + + if (listeners.length === 0) { + this._listeners.delete(serviceType); + } + } +} + +let _addresses; + +/** + * @private + */ +function getAddresses() { + return new Promise((resolve, reject) => { + if (_addresses) { + resolve(_addresses); + return; + } + + networkInfoService.listNetworkAddresses({ + onListedNetworkAddresses(aAddressArray) { + _addresses = aAddressArray.filter((address) => { + return address.indexOf('%p2p') === -1 && // No WiFi Direct interfaces + address.indexOf(':') === -1 && // XXX: No IPv6 for now + address != "127.0.0.1" // No ipv4 loopback addresses. + }); + + DEBUG && debug('getAddresses(): ' + _addresses); + resolve(_addresses); + }, + + onListNetworkAddressesFailed() { + DEBUG && debug('getAddresses() FAILED!'); + resolve([]); + } + }); + }); +} + +let _hostname; + +/** + * @private + */ +function getHostname() { + return new Promise((resolve) => { + if (_hostname) { + resolve(_hostname); + return; + } + + networkInfoService.getHostname({ + onGotHostname(aHostname) { + _hostname = aHostname.replace(/\s/g, '-') + '.local'; + + DEBUG && debug('getHostname(): ' + _hostname); + resolve(_hostname); + }, + + onGetHostnameFailed() { + DEBUG && debug('getHostname() FAILED'); + resolve('localhost'); + } + }); + }); +} + +/** + * Parse fully qualified domain name to service name, instance name, + * and domain name. See https://tools.ietf.org/html/rfc6763#section-7. + * + * Example: 'foo-bar-service._http._tcp.local' -> { + * serviceName: 'foo-bar-service', + * serviceType: '_http._tcp', + * domainName: 'local' + * } + * + * @private + */ +function _parseServiceDomainName(serviceDomainName) { + let parts = serviceDomainName.split('.'); + let index = Math.max(parts.lastIndexOf('_tcp'), parts.lastIndexOf('_udp')); + + return { + serviceName: parts.splice(0, index - 1).join('.'), + serviceType: parts.splice(0, 2).join('.'), + domainName: parts.join('.') + }; +} + +/** + * @private + */ +function _propertyBagToObject(propBag) { + let result = {}; + if (propBag.QueryInterface) { + propBag.QueryInterface(Ci.nsIPropertyBag2); + let propEnum = propBag.enumerator; + while (propEnum.hasMoreElements()) { + let prop = propEnum.getNext().QueryInterface(Ci.nsIProperty); + result[prop.name] = prop.value.toString(); + } + } else { + for (let name in propBag) { + result[name] = propBag[name].toString(); + } + } + return result; +} + +/** + * @private + */ +function _openSocket(addr, port, handler, multicastInterface) { + let socket = Cc['@mozilla.org/network/udp-socket;1'].createInstance(Ci.nsIUDPSocket); + socket.init2(addr, port, Services.scriptSecurityManager.getSystemPrincipal(), true); + + if (handler) { + socket.asyncListen({ + onPacketReceived: handler.onPacketReceived, + onStopListening: handler.onStopListening + }); + } + + if (multicastInterface) { + socket.joinMulticast(MDNS_MULTICAST_GROUP, multicastInterface); + } + + return socket; +} diff --git a/netwerk/dns/mdns/libmdns/moz.build b/netwerk/dns/mdns/libmdns/moz.build new file mode 100644 index 000000000..d2dca4955 --- /dev/null +++ b/netwerk/dns/mdns/libmdns/moz.build @@ -0,0 +1,56 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa' or \ + (CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk' and CONFIG['ANDROID_VERSION'] >= '16'): + UNIFIED_SOURCES += [ + 'MDNSResponderOperator.cpp', + 'MDNSResponderReply.cpp', + 'nsDNSServiceDiscovery.cpp', + ] + + LOCAL_INCLUDES += [ + '/netwerk/base', + ] + + if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk': + LOCAL_INCLUDES += [ + '%' + '%s/%s' % (CONFIG['ANDROID_SOURCE'], d) for d in [ + 'external/mdnsresponder/mDNSShared', + ] + ] + +else: + EXTRA_COMPONENTS += [ + 'nsDNSServiceDiscovery.js', + 'nsDNSServiceDiscovery.manifest', + ] + + EXTRA_JS_MODULES += [ + 'fallback/DataReader.jsm', + 'fallback/DataWriter.jsm', + 'fallback/DNSPacket.jsm', + 'fallback/DNSRecord.jsm', + 'fallback/DNSResourceRecord.jsm', + 'fallback/DNSTypes.jsm', + 'fallback/MulticastDNS.jsm', + ] + + if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android': + EXTRA_JS_MODULES += [ + 'MulticastDNSAndroid.jsm', + ] + +UNIFIED_SOURCES += [ + 'nsDNSServiceInfo.cpp', + 'nsMulticastDNSModule.cpp', +] + +include('/ipc/chromium/chromium-config.mozbuild') +FINAL_LIBRARY = 'xul' + +if CONFIG['GNU_CXX']: + CXXFLAGS += ['-Wno-error=shadow'] diff --git a/netwerk/dns/mdns/libmdns/nsDNSServiceDiscovery.cpp b/netwerk/dns/mdns/libmdns/nsDNSServiceDiscovery.cpp new file mode 100644 index 000000000..8ffa74b71 --- /dev/null +++ b/netwerk/dns/mdns/libmdns/nsDNSServiceDiscovery.cpp @@ -0,0 +1,285 @@ +/* -*- 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 "nsDNSServiceDiscovery.h" +#include "MDNSResponderOperator.h" +#include "nsICancelable.h" +#include "nsXULAppAPI.h" +#include "private/pprio.h" + +#ifdef MOZ_WIDGET_GONK +#include <cutils/properties.h> +#endif // MOZ_WIDGET_GONK + +namespace mozilla { +namespace net { + +namespace { + +inline void +StartService() +{ +#ifdef MOZ_WIDGET_GONK + char value[PROPERTY_VALUE_MAX] = { '\0' }; + property_get("init.svc.mdnsd", value, ""); + + if (strcmp(value, "running") == 0) { + return; + } + property_set("ctl.start", "mdnsd"); +#endif // MOZ_WIDGET_GONK +} + +inline void +StopService() +{ +#ifdef MOZ_WIDGET_GONK + char value[PROPERTY_VALUE_MAX] = { '\0' }; + property_get("init.svc.mdnsd", value, ""); + + if (strcmp(value, "stopped") == 0) { + return; + } + property_set("ctl.stop", "mdnsd"); +#endif // MOZ_WIDGET_GONK +} + +class ServiceCounter +{ +public: + static bool IsServiceRunning() + { + return !!sUseCount; + } + +private: + static uint32_t sUseCount; + +protected: + ServiceCounter() + { + MOZ_ASSERT(NS_IsMainThread()); + if (!sUseCount++) { + StartService(); + } + } + + virtual ~ServiceCounter() + { + MOZ_ASSERT(NS_IsMainThread()); + if (!--sUseCount) { + StopService(); + } + } +}; + +uint32_t ServiceCounter::sUseCount = 0; + +class DiscoveryRequest final : public nsICancelable + , private ServiceCounter +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSICANCELABLE + + explicit DiscoveryRequest(nsDNSServiceDiscovery* aService, + nsIDNSServiceDiscoveryListener* aListener); + +private: + virtual ~DiscoveryRequest() { Cancel(NS_OK); } + + RefPtr<nsDNSServiceDiscovery> mService; + nsIDNSServiceDiscoveryListener* mListener; +}; + +NS_IMPL_ISUPPORTS(DiscoveryRequest, nsICancelable) + +DiscoveryRequest::DiscoveryRequest(nsDNSServiceDiscovery* aService, + nsIDNSServiceDiscoveryListener* aListener) + : mService(aService) + , mListener(aListener) +{ +} + +NS_IMETHODIMP +DiscoveryRequest::Cancel(nsresult aReason) +{ + if (mService) { + mService->StopDiscovery(mListener); + } + + mService = nullptr; + return NS_OK; +} + +class RegisterRequest final : public nsICancelable + , private ServiceCounter +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSICANCELABLE + + explicit RegisterRequest(nsDNSServiceDiscovery* aService, + nsIDNSRegistrationListener* aListener); + +private: + virtual ~RegisterRequest() { Cancel(NS_OK); } + + RefPtr<nsDNSServiceDiscovery> mService; + nsIDNSRegistrationListener* mListener; +}; + +NS_IMPL_ISUPPORTS(RegisterRequest, nsICancelable) + +RegisterRequest::RegisterRequest(nsDNSServiceDiscovery* aService, + nsIDNSRegistrationListener* aListener) + : mService(aService) + , mListener(aListener) +{ +} + +NS_IMETHODIMP +RegisterRequest::Cancel(nsresult aReason) +{ + if (mService) { + mService->UnregisterService(mListener); + } + + mService = nullptr; + return NS_OK; +} + +} // namespace anonymous + +NS_IMPL_ISUPPORTS(nsDNSServiceDiscovery, nsIDNSServiceDiscovery) + +nsDNSServiceDiscovery::~nsDNSServiceDiscovery() +{ +#ifdef MOZ_WIDGET_GONK + StopService(); +#endif +} + +nsresult +nsDNSServiceDiscovery::Init() +{ + if (!XRE_IsParentProcess()) { + MOZ_ASSERT(false, "nsDNSServiceDiscovery can only be used in parent process"); + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +NS_IMETHODIMP +nsDNSServiceDiscovery::StartDiscovery(const nsACString& aServiceType, + nsIDNSServiceDiscoveryListener* aListener, + nsICancelable** aRetVal) +{ + MOZ_ASSERT(aRetVal); + + nsresult rv; + if (NS_WARN_IF(NS_FAILED(rv = StopDiscovery(aListener)))) { + return rv; + } + + nsCOMPtr<nsICancelable> req = new DiscoveryRequest(this, aListener); + RefPtr<BrowseOperator> browserOp = new BrowseOperator(aServiceType, + aListener); + if (NS_WARN_IF(NS_FAILED(rv = browserOp->Start()))) { + return rv; + } + + mDiscoveryMap.Put(aListener, browserOp); + + req.forget(aRetVal); + + return NS_OK; +} + +NS_IMETHODIMP +nsDNSServiceDiscovery::StopDiscovery(nsIDNSServiceDiscoveryListener* aListener) +{ + nsresult rv; + + RefPtr<BrowseOperator> browserOp; + if (!mDiscoveryMap.Get(aListener, getter_AddRefs(browserOp))) { + return NS_OK; + } + + browserOp->Cancel(); // cancel non-started operation + if (NS_WARN_IF(NS_FAILED(rv = browserOp->Stop()))) { + return rv; + } + + mDiscoveryMap.Remove(aListener); + return NS_OK; +} + +NS_IMETHODIMP +nsDNSServiceDiscovery::RegisterService(nsIDNSServiceInfo* aServiceInfo, + nsIDNSRegistrationListener* aListener, + nsICancelable** aRetVal) +{ + MOZ_ASSERT(aRetVal); + + nsresult rv; + if (NS_WARN_IF(NS_FAILED(rv = UnregisterService(aListener)))) { + return rv; + } + + nsCOMPtr<nsICancelable> req = new RegisterRequest(this, aListener); + RefPtr<RegisterOperator> registerOp = new RegisterOperator(aServiceInfo, + aListener); + if (NS_WARN_IF(NS_FAILED(rv = registerOp->Start()))) { + return rv; + } + + mRegisterMap.Put(aListener, registerOp); + + req.forget(aRetVal); + + return NS_OK; +} + +NS_IMETHODIMP +nsDNSServiceDiscovery::UnregisterService(nsIDNSRegistrationListener* aListener) +{ + nsresult rv; + + RefPtr<RegisterOperator> registerOp; + if (!mRegisterMap.Get(aListener, getter_AddRefs(registerOp))) { + return NS_OK; + } + + registerOp->Cancel(); // cancel non-started operation + if (NS_WARN_IF(NS_FAILED(rv = registerOp->Stop()))) { + return rv; + } + + mRegisterMap.Remove(aListener); + return NS_OK; +} + +NS_IMETHODIMP +nsDNSServiceDiscovery::ResolveService(nsIDNSServiceInfo* aServiceInfo, + nsIDNSServiceResolveListener* aListener) +{ + if (!ServiceCounter::IsServiceRunning()) { + return NS_ERROR_FAILURE; + } + + nsresult rv; + + RefPtr<ResolveOperator> resolveOp = new ResolveOperator(aServiceInfo, + aListener); + if (NS_WARN_IF(NS_FAILED(rv = resolveOp->Start()))) { + return rv; + } + + return NS_OK; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/dns/mdns/libmdns/nsDNSServiceDiscovery.h b/netwerk/dns/mdns/libmdns/nsDNSServiceDiscovery.h new file mode 100644 index 000000000..9bf2c798a --- /dev/null +++ b/netwerk/dns/mdns/libmdns/nsDNSServiceDiscovery.h @@ -0,0 +1,48 @@ +/* -*- 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/. */ + +#ifndef mozilla_netwerk_dns_mdns_libmdns_nsDNSServiceDiscovery_h +#define mozilla_netwerk_dns_mdns_libmdns_nsDNSServiceDiscovery_h + +#include "nsIDNSServiceDiscovery.h" +#include "nsCOMPtr.h" +#include "mozilla/RefPtr.h" +#include "nsRefPtrHashtable.h" + +namespace mozilla { +namespace net { + +class BrowseOperator; +class RegisterOperator; + +class nsDNSServiceDiscovery final : public nsIDNSServiceDiscovery +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIDNSSERVICEDISCOVERY + + explicit nsDNSServiceDiscovery() = default; + + /* + ** The mDNS service is started on demand. If no one uses, mDNS service will not + ** start. Therefore, all operations before service started will fail + ** and get error code |kDNSServiceErr_ServiceNotRunning| defined in dns_sd.h. + **/ + nsresult Init(); + + nsresult StopDiscovery(nsIDNSServiceDiscoveryListener* aListener); + nsresult UnregisterService(nsIDNSRegistrationListener* aListener); + +private: + virtual ~nsDNSServiceDiscovery(); + + nsRefPtrHashtable<nsISupportsHashKey, BrowseOperator> mDiscoveryMap; + nsRefPtrHashtable<nsISupportsHashKey, RegisterOperator> mRegisterMap; +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_netwerk_dns_mdns_libmdns_nsDNSServiceDiscovery_h diff --git a/netwerk/dns/mdns/libmdns/nsDNSServiceDiscovery.js b/netwerk/dns/mdns/libmdns/nsDNSServiceDiscovery.js new file mode 100644 index 000000000..b94f67297 --- /dev/null +++ b/netwerk/dns/mdns/libmdns/nsDNSServiceDiscovery.js @@ -0,0 +1,201 @@ +/* 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/. */ +"use strict"; + +const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components; + +Cu.import("resource://gre/modules/ExtensionUtils.jsm"); +Cu.import('resource://gre/modules/Services.jsm'); +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); + +let { PlatformInfo } = ExtensionUtils; + +if (PlatformInfo.os == "android" && !Services.prefs.getBoolPref("network.mdns.use_js_fallback")) { + Cu.import("resource://gre/modules/MulticastDNSAndroid.jsm"); +} else { + Cu.import("resource://gre/modules/MulticastDNS.jsm"); +} + +const DNSSERVICEDISCOVERY_CID = Components.ID("{f9346d98-f27a-4e89-b744-493843416480}"); +const DNSSERVICEDISCOVERY_CONTRACT_ID = "@mozilla.org/toolkit/components/mdnsresponder/dns-sd;1"; +const DNSSERVICEINFO_CONTRACT_ID = "@mozilla.org/toolkit/components/mdnsresponder/dns-info;1"; + +function log(aMsg) { + dump("-*- nsDNSServiceDiscovery.js : " + aMsg + "\n"); +} + +function generateUuid() { + var uuidGenerator = Components.classes["@mozilla.org/uuid-generator;1"]. + getService(Ci.nsIUUIDGenerator); + return uuidGenerator.generateUUID().toString(); +} + +// Helper class to transform return objects to correct type. +function ListenerWrapper(aListener, aMdns) { + this.listener = aListener; + this.mdns = aMdns; + + this.discoveryStarting = false; + this.stopDiscovery = false; + + this.registrationStarting = false; + this.stopRegistration = false; + + this.uuid = generateUuid(); +} + +ListenerWrapper.prototype = { + // Helper function for transforming an Object into nsIDNSServiceInfo. + makeServiceInfo: function (aServiceInfo) { + let serviceInfo = Cc[DNSSERVICEINFO_CONTRACT_ID].createInstance(Ci.nsIDNSServiceInfo); + + for (let name of ['host', 'address', 'port', 'serviceName', 'serviceType']) { + try { + serviceInfo[name] = aServiceInfo[name]; + } catch (e) { + // ignore exceptions + } + } + + let attributes; + try { + attributes = _toPropertyBag2(aServiceInfo.attributes); + } catch (err) { + // Ignore unset attributes in object. + log("Caught unset attributes error: " + err + " - " + err.stack); + attributes = Cc['@mozilla.org/hash-property-bag;1'] + .createInstance(Ci.nsIWritablePropertyBag2); + } + serviceInfo.attributes = attributes; + + return serviceInfo; + }, + + /* transparent types */ + onDiscoveryStarted: function(aServiceType) { + this.discoveryStarting = false; + this.listener.onDiscoveryStarted(aServiceType); + + if (this.stopDiscovery) { + this.mdns.stopDiscovery(aServiceType, this); + } + }, + onDiscoveryStopped: function(aServiceType) { + this.listener.onDiscoveryStopped(aServiceType); + }, + onStartDiscoveryFailed: function(aServiceType, aErrorCode) { + log('onStartDiscoveryFailed: ' + aServiceType + ' (' + aErrorCode + ')'); + this.discoveryStarting = false; + this.stopDiscovery = true; + this.listener.onStartDiscoveryFailed(aServiceType, aErrorCode); + }, + onStopDiscoveryFailed: function(aServiceType, aErrorCode) { + log('onStopDiscoveryFailed: ' + aServiceType + ' (' + aErrorCode + ')'); + this.listener.onStopDiscoveryFailed(aServiceType, aErrorCode); + }, + + /* transform types */ + onServiceFound: function(aServiceInfo) { + this.listener.onServiceFound(this.makeServiceInfo(aServiceInfo)); + }, + onServiceLost: function(aServiceInfo) { + this.listener.onServiceLost(this.makeServiceInfo(aServiceInfo)); + }, + onServiceRegistered: function(aServiceInfo) { + this.registrationStarting = false; + this.listener.onServiceRegistered(this.makeServiceInfo(aServiceInfo)); + + if (this.stopRegistration) { + this.mdns.unregisterService(aServiceInfo, this); + } + }, + onServiceUnregistered: function(aServiceInfo) { + this.listener.onServiceUnregistered(this.makeServiceInfo(aServiceInfo)); + }, + onServiceResolved: function(aServiceInfo) { + this.listener.onServiceResolved(this.makeServiceInfo(aServiceInfo)); + }, + + onRegistrationFailed: function(aServiceInfo, aErrorCode) { + log('onRegistrationFailed: (' + aErrorCode + ')'); + this.registrationStarting = false; + this.stopRegistration = true; + this.listener.onRegistrationFailed(this.makeServiceInfo(aServiceInfo), aErrorCode); + }, + onUnregistrationFailed: function(aServiceInfo, aErrorCode) { + log('onUnregistrationFailed: (' + aErrorCode + ')'); + this.listener.onUnregistrationFailed(this.makeServiceInfo(aServiceInfo), aErrorCode); + }, + onResolveFailed: function(aServiceInfo, aErrorCode) { + log('onResolveFailed: (' + aErrorCode + ')'); + this.listener.onResolveFailed(this.makeServiceInfo(aServiceInfo), aErrorCode); + } +}; + +function nsDNSServiceDiscovery() { + log("nsDNSServiceDiscovery"); + this.mdns = new MulticastDNS(); +} + +nsDNSServiceDiscovery.prototype = { + classID: DNSSERVICEDISCOVERY_CID, + QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference, Ci.nsIDNSServiceDiscovery]), + + startDiscovery: function(aServiceType, aListener) { + log("startDiscovery"); + let listener = new ListenerWrapper(aListener, this.mdns); + listener.discoveryStarting = true; + this.mdns.startDiscovery(aServiceType, listener); + + return { + QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable]), + cancel: (function() { + if (this.discoveryStarting || this.stopDiscovery) { + this.stopDiscovery = true; + return; + } + this.mdns.stopDiscovery(aServiceType, listener); + }).bind(listener) + }; + }, + + registerService: function(aServiceInfo, aListener) { + log("registerService"); + let listener = new ListenerWrapper(aListener, this.mdns); + listener.registrationStarting = true; + this.mdns.registerService(aServiceInfo, listener); + + return { + QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable]), + cancel: (function() { + if (this.registrationStarting || this.stopRegistration) { + this.stopRegistration = true; + return; + } + this.mdns.unregisterService(aServiceInfo, listener); + }).bind(listener) + }; + }, + + resolveService: function(aServiceInfo, aListener) { + log("resolveService"); + this.mdns.resolveService(aServiceInfo, new ListenerWrapper(aListener)); + } +}; + +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([nsDNSServiceDiscovery]); + +function _toPropertyBag2(obj) +{ + if (obj.QueryInterface) { + return obj.QueryInterface(Ci.nsIPropertyBag2); + } + + let result = Cc['@mozilla.org/hash-property-bag;1'] + .createInstance(Ci.nsIWritablePropertyBag2); + for (let name in obj) { + result.setPropertyAsAString(name, obj[name]); + } + return result; +} diff --git a/netwerk/dns/mdns/libmdns/nsDNSServiceDiscovery.manifest b/netwerk/dns/mdns/libmdns/nsDNSServiceDiscovery.manifest new file mode 100644 index 000000000..c17e719f2 --- /dev/null +++ b/netwerk/dns/mdns/libmdns/nsDNSServiceDiscovery.manifest @@ -0,0 +1,3 @@ +# nsDNSServiceDiscovery.js +component {f9346d98-f27a-4e89-b744-493843416480} nsDNSServiceDiscovery.js +contract @mozilla.org/toolkit/components/mdnsresponder/dns-sd;1 {f9346d98-f27a-4e89-b744-493843416480} diff --git a/netwerk/dns/mdns/libmdns/nsDNSServiceInfo.cpp b/netwerk/dns/mdns/libmdns/nsDNSServiceInfo.cpp new file mode 100644 index 000000000..7c67b49ac --- /dev/null +++ b/netwerk/dns/mdns/libmdns/nsDNSServiceInfo.cpp @@ -0,0 +1,209 @@ +/* -*- 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 "nsDNSServiceInfo.h" +#include "nsHashPropertyBag.h" +#include "nsIProperty.h" +#include "nsISimpleEnumerator.h" +#include "nsISupportsImpl.h" +#include "mozilla/Unused.h" + +namespace mozilla { +namespace net { + +NS_IMPL_ISUPPORTS(nsDNSServiceInfo, nsIDNSServiceInfo) + +nsDNSServiceInfo::nsDNSServiceInfo(nsIDNSServiceInfo* aServiceInfo) +{ + if (NS_WARN_IF(!aServiceInfo)) { + return; + } + + nsAutoCString str; + uint16_t value; + + if (NS_SUCCEEDED(aServiceInfo->GetHost(str))) { + Unused << NS_WARN_IF(NS_FAILED(SetHost(str))); + } + if (NS_SUCCEEDED(aServiceInfo->GetAddress(str))) { + Unused << NS_WARN_IF(NS_FAILED(SetAddress(str))); + } + if (NS_SUCCEEDED(aServiceInfo->GetPort(&value))) { + Unused << NS_WARN_IF(NS_FAILED(SetPort(value))); + } + if (NS_SUCCEEDED(aServiceInfo->GetServiceName(str))) { + Unused << NS_WARN_IF(NS_FAILED(SetServiceName(str))); + } + if (NS_SUCCEEDED(aServiceInfo->GetServiceType(str))) { + Unused << NS_WARN_IF(NS_FAILED(SetServiceType(str))); + } + if (NS_SUCCEEDED(aServiceInfo->GetDomainName(str))) { + Unused << NS_WARN_IF(NS_FAILED(SetDomainName(str))); + } + + nsCOMPtr<nsIPropertyBag2> attributes; // deep copy + if (NS_SUCCEEDED(aServiceInfo->GetAttributes(getter_AddRefs(attributes)))) { + nsCOMPtr<nsISimpleEnumerator> enumerator; + if (NS_WARN_IF(NS_FAILED(attributes->GetEnumerator(getter_AddRefs(enumerator))))) { + return; + } + + nsCOMPtr<nsIWritablePropertyBag2> newAttributes = new nsHashPropertyBag(); + + bool hasMoreElements; + while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMoreElements)) && + hasMoreElements) { + nsCOMPtr<nsISupports> element; + Unused << + NS_WARN_IF(NS_FAILED(enumerator->GetNext(getter_AddRefs(element)))); + nsCOMPtr<nsIProperty> property = do_QueryInterface(element); + MOZ_ASSERT(property); + + nsAutoString name; + nsCOMPtr<nsIVariant> value; + Unused << NS_WARN_IF(NS_FAILED(property->GetName(name))); + Unused << NS_WARN_IF(NS_FAILED(property->GetValue(getter_AddRefs(value)))); + nsAutoCString valueStr; + Unused << NS_WARN_IF(NS_FAILED(value->GetAsACString(valueStr))); + + Unused << NS_WARN_IF(NS_FAILED(newAttributes->SetPropertyAsACString(name, valueStr))); + } + + Unused << NS_WARN_IF(NS_FAILED(SetAttributes(newAttributes))); + } +} + +NS_IMETHODIMP +nsDNSServiceInfo::GetHost(nsACString& aHost) +{ + if (!mIsHostSet) { + return NS_ERROR_NOT_INITIALIZED; + } + aHost = mHost; + return NS_OK; +} + +NS_IMETHODIMP +nsDNSServiceInfo::SetHost(const nsACString& aHost) +{ + mHost = aHost; + mIsHostSet = true; + return NS_OK; +} + +NS_IMETHODIMP +nsDNSServiceInfo::GetAddress(nsACString& aAddress) +{ + if (!mIsAddressSet) { + return NS_ERROR_NOT_INITIALIZED; + } + aAddress = mAddress; + return NS_OK; +} + +NS_IMETHODIMP +nsDNSServiceInfo::SetAddress(const nsACString& aAddress) +{ + mAddress = aAddress; + mIsAddressSet = true; + return NS_OK; +} + +NS_IMETHODIMP +nsDNSServiceInfo::GetPort(uint16_t* aPort) +{ + if (NS_WARN_IF(!aPort)) { + return NS_ERROR_INVALID_ARG; + } + if (!mIsPortSet) { + return NS_ERROR_NOT_INITIALIZED; + } + *aPort = mPort; + return NS_OK; +} + +NS_IMETHODIMP +nsDNSServiceInfo::SetPort(uint16_t aPort) +{ + mPort = aPort; + mIsPortSet = true; + return NS_OK; +} + +NS_IMETHODIMP +nsDNSServiceInfo::GetServiceName(nsACString& aServiceName) +{ + if (!mIsServiceNameSet) { + return NS_ERROR_NOT_INITIALIZED; + } + aServiceName = mServiceName; + return NS_OK; +} + +NS_IMETHODIMP +nsDNSServiceInfo::SetServiceName(const nsACString& aServiceName) +{ + mServiceName = aServiceName; + mIsServiceNameSet = true; + return NS_OK; +} + +NS_IMETHODIMP +nsDNSServiceInfo::GetServiceType(nsACString& aServiceType) +{ + if (!mIsServiceTypeSet) { + return NS_ERROR_NOT_INITIALIZED; + } + aServiceType = mServiceType; + return NS_OK; +} + +NS_IMETHODIMP +nsDNSServiceInfo::SetServiceType(const nsACString& aServiceType) +{ + mServiceType = aServiceType; + mIsServiceTypeSet = true; + return NS_OK; +} + +NS_IMETHODIMP +nsDNSServiceInfo::GetDomainName(nsACString& aDomainName) +{ + if (!mIsDomainNameSet) { + return NS_ERROR_NOT_INITIALIZED; + } + aDomainName = mDomainName; + return NS_OK; +} + +NS_IMETHODIMP +nsDNSServiceInfo::SetDomainName(const nsACString& aDomainName) +{ + mDomainName = aDomainName; + mIsDomainNameSet = true; + return NS_OK; +} + +NS_IMETHODIMP +nsDNSServiceInfo::GetAttributes(nsIPropertyBag2** aAttributes) +{ + if (!mIsAttributesSet) { + return NS_ERROR_NOT_INITIALIZED; + } + nsCOMPtr<nsIPropertyBag2> attributes(mAttributes); + attributes.forget(aAttributes); + return NS_OK; +} + +NS_IMETHODIMP +nsDNSServiceInfo::SetAttributes(nsIPropertyBag2* aAttributes) +{ + mAttributes = aAttributes; + mIsAttributesSet = aAttributes ? true : false; + return NS_OK; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/dns/mdns/libmdns/nsDNSServiceInfo.h b/netwerk/dns/mdns/libmdns/nsDNSServiceInfo.h new file mode 100644 index 000000000..cca9c5d01 --- /dev/null +++ b/netwerk/dns/mdns/libmdns/nsDNSServiceInfo.h @@ -0,0 +1,50 @@ +/* -*- 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/. */ + +#ifndef mozilla_netwerk_dns_mdns_libmdns_nsDNSServiceInfo_h +#define mozilla_netwerk_dns_mdns_libmdns_nsDNSServiceInfo_h + +#include "nsCOMPtr.h" +#include "nsIDNSServiceDiscovery.h" +#include "nsIPropertyBag2.h" +#include "nsString.h" + +namespace mozilla { +namespace net { + +class nsDNSServiceInfo final : public nsIDNSServiceInfo +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIDNSSERVICEINFO + + explicit nsDNSServiceInfo() = default; + explicit nsDNSServiceInfo(nsIDNSServiceInfo* aServiceInfo); + +private: + virtual ~nsDNSServiceInfo() = default; + +private: + nsCString mHost; + nsCString mAddress; + uint16_t mPort = 0; + nsCString mServiceName; + nsCString mServiceType; + nsCString mDomainName; + nsCOMPtr<nsIPropertyBag2> mAttributes; + + bool mIsHostSet = false; + bool mIsAddressSet = false; + bool mIsPortSet = false; + bool mIsServiceNameSet = false; + bool mIsServiceTypeSet = false; + bool mIsDomainNameSet = false; + bool mIsAttributesSet = false; +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_netwerk_dns_mdns_libmdns_nsDNSServiceInfo_h diff --git a/netwerk/dns/mdns/libmdns/nsMulticastDNSModule.cpp b/netwerk/dns/mdns/libmdns/nsMulticastDNSModule.cpp new file mode 100644 index 000000000..22bad3bc7 --- /dev/null +++ b/netwerk/dns/mdns/libmdns/nsMulticastDNSModule.cpp @@ -0,0 +1,61 @@ +/* -*- 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/. */ + +#if defined(MOZ_WIDGET_COCOA) || (defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 16) +#define ENABLE_DNS_SERVICE_DISCOVERY +#endif + +#include "mozilla/ModuleUtils.h" + +#ifdef ENABLE_DNS_SERVICE_DISCOVERY +#include "nsDNSServiceDiscovery.h" +#endif + +#include "nsDNSServiceInfo.h" + +#ifdef ENABLE_DNS_SERVICE_DISCOVERY +using mozilla::net::nsDNSServiceDiscovery; +#define DNSSERVICEDISCOVERY_CID \ + {0x8df43d23, 0xd3f9, 0x4dd5, \ + { 0xb9, 0x65, 0xde, 0x2c, 0xa3, 0xf6, 0xa4, 0x2c }} +NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsDNSServiceDiscovery, Init) +NS_DEFINE_NAMED_CID(DNSSERVICEDISCOVERY_CID); +#endif // ENABLE_DNS_SERVICE_DISCOVERY + +using mozilla::net::nsDNSServiceInfo; +#define DNSSERVICEINFO_CID \ + {0x14a50f2b, 0x7ff6, 0x48a5, \ + { 0x88, 0xe3, 0x61, 0x5f, 0xd1, 0x11, 0xf5, 0xd3 }} +NS_GENERIC_FACTORY_CONSTRUCTOR(nsDNSServiceInfo) +NS_DEFINE_NAMED_CID(DNSSERVICEINFO_CID); + +static const mozilla::Module::CIDEntry knsDNSServiceDiscoveryCIDs[] = { +#ifdef ENABLE_DNS_SERVICE_DISCOVERY + { &kDNSSERVICEDISCOVERY_CID, false, nullptr, nsDNSServiceDiscoveryConstructor }, +#endif + { &kDNSSERVICEINFO_CID, false, nullptr, nsDNSServiceInfoConstructor }, + { nullptr } +}; + +static const mozilla::Module::ContractIDEntry knsDNSServiceDiscoveryContracts[] = { +#ifdef ENABLE_DNS_SERVICE_DISCOVERY + { DNSSERVICEDISCOVERY_CONTRACT_ID, &kDNSSERVICEDISCOVERY_CID }, +#endif + { DNSSERVICEINFO_CONTRACT_ID, &kDNSSERVICEINFO_CID }, + { nullptr } +}; + +static const mozilla::Module::CategoryEntry knsDNSServiceDiscoveryCategories[] = { + { nullptr } +}; + +static const mozilla::Module knsDNSServiceDiscoveryModule = { + mozilla::Module::kVersion, + knsDNSServiceDiscoveryCIDs, + knsDNSServiceDiscoveryContracts, + knsDNSServiceDiscoveryCategories +}; + +NSMODULE_DEFN(nsDNSServiceDiscoveryModule) = &knsDNSServiceDiscoveryModule; diff --git a/netwerk/dns/mdns/moz.build b/netwerk/dns/mdns/moz.build new file mode 100644 index 000000000..190bb48e5 --- /dev/null +++ b/netwerk/dns/mdns/moz.build @@ -0,0 +1,13 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +DIRS += ['libmdns'] + +XPIDL_SOURCES += [ + 'nsIDNSServiceDiscovery.idl', +] + +XPIDL_MODULE = 'necko_mdns' diff --git a/netwerk/dns/mdns/nsIDNSServiceDiscovery.idl b/netwerk/dns/mdns/nsIDNSServiceDiscovery.idl new file mode 100644 index 000000000..23c678ecc --- /dev/null +++ b/netwerk/dns/mdns/nsIDNSServiceDiscovery.idl @@ -0,0 +1,219 @@ +/* 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 "nsISupports.idl" + +interface nsICancelable; +interface nsIPropertyBag2; + +/** + * Service information + */ +[scriptable, uuid(670ed0f9-2fa5-4544-bf1e-ea58ac179374)] +interface nsIDNSServiceInfo : nsISupports +{ + /** + * The host name of the service. (E.g. "Android.local.") + * @throws NS_ERROR_NOT_INITIALIZED when getting unset value. + */ + attribute AUTF8String host; + + /** + * The IP address of the service. + * @throws NS_ERROR_NOT_INITIALIZED when getting unset value. + */ + attribute AUTF8String address; + + /** + * The port number of the service. (E.g. 80) + * @throws NS_ERROR_NOT_INITIALIZED when getting unset value. + */ + attribute unsigned short port; + + /** + * The service name of the service for display. (E.g. "My TV") + * @throws NS_ERROR_NOT_INITIALIZED when getting unset value. + */ + attribute AUTF8String serviceName; + + /** + * The type of the service. (E.g. "_http._tcp") + * @throws NS_ERROR_NOT_INITIALIZED when getting unset value. + */ + attribute AUTF8String serviceType; + + /** + * The domain name of the service. (E.g. "local.") + * @throws NS_ERROR_NOT_INITIALIZED when getting unset value. + */ + attribute AUTF8String domainName; + + /** + * The attributes of the service. + */ + attribute nsIPropertyBag2 attributes; +}; + +/** + * The callback interface for service discovery + */ +[scriptable, uuid(3025b7f2-97bb-435b-b43d-26731b3f5fc4)] +interface nsIDNSServiceDiscoveryListener : nsISupports +{ + /** + * Callback when the discovery begins. + * @param aServiceType + * the service type of |startDiscovery|. + */ + void onDiscoveryStarted(in AUTF8String aServiceType); + + /** + * Callback when the discovery ends. + * @param aServiceType + * the service type of |startDiscovery|. + */ + void onDiscoveryStopped(in AUTF8String aServiceType); + + /** + * Callback when the a service is found. + * @param aServiceInfo + * the info about the found service, where |serviceName|, |aServiceType|, and |domainName| are set. + */ + void onServiceFound(in nsIDNSServiceInfo aServiceInfo); + + /** + * Callback when the a service is lost. + * @param aServiceInfo + * the info about the lost service, where |serviceName|, |aServiceType|, and |domainName| are set. + */ + void onServiceLost(in nsIDNSServiceInfo aServiceInfo); + + /** + * Callback when the discovery cannot start. + * @param aServiceType + * the service type of |startDiscovery|. + * @param aErrorCode + * the error code. + */ + void onStartDiscoveryFailed(in AUTF8String aServiceType, in long aErrorCode); + + /** + * Callback when the discovery cannot stop. + * @param aServiceType + * the service type of |startDiscovery|. + * @param aErrorCode + * the error code. + */ + void onStopDiscoveryFailed(in AUTF8String aServiceType, in long aErrorCode); +}; + +/** + * The callback interface for service registration + */ +[scriptable, uuid(e165e4be-abf4-4963-a66d-ed3ca116e5e4)] +interface nsIDNSRegistrationListener : nsISupports +{ + const long ERROR_SERVICE_NOT_RUNNING = -65563; + + /** + * Callback when the service is registered successfully. + * @param aServiceInfo + * the info about the registered service, + * where |serviceName|, |aServiceType|, and |domainName| are set. + */ + void onServiceRegistered(in nsIDNSServiceInfo aServiceInfo); + + /** + * Callback when the service is unregistered successfully. + * @param aServiceInfo + * the info about the unregistered service. + */ + void onServiceUnregistered(in nsIDNSServiceInfo aServiceInfo); + + /** + * Callback when the service cannot be registered. + * @param aServiceInfo + * the info about the service to be registered. + * @param aErrorCode + * the error code. + */ + void onRegistrationFailed(in nsIDNSServiceInfo aServiceInfo, in long aErrorCode); + + /** + * Callback when the service cannot be unregistered. + * @param aServiceInfo + * the info about the registered service. + * @param aErrorCode + * the error code. + */ + void onUnregistrationFailed(in nsIDNSServiceInfo aServiceInfo, in long aErrorCode); +}; + +/** + * The callback interface for service resolve + */ +[scriptable, uuid(24ee6408-648e-421d-accf-c6e5adeccf97)] +interface nsIDNSServiceResolveListener : nsISupports +{ + /** + * Callback when the service is resolved successfully. + * @param aServiceInfo + * the info about the resolved service, where |host| and |port| are set. + */ + void onServiceResolved(in nsIDNSServiceInfo aServiceInfo); + + /** + * Callback when the service cannot be resolved. + * @param aServiceInfo + * the info about the service to be resolved. + * @param aErrorCode + * the error code. + */ + void onResolveFailed(in nsIDNSServiceInfo aServiceInfo, in long aErrorCode); +}; + +/** + * The interface for DNS service discovery/registration/resolve + */ +[scriptable, uuid(6487899b-beb1-455a-ba65-e4fd465066d7)] +interface nsIDNSServiceDiscovery : nsISupports +{ + /** + * Browse for instances of a service. + * @param aServiceType + * the service type to be discovered, E.g. "_http._tcp". + * @param aListener + * callback interface for discovery notifications. + * @return An object that can be used to cancel the service discovery. + */ + nsICancelable startDiscovery(in AUTF8String aServiceType, in nsIDNSServiceDiscoveryListener aListener); + + /** + * Register a service that is discovered via |startDiscovery| and |resolveService| calls. + * @param aServiceInfo + * the service information to be registered. + * |port| and |aServiceType| are required attributes. + * @param aListener + * callback interface for registration notifications. + * @return An object that can be used to cancel the service registration. + */ + nsICancelable registerService(in nsIDNSServiceInfo aServiceInfo, in nsIDNSRegistrationListener aListener); + + /** + * Resolve a service name discovered via |startDiscovery| to a target host name, port number. + * @param aServiceInfo + * the service information to be registered. + * |serviceName|, |aServiceType|, and |domainName| are required attributes as reported to the |onServiceFound| callback. + * @param aListener + * callback interface for registration notifications. + */ + void resolveService(in nsIDNSServiceInfo aServiceInfo, in nsIDNSServiceResolveListener aListener); +}; + +%{ C++ +#define DNSSERVICEDISCOVERY_CONTRACT_ID \ + "@mozilla.org/toolkit/components/mdnsresponder/dns-sd;1" +#define DNSSERVICEINFO_CONTRACT_ID \ + "@mozilla.org/toolkit/components/mdnsresponder/dns-info;1" +%} |