diff --git a/bpf_progs/netd.c b/bpf_progs/netd.c index b77a8d10ef..5759df7a33 100644 --- a/bpf_progs/netd.c +++ b/bpf_progs/netd.c @@ -112,6 +112,9 @@ DEFINE_BPF_RINGBUF_EXT(packet_trace_ringbuf, PacketTrace, PACKET_TRACE_BUF_SIZE, BPFLOADER_IGNORED_ON_VERSION, BPFLOADER_MAX_VER, LOAD_ON_ENG, LOAD_ON_USER, LOAD_ON_USERDEBUG); +DEFINE_BPF_MAP_RO_NETD(data_saver_enabled_map, ARRAY, uint32_t, bool, + DATA_SAVER_ENABLED_MAP_SIZE) + // iptables xt_bpf programs need to be usable by both netd and netutils_wrappers // selinux contexts, because even non-xt_bpf iptables mutations are implemented as // a full table dump, followed by an update in userspace, and then a reload into the kernel, diff --git a/bpf_progs/netd.h b/bpf_progs/netd.h index 2b28f06042..f960dcfc9b 100644 --- a/bpf_progs/netd.h +++ b/bpf_progs/netd.h @@ -126,6 +126,7 @@ static const int CONFIGURATION_MAP_SIZE = 2; static const int UID_OWNER_MAP_SIZE = 4000; static const int INGRESS_DISCARD_MAP_SIZE = 100; static const int PACKET_TRACE_BUF_SIZE = 32 * 1024; +static const int DATA_SAVER_ENABLED_MAP_SIZE = 1; #ifdef __cplusplus @@ -172,6 +173,7 @@ ASSERT_STRING_EQUAL(XT_BPF_DENYLIST_PROG_PATH, BPF_NETD_PATH "prog_netd_skfilte #define INGRESS_DISCARD_MAP_PATH BPF_NETD_PATH "map_netd_ingress_discard_map" #define PACKET_TRACE_RINGBUF_PATH BPF_NETD_PATH "map_netd_packet_trace_ringbuf" #define PACKET_TRACE_ENABLED_MAP_PATH BPF_NETD_PATH "map_netd_packet_trace_enabled_map" +#define DATA_SAVER_ENABLED_MAP_PATH BPF_NETD_PATH "map_netd_data_saver_enabled_map" #endif // __cplusplus diff --git a/framework/src/android/net/BpfNetMapsConstants.java b/framework/src/android/net/BpfNetMapsConstants.java index e0527f5240..36848e73d3 100644 --- a/framework/src/android/net/BpfNetMapsConstants.java +++ b/framework/src/android/net/BpfNetMapsConstants.java @@ -43,8 +43,14 @@ public class BpfNetMapsConstants { "/sys/fs/bpf/netd_shared/map_netd_uid_permission_map"; public static final String COOKIE_TAG_MAP_PATH = "/sys/fs/bpf/netd_shared/map_netd_cookie_tag_map"; + public static final String DATA_SAVER_ENABLED_MAP_PATH = + "/sys/fs/bpf/netd_shared/map_netd_data_saver_enabled_map"; public static final Struct.S32 UID_RULES_CONFIGURATION_KEY = new Struct.S32(0); public static final Struct.S32 CURRENT_STATS_MAP_CONFIGURATION_KEY = new Struct.S32(1); + public static final Struct.S32 DATA_SAVER_ENABLED_KEY = new Struct.S32(0); + + public static final short DATA_SAVER_DISABLED = 0; + public static final short DATA_SAVER_ENABLED = 1; // LINT.IfChange(match_type) public static final long NO_MATCH = 0; diff --git a/service/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java index 671c4ac3ab..6ade124885 100644 --- a/service/src/com/android/server/BpfNetMaps.java +++ b/service/src/com/android/server/BpfNetMaps.java @@ -19,6 +19,10 @@ package com.android.server; import static android.net.BpfNetMapsConstants.CONFIGURATION_MAP_PATH; import static android.net.BpfNetMapsConstants.COOKIE_TAG_MAP_PATH; import static android.net.BpfNetMapsConstants.CURRENT_STATS_MAP_CONFIGURATION_KEY; +import static android.net.BpfNetMapsConstants.DATA_SAVER_DISABLED; +import static android.net.BpfNetMapsConstants.DATA_SAVER_ENABLED; +import static android.net.BpfNetMapsConstants.DATA_SAVER_ENABLED_KEY; +import static android.net.BpfNetMapsConstants.DATA_SAVER_ENABLED_MAP_PATH; import static android.net.BpfNetMapsConstants.HAPPY_BOX_MATCH; import static android.net.BpfNetMapsConstants.IIF_MATCH; import static android.net.BpfNetMapsConstants.LOCKDOWN_VPN_MATCH; @@ -130,6 +134,8 @@ public class BpfNetMaps { private static IBpfMap sUidOwnerMap = null; private static IBpfMap sUidPermissionMap = null; private static IBpfMap sCookieTagMap = null; + // TODO: Add BOOL class and replace U8? + private static IBpfMap sDataSaverEnabledMap = null; private static final List> PERMISSION_LIST = Arrays.asList( Pair.create(PERMISSION_INTERNET, "PERMISSION_INTERNET"), @@ -177,6 +183,14 @@ public class BpfNetMaps { sCookieTagMap = cookieTagMap; } + /** + * Set dataSaverEnabledMap for test. + */ + @VisibleForTesting + public static void setDataSaverEnabledMapForTest(IBpfMap dataSaverEnabledMap) { + sDataSaverEnabledMap = dataSaverEnabledMap; + } + private static IBpfMap getConfigurationMap() { try { return new BpfMap<>( @@ -213,6 +227,15 @@ public class BpfNetMaps { } } + private static IBpfMap getDataSaverEnabledMap() { + try { + return new BpfMap<>( + DATA_SAVER_ENABLED_MAP_PATH, BpfMap.BPF_F_RDWR, S32.class, U8.class); + } catch (ErrnoException e) { + throw new IllegalStateException("Cannot open data saver enabled map", e); + } + } + private static void initBpfMaps() { if (sConfigurationMap == null) { sConfigurationMap = getConfigurationMap(); @@ -246,6 +269,15 @@ public class BpfNetMaps { if (sCookieTagMap == null) { sCookieTagMap = getCookieTagMap(); } + + if (sDataSaverEnabledMap == null) { + sDataSaverEnabledMap = getDataSaverEnabledMap(); + } + try { + sDataSaverEnabledMap.updateEntry(DATA_SAVER_ENABLED_KEY, new U8(DATA_SAVER_DISABLED)); + } catch (ErrnoException e) { + throw new IllegalStateException("Failed to initialize data saver configuration", e); + } } /** @@ -926,6 +958,27 @@ public class BpfNetMaps { } } + /** + * Set Data Saver enabled or disabled + * + * @param enable whether Data Saver is enabled or disabled. + * @throws UnsupportedOperationException if called on pre-T devices. + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + @RequiresApi(Build.VERSION_CODES.TIRAMISU) + public void setDataSaverEnabled(boolean enable) { + throwIfPreT("setDataSaverEnabled is not available on pre-T devices"); + + try { + final short config = enable ? DATA_SAVER_ENABLED : DATA_SAVER_DISABLED; + sDataSaverEnabledMap.updateEntry(DATA_SAVER_ENABLED_KEY, new U8(config)); + } catch (ErrnoException e) { + throw new ServiceSpecificException(e.errno, "Unable to set data saver: " + + Os.strerror(e.errno)); + } + } + /** Register callback for statsd to pull atom. */ @RequiresApi(Build.VERSION_CODES.TIRAMISU) public void setPullAtomCallback(final Context context) { @@ -1008,6 +1061,15 @@ public class BpfNetMaps { } } + private void dumpDataSaverConfig(final IndentingPrintWriter pw) { + try { + final short config = sDataSaverEnabledMap.getValue(DATA_SAVER_ENABLED_KEY).val; + // Any non-zero value converted from short to boolean is true by convention. + pw.println("sDataSaverEnabledMap: " + (config != DATA_SAVER_DISABLED)); + } catch (ErrnoException e) { + pw.println("Failed to read data saver configuration: " + e); + } + } /** * Dump BPF maps * @@ -1058,6 +1120,8 @@ public class BpfNetMaps { }); BpfDump.dumpMap(sUidPermissionMap, pw, "sUidPermissionMap", (uid, permission) -> uid.val + " " + permissionToString(permission.val)); + + dumpDataSaverConfig(pw); pw.decreaseIndent(); } } diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java index 5ed50ce96a..dc09237a8e 100755 --- a/service/src/com/android/server/ConnectivityService.java +++ b/service/src/com/android/server/ConnectivityService.java @@ -12556,6 +12556,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } } + @TargetApi(Build.VERSION_CODES.TIRAMISU) @Override public void setDataSaverEnabled(final boolean enable) { enforceNetworkStackOrSettingsPermission(); @@ -12568,6 +12569,12 @@ public class ConnectivityService extends IConnectivityManager.Stub // Lack of permission or binder errors. throw new IllegalStateException(e); } + + try { + mBpfNetMaps.setDataSaverEnabled(enable); + } catch (ServiceSpecificException | UnsupportedOperationException e) { + Log.e(TAG, "Failed to set data saver " + enable + " : " + e); + } } @Override diff --git a/tests/mts/bpf_existence_test.cpp b/tests/mts/bpf_existence_test.cpp index cff4d6f680..51a4eca1a2 100644 --- a/tests/mts/bpf_existence_test.cpp +++ b/tests/mts/bpf_existence_test.cpp @@ -94,6 +94,7 @@ static const set MAINLINE_FOR_T_PLUS = { NETD "map_netd_app_uid_stats_map", NETD "map_netd_configuration_map", NETD "map_netd_cookie_tag_map", + NETD "map_netd_data_saver_enabled_map", NETD "map_netd_iface_index_name_map", NETD "map_netd_iface_stats_map", NETD "map_netd_ingress_discard_map", diff --git a/tests/unit/java/com/android/server/BpfNetMapsTest.java b/tests/unit/java/com/android/server/BpfNetMapsTest.java index da5f7e13c9..ea2b26136c 100644 --- a/tests/unit/java/com/android/server/BpfNetMapsTest.java +++ b/tests/unit/java/com/android/server/BpfNetMapsTest.java @@ -17,6 +17,9 @@ package com.android.server; import static android.net.BpfNetMapsConstants.CURRENT_STATS_MAP_CONFIGURATION_KEY; +import static android.net.BpfNetMapsConstants.DATA_SAVER_ENABLED_KEY; +import static android.net.BpfNetMapsConstants.DATA_SAVER_DISABLED; +import static android.net.BpfNetMapsConstants.DATA_SAVER_ENABLED; import static android.net.BpfNetMapsConstants.DOZABLE_MATCH; import static android.net.BpfNetMapsConstants.HAPPY_BOX_MATCH; import static android.net.BpfNetMapsConstants.IIF_MATCH; @@ -141,6 +144,7 @@ public final class BpfNetMapsTest { private final IBpfMap mUidPermissionMap = new TestBpfMap<>(S32.class, U8.class); private final IBpfMap mCookieTagMap = spy(new TestBpfMap<>(CookieTagMapKey.class, CookieTagMapValue.class)); + private final IBpfMap mDataSaverEnabledMap = new TestBpfMap<>(S32.class, U8.class); @Before public void setUp() throws Exception { @@ -155,6 +159,8 @@ public final class BpfNetMapsTest { BpfNetMaps.setUidOwnerMapForTest(mUidOwnerMap); BpfNetMaps.setUidPermissionMapForTest(mUidPermissionMap); BpfNetMaps.setCookieTagMapForTest(mCookieTagMap); + BpfNetMaps.setDataSaverEnabledMapForTest(mDataSaverEnabledMap); + mDataSaverEnabledMap.updateEntry(DATA_SAVER_ENABLED_KEY, new U8(DATA_SAVER_DISABLED)); mBpfNetMaps = new BpfNetMaps(mContext, mNetd, mDeps); } @@ -1155,6 +1161,21 @@ public final class BpfNetMapsTest { assertDumpContains(getDump(), "cookie=123 tag=0x789 uid=456"); } + private void doTestDumpDataSaverConfig(final short value, final boolean expected) + throws Exception { + mDataSaverEnabledMap.updateEntry(DATA_SAVER_ENABLED_KEY, new U8(value)); + assertDumpContains(getDump(), + "sDataSaverEnabledMap: " + expected); + } + + @Test + @IgnoreUpTo(Build.VERSION_CODES.S_V2) + public void testDumpDataSaverConfig() throws Exception { + doTestDumpDataSaverConfig(DATA_SAVER_DISABLED, false); + doTestDumpDataSaverConfig(DATA_SAVER_ENABLED, true); + doTestDumpDataSaverConfig((short) 2, true); + } + @Test public void testGetUids() throws ErrnoException { final int uid0 = TEST_UIDS[0]; @@ -1183,4 +1204,23 @@ public final class BpfNetMapsTest { assertThrows(expected, () -> mBpfNetMaps.getUidsWithAllowRuleOnAllowListChain(FIREWALL_CHAIN_OEM_DENY_1)); } + + @Test + @IgnoreAfter(Build.VERSION_CODES.S_V2) + public void testSetDataSaverEnabledBeforeT() { + for (boolean enable : new boolean[] {true, false}) { + assertThrows(UnsupportedOperationException.class, + () -> mBpfNetMaps.setDataSaverEnabled(enable)); + } + } + + @Test + @IgnoreUpTo(Build.VERSION_CODES.S_V2) + public void testSetDataSaverEnabled() throws Exception { + for (boolean enable : new boolean[] {true, false}) { + mBpfNetMaps.setDataSaverEnabled(enable); + assertEquals(enable ? DATA_SAVER_ENABLED : DATA_SAVER_DISABLED, + mDataSaverEnabledMap.getValue(DATA_SAVER_ENABLED_KEY).val); + } + } }