From cb0aabd14cbf81ed6faa337da59a316cb842a6d7 Mon Sep 17 00:00:00 2001 From: Xiao Ma Date: Sat, 13 Apr 2024 14:06:34 +0900 Subject: [PATCH] Add RtmNetlinkPrefixMessage class to represent RTM_NEWPREFIX message. Bug: 315069348 Test: atest NetworkStaticLibTests Change-Id: I566fa6fcc85a19dab8edf2dccb356a53fdc8e6b2 --- .../module/util/netlink/NetlinkMessage.java | 2 + .../util/netlink/RtNetlinkPrefixMessage.java | 160 ++++++++++++++++ .../netlink/RtNetlinkPrefixMessageTest.java | 172 ++++++++++++++++++ 3 files changed, 334 insertions(+) create mode 100644 staticlibs/device/com/android/net/module/util/netlink/RtNetlinkPrefixMessage.java create mode 100644 staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkPrefixMessageTest.java 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 781a04e6cf..dfb2053f9b 100644 --- a/staticlibs/device/com/android/net/module/util/netlink/NetlinkMessage.java +++ b/staticlibs/device/com/android/net/module/util/netlink/NetlinkMessage.java @@ -150,6 +150,8 @@ public class NetlinkMessage { return (NetlinkMessage) RtNetlinkNeighborMessage.parse(nlmsghdr, byteBuffer); case NetlinkConstants.RTM_NEWNDUSEROPT: return (NetlinkMessage) NduseroptMessage.parse(nlmsghdr, byteBuffer); + case NetlinkConstants.RTM_NEWPREFIX: + return (NetlinkMessage) RtNetlinkPrefixMessage.parse(nlmsghdr, byteBuffer); default: return null; } } diff --git a/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkPrefixMessage.java b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkPrefixMessage.java new file mode 100644 index 0000000000..30c63fb8bf --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkPrefixMessage.java @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.netlink; + +import android.net.IpPrefix; +import android.system.OsConstants; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; + +import java.net.Inet6Address; +import java.nio.ByteBuffer; + +/** + * A NetlinkMessage subclass for rtnetlink address messages. + * + * RtNetlinkPrefixMessage.parse() must be called with a ByteBuffer that contains exactly one + * netlink message. + * + * see also: + * + * include/uapi/linux/rtnetlink.h + * + * @hide + */ +public class RtNetlinkPrefixMessage extends NetlinkMessage { + public static final short PREFIX_ADDRESS = 1; + public static final short PREFIX_CACHEINFO = 2; + + @NonNull + private StructPrefixMsg mPrefixmsg; + @NonNull + private IpPrefix mPrefix; + private long mPreferredLifetime; + private long mValidLifetime; + + @VisibleForTesting + public RtNetlinkPrefixMessage(@NonNull final StructNlMsgHdr header, + @NonNull final StructPrefixMsg prefixmsg, + @NonNull final IpPrefix prefix, + long preferred, long valid) { + super(header); + mPrefixmsg = prefixmsg; + mPrefix = prefix; + mPreferredLifetime = preferred; + mValidLifetime = valid; + } + + private RtNetlinkPrefixMessage(@NonNull StructNlMsgHdr header) { + this(header, null, null, 0 /* preferredLifetime */, 0 /* validLifetime */); + } + + @NonNull + public StructPrefixMsg getPrefixMsg() { + return mPrefixmsg; + } + + @NonNull + public IpPrefix getPrefix() { + return mPrefix; + } + + public long getPreferredLifetime() { + return mPreferredLifetime; + } + + public long getValidLifetime() { + return mValidLifetime; + } + + /** + * Parse rtnetlink prefix message from {@link ByteBuffer}. This method must be called with a + * ByteBuffer that contains exactly one netlink message. + * + * RTM_NEWPREFIX Message Format: + * +----------+- - -+-------------+- - -+---------------------+-----------------------+ + * | nlmsghdr | Pad | prefixmsg | Pad | PREFIX_ADDRESS attr | PREFIX_CACHEINFO attr | + * +----------+- - -+-------------+- - -+---------------------+-----------------------+ + * + * @param header netlink message header. + * @param byteBuffer the ByteBuffer instance that wraps the raw netlink message bytes. + */ + @Nullable + public static RtNetlinkPrefixMessage parse(@NonNull final StructNlMsgHdr header, + @NonNull final ByteBuffer byteBuffer) { + try { + final RtNetlinkPrefixMessage msg = new RtNetlinkPrefixMessage(header); + msg.mPrefixmsg = StructPrefixMsg.parse(byteBuffer); + + // PREFIX_ADDRESS + final int baseOffset = byteBuffer.position(); + StructNlAttr nlAttr = StructNlAttr.findNextAttrOfType(PREFIX_ADDRESS, byteBuffer); + if (nlAttr == null) return null; + final Inet6Address addr = (Inet6Address) nlAttr.getValueAsInetAddress(); + if (addr == null) return null; + msg.mPrefix = new IpPrefix(addr, msg.mPrefixmsg.prefix_len); + + // PREFIX_CACHEINFO + byteBuffer.position(baseOffset); + nlAttr = StructNlAttr.findNextAttrOfType(PREFIX_CACHEINFO, byteBuffer); + if (nlAttr == null) return null; + final ByteBuffer buffer = nlAttr.getValueAsByteBuffer(); + if (buffer == null) return null; + final StructPrefixCacheInfo cacheinfo = StructPrefixCacheInfo.parse(buffer); + msg.mPreferredLifetime = cacheinfo.preferred_time; + msg.mValidLifetime = cacheinfo.valid_time; + + return msg; + } catch (IllegalArgumentException e) { + return null; + } + } + + /** + * Write a rtnetlink prefix message to {@link ByteBuffer}. + */ + @VisibleForTesting + protected void pack(ByteBuffer byteBuffer) { + getHeader().pack(byteBuffer); + mPrefixmsg.pack(byteBuffer); + + // PREFIX_ADDRESS attribute + final StructNlAttr prefixAddress = + new StructNlAttr(PREFIX_ADDRESS, mPrefix.getRawAddress()); + prefixAddress.pack(byteBuffer); + + // PREFIX_CACHEINFO attribute + final StructPrefixCacheInfo cacheinfo = + new StructPrefixCacheInfo(mPreferredLifetime, mValidLifetime); + final StructNlAttr prefixCacheinfo = + new StructNlAttr(PREFIX_CACHEINFO, cacheinfo.writeToBytes()); + prefixCacheinfo.pack(byteBuffer); + } + + @Override + public String toString() { + return "RtNetlinkPrefixMessage{ " + + "nlmsghdr{" + mHeader.toString(OsConstants.NETLINK_ROUTE) + "}, " + + "prefixmsg{" + mPrefixmsg.toString() + "}, " + + "IP Prefix{" + mPrefix + "}, " + + "preferred lifetime{" + mPreferredLifetime + "}, " + + "valid lifetime{" + mValidLifetime + "} " + + "}"; + } +} diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkPrefixMessageTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkPrefixMessageTest.java new file mode 100644 index 0000000000..b1779cbd20 --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkPrefixMessageTest.java @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.netlink; + +import static android.system.OsConstants.NETLINK_ROUTE; + +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.IpPrefix; +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 RtNetlinkPrefixMessageTest { + private static final IpPrefix TEST_PREFIX = new IpPrefix("2001:db8:1:1::/64"); + + // An example of the full RTM_NEWPREFIX message. + private static final String RTM_NEWPREFIX_HEX = + "3C000000340000000000000000000000" // struct nlmsghr + + "0A0000002F00000003400300" // struct prefixmsg + + "1400010020010DB8000100010000000000000000" // PREFIX_ADDRESS + + "0C000200803A0900008D2700"; // PREFIX_CACHEINFO + + private ByteBuffer toByteBuffer(final String hexString) { + return ByteBuffer.wrap(HexDump.hexStringToByteArray(hexString)); + } + + @Test + public void testParseRtmNewPrefix() { + final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWPREFIX_HEX); + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); + final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE); + assertNotNull(msg); + assertTrue(msg instanceof RtNetlinkPrefixMessage); + final RtNetlinkPrefixMessage prefixmsg = (RtNetlinkPrefixMessage) msg; + + final StructNlMsgHdr hdr = prefixmsg.getHeader(); + assertNotNull(hdr); + assertEquals(60, hdr.nlmsg_len); + assertEquals(NetlinkConstants.RTM_NEWPREFIX, hdr.nlmsg_type); + assertEquals(0, hdr.nlmsg_flags); + assertEquals(0, hdr.nlmsg_seq); + assertEquals(0, hdr.nlmsg_pid); + + final StructPrefixMsg prefixmsgHdr = prefixmsg.getPrefixMsg(); + assertNotNull(prefixmsgHdr); + assertEquals((byte) OsConstants.AF_INET6, prefixmsgHdr.prefix_family); + assertEquals(3, prefixmsgHdr.prefix_type); + assertEquals(64, prefixmsgHdr.prefix_len); + assertEquals(0x03, prefixmsgHdr.prefix_flags); + assertEquals(0x2F, prefixmsgHdr.prefix_ifindex); + + assertEquals(prefixmsg.getPrefix(), TEST_PREFIX); + assertEquals(604800L, prefixmsg.getPreferredLifetime()); + assertEquals(2592000L, prefixmsg.getValidLifetime()); + } + + @Test + public void testPackRtmNewPrefix() { + final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWPREFIX_HEX); + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); + final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE); + assertNotNull(msg); + assertTrue(msg instanceof RtNetlinkPrefixMessage); + final RtNetlinkPrefixMessage prefixmsg = (RtNetlinkPrefixMessage) msg; + + final ByteBuffer packBuffer = ByteBuffer.allocate(60); + packBuffer.order(ByteOrder.LITTLE_ENDIAN); + prefixmsg.pack(packBuffer); + assertEquals(RTM_NEWPREFIX_HEX, HexDump.toHexString(packBuffer.array())); + } + + private static final String RTM_NEWPREFIX_WITHOUT_PREFIX_ADDRESS_HEX = + "24000000340000000000000000000000" // struct nlmsghr + + "0A0000002F00000003400300" // struct prefixmsg + + "0C000200803A0900008D2700"; // PREFIX_CACHEINFO + + @Test + public void testParseRtmNewPrefix_withoutPrefixAddressAttribute() { + final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWPREFIX_WITHOUT_PREFIX_ADDRESS_HEX); + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); + final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE); + assertNull(msg); + } + + private static final String RTM_NEWPREFIX_WITHOUT_PREFIX_CACHEINFO_HEX = + "30000000340000000000000000000000" // struct nlmsghr + + "0A0000002F00000003400300" // struct prefixmsg + + "140001002A0079E10ABCF6050000000000000000"; // PREFIX_ADDRESS + + @Test + public void testParseRtmNewPrefix_withoutPrefixCacheinfoAttribute() { + final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWPREFIX_WITHOUT_PREFIX_CACHEINFO_HEX); + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); + final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE); + assertNull(msg); + } + + private static final String RTM_NEWPREFIX_TRUNCATED_PREFIX_ADDRESS_HEX = + "3C000000340000000000000000000000" // struct nlmsghr + + "0A0000002F00000003400300" // struct prefixmsg + + "140001002A0079E10ABCF605000000000000" // PREFIX_ADDRESS (truncated) + + "0C000200803A0900008D2700"; // PREFIX_CACHEINFO + + @Test + public void testParseRtmNewPrefix_truncatedPrefixAddressAttribute() { + final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWPREFIX_TRUNCATED_PREFIX_ADDRESS_HEX); + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); + final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE); + assertNull(msg); + } + + private static final String RTM_NEWPREFIX_TRUNCATED_PREFIX_CACHEINFO_HEX = + "3C000000340000000000000000000000" // struct nlmsghr + + "0A0000002F00000003400300" // struct prefixmsg + + "140001002A0079E10ABCF6050000000000000000" // PREFIX_ADDRESS + + "0C000200803A0900008D"; // PREFIX_CACHEINFO (truncated) + + @Test + public void testParseRtmNewPrefix_truncatedPrefixCacheinfoAttribute() { + final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWPREFIX_TRUNCATED_PREFIX_CACHEINFO_HEX); + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); + final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE); + assertNull(msg); + } + + @Test + public void testToString() { + final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWPREFIX_HEX); + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); + final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE); + assertNotNull(msg); + assertTrue(msg instanceof RtNetlinkPrefixMessage); + final RtNetlinkPrefixMessage prefixmsg = (RtNetlinkPrefixMessage) msg; + final String expected = "RtNetlinkPrefixMessage{ " + + "nlmsghdr{StructNlMsgHdr{ nlmsg_len{60}, nlmsg_type{52(RTM_NEWPREFIX)}, " + + "nlmsg_flags{0()}, nlmsg_seq{0}, nlmsg_pid{0} }}, " + + "prefixmsg{prefix_family: 10, prefix_ifindex: 47, prefix_type: 3, " + + "prefix_len: 64, prefix_flags: 3}, " + + "IP Prefix{2001:db8:1:1::/64}, " + + "preferred lifetime{604800}, valid lifetime{2592000} }"; + assertEquals(expected, prefixmsg.toString()); + } +}