import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo

This commit is contained in:
Roy Tam 2018-01-19 03:59:58 +08:00
commit dcd9973243
150858 changed files with 23884658 additions and 0 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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);
}
});
}
};

View file

@ -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;
}

View file

@ -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
};
}
}

View file

@ -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;
}

View file

@ -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
};

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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']

View file

@ -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

View file

@ -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

View file

@ -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;
}

View file

@ -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}

View file

@ -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

View file

@ -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

View file

@ -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;