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