diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg index c301397593..68cbdc7d02 100644 --- a/PREUPLOAD.cfg +++ b/PREUPLOAD.cfg @@ -1,9 +1,11 @@ [Builtin Hooks] bpfmt = true clang_format = true +rustfmt = true [Builtin Hooks Options] clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp,hpp +rustfmt = --config-path=rustfmt.toml [Hook Scripts] checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT} @@ -11,3 +13,8 @@ checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPL ktlint_hook = ${REPO_ROOT}/prebuilts/ktlint/ktlint.py --disabled-rules comment-wrapping -f ${PREUPLOAD_FILES} hidden_api_txt_checksorted_hook = ${REPO_ROOT}/tools/platform-compat/hiddenapi/checksorted_sha.sh ${PREUPLOAD_COMMIT} ${REPO_ROOT} + +python_hook = ${REPO_ROOT}/packages/modules/Connectivity/preupload_hook_script.py ${PREUPLOAD_FILES} + +# go/alint for details +alint_hook = ${REPO_ROOT}/vendor/google/tools/alint diff --git a/TEST_MAPPING b/TEST_MAPPING index 8592af2859..9f8868c3ef 100644 --- a/TEST_MAPPING +++ b/TEST_MAPPING @@ -435,6 +435,23 @@ "name": "CtsNetTestCasesUpdateStatsPermission" } ], + "tv-presubmit": [ + { + "name": "CtsNetTestCases" + }, + { + "name": "CtsNativeNetTestCases" + }, + { + "name": "CtsNativeNetDnsTestCases" + }, + { + "name": "CtsNetTestCasesInternetPermission" + }, + { + "name": "CtsNetTestCasesUpdateStatsPermission" + } + ], "imports": [ { "path": "frameworks/base/core/java/android/net" diff --git a/Tethering/apex/Android.bp b/Tethering/apex/Android.bp index 531489ddb6..19e2c6f078 100644 --- a/Tethering/apex/Android.bp +++ b/Tethering/apex/Android.bp @@ -116,6 +116,10 @@ apex { compat_configs: [ "connectivity-platform-compat-config", ], + licenses: [ + "Android-Apache-2.0", + "opensourcerequest", + ], } apex_key { @@ -218,3 +222,21 @@ systemserverclasspath_fragment { standalone_contents: ["service-connectivity"], apex_available: ["com.android.tethering"], } + +// Extract the version number of the module from the manifest.json, and zip it as a raw resource for +// usage in android_test "resource_zips" build rules. +// The manifest.json is updated with new module versions in release branches when versions are cut. +java_genrule { + name: "connectivity-module-version-as-resource-zip", + srcs: [ + "manifest.json", + ], + out: ["connectivity-module-version-as-resource.zip"], + tools: ["soong_zip"], + + cmd: "mkdir -p $(genDir)/res/raw && " + + // The manifest has a line with '"version": 361099999'. Grep for the line, then for the int. + "grep -Eo '\"version\": *[0-9]+' $(in) | grep -Eo '[0-9]+' > " + + "$(genDir)/res/raw/target_module_version && " + + "$(location soong_zip) -o $(out) -C $(genDir)/res -D $(genDir)/res", +} diff --git a/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java b/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java index b460f0d0b1..9b6097d094 100644 --- a/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java +++ b/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java @@ -19,7 +19,7 @@ package com.android.networkstack.tethering.apishim.api31; import static android.net.netstats.provider.NetworkStatsProvider.QUOTA_UNLIMITED; import static com.android.net.module.util.NetworkStackConstants.RFC7421_PREFIX_LENGTH; -import static com.android.networkstack.tethering.TetheringConfiguration.TETHER_ACTIVE_SESSIONS_METRICS; +import static com.android.networkstack.tethering.TetheringFeatureFlags.TETHER_ACTIVE_SESSIONS_METRICS; import android.system.ErrnoException; import android.system.Os; diff --git a/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java index a651b1b198..e8796b9647 100644 --- a/Tethering/src/android/net/ip/IpServer.java +++ b/Tethering/src/android/net/ip/IpServer.java @@ -41,8 +41,9 @@ import static android.system.OsConstants.RT_SCOPE_UNIVERSE; import static com.android.net.module.util.Inet4AddressUtils.intToInet4AddressHTH; import static com.android.net.module.util.NetworkStackConstants.RFC7421_PREFIX_LENGTH; -import static com.android.networkstack.tethering.TetheringConfiguration.TETHERING_LOCAL_NETWORK_AGENT; import static com.android.networkstack.tethering.TetheringConfiguration.USE_SYNC_SM; +import static com.android.networkstack.tethering.TetheringFeatureFlags.TETHERING_LOCAL_NETWORK_AGENT; +import static com.android.networkstack.tethering.TetheringFeatureFlags.WIFIP2PGO_LOCAL_NETWORK_AGENT; import static com.android.networkstack.tethering.util.PrefixUtils.asIpPrefix; import static com.android.networkstack.tethering.util.TetheringMessageBase.BASE_IPSERVER; import static com.android.networkstack.tethering.util.TetheringUtils.getTransportTypeForTetherableType; @@ -334,7 +335,8 @@ public class IpServer extends StateMachineShim { private final Handler mHandler; private final Context mContext; - private final boolean mSupportLocalAgent; + private final boolean mSupportTetheringLocalAgent; + private final boolean mSupportWifiP2pGroupOwnerLocalAgent; // This will be null if the TetheredState is not entered or feature not supported. // This will be only accessed from the IpServer handler thread. @@ -380,8 +382,10 @@ public class IpServer extends StateMachineShim { mServingMode = STATE_AVAILABLE; // Tethering network agent is supported on V+, and will be rolled out gradually. - mSupportLocalAgent = SdkLevel.isAtLeastV() + mSupportTetheringLocalAgent = SdkLevel.isAtLeastV() && mDeps.isFeatureEnabled(mContext, TETHERING_LOCAL_NETWORK_AGENT); + mSupportWifiP2pGroupOwnerLocalAgent = mSupportTetheringLocalAgent + && mDeps.isFeatureEnabled(mContext, WIFIP2PGO_LOCAL_NETWORK_AGENT); mInitialState = new InitialState(); mLocalHotspotState = new LocalHotspotState(); @@ -1162,9 +1166,8 @@ public class IpServer extends StateMachineShim { @SuppressLint("NewApi") private void startServingInterface() { - // TODO: Enable Network Agent for Wifi P2P Group Owner mode when Network Agent - // for Group Client mode is supported. - if (mSupportLocalAgent && getScope() == CONNECTIVITY_SCOPE_GLOBAL) { + if (mSupportTetheringLocalAgent && (mSupportWifiP2pGroupOwnerLocalAgent + || getScope() != CONNECTIVITY_SCOPE_LOCAL)) { try { mTetheringAgent = mDeps.makeNetworkAgent(mContext, Looper.myLooper(), TAG, mInterfaceType, mLinkProperties); diff --git a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java index 75ab9eccf3..180791e813 100644 --- a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java +++ b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java @@ -34,7 +34,7 @@ import static com.android.net.module.util.ip.ConntrackMonitor.ConntrackEvent; import static com.android.networkstack.tethering.BpfUtils.DOWNSTREAM; import static com.android.networkstack.tethering.BpfUtils.UPSTREAM; import static com.android.networkstack.tethering.TetheringConfiguration.DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS; -import static com.android.networkstack.tethering.TetheringConfiguration.TETHER_ACTIVE_SESSIONS_METRICS; +import static com.android.networkstack.tethering.TetheringFeatureFlags.TETHER_ACTIVE_SESSIONS_METRICS; import static com.android.networkstack.tethering.UpstreamNetworkState.isVcnInterface; import static com.android.networkstack.tethering.util.TetheringUtils.getTetheringJniLibraryName; diff --git a/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java b/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java index 13a7a22f57..d1cc79dffc 100644 --- a/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java +++ b/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java @@ -16,7 +16,7 @@ package com.android.networkstack.tethering; -import static com.android.net.module.util.netlink.NetlinkUtils.SOCKET_RECV_BUFSIZE; +import static com.android.net.module.util.netlink.NetlinkUtils.SOCKET_DUMP_RECV_BUFSIZE; import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_DUMP; import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REQUEST; @@ -200,7 +200,7 @@ public class OffloadHardwareInterface { final FileDescriptor fd; try { fd = NetlinkUtils.netlinkSocketForProto(OsConstants.NETLINK_NETFILTER, - SOCKET_RECV_BUFSIZE); + SOCKET_DUMP_RECV_BUFSIZE); } catch (ErrnoException e) { mLog.e("Unable to create conntrack socket " + e); return null; diff --git a/Tethering/src/com/android/networkstack/tethering/RequestTracker.java b/Tethering/src/com/android/networkstack/tethering/RequestTracker.java index c91ff586e4..2608e7aaca 100644 --- a/Tethering/src/com/android/networkstack/tethering/RequestTracker.java +++ b/Tethering/src/com/android/networkstack/tethering/RequestTracker.java @@ -43,7 +43,7 @@ import java.util.Map; * Note: This class is not thread-safe. */ public class RequestTracker { - private static final String TAG = RequestTracker.class.getSimpleName(); + private static final String TAG = "TetheringRequestTracker"; @NonNull private final boolean mUseFuzzyMatching; diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java index e821d2b377..ed4752262a 100644 --- a/Tethering/src/com/android/networkstack/tethering/Tethering.java +++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java @@ -53,6 +53,7 @@ import static android.net.TetheringManager.TETHER_ERROR_UNAVAIL_IFACE; import static android.net.TetheringManager.TETHER_ERROR_UNKNOWN_IFACE; import static android.net.TetheringManager.TETHER_ERROR_UNKNOWN_REQUEST; import static android.net.TetheringManager.TETHER_ERROR_UNKNOWN_TYPE; +import static android.net.TetheringManager.TETHER_ERROR_UNSUPPORTED; import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_FAILED; import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_STARTED; import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_STOPPED; @@ -76,10 +77,6 @@ import static com.android.networkstack.tethering.TetheringConfiguration.TETHER_F import static com.android.networkstack.tethering.TetheringNotificationUpdater.DOWNSTREAM_NONE; import static com.android.networkstack.tethering.UpstreamNetworkMonitor.isCellular; import static com.android.networkstack.tethering.metrics.TetheringStatsLog.CORE_NETWORKING_TERRIBLE_ERROR_OCCURRED; -import static com.android.networkstack.tethering.metrics.TetheringStatsLog.CORE_NETWORKING_TERRIBLE_ERROR_OCCURRED__ERROR_TYPE__TYPE_LEGACY_TETHER_WITH_TYPE_WIFI; -import static com.android.networkstack.tethering.metrics.TetheringStatsLog.CORE_NETWORKING_TERRIBLE_ERROR_OCCURRED__ERROR_TYPE__TYPE_LEGACY_TETHER_WITH_TYPE_WIFI_P2P; -import static com.android.networkstack.tethering.metrics.TetheringStatsLog.CORE_NETWORKING_TERRIBLE_ERROR_OCCURRED__ERROR_TYPE__TYPE_LEGACY_TETHER_WITH_TYPE_WIFI_P2P_SUCCESS; -import static com.android.networkstack.tethering.metrics.TetheringStatsLog.CORE_NETWORKING_TERRIBLE_ERROR_OCCURRED__ERROR_TYPE__TYPE_LEGACY_TETHER_WITH_TYPE_WIFI_SUCCESS; import static com.android.networkstack.tethering.metrics.TetheringStatsLog.CORE_NETWORKING_TERRIBLE_ERROR_OCCURRED__ERROR_TYPE__TYPE_TETHER_WITH_PLACEHOLDER_REQUEST; import static com.android.networkstack.tethering.util.TetheringMessageBase.BASE_MAIN_SM; import static com.android.networkstack.tethering.util.TetheringUtils.createImplicitLocalOnlyTetheringRequest; @@ -153,11 +150,11 @@ import com.android.internal.util.StateMachine; import com.android.modules.utils.build.SdkLevel; import com.android.net.module.util.BaseNetdUnsolicitedEventListener; import com.android.net.module.util.CollectionUtils; +import com.android.net.module.util.FrameworkConnectivityStatsLog; import com.android.net.module.util.HandlerUtils; import com.android.net.module.util.NetdUtils; import com.android.net.module.util.RoutingCoordinatorManager; import com.android.net.module.util.SharedLog; -import com.android.net.module.util.TerribleErrorLog; import com.android.networkstack.apishim.common.BluetoothPanShim; import com.android.networkstack.apishim.common.BluetoothPanShim.TetheredInterfaceCallbackShim; import com.android.networkstack.apishim.common.BluetoothPanShim.TetheredInterfaceRequestShim; @@ -633,33 +630,40 @@ public class Tethering { if (type == TETHERING_BLUETOOTH && SdkLevel.isAtLeastT()) return; // Cannot happen: on S+, tetherableWigigRegexps is always empty. - if (type == TETHERING_WIGIG && SdkLevel.isAtLeastS()) return; - - // After V, disallow this legacy codepath from starting tethering of any type: - // everything must call ensureIpServerStarted directly. - // - // Don't touch the teardown path for now. It's more complicated because: - // - ensureIpServerStarted and ensureIpServerStopped act on different - // tethering types. - // - Depending on the type, ensureIpServerStopped is either called twice (once - // on interface down and once on interface removed) or just once (on - // interface removed). - // - // Note that this only affects WIFI and WIFI_P2P. The other types are either - // ignored above, or ignored by ensureIpServerStarted. Note that even for WIFI - // and WIFI_P2P, this code should not ever run in normal use, because the - // hotspot and p2p code do not call tether(). It's possible that this could - // happen in the field due to unforeseen OEM modifications. If it does happen, - // a terrible error is logged in tether(). - // TODO: fix the teardown path to stop depending on interface state notifications. - // These are not necessary since most/all link layers have their own teardown - // notifications, and can race with those notifications. - if (enabled && SdkLevel.isAtLeastB()) { + if (type == TETHERING_WIGIG + && (SdkLevel.isAtLeastS() || !hasSystemFeature(PackageManager.FEATURE_WIFI))) { return; } + // After V, don't allow this legacy codepath to create IpServers for any tethering type: + // everything must call ensureIpServerStarted directly. Also, on any release, don't create + // IpServers for WIFI or WIFI_P2P. These types always use the enableIpServing path. + // + // Don't touch the teardown path for now. It's more complicated because: + // - ensureIpServerStarted and ensureIpServerStopped act on different + // tethering types, and ensureIpServerStopped doesn't look at the type at all. + // - Depending on the type, ensureIpServerStopped is either called twice (once + // on interface down and once on interface removed) or just once (on + // interface removed). It's not clear whether it's possible to remove one of + // these two calls without causing breakage in the field - for example, it's + // possible that if the code did not call ensureIpServerStopped on wifi, then + // when wifi tethering is disabled and wifi goes into client mode, tethering + // has not brought down the IpServer because the WIFI_AP_STATE_CHANGED_ACTION + // was delayed. + // + // TODO: fix the teardown path to stop depending on interface state notifications. + // These are not necessary since most/all link layers have their own teardown + // notifications, and can race with those notifications. + if (enabled && SdkLevel.isAtLeastB()) return; + + if (enabled && (type == TETHERING_WIFI || type == TETHERING_WIFI_P2P)) return; + + // In this method, INVALID includes ETHERNET and VIRTUAL, because ifaceNameToType never + // returns these. The legacy codepath does not support these. + if (enabled && type == TETHERING_INVALID) return; + if (enabled) { - ensureIpServerStartedForInterface(iface); + ensureIpServerStarted(iface, type, false /* isNcm */); } else { ensureIpServerStopped(iface); } @@ -719,6 +723,7 @@ public class Tethering { void startTethering(final TetheringRequest request, final String callerPkg, final IIntResultListener listener) { mHandler.post(() -> { + mLog.i("startTethering: " + request); final int type = request.getTetheringType(); RequestTracker.AddResult result = mRequestTracker.addPendingRequest(request); // If tethering is already pending with a conflicting request, stop tethering before @@ -748,6 +753,7 @@ public class Tethering { void stopTethering(int type) { mHandler.post(() -> { + mLog.i("stopTethering: " + type); stopTetheringInternal(type); }); } @@ -757,6 +763,7 @@ public class Tethering { if (!isTetheringWithSoftApConfigEnabled()) return; final boolean hasNetworkSettings = hasCallingPermission(NETWORK_SETTINGS); mHandler.post(() -> { + mLog.i("stopTetheringRequest: " + request); if (mRequestTracker.findFuzzyMatchedRequest(request, !hasNetworkSettings) != null) { final int type = request.getTetheringType(); stopTetheringInternal(type); @@ -1149,43 +1156,20 @@ public class Tethering { if (type == TETHERING_INVALID) { Log.e(TAG, "Ignoring call to legacy tether for unknown iface " + iface); sendTetherResult(listener, TETHER_ERROR_UNKNOWN_IFACE); + return; + } + + if (type == TETHERING_WIFI || type == TETHERING_WIFI_P2P) { + // Metrics show no usage of this legacy codepath. Stop supporing it. + sendTetherResult(listener, TETHER_ERROR_UNSUPPORTED); + return; } TetheringRequest request = mRequestTracker.getNextPendingRequest(type); if (request == null) { request = createLegacyGlobalScopeTetheringRequest(type); } - int result = tetherInternal(request, iface); - switch (type) { - case TETHERING_WIFI: - TerribleErrorLog.logTerribleError(TetheringStatsLog::write, - "Legacy tether API called on Wifi iface " + iface, - CORE_NETWORKING_TERRIBLE_ERROR_OCCURRED, - CORE_NETWORKING_TERRIBLE_ERROR_OCCURRED__ERROR_TYPE__TYPE_LEGACY_TETHER_WITH_TYPE_WIFI); - if (result == TETHER_ERROR_NO_ERROR) { - TerribleErrorLog.logTerribleError(TetheringStatsLog::write, - "Legacy tether API succeeded on Wifi iface " + iface, - CORE_NETWORKING_TERRIBLE_ERROR_OCCURRED, - CORE_NETWORKING_TERRIBLE_ERROR_OCCURRED__ERROR_TYPE__TYPE_LEGACY_TETHER_WITH_TYPE_WIFI_SUCCESS); - } - break; - case TETHERING_WIFI_P2P: - TerribleErrorLog.logTerribleError(TetheringStatsLog::write, - "Legacy tether API called on Wifi P2P iface " + iface, - CORE_NETWORKING_TERRIBLE_ERROR_OCCURRED, - CORE_NETWORKING_TERRIBLE_ERROR_OCCURRED__ERROR_TYPE__TYPE_LEGACY_TETHER_WITH_TYPE_WIFI_P2P); - if (result == TETHER_ERROR_NO_ERROR) { - TerribleErrorLog.logTerribleError(TetheringStatsLog::write, - "Legacy tether API succeeded on Wifi P2P iface " + iface, - CORE_NETWORKING_TERRIBLE_ERROR_OCCURRED, - CORE_NETWORKING_TERRIBLE_ERROR_OCCURRED__ERROR_TYPE__TYPE_LEGACY_TETHER_WITH_TYPE_WIFI_P2P_SUCCESS); - } - break; - default: - // Do nothing - break; - } - sendTetherResult(listener, result); + sendTetherResult(listener, tetherInternal(request, iface)); } /** @@ -1195,11 +1179,12 @@ public class Tethering { * processInterfaceStateChanged beforehand, which is only possible for * - WIGIG Pre-S * - BLUETOOTH Pre-T + * It was previously supported but no longer allowed for: * - WIFI * - WIFI_P2P. - * Note that WIFI and WIFI_P2P already start tethering on their respective ifaces via - * WIFI_(AP/P2P_STATE_CHANGED broadcasts, which makes this API redundant for those types unless - * those broadcasts are disabled by OEM. + * because metrics show that there is no usage of this method for these types in production. + * These types start tethering on their respective ifaces via WIFI_(AP/P2P_STATE_CHANGED + * broadcasts instead. */ void legacyTether(String iface, final IIntResultListener listener) { mHandler.post(() -> handleLegacyTether(iface, listener)); @@ -1223,8 +1208,8 @@ public class Tethering { // processed, this will be a no-op and it will not return an error. tetherState.ipServer.enable(request); if (request.getRequestType() == REQUEST_TYPE_PLACEHOLDER) { - TerribleErrorLog.logTerribleError(TetheringStatsLog::write, - "Started tethering with placeholder request: " + request, + Log.i(TAG, "Started tethering with placeholder request: " + request); + TetheringStatsLog.write( CORE_NETWORKING_TERRIBLE_ERROR_OCCURRED, CORE_NETWORKING_TERRIBLE_ERROR_OCCURRED__ERROR_TYPE__TYPE_TETHER_WITH_PLACEHOLDER_REQUEST); } @@ -1283,13 +1268,6 @@ public class Tethering { return mEntitlementMgr.isTetherProvisioningRequired(cfg); } - private int getServedUsbType(boolean forNcmFunction) { - // TETHERING_NCM is only used if the device does not use NCM for regular USB tethering. - if (forNcmFunction && !mConfig.isUsingNcm()) return TETHERING_NCM; - - return TETHERING_USB; - } - // TODO: Figure out how to update for local hotspot mode interfaces. private void notifyTetherStatesChanged() { if (!isTetheringSupported()) return; @@ -1709,7 +1687,7 @@ public class Tethering { } private void enableIpServing(@NonNull TetheringRequest request, String ifname, boolean isNcm) { - ensureIpServerStartedForType(ifname, request.getTetheringType(), isNcm); + ensureIpServerStarted(ifname, request.getTetheringType(), isNcm); if (tetherInternal(request, ifname) != TETHER_ERROR_NO_ERROR) { Log.e(TAG, "unable start tethering on iface " + ifname); } @@ -1758,6 +1736,12 @@ public class Tethering { // After T, tethering always trust the iface pass by state change intent. This allow // tethering to deprecate tetherable p2p regexs after T. final int type = SdkLevel.isAtLeastT() ? TETHERING_WIFI_P2P : ifaceNameToType(ifname); + if (type != TETHERING_WIFI_P2P) { + FrameworkConnectivityStatsLog.write( + FrameworkConnectivityStatsLog.CORE_NETWORKING_TERRIBLE_ERROR_OCCURRED, + FrameworkConnectivityStatsLog.CORE_NETWORKING_TERRIBLE_ERROR_OCCURRED__ERROR_TYPE__TYPE_TETHER_WIFIP2P_TYPE_MISMATCH); + } + if (!checkTetherableType(type)) { mLog.e(ifname + " is not a tetherable iface, ignoring"); return; @@ -1808,6 +1792,11 @@ public class Tethering { mLog.e("Cannot enable IP serving in unknown WiFi mode: " + wifiIpMode); return; } + if (type != TETHERING_WIFI) { + FrameworkConnectivityStatsLog.write( + FrameworkConnectivityStatsLog.CORE_NETWORKING_TERRIBLE_ERROR_OCCURRED, + FrameworkConnectivityStatsLog.CORE_NETWORKING_TERRIBLE_ERROR_OCCURRED__ERROR_TYPE__TYPE_TETHER_WIFI_TYPE_MISMATCH); + } // After T, tethering always trust the iface pass by state change intent. This allow // tethering to deprecate tetherable wifi regexs after T. @@ -1830,14 +1819,7 @@ public class Tethering { // - allows requesting either tethering or local hotspot serving states // - only tethers the first matching interface in listInterfaces() // order of a given type - private void enableUsbIpServing(boolean forNcmFunction) { - // Note: TetheringConfiguration#isUsingNcm can change between the call to - // startTethering(TETHERING_USB) and the ACTION_USB_STATE broadcast. If the USB tethering - // function changes from NCM to RNDIS, this can lead to Tethering starting NCM tethering - // as local-only. But if this happens, the SettingsObserver will call stopTetheringInternal - // for both TETHERING_USB and TETHERING_NCM, so the local-only NCM interface will be - // stopped immediately. - final int tetheringType = getServedUsbType(forNcmFunction); + private void enableUsbIpServing(boolean isNcm) { String[] ifaces = null; try { ifaces = mNetd.interfaceGetList(); @@ -1846,17 +1828,24 @@ public class Tethering { return; } + // Note: TetheringConfiguration#isUsingNcm can change between the call to + // startTethering(TETHERING_USB) and the ACTION_USB_STATE broadcast. If the USB tethering + // function changes from NCM to RNDIS, this can lead to Tethering starting NCM tethering + // as local-only. But if this happens, the SettingsObserver will call stopTetheringInternal + // for both TETHERING_USB and TETHERING_NCM, so the local-only NCM interface will be + // stopped immediately. + final int tetheringType = (isNcm && !mConfig.isUsingNcm()) ? TETHERING_NCM : TETHERING_USB; final TetheringRequest request = mRequestTracker.getOrCreatePendingRequest(tetheringType); if (ifaces != null) { for (String iface : ifaces) { if (ifaceNameToType(iface) == tetheringType) { - enableIpServing(request, iface, forNcmFunction); + enableIpServing(request, iface, isNcm); return; } } } - mLog.e("could not enable IpServer for function " + (forNcmFunction ? "NCM" : "RNDIS")); + mLog.e("could not enable IpServer for function " + (isNcm ? "NCM" : "RNDIS")); } private void disableUsbIpServing(boolean forNcmFunction) { @@ -3106,20 +3095,7 @@ public class Tethering { return type != TETHERING_INVALID; } - private void ensureIpServerStartedForInterface(final String iface) { - // If we don't care about this type of interface, ignore. - final int interfaceType = ifaceNameToType(iface); - if (!checkTetherableType(interfaceType)) { - mLog.log(iface + " is used for " + interfaceType + " which is not tetherable" - + " (-1 == INVALID is expected on upstream interface)"); - return; - } - - ensureIpServerStartedForType(iface, interfaceType, false /* isNcm */); - } - - private void ensureIpServerStartedForType(final String iface, int interfaceType, - boolean isNcm) { + private void ensureIpServerStarted(final String iface, int interfaceType, boolean isNcm) { // If we have already started a TISM for this interface, skip. if (mTetherStates.containsKey(iface)) { mLog.log("active iface (" + iface + ") reported as added, ignoring"); diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java b/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java index 90418c34d0..2235871c3c 100644 --- a/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java +++ b/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java @@ -24,6 +24,9 @@ import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY; import static com.android.networkstack.apishim.ConstantsShim.KEY_CARRIER_SUPPORTS_TETHERING_BOOL; import static com.android.net.module.util.SdkUtil.isAtLeast25Q2; +import static com.android.networkstack.tethering.TetheringFeatureFlags.TETHER_ENABLE_SYNC_SM; +import static com.android.networkstack.tethering.TetheringFeatureFlags.TETHER_ENABLE_WEAR_TETHERING; +import static com.android.networkstack.tethering.TetheringFeatureFlags.TETHER_FORCE_UPSTREAM_AUTOMATIC_VERSION; import android.content.ContentResolver; import android.content.Context; @@ -101,19 +104,6 @@ public class TetheringConfiguration { public static final String USE_LEGACY_WIFI_P2P_DEDICATED_IP = "use_legacy_wifi_p2p_dedicated_ip"; - /** - * Experiment flag to force choosing upstreams automatically. - * - * This setting is intended to help force-enable the feature on OEM devices that disabled it - * via resource overlays, and later noticed issues. To that end, it overrides - * config_tether_upstream_automatic when set to true. - * - * This flag is enabled if !=0 and less than the module APEX version: see - * {@link DeviceConfigUtils#isTetheringFeatureEnabled}. It is also ignored after R, as later - * devices should just set config_tether_upstream_automatic to true instead. - */ - public static final String TETHER_FORCE_UPSTREAM_AUTOMATIC_VERSION = - "tether_force_upstream_automatic_version"; /** * Settings key to foce choosing usb functions for usb tethering. @@ -123,14 +113,6 @@ public class TetheringConfiguration { public static final String TETHER_FORCE_USB_FUNCTIONS = "tether_force_usb_functions"; - /** - * Experiment flag to enable TETHERING_WEAR. - */ - public static final String TETHER_ENABLE_WEAR_TETHERING = - "tether_enable_wear_tethering"; - - public static final String TETHER_ENABLE_SYNC_SM = "tether_enable_sync_sm"; - /** * Default value that used to periodic polls tether offload stats from tethering offload HAL * to make the data warnings work. @@ -140,18 +122,6 @@ public class TetheringConfiguration { /** A flag for using synchronous or asynchronous state machine. */ public static boolean USE_SYNC_SM = true; - /** - * A feature flag to control whether the active sessions metrics should be enabled. - * Disabled by default. - */ - public static final String TETHER_ACTIVE_SESSIONS_METRICS = "tether_active_sessions_metrics"; - - /** - * A feature flag to control whether the tethering local network agent should be enabled. - * Disabled by default. - */ - public static final String TETHERING_LOCAL_NETWORK_AGENT = "tethering_local_network_agent"; - public final String[] tetherableUsbRegexs; public final String[] tetherableWifiRegexs; public final String[] tetherableWigigRegexs; diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringFeatureFlags.java b/Tethering/src/com/android/networkstack/tethering/TetheringFeatureFlags.java new file mode 100644 index 0000000000..fd4aa510ea --- /dev/null +++ b/Tethering/src/com/android/networkstack/tethering/TetheringFeatureFlags.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2025 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 com.android.networkstack.tethering; + +import com.android.net.module.util.DeviceConfigUtils; + +/** + * The class that contains Tethering feature flags; + * + * @hide + */ +public class TetheringFeatureFlags { + /** + * A feature flag to control whether the automatic forced choosing upstreams is enabled. + * + * This setting is intended to help force-enable the feature on OEM devices that disabled it + * via resource overlays, and later noticed issues. To that end, it overrides + * config_tether_upstream_automatic when set to true. + * + * This flag is enabled if !=0 and less than the module APEX version: see + * {@link DeviceConfigUtils#isTetheringFeatureEnabled}. It is also ignored after R, as later + * devices should just set config_tether_upstream_automatic to true instead. + */ + public static final String TETHER_FORCE_UPSTREAM_AUTOMATIC_VERSION = + "tether_force_upstream_automatic_version"; + + /** + * A feature flag to control whether the TETHERING_WEAR is enabled. + */ + public static final String TETHER_ENABLE_WEAR_TETHERING = "tether_enable_wear_tethering"; + + /** + * A feature flag to control whether the force random prefix base selection is enabled. + */ + public static final String TETHER_FORCE_RANDOM_PREFIX_BASE_SELECTION = + "tether_force_random_prefix_base_selection"; + + /** + * A feature flag to control whether the synchronize state machine is enabled. + */ + public static final String TETHER_ENABLE_SYNC_SM = "tether_enable_sync_sm"; + + /** + * A feature flag to control whether the active sessions metrics should be enabled. + * Disabled by default. + */ + public static final String TETHER_ACTIVE_SESSIONS_METRICS = "tether_active_sessions_metrics"; + + /** + * A feature flag to control whether the tethering local network agent should be enabled. + * Disabled by default. + */ + public static final String TETHERING_LOCAL_NETWORK_AGENT = "tethering_local_network_agent"; + + /** + * A feature flag to control whether the Wifi P2P Group Owner local network agent + * should be enabled. Disabled by default. This depends on TETHERING_LOCAL_AGENT + * and will not be enabled if that flag is off. + */ + public static final String WIFIP2PGO_LOCAL_NETWORK_AGENT = "wifip2pgo_local_network_agent"; +} diff --git a/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java b/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java index 1323f28ff7..7f837f1990 100644 --- a/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java +++ b/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java @@ -108,7 +108,7 @@ public abstract class EthernetTetheringTestBase { // See TetheredInterfaceRequester.getInterface, isInterfaceForTetheringAvailable. private static final int SHORT_TIMEOUT_MS = 1000; private static final int TETHER_REACHABILITY_ATTEMPTS = 20; - protected static final long WAIT_RA_TIMEOUT_MS = 2000; + protected static final long WAIT_RA_TIMEOUT_MS = 10000; // Address and NAT prefix definition. protected static final MacAddress TEST_MAC = MacAddress.fromString("1:2:3:4:5:6"); @@ -296,7 +296,7 @@ public abstract class EthernetTetheringTestBase { final long deadline = SystemClock.uptimeMillis() + timeoutMs; do { byte[] pkt = reader.popPacket(timeoutMs); - if (isExpectedIcmpPacket(pkt, true /* hasEth */, false /* isIpv4 */, + if (pkt != null && isExpectedIcmpPacket(pkt, true /* hasEth */, false /* isIpv4 */, ICMPV6_ROUTER_ADVERTISEMENT)) { return; } diff --git a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java index d47f4b3294..14795b7320 100644 --- a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java +++ b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java @@ -93,8 +93,8 @@ import com.android.testutils.DeviceInfoUtils; import com.android.testutils.DumpTestUtils; import com.android.testutils.NetworkStackModuleTest; import com.android.testutils.PollPacketReader; -import com.android.testutils.RecorderCallback.CallbackEntry; import com.android.testutils.TestableNetworkCallback; +import com.android.testutils.TestableNetworkCallback.Event; import org.junit.After; import org.junit.Rule; @@ -219,6 +219,7 @@ public class EthernetTetheringTest extends EthernetTetheringTestBase { // Shamelessly copied from TetheringConfiguration. private static final String TETHERING_LOCAL_NETWORK_AGENT = "tethering_local_network_agent"; + private static final String WIFIP2PGO_LOCAL_NETWORK_AGENT = "wifip2pgo_local_network_agent"; @After public void tearDown() throws Exception { @@ -1245,15 +1246,29 @@ public class EthernetTetheringTest extends EthernetTetheringTestBase { } } + private void assumeMatchNonThreadLocalNetworksEnabled() { + assumeTrue(runAsShell(READ_COMPAT_CHANGE_CONFIG, LOG_COMPAT_CHANGE, + () -> CompatChanges.isChangeEnabled(ENABLE_MATCH_NON_THREAD_LOCAL_NETWORKS))); + } + @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) @Test - public void testLocalAgent_networkCallbacks() throws Exception { - final boolean isMatchNonThreadLocalNetworksEnabled = runAsShell( - READ_COMPAT_CHANGE_CONFIG, LOG_COMPAT_CHANGE, - () -> CompatChanges.isChangeEnabled(ENABLE_MATCH_NON_THREAD_LOCAL_NETWORKS)); - assumeTrue(isMatchNonThreadLocalNetworksEnabled); - + public void testTetheringLocalAgent_networkCallbacks() throws Exception { + assumeMatchNonThreadLocalNetworksEnabled(); mDeviceConfigRule.setConfig(NAMESPACE_TETHERING, TETHERING_LOCAL_NETWORK_AGENT, "1"); + doTestLocalAgent_networkCallbacks(CONNECTIVITY_SCOPE_GLOBAL); + } + + @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + @Test + public void testWifiP2pGroupOwnerLocalAgent_networkCallbacks() throws Exception { + assumeMatchNonThreadLocalNetworksEnabled(); + mDeviceConfigRule.setConfig(NAMESPACE_TETHERING, TETHERING_LOCAL_NETWORK_AGENT, "1"); + mDeviceConfigRule.setConfig(NAMESPACE_TETHERING, WIFIP2PGO_LOCAL_NETWORK_AGENT, "1"); + doTestLocalAgent_networkCallbacks(CONNECTIVITY_SCOPE_LOCAL); + } + + private void doTestLocalAgent_networkCallbacks(int scope) throws Exception { assumeFalse(isInterfaceForTetheringAvailable()); setIncludeTestInterfaces(true); @@ -1273,17 +1288,30 @@ public class EthernetTetheringTest extends EthernetTetheringTestBase { downstreamIface.getInterfaceName(), iface); final TetheringRequest request = new TetheringRequest.Builder(TETHERING_ETHERNET) - .setConnectivityScope(CONNECTIVITY_SCOPE_GLOBAL).build(); + .setConnectivityScope(scope).build(); tetheringEventCallback = enableTethering(iface, request, null /* any upstream */); - tetheringEventCallback.awaitInterfaceTethered(); + if (scope == CONNECTIVITY_SCOPE_GLOBAL) { + tetheringEventCallback.awaitInterfaceTethered(); + } else { + tetheringEventCallback.awaitInterfaceLocalOnly(); + } // Verify NetworkCallback works accordingly. - final Network network = networkCallback.expect(CallbackEntry.AVAILABLE).getNetwork(); - final CallbackEntry.CapabilitiesChanged capEvent = - networkCallback.eventuallyExpect(CallbackEntry.NETWORK_CAPS_UPDATED); + final Network network = networkCallback.expect(Event.AVAILABLE).getNetwork(); + final Event.CapabilitiesChanged capEvent = + networkCallback.eventuallyExpect(Event.NETWORK_CAPS_UPDATED); assertEquals(network, capEvent.getNetwork()); assertTrue(capEvent.getCaps().hasTransport(TRANSPORT_ETHERNET)); assertTrue(capEvent.getCaps().hasCapability(NET_CAPABILITY_LOCAL_NETWORK)); + + // Verify details in the LinkPropertiesChanged event. + final Event.LinkPropertiesChanged lpEvent = + networkCallback.eventuallyExpect(Event.LINK_PROPERTIES_CHANGED); + assertEquals(network, lpEvent.getNetwork()); + assertEquals(iface, lpEvent.getLp().getInterfaceName()); + assertTrue(lpEvent.getLp().hasIPv4Address()); + // At least one Ipv4 route for the subnet is needed. + assertTrue(lpEvent.getLp().getRoutes().size() >= 1); } finally { stopEthernetTethering(tetheringEventCallback); maybeCloseTestInterface(downstreamIface); diff --git a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java index dc90d686f0..1d0deff3d8 100644 --- a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java +++ b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java @@ -40,7 +40,8 @@ import static android.net.ip.IpServer.getTetherableIpv6Prefixes; import static com.android.modules.utils.build.SdkLevel.isAtLeastT; import static com.android.modules.utils.build.SdkLevel.isAtLeastV; import static com.android.net.module.util.Inet4AddressUtils.intToInet4AddressHTH; -import static com.android.networkstack.tethering.TetheringConfiguration.TETHERING_LOCAL_NETWORK_AGENT; +import static com.android.networkstack.tethering.TetheringFeatureFlags.TETHERING_LOCAL_NETWORK_AGENT; +import static com.android.networkstack.tethering.TetheringFeatureFlags.WIFIP2PGO_LOCAL_NETWORK_AGENT; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -1123,20 +1124,33 @@ public class IpServerTest { @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) @SetFeatureFlagsRule.FeatureFlag(name = TETHERING_LOCAL_NETWORK_AGENT, enabled = false) + @SetFeatureFlagsRule.FeatureFlag(name = WIFIP2PGO_LOCAL_NETWORK_AGENT) @Test public void testTetheringNetworkAgent_tetheringAgentDisabled() throws Exception { doTestTetheringNetworkAgent(CONNECTIVITY_SCOPE_GLOBAL, false); + // Even if the flag is enabled, no wifip2p network agent if the tethering network + // agent feature is not supported. + doTestTetheringNetworkAgent(CONNECTIVITY_SCOPE_LOCAL, false); } // Verify Tethering Network Agent feature doesn't affect Wi-fi P2P Group Owner although // the code is mostly shared. @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) @SetFeatureFlagsRule.FeatureFlag(name = TETHERING_LOCAL_NETWORK_AGENT) + @SetFeatureFlagsRule.FeatureFlag(name = WIFIP2PGO_LOCAL_NETWORK_AGENT, enabled = false) @Test public void testTetheringNetworkAgent_p2pGroupOwnerAgentDisabled() throws Exception { doTestTetheringNetworkAgent(CONNECTIVITY_SCOPE_LOCAL, false); } + @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + @SetFeatureFlagsRule.FeatureFlag(name = TETHERING_LOCAL_NETWORK_AGENT) + @SetFeatureFlagsRule.FeatureFlag(name = WIFIP2PGO_LOCAL_NETWORK_AGENT) + @Test + public void testTetheringNetworkAgent_p2pGroupOwnerAgentEnabled() throws Exception { + doTestTetheringNetworkAgent(CONNECTIVITY_SCOPE_LOCAL, true); + } + private void doTestTetheringNetworkAgent(int scope, boolean expectAgentEnabled) throws Exception { initStateMachine(TETHERING_USB); diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java index b537fb65a3..92ed6fc5b4 100644 --- a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java +++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java @@ -61,7 +61,7 @@ import static com.android.networkstack.tethering.BpfCoordinator.toIpv4MappedAddr import static com.android.networkstack.tethering.BpfUtils.DOWNSTREAM; import static com.android.networkstack.tethering.BpfUtils.UPSTREAM; import static com.android.networkstack.tethering.TetheringConfiguration.DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS; -import static com.android.networkstack.tethering.TetheringConfiguration.TETHER_ACTIVE_SESSIONS_METRICS; +import static com.android.networkstack.tethering.TetheringFeatureFlags.TETHER_ACTIVE_SESSIONS_METRICS; import static com.android.testutils.MiscAsserts.assertSameElements; import static org.junit.Assert.assertArrayEquals; diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java index 7bfdb22cfc..de39665ca3 100644 --- a/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java +++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java @@ -40,7 +40,6 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSess import static com.android.networkstack.apishim.ConstantsShim.KEY_CARRIER_SUPPORTS_TETHERING_BOOL; import static com.android.testutils.DevSdkIgnoreRule.IgnoreAfter; import static com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo; -import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -67,6 +66,7 @@ import android.content.pm.ModuleInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.res.Resources; +import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.PersistableBundle; @@ -603,7 +603,7 @@ public final class EntitlementManagerTest { .onTetherProvisioningFailed(TETHERING_WIFI, FAILED_TETHERING_REASON); } - @IgnoreUpTo(SC_V2) + @IgnoreUpTo(Build.VERSION_CODES.S_V2) @Test public void testUiProvisioningMultiUser_aboveT_createContextAsUserThrows() { mMockContext.setCreateContextAsUserException(new IllegalStateException()); @@ -611,14 +611,14 @@ public final class EntitlementManagerTest { doTestUiProvisioningMultiUser(false, 1); } - @IgnoreUpTo(SC_V2) + @IgnoreUpTo(Build.VERSION_CODES.S_V2) @Test public void testUiProvisioningMultiUser_aboveT() { doTestUiProvisioningMultiUser(true, 1); doTestUiProvisioningMultiUser(false, 0); } - @IgnoreAfter(SC_V2) + @IgnoreAfter(Build.VERSION_CODES.S_V2) @Test public void testUiProvisioningMultiUser_belowT() { doTestUiProvisioningMultiUser(true, 1); @@ -734,7 +734,7 @@ public final class EntitlementManagerTest { } @Test - @IgnoreUpTo(SC_V2) + @IgnoreUpTo(Build.VERSION_CODES.S_V2) public void requestLatestTetheringEntitlementResult_carrierDoesNotSupport_noProvisionCount() throws Exception { setupCarrierConfig(false); @@ -746,7 +746,7 @@ public final class EntitlementManagerTest { } @Test - @IgnoreUpTo(SC_V2) + @IgnoreUpTo(Build.VERSION_CODES.S_V2) public void reevaluateSimCardProvisioning_carrierUnsupportAndSimswitch() { setupForRequiredProvisioning(); @@ -771,7 +771,7 @@ public final class EntitlementManagerTest { } @Test - @IgnoreUpTo(SC_V2) + @IgnoreUpTo(Build.VERSION_CODES.S_V2) public void startProvisioningIfNeeded_carrierUnsupport() throws Exception { setupCarrierConfig(false); diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java index ada88fbb65..15653692cd 100644 --- a/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java +++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java @@ -26,6 +26,7 @@ import static android.net.TetheringManager.TETHERING_WIFI; import static android.net.TetheringManager.TETHERING_WIFI_P2P; import static android.net.ip.IpServer.CMD_NOTIFY_PREFIX_CONFLICT; +import static com.android.net.module.util.PrivateAddressCoordinator.TETHER_FORCE_RANDOM_PREFIX_BASE_SELECTION; import static com.android.networkstack.tethering.util.PrefixUtils.asIpPrefix; import static org.junit.Assert.assertEquals; @@ -50,7 +51,6 @@ import android.net.LinkProperties; import android.net.Network; import android.net.NetworkCapabilities; import android.net.ip.IpServer; -import android.os.Build; import android.os.IBinder; import androidx.test.filters.SmallTest; @@ -58,10 +58,8 @@ import androidx.test.runner.AndroidJUnit4; import com.android.net.module.util.IIpv4PrefixRequest; import com.android.net.module.util.PrivateAddressCoordinator; -import com.android.testutils.DevSdkIgnoreRule; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -73,9 +71,6 @@ import java.util.Arrays; @RunWith(AndroidJUnit4.class) @SmallTest public final class PrivateAddressCoordinatorTest { - @Rule - public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule(); - private static final String TEST_IFNAME = "test0"; @Mock private IpServer mHotspotIpServer; @@ -236,9 +231,11 @@ public final class PrivateAddressCoordinatorTest { assertEquals(usbAddress, newUsbAddress); final UpstreamNetworkState wifiUpstream = buildUpstreamNetworkState(mWifiNetwork, - hotspotAddress, null, makeNetworkCapabilities(TRANSPORT_WIFI)); + new LinkAddress("192.168.88.23/16"), null, + makeNetworkCapabilities(TRANSPORT_WIFI)); updateUpstreamPrefix(wifiUpstream); verify(mHotspotIpServer).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT); + verify(mUsbIpServer).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT); } private UpstreamNetworkState buildUpstreamNetworkState(final Network network, @@ -326,9 +323,11 @@ public final class PrivateAddressCoordinatorTest { assertFalse(localHotspotPrefix.containsPrefix(hotspotPrefix)); } - @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM) @Test public void testStartedPrefixRange() throws Exception { + when(mDeps.isFeatureNotChickenedOut(TETHER_FORCE_RANDOM_PREFIX_BASE_SELECTION)) + .thenReturn(true); + startedPrefixBaseTest("192.168.0.0/16", 0); startedPrefixBaseTest("192.168.0.0/16", 1); diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java index 42882c7c3c..99baf27e82 100644 --- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java +++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java @@ -31,6 +31,7 @@ import static com.android.networkstack.tethering.TetheringConfiguration.TETHER_E import static com.android.networkstack.tethering.TetheringConfiguration.TETHER_FORCE_USB_FUNCTIONS; import static com.android.networkstack.tethering.TetheringConfiguration.TETHER_USB_NCM_FUNCTION; import static com.android.networkstack.tethering.TetheringConfiguration.TETHER_USB_RNDIS_FUNCTION; +import static com.android.networkstack.tethering.TetheringFeatureFlags.TETHER_FORCE_UPSTREAM_AUTOMATIC_VERSION; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; @@ -179,7 +180,7 @@ public class TetheringConfigurationTest { @Override boolean isTetherForceUpstreamAutomaticFeatureEnabled() { - return isMockFlagEnabled(TetheringConfiguration.TETHER_FORCE_UPSTREAM_AUTOMATIC_VERSION, + return isMockFlagEnabled(TETHER_FORCE_UPSTREAM_AUTOMATIC_VERSION, false /* defaultEnabled */); } @@ -639,8 +640,7 @@ public class TetheringConfigurationTest { } private void setTetherForceUpstreamAutomaticFlagEnabled(Boolean enabled) { - mDeps.setFeatureEnabled( - TetheringConfiguration.TETHER_FORCE_UPSTREAM_AUTOMATIC_VERSION, enabled); + mDeps.setFeatureEnabled(TETHER_FORCE_UPSTREAM_AUTOMATIC_VERSION, enabled); } private void assertChooseUpstreamAutomaticallyIs(boolean value) { @@ -762,28 +762,22 @@ public class TetheringConfigurationTest { } private void setTetherEnableSyncSMFlagEnabled(Boolean enabled) { - mDeps.setFeatureEnabled(TetheringConfiguration.TETHER_ENABLE_SYNC_SM, enabled); + mDeps.setFeatureEnabled(TetheringFeatureFlags.TETHER_ENABLE_SYNC_SM, enabled); new TetheringConfiguration( mMockContext, mLog, INVALID_SUBSCRIPTION_ID, mDeps).readEnableSyncSM(mMockContext); } - private void assertEnableSyncSM(boolean value) { - assertEquals(value, TetheringConfiguration.USE_SYNC_SM); - } - @Test public void testEnableSyncSMFlag() throws Exception { // Test default enabled setTetherEnableSyncSMFlagEnabled(null); - assertEnableSyncSM(true); + assertEquals(true, TetheringConfiguration.USE_SYNC_SM); setTetherEnableSyncSMFlagEnabled(true); - assertEnableSyncSM(true); + assertEquals(true, TetheringConfiguration.USE_SYNC_SM); - // Feature is enabled by default after 25Q2 release. - if (!SdkUtil.isAtLeast25Q2()) { - setTetherEnableSyncSMFlagEnabled(false); - assertEnableSyncSM(false); - } + // Feature is enabled forcefully after 25Q2 release. + setTetherEnableSyncSMFlagEnabled(false); + assertEquals(SdkUtil.isAtLeast25Q2(), TetheringConfiguration.USE_SYNC_SM); } } diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java index 40a673d6d4..5614a35219 100644 --- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java +++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java @@ -57,6 +57,7 @@ import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR; import static android.net.TetheringManager.TETHER_ERROR_SERVICE_UNAVAIL; import static android.net.TetheringManager.TETHER_ERROR_UNKNOWN_IFACE; import static android.net.TetheringManager.TETHER_ERROR_UNKNOWN_REQUEST; +import static android.net.TetheringManager.TETHER_ERROR_UNSUPPORTED; import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_FAILED; import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_STARTED; import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_STOPPED; @@ -84,10 +85,10 @@ import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOA import static com.android.networkstack.tethering.TestConnectivityManager.BROADCAST_FIRST; import static com.android.networkstack.tethering.TestConnectivityManager.CALLBACKS_FIRST; import static com.android.networkstack.tethering.Tethering.UserRestrictionActionListener; -import static com.android.networkstack.tethering.TetheringConfiguration.TETHERING_LOCAL_NETWORK_AGENT; import static com.android.networkstack.tethering.TetheringConfiguration.TETHER_FORCE_USB_FUNCTIONS; import static com.android.networkstack.tethering.TetheringConfiguration.TETHER_USB_NCM_FUNCTION; import static com.android.networkstack.tethering.TetheringConfiguration.TETHER_USB_RNDIS_FUNCTION; +import static com.android.networkstack.tethering.TetheringFeatureFlags.TETHERING_LOCAL_NETWORK_AGENT; import static com.android.networkstack.tethering.TetheringNotificationUpdater.DOWNSTREAM_NONE; import static com.android.networkstack.tethering.UpstreamNetworkMonitor.EVENT_ON_CAPABILITIES; import static com.android.testutils.TestPermissionUtil.runAsShell; @@ -538,8 +539,8 @@ public class TetheringTest { public RoutingCoordinatorManager getRoutingCoordinator( final Context context, SharedLog log) { ConnectivityManager cm = context.getSystemService(ConnectivityManager.class); - when(mPrivateAddressCoordinatorDependencies.isFeatureEnabled(anyString())) - .thenReturn(false); + when(mPrivateAddressCoordinatorDependencies.isFeatureNotChickenedOut(anyString())) + .thenReturn(true); RoutingCoordinatorService service = new RoutingCoordinatorService( getINetd(context, log), cm::getAllNetworks, @@ -1007,23 +1008,11 @@ public class TetheringTest { } sendWifiApStateChanged(WIFI_AP_STATE_ENABLED); - // If, and only if, Tethering received an interface status changed then - // it creates a IpServer and sends out a broadcast indicating that the - // interface is "available". - if (emulateInterfaceStatusChanged) { - if (!SdkLevel.isAtLeastB()) { - // There is 1 IpServer state change event: STATE_AVAILABLE - verify(mNotificationUpdater, times(1)).onDownstreamChanged(DOWNSTREAM_NONE); - verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_AVAILABLE_TETHER); - verify(mWifiManager).updateInterfaceIpState( - TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED); - } else { - // Starting in B, ignore the interfaceStatusChanged - verify(mNotificationUpdater, never()).onDownstreamChanged(DOWNSTREAM_NONE); - verify(mWifiManager, never()).updateInterfaceIpState( - TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED); - } - } + // Wi-Fi tethering ignores interface up events. The behaviour is the same + // regardless of whether the interface up event was received or not. + verify(mNotificationUpdater, never()).onDownstreamChanged(DOWNSTREAM_NONE); + verify(mWifiManager, never()).updateInterfaceIpState( + TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED); verifyNoMoreInteractions(mNetd); verifyNoMoreInteractions(mWifiManager); } @@ -2045,18 +2034,10 @@ public class TetheringTest { sendWifiApStateChanged(WIFI_AP_STATE_ENABLED); mLooper.dispatchAll(); - if (!SdkLevel.isAtLeastB()) { - // There is 1 IpServer state change event: STATE_AVAILABLE from interfaceStatusChanged - verify(mNotificationUpdater, times(1)).onDownstreamChanged(DOWNSTREAM_NONE); - verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_AVAILABLE_TETHER); - verify(mWifiManager).updateInterfaceIpState( - TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED); - } else { - // Starting in B, ignore the interfaceStatusChanged - verify(mNotificationUpdater, never()).onDownstreamChanged(DOWNSTREAM_NONE); - verify(mWifiManager, never()).updateInterfaceIpState( - TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED); - } + // Wi-Fi tethering ignores interface up events. + verify(mNotificationUpdater, never()).onDownstreamChanged(DOWNSTREAM_NONE); + verify(mWifiManager, never()).updateInterfaceIpState( + TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED); verifyNoMoreInteractions(mNetd); verifyNoMoreInteractions(mWifiManager); } @@ -3420,39 +3401,22 @@ public class TetheringTest { @Test @IgnoreAfter(Build.VERSION_CODES.VANILLA_ICE_CREAM) - public void testRequestStaticIpLegacyTether() throws Exception { + public void testLegacyTetherUnsupported() throws Exception { initTetheringOnTestThread(); - // Call startTethering with static ip - final LinkAddress serverLinkAddr = new LinkAddress("192.168.0.123/24"); - final LinkAddress clientLinkAddr = new LinkAddress("192.168.0.42/24"); - final String serverAddr = "192.168.0.123"; - final int clientAddrParceled = 0xc0a8002a; - final ArgumentCaptor dhcpParamsCaptor = - ArgumentCaptor.forClass(DhcpServingParamsParcel.class); - mTethering.startTethering(createTetheringRequest(TETHERING_WIFI, - serverLinkAddr, clientLinkAddr, false, CONNECTIVITY_SCOPE_GLOBAL, null), - TEST_CALLER_PKG, null); + mTethering.interfaceAdded(TEST_WIFI_IFNAME); mLooper.dispatchAll(); - verifyWifiTetheringRequested(); - mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true); + mTethering.interfaceStatusChanged(TEST_WIFI_IFNAME, false); + mTethering.interfaceStatusChanged(TEST_WIFI_IFNAME, true); - // Call legacyTether on the interface before the link layer event comes back. - // This happens, for example, in pre-T bluetooth tethering: Settings calls startTethering, - // and then the bluetooth code calls the tether() API. - final ResultListener tetherResult = new ResultListener(TETHER_ERROR_NO_ERROR); - mTethering.legacyTether(TEST_WLAN_IFNAME, tetherResult); + final ResultListener result = new ResultListener(TETHER_ERROR_UNSUPPORTED); + mTethering.legacyTether(TEST_WIFI_IFNAME, result); mLooper.dispatchAll(); - tetherResult.assertHasResult(); + result.assertHasResult(); - // Verify that the static ip set in startTethering is used - verify(mNetd).interfaceSetCfg(argThat(cfg -> serverAddr.equals(cfg.ipv4Addr))); - verify(mIpServerDependencies, times(1)).makeDhcpServer(any(), dhcpParamsCaptor.capture(), - any()); - final DhcpServingParamsParcel params = dhcpParamsCaptor.getValue(); - assertEquals(serverAddr, intToInet4AddressHTH(params.serverAddr).getHostAddress()); - assertEquals(24, params.serverAddrPrefixLength); - assertEquals(clientAddrParceled, params.singleClientAddr); + // No IpServer should have been started, so no STATE_AVAILABLE should have been sent, and no + // tethering broadcasts should have been sent. + assertEquals(0, mIntents.size()); } @Test diff --git a/bpf/dns_helper/DnsBpfHelper.cpp b/bpf/dns_helper/DnsBpfHelper.cpp index cf2fa2b736..20add47b60 100644 --- a/bpf/dns_helper/DnsBpfHelper.cpp +++ b/bpf/dns_helper/DnsBpfHelper.cpp @@ -42,10 +42,16 @@ static inline void waitForNetProgsLoaded() { // infinite loop until success with 5/10/20/40/60/60/60... delay for (int delay = 5;; delay *= 2) { if (delay > 60) delay = 60; - if (base::WaitForProperty("init.svc.mdnsd_netbpfload", "stopped", std::chrono::seconds(delay)) - && mainlineNetBpfLoadDone()) return; + if (base::WaitForProperty("init.svc.mdnsd_netbpfload", "stopped", std::chrono::seconds(delay))) + break; LOG(WARNING) << "Waited " << delay << "s for init.svc.mdnsd_netbpfload=stopped, still waiting."; } + if (!mainlineNetBpfLoadDone()) { + LOG(ERROR) << "FATAL: init.svc.mdnsd_netbpfload=stopped, yet !mainlineNetBpfLoadDone"; + // mdnsd_netbpfload is marked 'reboot_on_failure', init should start a reboot very soon now, + // spamming logs with an abort is pointless + for (;;); + } } base::Result DnsBpfHelper::init() { diff --git a/bpf/headers/include/bpf/BpfClassic.h b/bpf/headers/include/bpf/BpfClassic.h index 26d8ad5dff..0bfe77baee 100644 --- a/bpf/headers/include/bpf/BpfClassic.h +++ b/bpf/headers/include/bpf/BpfClassic.h @@ -48,7 +48,7 @@ BPF_JUMP(BPF_JMP | BPF_JGT | BPF_K, (v), 0, 1), \ BPF_REJECT -// *THREE* instructions: compare and if *NOT* in range [lo, hi], jump over the reject statement +// *THREE* instructions: compare and if *IN* range [lo, hi] jump over the reject statement #define BPF3_REJECT_IF_NOT_IN_RANGE(lo, hi) \ BPF_JUMP(BPF_JMP | BPF_JGE | BPF_K, (lo), 0, 1), \ BPF_JUMP(BPF_JMP | BPF_JGT | BPF_K, (hi), 0, 1), \ @@ -97,42 +97,42 @@ #define BPF_LOAD_IPV4_U8(field) \ BPF_LOAD_NET_RELATIVE_U8(({ \ _Static_assert(field_sizeof(struct iphdr, field) == 1, "field of wrong size"); \ - offsetof(iphdr, field); \ + offsetof(struct iphdr, field); \ })) // Big/Network Endian 16-bit load from IPv4 header field. #define BPF_LOAD_IPV4_BE16(field) \ BPF_LOAD_NET_RELATIVE_BE16(({ \ _Static_assert(field_sizeof(struct iphdr, field) == 2, "field of wrong size"); \ - offsetof(iphdr, field); \ + offsetof(struct iphdr, field); \ })) // Big/Network Endian 32-bit load from IPv4 header field. #define BPF_LOAD_IPV4_BE32(field) \ BPF_LOAD_NET_RELATIVE_BE32(({ \ _Static_assert(field_sizeof(struct iphdr, field) == 4, "field of wrong size"); \ - offsetof(iphdr, field); \ + offsetof(struct iphdr, field); \ })) // 8-bit load from IPv6 header field. #define BPF_LOAD_IPV6_U8(field) \ BPF_LOAD_NET_RELATIVE_U8(({ \ _Static_assert(field_sizeof(struct ipv6hdr, field) == 1, "field of wrong size"); \ - offsetof(ipv6hdr, field); \ + offsetof(struct ipv6hdr, field); \ })) // Big/Network Endian 16-bit load from IPv6 header field. #define BPF_LOAD_IPV6_BE16(field) \ BPF_LOAD_NET_RELATIVE_BE16(({ \ _Static_assert(field_sizeof(struct ipv6hdr, field) == 2, "field of wrong size"); \ - offsetof(ipv6hdr, field); \ + offsetof(struct ipv6hdr, field); \ })) // Big/Network Endian 32-bit load from IPv6 header field. #define BPF_LOAD_IPV6_BE32(field) \ BPF_LOAD_NET_RELATIVE_BE32(({ \ _Static_assert(field_sizeof(struct ipv6hdr, field) == 4, "field of wrong size"); \ - offsetof(ipv6hdr, field); \ + offsetof(struct ipv6hdr, field); \ })) // Load the length of the IPv4 header into X index register. diff --git a/bpf/headers/include/bpf/BpfUtils.h b/bpf/headers/include/bpf/BpfUtils.h index ed08e1afe0..85502ecbd5 100644 --- a/bpf/headers/include/bpf/BpfUtils.h +++ b/bpf/headers/include/bpf/BpfUtils.h @@ -42,7 +42,11 @@ const bool isAtLeastS = (api_level >= 31); const bool isAtLeastT = (api_level >= 33); const bool isAtLeastU = (api_level >= 34); const bool isAtLeastV = (api_level >= 35); -const bool isAtLeast25Q2 = (api_level >= 36); +const bool isAtLeast25Q2 = (api_level >= 36); // 36.0 +const bool isAtLeast25Q3 = (api_level > 36); // 36.0+ (TODO: fix this!) +const bool isAtLeast25Q4 = false; // 36.1 +const bool isAtLeast26Q1 = false; // 36.1+ +const bool isAtLeast26Q2 = false; // 37.0 // See kernel's net/core/sock_diag.c __sock_gen_cookie() // the implementation of which guarantees 0 will never be returned, diff --git a/bpf/headers/include/bpf_helpers.h b/bpf/headers/include/bpf_helpers.h index 9d6b6f6920..2242daf378 100644 --- a/bpf/headers/include/bpf_helpers.h +++ b/bpf/headers/include/bpf_helpers.h @@ -62,9 +62,21 @@ // Android Mainline BpfLoader when running on Android V (sdk=35) #define BPFLOADER_MAINLINE_V_VERSION (BPFLOADER_MAINLINE_U_QPR3_VERSION + 1u) -// Android Mainline BpfLoader when running on Android 25Q2 (sdk=36) +// Android Mainline BpfLoader when running on Android 25Q2 (sdk=36 aka 36.0) #define BPFLOADER_MAINLINE_25Q2_VERSION (BPFLOADER_MAINLINE_V_VERSION + 1u) +// Android Mainline BpfLoader when running on Android 25Q3 (sdk 36.0+) +#define BPFLOADER_MAINLINE_25Q3_VERSION (BPFLOADER_MAINLINE_25Q2_VERSION + 1u) + +// Android Mainline BpfLoader when running on Android 25Q4 (sdk 36.1) +#define BPFLOADER_MAINLINE_25Q4_VERSION (BPFLOADER_MAINLINE_25Q3_VERSION + 1u) + +// Android Mainline BpfLoader when running on Android 26Q1 (sdk 36.1+) +#define BPFLOADER_MAINLINE_26Q1_VERSION (BPFLOADER_MAINLINE_25Q4_VERSION + 1u) + +// Android Mainline BpfLoader when running on Android 26Q2 (sdk 37.0) +#define BPFLOADER_MAINLINE_26Q2_VERSION (BPFLOADER_MAINLINE_26Q1_VERSION + 1u) + /* For mainline module use, you can #define BPFLOADER_{MIN/MAX}_VER * before #include "bpf_helpers.h" to change which bpfloaders will * process the resulting .o file. @@ -143,22 +155,45 @@ struct kver_uint { unsigned int kver; }; // Helpers for writing sdk level specific bpf programs // -// Note: we choose to follow sdk api level values, but there is no real need for this: -// These just need to be monotonically increasing. We could also use values ten or even -// a hundred times larger to leave room for quarters or months. We may also just use -// dates or something (2502 or 202506 for 25Q2) or even the mainline bpfloader version... +// Note: we choose to follow 'ro.build.version.sdk_full' +// (or just 'sdk' if 'sdk_full' is not available) values, +// multiplied by 100, with 1 added per QPR. +// This will (eventually) match our bpfloader versioning scheme. +// +// This is just for ease of use, really these are only +// ever compared to each other, so they only need to be +// monotonically increasing. +// // For now this easily suffices for our use case. +// +// Note: 24Q1 is the first trunk stable release, +// and thus where quarters start possibly mattering. +// +// We leave most of these as commented out documentation, +// as it's probably a bad idea to actually use them. struct sdk_level_uint { unsigned int sdk_level; }; #define SDK_LEVEL_(v) ((struct sdk_level_uint){ .sdk_level = (v) }) -#define SDK_LEVEL_NONE SDK_LEVEL_(0) -#define SDK_LEVEL_S SDK_LEVEL_(31) // Android 12 -#define SDK_LEVEL_Sv2 SDK_LEVEL_(32) // Android 12L -#define SDK_LEVEL_T SDK_LEVEL_(33) // Android 13 -#define SDK_LEVEL_U SDK_LEVEL_(34) // Android 14 -#define SDK_LEVEL_V SDK_LEVEL_(35) // Android 15 -#define SDK_LEVEL_24Q3 SDK_LEVEL_V -#define SDK_LEVEL_25Q2 SDK_LEVEL_(36) // Android 16 +// SDK_LEVEL_NONE SDK_LEVEL_(0) // mainline implies S+ +#define SDK_LEVEL_S SDK_LEVEL_(3100) // Android 12 [31] +// SDK_LEVEL_Sv2 SDK_LEVEL_(3200) // Android 12L [32] +#define SDK_LEVEL_T SDK_LEVEL_(3300) // Android 13 [33] +#define SDK_LEVEL_U SDK_LEVEL_(3400) // Android 14/U [34] +// SDK_LEVEL_U_QPR1 SDK_LEVEL_(3401) // Android 14/U QPR1 +// SDK_LEVEL_24Q1 SDK_LEVEL_(3402) // Android 14/U QPR2 +// SDK_LEVEL_24Q2 SDK_LEVEL_(3403) // Android 14/U QPR3 +#define SDK_LEVEL_24Q3 SDK_LEVEL_(3500) // Android 15/V [35] +// SDK_LEVEL_24Q4 SDK_LEVEL_(3501) // Android 15/V QPR1 +// SDK_LEVEL_25Q1 SDK_LEVEL_(3502) // Android 15/V QPR2 +#define SDK_LEVEL_25Q2 SDK_LEVEL_(3600) // Android 16 (B) [36.0] +// SDK_LEVEL_25Q3 SDK_LEVEL_(3601) // Android 16 QPR +#define SDK_LEVEL_25Q4 SDK_LEVEL_(3610) // Android 16.1 [36.1] +// SDK_LEVEL_26Q1 SDK_LEVEL_(3611) // Android 16.1 QPR +#define SDK_LEVEL_26Q2 SDK_LEVEL_(3700) // Android 17 (C) [37.0] +// SDK_LEVEL_26Q3 SDK_LEVEL_(3701) // Android 17 QPR +#define SDK_LEVEL_26Q4 SDK_LEVEL_(3710) // Android 17.1 [37.1] +// SDK_LEVEL_27Q1 SDK_LEVEL_(3711) // Android 17.1 QPR +#define SDK_LEVEL_27Q2 SDK_LEVEL_(3800) // Android 18 [38.0] #define SDK_LEVEL_IS_AT_LEAST(lvl, v) ((lvl).sdk_level >= (SDK_LEVEL_##v).sdk_level) @@ -236,6 +271,11 @@ static void* (*bpf_ringbuf_reserve_unsafe)(const struct bpf_map_def* ringbuf, BPF_FUNC_ringbuf_reserve; static void (*bpf_ringbuf_submit_unsafe)(const void* data, __u64 flags) = (void*) BPF_FUNC_ringbuf_submit; +static void* (*bpf_sk_storage_get_unsafe) (const struct bpf_map_def* sk_storage, const void* sk, + const void* value, unsigned long long flags) = (void*) + BPF_FUNC_sk_storage_get; +static int (*bpf_sk_storage_delete_unsafe) (const struct bpf_map_def* sk_storage, + const void* sk) = (void*) BPF_FUNC_sk_storage_delete; #define BPF_ANNOTATE_KV_PAIR(name, type_key, type_val) \ struct ____btf_map_##name { \ @@ -246,20 +286,13 @@ static void (*bpf_ringbuf_submit_unsafe)(const void* data, __u64 flags) = (void* __attribute__ ((section(".maps." #name), used)) \ ____btf_map_##name = { } -#define BPF_ASSERT_LOADER_VERSION(min_loader, ignore_eng, ignore_user, ignore_userdebug) \ - _Static_assert( \ - (min_loader) >= BPFLOADER_IGNORED_ON_VERSION || \ - !((ignore_eng).ignore_on_eng || \ - (ignore_user).ignore_on_user || \ - (ignore_userdebug).ignore_on_userdebug), \ - "bpfloader min version must be >= 0.33 in order to use ignored_on"); - #define ABSOLUTE(x) ((x) < 0 ? -(x) : (x)) -#define DEFAULT_BPF_MAP_FLAGS(type, num_entries, mapflags) \ - ( (mapflags) | \ - ((num_entries) < 0 ? BPF_F_NO_PREALLOC : 0) | \ - (type == BPF_MAP_TYPE_LPM_TRIE ? BPF_F_NO_PREALLOC : 0) \ +#define DEFAULT_BPF_MAP_FLAGS(type, num_entries, mapflags) \ + ( (mapflags) | \ + ((num_entries) < 0 ? BPF_F_NO_PREALLOC : 0) | \ + ( (type == BPF_MAP_TYPE_LPM_TRIE || \ + type == BPF_MAP_TYPE_SK_STORAGE) ? BPF_F_NO_PREALLOC : 0) \ ) #define DEFINE_BPF_MAP_BASE(the_map, TYPE, keysize, valuesize, num_entries, \ @@ -282,11 +315,7 @@ static void (*bpf_ringbuf_submit_unsafe)(const void* data, __u64 flags) = (void* .selinux_context = (selinux), \ .pin_subdir = (pindir), \ .shared = (share).shared, \ - .ignore_on_eng = (ignore_eng).ignore_on_eng, \ - .ignore_on_user = (ignore_user).ignore_on_user, \ - .ignore_on_userdebug = (ignore_userdebug).ignore_on_userdebug, \ - }; \ - BPF_ASSERT_LOADER_VERSION(minloader, ignore_eng, ignore_user, ignore_userdebug); + }; // Type safe macro to declare a ring buffer and related output functions. // Compatibility: @@ -329,6 +358,34 @@ static void (*bpf_ringbuf_submit_unsafe)(const void* data, __u64 flags) = (void* PRIVATE, BPFLOADER_MIN_VER, BPFLOADER_MAX_VER, \ LOAD_ON_ENG, LOAD_ON_USER, LOAD_ON_USERDEBUG) +// Type safe macro to declare a sk storage and related accessor functions. +// BPF_MAP_TYPE_SK_STORAGE was introduced in kernel 5.2 but this map requires BTF and +// BTF is enabled on kernel 5.10 or higher. +#define DEFINE_BPF_SK_STORAGE_EXT(the_map, ValueType, usr, grp, md, selinux, pindir, \ + share, min_loader, max_loader, ignore_eng, \ + ignore_user, ignore_userdebug, mapFlags) \ + DEFINE_BPF_MAP_BASE(the_map, SK_STORAGE, sizeof(uint32_t), sizeof(ValueType), \ + 0, usr, grp, md, selinux, pindir, share, \ + KVER_5_10, KVER_INF, min_loader, max_loader, \ + ignore_eng, ignore_user, ignore_userdebug, mapFlags); \ + BPF_ANNOTATE_KV_PAIR(the_map, uint32_t, ValueType); \ + \ + static inline __always_inline __unused ValueType* bpf_##the_map##_get( \ + const struct bpf_sock* sk, const ValueType* v, unsigned long long flags) { \ + return bpf_sk_storage_get_unsafe(&the_map, sk, v, flags); \ + }; \ + \ + static inline __always_inline __unused int bpf_##the_map##_delete( \ + const struct bpf_sock* sk) { \ + return bpf_sk_storage_delete_unsafe(&the_map, sk); \ + }; + +#define DEFINE_BPF_SK_STORAGE(the_map, TypeOfValue) \ + DEFINE_BPF_SK_STORAGE_EXT(the_map, TypeOfValue, \ + AID_ROOT, AID_NET_BW_ACCT, 0060, "fs_bpf_net_shared", "", \ + PRIVATE, BPFLOADER_MIN_VER, BPFLOADER_MAX_VER, \ + LOAD_ON_ENG, LOAD_ON_USER, LOAD_ON_USERDEBUG, 0) + /* There exist buggy kernels with pre-T OS, that due to * kernel patch "[ALPS05162612] bpf: fix ubsan error" * do not support userspace writes into non-zero index of bpf map arrays. @@ -473,9 +530,6 @@ static int (*bpf_trace_printk)(const char* fmt, int fmt_size, ...) = (void*) BPF .bpfloader_max_ver = (max_loader), \ .selinux_context = (selinux), \ .pin_subdir = (pindir), \ - .ignore_on_eng = (ignore_eng).ignore_on_eng, \ - .ignore_on_user = (ignore_user).ignore_on_user, \ - .ignore_on_userdebug = (ignore_userdebug).ignore_on_userdebug, \ }; \ SECTION(SECTION_NAME) \ int the_prog diff --git a/bpf/headers/include/bpf_map_def.h b/bpf/headers/include/bpf_map_def.h index 2e5afca97b..c98f40b4ea 100644 --- a/bpf/headers/include/bpf_map_def.h +++ b/bpf/headers/include/bpf_map_def.h @@ -117,25 +117,22 @@ _Static_assert(_Alignof(unsigned long long) == 8, "_Alignof unsigned long long ! // for maps: struct shared_bool { bool shared; }; #define PRIVATE ((struct shared_bool){ .shared = false }) -#define SHARED ((struct shared_bool){ .shared = true }) +//#define SHARED ((struct shared_bool){ .shared = true }) // for programs: struct optional_bool { bool optional; }; #define MANDATORY ((struct optional_bool){ .optional = false }) #define OPTIONAL ((struct optional_bool){ .optional = true }) -// for both maps and programs: -struct ignore_on_eng_bool { bool ignore_on_eng; }; -#define LOAD_ON_ENG ((struct ignore_on_eng_bool){ .ignore_on_eng = false }) -#define IGNORE_ON_ENG ((struct ignore_on_eng_bool){ .ignore_on_eng = true }) +// (deprecated) for both maps and programs: +struct ignore_on_eng_bool { }; +#define LOAD_ON_ENG ((struct ignore_on_eng_bool){ }) -struct ignore_on_user_bool { bool ignore_on_user; }; -#define LOAD_ON_USER ((struct ignore_on_user_bool){ .ignore_on_user = false }) -#define IGNORE_ON_USER ((struct ignore_on_user_bool){ .ignore_on_user = true }) +struct ignore_on_user_bool { }; +#define LOAD_ON_USER ((struct ignore_on_user_bool){ }) -struct ignore_on_userdebug_bool { bool ignore_on_userdebug; }; -#define LOAD_ON_USERDEBUG ((struct ignore_on_userdebug_bool){ .ignore_on_userdebug = false }) -#define IGNORE_ON_USERDEBUG ((struct ignore_on_userdebug_bool){ .ignore_on_userdebug = true }) +struct ignore_on_userdebug_bool { }; +#define LOAD_ON_USERDEBUG ((struct ignore_on_userdebug_bool){ }) // Length of strings (incl. selinux_context and pin_subdir) @@ -198,21 +195,7 @@ struct bpf_map_def { bool shared; // use empty string as 'file' component of pin path - allows cross .o map sharing - // The following 3 ignore_on_* fields were added in version 0.32 (U). These are ignored in - // older bpfloader versions, and zero in programs compiled before 0.32. - bool ignore_on_eng:1; - bool ignore_on_user:1; - bool ignore_on_userdebug:1; - // The following 5 ignore_on_* fields were added in version 0.38 (U). These are ignored in - // older bpfloader versions, and zero in programs compiled before 0.38. - // These are tests on the kernel architecture, ie. they ignore userspace bit-ness. - bool ignore_on_arm32:1; - bool ignore_on_aarch64:1; - bool ignore_on_x86_32:1; - bool ignore_on_x86_64:1; - bool ignore_on_riscv64:1; - - char pad0[2]; // manually pad up to 4 byte alignment, may be used for extensions in the future + char pad0[3]; // manually pad up to 4 byte alignment, may be used for extensions in the future unsigned int uid; // uid_t }; @@ -235,21 +218,7 @@ struct bpf_prog_def { bool optional; // program section (ie. function) may fail to load, continue onto next func. - // The following 3 ignore_on_* fields were added in version 0.33 (U). These are ignored in - // older bpfloader versions, and zero in programs compiled before 0.33. - bool ignore_on_eng:1; - bool ignore_on_user:1; - bool ignore_on_userdebug:1; - // The following 5 ignore_on_* fields were added in version 0.38 (U). These are ignored in - // older bpfloader versions, and zero in programs compiled before 0.38. - // These are tests on the kernel architecture, ie. they ignore userspace bit-ness. - bool ignore_on_arm32:1; - bool ignore_on_aarch64:1; - bool ignore_on_x86_32:1; - bool ignore_on_x86_64:1; - bool ignore_on_riscv64:1; - - char pad0[2]; // manually pad up to 4 byte alignment, may be used for extensions in the future + char pad0[3]; // manually pad up to 4 byte alignment, may be used for extensions in the future // The following fields were added in version 0.1 unsigned int bpfloader_min_ver; // if missing, defaults to 0, ie. v0.0 diff --git a/bpf/loader/NetBpfLoad.cpp b/bpf/loader/NetBpfLoad.cpp index bdc2e8c609..c61aec34a1 100644 --- a/bpf/loader/NetBpfLoad.cpp +++ b/bpf/loader/NetBpfLoad.cpp @@ -16,6 +16,7 @@ #define LOG_TAG "NetBpfLoad" +#include #include #include #include @@ -122,12 +123,6 @@ static constexpr bool specified(domain d) { return d != domain::unspecified; } -struct Location { - const char* const dir = ""; - const char* const prefix = ""; - const bool t_plus = true; -}; - // Returns the build type string (from ro.build.type). const std::string& getBuildType() { static std::string t = GetProperty("ro.build.type", "unknown"); @@ -644,10 +639,11 @@ static bool mapMatchesExpectations(const unique_fd& fd, const string& mapName, return true; } - ALOGE("bpf map name %s mismatch: desired/found: " + ALOGE("bpf map name %s mismatch: desired/found (errno: %d): " "type:%d/%d key:%u/%d value:%u/%d entries:%u/%d flags:%u/%d", - mapName.c_str(), type, fd_type, mapDef.key_size, fd_key_size, mapDef.value_size, - fd_value_size, mapDef.max_entries, fd_max_entries, desired_map_flags, fd_map_flags); + mapName.c_str(), errno, type, fd_type, mapDef.key_size, fd_key_size, + mapDef.value_size, fd_value_size, mapDef.max_entries, fd_max_entries, + desired_map_flags, fd_map_flags); return false; } @@ -857,7 +853,7 @@ static int createMaps(const char* elfPath, ifstream& elfFile, vector& if (ret) return ret; struct btf *btf = NULL; - auto scopeGuard = base::make_scope_guard([btf] { if (btf) btf__free(btf); }); + auto btfGuard = base::make_scope_guard([&btf] { if (btf) btf__free(btf); }); if (isAtLeastKernelVersion(5, 10, 0)) { // Untested on Linux Kernel 5.4, but likely compatible. // On Linux Kernels older than 4.18 BPF_BTF_LOAD command doesn't exist. @@ -910,25 +906,6 @@ static int createMaps(const char* elfPath, ifstream& elfFile, vector& continue; } - if ((md[i].ignore_on_eng && isEng()) || (md[i].ignore_on_user && isUser()) || - (md[i].ignore_on_userdebug && isUserdebug())) { - ALOGD("skipping map %s which is ignored on %s builds", mapNames[i].c_str(), - getBuildType().c_str()); - mapFds.push_back(unique_fd()); - continue; - } - - if ((isArm() && isKernel32Bit() && md[i].ignore_on_arm32) || - (isArm() && isKernel64Bit() && md[i].ignore_on_aarch64) || - (isX86() && isKernel32Bit() && md[i].ignore_on_x86_32) || - (isX86() && isKernel64Bit() && md[i].ignore_on_x86_64) || - (isRiscV() && md[i].ignore_on_riscv64)) { - ALOGD("skipping map %s which is ignored on %s", mapNames[i].c_str(), - describeArch()); - mapFds.push_back(unique_fd()); - continue; - } - enum bpf_map_type type = md[i].type; if (type == BPF_MAP_TYPE_LPM_TRIE && !isAtLeastKernelVersion(4, 14, 0)) { // On Linux Kernels older than 4.14 this map type doesn't exist - autoskip. @@ -976,6 +953,7 @@ static int createMaps(const char* elfPath, ifstream& elfFile, vector& if (specified(pin_subdir)) { ALOGV("map %s pin_subdir [%-32s] -> %d -> '%s'", mapNames[i].c_str(), md[i].pin_subdir, static_cast(pin_subdir), lookupPinSubdir(pin_subdir)); + abort(); } // Format of pin location is /sys/fs/bpf/map__ @@ -992,6 +970,7 @@ static int createMaps(const char* elfPath, ifstream& elfFile, vector& saved_errno = errno; ALOGD("bpf_create_map reusing map %s, ret: %d", mapNames[i].c_str(), fd.get()); reuse = true; + abort(); } else { union bpf_attr req = { .map_type = type, @@ -1181,23 +1160,6 @@ static int loadCodeSections(const char* elfPath, vector& cs, const if (bpfloader_ver < bpfMinVer) continue; if (bpfloader_ver >= bpfMaxVer) continue; - if ((cs[i].prog_def->ignore_on_eng && isEng()) || - (cs[i].prog_def->ignore_on_user && isUser()) || - (cs[i].prog_def->ignore_on_userdebug && isUserdebug())) { - ALOGD("cs[%d].name:%s is ignored on %s builds", i, name.c_str(), - getBuildType().c_str()); - continue; - } - - if ((isArm() && isKernel32Bit() && cs[i].prog_def->ignore_on_arm32) || - (isArm() && isKernel64Bit() && cs[i].prog_def->ignore_on_aarch64) || - (isX86() && isKernel32Bit() && cs[i].prog_def->ignore_on_x86_32) || - (isX86() && isKernel64Bit() && cs[i].prog_def->ignore_on_x86_64) || - (isRiscV() && cs[i].prog_def->ignore_on_riscv64)) { - ALOGD("cs[%d].name:%s is ignored on %s", i, name.c_str(), describeArch()); - continue; - } - if (specified(selinux_context)) { ALOGV("prog %s selinux_context [%-32s] -> %d -> '%s' (%s)", name.c_str(), cs[i].prog_def->selinux_context, static_cast(selinux_context), @@ -1208,6 +1170,7 @@ static int loadCodeSections(const char* elfPath, vector& cs, const ALOGV("prog %s pin_subdir [%-32s] -> %d -> '%s'", name.c_str(), cs[i].prog_def->pin_subdir, static_cast(pin_subdir), lookupPinSubdir(pin_subdir)); + abort(); } // strip any potential $foo suffix @@ -1399,10 +1362,7 @@ int loadProg(const char* const elfPath, const unsigned int bpfloader_ver, ALOGV("map_fd found at %d is %d in %s", i, mapFds[i].get(), elfPath); ret = readCodeSections(elfFile, cs); - // BPF .o's with no programs are only supported by mainline netbpfload, - // make sure .o's targeting non-mainline (ie. S) bpfloader don't show up. - if (ret == -ENOENT && bpfLoaderMinVer >= BPFLOADER_MAINLINE_S_VERSION) - return 0; + if (ret == -ENOENT) return 0; if (ret) { ALOGE("Couldn't read all code sections in %s", elfPath); return ret; @@ -1425,85 +1385,62 @@ static bool exists(const char* const path) { } #define APEXROOT "/apex/com.android.tethering" -#define BPFROOT APEXROOT "/etc/bpf" +#define BPFROOT APEXROOT "/etc/bpf/mainline/" -const Location locations[] = { - // S+ Tethering mainline module (network_stack): tether offload - { - .dir = BPFROOT "/tethering/", - .prefix = "tethering/", - .t_plus = false, - }, - // T+ Tethering mainline module (shared with netd & system server) - // netutils_wrapper (for iptables xt_bpf) has access to programs - { - .dir = BPFROOT "/netd_shared/", - .prefix = "netd_shared/", - }, - // T+ Tethering mainline module (shared with netd & system server) - // netutils_wrapper has no access, netd has read only access - { - .dir = BPFROOT "/netd_readonly/", - .prefix = "netd_readonly/", - }, - // T+ Tethering mainline module (shared with system server) - { - .dir = BPFROOT "/net_shared/", - .prefix = "net_shared/", - }, - // T+ Tethering mainline module (not shared, just network_stack) - { - .dir = BPFROOT "/net_private/", - .prefix = "net_private/", - }, -}; - -static int loadAllElfObjects(const unsigned int bpfloader_ver, const Location& location) { - int retVal = 0; - DIR* dir; - struct dirent* ent; - - if ((dir = opendir(location.dir)) != NULL) { - while ((ent = readdir(dir)) != NULL) { - string s = ent->d_name; - if (!EndsWith(s, ".o")) continue; - - string progPath(location.dir); - progPath += s; - - int ret = loadProg(progPath.c_str(), bpfloader_ver, location.prefix); - if (ret) { - retVal = ret; - ALOGE("Failed to load object: %s, ret: %s", progPath.c_str(), std::strerror(-ret)); - } else { - ALOGD("Loaded object: %s", progPath.c_str()); - } - } - closedir(dir); +static int loadObject(const unsigned int bpfloader_ver, const char* const prefix, + const char* const fname) { + string progPath = string(BPFROOT) + fname; + int ret = loadProg(progPath.c_str(), bpfloader_ver, prefix); + if (ret) { + ALOGE("Failed to load object: %s, ret: %s", progPath.c_str(), std::strerror(-ret)); + return 1; } - return retVal; + ALOGD("Loaded object: %s", progPath.c_str()); + return 0; } -static int createSysFsBpfSubDir(const char* const prefix) { - if (*prefix) { - mode_t prevUmask = umask(0); +static int loadAllObjects(const unsigned int bpfloader_ver) { + // S+ Tethering mainline module (network_stack): tether offload + // loads under /sys/fs/bpf/tethering: + if (loadObject(bpfloader_ver, "tethering/", "offload.o")) return 1; + if (loadObject(bpfloader_ver, "tethering/", "test.o")) return 1; + if (isAtLeastT) { + // T+ Tethering mainline module loads under: + // /sys/fs/bpf/net_shared: shared with netd & system server + if (loadObject(bpfloader_ver, "net_shared/", "clatd.o")) return 1; + if (loadObject(bpfloader_ver, "net_shared/", "dscpPolicy.o")) return 1; - string s = "/sys/fs/bpf/"; - s += prefix; + // /sys/fs/bpf/netd_shared: shared with netd & system server + // - netutils_wrapper (for iptables xt_bpf) has access to programs - errno = 0; - int ret = mkdir(s.c_str(), S_ISVTX | S_IRWXU | S_IRWXG | S_IRWXO); - if (ret && errno != EEXIST) { - const int err = errno; - ALOGE("Failed to create directory: %s, ret: %s", s.c_str(), std::strerror(err)); - return -err; - } + // WARNING: Android T+ non-updatable netd depends on both of the + // 'netd_shared' & 'netd' strings for xt_bpf programs it loads + if (loadObject(bpfloader_ver, "netd_shared/", "netd.o")) return 1; - umask(prevUmask); + // /sys/fs/bpf/netd_readonly: shared with netd & system server + // - netutils_wrapper has no access, netd has read only access + + // /sys/fs/bpf/net_private: not shared, just network_stack } return 0; } +static int createDir(const char* const dir) { + mode_t prevUmask = umask(0); + + errno = 0; + int ret = mkdir(dir, S_ISVTX | S_IRWXU | S_IRWXG | S_IRWXO); + if (ret && errno != EEXIST) { + const int err = errno; + umask(prevUmask); + ALOGE("Failed to create directory: %s, ret: %s", dir, std::strerror(err)); + return -err; + } + + umask(prevUmask); + return 0; +} + // Technically 'value' doesn't need to be newline terminated, but it's best // to include a newline to match 'echo "value" > /proc/sys/...foo' behaviour, // which is usually how kernel devs test the actual sysctl interfaces. @@ -1674,6 +1611,10 @@ static int doLoad(char** argv, char * const envp[]) { if (runningAsRoot) ++bpfloader_ver; // [45] BPFLOADER_MAINLINE_U_QPR3_VERSION if (isAtLeastV) ++bpfloader_ver; // [46] BPFLOADER_MAINLINE_V_VERSION if (isAtLeast25Q2) ++bpfloader_ver; // [47] BPFLOADER_MAINLINE_25Q2_VERSION + if (isAtLeast25Q3) ++bpfloader_ver; // [48] BPFLOADER_MAINLINE_25Q3_VERSION + if (isAtLeast25Q4) ++bpfloader_ver; // [49] BPFLOADER_MAINLINE_25Q4_VERSION + if (isAtLeast26Q1) ++bpfloader_ver; // [50] BPFLOADER_MAINLINE_26Q1_VERSION + if (isAtLeast26Q2) ++bpfloader_ver; // [51] BPFLOADER_MAINLINE_26Q2_VERSION ALOGI("NetBpfLoad v0.%u (%s) api:%d/%d kver:%07x (%s) libbpf: v%u.%u " "uid:%d rc:%d%d", @@ -1720,6 +1661,13 @@ static int doLoad(char** argv, char * const envp[]) { return 1; } + // 25Q4 bumps the kernel requirement up to 5.10 + // see also: //system/netd/tests/kernel_test.cpp TestKernel510 + if (isAtLeast25Q4 && !isAtLeastKernelVersion(5, 10, 0)) { + ALOGE("Android 25Q4 requires kernel 5.10."); + return 1; + } + // Technically already required by U, but only enforce on V+ // see also: //system/netd/tests/kernel_test.cpp TestKernel64Bit if (isAtLeastV && isKernel32Bit() && isAtLeastKernelVersion(5, 16, 0)) { @@ -1816,9 +1764,16 @@ static int doLoad(char** argv, char * const envp[]) { } } - // On handheld, 6.6 is highest version supported by Android V (sdk=35), so this is for sdk=36+ - if (!isArm() && isUserspace32bit() && isAtLeastKernelVersion(6, 7, 0)) { - ALOGE("64-bit userspace required on 6.7+ kernels."); + // Linux 6.12 was an LTS released at the end of 2024 (Nov 17), + // and was first supported by Android 16 / 25Q2 (released in June 2025). + // The next Linux LTS should be released near the end of 2025, + // and will likely be 6.18. + // Since officially Android only supports LTS, 6.13+ really means 6.18+, + // and won't be supported before 2026, most likely Android 17 / 26Q2. + // 6.13+ (implying 26Q2+) requires 64-bit userspace. + if (isUserspace32bit() && isAtLeastKernelVersion(6, 13, 0)) { + // due to previous check only reachable on Arm && (<=T kernel uprev || TV || Wear) + ALOGE("64-bit userspace required on 6.13+ kernels."); return 1; } @@ -1832,7 +1787,12 @@ static int doLoad(char** argv, char * const envp[]) { int v = fscanf(f, "# %d %d %d %d %d #", &y, &q, &a, &b, &c); ALOGI("detected %d of 5: %dQ%d api:%d.%d.%d", v, y, q, a, b, c); fclose(f); - if (v != 5 || y != 2025 || q != 2 || a != 36 || b || c) return 1; + if (v != 5) return 1; + if (y < 2025 || y > 2099) return 1; + if (q < 1 || q > 4) return 1; + if (a < 36) return 1; + if (b < 0 || b > 4) return 1; + if (c < 0) return 1; } // Ensure we can determine the Android build type. @@ -1873,36 +1833,76 @@ static int doLoad(char** argv, char * const envp[]) { if (writeProcSysFile("/proc/sys/net/core/bpf_jit_kallsyms", "1\n")) return 1; } + if (runningAsRoot) { // implies U QPR3+ and kernel 4.14+ + // There should not be any programs or maps yet + errno = 0; + uint32_t progId = bpfGetNextProgId(0); // expect 0 with errno == ENOENT + if (progId || errno != ENOENT) { + ALOGE("bpfGetNextProgId(zero) returned %u (errno %d)", progId, errno); + return 1; + } + errno = 0; + uint32_t mapId = bpfGetNextMapId(0); // expect 0 with errno == ENOENT + if (mapId || errno != ENOENT) { + ALOGE("bpfGetNextMapId(zero) returned %u (errno %d)", mapId, errno); + return 1; + } + } else if (isAtLeastKernelVersion(4, 14, 0)) { // implies S through U QPR2 + // bpfGetNext{Prog,Map}Id require 4.14+ + // furthermore since we're not running as root, we're not the initial + // platform bpfloader, so there may already be some maps & programs. + uint32_t mapId = 0; + while (true) { + errno = 0; + uint32_t next = bpfGetNextMapId(mapId); + if (!next && errno == ENOENT) break; + if (next <= mapId) { + ALOGE("bpfGetNextMapId(%u) returned %u errno %d", mapId, next, errno); + return 1; + } + mapId = next; + } + // mapId is now the last map id, creating a new map should change that + unique_fd map(createMap(BPF_MAP_TYPE_ARRAY, sizeof(int), sizeof(int), 1, 0)); + errno = 0; + uint32_t next = bpfGetNextMapId(mapId); + if (next <= mapId) { + // We should fail here on Xiaomi S 4.14.180 due to kernel uapi bug, + // which causes bpfGetNextMapId to behave as bpfGetNextProgId, + // and thus it should return 0 with errno == ENOENT. + ALOGE("bpfGetNextMapId(final %d) returned %d errno %d", mapId, next, errno); + return 1; + } + } else { // implies S/T with 4.9 kernel + // nothing we can do. + } + // Create all the pin subdirectories // (this must be done first to allow selinux_context and pin_subdir functionality, // which could otherwise fail with ENOENT during object pinning or renaming, // due to ordering issues) - for (const auto& location : locations) { - if (location.t_plus && !isAtLeastT) continue; - if (createSysFsBpfSubDir(location.prefix)) return 1; - } + if (createDir("/sys/fs/bpf/tethering")) return 1; + // This is technically T+ but S also needs it for the 'mainline_done' file. + if (createDir("/sys/fs/bpf/netd_shared")) return 1; if (isAtLeastT) { - // Note: there's no actual src dir for fs_bpf_loader .o's, - // so it is not listed in 'locations[].prefix'. - // This is because this is primarily meant for triggering genfscon rules, - // and as such this will likely always be the case. - // Thus we need to manually create the /sys/fs/bpf/loader subdirectory. - if (createSysFsBpfSubDir("loader")) return 1; + if (createDir("/sys/fs/bpf/netd_readonly")) return 1; + if (createDir("/sys/fs/bpf/net_shared")) return 1; + if (createDir("/sys/fs/bpf/net_private")) return 1; + + // This one is primarily meant for triggering genfscon rules. + if (createDir("/sys/fs/bpf/loader")) return 1; } // Load all ELF objects, create programs and maps, and pin them - for (const auto& location : locations) { - if (location.t_plus && !isAtLeastT) continue; - if (loadAllElfObjects(bpfloader_ver, location) != 0) { - ALOGE("=== CRITICAL FAILURE LOADING BPF PROGRAMS FROM %s ===", location.dir); - ALOGE("If this triggers reliably, you're probably missing kernel options or patches."); - ALOGE("If this triggers randomly, you might be hitting some memory allocation " - "problems or startup script race."); - ALOGE("--- DO NOT EXPECT SYSTEM TO BOOT SUCCESSFULLY ---"); - sleep(20); - return 2; - } + if (loadAllObjects(bpfloader_ver)) { + ALOGE("=== CRITICAL FAILURE LOADING BPF PROGRAMS ==="); + ALOGE("If this triggers reliably, you're probably missing kernel options or patches."); + ALOGE("If this triggers randomly, you might be hitting some memory allocation " + "problems or startup script race."); + ALOGE("--- DO NOT EXPECT SYSTEM TO BOOT SUCCESSFULLY ---"); + sleep(20); + return 2; } int key = 1; @@ -1911,14 +1911,11 @@ static int doLoad(char** argv, char * const envp[]) { createMap(BPF_MAP_TYPE_ARRAY, sizeof(key), sizeof(value), 2, 0)); if (writeToMapEntry(map, &key, &value, BPF_ANY)) { ALOGE("Critical kernel bug - failure to write into index 1 of 2 element bpf map array."); - return 1; + if (isAtLeastT) return 1; } - // on S we haven't created this subdir yet, but we need it for 'mainline_done' flag below - if (!isAtLeastT && createSysFsBpfSubDir("netd_shared")) return 1; - // leave a flag that we're done - if (createSysFsBpfSubDir("netd_shared/mainline_done")) return 1; + if (createDir("/sys/fs/bpf/netd_shared/mainline_done")) return 1; // platform bpfloader will only succeed when run as root if (!runningAsRoot) { diff --git a/bpf/loader/netbpfload.35rc b/bpf/loader/netbpfload.35rc index 0fbcb5abdd..683abceb5d 100644 --- a/bpf/loader/netbpfload.35rc +++ b/bpf/loader/netbpfload.35rc @@ -1,9 +1,67 @@ service bpfloader /apex/com.android.tethering/bin/netbpfload + # netbpfload will do network bpf loading, then execute /system/bin/bpfloader capabilities CHOWN SYS_ADMIN NET_ADMIN + # The following group memberships are a workaround for lack of DAC_OVERRIDE + # and allow us to open (among other things) files that we created and are + # no longer root owned (due to CHOWN) but still have group read access to + # one of the following groups. This is not perfect, but a more correct + # solution requires significantly more effort to implement. group root graphics network_stack net_admin net_bw_acct net_bw_stats net_raw system user root file /dev/kmsg w + # + # Set RLIMIT_MEMLOCK to 1GiB for bpfloader + # + # Actually only 8MiB would be needed if bpfloader ran as its own uid. + # + # However, while the rlimit is per-thread, the accounting is system wide. + # So, for example, if the graphics stack has already allocated 10MiB of + # memlock data before bpfloader even gets a chance to run, it would fail + # if its memlock rlimit is only 8MiB - since there would be none left for it. + # + # bpfloader succeeding is critical to system health, since a failure will + # cause netd crashloop and thus system server crashloop... and the only + # recovery is a full kernel reboot. + # + # We've had issues where devices would sometimes (rarely) boot into + # a crashloop because bpfloader would occasionally lose a boot time + # race against the graphics stack's boot time locked memory allocation. + # + # Thus bpfloader's memlock has to be 8MB higher then the locked memory + # consumption of the root uid anywhere else in the system... + # But we don't know what that is for all possible devices... + # + # Ideally, we'd simply grant bpfloader the IPC_LOCK capability and it + # would simply ignore it's memlock rlimit... but it turns that this + # capability is not even checked by the kernel's bpf system call. + # + # As such we simply use 1GiB as a reasonable approximation of infinity. + # rlimit memlock 1073741824 1073741824 oneshot + # + # How to debug bootloops caused by 'bpfloader-failed'. + # + # 1. On some lower RAM devices (like wembley) you may need to first enable developer mode + # (from the Settings app UI), and change the developer option "Logger buffer sizes" + # from the default (wembley: 64kB) to the maximum (1M) per log buffer. + # Otherwise buffer will overflow before you manage to dump it and you'll get useless logs. + # + # 2. comment out 'reboot_on_failure reboot,bpfloader-failed' below + # 3. rebuild/reflash/reboot + # 4. as the device is booting up capture bpfloader logs via: + # adb logcat -s 'bpfloader:*' 'LibBpfLoader:*' 'NetBpfLoad:*' 'NetBpfLoader:*' + # + # something like: + # $ adb reboot; sleep 1; adb wait-for-device; adb root; sleep 1; adb wait-for-device; adb logcat -s 'bpfloader:*' 'LibBpfLoader:*' 'NetBpfLoad:*' 'NetBpfLoader:*' + # will take care of capturing logs as early as possible + # + # 5. look through the logs from the kernel's bpf verifier that bpfloader dumps out, + # it usually makes sense to search back from the end and find the particular + # bpf verifier failure that caused bpfloader to terminate early with an error code. + # This will probably be something along the lines of 'too many jumps' or + # 'cannot prove return value is 0 or 1' or 'unsupported / unknown operation / helper', + # 'invalid bpf_context access', etc. + # reboot_on_failure reboot,bpfloader-failed override diff --git a/bpf/loader/netbpfload.rc b/bpf/loader/netbpfload.rc index 4cc6284cfc..ca7a8c1dba 100644 --- a/bpf/loader/netbpfload.rc +++ b/bpf/loader/netbpfload.rc @@ -1,71 +1,15 @@ +# 2025 2 36 0 1 # 25q3 sdk/api level 36.0 - Android 16 QPR1 + +# Previous versions (for documentation) +# 2025 2 36 0 0 1 # 25q3 sdk/api level 36.0 - Android 16 QPR1 # 2025 2 36 0 0 # 25q2 sdk/api level 36.0 - Android 16 Baklava QPR0 +# (versions before A16 do not include anything here) # Note: This will actually execute /apex/com.android.tethering/bin/netbpfload # by virtue of 'service bpfloader' being overridden by the apex shipped .rc -# Warning: most of the below settings are irrelevant unless the apex is missing. +# Warning: the below settings are irrelevant unless the apex is missing. service bpfloader /system/bin/false - # netbpfload will do network bpf loading, then execute /system/bin/bpfloader - #! capabilities CHOWN SYS_ADMIN NET_ADMIN - # The following group memberships are a workaround for lack of DAC_OVERRIDE - # and allow us to open (among other things) files that we created and are - # no longer root owned (due to CHOWN) but still have group read access to - # one of the following groups. This is not perfect, but a more correct - # solution requires significantly more effort to implement. - #! group root graphics network_stack net_admin net_bw_acct net_bw_stats net_raw system user root - # - # Set RLIMIT_MEMLOCK to 1GiB for bpfloader - # - # Actually only 8MiB would be needed if bpfloader ran as its own uid. - # - # However, while the rlimit is per-thread, the accounting is system wide. - # So, for example, if the graphics stack has already allocated 10MiB of - # memlock data before bpfloader even gets a chance to run, it would fail - # if its memlock rlimit is only 8MiB - since there would be none left for it. - # - # bpfloader succeeding is critical to system health, since a failure will - # cause netd crashloop and thus system server crashloop... and the only - # recovery is a full kernel reboot. - # - # We've had issues where devices would sometimes (rarely) boot into - # a crashloop because bpfloader would occasionally lose a boot time - # race against the graphics stack's boot time locked memory allocation. - # - # Thus bpfloader's memlock has to be 8MB higher then the locked memory - # consumption of the root uid anywhere else in the system... - # But we don't know what that is for all possible devices... - # - # Ideally, we'd simply grant bpfloader the IPC_LOCK capability and it - # would simply ignore it's memlock rlimit... but it turns that this - # capability is not even checked by the kernel's bpf system call. - # - # As such we simply use 1GiB as a reasonable approximation of infinity. - # - #! rlimit memlock 1073741824 1073741824 oneshot - # - # How to debug bootloops caused by 'bpfloader-failed'. - # - # 1. On some lower RAM devices (like wembley) you may need to first enable developer mode - # (from the Settings app UI), and change the developer option "Logger buffer sizes" - # from the default (wembley: 64kB) to the maximum (1M) per log buffer. - # Otherwise buffer will overflow before you manage to dump it and you'll get useless logs. - # - # 2. comment out 'reboot_on_failure reboot,bpfloader-failed' below - # 3. rebuild/reflash/reboot - # 4. as the device is booting up capture bpfloader logs via: - # adb logcat -s 'bpfloader:*' 'LibBpfLoader:*' 'NetBpfLoad:*' 'NetBpfLoader:*' - # - # something like: - # $ adb reboot; sleep 1; adb wait-for-device; adb root; sleep 1; adb wait-for-device; adb logcat -s 'bpfloader:*' 'LibBpfLoader:*' 'NetBpfLoad:*' 'NetBpfLoader:*' - # will take care of capturing logs as early as possible - # - # 5. look through the logs from the kernel's bpf verifier that bpfloader dumps out, - # it usually makes sense to search back from the end and find the particular - # bpf verifier failure that caused bpfloader to terminate early with an error code. - # This will probably be something along the lines of 'too many jumps' or - # 'cannot prove return value is 0 or 1' or 'unsupported / unknown operation / helper', - # 'invalid bpf_context access', etc. - # reboot_on_failure reboot,netbpfload-missing updatable diff --git a/bpf/netd/BpfHandler.cpp b/bpf/netd/BpfHandler.cpp index 680c05eb00..38d3db5b57 100644 --- a/bpf/netd/BpfHandler.cpp +++ b/bpf/netd/BpfHandler.cpp @@ -214,11 +214,17 @@ static inline void waitForNetProgsLoaded() { // infinite loop until success with 5/10/20/40/60/60/60... delay for (int delay = 5;; delay *= 2) { if (delay > 60) delay = 60; - if (WaitForProperty("init.svc.mdnsd_netbpfload", "stopped", std::chrono::seconds(delay)) - && mainlineNetBpfLoadDone()) - return; + if (WaitForProperty("init.svc.mdnsd_netbpfload", "stopped", std::chrono::seconds(delay))) + break; ALOGW("Waited %ds for init.svc.mdnsd_netbpfload=stopped, still waiting...", delay); } + if (!mainlineNetBpfLoadDone()) { + ALOGE("FATAL: init.svc.mdnsd_netbpfload=stopped, yet !mainlineNetBpfLoadDone"); + // mdnsd_netbpfload is marked 'reboot_on_failure', init should start a reboot very soon now, + // spamming logs with an abort is pointless + for (;;); + } + return; } static inline void waitForBpf() { diff --git a/bpf/progs/Android.bp b/bpf/progs/Android.bp index 2bfe613bb0..960efda2b8 100644 --- a/bpf/progs/Android.bp +++ b/bpf/progs/Android.bp @@ -63,34 +63,32 @@ cc_library_headers { // // bpf kernel programs // +bpf { + name: "clatd.o", + srcs: ["clatd.c"], + sub_dir: "mainline", +} + bpf { name: "dscpPolicy.o", srcs: ["dscpPolicy.c"], - sub_dir: "net_shared", + sub_dir: "mainline", +} + +bpf { + name: "netd.o", + srcs: ["netd.c"], + sub_dir: "mainline", } bpf { name: "offload.o", srcs: ["offload.c"], - sub_dir: "tethering", + sub_dir: "mainline", } bpf { name: "test.o", srcs: ["test.c"], - sub_dir: "tethering", -} - -bpf { - name: "clatd.o", - srcs: ["clatd.c"], - sub_dir: "net_shared", -} - -bpf { - // WARNING: Android T's non-updatable netd depends on 'netd' string for xt_bpf programs it loads - name: "netd.o", - srcs: ["netd.c"], - // WARNING: Android T's non-updatable netd depends on 'netd_shared' string for xt_bpf programs - sub_dir: "netd_shared", + sub_dir: "mainline", } diff --git a/bpf/progs/clatd.c b/bpf/progs/clatd.c index f4e4f3bb99..e812e6867f 100644 --- a/bpf/progs/clatd.c +++ b/bpf/progs/clatd.c @@ -288,8 +288,8 @@ DEFINE_BPF_PROG("schedcls/egress4/clat_rawip", AID_ROOT, AID_SYSTEM, sched_cls_e // We cannot handle IP options, just standard 20 byte == 5 dword minimal IPv4 header if (ip4->ihl != 5) return TC_ACT_PIPE; - // Packet must not be multicast - if ((ip4->daddr & htonl(0xf0000000)) == htonl(0xe0000000)) return TC_ACT_PIPE; + // Packet must not be multicast, if it is just outright drop it. + if ((ip4->daddr & htonl(0xf0000000)) == htonl(0xe0000000)) return TC_ACT_SHOT; // Calculate the IPv4 one's complement checksum of the IPv4 header. __wsum sum4 = 0; @@ -401,4 +401,3 @@ DEFINE_BPF_PROG("schedcls/egress4/clat_rawip", AID_ROOT, AID_SYSTEM, sched_cls_e } LICENSE("Apache 2.0"); -CRITICAL("Connectivity"); diff --git a/bpf/progs/dscpPolicy.c b/bpf/progs/dscpPolicy.c index 94d717be5d..fca3fe29e6 100644 --- a/bpf/progs/dscpPolicy.c +++ b/bpf/progs/dscpPolicy.c @@ -278,4 +278,3 @@ DEFINE_BPF_PROG_KVER("schedcls/set_dscp_ether", AID_ROOT, AID_SYSTEM, schedcls_s } LICENSE("Apache 2.0"); -CRITICAL("Connectivity"); diff --git a/bpf/progs/netd.c b/bpf/progs/netd.c index c943a64b52..cfcdc5c104 100644 --- a/bpf/progs/netd.c +++ b/bpf/progs/netd.c @@ -81,6 +81,8 @@ DEFINE_BPF_MAP_NO_NETD(ingress_discard_map, HASH, IngressDiscardKey, IngressDisc DEFINE_BPF_MAP_RW_NETD(lock_array_test_map, ARRAY, uint32_t, bool, 1) DEFINE_BPF_MAP_RW_NETD(lock_hash_test_map, HASH, uint32_t, bool, 1) +DEFINE_BPF_SK_STORAGE(sk_storage, SkStorageValue) + /* never actually used from ebpf */ DEFINE_BPF_MAP_NO_NETD(iface_index_name_map, HASH, uint32_t, IfaceValue, IFACE_INDEX_NAME_MAP_SIZE) @@ -191,13 +193,17 @@ DEFINE_BPF_MAP_EXT(local_net_blocked_uid_map, HASH, uint32_t, bool, -1000, uint64_t packets = 1; \ uint64_t bytes = skb->len; \ if (bytes > mtu) { \ - bool is_ipv6 = (skb->protocol == htons(ETH_P_IPV6)); \ - int ip_overhead = (is_ipv6 ? sizeof(struct ipv6hdr) : sizeof(struct iphdr)); \ - int tcp_overhead = ip_overhead + sizeof(struct tcphdr) + 12; \ - int mss = mtu - tcp_overhead; \ - uint64_t payload = bytes - tcp_overhead; \ - packets = (payload + mss - 1) / mss; \ - bytes = tcp_overhead * packets + payload; \ + const bool is5_4 = KVER_IS_AT_LEAST(kver, 5, 4, 0); \ + const bool is_ipv6 = (skb->protocol == htons(ETH_P_IPV6)); \ + const int ip_overhead = is_ipv6 ? sizeof(struct ipv6hdr) : sizeof(struct iphdr); \ + struct bpf_sock * const sk = is5_4 && skb->sk ? bpf_sk_fullsock(skb->sk) : NULL; \ + const bool is_tcp = !sk || sk->protocol == IPPROTO_TCP; \ + const int L4_size = is_tcp ? sizeof(struct tcphdr) + 12 : sizeof(struct udphdr); \ + const int overhead = ip_overhead + L4_size; \ + const int mss = mtu - overhead; \ + const uint64_t payload = bytes - overhead; \ + packets = is5_4 ? skb->gso_segs : (payload + mss - 1) / mss; \ + bytes = overhead * packets + payload; \ } \ if (egress.egress) { \ __sync_fetch_and_add(&value->txPackets, packets); \ @@ -643,11 +649,18 @@ DEFINE_NETD_BPF_PROG_RANGES("cgroupskb/ingress/stats$5_10_u", return bpf_traffic_account(skb, INGRESS, KVER_5_10, SDK_LEVEL_U); } -// Android T/U/V 4.19 & T/U/V/25Q2 5.4 & T 5.10/5.15 -DEFINE_NETD_BPF_PROG_KVER_RANGE("cgroupskb/ingress/stats$4_19", - bpf_cgroup_ingress_4_19, KVER_4_19, KVER_INF) +// Android T/U/V/25Q2 5.4 & T 5.10/5.15 +DEFINE_NETD_BPF_PROG_KVER_RANGE("cgroupskb/ingress/stats$5_4", + bpf_cgroup_ingress_5_4, KVER_5_4, KVER_INF) (struct __sk_buff* skb) { - return bpf_traffic_account(skb, INGRESS, KVER_4_19, SDK_LEVEL_T); + return bpf_traffic_account(skb, INGRESS, KVER_5_4, SDK_LEVEL_T); +} + +// Android T/U/V 4.19 +DEFINE_NETD_BPF_PROG_KVER_RANGE("cgroupskb/ingress/stats$4_19", + bpf_cgroup_ingress_4_19, KVER_4_19, KVER_5_4) +(struct __sk_buff* skb) { +return bpf_traffic_account(skb, INGRESS, KVER_4_19, SDK_LEVEL_T); } // Android T 4.9 & T/U 4.14 @@ -683,11 +696,18 @@ DEFINE_NETD_BPF_PROG_RANGES("cgroupskb/egress/stats$5_10_u", return bpf_traffic_account(skb, EGRESS, KVER_5_10, SDK_LEVEL_U); } -// Android T/U/V 4.19 & T/U/V/25Q2 5.4 & T 5.10/5.15 -DEFINE_NETD_BPF_PROG_KVER_RANGE("cgroupskb/egress/stats$4_19", - bpf_cgroup_egress_4_19, KVER_4_19, KVER_INF) +// Android T/U/V/25Q2 5.4 & T 5.10/5.15 +DEFINE_NETD_BPF_PROG_KVER_RANGE("cgroupskb/egress/stats$5_4", + bpf_cgroup_egress_5_4, KVER_5_4, KVER_INF) (struct __sk_buff* skb) { - return bpf_traffic_account(skb, EGRESS, KVER_4_19, SDK_LEVEL_T); + return bpf_traffic_account(skb, EGRESS, KVER_5_4, SDK_LEVEL_T); +} + +// Android T/U/V 4.19 +DEFINE_NETD_BPF_PROG_KVER_RANGE("cgroupskb/egress/stats$4_19", + bpf_cgroup_egress_4_19, KVER_4_19, KVER_5_4) +(struct __sk_buff* skb) { +return bpf_traffic_account(skb, EGRESS, KVER_4_19, SDK_LEVEL_T); } // Android T 4.9 & T/U 4.14 @@ -784,8 +804,12 @@ static __always_inline inline uint8_t get_app_permissions(uint32_t uid) { return permissions ? *permissions : BPF_PERMISSION_INTERNET; } -DEFINE_NETD_BPF_PROG_KVER("cgroupsock/inet_create", inet_socket_create, KVER_4_14) -(__unused struct bpf_sock* sk) { +static __always_inline inline int inet_socket_create(struct bpf_sock* sk, + const struct kver_uint kver) { + if (KVER_IS_AT_LEAST(kver, 5, 10, 0)) { + SkStorageValue *v = bpf_sk_storage_get(sk, 0, BPF_SK_STORAGE_GET_F_CREATE); + if (v) v->cookie = bpf_get_sk_cookie(sk); + } uint64_t uid = bpf_get_current_uid_gid() & 0xffffffff; if (get_app_permissions(uid) & BPF_PERMISSION_INTERNET) { return bpf_owner_firewall_match(uid) == PASS ? BPF_ALLOW : BPF_DISALLOW; @@ -794,6 +818,17 @@ DEFINE_NETD_BPF_PROG_KVER("cgroupsock/inet_create", inet_socket_create, KVER_4_1 } } +DEFINE_NETD_BPF_PROG_KVER("cgroupsock/inet_create$5_10", inet_socket_create_5_10, KVER_5_10) +(struct bpf_sock* sk) { + return inet_socket_create(sk, KVER_5_10); +} + +DEFINE_NETD_BPF_PROG_KVER_RANGE("cgroupsock/inet_create$4_14", + inet_socket_create_4_14, KVER_4_14, KVER_5_10) +(struct bpf_sock* sk) { + return inet_socket_create(sk, KVER_4_14); +} + DEFINE_NETD_BPF_PROG_KVER("cgroupsockrelease/inet_release", inet_socket_release, KVER_5_10) (struct bpf_sock* sk) { uint64_t cookie = bpf_get_sk_cookie(sk); @@ -904,4 +939,3 @@ DEFINE_NETD_V_BPF_PROG_KVER("setsockopt/prog", setsockopt_prog, KVER_5_4) } LICENSE("Apache 2.0"); -CRITICAL("Connectivity and netd"); diff --git a/bpf/progs/netd.h b/bpf/progs/netd.h index 1e29091093..964c6cd40b 100644 --- a/bpf/progs/netd.h +++ b/bpf/progs/netd.h @@ -90,6 +90,11 @@ typedef struct { } PacketTrace; STRUCT_SIZE(PacketTrace, 8+4+4 + 4+4 + 2+2 + 1+1+1+1); +typedef struct { + uint64_t cookie; +} SkStorageValue; +STRUCT_SIZE(SkStorageValue, 8); + // Since we cannot garbage collect the stats map since device boot, we need to make these maps as // large as possible. The maximum size of number of map entries we can have is depend on the rlimit // of MEM_LOCK granted to netd. The memory space needed by each map can be calculated by the diff --git a/bpf/progs/offload.c b/bpf/progs/offload.c index b34fe6fd88..ae6f9aea95 100644 --- a/bpf/progs/offload.c +++ b/bpf/progs/offload.c @@ -98,6 +98,7 @@ static inline __always_inline int do_forward6(struct __sk_buff* skb, // Let the kernel's stack handle these cases and generate appropriate ICMP errors. if (ip6->hop_limit <= 1) TC_PUNT(LOW_TTL); + int gso_hdr_size = sizeof(struct ipv6hdr); // If hardware offload is running and programming flows based on conntrack entries, // try not to interfere with it. if (ip6->nexthdr == IPPROTO_TCP) { @@ -117,6 +118,10 @@ static inline __always_inline int do_forward6(struct __sk_buff* skb, // Do not offload TCP packets with any one of the SYN/FIN/RST flags if (tcph->syn || tcph->fin || tcph->rst) TC_PUNT(TCPV6_CONTROL_PACKET); + + gso_hdr_size += tcph->doff * 4; + } else if (ip6->nexthdr == IPPROTO_UDP) { + gso_hdr_size += sizeof(struct udphdr); } // Protect against forwarding packets sourced from ::1 or fe80::/64 or other weirdness. @@ -168,21 +173,24 @@ static inline __always_inline int do_forward6(struct __sk_buff* skb, // Required IPv6 minimum mtu is 1280, below that not clear what we should do, abort... if (v->pmtu < IPV6_MIN_MTU) TC_PUNT(BELOW_IPV6_MTU); - // Approximate handling of TCP/IPv6 overhead for incoming LRO/GRO packets: default - // outbound path mtu of 1500 is not necessarily correct, but worst case we simply - // undercount, which is still better then not accounting for this overhead at all. - // Note: this really shouldn't be device/path mtu at all, but rather should be - // derived from this particular connection's mss (ie. from gro segment size). - // This would require a much newer kernel with newer ebpf accessors. - // (This is also blindly assuming 12 bytes of tcp timestamp option in tcp header) + // Handling of IPv6 overhead for incoming LRO/GRO packets uint64_t packets = 1; uint64_t L3_bytes = skb->len - l2_header_size; + // I don't trust v->pmtu to not be higher then it should be: it might just + // be device (instead of path/route/ipv6) mtu and that itself might be wrong... if (L3_bytes > v->pmtu) { - const int tcp6_overhead = sizeof(struct ipv6hdr) + sizeof(struct tcphdr) + 12; - const int mss = v->pmtu - tcp6_overhead; - const uint64_t payload = L3_bytes - tcp6_overhead; - packets = (payload + mss - 1) / mss; - L3_bytes = tcp6_overhead * packets + payload; + if (gso_hdr_size == sizeof(struct ipv6hdr)) TC_PUNT(UNKNOWN_IPV6_GSO); + const int mss = v->pmtu - gso_hdr_size; + const uint64_t payload = L3_bytes - gso_hdr_size; + if (KVER_IS_AT_LEAST(kver, 5, 4, 0)) { + if (skb->gso_segs <= 1) TC_PUNT(ABOVE_IPV6_PMTU); + // ?udp gso frags: gso_size is variable (thus 0), could this fail to trigger? + if (gso_hdr_size + skb->gso_size > v->pmtu) TC_PUNT(ABOVE_IPV6_PMTU_GSO); + packets = skb->gso_segs; + } else { + packets = (payload + mss - 1) / mss; + } + L3_bytes = gso_hdr_size * packets + payload; } // Are we past the limit? If so, then abort... @@ -246,14 +254,28 @@ static inline __always_inline int do_forward6(struct __sk_buff* skb, return bpf_redirect(v->oif, 0 /* this is effectively BPF_F_EGRESS */); } -DEFINE_BPF_PROG("schedcls/tether_downstream6_ether", AID_ROOT, AID_NETWORK_STACK, - sched_cls_tether_downstream6_ether) +// implementation for 5.4+ (can use skb->gso_{segs,size}) +DEFINE_BPF_PROG_KVER("schedcls/tether_downstream6_ether$5_4", AID_ROOT, AID_NETWORK_STACK, + sched_cls_tether_downstream6_ether_5_4, KVER_5_4) +(struct __sk_buff* skb) { + return do_forward6(skb, ETHER, DOWNSTREAM, KVER_5_4); +} + +DEFINE_BPF_PROG_KVER("schedcls/tether_upstream6_ether$5_4", AID_ROOT, AID_NETWORK_STACK, + sched_cls_tether_upstream6_ether_5_4, KVER_5_4) +(struct __sk_buff* skb) { + return do_forward6(skb, ETHER, UPSTREAM, KVER_5_4); +} + +// implementation for 4.9/4.14/4.19 +DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_downstream6_ether", AID_ROOT, AID_NETWORK_STACK, + sched_cls_tether_downstream6_ether, KVER_NONE, KVER_5_4) (struct __sk_buff* skb) { return do_forward6(skb, ETHER, DOWNSTREAM, KVER_NONE); } -DEFINE_BPF_PROG("schedcls/tether_upstream6_ether", AID_ROOT, AID_NETWORK_STACK, - sched_cls_tether_upstream6_ether) +DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_upstream6_ether", AID_ROOT, AID_NETWORK_STACK, + sched_cls_tether_upstream6_ether, KVER_NONE, KVER_5_4) (struct __sk_buff* skb) { return do_forward6(skb, ETHER, UPSTREAM, KVER_NONE); } @@ -271,15 +293,28 @@ DEFINE_BPF_PROG("schedcls/tether_upstream6_ether", AID_ROOT, AID_NETWORK_STACK, // and there is a test in kernel/tests/net/test/bpf_test.py testSkbChangeHead() // and in system/netd/tests/binder_test.cpp NetdBinderTest TetherOffloadForwarding. // -// Hence, these mandatory (must load successfully) implementations for 4.14+ kernels: -DEFINE_BPF_PROG_KVER("schedcls/tether_downstream6_rawip$4_14", AID_ROOT, AID_NETWORK_STACK, - sched_cls_tether_downstream6_rawip_4_14, KVER_4_14) +// Hence, these mandatory (must load successfully) implementations for 5.4+ kernels: +DEFINE_BPF_PROG_KVER("schedcls/tether_downstream6_rawip$5_4", AID_ROOT, AID_NETWORK_STACK, + sched_cls_tether_downstream6_rawip_5_4, KVER_5_4) +(struct __sk_buff* skb) { + return do_forward6(skb, RAWIP, DOWNSTREAM, KVER_5_4); +} + +DEFINE_BPF_PROG_KVER("schedcls/tether_upstream6_rawip$5_4", AID_ROOT, AID_NETWORK_STACK, + sched_cls_tether_upstream6_rawip_5_4, KVER_5_4) +(struct __sk_buff* skb) { + return do_forward6(skb, RAWIP, UPSTREAM, KVER_5_4); +} + +// and for 4.14/4.19 kernels +DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_downstream6_rawip$4_14", AID_ROOT, AID_NETWORK_STACK, + sched_cls_tether_downstream6_rawip_4_14, KVER_4_14, KVER_5_4) (struct __sk_buff* skb) { return do_forward6(skb, RAWIP, DOWNSTREAM, KVER_4_14); } -DEFINE_BPF_PROG_KVER("schedcls/tether_upstream6_rawip$4_14", AID_ROOT, AID_NETWORK_STACK, - sched_cls_tether_upstream6_rawip_4_14, KVER_4_14) +DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_upstream6_rawip$4_14", AID_ROOT, AID_NETWORK_STACK, + sched_cls_tether_upstream6_rawip_4_14, KVER_4_14, KVER_5_4) (struct __sk_buff* skb) { return do_forward6(skb, RAWIP, UPSTREAM, KVER_4_14); } @@ -387,21 +422,28 @@ static inline __always_inline int do_forward4_bottom(struct __sk_buff* skb, // Required IPv4 minimum mtu is 68, below that not clear what we should do, abort... if (v->pmtu < 68) TC_PUNT(BELOW_IPV4_MTU); - // Approximate handling of TCP/IPv4 overhead for incoming LRO/GRO packets: default - // outbound path mtu of 1500 is not necessarily correct, but worst case we simply - // undercount, which is still better then not accounting for this overhead at all. - // Note: this really shouldn't be device/path mtu at all, but rather should be - // derived from this particular connection's mss (ie. from gro segment size). - // This would require a much newer kernel with newer ebpf accessors. - // (This is also blindly assuming 12 bytes of tcp timestamp option in tcp header) + // Handling of IPv4 overhead for incoming LRO/GRO packets uint64_t packets = 1; uint64_t L3_bytes = skb->len - l2_header_size; + // I don't trust v->pmtu to not be higher then it should be: it might just + // be device (instead of path/route) mtu and that itself might be wrong... if (L3_bytes > v->pmtu) { - const int tcp4_overhead = sizeof(struct iphdr) + sizeof(struct tcphdr) + 12; - const int mss = v->pmtu - tcp4_overhead; - const uint64_t payload = L3_bytes - tcp4_overhead; - packets = (payload + mss - 1) / mss; - L3_bytes = tcp4_overhead * packets + payload; + // we know it is IPv4 without IP options, and either TCP or UDP + const int hdr_sz = sizeof(struct iphdr) + (is_tcp ? tcph->doff * 4 : sizeof(struct udphdr)); + const uint64_t payload = L3_bytes - hdr_sz; + if (KVER_IS_AT_LEAST(kver, 5, 4, 0)) { + if (skb->gso_segs <= 1) TC_PUNT(ABOVE_IPV4_PMTU); + // ?udp gso frags: gso_size is variable (thus 0), could this fail to trigger? + if (hdr_sz + skb->gso_size > v->pmtu) TC_PUNT(ABOVE_IPV4_PMTU_GSO); + packets = skb->gso_segs; + } else { + // pmtu may be larger then hdr_sz + gso_size, thus we may undercount nr of packets, + // which is still better then not accounting for this overhead at all. + // We cannot punt over-pmtu gso packets without simply disabling offload entirely... + const int mss = v->pmtu - hdr_sz; + packets = (payload + mss - 1) / mss; + } + L3_bytes = hdr_sz * packets + payload; } // Are we past the limit? If so, then abort... @@ -598,39 +640,74 @@ static inline __always_inline int do_forward4(struct __sk_buff* skb, } } -// Full featured (required) implementations for 5.8+ kernels (these are S+ by definition) +// Full featured (required) implementations for 5.10+ kernels (these are S+ by definition) -DEFINE_BPF_PROG_KVER("schedcls/tether_downstream4_rawip$5_8", AID_ROOT, AID_NETWORK_STACK, - sched_cls_tether_downstream4_rawip_5_8, KVER_5_10) +DEFINE_BPF_PROG_KVER("schedcls/tether_downstream4_rawip$5_10", AID_ROOT, AID_NETWORK_STACK, + sched_cls_tether_downstream4_rawip_5_10, KVER_5_10) (struct __sk_buff* skb) { return do_forward4(skb, RAWIP, DOWNSTREAM, UPDATETIME, KVER_5_10); } -DEFINE_BPF_PROG_KVER("schedcls/tether_upstream4_rawip$5_8", AID_ROOT, AID_NETWORK_STACK, - sched_cls_tether_upstream4_rawip_5_8, KVER_5_10) +DEFINE_BPF_PROG_KVER("schedcls/tether_upstream4_rawip$5_10", AID_ROOT, AID_NETWORK_STACK, + sched_cls_tether_upstream4_rawip_5_10, KVER_5_10) (struct __sk_buff* skb) { return do_forward4(skb, RAWIP, UPSTREAM, UPDATETIME, KVER_5_10); } -DEFINE_BPF_PROG_KVER("schedcls/tether_downstream4_ether$5_8", AID_ROOT, AID_NETWORK_STACK, - sched_cls_tether_downstream4_ether_5_8, KVER_5_10) +DEFINE_BPF_PROG_KVER("schedcls/tether_downstream4_ether$5_10", AID_ROOT, AID_NETWORK_STACK, + sched_cls_tether_downstream4_ether_5_10, KVER_5_10) (struct __sk_buff* skb) { return do_forward4(skb, ETHER, DOWNSTREAM, UPDATETIME, KVER_5_10); } -DEFINE_BPF_PROG_KVER("schedcls/tether_upstream4_ether$5_8", AID_ROOT, AID_NETWORK_STACK, - sched_cls_tether_upstream4_ether_5_8, KVER_5_10) +DEFINE_BPF_PROG_KVER("schedcls/tether_upstream4_ether$5_10", AID_ROOT, AID_NETWORK_STACK, + sched_cls_tether_upstream4_ether_5_10, KVER_5_10) (struct __sk_buff* skb) { return do_forward4(skb, ETHER, UPSTREAM, UPDATETIME, KVER_5_10); } -// Full featured (optional) implementations for 4.14-S, 4.19-S & 5.4-S kernels -// (optional, because we need to be able to fallback for 4.14/4.19/5.4 pre-S kernels) +// Full featured (optional) implementations for 5.4-S kernels +// (optional, because we need to be able to fallback for 5.4 pre-S kernels) + +DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE("schedcls/tether_downstream4_rawip$opt54", + AID_ROOT, AID_NETWORK_STACK, + sched_cls_tether_downstream4_rawip_opt54, + KVER_5_4, KVER_5_10) +(struct __sk_buff* skb) { + return do_forward4(skb, RAWIP, DOWNSTREAM, UPDATETIME, KVER_5_4); +} + +DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_rawip$opt54", + AID_ROOT, AID_NETWORK_STACK, + sched_cls_tether_upstream4_rawip_opt54, + KVER_5_4, KVER_5_10) +(struct __sk_buff* skb) { + return do_forward4(skb, RAWIP, UPSTREAM, UPDATETIME, KVER_5_4); +} + +DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE("schedcls/tether_downstream4_ether$opt54", + AID_ROOT, AID_NETWORK_STACK, + sched_cls_tether_downstream4_ether_opt54, + KVER_5_4, KVER_5_10) +(struct __sk_buff* skb) { + return do_forward4(skb, ETHER, DOWNSTREAM, UPDATETIME, KVER_5_4); +} + +DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_ether$opt54", + AID_ROOT, AID_NETWORK_STACK, + sched_cls_tether_upstream4_ether_opt54, + KVER_5_4, KVER_5_10) +(struct __sk_buff* skb) { + return do_forward4(skb, ETHER, UPSTREAM, UPDATETIME, KVER_5_4); +} + +// Full featured (optional) implementations for 4.14-S & 4.19-S kernels +// (optional, because we need to be able to fallback for 4.14/4.19 pre-S kernels) DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE("schedcls/tether_downstream4_rawip$opt", AID_ROOT, AID_NETWORK_STACK, sched_cls_tether_downstream4_rawip_opt, - KVER_4_14, KVER_5_10) + KVER_4_14, KVER_5_4) (struct __sk_buff* skb) { return do_forward4(skb, RAWIP, DOWNSTREAM, UPDATETIME, KVER_4_14); } @@ -638,7 +715,7 @@ DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE("schedcls/tether_downstream4_rawip$opt", DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_rawip$opt", AID_ROOT, AID_NETWORK_STACK, sched_cls_tether_upstream4_rawip_opt, - KVER_4_14, KVER_5_10) + KVER_4_14, KVER_5_4) (struct __sk_buff* skb) { return do_forward4(skb, RAWIP, UPSTREAM, UPDATETIME, KVER_4_14); } @@ -646,7 +723,7 @@ DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_rawip$opt", DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE("schedcls/tether_downstream4_ether$opt", AID_ROOT, AID_NETWORK_STACK, sched_cls_tether_downstream4_ether_opt, - KVER_4_14, KVER_5_10) + KVER_4_14, KVER_5_4) (struct __sk_buff* skb) { return do_forward4(skb, ETHER, DOWNSTREAM, UPDATETIME, KVER_4_14); } @@ -654,7 +731,7 @@ DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE("schedcls/tether_downstream4_ether$opt", DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_ether$opt", AID_ROOT, AID_NETWORK_STACK, sched_cls_tether_upstream4_ether_opt, - KVER_4_14, KVER_5_10) + KVER_4_14, KVER_5_4) (struct __sk_buff* skb) { return do_forward4(skb, ETHER, UPSTREAM, UPDATETIME, KVER_4_14); } @@ -750,74 +827,7 @@ DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_ether$stub", AID_ROOT, AID // ----- XDP Support ----- +// java populates this map, and bpf_existence_test requires it on S+ DEFINE_BPF_MAP_GRW(tether_dev_map, DEVMAP_HASH, uint32_t, uint32_t, 64, AID_NETWORK_STACK) -static inline __always_inline int do_xdp_forward6(__unused struct xdp_md *ctx, - __unused const struct rawip_bool rawip, __unused const struct stream_bool stream) { - return XDP_PASS; -} - -static inline __always_inline int do_xdp_forward4(__unused struct xdp_md *ctx, - __unused const struct rawip_bool rawip, __unused const struct stream_bool stream) { - return XDP_PASS; -} - -static inline __always_inline int do_xdp_forward_ether(struct xdp_md *ctx, - const struct stream_bool stream) { - const void* data = (void*)(long)ctx->data; - const void* data_end = (void*)(long)ctx->data_end; - const struct ethhdr* eth = data; - - // Make sure we actually have an ethernet header - if ((void*)(eth + 1) > data_end) return XDP_PASS; - - if (eth->h_proto == htons(ETH_P_IPV6)) - return do_xdp_forward6(ctx, ETHER, stream); - if (eth->h_proto == htons(ETH_P_IP)) - return do_xdp_forward4(ctx, ETHER, stream); - - // Anything else we don't know how to handle... - return XDP_PASS; -} - -static inline __always_inline int do_xdp_forward_rawip(struct xdp_md *ctx, - const struct stream_bool stream) { - const void* data = (void*)(long)ctx->data; - const void* data_end = (void*)(long)ctx->data_end; - - // The top nibble of both IPv4 and IPv6 headers is the IP version. - if (data_end - data < 1) return XDP_PASS; - const uint8_t v = (*(uint8_t*)data) >> 4; - - if (v == 6) return do_xdp_forward6(ctx, RAWIP, stream); - if (v == 4) return do_xdp_forward4(ctx, RAWIP, stream); - - // Anything else we don't know how to handle... - return XDP_PASS; -} - -#define DEFINE_XDP_PROG(str, func) \ - DEFINE_BPF_PROG_KVER(str, AID_ROOT, AID_NETWORK_STACK, func, KVER_5_10)(struct xdp_md *ctx) - -DEFINE_XDP_PROG("xdp/tether_downstream_ether", - xdp_tether_downstream_ether) { - return do_xdp_forward_ether(ctx, DOWNSTREAM); -} - -DEFINE_XDP_PROG("xdp/tether_downstream_rawip", - xdp_tether_downstream_rawip) { - return do_xdp_forward_rawip(ctx, DOWNSTREAM); -} - -DEFINE_XDP_PROG("xdp/tether_upstream_ether", - xdp_tether_upstream_ether) { - return do_xdp_forward_ether(ctx, UPSTREAM); -} - -DEFINE_XDP_PROG("xdp/tether_upstream_rawip", - xdp_tether_upstream_rawip) { - return do_xdp_forward_rawip(ctx, UPSTREAM); -} - LICENSE("Apache 2.0"); -CRITICAL("Connectivity (Tethering)"); diff --git a/bpf/progs/offload.h b/bpf/progs/offload.h index 1e28f01f7c..a4f72ba4b6 100644 --- a/bpf/progs/offload.h +++ b/bpf/progs/offload.h @@ -39,7 +39,12 @@ ERR(NO_STATS_ENTRY) \ ERR(NO_LIMIT_ENTRY) \ ERR(BELOW_IPV4_MTU) \ + ERR(ABOVE_IPV4_PMTU) \ + ERR(ABOVE_IPV4_PMTU_GSO) \ ERR(BELOW_IPV6_MTU) \ + ERR(ABOVE_IPV6_PMTU) \ + ERR(ABOVE_IPV6_PMTU_GSO) \ + ERR(UNKNOWN_IPV6_GSO) \ ERR(LIMIT_REACHED) \ ERR(CHANGE_HEAD_FAILED) \ ERR(TOO_SHORT) \ diff --git a/bpf/progs/test.c b/bpf/progs/test.c index 4dba6b9878..20a345a524 100644 --- a/bpf/progs/test.c +++ b/bpf/progs/test.c @@ -43,4 +43,3 @@ DEFINE_BPF_PROG_KVER("skfilter/match", AID_ROOT, AID_ROOT, match, KVER_INF) } LICENSE("Apache 2.0"); -CRITICAL("Networking xTS tests"); diff --git a/bpf/syscall_wrappers/include/BpfSyscallWrappers.h b/bpf/syscall_wrappers/include/BpfSyscallWrappers.h index 1d72b7792c..fe7b8c1345 100644 --- a/bpf/syscall_wrappers/include/BpfSyscallWrappers.h +++ b/bpf/syscall_wrappers/include/BpfSyscallWrappers.h @@ -235,15 +235,32 @@ inline int detachSingleProgram(bpf_attach_type type, const borrowed_fd& prog_fd, } // Available in 4.12 and later kernels. -inline int runProgram(const borrowed_fd& prog_fd, const void* data, - const uint32_t data_size) { - return bpf(BPF_PROG_RUN, { - .test = { - .prog_fd = static_cast<__u32>(prog_fd.get()), - .data_size_in = data_size, - .data_in = ptr_to_u64(data), - }, - }); +inline int runProgram(const borrowed_fd &prog_fd, const void *data, + const uint32_t data_size, const void *ctx = nullptr, + const uint32_t ctx_size = 0) { + return bpf(BPF_PROG_RUN, + { + .test = + { + .prog_fd = static_cast<__u32>(prog_fd.get()), + .data_size_in = data_size, + .data_in = ptr_to_u64(data), + .ctx_size_in = ctx_size, + .ctx_in = ptr_to_u64(ctx), + }, + }); +} + +// 4.14+: returns next id > prog_id, or 0 (and sets errno) +inline uint32_t bpfGetNextProgId(const uint32_t prog_id) { + bpf_attr arg = { .start_id = prog_id }; + return bpf(BPF_PROG_GET_NEXT_ID, &arg) ? 0 : arg.next_id; +} + +// 4.14+: returns next id > map_id, or 0 (and sets errno) +inline uint32_t bpfGetNextMapId(const uint32_t map_id) { + bpf_attr arg = { .start_id = map_id }; + return bpf(BPF_MAP_GET_NEXT_ID, &arg) ? 0 : arg.next_id; } // BPF_OBJ_GET_INFO_BY_FD requires 4.14+ kernel @@ -260,7 +277,7 @@ inline int bpfGetFd ## NAME(const borrowed_fd& fd) { \ .info_len = sizeof(info), \ .info = ptr_to_u64(&info), \ }}; \ - int rv = bpf(BPF_OBJ_GET_INFO_BY_FD, attr); \ + int rv = bpf(BPF_OBJ_GET_INFO_BY_FD, &attr); \ if (rv) return rv; \ if (attr.info.info_len < offsetof(bpf_ ## TYPE ## _info, FIELD) + sizeof(info.FIELD)) { \ errno = EOPNOTSUPP; \ diff --git a/bpf/tests/mts/bpf_existence_test.cpp b/bpf/tests/mts/bpf_existence_test.cpp index 4d5f9b54c7..a6c774f83a 100644 --- a/bpf/tests/mts/bpf_existence_test.cpp +++ b/bpf/tests/mts/bpf_existence_test.cpp @@ -37,6 +37,7 @@ using android::bpf::isAtLeastT; using android::bpf::isAtLeastU; using android::bpf::isAtLeastV; using android::bpf::isAtLeast25Q2; +using android::bpf::isAtLeast25Q4; #define PLATFORM "/sys/fs/bpf/" #define TETHERING "/sys/fs/bpf/tethering/" @@ -225,6 +226,8 @@ TEST_F(BpfExistenceTest, TestPrograms) { if (isAtLeast25Q2) ASSERT_TRUE(isAtLeastKernelVersion(5, 4, 0)); DO_EXPECT(isAtLeast25Q2, MAINLINE_FOR_25Q2_PLUS); + if (isAtLeast25Q4) ASSERT_TRUE(isAtLeastKernelVersion(5, 10, 0)); + for (const auto& file : mustExist) { EXPECT_EQ(0, access(file.c_str(), R_OK)) << file << " does not exist"; } diff --git a/clatd/main.c b/clatd/main.c index 67cb5ed16a..8fc9edaf08 100644 --- a/clatd/main.c +++ b/clatd/main.c @@ -152,7 +152,7 @@ void enable_seccomp(void) { // _exit(0) BPF2_SECCOMP_ALLOW_IF_EQUAL(__NR_exit_group), // 94 -#if defined(__aarch64__) +#if defined(__aarch64__) && 0 // disabled // Pixels are aarch64 - if we break clatd functionality on them, // we *will* notice on GoogleGuest WiFi network (which is ipv6 only) BPF_SECCOMP_KILL, diff --git a/common/Android.bp b/common/Android.bp index 5fabf414e5..acd2349bc9 100644 --- a/common/Android.bp +++ b/common/Android.bp @@ -20,11 +20,93 @@ package { default_applicable_licenses: ["Android-Apache-2.0"], } -build = ["FlaggedApi.bp"] +aconfig_declarations { + name: "com.android.net.flags-aconfig", + package: "com.android.net.flags", + exportable: true, + container: "com.android.tethering", + srcs: ["flags.aconfig"], + visibility: ["//packages/modules/Connectivity:__subpackages__"], +} -// This is a placeholder comment to avoid merge conflicts -// as the above target may not exist -// depending on the branch +java_aconfig_library { + name: "com.android.net.flags-aconfig-java", + aconfig_declarations: "com.android.net.flags-aconfig", + defaults: ["framework-minus-apex-aconfig-java-defaults"], + min_sdk_version: "30", + apex_available: [ + "com.android.tethering", + ], +} + +// This library MUST NOT be available to platform. This is because if the +// platform depends on a flag, it will not be able to delete that flag until +// that platform version is no longer supported by mainline. Additionally, +// there is no any safety mechanism that prevents the module from +// deleting the flag by mistake, regressing on previous platform versions. +java_aconfig_library { + name: "com.android.net.flags-aconfig-java-export", + aconfig_declarations: "com.android.net.flags-aconfig", + defaults: ["framework-minus-apex-aconfig-java-defaults"], + min_sdk_version: "30", + apex_available: [ + "com.android.wifi", + ], + mode: "exported", +} + +aconfig_declarations { + name: "com.android.net.thread.flags-aconfig", + package: "com.android.net.thread.flags", + container: "com.android.tethering", + srcs: ["thread_flags.aconfig"], + visibility: ["//packages/modules/Connectivity:__subpackages__"], +} + +java_aconfig_library { + name: "com.android.net.thread.flags-aconfig-java", + aconfig_declarations: "com.android.net.thread.flags-aconfig", + defaults: ["framework-minus-apex-aconfig-java-defaults"], + min_sdk_version: "30", + apex_available: [ + "com.android.tethering", + ], +} + +aconfig_declarations { + name: "nearby_flags", + package: "com.android.nearby.flags", + container: "com.android.tethering", + srcs: ["nearby_flags.aconfig"], + visibility: ["//packages/modules/Connectivity:__subpackages__"], +} + +java_aconfig_library { + name: "com.android.nearby.flags-aconfig-java", + aconfig_declarations: "nearby_flags", + defaults: ["framework-minus-apex-aconfig-java-defaults"], + min_sdk_version: "30", + apex_available: [ + "com.android.tethering", + ], +} + +aconfig_declarations { + name: "com.android.networksecurity.flags-aconfig", + package: "com.android.net.ct.flags", + container: "com.android.tethering", + srcs: ["networksecurity_flags.aconfig"], + visibility: ["//packages/modules/Connectivity:__subpackages__"], +} + +java_aconfig_library { + name: "networksecurity_flags_java_lib", + aconfig_declarations: "com.android.networksecurity.flags-aconfig", + min_sdk_version: "30", + defaults: ["framework-minus-apex-aconfig-java-defaults"], + apex_available: ["com.android.tethering"], + visibility: ["//packages/modules/Connectivity:__subpackages__"], +} // The library requires the final artifact to contain net-utils-device-common-struct-base. java_library { diff --git a/common/FlaggedApi.bp b/common/FlaggedApi.bp deleted file mode 100644 index f89ff9d22c..0000000000 --- a/common/FlaggedApi.bp +++ /dev/null @@ -1,98 +0,0 @@ -// -// Copyright (C) 2024 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. -// - -aconfig_declarations { - name: "com.android.net.flags-aconfig", - package: "com.android.net.flags", - exportable: true, - container: "com.android.tethering", - srcs: ["flags.aconfig"], - visibility: ["//packages/modules/Connectivity:__subpackages__"], -} - -java_aconfig_library { - name: "com.android.net.flags-aconfig-java", - aconfig_declarations: "com.android.net.flags-aconfig", - defaults: ["framework-minus-apex-aconfig-java-defaults"], - min_sdk_version: "30", - apex_available: [ - "com.android.tethering", - ], -} - -java_aconfig_library { - name: "com.android.net.flags-aconfig-java-export", - aconfig_declarations: "com.android.net.flags-aconfig", - defaults: ["framework-minus-apex-aconfig-java-defaults"], - min_sdk_version: "30", - apex_available: [ - "com.android.wifi", - ], - mode: "exported", -} - -aconfig_declarations { - name: "com.android.net.thread.flags-aconfig", - package: "com.android.net.thread.flags", - container: "com.android.tethering", - srcs: ["thread_flags.aconfig"], - visibility: ["//packages/modules/Connectivity:__subpackages__"], -} - -java_aconfig_library { - name: "com.android.net.thread.flags-aconfig-java", - aconfig_declarations: "com.android.net.thread.flags-aconfig", - defaults: ["framework-minus-apex-aconfig-java-defaults"], - min_sdk_version: "30", - apex_available: [ - "com.android.tethering", - ], -} - -aconfig_declarations { - name: "nearby_flags", - package: "com.android.nearby.flags", - container: "com.android.tethering", - srcs: ["nearby_flags.aconfig"], - visibility: ["//packages/modules/Connectivity:__subpackages__"], -} - -java_aconfig_library { - name: "com.android.nearby.flags-aconfig-java", - aconfig_declarations: "nearby_flags", - defaults: ["framework-minus-apex-aconfig-java-defaults"], - min_sdk_version: "30", - apex_available: [ - "com.android.tethering", - ], -} - -aconfig_declarations { - name: "com.android.networksecurity.flags-aconfig", - package: "com.android.net.ct.flags", - container: "com.android.tethering", - srcs: ["networksecurity_flags.aconfig"], - visibility: ["//packages/modules/Connectivity:__subpackages__"], -} - -java_aconfig_library { - name: "networksecurity_flags_java_lib", - aconfig_declarations: "com.android.networksecurity.flags-aconfig", - min_sdk_version: "30", - defaults: ["framework-minus-apex-aconfig-java-defaults"], - apex_available: ["com.android.tethering"], - visibility: ["//packages/modules/Connectivity:__subpackages__"], -} diff --git a/common/flags.aconfig b/common/flags.aconfig index 51b4fc009f..9fcd981be9 100644 --- a/common/flags.aconfig +++ b/common/flags.aconfig @@ -1,8 +1,9 @@ package: "com.android.net.flags" container: "com.android.tethering" -# This file contains aconfig flags for FlaggedAPI annotations -# Flags used from platform code must be in under frameworks +# This file contains aconfig flags for toggling features entirely implemented within the tethering +# module. Flags used from platform code must be in the android.net.platform.flags package in +# frameworks/base. flag { name: "set_data_saver_via_cm" @@ -174,3 +175,27 @@ flag { bug: "388774939" is_fixed_read_only: true } + +flag { + name: "mdns_advertising_improvement" + is_exported: true + namespace: "android_core_networking" + description: "Flag for MDNS advertising quality, reliability and performance improvement" + bug: "373270045" +} + +flag { + name: "ethernet_desktop_config" + namespace: "android_core_networking" + description: "The flag controls support for ethernet configuration tracking and store by MAC address." + bug: "397536853" + is_fixed_read_only: true +} + +flag { + name: "qos_filter_ip_range_matching" + namespace: "android_core_networking" + description: "Flag for API to provide IP address range based QoS socket matching." + bug: "406146808" + is_fixed_read_only: true +} diff --git a/framework-t/api/current.txt b/framework-t/api/current.txt index d66482cb16..610ec28d6f 100644 --- a/framework-t/api/current.txt +++ b/framework-t/api/current.txt @@ -250,6 +250,7 @@ package android.net.nsd { method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void discoverServices(@NonNull String, int, @NonNull android.net.NetworkRequest, @NonNull java.util.concurrent.Executor, @NonNull android.net.nsd.NsdManager.DiscoveryListener); method public void registerService(android.net.nsd.NsdServiceInfo, int, android.net.nsd.NsdManager.RegistrationListener); method public void registerService(@NonNull android.net.nsd.NsdServiceInfo, int, @NonNull java.util.concurrent.Executor, @NonNull android.net.nsd.NsdManager.RegistrationListener); + method @FlaggedApi("com.android.net.flags.mdns_advertising_improvement") public void registerService(@NonNull android.net.nsd.AdvertisingRequest, @NonNull java.util.concurrent.Executor, @NonNull android.net.nsd.NsdManager.RegistrationListener); method public void registerServiceInfoCallback(@NonNull android.net.nsd.NsdServiceInfo, @NonNull java.util.concurrent.Executor, @NonNull android.net.nsd.NsdManager.ServiceInfoCallback); method @Deprecated public void resolveService(android.net.nsd.NsdServiceInfo, android.net.nsd.NsdManager.ResolveListener); method @Deprecated public void resolveService(@NonNull android.net.nsd.NsdServiceInfo, @NonNull java.util.concurrent.Executor, @NonNull android.net.nsd.NsdManager.ResolveListener); diff --git a/framework-t/src/android/net/EthernetConfiguration.java b/framework-t/src/android/net/EthernetConfiguration.java new file mode 100644 index 0000000000..a736bfc506 --- /dev/null +++ b/framework-t/src/android/net/EthernetConfiguration.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2025 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; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Objects; + +/** + * This class represents the static IP configuration data such as IP configuration (static IPv4 + * address, IPv4 gateway, DNS servers, etc.) and network capabilities for a configured ethernet + * network interface. + * + * - Intended Usage and Scope: + * This class implements android.os.Parcelable and resides in the framework JAR. This is + * necessary for being used as an AIDL object to share code with EthernetPortInfo and + * EthernetNetworkUpdateRequest. + * + * - Future Evolution: + * This class is meant to progressively replace EthernetNetworkUpdateRequest in EthernetManager's + * {@code @SystemApi} and be exposed through {@code @SystemApi} getters as well. + * + * @hide + */ +public class EthernetConfiguration implements Parcelable { + /** + * Static IP configuration data (static IPv4 address, IPv4 gateway, DNS servers, etc.) + */ + private final IpConfiguration mIpConfiguration; + + /** + * This is only for the requestable bits of NetworkCapabilities that an external caller can + * pass through a EthernetNetworkUpdateRequest. + */ + private final NetworkCapabilities mNetworkCapabilities; + + public EthernetConfiguration(@NonNull IpConfiguration ipConfiguration, + @NonNull NetworkCapabilities capabilities) { + Objects.requireNonNull(ipConfiguration); + Objects.requireNonNull(capabilities); + mIpConfiguration = ipConfiguration; + mNetworkCapabilities = capabilities; + } + + /** + * Get the IpConfiguration object associated with this EthernetConfiguration. + */ + @NonNull + public IpConfiguration getIpConfiguration() { + return new IpConfiguration(mIpConfiguration); + } + + /** + * Get the NetworkCapabilities object associated with this EthernetConfiguration. + */ + @NonNull + public NetworkCapabilities getNetworkCapabilities() { + return new NetworkCapabilities(mNetworkCapabilities); + } + + @Override + public String toString() { + return "IP configurations: " + mIpConfiguration.toString() + + "Network capabilities: " + mNetworkCapabilities.toString(); + } + + @Override + public boolean equals(@Nullable Object o) { + if (o == this) { + return true; + } + + if (!(o instanceof EthernetConfiguration)) { + return false; + } + + final EthernetConfiguration other = (EthernetConfiguration) o; + return Objects.equals(this.mIpConfiguration, other.mIpConfiguration) + && Objects.equals(this.mNetworkCapabilities, other.mNetworkCapabilities); + } + + @Override + public int hashCode() { + return Objects.hash(mIpConfiguration, mNetworkCapabilities); + } + + /** Implement the Parcelable interface */ + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeParcelable(mIpConfiguration, flags); + dest.writeParcelable(mNetworkCapabilities, flags); + } + + /** Implement the Parcelable interface */ + public static final @NonNull Creator CREATOR = + new Creator() { + @Override + public EthernetConfiguration createFromParcel(Parcel in) { + final IpConfiguration config = in.readParcelable( + IpConfiguration.class.getClassLoader()); + final NetworkCapabilities capabilities = in.readParcelable( + NetworkCapabilities.class.getClassLoader()); + return new EthernetConfiguration(config, capabilities); + } + + @Override + public EthernetConfiguration[] newArray(int size) { + return new EthernetConfiguration[size]; + } + }; + + /** Implement the Parcelable interface */ + @Override + public int describeContents() { + return 0; + } +} diff --git a/framework-t/src/android/net/EthernetNetworkSpecifier.java b/framework-t/src/android/net/EthernetNetworkSpecifier.java index 90c0361821..30878571cc 100644 --- a/framework-t/src/android/net/EthernetNetworkSpecifier.java +++ b/framework-t/src/android/net/EthernetNetworkSpecifier.java @@ -25,57 +25,107 @@ import android.text.TextUtils; import java.util.Objects; /** - * A {@link NetworkSpecifier} used to identify ethernet interfaces. + * A {@link NetworkSpecifier} used to identify ethernet interfaces. Interfaces can be identified + * by matching either interface name and/or MAC address. */ public final class EthernetNetworkSpecifier extends NetworkSpecifier implements Parcelable { - /** - * Name of the network interface. + * Name of the network interface, or null. */ - @NonNull + @Nullable private final String mInterfaceName; /** - * Create a new EthernetNetworkSpecifier. + * MAC address of the network interface, or null. + */ + @Nullable + private final MacAddress mMacAddress; + + /** + * Create a new EthernetNetworkSpecifier with an interface name. * @param interfaceName Name of the ethernet interface the specifier refers to. */ public EthernetNetworkSpecifier(@NonNull String interfaceName) { if (TextUtils.isEmpty(interfaceName)) { - throw new IllegalArgumentException(); + throw new IllegalArgumentException("Interface name cannot be null"); } mInterfaceName = interfaceName; + mMacAddress = null; } /** - * Get the name of the ethernet interface the specifier refers to. + * Create a new EthernetNetworkSpecifier. Either interface name or MAC address needs to be + * valid. + * @param interfaceName Name of the ethernet interface the specifier refers to, or null. + * @param macAddress MAC address of the ethernet interface the specifier refers to, or null. + * @hide + */ + public EthernetNetworkSpecifier(@Nullable String interfaceName, + @Nullable MacAddress macAddress) { + if (TextUtils.isEmpty(interfaceName) && macAddress == null) { + throw new IllegalArgumentException("Either interface name or MAC address needs to be" + + " valid."); + } + mInterfaceName = interfaceName; + mMacAddress = macAddress; + } + + /** + * Get the name of the ethernet interface the specifier refers to, or null. */ @Nullable public String getInterfaceName() { - // This may be null in the future to support specifiers based on data other than the - // interface name. return mInterfaceName; } - /** @hide */ + /** + * Get the MAC address of the ethernet interface the specifier refers to, or null. + * @hide + */ + @Nullable + public MacAddress getMacAddress() { + return mMacAddress; + } + + /** + * Check if this specifier can be satisfied by another specifier by interface name or/and MAC + * address. + * Note that because ethernet Networks always set an EthernetNetworkSpecifier that includes both + * interface name and MAC address, the match is asymmetrical. + * @hide + */ @Override public boolean canBeSatisfiedBy(@Nullable NetworkSpecifier other) { - return equals(other); + if (!(other instanceof EthernetNetworkSpecifier)) return false; + final EthernetNetworkSpecifier rhs = (EthernetNetworkSpecifier) other; + // If interface name is specified, match interface name. + if (mInterfaceName != null && !mInterfaceName.equals(rhs.mInterfaceName)) { + return false; + } + // If MAC address is specified, match MAC address. + if (mMacAddress != null && !mMacAddress.equals(rhs.mMacAddress)) { + return false; + } + return true; } @Override public boolean equals(@Nullable Object o) { if (!(o instanceof EthernetNetworkSpecifier)) return false; - return TextUtils.equals(mInterfaceName, ((EthernetNetworkSpecifier) o).mInterfaceName); + EthernetNetworkSpecifier rhs = (EthernetNetworkSpecifier) o; + return TextUtils.equals(mInterfaceName, rhs.mInterfaceName) + && Objects.equals(mMacAddress, rhs.mMacAddress); } @Override public int hashCode() { - return Objects.hashCode(mInterfaceName); + return Objects.hash(mMacAddress, mInterfaceName); } @Override public String toString() { - return "EthernetNetworkSpecifier (" + mInterfaceName + ")"; + return "EthernetNetworkSpecifier ( interface name: " + mInterfaceName + + ", MAC address: " + Objects.toString(mMacAddress) + ")"; } @Override @@ -86,12 +136,15 @@ public final class EthernetNetworkSpecifier extends NetworkSpecifier implements @Override public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeString(mInterfaceName); + dest.writeParcelable(mMacAddress, flags); } public static final @NonNull Parcelable.Creator CREATOR = new Parcelable.Creator() { public EthernetNetworkSpecifier createFromParcel(Parcel in) { - return new EthernetNetworkSpecifier(in.readString()); + final String ifname = in.readString(); + final MacAddress addr = in.readParcelable(MacAddress.class.getClassLoader()); + return new EthernetNetworkSpecifier(ifname, addr); } public EthernetNetworkSpecifier[] newArray(int size) { return new EthernetNetworkSpecifier[size]; diff --git a/framework-t/src/android/net/EthernetPortInfo.java b/framework-t/src/android/net/EthernetPortInfo.java new file mode 100644 index 0000000000..ba811eeb4c --- /dev/null +++ b/framework-t/src/android/net/EthernetPortInfo.java @@ -0,0 +1,215 @@ +/* + * Copyright (C) 2025 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; + +import android.annotation.Nullable; +import android.net.EthernetManager.InterfaceState; +import android.net.EthernetManager.Role; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; + +import java.util.Objects; + +/** + * A class representing information of an ethernet interface. + * EthernetPortInfo is mainly used in network callbacks. + * @hide + */ +public final class EthernetPortInfo implements Parcelable { + // Role of this ethernet interface, one of {@link EthernetManager.ROLE_SERVER}, + // {@link EthernetManager.ROLE_CLIENT}, or {@link EthernetManager.ROLE_NONE}. + @Role + private final int mRole; + + // State of this ethernet interface, one of {@link EthernetManager.STATE_LINK_UP}, + // {@link EthernetManager.STATE_LINK_DOWN}, or {@link EthernetManager.STATE_ABSENT}. + @InterfaceState + private final int mState; + + // Configuration of this ethernet interface. + private final EthernetConfiguration mConfig; + + // Name of this ethernet interface. + private final String mInterfaceName; + + // MAC address of this ethernet interface. + private final MacAddress mMacAddress; + + // Interface index of this ethernet interface. + private final int mInterfaceIndex; + + public EthernetPortInfo(String interfaceName, MacAddress macAddress, + int interfaceIndex, EthernetConfiguration config, @Role int role, + @InterfaceState int state) { + if (role != EthernetManager.ROLE_CLIENT && role != EthernetManager.ROLE_SERVER + && role != EthernetManager.ROLE_NONE) { + throw new IllegalArgumentException("Interface role is not valid, should be one of " + + "{EthernetManager.ROLE_SERVER, EthernetManager.ROLE_CLIENT, " + + "EthernetManager.ROLE_NONE}."); + } + mRole = role; + + if (state != EthernetManager.STATE_ABSENT && state != EthernetManager.STATE_LINK_DOWN + && state != EthernetManager.STATE_LINK_UP) { + throw new IllegalArgumentException("Interface state is not valid, should be one of " + + "{EthernetManager.STATE_ABSENT, EthernetManager.STATE_LINK_DOWN, " + + "EthernetManager.STATE_LINK_UP}."); + } + mState = state; + + if (TextUtils.isEmpty(interfaceName) || macAddress == null || interfaceIndex <= 0) { + throw new IllegalArgumentException("All of interface name, MAC address and interface" + + " index need to be valid."); + } + mInterfaceName = interfaceName; + mMacAddress = macAddress; + mInterfaceIndex = interfaceIndex; + + Objects.requireNonNull(config); + mConfig = config; + } + + /** + * Returns role of this ethernet interface, one of {@link EthernetManager.ROLE_SERVER}, + * {@link EthernetManager.ROLE_CLIENT}, or {@link EthernetManager.ROLE_NONE}. + */ + @Role + public int getRole() { + return mRole; + } + + + /** + * Returns state of this ethernet interface, one of {@link EthernetManager.STATE_LINK_UP}, + * {@link EthernetManager.STATE_LINK_DOWN}, or {@link EthernetManager.STATE_ABSENT}. + */ + @InterfaceState + public int getState() { + return mState; + } + + /** + * Returns the static configuration regarding meteredness. Note that this meteredness is only + * configured meteredness, while real meteredness state is only retrievable via + * {@link ConnectivityManager}. + */ + public boolean getMeteredConfiguration() { + return mConfig.getNetworkCapabilities() + .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED); + } + + /** Returns the name of this ethernet interface */ + public String getInterfaceName() { + return mInterfaceName; + } + + /** Returns the MAC address of this ethernet interface */ + public MacAddress getMacAddress() { + return mMacAddress; + } + + /** Returns the interface index of this ethernet interface */ + public int getInterfaceIndex() { + return mInterfaceIndex; + } + + /** Returns the configuration of this ethernet interface */ + public EthernetConfiguration getConfiguration() { + return mConfig; + } + + /** Returns a NetworkSpecifier that is guaranteed to match with this ethernet interface. */ + public NetworkSpecifier getNetworkSpecifier() { + return new EthernetNetworkSpecifier(mInterfaceName, mMacAddress); + } + + @Override + public String toString() { + return "Role:" + mRole + + ", State:" + mState + + ", Configurations: " + mConfig + + ", Interface name:" + mInterfaceName + + ", MAC address:" + Objects.toString(mMacAddress) + + ", Interface index:" + mInterfaceIndex; + } + + @Override + public int hashCode() { + return Objects.hash(mRole, mState, mConfig, + mInterfaceName, mMacAddress, mInterfaceIndex); + } + + @Override + public boolean equals(@Nullable Object o) { + if (o == this) { + return true; + } + + if (!(o instanceof EthernetPortInfo)) { + return false; + } + + final EthernetPortInfo other = (EthernetPortInfo) o; + return (this.mRole == other.mRole) + && (this.mState == other.mState) + && Objects.equals(this.mConfig, other.mConfig) + && TextUtils.equals(this.mInterfaceName, other.mInterfaceName) + && Objects.equals(this.mMacAddress, other.mMacAddress) + && (this.mInterfaceIndex == other.mInterfaceIndex); + } + + /** Implement the Parcelable interface */ + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mRole); + dest.writeInt(mState); + dest.writeParcelable(mConfig, flags); + dest.writeString(mInterfaceName); + dest.writeParcelable(mMacAddress, flags); + dest.writeInt(mInterfaceIndex); + } + + /** Implement the Parcelable interface */ + public static final Creator CREATOR = + new Creator() { + @Override + public EthernetPortInfo createFromParcel(Parcel in) { + int role = in.readInt(); + int state = in.readInt(); + EthernetConfiguration config = in.readParcelable( + EthernetConfiguration.class.getClassLoader()); + String ifname = in.readString(); + MacAddress hwaddr = in.readParcelable( + MacAddress.class.getClassLoader()); + int index = in.readInt(); + return new EthernetPortInfo( + ifname, hwaddr, index, config, role, state); + } + + @Override + public EthernetPortInfo[] newArray(int size) { + return new EthernetPortInfo[size]; + } + }; + + /** Implement the Parcelable interface */ + @Override + public int describeContents() { + return 0; + } +} diff --git a/framework-t/src/android/net/NetworkIdentity.java b/framework-t/src/android/net/NetworkIdentity.java index 947a0925c6..a48032b6b4 100644 --- a/framework-t/src/android/net/NetworkIdentity.java +++ b/framework-t/src/android/net/NetworkIdentity.java @@ -22,6 +22,9 @@ import static android.net.ConnectivityManager.TYPE_WIFI; import static android.net.NetworkTemplate.NETWORK_TYPE_ALL; import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; +import static com.android.net.module.util.CollectionUtils.intArrayToSet; +import static com.android.net.module.util.NetworkCapabilitiesUtils.TYPE_TEST; + import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -43,6 +46,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Objects; +import java.util.Set; /** * Network definition that includes strong identity. Analogous to combining @@ -86,9 +90,6 @@ public class NetworkIdentity { private static final long SUPPORTED_OEM_MANAGED_TYPES = OEM_PAID | OEM_PRIVATE; - // Need to be synchronized with ConnectivityManager. - // TODO: Use {@code ConnectivityManager#*} when visible. - static final int TYPE_TEST = 18; private static final int MAX_NETWORK_TYPE = TYPE_TEST; private static final int MIN_NETWORK_TYPE = TYPE_MOBILE; @@ -101,11 +102,13 @@ public class NetworkIdentity { final boolean mMetered; final boolean mDefaultNetwork; final int mOemManaged; + final long mTransportTypesBits; /** @hide */ public NetworkIdentity( int type, int ratType, @Nullable String subscriberId, @Nullable String wifiNetworkKey, - boolean roaming, boolean metered, boolean defaultNetwork, int oemManaged, int subId) { + boolean roaming, boolean metered, boolean defaultNetwork, int oemManaged, int subId, + long transportTypesBits) { mType = type; mRatType = ratType; mSubscriberId = subscriberId; @@ -115,25 +118,26 @@ public class NetworkIdentity { mDefaultNetwork = defaultNetwork; mOemManaged = oemManaged; mSubId = subId; + mTransportTypesBits = transportTypesBits; } @Override public int hashCode() { return Objects.hash(mType, mRatType, mSubscriberId, mWifiNetworkKey, mRoaming, mMetered, - mDefaultNetwork, mOemManaged, mSubId); + mDefaultNetwork, mOemManaged, mSubId, mTransportTypesBits); } @Override public boolean equals(@Nullable Object obj) { - if (obj instanceof NetworkIdentity) { - final NetworkIdentity ident = (NetworkIdentity) obj; + if (obj instanceof NetworkIdentity ident) { return mType == ident.mType && mRatType == ident.mRatType && mRoaming == ident.mRoaming && Objects.equals(mSubscriberId, ident.mSubscriberId) && Objects.equals(mWifiNetworkKey, ident.mWifiNetworkKey) && mMetered == ident.mMetered && mDefaultNetwork == ident.mDefaultNetwork && mOemManaged == ident.mOemManaged - && mSubId == ident.mSubId; + && mSubId == ident.mSubId + && mTransportTypesBits == ident.mTransportTypesBits; } return false; } @@ -162,6 +166,8 @@ public class NetworkIdentity { builder.append(", defaultNetwork=").append(mDefaultNetwork); builder.append(", oemManaged=").append(getOemManagedNames(mOemManaged)); builder.append(", subId=").append(mSubId); + // TODO: Print human friendly names instead. + builder.append(", transports=").append(getTransportTypes()); return builder.append("}").toString(); } @@ -204,7 +210,7 @@ public class NetworkIdentity { proto.write(NetworkIdentityProto.METERED, mMetered); proto.write(NetworkIdentityProto.DEFAULT_NETWORK, mDefaultNetwork); proto.write(NetworkIdentityProto.OEM_MANAGED_NETWORK, mOemManaged); - + proto.write(NetworkIdentityProto.TRANSPORT_TYPES, mTransportTypesBits); proto.end(start); } @@ -273,6 +279,14 @@ public class NetworkIdentity { return mSubId; } + /** + * Get transport types + * @hide + */ + public Set getTransportTypes() { + return intArrayToSet(BitUtils.unpackBits(mTransportTypesBits)); + } + /** * Assemble a {@link NetworkIdentity} from the passed arguments. * @@ -322,6 +336,9 @@ public class NetworkIdentity { public static int compare(@NonNull NetworkIdentity left, @NonNull NetworkIdentity right) { Objects.requireNonNull(right); int res = Integer.compare(left.mType, right.mType); + if (res == 0) { + res = Long.compare(left.mTransportTypesBits, right.mTransportTypesBits); + } if (res == 0) { res = Integer.compare(left.mRatType, right.mRatType); } @@ -362,6 +379,7 @@ public class NetworkIdentity { private boolean mDefaultNetwork; private int mOemManaged; private int mSubId; + private long mTransportTypes; /** * Creates a new Builder. @@ -377,6 +395,7 @@ public class NetworkIdentity { mDefaultNetwork = false; mOemManaged = NetworkTemplate.OEM_MANAGED_NO; mSubId = INVALID_SUBSCRIPTION_ID; + mTransportTypes = 0L; } /** @@ -389,6 +408,7 @@ public class NetworkIdentity { * - metered * - oemManaged * - wifiNetworkKey + * - transportTypes * * @param snapshot The target {@link NetworkStateSnapshot} object. * @return The builder object. @@ -397,6 +417,7 @@ public class NetworkIdentity { @NonNull public Builder setNetworkStateSnapshot(@NonNull NetworkStateSnapshot snapshot) { setType(snapshot.getLegacyType()); + setTransportTypes(intArrayToSet(snapshot.getNetworkCapabilities().getTransportTypes())); setSubscriberId(snapshot.getSubscriberId()); setRoaming(!snapshot.getNetworkCapabilities().hasCapability( @@ -406,6 +427,7 @@ public class NetworkIdentity { setOemManaged(getOemBitfield(snapshot.getNetworkCapabilities())); + // TODO: Use the transports instead. if (mType == TYPE_WIFI) { final NetworkCapabilities nc = snapshot.getNetworkCapabilities(); final TransportInfo transportInfo = nc.getTransportInfo(); @@ -580,6 +602,17 @@ public class NetworkIdentity { return this; } + /** + * Set the transport types. + * + * @hide + */ + @NonNull + public Builder setTransportTypes(Set transportTypes) { + mTransportTypes = BitUtils.packBits(CollectionUtils.toIntArray(transportTypes)); + return this; + } + private void ensureValidParameters() { // Assert non-mobile network cannot have a ratType. if (mType != TYPE_MOBILE && mRatType != NetworkTemplate.NETWORK_TYPE_ALL) { @@ -602,7 +635,7 @@ public class NetworkIdentity { public NetworkIdentity build() { ensureValidParameters(); return new NetworkIdentity(mType, mRatType, mSubscriberId, mWifiNetworkKey, - mRoaming, mMetered, mDefaultNetwork, mOemManaged, mSubId); + mRoaming, mMetered, mDefaultNetwork, mOemManaged, mSubId, mTransportTypes); } } } diff --git a/framework-t/src/android/net/NetworkIdentitySet.java b/framework-t/src/android/net/NetworkIdentitySet.java index d88408eef0..a0b3d3dec2 100644 --- a/framework-t/src/android/net/NetworkIdentitySet.java +++ b/framework-t/src/android/net/NetworkIdentitySet.java @@ -19,10 +19,17 @@ package android.net; import static android.net.ConnectivityManager.TYPE_MOBILE; import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; +import static com.android.net.module.util.CollectionUtils.toIntArray; +import static com.android.net.module.util.NetworkCapabilitiesUtils.deduceTransportTypeForLegacyNetworkType; + import android.annotation.NonNull; import android.service.NetworkIdentitySetProto; import android.util.proto.ProtoOutputStream; +import androidx.annotation.VisibleForTesting; + +import com.android.net.module.util.BitUtils; + import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; @@ -44,6 +51,7 @@ public class NetworkIdentitySet extends HashSet { private static final int VERSION_ADD_DEFAULT_NETWORK = 5; private static final int VERSION_ADD_OEM_MANAGED_NETWORK = 6; private static final int VERSION_ADD_SUB_ID = 7; + private static final int VERSION_ADD_TRANSPORT_TYPES = 8; /** * Construct a {@link NetworkIdentitySet} object. @@ -112,8 +120,23 @@ public class NetworkIdentitySet extends HashSet { subId = INVALID_SUBSCRIPTION_ID; } + final long transportTypesBits; + if (version >= VERSION_ADD_TRANSPORT_TYPES) { + transportTypesBits = in.readLong(); + } else { + final int deducedTransport = deduceTransportTypeForLegacyNetworkType(type); + if (deducedTransport != -1) { + transportTypesBits = BitUtils.packBits(new int[]{ deducedTransport }); + } else { + // Ignore legacy or unknown types. This is fine since this means + // the legacy data cannot be queried by transport types, + // which is not even supported now. + transportTypesBits = 0; + } + } + add(new NetworkIdentity(type, ratType, subscriberId, networkId, roaming, metered, - defaultNetwork, oemNetCapabilities, subId)); + defaultNetwork, oemNetCapabilities, subId, transportTypesBits)); } } @@ -122,7 +145,7 @@ public class NetworkIdentitySet extends HashSet { * @hide */ public void writeToStream(DataOutput out) throws IOException { - out.writeInt(VERSION_ADD_SUB_ID); + out.writeInt(VERSION_ADD_TRANSPORT_TYPES); out.writeInt(size()); for (NetworkIdentity ident : this) { out.writeInt(ident.getType()); @@ -134,6 +157,7 @@ public class NetworkIdentitySet extends HashSet { out.writeBoolean(ident.isDefaultNetwork()); out.writeInt(ident.getOemManaged()); out.writeInt(ident.getSubId()); + out.writeLong(BitUtils.packBits(toIntArray(ident.getTransportTypes()))); } } @@ -186,7 +210,13 @@ public class NetworkIdentitySet extends HashSet { return true; } - private static void writeOptionalString(DataOutput out, String value) throws IOException { + /** + * Writes an optional string to a {@link DataOutput} stream. + * + * @hide + */ + @VisibleForTesting + public static void writeOptionalString(DataOutput out, String value) throws IOException { if (value != null) { out.writeByte(1); out.writeUTF(value); diff --git a/framework-t/src/android/net/NetworkStats.java b/framework-t/src/android/net/NetworkStats.java index a2c4fc374e..a50352e463 100644 --- a/framework-t/src/android/net/NetworkStats.java +++ b/framework-t/src/android/net/NetworkStats.java @@ -44,6 +44,7 @@ import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Arrays; +import java.util.BitSet; import java.util.HashSet; import java.util.Iterator; import java.util.List; @@ -494,6 +495,21 @@ public final class NetworkStats implements Parcelable, Iterable (limitUid == UID_ALL || limitUid == e.uid) - && (limitTag == TAG_ALL || limitTag == e.tag) - && (limitIfaces == INTERFACES_ALL - || CollectionUtils.contains(limitIfaces, e.iface))); + filter(e -> e.matches(limitUid, limitIfaces, limitTag)); } /** @@ -1410,6 +1423,39 @@ public final class NetworkStats implements Parcelable, Iterable subscriberIds) { - Objects.requireNonNull(subscriberIds); + requireNonNull(subscriberIds); mMatchSubscriberIds.clear(); mMatchSubscriberIds.addAll(subscriberIds); return this; @@ -1089,7 +1176,7 @@ public final class NetworkTemplate implements Parcelable { */ @NonNull public Builder setWifiNetworkKeys(@NonNull Set wifiNetworkKeys) { - Objects.requireNonNull(wifiNetworkKeys); + requireNonNull(wifiNetworkKeys); for (String key : wifiNetworkKeys) { if (key == null) { throw new IllegalArgumentException("Null is not a valid key"); @@ -1151,6 +1238,44 @@ public final class NetworkTemplate implements Parcelable { return this; } + /** + * Sets the transport type filter for this template. + * + * This is only valid when the template is constructed by using + * {@link NetworkTemplate.Builder}. + * + * @param transportType {@code NetworkCapabilities#TRANSPORT_*} constant. + * @return This {@code Builder} for chaining. + @hide + */ + @NonNull + public Builder setTransportType(int transportType) { + if (mMatchRule != MATCH_ALL) { + throw new IllegalArgumentException("setTransportType is only supported for builder" + + " built by default constructor."); + } + return setTransportTypes(new int[] {transportType}); + } + + /** + * Sets the transport types filter for this template. + * + * - If the array is empty, matches any transport type (wildcard). + * - If not empty, matches networks possessing ALL specified transport types. + * For example, a set with {@code TRANSPORT_WIFI} and {@code TRANSPORT_VPN} + * requires a network to have both. + * + * @param transportTypes The array of {@code NetworkCapabilities#TRANSPORT_*} constants. + * An empty array acts as a wildcard for all transports. + * @return This {@code Builder} for chaining. + * @hide + */ + @NonNull + private Builder setTransportTypes(int[] transportTypes) { + mTransportTypesBits = BitUtils.packBits(transportTypes); + return this; + } + /** * Set the OEM managed filter. * @@ -1178,6 +1303,7 @@ public final class NetworkTemplate implements Parcelable { private void assertRequestableParameters() { validateWifiNetworkKeys(); + validateTransportTypes(); // TODO: Check all the input are legitimate. } @@ -1190,6 +1316,22 @@ public final class NetworkTemplate implements Parcelable { } } + /** + * Validates that: + * 1. For MATCH_ALL, exactly one transport type is specified. + * 2. For all other match rules, no transport type should be specified. + */ + private void validateTransportTypes() { + final int transportCount = Long.bitCount(mTransportTypesBits); + if (mMatchRule != MATCH_ALL && transportCount != 0) { + throw new IllegalArgumentException( + "Transports type can't be set with a match rule."); + } else if (mMatchRule == MATCH_ALL && transportCount != 1) { + throw new IllegalArgumentException( + "Transport type not set (use Builder#setTransportType)."); + } + } + /** * Builds the instance of the NetworkTemplate. * @@ -1201,7 +1343,7 @@ public final class NetworkTemplate implements Parcelable { return new NetworkTemplate(mMatchRule, mMatchSubscriberIds.toArray(new String[0]), mMatchWifiNetworkKeys.toArray(new String[0]), mMetered, mRoaming, - mDefaultNetwork, mRatType, mOemManaged); + mDefaultNetwork, mRatType, mOemManaged, mTransportTypesBits); } } } diff --git a/framework-t/src/android/net/nsd/AdvertisingRequest.java b/framework-t/src/android/net/nsd/AdvertisingRequest.java index a62df65901..c2e7a0c7c9 100644 --- a/framework-t/src/android/net/nsd/AdvertisingRequest.java +++ b/framework-t/src/android/net/nsd/AdvertisingRequest.java @@ -52,6 +52,19 @@ public final class AdvertisingRequest implements Parcelable { */ public static final long FLAG_SKIP_PROBING = 1 << 1; + /** + * Don't announce the subtype of the service. + * @hide + */ + public static final long FLAG_SKIP_SUBTYPE_ANNOUNCEMENTS = 1L << 63; + + /** + * Only for hardware offload, if this flag is set, NsdManager will only send callbacks to + * registered OffloadEngine, and not reply to mDNS queries by itself. + * @hide + */ + public static final long FLAG_OFFLOAD_ONLY = 1L << 62; + /** @hide */ @Retention(RetentionPolicy.SOURCE) @LongDef(flag = true, prefix = {"FLAG_"}, value = { diff --git a/framework-t/src/android/net/nsd/NsdManager.java b/framework-t/src/android/net/nsd/NsdManager.java index 426a92d2bf..d5fa1deabe 100644 --- a/framework-t/src/android/net/nsd/NsdManager.java +++ b/framework-t/src/android/net/nsd/NsdManager.java @@ -1353,9 +1353,8 @@ public final class NsdManager { * @param listener The listener notifies of a successful registration and is used to * unregister this service through a call on {@link #unregisterService}. Cannot be null. * - * @hide */ -// @FlaggedApi(Flags.ADVERTISE_REQUEST_API) + @FlaggedApi(Flags.FLAG_MDNS_ADVERTISING_IMPROVEMENT) public void registerService(@NonNull AdvertisingRequest advertisingRequest, @NonNull Executor executor, @NonNull RegistrationListener listener) { diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt index bef29a4283..e631e5d9a4 100644 --- a/framework/api/system-current.txt +++ b/framework/api/system-current.txt @@ -433,8 +433,10 @@ package android.net { public abstract class QosFilter { method @NonNull public abstract android.net.Network getNetwork(); method public abstract boolean matchesLocalAddress(@NonNull java.net.InetAddress, int, int); + method @FlaggedApi("com.android.net.flags.qos_filter_ip_range_matching") public abstract boolean matchesLocalPrefix(@NonNull android.net.IpPrefix, int, int); method public boolean matchesProtocol(int); method public abstract boolean matchesRemoteAddress(@NonNull java.net.InetAddress, int, int); + method @FlaggedApi("com.android.net.flags.qos_filter_ip_range_matching") public abstract boolean matchesRemotePrefix(@NonNull android.net.IpPrefix, int, int); } public final class QosSession implements android.os.Parcelable { diff --git a/framework/proto/netstats.proto b/framework/proto/netstats.proto index 3c9f73cc57..24e8714dcf 100644 --- a/framework/proto/netstats.proto +++ b/framework/proto/netstats.proto @@ -64,6 +64,8 @@ message NetworkIdentityProto { optional bool default_network = 6; optional int32 oem_managed_network = 7; + + optional int64 transport_types = 8; } // Corresponds to NetworkStatsRecorder. diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index f8a1293a34..3a7d070670 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -6856,4 +6856,68 @@ public class ConnectivityManager { throw e.rethrowFromSystemServer(); } } + + /** + * Register QUIC UDP socket and UDP payload that can close QUIC connection. + * + * When the app loses network access (e.g. due to freezer or firewall chain), + * ConnectivityService 1)destroys the registered UDP socket by sending a SOCK_DESTROY netlink + * message and 2)sends the registered UDP payload to the server. + * This prevents unnecessary modem wakeups caused by packets from the server after the apps + * lose network access. + * + * Additionally, to close QUIC connection for apps that crash or are killed, ConnectivityService + * sends the registered UDP payload when it receives a socket destroy diag message for the + * registered socket. + * + * If apps call this method, they must call {@link #unregisterQuicConnectionClosePayload} before + * they close the QUIC connection and the socket. + * Otherwise, the registered UDP payload will be sent to the server, even if the QUIC connection + * was already properly closed. This is because ConnectivityService cannot distinguish whether + * the socket was closed due to the app being killed or because the app forgets to call + * {@link #unregisterQuicConnectionClosePayload}. + * + * The registered payload will be sent using the source and destination addresses and ports of + * the connected UDP socket provided. So, the app must not reuse the same socket to reconnect + * to a different server, even if first calls unregisterQuicConnectionClosePayload. This is + * because this method operates asynchronously. + * + * If the number of registered socket and payload hits the limit, ConnectivityService silently + * ignores new registration. ConnectivityService cannot know about this synchronously, but the + * caller couldn't really do anything with this information anyway. + * TODO: Consider adding a result listener if this API becomes public. + * + * @param pfd The {@link ParcelFileDescriptor} for the connected UDP socket. + * The registered payload will be sent using the source and destination addresses and + * ports of this connected UDP socket. + * @param payload The UDP payload that can close QUIC connection. + * + * @hide + */ + public void registerQuicConnectionClosePayload(final ParcelFileDescriptor pfd, + final byte[] payload) { + try { + mService.registerQuicConnectionClosePayload(pfd, payload); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Unregister the QUIC socket previously registered by + * {@link #registerQuicConnectionClosePayload} + * + * This method is a no-op if the provided UDP socket is not registered. + * + * @param pfd The {@link ParcelFileDescriptor} for the UDP socket. + * + * @hide + */ + public void unregisterQuicConnectionClosePayload(final ParcelFileDescriptor pfd) { + try { + mService.unregisterQuicConnectionClosePayload(pfd); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/framework/src/android/net/IConnectivityManager.aidl b/framework/src/android/net/IConnectivityManager.aidl index a270684c40..0146e729a7 100644 --- a/framework/src/android/net/IConnectivityManager.aidl +++ b/framework/src/android/net/IConnectivityManager.aidl @@ -266,4 +266,7 @@ interface IConnectivityManager long getEnabledConnectivityManagerFeatures(); boolean isConnectivityServiceFeatureEnabledForTesting(String featureFlag); + + oneway void registerQuicConnectionClosePayload(in ParcelFileDescriptor pfd, in byte[] payload); + oneway void unregisterQuicConnectionClosePayload(in ParcelFileDescriptor pfd); } diff --git a/framework/src/android/net/Network.java b/framework/src/android/net/Network.java index 53f171a017..bc8071277a 100644 --- a/framework/src/android/net/Network.java +++ b/framework/src/android/net/Network.java @@ -29,6 +29,7 @@ import android.system.Os; import android.system.OsConstants; import com.android.internal.annotations.GuardedBy; +import com.android.modules.utils.build.SdkLevel; import libcore.io.IoUtils; import libcore.net.http.Dns; @@ -102,12 +103,7 @@ public class Network implements Parcelable { // and permission checks are made by netd (attempts to bypass Private DNS // without appropriate permission are silently turned into vanilla DNS // requests). This only affects DNS queries made using this network object. - // - // It it not parceled to receivers because (a) it can be set or cleared at - // anytime and (b) receivers should be explicit about attempts to bypass - // Private DNS so that the intent of the code is easily determined and - // code search audits are possible. - private final transient boolean mPrivateDnsBypass; + private final boolean mPrivateDnsBypass; /** * @hide @@ -170,6 +166,18 @@ public class Network implements Parcelable { return new Network(netId, true); } + /** + * Obtain a Network object that respects the Private DNS settings when attempting + * to use {@link #getAllByName(String)}/{@link #getByName(String)} methods on the given + * instance for hostname resolution. + * + * @hide + */ + // TODO : @SystemApi if this becomes useful. + public @NonNull Network getPrivateDnsNonBypassingCopy() { + return new Network(netId, false); + } + /** * Get the unique id of the network. * @@ -497,31 +505,35 @@ public class Network implements Parcelable { } public void writeToParcel(Parcel dest, int flags) { dest.writeInt(netId); + if (SdkLevel.isAtLeastB()) { + dest.writeBoolean(mPrivateDnsBypass); + } else { + dest.writeBoolean(false); + } } public static final @android.annotation.NonNull Creator CREATOR = - new Creator() { - public Network createFromParcel(Parcel in) { - int netId = in.readInt(); + new Creator<>() { + public Network createFromParcel(Parcel in) { + final int netId = in.readInt(); + final boolean bypass = in.readBoolean(); + return new Network(netId, bypass); + } - return new Network(netId); - } - - public Network[] newArray(int size) { - return new Network[size]; - } - }; + public Network[] newArray(int size) { + return new Network[size]; + } + }; @Override public boolean equals(@Nullable Object obj) { - if (!(obj instanceof Network)) return false; - Network other = (Network)obj; + if (!(obj instanceof Network other)) return false; return this.netId == other.netId; } @Override public int hashCode() { - return netId * 11; + return netId; } @Override diff --git a/framework/src/android/net/QosFilter.java b/framework/src/android/net/QosFilter.java index a731b23c10..c28671085f 100644 --- a/framework/src/android/net/QosFilter.java +++ b/framework/src/android/net/QosFilter.java @@ -16,9 +16,12 @@ package android.net; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.SystemApi; +import com.android.net.flags.Flags; + import java.net.InetAddress; /** @@ -93,6 +96,38 @@ public abstract class QosFilter { public abstract boolean matchesRemoteAddress(@NonNull InetAddress address, int startPort, int endPort); + /** + * Determines whether or not the parameters will be matched with prefix length, source address + * and port. + * + * @param ipPrefix the UE side IP address and prefix length included in IP packet filter set of + * a QoS flow assigned on {@link Network}. + * @param startPort the start of UE side port range included in IP packet filter set of a QoS + * flow assigned on {@link Network}. + * @param endPort the end of UE side port range included in IP packet filter set of a QoS flow + * assigned on {@link Network}. + * @return whether the parameters match the UE side address and port of the filter + */ + @FlaggedApi(Flags.FLAG_QOS_FILTER_IP_RANGE_MATCHING) + public abstract boolean matchesLocalPrefix( + @NonNull IpPrefix ipPrefix, int startPort, int endPort); + + /** + * Determines whether or not the parameters will be matched with prefix length, remote address + * and port. + * + * @param ipPrefix the remote IP address and prefix length included in IP packet filter set of a + * QoS flow assigned on {@link Network}. + * @param startPort the start of remote port range included in IP packet filter set of a QoS + * flow assigned on {@link Network}. + * @param endPort the end of the remote range included in IP packet filter set of a QoS flow + * assigned on {@link Network}. + * @return whether the parameters match the remote address and port of the filter + */ + @FlaggedApi(Flags.FLAG_QOS_FILTER_IP_RANGE_MATCHING) + public abstract boolean matchesRemotePrefix( + @NonNull IpPrefix ipPrefix, int startPort, int endPort); + /** * Determines whether or not the parameter will be matched with this filter. * diff --git a/framework/src/android/net/QosSocketFilter.java b/framework/src/android/net/QosSocketFilter.java index 5ceeb67127..ecc2af1446 100644 --- a/framework/src/android/net/QosSocketFilter.java +++ b/framework/src/android/net/QosSocketFilter.java @@ -36,6 +36,7 @@ import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import java.io.FileDescriptor; +import java.net.Inet4Address; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Socket; @@ -179,10 +180,11 @@ public class QosSocketFilter extends QosFilter { @Override public boolean matchesLocalAddress(@NonNull final InetAddress address, final int startPort, final int endPort) { - if (mQosSocketInfo.getLocalSocketAddress() == null) { - return false; - } - return matchesAddress(mQosSocketInfo.getLocalSocketAddress(), address, startPort, + return matchesLocalPrefix( + address instanceof Inet4Address + ? new IpPrefix(address, 32) + : new IpPrefix(address, 128), + startPort, endPort); } @@ -192,11 +194,33 @@ public class QosSocketFilter extends QosFilter { @Override public boolean matchesRemoteAddress(@NonNull final InetAddress address, final int startPort, final int endPort) { + return matchesRemotePrefix( + address instanceof Inet4Address + ? new IpPrefix(address, 32) + : new IpPrefix(address, 128), + startPort, + endPort); + } + + /** {@inheritDoc} */ + @Override + public boolean matchesLocalPrefix( + @NonNull final IpPrefix ipPrefix, final int startPort, final int endPort) { + if (mQosSocketInfo.getLocalSocketAddress() == null) { + return false; + } + return matchesAddress(mQosSocketInfo.getLocalSocketAddress(), ipPrefix, startPort, endPort); + } + + /** {@inheritDoc} */ + @Override + public boolean matchesRemotePrefix( + @NonNull final IpPrefix ipPrefix, final int startPort, final int endPort) { if (mQosSocketInfo.getRemoteSocketAddress() == null) { return false; } - return matchesAddress(mQosSocketInfo.getRemoteSocketAddress(), address, startPort, - endPort); + return matchesAddress( + mQosSocketInfo.getRemoteSocketAddress(), ipPrefix, startPort, endPort); } /** @@ -212,25 +236,27 @@ public class QosSocketFilter extends QosFilter { } /** - * Called from {@link QosSocketFilter#matchesLocalAddress(InetAddress, int, int)} - * and {@link QosSocketFilter#matchesRemoteAddress(InetAddress, int, int)} with the - * filterSocketAddress coming from {@link QosSocketInfo#getLocalSocketAddress()}. - *

