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.networkstack.tethering;
17 
18 import static android.system.OsConstants.EEXIST;
19 import static android.system.OsConstants.ENOENT;
20 
21 import android.system.ErrnoException;
22 
23 import androidx.annotation.NonNull;
24 import androidx.annotation.Nullable;
25 
26 import com.android.internal.annotations.VisibleForTesting;
27 import com.android.net.module.util.Struct;
28 
29 import java.nio.ByteBuffer;
30 import java.nio.ByteOrder;
31 import java.util.NoSuchElementException;
32 import java.util.Objects;
33 import java.util.function.BiConsumer;
34 
35 /**
36  * BpfMap is a key -> value mapping structure that is designed to maintained the bpf map entries.
37  * This is a wrapper class of in-kernel data structure. The in-kernel data can be read/written by
38  * passing syscalls with map file descriptor.
39  *
40  * @param <K> the key of the map.
41  * @param <V> the value of the map.
42  */
43 public class BpfMap<K extends Struct, V extends Struct> implements AutoCloseable {
44     static {
45         System.loadLibrary("tetherutilsjni");
46     }
47 
48     // Following definitions from kernel include/uapi/linux/bpf.h
49     public static final int BPF_F_RDWR = 0;
50     public static final int BPF_F_RDONLY = 1 << 3;
51     public static final int BPF_F_WRONLY = 1 << 4;
52 
53     public static final int BPF_MAP_TYPE_HASH = 1;
54 
55     private static final int BPF_F_NO_PREALLOC = 1;
56 
57     private static final int BPF_ANY = 0;
58     private static final int BPF_NOEXIST = 1;
59     private static final int BPF_EXIST = 2;
60 
61     private final int mMapFd;
62     private final Class<K> mKeyClass;
63     private final Class<V> mValueClass;
64     private final int mKeySize;
65     private final int mValueSize;
66 
67     /**
68      * Create a BpfMap map wrapper with "path" of filesystem.
69      *
70      * @param flag the access mode, one of BPF_F_RDWR, BPF_F_RDONLY, or BPF_F_WRONLY.
71      * @throws ErrnoException if the BPF map associated with {@code path} cannot be retrieved.
72      * @throws NullPointerException if {@code path} is null.
73      */
BpfMap(@onNull final String path, final int flag, final Class<K> key, final Class<V> value)74     public BpfMap(@NonNull final String path, final int flag, final Class<K> key,
75             final Class<V> value) throws ErrnoException, NullPointerException {
76         mMapFd = bpfFdGet(path, flag);
77 
78         mKeyClass = key;
79         mValueClass = value;
80         mKeySize = Struct.getSize(key);
81         mValueSize = Struct.getSize(value);
82     }
83 
84      /**
85      * Constructor for testing only.
86      * The derived class implements an internal mocked map. It need to implement all functions
87      * which are related with the native BPF map because the BPF map handler is not initialized.
88      * See BpfCoordinatorTest#TestBpfMap.
89      */
90     @VisibleForTesting
BpfMap(final Class<K> key, final Class<V> value)91     protected BpfMap(final Class<K> key, final Class<V> value) {
92         mMapFd = -1;
93         mKeyClass = key;
94         mValueClass = value;
95         mKeySize = Struct.getSize(key);
96         mValueSize = Struct.getSize(value);
97     }
98 
99     /**
100      * Update an existing or create a new key -> value entry in an eBbpf map.
101      * (use insertOrReplaceEntry() if you need to know whether insert or replace happened)
102      */
updateEntry(K key, V value)103     public void updateEntry(K key, V value) throws ErrnoException {
104         writeToMapEntry(mMapFd, key.writeToBytes(), value.writeToBytes(), BPF_ANY);
105     }
106 
107     /**
108      * If the key does not exist in the map, insert key -> value entry into eBpf map.
109      * Otherwise IllegalStateException will be thrown.
110      */
insertEntry(K key, V value)111     public void insertEntry(K key, V value)
112             throws ErrnoException, IllegalStateException {
113         try {
114             writeToMapEntry(mMapFd, key.writeToBytes(), value.writeToBytes(), BPF_NOEXIST);
115         } catch (ErrnoException e) {
116             if (e.errno == EEXIST) throw new IllegalStateException(key + " already exists");
117 
118             throw e;
119         }
120     }
121 
122     /**
123      * If the key already exists in the map, replace its value. Otherwise NoSuchElementException
124      * will be thrown.
125      */
replaceEntry(K key, V value)126     public void replaceEntry(K key, V value)
127             throws ErrnoException, NoSuchElementException {
128         try {
129             writeToMapEntry(mMapFd, key.writeToBytes(), value.writeToBytes(), BPF_EXIST);
130         } catch (ErrnoException e) {
131             if (e.errno == ENOENT) throw new NoSuchElementException(key + " not found");
132 
133             throw e;
134         }
135     }
136 
137     /**
138      * Update an existing or create a new key -> value entry in an eBbpf map.
139      * Returns true if inserted, false if replaced.
140      * (use updateEntry() if you don't care whether insert or replace happened)
141      * Note: see inline comment below if running concurrently with delete operations.
142      */
insertOrReplaceEntry(K key, V value)143     public boolean insertOrReplaceEntry(K key, V value)
144             throws ErrnoException {
145         try {
146             writeToMapEntry(mMapFd, key.writeToBytes(), value.writeToBytes(), BPF_NOEXIST);
147             return true;   /* insert succeeded */
148         } catch (ErrnoException e) {
149             if (e.errno != EEXIST) throw e;
150         }
151         try {
152             writeToMapEntry(mMapFd, key.writeToBytes(), value.writeToBytes(), BPF_EXIST);
153             return false;   /* replace succeeded */
154         } catch (ErrnoException e) {
155             if (e.errno != ENOENT) throw e;
156         }
157         /* If we reach here somebody deleted after our insert attempt and before our replace:
158          * this implies a race happened.  The kernel bpf delete interface only takes a key,
159          * and not the value, so we can safely pretend the replace actually succeeded and
160          * was immediately followed by the other thread's delete, since the delete cannot
161          * observe the potential change to the value.
162          */
163         return false;   /* pretend replace succeeded */
164     }
165 
166     /** Remove existing key from eBpf map. Return false if map was not modified. */
deleteEntry(K key)167     public boolean deleteEntry(K key) throws ErrnoException {
168         return deleteMapEntry(mMapFd, key.writeToBytes());
169     }
170 
171     /** Returns {@code true} if this map contains no elements. */
isEmpty()172     public boolean isEmpty() throws ErrnoException {
173         return getFirstKey() == null;
174     }
175 
getNextKeyInternal(@ullable K key)176     private K getNextKeyInternal(@Nullable K key) throws ErrnoException {
177         final byte[] rawKey = getNextRawKey(
178                 key == null ? null : key.writeToBytes());
179         if (rawKey == null) return null;
180 
181         final ByteBuffer buffer = ByteBuffer.wrap(rawKey);
182         buffer.order(ByteOrder.nativeOrder());
183         return Struct.parse(mKeyClass, buffer);
184     }
185 
186     /**
187      * Get the next key of the passed-in key. If the passed-in key is not found, return the first
188      * key. If the passed-in key is the last one, return null.
189      *
190      * TODO: consider allowing null passed-in key.
191      */
getNextKey(@onNull K key)192     public K getNextKey(@NonNull K key) throws ErrnoException {
193         Objects.requireNonNull(key);
194         return getNextKeyInternal(key);
195     }
196 
getNextRawKey(@ullable final byte[] key)197     private byte[] getNextRawKey(@Nullable final byte[] key) throws ErrnoException {
198         byte[] nextKey = new byte[mKeySize];
199         if (getNextMapKey(mMapFd, key, nextKey)) return nextKey;
200 
201         return null;
202     }
203 
204     /** Get the first key of eBpf map. */
getFirstKey()205     public K getFirstKey() throws ErrnoException {
206         return getNextKeyInternal(null);
207     }
208 
209     /** Check whether a key exists in the map. */
containsKey(@onNull K key)210     public boolean containsKey(@NonNull K key) throws ErrnoException {
211         Objects.requireNonNull(key);
212 
213         final byte[] rawValue = getRawValue(key.writeToBytes());
214         return rawValue != null;
215     }
216 
217     /** Retrieve a value from the map. Return null if there is no such key. */
getValue(@onNull K key)218     public V getValue(@NonNull K key) throws ErrnoException {
219         Objects.requireNonNull(key);
220         final byte[] rawValue = getRawValue(key.writeToBytes());
221 
222         if (rawValue == null) return null;
223 
224         final ByteBuffer buffer = ByteBuffer.wrap(rawValue);
225         buffer.order(ByteOrder.nativeOrder());
226         return Struct.parse(mValueClass, buffer);
227     }
228 
getRawValue(final byte[] key)229     private byte[] getRawValue(final byte[] key) throws ErrnoException {
230         byte[] value = new byte[mValueSize];
231         if (findMapEntry(mMapFd, key, value)) return value;
232 
233         return null;
234     }
235 
236     /**
237      * Iterate through the map and handle each key -> value retrieved base on the given BiConsumer.
238      * The given BiConsumer may to delete the passed-in entry, but is not allowed to perform any
239      * other structural modifications to the map, such as adding entries or deleting other entries.
240      * Otherwise, iteration will result in undefined behaviour.
241      */
forEach(BiConsumer<K, V> action)242     public void forEach(BiConsumer<K, V> action) throws ErrnoException {
243         @Nullable K nextKey = getFirstKey();
244 
245         while (nextKey != null) {
246             @NonNull final K curKey = nextKey;
247             @NonNull final V value = getValue(curKey);
248 
249             nextKey = getNextKey(curKey);
250             action.accept(curKey, value);
251         }
252     }
253 
254     @Override
close()255     public void close() throws ErrnoException {
256         closeMap(mMapFd);
257     }
258 
259     /**
260      * Clears the map. The map may already be empty.
261      *
262      * @throws ErrnoException if the map is already closed, if an error occurred during iteration,
263      *                        or if a non-ENOENT error occurred when deleting a key.
264      */
clear()265     public void clear() throws ErrnoException {
266         K key = getFirstKey();
267         while (key != null) {
268             deleteEntry(key);  // ignores ENOENT.
269             key = getFirstKey();
270         }
271     }
272 
closeMap(int fd)273     private static native int closeMap(int fd) throws ErrnoException;
274 
bpfFdGet(String path, int mode)275     private native int bpfFdGet(String path, int mode) throws ErrnoException, NullPointerException;
276 
writeToMapEntry(int fd, byte[] key, byte[] value, int flags)277     private native void writeToMapEntry(int fd, byte[] key, byte[] value, int flags)
278             throws ErrnoException;
279 
deleteMapEntry(int fd, byte[] key)280     private native boolean deleteMapEntry(int fd, byte[] key) throws ErrnoException;
281 
282     // If key is found, the operation returns true and the nextKey would reference to the next
283     // element.  If key is not found, the operation returns true and the nextKey would reference to
284     // the first element.  If key is the last element, false is returned.
getNextMapKey(int fd, byte[] key, byte[] nextKey)285     private native boolean getNextMapKey(int fd, byte[] key, byte[] nextKey) throws ErrnoException;
286 
findMapEntry(int fd, byte[] key, byte[] value)287     private native boolean findMapEntry(int fd, byte[] key, byte[] value) throws ErrnoException;
288 }
289