blob: 37c58f079d728e71846a5349456a08df66154fd3 [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 Laic3dc5b62023-09-06 19:10:02 +080020import static android.net.BpfNetMapsConstants.HAPPY_BOX_MATCH;
21import static android.net.BpfNetMapsConstants.PENALTY_BOX_MATCH;
Junyu Lai626045a2023-08-28 18:49:44 +080022import static android.net.BpfNetMapsConstants.UID_OWNER_MAP_PATH;
23import static android.net.BpfNetMapsConstants.UID_RULES_CONFIGURATION_KEY;
24import static android.net.BpfNetMapsUtils.getMatchByFirewallChain;
25import static android.net.BpfNetMapsUtils.isFirewallAllowList;
26import static android.net.BpfNetMapsUtils.throwIfPreT;
27import static android.net.ConnectivityManager.FIREWALL_RULE_ALLOW;
28import static android.net.ConnectivityManager.FIREWALL_RULE_DENY;
29
30import android.annotation.NonNull;
31import android.annotation.RequiresApi;
32import android.os.Build;
33import android.os.ServiceSpecificException;
34import android.system.ErrnoException;
35import android.system.Os;
36
37import com.android.internal.annotations.VisibleForTesting;
38import com.android.modules.utils.build.SdkLevel;
39import com.android.net.module.util.BpfMap;
40import com.android.net.module.util.IBpfMap;
41import com.android.net.module.util.Struct;
42import com.android.net.module.util.Struct.S32;
43import com.android.net.module.util.Struct.U32;
44
45/**
46 * A helper class to *read* java BpfMaps.
47 * @hide
48 */
49@RequiresApi(Build.VERSION_CODES.TIRAMISU) // BPF maps were only mainlined in T
50public class BpfNetMapsReader {
51 // Locally store the handle of bpf maps. The FileDescriptors are statically cached inside the
52 // BpfMap implementation.
53
54 // Bpf map to store various networking configurations, the format of the value is different
55 // for different keys. See BpfNetMapsConstants#*_CONFIGURATION_KEY for keys.
56 private final IBpfMap<S32, U32> mConfigurationMap;
57 // Bpf map to store per uid traffic control configurations.
58 // See {@link UidOwnerValue} for more detail.
59 private final IBpfMap<S32, UidOwnerValue> mUidOwnerMap;
60 private final Dependencies mDeps;
61
Junyu Laie0031522023-08-29 18:32:57 +080062 // Bitmaps for calculating whether a given uid is blocked by firewall chains.
63 private static final long sMaskDropIfSet;
64 private static final long sMaskDropIfUnset;
65
66 static {
67 long maskDropIfSet = 0L;
68 long maskDropIfUnset = 0L;
69
70 for (int chain : BpfNetMapsConstants.ALLOW_CHAINS) {
71 final long match = getMatchByFirewallChain(chain);
72 maskDropIfUnset |= match;
73 }
74 for (int chain : BpfNetMapsConstants.DENY_CHAINS) {
75 final long match = getMatchByFirewallChain(chain);
76 maskDropIfSet |= match;
77 }
78 sMaskDropIfSet = maskDropIfSet;
79 sMaskDropIfUnset = maskDropIfUnset;
80 }
81
82 private static class SingletonHolder {
83 static final BpfNetMapsReader sInstance = new BpfNetMapsReader();
84 }
85
86 @NonNull
87 public static BpfNetMapsReader getInstance() {
88 return SingletonHolder.sInstance;
89 }
90
91 private BpfNetMapsReader() {
Junyu Lai626045a2023-08-28 18:49:44 +080092 this(new Dependencies());
93 }
94
Junyu Laie0031522023-08-29 18:32:57 +080095 // While the production code uses the singleton to optimize for performance and deal with
96 // concurrent access, the test needs to use a non-static approach for dependency injection and
97 // mocking virtual bpf maps.
Junyu Lai626045a2023-08-28 18:49:44 +080098 @VisibleForTesting
99 public BpfNetMapsReader(@NonNull Dependencies deps) {
100 if (!SdkLevel.isAtLeastT()) {
101 throw new UnsupportedOperationException(
102 BpfNetMapsReader.class.getSimpleName() + " is not supported below Android T");
103 }
104 mDeps = deps;
105 mConfigurationMap = mDeps.getConfigurationMap();
106 mUidOwnerMap = mDeps.getUidOwnerMap();
107 }
108
109 /**
110 * Dependencies of BpfNetMapReader, for injection in tests.
111 */
112 @VisibleForTesting
113 public static class Dependencies {
114 /** Get the configuration map. */
115 public IBpfMap<S32, U32> getConfigurationMap() {
116 try {
117 return new BpfMap<>(CONFIGURATION_MAP_PATH, BpfMap.BPF_F_RDONLY,
118 S32.class, U32.class);
119 } catch (ErrnoException e) {
120 throw new IllegalStateException("Cannot open configuration map", e);
121 }
122 }
123
124 /** Get the uid owner map. */
125 public IBpfMap<S32, UidOwnerValue> getUidOwnerMap() {
126 try {
127 return new BpfMap<>(UID_OWNER_MAP_PATH, BpfMap.BPF_F_RDONLY,
128 S32.class, UidOwnerValue.class);
129 } catch (ErrnoException e) {
130 throw new IllegalStateException("Cannot open uid owner map", e);
131 }
132 }
133 }
134
135 /**
136 * Get the specified firewall chain's status.
137 *
138 * @param chain target chain
139 * @return {@code true} if chain is enabled, {@code false} if chain is not enabled.
140 * @throws UnsupportedOperationException if called on pre-T devices.
141 * @throws ServiceSpecificException in case of failure, with an error code indicating the
142 * cause of the failure.
143 */
144 public boolean isChainEnabled(final int chain) {
145 return isChainEnabled(mConfigurationMap, chain);
146 }
147
148 /**
149 * Get firewall rule of specified firewall chain on specified uid.
150 *
151 * @param chain target chain
152 * @param uid target uid
153 * @return either {@link ConnectivityManager#FIREWALL_RULE_ALLOW} or
154 * {@link ConnectivityManager#FIREWALL_RULE_DENY}.
155 * @throws UnsupportedOperationException if called on pre-T devices.
156 * @throws ServiceSpecificException in case of failure, with an error code indicating the
157 * cause of the failure.
158 */
159 public int getUidRule(final int chain, final int uid) {
160 return getUidRule(mUidOwnerMap, chain, uid);
161 }
162
163 /**
164 * Get the specified firewall chain's status.
165 *
166 * @param configurationMap target configurationMap
167 * @param chain target chain
168 * @return {@code true} if chain is enabled, {@code false} if chain is not enabled.
169 * @throws UnsupportedOperationException if called on pre-T devices.
170 * @throws ServiceSpecificException in case of failure, with an error code indicating the
171 * cause of the failure.
172 */
173 public static boolean isChainEnabled(
174 final IBpfMap<Struct.S32, Struct.U32> configurationMap, final int chain) {
175 throwIfPreT("isChainEnabled is not available on pre-T devices");
176
177 final long match = getMatchByFirewallChain(chain);
178 try {
179 final Struct.U32 config = configurationMap.getValue(UID_RULES_CONFIGURATION_KEY);
180 return (config.val & match) != 0;
181 } catch (ErrnoException e) {
182 throw new ServiceSpecificException(e.errno,
183 "Unable to get firewall chain status: " + Os.strerror(e.errno));
184 }
185 }
186
187 /**
188 * Get firewall rule of specified firewall chain on specified uid.
189 *
190 * @param uidOwnerMap target uidOwnerMap.
191 * @param chain target chain.
192 * @param uid target uid.
193 * @return either FIREWALL_RULE_ALLOW or FIREWALL_RULE_DENY
194 * @throws UnsupportedOperationException if called on pre-T devices.
195 * @throws ServiceSpecificException in case of failure, with an error code indicating the
196 * cause of the failure.
197 */
198 public static int getUidRule(final IBpfMap<Struct.S32, UidOwnerValue> uidOwnerMap,
199 final int chain, final int uid) {
200 throwIfPreT("getUidRule is not available on pre-T devices");
201
202 final long match = getMatchByFirewallChain(chain);
203 final boolean isAllowList = isFirewallAllowList(chain);
204 try {
205 final UidOwnerValue uidMatch = uidOwnerMap.getValue(new Struct.S32(uid));
206 final boolean isMatchEnabled = uidMatch != null && (uidMatch.rule & match) != 0;
207 return isMatchEnabled == isAllowList ? FIREWALL_RULE_ALLOW : FIREWALL_RULE_DENY;
208 } catch (ErrnoException e) {
209 throw new ServiceSpecificException(e.errno,
210 "Unable to get uid rule status: " + Os.strerror(e.errno));
211 }
212 }
Junyu Laie0031522023-08-29 18:32:57 +0800213
214 /**
215 * Return whether the network is blocked by firewall chains for the given uid.
216 *
217 * @param uid The target uid.
Junyu Laic3dc5b62023-09-06 19:10:02 +0800218 * @param isNetworkMetered Whether the target network is metered.
219 * @param isDataSaverEnabled Whether the data saver is enabled.
Junyu Laie0031522023-08-29 18:32:57 +0800220 *
221 * @return True if the network is blocked. Otherwise, false.
222 * @throws ServiceSpecificException if the read fails.
223 *
224 * @hide
225 */
Junyu Laic3dc5b62023-09-06 19:10:02 +0800226 public boolean isUidNetworkingBlocked(final int uid, boolean isNetworkMetered,
227 boolean isDataSaverEnabled) {
Junyu Laie0031522023-08-29 18:32:57 +0800228 throwIfPreT("isUidBlockedByFirewallChains is not available on pre-T devices");
229
230 final long uidRuleConfig;
231 final long uidMatch;
232 try {
233 uidRuleConfig = mConfigurationMap.getValue(UID_RULES_CONFIGURATION_KEY).val;
234 final UidOwnerValue value = mUidOwnerMap.getValue(new S32(uid));
235 uidMatch = (value != null) ? value.rule : 0L;
236 } catch (ErrnoException e) {
237 throw new ServiceSpecificException(e.errno,
238 "Unable to get firewall chain status: " + Os.strerror(e.errno));
239 }
240
241 final boolean blockedByAllowChains = 0 != (uidRuleConfig & ~uidMatch & sMaskDropIfUnset);
242 final boolean blockedByDenyChains = 0 != (uidRuleConfig & uidMatch & sMaskDropIfSet);
Junyu Laic3dc5b62023-09-06 19:10:02 +0800243 if (blockedByAllowChains || blockedByDenyChains) {
244 return true;
245 }
246
247 if (!isNetworkMetered) return false;
248 if ((uidMatch & PENALTY_BOX_MATCH) != 0) return true;
249 if ((uidMatch & HAPPY_BOX_MATCH) != 0) return false;
250 return isDataSaverEnabled;
Junyu Laie0031522023-08-29 18:32:57 +0800251 }
Junyu Lai626045a2023-08-28 18:49:44 +0800252}