/* * Copyright (C) 2024 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 android.net.apf; import static com.android.net.module.util.NetworkStackConstants.IPV4_ADDR_ALL_HOST_MULTICAST; import android.annotation.NonNull; import android.net.MacAddress; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.net.module.util.HexDump; import java.io.BufferedReader; import java.io.IOException; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.net.UnknownHostException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; public final class ProcfsParsingUtils { public static final String TAG = ProcfsParsingUtils.class.getSimpleName(); private static final String IPV6_CONF_PATH = "/proc/sys/net/ipv6/conf/"; private static final String IPV6_ANYCAST_PATH = "/proc/net/anycast6"; private static final String ETHER_MCAST_PATH = "/proc/net/dev_mcast"; private static final String IPV4_MCAST_PATH = "/proc/net/igmp"; private static final String IPV6_MCAST_PATH = "/proc/net/igmp6"; private static final String IPV4_DEFAULT_TTL_PATH = "/proc/sys/net/ipv4/ip_default_ttl"; private ProcfsParsingUtils() { } /** * Reads the contents of a text file line by line. * * @param filePath The absolute path to the file to read. * @return A List of Strings where each String represents a line from the file. * If an error occurs during reading, an empty list is returned, and an error is logged. */ private static List readFile(final String filePath) { final List lines = new ArrayList<>(); try (BufferedReader reader = Files.newBufferedReader(Paths.get(filePath), StandardCharsets.UTF_8)) { String line; while ((line = reader.readLine()) != null) { lines.add(line); } } catch (IOException e) { Log.wtf(TAG, "failed to read " + filePath, e); } return lines; } /** * Parses the Neighbor Discovery traffic class from a list of strings. * * This function expects a list containing a single string representing the ND traffic class. * If the list is empty or contains multiple lines, it assumes a default traffic class of 0. * * @param lines A list of strings, ideally containing one line with the ND traffic class. * @return The parsed ND traffic class as an integer, or 0 if the input is invalid. */ @VisibleForTesting public static int parseNdTrafficClass(final List lines) { if (lines.size() != 1) { return 0; // default } return Integer.parseInt(lines.get(0)); } /** * Parses the default TTL value from the procfs file lines. */ @VisibleForTesting public static int parseDefaultTtl(final List lines) { if (lines.size() != 1) { return 64; // default ttl value as per rfc1700 } try { // ttl must be in the range [1, 255] return Math.max(1, Math.min(255, Integer.parseInt(lines.get(0)))); } catch (NumberFormatException e) { Log.e(TAG, "failed to parse default ttl.", e); return 64; // default ttl value as per rfc1700 } } /** * Parses anycast6 addresses associated with a specific interface from a list of strings. * * This function searches the input list for a line containing the specified interface name. * If found, it extracts the IPv6 address from that line and * converts it into an `Inet6Address` object. * * @param lines A list of strings where each line is expected to contain * interface and address information. * @param ifname The name of the network interface to search for. * @return A list of The `Inet6Address` representing the anycast address * associated with the specified interface, * If an error occurs during parsing, an empty list is returned. */ @VisibleForTesting public static List parseAnycast6Addresses( @NonNull List lines, @NonNull String ifname) { final List addresses = new ArrayList<>(); try { for (String line : lines) { final String[] fields = line.split("\\s+"); if (!fields[1].equals(ifname)) { continue; } final byte[] addr = HexDump.hexStringToByteArray(fields[2]); addresses.add((Inet6Address) InetAddress.getByAddress(addr)); } } catch (UnknownHostException e) { Log.wtf("failed to convert to Inet6Address.", e); addresses.clear(); } return addresses; } /** * Parses Ethernet multicast MAC addresses with a specific interface from a list of strings. * * @param lines A list of strings, each containing interface and MAC address information. * @param ifname The name of the network interface for which to extract multicast addresses. * @return A list of MacAddress objects representing the parsed multicast addresses. */ @VisibleForTesting public static List parseEtherMulticastAddresses( @NonNull List lines, @NonNull String ifname) { final List addresses = new ArrayList<>(); for (String line: lines) { final String[] fields = line.split("\\s+"); if (!fields[1].equals(ifname)) { continue; } final byte[] addr = HexDump.hexStringToByteArray(fields[4]); addresses.add(MacAddress.fromBytes(addr)); } return addresses; } /** * Parses IPv6 multicast addresses associated with a specific interface from a list of strings. * * @param lines A list of strings, each containing interface and IPv6 address information. * @param ifname The name of the network interface for which to extract multicast addresses. * @return A list of Inet6Address objects representing the parsed IPv6 multicast addresses. * If an error occurs during parsing, an empty list is returned. */ @VisibleForTesting public static List parseIPv6MulticastAddresses( @NonNull List lines, @NonNull String ifname) { final List addresses = new ArrayList<>(); try { for (String line: lines) { final String[] fields = line.split("\\s+"); if (!fields[1].equals(ifname)) { continue; } final byte[] addr = HexDump.hexStringToByteArray(fields[2]); addresses.add((Inet6Address) InetAddress.getByAddress(addr)); } } catch (UnknownHostException e) { Log.wtf(TAG, "failed to convert to Inet6Address.", e); addresses.clear(); } return addresses; } /** * Parses IPv4 multicast addresses associated with a specific interface from a list of strings. * * @param lines A list of strings, each containing interface and IPv4 address information. * @param ifname The name of the network interface for which to extract multicast addresses. * @param endian The byte order of the address, almost always use native order. * @return A list of Inet4Address objects representing the parsed IPv4 multicast addresses. * If an error occurs during parsing, * a list contains IPv4 all host (224.0.0.1) is returned. */ @VisibleForTesting public static List parseIPv4MulticastAddresses( @NonNull List lines, @NonNull String ifname, @NonNull ByteOrder endian) { final List ipAddresses = new ArrayList<>(); try { String name = ""; // parse output similar to `ip maddr` command (iproute2/ip/ipmaddr.c#read_igmp()) for (String line : lines) { final String[] parts = line.trim().split("\\s+"); if (!line.startsWith("\t")) { name = parts[1]; if (name.endsWith(":")) { name = name.substring(0, name.length() - 1); } continue; } if (!name.equals(ifname)) { continue; } final String hexIp = parts[0]; final byte[] ipArray = HexDump.hexStringToByteArray(hexIp); final byte[] convertArray = (endian == ByteOrder.LITTLE_ENDIAN) ? convertIPv4BytesToBigEndian(ipArray) : ipArray; final Inet4Address ipv4Address = (Inet4Address) InetAddress.getByAddress(convertArray); ipAddresses.add(ipv4Address); } } catch (Exception e) { Log.wtf(TAG, "failed to convert to Inet4Address.", e); // always return IPv4 all host address (224.0.0.1) if any error during parsing. // this aligns with kernel behavior, it will join 224.0.0.1 when the interface is up. ipAddresses.clear(); ipAddresses.add(IPV4_ADDR_ALL_HOST_MULTICAST); } return ipAddresses; } /** * Converts an IPv4 address from little-endian byte order to big-endian byte order. * * @param bytes The IPv4 address in little-endian byte order. * @return The IPv4 address in big-endian byte order. */ private static byte[] convertIPv4BytesToBigEndian(byte[] bytes) { final ByteBuffer buffer = ByteBuffer.wrap(bytes); buffer.order(ByteOrder.LITTLE_ENDIAN); final ByteBuffer bigEndianBuffer = ByteBuffer.allocate(4); bigEndianBuffer.order(ByteOrder.BIG_ENDIAN); bigEndianBuffer.putInt(buffer.getInt()); return bigEndianBuffer.array(); } /** * Returns the default TTL value for IPv4 packets. */ public static int getIpv4DefaultTtl() { return parseDefaultTtl(readFile(IPV4_DEFAULT_TTL_PATH)); } /** * Returns the default HopLimit value for IPv6 packets. */ public static int getIpv6DefaultHopLimit(@NonNull String ifname) { final String hopLimitPath = IPV6_CONF_PATH + ifname + "/hop_limit"; return parseDefaultTtl(readFile(hopLimitPath)); } /** * Returns the traffic class for the specified interface. * The function loads the existing traffic class from the file * `/proc/sys/net/ipv6/conf/{ifname}/ndisc_tclass`. If the file does not exist, the * function returns 0. * * @param ifname The name of the interface. * @return The traffic class for the interface. */ public static int getNdTrafficClass(final String ifname) { final String ndTcPath = IPV6_CONF_PATH + ifname + "/ndisc_tclass"; final List lines = readFile(ndTcPath); return parseNdTrafficClass(lines); } /** * The function loads the existing IPv6 anycast address from the file `/proc/net/anycast6`. * If the file does not exist or the interface is not found, the function * returns an empty list. * * @param ifname The name of the interface. * @return A list of the IPv6 anycast addresses for the interface. */ public static List getAnycast6Addresses(@NonNull String ifname) { final List lines = readFile(IPV6_ANYCAST_PATH); return parseAnycast6Addresses(lines, ifname); } /** * The function loads the existing Ethernet multicast addresses from * the file `/proc/net/dev_mcast`. * If the file does not exist or the interface is not found, the function returns empty list. * * @param ifname The name of the interface. * @return A list of MacAddress objects representing the multicast addresses * found for the interface. * If the file cannot be read or there are no addresses, an empty list is returned. */ public static List getEtherMulticastAddresses(@NonNull String ifname) { final List lines = readFile(ETHER_MCAST_PATH); return parseEtherMulticastAddresses(lines, ifname); } /** * The function loads the existing IPv6 multicast addresses from the file `/proc/net/igmp6`. * If the file does not exist or the interface is not found, the function returns empty list. * * @param ifname The name of the network interface to query. * @return A list of Inet6Address objects representing the IPv6 multicast addresses * found for the interface. * If the file cannot be read or there are no addresses, an empty list is returned. */ public static List getIpv6MulticastAddresses(@NonNull String ifname) { final List lines = readFile(IPV6_MCAST_PATH); return parseIPv6MulticastAddresses(lines, ifname); } /** * The function loads the existing IPv4 multicast addresses from the file `/proc/net/igmp6`. * If the file does not exist or the interface is not found, the function returns empty list. * * @param ifname The name of the network interface to query. * @return A list of Inet4Address objects representing the IPv4 multicast addresses * found for the interface. * If the file cannot be read or there are no addresses, an empty list is returned. */ public static List getIPv4MulticastAddresses(@NonNull String ifname) { final List lines = readFile(IPV4_MCAST_PATH); // follow the same pattern as NetlinkMonitor#handlePacket() for device's endian order return parseIPv4MulticastAddresses(lines, ifname, ByteOrder.nativeOrder()); } }