• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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