Add data structures to parse netlink link messages.
Bug: 163492391
Test: atest NetworkStaticLibsTests
Change-Id: Ida8c22a782eeaca738487d0dc041a459ef6bb936
diff --git a/staticlibs/device/com/android/net/module/util/netlink/NetlinkConstants.java b/staticlibs/device/com/android/net/module/util/netlink/NetlinkConstants.java
index cf9d2c5..11aee84 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/NetlinkConstants.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/NetlinkConstants.java
@@ -70,6 +70,7 @@
if (family == OsConstants.AF_INET) return "AF_INET";
if (family == OsConstants.AF_INET6) return "AF_INET6";
if (family == OsConstants.AF_NETLINK) return "AF_NETLINK";
+ if (family == OsConstants.AF_UNSPEC) return "AF_UNSPEC";
return String.valueOf(family);
}
@@ -142,9 +143,13 @@
public static final short SOCK_DIAG_BY_FAMILY = 20;
// Netlink groups.
+ public static final int RTMGRP_LINK = 1;
public static final int RTNLGRP_ND_USEROPT = 20;
public static final int RTMGRP_ND_USEROPT = 1 << (RTNLGRP_ND_USEROPT - 1);
+ // Device flags.
+ public static final int IFF_LOWER_UP = 1 << 16;
+
/**
* Convert a netlink message type to a string for control message.
*/
diff --git a/staticlibs/device/com/android/net/module/util/netlink/NetlinkMessage.java b/staticlibs/device/com/android/net/module/util/netlink/NetlinkMessage.java
index 723d682..d83c12f 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/NetlinkMessage.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/NetlinkMessage.java
@@ -120,6 +120,9 @@
private static NetlinkMessage parseRtMessage(@NonNull StructNlMsgHdr nlmsghdr,
@NonNull ByteBuffer byteBuffer) {
switch (nlmsghdr.nlmsg_type) {
+ case NetlinkConstants.RTM_NEWLINK:
+ case NetlinkConstants.RTM_DELLINK:
+ return (NetlinkMessage) RtNetlinkLinkMessage.parse(nlmsghdr, byteBuffer);
case NetlinkConstants.RTM_NEWNEIGH:
case NetlinkConstants.RTM_DELNEIGH:
case NetlinkConstants.RTM_GETNEIGH:
diff --git a/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkLinkMessage.java b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkLinkMessage.java
new file mode 100644
index 0000000..92ec0c4
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkLinkMessage.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2021 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.netlink;
+
+import android.net.MacAddress;
+import android.system.OsConstants;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import java.nio.ByteBuffer;
+
+/**
+ * A NetlinkMessage subclass for rtnetlink link messages.
+ *
+ * RtNetlinkLinkMessage.parse() must be called with a ByteBuffer that contains exactly one netlink
+ * message.
+ *
+ * see also:
+ *
+ * include/uapi/linux/rtnetlink.h
+ *
+ * @hide
+ */
+public class RtNetlinkLinkMessage extends NetlinkMessage {
+ public static final short IFLA_ADDRESS = 1;
+ public static final short IFLA_IFNAME = 3;
+ public static final short IFLA_MTU = 4;
+
+ private int mMtu;
+ @NonNull
+ private StructIfinfoMsg mIfinfomsg;
+ @Nullable
+ private MacAddress mHardwareAddress;
+ @Nullable
+ private String mInterfaceName;
+
+ private RtNetlinkLinkMessage(@NonNull StructNlMsgHdr header) {
+ super(header);
+ mIfinfomsg = null;
+ mMtu = 0;
+ mHardwareAddress = null;
+ mInterfaceName = null;
+ }
+
+ public int getMtu() {
+ return mMtu;
+ }
+
+ @NonNull
+ public StructIfinfoMsg getIfinfoHeader() {
+ return mIfinfomsg;
+ }
+
+ @Nullable
+ public MacAddress getHardwareAddress() {
+ return mHardwareAddress;
+ }
+
+ @Nullable
+ public String getInterfaceName() {
+ return mInterfaceName;
+ }
+
+ /**
+ * Parse rtnetlink link message from {@link ByteBuffer}. This method must be called with a
+ * ByteBuffer that contains exactly one netlink message.
+ *
+ * @param header netlink message header.
+ * @param byteBuffer the ByteBuffer instance that wraps the raw netlink message bytes.
+ */
+ @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;
+
+ // IFLA_MTU
+ final int baseOffset = byteBuffer.position();
+ StructNlAttr nlAttr = StructNlAttr.findNextAttrOfType(IFLA_MTU, byteBuffer);
+ if (nlAttr != null) {
+ linkMsg.mMtu = nlAttr.getValueAsInt(0 /* default value */);
+ }
+
+ // IFLA_ADDRESS
+ byteBuffer.position(baseOffset);
+ nlAttr = StructNlAttr.findNextAttrOfType(IFLA_ADDRESS, byteBuffer);
+ if (nlAttr != null) {
+ linkMsg.mHardwareAddress = nlAttr.getValueAsMacAddress();
+ }
+
+ // IFLA_IFNAME
+ byteBuffer.position(baseOffset);
+ nlAttr = StructNlAttr.findNextAttrOfType(IFLA_IFNAME, byteBuffer);
+ if (nlAttr != null) {
+ linkMsg.mInterfaceName = nlAttr.getValueAsString();
+ }
+
+ return linkMsg;
+ }
+
+ /**
+ * Write a rtnetlink link message to {@link ByteBuffer}.
+ */
+ @VisibleForTesting
+ protected void pack(ByteBuffer byteBuffer) {
+ getHeader().pack(byteBuffer);
+ mIfinfomsg.pack(byteBuffer);
+
+ if (mMtu != 0) {
+ final StructNlAttr mtu = new StructNlAttr(IFLA_MTU, mMtu);
+ mtu.pack(byteBuffer);
+ }
+ if (mHardwareAddress != null) {
+ final StructNlAttr hardwareAddress = new StructNlAttr(IFLA_ADDRESS, mHardwareAddress);
+ hardwareAddress.pack(byteBuffer);
+ }
+ if (mInterfaceName != null) {
+ final StructNlAttr ifname = new StructNlAttr(IFLA_IFNAME, mInterfaceName);
+ ifname.pack(byteBuffer);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "RtNetlinkLinkMessage{ "
+ + "nlmsghdr{" + mHeader.toString(OsConstants.NETLINK_ROUTE) + "}, "
+ + "Ifinfomsg{" + mIfinfomsg.toString() + "}, "
+ + "Hardware Address{" + mHardwareAddress + "}, "
+ + "MTU{" + mMtu + "}, "
+ + "Ifname{" + mInterfaceName + "} "
+ + "}";
+ }
+}
diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructIfinfoMsg.java b/staticlibs/device/com/android/net/module/util/netlink/StructIfinfoMsg.java
new file mode 100644
index 0000000..881dc18
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/netlink/StructIfinfoMsg.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2021 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.netlink;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+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;
+
+/**
+ * struct ifinfomsg
+ *
+ * see also:
+ *
+ * include/uapi/linux/rtnetlink.h
+ *
+ * @hide
+ */
+public class StructIfinfoMsg extends Struct {
+ // Already aligned.
+ public static final int STRUCT_SIZE = 16;
+
+ @Field(order = 0, type = Type.U8, padding = 1)
+ public final short family;
+ @Field(order = 1, type = Type.U16)
+ public final int type;
+ @Field(order = 2, type = Type.S32)
+ public final int index;
+ @Field(order = 3, type = Type.U32)
+ public final long flags;
+ @Field(order = 4, type = Type.U32)
+ public final long change;
+
+ StructIfinfoMsg(short family, int type, int index, long flags, long change) {
+ this.family = family;
+ this.type = type;
+ this.index = index;
+ this.flags = flags;
+ this.change = change;
+ }
+
+ /**
+ * Parse a rtnetlink link message header from a {@link ByteBuffer}.
+ *
+ * @param byteBuffer The buffer from which to parse the rtnetlink link message header.
+ * @return the parsed rtnetlink link message header, or {@code null} if the rtnetlink message
+ * header could not be parsed successfully (for example, if it was truncated).
+ */
+ @Nullable
+ public static StructIfinfoMsg parse(@NonNull final ByteBuffer byteBuffer) {
+ if (byteBuffer.remaining() < STRUCT_SIZE) return null;
+
+ // The ByteOrder must already have been set to native order.
+ return StructIfinfoMsg.parse(StructIfinfoMsg.class, byteBuffer);
+ }
+
+ /**
+ * Write ifinfomsg to {@link ByteBuffer}.
+ */
+ public void pack(@NonNull final ByteBuffer byteBuffer) {
+ // The ByteOrder must already have been set to native order.
+ this.writeToByteBuffer(byteBuffer);
+ }
+}
diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructNlAttr.java b/staticlibs/device/com/android/net/module/util/netlink/StructNlAttr.java
index 80f0057..485e67c 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/StructNlAttr.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/StructNlAttr.java
@@ -16,12 +16,17 @@
package com.android.net.module.util.netlink;
+import android.net.MacAddress;
+
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
+import java.util.Arrays;
/**
* struct nlattr
@@ -173,11 +178,35 @@
}
}
- public StructNlAttr(short type, InetAddress ip) {
+ public StructNlAttr(short type, @NonNull final byte[] value) {
+ nla_type = type;
+ setValue(value);
+ }
+
+ public StructNlAttr(short type, @NonNull final InetAddress ip) {
nla_type = type;
setValue(ip.getAddress());
}
+ public StructNlAttr(short type, @NonNull final MacAddress mac) {
+ nla_type = type;
+ setValue(mac.toByteArray());
+ }
+
+ public StructNlAttr(short type, @NonNull final String string) {
+ nla_type = type;
+ byte[] value = null;
+ try {
+ final byte[] stringBytes = string.getBytes("UTF-8");
+ // Append '\0' at the end of interface name string bytes.
+ value = Arrays.copyOf(stringBytes, stringBytes.length + 1);
+ } catch (UnsupportedEncodingException ignored) {
+ // Do nothing.
+ } finally {
+ setValue(value);
+ }
+ }
+
public StructNlAttr(short type, StructNlAttr... nested) {
this();
nla_type = makeNestedType(type);
@@ -270,7 +299,11 @@
/**
* Get attribute value as InetAddress.
+ *
+ * @return the InetAddress instance representation of attribute value or null if IP address
+ * is of illegal length.
*/
+ @Nullable
public InetAddress getValueAsInetAddress() {
if (nla_value == null) return null;
@@ -282,6 +315,43 @@
}
/**
+ * Get attribute value as MacAddress.
+ *
+ * @return the MacAddress instance representation of attribute value or null if the given byte
+ * array is not a valid representation(e.g, not all link layers have 6-byte link-layer
+ * addresses)
+ */
+ @Nullable
+ public MacAddress getValueAsMacAddress() {
+ if (nla_value == null) return null;
+
+ try {
+ return MacAddress.fromBytes(nla_value);
+ } catch (IllegalArgumentException ignored) {
+ return null;
+ }
+ }
+
+ /**
+ * Get attribute value as a unicode string.
+ *
+ * @return a unicode string or null if UTF-8 charset is not supported.
+ */
+ @Nullable
+ public String getValueAsString() {
+ if (nla_value == null) return null;
+ // Check the attribute value length after removing string termination flag '\0'.
+ if (nla_value.length < (nla_len - NLA_HEADERLEN - 1)) return null;
+
+ try {
+ final byte[] array = Arrays.copyOf(nla_value, nla_len - NLA_HEADERLEN - 1);
+ return new String(array, "UTF-8");
+ } catch (UnsupportedEncodingException | NegativeArraySizeException ignored) {
+ return null;
+ }
+ }
+
+ /**
* Write the netlink attribute to {@link ByteBuffer}.
*/
public void pack(ByteBuffer byteBuffer) {
diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructNlMsgHdr.java b/staticlibs/device/com/android/net/module/util/netlink/StructNlMsgHdr.java
index 9567cce..5052cb8 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/StructNlMsgHdr.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/StructNlMsgHdr.java
@@ -166,7 +166,7 @@
return "StructNlMsgHdr{ "
+ "nlmsg_len{" + nlmsg_len + "}, "
+ "nlmsg_type{" + typeStr + "}, "
- + "nlmsg_flags{" + flagsStr + ")}, "
+ + "nlmsg_flags{" + flagsStr + "}, "
+ "nlmsg_seq{" + nlmsg_seq + "}, "
+ "nlmsg_pid{" + nlmsg_pid + "} "
+ "}";
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/ConntrackMessageTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/ConntrackMessageTest.java
index cea763c..f02b4cb 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/ConntrackMessageTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/ConntrackMessageTest.java
@@ -422,7 +422,7 @@
final String expected = ""
+ "ConntrackMessage{"
+ "nlmsghdr{StructNlMsgHdr{ nlmsg_len{140}, nlmsg_type{256(IPCTNL_MSG_CT_NEW)}, "
- + "nlmsg_flags{1536(NLM_F_MATCH))}, nlmsg_seq{0}, nlmsg_pid{0} }}, "
+ + "nlmsg_flags{1536(NLM_F_MATCH)}, nlmsg_seq{0}, nlmsg_pid{0} }}, "
+ "nfgenmsg{NfGenMsg{ nfgen_family{AF_INET}, version{0}, res_id{4660} }}, "
+ "tuple_orig{Tuple{IPPROTO_TCP: 192.168.80.12:62449 -> 140.112.8.116:443}}, "
+ "tuple_reply{Tuple{IPPROTO_TCP: 140.112.8.116:443 -> 100.81.179.1:62449}}, "
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
new file mode 100644
index 0000000..58a7478
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkLinkMessageTest.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2021 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.netlink;
+
+import static android.system.OsConstants.NETLINK_ROUTE;
+
+import static com.android.net.module.util.NetworkStackConstants.ETHER_MTU;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.net.MacAddress;
+import android.system.OsConstants;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.net.module.util.HexDump;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class RtNetlinkLinkMessageTest {
+ private static final String TAG = "RtNetlinkLinkMessageTest";
+
+ // An example of the full RTM_NEWLINK message.
+ private static final String RTM_NEWLINK_HEX =
+ "64000000100000000000000000000000" // struct nlmsghr
+ + "000001001E0000000210000000000000" // struct ifinfo
+ + "0A000300776C616E30000000" // IFLA_IFNAME
+ + "08000D00B80B0000" // IFLA_PROTINFO
+ + "0500100002000000" // IFLA_OPERSTATE
+ + "0500110001000000" // IFLA_LINKMODE
+ + "08000400DC050000" // IFLA_MTU
+ + "0A00010092C3E3C9374E0000" // IFLA_ADDRESS
+ + "0A000200FFFFFFFFFFFF0000"; // IFLA_BROADCAST
+
+ private ByteBuffer toByteBuffer(final String hexString) {
+ return ByteBuffer.wrap(HexDump.hexStringToByteArray(hexString));
+ }
+
+ @Test
+ public void testParseRtmNewLink() {
+ final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWLINK_HEX);
+ byteBuffer.order(ByteOrder.LITTLE_ENDIAN); // For testing.
+ final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE);
+ assertNotNull(msg);
+ assertTrue(msg instanceof RtNetlinkLinkMessage);
+ final RtNetlinkLinkMessage linkMsg = (RtNetlinkLinkMessage) msg;
+
+ final StructNlMsgHdr hdr = linkMsg.getHeader();
+ assertNotNull(hdr);
+ assertEquals(100, hdr.nlmsg_len);
+ assertEquals(NetlinkConstants.RTM_NEWLINK, hdr.nlmsg_type);
+ assertEquals(0, hdr.nlmsg_flags);
+ assertEquals(0, hdr.nlmsg_seq);
+ assertEquals(0, hdr.nlmsg_pid);
+
+ final StructIfinfoMsg ifinfomsgHdr = linkMsg.getIfinfoHeader();
+ assertNotNull(ifinfomsgHdr);
+ assertEquals((byte) OsConstants.AF_UNSPEC, ifinfomsgHdr.family);
+ assertEquals(OsConstants.ARPHRD_ETHER, ifinfomsgHdr.type);
+ assertEquals(30, ifinfomsgHdr.index);
+ assertEquals(0, ifinfomsgHdr.change);
+
+ assertEquals(ETHER_MTU, linkMsg.getMtu());
+ assertEquals(MacAddress.fromString("92:C3:E3:C9:37:4E"), linkMsg.getHardwareAddress());
+ assertTrue(linkMsg.getInterfaceName().equals("wlan0"));
+ }
+
+ private static final String RTM_NEWLINK_PACK_HEX =
+ "34000000100000000000000000000000" // struct nlmsghr
+ + "000001001E0000000210000000000000" // struct ifinfo
+ + "08000400DC050000" // IFLA_MTU
+ + "0A00010092C3E3C9374E0000" // IFLA_ADDRESS
+ + "0A000300776C616E30000000"; // IFLA_IFNAME
+
+ @Test
+ public void testPackRtmNewLink() {
+ final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWLINK_PACK_HEX);
+ byteBuffer.order(ByteOrder.LITTLE_ENDIAN); // For testing.
+ final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE);
+ assertNotNull(msg);
+ assertTrue(msg instanceof RtNetlinkLinkMessage);
+ final RtNetlinkLinkMessage linkMsg = (RtNetlinkLinkMessage) msg;
+
+ final ByteBuffer packBuffer = ByteBuffer.allocate(64);
+ packBuffer.order(ByteOrder.LITTLE_ENDIAN); // For testing.
+ linkMsg.pack(packBuffer);
+ assertEquals(RTM_NEWLINK_PACK_HEX, HexDump.toHexString(packBuffer.array()));
+ }
+
+ private static final String RTM_NEWLINK_TRUNCATED_HEX =
+ "54000000100000000000000000000000" // struct nlmsghr
+ + "000001001E0000000210000000000000" // struct ifinfo
+ + "08000D00B80B0000" // IFLA_PROTINFO
+ + "0500100002000000" // IFLA_OPERSTATE
+ + "0800010092C3E3C9" // IFLA_ADDRESS(truncated)
+ + "0500110001000000" // IFLA_LINKMODE
+ + "0A000300776C616E30000000" // IFLA_IFNAME
+ + "08000400DC050000"; // IFLA_MTU
+
+ @Test
+ public void testTruncatedRtmNewLink() {
+ final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWLINK_TRUNCATED_HEX);
+ byteBuffer.order(ByteOrder.LITTLE_ENDIAN); // For testing.
+ final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE);
+ assertNotNull(msg);
+ assertTrue(msg instanceof RtNetlinkLinkMessage);
+ final RtNetlinkLinkMessage linkMsg = (RtNetlinkLinkMessage) msg;
+
+ // Truncated IFLA_ADDRESS attribute doesn't affect parsing other attrs.
+ assertNull(linkMsg.getHardwareAddress());
+ assertEquals(ETHER_MTU, linkMsg.getMtu());
+ assertTrue(linkMsg.getInterfaceName().equals("wlan0"));
+ }
+
+ @Test
+ public void testToString() {
+ final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWLINK_HEX);
+ byteBuffer.order(ByteOrder.LITTLE_ENDIAN); // For testing.
+ final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE);
+ assertNotNull(msg);
+ assertTrue(msg instanceof RtNetlinkLinkMessage);
+ final RtNetlinkLinkMessage linkMsg = (RtNetlinkLinkMessage) msg;
+ final String expected = "RtNetlinkLinkMessage{ "
+ + "nlmsghdr{"
+ + "StructNlMsgHdr{ nlmsg_len{100}, nlmsg_type{16(RTM_NEWLINK)}, nlmsg_flags{0()}, "
+ + "nlmsg_seq{0}, nlmsg_pid{0} }}, "
+ + "Ifinfomsg{"
+ + "family: 0, type: 1, index: 30, flags: 4098, change: 0}, "
+ + "Hardware Address{92:c3:e3:c9:37:4e}, " + "MTU{1500}, "
+ + "Ifname{wlan0} "
+ + "}";
+ assertEquals(expected, linkMsg.toString());
+ }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNlAttrTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNlAttrTest.java
new file mode 100644
index 0000000..72e179b
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNlAttrTest.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2021 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.netlink;
+
+import static com.android.net.module.util.netlink.RtNetlinkLinkMessage.IFLA_ADDRESS;
+import static com.android.net.module.util.netlink.RtNetlinkLinkMessage.IFLA_IFNAME;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import android.net.MacAddress;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class StructNlAttrTest {
+ private static final MacAddress TEST_MAC_ADDRESS = MacAddress.fromString("00:11:22:33:44:55");
+ private static final String TEST_INTERFACE_NAME = "wlan0";
+
+ @Test
+ public void testGetValueAsMacAddress() {
+ final StructNlAttr attr1 = new StructNlAttr(IFLA_ADDRESS, TEST_MAC_ADDRESS);
+ final MacAddress address1 = attr1.getValueAsMacAddress();
+ assertEquals(address1, TEST_MAC_ADDRESS);
+
+ // Invalid mac address byte array.
+ final byte[] array = new byte[] {
+ (byte) 0x00, (byte) 0x11, (byte) 0x22, (byte) 0x33,
+ (byte) 0x44, (byte) 0x55, (byte) 0x66,
+ };
+ final StructNlAttr attr2 = new StructNlAttr(IFLA_ADDRESS, array);
+ final MacAddress address2 = attr2.getValueAsMacAddress();
+ assertNull(address2);
+ }
+
+ @Test
+ public void testGetValueAsString() {
+ final StructNlAttr attr1 = new StructNlAttr(IFLA_IFNAME, TEST_INTERFACE_NAME);
+ final String str1 = attr1.getValueAsString();
+ assertEquals(str1, TEST_INTERFACE_NAME);
+
+ final byte[] array = new byte[] {
+ (byte) 0x77, (byte) 0x6c, (byte) 0x61, (byte) 0x6E, (byte) 0x30, (byte) 0x00,
+ };
+ final StructNlAttr attr2 = new StructNlAttr(IFLA_IFNAME, array);
+ final String str2 = attr2.getValueAsString();
+ assertEquals(str2, TEST_INTERFACE_NAME);
+ }
+}