netd: Route MTU

- Route may include optional MTU parameter
- Change route is added so routes don't need to be deleted then re-added
- Add/Del/Change functions to pass route info as parcel

Bug: 142892223
Test: new unit tests
Change-Id: Idc32ecb0520b1f4136b3fe0e3f7b6800fb3005a6
diff --git a/server/Android.bp b/server/Android.bp
index ad4e4ff..479dea7 100644
--- a/server/Android.bp
+++ b/server/Android.bp
@@ -31,6 +31,7 @@
         "binder/android/net/INetdUnsolicitedEventListener.aidl",
         "binder/android/net/InterfaceConfigurationParcel.aidl",
         "binder/android/net/MarkMaskParcel.aidl",
+        "binder/android/net/RouteInfoParcel.aidl",
         "binder/android/net/TetherConfigParcel.aidl",
         "binder/android/net/TetherStatsParcel.aidl",
         "binder/android/net/UidRangeParcel.aidl",
diff --git a/server/NetdHwService.cpp b/server/NetdHwService.cpp
index b209aca..15855da 100644
--- a/server/NetdHwService.cpp
+++ b/server/NetdHwService.cpp
@@ -108,7 +108,8 @@
     RETURN_IF_NOT_OEM_NETWORK(netId);
 
     return toHalStatus(gCtls->netCtrl.addRoute(netId, ifname.c_str(), destination.c_str(),
-                                               maybeNullString(nexthop), false, INVALID_UID));
+                                               maybeNullString(nexthop), false, INVALID_UID,
+                                               0 /* mtu */));
 }
 
 Return <StatusCode> NetdHwService::removeRouteFromOemNetwork(
diff --git a/server/NetdNativeService.cpp b/server/NetdNativeService.cpp
index 322604a..c951c24 100644
--- a/server/NetdNativeService.cpp
+++ b/server/NetdNativeService.cpp
@@ -969,6 +969,39 @@
     return binder::Status::ok();
 }
 
+binder::Status NetdNativeService::networkAddRouteParcel(int32_t netId,
+                                                        const RouteInfoParcel& route) {
+    // Public methods of NetworkController are thread-safe.
+    ENFORCE_NETWORK_STACK_PERMISSIONS();
+    bool legacy = false;
+    uid_t uid = 0;  // UID is only meaningful for legacy routes.
+
+    // convert Parcel to parameters
+    int res = gCtls->netCtrl.addRoute(netId, route.ifName.c_str(), route.destination.c_str(),
+                                      route.nextHop.empty() ? nullptr : route.nextHop.c_str(),
+                                      legacy, uid, route.mtu);
+    return statusFromErrcode(res);
+}
+
+binder::Status NetdNativeService::networkUpdateRouteParcel(int32_t netId,
+                                                           const RouteInfoParcel& route) {
+    // Public methods of NetworkController are thread-safe.
+    ENFORCE_NETWORK_STACK_PERMISSIONS();
+    bool legacy = false;
+    uid_t uid = 0;  // UID is only meaningful for legacy routes.
+
+    // convert Parcel to parameters
+    int res = gCtls->netCtrl.updateRoute(netId, route.ifName.c_str(), route.destination.c_str(),
+                                         route.nextHop.empty() ? nullptr : route.nextHop.c_str(),
+                                         legacy, uid, route.mtu);
+    return statusFromErrcode(res);
+}
+
+binder::Status NetdNativeService::networkRemoveRouteParcel(int32_t netId,
+                                                           const RouteInfoParcel& route) {
+    return networkRemoveRoute(netId, route.ifName, route.destination, route.nextHop);
+}
+
 binder::Status NetdNativeService::networkAddRoute(int32_t netId, const std::string& ifName,
                                                   const std::string& destination,
                                                   const std::string& nextHop) {
@@ -977,7 +1010,7 @@
     bool legacy = false;
     uid_t uid = 0;  // UID is only meaningful for legacy routes.
     int res = gCtls->netCtrl.addRoute(netId, ifName.c_str(), destination.c_str(),
-                                      nextHop.empty() ? nullptr : nextHop.c_str(), legacy, uid);
+                                      nextHop.empty() ? nullptr : nextHop.c_str(), legacy, uid, 0);
     return statusFromErrcode(res);
 }
 
@@ -999,7 +1032,7 @@
     bool legacy = true;
     int res = gCtls->netCtrl.addRoute(netId, ifName.c_str(), destination.c_str(),
                                       nextHop.empty() ? nullptr : nextHop.c_str(), legacy,
-                                      (uid_t) uid);
+                                      (uid_t)uid, 0);
     return statusFromErrcode(res);
 }
 
