/* 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 "mozilla/dom/NetDashboardBinding.h" #include "mozilla/dom/ToJSValue.h" #include "mozilla/ErrorNames.h" #include "mozilla/net/Dashboard.h" #include "mozilla/net/HttpInfo.h" #include "mozilla/net/HTTPSSVC.h" #include "mozilla/net/SocketProcessParent.h" #include "nsHttp.h" #include "nsICancelable.h" #include "nsIDNSListener.h" #include "nsIDNSService.h" #include "nsIDNSRecord.h" #include "nsIDNSByTypeRecord.h" #include "nsIInputStream.h" #include "nsINamed.h" #include "nsINetAddr.h" #include "nsISocketTransport.h" #include "nsProxyRelease.h" #include "nsSocketTransportService2.h" #include "nsThreadUtils.h" #include "nsURLHelper.h" #include "mozilla/Logging.h" #include "nsIOService.h" #include "../cache2/CacheFileUtils.h" using mozilla::AutoSafeJSContext; using mozilla::dom::Sequence; using mozilla::dom::ToJSValue; namespace mozilla { namespace net { class SocketData : public nsISupports { public: NS_DECL_THREADSAFE_ISUPPORTS SocketData() = default; uint64_t mTotalSent{0}; uint64_t mTotalRecv{0}; nsTArray mData; nsMainThreadPtrHandle mCallback; nsIEventTarget* mEventTarget{nullptr}; private: virtual ~SocketData() = default; }; static void GetErrorString(nsresult rv, nsAString& errorString); NS_IMPL_ISUPPORTS0(SocketData) class HttpData : public nsISupports { virtual ~HttpData() = default; public: NS_DECL_THREADSAFE_ISUPPORTS HttpData() = default; nsTArray mData; nsMainThreadPtrHandle mCallback; nsIEventTarget* mEventTarget{nullptr}; }; NS_IMPL_ISUPPORTS0(HttpData) class WebSocketRequest : public nsISupports { virtual ~WebSocketRequest() = default; public: NS_DECL_THREADSAFE_ISUPPORTS WebSocketRequest() = default; nsMainThreadPtrHandle mCallback; nsIEventTarget* mEventTarget{nullptr}; }; NS_IMPL_ISUPPORTS0(WebSocketRequest) class DnsData : public nsISupports { virtual ~DnsData() = default; public: NS_DECL_THREADSAFE_ISUPPORTS DnsData() = default; nsTArray mData; nsMainThreadPtrHandle mCallback; nsIEventTarget* mEventTarget{nullptr}; }; NS_IMPL_ISUPPORTS0(DnsData) class ConnectionData : public nsITransportEventSink, public nsITimerCallback, public nsINamed { virtual ~ConnectionData() { if (mTimer) { mTimer->Cancel(); } } public: NS_DECL_THREADSAFE_ISUPPORTS NS_DECL_NSITRANSPORTEVENTSINK NS_DECL_NSITIMERCALLBACK NS_IMETHOD GetName(nsACString& aName) override { aName.AssignLiteral("net::ConnectionData"); return NS_OK; } void StartTimer(uint32_t aTimeout); void StopTimer(); explicit ConnectionData(Dashboard* target) { mDashboard = target; } nsCOMPtr mSocket; nsCOMPtr mStreamIn; nsCOMPtr mTimer; nsMainThreadPtrHandle mCallback; nsIEventTarget* mEventTarget{nullptr}; Dashboard* mDashboard; nsCString mHost; uint32_t mPort{0}; nsCString mProtocol; uint32_t mTimeout{0}; nsString mStatus; }; NS_IMPL_ISUPPORTS(ConnectionData, nsITransportEventSink, nsITimerCallback, nsINamed) class RcwnData : public nsISupports { virtual ~RcwnData() = default; public: NS_DECL_THREADSAFE_ISUPPORTS RcwnData() = default; nsMainThreadPtrHandle mCallback; nsIEventTarget* mEventTarget{nullptr}; }; NS_IMPL_ISUPPORTS0(RcwnData) NS_IMETHODIMP ConnectionData::OnTransportStatus(nsITransport* aTransport, nsresult aStatus, int64_t aProgress, int64_t aProgressMax) { if (aStatus == NS_NET_STATUS_CONNECTED_TO) { StopTimer(); } GetErrorString(aStatus, mStatus); mEventTarget->Dispatch(NewRunnableMethod>( "net::Dashboard::GetConnectionStatus", mDashboard, &Dashboard::GetConnectionStatus, this), NS_DISPATCH_NORMAL); return NS_OK; } NS_IMETHODIMP ConnectionData::Notify(nsITimer* aTimer) { MOZ_ASSERT(aTimer == mTimer); if (mSocket) { mSocket->Close(NS_ERROR_ABORT); mSocket = nullptr; mStreamIn = nullptr; } mTimer = nullptr; mStatus.AssignLiteral(u"NS_ERROR_NET_TIMEOUT"); mEventTarget->Dispatch(NewRunnableMethod>( "net::Dashboard::GetConnectionStatus", mDashboard, &Dashboard::GetConnectionStatus, this), NS_DISPATCH_NORMAL); return NS_OK; } void ConnectionData::StartTimer(uint32_t aTimeout) { if (!mTimer) { mTimer = NS_NewTimer(); } mTimer->InitWithCallback(this, aTimeout * 1000, nsITimer::TYPE_ONE_SHOT); } void ConnectionData::StopTimer() { if (mTimer) { mTimer->Cancel(); mTimer = nullptr; } } class LookupHelper; class LookupArgument : public nsISupports { virtual ~LookupArgument() = default; public: NS_DECL_THREADSAFE_ISUPPORTS LookupArgument(nsIDNSRecord* aRecord, LookupHelper* aHelper) { mRecord = aRecord; mHelper = aHelper; } nsCOMPtr mRecord; RefPtr mHelper; }; NS_IMPL_ISUPPORTS0(LookupArgument) class LookupHelper final : public nsIDNSListener { virtual ~LookupHelper() { if (mCancel) { mCancel->Cancel(NS_ERROR_ABORT); } } public: NS_DECL_THREADSAFE_ISUPPORTS NS_DECL_NSIDNSLISTENER LookupHelper() = default; nsresult ConstructAnswer(LookupArgument* aArgument); nsresult ConstructHTTPSRRAnswer(LookupArgument* aArgument); public: nsCOMPtr mCancel; nsMainThreadPtrHandle mCallback; nsIEventTarget* mEventTarget{nullptr}; nsresult mStatus{NS_ERROR_NOT_INITIALIZED}; }; NS_IMPL_ISUPPORTS(LookupHelper, nsIDNSListener) NS_IMETHODIMP LookupHelper::OnLookupComplete(nsICancelable* aRequest, nsIDNSRecord* aRecord, nsresult aStatus) { MOZ_ASSERT(aRequest == mCancel); mCancel = nullptr; mStatus = aStatus; nsCOMPtr httpsRecord = do_QueryInterface(aRecord); if (httpsRecord) { RefPtr arg = new LookupArgument(aRecord, this); mEventTarget->Dispatch( NewRunnableMethod>( "net::LookupHelper::ConstructHTTPSRRAnswer", this, &LookupHelper::ConstructHTTPSRRAnswer, arg), NS_DISPATCH_NORMAL); return NS_OK; } RefPtr arg = new LookupArgument(aRecord, this); mEventTarget->Dispatch(NewRunnableMethod>( "net::LookupHelper::ConstructAnswer", this, &LookupHelper::ConstructAnswer, arg), NS_DISPATCH_NORMAL); return NS_OK; } nsresult LookupHelper::ConstructAnswer(LookupArgument* aArgument) { nsIDNSRecord* aRecord = aArgument->mRecord; AutoSafeJSContext cx; mozilla::dom::DNSLookupDict dict; dict.mAddress.Construct(); Sequence& addresses = dict.mAddress.Value(); nsCOMPtr record = do_QueryInterface(aRecord); if (NS_SUCCEEDED(mStatus) && record) { dict.mAnswer = true; bool hasMore; record->HasMore(&hasMore); while (hasMore) { nsString* nextAddress = addresses.AppendElement(fallible); if (!nextAddress) { return NS_ERROR_OUT_OF_MEMORY; } nsCString nextAddressASCII; record->GetNextAddrAsString(nextAddressASCII); CopyASCIItoUTF16(nextAddressASCII, *nextAddress); record->HasMore(&hasMore); } } else { dict.mAnswer = false; GetErrorString(mStatus, dict.mError); } JS::Rooted val(cx); if (!ToJSValue(cx, dict, &val)) { return NS_ERROR_FAILURE; } this->mCallback->OnDashboardDataAvailable(val); return NS_OK; } static void CStringToHexString(const nsACString& aIn, nsAString& aOut) { static const char* const lut = "0123456789ABCDEF"; size_t len = aIn.Length(); aOut.SetCapacity(2 * len); for (size_t i = 0; i < aIn.Length(); ++i) { const char c = static_cast(aIn[i]); aOut.Append(lut[(c >> 4) & 0x0F]); aOut.Append(lut[c & 15]); } } nsresult LookupHelper::ConstructHTTPSRRAnswer(LookupArgument* aArgument) { nsCOMPtr httpsRecord = do_QueryInterface(aArgument->mRecord); AutoSafeJSContext cx; mozilla::dom::HTTPSRRLookupDict dict; dict.mRecords.Construct(); Sequence& records = dict.mRecords.Value(); if (NS_SUCCEEDED(mStatus) && httpsRecord) { dict.mAnswer = true; nsTArray> svcbRecords; httpsRecord->GetRecords(svcbRecords); for (const auto& record : svcbRecords) { dom::HTTPSRecord* nextRecord = records.AppendElement(fallible); if (!nextRecord) { return NS_ERROR_OUT_OF_MEMORY; } Unused << record->GetPriority(&nextRecord->mPriority); nsCString name; Unused << record->GetName(name); CopyASCIItoUTF16(name, nextRecord->mTargetName); nsTArray> values; Unused << record->GetValues(values); if (values.IsEmpty()) { continue; } for (const auto& value : values) { uint16_t type; Unused << value->GetType(&type); switch (type) { case SvcParamKeyAlpn: { nextRecord->mAlpn.Construct(); nextRecord->mAlpn.Value().mType = type; nsCOMPtr alpnParam = do_QueryInterface(value); nsTArray alpn; Unused << alpnParam->GetAlpn(alpn); nsAutoCString alpnStr; for (const auto& str : alpn) { alpnStr.Append(str); alpnStr.Append(','); } CopyASCIItoUTF16(Span(alpnStr.BeginReading(), alpnStr.Length() - 1), nextRecord->mAlpn.Value().mAlpn); break; } case SvcParamKeyNoDefaultAlpn: { nextRecord->mNoDefaultAlpn.Construct(); nextRecord->mNoDefaultAlpn.Value().mType = type; break; } case SvcParamKeyPort: { nextRecord->mPort.Construct(); nextRecord->mPort.Value().mType = type; nsCOMPtr portParam = do_QueryInterface(value); Unused << portParam->GetPort(&nextRecord->mPort.Value().mPort); break; } case SvcParamKeyIpv4Hint: { nextRecord->mIpv4Hint.Construct(); nextRecord->mIpv4Hint.Value().mType = type; nsCOMPtr ipv4Param = do_QueryInterface(value); nsTArray> ipv4Hint; Unused << ipv4Param->GetIpv4Hint(ipv4Hint); if (!ipv4Hint.IsEmpty()) { nextRecord->mIpv4Hint.Value().mAddress.Construct(); for (const auto& address : ipv4Hint) { nsString* nextAddress = nextRecord->mIpv4Hint.Value() .mAddress.Value() .AppendElement(fallible); if (!nextAddress) { return NS_ERROR_OUT_OF_MEMORY; } nsCString addressASCII; Unused << address->GetAddress(addressASCII); CopyASCIItoUTF16(addressASCII, *nextAddress); } } break; } case SvcParamKeyIpv6Hint: { nextRecord->mIpv6Hint.Construct(); nextRecord->mIpv6Hint.Value().mType = type; nsCOMPtr ipv6Param = do_QueryInterface(value); nsTArray> ipv6Hint; Unused << ipv6Param->GetIpv6Hint(ipv6Hint); if (!ipv6Hint.IsEmpty()) { nextRecord->mIpv6Hint.Value().mAddress.Construct(); for (const auto& address : ipv6Hint) { nsString* nextAddress = nextRecord->mIpv6Hint.Value() .mAddress.Value() .AppendElement(fallible); if (!nextAddress) { return NS_ERROR_OUT_OF_MEMORY; } nsCString addressASCII; Unused << address->GetAddress(addressASCII); CopyASCIItoUTF16(addressASCII, *nextAddress); } } break; } case SvcParamKeyEchConfig: { nextRecord->mEchConfig.Construct(); nextRecord->mEchConfig.Value().mType = type; nsCOMPtr echConfigParam = do_QueryInterface(value); nsCString echConfigStr; Unused << echConfigParam->GetEchconfig(echConfigStr); CStringToHexString(echConfigStr, nextRecord->mEchConfig.Value().mEchConfig); break; } case SvcParamKeyODoHConfig: { nextRecord->mODoHConfig.Construct(); nextRecord->mODoHConfig.Value().mType = type; nsCOMPtr ODoHConfigParam = do_QueryInterface(value); nsCString ODoHConfigStr; Unused << ODoHConfigParam->GetODoHConfig(ODoHConfigStr); CStringToHexString(ODoHConfigStr, nextRecord->mODoHConfig.Value().mODoHConfig); break; } default: break; } } } } else { dict.mAnswer = false; GetErrorString(mStatus, dict.mError); } JS::Rooted val(cx); if (!ToJSValue(cx, dict, &val)) { return NS_ERROR_FAILURE; } this->mCallback->OnDashboardDataAvailable(val); return NS_OK; } NS_IMPL_ISUPPORTS(Dashboard, nsIDashboard, nsIDashboardEventNotifier) Dashboard::Dashboard() { mEnableLogging = false; } NS_IMETHODIMP Dashboard::RequestSockets(nsINetDashboardCallback* aCallback) { RefPtr socketData = new SocketData(); socketData->mCallback = new nsMainThreadPtrHolder( "nsINetDashboardCallback", aCallback, true); socketData->mEventTarget = GetCurrentSerialEventTarget(); if (nsIOService::UseSocketProcess()) { if (!gIOService->SocketProcessReady()) { return NS_ERROR_NOT_AVAILABLE; } RefPtr self(this); SocketProcessParent::GetSingleton()->SendGetSocketData()->Then( GetMainThreadSerialEventTarget(), __func__, [self{std::move(self)}, socketData{std::move(socketData)}](SocketDataArgs&& args) { socketData->mData.Assign(args.info()); socketData->mTotalSent = args.totalSent(); socketData->mTotalRecv = args.totalRecv(); socketData->mEventTarget->Dispatch( NewRunnableMethod>( "net::Dashboard::GetSockets", self, &Dashboard::GetSockets, socketData), NS_DISPATCH_NORMAL); }, [self](const mozilla::ipc::ResponseRejectReason) {}); return NS_OK; } gSocketTransportService->Dispatch( NewRunnableMethod>( "net::Dashboard::GetSocketsDispatch", this, &Dashboard::GetSocketsDispatch, socketData), NS_DISPATCH_NORMAL); return NS_OK; } nsresult Dashboard::GetSocketsDispatch(SocketData* aSocketData) { RefPtr socketData = aSocketData; if (gSocketTransportService) { gSocketTransportService->GetSocketConnections(&socketData->mData); socketData->mTotalSent = gSocketTransportService->GetSentBytes(); socketData->mTotalRecv = gSocketTransportService->GetReceivedBytes(); } socketData->mEventTarget->Dispatch( NewRunnableMethod>("net::Dashboard::GetSockets", this, &Dashboard::GetSockets, socketData), NS_DISPATCH_NORMAL); return NS_OK; } nsresult Dashboard::GetSockets(SocketData* aSocketData) { RefPtr socketData = aSocketData; AutoSafeJSContext cx; mozilla::dom::SocketsDict dict; dict.mSockets.Construct(); dict.mSent = 0; dict.mReceived = 0; Sequence& sockets = dict.mSockets.Value(); uint32_t length = socketData->mData.Length(); if (!sockets.SetCapacity(length, fallible)) { JS_ReportOutOfMemory(cx); return NS_ERROR_OUT_OF_MEMORY; } for (uint32_t i = 0; i < socketData->mData.Length(); i++) { dom::SocketElement& mSocket = *sockets.AppendElement(fallible); CopyASCIItoUTF16(socketData->mData[i].host, mSocket.mHost); mSocket.mPort = socketData->mData[i].port; mSocket.mActive = socketData->mData[i].active; CopyASCIItoUTF16(socketData->mData[i].type, mSocket.mType); mSocket.mSent = (double)socketData->mData[i].sent; mSocket.mReceived = (double)socketData->mData[i].received; dict.mSent += socketData->mData[i].sent; dict.mReceived += socketData->mData[i].received; } dict.mSent += socketData->mTotalSent; dict.mReceived += socketData->mTotalRecv; JS::Rooted val(cx); if (!ToJSValue(cx, dict, &val)) return NS_ERROR_FAILURE; socketData->mCallback->OnDashboardDataAvailable(val); return NS_OK; } NS_IMETHODIMP Dashboard::RequestHttpConnections(nsINetDashboardCallback* aCallback) { RefPtr httpData = new HttpData(); httpData->mCallback = new nsMainThreadPtrHolder( "nsINetDashboardCallback", aCallback, true); httpData->mEventTarget = GetCurrentSerialEventTarget(); if (nsIOService::UseSocketProcess()) { if (!gIOService->SocketProcessReady()) { return NS_ERROR_NOT_AVAILABLE; } RefPtr self(this); SocketProcessParent::GetSingleton()->SendGetHttpConnectionData()->Then( GetMainThreadSerialEventTarget(), __func__, [self{std::move(self)}, httpData](nsTArray&& params) { httpData->mData.Assign(std::move(params)); self->GetHttpConnections(httpData); httpData->mEventTarget->Dispatch( NewRunnableMethod>( "net::Dashboard::GetHttpConnections", self, &Dashboard::GetHttpConnections, httpData), NS_DISPATCH_NORMAL); }, [self](const mozilla::ipc::ResponseRejectReason) {}); return NS_OK; } gSocketTransportService->Dispatch(NewRunnableMethod>( "net::Dashboard::GetHttpDispatch", this, &Dashboard::GetHttpDispatch, httpData), NS_DISPATCH_NORMAL); return NS_OK; } nsresult Dashboard::GetHttpDispatch(HttpData* aHttpData) { RefPtr httpData = aHttpData; HttpInfo::GetHttpConnectionData(&httpData->mData); httpData->mEventTarget->Dispatch( NewRunnableMethod>("net::Dashboard::GetHttpConnections", this, &Dashboard::GetHttpConnections, httpData), NS_DISPATCH_NORMAL); return NS_OK; } nsresult Dashboard::GetHttpConnections(HttpData* aHttpData) { RefPtr httpData = aHttpData; AutoSafeJSContext cx; mozilla::dom::HttpConnDict dict; dict.mConnections.Construct(); using mozilla::dom::DnsAndSockInfoDict; using mozilla::dom::HttpConnectionElement; using mozilla::dom::HttpConnInfo; Sequence& connections = dict.mConnections.Value(); uint32_t length = httpData->mData.Length(); if (!connections.SetCapacity(length, fallible)) { JS_ReportOutOfMemory(cx); return NS_ERROR_OUT_OF_MEMORY; } for (uint32_t i = 0; i < httpData->mData.Length(); i++) { HttpConnectionElement& connection = *connections.AppendElement(fallible); CopyASCIItoUTF16(httpData->mData[i].host, connection.mHost); connection.mPort = httpData->mData[i].port; CopyASCIItoUTF16(httpData->mData[i].httpVersion, connection.mHttpVersion); connection.mSsl = httpData->mData[i].ssl; connection.mActive.Construct(); connection.mIdle.Construct(); connection.mDnsAndSocks.Construct(); Sequence& active = connection.mActive.Value(); Sequence& idle = connection.mIdle.Value(); Sequence& dnsAndSocks = connection.mDnsAndSocks.Value(); if (!active.SetCapacity(httpData->mData[i].active.Length(), fallible) || !idle.SetCapacity(httpData->mData[i].idle.Length(), fallible) || !dnsAndSocks.SetCapacity(httpData->mData[i].dnsAndSocks.Length(), fallible)) { JS_ReportOutOfMemory(cx); return NS_ERROR_OUT_OF_MEMORY; } for (uint32_t j = 0; j < httpData->mData[i].active.Length(); j++) { HttpConnInfo& info = *active.AppendElement(fallible); info.mRtt = httpData->mData[i].active[j].rtt; info.mTtl = httpData->mData[i].active[j].ttl; info.mProtocolVersion = httpData->mData[i].active[j].protocolVersion; } for (uint32_t j = 0; j < httpData->mData[i].idle.Length(); j++) { HttpConnInfo& info = *idle.AppendElement(fallible); info.mRtt = httpData->mData[i].idle[j].rtt; info.mTtl = httpData->mData[i].idle[j].ttl; info.mProtocolVersion = httpData->mData[i].idle[j].protocolVersion; } for (uint32_t j = 0; j < httpData->mData[i].dnsAndSocks.Length(); j++) { DnsAndSockInfoDict& info = *dnsAndSocks.AppendElement(fallible); info.mSpeculative = httpData->mData[i].dnsAndSocks[j].speculative; } } JS::Rooted val(cx); if (!ToJSValue(cx, dict, &val)) { return NS_ERROR_FAILURE; } httpData->mCallback->OnDashboardDataAvailable(val); return NS_OK; } NS_IMETHODIMP Dashboard::GetEnableLogging(bool* value) { *value = mEnableLogging; return NS_OK; } NS_IMETHODIMP Dashboard::SetEnableLogging(const bool value) { mEnableLogging = value; return NS_OK; } NS_IMETHODIMP Dashboard::AddHost(const nsACString& aHost, uint32_t aSerial, bool aEncrypted) { if (mEnableLogging) { mozilla::MutexAutoLock lock(mWs.lock); LogData mData(nsCString(aHost), aSerial, aEncrypted); if (mWs.data.Contains(mData)) { return NS_OK; } // XXX(Bug 1631371) Check if this should use a fallible operation as it // pretended earlier. mWs.data.AppendElement(mData); return NS_OK; } return NS_ERROR_FAILURE; } NS_IMETHODIMP Dashboard::RemoveHost(const nsACString& aHost, uint32_t aSerial) { if (mEnableLogging) { mozilla::MutexAutoLock lock(mWs.lock); int32_t index = mWs.IndexOf(nsCString(aHost), aSerial); if (index == -1) return NS_ERROR_FAILURE; mWs.data.RemoveElementAt(index); return NS_OK; } return NS_ERROR_FAILURE; } NS_IMETHODIMP Dashboard::NewMsgSent(const nsACString& aHost, uint32_t aSerial, uint32_t aLength) { if (mEnableLogging) { mozilla::MutexAutoLock lock(mWs.lock); int32_t index = mWs.IndexOf(nsCString(aHost), aSerial); if (index == -1) return NS_ERROR_FAILURE; mWs.data[index].mMsgSent++; mWs.data[index].mSizeSent += aLength; return NS_OK; } return NS_ERROR_FAILURE; } NS_IMETHODIMP Dashboard::NewMsgReceived(const nsACString& aHost, uint32_t aSerial, uint32_t aLength) { if (mEnableLogging) { mozilla::MutexAutoLock lock(mWs.lock); int32_t index = mWs.IndexOf(nsCString(aHost), aSerial); if (index == -1) return NS_ERROR_FAILURE; mWs.data[index].mMsgReceived++; mWs.data[index].mSizeReceived += aLength; return NS_OK; } return NS_ERROR_FAILURE; } NS_IMETHODIMP Dashboard::RequestWebsocketConnections(nsINetDashboardCallback* aCallback) { RefPtr wsRequest = new WebSocketRequest(); wsRequest->mCallback = new nsMainThreadPtrHolder( "nsINetDashboardCallback", aCallback, true); wsRequest->mEventTarget = GetCurrentSerialEventTarget(); wsRequest->mEventTarget->Dispatch( NewRunnableMethod>( "net::Dashboard::GetWebSocketConnections", this, &Dashboard::GetWebSocketConnections, wsRequest), NS_DISPATCH_NORMAL); return NS_OK; } nsresult Dashboard::GetWebSocketConnections(WebSocketRequest* aWsRequest) { RefPtr wsRequest = aWsRequest; AutoSafeJSContext cx; mozilla::dom::WebSocketDict dict; dict.mWebsockets.Construct(); Sequence& websockets = dict.mWebsockets.Value(); mozilla::MutexAutoLock lock(mWs.lock); uint32_t length = mWs.data.Length(); if (!websockets.SetCapacity(length, fallible)) { JS_ReportOutOfMemory(cx); return NS_ERROR_OUT_OF_MEMORY; } for (uint32_t i = 0; i < mWs.data.Length(); i++) { dom::WebSocketElement& websocket = *websockets.AppendElement(fallible); CopyASCIItoUTF16(mWs.data[i].mHost, websocket.mHostport); websocket.mMsgsent = mWs.data[i].mMsgSent; websocket.mMsgreceived = mWs.data[i].mMsgReceived; websocket.mSentsize = mWs.data[i].mSizeSent; websocket.mReceivedsize = mWs.data[i].mSizeReceived; websocket.mEncrypted = mWs.data[i].mEncrypted; } JS::Rooted val(cx); if (!ToJSValue(cx, dict, &val)) { return NS_ERROR_FAILURE; } wsRequest->mCallback->OnDashboardDataAvailable(val); return NS_OK; } NS_IMETHODIMP Dashboard::RequestDNSInfo(nsINetDashboardCallback* aCallback) { RefPtr dnsData = new DnsData(); dnsData->mCallback = new nsMainThreadPtrHolder( "nsINetDashboardCallback", aCallback, true); nsresult rv; dnsData->mData.Clear(); dnsData->mEventTarget = GetCurrentSerialEventTarget(); if (!mDnsService) { mDnsService = do_GetService("@mozilla.org/network/dns-service;1", &rv); if (NS_FAILED(rv)) { return rv; } } if (nsIOService::UseSocketProcess()) { if (!gIOService->SocketProcessReady()) { return NS_ERROR_NOT_AVAILABLE; } RefPtr self(this); SocketProcessParent::GetSingleton()->SendGetDNSCacheEntries()->Then( GetMainThreadSerialEventTarget(), __func__, [self{std::move(self)}, dnsData{std::move(dnsData)}](nsTArray&& entries) { dnsData->mData.Assign(std::move(entries)); dnsData->mEventTarget->Dispatch( NewRunnableMethod>( "net::Dashboard::GetDNSCacheEntries", self, &Dashboard::GetDNSCacheEntries, dnsData), NS_DISPATCH_NORMAL); }, [self](const mozilla::ipc::ResponseRejectReason) {}); return NS_OK; } gSocketTransportService->Dispatch( NewRunnableMethod>("net::Dashboard::GetDnsInfoDispatch", this, &Dashboard::GetDnsInfoDispatch, dnsData), NS_DISPATCH_NORMAL); return NS_OK; } nsresult Dashboard::GetDnsInfoDispatch(DnsData* aDnsData) { RefPtr dnsData = aDnsData; if (mDnsService) { mDnsService->GetDNSCacheEntries(&dnsData->mData); } dnsData->mEventTarget->Dispatch( NewRunnableMethod>("net::Dashboard::GetDNSCacheEntries", this, &Dashboard::GetDNSCacheEntries, dnsData), NS_DISPATCH_NORMAL); return NS_OK; } nsresult Dashboard::GetDNSCacheEntries(DnsData* dnsData) { AutoSafeJSContext cx; mozilla::dom::DNSCacheDict dict; dict.mEntries.Construct(); Sequence& entries = dict.mEntries.Value(); uint32_t length = dnsData->mData.Length(); if (!entries.SetCapacity(length, fallible)) { JS_ReportOutOfMemory(cx); return NS_ERROR_OUT_OF_MEMORY; } for (uint32_t i = 0; i < dnsData->mData.Length(); i++) { dom::DnsCacheEntry& entry = *entries.AppendElement(fallible); entry.mHostaddr.Construct(); Sequence& addrs = entry.mHostaddr.Value(); if (!addrs.SetCapacity(dnsData->mData[i].hostaddr.Length(), fallible)) { JS_ReportOutOfMemory(cx); return NS_ERROR_OUT_OF_MEMORY; } CopyASCIItoUTF16(dnsData->mData[i].hostname, entry.mHostname); entry.mExpiration = dnsData->mData[i].expiration; entry.mTrr = dnsData->mData[i].TRR; for (uint32_t j = 0; j < dnsData->mData[i].hostaddr.Length(); j++) { nsString* addr = addrs.AppendElement(fallible); if (!addr) { JS_ReportOutOfMemory(cx); return NS_ERROR_OUT_OF_MEMORY; } CopyASCIItoUTF16(dnsData->mData[i].hostaddr[j], *addr); } if (dnsData->mData[i].family == PR_AF_INET6) { entry.mFamily.AssignLiteral(u"ipv6"); } else { entry.mFamily.AssignLiteral(u"ipv4"); } entry.mOriginAttributesSuffix = NS_ConvertUTF8toUTF16(dnsData->mData[i].originAttributesSuffix); entry.mFlags = NS_ConvertUTF8toUTF16(dnsData->mData[i].flags); } JS::Rooted val(cx); if (!ToJSValue(cx, dict, &val)) { return NS_ERROR_FAILURE; } dnsData->mCallback->OnDashboardDataAvailable(val); return NS_OK; } NS_IMETHODIMP Dashboard::RequestDNSLookup(const nsACString& aHost, nsINetDashboardCallback* aCallback) { nsresult rv; if (!mDnsService) { mDnsService = do_GetService("@mozilla.org/network/dns-service;1", &rv); if (NS_FAILED(rv)) { return rv; } } RefPtr helper = new LookupHelper(); helper->mCallback = new nsMainThreadPtrHolder( "nsINetDashboardCallback", aCallback, true); helper->mEventTarget = GetCurrentSerialEventTarget(); OriginAttributes attrs; rv = mDnsService->AsyncResolveNative( aHost, nsIDNSService::RESOLVE_TYPE_DEFAULT, nsIDNSService::RESOLVE_DEFAULT_FLAGS, nullptr, helper.get(), NS_GetCurrentThread(), attrs, getter_AddRefs(helper->mCancel)); return rv; } NS_IMETHODIMP Dashboard::RequestDNSHTTPSRRLookup(const nsACString& aHost, nsINetDashboardCallback* aCallback) { nsresult rv; if (!mDnsService) { mDnsService = do_GetService("@mozilla.org/network/dns-service;1", &rv); if (NS_FAILED(rv)) { return rv; } } RefPtr helper = new LookupHelper(); helper->mCallback = new nsMainThreadPtrHolder( "nsINetDashboardCallback", aCallback, true); helper->mEventTarget = GetCurrentSerialEventTarget(); OriginAttributes attrs; rv = mDnsService->AsyncResolveNative( aHost, nsIDNSService::RESOLVE_TYPE_HTTPSSVC, nsIDNSService::RESOLVE_DEFAULT_FLAGS, nullptr, helper.get(), NS_GetCurrentThread(), attrs, getter_AddRefs(helper->mCancel)); return rv; } NS_IMETHODIMP Dashboard::RequestRcwnStats(nsINetDashboardCallback* aCallback) { RefPtr rcwnData = new RcwnData(); rcwnData->mEventTarget = GetCurrentSerialEventTarget(); rcwnData->mCallback = new nsMainThreadPtrHolder( "nsINetDashboardCallback", aCallback, true); return rcwnData->mEventTarget->Dispatch( NewRunnableMethod>("net::Dashboard::GetRcwnData", this, &Dashboard::GetRcwnData, rcwnData), NS_DISPATCH_NORMAL); } nsresult Dashboard::GetRcwnData(RcwnData* aData) { AutoSafeJSContext cx; mozilla::dom::RcwnStatus dict; dict.mTotalNetworkRequests = gIOService->GetTotalRequestNumber(); dict.mRcwnCacheWonCount = gIOService->GetCacheWonRequestNumber(); dict.mRcwnNetWonCount = gIOService->GetNetWonRequestNumber(); uint32_t cacheSlow, cacheNotSlow; CacheFileUtils::CachePerfStats::GetSlowStats(&cacheSlow, &cacheNotSlow); dict.mCacheSlowCount = cacheSlow; dict.mCacheNotSlowCount = cacheNotSlow; dict.mPerfStats.Construct(); Sequence& perfStats = dict.mPerfStats.Value(); uint32_t length = CacheFileUtils::CachePerfStats::LAST; if (!perfStats.SetCapacity(length, fallible)) { JS_ReportOutOfMemory(cx); return NS_ERROR_OUT_OF_MEMORY; } for (uint32_t i = 0; i < length; i++) { CacheFileUtils::CachePerfStats::EDataType perfType = static_cast(i); dom::RcwnPerfStats& elem = *perfStats.AppendElement(fallible); elem.mAvgShort = CacheFileUtils::CachePerfStats::GetAverage(perfType, false); elem.mAvgLong = CacheFileUtils::CachePerfStats::GetAverage(perfType, true); elem.mStddevLong = CacheFileUtils::CachePerfStats::GetStdDev(perfType, true); } JS::Rooted val(cx); if (!ToJSValue(cx, dict, &val)) { return NS_ERROR_FAILURE; } aData->mCallback->OnDashboardDataAvailable(val); return NS_OK; } void HttpConnInfo::SetHTTPProtocolVersion(HttpVersion pv) { switch (pv) { case HttpVersion::v0_9: protocolVersion.AssignLiteral(u"http/0.9"); break; case HttpVersion::v1_0: protocolVersion.AssignLiteral(u"http/1.0"); break; case HttpVersion::v1_1: protocolVersion.AssignLiteral(u"http/1.1"); break; case HttpVersion::v2_0: protocolVersion.AssignLiteral(u"http/2"); break; case HttpVersion::v3_0: protocolVersion.AssignLiteral(u"http/3"); break; default: protocolVersion.AssignLiteral(u"unknown protocol version"); } } NS_IMETHODIMP Dashboard::GetLogPath(nsACString& aLogPath) { aLogPath.SetLength(2048); uint32_t len = LogModule::GetLogFile(aLogPath.BeginWriting(), 2048); aLogPath.SetLength(len); return NS_OK; } NS_IMETHODIMP Dashboard::RequestConnection(const nsACString& aHost, uint32_t aPort, const char* aProtocol, uint32_t aTimeout, nsINetDashboardCallback* aCallback) { nsresult rv; RefPtr connectionData = new ConnectionData(this); connectionData->mHost = aHost; connectionData->mPort = aPort; connectionData->mProtocol = aProtocol; connectionData->mTimeout = aTimeout; connectionData->mCallback = new nsMainThreadPtrHolder( "nsINetDashboardCallback", aCallback, true); connectionData->mEventTarget = GetCurrentSerialEventTarget(); rv = TestNewConnection(connectionData); if (NS_FAILED(rv)) { mozilla::net::GetErrorString(rv, connectionData->mStatus); connectionData->mEventTarget->Dispatch( NewRunnableMethod>( "net::Dashboard::GetConnectionStatus", this, &Dashboard::GetConnectionStatus, connectionData), NS_DISPATCH_NORMAL); return rv; } return NS_OK; } nsresult Dashboard::GetConnectionStatus(ConnectionData* aConnectionData) { RefPtr connectionData = aConnectionData; AutoSafeJSContext cx; mozilla::dom::ConnStatusDict dict; dict.mStatus = connectionData->mStatus; JS::Rooted val(cx); if (!ToJSValue(cx, dict, &val)) return NS_ERROR_FAILURE; connectionData->mCallback->OnDashboardDataAvailable(val); return NS_OK; } nsresult Dashboard::TestNewConnection(ConnectionData* aConnectionData) { RefPtr connectionData = aConnectionData; nsresult rv; if (!connectionData->mHost.Length() || !net_IsValidHostName(connectionData->mHost)) { return NS_ERROR_UNKNOWN_HOST; } if (connectionData->mProtocol.EqualsLiteral("ssl")) { AutoTArray socketTypes = {connectionData->mProtocol}; rv = gSocketTransportService->CreateTransport( socketTypes, connectionData->mHost, connectionData->mPort, nullptr, nullptr, getter_AddRefs(connectionData->mSocket)); } else { rv = gSocketTransportService->CreateTransport( nsTArray(), connectionData->mHost, connectionData->mPort, nullptr, nullptr, getter_AddRefs(connectionData->mSocket)); } if (NS_FAILED(rv)) { return rv; } rv = connectionData->mSocket->SetEventSink(connectionData, GetCurrentSerialEventTarget()); if (NS_FAILED(rv)) { return rv; } rv = connectionData->mSocket->OpenInputStream( nsITransport::OPEN_BLOCKING, 0, 0, getter_AddRefs(connectionData->mStreamIn)); if (NS_FAILED(rv)) { return rv; } connectionData->StartTimer(connectionData->mTimeout); return rv; } using ErrorEntry = struct { nsresult key; const char* error; }; #undef ERROR #define ERROR(key, val) \ { key, #key } ErrorEntry socketTransportStatuses[] = { ERROR(NS_NET_STATUS_RESOLVING_HOST, FAILURE(3)), ERROR(NS_NET_STATUS_RESOLVED_HOST, FAILURE(11)), ERROR(NS_NET_STATUS_CONNECTING_TO, FAILURE(7)), ERROR(NS_NET_STATUS_CONNECTED_TO, FAILURE(4)), ERROR(NS_NET_STATUS_TLS_HANDSHAKE_STARTING, FAILURE(12)), ERROR(NS_NET_STATUS_TLS_HANDSHAKE_ENDED, FAILURE(13)), ERROR(NS_NET_STATUS_SENDING_TO, FAILURE(5)), ERROR(NS_NET_STATUS_WAITING_FOR, FAILURE(10)), ERROR(NS_NET_STATUS_RECEIVING_FROM, FAILURE(6)), }; #undef ERROR static void GetErrorString(nsresult rv, nsAString& errorString) { for (auto& socketTransportStatus : socketTransportStatuses) { if (socketTransportStatus.key == rv) { errorString.AssignASCII(socketTransportStatus.error); return; } } nsAutoCString errorCString; mozilla::GetErrorName(rv, errorCString); CopyUTF8toUTF16(errorCString, errorString); } } // namespace net } // namespace mozilla