blob: 4ab6d3e0b6a351f5f5271af25bd2123879dbbdc9 [file] [log] [blame]
Junyu Lai626045a2023-08-28 18:49:44 +08001/*
2 * Copyright (C) 2023 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.net;
18
19import static android.net.BpfNetMapsConstants.CONFIGURATION_MAP_PATH;
Junyu Lai9e880522023-11-14 15:44:37 +080020import static android.net.BpfNetMapsConstants.DATA_SAVER_ENABLED;
21import static android.net.BpfNetMapsConstants.DATA_SAVER_ENABLED_KEY;
22import static android.net.BpfNetMapsConstants.DATA_SAVER_ENABLED_MAP_PATH;
Junyu Laic3dc5b62023-09-06 19:10:02 +080023import static android.net.BpfNetMapsConstants.HAPPY_BOX_MATCH;
24import static android.net.BpfNetMapsConstants.PENALTY_BOX_MATCH;
Junyu Lai626045a2023-08-28 18:49:44 +080025import static android.net.BpfNetMapsConstants.UID_OWNER_MAP_PATH;
26import static android.net.BpfNetMapsConstants.UID_RULES_CONFIGURATION_KEY;
27import static android.net.BpfNetMapsUtils.getMatchByFirewallChain;
28import static android.net.BpfNetMapsUtils.isFirewallAllowList;
29import static android.net.BpfNetMapsUtils.throwIfPreT;
30import static android.net.ConnectivityManager.FIREWALL_RULE_ALLOW;
31import static android.net.ConnectivityManager.FIREWALL_RULE_DENY;
32
33import android.annotation.NonNull;
34import android.annotation.RequiresApi;
35import android.os.Build;
36import android.os.ServiceSpecificException;
37import android.system.ErrnoException;
38import android.system.Os;
Junyu Lai9e880522023-11-14 15:44:37 +080039import android.util.Log;
Junyu Lai626045a2023-08-28 18:49:44 +080040
41import com.android.internal.annotations.VisibleForTesting;
42import com.android.modules.utils.build.SdkLevel;
43import com.android.net.module.util.BpfMap;
44import com.android.net.module.util.IBpfMap;
Junyu Lai626045a2023-08-28 18:49:44 +080045import com.android.net.module.util.Struct.S32;
46import com.android.net.module.util.Struct.U32;
Junyu Lai9e880522023-11-14 15:44:37 +080047import com.android.net.module.util.Struct.U8;
Junyu Lai626045a2023-08-28 18:49:44 +080048
49/**
50 * A helper class to *read* java BpfMaps.
51 * @hide
52 */
53@RequiresApi(Build.VERSION_CODES.TIRAMISU) // BPF maps were only mainlined in T
54public class BpfNetMapsReader {
Junyu Lai9e880522023-11-14 15:44:37 +080055 private static final String TAG = BpfNetMapsReader.class.getSimpleName();
56
Junyu Lai626045a2023-08-28 18:49:44 +080057 // Locally store the handle of bpf maps. The FileDescriptors are statically cached inside the
58 // BpfMap implementation.
59
60 // Bpf map to store various networking configurations, the format of the value is different
61 // for different keys. See BpfNetMapsConstants#*_CONFIGURATION_KEY for keys.
62 private final IBpfMap<S32, U32> mConfigurationMap;
63 // Bpf map to store per uid traffic control configurations.
64 // See {@link UidOwnerValue} for more detail.
65 private final IBpfMap<S32, UidOwnerValue> mUidOwnerMap;
Junyu Lai9e880522023-11-14 15:44:37 +080066 private final IBpfMap<S32, U8> mDataSaverEnabledMap;
Junyu Lai626045a2023-08-28 18:49:44 +080067 private final Dependencies mDeps;
68
Junyu Laie0031522023-08-29 18:32:57 +080069 // Bitmaps for calculating whether a given uid is blocked by firewall chains.
70 private static final long sMaskDropIfSet;
71 private static final long sMaskDropIfUnset;
72
73 static {
74 long maskDropIfSet = 0L;
75 long maskDropIfUnset = 0L;
76
77 for (int chain : BpfNetMapsConstants.ALLOW_CHAINS) {
78 final long match = getMatchByFirewallChain(chain);
79 maskDropIfUnset |= match;
80 }
81 for (int chain : BpfNetMapsConstants.DENY_CHAINS) {
82 final long match = getMatchByFirewallChain(chain);
83 maskDropIfSet |= match;
84 }
85 sMaskDropIfSet = maskDropIfSet;
86 sMaskDropIfUnset = maskDropIfUnset;
87 }
88
89 private static class SingletonHolder {
90 static final BpfNetMapsReader sInstance = new BpfNetMapsReader();
91 }
92
93 @NonNull
94 public static BpfNetMapsReader getInstance() {
95 return SingletonHolder.sInstance;
96 }
97
98 private BpfNetMapsReader() {
Junyu Lai626045a2023-08-28 18:49:44 +080099 this(new Dependencies());
100 }
101
Junyu Laie0031522023-08-29 18:32:57 +0800102 // While the production code uses the singleton to optimize for performance and deal with
103 // concurrent access, the test needs to use a non-static approach for dependency injection and
104 // mocking virtual bpf maps.
Junyu Lai626045a2023-08-28 18:49:44 +0800105 @VisibleForTesting
106 public BpfNetMapsReader(@NonNull Dependencies deps) {
107 if (!SdkLevel.isAtLeastT()) {
108 throw new UnsupportedOperationException(
109 BpfNetMapsReader.class.getSimpleName() + " is not supported below Android T");
110 }
111 mDeps = deps;
112 mConfigurationMap = mDeps.getConfigurationMap();
113 mUidOwnerMap = mDeps.getUidOwnerMap();
Junyu Lai9e880522023-11-14 15:44:37 +0800114 mDataSaverEnabledMap = mDeps.getDataSaverEnabledMap();
Junyu Lai626045a2023-08-28 18:49:44 +0800115 }
116
117 /**
118 * Dependencies of BpfNetMapReader, for injection in tests.
119 */
120 @VisibleForTesting
121 public static class Dependencies {
122 /** Get the configuration map. */
123 public IBpfMap<S32, U32> getConfigurationMap() {
124 try {
125 return new BpfMap<>(CONFIGURATION_MAP_PATH, BpfMap.BPF_F_RDONLY,
126 S32.class, U32.class);
127 } catch (ErrnoException e) {
128 throw new IllegalStateException("Cannot open configuration map", e);
129 }
130 }
131
132 /** Get the uid owner map. */
133 public IBpfMap<S32, UidOwnerValue> getUidOwnerMap() {
134 try {
135 return new BpfMap<>(UID_OWNER_MAP_PATH, BpfMap.BPF_F_RDONLY,
136 S32.class, UidOwnerValue.class);
137 } catch (ErrnoException e) {
138 throw new IllegalStateException("Cannot open uid owner map", e);
139 }
140 }
Junyu Lai9e880522023-11-14 15:44:37 +0800141
142 /** Get the data saver enabled map. */
143 public IBpfMap<S32, U8> getDataSaverEnabledMap() {
144 try {
145 return new BpfMap<>(DATA_SAVER_ENABLED_MAP_PATH, BpfMap.BPF_F_RDONLY, S32.class,
146 U8.class);
147 } catch (ErrnoException e) {
148 throw new IllegalStateException("Cannot open data saver enabled map", e);
149 }
150 }
Junyu Lai626045a2023-08-28 18:49:44 +0800151 }
152
153 /**
154 * Get the specified firewall chain's status.
155 *
156 * @param chain target chain
157 * @return {@code true} if chain is enabled, {@code false} if chain is not enabled.
158 * @throws UnsupportedOperationException if called on pre-T devices.
159 * @throws ServiceSpecificException in case of failure, with an error code indicating the
160 * cause of the failure.
161 */
162 public boolean isChainEnabled(final int chain) {
163 return isChainEnabled(mConfigurationMap, chain);
164 }
165
166 /**
167 * Get firewall rule of specified firewall chain on specified uid.
168 *
169 * @param chain target chain
170 * @param uid target uid
171 * @return either {@link ConnectivityManager#FIREWALL_RULE_ALLOW} or
172 * {@link ConnectivityManager#FIREWALL_RULE_DENY}.
173 * @throws UnsupportedOperationException if called on pre-T devices.
174 * @throws ServiceSpecificException in case of failure, with an error code indicating the
175 * cause of the failure.
176 */
177 public int getUidRule(final int chain, final int uid) {
178 return getUidRule(mUidOwnerMap, chain, uid);
179 }
180
181 /**
182 * Get the specified firewall chain's status.
183 *
184 * @param configurationMap target configurationMap
185 * @param chain target chain
186 * @return {@code true} if chain is enabled, {@code false} if chain is not enabled.
187 * @throws UnsupportedOperationException if called on pre-T devices.
188 * @throws ServiceSpecificException in case of failure, with an error code indicating the
189 * cause of the failure.
190 */
191 public static boolean isChainEnabled(
Junyu Lai9e880522023-11-14 15:44:37 +0800192 final IBpfMap<S32, U32> configurationMap, final int chain) {
Junyu Lai626045a2023-08-28 18:49:44 +0800193 throwIfPreT("isChainEnabled is not available on pre-T devices");
194
195 final long match = getMatchByFirewallChain(chain);
196 try {
Junyu Lai9e880522023-11-14 15:44:37 +0800197 final U32 config = configurationMap.getValue(UID_RULES_CONFIGURATION_KEY);
Junyu Lai626045a2023-08-28 18:49:44 +0800198 return (config.val & match) != 0;
199 } catch (ErrnoException e) {
200 throw new ServiceSpecificException(e.errno,
201 "Unable to get firewall chain status: " + Os.strerror(e.errno));
202 }
203 }
204
205 /**
206 * Get firewall rule of specified firewall chain on specified uid.
207 *
208 * @param uidOwnerMap target uidOwnerMap.
209 * @param chain target chain.
210 * @param uid target uid.
211 * @return either FIREWALL_RULE_ALLOW or FIREWALL_RULE_DENY
212 * @throws UnsupportedOperationException if called on pre-T devices.
213 * @throws ServiceSpecificException in case of failure, with an error code indicating the
214 * cause of the failure.
215 */
Junyu Lai9e880522023-11-14 15:44:37 +0800216 public static int getUidRule(final IBpfMap<S32, UidOwnerValue> uidOwnerMap,
Junyu Lai626045a2023-08-28 18:49:44 +0800217 final int chain, final int uid) {
218 throwIfPreT("getUidRule is not available on pre-T devices");
219
220 final long match = getMatchByFirewallChain(chain);
221 final boolean isAllowList = isFirewallAllowList(chain);
222 try {
Junyu Lai9e880522023-11-14 15:44:37 +0800223 final UidOwnerValue uidMatch = uidOwnerMap.getValue(new S32(uid));
Junyu Lai626045a2023-08-28 18:49:44 +0800224 final boolean isMatchEnabled = uidMatch != null && (uidMatch.rule & match) != 0;
225 return isMatchEnabled == isAllowList ? FIREWALL_RULE_ALLOW : FIREWALL_RULE_DENY;
226 } catch (ErrnoException e) {
227 throw new ServiceSpecificException(e.errno,
228 "Unable to get uid rule status: " + Os.strerror(e.errno));
229 }
230 }
Junyu Laie0031522023-08-29 18:32:57 +0800231
232 /**
233 * Return whether the network is blocked by firewall chains for the given uid.
234 *
235 * @param uid The target uid.
Junyu Laic3dc5b62023-09-06 19:10:02 +0800236 * @param isNetworkMetered Whether the target network is metered.
237 * @param isDataSaverEnabled Whether the data saver is enabled.
Junyu Laie0031522023-08-29 18:32:57 +0800238 *
239 * @return True if the network is blocked. Otherwise, false.
240 * @throws ServiceSpecificException if the read fails.
241 *
242 * @hide
243 */
Junyu Laic3dc5b62023-09-06 19:10:02 +0800244 public boolean isUidNetworkingBlocked(final int uid, boolean isNetworkMetered,
245 boolean isDataSaverEnabled) {
Junyu Laie0031522023-08-29 18:32:57 +0800246 throwIfPreT("isUidBlockedByFirewallChains is not available on pre-T devices");
247
248 final long uidRuleConfig;
249 final long uidMatch;
250 try {
251 uidRuleConfig = mConfigurationMap.getValue(UID_RULES_CONFIGURATION_KEY).val;
252 final UidOwnerValue value = mUidOwnerMap.getValue(new S32(uid));
253 uidMatch = (value != null) ? value.rule : 0L;
254 } catch (ErrnoException e) {
255 throw new ServiceSpecificException(e.errno,
256 "Unable to get firewall chain status: " + Os.strerror(e.errno));
257 }
258
259 final boolean blockedByAllowChains = 0 != (uidRuleConfig & ~uidMatch & sMaskDropIfUnset);
260 final boolean blockedByDenyChains = 0 != (uidRuleConfig & uidMatch & sMaskDropIfSet);
Junyu Laic3dc5b62023-09-06 19:10:02 +0800261 if (blockedByAllowChains || blockedByDenyChains) {
262 return true;
263 }
264
265 if (!isNetworkMetered) return false;
266 if ((uidMatch & PENALTY_BOX_MATCH) != 0) return true;
267 if ((uidMatch & HAPPY_BOX_MATCH) != 0) return false;
268 return isDataSaverEnabled;
Junyu Laie0031522023-08-29 18:32:57 +0800269 }
Junyu Lai9e880522023-11-14 15:44:37 +0800270
271 /**
272 * Get Data Saver enabled or disabled
273 *
274 * @return whether Data Saver is enabled or disabled.
275 * @throws ServiceSpecificException in case of failure, with an error code indicating the
276 * cause of the failure.
277 */
278 public boolean getDataSaverEnabled() {
279 throwIfPreT("getDataSaverEnabled is not available on pre-T devices");
280
281 // Note that this is not expected to be called until V given that it relies on the
282 // counterpart platform solution to set data saver status to bpf.
283 // See {@code NetworkManagementService#setDataSaverModeEnabled}.
284 if (!SdkLevel.isAtLeastV()) {
285 Log.wtf(TAG, "getDataSaverEnabled is not expected to be called on pre-V devices");
286 }
287
288 try {
289 return mDataSaverEnabledMap.getValue(DATA_SAVER_ENABLED_KEY).val == DATA_SAVER_ENABLED;
290 } catch (ErrnoException e) {
291 throw new ServiceSpecificException(e.errno, "Unable to get data saver: "
292 + Os.strerror(e.errno));
293 }
294 }
Junyu Lai626045a2023-08-28 18:49:44 +0800295}