diff --git a/server/NetdNativeService.h b/server/NetdNativeService.h
index 12dbd37..34d6215 100644
--- a/server/NetdNativeService.h
+++ b/server/NetdNativeService.h
@@ -76,6 +76,9 @@
                                           const std::vector<UidRangeParcel>& uids) override;
     binder::Status networkRejectNonSecureVpn(bool enable,
                                              const std::vector<UidRangeParcel>& uids) override;
+    binder::Status networkAddRouteParcel(int32_t netId, const RouteInfoParcel& route) override;
+    binder::Status networkUpdateRouteParcel(int32_t netId, const RouteInfoParcel& route) override;
+    binder::Status networkRemoveRouteParcel(int32_t netId, const RouteInfoParcel& route) override;
     binder::Status networkAddRoute(int32_t netId, const std::string& ifName,
                                    const std::string& destination,
                                    const std::string& nextHop) override;
diff --git a/server/NetlinkCommands.h b/server/NetlinkCommands.h
index c07194d..11fb8e0 100644
--- a/server/NetlinkCommands.h
+++ b/server/NetlinkCommands.h
@@ -28,6 +28,7 @@
 
 const uint16_t NETLINK_REQUEST_FLAGS = NLM_F_REQUEST | NLM_F_ACK;
 const uint16_t NETLINK_ROUTE_CREATE_FLAGS = NETLINK_REQUEST_FLAGS | NLM_F_CREATE | NLM_F_EXCL;
+const uint16_t NETLINK_ROUTE_REPLACE_FLAGS = NETLINK_REQUEST_FLAGS | NLM_F_REPLACE;
 // Don't create rules with NLM_F_EXCL, because operations such as changing network permissions rely
 // on make-before-break. The kernel did not complain about duplicate rules until ~4.9, at which
 // point it started returning EEXIST. See for example b/69607866 . We can't just ignore the EEXIST
diff --git a/server/NetworkController.cpp b/server/NetworkController.cpp
index 3e428ff..6a11f92 100644
--- a/server/NetworkController.cpp
+++ b/server/NetworkController.cpp
@@ -585,13 +585,18 @@
 }
 
 int NetworkController::addRoute(unsigned netId, const char* interface, const char* destination,
-                                const char* nexthop, bool legacy, uid_t uid) {
-    return modifyRoute(netId, interface, destination, nexthop, true, legacy, uid);
+                                const char* nexthop, bool legacy, uid_t uid, int mtu) {
+    return modifyRoute(netId, interface, destination, nexthop, ROUTE_ADD, legacy, uid, mtu);
+}
+
+int NetworkController::updateRoute(unsigned netId, const char* interface, const char* destination,
+                                   const char* nexthop, bool legacy, uid_t uid, int mtu) {
+    return modifyRoute(netId, interface, destination, nexthop, ROUTE_UPDATE, legacy, uid, mtu);
 }
 
 int NetworkController::removeRoute(unsigned netId, const char* interface, const char* destination,
                                    const char* nexthop, bool legacy, uid_t uid) {
-    return modifyRoute(netId, interface, destination, nexthop, false, legacy, uid);
+    return modifyRoute(netId, interface, destination, nexthop, ROUTE_REMOVE, legacy, uid, 0);
 }
 
 void NetworkController::addInterfaceAddress(unsigned ifIndex, const char* address) {
@@ -768,7 +773,8 @@
 }
 
 int NetworkController::modifyRoute(unsigned netId, const char* interface, const char* destination,
-                                   const char* nexthop, bool add, bool legacy, uid_t uid) {
+                                   const char* nexthop, enum RouteOperation op, bool legacy,
+                                   uid_t uid, int mtu) {
     ScopedRLock lock(mRWLock);
 
     if (!isValidNetworkLocked(netId)) {
@@ -798,8 +804,15 @@
         tableType = RouteController::INTERFACE;
     }
 
-    return add ? RouteController::addRoute(interface, destination, nexthop, tableType) :
-                 RouteController::removeRoute(interface, destination, nexthop, tableType);
+    switch (op) {
+        case ROUTE_ADD:
+            return RouteController::addRoute(interface, destination, nexthop, tableType, mtu);
+        case ROUTE_UPDATE:
+            return RouteController::updateRoute(interface, destination, nexthop, tableType, mtu);
+        case ROUTE_REMOVE:
+            return RouteController::removeRoute(interface, destination, nexthop, tableType);
+    }
+    return -EINVAL;
 }
 
 int NetworkController::modifyFallthroughLocked(unsigned vpnNetId, bool add) {
diff --git a/server/NetworkController.h b/server/NetworkController.h
index e0abde0..ff49c02 100644
--- a/server/NetworkController.h
+++ b/server/NetworkController.h
@@ -88,6 +88,9 @@
     static constexpr int LOCAL_NET_ID = INetd::LOCAL_NET_ID;
     static constexpr int DUMMY_NET_ID = 51;
 
+    // Route mode for modify route
+    enum RouteOperation { ROUTE_ADD, ROUTE_UPDATE, ROUTE_REMOVE };
+
     NetworkController();
 
     unsigned getDefaultNetwork() const;
@@ -122,7 +125,9 @@
     // Routes are added to tables determined by the interface, so only |interface| is actually used.
     // |netId| is given only to sanity check that the interface has the correct netId.
     [[nodiscard]] int addRoute(unsigned netId, const char* interface, const char* destination,
-                               const char* nexthop, bool legacy, uid_t uid);
+                               const char* nexthop, bool legacy, uid_t uid, int mtu);
+    [[nodiscard]] int updateRoute(unsigned netId, const char* interface, const char* destination,
+                                  const char* nexthop, bool legacy, uid_t uid, int mtu);
     [[nodiscard]] int removeRoute(unsigned netId, const char* interface, const char* destination,
                                   const char* nexthop, bool legacy, uid_t uid);
 
