Add "throw" and "unreachable" routes to NetdBinderTest

Add "throw" and "unreachable" routes data to
NetdBinderTest#NetworkAddRemoveRouteUserPermission test case.

Modify 'ipRouteString' util method to correctly generate "throw" and
"unreachable" routes representation.

Bug: 186082280
Test: atest NetdBinderTest
Change-Id: Idd57eb85ffe2488e9ff6f25d68e58ef083c9cd6c
diff --git a/tests/binder_test.cpp b/tests/binder_test.cpp
index 3c42717..1a0886e 100644
--- a/tests/binder_test.cpp
+++ b/tests/binder_test.cpp
@@ -24,6 +24,7 @@
 #include <cstdlib>
 #include <iostream>
 #include <mutex>
+#include <numeric>
 #include <regex>
 #include <set>
 #include <string>
@@ -1764,39 +1765,86 @@
 
 namespace {
 
-std::string ipRouteString(const std::string& ifName, const std::string& dst,
-                          const std::string& nextHop, const std::string& mtu) {
-    std::string dstString = (dst == "0.0.0.0/0" || dst == "::/0") ? "default" : dst;
+// Output looks like this:
+//
+// IPv4:
+//
+// throw        dst                         proto static    scope link
+// unreachable  dst                         proto static    scope link
+//              dst via nextHop dev ifName  proto static
+//              dst             dev ifName  proto static    scope link
+//
+// IPv6:
+//
+// throw        dst             dev lo      proto static    metric 1024
+// unreachable  dst             dev lo      proto static    metric 1024
+//              dst via nextHop dev ifName  proto static    metric 1024
+//              dst             dev ifName  proto static    metric 1024
+std::string ipRoutePrefix(const std::string& ifName, const std::string& dst,
+                          const std::string& nextHop) {
+    std::string prefixString;
 
-    if (!nextHop.empty()) {
-        dstString += " via " + nextHop;
+    bool isThrow = nextHop == "throw";
+    bool isUnreachable = nextHop == "unreachable";
+    bool isDefault = (dst == "0.0.0.0/0" || dst == "::/0");
+    bool isIPv6 = dst.find(':') != std::string::npos;
+    bool isThrowOrUnreachable = isThrow || isUnreachable;
+
+    if (isThrowOrUnreachable) {
+        prefixString += nextHop + " ";
     }
 
-    dstString += " dev " + ifName;
+    prefixString += isDefault ? "default" : dst;
+
+    if (!nextHop.empty() && !isThrowOrUnreachable) {
+        prefixString += " via " + nextHop;
+    }
+
+    if (isThrowOrUnreachable) {
+        if (isIPv6) {
+            prefixString += " dev lo";
+        }
+    } else {
+        prefixString += " dev " + ifName;
+    }
+
+    prefixString += " proto static";
+
+    // IPv6 routes report the metric, IPv4 routes report the scope.
+    if (isIPv6) {
+        prefixString += " metric 1024";
+    } else {
+        if (nextHop.empty() || isThrowOrUnreachable) {
+            prefixString += " scope link";
+        }
+    }
+
+    return prefixString;
+}
+
+std::vector<std::string> ipRouteSubstrings(const std::string& ifName, const std::string& dst,
+                                           const std::string& nextHop, const std::string& mtu) {
+    std::vector<std::string> routeSubstrings;
+
+    routeSubstrings.push_back(ipRoutePrefix(ifName, dst, nextHop));
 
     if (!mtu.empty()) {
-        dstString += " proto static";
-        // IPv6 routes report the metric, IPv4 routes report the scope.
-        // TODO: move away from specifying the entire string and use a regexp instead.
-        if (dst.find(':') != std::string::npos) {
-            dstString += " metric 1024";
-        } else {
-            if (nextHop.empty()) {
-                dstString += " scope link";
-            }
-        }
-        dstString += " mtu " + mtu;
+        // Add separate substring to match mtu value.
+        // This is needed because on some devices "error -11"/"error -113" appears between ip prefix
+        // and mtu for throw/unreachable routes.
+        routeSubstrings.push_back("mtu " + mtu);
     }
 
-    return dstString;
+    return routeSubstrings;
 }
 
 void expectNetworkRouteExistsWithMtu(const char* ipVersion, const std::string& ifName,
                                      const std::string& dst, const std::string& nextHop,
                                      const std::string& mtu, const char* table) {
-    std::string routeString = ipRouteString(ifName, dst, nextHop, mtu);
-    EXPECT_TRUE(ipRouteExists(ipVersion, table, routeString))
-            << "Couldn't find route to " << dst << ": '" << routeString << "' in table " << table;
+    std::vector<std::string> routeSubstrings = ipRouteSubstrings(ifName, dst, nextHop, mtu);
+    EXPECT_TRUE(ipRouteExists(ipVersion, table, routeSubstrings))
+            << "Couldn't find route to " << dst << ": [" << Join(routeSubstrings, ", ")
+            << "] in table " << table;
 }
 
 void expectNetworkRouteExists(const char* ipVersion, const std::string& ifName,
@@ -1808,9 +1856,9 @@
 void expectNetworkRouteDoesNotExist(const char* ipVersion, const std::string& ifName,
                                     const std::string& dst, const std::string& nextHop,
                                     const char* table) {
-    std::string routeString = ipRouteString(ifName, dst, nextHop, "");
-    EXPECT_FALSE(ipRouteExists(ipVersion, table, routeString))
-            << "Found unexpected route " << routeString << " in table " << table;
+    std::vector<std::string> routeSubstrings = ipRouteSubstrings(ifName, dst, nextHop, "");
+    EXPECT_FALSE(ipRouteExists(ipVersion, table, routeSubstrings))
+            << "Found unexpected route [" << Join(routeSubstrings, ", ") << "] in table " << table;
 }
 
 bool ipRuleExists(const char* ipVersion, const std::string& ipRule) {
@@ -1922,6 +1970,14 @@
             {IP_RULE_V6, "::/0", "2001:db8::", true},
             {IP_RULE_V6, "2001:db8:cafe::/64", "2001:db8::", true},
             {IP_RULE_V4, "fe80::/64", "0.0.0.0", false},
+            {IP_RULE_V4, "10.251.10.2/31", "throw", true},
+            {IP_RULE_V4, "10.251.10.2/31", "unreachable", true},
+            {IP_RULE_V4, "0.0.0.0/0", "throw", true},
+            {IP_RULE_V4, "0.0.0.0/0", "unreachable", true},
+            {IP_RULE_V6, "::/0", "throw", true},
+            {IP_RULE_V6, "::/0", "unreachable", true},
+            {IP_RULE_V6, "2001:db8:cafe::/64", "throw", true},
+            {IP_RULE_V6, "2001:db8:cafe::/64", "unreachable", true},
     };
 
     static const struct {
@@ -4710,4 +4766,4 @@
             EXPECT_TRUE(mNetd->networkRemoveUidRangesParcel(uidRangeConfig).isOk());
         }
     }
-}
\ No newline at end of file
+}
diff --git a/tests/test_utils.cpp b/tests/test_utils.cpp
index 27b17c5..93c7dd8 100644
--- a/tests/test_utils.cpp
+++ b/tests/test_utils.cpp
@@ -88,10 +88,19 @@
     return runCommand(command);
 }
 
-bool ipRouteExists(const char* ipVersion, const char* table, const std::string& ipRoute) {
+bool ipRouteExists(const char* ipVersion, const char* table,
+                   const std::vector<std::string>& ipRouteSubstrings) {
     std::vector<std::string> routes = listIpRoutes(ipVersion, table);
     for (const auto& route : routes) {
-        if (route.find(ipRoute) != std::string::npos) {
+        bool matched = true;
+        for (const auto& substring : ipRouteSubstrings) {
+            if (route.find(substring) == std::string::npos) {
+                matched = false;
+                break;
+            }
+        }
+
+        if (matched) {
             return true;
         }
     }
diff --git a/tests/test_utils.h b/tests/test_utils.h
index c8cd6ce..de6c221 100644
--- a/tests/test_utils.h
+++ b/tests/test_utils.h
@@ -35,4 +35,5 @@
 
 std::vector<std::string> listIpRoutes(const char* ipVersion, const char* table);
 
-bool ipRouteExists(const char* ipVersion, const char* table, const std::string& ipRoute);
+bool ipRouteExists(const char* ipVersion, const char* table,
+                   const std::vector<std::string>& ipRouteSubstrings);