From 08622b8ecb46234f0b04a684b6a02b94f0f0ca8c Mon Sep 17 00:00:00 2001 From: Yang Sun <sunytt@google.com> Date: Wed, 6 Dec 2023 19:51:44 +0800 Subject: [PATCH] Add support for multicast route in RtNetlinkRouteMessage Test: atest NetworkStaticLibTests:com.android.net.moduletests.util.netlink.RtNetlinkRouteMessageTest Change-Id: I2f3d69a5bda81de65e4a36e0db61d98dcf082b2d --- .../module/util/netlink/NetlinkConstants.java | 3 + .../util/netlink/RtNetlinkRouteMessage.java | 158 +++++++++++++++--- .../netlink/RtNetlinkRouteMessageTest.java | 118 ++++++++++++- 3 files changed, 255 insertions(+), 24 deletions(-) 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 44c51d8a8c..ad7a4d7f61 100644 --- a/staticlibs/device/com/android/net/module/util/netlink/NetlinkConstants.java +++ b/staticlibs/device/com/android/net/module/util/netlink/NetlinkConstants.java @@ -151,6 +151,9 @@ public class NetlinkConstants { public static final int RTNLGRP_ND_USEROPT = 20; public static final int RTMGRP_ND_USEROPT = 1 << (RTNLGRP_ND_USEROPT - 1); + // Netlink family + public static final short RTNL_FAMILY_IP6MR = 129; + // Device flags. public static final int IFF_UP = 1 << 0; public static final int IFF_LOWER_UP = 1 << 16; diff --git a/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkRouteMessage.java b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkRouteMessage.java index 9acac69cc2..c21e25fcf6 100644 --- a/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkRouteMessage.java +++ b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkRouteMessage.java @@ -19,11 +19,15 @@ package com.android.net.module.util.netlink; import static android.system.OsConstants.AF_INET; import static android.system.OsConstants.AF_INET6; +import static android.system.OsConstants.NETLINK_ROUTE; import static com.android.net.module.util.NetworkStackConstants.IPV4_ADDR_ANY; import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ANY; +import static com.android.net.module.util.netlink.NetlinkConstants.hexify; +import static com.android.net.module.util.netlink.NetlinkConstants.RTNL_FAMILY_IP6MR; import android.annotation.SuppressLint; import android.net.IpPrefix; +import android.net.RouteInfo; import android.system.OsConstants; import androidx.annotation.NonNull; @@ -34,6 +38,9 @@ import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.IntBuffer; +import java.util.Arrays; /** * A NetlinkMessage subclass for rtnetlink route messages. @@ -49,31 +56,62 @@ import java.nio.ByteBuffer; */ public class RtNetlinkRouteMessage extends NetlinkMessage { public static final short RTA_DST = 1; + public static final short RTA_SRC = 2; + public static final short RTA_IIF = 3; public static final short RTA_OIF = 4; public static final short RTA_GATEWAY = 5; public static final short RTA_CACHEINFO = 12; + public static final short RTA_EXPIRES = 23; - private int mIfindex; + public static final short RTNH_F_UNRESOLVED = 32; // The multicast route is unresolved + + public static final String TAG = "NetlinkRouteMessage"; + + // For multicast routes, whether the route is resolved or unresolved + private boolean mIsResolved; + // The interface index for incoming interface, this is set for multicast + // routes, see common/net/ipv4/ipmr_base.c mr_fill_mroute + private int mIifIndex; // Incoming interface of a route, for resolved multicast routes + private int mOifIndex; @NonNull private StructRtMsg mRtmsg; - @NonNull - private IpPrefix mDestination; + @Nullable + private IpPrefix mSource; // Source address of a route, for all multicast routes + @Nullable + private IpPrefix mDestination; // Destination of a route, can be null for RTM_GETROUTE @Nullable private InetAddress mGateway; @Nullable private StructRtaCacheInfo mRtaCacheInfo; + private long mSinceLastUseMillis; // Milliseconds since the route was used, + // for resolved multicast routes - private RtNetlinkRouteMessage(StructNlMsgHdr header) { + public RtNetlinkRouteMessage(StructNlMsgHdr header, StructRtMsg rtMsg) { super(header); - mRtmsg = null; + mRtmsg = rtMsg; + mSource = null; mDestination = null; mGateway = null; - mIfindex = 0; + mIifIndex = 0; + mOifIndex = 0; mRtaCacheInfo = null; + mSinceLastUseMillis = -1; + } + + /** + * Returns if the route is resolved. This is always true for unicast, + * and may be false only for multicast routes. + */ + public boolean isResolved() { + return mIsResolved; + } + + public int getIifIndex() { + return mIifIndex; } public int getInterfaceIndex() { - return mIfindex; + return mOifIndex; } @NonNull @@ -86,6 +124,14 @@ public class RtNetlinkRouteMessage extends NetlinkMessage { return mDestination; } + /** + * Get source address of a route. This is for multicast routes. + */ + @NonNull + public IpPrefix getSource() { + return mSource; + } + @Nullable public InetAddress getGateway() { return mGateway; @@ -96,6 +142,18 @@ public class RtNetlinkRouteMessage extends NetlinkMessage { return mRtaCacheInfo; } + /** + * RTA_EXPIRES attribute returned by kernel to indicate the clock ticks + * from the route was last used to now, converted to milliseconds. + * This is set for multicast routes. + * + * Note that this value is not updated with the passage of time. It always + * returns the value that was read when the netlink message was parsed. + */ + public long getSinceLastUseMillis() { + return mSinceLastUseMillis; + } + /** * Check whether the address families of destination and gateway match rtm_family in * StructRtmsg. @@ -107,7 +165,8 @@ public class RtNetlinkRouteMessage extends NetlinkMessage { private static boolean matchRouteAddressFamily(@NonNull final InetAddress address, int family) { return ((address instanceof Inet4Address) && (family == AF_INET)) - || ((address instanceof Inet6Address) && (family == AF_INET6)); + || ((address instanceof Inet6Address) && + (family == AF_INET6 || family == RTNL_FAMILY_IP6MR)); } /** @@ -121,11 +180,11 @@ public class RtNetlinkRouteMessage extends NetlinkMessage { @Nullable public static RtNetlinkRouteMessage parse(@NonNull final StructNlMsgHdr header, @NonNull final ByteBuffer byteBuffer) { - final RtNetlinkRouteMessage routeMsg = new RtNetlinkRouteMessage(header); - - routeMsg.mRtmsg = StructRtMsg.parse(byteBuffer); - if (routeMsg.mRtmsg == null) return null; + final StructRtMsg rtmsg = StructRtMsg.parse(byteBuffer); + if (rtmsg == null) return null; + final RtNetlinkRouteMessage routeMsg = new RtNetlinkRouteMessage(header, rtmsg); int rtmFamily = routeMsg.mRtmsg.family; + routeMsg.mIsResolved = ((routeMsg.mRtmsg.flags & RTNH_F_UNRESOLVED) == 0); // RTA_DST final int baseOffset = byteBuffer.position(); @@ -139,12 +198,24 @@ public class RtNetlinkRouteMessage extends NetlinkMessage { routeMsg.mDestination = new IpPrefix(destination, routeMsg.mRtmsg.dstLen); } else if (rtmFamily == AF_INET) { routeMsg.mDestination = new IpPrefix(IPV4_ADDR_ANY, 0); - } else if (rtmFamily == AF_INET6) { + } else if (rtmFamily == AF_INET6 || rtmFamily == RTNL_FAMILY_IP6MR) { routeMsg.mDestination = new IpPrefix(IPV6_ADDR_ANY, 0); } else { return null; } + // RTA_SRC + byteBuffer.position(baseOffset); + nlAttr = StructNlAttr.findNextAttrOfType(RTA_SRC, byteBuffer); + if (nlAttr != null) { + final InetAddress source = nlAttr.getValueAsInetAddress(); + // If the RTA_SRC attribute is malformed, return null. + if (source == null) return null; + // If the address family of destination doesn't match rtm_family, return null. + if (!matchRouteAddressFamily(source, rtmFamily)) return null; + routeMsg.mSource = new IpPrefix(source, routeMsg.mRtmsg.srcLen); + } + // RTA_GATEWAY byteBuffer.position(baseOffset); nlAttr = StructNlAttr.findNextAttrOfType(RTA_GATEWAY, byteBuffer); @@ -156,6 +227,17 @@ public class RtNetlinkRouteMessage extends NetlinkMessage { if (!matchRouteAddressFamily(routeMsg.mGateway, rtmFamily)) return null; } + // RTA_IIF + byteBuffer.position(baseOffset); + nlAttr = StructNlAttr.findNextAttrOfType(RTA_IIF, byteBuffer); + if (nlAttr != null) { + Integer iifInteger = nlAttr.getValueAsInteger(); + if (iifInteger == null) { + return null; + } + routeMsg.mIifIndex = iifInteger; + } + // RTA_OIF byteBuffer.position(baseOffset); nlAttr = StructNlAttr.findNextAttrOfType(RTA_OIF, byteBuffer); @@ -164,7 +246,7 @@ public class RtNetlinkRouteMessage extends NetlinkMessage { // the interface index to a name themselves. This may not succeed or may be // incorrect, because the interface might have been deleted, or even deleted // and re-added with a different index, since the netlink message was sent. - routeMsg.mIfindex = nlAttr.getValueAsInt(0 /* 0 isn't a valid ifindex */); + routeMsg.mOifIndex = nlAttr.getValueAsInt(0 /* 0 isn't a valid ifindex */); } // RTA_CACHEINFO @@ -174,33 +256,59 @@ public class RtNetlinkRouteMessage extends NetlinkMessage { routeMsg.mRtaCacheInfo = StructRtaCacheInfo.parse(nlAttr.getValueAsByteBuffer()); } + // RTA_EXPIRES + byteBuffer.position(baseOffset); + nlAttr = StructNlAttr.findNextAttrOfType(RTA_EXPIRES, byteBuffer); + if (nlAttr != null) { + final Long sinceLastUseCentis = nlAttr.getValueAsLong(); + // If the RTA_EXPIRES attribute is malformed, return null. + if (sinceLastUseCentis == null) return null; + // RTA_EXPIRES returns time in clock ticks of USER_HZ(100), which is centiseconds + routeMsg.mSinceLastUseMillis = sinceLastUseCentis * 10; + } + return routeMsg; } /** * Write a rtnetlink address message to {@link ByteBuffer}. */ - @VisibleForTesting - protected void pack(ByteBuffer byteBuffer) { + public void pack(ByteBuffer byteBuffer) { getHeader().pack(byteBuffer); mRtmsg.pack(byteBuffer); - final StructNlAttr destination = new StructNlAttr(RTA_DST, mDestination.getAddress()); - destination.pack(byteBuffer); + if (mSource != null) { + final StructNlAttr source = new StructNlAttr(RTA_SRC, mSource.getAddress()); + source.pack(byteBuffer); + } + + if (mDestination != null) { + final StructNlAttr destination = new StructNlAttr(RTA_DST, mDestination.getAddress()); + destination.pack(byteBuffer); + } if (mGateway != null) { final StructNlAttr gateway = new StructNlAttr(RTA_GATEWAY, mGateway.getAddress()); gateway.pack(byteBuffer); } - if (mIfindex != 0) { - final StructNlAttr ifindex = new StructNlAttr(RTA_OIF, mIfindex); - ifindex.pack(byteBuffer); + if (mIifIndex != 0) { + final StructNlAttr iifindex = new StructNlAttr(RTA_IIF, mIifIndex); + iifindex.pack(byteBuffer); + } + if (mOifIndex != 0) { + final StructNlAttr oifindex = new StructNlAttr(RTA_OIF, mOifIndex); + oifindex.pack(byteBuffer); } if (mRtaCacheInfo != null) { final StructNlAttr cacheInfo = new StructNlAttr(RTA_CACHEINFO, mRtaCacheInfo.writeToBytes()); cacheInfo.pack(byteBuffer); } + if (mSinceLastUseMillis >= 0) { + final long sinceLastUseCentis = mSinceLastUseMillis / 10; + final StructNlAttr expires = new StructNlAttr(RTA_EXPIRES, sinceLastUseCentis); + expires.pack(byteBuffer); + } } @Override @@ -208,10 +316,14 @@ public class RtNetlinkRouteMessage extends NetlinkMessage { return "RtNetlinkRouteMessage{ " + "nlmsghdr{" + mHeader.toString(OsConstants.NETLINK_ROUTE) + "}, " + "Rtmsg{" + mRtmsg.toString() + "}, " - + "destination{" + mDestination.getAddress().getHostAddress() + "}, " + + (mSource == null ? "" : "source{" + mSource.getAddress().getHostAddress() + "}, ") + + (mDestination == null ? + "" : "destination{" + mDestination.getAddress().getHostAddress() + "}, ") + "gateway{" + (mGateway == null ? "" : mGateway.getHostAddress()) + "}, " - + "ifindex{" + mIfindex + "}, " + + (mIifIndex == 0 ? "" : "iifindex{" + mIifIndex + "}, ") + + "oifindex{" + mOifIndex + "}, " + "rta_cacheinfo{" + (mRtaCacheInfo == null ? "" : mRtaCacheInfo.toString()) + "} " + + (mSinceLastUseMillis < 0 ? "" : "sinceLastUseMillis{" + mSinceLastUseMillis + "}") + "}"; } } diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkRouteMessageTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkRouteMessageTest.java index 9881653a5e..ca407d3fc0 100644 --- a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkRouteMessageTest.java +++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkRouteMessageTest.java @@ -38,6 +38,7 @@ import org.junit.runner.RunWith; import java.net.Inet6Address; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.util.Arrays; @RunWith(AndroidJUnit4.class) @SmallTest @@ -127,6 +128,72 @@ public class RtNetlinkRouteMessageTest { assertEquals(RTM_NEWROUTE_PACK_HEX, HexDump.toHexString(packBuffer.array())); } + private static final String RTM_GETROUTE_MULTICAST_IPV6_HEX = + "1C0000001A0001030000000000000000" // struct nlmsghr + + "810000000000000000000000"; // struct rtmsg + + private static final String RTM_NEWROUTE_MULTICAST_IPV6_HEX = + "88000000180002000000000000000000" // struct nlmsghr + + "81808000FE11000500000000" // struct rtmsg + + "08000F00FE000000" // RTA_TABLE + + "14000200FDACC0F1DBDB000195B7C1A464F944EA" // RTA_SRC + + "14000100FF040000000000000000000000001234" // RTA_DST + + "0800030014000000" // RTA_IIF + + "0C0009000800000111000000" // RTA_MULTIPATH + + "1C00110001000000000000009400000000000000" // RTA_STATS + + "0000000000000000" + + "0C0017007617000000000000"; // RTA_EXPIRES + + @Test + public void testParseRtmNewRoute_MulticastIpv6() { + final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWROUTE_MULTICAST_IPV6_HEX); + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); // For testing. + + final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE); + assertNotNull(msg); + assertTrue(msg instanceof RtNetlinkRouteMessage); + final RtNetlinkRouteMessage routeMsg = (RtNetlinkRouteMessage) msg; + final StructNlMsgHdr hdr = routeMsg.getHeader(); + assertNotNull(hdr); + assertEquals(136, hdr.nlmsg_len); + assertEquals(NetlinkConstants.RTM_NEWROUTE, hdr.nlmsg_type); + + final StructRtMsg rtmsg = routeMsg.getRtMsgHeader(); + assertNotNull(rtmsg); + assertEquals((byte) 129, (byte) rtmsg.family); + assertEquals(128, rtmsg.dstLen); + assertEquals(128, rtmsg.srcLen); + assertEquals(0xFE, rtmsg.table); + + assertEquals(routeMsg.getSource(), + new IpPrefix("fdac:c0f1:dbdb:1:95b7:c1a4:64f9:44ea/128")); + assertEquals(routeMsg.getDestination(), new IpPrefix("ff04::1234/128")); + assertEquals(20, routeMsg.getIifIndex()); + assertEquals(60060, routeMsg.getSinceLastUseMillis()); + } + + // NEWROUTE message for multicast IPv6 with the packed attributes + private static final String RTM_NEWROUTE_MULTICAST_IPV6_PACK_HEX = + "58000000180002000000000000000000" // struct nlmsghr + + "81808000FE11000500000000" // struct rtmsg + + "14000200FDACC0F1DBDB000195B7C1A464F944EA" // RTA_SRC + + "14000100FF040000000000000000000000001234" // RTA_DST + + "0800030014000000" // RTA_IIF + + "0C0017007617000000000000"; // RTA_EXPIRES + @Test + public void testPackRtmNewRoute_MulticastIpv6() { + final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWROUTE_MULTICAST_IPV6_PACK_HEX); + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); // For testing. + final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE); + final RtNetlinkRouteMessage routeMsg = (RtNetlinkRouteMessage) msg; + + final ByteBuffer packBuffer = ByteBuffer.allocate(88); + packBuffer.order(ByteOrder.LITTLE_ENDIAN); // For testing. + routeMsg.pack(packBuffer); + assertEquals(RTM_NEWROUTE_MULTICAST_IPV6_PACK_HEX, + HexDump.toHexString(packBuffer.array())); + } + private static final String RTM_NEWROUTE_TRUNCATED_HEX = "48000000180000060000000000000000" // struct nlmsghr + "0A400000FC02000100000000" // struct rtmsg @@ -220,10 +287,59 @@ public class RtNetlinkRouteMessageTest { + "scope: 0, type: 1, flags: 0}, " + "destination{2001:db8:1::}, " + "gateway{fe80::1}, " - + "ifindex{735}, " + + "oifindex{735}, " + "rta_cacheinfo{clntref: 0, lastuse: 0, expires: 59998, error: 0, used: 0, " + "id: 0, ts: 0, tsage: 0} " + "}"; assertEquals(expected, routeMsg.toString()); } + + @Test + public void testToString_RtmGetRoute() { + final ByteBuffer byteBuffer = toByteBuffer(RTM_GETROUTE_MULTICAST_IPV6_HEX); + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); // For testing. + final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE); + assertNotNull(msg); + assertTrue(msg instanceof RtNetlinkRouteMessage); + final RtNetlinkRouteMessage routeMsg = (RtNetlinkRouteMessage) msg; + final String expected = "RtNetlinkRouteMessage{ " + + "nlmsghdr{" + + "StructNlMsgHdr{ nlmsg_len{28}, nlmsg_type{26(RTM_GETROUTE)}, " + + "nlmsg_flags{769(NLM_F_REQUEST|NLM_F_DUMP)}, nlmsg_seq{0}, nlmsg_pid{0} }}, " + + "Rtmsg{" + + "family: 129, dstLen: 0, srcLen: 0, tos: 0, table: 0, protocol: 0, " + + "scope: 0, type: 0, flags: 0}, " + + "destination{::}, " + + "gateway{}, " + + "oifindex{0}, " + + "rta_cacheinfo{} " + + "}"; + assertEquals(expected, routeMsg.toString()); + } + + @Test + public void testToString_RtmNewRouteMulticastIpv6() { + final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWROUTE_MULTICAST_IPV6_HEX); + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); // For testing. + final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE); + assertNotNull(msg); + assertTrue(msg instanceof RtNetlinkRouteMessage); + final RtNetlinkRouteMessage routeMsg = (RtNetlinkRouteMessage) msg; + final String expected = "RtNetlinkRouteMessage{ " + + "nlmsghdr{" + + "StructNlMsgHdr{ nlmsg_len{136}, nlmsg_type{24(RTM_NEWROUTE)}, " + + "nlmsg_flags{2(NLM_F_MULTI)}, nlmsg_seq{0}, nlmsg_pid{0} }}, " + + "Rtmsg{" + + "family: 129, dstLen: 128, srcLen: 128, tos: 0, table: 254, protocol: 17, " + + "scope: 0, type: 5, flags: 0}, " + + "source{fdac:c0f1:dbdb:1:95b7:c1a4:64f9:44ea}, " + + "destination{ff04::1234}, " + + "gateway{}, " + + "iifindex{20}, " + + "oifindex{0}, " + + "rta_cacheinfo{} " + + "sinceLastUseMillis{60060}" + + "}"; + assertEquals(expected, routeMsg.toString()); + } } -- GitLab