Make RtNetlinkLinkMessage class immutable after creation
This CL marks all the fields of RtNetlinkLinkMessage class final. That
means once the instance is created, the instance is immutable.
Also, the CL adds a static create method that verifies the arguments,
and updates the nlmsg_len field of the header before creation.
Bug: 364501824
Test: atest NetworkStaticLibTests
Change-Id: I08af4a6e68df9aee8ae76d953fac5d81f0b02c84
diff --git a/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkLinkMessage.java b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkLinkMessage.java
index 0c49edc..72c770a 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkLinkMessage.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkLinkMessage.java
@@ -16,6 +16,8 @@
package com.android.net.module.util.netlink;
+import static com.android.net.module.util.NetworkStackConstants.ETHER_ADDR_LEN;
+
import android.net.MacAddress;
import android.system.OsConstants;
@@ -24,6 +26,7 @@
import androidx.annotation.VisibleForTesting;
import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
/**
* A NetlinkMessage subclass for rtnetlink link messages.
@@ -46,29 +49,55 @@
public static final short IN6_ADDR_GEN_MODE_NONE = 1;
- private int mMtu;
- @NonNull
- private StructIfinfoMsg mIfinfomsg;
- @Nullable
- private MacAddress mHardwareAddress;
- @Nullable
- private String mInterfaceName;
+ // The maximum buffer size to hold an interface name including the null-terminator '\0'.
+ private static final int IFNAMSIZ = 16;
+ // The default value of MTU, which means the MTU is unspecified.
+ private static final int DEFAULT_MTU = 0;
- private RtNetlinkLinkMessage(@NonNull StructNlMsgHdr header) {
- super(header);
- mIfinfomsg = null;
- mMtu = 0;
- mHardwareAddress = null;
- mInterfaceName = null;
+ @NonNull
+ private final StructIfinfoMsg mIfinfomsg;
+ private final int mMtu;
+ @Nullable
+ private final MacAddress mHardwareAddress;
+ @Nullable
+ private final String mInterfaceName;
+
+ /**
+ * Creates an {@link RtNetlinkLinkMessage} instance.
+ *
+ * <p>This method validates the arguments and returns {@code null} if any of them are invalid.
+ * nlmsghdr's nlmsg_len will be updated to the correct length before creation.
+ *
+ * @param nlmsghdr The Netlink message header. Must not be {@code null}.
+ * @param ifinfomsg The interface information message. Must not be {@code null}.
+ * @param mtu The Maximum Transmission Unit (MTU) value for the link.
+ * @param hardwareAddress The hardware address (MAC address) of the link. May be {@code null}.
+ * @param interfaceName The name of the interface. May be {@code null}.
+ * @return A new {@link RtNetlinkLinkMessage} instance, or {@code null} if the input arguments
+ * are invalid.
+ */
+ @Nullable
+ public static RtNetlinkLinkMessage build(@NonNull StructNlMsgHdr nlmsghdr,
+ @NonNull StructIfinfoMsg ifinfomsg, int mtu, @Nullable MacAddress hardwareAddress,
+ @Nullable String interfaceName) {
+ if (mtu < 0) {
+ return null;
+ }
+ if (interfaceName != null
+ && (interfaceName.isEmpty() || interfaceName.length() + 1 > IFNAMSIZ)) {
+ return null;
+ }
+
+ nlmsghdr.nlmsg_len = calculateMessageLength(mtu, hardwareAddress, interfaceName);
+ return new RtNetlinkLinkMessage(nlmsghdr, ifinfomsg, mtu, hardwareAddress, interfaceName);
}
- @VisibleForTesting
- public RtNetlinkLinkMessage(@NonNull StructNlMsgHdr nlmsghdr,
- int mtu, @NonNull StructIfinfoMsg ifinfomsg, @NonNull MacAddress hardwareAddress,
- @NonNull String interfaceName) {
+ private RtNetlinkLinkMessage(@NonNull StructNlMsgHdr nlmsghdr,
+ @NonNull StructIfinfoMsg ifinfomsg, int mtu, @Nullable MacAddress hardwareAddress,
+ @Nullable String interfaceName) {
super(nlmsghdr);
- mMtu = mtu;
mIfinfomsg = ifinfomsg;
+ mMtu = mtu;
mHardwareAddress = hardwareAddress;
mInterfaceName = interfaceName;
}
@@ -102,33 +131,46 @@
@Nullable
public static RtNetlinkLinkMessage parse(@NonNull final StructNlMsgHdr header,
@NonNull final ByteBuffer byteBuffer) {
- final RtNetlinkLinkMessage linkMsg = new RtNetlinkLinkMessage(header);
-
- linkMsg.mIfinfomsg = StructIfinfoMsg.parse(byteBuffer);
- if (linkMsg.mIfinfomsg == null) return null;
+ final StructIfinfoMsg ifinfoMsg = StructIfinfoMsg.parse(byteBuffer);
+ if (ifinfoMsg == null) {
+ return null;
+ }
// IFLA_MTU
+ int mtu = DEFAULT_MTU;
final int baseOffset = byteBuffer.position();
StructNlAttr nlAttr = StructNlAttr.findNextAttrOfType(IFLA_MTU, byteBuffer);
if (nlAttr != null) {
- linkMsg.mMtu = nlAttr.getValueAsInt(0 /* default value */);
+ mtu = nlAttr.getValueAsInt(DEFAULT_MTU);
}
// IFLA_ADDRESS
+ MacAddress hardwareAddress = null;
byteBuffer.position(baseOffset);
nlAttr = StructNlAttr.findNextAttrOfType(IFLA_ADDRESS, byteBuffer);
if (nlAttr != null) {
- linkMsg.mHardwareAddress = nlAttr.getValueAsMacAddress();
+ hardwareAddress = nlAttr.getValueAsMacAddress();
}
// IFLA_IFNAME
+ String interfaceName = null;
byteBuffer.position(baseOffset);
nlAttr = StructNlAttr.findNextAttrOfType(IFLA_IFNAME, byteBuffer);
if (nlAttr != null) {
- linkMsg.mInterfaceName = nlAttr.getValueAsString();
+ interfaceName = nlAttr.getValueAsString();
}
- return linkMsg;
+ return new RtNetlinkLinkMessage(header, ifinfoMsg, mtu, hardwareAddress, interfaceName);
+ }
+
+ /**
+ * Write a rtnetlink link message to {@link byte} array.
+ */
+ public byte[] pack(ByteOrder order) {
+ byte[] bytes = new byte[mHeader.nlmsg_len];
+ ByteBuffer buffer = ByteBuffer.wrap(bytes).order(order);
+ pack(buffer);
+ return bytes;
}
/**
@@ -136,10 +178,10 @@
*/
@VisibleForTesting
protected void pack(ByteBuffer byteBuffer) {
- getHeader().pack(byteBuffer);
+ mHeader.pack(byteBuffer);
mIfinfomsg.pack(byteBuffer);
- if (mMtu != 0) {
+ if (mMtu != DEFAULT_MTU) {
final StructNlAttr mtu = new StructNlAttr(IFLA_MTU, mMtu);
mtu.pack(byteBuffer);
}
@@ -153,11 +195,34 @@
}
}
+ /**
+ * Calculate the byte length of the packed buffer.
+ */
+ private static int calculateMessageLength(int mtu, MacAddress hardwareAddress,
+ String interfaceName) {
+ int length = StructNlMsgHdr.STRUCT_SIZE + StructIfinfoMsg.STRUCT_SIZE;
+
+ if (mtu != DEFAULT_MTU) {
+ length += NetlinkConstants.alignedLengthOf(StructNlAttr.NLA_HEADERLEN + Integer.BYTES);
+ }
+ if (hardwareAddress != null) {
+ length += NetlinkConstants.alignedLengthOf(
+ StructNlAttr.NLA_HEADERLEN + ETHER_ADDR_LEN);
+ }
+ if (interfaceName != null) {
+ length += NetlinkConstants.alignedLengthOf(
+ // The string should be end with '\0', so the length should plus 1.
+ StructNlAttr.NLA_HEADERLEN + interfaceName.length() + 1);
+ }
+
+ return length;
+ }
+
@Override
public String toString() {
return "RtNetlinkLinkMessage{ "
+ "nlmsghdr{" + mHeader.toString(OsConstants.NETLINK_ROUTE) + "}, "
- + "Ifinfomsg{" + mIfinfomsg.toString() + "}, "
+ + "Ifinfomsg{" + mIfinfomsg + "}, "
+ "Hardware Address{" + mHardwareAddress + "}, "
+ "MTU{" + mMtu + "}, "
+ "Ifname{" + mInterfaceName + "} "
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkLinkMessageTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkLinkMessageTest.java
index 9db63db..afe220f 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkLinkMessageTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkLinkMessageTest.java
@@ -124,14 +124,14 @@
}
private static final String RTM_NEWLINK_PACK_HEX =
- "34000000100000000000000000000000" // struct nlmsghr
+ "40000000100000000000000000000000" // struct nlmsghr
+ "000001001E0000000210000000000000" // struct ifinfo
+ "08000400DC050000" // IFLA_MTU
+ "0A00010092C3E3C9374E0000" // IFLA_ADDRESS
+ "0A000300776C616E30000000"; // IFLA_IFNAME(wlan0)
@Test
- public void testPackRtmNewLink() {
+ public void testParseAndPackRtmNewLink() {
final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWLINK_PACK_HEX);
byteBuffer.order(ByteOrder.LITTLE_ENDIAN); // For testing.
final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE);
@@ -145,6 +145,21 @@
assertEquals(RTM_NEWLINK_PACK_HEX, HexDump.toHexString(packBuffer.array()));
}
+ @Test
+ public void testPackRtmNewLink() {
+ final RtNetlinkLinkMessage linkMsg = RtNetlinkLinkMessage.build(
+ // nlmsg_len will be updated inside create() method, so it's ok to set 0 here.
+ new StructNlMsgHdr(0 /*nlmsg_len*/, (short) 0x10, (short) 0, 0),
+ new StructIfinfoMsg((byte) 0, (short) 1, 0x1e, 0x1002, 0),
+ 1500,
+ MacAddress.fromString("92:c3:e3:c9:37:4e"),
+ "wlan0");
+ assertNotNull(linkMsg);
+
+ final byte[] packBytes = linkMsg.pack(ByteOrder.LITTLE_ENDIAN);
+ assertEquals(RTM_NEWLINK_PACK_HEX, HexDump.toHexString(packBytes));
+ }
+
private static final String RTM_NEWLINK_TRUNCATED_HEX =
"54000000100000000000000000000000" // struct nlmsghr
+ "000001001E0000000210000000000000" // struct ifinfo