1 /*
2  * Copyright (C) 2020 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 package com.android.net.module.util;
17 
18 import static android.system.OsConstants.EBUSY;
19 import static android.system.OsConstants.EEXIST;
20 import static android.system.OsConstants.ENOENT;
21 
22 import android.os.Build;
23 import android.os.ParcelFileDescriptor;
24 import android.system.ErrnoException;
25 import android.util.Pair;
26 
27 import androidx.annotation.NonNull;
28 import androidx.annotation.Nullable;
29 import androidx.annotation.RequiresApi;
30 
31 import java.nio.ByteBuffer;
32 import java.nio.ByteOrder;
33 import java.util.NoSuchElementException;
34 import java.util.Objects;
35 import java.util.concurrent.ConcurrentHashMap;
36 
37 /**
38  * BpfMap is a key -> value mapping structure that is designed to maintained the bpf map entries.
39  * This is a wrapper class of in-kernel data structure. The in-kernel data can be read/written by
40  * passing syscalls with map file descriptor.
41  *
42  * @param <K> the key of the map.
43  * @param <V> the value of the map.
44  */
45 @RequiresApi(Build.VERSION_CODES.S)
46 public class BpfMap<K extends Struct, V extends Struct> implements IBpfMap<K, V> {
47     static {
JniUtil.getJniLibraryName()48         System.loadLibrary(JniUtil.getJniLibraryName(BpfMap.class.getPackage()));
49     }
50 
51     // Following definitions from kernel include/uapi/linux/bpf.h
52     public static final int BPF_F_RDWR = 0;
53     public static final int BPF_F_RDONLY = 1 << 3;
54     public static final int BPF_F_WRONLY = 1 << 4;
55 
56     // magic value for jni consumption, invalid from kernel point of view
57     public static final int BPF_F_RDWR_EXCLUSIVE = BPF_F_RDONLY | BPF_F_WRONLY;
58 
59     public static final int BPF_MAP_TYPE_HASH = 1;
60 
61     private static final int BPF_F_NO_PREALLOC = 1;
62 
63     private static final int BPF_ANY = 0;
64     private static final int BPF_NOEXIST = 1;
65     private static final int BPF_EXIST = 2;
66 
67     private final ParcelFileDescriptor mMapFd;
68     private final Class<K> mKeyClass;
69     private final Class<V> mValueClass;
70     private final int mKeySize;
71     private final int mValueSize;
72 
73     private static ConcurrentHashMap<Pair<String, Integer>, ParcelFileDescriptor> sFdCache =
74             new ConcurrentHashMap<>();
75 
checkModeExclusivity(ParcelFileDescriptor fd, int mode)76     private static ParcelFileDescriptor checkModeExclusivity(ParcelFileDescriptor fd, int mode)
77             throws ErrnoException {
78         if (mode == BPF_F_RDWR_EXCLUSIVE) throw new ErrnoException("cachedBpfFdGet", EBUSY);
79         return fd;
80     }
81 
cachedBpfFdGet(String path, int mode, int keySize, int valueSize)82     private static ParcelFileDescriptor cachedBpfFdGet(String path, int mode,
83                                                        int keySize, int valueSize)
84             throws ErrnoException, NullPointerException {
85         // Supports up to 1023 byte key and 65535 byte values
86         // Creating a BpfMap with larger keys/values seems like a bad idea any way...
87         keySize &= 1023; // 10-bits
88         valueSize &= 65535; // 16-bits
89         var key = Pair.create(path, (mode << 26) ^ (keySize << 16) ^ valueSize);
90         // unlocked fetch is safe: map is concurrent read capable, and only inserted into
91         ParcelFileDescriptor fd = sFdCache.get(key);
92         if (fd != null) return checkModeExclusivity(fd, mode);
93         // ok, no cached fd present, need to grab a lock
94         synchronized (BpfMap.class) {
95             // need to redo the check
96             fd = sFdCache.get(key);
97             if (fd != null) return checkModeExclusivity(fd, mode);
98             // okay, we really haven't opened this before...
99             fd = ParcelFileDescriptor.adoptFd(nativeBpfFdGet(path, mode, keySize, valueSize));
100             sFdCache.put(key, fd);
101             return fd;
102         }
103     }
104 
105     /**
106      * Create a BpfMap map wrapper with "path" of filesystem.
107      *
108      * @param flag the access mode, one of BPF_F_RDWR, BPF_F_RDONLY, or BPF_F_WRONLY.
109      * @throws ErrnoException if the BPF map associated with {@code path} cannot be retrieved.
110      * @throws NullPointerException if {@code path} is null.
111      */
BpfMap(@onNull final String path, final int flag, final Class<K> key, final Class<V> value)112     public BpfMap(@NonNull final String path, final int flag, final Class<K> key,
113             final Class<V> value) throws ErrnoException, NullPointerException {
114         mKeyClass = key;
115         mValueClass = value;
116         mKeySize = Struct.getSize(key);
117         mValueSize = Struct.getSize(value);
118         mMapFd = cachedBpfFdGet(path, flag, mKeySize, mValueSize);
119     }
120 
121     /**
122      * Create a R/W BpfMap map wrapper with "path" of filesystem.
123      *
124      * @throws ErrnoException if the BPF map associated with {@code path} cannot be retrieved.
125      * @throws NullPointerException if {@code path} is null.
126      */
BpfMap(@onNull final String path, final Class<K> key, final Class<V> value)127     public BpfMap(@NonNull final String path, final Class<K> key,
128             final Class<V> value) throws ErrnoException, NullPointerException {
129         this(path, BPF_F_RDWR, key, value);
130     }
131 
132     /**
133      * Update an existing or create a new key -> value entry in an eBbpf map.
134      * (use insertOrReplaceEntry() if you need to know whether insert or replace happened)
135      */
136     @Override
updateEntry(K key, V value)137     public void updateEntry(K key, V value) throws ErrnoException {
138         nativeWriteToMapEntry(mMapFd.getFd(), key.writeToBytes(), value.writeToBytes(), BPF_ANY);
139     }
140 
141     /**
142      * If the key does not exist in the map, insert key -> value entry into eBpf map.
143      * Otherwise IllegalStateException will be thrown.
144      */
145     @Override
insertEntry(K key, V value)146     public void insertEntry(K key, V value)
147             throws ErrnoException, IllegalStateException {
148         try {
149             nativeWriteToMapEntry(mMapFd.getFd(), key.writeToBytes(), value.writeToBytes(),
150                     BPF_NOEXIST);
151         } catch (ErrnoException e) {
152             if (e.errno == EEXIST) throw new IllegalStateException(key + " already exists");
153 
154             throw e;
155         }
156     }
157 
158     /**
159      * If the key already exists in the map, replace its value. Otherwise NoSuchElementException
160      * will be thrown.
161      */
162     @Override
replaceEntry(K key, V value)163     public void replaceEntry(K key, V value)
164             throws ErrnoException, NoSuchElementException {
165         try {
166             nativeWriteToMapEntry(mMapFd.getFd(), key.writeToBytes(), value.writeToBytes(),
167                     BPF_EXIST);
168         } catch (ErrnoException e) {
169             if (e.errno == ENOENT) throw new NoSuchElementException(key + " not found");
170 
171             throw e;
172         }
173     }
174 
175     /**
176      * Update an existing or create a new key -> value entry in an eBbpf map.
177      * Returns true if inserted, false if replaced.
178      * (use updateEntry() if you don't care whether insert or replace happened)
179      * Note: see inline comment below if running concurrently with delete operations.
180      */
181     @Override
insertOrReplaceEntry(K key, V value)182     public boolean insertOrReplaceEntry(K key, V value)
183             throws ErrnoException {
184         try {
185             nativeWriteToMapEntry(mMapFd.getFd(), key.writeToBytes(), value.writeToBytes(),
186                     BPF_NOEXIST);
187             return true;   /* insert succeeded */
188         } catch (ErrnoException e) {
189             if (e.errno != EEXIST) throw e;
190         }
191         try {
192             nativeWriteToMapEntry(mMapFd.getFd(), key.writeToBytes(), value.writeToBytes(),
193                     BPF_EXIST);
194             return false;   /* replace succeeded */
195         } catch (ErrnoException e) {
196             if (e.errno != ENOENT) throw e;
197         }
198         /* If we reach here somebody deleted after our insert attempt and before our replace:
199          * this implies a race happened.  The kernel bpf delete interface only takes a key,
200          * and not the value, so we can safely pretend the replace actually succeeded and
201          * was immediately followed by the other thread's delete, since the delete cannot
202          * observe the potential change to the value.
203          */
204         return false;   /* pretend replace succeeded */
205     }
206 
207     /** Remove existing key from eBpf map. Return false if map was not modified. */
208     @Override
deleteEntry(K key)209     public boolean deleteEntry(K key) throws ErrnoException {
210         return nativeDeleteMapEntry(mMapFd.getFd(), key.writeToBytes());
211     }
212 
getNextKeyInternal(@ullable K key)213     private K getNextKeyInternal(@Nullable K key) throws ErrnoException {
214         byte[] rawKey = new byte[mKeySize];
215 
216         if (!nativeGetNextMapKey(mMapFd.getFd(),
217                                  key == null ? null : key.writeToBytes(),
218                                  rawKey)) return null;
219 
220         final ByteBuffer buffer = ByteBuffer.wrap(rawKey);
221         buffer.order(ByteOrder.nativeOrder());
222         return Struct.parse(mKeyClass, buffer);
223     }
224 
225     /**
226      * Get the next key of the passed-in key. If the passed-in key is not found, return the first
227      * key. If the passed-in key is the last one, return null.
228      *
229      * TODO: consider allowing null passed-in key.
230      */
231     @Override
getNextKey(@onNull K key)232     public K getNextKey(@NonNull K key) throws ErrnoException {
233         Objects.requireNonNull(key);
234         return getNextKeyInternal(key);
235     }
236 
237     /** Get the first key of eBpf map. */
238     @Override
getFirstKey()239     public K getFirstKey() throws ErrnoException {
240         return getNextKeyInternal(null);
241     }
242 
243     /** Check whether a key exists in the map. */
244     @Override
containsKey(@onNull K key)245     public boolean containsKey(@NonNull K key) throws ErrnoException {
246         Objects.requireNonNull(key);
247 
248         byte[] rawValue = new byte[mValueSize];
249         return nativeFindMapEntry(mMapFd.getFd(), key.writeToBytes(), rawValue);
250     }
251 
252     /** Retrieve a value from the map. Return null if there is no such key. */
253     @Override
getValue(@onNull K key)254     public V getValue(@NonNull K key) throws ErrnoException {
255         Objects.requireNonNull(key);
256 
257         byte[] rawValue = new byte[mValueSize];
258         if (!nativeFindMapEntry(mMapFd.getFd(), key.writeToBytes(), rawValue)) return null;
259 
260         final ByteBuffer buffer = ByteBuffer.wrap(rawValue);
261         buffer.order(ByteOrder.nativeOrder());
262         return Struct.parse(mValueClass, buffer);
263     }
264 
265     /** Synchronize Kernel RCU */
synchronizeKernelRCU()266     public static void synchronizeKernelRCU() throws ErrnoException {
267         nativeSynchronizeKernelRCU();
268     }
269 
nativeBpfFdGet(String path, int mode, int keySize, int valueSize)270     private static native int nativeBpfFdGet(String path, int mode, int keySize, int valueSize)
271             throws ErrnoException, NullPointerException;
272 
273     // Note: the following methods appear to not require the object by virtue of taking the
274     // fd as an int argument, but the hidden reference to this is actually what prevents
275     // the object from being garbage collected (and thus potentially maps closed) prior
276     // to the native code actually running (with a possibly already closed fd).
277 
nativeWriteToMapEntry(int fd, byte[] key, byte[] value, int flags)278     private native void nativeWriteToMapEntry(int fd, byte[] key, byte[] value, int flags)
279             throws ErrnoException;
280 
nativeDeleteMapEntry(int fd, byte[] key)281     private native boolean nativeDeleteMapEntry(int fd, byte[] key) throws ErrnoException;
282 
283     // If key is found, the operation returns true and the nextKey would reference to the next
284     // element.  If key is not found, the operation returns true and the nextKey would reference to
285     // the first element.  If key is the last element, false is returned.
nativeGetNextMapKey(int fd, byte[] key, byte[] nextKey)286     private native boolean nativeGetNextMapKey(int fd, byte[] key, byte[] nextKey)
287             throws ErrnoException;
288 
nativeFindMapEntry(int fd, byte[] key, byte[] value)289     private native boolean nativeFindMapEntry(int fd, byte[] key, byte[] value)
290             throws ErrnoException;
291 
nativeSynchronizeKernelRCU()292     private static native void nativeSynchronizeKernelRCU() throws ErrnoException;
293 }
294