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