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