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