From b18d1fbc4a936638b1c1197d45ffc12a3ac1cddf Mon Sep 17 00:00:00 2001 From: Polly Shaw Date: Thu, 7 Jun 2018 23:07:28 +0100 Subject: [PATCH] Bug 356831 - Proxy autodiscovery doesn't check DHCP (option 252) r=bagder,valentin This patch addresses an issue with Firefox's proxy detection on networks which do not have their a proxy auto-configuration (PAC) file hosted at http://wpad/wpad.dat, and instead make use of DHCP option 252 for broadcasting the address of the PAC file. See https://findproxyforurl.com/wpad-introduction/ for an introduction to the protocol. Prior to this patch, proxy auto-detect missed out the DHCP query stage, and just looked for a PAC file at http://wpad/wpad.dat This patch only addresses the issue for Firefox on Windows, although it defines a DHCP client interface which could be implemented on other platforms. The high-level components of this patch are: * nsIDHCPClient.idl - this is an interface which has been defined for querying the DHCP server. * nsPACMan.cpp - where previously when the PAC URL was simply set to a constant of http://wpad/wpad.dat, it now dispatches an asynchronous command to the proxy thread. The class ExecutePACThreadAction has been augmented to include an instruction to 'ConfigureWPAD' (Configure Web-proxy auto-detect), and a new class, 'ConfigureWPADComplete' has been created to relay the result (the URL of the PAC file) back to the nsPACMan object. * nsProtocolProxyService.cpp Minor changes to reflect the fact that the PAC URL not being set does not always mean there is no PAC to be used; instead it could be in the process of being detected. * TestPACMan.cpp This is a new file, and tests only the DHCP auto-detect functionality. Some tests use multiple threads, as they test the non-blocking proxy detection. * DHCPUtils.cpp A class containing the main logic for querying DHCP. * WindowsNetworkFunctionsWrapper.cpp A very thin wrapper around the Windows API calls needed by DHCPUtils. This class was introduced so it could be mocked out in tests. * nsWindowsDHCPClient.cpp * An implementation of the interface defined in nsIDHCPClient.idl. Fairly thin: most logic is implemented in DHCPUtils. * TestDHCPUtils.cpp Tests for DHCPUtils and nsWindowsDHCPClient MozReview-Commit-ID: 4xFQz3tOLEx --HG-- extra : rebase_source : 4fb364432625418acf743b67a2caadc5a92caf7c --- modules/libpref/init/all.js | 1 + netwerk/base/moz.build | 1 + netwerk/base/nsIDHCPClient.idl | 19 ++ netwerk/base/nsPACMan.cpp | 195 +++++++++-- netwerk/base/nsPACMan.h | 58 +++- netwerk/base/nsProtocolProxyService.cpp | 41 +-- netwerk/base/nsProtocolProxyService.h | 1 + netwerk/build/nsNetCID.h | 4 + netwerk/test/gtest/TestPACMan.cpp | 282 +++++++++++++++ netwerk/test/gtest/moz.build | 6 + toolkit/moz.build | 3 +- .../system/windowsDHCPClient/DHCPUtils.cpp | 256 ++++++++++++++ toolkit/system/windowsDHCPClient/DHCPUtils.h | 34 ++ .../WindowsNetworkFunctionsWrapper.cpp | 46 +++ .../WindowsNetworkFunctionsWrapper.h | 60 ++++ toolkit/system/windowsDHCPClient/moz.build | 18 + .../windowsDHCPClient/nsWindowsDHCPClient.cpp | 103 ++++++ .../windowsDHCPClient/nsWindowsDHCPClient.h | 38 +++ .../tests/gtest/TestDHCPUtils.cpp | 323 ++++++++++++++++++ .../windowsDHCPClient/tests/gtest/moz.build | 21 ++ 20 files changed, 1449 insertions(+), 61 deletions(-) create mode 100644 netwerk/base/nsIDHCPClient.idl create mode 100644 netwerk/test/gtest/TestPACMan.cpp create mode 100644 toolkit/system/windowsDHCPClient/DHCPUtils.cpp create mode 100644 toolkit/system/windowsDHCPClient/DHCPUtils.h create mode 100644 toolkit/system/windowsDHCPClient/WindowsNetworkFunctionsWrapper.cpp create mode 100644 toolkit/system/windowsDHCPClient/WindowsNetworkFunctionsWrapper.h create mode 100644 toolkit/system/windowsDHCPClient/moz.build create mode 100644 toolkit/system/windowsDHCPClient/nsWindowsDHCPClient.cpp create mode 100644 toolkit/system/windowsDHCPClient/nsWindowsDHCPClient.h create mode 100644 toolkit/system/windowsDHCPClient/tests/gtest/TestDHCPUtils.cpp create mode 100644 toolkit/system/windowsDHCPClient/tests/gtest/moz.build diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index 427ace832c7d..84aecd7e1c35 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -2229,6 +2229,7 @@ pref("network.proxy.autoconfig_url.include_path", false); // until we reach interval_max or the PAC file is successfully loaded). pref("network.proxy.autoconfig_retry_interval_min", 5); // 5 seconds pref("network.proxy.autoconfig_retry_interval_max", 300); // 5 minutes +pref("network.proxy.enable_wpad_over_dhcp", true); // Use the HSTS preload list by default pref("network.stricttransportsecurity.preloadlist", true); diff --git a/netwerk/base/moz.build b/netwerk/base/moz.build index 1041bcb3682b..df8e71d54b49 100644 --- a/netwerk/base/moz.build +++ b/netwerk/base/moz.build @@ -38,6 +38,7 @@ XPIDL_SOURCES += [ 'nsIDashboard.idl', 'nsIDashboardEventNotifier.idl', 'nsIDeprecationWarner.idl', + 'nsIDHCPClient.idl', 'nsIDivertableChannel.idl', 'nsIDownloader.idl', 'nsIEncodedChannel.idl', diff --git a/netwerk/base/nsIDHCPClient.idl b/netwerk/base/nsIDHCPClient.idl new file mode 100644 index 000000000000..1d986ebd5238 --- /dev/null +++ b/netwerk/base/nsIDHCPClient.idl @@ -0,0 +1,19 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/** + * This interface allows the proxy code to access the DHCP Options in a platform-specific way + */ +[scriptable, uuid(aee75dc0-be1a-46b9-9e0c-31a6899c175c)] +interface nsIDHCPClient : nsISupports +{ + + /** + * returns the DHCP Option designated by the option parameter + */ + ACString getOption(in uint8_t option); +}; diff --git a/netwerk/base/nsPACMan.cpp b/netwerk/base/nsPACMan.cpp index d3ec6e92a13b..e9c2670598d4 100644 --- a/netwerk/base/nsPACMan.cpp +++ b/netwerk/base/nsPACMan.cpp @@ -5,17 +5,20 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "nsPACMan.h" -#include "nsThreadUtils.h" + +#include "mozilla/Preferences.h" +#include "nsContentUtils.h" +#include "nsIAsyncVerifyRedirectCallback.h" #include "nsIAuthPrompt.h" -#include "nsIPromptFactory.h" +#include "nsIDHCPClient.h" #include "nsIHttpChannel.h" #include "nsIPrefService.h" #include "nsIPrefBranch.h" -#include "nsNetUtil.h" -#include "nsIAsyncVerifyRedirectCallback.h" +#include "nsIPromptFactory.h" +#include "nsIProtocolProxyService.h" #include "nsISystemProxySettings.h" -#include "nsContentUtils.h" -#include "mozilla/Preferences.h" +#include "nsNetUtil.h" +#include "nsThreadUtils.h" //----------------------------------------------------------------------------- @@ -26,6 +29,8 @@ LazyLogModule gProxyLog("proxy"); #undef LOG #define LOG(args) MOZ_LOG(gProxyLog, LogLevel::Debug, args) +#define MOZ_WPAD_URL "http://wpad/wpad.dat" +#define MOZ_DHCP_WPAD_OPTION 252 // The PAC thread does evaluations of both PAC files and // nsISystemProxySettings because they can both block the calling thread and we @@ -75,6 +80,26 @@ GetExtraJSContextHeapSize() return extraSize < 0 ? 0 : extraSize; } +// Read network proxy type from preference +// Used to verify that the preference is WPAD in nsPACMan::ConfigureWPAD +nsresult +GetNetworkProxyTypeFromPref(int32_t* type) +{ + *type = 0; + nsCOMPtr prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); + + if (!prefs) { + LOG(("Failed to get a preference service object")); + return NS_ERROR_FACTORY_NOT_REGISTERED; + } + nsresult rv = prefs->GetIntPref("network.proxy.type", type); + if (!NS_SUCCEEDED(rv)) { + LOG(("Failed to retrieve network.proxy.type from prefs")); + return rv; + } + LOG(("network.proxy.type pref retrieved: %d\n", *type)); + return NS_OK; +} //----------------------------------------------------------------------------- @@ -92,12 +117,12 @@ public: { } - void SetPACString(const nsCString &pacString) + void SetPACString(const nsACString &pacString) { mPACString = pacString; } - void SetPACURL(const nsCString &pacURL) + void SetPACURL(const nsACString &pacURL) { mPACURL = pacURL; } @@ -191,14 +216,43 @@ public: } private: - RefPtr mPACMan; + RefPtr mPACMan; +}; + + +//----------------------------------------------------------------------------- + +// ConfigureWPADComplete allows the PAC thread to tell the main thread that +// the URL for the PAC file has been found +class ConfigureWPADComplete final : public Runnable +{ +public: + ConfigureWPADComplete(nsPACMan *aPACMan, const nsACString &aPACURISpec) + : Runnable("net::ConfigureWPADComplete"), + mPACMan(aPACMan), + mPACURISpec(aPACURISpec) + { + } + + NS_IMETHOD Run() override + { + MOZ_ASSERT(NS_IsMainThread(), "wrong thread"); + mPACMan->AssignPACURISpec(mPACURISpec); + mPACMan->ContinueLoadingAfterPACUriKnown(); + return NS_OK; + } + +private: + RefPtr mPACMan; + nsCString mPACURISpec; }; //----------------------------------------------------------------------------- // ExecutePACThreadAction is used to proxy actions from the main -// thread onto the PAC thread. There are 3 options: process the queue, -// cancel the queue, and setup the javascript context with a new PAC file +// thread onto the PAC thread. There are 4 options: process the queue, +// cancel the queue, query DHCP for the PAC option +// and setup the javascript context with a new PAC file class ExecutePACThreadAction final : public Runnable { @@ -211,6 +265,7 @@ public: , mCancelStatus(NS_OK) , mSetupPAC(false) , mExtraHeapSize(0) + , mConfigureWPAD(false) { } void CancelQueue (nsresult status) @@ -221,7 +276,7 @@ public: void SetupPAC (const char *text, uint32_t datalen, - nsCString &pacURI, + const nsACString &pacURI, uint32_t extraHeapSize) { mSetupPAC = true; @@ -230,6 +285,11 @@ public: mExtraHeapSize = extraHeapSize; } + void ConfigureWPAD() + { + mConfigureWPAD = true; + } + NS_IMETHOD Run() override { MOZ_ASSERT(!NS_IsMainThread(), "wrong thread"); @@ -254,6 +314,15 @@ public: return NS_OK; } + if (mConfigureWPAD) { + nsAutoCString spec; + mConfigureWPAD = false; + mPACMan->ConfigureWPAD(spec); + RefPtr runnable = new ConfigureWPADComplete(mPACMan, spec); + mPACMan->Dispatch(runnable.forget()); + return NS_OK; + } + mPACMan->ProcessPendingQ(); return NS_OK; } @@ -268,6 +337,7 @@ private: uint32_t mExtraHeapSize; nsCString mSetupPACData; nsCString mSetupPACURI; + bool mConfigureWPAD; }; //----------------------------------------------------------------------------- @@ -289,7 +359,7 @@ PendingPACQuery::PendingPACQuery(nsPACMan* pacMan, } void -PendingPACQuery::Complete(nsresult status, const nsCString &pacString) +PendingPACQuery::Complete(nsresult status, const nsACString &pacString) { if (!mCallback) return; @@ -302,7 +372,7 @@ PendingPACQuery::Complete(nsresult status, const nsCString &pacString) } void -PendingPACQuery::UseAlternatePACFile(const nsCString &pacURL) +PendingPACQuery::UseAlternatePACFile(const nsACString &pacURL) { if (!mCallback) return; @@ -337,6 +407,9 @@ nsPACMan::nsPACMan(nsIEventTarget *mainThreadEventTarget) , mShutdown(false) , mLoadFailureCount(0) , mInProgress(false) + , mAutoDetect(false) + , mWPADOverDHCPEnabled(false) + , mProxyConfigType(0) { MOZ_ASSERT(NS_IsMainThread(), "pacman must be created on main thread"); if (!sThreadLocalSetup){ @@ -354,6 +427,7 @@ nsPACMan::~nsPACMan() if (mPACThread) { if (NS_IsMainThread()) { mPACThread->Shutdown(); + mPACThread = nullptr; } else { RefPtr runnable = new ShutdownThread(mPACThread); @@ -396,7 +470,7 @@ nsPACMan::AsyncGetProxyForURI(nsIURI *uri, TimeStamp::Now() > mScheduledReload) { LOG(("nsPACMan::AsyncGetProxyForURI reload as scheduled\n")); - LoadPACFromURI(EmptyCString()); + LoadPACFromURI(mAutoDetect? EmptyCString(): mPACURISpec); } RefPtr query = @@ -429,16 +503,15 @@ nsPACMan::PostQuery(PendingPACQuery *query) } nsresult -nsPACMan::LoadPACFromURI(const nsCString &spec) +nsPACMan::LoadPACFromURI(const nsACString &aSpec) { NS_ENSURE_STATE(!mShutdown); - NS_ENSURE_ARG(!spec.IsEmpty() || !mPACURISpec.IsEmpty()); nsCOMPtr loader = do_CreateInstance(NS_STREAMLOADER_CONTRACTID); NS_ENSURE_STATE(loader); - LOG(("nsPACMan::LoadPACFromURI %s\n", spec.get())); + LOG(("nsPACMan::LoadPACFromURI aSpec: %s\n", aSpec.BeginReading())); // Since we might get called from nsProtocolProxyService::Init, we need to // post an event back to the main thread before we try to use the IO service. // @@ -459,18 +532,67 @@ nsPACMan::LoadPACFromURI(const nsCString &spec) CancelExistingLoad(); mLoader = loader; - if (!spec.IsEmpty()) { - mPACURISpec = spec; - mPACURIRedirectSpec.Truncate(); - mNormalPACURISpec.Truncate(); // set at load time - mLoadFailureCount = 0; // reset - } + mPACURIRedirectSpec.Truncate(); + mNormalPACURISpec.Truncate(); // set at load time + mLoadFailureCount = 0; // reset + mAutoDetect = aSpec.IsEmpty(); + mPACURISpec.Assign(aSpec); // reset to Null mScheduledReload = TimeStamp(); return NS_OK; } +nsresult +nsPACMan::GetPACFromDHCP(nsACString &aSpec) +{ + MOZ_ASSERT(!NS_IsMainThread(), "wrong thread"); + if (!mDHCPClient) { + LOG(("nsPACMan::GetPACFromDHCP DHCP option %d query failed because there is no DHCP client available\n", MOZ_DHCP_WPAD_OPTION)); + return NS_ERROR_NOT_IMPLEMENTED; + } + nsresult rv; + rv = mDHCPClient->GetOption(MOZ_DHCP_WPAD_OPTION, aSpec); + if (NS_FAILED(rv)) { + LOG(("nsPACMan::GetPACFromDHCP DHCP option %d query failed with result %d\n", MOZ_DHCP_WPAD_OPTION, (uint32_t)rv)); + } else { + LOG(("nsPACMan::GetPACFromDHCP DHCP option %d query succeeded, finding PAC URL %s\n", MOZ_DHCP_WPAD_OPTION, aSpec.BeginReading())); + } + return rv; +} + +nsresult +nsPACMan::ConfigureWPAD(nsACString &aSpec) +{ + MOZ_ASSERT(!NS_IsMainThread(), "wrong thread"); + + MOZ_RELEASE_ASSERT(mProxyConfigType == nsIProtocolProxyService::PROXYCONFIG_WPAD, + "WPAD is being executed when not selected by user"); + + aSpec.Truncate(); + if (mWPADOverDHCPEnabled) { + GetPACFromDHCP(aSpec); + } + + if (aSpec.IsEmpty()) { + // We diverge from the WPAD spec here in that we don't walk the + // hosts's FQDN, stripping components until we hit a TLD. Doing so + // is dangerous in the face of an incomplete list of TLDs, and TLDs + // get added over time. We could consider doing only a single + // substitution of the first component, if that proves to help + // compatibility. + aSpec.AssignLiteral(MOZ_WPAD_URL); + } + return NS_OK; +} + +void +nsPACMan::AssignPACURISpec(const nsACString &aSpec) +{ + MOZ_ASSERT(NS_IsMainThread(), "wrong thread"); + mPACURISpec.Assign(aSpec); +} + void nsPACMan::StartLoading() { @@ -483,6 +605,29 @@ nsPACMan::StartLoading() return; } + if (mAutoDetect) { + GetNetworkProxyTypeFromPref(&mProxyConfigType); + RefPtr wpadConfigurer = + new ExecutePACThreadAction(this); + wpadConfigurer->ConfigureWPAD(); + if (mPACThread) { + mPACThread->Dispatch(wpadConfigurer, nsIEventTarget::DISPATCH_NORMAL); + } + } else { + ContinueLoadingAfterPACUriKnown(); + } +} + +void +nsPACMan::ContinueLoadingAfterPACUriKnown() +{ + MOZ_ASSERT(NS_IsMainThread(), "wrong thread"); + + // CancelExistingLoad was called... + if (!mLoader) { + PostCancelPendingQ(NS_ERROR_ABORT); + return; + } if (NS_SUCCEEDED(mLoader->Init(this, nullptr))) { // Always hit the origin server when loading PAC. nsCOMPtr ios = do_GetIOService(); @@ -800,6 +945,8 @@ nsresult nsPACMan::Init(nsISystemProxySettings *systemProxySettings) { mSystemProxySettings = systemProxySettings; + mDHCPClient = do_GetService(NS_DHCPCLIENT_CONTRACTID); + nsresult rv = NS_NewNamedThread("ProxyResolution", getter_AddRefs(mPACThread)); diff --git a/netwerk/base/nsPACMan.h b/netwerk/base/nsPACMan.h index 95c46c2194c0..77518f4e7051 100644 --- a/netwerk/base/nsPACMan.h +++ b/netwerk/base/nsPACMan.h @@ -7,23 +7,24 @@ #ifndef nsPACMan_h__ #define nsPACMan_h__ -#include "nsIStreamLoader.h" -#include "nsIInterfaceRequestor.h" -#include "nsIChannelEventSink.h" -#include "ProxyAutoConfig.h" -#include "nsThreadUtils.h" -#include "nsIURI.h" -#include "nsCOMPtr.h" -#include "nsString.h" +#include "mozilla/Atomics.h" #include "mozilla/Attributes.h" #include "mozilla/LinkedList.h" -#include "nsAutoPtr.h" -#include "mozilla/TimeStamp.h" #include "mozilla/Logging.h" -#include "mozilla/Atomics.h" #include "mozilla/net/NeckoTargetHolder.h" +#include "mozilla/TimeStamp.h" +#include "nsAutoPtr.h" +#include "nsCOMPtr.h" +#include "nsIChannelEventSink.h" +#include "nsIInterfaceRequestor.h" +#include "nsIStreamLoader.h" +#include "nsThreadUtils.h" +#include "nsIURI.h" +#include "nsString.h" +#include "ProxyAutoConfig.h" class nsISystemProxySettings; +class nsIDHCPClient; class nsIThread; namespace mozilla { @@ -52,8 +53,8 @@ public: * newPACURL should be 0 length. */ virtual void OnQueryComplete(nsresult status, - const nsCString &pacString, - const nsCString &newPACURL) = 0; + const nsACString &pacString, + const nsACString &newPACURL) = 0; }; class PendingPACQuery final : public Runnable, @@ -65,8 +66,8 @@ public: bool mainThreadResponse); // can be called from either thread - void Complete(nsresult status, const nsCString &pacString); - void UseAlternatePACFile(const nsCString &pacURL); + void Complete(nsresult status, const nsACString &pacString); + void UseAlternatePACFile(const nsACString &pacURL); nsCString mSpec; nsCString mScheme; @@ -127,11 +128,11 @@ public: * the PAC file, any asynchronous PAC queries will be queued up to be * processed once the PAC file finishes loading. * - * @param pacSpec + * @param aSpec * The non normalized uri spec of this URI used for comparison with * system proxy settings to determine if the PAC uri has changed. */ - nsresult LoadPACFromURI(const nsCString &pacSpec); + nsresult LoadPACFromURI(const nsACString &aSpec); /** * Returns true if we are currently loading the PAC file. @@ -166,6 +167,10 @@ public: return IsPACURI(tmp); } + bool IsUsingWPAD() { + return mAutoDetect; + } + nsresult Init(nsISystemProxySettings *); static nsPACMan *sInstance; @@ -173,6 +178,8 @@ public: void ProcessPendingQ(); void CancelPendingQ(nsresult); + void SetWPADOverDHCPEnabled(bool aValue) { mWPADOverDHCPEnabled = aValue; } + private: NS_DECL_NSISTREAMLOADEROBSERVER NS_DECL_NSIINTERFACEREQUESTOR @@ -180,8 +187,10 @@ private: friend class PendingPACQuery; friend class PACLoadComplete; + friend class ConfigureWPADComplete; friend class ExecutePACThreadAction; friend class WaitForThreadShutdown; + friend class TestPACMan; ~nsPACMan(); @@ -195,6 +204,11 @@ private: */ void StartLoading(); + /** + * Continue loading the PAC file. + */ + void ContinueLoadingAfterPACUriKnown(); + /** * Reload the PAC file if there is reason to. */ @@ -212,15 +226,22 @@ private: */ nsresult PostQuery(PendingPACQuery *query); + // Having found the PAC URI on the PAC thread, copy it to a string which + // can be altered on the main thread. + void AssignPACURISpec(const nsACString &aSpec); + // PAC thread operations only void PostProcessPendingQ(); void PostCancelPendingQ(nsresult); bool ProcessPending(); + nsresult GetPACFromDHCP(nsACString &aSpec); + nsresult ConfigureWPAD(nsACString &aSpec); private: ProxyAutoConfig mPAC; nsCOMPtr mPACThread; nsCOMPtr mSystemProxySettings; + nsCOMPtr mDHCPClient; LinkedList mPendingQ; /* pac thread only */ @@ -239,6 +260,9 @@ private: bool mInProgress; bool mIncludePath; + bool mAutoDetect; + bool mWPADOverDHCPEnabled; + int32_t mProxyConfigType; }; extern LazyLogModule gProxyLog; diff --git a/netwerk/base/nsProtocolProxyService.cpp b/netwerk/base/nsProtocolProxyService.cpp index ff71005faa82..7b65dca6966d 100644 --- a/netwerk/base/nsProtocolProxyService.cpp +++ b/netwerk/base/nsProtocolProxyService.cpp @@ -62,7 +62,6 @@ namespace net { #define PROXY_PREF_BRANCH "network.proxy" #define PROXY_PREF(x) PROXY_PREF_BRANCH "." x -#define WPAD_URL "http://wpad/wpad.dat" //---------------------------------------------------------------------------- @@ -311,8 +310,8 @@ private: // Called asynchronously, so we do not need to post another PLEvent // before calling DoCallback. void OnQueryComplete(nsresult status, - const nsCString &pacString, - const nsCString &newPACURL) override + const nsACString &pacString, + const nsACString &newPACURL) override { // If we've already called DoCallback then, nothing more to do. if (!mCallback) @@ -821,6 +820,7 @@ nsProtocolProxyService::nsProtocolProxyService() , mSOCKSProxyVersion(4) , mSOCKSProxyRemoteDNS(false) , mProxyOverTLS(true) + , mWPADOverDHCPEnabled(false) , mPACMan(nullptr) , mSessionStart(PR_Now()) , mFailedProxyTimeout(30 * 60) // 30 minute default @@ -1087,6 +1087,12 @@ nsProtocolProxyService::PrefsChanged(nsIPrefBranch *prefBranch, mProxyOverTLS); } + if (!pref || !strcmp(pref, PROXY_PREF("enable_wpad_over_dhcp"))) { + proxy_GetBoolPref(prefBranch, PROXY_PREF("enable_wpad_over_dhcp"), + mWPADOverDHCPEnabled); + reloadPAC = reloadPAC || mProxyConfig == PROXYCONFIG_WPAD; + } + if (!pref || !strcmp(pref, PROXY_PREF("failover_timeout"))) proxy_GetIntPref(prefBranch, PROXY_PREF("failover_timeout"), mFailedProxyTimeout); @@ -1119,19 +1125,15 @@ nsProtocolProxyService::PrefsChanged(nsIPrefBranch *prefBranch, ResetPACThread(); } } else if (mProxyConfig == PROXYCONFIG_WPAD) { - // We diverge from the WPAD spec here in that we don't walk the - // hosts's FQDN, stripping components until we hit a TLD. Doing so - // is dangerous in the face of an incomplete list of TLDs, and TLDs - // get added over time. We could consider doing only a single - // substitution of the first component, if that proves to help - // compatibility. - tempString.AssignLiteral(WPAD_URL); + LOG(("Auto-detecting proxy - Reset Pac Thread")); + ResetPACThread(); } else if (mSystemProxySettings) { // Get System Proxy settings if available AsyncConfigureFromPAC(false, false); } - if (!tempString.IsEmpty()) + if (!tempString.IsEmpty() || mProxyConfig == PROXYCONFIG_WPAD) { ConfigureFromPAC(tempString, false); + } } } @@ -1482,7 +1484,6 @@ nsProtocolProxyService::SetupPACThread(nsIEventTarget *mainThreadEventTarget) else { rv = mPACMan->Init(nullptr); } - if (NS_FAILED(rv)) { mPACMan->Shutdown(); mPACMan = nullptr; @@ -1508,11 +1509,15 @@ nsProtocolProxyService::ConfigureFromPAC(const nsCString &spec, nsresult rv = SetupPACThread(); NS_ENSURE_SUCCESS(rv, rv); - if (mPACMan->IsPACURI(spec) && !forceReload) + bool autodetect = spec.IsEmpty(); + if (!forceReload && ((!autodetect && mPACMan->IsPACURI(spec)) || + (autodetect && mPACMan->IsUsingWPAD()))) { return NS_OK; + } mFailedProxies.Clear(); + mPACMan->SetWPADOverDHCPEnabled(mWPADOverDHCPEnabled); return mPACMan->LoadPACFromURI(spec); } @@ -1565,8 +1570,6 @@ nsProtocolProxyService::ReloadPAC() nsAutoCString pacSpec; if (type == PROXYCONFIG_PAC) prefs->GetCharPref(PROXY_PREF("autoconfig_url"), pacSpec); - else if (type == PROXYCONFIG_WPAD) - pacSpec.AssignLiteral(WPAD_URL); else if (type == PROXYCONFIG_SYSTEM) { if (mSystemProxySettings) { AsyncConfigureFromPAC(true, true); @@ -1575,7 +1578,7 @@ nsProtocolProxyService::ReloadPAC() } } - if (!pacSpec.IsEmpty()) + if (!pacSpec.IsEmpty() || type == PROXYCONFIG_WPAD) ConfigureFromPAC(pacSpec, true); return NS_OK; } @@ -1599,8 +1602,8 @@ class nsAsyncBridgeRequest final : public nsPACManCallback } void OnQueryComplete(nsresult status, - const nsCString &pacString, - const nsCString &newPACURL) override + const nsACString &pacString, + const nsACString &newPACURL) override { MutexAutoLock lock(mMutex); mCompleted = true; @@ -2546,4 +2549,4 @@ nsProtocolProxyService::PruneProxyInfo(const nsProtocolInfo &info, } } // namespace net -} // namespace mozilla +} // namespace mozilla \ No newline at end of file diff --git a/netwerk/base/nsProtocolProxyService.h b/netwerk/base/nsProtocolProxyService.h index 5398a58643f4..4a41af2e0c9c 100644 --- a/netwerk/base/nsProtocolProxyService.h +++ b/netwerk/base/nsProtocolProxyService.h @@ -400,6 +400,7 @@ protected: int32_t mSOCKSProxyVersion; bool mSOCKSProxyRemoteDNS; bool mProxyOverTLS; + bool mWPADOverDHCPEnabled; RefPtr mPACMan; // non-null if we are using PAC nsCOMPtr mSystemProxySettings; diff --git a/netwerk/build/nsNetCID.h b/netwerk/build/nsNetCID.h index 0ee916fd0da4..dfdad224afe3 100644 --- a/netwerk/build/nsNetCID.h +++ b/netwerk/build/nsNetCID.h @@ -335,6 +335,10 @@ #define NS_SYSTEMPROXYSETTINGS_CONTRACTID \ "@mozilla.org/system-proxy-settings;1" +// component implementing nsIDHCPClient. +#define NS_DHCPCLIENT_CONTRACTID \ + "@mozilla.org/dhcp-client;1" + // service implementing nsIStreamTransportService #define NS_STREAMTRANSPORTSERVICE_CONTRACTID \ "@mozilla.org/network/stream-transport-service;1" diff --git a/netwerk/test/gtest/TestPACMan.cpp b/netwerk/test/gtest/TestPACMan.cpp new file mode 100644 index 000000000000..979d7ea8153f --- /dev/null +++ b/netwerk/test/gtest/TestPACMan.cpp @@ -0,0 +1,282 @@ +#include "gtest/gtest.h" +#include "nsServiceManagerUtils.h" +#include "../../../xpcom/threads/nsThreadManager.h" +#include "nsIDHCPClient.h" +#include "nsIPrefBranch.h" +#include "nsComponentManager.h" +#include "mozilla/ModuleUtils.h" +#include "../../base/nsPACMan.h" + + +#define TEST_WPAD_DHCP_OPTION "http://pac/pac.dat" +#define TEST_ASSIGNED_PAC_URL "http://assignedpac/pac.dat" +#define WPAD_PREF 4 +#define NETWORK_PROXY_TYPE_PREF_NAME "network.proxy.type" +#define GETTING_NETWORK_PROXY_TYPE_FAILED -1 + +nsCString WPADOptionResult; + +namespace mozilla { +namespace net { + +nsresult +SetNetworkProxyType(int32_t pref) +{ + nsCOMPtr prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); + + if (!prefs) { + return NS_ERROR_FACTORY_NOT_REGISTERED; + } + return prefs->SetIntPref(NETWORK_PROXY_TYPE_PREF_NAME, pref); +} + +nsresult +GetNetworkProxyType(int32_t* pref) +{ + nsCOMPtr prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); + + if (!prefs) { + return NS_ERROR_FACTORY_NOT_REGISTERED; + } + return prefs->GetIntPref(NETWORK_PROXY_TYPE_PREF_NAME, pref); +} + +class nsTestDHCPClient final : public nsIDHCPClient +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIDHCPCLIENT + + nsTestDHCPClient() {}; + nsresult Init(){ + return NS_OK; + }; + +private: + ~nsTestDHCPClient() {}; +}; + +NS_IMETHODIMP +nsTestDHCPClient::GetOption(uint8_t option, nsACString & _retval) +{ + _retval.Assign(WPADOptionResult); + return NS_OK; +} + +NS_IMPL_ISUPPORTS(nsTestDHCPClient, nsIDHCPClient) + +#define NS_TESTDHCPCLIENTSERVICE_CID /* {FEBF1D69-4D7D-4891-9524-045AD18B5592} */\ + { 0xFEBF1D69, 0x4D7D, 0x4891, \ + {0x95, 0x24, 0x04, 0x5a, 0xd1, 0x8b, 0x55, 0x92 } } + +NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsTestDHCPClient, Init) +NS_DEFINE_NAMED_CID(NS_TESTDHCPCLIENTSERVICE_CID); + +static const mozilla::Module::CIDEntry kSysDHCPClientCIDs[] = { + { &kNS_TESTDHCPCLIENTSERVICE_CID, false, nullptr, nsTestDHCPClientConstructor }, + { nullptr } +}; + +static const mozilla::Module::ContractIDEntry kSysDHCPClientContracts[] = { + { NS_DHCPCLIENT_CONTRACTID, &kNS_TESTDHCPCLIENTSERVICE_CID }, + { nullptr } +}; + +static const mozilla::Module kSysDHCPClientModule = { + mozilla::Module::kVersion, + kSysDHCPClientCIDs, + kSysDHCPClientContracts +}; + +NSMODULE_DEFN(nsDHCPClientModule) = &kSysDHCPClientModule; + +void +SetOptionResult(const char* result) +{ + WPADOptionResult.Assign(result); +} + +class ProcessPendingEventsAction final : public Runnable +{ +public: + ProcessPendingEventsAction() : Runnable("net::ProcessPendingEventsAction") { } + + NS_IMETHOD + Run() override + { + if (NS_HasPendingEvents(nullptr)) { + NS_WARNING("Found pending requests on PAC thread"); + nsresult rv; + rv = NS_ProcessPendingEvents(nullptr); + EXPECT_EQ(NS_OK, rv); + } + NS_WARNING("No pending requests on PAC thread"); + return NS_OK; + } +}; + +class TestPACMan : public ::testing::Test { + protected: + + RefPtr mPACMan; + + void + ProcessAllEvents() + { + ProcessPendingEventsOnPACThread(); + nsresult rv; + while (NS_HasPendingEvents(nullptr)) { + NS_WARNING("Pending events on main thread"); + rv = NS_ProcessPendingEvents(nullptr); + ASSERT_EQ(NS_OK, rv); + ProcessPendingEventsOnPACThread(); + } + NS_WARNING("End of pending events on main thread"); + } + + + // This method is used to ensure that all pending events on the main thread + // and the Proxy thread are processsed. + // It iterates over ProcessAllEvents because simply calling ProcessAllEvents once + // did not reliably process the events on both threads on all platforms. + void + ProcessAllEventsTenTimes(){ + for (int i = 0; i < 10; i++) { + ProcessAllEvents(); + } + } + + virtual void + SetUp() + { + ASSERT_EQ(NS_OK, GetNetworkProxyType(&originalNetworkProxyTypePref)); + nsFactoryEntry* factoryEntry = nsComponentManagerImpl::gComponentManager + ->GetFactoryEntry(kNS_TESTDHCPCLIENTSERVICE_CID); + if (factoryEntry) { + nsresult rv = nsComponentManagerImpl::gComponentManager->UnregisterFactory(kNS_TESTDHCPCLIENTSERVICE_CID, factoryEntry->mFactory); + ASSERT_EQ(NS_OK, rv); + } + nsComponentManagerImpl::gComponentManager->RegisterModule(&kSysDHCPClientModule, nullptr); + + mPACMan = new nsPACMan(nullptr); + mPACMan->SetWPADOverDHCPEnabled(true); + mPACMan->Init(nullptr); + ASSERT_EQ(NS_OK, SetNetworkProxyType(WPAD_PREF)); + + } + + virtual void + TearDown() + { + + mPACMan->Shutdown(); + if (originalNetworkProxyTypePref != GETTING_NETWORK_PROXY_TYPE_FAILED) { + ASSERT_EQ(NS_OK, SetNetworkProxyType(originalNetworkProxyTypePref)); + } + } + + nsCOMPtr + GetPACManDHCPCient() + { + return mPACMan->mDHCPClient; + } + + void + SetPACManDHCPCient(nsCOMPtr aValue) + { + mPACMan->mDHCPClient = aValue; + } + + void + AssertPACSpecEqualTo(const char* aExpected) + { + ASSERT_STREQ(aExpected, mPACMan->mPACURISpec.Data()); + } + + private: + + int32_t originalNetworkProxyTypePref = GETTING_NETWORK_PROXY_TYPE_FAILED; + + void ProcessPendingEventsOnPACThread(){ + RefPtr action = + new ProcessPendingEventsAction(); + + mPACMan->mPACThread->Dispatch(action, nsIEventTarget::DISPATCH_SYNC); + } +}; + +TEST_F(TestPACMan, TestCreateDHCPClientAndGetOption) { + SetOptionResult(TEST_WPAD_DHCP_OPTION); + nsCString spec; + + GetPACManDHCPCient()->GetOption(252, spec); + + ASSERT_STREQ(TEST_WPAD_DHCP_OPTION, spec.Data()); +} + +TEST_F(TestPACMan, TestCreateDHCPClientAndGetEmptyOption) { + SetOptionResult(""); + nsCString spec; + spec.AssignLiteral(TEST_ASSIGNED_PAC_URL); + + GetPACManDHCPCient()->GetOption(252, spec); + + ASSERT_TRUE(spec.IsEmpty()); +} + +TEST_F(TestPACMan, WhenTheDHCPClientExistsAndDHCPIsNonEmptyDHCPOptionIsUsedAsPACUri) { + SetOptionResult(TEST_WPAD_DHCP_OPTION); + + mPACMan->LoadPACFromURI(EmptyCString()); + ProcessAllEventsTenTimes(); + + ASSERT_STREQ(TEST_WPAD_DHCP_OPTION, WPADOptionResult.Data()); + AssertPACSpecEqualTo(TEST_WPAD_DHCP_OPTION); +} + +TEST_F(TestPACMan, WhenTheDHCPResponseIsEmptyWPADDefaultsToStandardURL) { + SetOptionResult(EmptyCString().Data()); + + mPACMan->LoadPACFromURI(EmptyCString()); + ASSERT_TRUE(NS_HasPendingEvents(nullptr)); + ProcessAllEventsTenTimes(); + + ASSERT_STREQ("", WPADOptionResult.Data()); + AssertPACSpecEqualTo("http://wpad/wpad.dat"); +} + +TEST_F(TestPACMan, WhenThereIsNoDHCPClientWPADDefaultsToStandardURL) { + SetOptionResult(TEST_WPAD_DHCP_OPTION); + SetPACManDHCPCient(nullptr); + + mPACMan->LoadPACFromURI(EmptyCString()); + ProcessAllEventsTenTimes(); + + ASSERT_STREQ(TEST_WPAD_DHCP_OPTION, WPADOptionResult.Data()); + AssertPACSpecEqualTo("http://wpad/wpad.dat"); +} + +TEST_F(TestPACMan, WhenWPADOverDHCPIsPreffedOffWPADDefaultsToStandardURL) { + SetOptionResult(TEST_WPAD_DHCP_OPTION); + mPACMan->SetWPADOverDHCPEnabled(false); + + mPACMan->LoadPACFromURI(EmptyCString()); + ProcessAllEventsTenTimes(); + + ASSERT_STREQ(TEST_WPAD_DHCP_OPTION, WPADOptionResult.Data()); + AssertPACSpecEqualTo("http://wpad/wpad.dat"); +} + +TEST_F(TestPACMan, WhenPACUriIsSetDirectlyItIsUsedRatherThanWPAD) { + SetOptionResult(TEST_WPAD_DHCP_OPTION); + nsCString spec; + spec.AssignLiteral(TEST_ASSIGNED_PAC_URL); + + mPACMan->LoadPACFromURI(spec); + ProcessAllEventsTenTimes(); + + AssertPACSpecEqualTo(TEST_ASSIGNED_PAC_URL); +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/test/gtest/moz.build b/netwerk/test/gtest/moz.build index 927c5866b497..0c6fdae9ce05 100644 --- a/netwerk/test/gtest/moz.build +++ b/netwerk/test/gtest/moz.build @@ -10,6 +10,7 @@ UNIFIED_SOURCES += [ 'TestHttpAuthUtils.cpp', 'TestMIMEInputStream.cpp', 'TestMozURL.cpp', + 'TestPACMan.cpp', 'TestPartiallySeekableInputStream.cpp', 'TestProtocolProxyService.cpp', 'TestReadStreamToString.cpp', @@ -30,3 +31,8 @@ LOCAL_INCLUDES += [ include('/ipc/chromium/chromium-config.mozbuild') FINAL_LIBRARY = 'xul-gtest' + +LOCAL_INCLUDES += [ + '!/xpcom', + '/xpcom/components' +] \ No newline at end of file diff --git a/toolkit/moz.build b/toolkit/moz.build index ed8bceb911b5..9cb23a65616d 100644 --- a/toolkit/moz.build +++ b/toolkit/moz.build @@ -40,7 +40,8 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gtk3': elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': DIRS += ['system/osxproxy'] elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows': - DIRS += ['system/windowsproxy'] + DIRS += ['system/windowsproxy', + 'system/windowsDHCPClient'] elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android': DIRS += ['system/androidproxy'] diff --git a/toolkit/system/windowsDHCPClient/DHCPUtils.cpp b/toolkit/system/windowsDHCPClient/DHCPUtils.cpp new file mode 100644 index 000000000000..3936d4298f42 --- /dev/null +++ b/toolkit/system/windowsDHCPClient/DHCPUtils.cpp @@ -0,0 +1,256 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "DHCPUtils.h" +#include +#include "mozilla\Logging.h" +#include "nsString.h" + + +#define MOZ_WORKING_BUFFER_SIZE_NETWORK_ADAPTERS 15000 +#define MOZ_WORKING_BUFFER_SIZE_DHCP_PARAMS 1000 +#define MOZ_MAX_TRIES 3 +namespace mozilla { +namespace toolkit { +namespace system { +namespace windowsDHCPClient { + +// +// The comments on this page reference the following Microsoft documentation pages (both retrieved 2017-06-27) +// [1] https://msdn.microsoft.com/en-us/library/windows/desktop/aa365915(v=vs.85).aspx +// [2] https://msdn.microsoft.com/en-us/library/windows/desktop/aa363298(v=vs.85).aspx +mozilla::LazyLogModule gDhcpUtilsLog("dhcpUtils"); + +#undef LOG +#define LOG(args) MOZ_LOG(gDhcpUtilsLog, LogLevel::Debug, args) + +bool +IsCurrentAndHasDHCP(PIP_ADAPTER_ADDRESSES aAddresses) +{ + return aAddresses->OperStatus == 1 && + (aAddresses->Dhcpv4Server.iSockaddrLength || + aAddresses->Dhcpv6Server.iSockaddrLength); +} + +nsresult +GetActiveDHCPNetworkAdapterName(nsACString& aNetworkAdapterName, + WindowsNetworkFunctionsWrapper* aWindowsNetworkFunctionsWrapper) +{ + /* Declare and initialize variables */ + + uint32_t dwSize = 0; + uint32_t dwRetVal = 0; + nsresult rv = NS_ERROR_FAILURE; + + // Set the flags to pass to GetAdaptersAddresses + uint32_t flags = GAA_FLAG_INCLUDE_PREFIX; + + // default to unspecified address family (both) + uint32_t family = AF_UNSPEC; + + // Allocate a 15 KB buffer to start with. + uint32_t outBufLen = MOZ_WORKING_BUFFER_SIZE_NETWORK_ADAPTERS; + uint32_t iterations = 0; + + aNetworkAdapterName.Truncate(); + + // Now we try calling the GetAdaptersAddresses method until the return value + // is not ERROR_BUFFER_OVERFLOW. According to [1] + // + // + // > When the return value is ERROR_BUFFER_OVERFLOW, the SizePointer parameter returned + // > points to the required size of the buffer to hold the adapter information. + // > Note that it is possible for the buffer size required for the IP_ADAPTER_ADDRESSES + // > structures pointed to by the AdapterAddresses parameter to change between + // > subsequent calls to the GetAdaptersAddresses function if an adapter address + // > is added or removed. However, this method of using the GetAdaptersAddresses + // > function is strongly discouraged. This method requires calling the + // > GetAdaptersAddresses function multiple times. + // > + // > The recommended method of calling the GetAdaptersAddresses function is + // > to pre-allocate a 15KB working buffer pointed to by the AdapterAddresses parameter. + // > On typical computers, this dramatically reduces the chances that the + // > GetAdaptersAddresses function returns ERROR_BUFFER_OVERFLOW, which would require + // > calling GetAdaptersAddresses function multiple times. + // + // + // The possibility of the buffer size changing between calls to + // GetAdaptersAddresses is why we allow the following code to be called several times, + // rather than just the two that would be neccessary if we could rely on the + // value returned in outBufLen being the true size needed. + + std::vector pAddresses; + do { + pAddresses.resize(outBufLen/sizeof(IP_ADAPTER_ADDRESSES)); + + dwRetVal = + aWindowsNetworkFunctionsWrapper->GetAdaptersAddressesWrapped( + family, flags, nullptr, pAddresses.data(), (PULONG)&outBufLen); + + if (dwRetVal == ERROR_BUFFER_OVERFLOW) { + iterations++; + } + } while (dwRetVal == ERROR_BUFFER_OVERFLOW && iterations < MOZ_MAX_TRIES); + + switch(dwRetVal) { + case NO_ERROR: + { + // set default return value if we don't find a suitable network adapter + rv = NS_ERROR_NOT_AVAILABLE; + PIP_ADAPTER_ADDRESSES pCurrAddresses = pAddresses.data(); + while (pCurrAddresses) { + if (IsCurrentAndHasDHCP(pCurrAddresses)) { + rv = NS_OK; + aNetworkAdapterName.Assign(pCurrAddresses->AdapterName); + break; + } + pCurrAddresses = pCurrAddresses->Next; + } + } + break; + case ERROR_NO_DATA: + rv = NS_ERROR_NOT_AVAILABLE; + break; + default: + MOZ_LOG(gDhcpUtilsLog, mozilla::LogLevel::Warning, + ("GetAdaptersAddresses returned %d", dwRetVal)); + rv = NS_ERROR_FAILURE; + break; + } + return rv; +} + + +DWORD +IterateDHCPInformRequestsUntilBufferLargeEnough( + DHCPCAPI_PARAMS& aDhcpRequestedOptionParams, + wchar_t* aWideNetworkAdapterName, + std::vector& aBuffer, + WindowsNetworkFunctionsWrapper* aWindowsNetworkFunctionsWrapper) +{ + uint32_t iterations = 0; + uint32_t outBufLen = MOZ_WORKING_BUFFER_SIZE_DHCP_PARAMS; + + DHCPCAPI_PARAMS_ARRAY RequestParams = { + 1, // only one option to request + &aDhcpRequestedOptionParams + }; + + // According to [2], + // the following is for 'Optional data to be requested, + // in addition to the data requested in the RecdParams array.' + // We are not requesting anything in addition, so this is empty. + DHCPCAPI_PARAMS_ARRAY SendParams = { + 0, + nullptr + }; + + DWORD winAPIResponse; + // Now we try calling the DHCPRequestParams method until the return value + // is not ERROR_MORE_DATA. According to [2]: + // + // + // > Note that the required size of Buffer may increase during the time that elapses + // > between the initial function call's return and a subsequent call; + // > therefore, the required size of Buffer (indicated in pSize) + // > provides an indication of the approximate size required of Buffer, + // > rather than guaranteeing that subsequent calls will return successfully + // > if Buffer is set to the size indicated in pSize. + // + // + // This is why we allow this DHCPRequestParams to be called several times, + // rather than just the two that would be neccessary if we could rely on the + // value returned in outBufLen being the true size needed. + do { + aBuffer.resize(outBufLen); + + winAPIResponse = aWindowsNetworkFunctionsWrapper->DhcpRequestParamsWrapped( + DHCPCAPI_REQUEST_SYNCHRONOUS, // Flags + nullptr, // Reserved + aWideNetworkAdapterName, // Adapter Name + nullptr, // not using class id + SendParams, // sent parameters + RequestParams, // requesting params + (PBYTE)aBuffer.data(), // buffer for the output of RequestParams + (PULONG)&outBufLen, // buffer size + nullptr // Request ID for persistent requests - not needed here + ); + + if (winAPIResponse == ERROR_MORE_DATA) { + iterations++; + } + } while (winAPIResponse == ERROR_MORE_DATA && iterations < MOZ_MAX_TRIES); + return winAPIResponse; +} + +nsresult +RetrieveOption( + const nsACString& aAdapterName, + uint8_t aOption, + std::vector& aOptionValueBuf, + uint32_t* aOptionSize, + WindowsNetworkFunctionsWrapper* aWindowsNetworkFunctionsWrapper) +{ + + nsresult rv; + nsAutoString wideNetworkAdapterName = NS_ConvertUTF8toUTF16(aAdapterName); + + DHCPCAPI_PARAMS DhcpRequestedOptionParams = { + 0, // Flags - Reserved, must be set to zero [2] + aOption, // OptionId + false, // whether this is vendor specific - let's assume not + nullptr, // data filled in on return + 0 // nBytes used by return data + }; + + std::vector tmpBuffer(MOZ_WORKING_BUFFER_SIZE_DHCP_PARAMS); // a buffer for the DHCP response object + DWORD winAPIResponse = IterateDHCPInformRequestsUntilBufferLargeEnough(DhcpRequestedOptionParams, + wideNetworkAdapterName.get(), + tmpBuffer, + aWindowsNetworkFunctionsWrapper); + + switch (winAPIResponse){ + case NO_ERROR: + { + if (DhcpRequestedOptionParams.nBytesData == 0) { + *aOptionSize = 0; + rv = NS_ERROR_NOT_AVAILABLE; + break; + } + + if (*aOptionSize >= DhcpRequestedOptionParams.nBytesData) { + rv = NS_OK; + } else { + rv = NS_ERROR_LOSS_OF_SIGNIFICANT_DATA; + } + + uint32_t actualSizeReturned = + *aOptionSize > DhcpRequestedOptionParams.nBytesData? + DhcpRequestedOptionParams.nBytesData: *aOptionSize; + + memcpy(aOptionValueBuf.data(), + DhcpRequestedOptionParams.Data, actualSizeReturned); + *aOptionSize = DhcpRequestedOptionParams.nBytesData; + break; + } + case ERROR_INVALID_PARAMETER: + MOZ_LOG(gDhcpUtilsLog, mozilla::LogLevel::Warning, + ("RetrieveOption returned %d (ERROR_INVALID_PARAMETER) when option %d requested", + winAPIResponse, aOption)); + rv = NS_ERROR_INVALID_ARG; + break; + default: + MOZ_LOG(gDhcpUtilsLog, mozilla::LogLevel::Warning, + ("RetrieveOption returned %d when option %d requested", winAPIResponse, aOption)); + rv = NS_ERROR_FAILURE; + } + return rv; +} + +} // namespace windowsDHCPClient +} // namespace system +} // namespace toolkit +} // namespace mozilla diff --git a/toolkit/system/windowsDHCPClient/DHCPUtils.h b/toolkit/system/windowsDHCPClient/DHCPUtils.h new file mode 100644 index 000000000000..a9790a277787 --- /dev/null +++ b/toolkit/system/windowsDHCPClient/DHCPUtils.h @@ -0,0 +1,34 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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_toolkit_system_windowsDHCPClient_DHCPUtils_h +#define mozilla_toolkit_system_windowsDHCPClient_DHCPUtils_h + +#include "WindowsNetworkFunctionsWrapper.h" +#include + +namespace mozilla { +namespace toolkit { +namespace system { +namespace windowsDHCPClient { + +nsresult GetActiveDHCPNetworkAdapterName( + nsACString& aNetworkAdapterName, + WindowsNetworkFunctionsWrapper* aWindowsNetworkFunctionsWrapper); + +nsresult RetrieveOption( + const nsACString& aAdapterName, + uint8_t aOption, + std::vector& aOptionValueBuf, + uint32_t* aOptionSize, + WindowsNetworkFunctionsWrapper* aWindowsNetworkFunctionsWrapper +); + +} // namespace windowsDHCPClient +} // namespace system +} // namespace toolkit +} // namespace mozilla +#endif // mozilla_toolkit_system_windowsDHCPClient_DHCPUtils_h diff --git a/toolkit/system/windowsDHCPClient/WindowsNetworkFunctionsWrapper.cpp b/toolkit/system/windowsDHCPClient/WindowsNetworkFunctionsWrapper.cpp new file mode 100644 index 000000000000..1369cfd3ec5d --- /dev/null +++ b/toolkit/system/windowsDHCPClient/WindowsNetworkFunctionsWrapper.cpp @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "WindowsNetworkFunctionsWrapper.h" + + +#pragma comment(lib, "IPHLPAPI.lib") +#pragma comment(lib, "dhcpcsvc.lib" ) + +namespace mozilla { +namespace toolkit { +namespace system { +namespace windowsDHCPClient { + +NS_IMPL_ISUPPORTS(WindowsNetworkFunctionsWrapper, nsISupports) + +ULONG WindowsNetworkFunctionsWrapper::GetAdaptersAddressesWrapped( + _In_ ULONG aFamily, + _In_ ULONG aFlags, + _In_ PVOID aReserved, + _Inout_ PIP_ADAPTER_ADDRESSES aAdapterAddresses, + _Inout_ PULONG aSizePointer) +{ + return GetAdaptersAddresses(aFamily, aFlags, aReserved, aAdapterAddresses, aSizePointer); +} + +DWORD WindowsNetworkFunctionsWrapper::DhcpRequestParamsWrapped( + _In_ DWORD aFlags, + _In_ LPVOID aReserved, + _In_ LPWSTR aAdapterName, + _In_ LPDHCPCAPI_CLASSID aClassId, + _In_ DHCPCAPI_PARAMS_ARRAY aSendParams, + _Inout_ DHCPCAPI_PARAMS_ARRAY aRecdParams, + _In_ LPBYTE aBuffer, + _Inout_ LPDWORD apSize, + _In_ LPWSTR aRequestIdStr) +{ + return DhcpRequestParams(aFlags, aReserved, aAdapterName, aClassId, aSendParams, aRecdParams, aBuffer, apSize, aRequestIdStr); +} +} // namespace windowsDHCPClient +} // namespace system +} // namespace toolkit +} // namespace mozilla diff --git a/toolkit/system/windowsDHCPClient/WindowsNetworkFunctionsWrapper.h b/toolkit/system/windowsDHCPClient/WindowsNetworkFunctionsWrapper.h new file mode 100644 index 000000000000..a18bad715ab0 --- /dev/null +++ b/toolkit/system/windowsDHCPClient/WindowsNetworkFunctionsWrapper.h @@ -0,0 +1,60 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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_toolkit_system_windowsDHCPClient_windowsNetworkFunctionsWrapper_h +#define mozilla_toolkit_system_windowsDHCPClient_windowsNetworkFunctionsWrapper_h + +#include // there is a compilation error if Winsock.h is not + // declared before dhcpcsdk.h +#include +#include + +#include "nsISupports.h" + +// Thin wrapper around low-level network functions needed for DHCP querying for web proxy +namespace mozilla { +namespace toolkit { +namespace system { +namespace windowsDHCPClient { + +class WindowsNetworkFunctionsWrapper : nsISupports +{ + + public: + + NS_DECL_THREADSAFE_ISUPPORTS + WindowsNetworkFunctionsWrapper(){}; + + virtual ULONG GetAdaptersAddressesWrapped( + _In_ ULONG aFamily, + _In_ ULONG aFlags, + _In_ PVOID aReserved, + _Inout_ PIP_ADAPTER_ADDRESSES aAdapterAddresses, + _Inout_ PULONG aSizePointer + ); + + virtual DWORD DhcpRequestParamsWrapped( + _In_ DWORD aFlags, + _In_ LPVOID aReserved, + _In_ LPWSTR aAdapterName, + _In_ LPDHCPCAPI_CLASSID aClassId, + _In_ DHCPCAPI_PARAMS_ARRAY aSendParams, + _Inout_ DHCPCAPI_PARAMS_ARRAY aRecdParams, + _In_ LPBYTE aBuffer, + _Inout_ LPDWORD apSize, + _In_ LPWSTR aRequestIdStr + ); + + protected: + ~WindowsNetworkFunctionsWrapper(){}; + +}; + +} // namespace windowsDHCPClient +} // namespace system +} // namespace toolkit +} // namespace mozilla +#endif //mozilla_toolkit_system_windowsDHCPClient_windowsNetworkFunctionsWrapper_h diff --git a/toolkit/system/windowsDHCPClient/moz.build b/toolkit/system/windowsDHCPClient/moz.build new file mode 100644 index 000000000000..3124e393a89d --- /dev/null +++ b/toolkit/system/windowsDHCPClient/moz.build @@ -0,0 +1,18 @@ +# -*- 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/. + +with Files('**'): + BUG_COMPONENT = ('Core', 'Networking: HTTP') + +TEST_DIRS += ['tests/gtest'] + +SOURCES += [ + 'DHCPUtils.cpp', + 'nsWindowsDHCPClient.cpp', + 'WindowsNetworkFunctionsWrapper.cpp' +] + +FINAL_LIBRARY = 'xul' \ No newline at end of file diff --git a/toolkit/system/windowsDHCPClient/nsWindowsDHCPClient.cpp b/toolkit/system/windowsDHCPClient/nsWindowsDHCPClient.cpp new file mode 100644 index 000000000000..19fa5189a4a8 --- /dev/null +++ b/toolkit/system/windowsDHCPClient/nsWindowsDHCPClient.cpp @@ -0,0 +1,103 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "nsWindowsDHCPClient.h" + +#include + +#include "DHCPUtils.h" +#include "nsNetCID.h" +#include "nsString.h" +#include "mozilla/Logging.h" +#include "mozilla/ModuleUtils.h" + +namespace mozilla { +namespace toolkit { +namespace system { +namespace windowsDHCPClient { + +LazyLogModule gDhcpLog("windowsDHCPClient"); + +#undef LOG +#define LOG(args) MOZ_LOG(gDhcpLog, LogLevel::Debug, args) + +#define MOZ_MAX_DHCP_OPTION_LENGTH 255 // this is the maximum option length in DHCP V4 and 6 + +NS_IMPL_ISUPPORTS(nsWindowsDHCPClient, nsIDHCPClient) + +nsresult +nsWindowsDHCPClient::Init() +{ + return NS_OK; +} + +NS_IMETHODIMP +nsWindowsDHCPClient::GetOption(uint8_t aOption, nsACString& aRetVal) +{ + nsCString networkAdapterName; + nsresult rv; + rv = GetActiveDHCPNetworkAdapterName(networkAdapterName, mNetworkFunctions); + if (rv != NS_OK) { + LOG(("Failed to get network adapter name in nsWindowsDHCPClient::GetOption due to error %d", rv)); + return rv; + } + + uint32_t sizeOptionValue = MOZ_MAX_DHCP_OPTION_LENGTH; + std::vector optionValue; + + bool retryingAfterLossOfSignificantData = false; + do { + optionValue.resize(sizeOptionValue); + rv = RetrieveOption(networkAdapterName, + aOption, + optionValue, + &sizeOptionValue, + mNetworkFunctions); + if (rv == NS_ERROR_LOSS_OF_SIGNIFICANT_DATA) { + LOG(("In nsWindowsDHCPClient::GetOption, DHCP Option %d required %d bytes", aOption, sizeOptionValue)); + if (retryingAfterLossOfSignificantData) { + break; + } + retryingAfterLossOfSignificantData = true; + } + } while (rv == NS_ERROR_LOSS_OF_SIGNIFICANT_DATA); + if (rv != NS_OK) { + LOG(("Failed to get DHCP Option %d nsWindowsDHCPClient::GetOption due to error %d", aOption, rv)); + return rv; + } + aRetVal.Assign(optionValue.data(), sizeOptionValue); + return NS_OK; +} + +#define NS_WINDOWSDHCPCLIENTSERVICE_CID /* {FEBF1D69-4D7D-4891-9524-045AD18B5592} */\ + { 0xFEBF1D69, 0x4D7D, 0x4891, \ + {0x95, 0x24, 0x04, 0x5a, 0xd1, 0x8b, 0x55, 0x92 } } + +NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsWindowsDHCPClient, Init) +NS_DEFINE_NAMED_CID(NS_WINDOWSDHCPCLIENTSERVICE_CID); + +static const mozilla::Module::CIDEntry kSysDHCPClientCIDs[] = { + { &kNS_WINDOWSDHCPCLIENTSERVICE_CID, false, nullptr, nsWindowsDHCPClientConstructor }, + { nullptr } +}; + +static const mozilla::Module::ContractIDEntry kSysDHCPClientContracts[] = { + { NS_DHCPCLIENT_CONTRACTID, &kNS_WINDOWSDHCPCLIENTSERVICE_CID }, + { nullptr } +}; + +static const mozilla::Module kSysDHCPClientModule = { + mozilla::Module::kVersion, + kSysDHCPClientCIDs, + kSysDHCPClientContracts +}; + +NSMODULE_DEFN(nsDHCPClientModule) = &kSysDHCPClientModule; + +} // namespace windowsDHCPClient +} // namespace system +} // namespace toolkit +} // namespace mozilla diff --git a/toolkit/system/windowsDHCPClient/nsWindowsDHCPClient.h b/toolkit/system/windowsDHCPClient/nsWindowsDHCPClient.h new file mode 100644 index 000000000000..99b4786d0518 --- /dev/null +++ b/toolkit/system/windowsDHCPClient/nsWindowsDHCPClient.h @@ -0,0 +1,38 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "nsIDHCPClient.h" +#include "nsIServiceManager.h" +#include "nsNetCID.h" +#include "WindowsNetworkFunctionsWrapper.h" + +namespace mozilla { +namespace toolkit { +namespace system { +namespace windowsDHCPClient { + +class nsWindowsDHCPClient final : public nsIDHCPClient +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIDHCPCLIENT + + explicit nsWindowsDHCPClient(WindowsNetworkFunctionsWrapper *aNetworkFunctions = new WindowsNetworkFunctionsWrapper()) + : mNetworkFunctions(aNetworkFunctions) {}; + nsresult Init(); + +private: + + ~nsWindowsDHCPClient() {}; + WindowsNetworkFunctionsWrapper* mNetworkFunctions; + +}; + + +} // namespace windowsDHCPClient +} // namespace system +} // namespace toolkit +} // namespace mozilla diff --git a/toolkit/system/windowsDHCPClient/tests/gtest/TestDHCPUtils.cpp b/toolkit/system/windowsDHCPClient/tests/gtest/TestDHCPUtils.cpp new file mode 100644 index 000000000000..7f415f7cd1f3 --- /dev/null +++ b/toolkit/system/windowsDHCPClient/tests/gtest/TestDHCPUtils.cpp @@ -0,0 +1,323 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "DHCPUtils.h" +#include "gtest/gtest.h" +#include "nsString.h" +#include "nsWindowsDHCPClient.h" + +using namespace mozilla::toolkit::system::windowsDHCPClient; + + + +class WindowsNetworkFunctionsMock : public WindowsNetworkFunctionsWrapper { + + public: + WindowsNetworkFunctionsMock():mAddressesToReturn(nullptr) { + memset(mOptions, 0, sizeof(char*) * 256); + } + + + ULONG GetAdaptersAddressesWrapped( + _In_ ULONG Family, + _In_ ULONG Flags, + _In_ PVOID Reserved, + _Inout_ PIP_ADAPTER_ADDRESSES AdapterAddresses, + _Inout_ PULONG SizePointer + ){ + if (*SizePointer < sizeof(*mAddressesToReturn)){ + *SizePointer = sizeof(*mAddressesToReturn); + return ERROR_BUFFER_OVERFLOW; + } + + *SizePointer = sizeof(*mAddressesToReturn); + memcpy(AdapterAddresses, mAddressesToReturn, + *SizePointer); + return 0; + } + + DWORD DhcpRequestParamsWrapped( + _In_ DWORD Flags, + _In_ LPVOID Reserved, + _In_ LPWSTR AdapterName, + _In_ LPDHCPCAPI_CLASSID ClassId, + _In_ DHCPCAPI_PARAMS_ARRAY SendParams, + _Inout_ DHCPCAPI_PARAMS_ARRAY RecdParams, + _In_ LPBYTE Buffer, + _Inout_ LPDWORD pSize, + _In_ LPWSTR RequestIdStr + ) + { + mLastRequestedNetworkAdapterName.Assign(AdapterName); + + if (mOptions[RecdParams.Params[0].OptionId] == nullptr) { + RecdParams.Params[0].nBytesData = 0; + } + else { + RecdParams.Params[0].Data = Buffer; + size_t lengthOfValue = strlen(mOptions[RecdParams.Params[0].OptionId]); + if (*pSize > lengthOfValue) { + memcpy(Buffer, mOptions[RecdParams.Params[0].OptionId], lengthOfValue); + RecdParams.Params[0].nBytesData = lengthOfValue; + } else { + *pSize = lengthOfValue; + return ERROR_MORE_DATA; + } + } + return 0; + } + + void + AddAdapterAddresses(IP_ADAPTER_ADDRESSES& aAddressesToAdd) + { + if (mAddressesToReturn == nullptr) { + mAddressesToReturn = &aAddressesToAdd; + return; + } + IP_ADAPTER_ADDRESSES* tail = mAddressesToReturn; + + while (tail->Next != nullptr) { + tail = tail->Next; + } + tail->Next = &aAddressesToAdd; + } + + void + SetDHCPOption(uint8_t option, char* value) + { + mOptions[option] = value; + } + + nsString + GetLastRequestedNetworkAdapterName() + { + return mLastRequestedNetworkAdapterName; + } + + private: + IP_ADAPTER_ADDRESSES* mAddressesToReturn = nullptr; + char* mOptions[256]; + nsString mLastRequestedNetworkAdapterName; + + +}; + +class TestDHCPUtils : public ::testing::Test { + protected: + RefPtr mMockWindowsFunctions; + nsCString mDefaultAdapterName; + + virtual void + SetUp() + { + mMockWindowsFunctions = new WindowsNetworkFunctionsMock(); + mDefaultAdapterName.AssignLiteral("my favourite network adapter"); + } + + void + Given_DHCP_Option_Is(uint8_t option, char* value) + { + mMockWindowsFunctions.get()->SetDHCPOption(option, value); + } + + void + Given_Network_Adapter_Called( + IP_ADAPTER_ADDRESSES& adapterAddresses, + char* adapterName) + { + adapterAddresses.AdapterName = adapterName; + adapterAddresses.Next = nullptr; + adapterAddresses.Dhcpv4Server.iSockaddrLength = 0; + adapterAddresses.Dhcpv6Server.iSockaddrLength = 0; + AddAdapterAddresses(adapterAddresses); + } + + void + Given_Network_Adapter_Supports_DHCP_V4(IP_ADAPTER_ADDRESSES& adapterAddresses) + { + adapterAddresses.Dhcpv4Server.iSockaddrLength = 4; + } + + void + Given_Network_Adapter_Supports_DHCP_V6(IP_ADAPTER_ADDRESSES& adapterAddresses) + { + adapterAddresses.Dhcpv6Server.iSockaddrLength = 12; + } + + void + Given_Network_Adapter_Has_Operational_Status( + IP_ADAPTER_ADDRESSES& adapterAddresses, + IF_OPER_STATUS operStatus) + { + adapterAddresses.OperStatus = operStatus; + } + + private: + void + AddAdapterAddresses(IP_ADAPTER_ADDRESSES& aAddressToAdd) + { + mMockWindowsFunctions.get()->AddAdapterAddresses(aAddressToAdd); + } + + +}; + +// following class currently just distinguishes tests of nsWindowsDHCPClient from +// tests of DHCPUtils. +class TestNsWindowsDHCPClient : public TestDHCPUtils { }; + + +TEST_F(TestDHCPUtils, TestGetAdaptersAddresses) +{ + IP_ADAPTER_ADDRESSES adapterAddresses = {}; + Given_Network_Adapter_Called(adapterAddresses, "my favourite network adapter"); + Given_Network_Adapter_Supports_DHCP_V4(adapterAddresses); + Given_Network_Adapter_Has_Operational_Status(adapterAddresses, IfOperStatusUp); + + nsCString networkAdapterName; + + ASSERT_EQ(NS_OK, GetActiveDHCPNetworkAdapterName(networkAdapterName, mMockWindowsFunctions)); + + ASSERT_STREQ(networkAdapterName.Data(), "my favourite network adapter"); +} + +TEST_F(TestDHCPUtils, TestGetAdaptersAddressesNoAvailableNetworks) +{ + IP_ADAPTER_ADDRESSES adapterAddresses = {}; + Given_Network_Adapter_Called(adapterAddresses, "my favourite network adapter"); + Given_Network_Adapter_Supports_DHCP_V4(adapterAddresses); + Given_Network_Adapter_Has_Operational_Status(adapterAddresses, IfOperStatusDown); + + nsCString networkAdapterName; + ASSERT_EQ(NS_ERROR_NOT_AVAILABLE, GetActiveDHCPNetworkAdapterName(networkAdapterName, mMockWindowsFunctions)); + + ASSERT_STREQ(networkAdapterName.Data(), ""); +} + +TEST_F(TestDHCPUtils, TestGetAdaptersAddressesNoNetworksWithDHCP) +{ + IP_ADAPTER_ADDRESSES adapterAddresses = {}; + Given_Network_Adapter_Called(adapterAddresses, "my favourite network adapter"); + Given_Network_Adapter_Has_Operational_Status(adapterAddresses, IfOperStatusUp); + + nsCString networkAdapterName; + ASSERT_EQ(NS_ERROR_NOT_AVAILABLE, GetActiveDHCPNetworkAdapterName(networkAdapterName, mMockWindowsFunctions)); + + ASSERT_STREQ(networkAdapterName.Data(), ""); +} + +TEST_F(TestDHCPUtils, TestGetAdaptersAddressesSecondNetworkIsAvailable) +{ + IP_ADAPTER_ADDRESSES adapterAddresses = {}; + Given_Network_Adapter_Called(adapterAddresses, "my favourite network adapter"); + Given_Network_Adapter_Supports_DHCP_V4(adapterAddresses); + Given_Network_Adapter_Has_Operational_Status(adapterAddresses, IfOperStatusDown); + + + IP_ADAPTER_ADDRESSES secondAdapterAddresses = {}; + Given_Network_Adapter_Called(secondAdapterAddresses, "my second favourite network adapter"); + Given_Network_Adapter_Supports_DHCP_V6(secondAdapterAddresses); + Given_Network_Adapter_Has_Operational_Status(secondAdapterAddresses, IfOperStatusUp); + + nsCString networkAdapterName; + ASSERT_EQ(NS_OK, GetActiveDHCPNetworkAdapterName(networkAdapterName, mMockWindowsFunctions)); + + ASSERT_STREQ(networkAdapterName.Data(), "my second favourite network adapter"); +} + + +TEST_F(TestDHCPUtils, TestGetOption) +{ + + char* pacURL = "http://pac.com"; + Given_DHCP_Option_Is(1, "My network option"); + Given_DHCP_Option_Is(252, pacURL); + + std::vector optionValue(255, *"originalValue originalValue"); + memcpy(optionValue.data(), "originalValue originalValue", strlen("originalValue originalValue") + 1); + + uint32_t size = 255; + + nsresult retVal = RetrieveOption(mDefaultAdapterName, 252, optionValue, &size, mMockWindowsFunctions); + + ASSERT_EQ(strlen(pacURL), size); + ASSERT_STREQ("http://pac.comoriginalValue", optionValue.data()); + ASSERT_EQ(NS_OK, retVal); +} + +TEST_F(TestDHCPUtils, TestGetAbsentOption) +{ + std::vector optionValue(255); + uint32_t size = 256; + memcpy(optionValue.data(), "originalValue", strlen("originalValue") + 1); + + nsresult retVal = RetrieveOption(mDefaultAdapterName, 252, optionValue, &size, mMockWindowsFunctions); + + ASSERT_EQ(0, size); + ASSERT_EQ(NS_ERROR_NOT_AVAILABLE, retVal); +} + +TEST_F(TestDHCPUtils, TestGetTooLongOption) +{ + Given_DHCP_Option_Is(252, "http://pac.com"); + + std::vector optionValue(255); + memcpy(optionValue.data(), "originalValue", strlen("originalValue") + 1); + uint32_t size = 4; + nsresult retVal = RetrieveOption(mDefaultAdapterName, 252, optionValue, &size, mMockWindowsFunctions); + + ASSERT_STREQ("httpinalValue", optionValue.data()); + ASSERT_EQ(NS_ERROR_LOSS_OF_SIGNIFICANT_DATA, retVal); + ASSERT_EQ(strlen("http://pac.com"), size); +} + +TEST_F(TestNsWindowsDHCPClient, TestGettingOptionThroughNSWindowsDHCPClient) +{ + IP_ADAPTER_ADDRESSES adapterAddresses = {}; + Given_Network_Adapter_Called(adapterAddresses, "my favourite network adapter"); + Given_Network_Adapter_Supports_DHCP_V4(adapterAddresses); + Given_Network_Adapter_Has_Operational_Status(adapterAddresses, IfOperStatusUp); + Given_DHCP_Option_Is(252, "http://pac.com"); + + nsCString optionValue; + nsCOMPtr dhcpClient = new nsWindowsDHCPClient(mMockWindowsFunctions); + nsresult retVal = dhcpClient->GetOption(252, optionValue); + + ASSERT_STREQ("http://pac.com", optionValue.Data()); + ASSERT_STREQ(L"my favourite network adapter", mMockWindowsFunctions->GetLastRequestedNetworkAdapterName().Data()); + ASSERT_EQ(NS_OK, retVal); +} + +TEST_F(TestNsWindowsDHCPClient, TestGettingOptionThroughNSWindowsDHCPClientWhenNoAvailableNetworkAdapter) +{ + IP_ADAPTER_ADDRESSES adapterAddresses = {}; + Given_Network_Adapter_Called(adapterAddresses, "my favourite network adapter"); + Given_Network_Adapter_Has_Operational_Status(adapterAddresses, IfOperStatusDown); + Given_DHCP_Option_Is(252, "http://pac.com"); + + nsCString optionValue; + nsCOMPtr dhcpClient = new nsWindowsDHCPClient(mMockWindowsFunctions); + nsresult retVal = dhcpClient->GetOption(252, optionValue); + + ASSERT_STREQ("", optionValue.Data()); + ASSERT_EQ(NS_ERROR_NOT_AVAILABLE, retVal); +} + +TEST_F(TestNsWindowsDHCPClient, TestGettingAbsentOptionThroughNSWindowsDHCPClient) +{ + IP_ADAPTER_ADDRESSES adapterAddresses = {}; + Given_Network_Adapter_Called(adapterAddresses, "my favourite network adapter"); + Given_Network_Adapter_Supports_DHCP_V6(adapterAddresses); + Given_Network_Adapter_Has_Operational_Status(adapterAddresses, IfOperStatusUp); + + nsCString optionValue; + nsCOMPtr dhcpClient = new nsWindowsDHCPClient(mMockWindowsFunctions); + nsresult retVal = dhcpClient->GetOption(252, optionValue); + + ASSERT_STREQ("", optionValue.Data()); + ASSERT_EQ(NS_ERROR_NOT_AVAILABLE, retVal); +} diff --git a/toolkit/system/windowsDHCPClient/tests/gtest/moz.build b/toolkit/system/windowsDHCPClient/tests/gtest/moz.build new file mode 100644 index 000000000000..7c1998ed2224 --- /dev/null +++ b/toolkit/system/windowsDHCPClient/tests/gtest/moz.build @@ -0,0 +1,21 @@ +# -*- 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/. + +with Files('**'): + BUG_COMPONENT = ('Core', 'Networking: HTTP') + +UNIFIED_SOURCES += [ + 'TestDHCPUtils.cpp', +] + +LOCAL_INCLUDES += [ + '/toolkit/system/windowsDHCPClient', +] + +FINAL_LIBRARY = 'xul-gtest' + +if CONFIG['GNU_CXX']: + CXXFLAGS += ['-Wshadow']