Loading common/netlinkclient/src/android/net/netlink/NduseroptMessage.java +8 −4 Original line number Diff line number Diff line Loading @@ -66,22 +66,23 @@ public class NduseroptMessage extends NetlinkMessage { super(header); // The structure itself. buf.order(ByteOrder.nativeOrder()); buf.order(ByteOrder.nativeOrder()); // Restored in the finally clause inside parse(). final int start = buf.position(); family = buf.get(); buf.get(); // Skip 1 byte of padding. opts_len = Short.toUnsignedInt(buf.getShort()); ifindex = buf.getInt(); icmp_type = buf.get(); icmp_code = buf.get(); buf.order(ByteOrder.BIG_ENDIAN); buf.position(buf.position() + 6); // Skip 6 bytes of padding. // The ND option. // Ensure we don't read past opts_len even if the option length is invalid. // Note that this check is not really necessary since if the option length is not valid, // this struct won't be very useful to the caller. buf.order(ByteOrder.BIG_ENDIAN); int oldLimit = buf.limit(); buf.limit(STRUCT_SIZE + opts_len); buf.limit(start + STRUCT_SIZE + opts_len); try { option = NdOption.parse(buf); } finally { Loading @@ -89,7 +90,7 @@ public class NduseroptMessage extends NetlinkMessage { } // The source address. int newPosition = STRUCT_SIZE + opts_len; int newPosition = start + STRUCT_SIZE + opts_len; if (newPosition >= buf.limit()) { throw new IllegalArgumentException("ND options extend past end of buffer"); } Loading Loading @@ -118,6 +119,7 @@ public class NduseroptMessage extends NetlinkMessage { */ public static NduseroptMessage parse(@NonNull StructNlMsgHdr header, @NonNull ByteBuffer buf) { if (buf == null || buf.remaining() < STRUCT_SIZE) return null; ByteOrder oldOrder = buf.order(); try { return new NduseroptMessage(header, buf); } catch (IllegalArgumentException | UnknownHostException | BufferUnderflowException e) { Loading @@ -125,6 +127,8 @@ public class NduseroptMessage extends NetlinkMessage { // Convention in this package is that null indicates that the option was truncated, so // callers must already handle it. return null; } finally { buf.order(oldOrder); } } Loading src/android/net/dhcp/DhcpClient.java +6 −0 Original line number Diff line number Diff line Loading @@ -233,6 +233,9 @@ public class DhcpClient extends StateMachine { public static final int CMD_START_PRECONNECTION = PUBLIC_BASE + 10; public static final int CMD_ABORT_PRECONNECTION = PUBLIC_BASE + 11; // Command to rebind the leased IPv4 address on L2 roaming happened. public static final int CMD_REFRESH_LINKADDRESS = PUBLIC_BASE + 12; /* Message.arg1 arguments to CMD_POST_DHCP_ACTION notification */ public static final int DHCP_SUCCESS = 1; public static final int DHCP_FAILURE = 2; Loading Loading @@ -1674,6 +1677,9 @@ public class DhcpClient extends StateMachine { case CMD_RENEW_DHCP: preDhcpTransitionTo(mWaitBeforeRenewalState, mDhcpRenewingState); return HANDLED; case CMD_REFRESH_LINKADDRESS: transitionTo(mDhcpRebindingState); return HANDLED; default: return NOT_HANDLED; } Loading src/android/net/dhcp/DhcpPacket.java +2 −1 Original line number Diff line number Diff line Loading @@ -335,7 +335,8 @@ public abstract class DhcpPacket { * proposed by the client (from an earlier DHCP negotiation) or * supplied by the server. */ protected final Inet4Address mClientIp; @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) public final Inet4Address mClientIp; protected final Inet4Address mYourIp; private final Inet4Address mNextIp; protected final Inet4Address mRelayIp; Loading src/android/net/ip/IpClient.java +68 −4 Original line number Diff line number Diff line Loading @@ -32,6 +32,7 @@ import android.net.Layer2InformationParcelable; import android.net.Layer2PacketParcelable; import android.net.LinkAddress; import android.net.LinkProperties; import android.net.MacAddress; import android.net.NattKeepalivePacketDataParcelable; import android.net.NetworkStackIpMemoryStore; import android.net.ProvisioningConfigurationParcelable; Loading Loading @@ -92,6 +93,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; Loading Loading @@ -381,7 +383,8 @@ public class IpClient extends StateMachine { private static final int CMD_ADD_KEEPALIVE_PACKET_FILTER_TO_APF = 13; private static final int CMD_REMOVE_KEEPALIVE_PACKET_FILTER_FROM_APF = 14; private static final int CMD_UPDATE_L2KEY_GROUPHINT = 15; protected static final int CMD_COMPLETE_PRECONNECTION = 16; private static final int CMD_COMPLETE_PRECONNECTION = 16; private static final int CMD_UPDATE_L2INFORMATION = 17; // Internal commands to use instead of trying to call transitionTo() inside // a given State's enter() method. Calling transitionTo() from enter/exit Loading Loading @@ -423,6 +426,14 @@ public class IpClient extends StateMachine { new byte[] { (byte) 0x00, (byte) 0x17, (byte) 0xf2, (byte) 0x06 } )); // Initialize configurable particular SSID set supporting DHCP Roaming feature. See // b/131797393 for more details. private static final Set<String> DHCP_ROAMING_SSID_SET = new HashSet<>( Arrays.asList( "0001docomo", "ollehWiFi", "olleh GiGa WiFi", "KT WiFi", "KT GiGA WiFi", "marente" )); private final State mStoppedState = new StoppedState(); private final State mStoppingState = new StoppingState(); private final State mClearingIpAddressesState = new ClearingIpAddressesState(); Loading Loading @@ -470,6 +481,7 @@ public class IpClient extends StateMachine { private String mGroupHint; // The group hint for this network, for writing into the memory store private boolean mMulticastFiltering; private long mStartTimeMillis; private MacAddress mCurrentBssid; /** * Reading the snapshot is an asynchronous operation initiated by invoking Loading Loading @@ -704,7 +716,6 @@ public class IpClient extends StateMachine { enforceNetworkStackCallingPermission(); IpClient.this.notifyPreconnectionComplete(success); } @Override public void updateLayer2Information(Layer2InformationParcelable info) { enforceNetworkStackCallingPermission(); Loading Loading @@ -772,6 +783,16 @@ public class IpClient extends StateMachine { return; } final ScanResultInfo scanResultInfo = req.mScanResultInfo; mCurrentBssid = null; if (scanResultInfo != null) { try { mCurrentBssid = MacAddress.fromString(scanResultInfo.getBssid()); } catch (IllegalArgumentException e) { Log.wtf(mTag, "Invalid BSSID: " + scanResultInfo.getBssid() + " in provisioning configuration", e); } } sendMessage(CMD_START, new android.net.shared.ProvisioningConfiguration(req)); } Loading Loading @@ -819,10 +840,15 @@ public class IpClient extends StateMachine { /** * Set the L2 key and group hint for storing info into the memory store. * * This method is only supported on Q devices. For R or above releases, * caller should call #updateLayer2Information() instead. */ public void setL2KeyAndGroupHint(String l2Key, String groupHint) { if (!ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.Q)) { sendMessage(CMD_UPDATE_L2KEY_GROUPHINT, new Pair<>(l2Key, groupHint)); } } /** * Set the HTTP Proxy configuration to use. Loading Loading @@ -880,7 +906,7 @@ public class IpClient extends StateMachine { * Update the network bssid, L2Key and GroupHint layer2 information. */ public void updateLayer2Information(@NonNull Layer2InformationParcelable info) { // TODO: add specific implementation. sendMessage(CMD_UPDATE_L2INFORMATION, info); } /** Loading Loading @@ -1502,6 +1528,36 @@ public class IpClient extends StateMachine { } } private void handleUpdateL2Information(@NonNull Layer2InformationParcelable info) { mL2Key = info.l2Key; mGroupHint = info.groupHint; // This means IpClient is still in the StoppedState, WiFi is trying to associate // to the AP, just update L2Key and GroupHint at this stage, because these members // will be used when starting DhcpClient. if (info.bssid == null || mCurrentBssid == null) return; // If the BSSID has not changed, there is nothing to do. if (info.bssid.equals(mCurrentBssid)) return; if (mIpReachabilityMonitor != null) { mIpReachabilityMonitor.probeAll(); } // Check whether to refresh previous IP lease on L2 roaming happened. final String ssid = removeDoubleQuotes(mConfiguration.mDisplayName); if (DHCP_ROAMING_SSID_SET.contains(ssid) && mDhcpClient != null) { if (DBG) { Log.d(mTag, "L2 roaming happened from " + mCurrentBssid + " to " + info.bssid + " , SSID: " + ssid + " , starting refresh leased IP address"); } mDhcpClient.sendMessage(DhcpClient.CMD_REFRESH_LINKADDRESS); } mCurrentBssid = info.bssid; } class StoppedState extends State { @Override public void enter() { Loading Loading @@ -1554,6 +1610,10 @@ public class IpClient extends StateMachine { break; } case CMD_UPDATE_L2INFORMATION: handleUpdateL2Information((Layer2InformationParcelable) msg.obj); break; case CMD_SET_MULTICAST_FILTER: mMulticastFiltering = (boolean) msg.obj; break; Loading Loading @@ -1763,6 +1823,10 @@ public class IpClient extends StateMachine { break; } case CMD_UPDATE_L2INFORMATION: handleUpdateL2Information((Layer2InformationParcelable) msg.obj); break; case EVENT_PROVISIONING_TIMEOUT: handleProvisioningFailure(); break; Loading src/com/android/server/connectivity/NetworkMonitor.java +187 −13 Original line number Diff line number Diff line Loading @@ -141,6 +141,7 @@ import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.Log; import android.util.Pair; import android.util.SparseArray; import androidx.annotation.ArrayRes; import androidx.annotation.BoolRes; Loading Loading @@ -192,7 +193,13 @@ import java.util.Objects; import java.util.Random; import java.util.StringJoiner; import java.util.UUID; import java.util.concurrent.CompletionService; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorCompletionService; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.function.Function; import java.util.regex.Matcher; Loading @@ -218,6 +225,8 @@ public class NetworkMonitor extends StateMachine { private static final int SOCKET_TIMEOUT_MS = 10000; private static final int PROBE_TIMEOUT_MS = 3000; private static final int UNSET_MCC_OR_MNC = -1; private static final int CAPPORT_API_MAX_JSON_LENGTH = 4096; private static final String ACCEPT_HEADER = "Accept"; private static final String CONTENT_TYPE_HEADER = "Content-Type"; Loading @@ -241,6 +250,24 @@ public class NetworkMonitor extends StateMachine { } } @VisibleForTesting protected static final class MccMncOverrideInfo { public final int mcc; public final int mnc; MccMncOverrideInfo(int mcc, int mnc) { this.mcc = mcc; this.mnc = mnc; } } @VisibleForTesting protected static final SparseArray<MccMncOverrideInfo> sCarrierIdToMccMnc = new SparseArray<>(); static { // CTC sCarrierIdToMccMnc.put(1854, new MccMncOverrideInfo(460, 03)); } /** * ConnectivityService has sent a notification to indicate that network has connected. * Initiates Network Validation. Loading Loading @@ -351,6 +378,10 @@ public class NetworkMonitor extends StateMachine { // Delay between reevaluations once a captive portal has been found. private static final int CAPTIVE_PORTAL_REEVALUATE_DELAY_MS = 10 * 60 * 1000; private static final int NETWORK_VALIDATION_RESULT_INVALID = 0; // Max thread pool size for parallel probing. Fixed thread pool size to control the thread // number used for either HTTP or HTTPS probing. @VisibleForTesting static final int MAX_PROBE_THREAD_POOL_SIZE = 5; private String mPrivateDnsProviderHostname = ""; private final Context mContext; Loading Loading @@ -1507,25 +1538,51 @@ public class NetworkMonitor extends StateMachine { } } /** * Return a matched MccMncOverrideInfo if carrier id and sim mccmnc are matching a record in * sCarrierIdToMccMnc. */ @VisibleForTesting protected Context getContextByMccIfNoSimCardOrDefault() { @Nullable MccMncOverrideInfo getMccMncOverrideInfo() { final int carrierId = mTelephonyManager.getSimCarrierId(); return sCarrierIdToMccMnc.get(carrierId); } private Context getContextByMccMnc(final int mcc, final int mnc) { final Configuration config = mContext.getResources().getConfiguration(); if (mcc != UNSET_MCC_OR_MNC) config.mcc = mcc; if (mnc != UNSET_MCC_OR_MNC) config.mnc = mnc; return mContext.createConfigurationContext(config); } @VisibleForTesting protected Context getCustomizedContextOrDefault() { // Return customized context if carrier id can match a record in sCarrierIdToMccMnc. final MccMncOverrideInfo overrideInfo = getMccMncOverrideInfo(); if (overrideInfo != null) { return getContextByMccMnc(overrideInfo.mcc, overrideInfo.mnc); } // Use neighbor mcc feature only works when the config_no_sim_card_uses_neighbor_mcc is // true and there is no sim card inserted. final boolean useNeighborResource = getResBooleanConfig(mContext, R.bool.config_no_sim_card_uses_neighbor_mcc, false); if (!useNeighborResource || TelephonyManager.SIM_STATE_READY == mTelephonyManager.getSimState()) { return mContext; } final String mcc = getLocationMcc(); if (TextUtils.isEmpty(mcc)) { return mContext; } final Configuration config = mContext.getResources().getConfiguration(); config.mcc = Integer.parseInt(mcc); return mContext.createConfigurationContext(config); return getContextByMccMnc(Integer.parseInt(mcc), UNSET_MCC_OR_MNC); } private String getCaptivePortalServerHttpsUrl() { final Context targetContext = getContextByMccIfNoSimCardOrDefault(); final Context targetContext = getCustomizedContextOrDefault(); return getSettingFromResource(targetContext, R.string.config_captive_portal_https_url, R.string.default_captive_portal_https_url, CAPTIVE_PORTAL_HTTPS_URL); } Loading Loading @@ -1604,7 +1661,7 @@ public class NetworkMonitor extends StateMachine { * on one URL that can be used, while NetworkMonitor may implement more complex logic. */ public String getCaptivePortalServerHttpUrl() { final Context targetContext = getContextByMccIfNoSimCardOrDefault(); final Context targetContext = getCustomizedContextOrDefault(); return getSettingFromResource(targetContext, R.string.config_captive_portal_http_url, R.string.default_captive_portal_http_url, CAPTIVE_PORTAL_HTTP_URL); } Loading Loading @@ -1759,7 +1816,7 @@ public class NetworkMonitor extends StateMachine { */ private <T> T[] getProbeUrlArrayConfig(@NonNull T[] providerValue, @ArrayRes int configResId, @ArrayRes int defaultResId, @NonNull Function<String, T> resourceConverter) { final Resources res = getContextByMccIfNoSimCardOrDefault().getResources(); final Resources res = getCustomizedContextOrDefault().getResources(); return getProbeUrlArrayConfig(providerValue, configResId, res.getStringArray(defaultResId), resourceConverter); } Loading @@ -1777,7 +1834,7 @@ public class NetworkMonitor extends StateMachine { */ private <T> T[] getProbeUrlArrayConfig(@NonNull T[] providerValue, @ArrayRes int configResId, String[] defaultConfig, @NonNull Function<String, T> resourceConverter) { final Resources res = getContextByMccIfNoSimCardOrDefault().getResources(); final Resources res = getCustomizedContextOrDefault().getResources(); String[] configValue = res.getStringArray(configResId); if (configValue.length == 0) { Loading Loading @@ -1885,9 +1942,13 @@ public class NetworkMonitor extends StateMachine { if (pacUrl != null) { result = sendDnsAndHttpProbes(null, pacUrl, ValidationProbeEvent.PROBE_PAC); reportHttpProbeResult(NETWORK_VALIDATION_PROBE_HTTP, result); } else if (mUseHttps && httpsUrls.length == 1 && httpUrls.length == 1) { // Probe results are reported inside sendHttpAndHttpsParallelWithFallbackProbes. result = sendHttpAndHttpsParallelWithFallbackProbes( proxyInfo, httpsUrls[0], httpUrls[0]); } else if (mUseHttps) { // Probe results are reported inside sendParallelHttpProbes. result = sendParallelHttpProbes(proxyInfo, httpsUrls[0], httpUrls[0]); // Support result aggregation from multiple Urls. result = sendMultiParallelHttpAndHttpsProbes(proxyInfo, httpsUrls, httpUrls); } else { result = sendDnsAndHttpProbes(proxyInfo, httpUrls[0], ValidationProbeEvent.PROBE_HTTP); reportHttpProbeResult(NETWORK_VALIDATION_PROBE_HTTP, result); Loading Loading @@ -2279,7 +2340,9 @@ public class NetworkMonitor extends StateMachine { if (capportData != null && capportData.isCaptive()) { if (capportData.getUserPortalUrl() == null) { validationLog("Missing user-portal-url from capport response"); return sendDnsAndHttpProbes(mProxy, mUrl, ValidationProbeEvent.PROBE_HTTP); return new CapportApiProbeResult( sendDnsAndHttpProbes(mProxy, mUrl, ValidationProbeEvent.PROBE_HTTP), capportData); } final String loginUrlString = capportData.getUserPortalUrl().toString(); // Starting from R (where CaptivePortalData was introduced), the captive portal app Loading @@ -2299,7 +2362,7 @@ public class NetworkMonitor extends StateMachine { // redirect when there is one. final CaptivePortalProbeResult res = sendDnsAndHttpProbes(mProxy, mUrl, ValidationProbeEvent.PROBE_HTTP); return capportData == null ? res : new CapportApiProbeResult(res, capportData); return mCaptivePortalApiUrl == null ? res : new CapportApiProbeResult(res, capportData); } } Loading @@ -2316,6 +2379,117 @@ public class NetworkMonitor extends StateMachine { && captivePortalApiUrl == null); } private CaptivePortalProbeResult sendMultiParallelHttpAndHttpsProbes(@NonNull ProxyInfo proxy, @NonNull URL[] httpsUrls, @NonNull URL[] httpUrls) { // If multiple URLs are required to ensure the correctness of validation, send parallel // probes to explore the result in separate probe threads and aggregate those results into // one as the final result for either HTTP or HTTPS. // Number of probes to wait for. final int num = httpsUrls.length + httpUrls.length; // Fixed pool to prevent configuring too many urls to exhaust system resource. final ExecutorService executor = Executors.newFixedThreadPool( Math.min(num, MAX_PROBE_THREAD_POOL_SIZE)); final CompletionService<CaptivePortalProbeResult> ecs = new ExecutorCompletionService<CaptivePortalProbeResult>(executor); final Uri capportApiUrl = getCaptivePortalApiUrl(mLinkProperties); final List<Future<CaptivePortalProbeResult>> futures = new ArrayList<>(); try { // Queue https and http probe. // Each of these HTTP probes will start with probing capport API if present. So if // multiple HTTP URLs are configured, AP will send multiple identical accesses to the // capport URL. Thus, send capport API probing with one of the HTTP probe is enough. // Probe capport API with the first HTTP probe. // TODO: Have the capport probe as a different probe for cleanliness. final URL urlMaybeWithCapport = httpUrls[0]; for (final URL url : httpUrls) { futures.add(ecs.submit(() -> new HttpProbe(proxy, url, url.equals(urlMaybeWithCapport) ? capportApiUrl : null).sendProbe())); } for (final URL url : httpsUrls) { futures.add( ecs.submit(() -> new HttpsProbe(proxy, url, capportApiUrl).sendProbe())); } final ArrayList<CaptivePortalProbeResult> completedProbes = new ArrayList<>(); for (int i = 0; i < num; i++) { completedProbes.add(ecs.take().get()); final CaptivePortalProbeResult res = evaluateCapportResult( completedProbes, httpsUrls.length, capportApiUrl != null /* hasCapport */); if (res != null) { reportProbeResult(res); return res; } } } catch (ExecutionException e) { Log.e(TAG, "Error sending probes.", e); } catch (InterruptedException e) { // Ignore interrupted probe result because result is not important to conclude the // result. } finally { // Interrupt ongoing probes since we have already gotten result from one of them. futures.forEach(future -> future.cancel(true)); executor.shutdownNow(); } return CaptivePortalProbeResult.failed(ValidationProbeEvent.PROBE_HTTPS); } @Nullable private CaptivePortalProbeResult evaluateCapportResult( List<CaptivePortalProbeResult> probes, int numHttps, boolean hasCapport) { CaptivePortalProbeResult capportResult = null; CaptivePortalProbeResult httpPortalResult = null; int httpSuccesses = 0; int httpsSuccesses = 0; int httpsFailures = 0; for (CaptivePortalProbeResult probe : probes) { if (probe instanceof CapportApiProbeResult) { capportResult = probe; } else if (probe.isConcludedFromHttps()) { if (probe.isSuccessful()) httpsSuccesses++; else httpsFailures++; } else { // http probes if (probe.isPortal()) { // Unlike https probe, http probe may have redirect url information kept in the // probe result. Thus, the result can not be newly created with response code // only. If the captive portal behavior will be varied because of different // probe URLs, this means that if the portal returns different redirect URLs for // different probes and has a different behavior depending on the URL, then the // behavior of the login page may differ depending on the order in which the // probes terminate. However, NetworkMonitor does have to choose one of the // redirect URLs and right now there is no clue at all which of the probe has // the better redirect URL, so there is no telling which is best to use. // Therefore the current code just uses whichever happens to be the last one to // complete. httpPortalResult = probe; } else if (probe.isSuccessful()) { httpSuccesses++; } } } // If there is Capport url configured but the result is not available yet, wait for it. if (hasCapport && capportResult == null) return null; // Capport API saying it's a portal is authoritative. if (capportResult != null && capportResult.isPortal()) return capportResult; // Any HTTP probes saying probe portal is conclusive. if (httpPortalResult != null) return httpPortalResult; // Any HTTPS probes works then the network validates. if (httpsSuccesses > 0) { return CaptivePortalProbeResult.success(1 << ValidationProbeEvent.PROBE_HTTPS); } // All HTTPS failed and at least one HTTP succeeded, then it's partial. if (httpsFailures == numHttps && httpSuccesses > 0) { return CaptivePortalProbeResult.PARTIAL; } // Otherwise, the result is unknown yet. return null; } private void reportProbeResult(@NonNull CaptivePortalProbeResult res) { if (res instanceof CapportApiProbeResult) { maybeReportCaptivePortalData(((CapportApiProbeResult) res).getCaptivePortalData()); Loading @@ -2332,7 +2506,7 @@ public class NetworkMonitor extends StateMachine { } } private CaptivePortalProbeResult sendParallelHttpProbes( private CaptivePortalProbeResult sendHttpAndHttpsParallelWithFallbackProbes( ProxyInfo proxy, URL httpsUrl, URL httpUrl) { // Number of probes to wait for. If a probe completes with a conclusive answer // it shortcuts the latch immediately by forcing the count to 0. Loading Loading
common/netlinkclient/src/android/net/netlink/NduseroptMessage.java +8 −4 Original line number Diff line number Diff line Loading @@ -66,22 +66,23 @@ public class NduseroptMessage extends NetlinkMessage { super(header); // The structure itself. buf.order(ByteOrder.nativeOrder()); buf.order(ByteOrder.nativeOrder()); // Restored in the finally clause inside parse(). final int start = buf.position(); family = buf.get(); buf.get(); // Skip 1 byte of padding. opts_len = Short.toUnsignedInt(buf.getShort()); ifindex = buf.getInt(); icmp_type = buf.get(); icmp_code = buf.get(); buf.order(ByteOrder.BIG_ENDIAN); buf.position(buf.position() + 6); // Skip 6 bytes of padding. // The ND option. // Ensure we don't read past opts_len even if the option length is invalid. // Note that this check is not really necessary since if the option length is not valid, // this struct won't be very useful to the caller. buf.order(ByteOrder.BIG_ENDIAN); int oldLimit = buf.limit(); buf.limit(STRUCT_SIZE + opts_len); buf.limit(start + STRUCT_SIZE + opts_len); try { option = NdOption.parse(buf); } finally { Loading @@ -89,7 +90,7 @@ public class NduseroptMessage extends NetlinkMessage { } // The source address. int newPosition = STRUCT_SIZE + opts_len; int newPosition = start + STRUCT_SIZE + opts_len; if (newPosition >= buf.limit()) { throw new IllegalArgumentException("ND options extend past end of buffer"); } Loading Loading @@ -118,6 +119,7 @@ public class NduseroptMessage extends NetlinkMessage { */ public static NduseroptMessage parse(@NonNull StructNlMsgHdr header, @NonNull ByteBuffer buf) { if (buf == null || buf.remaining() < STRUCT_SIZE) return null; ByteOrder oldOrder = buf.order(); try { return new NduseroptMessage(header, buf); } catch (IllegalArgumentException | UnknownHostException | BufferUnderflowException e) { Loading @@ -125,6 +127,8 @@ public class NduseroptMessage extends NetlinkMessage { // Convention in this package is that null indicates that the option was truncated, so // callers must already handle it. return null; } finally { buf.order(oldOrder); } } Loading
src/android/net/dhcp/DhcpClient.java +6 −0 Original line number Diff line number Diff line Loading @@ -233,6 +233,9 @@ public class DhcpClient extends StateMachine { public static final int CMD_START_PRECONNECTION = PUBLIC_BASE + 10; public static final int CMD_ABORT_PRECONNECTION = PUBLIC_BASE + 11; // Command to rebind the leased IPv4 address on L2 roaming happened. public static final int CMD_REFRESH_LINKADDRESS = PUBLIC_BASE + 12; /* Message.arg1 arguments to CMD_POST_DHCP_ACTION notification */ public static final int DHCP_SUCCESS = 1; public static final int DHCP_FAILURE = 2; Loading Loading @@ -1674,6 +1677,9 @@ public class DhcpClient extends StateMachine { case CMD_RENEW_DHCP: preDhcpTransitionTo(mWaitBeforeRenewalState, mDhcpRenewingState); return HANDLED; case CMD_REFRESH_LINKADDRESS: transitionTo(mDhcpRebindingState); return HANDLED; default: return NOT_HANDLED; } Loading
src/android/net/dhcp/DhcpPacket.java +2 −1 Original line number Diff line number Diff line Loading @@ -335,7 +335,8 @@ public abstract class DhcpPacket { * proposed by the client (from an earlier DHCP negotiation) or * supplied by the server. */ protected final Inet4Address mClientIp; @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) public final Inet4Address mClientIp; protected final Inet4Address mYourIp; private final Inet4Address mNextIp; protected final Inet4Address mRelayIp; Loading
src/android/net/ip/IpClient.java +68 −4 Original line number Diff line number Diff line Loading @@ -32,6 +32,7 @@ import android.net.Layer2InformationParcelable; import android.net.Layer2PacketParcelable; import android.net.LinkAddress; import android.net.LinkProperties; import android.net.MacAddress; import android.net.NattKeepalivePacketDataParcelable; import android.net.NetworkStackIpMemoryStore; import android.net.ProvisioningConfigurationParcelable; Loading Loading @@ -92,6 +93,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; Loading Loading @@ -381,7 +383,8 @@ public class IpClient extends StateMachine { private static final int CMD_ADD_KEEPALIVE_PACKET_FILTER_TO_APF = 13; private static final int CMD_REMOVE_KEEPALIVE_PACKET_FILTER_FROM_APF = 14; private static final int CMD_UPDATE_L2KEY_GROUPHINT = 15; protected static final int CMD_COMPLETE_PRECONNECTION = 16; private static final int CMD_COMPLETE_PRECONNECTION = 16; private static final int CMD_UPDATE_L2INFORMATION = 17; // Internal commands to use instead of trying to call transitionTo() inside // a given State's enter() method. Calling transitionTo() from enter/exit Loading Loading @@ -423,6 +426,14 @@ public class IpClient extends StateMachine { new byte[] { (byte) 0x00, (byte) 0x17, (byte) 0xf2, (byte) 0x06 } )); // Initialize configurable particular SSID set supporting DHCP Roaming feature. See // b/131797393 for more details. private static final Set<String> DHCP_ROAMING_SSID_SET = new HashSet<>( Arrays.asList( "0001docomo", "ollehWiFi", "olleh GiGa WiFi", "KT WiFi", "KT GiGA WiFi", "marente" )); private final State mStoppedState = new StoppedState(); private final State mStoppingState = new StoppingState(); private final State mClearingIpAddressesState = new ClearingIpAddressesState(); Loading Loading @@ -470,6 +481,7 @@ public class IpClient extends StateMachine { private String mGroupHint; // The group hint for this network, for writing into the memory store private boolean mMulticastFiltering; private long mStartTimeMillis; private MacAddress mCurrentBssid; /** * Reading the snapshot is an asynchronous operation initiated by invoking Loading Loading @@ -704,7 +716,6 @@ public class IpClient extends StateMachine { enforceNetworkStackCallingPermission(); IpClient.this.notifyPreconnectionComplete(success); } @Override public void updateLayer2Information(Layer2InformationParcelable info) { enforceNetworkStackCallingPermission(); Loading Loading @@ -772,6 +783,16 @@ public class IpClient extends StateMachine { return; } final ScanResultInfo scanResultInfo = req.mScanResultInfo; mCurrentBssid = null; if (scanResultInfo != null) { try { mCurrentBssid = MacAddress.fromString(scanResultInfo.getBssid()); } catch (IllegalArgumentException e) { Log.wtf(mTag, "Invalid BSSID: " + scanResultInfo.getBssid() + " in provisioning configuration", e); } } sendMessage(CMD_START, new android.net.shared.ProvisioningConfiguration(req)); } Loading Loading @@ -819,10 +840,15 @@ public class IpClient extends StateMachine { /** * Set the L2 key and group hint for storing info into the memory store. * * This method is only supported on Q devices. For R or above releases, * caller should call #updateLayer2Information() instead. */ public void setL2KeyAndGroupHint(String l2Key, String groupHint) { if (!ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.Q)) { sendMessage(CMD_UPDATE_L2KEY_GROUPHINT, new Pair<>(l2Key, groupHint)); } } /** * Set the HTTP Proxy configuration to use. Loading Loading @@ -880,7 +906,7 @@ public class IpClient extends StateMachine { * Update the network bssid, L2Key and GroupHint layer2 information. */ public void updateLayer2Information(@NonNull Layer2InformationParcelable info) { // TODO: add specific implementation. sendMessage(CMD_UPDATE_L2INFORMATION, info); } /** Loading Loading @@ -1502,6 +1528,36 @@ public class IpClient extends StateMachine { } } private void handleUpdateL2Information(@NonNull Layer2InformationParcelable info) { mL2Key = info.l2Key; mGroupHint = info.groupHint; // This means IpClient is still in the StoppedState, WiFi is trying to associate // to the AP, just update L2Key and GroupHint at this stage, because these members // will be used when starting DhcpClient. if (info.bssid == null || mCurrentBssid == null) return; // If the BSSID has not changed, there is nothing to do. if (info.bssid.equals(mCurrentBssid)) return; if (mIpReachabilityMonitor != null) { mIpReachabilityMonitor.probeAll(); } // Check whether to refresh previous IP lease on L2 roaming happened. final String ssid = removeDoubleQuotes(mConfiguration.mDisplayName); if (DHCP_ROAMING_SSID_SET.contains(ssid) && mDhcpClient != null) { if (DBG) { Log.d(mTag, "L2 roaming happened from " + mCurrentBssid + " to " + info.bssid + " , SSID: " + ssid + " , starting refresh leased IP address"); } mDhcpClient.sendMessage(DhcpClient.CMD_REFRESH_LINKADDRESS); } mCurrentBssid = info.bssid; } class StoppedState extends State { @Override public void enter() { Loading Loading @@ -1554,6 +1610,10 @@ public class IpClient extends StateMachine { break; } case CMD_UPDATE_L2INFORMATION: handleUpdateL2Information((Layer2InformationParcelable) msg.obj); break; case CMD_SET_MULTICAST_FILTER: mMulticastFiltering = (boolean) msg.obj; break; Loading Loading @@ -1763,6 +1823,10 @@ public class IpClient extends StateMachine { break; } case CMD_UPDATE_L2INFORMATION: handleUpdateL2Information((Layer2InformationParcelable) msg.obj); break; case EVENT_PROVISIONING_TIMEOUT: handleProvisioningFailure(); break; Loading
src/com/android/server/connectivity/NetworkMonitor.java +187 −13 Original line number Diff line number Diff line Loading @@ -141,6 +141,7 @@ import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.Log; import android.util.Pair; import android.util.SparseArray; import androidx.annotation.ArrayRes; import androidx.annotation.BoolRes; Loading Loading @@ -192,7 +193,13 @@ import java.util.Objects; import java.util.Random; import java.util.StringJoiner; import java.util.UUID; import java.util.concurrent.CompletionService; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorCompletionService; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.function.Function; import java.util.regex.Matcher; Loading @@ -218,6 +225,8 @@ public class NetworkMonitor extends StateMachine { private static final int SOCKET_TIMEOUT_MS = 10000; private static final int PROBE_TIMEOUT_MS = 3000; private static final int UNSET_MCC_OR_MNC = -1; private static final int CAPPORT_API_MAX_JSON_LENGTH = 4096; private static final String ACCEPT_HEADER = "Accept"; private static final String CONTENT_TYPE_HEADER = "Content-Type"; Loading @@ -241,6 +250,24 @@ public class NetworkMonitor extends StateMachine { } } @VisibleForTesting protected static final class MccMncOverrideInfo { public final int mcc; public final int mnc; MccMncOverrideInfo(int mcc, int mnc) { this.mcc = mcc; this.mnc = mnc; } } @VisibleForTesting protected static final SparseArray<MccMncOverrideInfo> sCarrierIdToMccMnc = new SparseArray<>(); static { // CTC sCarrierIdToMccMnc.put(1854, new MccMncOverrideInfo(460, 03)); } /** * ConnectivityService has sent a notification to indicate that network has connected. * Initiates Network Validation. Loading Loading @@ -351,6 +378,10 @@ public class NetworkMonitor extends StateMachine { // Delay between reevaluations once a captive portal has been found. private static final int CAPTIVE_PORTAL_REEVALUATE_DELAY_MS = 10 * 60 * 1000; private static final int NETWORK_VALIDATION_RESULT_INVALID = 0; // Max thread pool size for parallel probing. Fixed thread pool size to control the thread // number used for either HTTP or HTTPS probing. @VisibleForTesting static final int MAX_PROBE_THREAD_POOL_SIZE = 5; private String mPrivateDnsProviderHostname = ""; private final Context mContext; Loading Loading @@ -1507,25 +1538,51 @@ public class NetworkMonitor extends StateMachine { } } /** * Return a matched MccMncOverrideInfo if carrier id and sim mccmnc are matching a record in * sCarrierIdToMccMnc. */ @VisibleForTesting protected Context getContextByMccIfNoSimCardOrDefault() { @Nullable MccMncOverrideInfo getMccMncOverrideInfo() { final int carrierId = mTelephonyManager.getSimCarrierId(); return sCarrierIdToMccMnc.get(carrierId); } private Context getContextByMccMnc(final int mcc, final int mnc) { final Configuration config = mContext.getResources().getConfiguration(); if (mcc != UNSET_MCC_OR_MNC) config.mcc = mcc; if (mnc != UNSET_MCC_OR_MNC) config.mnc = mnc; return mContext.createConfigurationContext(config); } @VisibleForTesting protected Context getCustomizedContextOrDefault() { // Return customized context if carrier id can match a record in sCarrierIdToMccMnc. final MccMncOverrideInfo overrideInfo = getMccMncOverrideInfo(); if (overrideInfo != null) { return getContextByMccMnc(overrideInfo.mcc, overrideInfo.mnc); } // Use neighbor mcc feature only works when the config_no_sim_card_uses_neighbor_mcc is // true and there is no sim card inserted. final boolean useNeighborResource = getResBooleanConfig(mContext, R.bool.config_no_sim_card_uses_neighbor_mcc, false); if (!useNeighborResource || TelephonyManager.SIM_STATE_READY == mTelephonyManager.getSimState()) { return mContext; } final String mcc = getLocationMcc(); if (TextUtils.isEmpty(mcc)) { return mContext; } final Configuration config = mContext.getResources().getConfiguration(); config.mcc = Integer.parseInt(mcc); return mContext.createConfigurationContext(config); return getContextByMccMnc(Integer.parseInt(mcc), UNSET_MCC_OR_MNC); } private String getCaptivePortalServerHttpsUrl() { final Context targetContext = getContextByMccIfNoSimCardOrDefault(); final Context targetContext = getCustomizedContextOrDefault(); return getSettingFromResource(targetContext, R.string.config_captive_portal_https_url, R.string.default_captive_portal_https_url, CAPTIVE_PORTAL_HTTPS_URL); } Loading Loading @@ -1604,7 +1661,7 @@ public class NetworkMonitor extends StateMachine { * on one URL that can be used, while NetworkMonitor may implement more complex logic. */ public String getCaptivePortalServerHttpUrl() { final Context targetContext = getContextByMccIfNoSimCardOrDefault(); final Context targetContext = getCustomizedContextOrDefault(); return getSettingFromResource(targetContext, R.string.config_captive_portal_http_url, R.string.default_captive_portal_http_url, CAPTIVE_PORTAL_HTTP_URL); } Loading Loading @@ -1759,7 +1816,7 @@ public class NetworkMonitor extends StateMachine { */ private <T> T[] getProbeUrlArrayConfig(@NonNull T[] providerValue, @ArrayRes int configResId, @ArrayRes int defaultResId, @NonNull Function<String, T> resourceConverter) { final Resources res = getContextByMccIfNoSimCardOrDefault().getResources(); final Resources res = getCustomizedContextOrDefault().getResources(); return getProbeUrlArrayConfig(providerValue, configResId, res.getStringArray(defaultResId), resourceConverter); } Loading @@ -1777,7 +1834,7 @@ public class NetworkMonitor extends StateMachine { */ private <T> T[] getProbeUrlArrayConfig(@NonNull T[] providerValue, @ArrayRes int configResId, String[] defaultConfig, @NonNull Function<String, T> resourceConverter) { final Resources res = getContextByMccIfNoSimCardOrDefault().getResources(); final Resources res = getCustomizedContextOrDefault().getResources(); String[] configValue = res.getStringArray(configResId); if (configValue.length == 0) { Loading Loading @@ -1885,9 +1942,13 @@ public class NetworkMonitor extends StateMachine { if (pacUrl != null) { result = sendDnsAndHttpProbes(null, pacUrl, ValidationProbeEvent.PROBE_PAC); reportHttpProbeResult(NETWORK_VALIDATION_PROBE_HTTP, result); } else if (mUseHttps && httpsUrls.length == 1 && httpUrls.length == 1) { // Probe results are reported inside sendHttpAndHttpsParallelWithFallbackProbes. result = sendHttpAndHttpsParallelWithFallbackProbes( proxyInfo, httpsUrls[0], httpUrls[0]); } else if (mUseHttps) { // Probe results are reported inside sendParallelHttpProbes. result = sendParallelHttpProbes(proxyInfo, httpsUrls[0], httpUrls[0]); // Support result aggregation from multiple Urls. result = sendMultiParallelHttpAndHttpsProbes(proxyInfo, httpsUrls, httpUrls); } else { result = sendDnsAndHttpProbes(proxyInfo, httpUrls[0], ValidationProbeEvent.PROBE_HTTP); reportHttpProbeResult(NETWORK_VALIDATION_PROBE_HTTP, result); Loading Loading @@ -2279,7 +2340,9 @@ public class NetworkMonitor extends StateMachine { if (capportData != null && capportData.isCaptive()) { if (capportData.getUserPortalUrl() == null) { validationLog("Missing user-portal-url from capport response"); return sendDnsAndHttpProbes(mProxy, mUrl, ValidationProbeEvent.PROBE_HTTP); return new CapportApiProbeResult( sendDnsAndHttpProbes(mProxy, mUrl, ValidationProbeEvent.PROBE_HTTP), capportData); } final String loginUrlString = capportData.getUserPortalUrl().toString(); // Starting from R (where CaptivePortalData was introduced), the captive portal app Loading @@ -2299,7 +2362,7 @@ public class NetworkMonitor extends StateMachine { // redirect when there is one. final CaptivePortalProbeResult res = sendDnsAndHttpProbes(mProxy, mUrl, ValidationProbeEvent.PROBE_HTTP); return capportData == null ? res : new CapportApiProbeResult(res, capportData); return mCaptivePortalApiUrl == null ? res : new CapportApiProbeResult(res, capportData); } } Loading @@ -2316,6 +2379,117 @@ public class NetworkMonitor extends StateMachine { && captivePortalApiUrl == null); } private CaptivePortalProbeResult sendMultiParallelHttpAndHttpsProbes(@NonNull ProxyInfo proxy, @NonNull URL[] httpsUrls, @NonNull URL[] httpUrls) { // If multiple URLs are required to ensure the correctness of validation, send parallel // probes to explore the result in separate probe threads and aggregate those results into // one as the final result for either HTTP or HTTPS. // Number of probes to wait for. final int num = httpsUrls.length + httpUrls.length; // Fixed pool to prevent configuring too many urls to exhaust system resource. final ExecutorService executor = Executors.newFixedThreadPool( Math.min(num, MAX_PROBE_THREAD_POOL_SIZE)); final CompletionService<CaptivePortalProbeResult> ecs = new ExecutorCompletionService<CaptivePortalProbeResult>(executor); final Uri capportApiUrl = getCaptivePortalApiUrl(mLinkProperties); final List<Future<CaptivePortalProbeResult>> futures = new ArrayList<>(); try { // Queue https and http probe. // Each of these HTTP probes will start with probing capport API if present. So if // multiple HTTP URLs are configured, AP will send multiple identical accesses to the // capport URL. Thus, send capport API probing with one of the HTTP probe is enough. // Probe capport API with the first HTTP probe. // TODO: Have the capport probe as a different probe for cleanliness. final URL urlMaybeWithCapport = httpUrls[0]; for (final URL url : httpUrls) { futures.add(ecs.submit(() -> new HttpProbe(proxy, url, url.equals(urlMaybeWithCapport) ? capportApiUrl : null).sendProbe())); } for (final URL url : httpsUrls) { futures.add( ecs.submit(() -> new HttpsProbe(proxy, url, capportApiUrl).sendProbe())); } final ArrayList<CaptivePortalProbeResult> completedProbes = new ArrayList<>(); for (int i = 0; i < num; i++) { completedProbes.add(ecs.take().get()); final CaptivePortalProbeResult res = evaluateCapportResult( completedProbes, httpsUrls.length, capportApiUrl != null /* hasCapport */); if (res != null) { reportProbeResult(res); return res; } } } catch (ExecutionException e) { Log.e(TAG, "Error sending probes.", e); } catch (InterruptedException e) { // Ignore interrupted probe result because result is not important to conclude the // result. } finally { // Interrupt ongoing probes since we have already gotten result from one of them. futures.forEach(future -> future.cancel(true)); executor.shutdownNow(); } return CaptivePortalProbeResult.failed(ValidationProbeEvent.PROBE_HTTPS); } @Nullable private CaptivePortalProbeResult evaluateCapportResult( List<CaptivePortalProbeResult> probes, int numHttps, boolean hasCapport) { CaptivePortalProbeResult capportResult = null; CaptivePortalProbeResult httpPortalResult = null; int httpSuccesses = 0; int httpsSuccesses = 0; int httpsFailures = 0; for (CaptivePortalProbeResult probe : probes) { if (probe instanceof CapportApiProbeResult) { capportResult = probe; } else if (probe.isConcludedFromHttps()) { if (probe.isSuccessful()) httpsSuccesses++; else httpsFailures++; } else { // http probes if (probe.isPortal()) { // Unlike https probe, http probe may have redirect url information kept in the // probe result. Thus, the result can not be newly created with response code // only. If the captive portal behavior will be varied because of different // probe URLs, this means that if the portal returns different redirect URLs for // different probes and has a different behavior depending on the URL, then the // behavior of the login page may differ depending on the order in which the // probes terminate. However, NetworkMonitor does have to choose one of the // redirect URLs and right now there is no clue at all which of the probe has // the better redirect URL, so there is no telling which is best to use. // Therefore the current code just uses whichever happens to be the last one to // complete. httpPortalResult = probe; } else if (probe.isSuccessful()) { httpSuccesses++; } } } // If there is Capport url configured but the result is not available yet, wait for it. if (hasCapport && capportResult == null) return null; // Capport API saying it's a portal is authoritative. if (capportResult != null && capportResult.isPortal()) return capportResult; // Any HTTP probes saying probe portal is conclusive. if (httpPortalResult != null) return httpPortalResult; // Any HTTPS probes works then the network validates. if (httpsSuccesses > 0) { return CaptivePortalProbeResult.success(1 << ValidationProbeEvent.PROBE_HTTPS); } // All HTTPS failed and at least one HTTP succeeded, then it's partial. if (httpsFailures == numHttps && httpSuccesses > 0) { return CaptivePortalProbeResult.PARTIAL; } // Otherwise, the result is unknown yet. return null; } private void reportProbeResult(@NonNull CaptivePortalProbeResult res) { if (res instanceof CapportApiProbeResult) { maybeReportCaptivePortalData(((CapportApiProbeResult) res).getCaptivePortalData()); Loading @@ -2332,7 +2506,7 @@ public class NetworkMonitor extends StateMachine { } } private CaptivePortalProbeResult sendParallelHttpProbes( private CaptivePortalProbeResult sendHttpAndHttpsParallelWithFallbackProbes( ProxyInfo proxy, URL httpsUrl, URL httpUrl) { // Number of probes to wait for. If a probe completes with a conclusive answer // it shortcuts the latch immediately by forcing the count to 0. Loading