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