blob: 2534d61612b837747cbf8de50fc94a7dc9d9021a [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.net.module.util;
import android.system.ErrnoException;
import android.system.Os;
import android.util.Base64;
import android.util.Pair;
import androidx.annotation.NonNull;
import java.io.PrintWriter;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.function.BiFunction;
/**
* The classes and the methods for BPF dump utilization.
*/
public class BpfDump {
// Using "," as a separator between base64 encoded key and value is safe because base64
// characters are [0-9a-zA-Z/=+].
private static final String BASE64_DELIMITER = ",";
/**
* Encode BPF key and value into a base64 format string which uses the delimiter ',':
* <base64 encoded key>,<base64 encoded value>
*/
public static <K extends Struct, V extends Struct> String toBase64EncodedString(
@NonNull final K key, @NonNull final V value) {
final byte[] keyBytes = key.writeToBytes();
final String keyBase64Str = Base64.encodeToString(keyBytes, Base64.DEFAULT)
.replace("\n", "");
final byte[] valueBytes = value.writeToBytes();
final String valueBase64Str = Base64.encodeToString(valueBytes, Base64.DEFAULT)
.replace("\n", "");
return keyBase64Str + BASE64_DELIMITER + valueBase64Str;
}
/**
* Decode Struct from a base64 format string
*/
private static <T extends Struct> T parseStruct(
Class<T> structClass, @NonNull String base64Str) {
final byte[] bytes = Base64.decode(base64Str, Base64.DEFAULT);
final ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
byteBuffer.order(ByteOrder.nativeOrder());
return Struct.parse(structClass, byteBuffer);
}
/**
* Decode BPF key and value from a base64 format string which uses the delimiter ',':
* <base64 encoded key>,<base64 encoded value>
*/
public static <K extends Struct, V extends Struct> Pair<K, V> fromBase64EncodedString(
Class<K> keyClass, Class<V> valueClass, @NonNull String base64Str) {
String[] keyValueStrs = base64Str.split(BASE64_DELIMITER);
if (keyValueStrs.length != 2 /* key + value */) {
throw new IllegalArgumentException("Invalid base64Str (" + base64Str + "), base64Str"
+ " must contain exactly one delimiter '" + BASE64_DELIMITER + "'");
}
final K k = parseStruct(keyClass, keyValueStrs[0]);
final V v = parseStruct(valueClass, keyValueStrs[1]);
return new Pair<>(k, v);
}
/**
* Dump the BpfMap name and entries
*/
public static <K extends Struct, V extends Struct> void dumpMap(IBpfMap<K, V> map,
PrintWriter pw, String mapName, BiFunction<K, V, String> entryToString) {
dumpMap(map, pw, mapName, "" /* header */, entryToString);
}
/**
* Dump the BpfMap name, header, and entries
*/
public static <K extends Struct, V extends Struct> void dumpMap(IBpfMap<K, V> map,
PrintWriter pw, String mapName, String header, BiFunction<K, V, String> entryToString) {
pw.println(mapName + ":");
if (!header.isEmpty()) {
pw.println(" " + header);
}
try {
map.forEach((key, value) -> {
// Value could be null if there is a concurrent entry deletion.
// http://b/220084230.
if (value != null) {
pw.println(" " + entryToString.apply(key, value));
} else {
pw.println("Entry is deleted while dumping, iterating from first entry");
}
});
} catch (ErrnoException e) {
pw.println("Map dump end with error: " + Os.strerror(e.errno));
}
}
// TODO: add a helper to dump bpf map content with the map name, the header line
// (ex: "BPF ingress map: iif nat64Prefix v6Addr -> v4Addr oif"), a lambda that
// knows how to dump each line, and the PrintWriter.
}