@@ -158,7 +163,8 @@
     [[nodiscard]] int createPhysicalNetworkLocked(unsigned netId, Permission permission);
 
     [[nodiscard]] int modifyRoute(unsigned netId, const char* interface, const char* destination,
-                                  const char* nexthop, bool add, bool legacy, uid_t uid);
+                                  const char* nexthop, RouteOperation op, bool legacy, uid_t uid,
+                                  int mtu);
     [[nodiscard]] int modifyFallthroughLocked(unsigned vpnNetId, bool add);
     void updateTcpSocketMonitorPolling();
 
diff --git a/server/PhysicalNetwork.cpp b/server/PhysicalNetwork.cpp
index 56f4ac3..2808fbe 100644
--- a/server/PhysicalNetwork.cpp
+++ b/server/PhysicalNetwork.cpp
@@ -88,8 +88,8 @@
         // If any of these operations fail, there's no point in logging because RouteController will
         // have already logged a message. There's also no point returning an error since there's
         // nothing we can do.
-        (void) RouteController::addRoute(interface.c_str(), dst, "throw",
-                                         RouteController::INTERFACE);
+        (void)RouteController::addRoute(interface.c_str(), dst, "throw", RouteController::INTERFACE,
+                                        0 /* mtu */);
         (void) RouteController::removeRoute(interface.c_str(), dst, "throw",
                                          RouteController::INTERFACE);
     }
diff --git a/server/RouteController.cpp b/server/RouteController.cpp
index a62ed6b..c4c3cfb 100644
--- a/server/RouteController.cpp
+++ b/server/RouteController.cpp
@@ -118,6 +118,13 @@
 rtattr RTATTR_OIF       = { U16_RTA_LENGTH(sizeof(uint32_t)),           RTA_OIF };
 rtattr RTATTR_PRIO      = { U16_RTA_LENGTH(sizeof(uint32_t)),           RTA_PRIORITY };
 
+// One or more nested attributes in the RTA_METRICS attribute.
+rtattr RTATTRX_MTU      = { U16_RTA_LENGTH(sizeof(uint32_t)),           RTAX_MTU};
+
+// The RTA_METRICS attribute itself.
+constexpr int RTATTR_METRICS_SIZE = sizeof(RTATTRX_MTU) + sizeof(uint32_t);
+rtattr RTATTR_METRICS   = { U16_RTA_LENGTH(RTATTR_METRICS_SIZE),         RTA_METRICS };
+
 uint8_t PADDING_BUFFER[RTA_ALIGNTO] = {0, 0, 0, 0};
 
 // END CONSTANTS ----------------------------------------------------------------------------------
@@ -339,8 +346,8 @@
 
 // Adds or deletes an IPv4 or IPv6 route.
 // Returns 0 on success or negative errno on failure.
