Merge "Add DnsResolverModuleTest annotation" into main
diff --git a/staticlibs/Android.bp b/staticlibs/Android.bp
index ff65228..e1b5601 100644
--- a/staticlibs/Android.bp
+++ b/staticlibs/Android.bp
@@ -35,6 +35,7 @@
name: "net-utils-device-common",
srcs: [
"device/com/android/net/module/util/DeviceConfigUtils.java",
+ "device/com/android/net/module/util/DomainUtils.java",
"device/com/android/net/module/util/FdEventsReader.java",
"device/com/android/net/module/util/NetworkMonitorUtils.java",
"device/com/android/net/module/util/PacketReader.java",
diff --git a/staticlibs/device/com/android/net/module/util/DeviceConfigUtils.java b/staticlibs/device/com/android/net/module/util/DeviceConfigUtils.java
index dae4eb9..138c1e5 100644
--- a/staticlibs/device/com/android/net/module/util/DeviceConfigUtils.java
+++ b/staticlibs/device/com/android/net/module/util/DeviceConfigUtils.java
@@ -53,6 +53,8 @@
"com.android.server.connectivity.intent.action.SERVICE_CONNECTIVITY_RESOURCES_APK";
private static final String CONNECTIVITY_RES_PKG_DIR = "/apex/" + TETHERING_MODULE_NAME + "/";
+ private static final long DEFAULT_PACKAGE_VERSION = 1000;
+
@VisibleForTesting
public static void resetPackageVersionCacheForTest() {
sPackageVersion = -1;
@@ -60,17 +62,21 @@
}
private static volatile long sPackageVersion = -1;
- private static long getPackageVersion(@NonNull final Context context)
- throws PackageManager.NameNotFoundException {
+ private static long getPackageVersion(@NonNull final Context context) {
// sPackageVersion may be set by another thread just after this check, but querying the
// package version several times on rare occasions is fine.
if (sPackageVersion >= 0) {
return sPackageVersion;
}
- final long version = context.getPackageManager().getPackageInfo(
- context.getPackageName(), 0).getLongVersionCode();
- sPackageVersion = version;
- return version;
+ try {
+ final long version = context.getPackageManager().getPackageInfo(
+ context.getPackageName(), 0).getLongVersionCode();
+ sPackageVersion = version;
+ return version;
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "Failed to get package info: " + e);
+ return DEFAULT_PACKAGE_VERSION;
+ }
}
/**
@@ -173,13 +179,8 @@
*/
public static boolean isFeatureEnabled(@NonNull Context context, @NonNull String namespace,
@NonNull String name, boolean defaultEnabled) {
- try {
- final long packageVersion = getPackageVersion(context);
- return isFeatureEnabled(context, packageVersion, namespace, name, defaultEnabled);
- } catch (PackageManager.NameNotFoundException e) {
- Log.e(TAG, "Could not find the package name", e);
- return false;
- }
+ final long packageVersion = getPackageVersion(context);
+ return isFeatureEnabled(context, packageVersion, namespace, name, defaultEnabled);
}
/**
@@ -205,18 +206,12 @@
throw new IllegalArgumentException(
"This method is only usable by the tethering module");
}
- try {
- final long packageVersion = getTetheringModuleVersion(context);
- return isFeatureEnabled(context, packageVersion, namespace, name, defaultEnabled);
- } catch (PackageManager.NameNotFoundException e) {
- Log.e(TAG, "Could not find the module name", e);
- return false;
- }
+ final long packageVersion = getTetheringModuleVersion(context);
+ return isFeatureEnabled(context, packageVersion, namespace, name, defaultEnabled);
}
private static boolean isFeatureEnabled(@NonNull Context context, long packageVersion,
- @NonNull String namespace, String name, boolean defaultEnabled)
- throws PackageManager.NameNotFoundException {
+ @NonNull String namespace, String name, boolean defaultEnabled) {
final int propertyVersion = getDeviceConfigPropertyInt(namespace, name,
0 /* default value */);
return (propertyVersion == 0 && defaultEnabled)
@@ -251,11 +246,17 @@
}
private static volatile long sModuleVersion = -1;
- private static long getTetheringModuleVersion(@NonNull Context context)
- throws PackageManager.NameNotFoundException {
+ private static long getTetheringModuleVersion(@NonNull Context context) {
if (sModuleVersion >= 0) return sModuleVersion;
- sModuleVersion = resolveTetheringModuleVersion(context);
+ try {
+ sModuleVersion = resolveTetheringModuleVersion(context);
+ } catch (PackageManager.NameNotFoundException e) {
+ // It's expected to fail tethering module version resolution on the devices with
+ // flattened apex
+ Log.e(TAG, "Failed to resolve tethering module version: " + e);
+ return DEFAULT_PACKAGE_VERSION;
+ }
return sModuleVersion;
}
diff --git a/staticlibs/device/com/android/net/module/util/DomainUtils.java b/staticlibs/device/com/android/net/module/util/DomainUtils.java
new file mode 100644
index 0000000..80e0b64
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/DomainUtils.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2023 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 com.android.net.module.util;
+
+import android.util.ArrayMap;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.net.module.util.DnsPacketUtils.DnsRecordParser;
+
+import java.nio.BufferOverflowException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+
+/**
+ * Utilities for encoding/decoding the domain name or domain search list.
+ *
+ * @hide
+ */
+public final class DomainUtils {
+ private static final String TAG = "DomainUtils";
+ private static final int MAX_OPTION_LEN = 255;
+
+ @NonNull
+ private static String getSubstring(@NonNull final String string, @NonNull final String[] labels,
+ int index) {
+ int beginIndex = 0;
+ for (int i = 0; i < index; i++) {
+ beginIndex += labels[i].length() + 1; // include the dot
+ }
+ return string.substring(beginIndex);
+ }
+
+ /**
+ * Encode the given single domain name to byte array, comply with RFC1035 section-3.1.
+ *
+ * @return null if the given domain string is invalid, otherwise, return a byte array
+ * wrapping the encoded domain, not including any padded octets, caller should
+ * pad zero octets at the end if needed.
+ */
+ @Nullable
+ public static byte[] encode(@NonNull final String domain) {
+ if (!DnsRecordParser.isHostName(domain)) return null;
+ return encode(new String[]{ domain }, false /* compression */);
+ }
+
+ /**
+ * Encode the given multiple domain names to byte array, comply with RFC1035 section-3.1
+ * and section 4.1.4 (message compression) if enabled.
+ *
+ * @return Null if encode fails due to BufferOverflowException, otherwise, return a byte
+ * array wrapping the encoded domains, not including any padded octets, caller
+ * should pad zero octets at the end if needed. The byte array may be empty if
+ * the given domain strings are invalid.
+ */
+ @Nullable
+ public static byte[] encode(@NonNull final String[] domains, boolean compression) {
+ try {
+ final ByteBuffer buffer = ByteBuffer.allocate(MAX_OPTION_LEN);
+ final ArrayMap<String, Integer> offsetMap = new ArrayMap<>();
+ for (int i = 0; i < domains.length; i++) {
+ if (!DnsRecordParser.isHostName(domains[i])) {
+ Log.e(TAG, "Skip invalid domain name " + domains[i]);
+ continue;
+ }
+ final String[] labels = domains[i].split("\\.");
+ for (int j = 0; j < labels.length; j++) {
+ if (compression) {
+ final String suffix = getSubstring(domains[i], labels, j);
+ if (offsetMap.containsKey(suffix)) {
+ int offsetOfSuffix = offsetMap.get(suffix);
+ offsetOfSuffix |= 0xC000;
+ buffer.putShort((short) offsetOfSuffix);
+ break; // unnecessary to put the compressed string into map
+ } else {
+ offsetMap.put(suffix, buffer.position());
+ }
+ }
+ // encode the domain name string without compression when:
+ // - compression feature isn't enabled,
+ // - suffix does not match any string in the map.
+ final byte[] labelBytes = labels[j].getBytes(StandardCharsets.UTF_8);
+ buffer.put((byte) labelBytes.length);
+ buffer.put(labelBytes);
+ if (j == labels.length - 1) {
+ // Pad terminate label at the end of last label.
+ buffer.put((byte) 0);
+ }
+ }
+ }
+ buffer.flip();
+ final byte[] out = new byte[buffer.limit()];
+ buffer.get(out);
+ return out;
+ } catch (BufferOverflowException e) {
+ Log.e(TAG, "Fail to encode domain name and stop encoding", e);
+ return null;
+ }
+ }
+
+ /**
+ * Decode domain name(s) from the given byteBuffer. Decode follows RFC1035 section 3.1 and
+ * section 4.1.4(message compression).
+ *
+ * @return domain name(s) string array with space separated, or empty string if decode fails.
+ */
+ @NonNull
+ public static String[] decode(@NonNull final ByteBuffer buffer, boolean compression) {
+ final ArrayList<String> domainList = new ArrayList<>();
+ while (buffer.remaining() > 0) {
+ try {
+ // TODO: replace the recursion with loop in parseName and don't need to pass in the
+ // maxLabelCount parameter to prevent recursion from overflowing stack.
+ final String domain = DnsRecordParser.parseName(buffer, 0 /* depth */,
+ 15 /* maxLabelCount */, compression);
+ if (!DnsRecordParser.isHostName(domain)) continue;
+ domainList.add(domain);
+ } catch (BufferUnderflowException | DnsPacket.ParseException e) {
+ Log.e(TAG, "Fail to parse domain name and stop parsing", e);
+ break;
+ }
+ }
+ return domainList.toArray(new String[0]);
+ }
+}
diff --git a/staticlibs/device/com/android/net/module/util/netlink/NetlinkUtils.java b/staticlibs/device/com/android/net/module/util/netlink/NetlinkUtils.java
index 0f7bd80..33bd36d 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/NetlinkUtils.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/NetlinkUtils.java
@@ -33,6 +33,7 @@
import android.net.util.SocketUtils;
import android.system.ErrnoException;
import android.system.Os;
+import android.system.OsConstants;
import android.system.StructTimeval;
import android.util.Log;
@@ -196,6 +197,27 @@
}
/**
+ * Send an RTM_DELADDR message to kernel to delete an IPv6 address.
+ *
+ * @param ifIndex interface index.
+ * @param ip IPv6 address to be deleted.
+ * @param prefixlen IPv6 address prefix length.
+ */
+ public static boolean sendRtmDelAddressRequest(int ifIndex, final Inet6Address ip,
+ short prefixlen) {
+ Objects.requireNonNull(ip, "IPv6 address to be deleted should not be null.");
+ final byte[] msg = RtNetlinkAddressMessage.newRtmDelAddressMessage(1 /* seqNo*/, ip,
+ prefixlen, ifIndex);
+ try {
+ NetlinkUtils.sendOneShotKernelMessage(NETLINK_ROUTE, msg);
+ return true;
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Fail to send RTM_DELADDR to delete " + ip.getHostAddress(), e);
+ return false;
+ }
+ }
+
+ /**
* Create netlink socket with the given netlink protocol type.
*
* @return fd the fileDescriptor of the socket.
@@ -270,5 +292,20 @@
return Os.write(fd, bytes, offset, count);
}
+ private static final long CLOCK_TICKS_PER_SECOND = Os.sysconf(OsConstants._SC_CLK_TCK);
+
+ /**
+ * Convert the system time in clock ticks(clock_t type in times(), not in clock()) to
+ * milliseconds. times() clock_t ticks at the kernel's USER_HZ (100) while clock() clock_t
+ * ticks at CLOCKS_PER_SEC (1000000).
+ *
+ * See the NOTES on https://man7.org/linux/man-pages/man2/times.2.html for the difference
+ * of clock_t used in clock() and times().
+ */
+ public static long ticksToMilliSeconds(int intClockTicks) {
+ final long longClockTicks = intClockTicks & 0xffffffffL;
+ return (longClockTicks * 1000) / CLOCK_TICKS_PER_SECOND;
+ }
+
private NetlinkUtils() {}
}
diff --git a/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkAddressMessage.java b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkAddressMessage.java
index 2829b92..cbe0ab0 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkAddressMessage.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkAddressMessage.java
@@ -161,7 +161,7 @@
}
/**
- * A convenience method to create an RTM_NEWADDR message.
+ * A convenience method to create a RTM_NEWADDR message.
*/
public static byte[] newRtmNewAddressMessage(int seqNo, @NonNull final InetAddress ip,
short prefixlen, int flags, byte scope, int ifIndex, long preferred, long valid) {
@@ -192,6 +192,51 @@
return bytes;
}
+ /**
+ * A convenience method to create a RTM_DELADDR message.
+ */
+ public static byte[] newRtmDelAddressMessage(int seqNo, @NonNull final InetAddress ip,
+ short prefixlen, int ifIndex) {
+ Objects.requireNonNull(ip, "IP address to be deleted via netlink message cannot be null");
+
+ final int ifaAddrAttrLength = NetlinkConstants.alignedLengthOf(
+ StructNlAttr.NLA_HEADERLEN + ip.getAddress().length);
+ final int length = StructNlMsgHdr.STRUCT_SIZE + StructIfaddrMsg.STRUCT_SIZE
+ + ifaAddrAttrLength;
+ final byte[] bytes = new byte[length];
+ final ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
+ byteBuffer.order(ByteOrder.nativeOrder());
+
+ final StructNlMsgHdr nlmsghdr = new StructNlMsgHdr();
+ nlmsghdr.nlmsg_len = length;
+ nlmsghdr.nlmsg_type = NetlinkConstants.RTM_DELADDR;
+ nlmsghdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
+ nlmsghdr.nlmsg_seq = seqNo;
+ nlmsghdr.pack(byteBuffer);
+
+ final byte family =
+ (byte) ((ip instanceof Inet6Address) ? OsConstants.AF_INET6 : OsConstants.AF_INET);
+ // Actually kernel ignores scope and flags(only deal with IFA_F_MANAGETEMPADDR, it
+ // indicates that all relevant IPv6 temporary addresses should be deleted as well when
+ // user space intends to delete a global IPv6 address with IFA_F_MANAGETEMPADDR), so
+ // far IFA_F_MANAGETEMPADDR flag isn't used in user space, it's fine to ignore it.
+ // However, we need to add IFA_FLAGS attribute in RTM_DELADDR if flags parsing should
+ // be supported in the future.
+ final StructIfaddrMsg ifaddrmsg = new StructIfaddrMsg(family, prefixlen,
+ (short) 0 /* flags */, (short) 0 /* scope */, ifIndex);
+ ifaddrmsg.pack(byteBuffer);
+
+ final StructNlAttr address = new StructNlAttr(IFA_ADDRESS, ip);
+ address.pack(byteBuffer);
+
+ return bytes;
+ }
+
+ // This function helper gives the required buffer size for IFA_ADDRESS, IFA_CACHEINFO and
+ // IFA_FLAGS attributes encapsulation. However, that's not a mandatory requirement for all
+ // RtNetlinkAddressMessage, e.g. RTM_DELADDR sent from user space to kernel to delete an
+ // IP address only requires IFA_ADDRESS attribute. The caller should check if these attributes
+ // are necessary to carry when constructing a RtNetlinkAddressMessage.
private int getRequiredSpace() {
int spaceRequired = StructNlMsgHdr.STRUCT_SIZE + StructIfaddrMsg.STRUCT_SIZE;
// IFA_ADDRESS attr
diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructIfaddrMsg.java b/staticlibs/device/com/android/net/module/util/netlink/StructIfaddrMsg.java
index 2802726..f9781a7 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/StructIfaddrMsg.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/StructIfaddrMsg.java
@@ -18,7 +18,6 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
import com.android.net.module.util.Struct;
import com.android.net.module.util.Struct.Field;
@@ -50,7 +49,6 @@
@Field(order = 4, type = Type.S32)
public final int index;
- @VisibleForTesting
public StructIfaddrMsg(short family, short prefixLen, short flags, short scope, int index) {
this.family = family;
this.prefixLen = prefixLen;
diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructNdaCacheInfo.java b/staticlibs/device/com/android/net/module/util/netlink/StructNdaCacheInfo.java
index 79d5ff4..1f9bb7e 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/StructNdaCacheInfo.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/StructNdaCacheInfo.java
@@ -16,8 +16,7 @@
package com.android.net.module.util.netlink;
-import android.system.Os;
-import android.system.OsConstants;
+import static com.android.net.module.util.netlink.NetlinkUtils.ticksToMilliSeconds;
import java.nio.ByteBuffer;
@@ -57,15 +56,6 @@
return struct;
}
- // TODO: investigate whether this can change during device runtime and
- // decide what (if anything) should be done about that.
- private static final long CLOCK_TICKS_PER_SECOND = Os.sysconf(OsConstants._SC_CLK_TCK);
-
- private static long ticksToMilliSeconds(int intClockTicks) {
- final long longClockTicks = (long) intClockTicks & 0xffffffff;
- return (longClockTicks * 1000) / CLOCK_TICKS_PER_SECOND;
- }
-
/**
* Explanatory notes, for reference.
*
diff --git a/staticlibs/device/com/android/net/module/util/structs/RouteInformationOption.java b/staticlibs/device/com/android/net/module/util/structs/RouteInformationOption.java
new file mode 100644
index 0000000..49bafed
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/structs/RouteInformationOption.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2023 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 com.android.net.module.util.structs;
+
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_RIO;
+
+import android.net.IpPrefix;
+
+import androidx.annotation.NonNull;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * ICMPv6 route information option, as per https://tools.ietf.org/html/rfc4191.
+ *
+ * 0 1 2 3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Type | Length | Prefix Length |Resvd|Prf|Resvd|
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Route Lifetime |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Prefix (Variable Length) |
+ * . .
+ * . .
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+public class RouteInformationOption extends Struct {
+ public enum Preference {
+ HIGH((byte) 0x1),
+ MEDIUM((byte) 0x0),
+ LOW((byte) 0x3),
+ RESERVED((byte) 0x2);
+
+ final byte mValue;
+ Preference(byte value) {
+ this.mValue = value;
+ }
+ }
+
+ @Field(order = 0, type = Type.S8)
+ public final byte type;
+ @Field(order = 1, type = Type.S8)
+ public final byte length; // Length in 8-byte octets
+ @Field(order = 2, type = Type.U8)
+ public final short prefixLen;
+ @Field(order = 3, type = Type.S8)
+ public final byte prf;
+ @Field(order = 4, type = Type.U32)
+ public final long routeLifetime;
+ @Field(order = 5, type = Type.ByteArray, arraysize = 16)
+ public final byte[] prefix;
+
+ RouteInformationOption(final byte type, final byte length, final short prefixLen,
+ final byte prf, final long routeLifetime, @NonNull final byte[] prefix) {
+ this.type = type;
+ this.length = length;
+ this.prefixLen = prefixLen;
+ this.prf = prf;
+ this.routeLifetime = routeLifetime;
+ this.prefix = prefix;
+ }
+
+ /**
+ * Build a Route Information option from the required specified parameters.
+ */
+ public static ByteBuffer build(final IpPrefix prefix, final Preference prf,
+ final long routeLifetime) {
+ // The prefix field is always assumed to have 16 bytes, but the number of leading
+ // bits in this prefix depends on IpPrefix#prefixLength, then we can simply set the
+ // option length to 3.
+ final RouteInformationOption option = new RouteInformationOption(
+ (byte) ICMPV6_ND_OPTION_RIO, (byte) 3 /* option length */,
+ (short) prefix.getPrefixLength(), (byte) (prf.mValue << 3), routeLifetime,
+ prefix.getRawAddress());
+ return ByteBuffer.wrap(option.writeToBytes(ByteOrder.BIG_ENDIAN));
+ }
+}
diff --git a/staticlibs/framework/com/android/net/module/util/DnsPacket.java b/staticlibs/framework/com/android/net/module/util/DnsPacket.java
index 79ca3a3..0dcdf1e 100644
--- a/staticlibs/framework/com/android/net/module/util/DnsPacket.java
+++ b/staticlibs/framework/com/android/net/module/util/DnsPacket.java
@@ -19,7 +19,6 @@
import static android.net.DnsResolver.TYPE_A;
import static android.net.DnsResolver.TYPE_AAAA;
-import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
import static com.android.net.module.util.DnsPacketUtils.DnsRecordParser.domainNameToLabels;
@@ -245,9 +244,11 @@
* / RDATA /
* / /
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ *
+ * Note that this class is meant to be used by composition and not inheritance, and
+ * that classes implementing more specific DNS records should call #parse.
*/
- // TODO: Make DnsResourceRecord and DnsQuestion subclasses of DnsRecord, and construct
- // corresponding object from factory methods.
+ // TODO: Make DnsResourceRecord and DnsQuestion subclasses of DnsRecord.
public static class DnsRecord {
// Refer to RFC 1035 section 2.3.4 for MAXNAMESIZE.
// NAME_NORMAL and NAME_COMPRESSION are used for checking name compression,
@@ -281,8 +282,7 @@
* @param buf ByteBuffer input of record, must be in network byte order
* (which is the default).
*/
- @VisibleForTesting(visibility = PACKAGE)
- public DnsRecord(@RecordType int rType, @NonNull ByteBuffer buf)
+ private DnsRecord(@RecordType int rType, @NonNull ByteBuffer buf)
throws BufferUnderflowException, ParseException {
Objects.requireNonNull(buf);
this.rType = rType;
@@ -307,6 +307,31 @@
}
/**
+ * Create a new DnsRecord or subclass of DnsRecord instance from a positioned ByteBuffer.
+ *
+ * Peek the nsType, sending the buffer to corresponding DnsRecord subclass constructors
+ * to allow constructing the corresponding object.
+ */
+ @VisibleForTesting(visibility = PRIVATE)
+ public static DnsRecord parse(@RecordType int rType, @NonNull ByteBuffer buf)
+ throws BufferUnderflowException, ParseException {
+ Objects.requireNonNull(buf);
+ final int oldPos = buf.position();
+ // Parsed name not used, just for jumping to nsType position.
+ DnsRecordParser.parseName(buf, 0 /* Parse depth */,
+ true /* isNameCompressionSupported */);
+ // Peek the nsType.
+ final int nsType = Short.toUnsignedInt(buf.getShort());
+ buf.position(oldPos);
+ // Return a DnsRecord instance by default for backward compatibility, this is useful
+ // when a partner supports new type of DnsRecord but does not inherit DnsRecord.
+ switch (nsType) {
+ default:
+ return new DnsRecord(rType, buf);
+ }
+ }
+
+ /**
* Make an A or AAAA record based on the specified parameters.
*
* @param rType Type of the record, can be {@link #ANSECTION}, {@link #ARSECTION}
@@ -507,7 +532,7 @@
mRecords[i] = new ArrayList(count);
for (int j = 0; j < count; ++j) {
try {
- mRecords[i].add(new DnsRecord(i, buffer));
+ mRecords[i].add(DnsRecord.parse(i, buffer));
} catch (BufferUnderflowException e) {
throw new ParseException("Parse record fail", e);
}
diff --git a/staticlibs/framework/com/android/net/module/util/DnsPacketUtils.java b/staticlibs/framework/com/android/net/module/util/DnsPacketUtils.java
index c47bfa0..105d783 100644
--- a/staticlibs/framework/com/android/net/module/util/DnsPacketUtils.java
+++ b/staticlibs/framework/com/android/net/module/util/DnsPacketUtils.java
@@ -130,16 +130,25 @@
/**
* Parses the domain / target name of a DNS record.
+ */
+ public static String parseName(final ByteBuffer buf, int depth,
+ boolean isNameCompressionSupported) throws
+ BufferUnderflowException, DnsPacket.ParseException {
+ return parseName(buf, depth, MAXLABELCOUNT, isNameCompressionSupported);
+ }
+
+ /**
+ * Parses the domain / target name of a DNS record.
*
* As described in RFC 1035 Section 4.1.3, the NAME field of a DNS Resource Record always
* supports Name Compression, whereas domain names contained in the RDATA payload of a DNS
* record may or may not support Name Compression, depending on the record TYPE. Moreover,
* even if Name Compression is supported, its usage is left to the implementation.
*/
- public static String parseName(ByteBuffer buf, int depth,
+ public static String parseName(final ByteBuffer buf, int depth, int maxLabelCount,
boolean isNameCompressionSupported) throws
BufferUnderflowException, DnsPacket.ParseException {
- if (depth > MAXLABELCOUNT) {
+ if (depth > maxLabelCount) {
throw new DnsPacket.ParseException("Failed to parse name, too many labels");
}
final int len = Byte.toUnsignedInt(buf.get());
@@ -158,7 +167,8 @@
"Parse compression name fail, invalid compression");
}
buf.position(offset);
- final String pointed = parseName(buf, depth + 1, isNameCompressionSupported);
+ final String pointed = parseName(buf, depth + 1, maxLabelCount,
+ isNameCompressionSupported);
buf.position(oldPos);
return pointed;
} else {
@@ -168,7 +178,8 @@
if (head.length() > MAXLABELSIZE) {
throw new DnsPacket.ParseException("Parse name fail, invalid label length");
}
- final String tail = parseName(buf, depth + 1, isNameCompressionSupported);
+ final String tail = parseName(buf, depth + 1, maxLabelCount,
+ isNameCompressionSupported);
return TextUtils.isEmpty(tail) ? head : head + "." + tail;
}
}
diff --git a/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java b/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
index 6a6f5e1..24aabbc 100644
--- a/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
+++ b/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
@@ -140,6 +140,7 @@
* ICMPv6 constants.
*
* See also:
+ * - https://tools.ietf.org/html/rfc4191
* - https://tools.ietf.org/html/rfc4443
* - https://tools.ietf.org/html/rfc4861
*/
@@ -157,6 +158,7 @@
public static final int ICMPV6_ND_OPTION_TLLA = 2;
public static final int ICMPV6_ND_OPTION_PIO = 3;
public static final int ICMPV6_ND_OPTION_MTU = 5;
+ public static final int ICMPV6_ND_OPTION_RIO = 24;
public static final int ICMPV6_ND_OPTION_RDNSS = 25;
public static final int ICMPV6_ND_OPTION_PREF64 = 38;
diff --git a/staticlibs/native/bpf_headers/include/bpf/BpfClassic.h b/staticlibs/native/bpf_headers/include/bpf/BpfClassic.h
index 9b38dee..dd0804c 100644
--- a/staticlibs/native/bpf_headers/include/bpf/BpfClassic.h
+++ b/staticlibs/native/bpf_headers/include/bpf/BpfClassic.h
@@ -22,11 +22,25 @@
// Reject the packet
#define BPF_REJECT BPF_STMT(BPF_RET | BPF_K, 0)
+// *TWO* instructions: compare and if not equal jump over the accept statement
+#define BPF2_ACCEPT_IF_EQUAL(v) \
+ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, (v), 0, 1), \
+ BPF_ACCEPT
+
// *TWO* instructions: compare and if equal jump over the reject statement
#define BPF2_REJECT_IF_NOT_EQUAL(v) \
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, (v), 1, 0), \
BPF_REJECT
+// *TWO* instructions: compare and if none of the bits are set jump over the reject statement
+#define BPF2_REJECT_IF_ANY_BITS_SET(v) \
+ BPF_JUMP(BPF_JMP | BPF_JSET | BPF_K, (v), 0, 1), \
+ BPF_REJECT
+
+// loads skb->protocol
+#define BPF_LOAD_SKB_PROTOCOL \
+ BPF_STMT(BPF_LD | BPF_H | BPF_ABS, (__u32)SKF_AD_OFF + SKF_AD_PROTOCOL)
+
// 8-bit load relative to start of link layer (mac/ethernet) header.
#define BPF_LOAD_MAC_RELATIVE_U8(ofs) \
BPF_STMT(BPF_LD | BPF_B | BPF_ABS, (__u32)SKF_LL_OFF + (ofs))
diff --git a/staticlibs/tests/unit/Android.bp b/staticlibs/tests/unit/Android.bp
index 40371e6..8c49954 100644
--- a/staticlibs/tests/unit/Android.bp
+++ b/staticlibs/tests/unit/Android.bp
@@ -22,6 +22,7 @@
"net-utils-device-common-bpf",
"net-utils-device-common-ip",
"net-utils-device-common-wear",
+ "truth",
],
libs: [
"android.test.runner",
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/DeviceConfigUtilsTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/DeviceConfigUtilsTest.java
index b75939b..328c39a 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/DeviceConfigUtilsTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/DeviceConfigUtilsTest.java
@@ -246,10 +246,34 @@
@Test
public void testFeatureIsEnabledWithException() throws Exception {
doThrow(NameNotFoundException.class).when(mPm).getPackageInfo(anyString(), anyInt());
+
+ // Feature should be enabled by flag value "1".
+ doReturn("1").when(() -> DeviceConfig.getProperty(eq(TEST_NAME_SPACE),
+ eq(TEST_EXPERIMENT_FLAG)));
+ assertTrue(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
+ TEST_EXPERIMENT_FLAG));
+ assertTrue(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
+ TEST_EXPERIMENT_FLAG, TEST_APEX_NAME, false /* defaultEnabled */));
+
+ // Feature should be disabled by flag value "999999999".
+ doReturn("999999999").when(() -> DeviceConfig.getProperty(eq(TEST_NAME_SPACE),
+ eq(TEST_EXPERIMENT_FLAG)));
assertFalse(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
TEST_EXPERIMENT_FLAG));
assertFalse(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
TEST_EXPERIMENT_FLAG, TEST_APEX_NAME, false /* defaultEnabled */));
+
+ // Follow defaultEnabled if the flag is not set
+ doReturn(null).when(() -> DeviceConfig.getProperty(eq(TEST_NAME_SPACE),
+ eq(TEST_EXPERIMENT_FLAG)));
+ assertFalse(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
+ TEST_EXPERIMENT_FLAG, false /* defaultEnabled */));
+ assertTrue(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
+ TEST_EXPERIMENT_FLAG, true /* defaultEnabled */));
+ assertFalse(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
+ TEST_EXPERIMENT_FLAG, TEST_APEX_NAME, false /* defaultEnabled */));
+ assertTrue(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
+ TEST_EXPERIMENT_FLAG, TEST_APEX_NAME, true /* defaultEnabled */));
}
@Test
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/DnsPacketTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/DnsPacketTest.java
index 409c1eb..28e183a 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/DnsPacketTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/DnsPacketTest.java
@@ -219,7 +219,7 @@
0x00, 0x04, /* Data length */
(byte) 0xac, (byte) 0xd9, (byte) 0xa1, (byte) 0x84 /* Address */};
final DnsPacket.DnsRecord questionsFromBytes =
- new DnsPacket.DnsRecord(DnsPacket.QDSECTION, ByteBuffer.wrap(qdWithTTLRData));
+ DnsPacket.DnsRecord.parse(DnsPacket.QDSECTION, ByteBuffer.wrap(qdWithTTLRData));
assertEquals(0, questionsFromBytes.ttl);
assertNull(questionsFromBytes.getRR());
@@ -230,12 +230,12 @@
0x00, 0x01, /* Type */
0x00, 0x01, /* Class */};
assertThrows(BufferUnderflowException.class, () ->
- new DnsPacket.DnsRecord(DnsPacket.ANSECTION, ByteBuffer.wrap(anWithoutTTLRData)));
+ DnsPacket.DnsRecord.parse(DnsPacket.ANSECTION, ByteBuffer.wrap(anWithoutTTLRData)));
}
private void assertDnsRecordRoundTrip(DnsPacket.DnsRecord before)
throws IOException {
- final DnsPacket.DnsRecord after = new DnsPacket.DnsRecord(before.rType,
+ final DnsPacket.DnsRecord after = DnsPacket.DnsRecord.parse(before.rType,
ByteBuffer.wrap(before.getBytes()));
assertEquals(after, before);
}
@@ -393,7 +393,7 @@
"test.com", TYPE_AAAA, CLASS_IN);
final DnsPacket.DnsRecord testAnswer = DnsPacket.DnsRecord.makeCNameRecord(
DnsPacket.ANSECTION, "test.com", CLASS_IN, 9, "www.test.com");
- final DnsPacket.DnsRecord questionFromBytes = new DnsPacket.DnsRecord(DnsPacket.QDSECTION,
+ final DnsPacket.DnsRecord questionFromBytes = DnsPacket.DnsRecord.parse(DnsPacket.QDSECTION,
ByteBuffer.wrap(testQuestion.getBytes()));
assertEquals(testQuestion, questionFromBytes);
assertEquals(testQuestion.hashCode(), questionFromBytes.hashCode());
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/DomainUtilsTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/DomainUtilsTest.java
new file mode 100644
index 0000000..606ed5f
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/DomainUtilsTest.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2023 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 com.android.net.module.util;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import libcore.util.HexEncoding;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.nio.ByteBuffer;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class DomainUtilsTest {
+ @Test
+ public void testEncodeInvalidDomain() {
+ byte[] buffer = DomainUtils.encode(".google.com");
+ assertNull(buffer);
+
+ buffer = DomainUtils.encode("google.com.");
+ assertNull(buffer);
+
+ buffer = DomainUtils.encode("-google.com");
+ assertNull(buffer);
+
+ buffer = DomainUtils.encode("google.com-");
+ assertNull(buffer);
+
+ buffer = DomainUtils.encode("google..com");
+ assertNull(buffer);
+
+ buffer = DomainUtils.encode("google!.com");
+ assertNull(buffer);
+
+ buffer = DomainUtils.encode("google.o");
+ assertNull(buffer);
+
+ buffer = DomainUtils.encode("google,com");
+ assertNull(buffer);
+ }
+
+ @Test
+ public void testEncodeValidDomainNamesWithoutCompression() {
+ // Single domain: "google.com"
+ String suffix = "06676F6F676C6503636F6D00";
+ byte[] buffer = DomainUtils.encode("google.com");
+ //assertNotNull(buffer);
+ assertEquals(suffix, HexEncoding.encodeToString(buffer));
+
+ // Single domain: "google-guest.com"
+ suffix = "0C676F6F676C652D677565737403636F6D00";
+ buffer = DomainUtils.encode("google-guest.com");
+ assertNotNull(buffer);
+ assertEquals(suffix, HexEncoding.encodeToString(buffer));
+
+ // domain search list: "example.corp.google.com", "corp.google.com", "google.com"
+ suffix = "076578616D706C6504636F727006676F6F676C6503636F6D00" // example.corp.google.com
+ + "04636F727006676F6F676C6503636F6D00" // corp.google.com
+ + "06676F6F676C6503636F6D00"; // google.com
+ buffer = DomainUtils.encode(new String[] {
+ "example.corp.google.com", "corp.google.com", "google.com"},
+ false /* compression */);
+ assertNotNull(buffer);
+ assertEquals(suffix, HexEncoding.encodeToString(buffer));
+
+
+ // domain search list: "example.corp.google.com", "corp..google.com"(invalid domain),
+ // "google.com"
+ suffix = "076578616D706C6504636F727006676F6F676C6503636F6D00" // example.corp.google.com
+ + "06676F6F676C6503636F6D00"; // google.com
+ buffer = DomainUtils.encode(new String[] {
+ "example.corp.google.com", "corp..google.com", "google.com"},
+ false /* compression */);
+ assertNotNull(buffer);
+ assertEquals(suffix, HexEncoding.encodeToString(buffer));
+
+ // Invalid domain search list: "corp..google.com", "..google.com"
+ buffer = DomainUtils.encode(new String[] {"corp..google.com", "..google.com"},
+ false /* compression */);
+ assertEquals(0, buffer.length);
+ }
+
+ @Test
+ public void testEncodeValidDomainNamesWithCompression() {
+ // domain search list: "example.corp.google.com", "corp.google.com", "google.com"
+ String suffix =
+ "076578616D706C6504636F727006676F6F676C6503636F6D00" // example.corp.google.com
+ + "C008" // corp.google.com
+ + "C00D"; // google.com
+ byte[] buffer = DomainUtils.encode(new String[] {
+ "example.corp.google.com", "corp.google.com", "google.com"}, true);
+ assertNotNull(buffer);
+ assertEquals(suffix, HexEncoding.encodeToString(buffer));
+
+ // domain search list: "example.corp.google.com", "a.example.corp.google.com", "google.com"
+ suffix = "076578616D706C6504636F727006676F6F676C6503636F6D00" // example.corp.google.com
+ + "0161C000" // a.example.corp.google.com
+ + "C00D"; // google.com
+ buffer = DomainUtils.encode(new String[] {
+ "example.corp.google.com", "a.example.corp.google.com", "google.com"}, true);
+ assertNotNull(buffer);
+ assertEquals(suffix, HexEncoding.encodeToString(buffer));
+
+ // domain search list: "example.corp.google.com", "google.com", "gle.com"
+ suffix = "076578616D706C6504636F727006676F6F676C6503636F6D00" // example.corp.google.com
+ + "C00D" // google.com
+ + "03676C65C014"; // gle.com
+ buffer = DomainUtils.encode(new String[] {
+ "example.corp.google.com", "google.com", "gle.com"}, true);
+ assertNotNull(buffer);
+ assertEquals(suffix, HexEncoding.encodeToString(buffer));
+
+ // domain search list: "example.corp.google.com", "google.com", "google"
+ suffix = "076578616D706C6504636F727006676F6F676C6503636F6D00" // example.corp.google.com
+ + "C00D"; // google.com
+ buffer = DomainUtils.encode(new String[] {
+ "example.corp.google.com", "google.com", "google"}, true);
+ assertNotNull(buffer);
+ assertEquals(suffix, HexEncoding.encodeToString(buffer));
+
+ // domain search list: "example.corp.google.com", "..google.com"(invalid domain), "google"
+ suffix = "076578616D706C6504636F727006676F6F676C6503636F6D00"; // example.corp.google.com
+ buffer = DomainUtils.encode(new String[] {
+ "example.corp.google.com", "..google.com", "google"}, true);
+ assertNotNull(buffer);
+ assertEquals(suffix, HexEncoding.encodeToString(buffer));
+
+ // domain search list: "example.corp.google.com", "suffix.example.edu.cn", "edu.cn"
+ suffix = "076578616D706C6504636F727006676F6F676C6503636F6D00" // example.corp.google.com
+ + "06737566666978076578616D706C650365647502636E00" // suffix.example.edu.cn
+ + "C028"; // edu.cn
+ buffer = DomainUtils.encode(new String[] {
+ "example.corp.google.com", "suffix.example.edu.cn", "edu.cn"}, true);
+ assertNotNull(buffer);
+ assertEquals(suffix, HexEncoding.encodeToString(buffer));
+
+ // domain search list: "google.com", "example.com", "sub.example.com"
+ suffix = "06676F6F676C6503636F6D00" // google.com
+ + "076578616D706C65C007" // example.com
+ + "03737562C00C"; // sub.example.com
+ buffer = DomainUtils.encode(new String[] {
+ "google.com", "example.com", "sub.example.com"}, true);
+ assertNotNull(buffer);
+ assertEquals(suffix, HexEncoding.encodeToString(buffer));
+ }
+
+ @Test
+ public void testDecodeDomainNames() {
+ String suffixes = "06676F6F676C6503636F6D00" // google.com
+ + "076578616D706C6503636F6D00" // example.com
+ + "06676F6F676C6500"; // google
+ String[] expected = new String[] {"google.com", "example.com"};
+ ByteBuffer buffer = ByteBuffer.wrap(HexEncoding.decode(suffixes));
+ String[] suffixString = DomainUtils.decode(buffer, false /* compression */);
+ assertArrayEquals(expected, suffixString);
+
+ // include suffix with invalid length: 64
+ suffixes = "06676F6F676C6503636F6D00" // google.com
+ + "406578616D706C6503636F6D00" // example.com(length=64)
+ + "06676F6F676C6500"; // google
+ expected = new String[] {"google.com"};
+ buffer = ByteBuffer.wrap(HexEncoding.decode(suffixes));
+ suffixString = DomainUtils.decode(buffer, false /* compression */);
+ assertArrayEquals(expected, suffixString);
+
+ // include suffix with invalid length: 0
+ suffixes = "06676F6F676C6503636F6D00" // google.com
+ + "076578616D706C6503636F6D00" // example.com
+ + "00676F6F676C6500"; // google(length=0)
+ expected = new String[] {"google.com", "example.com"};
+ buffer = ByteBuffer.wrap(HexEncoding.decode(suffixes));
+ suffixString = DomainUtils.decode(buffer, false /* compression */);
+ assertArrayEquals(expected, suffixString);
+
+ suffixes =
+ "076578616D706C6504636F727006676F6F676C6503636F6D00" // example.corp.google.com
+ + "C008" // corp.google.com
+ + "C00D"; // google.com
+ expected = new String[] {"example.corp.google.com", "corp.google.com", "google.com"};
+ buffer = ByteBuffer.wrap(HexEncoding.decode(suffixes));
+ suffixString = DomainUtils.decode(buffer, true /* compression */);
+ assertArrayEquals(expected, suffixString);
+ }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NetlinkUtilsTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NetlinkUtilsTest.java
index 6fbfbf9..dac5911 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NetlinkUtilsTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NetlinkUtilsTest.java
@@ -26,6 +26,8 @@
import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_DUMP;
import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REQUEST;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@@ -50,6 +52,7 @@
import org.junit.runner.RunWith;
import java.io.FileDescriptor;
+import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.file.Files;
@@ -170,7 +173,9 @@
assertEquals(0, response.position());
assertEquals(ByteOrder.nativeOrder(), response.order());
- final StructNlMsgHdr nlmsghdr = StructNlMsgHdr.parse(response);
+ final NetlinkMessage msg = NetlinkMessage.parse(response, NETLINK_ROUTE);
+ assertNotNull(msg);
+ final StructNlMsgHdr nlmsghdr = msg.getHeader();
assertNotNull(nlmsghdr);
if (nlmsghdr.nlmsg_type == NetlinkConstants.NLMSG_DONE) {
@@ -181,12 +186,15 @@
assertTrue((nlmsghdr.nlmsg_flags & StructNlMsgHdr.NLM_F_MULTI) != 0);
assertEquals(testSeqno, nlmsghdr.nlmsg_seq);
assertEquals(localAddr.getPortId(), nlmsghdr.nlmsg_pid);
+ assertTrue(msg instanceof RtNetlinkAddressMessage);
addrMessageCount++;
- final IfaddrMsg ifaMsg = Struct.parse(IfaddrMsg.class, response);
- assertTrue(
- "Non-IP address family: " + ifaMsg.family,
- ifaMsg.family == AF_INET || ifaMsg.family == AF_INET6);
+ // From the query response we can see the RTM_NEWADDR messages representing for IPv4
+ // and IPv6 loopback address: 127.0.0.1 and ::1.
+ final StructIfaddrMsg ifaMsg = ((RtNetlinkAddressMessage) msg).getIfaddrHeader();
+ final InetAddress ipAddress = ((RtNetlinkAddressMessage) msg).getIpAddress();
+ assertThat((int) ifaMsg.family).isAnyOf(AF_INET, AF_INET6);
+ assertTrue(ipAddress.isLoopbackAddress());
}
assertTrue(addrMessageCount > 0);
@@ -196,7 +204,7 @@
/** A convenience method to create an RTM_GETADDR request message. */
private static byte[] newGetAddrRequest(int seqNo) {
- final int length = StructNlMsgHdr.STRUCT_SIZE + Struct.getSize(RtgenMsg.class);
+ final int length = StructNlMsgHdr.STRUCT_SIZE + Struct.getSize(StructIfaddrMsg.class);
final byte[] bytes = new byte[length];
final ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
byteBuffer.order(ByteOrder.nativeOrder());
@@ -208,38 +216,11 @@
nlmsghdr.nlmsg_seq = seqNo;
nlmsghdr.pack(byteBuffer);
- final RtgenMsg rtgenMsg = new RtgenMsg();
- rtgenMsg.family = (byte) AF_UNSPEC;
- rtgenMsg.writeToByteBuffer(byteBuffer);
+ final StructIfaddrMsg addrMsg = new StructIfaddrMsg((byte) AF_UNSPEC /* family */,
+ (short) 0 /* prefixLen */, (short) 0 /* flags */, (short) 0 /* scope */,
+ 0 /* index */);
+ addrMsg.pack(byteBuffer);
return bytes;
}
-
- /** From uapi/linux/rtnetlink.h */
- private static class RtgenMsg extends Struct {
- @Field(order = 0, type = Type.U8)
- public short family;
- }
-
- /**
- * From uapi/linux/ifaddr.h
- *
- * Public ensures visibility to Struct class
- */
- public static class IfaddrMsg extends Struct {
- @Field(order = 0, type = Type.U8)
- public short family;
-
- @Field(order = 1, type = Type.U8)
- public short prefixlen;
-
- @Field(order = 2, type = Type.U8)
- public short flags;
-
- @Field(order = 3, type = Type.U8)
- public short scope;
-
- @Field(order = 4, type = Type.S32)
- public int index;
- }
}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkAddressMessageTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkAddressMessageTest.java
index 99d96b5..01126d2 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkAddressMessageTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkAddressMessageTest.java
@@ -179,6 +179,34 @@
}
@Test
+ public void testCreateRtmDelAddressMessage() {
+ // Hexadecimal representation of our created packet.
+ final String expectedDelAddressHex =
+ // struct nlmsghdr
+ "2C000000" + // length = 44
+ "1500" + // type = 21 (RTM_DELADDR)
+ "0500" + // flags = NLM_F_ACK | NLM_F_REQUEST
+ "01000000" + // seqno = 1
+ "00000000" + // pid = 0 (send to kernel)
+ // struct IfaddrMsg
+ "0A" + // family = inet6
+ "40" + // prefix len = 64
+ "00" + // flags = 0
+ "00" + // scope = RT_SCOPE_UNIVERSE
+ "3B000000" + // ifindex = 59
+ // struct nlattr: IFA_ADDRESS
+ "1400" + // len
+ "0100" + // type
+ "20010DB8000100000000000000000100"; // IP address = 2001:db8:1::100
+ final byte[] expectedDelAddress =
+ HexEncoding.decode(expectedDelAddressHex.toCharArray(), false);
+
+ final byte[] bytes = RtNetlinkAddressMessage.newRtmDelAddressMessage(1 /* seqno */,
+ TEST_GLOBAL_ADDRESS, (short) 64 /* prefix len */, 59 /* ifindex */);
+ assertArrayEquals(expectedDelAddress, bytes);
+ }
+
+ @Test
public void testCreateRtmNewAddressMessage_nullIpAddress() {
assertThrows(NullPointerException.class,
() -> RtNetlinkAddressMessage.newRtmNewAddressMessage(1 /* seqno */,
@@ -189,6 +217,13 @@
}
@Test
+ public void testCreateRtmDelAddressMessage_nullIpAddress() {
+ assertThrows(NullPointerException.class,
+ () -> RtNetlinkAddressMessage.newRtmDelAddressMessage(1 /* seqno */,
+ null /* IP address */, (short) 0 /* prefix len */, 59 /* ifindex */));
+ }
+
+ @Test
public void testCreateRtmNewAddressMessage_u32Flags() {
// Hexadecimal representation of our created packet.
final String expectedNewAddressHex =