diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/android/net/captiveportal/CapportApiProbeResult.java | 47 | ||||
-rwxr-xr-x | src/com/android/server/connectivity/NetworkMonitor.java | 157 |
2 files changed, 137 insertions, 67 deletions
diff --git a/src/android/net/captiveportal/CapportApiProbeResult.java b/src/android/net/captiveportal/CapportApiProbeResult.java new file mode 100644 index 0000000..3ef07ce --- /dev/null +++ b/src/android/net/captiveportal/CapportApiProbeResult.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.net.captiveportal; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.networkstack.apishim.CaptivePortalDataShim; + +/** + * Captive portal probe detection result including capport API detection result. + * @hide + */ +public class CapportApiProbeResult extends CaptivePortalProbeResult { + @NonNull + private final CaptivePortalDataShim mCapportData; + + public CapportApiProbeResult(@NonNull CaptivePortalProbeResult result, + @NonNull CaptivePortalDataShim capportData) { + this(result.mHttpResponseCode, result.redirectUrl, result.detectUrl, capportData, + result.probeType); + } + + public CapportApiProbeResult(int httpResponseCode, @Nullable String redirectUrl, + @Nullable String detectUrl, @Nullable CaptivePortalDataShim capportData, + int probeType) { + super(httpResponseCode, redirectUrl, detectUrl, probeType); + mCapportData = capportData; + } + + public CaptivePortalDataShim getCaptivePortalData() { + return mCapportData; + } +} diff --git a/src/com/android/server/connectivity/NetworkMonitor.java b/src/com/android/server/connectivity/NetworkMonitor.java index eed63e6..40ad87d 100755 --- a/src/com/android/server/connectivity/NetworkMonitor.java +++ b/src/com/android/server/connectivity/NetworkMonitor.java @@ -103,6 +103,7 @@ import android.net.NetworkCapabilities; import android.net.ProxyInfo; import android.net.TrafficStats; import android.net.Uri; +import android.net.captiveportal.CapportApiProbeResult; import android.net.captiveportal.CaptivePortalProbeResult; import android.net.captiveportal.CaptivePortalProbeSpec; import android.net.metrics.IpConnectivityLog; @@ -406,7 +407,8 @@ public class NetworkMonitor extends StateMachine { private final Stopwatch mEvaluationTimer = new Stopwatch(); // This variable is set before transitioning to the mCaptivePortalState. - private CaptivePortalProbeResult mLastPortalProbeResult = CaptivePortalProbeResult.FAILED; + private CaptivePortalProbeResult mLastPortalProbeResult = + CaptivePortalProbeResult.failed(CaptivePortalProbeResult.PROBE_UNKNOWN); // Random generator to select fallback URL index private final Random mRandom; @@ -1840,7 +1842,7 @@ public class NetworkMonitor extends StateMachine { protected CaptivePortalProbeResult isCaptivePortal() { if (!mIsCaptivePortalCheckEnabled) { validationLog("Validation disabled."); - return CaptivePortalProbeResult.SUCCESS; + return CaptivePortalProbeResult.success(CaptivePortalProbeResult.PROBE_UNKNOWN); } URL pacUrl = null; @@ -1868,13 +1870,13 @@ public class NetworkMonitor extends StateMachine { if (proxyInfo != null && !Uri.EMPTY.equals(proxyInfo.getPacFileUrl())) { pacUrl = makeURL(proxyInfo.getPacFileUrl().toString()); if (pacUrl == null) { - return CaptivePortalProbeResult.FAILED; + return CaptivePortalProbeResult.failed(CaptivePortalProbeResult.PROBE_UNKNOWN); } } if ((pacUrl == null) && (httpUrls.length == 0 || httpsUrls.length == 0 || httpUrls[0] == null || httpsUrls[0] == null)) { - return CaptivePortalProbeResult.FAILED; + return CaptivePortalProbeResult.failed(CaptivePortalProbeResult.PROBE_UNKNOWN); } long startTime = SystemClock.elapsedRealtime(); @@ -2073,7 +2075,8 @@ public class NetworkMonitor extends StateMachine { logValidationProbe(probeTimer.stop(), probeType, httpResponseCode); if (probeSpec == null) { - return new CaptivePortalProbeResult(httpResponseCode, redirectUrl, url.toString()); + return new CaptivePortalProbeResult(httpResponseCode, redirectUrl, url.toString(), + 1 << probeType); } else { return probeSpec.getResult(httpResponseCode, redirectUrl); } @@ -2162,33 +2165,29 @@ public class NetworkMonitor extends StateMachine { } } - private abstract static class ProbeThread extends Thread { + private class ProbeThread extends Thread { private final CountDownLatch mLatch; - private final ProxyInfo mProxy; - private final URL mUrl; - protected final Uri mCaptivePortalApiUrl; + private final Probe mProbe; - protected ProbeThread(CountDownLatch latch, ProxyInfo proxy, URL url, + ProbeThread(CountDownLatch latch, ProxyInfo proxy, URL url, int probeType, Uri captivePortalApiUrl) { mLatch = latch; - mProxy = proxy; - mUrl = url; - mCaptivePortalApiUrl = captivePortalApiUrl; + mProbe = (probeType == ValidationProbeEvent.PROBE_HTTPS) + ? new HttpsProbe(proxy, url, captivePortalApiUrl) + : new HttpProbe(proxy, url, captivePortalApiUrl); + mResult = CaptivePortalProbeResult.failed(probeType); } - private volatile CaptivePortalProbeResult mResult = CaptivePortalProbeResult.FAILED; + private volatile CaptivePortalProbeResult mResult; public CaptivePortalProbeResult result() { return mResult; } - protected abstract CaptivePortalProbeResult sendProbe(ProxyInfo proxy, URL url); - public abstract boolean isConclusiveResult(CaptivePortalProbeResult result); - @Override public void run() { - mResult = sendProbe(mProxy, mUrl); - if (isConclusiveResult(mResult)) { + mResult = mProbe.sendProbe(); + if (isConclusiveResult(mResult, mProbe.mCaptivePortalApiUrl)) { // Stop waiting immediately if any probe is conclusive. while (mLatch.getCount() > 0) { mLatch.countDown(); @@ -2199,36 +2198,34 @@ public class NetworkMonitor extends StateMachine { } } - final class HttpsProbeThread extends ProbeThread { - HttpsProbeThread(CountDownLatch latch, ProxyInfo proxy, URL url, Uri captivePortalApiUrl) { - super(latch, proxy, url, captivePortalApiUrl); + private abstract static class Probe { + protected final ProxyInfo mProxy; + protected final URL mUrl; + protected final Uri mCaptivePortalApiUrl; + + protected Probe(ProxyInfo proxy, URL url, Uri captivePortalApiUrl) { + mProxy = proxy; + mUrl = url; + mCaptivePortalApiUrl = captivePortalApiUrl; } + // sendProbe() is synchronous and blocks until it has the result. + protected abstract CaptivePortalProbeResult sendProbe(); + } - @Override - protected CaptivePortalProbeResult sendProbe(ProxyInfo proxy, URL url) { - return sendDnsAndHttpProbes(proxy, url, ValidationProbeEvent.PROBE_HTTPS); + final class HttpsProbe extends Probe { + HttpsProbe(ProxyInfo proxy, URL url, Uri captivePortalApiUrl) { + super(proxy, url, captivePortalApiUrl); } @Override - public boolean isConclusiveResult(CaptivePortalProbeResult result) { - // isPortal() is not expected on the HTTPS probe, but check it nonetheless. - // In case the capport API is available, the API is authoritative on whether there is - // a portal, so the HTTPS probe is not enough to conclude there is connectivity, - // and a determination will be made once the capport API probe returns. Note that the - // API can only force the system to detect a portal even if the HTTPS probe succeeds. - // It cannot force the system to detect no portal if the HTTPS probe fails. - return (result.isPortal() || result.isSuccessful()) && mCaptivePortalApiUrl == null; + protected CaptivePortalProbeResult sendProbe() { + return sendDnsAndHttpProbes(mProxy, mUrl, ValidationProbeEvent.PROBE_HTTPS); } } - final class HttpProbeThread extends ProbeThread { - private volatile CaptivePortalDataShim mCapportData; - HttpProbeThread(CountDownLatch latch, ProxyInfo proxy, URL url, Uri captivePortalApiUrl) { - super(latch, proxy, url, captivePortalApiUrl); - } - - CaptivePortalDataShim getCaptivePortalData() { - return mCapportData; + final class HttpProbe extends Probe { + HttpProbe(ProxyInfo proxy, URL url, Uri captivePortalApiUrl) { + super(proxy, url, captivePortalApiUrl); } private CaptivePortalDataShim tryCapportApiProbe() { @@ -2277,33 +2274,61 @@ public class NetworkMonitor extends StateMachine { } @Override - protected CaptivePortalProbeResult sendProbe(ProxyInfo proxy, URL url) { - mCapportData = tryCapportApiProbe(); - if (mCapportData != null && mCapportData.isCaptive()) { - if (mCapportData.getUserPortalUrl() == null) { + protected CaptivePortalProbeResult sendProbe() { + final CaptivePortalDataShim capportData = tryCapportApiProbe(); + if (capportData != null && capportData.isCaptive()) { + if (capportData.getUserPortalUrl() == null) { validationLog("Missing user-portal-url from capport response"); - return sendDnsAndHttpProbes(proxy, url, ValidationProbeEvent.PROBE_HTTP); + return sendDnsAndHttpProbes(mProxy, mUrl, ValidationProbeEvent.PROBE_HTTP); } - final String loginUrlString = mCapportData.getUserPortalUrl().toString(); + final String loginUrlString = capportData.getUserPortalUrl().toString(); // Starting from R (where CaptivePortalData was introduced), the captive portal app // delegates to NetworkMonitor for verifying when the network validates instead of // probing the detectUrl. So pass the detectUrl to have the portal open on that, // page; CaptivePortalLogin will not use it for probing. - return new CaptivePortalProbeResult( + return new CapportApiProbeResult( CaptivePortalProbeResult.PORTAL_CODE, loginUrlString /* redirectUrl */, - loginUrlString /* detectUrl */); + loginUrlString /* detectUrl */, + capportData, + 1 << ValidationProbeEvent.PROBE_HTTP); } // If the API says it's not captive, still check for HTTP connectivity. This helps // with partial connectivity detection, and a broken API saying that there is no // redirect when there is one. - return sendDnsAndHttpProbes(proxy, url, ValidationProbeEvent.PROBE_HTTP); + final CaptivePortalProbeResult res = + sendDnsAndHttpProbes(mProxy, mUrl, ValidationProbeEvent.PROBE_HTTP); + return capportData == null ? res : new CapportApiProbeResult(res, capportData); } + } - @Override - public boolean isConclusiveResult(CaptivePortalProbeResult result) { - return result.isPortal(); + private static boolean isConclusiveResult(@NonNull CaptivePortalProbeResult result, + @Nullable Uri captivePortalApiUrl) { + // isPortal() is not expected on the HTTPS probe, but treat the network as portal would make + // sense if the probe reports portal. In case the capport API is available, the API is + // authoritative on whether there is a portal, so the HTTPS probe is not enough to conclude + // there is connectivity, and a determination will be made once the capport API probe + // returns. Note that the API can only force the system to detect a portal even if the HTTPS + // probe succeeds. It cannot force the system to detect no portal if the HTTPS probe fails. + return result.isPortal() + || (result.isConcludedFromHttps() && result.isSuccessful() + && captivePortalApiUrl == null); + } + + private void reportProbeResult(@NonNull CaptivePortalProbeResult res) { + if (res instanceof CapportApiProbeResult) { + maybeReportCaptivePortalData(((CapportApiProbeResult) res).getCaptivePortalData()); + } + + // This is not a if-else case since partial connectivity will concluded from both HTTP and + // HTTPS probe. Both HTTP and HTTPS result should be reported. + if (res.isConcludedFromHttps()) { + reportHttpProbeResult(NETWORK_VALIDATION_PROBE_HTTPS, res); + } + + if (res.isConcludedFromHttp()) { + reportHttpProbeResult(NETWORK_VALIDATION_PROBE_HTTP, res); } } @@ -2314,9 +2339,10 @@ public class NetworkMonitor extends StateMachine { final CountDownLatch latch = new CountDownLatch(2); final Uri capportApiUrl = getCaptivePortalApiUrl(mLinkProperties); - final HttpsProbeThread httpsProbe = new HttpsProbeThread(latch, proxy, httpsUrl, - capportApiUrl); - final HttpProbeThread httpProbe = new HttpProbeThread(latch, proxy, httpUrl, capportApiUrl); + final ProbeThread httpsProbe = new ProbeThread(latch, proxy, httpsUrl, + ValidationProbeEvent.PROBE_HTTPS, capportApiUrl); + final ProbeThread httpProbe = new ProbeThread(latch, proxy, httpUrl, + ValidationProbeEvent.PROBE_HTTP, capportApiUrl); try { httpsProbe.start(); @@ -2324,22 +2350,20 @@ public class NetworkMonitor extends StateMachine { latch.await(PROBE_TIMEOUT_MS, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { validationLog("Error: probes wait interrupted!"); - return CaptivePortalProbeResult.FAILED; + return CaptivePortalProbeResult.failed(CaptivePortalProbeResult.PROBE_UNKNOWN); } final CaptivePortalProbeResult httpsResult = httpsProbe.result(); final CaptivePortalProbeResult httpResult = httpProbe.result(); // Look for a conclusive probe result first. - if (httpProbe.isConclusiveResult(httpResult)) { - maybeReportCaptivePortalData(httpProbe.getCaptivePortalData()); - reportHttpProbeResult(NETWORK_VALIDATION_PROBE_HTTP, httpResult); + if (isConclusiveResult(httpResult, capportApiUrl)) { + reportProbeResult(httpProbe.result()); return httpResult; } - if (httpsProbe.isConclusiveResult(httpsResult)) { - maybeReportCaptivePortalData(httpProbe.getCaptivePortalData()); - reportHttpProbeResult(NETWORK_VALIDATION_PROBE_HTTPS, httpsResult); + if (isConclusiveResult(httpsResult, capportApiUrl)) { + reportProbeResult(httpsProbe.result()); return httpsResult; } // Consider a DNS response with a private IP address on the HTTP probe as an indication that @@ -2350,7 +2374,7 @@ public class NetworkMonitor extends StateMachine { // probe should not be delayed by this check. if (mPrivateIpNoInternetEnabled && (httpResult.isDnsPrivateIpResponse())) { validationLog("DNS response to the URL is private IP"); - return CaptivePortalProbeResult.FAILED; + return CaptivePortalProbeResult.failed(1 << ValidationProbeEvent.PROBE_HTTP); } // If a fallback method exists, use it to retry portal detection. // If we have new-style probe specs, use those. Otherwise, use the fallback URLs. @@ -2367,8 +2391,7 @@ public class NetworkMonitor extends StateMachine { // Otherwise wait until http and https probes completes and use their results. try { httpProbe.join(); - maybeReportCaptivePortalData(httpProbe.getCaptivePortalData()); - reportHttpProbeResult(NETWORK_VALIDATION_PROBE_HTTP, httpProbe.result()); + reportProbeResult(httpProbe.result()); if (httpProbe.result().isPortal()) { return httpProbe.result(); @@ -2386,7 +2409,7 @@ public class NetworkMonitor extends StateMachine { return httpsProbe.result(); } catch (InterruptedException e) { validationLog("Error: http or https probe wait interrupted!"); - return CaptivePortalProbeResult.FAILED; + return CaptivePortalProbeResult.failed(CaptivePortalProbeResult.PROBE_UNKNOWN); } } |