- * This method exists for testing purposes since {@link QosSocketInfo} couldn't be mocked - * due to being final. + * Called from {@link QosSocketFilter#matchesLocalPrefix(IpPrefix, int, int)} and {@link + * QosSocketFilter#matchesRemotePrefix(IpPrefix, int, int)} with the filterSocketAddress coming + * from {@link QosSocketInfo#getLocalSocketAddress()}. + * + *

This method exists for testing purposes since {@link QosSocketInfo} couldn't be mocked due + * to being final. * * @param filterSocketAddress the socket address of the filter - * @param address the address to compare the filterSocketAddressWith + * @param ipPrefix the IP address with prefix length to compare the filterSocketAddressWith * @param startPort the start of the port range to check * @param endPort the end of the port range to check */ @VisibleForTesting - public static boolean matchesAddress(@NonNull final InetSocketAddress filterSocketAddress, - @NonNull final InetAddress address, - final int startPort, final int endPort) { + public static boolean matchesAddress( + @NonNull final InetSocketAddress filterSocketAddress, + @NonNull final IpPrefix ipPrefix, + final int startPort, + final int endPort) { return startPort <= filterSocketAddress.getPort() && endPort >= filterSocketAddress.getPort() - && (address.isAnyLocalAddress() - || filterSocketAddress.getAddress().equals(address)); + && (ipPrefix.getAddress().isAnyLocalAddress() + || ipPrefix.contains(filterSocketAddress.getAddress())); } } diff --git a/framework/src/android/net/connectivity/ConnectivityCompatChanges.java b/framework/src/android/net/connectivity/ConnectivityCompatChanges.java index 27d605acf9..c3279306f9 100644 --- a/framework/src/android/net/connectivity/ConnectivityCompatChanges.java +++ b/framework/src/android/net/connectivity/ConnectivityCompatChanges.java @@ -111,6 +111,8 @@ public final class ConnectivityCompatChanges { * {@link android.net.ConnectivityManager#getActiveNetwork()} always returns null. * {@link android.net.ConnectivityManager.NetworkCallback#onBlockedStatusChanged()} is always * called with blocked=true. + * {@link android.net.ConnectivityManager#requestNetwork} can not bring up metered network if + * the uid is restricted from using metered networks. *

