Add data structures to parse netlink route messages.

Bug: 163492391
Test: atest NetworkStaticLibsTests
Change-Id: If8c960849f4840b7c516c0d2579da8a805978e13
This commit is contained in:
Xiao Ma
2021-08-17 14:26:27 +00:00
parent a397d8998b
commit 2c0b10cb0a
5 changed files with 498 additions and 0 deletions

View File

@@ -146,12 +146,26 @@ public class NetlinkConstants {
public static final int RTMGRP_LINK = 1; public static final int RTMGRP_LINK = 1;
public static final int RTMGRP_IPV4_IFADDR = 0x10; public static final int RTMGRP_IPV4_IFADDR = 0x10;
public static final int RTMGRP_IPV6_IFADDR = 0x100; 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 RTNLGRP_ND_USEROPT = 20;
public static final int RTMGRP_ND_USEROPT = 1 << (RTNLGRP_ND_USEROPT - 1); public static final int RTMGRP_ND_USEROPT = 1 << (RTNLGRP_ND_USEROPT - 1);
// Device flags. // Device flags.
public static final int IFF_LOWER_UP = 1 << 16; 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. * Convert a netlink message type to a string for control message.
*/ */

View File

@@ -126,6 +126,9 @@ public class NetlinkMessage {
case NetlinkConstants.RTM_NEWADDR: case NetlinkConstants.RTM_NEWADDR:
case NetlinkConstants.RTM_DELADDR: case NetlinkConstants.RTM_DELADDR:
return (NetlinkMessage) RtNetlinkAddressMessage.parse(nlmsghdr, byteBuffer); 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_NEWNEIGH:
case NetlinkConstants.RTM_DELNEIGH: case NetlinkConstants.RTM_DELNEIGH:
case NetlinkConstants.RTM_GETNEIGH: case NetlinkConstants.RTM_GETNEIGH:

View File

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

View File

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

View File

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