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