* For backwards compatibility, apps running on older releases, or targeting older SDK levels, * network access from apps without {@link android.Manifest.permission#INTERNET} permission is diff --git a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyDownloader.java b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyDownloader.java index 41b58fac59..e8e2a98937 100644 --- a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyDownloader.java +++ b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyDownloader.java @@ -32,8 +32,9 @@ import com.android.server.net.ct.DownloadHelper.DownloadStatus; import java.io.IOException; import java.io.InputStream; -import java.security.GeneralSecurityException; import java.util.Collection; +import java.util.HashMap; +import java.util.Map; import java.util.Optional; /** Helper class to download certificate transparency log files. */ @@ -43,22 +44,21 @@ class CertificateTransparencyDownloader extends BroadcastReceiver { private static final String TAG = "CertificateTransparencyDownloader"; private final Context mContext; - private final DataStore mDataStore; private final DownloadHelper mDownloadHelper; private final SignatureVerifier mSignatureVerifier; private final CertificateTransparencyLogger mLogger; private final Collection mCompatVersions; + private final Map mDownloadIds = new HashMap<>(); + CertificateTransparencyDownloader( Context context, - DataStore dataStore, DownloadHelper downloadHelper, SignatureVerifier signatureVerifier, CertificateTransparencyLogger logger, Collection compatVersions) { mContext = context; mSignatureVerifier = signatureVerifier; - mDataStore = dataStore; mDownloadHelper = downloadHelper; mLogger = logger; mCompatVersions = compatVersions; @@ -67,8 +67,7 @@ class CertificateTransparencyDownloader extends BroadcastReceiver { long startPublicKeyDownload() { long downloadId = download(Config.URL_PUBLIC_KEY); if (downloadId != -1) { - mDataStore.setPropertyLong(Config.PUBLIC_KEY_DOWNLOAD_ID, downloadId); - mDataStore.store(); + mDownloadIds.put(Config.PUBLIC_KEY_DOWNLOAD_ID, downloadId); } return downloadId; } @@ -76,8 +75,7 @@ class CertificateTransparencyDownloader extends BroadcastReceiver { private long startMetadataDownload(CompatibilityVersion compatVersion) { long downloadId = download(compatVersion.getMetadataUrl()); if (downloadId != -1) { - mDataStore.setPropertyLong(compatVersion.getMetadataPropertyName(), downloadId); - mDataStore.store(); + mDownloadIds.put(compatVersion.getMetadataPropertyName(), downloadId); } return downloadId; } @@ -97,8 +95,7 @@ class CertificateTransparencyDownloader extends BroadcastReceiver { long startContentDownload(CompatibilityVersion compatVersion) { long downloadId = download(compatVersion.getContentUrl()); if (downloadId != -1) { - mDataStore.setPropertyLong(compatVersion.getContentPropertyName(), downloadId); - mDataStore.store(); + mDownloadIds.put(compatVersion.getContentPropertyName(), downloadId); } return downloadId; } @@ -151,10 +148,9 @@ class CertificateTransparencyDownloader extends BroadcastReceiver { return; } - try { - mSignatureVerifier.setPublicKeyFrom(publicKeyUri); - } catch (GeneralSecurityException | IOException | IllegalArgumentException e) { - Log.e(TAG, "Error setting the public Key", e); + LogListUpdateStatus updateStatus = mSignatureVerifier.setPublicKeyFrom(publicKeyUri); + if (!updateStatus.isPublicKeySet()) { + mLogger.logCTLogListUpdateStateChangedEvent(updateStatus); return; } @@ -237,19 +233,17 @@ class CertificateTransparencyDownloader extends BroadcastReceiver { @VisibleForTesting long getPublicKeyDownloadId() { - return mDataStore.getPropertyLong(Config.PUBLIC_KEY_DOWNLOAD_ID, /* defaultValue= */ -1); + return mDownloadIds.getOrDefault(Config.PUBLIC_KEY_DOWNLOAD_ID, -1L); } @VisibleForTesting long getMetadataDownloadId(CompatibilityVersion compatVersion) { - return mDataStore.getPropertyLong( - compatVersion.getMetadataPropertyName(), /* defaultValue */ -1); + return mDownloadIds.getOrDefault(compatVersion.getMetadataPropertyName(), -1L); } @VisibleForTesting long getContentDownloadId(CompatibilityVersion compatVersion) { - return mDataStore.getPropertyLong( - compatVersion.getContentPropertyName(), /* defaultValue= */ -1); + return mDownloadIds.getOrDefault(compatVersion.getContentPropertyName(), -1L); } @VisibleForTesting diff --git a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyJob.java b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyJob.java index 286f3268bf..7240d729b7 100644 --- a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyJob.java +++ b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyJob.java @@ -37,7 +37,6 @@ public class CertificateTransparencyJob extends BroadcastReceiver { private static final String TAG = "CertificateTransparencyJob"; private final Context mContext; - private final DataStore mDataStore; private final CertificateTransparencyDownloader mCertificateTransparencyDownloader; private final SignatureVerifier mSignatureVerifier; private final Collection mCompatVersions; @@ -50,12 +49,10 @@ public class CertificateTransparencyJob extends BroadcastReceiver { /** Creates a new {@link CertificateTransparencyJob} object. */ public CertificateTransparencyJob( Context context, - DataStore dataStore, CertificateTransparencyDownloader certificateTransparencyDownloader, SignatureVerifier signatureVerifier, Collection compatVersions) { mContext = context; - mDataStore = dataStore; mCertificateTransparencyDownloader = certificateTransparencyDownloader; mSignatureVerifier = signatureVerifier; mCompatVersions = compatVersions; @@ -132,7 +129,6 @@ public class CertificateTransparencyJob extends BroadcastReceiver { } private void startDependencies() { - mDataStore.load(); mSignatureVerifier.loadAllowedKeys(); mContext.registerReceiver( mCertificateTransparencyDownloader, @@ -147,7 +143,6 @@ public class CertificateTransparencyJob extends BroadcastReceiver { private void stopDependencies() { mContext.unregisterReceiver(mCertificateTransparencyDownloader); mSignatureVerifier.clearAllowedKeys(); - mDataStore.delete(); if (Config.DEBUG) { Log.d(TAG, "CertificateTransparencyJob dependencies stopped."); diff --git a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyLogger.java b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyLogger.java index 2a37d8f778..b9c6d33533 100644 --- a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyLogger.java +++ b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyLogger.java @@ -36,11 +36,14 @@ public interface CertificateTransparencyLogger { UNKNOWN_STATE, HTTP_ERROR, LOG_LIST_INVALID, + PUBLIC_KEY_INVALID, + PUBLIC_KEY_NOT_ALLOWED, PUBLIC_KEY_NOT_FOUND, SIGNATURE_INVALID, SIGNATURE_NOT_FOUND, SIGNATURE_VERIFICATION_FAILED, SUCCESS, + UNABLE_TO_READ_FILE, VERSION_ALREADY_EXISTS } } \ No newline at end of file diff --git a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyLoggerImpl.java b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyLoggerImpl.java index f617523f71..bec9cdcaeb 100644 --- a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyLoggerImpl.java +++ b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyLoggerImpl.java @@ -22,11 +22,14 @@ import static com.android.server.net.ct.CertificateTransparencyStatsLog.CERTIFIC import static com.android.server.net.ct.CertificateTransparencyStatsLog.CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_STATE_CHANGED__UPDATE_STATUS__FAILURE_HTTP_ERROR; import static com.android.server.net.ct.CertificateTransparencyStatsLog.CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_STATE_CHANGED__UPDATE_STATUS__FAILURE_LOG_LIST_INVALID; import static com.android.server.net.ct.CertificateTransparencyStatsLog.CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_STATE_CHANGED__UPDATE_STATUS__FAILURE_NO_DISK_SPACE; +import static com.android.server.net.ct.CertificateTransparencyStatsLog.CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_STATE_CHANGED__UPDATE_STATUS__FAILURE_PUBLIC_KEY_INVALID; +import static com.android.server.net.ct.CertificateTransparencyStatsLog.CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_STATE_CHANGED__UPDATE_STATUS__FAILURE_PUBLIC_KEY_NOT_ALLOWED; import static com.android.server.net.ct.CertificateTransparencyStatsLog.CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_STATE_CHANGED__UPDATE_STATUS__FAILURE_PUBLIC_KEY_NOT_FOUND; import static com.android.server.net.ct.CertificateTransparencyStatsLog.CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_STATE_CHANGED__UPDATE_STATUS__FAILURE_SIGNATURE_INVALID; import static com.android.server.net.ct.CertificateTransparencyStatsLog.CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_STATE_CHANGED__UPDATE_STATUS__FAILURE_SIGNATURE_NOT_FOUND; import static com.android.server.net.ct.CertificateTransparencyStatsLog.CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_STATE_CHANGED__UPDATE_STATUS__FAILURE_SIGNATURE_VERIFICATION; import static com.android.server.net.ct.CertificateTransparencyStatsLog.CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_STATE_CHANGED__UPDATE_STATUS__FAILURE_TOO_MANY_REDIRECTS; +import static com.android.server.net.ct.CertificateTransparencyStatsLog.CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_STATE_CHANGED__UPDATE_STATUS__FAILURE_UNABLE_TO_READ_FILE; import static com.android.server.net.ct.CertificateTransparencyStatsLog.CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_STATE_CHANGED__UPDATE_STATUS__FAILURE_UNKNOWN; import static com.android.server.net.ct.CertificateTransparencyStatsLog.CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_STATE_CHANGED__UPDATE_STATUS__FAILURE_VERSION_ALREADY_EXISTS; import static com.android.server.net.ct.CertificateTransparencyStatsLog.CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_STATE_CHANGED__UPDATE_STATUS__PENDING_WAITING_FOR_WIFI; @@ -37,32 +40,17 @@ import android.app.DownloadManager; /** Implementation for logging to statsd for Certificate Transparency. */ class CertificateTransparencyLoggerImpl implements CertificateTransparencyLogger { - private final DataStore mDataStore; - - CertificateTransparencyLoggerImpl(DataStore dataStore) { - mDataStore = dataStore; - } - @Override public void logCTLogListUpdateStateChangedEvent(LogListUpdateStatus updateStatus) { - if (updateStatus.isSuccessful()) { - resetFailureCount(); - } else { - updateFailureCount(); - } - int updateState = updateStatus .downloadStatus() .map(s -> downloadStatusToFailureReason(s)) .orElseGet(() -> localEnumToStatsLogEnum(updateStatus.state())); - int failureCount = - mDataStore.getPropertyInt( - Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0); logCTLogListUpdateStateChangedEvent( updateState, - failureCount, + /* failureCount= */ updateStatus.isSuccessful() ? 0 : 1, updateStatus.httpErrorStatusCode(), updateStatus.signature(), updateStatus.logListTimestamp()); @@ -83,27 +71,6 @@ class CertificateTransparencyLoggerImpl implements CertificateTransparencyLogger logListTimestamp); } - /** - * Resets the number of consecutive log list update failures in the data store back to zero. - */ - private void resetFailureCount() { - mDataStore.setPropertyInt(Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* value= */ 0); - mDataStore.store(); - } - - /** - * Updates the data store with the current number of consecutive log list update failures. - */ - private void updateFailureCount() { - int failure_count = - mDataStore.getPropertyInt( - Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0); - int new_failure_count = failure_count + 1; - - mDataStore.setPropertyInt(Config.LOG_LIST_UPDATE_FAILURE_COUNT, new_failure_count); - mDataStore.store(); - } - /** Converts DownloadStatus reason into failure reason to log. */ private int downloadStatusToFailureReason(int downloadStatusReason) { switch (downloadStatusReason) { @@ -133,6 +100,10 @@ class CertificateTransparencyLoggerImpl implements CertificateTransparencyLogger return CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_STATE_CHANGED__UPDATE_STATUS__FAILURE_HTTP_ERROR; case LOG_LIST_INVALID: return CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_STATE_CHANGED__UPDATE_STATUS__FAILURE_LOG_LIST_INVALID; + case PUBLIC_KEY_INVALID: + return CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_STATE_CHANGED__UPDATE_STATUS__FAILURE_PUBLIC_KEY_INVALID; + case PUBLIC_KEY_NOT_ALLOWED: + return CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_STATE_CHANGED__UPDATE_STATUS__FAILURE_PUBLIC_KEY_NOT_ALLOWED; case PUBLIC_KEY_NOT_FOUND: return CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_STATE_CHANGED__UPDATE_STATUS__FAILURE_PUBLIC_KEY_NOT_FOUND; case SIGNATURE_INVALID: @@ -143,6 +114,8 @@ class CertificateTransparencyLoggerImpl implements CertificateTransparencyLogger return CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_STATE_CHANGED__UPDATE_STATUS__FAILURE_SIGNATURE_VERIFICATION; case SUCCESS: return CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_STATE_CHANGED__UPDATE_STATUS__SUCCESS; + case UNABLE_TO_READ_FILE: + return CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_STATE_CHANGED__UPDATE_STATUS__FAILURE_UNABLE_TO_READ_FILE; case VERSION_ALREADY_EXISTS: return CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_STATE_CHANGED__UPDATE_STATUS__FAILURE_VERSION_ALREADY_EXISTS; case UNKNOWN_STATE: diff --git a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyService.java b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyService.java index 5e530c74a6..edf44f4875 100644 --- a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyService.java +++ b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyService.java @@ -52,14 +52,9 @@ public class CertificateTransparencyService extends ICertificateTransparencyMana /** Creates a new {@link CertificateTransparencyService} object. */ public CertificateTransparencyService(Context context) { - DataStore dataStore = new DataStore(Config.PREFERENCES_FILE); SignatureVerifier signatureVerifier = new SignatureVerifier(context); Collection compatVersions = Arrays.asList( - new CompatibilityVersion( - Config.COMPATIBILITY_VERSION_V1, - Config.URL_SIGNATURE_V1, - Config.URL_LOG_LIST_V1), new CompatibilityVersion( Config.COMPATIBILITY_VERSION_V2, Config.URL_SIGNATURE_V2, @@ -68,13 +63,11 @@ public class CertificateTransparencyService extends ICertificateTransparencyMana mCertificateTransparencyJob = new CertificateTransparencyJob( context, - dataStore, new CertificateTransparencyDownloader( context, - dataStore, new DownloadHelper(context), signatureVerifier, - new CertificateTransparencyLoggerImpl(dataStore), + new CertificateTransparencyLoggerImpl(), compatVersions), signatureVerifier, compatVersions); diff --git a/networksecurity/service/src/com/android/server/net/ct/CompatibilityVersion.java b/networksecurity/service/src/com/android/server/net/ct/CompatibilityVersion.java index 0a91963ce4..201d6ad1a0 100644 --- a/networksecurity/service/src/com/android/server/net/ct/CompatibilityVersion.java +++ b/networksecurity/service/src/com/android/server/net/ct/CompatibilityVersion.java @@ -32,6 +32,7 @@ import org.json.JSONObject; import java.io.ByteArrayInputStream; import java.io.File; +import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; @@ -55,8 +56,7 @@ class CompatibilityVersion { private final File mVersionDirectory; private final File mCurrentLogsDirSymlink; - CompatibilityVersion( - String compatVersion, String metadataUrl, String contentUrl) { + CompatibilityVersion(String compatVersion, String metadataUrl, String contentUrl) { mCompatVersion = compatVersion; mMetadataUrl = metadataUrl; mContentUrl = contentUrl; @@ -79,22 +79,74 @@ class CompatibilityVersion { */ LogListUpdateStatus install(InputStream newContent, LogListUpdateStatus.Builder statusBuilder) throws IOException { - String content = new String(newContent.readAllBytes(), UTF_8); + byte[] contentBytes = newContent.readAllBytes(); + File logsDir = null; + long timestamp; try { - JSONObject contentJson = new JSONObject(content); - return install( - new ByteArrayInputStream(content.getBytes()), - contentJson.getString("version"), - statusBuilder.setLogListTimestamp(contentJson.getLong("log_list_timestamp"))); + JSONObject contentJson = new JSONObject(new String(contentBytes, UTF_8)); + logsDir = + new File(mVersionDirectory, LOGS_DIR_PREFIX + contentJson.getString("version")); + timestamp = contentJson.getLong("log_list_timestamp"); } catch (JSONException e) { Log.e(TAG, "invalid log list format", e); - return statusBuilder.setState(CTLogListUpdateState.LOG_LIST_INVALID).build(); } + + if (!shouldInstall(logsDir, timestamp)) { + Log.i(TAG, logsDir + " already exists, skipping install."); + deleteOldLogDirectories(); + return statusBuilder + .setLogListTimestamp(timestamp) + .setState(CTLogListUpdateState.VERSION_ALREADY_EXISTS) + .build(); + } + + return install( + new ByteArrayInputStream(contentBytes), + logsDir, + statusBuilder.setLogListTimestamp(timestamp)); + } + + boolean shouldInstall(File logsDir, long newTimestamp) throws IOException { + // This new version was not seen before, proceed with installation. + if (!logsDir.exists()) { + return true; + } + + // If the symlink has not been updated then the previous installation failed and + // this is a re-attempt. Clean-up leftover files and try again. + if (!logsDir.getCanonicalPath().equals(mCurrentLogsDirSymlink.getCanonicalPath())) { + Log.i(TAG, logsDir + " installation failed, reattempt."); + DirectoryUtils.removeDir(logsDir); + return true; + } + + long existingTimestamp; + try (InputStream logListFile = + new FileInputStream(new File(logsDir, LOGS_LIST_FILE_NAME))) { + existingTimestamp = + new JSONObject(new String(logListFile.readAllBytes(), UTF_8)) + .getLong("log_list_timestamp"); + } catch (JSONException e) { + Log.w(TAG, "The existing log list is not a valid JSON file", e); + DirectoryUtils.removeDir(logsDir); + return true; + } + // If the previous installation was successful but the new log list has a later timestamp it + // means it's a bug fix for a previously broken version. + if (existingTimestamp < newTimestamp) { + Log.i(TAG, "The new log list has a later timestamp."); + DirectoryUtils.removeDir(logsDir); + return true; + } + + // In all other cases, the update died between steps 5 and 6 and so we cannot delete the + // directory since it is in use. + return false; } LogListUpdateStatus install( - InputStream newContent, String version, LogListUpdateStatus.Builder statusBuilder) + InputStream newContent, File newLogsDir, LogListUpdateStatus.Builder statusBuilder) throws IOException { // To support atomically replacing the old configuration directory with the new // there's a bunch of steps. We create a new directory with the logs and then do @@ -103,32 +155,18 @@ class CompatibilityVersion { DirectoryUtils.makeDir(sRootDirectory); DirectoryUtils.makeDir(mVersionDirectory); - File newLogsDir = new File(mVersionDirectory, LOGS_DIR_PREFIX + version); - // 2. Handle the corner case where the new directory already exists. - if (newLogsDir.exists()) { - // If the symlink has already been updated then the update died between steps 6 - // and 7 and so we cannot delete the directory since it is in use. - if (newLogsDir.getCanonicalPath().equals(mCurrentLogsDirSymlink.getCanonicalPath())) { - Log.i(TAG, newLogsDir + " already exists, skipping install."); - deleteOldLogDirectories(); - return statusBuilder.setState(CTLogListUpdateState.VERSION_ALREADY_EXISTS).build(); - } - // If the symlink has not been updated then the previous installation failed and - // this is a re-attempt. Clean-up leftover files and try again. - DirectoryUtils.removeDir(newLogsDir); - } try { - // 3. Create a new logs-/ directory to store the new list. + // 2. Create a new logs-/ directory to store the new list. DirectoryUtils.makeDir(newLogsDir); - // 4. Move the log list json file in logs-/ . + // 3. Move the log list json file in logs-/ . File logListFile = new File(newLogsDir, LOGS_LIST_FILE_NAME); if (Files.copy(newContent, logListFile.toPath()) == 0) { throw new IOException("The log list appears empty"); } DirectoryUtils.setWorldReadable(logListFile); - // 5. Create temp symlink. We rename to the target symlink for an atomic update. + // 4. Create temp symlink. We rename to the target symlink for an atomic update. File tempSymlink = new File(mVersionDirectory, "new_symlink"); try { Os.symlink(newLogsDir.getCanonicalPath(), tempSymlink.getCanonicalPath()); @@ -136,13 +174,13 @@ class CompatibilityVersion { throw new IOException("Failed to create symlink", e); } - // 6. Update the symlink target, this is the actual update step. + // 5. Update the symlink target, this is the actual update step. tempSymlink.renameTo(mCurrentLogsDirSymlink.getAbsoluteFile()); } catch (IOException | RuntimeException e) { DirectoryUtils.removeDir(newLogsDir); throw e; } - // 7. Cleanup + // 6. Cleanup Log.i(TAG, "New logs installed at " + newLogsDir); deleteOldLogDirectories(); return statusBuilder.setState(CTLogListUpdateState.SUCCESS).build(); diff --git a/networksecurity/service/src/com/android/server/net/ct/Config.java b/networksecurity/service/src/com/android/server/net/ct/Config.java index 72b715aefe..85351696ef 100644 --- a/networksecurity/service/src/com/android/server/net/ct/Config.java +++ b/networksecurity/service/src/com/android/server/net/ct/Config.java @@ -15,24 +15,11 @@ */ package com.android.server.net.ct; -import android.content.ApexEnvironment; - -import com.android.net.module.util.DeviceConfigUtils; - -import java.io.File; - /** Class holding the constants used by the CT feature. */ final class Config { static final boolean DEBUG = false; - // preferences file - private static final File DEVICE_PROTECTED_DATA_DIR = - ApexEnvironment.getApexEnvironment(DeviceConfigUtils.TETHERING_MODULE_NAME) - .getDeviceProtectedDataDir(); - private static final String PREFERENCES_FILE_NAME = "ct.preferences"; - static final File PREFERENCES_FILE = new File(DEVICE_PROTECTED_DATA_DIR, PREFERENCES_FILE_NAME); - // CT paths static final String CT_ROOT_DIRECTORY_PATH = "/data/misc/keychain/ct/"; static final String URL_PREFIX = "https://www.gstatic.com/android/certificate_transparency/"; diff --git a/networksecurity/service/src/com/android/server/net/ct/DataStore.java b/networksecurity/service/src/com/android/server/net/ct/DataStore.java deleted file mode 100644 index 1f99efa8e4..0000000000 --- a/networksecurity/service/src/com/android/server/net/ct/DataStore.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (C) 2024 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 com.android.server.net.ct; - -import android.util.Log; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.Optional; -import java.util.Properties; - -/** Class to persist data needed by CT. */ -class DataStore extends Properties { - - private static final String TAG = "CertificateTransparency"; - - private final File mPropertyFile; - - DataStore(File file) { - super(); - mPropertyFile = file; - } - - void load() { - if (!mPropertyFile.exists()) { - return; - } - try (InputStream in = new FileInputStream(mPropertyFile)) { - load(in); - } catch (IOException | IllegalArgumentException e) { - Log.e(TAG, "Error loading property store", e); - delete(); - } - } - - void store() { - try (OutputStream out = new FileOutputStream(mPropertyFile)) { - store(out, ""); - } catch (IOException e) { - Log.e(TAG, "Error storing property store", e); - } - } - - boolean delete() { - clear(); - return mPropertyFile.delete(); - } - - long getPropertyLong(String key, long defaultValue) { - return Optional.ofNullable(getProperty(key)).map(Long::parseLong).orElse(defaultValue); - } - - Object setPropertyLong(String key, long value) { - return setProperty(key, Long.toString(value)); - } - - int getPropertyInt(String key, int defaultValue) { - return Optional.ofNullable(getProperty(key)).map(Integer::parseInt).orElse(defaultValue); - } - - Object setPropertyInt(String key, int value) { - return setProperty(key, Integer.toString(value)); - } -} diff --git a/networksecurity/service/src/com/android/server/net/ct/LogListUpdateStatus.java b/networksecurity/service/src/com/android/server/net/ct/LogListUpdateStatus.java index 3f9b762b4d..8b27529b45 100644 --- a/networksecurity/service/src/com/android/server/net/ct/LogListUpdateStatus.java +++ b/networksecurity/service/src/com/android/server/net/ct/LogListUpdateStatus.java @@ -15,11 +15,14 @@ */ package com.android.server.net.ct; +import static com.android.server.net.ct.CertificateTransparencyLogger.CTLogListUpdateState.PUBLIC_KEY_INVALID; +import static com.android.server.net.ct.CertificateTransparencyLogger.CTLogListUpdateState.PUBLIC_KEY_NOT_ALLOWED; import static com.android.server.net.ct.CertificateTransparencyLogger.CTLogListUpdateState.PUBLIC_KEY_NOT_FOUND; import static com.android.server.net.ct.CertificateTransparencyLogger.CTLogListUpdateState.SIGNATURE_INVALID; import static com.android.server.net.ct.CertificateTransparencyLogger.CTLogListUpdateState.SIGNATURE_NOT_FOUND; import static com.android.server.net.ct.CertificateTransparencyLogger.CTLogListUpdateState.SIGNATURE_VERIFICATION_FAILED; import static com.android.server.net.ct.CertificateTransparencyLogger.CTLogListUpdateState.SUCCESS; +import static com.android.server.net.ct.CertificateTransparencyLogger.CTLogListUpdateState.UNABLE_TO_READ_FILE; import com.android.server.net.ct.CertificateTransparencyLogger.CTLogListUpdateState; @@ -41,12 +44,20 @@ public abstract class LogListUpdateStatus { abstract Optional downloadStatus(); + boolean isPublicKeySet() { + // Check that none of the public key setting failures have been set as the state + return state() != PUBLIC_KEY_INVALID + && state() != PUBLIC_KEY_NOT_ALLOWED + && state() != UNABLE_TO_READ_FILE; + } + boolean isSignatureVerified() { // Check that none of the signature verification failures have been set as the state return state() != PUBLIC_KEY_NOT_FOUND && state() != SIGNATURE_INVALID && state() != SIGNATURE_NOT_FOUND - && state() != SIGNATURE_VERIFICATION_FAILED; + && state() != SIGNATURE_VERIFICATION_FAILED + && state() != UNABLE_TO_READ_FILE; } boolean hasSignature() { diff --git a/networksecurity/service/src/com/android/server/net/ct/SignatureVerifier.java b/networksecurity/service/src/com/android/server/net/ct/SignatureVerifier.java index 87a497383f..42bba035fb 100644 --- a/networksecurity/service/src/com/android/server/net/ct/SignatureVerifier.java +++ b/networksecurity/service/src/com/android/server/net/ct/SignatureVerifier.java @@ -15,10 +15,13 @@ */ package com.android.server.net.ct; +import static com.android.server.net.ct.CertificateTransparencyLogger.CTLogListUpdateState.PUBLIC_KEY_INVALID; +import static com.android.server.net.ct.CertificateTransparencyLogger.CTLogListUpdateState.PUBLIC_KEY_NOT_ALLOWED; import static com.android.server.net.ct.CertificateTransparencyLogger.CTLogListUpdateState.PUBLIC_KEY_NOT_FOUND; import static com.android.server.net.ct.CertificateTransparencyLogger.CTLogListUpdateState.SIGNATURE_INVALID; import static com.android.server.net.ct.CertificateTransparencyLogger.CTLogListUpdateState.SIGNATURE_NOT_FOUND; import static com.android.server.net.ct.CertificateTransparencyLogger.CTLogListUpdateState.SIGNATURE_VERIFICATION_FAILED; +import static com.android.server.net.ct.CertificateTransparencyLogger.CTLogListUpdateState.UNABLE_TO_READ_FILE; import android.annotation.NonNull; import android.annotation.RequiresApi; @@ -84,28 +87,38 @@ public class SignatureVerifier { mPublicKey = Optional.empty(); } - void setPublicKeyFrom(Uri file) throws GeneralSecurityException, IOException { + LogListUpdateStatus setPublicKeyFrom(Uri file) { try (InputStream fileStream = mContext.getContentResolver().openInputStream(file)) { - setPublicKey(new String(fileStream.readAllBytes())); + return setPublicKey(new String(fileStream.readAllBytes())); + } catch (IOException e) { + Log.e(TAG, "Could not read the public key file", e); + return LogListUpdateStatus.builder().setState(UNABLE_TO_READ_FILE).build(); } } - void setPublicKey(String publicKey) throws GeneralSecurityException { + private LogListUpdateStatus setPublicKey(String publicKey) { byte[] decodedPublicKey = null; + LogListUpdateStatus.Builder statusBuilder = LogListUpdateStatus.builder(); + try { decodedPublicKey = Base64.getDecoder().decode(publicKey); - } catch (IllegalArgumentException e) { - throw new GeneralSecurityException("Invalid public key base64 encoding", e); - } - setPublicKey( + setPublicKey( KeyFactory.getInstance("RSA") .generatePublic(new X509EncodedKeySpec(decodedPublicKey))); + } catch (IllegalArgumentException e) { + statusBuilder.setState(PUBLIC_KEY_INVALID); + Log.w(TAG, "Invalid public key base64 encoding", e); + } catch (GeneralSecurityException e) { + statusBuilder.setState(PUBLIC_KEY_NOT_ALLOWED); + Log.e(TAG, "Public key not in allowlist", e); + } + + return statusBuilder.build(); } @VisibleForTesting void setPublicKey(PublicKey publicKey) throws GeneralSecurityException { if (!mAllowedKeys.contains(publicKey)) { - // TODO(b/400704086): add logging for this failure. throw new GeneralSecurityException("Public key not in allowlist"); } mPublicKey = Optional.of(publicKey); @@ -144,7 +157,11 @@ public class SignatureVerifier { Log.e(TAG, "Key invalid for log list verification", e); statusBuilder.setState(SIGNATURE_INVALID); return statusBuilder.build(); - } catch (IOException | GeneralSecurityException e) { + } catch (IOException e) { + Log.e(TAG, "Could not read log list file", e); + statusBuilder.setState(UNABLE_TO_READ_FILE); + return statusBuilder.build(); + } catch (GeneralSecurityException e) { Log.e(TAG, "Could not verify new log list", e); statusBuilder.setState(SIGNATURE_VERIFICATION_FAILED); return statusBuilder.build(); diff --git a/networksecurity/tests/test-apps/Android.bp b/networksecurity/tests/test-apps/Android.bp new file mode 100644 index 0000000000..2882ca74fb --- /dev/null +++ b/networksecurity/tests/test-apps/Android.bp @@ -0,0 +1,34 @@ +// Copyright (C) 2025 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 { + default_team: "trendy_team_rubidium_sdk_runtime", + default_applicable_licenses: ["Android-Apache-2.0"], +} + +android_app { + name: "CertificateTransparencyDemoApp", + + manifest: "AndroidManifest.xml", + + resource_dirs: ["res"], + srcs: [ + "src/**/*.java", + ], + static_libs: [ + "androidx-constraintlayout_constraintlayout", + ], + + sdk_version: "current", +} diff --git a/networksecurity/tests/test-apps/AndroidManifest.xml b/networksecurity/tests/test-apps/AndroidManifest.xml new file mode 100644 index 0000000000..276eb0fa31 --- /dev/null +++ b/networksecurity/tests/test-apps/AndroidManifest.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + diff --git a/networksecurity/tests/test-apps/res/layout/activity_main.xml b/networksecurity/tests/test-apps/res/layout/activity_main.xml new file mode 100644 index 0000000000..513af95b3a --- /dev/null +++ b/networksecurity/tests/test-apps/res/layout/activity_main.xml @@ -0,0 +1,58 @@ + + + + +