Merge "Add data structures to parse netlink route messages."
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 07b52d8..83a82b7 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/NetlinkConstants.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/NetlinkConstants.java
@@ -146,12 +146,26 @@
     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/staticlibs/device/com/android/net/module/util/netlink/NetlinkMessage.java b/staticlibs/device/com/android/net/module/util/netlink/NetlinkMessage.java
index 708736e..a216752 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/NetlinkMessage.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/NetlinkMessage.java
@@ -126,6 +126,9 @@
             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/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkRouteMessage.java b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkRouteMessage.java
new file mode 100644
index 0000000..c5efcb2
--- /dev/null
+++ b/staticlibs/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/staticlibs/device/com/android/net/module/util/netlink/StructRtMsg.java b/staticlibs/device/com/android/net/module/util/netlink/StructRtMsg.java
new file mode 100644
index 0000000..3cd7292
--- /dev/null
+++ b/staticlibs/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/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
new file mode 100644
index 0000000..392314f
--- /dev/null
+++ b/staticlibs/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());
+    }
+}