diff options
author | Xiao Ma <xiaom@google.com> | 2021-11-05 09:58:23 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2021-11-05 09:58:23 +0000 |
commit | 95f78385fd77a9e91db3621d4604a1f4a02f3d45 (patch) | |
tree | 724a7448ec3d47aec8a8ae0f0c3ad10766f9a049 | |
parent | 37e0cdf61042a0599eb8840bcac75b0375534dd9 (diff) | |
parent | ef623a23fee24cf8bf059cb1b0cc282c85c26e33 (diff) | |
download | net-95f78385fd77a9e91db3621d4604a1f4a02f3d45.tar.gz |
Merge "Add data structures to parse netlink route messages." am: ef623a23fe
Original change: https://android-review.googlesource.com/c/platform/frameworks/libs/net/+/1798227
Change-Id: Ia4b8760aa7878387a6808b70696f459cde21e37c
5 files changed, 498 insertions, 0 deletions
diff --git a/common/device/com/android/net/module/util/netlink/NetlinkConstants.java b/common/device/com/android/net/module/util/netlink/NetlinkConstants.java index 07b52d8c..83a82b74 100644 --- a/common/device/com/android/net/module/util/netlink/NetlinkConstants.java +++ b/common/device/com/android/net/module/util/netlink/NetlinkConstants.java @@ -146,12 +146,26 @@ public class NetlinkConstants { public static final int RTMGRP_LINK = 1; public static final int RTMGRP_IPV4_IFADDR = 0x10; public static final int RTMGRP_IPV6_IFADDR = 0x100; + public static final int RTMGRP_IPV6_ROUTE = 0x400; 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; + // Known values for struct rtmsg rtm_protocol. + public static final short RTPROT_KERNEL = 2; + public static final short RTPROT_RA = 9; + + // Known values for struct rtmsg rtm_scope. + public static final short RT_SCOPE_UNIVERSE = 0; + + // Known values for struct rtmsg rtm_type. + public static final short RTN_UNICAST = 1; + + // Known values for struct rtmsg rtm_flags. + public static final int RTM_F_CLONED = 0x200; + /** * Convert a netlink message type to a string for control message. */ diff --git a/common/device/com/android/net/module/util/netlink/NetlinkMessage.java b/common/device/com/android/net/module/util/netlink/NetlinkMessage.java index 708736e5..a216752b 100644 --- a/common/device/com/android/net/module/util/netlink/NetlinkMessage.java +++ b/common/device/com/android/net/module/util/netlink/NetlinkMessage.java @@ -126,6 +126,9 @@ public class NetlinkMessage { case NetlinkConstants.RTM_NEWADDR: case NetlinkConstants.RTM_DELADDR: return (NetlinkMessage) RtNetlinkAddressMessage.parse(nlmsghdr, byteBuffer); + case NetlinkConstants.RTM_NEWROUTE: + case NetlinkConstants.RTM_DELROUTE: + return (NetlinkMessage) RtNetlinkRouteMessage.parse(nlmsghdr, byteBuffer); case NetlinkConstants.RTM_NEWNEIGH: case NetlinkConstants.RTM_DELNEIGH: case NetlinkConstants.RTM_GETNEIGH: diff --git a/common/device/com/android/net/module/util/netlink/RtNetlinkRouteMessage.java b/common/device/com/android/net/module/util/netlink/RtNetlinkRouteMessage.java new file mode 100644 index 00000000..c5efcb26 --- /dev/null +++ b/common/device/com/android/net/module/util/netlink/RtNetlinkRouteMessage.java @@ -0,0 +1,193 @@ +/* + * 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.AF_INET; +import static android.system.OsConstants.AF_INET6; + +import static com.android.net.module.util.NetworkStackConstants.IPV4_ADDR_ANY; +import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ANY; + +import android.net.IpPrefix; +import android.system.OsConstants; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; + +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.nio.ByteBuffer; + +/** + * A NetlinkMessage subclass for rtnetlink route messages. + * + * RtNetlinkRouteMessage.parse() must be called with a ByteBuffer that contains exactly one + * netlink message. + * + * see also: + * + * include/uapi/linux/rtnetlink.h + * + * @hide + */ +public class RtNetlinkRouteMessage extends NetlinkMessage { + public static final short RTA_DST = 1; + public static final short RTA_OIF = 4; + public static final short RTA_GATEWAY = 5; + + private int mIfindex; + @NonNull + private StructRtMsg mRtmsg; + @NonNull + private IpPrefix mDestination; + @Nullable + private InetAddress mGateway; + + private RtNetlinkRouteMessage(StructNlMsgHdr header) { + super(header); + mRtmsg = null; + mDestination = null; + mGateway = null; + mIfindex = 0; + } + + public int getInterfaceIndex() { + return mIfindex; + } + + @NonNull + public StructRtMsg getRtMsgHeader() { + return mRtmsg; + } + + @NonNull + public IpPrefix getDestination() { + return mDestination; + } + + @Nullable + public InetAddress getGateway() { + return mGateway; + } + + /** + * Check whether the address families of destination and gateway match rtm_family in + * StructRtmsg. + * + * For example, IPv4-mapped IPv6 addresses as an IPv6 address will be always converted to IPv4 + * address, that's incorrect when upper layer creates a new {@link RouteInfo} class instance + * for IPv6 route with the converted IPv4 gateway. + */ + private static boolean matchRouteAddressFamily(@NonNull final InetAddress address, + int family) { + return ((address instanceof Inet4Address) && (family == AF_INET)) + || ((address instanceof Inet6Address) && (family == AF_INET6)); + } + + /** + * Parse rtnetlink route 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 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; + int rtmFamily = routeMsg.mRtmsg.family; + + // RTA_DST + final int baseOffset = byteBuffer.position(); + StructNlAttr nlAttr = StructNlAttr.findNextAttrOfType(RTA_DST, byteBuffer); + if (nlAttr != null) { + final InetAddress destination = nlAttr.getValueAsInetAddress(); + // If the RTA_DST attribute is malformed, return null. + if (destination == null) return null; + // If the address family of destination doesn't match rtm_family, return null. + if (!matchRouteAddressFamily(destination, rtmFamily)) return null; + 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) { + routeMsg.mDestination = new IpPrefix(IPV6_ADDR_ANY, 0); + } else { + return null; + } + + // RTA_GATEWAY + byteBuffer.position(baseOffset); + nlAttr = StructNlAttr.findNextAttrOfType(RTA_GATEWAY, byteBuffer); + if (nlAttr != null) { + routeMsg.mGateway = nlAttr.getValueAsInetAddress(); + // If the RTA_GATEWAY attribute is malformed, return null. + if (routeMsg.mGateway == null) return null; + // If the address family of gateway doesn't match rtm_family, return null. + if (!matchRouteAddressFamily(routeMsg.mGateway, rtmFamily)) return null; + } + + // RTA_OIF + byteBuffer.position(baseOffset); + nlAttr = StructNlAttr.findNextAttrOfType(RTA_OIF, byteBuffer); + if (nlAttr != null) { + // Any callers that deal with interface names are responsible for converting + // 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 */); + } + + return routeMsg; + } + + /** + * Write a rtnetlink address message to {@link ByteBuffer}. + */ + @VisibleForTesting + protected void pack(ByteBuffer byteBuffer) { + getHeader().pack(byteBuffer); + mRtmsg.pack(byteBuffer); + + 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); + } + } + + @Override + public String toString() { + return "RtNetlinkRouteMessage{ " + + "nlmsghdr{" + mHeader.toString(OsConstants.NETLINK_ROUTE) + "}, " + + "Rtmsg{" + mRtmsg.toString() + "}, " + + "destination{" + mDestination.getAddress().getHostAddress() + "}, " + + "gateway{" + (mGateway == null ? "" : mGateway.getHostAddress()) + "}, " + + "ifindex{" + mIfindex + "} " + + "}"; + } +} diff --git a/common/device/com/android/net/module/util/netlink/StructRtMsg.java b/common/device/com/android/net/module/util/netlink/StructRtMsg.java new file mode 100644 index 00000000..3cd72922 --- /dev/null +++ b/common/device/com/android/net/module/util/netlink/StructRtMsg.java @@ -0,0 +1,95 @@ +/* + * 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 rtmsg + * + * see also: + * + * include/uapi/linux/rtnetlink.h + * + * @hide + */ +public class StructRtMsg extends Struct { + // Already aligned. + public static final int STRUCT_SIZE = 12; + + @Field(order = 0, type = Type.U8) + public final short family; // Address family of route. + @Field(order = 1, type = Type.U8) + public final short dstLen; // Length of destination. + @Field(order = 2, type = Type.U8) + public final short srcLen; // Length of source. + @Field(order = 3, type = Type.U8) + public final short tos; // TOS filter. + @Field(order = 4, type = Type.U8) + public final short table; // Routing table ID. + @Field(order = 5, type = Type.U8) + public final short protocol; // Routing protocol. + @Field(order = 6, type = Type.U8) + public final short scope; // distance to the destination. + @Field(order = 7, type = Type.U8) + public final short type; // route type + @Field(order = 8, type = Type.U32) + public final long flags; + + StructRtMsg(short family, short dstLen, short srcLen, short tos, short table, short protocol, + short scope, short type, long flags) { + this.family = family; + this.dstLen = dstLen; + this.srcLen = srcLen; + this.tos = tos; + this.table = table; + this.protocol = protocol; + this.scope = scope; + this.type = type; + this.flags = flags; + } + + /** + * Parse a rtmsg struct from a {@link ByteBuffer}. + * + * @param byteBuffer The buffer from which to parse the rtmsg struct. + * @return the parsed rtmsg struct, or {@code null} if the rtmsg struct could not be + * parsed successfully (for example, if it was truncated). + */ + @Nullable + public static StructRtMsg parse(@NonNull final ByteBuffer byteBuffer) { + if (byteBuffer.remaining() < STRUCT_SIZE) return null; + + // The ByteOrder must already have been set to native order. + return Struct.parse(StructRtMsg.class, byteBuffer); + } + + /** + * Write the rtmsg struct 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/common/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkRouteMessageTest.java b/common/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkRouteMessageTest.java new file mode 100644 index 00000000..392314fb --- /dev/null +++ b/common/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkRouteMessageTest.java @@ -0,0 +1,193 @@ +/* + * 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 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.InetAddresses; +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.net.Inet6Address; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class RtNetlinkRouteMessageTest { + private static final IpPrefix TEST_IPV6_GLOBAL_PREFIX = new IpPrefix("2001:db8:1::/64"); + private static final Inet6Address TEST_IPV6_LINK_LOCAL_GATEWAY = + (Inet6Address) InetAddresses.parseNumericAddress("fe80::1"); + + // An example of the full RTM_NEWROUTE message. + private static final String RTM_NEWROUTE_HEX = + "88000000180000060000000000000000" // struct nlmsghr + + "0A400000FC02000100000000" // struct rtmsg + + "08000F00C7060000" // RTA_TABLE + + "1400010020010DB8000100000000000000000000" // RTA_DST + + "08000400DF020000" // RTA_OIF + + "0800060000010000" // RTA_PRIORITY + + "24000C0000000000000000005EEA000000000000" // RTA_CACHEINFO + + "00000000000000000000000000000000" + + "14000500FE800000000000000000000000000001" // RTA_GATEWAY + + "0500140000000000"; // RTA_PREF + + private ByteBuffer toByteBuffer(final String hexString) { + return ByteBuffer.wrap(HexDump.hexStringToByteArray(hexString)); + } + + @Test + public void testParseRtmRouteAddress() { + final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWROUTE_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); + assertEquals(0x600, hdr.nlmsg_flags); + assertEquals(0, hdr.nlmsg_seq); + assertEquals(0, hdr.nlmsg_pid); + + final StructRtMsg rtmsg = routeMsg.getRtMsgHeader(); + assertNotNull(rtmsg); + assertEquals((byte) OsConstants.AF_INET6, rtmsg.family); + assertEquals(64, rtmsg.dstLen); + assertEquals(0, rtmsg.srcLen); + assertEquals(0, rtmsg.tos); + assertEquals(0xFC, rtmsg.table); + assertEquals(NetlinkConstants.RTPROT_KERNEL, rtmsg.protocol); + assertEquals(NetlinkConstants.RT_SCOPE_UNIVERSE, rtmsg.scope); + assertEquals(NetlinkConstants.RTN_UNICAST, rtmsg.type); + assertEquals(0, rtmsg.flags); + + assertEquals(routeMsg.getDestination(), TEST_IPV6_GLOBAL_PREFIX); + assertEquals(735, routeMsg.getInterfaceIndex()); + assertEquals((Inet6Address) routeMsg.getGateway(), TEST_IPV6_LINK_LOCAL_GATEWAY); + } + + private static final String RTM_NEWROUTE_PACK_HEX = + "4C000000180000060000000000000000" // struct nlmsghr + + "0A400000FC02000100000000" // struct rtmsg + + "1400010020010DB8000100000000000000000000" // RTA_DST + + "14000500FE800000000000000000000000000001" // RTA_GATEWAY + + "08000400DF020000"; // RTA_OIF + + @Test + public void testPackRtmNewRoute() { + final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWROUTE_PACK_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 ByteBuffer packBuffer = ByteBuffer.allocate(76); + packBuffer.order(ByteOrder.LITTLE_ENDIAN); // For testing. + routeMsg.pack(packBuffer); + assertEquals(RTM_NEWROUTE_PACK_HEX, HexDump.toHexString(packBuffer.array())); + } + + private static final String RTM_NEWROUTE_TRUNCATED_HEX = + "48000000180000060000000000000000" // struct nlmsghr + + "0A400000FC02000100000000" // struct rtmsg + + "1400010020010DB8000100000000000000000000" // RTA_DST + + "10000500FE8000000000000000000000" // RTA_GATEWAY(truncated) + + "08000400DF020000"; // RTA_OIF + + @Test + public void testTruncatedRtmNewRoute() { + final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWROUTE_TRUNCATED_HEX); + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); // For testing. + final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE); + // Parsing RTM_NEWROUTE with truncated RTA_GATEWAY attribute returns null. + assertNull(msg); + } + + private static final String RTM_NEWROUTE_IPV4_MAPPED_IPV6_GATEWAY_HEX = + "4C000000180000060000000000000000" // struct nlmsghr + + "0A400000FC02000100000000" // struct rtmsg + + "1400010020010DB8000100000000000000000000" // RTA_DST(2001:db8:1::/64) + + "1400050000000000000000000000FFFF0A010203" // RTA_GATEWAY(::ffff:10.1.2.3) + + "08000400DF020000"; // RTA_OIF + + @Test + public void testParseRtmRouteAddress_IPv4MappedIPv6Gateway() { + final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWROUTE_IPV4_MAPPED_IPV6_GATEWAY_HEX); + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); // For testing. + final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE); + // Parsing RTM_NEWROUTE with IPv4-mapped IPv6 gateway address, which doesn't match + // rtm_family after address parsing. + assertNull(msg); + } + + private static final String RTM_NEWROUTE_IPV4_MAPPED_IPV6_DST_HEX = + "4C000000180000060000000000000000" // struct nlmsghr + + "0A780000FC02000100000000" // struct rtmsg + + "1400010000000000000000000000FFFF0A000000" // RTA_DST(::ffff:10.0.0.0/120) + + "14000500FE800000000000000000000000000001" // RTA_GATEWAY(fe80::1) + + "08000400DF020000"; // RTA_OIF + + @Test + public void testParseRtmRouteAddress_IPv4MappedIPv6Destination() { + final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWROUTE_IPV4_MAPPED_IPV6_DST_HEX); + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); // For testing. + final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE); + // Parsing RTM_NEWROUTE with IPv4-mapped IPv6 destination prefix, which doesn't match + // rtm_family after address parsing. + assertNull(msg); + } + + @Test + public void testToString() { + final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWROUTE_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{1536(NLM_F_MATCH)}, nlmsg_seq{0}, nlmsg_pid{0} }}, " + + "Rtmsg{" + + "family: 10, dstLen: 64, srcLen: 0, tos: 0, table: 252, protocol: 2, " + + "scope: 0, type: 1, flags: 0}, " + + "destination{2001:db8:1::}, " + + "gateway{fe80::1}, " + + "ifindex{735} " + + "}"; + assertEquals(expected, routeMsg.toString()); + } +} |