blob: ee422abd9c9f979c977e21b9611c56eab42c77bd [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;
39
40import com.android.internal.annotations.VisibleForTesting;
41import com.android.modules.utils.build.SdkLevel;
42import com.android.net.module.util.BpfMap;
43import com.android.net.module.util.IBpfMap;
Junyu Lai626045a2023-08-28 18:49:44 +080044import com.android.net.module.util.Struct.S32;
45import com.android.net.module.util.Struct.U32;
Junyu Lai9e880522023-11-14 15:44:37 +080046import com.android.net.module.util.Struct.U8;
Junyu Lai626045a2023-08-28 18:49:44 +080047
48/**
49 * A helper class to *read* java BpfMaps.
50 * @hide
51 */
52@RequiresApi(Build.VERSION_CODES.TIRAMISU) // BPF maps were only mainlined in T
53public class BpfNetMapsReader {
Junyu Lai9e880522023-11-14 15:44:37 +080054 private static final String TAG = BpfNetMapsReader.class.getSimpleName();
55
Junyu Lai626045a2023-08-28 18:49:44 +080056 // Locally store the handle of bpf maps. The FileDescriptors are statically cached inside the
57 // BpfMap implementation.
58
59 // Bpf map to store various networking configurations, the format of the value is different
60 // for different keys. See BpfNetMapsConstants#*_CONFIGURATION_KEY for keys.
61 private final IBpfMap<S32, U32> mConfigurationMap;
62 // Bpf map to store per uid traffic control configurations.
63 // See {@link UidOwnerValue} for more detail.
64 private final IBpfMap<S32, UidOwnerValue> mUidOwnerMap;
Junyu Lai9e880522023-11-14 15:44:37 +080065 private final IBpfMap<S32, U8> mDataSaverEnabledMap;
Junyu Lai626045a2023-08-28 18:49:44 +080066 private final Dependencies mDeps;
67
Junyu Laie0031522023-08-29 18:32:57 +080068 // Bitmaps for calculating whether a given uid is blocked by firewall chains.
69 private static final long sMaskDropIfSet;
70 private static final long sMaskDropIfUnset;
71
72 static {
73 long maskDropIfSet = 0L;
74 long maskDropIfUnset = 0L;
75
76 for (int chain : BpfNetMapsConstants.ALLOW_CHAINS) {
77 final long match = getMatchByFirewallChain(chain);
78 maskDropIfUnset |= match;
79 }
80 for (int chain : BpfNetMapsConstants.DENY_CHAINS) {
81 final long match = getMatchByFirewallChain(chain);
82 maskDropIfSet |= match;
83 }
84 sMaskDropIfSet = maskDropIfSet;
85 sMaskDropIfUnset = maskDropIfUnset;
86 }
87
88 private static class SingletonHolder {
89 static final BpfNetMapsReader sInstance = new BpfNetMapsReader();
90 }
91
92 @NonNull
93 public static BpfNetMapsReader getInstance() {
94 return SingletonHolder.sInstance;
95 }
96
97 private BpfNetMapsReader() {
Junyu Lai626045a2023-08-28 18:49:44 +080098 this(new Dependencies());
99 }
100
Junyu Laie0031522023-08-29 18:32:57 +0800101 // While the production code uses the singleton to optimize for performance and deal with
102 // concurrent access, the test needs to use a non-static approach for dependency injection and
103 // mocking virtual bpf maps.
Junyu Lai626045a2023-08-28 18:49:44 +0800104 @VisibleForTesting
105 public BpfNetMapsReader(@NonNull Dependencies deps) {
106 if (!SdkLevel.isAtLeastT()) {
107 throw new UnsupportedOperationException(
108 BpfNetMapsReader.class.getSimpleName() + " is not supported below Android T");
109 }
110 mDeps = deps;
111 mConfigurationMap = mDeps.getConfigurationMap();
112 mUidOwnerMap = mDeps.getUidOwnerMap();
Junyu Lai9e880522023-11-14 15:44:37 +0800113 mDataSaverEnabledMap = mDeps.getDataSaverEnabledMap();
Junyu Lai626045a2023-08-28 18:49:44 +0800114 }
115
116 /**
117 * Dependencies of BpfNetMapReader, for injection in tests.
118 */
119 @VisibleForTesting
120 public static class Dependencies {
121 /** Get the configuration map. */
122 public IBpfMap<S32, U32> getConfigurationMap() {
123 try {
124 return new BpfMap<>(CONFIGURATION_MAP_PATH, BpfMap.BPF_F_RDONLY,
125 S32.class, U32.class);
126 } catch (ErrnoException e) {
127 throw new IllegalStateException("Cannot open configuration map", e);
128 }
129 }
130
131 /** Get the uid owner map. */
132 public IBpfMap<S32, UidOwnerValue> getUidOwnerMap() {
133 try {
134 return new BpfMap<>(UID_OWNER_MAP_PATH, BpfMap.BPF_F_RDONLY,
135 S32.class, UidOwnerValue.class);
136 } catch (ErrnoException e) {
137 throw new IllegalStateException("Cannot open uid owner map", e);
138 }
139 }
Junyu Lai9e880522023-11-14 15:44:37 +0800140
141 /** Get the data saver enabled map. */
142 public IBpfMap<S32, U8> getDataSaverEnabledMap() {
143 try {
144 return new BpfMap<>(DATA_SAVER_ENABLED_MAP_PATH, BpfMap.BPF_F_RDONLY, S32.class,
145 U8.class);
146 } catch (ErrnoException e) {
147 throw new IllegalStateException("Cannot open data saver enabled map", e);
148 }
149 }
Junyu Lai626045a2023-08-28 18:49:44 +0800150 }
151
152 /**
153 * Get the specified firewall chain's status.
154 *
155 * @param chain target chain
156 * @return {@code true} if chain is enabled, {@code false} if chain is not enabled.
157 * @throws UnsupportedOperationException if called on pre-T devices.
158 * @throws ServiceSpecificException in case of failure, with an error code indicating the
159 * cause of the failure.
160 */
161 public boolean isChainEnabled(final int chain) {
162 return isChainEnabled(mConfigurationMap, chain);
163 }
164
165 /**
166 * Get firewall rule of specified firewall chain on specified uid.
167 *
168 * @param chain target chain
169 * @param uid target uid
170 * @return either {@link ConnectivityManager#FIREWALL_RULE_ALLOW} or
171 * {@link ConnectivityManager#FIREWALL_RULE_DENY}.
172 * @throws UnsupportedOperationException if called on pre-T devices.
173 * @throws ServiceSpecificException in case of failure, with an error code indicating the
174 * cause of the failure.
175 */
176 public int getUidRule(final int chain, final int uid) {
177 return getUidRule(mUidOwnerMap, chain, uid);
178 }
179
180 /**
181 * Get the specified firewall chain's status.
182 *
183 * @param configurationMap target configurationMap
184 * @param chain target chain
185 * @return {@code true} if chain is enabled, {@code false} if chain is not enabled.
186 * @throws UnsupportedOperationException if called on pre-T devices.
187 * @throws ServiceSpecificException in case of failure, with an error code indicating the
188 * cause of the failure.
189 */
190 public static boolean isChainEnabled(
Junyu Lai9e880522023-11-14 15:44:37 +0800191 final IBpfMap<S32, U32> configurationMap, final int chain) {
Junyu Lai626045a2023-08-28 18:49:44 +0800192 throwIfPreT("isChainEnabled is not available on pre-T devices");
193
194 final long match = getMatchByFirewallChain(chain);
195 try {
Junyu Lai9e880522023-11-14 15:44:37 +0800196 final U32 config = configurationMap.getValue(UID_RULES_CONFIGURATION_KEY);
Junyu Lai626045a2023-08-28 18:49:44 +0800197 return (config.val & match) != 0;
198 } catch (ErrnoException e) {
199 throw new ServiceSpecificException(e.errno,
200 "Unable to get firewall chain status: " + Os.strerror(e.errno));
201 }
202 }
203
204 /**
205 * Get firewall rule of specified firewall chain on specified uid.
206 *
207 * @param uidOwnerMap target uidOwnerMap.
208 * @param chain target chain.
209 * @param uid target uid.
210 * @return either FIREWALL_RULE_ALLOW or FIREWALL_RULE_DENY
211 * @throws UnsupportedOperationException if called on pre-T devices.
212 * @throws ServiceSpecificException in case of failure, with an error code indicating the
213 * cause of the failure.
214 */
Junyu Lai9e880522023-11-14 15:44:37 +0800215 public static int getUidRule(final IBpfMap<S32, UidOwnerValue> uidOwnerMap,
Junyu Lai626045a2023-08-28 18:49:44 +0800216 final int chain, final int uid) {
217 throwIfPreT("getUidRule is not available on pre-T devices");
218
219 final long match = getMatchByFirewallChain(chain);
220 final boolean isAllowList = isFirewallAllowList(chain);
221 try {
Junyu Lai9e880522023-11-14 15:44:37 +0800222 final UidOwnerValue uidMatch = uidOwnerMap.getValue(new S32(uid));
Junyu Lai626045a2023-08-28 18:49:44 +0800223 final boolean isMatchEnabled = uidMatch != null && (uidMatch.rule & match) != 0;
224 return isMatchEnabled == isAllowList ? FIREWALL_RULE_ALLOW : FIREWALL_RULE_DENY;
225 } catch (ErrnoException e) {
226 throw new ServiceSpecificException(e.errno,
227 "Unable to get uid rule status: " + Os.strerror(e.errno));
228 }
229 }
Junyu Laie0031522023-08-29 18:32:57 +0800230
231 /**
232 * Return whether the network is blocked by firewall chains for the given uid.
233 *
234 * @param uid The target uid.
Junyu Laic3dc5b62023-09-06 19:10:02 +0800235 * @param isNetworkMetered Whether the target network is metered.
236 * @param isDataSaverEnabled Whether the data saver is enabled.
Junyu Laie0031522023-08-29 18:32:57 +0800237 *
238 * @return True if the network is blocked. Otherwise, false.
239 * @throws ServiceSpecificException if the read fails.
240 *
241 * @hide
242 */
Junyu Laic3dc5b62023-09-06 19:10:02 +0800243 public boolean isUidNetworkingBlocked(final int uid, boolean isNetworkMetered,
244 boolean isDataSaverEnabled) {
Junyu Laie0031522023-08-29 18:32:57 +0800245 throwIfPreT("isUidBlockedByFirewallChains is not available on pre-T devices");
246
247 final long uidRuleConfig;
248 final long uidMatch;
249 try {
250 uidRuleConfig = mConfigurationMap.getValue(UID_RULES_CONFIGURATION_KEY).val;
251 final UidOwnerValue value = mUidOwnerMap.getValue(new S32(uid));
252 uidMatch = (value != null) ? value.rule : 0L;
253 } catch (ErrnoException e) {
254 throw new ServiceSpecificException(e.errno,
255 "Unable to get firewall chain status: " + Os.strerror(e.errno));
256 }
257
258 final boolean blockedByAllowChains = 0 != (uidRuleConfig & ~uidMatch & sMaskDropIfUnset);
259 final boolean blockedByDenyChains = 0 != (uidRuleConfig & uidMatch & sMaskDropIfSet);
Junyu Laic3dc5b62023-09-06 19:10:02 +0800260 if (blockedByAllowChains || blockedByDenyChains) {
261 return true;
262 }
263
264 if (!isNetworkMetered) return false;
265 if ((uidMatch & PENALTY_BOX_MATCH) != 0) return true;
266 if ((uidMatch & HAPPY_BOX_MATCH) != 0) return false;
267 return isDataSaverEnabled;
Junyu Laie0031522023-08-29 18:32:57 +0800268 }
Junyu Lai9e880522023-11-14 15:44:37 +0800269
270 /**
271 * Get Data Saver enabled or disabled
272 *
273 * @return whether Data Saver is enabled or disabled.
274 * @throws ServiceSpecificException in case of failure, with an error code indicating the
275 * cause of the failure.
276 */
277 public boolean getDataSaverEnabled() {
278 throwIfPreT("getDataSaverEnabled is not available on pre-T devices");
279
Junyu Lai9e880522023-11-14 15:44:37 +0800280 try {
281 return mDataSaverEnabledMap.getValue(DATA_SAVER_ENABLED_KEY).val == DATA_SAVER_ENABLED;
282 } catch (ErrnoException e) {
283 throw new ServiceSpecificException(e.errno, "Unable to get data saver: "
284 + Os.strerror(e.errno));
285 }
286 }
Junyu Lai626045a2023-08-28 18:49:44 +0800287}