Add support for multicast route in RtNetlinkRouteMessage

Test: atest NetworkStaticLibTests:com.android.net.moduletests.util.netlink.RtNetlinkRouteMessageTest
Change-Id: I2f3d69a5bda81de65e4a36e0db61d98dcf082b2d
This commit is contained in:
Yang Sun 2023-12-06 19:51:44 +08:00
commit 08622b8ecb
3 changed files with 255 additions and 24 deletions

View file

@ -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;

View file

@ -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 + "}")
+ "}";
}
}

View file

@ -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());
}
}