blob: 16fd4b1d5f4d8dd3fc540982670e18230a0d2869 [file] [log] [blame]
/*
* 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<String> readFile(final String filePath) {
final List<String> 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<String> 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<String> 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<Inet6Address> parseAnycast6Addresses(
@NonNull List<String> lines, @NonNull String ifname) {
final List<Inet6Address> 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<MacAddress> parseEtherMulticastAddresses(
@NonNull List<String> lines, @NonNull String ifname) {
final List<MacAddress> 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<Inet6Address> parseIPv6MulticastAddresses(
@NonNull List<String> lines, @NonNull String ifname) {
final List<Inet6Address> 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<Inet4Address> parseIPv4MulticastAddresses(
@NonNull List<String> lines, @NonNull String ifname, @NonNull ByteOrder endian) {
final List<Inet4Address> 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<String> 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<Inet6Address> getAnycast6Addresses(@NonNull String ifname) {
final List<String> 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<MacAddress> getEtherMulticastAddresses(@NonNull String ifname) {
final List<String> 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<Inet6Address> getIpv6MulticastAddresses(@NonNull String ifname) {
final List<String> 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<Inet4Address> getIPv4MulticastAddresses(@NonNull String ifname) {
final List<String> lines = readFile(IPV4_MCAST_PATH);
// follow the same pattern as NetlinkMonitor#handlePacket() for device's endian order
return parseIPv4MulticastAddresses(lines, ifname, ByteOrder.nativeOrder());
}
}