-int modifyIpRoute(uint16_t action, uint32_t table, const char* interface, const char* destination,
-                  const char* nexthop) {
+int modifyIpRoute(uint16_t action, uint16_t flags, uint32_t table, const char* interface,
+                  const char* destination, const char* nexthop, uint32_t mtu) {
     // At least the destination must be non-null.
     if (!destination) {
         ALOGE("null destination");
@@ -410,22 +417,23 @@
     rtattr rtaGateway = { U16_RTA_LENGTH(rawLength), RTA_GATEWAY };
 
     iovec iov[] = {
-        { nullptr,          0 },
-        { &route,        sizeof(route) },
-        { &RTATTR_TABLE, sizeof(RTATTR_TABLE) },
-        { &table,        sizeof(table) },
-        { &rtaDst,       sizeof(rtaDst) },
-        { rawAddress,    static_cast<size_t>(rawLength) },
-        { &RTATTR_OIF,   interface != OIF_NONE ? sizeof(RTATTR_OIF) : 0 },
-        { &ifindex,      interface != OIF_NONE ? sizeof(ifindex) : 0 },
-        { &rtaGateway,   nexthop ? sizeof(rtaGateway) : 0 },
-        { rawNexthop,    nexthop ? static_cast<size_t>(rawLength) : 0 },
-        { &RTATTR_PRIO,  isDefaultThrowRoute ? sizeof(RTATTR_PRIO) : 0 },
-        { &PRIO_THROW,   isDefaultThrowRoute ? sizeof(PRIO_THROW) : 0 },
+        { nullptr,         0 },
+        { &route,          sizeof(route) },
+        { &RTATTR_TABLE,   sizeof(RTATTR_TABLE) },
+        { &table,          sizeof(table) },
+        { &rtaDst,         sizeof(rtaDst) },
+        { rawAddress,      static_cast<size_t>(rawLength) },
+        { &RTATTR_OIF,     interface != OIF_NONE ? sizeof(RTATTR_OIF) : 0 },
+        { &ifindex,        interface != OIF_NONE ? sizeof(ifindex) : 0 },
+        { &rtaGateway,     nexthop ? sizeof(rtaGateway) : 0 },
+        { rawNexthop,      nexthop ? static_cast<size_t>(rawLength) : 0 },
+        { &RTATTR_METRICS, mtu != 0 ? sizeof(RTATTR_METRICS) : 0 },
+        { &RTATTRX_MTU,    mtu != 0 ? sizeof(RTATTRX_MTU) : 0 },
+        { &mtu,            mtu != 0 ? sizeof(mtu) : 0 },
+        { &RTATTR_PRIO,    isDefaultThrowRoute ? sizeof(RTATTR_PRIO) : 0 },
+        { &PRIO_THROW,     isDefaultThrowRoute ? sizeof(PRIO_THROW) : 0 },
     };
 
-    uint16_t flags = (action == RTM_NEWROUTE) ? NETLINK_ROUTE_CREATE_FLAGS : NETLINK_REQUEST_FLAGS;
-
     // Allow creating multiple link-local routes in the same table, so we can make IPv6
     // work on all interfaces in the local_network table.
     if (family == AF_INET6 && IN6_IS_ADDR_LINKLOCAL(reinterpret_cast<in6_addr*>(rawAddress))) {
@@ -697,11 +705,13 @@
         return ret;
     }
 
-    if ((ret = modifyIpRoute(RTM_NEWROUTE, table, interface, "0.0.0.0/0", nullptr))) {
+    if ((ret = modifyIpRoute(RTM_NEWROUTE, NETLINK_ROUTE_CREATE_FLAGS, table, interface,
+                             "0.0.0.0/0", nullptr, 0 /* mtu */))) {
         return ret;
     }
 
-    if ((ret = modifyIpRoute(RTM_NEWROUTE, table, interface, "::/0", nullptr))) {
+    if ((ret = modifyIpRoute(RTM_NEWROUTE, NETLINK_ROUTE_CREATE_FLAGS, table, interface, "::/0",
+                             nullptr, 0 /* mtu */))) {
         return ret;
     }
 
@@ -859,8 +869,9 @@
 
 // Adds or removes an IPv4 or IPv6 route to the specified table.
 // Returns 0 on success or negative errno on failure.
-int RouteController::modifyRoute(uint16_t action, const char* interface, const char* destination,
-                                 const char* nexthop, TableType tableType) {
+int RouteController::modifyRoute(uint16_t action, uint16_t flags, const char* interface,
+                                 const char* destination, const char* nexthop, TableType tableType,
+                                 int mtu) {
     uint32_t table;
     switch (tableType) {
         case RouteController::INTERFACE: {
@@ -884,7 +895,7 @@
         }
     }
 
-    int ret = modifyIpRoute(action, table, interface, destination, nexthop);
+    int ret = modifyIpRoute(action, flags, table, interface, destination, nexthop, mtu);
     // Trying to add a route that already exists shouldn't cause an error.
     if (ret && !(action == RTM_NEWROUTE && ret == -EEXIST)) {
         return ret;
@@ -1066,13 +1077,21 @@
 }
 
 int RouteController::addRoute(const char* interface, const char* destination, const char* nexthop,
-                              TableType tableType) {
-    return modifyRoute(RTM_NEWROUTE, interface, destination, nexthop, tableType);
+                              TableType tableType, int mtu) {
+    return modifyRoute(RTM_NEWROUTE, NETLINK_ROUTE_CREATE_FLAGS, interface, destination, nexthop,
+                       tableType, mtu);
 }
 
 int RouteController::removeRoute(const char* interface, const char* destination,
                                  const char* nexthop, TableType tableType) {
-    return modifyRoute(RTM_DELROUTE, interface, destination, nexthop, tableType);
+    return modifyRoute(RTM_DELROUTE, NETLINK_REQUEST_FLAGS, interface, destination, nexthop,
+                       tableType, 0);
+}
+
+int RouteController::updateRoute(const char* interface, const char* destination,
+                                 const char* nexthop, TableType tableType, int mtu) {
+    return modifyRoute(RTM_NEWROUTE, NETLINK_ROUTE_REPLACE_FLAGS, interface, destination, nexthop,
+                       tableType, mtu);
 }
 
 int RouteController::enableTethering(const char* inputInterface, const char* outputInterface) {
diff --git a/server/RouteController.h b/server/RouteController.h
index e36320f..656fc21 100644
--- a/server/RouteController.h
+++ b/server/RouteController.h
@@ -90,9 +90,11 @@
     // |nexthop| can be NULL (to indicate a directly-connected route), "unreachable" (to indicate a
     // route that's blocked), "throw" (to indicate the lack of a match), or a regular IP address.
     [[nodiscard]] static int addRoute(const char* interface, const char* destination,
-                                      const char* nexthop, TableType tableType);
+                                      const char* nexthop, TableType tableType, int mtu);
     [[nodiscard]] static int removeRoute(const char* interface, const char* destination,
                                          const char* nexthop, TableType tableType);
+    [[nodiscard]] static int updateRoute(const char* interface, const char* destination,
+                                         const char* nexthop, TableType tableType, int mtu);
 
     [[nodiscard]] static int enableTethering(const char* inputInterface,
                                              const char* outputInterface);
@@ -125,8 +127,9 @@
     static int modifyDefaultNetwork(uint16_t action, const char* interface, Permission permission);
     static int modifyPhysicalNetwork(unsigned netId, const char* interface, Permission permission,
                                      bool add);
-    static int modifyRoute(uint16_t action, const char* interface, const char* destination,
-                           const char* nexthop, TableType tableType);
+    static int modifyRoute(uint16_t action, uint16_t flags, const char* interface,
+                           const char* destination, const char* nexthop, TableType tableType,
+                           int mtu);
     static int modifyTetheredNetwork(uint16_t action, const char* inputInterface,
                                      const char* outputInterface);
     static int modifyVpnFallthroughRule(uint16_t action, unsigned vpnNetId,
@@ -140,8 +143,9 @@
 // Public because they are called by by RouteControllerTest.cpp.
 // TODO: come up with a scheme of unit testing this code that does not rely on making all its
 // functions public.
-[[nodiscard]] int modifyIpRoute(uint16_t action, uint32_t table, const char* interface,
-                                const char* destination, const char* nexthop);
+[[nodiscard]] int modifyIpRoute(uint16_t action, uint16_t flags, uint32_t table,
+                                const char* interface, const char* destination, const char* nexthop,
+                                uint32_t mtu);
 uint32_t getRulePriority(const nlmsghdr *nlh);
 [[nodiscard]] int modifyIncomingPacketMark(unsigned netId, const char* interface,
                                            Permission permission, bool add);
diff --git a/server/RouteControllerTest.cpp b/server/RouteControllerTest.cpp
index 20b3618..fed15a3 100644
--- a/server/RouteControllerTest.cpp
+++ b/server/RouteControllerTest.cpp
@@ -79,18 +79,24 @@
     static_assert(table2 < RouteController::ROUTE_TABLE_OFFSET_FROM_INDEX,
                   "Test table2 number too large");
 
-    EXPECT_EQ(0, modifyIpRoute(RTM_NEWROUTE, table1, "lo", "192.0.2.2/32", nullptr));
-    EXPECT_EQ(0, modifyIpRoute(RTM_NEWROUTE, table1, "lo", "192.0.2.3/32", nullptr));
-    EXPECT_EQ(0, modifyIpRoute(RTM_NEWROUTE, table2, "lo", "192.0.2.4/32", nullptr));
+    EXPECT_EQ(0, modifyIpRoute(RTM_NEWROUTE, NETLINK_ROUTE_CREATE_FLAGS, table1, "lo",
+              "192.0.2.2/32", nullptr, 0 /* mtu */));
+    EXPECT_EQ(0, modifyIpRoute(RTM_NEWROUTE, NETLINK_ROUTE_CREATE_FLAGS, table1, "lo",
+              "192.0.2.3/32", nullptr, 0 /* mtu */));
+    EXPECT_EQ(0, modifyIpRoute(RTM_NEWROUTE, NETLINK_ROUTE_CREATE_FLAGS, table2, "lo",
+              "192.0.2.4/32", nullptr, 0 /* mtu */));
 
     EXPECT_EQ(0, flushRoutes(table1));
 
     EXPECT_EQ(-ESRCH,
-              modifyIpRoute(RTM_DELROUTE, table1, "lo", "192.0.2.2/32", nullptr));
+              modifyIpRoute(RTM_DELROUTE, NETLINK_ROUTE_CREATE_FLAGS, table1, "lo", "192.0.2.2/32",
+                            nullptr, 0 /* mtu */));
     EXPECT_EQ(-ESRCH,
-              modifyIpRoute(RTM_DELROUTE, table1, "lo", "192.0.2.3/32", nullptr));
+              modifyIpRoute(RTM_DELROUTE, NETLINK_ROUTE_CREATE_FLAGS, table1, "lo", "192.0.2.3/32",
+                            nullptr, 0 /* mtu */));
     EXPECT_EQ(0,
-              modifyIpRoute(RTM_DELROUTE, table2, "lo", "192.0.2.4/32", nullptr));
+              modifyIpRoute(RTM_DELROUTE, NETLINK_ROUTE_CREATE_FLAGS, table2, "lo", "192.0.2.4/32",
+                            nullptr, 0 /* mtu */));
 }
 
 TEST_F(RouteControllerTest, TestModifyIncomingPacketMark) {
diff --git a/server/binder/android/net/INetd.aidl b/server/binder/android/net/INetd.aidl
index 8192bb9..cecba46 100644
--- a/server/binder/android/net/INetd.aidl
+++ b/server/binder/android/net/INetd.aidl
@@ -19,6 +19,7 @@
 import android.net.INetdUnsolicitedEventListener;
 import android.net.InterfaceConfigurationParcel;
 import android.net.MarkMaskParcel;
+import android.net.RouteInfoParcel;
 import android.net.TetherConfigParcel;
 import android.net.TetherStatsParcel;
 import android.net.UidRangeParcel;
@@ -1214,4 +1215,32 @@
      * @return A MarkMaskParcel of the given network id.
      */
     MarkMaskParcel getFwmarkForNetwork(int netId);
+
+    /**
+    * Add a route for specific network
+    *
+    * @param netId the network to add the route to
+    * @param routeInfo parcelable with route information
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void networkAddRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo);
+
+    /**
+    * Update a route for specific network
+    *
+    * @param routeInfo parcelable with route information
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void networkUpdateRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo);
+
+    /**
+    * Remove a route for specific network
+    *
+    * @param routeInfo parcelable with route information
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void networkRemoveRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo);
 }
diff --git a/server/binder/android/net/RouteInfoParcel.aidl b/server/binder/android/net/RouteInfoParcel.aidl
new file mode 100644
index 0000000..fcc86e3
--- /dev/null
+++ b/server/binder/android/net/RouteInfoParcel.aidl
@@ -0,0 +1,28 @@
+/**
+ * Copyright (c) 2020, 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 android.net;
+
+parcelable RouteInfoParcel {
+  // The destination of the route.
+  @utf8InCpp String destination;
+  // The name of interface of the route. This interface should be assigned to the netID.
+  @utf8InCpp String ifName;
+  // The route's next hop address, or one of the NEXTHOP_* constants defined in INetd.aidl.
+  @utf8InCpp String nextHop;
+  // The MTU of the route.
+  int mtu;
+}