1 /* 2 * Copyright (C) 2022 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.R_OK; 19 20 import android.system.ErrnoException; 21 import android.system.Os; 22 import android.util.Base64; 23 import android.util.Pair; 24 25 import androidx.annotation.NonNull; 26 import androidx.annotation.VisibleForTesting; 27 28 import java.io.PrintWriter; 29 import java.nio.ByteBuffer; 30 import java.nio.ByteOrder; 31 import java.util.function.BiFunction; 32 33 /** 34 * The classes and the methods for BPF dump utilization. 35 */ 36 public class BpfDump { 37 // Using "," as a separator between base64 encoded key and value is safe because base64 38 // characters are [0-9a-zA-Z/=+]. 39 private static final String BASE64_DELIMITER = ","; 40 41 /** 42 * Encode BPF key and value into a base64 format string which uses the delimiter ',': 43 * <base64 encoded key>,<base64 encoded value> 44 */ toBase64EncodedString( @onNull final K key, @NonNull final V value)45 public static <K extends Struct, V extends Struct> String toBase64EncodedString( 46 @NonNull final K key, @NonNull final V value) { 47 final byte[] keyBytes = key.writeToBytes(); 48 final String keyBase64Str = Base64.encodeToString(keyBytes, Base64.DEFAULT) 49 .replace("\n", ""); 50 final byte[] valueBytes = value.writeToBytes(); 51 final String valueBase64Str = Base64.encodeToString(valueBytes, Base64.DEFAULT) 52 .replace("\n", ""); 53 54 return keyBase64Str + BASE64_DELIMITER + valueBase64Str; 55 } 56 57 /** 58 * Decode Struct from a base64 format string 59 */ parseStruct( Class<T> structClass, @NonNull String base64Str)60 private static <T extends Struct> T parseStruct( 61 Class<T> structClass, @NonNull String base64Str) { 62 final byte[] bytes = Base64.decode(base64Str, Base64.DEFAULT); 63 final ByteBuffer byteBuffer = ByteBuffer.wrap(bytes); 64 byteBuffer.order(ByteOrder.nativeOrder()); 65 return Struct.parse(structClass, byteBuffer); 66 } 67 68 /** 69 * Decode BPF key and value from a base64 format string which uses the delimiter ',': 70 * <base64 encoded key>,<base64 encoded value> 71 */ fromBase64EncodedString( Class<K> keyClass, Class<V> valueClass, @NonNull String base64Str)72 public static <K extends Struct, V extends Struct> Pair<K, V> fromBase64EncodedString( 73 Class<K> keyClass, Class<V> valueClass, @NonNull String base64Str) { 74 String[] keyValueStrs = base64Str.split(BASE64_DELIMITER); 75 if (keyValueStrs.length != 2 /* key + value */) { 76 throw new IllegalArgumentException("Invalid base64Str (" + base64Str + "), base64Str" 77 + " must contain exactly one delimiter '" + BASE64_DELIMITER + "'"); 78 } 79 final K k = parseStruct(keyClass, keyValueStrs[0]); 80 final V v = parseStruct(valueClass, keyValueStrs[1]); 81 return new Pair<>(k, v); 82 } 83 84 /** 85 * Dump the BpfMap entries with base64 format encoding. 86 */ dumpRawMap(IBpfMap<K, V> map, PrintWriter pw)87 public static <K extends Struct, V extends Struct> void dumpRawMap(IBpfMap<K, V> map, 88 PrintWriter pw) { 89 try { 90 if (map == null) { 91 pw.println("BPF map is null"); 92 return; 93 } 94 if (map.isEmpty()) { 95 pw.println("No entries"); 96 return; 97 } 98 map.forEach((k, v) -> pw.println(toBase64EncodedString(k, v))); 99 } catch (ErrnoException e) { 100 pw.println("Map dump end with error: " + Os.strerror(e.errno)); 101 } 102 } 103 104 /** 105 * Dump the BpfMap name and entries 106 */ dumpMap(IBpfMap<K, V> map, PrintWriter pw, String mapName, BiFunction<K, V, String> entryToString)107 public static <K extends Struct, V extends Struct> void dumpMap(IBpfMap<K, V> map, 108 PrintWriter pw, String mapName, BiFunction<K, V, String> entryToString) { 109 dumpMap(map, pw, mapName, "" /* header */, entryToString); 110 } 111 112 /** 113 * Dump the BpfMap name, header, and entries 114 */ dumpMap(IBpfMap<K, V> map, PrintWriter pw, String mapName, String header, BiFunction<K, V, String> entryToString)115 public static <K extends Struct, V extends Struct> void dumpMap(IBpfMap<K, V> map, 116 PrintWriter pw, String mapName, String header, BiFunction<K, V, String> entryToString) { 117 pw.println(mapName + ":"); 118 if (!header.isEmpty()) { 119 pw.println(" " + header); 120 } 121 try { 122 map.forEach((key, value) -> { 123 // Value could be null if there is a concurrent entry deletion. 124 // http://b/220084230. 125 if (value != null) { 126 pw.println(" " + entryToString.apply(key, value)); 127 } else { 128 pw.println("Entry is deleted while dumping, iterating from first entry"); 129 } 130 }); 131 } catch (ErrnoException e) { 132 pw.println("Map dump end with error: " + Os.strerror(e.errno)); 133 } 134 } 135 136 public static class Dependencies { 137 /** 138 * Call {@link Os#access} 139 */ access(String path, int mode)140 public boolean access(String path, int mode) throws ErrnoException { 141 return Os.access(path, mode); 142 } 143 } 144 145 /** 146 * Dump the BpfMap status 147 */ dumpMapStatus(IBpfMap<K, V> map, PrintWriter pw, String mapName, String path)148 public static <K extends Struct, V extends Struct> void dumpMapStatus(IBpfMap<K, V> map, 149 PrintWriter pw, String mapName, String path) { 150 dumpMapStatus(map, pw, mapName, path, new Dependencies()); 151 } 152 153 /** 154 * Dump the BpfMap status. Only test should use this method directly. 155 */ 156 @VisibleForTesting dumpMapStatus(IBpfMap<K, V> map, PrintWriter pw, String mapName, String path, Dependencies deps)157 public static <K extends Struct, V extends Struct> void dumpMapStatus(IBpfMap<K, V> map, 158 PrintWriter pw, String mapName, String path, Dependencies deps) { 159 if (map != null) { 160 pw.println(mapName + ": OK"); 161 return; 162 } 163 try { 164 deps.access(path, R_OK); 165 pw.println(mapName + ": NULL(map is pinned to " + path + ")"); 166 } catch (ErrnoException e) { 167 pw.println(mapName + ": NULL(map is not pinned to " + path + ": " 168 + Os.strerror(e.errno) + ")"); 169 } 170 } 171 172 // TODO: add a helper to dump bpf map content with the map name, the header line 173 // (ex: "BPF ingress map: iif nat64Prefix v6Addr -> v4Addr oif"), a lambda that 174 // knows how to dump each line, and the PrintWriter. 175 } 176