summaryrefslogtreecommitdiffstats
path: root/netwerk/dns/mdns
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /netwerk/dns/mdns
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'netwerk/dns/mdns')
-rw-r--r--netwerk/dns/mdns/libmdns/MDNSResponderOperator.cpp779
-rw-r--r--netwerk/dns/mdns/libmdns/MDNSResponderOperator.h152
-rw-r--r--netwerk/dns/mdns/libmdns/MDNSResponderReply.cpp302
-rw-r--r--netwerk/dns/mdns/libmdns/MDNSResponderReply.h164
-rw-r--r--netwerk/dns/mdns/libmdns/MulticastDNSAndroid.jsm244
-rw-r--r--netwerk/dns/mdns/libmdns/fallback/DNSPacket.jsm297
-rw-r--r--netwerk/dns/mdns/libmdns/fallback/DNSRecord.jsm70
-rw-r--r--netwerk/dns/mdns/libmdns/fallback/DNSResourceRecord.jsm221
-rw-r--r--netwerk/dns/mdns/libmdns/fallback/DNSTypes.jsm100
-rw-r--r--netwerk/dns/mdns/libmdns/fallback/DataReader.jsm133
-rw-r--r--netwerk/dns/mdns/libmdns/fallback/DataWriter.jsm98
-rw-r--r--netwerk/dns/mdns/libmdns/fallback/MulticastDNS.jsm875
-rw-r--r--netwerk/dns/mdns/libmdns/moz.build56
-rw-r--r--netwerk/dns/mdns/libmdns/nsDNSServiceDiscovery.cpp285
-rw-r--r--netwerk/dns/mdns/libmdns/nsDNSServiceDiscovery.h48
-rw-r--r--netwerk/dns/mdns/libmdns/nsDNSServiceDiscovery.js201
-rw-r--r--netwerk/dns/mdns/libmdns/nsDNSServiceDiscovery.manifest3
-rw-r--r--netwerk/dns/mdns/libmdns/nsDNSServiceInfo.cpp209
-rw-r--r--netwerk/dns/mdns/libmdns/nsDNSServiceInfo.h50
-rw-r--r--netwerk/dns/mdns/libmdns/nsMulticastDNSModule.cpp61
-rw-r--r--netwerk/dns/mdns/moz.build13
-rw-r--r--netwerk/dns/mdns/nsIDNSServiceDiscovery.idl219
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"
+%}