• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 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 android.net.apf;
17 
18 import static com.android.net.module.util.NetworkStackConstants.IPV4_ADDR_ALL_HOST_MULTICAST;
19 
20 import android.annotation.NonNull;
21 import android.net.MacAddress;
22 import android.util.Log;
23 
24 import com.android.internal.annotations.VisibleForTesting;
25 import com.android.net.module.util.HexDump;
26 
27 import java.io.BufferedReader;
28 import java.io.IOException;
29 import java.net.Inet4Address;
30 import java.net.Inet6Address;
31 import java.net.InetAddress;
32 import java.net.UnknownHostException;
33 import java.nio.ByteBuffer;
34 import java.nio.ByteOrder;
35 import java.nio.charset.StandardCharsets;
36 import java.nio.file.Files;
37 import java.nio.file.Paths;
38 import java.util.ArrayList;
39 import java.util.List;
40 
41 public final class ProcfsParsingUtils {
42     public static final String TAG = ProcfsParsingUtils.class.getSimpleName();
43 
44     private static final String IPV6_CONF_PATH = "/proc/sys/net/ipv6/conf/";
45     private static final String IPV6_ANYCAST_PATH = "/proc/net/anycast6";
46     private static final String ETHER_MCAST_PATH = "/proc/net/dev_mcast";
47     private static final String IPV4_MCAST_PATH = "/proc/net/igmp";
48     private static final String IPV6_MCAST_PATH = "/proc/net/igmp6";
49     private static final String IPV4_DEFAULT_TTL_PATH = "/proc/sys/net/ipv4/ip_default_ttl";
50 
ProcfsParsingUtils()51     private ProcfsParsingUtils() {
52     }
53 
54     /**
55      * Reads the contents of a text file line by line.
56      *
57      * @param filePath The absolute path to the file to read.
58      * @return A List of Strings where each String represents a line from the file.
59      *         If an error occurs during reading, an empty list is returned, and an error is logged.
60      */
readFile(final String filePath)61     private static List<String> readFile(final String filePath) {
62         final List<String> lines = new ArrayList<>();
63         try (BufferedReader reader =
64                      Files.newBufferedReader(Paths.get(filePath), StandardCharsets.UTF_8)) {
65             String line;
66             while ((line = reader.readLine()) != null) {
67                 lines.add(line);
68             }
69         } catch (IOException e) {
70             Log.wtf(TAG, "failed to read " + filePath, e);
71         }
72 
73         return lines;
74     }
75 
76     /**
77      * Parses the Neighbor Discovery traffic class from a list of strings.
78      *
79      * This function expects a list containing a single string representing the ND traffic class.
80      * If the list is empty or contains multiple lines, it assumes a default traffic class of 0.
81      *
82      * @param lines A list of strings, ideally containing one line with the ND traffic class.
83      * @return The parsed ND traffic class as an integer, or 0 if the input is invalid.
84      */
85     @VisibleForTesting
parseNdTrafficClass(final List<String> lines)86     public static int parseNdTrafficClass(final List<String> lines) {
87         if (lines.size() != 1) {
88             return 0;   // default
89         }
90 
91         return Integer.parseInt(lines.get(0));
92     }
93 
94     /**
95      * Parses the default TTL value from the procfs file lines.
96      */
97     @VisibleForTesting
parseDefaultTtl(final List<String> lines)98     public static int parseDefaultTtl(final List<String> lines) {
99         if (lines.size() != 1) {
100             return 64;  // default ttl value as per rfc1700
101         }
102         try {
103             // ttl must be in the range [1, 255]
104             return Math.max(1, Math.min(255, Integer.parseInt(lines.get(0))));
105         } catch (NumberFormatException e) {
106             Log.e(TAG, "failed to parse default ttl.", e);
107             return 64; // default ttl value as per rfc1700
108         }
109     }
110 
111     /**
112      * Parses anycast6 addresses associated with a specific interface from a list of strings.
113      *
114      * This function searches the input list for a line containing the specified interface name.
115      * If found, it extracts the IPv6 address from that line and
116      * converts it into an `Inet6Address` object.
117      *
118      * @param lines   A list of strings where each line is expected to contain
119      *                interface and address information.
120      * @param ifname  The name of the network interface to search for.
121      * @return        A list of The `Inet6Address` representing the anycast address
122      *                associated with the specified interface,
123      *                If an error occurs during parsing, an empty list is returned.
124      */
125     @VisibleForTesting
parseAnycast6Addresses( @onNull List<String> lines, @NonNull String ifname)126     public static List<Inet6Address> parseAnycast6Addresses(
127             @NonNull List<String> lines, @NonNull String ifname) {
128         final List<Inet6Address> addresses = new ArrayList<>();
129         try {
130             for (String line : lines) {
131                 final String[] fields = line.split("\\s+");
132                 if (!fields[1].equals(ifname)) {
133                     continue;
134                 }
135 
136                 final byte[] addr = HexDump.hexStringToByteArray(fields[2]);
137                 addresses.add((Inet6Address) InetAddress.getByAddress(addr));
138             }
139         } catch (UnknownHostException e) {
140             Log.wtf("failed to convert to Inet6Address.", e);
141             addresses.clear();
142         }
143         return addresses;
144     }
145 
146     /**
147      * Parses Ethernet multicast MAC addresses with a specific interface from a list of strings.
148      *
149      * @param lines A list of strings, each containing interface and MAC address information.
150      * @param ifname The name of the network interface for which to extract multicast addresses.
151      * @return A list of MacAddress objects representing the parsed multicast addresses.
152      */
153     @VisibleForTesting
parseEtherMulticastAddresses( @onNull List<String> lines, @NonNull String ifname)154     public static List<MacAddress> parseEtherMulticastAddresses(
155             @NonNull List<String> lines, @NonNull String ifname) {
156         final List<MacAddress> addresses = new ArrayList<>();
157         for (String line: lines) {
158             final String[] fields = line.split("\\s+");
159             if (!fields[1].equals(ifname)) {
160                 continue;
161             }
162 
163             final byte[] addr = HexDump.hexStringToByteArray(fields[4]);
164             addresses.add(MacAddress.fromBytes(addr));
165         }
166 
167         return addresses;
168     }
169 
170     /**
171      * Parses IPv6 multicast addresses associated with a specific interface from a list of strings.
172      *
173      * @param lines A list of strings, each containing interface and IPv6 address information.
174      * @param ifname The name of the network interface for which to extract multicast addresses.
175      * @return A list of Inet6Address objects representing the parsed IPv6 multicast addresses.
176      *         If an error occurs during parsing, an empty list is returned.
177      */
178     @VisibleForTesting
parseIPv6MulticastAddresses( @onNull List<String> lines, @NonNull String ifname)179     public static List<Inet6Address> parseIPv6MulticastAddresses(
180             @NonNull List<String> lines, @NonNull String ifname) {
181         final List<Inet6Address> addresses = new ArrayList<>();
182         try {
183             for (String line: lines) {
184                 final String[] fields = line.split("\\s+");
185                 if (!fields[1].equals(ifname)) {
186                     continue;
187                 }
188 
189                 final byte[] addr = HexDump.hexStringToByteArray(fields[2]);
190                 addresses.add((Inet6Address) InetAddress.getByAddress(addr));
191             }
192         } catch (UnknownHostException e) {
193             Log.wtf(TAG, "failed to convert to Inet6Address.", e);
194             addresses.clear();
195         }
196 
197         return addresses;
198     }
199 
200     /**
201      * Parses IPv4 multicast addresses associated with a specific interface from a list of strings.
202      *
203      * @param lines A list of strings, each containing interface and IPv4 address information.
204      * @param ifname The name of the network interface for which to extract multicast addresses.
205      * @param endian The byte order of the address, almost always use native order.
206      * @return A list of Inet4Address objects representing the parsed IPv4 multicast addresses.
207      *         If an error occurs during parsing,
208      *         a list contains IPv4 all host (224.0.0.1) is returned.
209      */
210     @VisibleForTesting
parseIPv4MulticastAddresses( @onNull List<String> lines, @NonNull String ifname, @NonNull ByteOrder endian)211     public static List<Inet4Address> parseIPv4MulticastAddresses(
212             @NonNull List<String> lines, @NonNull String ifname, @NonNull ByteOrder endian) {
213         final List<Inet4Address> ipAddresses = new ArrayList<>();
214 
215         try {
216             String name = "";
217             // parse output similar to `ip maddr` command (iproute2/ip/ipmaddr.c#read_igmp())
218             for (String line : lines) {
219                 final String[] parts = line.trim().split("\\s+");
220                 if (!line.startsWith("\t")) {
221                     name = parts[1];
222                     if (name.endsWith(":")) {
223                         name = name.substring(0, name.length() - 1);
224                     }
225                     continue;
226                 }
227 
228                 if (!name.equals(ifname)) {
229                     continue;
230                 }
231 
232                 final String hexIp = parts[0];
233                 final byte[] ipArray = HexDump.hexStringToByteArray(hexIp);
234                 final byte[] convertArray =
235                     (endian == ByteOrder.LITTLE_ENDIAN)
236                         ? convertIPv4BytesToBigEndian(ipArray) : ipArray;
237                 final Inet4Address ipv4Address =
238                         (Inet4Address) InetAddress.getByAddress(convertArray);
239 
240                 ipAddresses.add(ipv4Address);
241             }
242         } catch (Exception e) {
243             Log.wtf(TAG, "failed to convert to Inet4Address.", e);
244             // always return IPv4 all host address (224.0.0.1) if any error during parsing.
245             // this aligns with kernel behavior, it will join 224.0.0.1 when the interface is up.
246             ipAddresses.clear();
247             ipAddresses.add(IPV4_ADDR_ALL_HOST_MULTICAST);
248         }
249 
250         return ipAddresses;
251     }
252 
253     /**
254      * Converts an IPv4 address from little-endian byte order to big-endian byte order.
255      *
256      * @param bytes The IPv4 address in little-endian byte order.
257      * @return The IPv4 address in big-endian byte order.
258      */
convertIPv4BytesToBigEndian(byte[] bytes)259     private static byte[] convertIPv4BytesToBigEndian(byte[] bytes) {
260         final ByteBuffer buffer = ByteBuffer.wrap(bytes);
261         buffer.order(ByteOrder.LITTLE_ENDIAN);
262         final ByteBuffer bigEndianBuffer = ByteBuffer.allocate(4);
263         bigEndianBuffer.order(ByteOrder.BIG_ENDIAN);
264         bigEndianBuffer.putInt(buffer.getInt());
265         return bigEndianBuffer.array();
266     }
267 
268     /**
269      * Returns the default TTL value for IPv4 packets.
270      */
getIpv4DefaultTtl()271     public static int getIpv4DefaultTtl() {
272         return parseDefaultTtl(readFile(IPV4_DEFAULT_TTL_PATH));
273     }
274 
275     /**
276      * Returns the default HopLimit value for IPv6 packets.
277      */
getIpv6DefaultHopLimit(@onNull String ifname)278     public static int getIpv6DefaultHopLimit(@NonNull String ifname) {
279         final String hopLimitPath = IPV6_CONF_PATH + ifname + "/hop_limit";
280         return parseDefaultTtl(readFile(hopLimitPath));
281     }
282 
283     /**
284      * Returns the traffic class for the specified interface.
285      * The function loads the existing traffic class from the file
286      * `/proc/sys/net/ipv6/conf/{ifname}/ndisc_tclass`. If the file does not exist, the
287      * function returns 0.
288      *
289      * @param ifname The name of the interface.
290      * @return The traffic class for the interface.
291      */
getNdTrafficClass(final String ifname)292     public static int getNdTrafficClass(final String ifname) {
293         final String ndTcPath = IPV6_CONF_PATH + ifname + "/ndisc_tclass";
294         final List<String> lines = readFile(ndTcPath);
295         return parseNdTrafficClass(lines);
296     }
297 
298     /**
299      * The function loads the existing IPv6 anycast address from the file `/proc/net/anycast6`.
300      * If the file does not exist or the interface is not found, the function
301      * returns an empty list.
302      *
303      * @param ifname The name of the interface.
304      * @return A list of the IPv6 anycast addresses for the interface.
305      */
getAnycast6Addresses(@onNull String ifname)306     public static List<Inet6Address> getAnycast6Addresses(@NonNull String ifname) {
307         final List<String> lines = readFile(IPV6_ANYCAST_PATH);
308         return parseAnycast6Addresses(lines, ifname);
309     }
310 
311     /**
312      * The function loads the existing Ethernet multicast addresses from
313      * the file `/proc/net/dev_mcast`.
314      * If the file does not exist or the interface is not found, the function returns empty list.
315      *
316      * @param ifname The name of the interface.
317      * @return A list of MacAddress objects representing the multicast addresses
318      *         found for the interface.
319      *         If the file cannot be read or there are no addresses, an empty list is returned.
320      */
getEtherMulticastAddresses(@onNull String ifname)321     public static List<MacAddress> getEtherMulticastAddresses(@NonNull String ifname) {
322         final List<String> lines = readFile(ETHER_MCAST_PATH);
323         return parseEtherMulticastAddresses(lines, ifname);
324     }
325 
326     /**
327      * The function loads the existing IPv6 multicast addresses from the file `/proc/net/igmp6`.
328      * If the file does not exist or the interface is not found, the function returns empty list.
329      *
330      * @param ifname The name of the network interface to query.
331      * @return A list of Inet6Address objects representing the IPv6 multicast addresses
332      *         found for the interface.
333      *         If the file cannot be read or there are no addresses, an empty list is returned.
334      */
getIpv6MulticastAddresses(@onNull String ifname)335     public static List<Inet6Address> getIpv6MulticastAddresses(@NonNull String ifname) {
336         final List<String> lines = readFile(IPV6_MCAST_PATH);
337         return parseIPv6MulticastAddresses(lines, ifname);
338     }
339 
340     /**
341      * The function loads the existing IPv4 multicast addresses from the file `/proc/net/igmp6`.
342      * If the file does not exist or the interface is not found, the function returns empty list.
343      *
344      * @param ifname The name of the network interface to query.
345      * @return A list of Inet4Address objects representing the IPv4 multicast addresses
346      *         found for the interface.
347      *         If the file cannot be read or there are no addresses, an empty list is returned.
348      */
getIPv4MulticastAddresses(@onNull String ifname)349     public static List<Inet4Address> getIPv4MulticastAddresses(@NonNull String ifname) {
350         final List<String> lines = readFile(IPV4_MCAST_PATH);
351         // follow the same pattern as NetlinkMonitor#handlePacket() for device's endian order
352         return parseIPv4MulticastAddresses(lines, ifname, ByteOrder.nativeOrder());
353     }
354 }
355