[posix] install high priority kernel routes for OMR prefixes (#7636)

diff --git a/include/openthread/instance.h b/include/openthread/instance.h
index 659d364..9fe27ff 100644
--- a/include/openthread/instance.h
+++ b/include/openthread/instance.h
@@ -53,7 +53,7 @@
  * @note This number versions both OpenThread platform and user APIs.
  *
  */
-#define OPENTHREAD_API_VERSION (205)
+#define OPENTHREAD_API_VERSION (206)
 
 /**
  * @addtogroup api-instance
diff --git a/include/openthread/netdata.h b/include/openthread/netdata.h
index 90f2e64..74b92e8 100644
--- a/include/openthread/netdata.h
+++ b/include/openthread/netdata.h
@@ -231,6 +231,20 @@
                                                       const struct otJoinerDiscerner *aDiscerner);
 
 /**
+ * This function checks whether a given Prefix can act as a valid OMR prefix and also the Leader's Network Data contains
+ * this prefix.
+ *
+ * @param[in]  aInstance  A pointer to an OpenThread instance.
+ * @param[in]  aPrefix    A pointer to the IPv6 prefix.
+ *
+ * @returns  Whether @p aPrefix is a valid OMR prefix and Leader's Network Data contains the OMR prefix @p aPrefix.
+ *
+ * @note This API is only available when `OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE` is used.
+ *
+ */
+bool otNetDataContainsOmrPrefix(otInstance *aInstance, const otIp6Prefix *aPrefix);
+
+/**
  * @}
  *
  */
diff --git a/src/core/api/netdata_api.cpp b/src/core/api/netdata_api.cpp
index e646645..c28b987 100644
--- a/src/core/api/netdata_api.cpp
+++ b/src/core/api/netdata_api.cpp
@@ -60,6 +60,13 @@
     return error;
 }
 
+#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
+bool otNetDataContainsOmrPrefix(otInstance *aInstance, const otIp6Prefix *aPrefix)
+{
+    return AsCoreType(aInstance).Get<NetworkData::Leader>().ContainsOmrPrefix(AsCoreType(aPrefix));
+}
+#endif
+
 otError otNetDataGetNextRoute(otInstance *aInstance, otNetworkDataIterator *aIterator, otExternalRouteConfig *aConfig)
 {
     Error error = kErrorNone;
diff --git a/src/core/border_router/routing_manager.hpp b/src/core/border_router/routing_manager.hpp
index 84d6bc3..0023e9e 100644
--- a/src/core/border_router/routing_manager.hpp
+++ b/src/core/border_router/routing_manager.hpp
@@ -185,6 +185,25 @@
      */
     Error HandleInfraIfStateChanged(uint32_t aInfraIfIndex, bool aIsRunning);
 
+    /**
+     * This method checks if the on-mesh prefix configuration is a valid OMR prefix.
+     *
+     * @param[in] aOnMeshPrefixConfig  The on-mesh prefix configuration to check.
+     *
+     * @returns  Whether the on-mesh prefix configuration is a valid OMR prefix.
+     *
+     */
+    static bool IsValidOmrPrefix(const NetworkData::OnMeshPrefixConfig &aOnMeshPrefixConfig);
+
+    /**
+     * This method checks if the OMR prefix is valid (i.e. GUA/ULA prefix with length being 64).
+     *
+     * @param[in]  aOmrPrefix  The OMR prefix to check.
+     * @returns    Whether the OMR prefix is valid.
+     *
+     */
+    static bool IsValidOmrPrefix(const Ip6::Prefix &aOmrPrefix);
+
 private:
     typedef NetworkData::RoutePreference RoutePreference;
 
@@ -347,8 +366,6 @@
     void ResetDiscoveredPrefixStaleTimer(void);
 
     static bool IsValidBrUlaPrefix(const Ip6::Prefix &aBrUlaPrefix);
-    static bool IsValidOmrPrefix(const NetworkData::OnMeshPrefixConfig &aOnMeshPrefixConfig);
-    static bool IsValidOmrPrefix(const Ip6::Prefix &aOmrPrefix);
     static bool IsValidOnLinkPrefix(const RouterAdv::PrefixInfoOption &aPio);
     static bool IsValidOnLinkPrefix(const Ip6::Prefix &aOnLinkPrefix);
 
diff --git a/src/core/thread/network_data_leader_ftd.cpp b/src/core/thread/network_data_leader_ftd.cpp
index 0726add..770c241 100644
--- a/src/core/thread/network_data_leader_ftd.cpp
+++ b/src/core/thread/network_data_leader_ftd.cpp
@@ -1378,6 +1378,45 @@
     return error;
 }
 
+#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
+bool Leader::ContainsOmrPrefix(const Ip6::Prefix &aPrefix)
+{
+    PrefixTlv *prefixTlv;
+    bool       contains = false;
+
+    VerifyOrExit(BorderRouter::RoutingManager::IsValidOmrPrefix(aPrefix));
+
+    prefixTlv = FindPrefix(aPrefix);
+    VerifyOrExit(prefixTlv != nullptr);
+
+    for (int i = 0; i < 2; i++)
+    {
+        const BorderRouterTlv *borderRouter = prefixTlv->FindSubTlv<BorderRouterTlv>(/* aStable */ (i == 0));
+
+        if (borderRouter == nullptr)
+        {
+            continue;
+        }
+
+        for (const BorderRouterEntry *entry = borderRouter->GetFirstEntry(); entry <= borderRouter->GetLastEntry();
+             entry                          = entry->GetNext())
+        {
+            OnMeshPrefixConfig config;
+
+            config.SetFrom(*prefixTlv, *borderRouter, *entry);
+
+            if (BorderRouter::RoutingManager::IsValidOmrPrefix(config))
+            {
+                ExitNow(contains = true);
+            }
+        }
+    }
+
+exit:
+    return contains;
+}
+#endif
+
 } // namespace NetworkData
 } // namespace ot
 
diff --git a/src/core/thread/network_data_leader_ftd.hpp b/src/core/thread/network_data_leader_ftd.hpp
index 2fecf38..c1f5764 100644
--- a/src/core/thread/network_data_leader_ftd.hpp
+++ b/src/core/thread/network_data_leader_ftd.hpp
@@ -173,6 +173,19 @@
      */
     Error RemoveStaleChildEntries(Coap::ResponseHandler aHandler, void *aContext);
 
+#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
+    /**
+     * This method indicates whether a given Prefix can act as a valid OMR prefix and exists in the network data.
+     *
+     * @param[in]  aPrefix   The OMR prefix to check.
+     *
+     * @retval TRUE  If @p aPrefix is a valid OMR prefix and Network Data contains @p aPrefix.
+     * @retval FALSE Otherwise.
+     *
+     */
+    bool ContainsOmrPrefix(const Ip6::Prefix &aPrefix);
+#endif
+
 private:
     class ChangedFlags
     {
diff --git a/src/core/thread/network_data_types.hpp b/src/core/thread/network_data_types.hpp
index 20752ab..06ff0a9 100644
--- a/src/core/thread/network_data_types.hpp
+++ b/src/core/thread/network_data_types.hpp
@@ -169,6 +169,7 @@
                            public Equatable<OnMeshPrefixConfig>
 {
     friend class NetworkData;
+    friend class Leader;
     friend class Local;
     friend class Publisher;
 
diff --git a/src/posix/platform/netif.cpp b/src/posix/platform/netif.cpp
index 0ecb888..572d59f 100644
--- a/src/posix/platform/netif.cpp
+++ b/src/posix/platform/netif.cpp
@@ -137,6 +137,7 @@
 
 #endif // defined(__APPLE__) || defined(__NetBSD__) || defined(__FreeBSD__)
 
+#include <openthread/border_router.h>
 #include <openthread/icmp6.h>
 #include <openthread/instance.h>
 #include <openthread/ip6.h>
@@ -193,6 +194,13 @@
 static uint32_t sNetlinkSequence = 0; ///< Netlink message sequence.
 #endif
 
+#if OPENTHREAD_POSIX_CONFIG_INSTALL_OMR_ROUTES_ENABLE && __linux__
+static constexpr uint32_t kOmrRoutesPriority = OPENTHREAD_POSIX_CONFIG_OMR_ROUTES_PRIORITY;
+static constexpr uint8_t  kMaxOmrRoutesNum   = OPENTHREAD_POSIX_CONFIG_MAX_OMR_ROUTES_NUM;
+static uint8_t            sAddedOmrRoutesNum = 0;
+static otIp6Prefix        sAddedOmrRoutes[kMaxOmrRoutesNum];
+#endif
+
 #if OPENTHREAD_POSIX_CONFIG_INSTALL_EXTERNAL_ROUTES_ENABLE && __linux__
 static constexpr uint32_t kExternalRoutePriority  = OPENTHREAD_POSIX_CONFIG_EXTERNAL_ROUTE_PRIORITY;
 static constexpr uint8_t  kMaxExternalRoutesNum   = OPENTHREAD_POSIX_CONFIG_MAX_EXTERNAL_ROUTE_NUM;
@@ -345,8 +353,19 @@
     AddRtAttr(aHeader, aMaxLen, aType, &aData, sizeof(aData));
 }
 
-static void UpdateUnicastLinux(const otIp6AddressInfo &aAddressInfo, bool aIsAdded)
+#if OPENTHREAD_POSIX_CONFIG_INSTALL_OMR_ROUTES_ENABLE
+static bool IsOmrAddress(otInstance *aInstance, const otIp6AddressInfo &aAddressInfo)
 {
+    otIp6Prefix addressPrefix{*aAddressInfo.mAddress, aAddressInfo.mPrefixLength};
+
+    return otNetDataContainsOmrPrefix(aInstance, &addressPrefix);
+}
+#endif
+
+static void UpdateUnicastLinux(otInstance *aInstance, const otIp6AddressInfo &aAddressInfo, bool aIsAdded)
+{
+    OT_UNUSED_VARIABLE(aInstance);
+
     struct
     {
         struct nlmsghdr  nh;
@@ -380,12 +399,26 @@
         AddRtAttr(&req.nh, sizeof(req), IFA_CACHEINFO, &cacheinfo, sizeof(cacheinfo));
     }
 
-#if OPENTHREAD_POSIX_CONFIG_NETIF_PREFIX_ROUTE_METRIC > 0
-    if (aAddressInfo.mScope > ot::Ip6::Address::kLinkLocalScope)
+#if OPENTHREAD_POSIX_CONFIG_INSTALL_OMR_ROUTES_ENABLE
+    if (IsOmrAddress(aInstance, aAddressInfo))
     {
-        AddRtAttrUint32(&req.nh, sizeof(req), IFA_RT_PRIORITY, OPENTHREAD_POSIX_CONFIG_NETIF_PREFIX_ROUTE_METRIC);
+        // Remove prefix route for OMR address if `OPENTHREAD_POSIX_CONFIG_INSTALL_OMR_ROUTES_ENABLE` is enabled to
+        // avoid having two routes.
+        if (aIsAdded)
+        {
+            AddRtAttrUint32(&req.nh, sizeof(req), IFA_FLAGS, IFA_F_NOPREFIXROUTE);
+        }
     }
+    else
 #endif
+    {
+#if OPENTHREAD_POSIX_CONFIG_NETIF_PREFIX_ROUTE_METRIC > 0
+        if (aAddressInfo.mScope > ot::Ip6::Address::kLinkLocalScope)
+        {
+            AddRtAttrUint32(&req.nh, sizeof(req), IFA_RT_PRIORITY, OPENTHREAD_POSIX_CONFIG_NETIF_PREFIX_ROUTE_METRIC);
+        }
+#endif
+    }
 
     if (send(sNetlinkFd, &req, req.nh.nlmsg_len, 0) != -1)
     {
@@ -410,7 +443,7 @@
     assert(sIpFd >= 0);
 
 #if defined(__linux__)
-    UpdateUnicastLinux(aAddressInfo, aIsAdded);
+    UpdateUnicastLinux(aInstance, aAddressInfo, aIsAdded);
 #elif defined(__APPLE__) || defined(__NetBSD__) || defined(__FreeBSD__)
     {
         int                 rval;
@@ -531,8 +564,10 @@
     }
 }
 
-#if OPENTHREAD_POSIX_CONFIG_INSTALL_EXTERNAL_ROUTES_ENABLE && __linux__
-static otError AddExternalRoute(const otIp6Prefix &aPrefix)
+#if __linux__ && \
+    (OPENTHREAD_POSIX_CONFIG_INSTALL_OMR_ROUTES_ENABLE || OPENTHREAD_POSIX_CONFIG_INSTALL_EXTERNAL_ROUTES_ENABLE)
+
+static otError AddRoute(const otIp6Prefix &aPrefix, uint32_t aPriority)
 {
     constexpr unsigned int kBufSize = 128;
     struct
@@ -548,7 +583,6 @@
 
     VerifyOrExit(netifIdx > 0, error = OT_ERROR_INVALID_STATE);
     VerifyOrExit(sNetlinkFd >= 0, error = OT_ERROR_INVALID_STATE);
-    VerifyOrExit(sAddedExternalRoutesNum < kMaxExternalRoutesNum, error = OT_ERROR_NO_BUFS);
 
     req.header.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE | NLM_F_EXCL;
 
@@ -570,7 +604,7 @@
     otIp6AddressToString(&aPrefix.mPrefix, addrBuf, OT_IP6_ADDRESS_STRING_SIZE);
     inet_pton(AF_INET6, addrBuf, data);
     AddRtAttr(reinterpret_cast<nlmsghdr *>(&req), sizeof(req), RTA_DST, data, sizeof(data));
-    AddRtAttrUint32(&req.header, sizeof(req), RTA_PRIORITY, kExternalRoutePriority);
+    AddRtAttrUint32(&req.header, sizeof(req), RTA_PRIORITY, aPriority);
     AddRtAttrUint32(&req.header, sizeof(req), RTA_OIF, netifIdx);
 
     if (send(sNetlinkFd, &req, sizeof(req), 0) < 0)
@@ -582,7 +616,7 @@
     return error;
 }
 
-static otError DeleteExternalRoute(const otIp6Prefix &aPrefix)
+static otError DeleteRoute(const otIp6Prefix &aPrefix)
 {
     constexpr unsigned int kBufSize = 512;
     struct
@@ -631,6 +665,105 @@
     return error;
 }
 
+#endif // __linux__ && (OPENTHREAD_POSIX_CONFIG_INSTALL_OMR_ROUTES_ENABLE ||
+       // OPENTHREAD_POSIX_CONFIG_INSTALL_EXTERNAL_ROUTES_ENABLE)
+
+#if OPENTHREAD_POSIX_CONFIG_INSTALL_OMR_ROUTES_ENABLE && __linux__
+
+static bool HasAddedOmrRoute(const otIp6Prefix &aOmrPrefix)
+{
+    bool found = false;
+
+    for (uint8_t i = 0; i < sAddedOmrRoutesNum; ++i)
+    {
+        if (otIp6ArePrefixesEqual(&sAddedOmrRoutes[i], &aOmrPrefix))
+        {
+            found = true;
+            break;
+        }
+    }
+
+    return found;
+}
+
+static otError AddOmrRoute(const otIp6Prefix &aPrefix)
+{
+    otError error;
+
+    VerifyOrExit(sAddedOmrRoutesNum < kMaxOmrRoutesNum, error = OT_ERROR_NO_BUFS);
+
+    error = AddRoute(aPrefix, kOmrRoutesPriority);
+exit:
+    return error;
+}
+
+static void UpdateOmrRoutes(otInstance *aInstance)
+{
+    otError               error;
+    otNetworkDataIterator iterator = OT_NETWORK_DATA_ITERATOR_INIT;
+    otBorderRouterConfig  config;
+    char                  prefixString[OT_IP6_PREFIX_STRING_SIZE];
+
+    // Remove kernel routes if the OMR prefix is removed
+    for (int i = 0; i < static_cast<int>(sAddedOmrRoutesNum); ++i)
+    {
+        if (otNetDataContainsOmrPrefix(aInstance, &sAddedOmrRoutes[i]))
+        {
+            continue;
+        }
+
+        otIp6PrefixToString(&sAddedOmrRoutes[i], prefixString, sizeof(prefixString));
+        if ((error = DeleteRoute(sAddedOmrRoutes[i])) != OT_ERROR_NONE)
+        {
+            otLogWarnPlat("[netif] Failed to delete an OMR route %s in kernel: %s", prefixString,
+                          otThreadErrorToString(error));
+        }
+        else
+        {
+            sAddedOmrRoutes[i] = sAddedOmrRoutes[sAddedOmrRoutesNum - 1];
+            --sAddedOmrRoutesNum;
+            --i;
+            otLogInfoPlat("[netif] Successfully deleted an OMR route %s in kernel", prefixString);
+        }
+    }
+
+    // Add kernel routes for OMR prefixes in Network Data
+    while (otNetDataGetNextOnMeshPrefix(aInstance, &iterator, &config) == OT_ERROR_NONE)
+    {
+        if (HasAddedOmrRoute(config.mPrefix))
+        {
+            continue;
+        }
+
+        otIp6PrefixToString(&config.mPrefix, prefixString, sizeof(prefixString));
+        if ((error = AddOmrRoute(config.mPrefix)) != OT_ERROR_NONE)
+        {
+            otLogWarnPlat("[netif] Failed to add an OMR route %s in kernel: %s", prefixString,
+                          otThreadErrorToString(error));
+        }
+        else
+        {
+            sAddedOmrRoutes[sAddedOmrRoutesNum++] = config.mPrefix;
+            otLogInfoPlat("[netif] Successfully added an OMR route %s in kernel: %s", prefixString);
+        }
+    }
+}
+
+#endif // OPENTHREAD_POSIX_CONFIG_INSTALL_OMR_ROUTES_ENABLE && __linux__
+
+#if OPENTHREAD_POSIX_CONFIG_INSTALL_EXTERNAL_ROUTES_ENABLE && __linux__
+
+static otError AddExternalRoute(const otIp6Prefix &aPrefix)
+{
+    otError error;
+
+    VerifyOrExit(sAddedExternalRoutesNum < kMaxExternalRoutesNum, error = OT_ERROR_NO_BUFS);
+
+    error = AddRoute(aPrefix, kExternalRoutePriority);
+exit:
+    return error;
+}
+
 bool HasExternalRouteInNetData(otInstance *aInstance, const otIp6Prefix &aExternalRoute)
 {
     otNetworkDataIterator iterator = OT_NETWORK_DATA_ITERATOR_INIT;
@@ -676,9 +809,10 @@
         {
             continue;
         }
-        if ((error = DeleteExternalRoute(sAddedExternalRoutes[i])) != OT_ERROR_NONE)
+
+        otIp6PrefixToString(&sAddedExternalRoutes[i], prefixString, sizeof(prefixString));
+        if ((error = DeleteRoute(sAddedExternalRoutes[i])) != OT_ERROR_NONE)
         {
-            otIp6PrefixToString(&sAddedExternalRoutes[i], prefixString, sizeof(prefixString));
             otLogWarnPlat("[netif] Failed to delete an external route %s in kernel: %s", prefixString,
                           otThreadErrorToString(error));
         }
@@ -687,6 +821,7 @@
             sAddedExternalRoutes[i] = sAddedExternalRoutes[sAddedExternalRoutesNum - 1];
             --sAddedExternalRoutesNum;
             --i;
+            otLogWarnPlat("[netif] Successfully deleted an external route %s in kernel", prefixString);
         }
     }
 
@@ -698,15 +833,17 @@
         }
         VerifyOrExit(sAddedExternalRoutesNum < kMaxExternalRoutesNum,
                      otLogWarnPlat("[netif] No buffer to add more external routes in kernel"));
+
+        otIp6PrefixToString(&config.mPrefix, prefixString, sizeof(prefixString));
         if ((error = AddExternalRoute(config.mPrefix)) != OT_ERROR_NONE)
         {
-            otIp6PrefixToString(&config.mPrefix, prefixString, sizeof(prefixString));
             otLogWarnPlat("[netif] Failed to add an external route %s in kernel: %s", prefixString,
                           otThreadErrorToString(error));
         }
         else
         {
             sAddedExternalRoutes[sAddedExternalRoutesNum++] = config.mPrefix;
+            otLogWarnPlat("[netif] Successfully added an external route %s in kernel: %s", prefixString);
         }
     }
 exit:
@@ -732,18 +869,18 @@
     {
         UpdateLink(aInstance);
     }
-#if OPENTHREAD_POSIX_CONFIG_INSTALL_EXTERNAL_ROUTES_ENABLE && __linux__
     if (OT_CHANGED_THREAD_NETDATA & aFlags)
     {
+#if OPENTHREAD_POSIX_CONFIG_INSTALL_OMR_ROUTES_ENABLE && __linux__
+        UpdateOmrRoutes(aInstance);
+#endif
+#if OPENTHREAD_POSIX_CONFIG_INSTALL_EXTERNAL_ROUTES_ENABLE && __linux__
         UpdateExternalRoutes(aInstance);
-    }
 #endif
 #if OPENTHREAD_POSIX_CONFIG_FIREWALL_ENABLE
-    if (OT_CHANGED_THREAD_NETDATA & aFlags)
-    {
         ot::Posix::UpdateIpSets(aInstance);
-    }
 #endif
+    }
 }
 
 static void processReceive(otMessage *aMessage, void *aContext)
diff --git a/src/posix/platform/openthread-posix-config.h b/src/posix/platform/openthread-posix-config.h
index bc43bdf..62e1324 100644
--- a/src/posix/platform/openthread-posix-config.h
+++ b/src/posix/platform/openthread-posix-config.h
@@ -132,12 +132,50 @@
  * This setting configures the prefix route metric on the Thread network interface.
  * Define as 0 to use use the default prefix route metric.
  *
+ * Note: The feature works on Linux kernel v4.18+.
+ *
  */
 #ifndef OPENTHREAD_POSIX_CONFIG_NETIF_PREFIX_ROUTE_METRIC
 #define OPENTHREAD_POSIX_CONFIG_NETIF_PREFIX_ROUTE_METRIC 0
 #endif
 
 /**
+ * @def OPENTHREAD_POSIX_CONFIG_INSTALL_OMR_ROUTES_ENABLE
+ *
+ * Define as 1 to add OMR routes to POSIX kernel when OMR prefixes are changed in netdata.
+ *
+ * Note: This feature can be used to add OMR routes with non-default priority. Unlike
+ * `OPENTHREAD_POSIX_CONFIG_NETIF_PREFIX_ROUTE_METRIC`, it works on Linux kernels before v4.18.
+ * However, `OPENTHREAD_POSIX_CONFIG_NETIF_PREFIX_ROUTE_METRIC` should be preferred on Linux kernel v4.18+.
+ *
+ */
+#ifndef OPENTHREAD_POSIX_CONFIG_INSTALL_OMR_ROUTES_ENABLE
+#define OPENTHREAD_POSIX_CONFIG_INSTALL_OMR_ROUTES_ENABLE 0
+#endif
+
+/**
+ * @def OPENTHREAD_POSIX_CONFIG_OMR_ROUTES_PRIORITY
+ *
+ * This macro defines the priority of OMR routes added to kernel. The larger the number, the lower the priority. We
+ * need to assign a high priority to such routes so that kernel prefers the Thread link rather than infrastructure.
+ * Otherwise we may unnecessarily transmit packets via infrastructure, which potentially causes looping issue.
+ *
+ */
+#ifndef OPENTHREAD_POSIX_CONFIG_OMR_ROUTES_PRIORITY
+#define OPENTHREAD_POSIX_CONFIG_OMR_ROUTES_PRIORITY 1
+#endif
+
+/**
+ * @def OPENTHREAD_POSIX_CONFIG_MAX_OMR_ROUTES_NUM
+ *
+ * This macro defines the max number of OMR routes that can be added to kernel.
+ *
+ */
+#ifndef OPENTHREAD_POSIX_CONFIG_MAX_OMR_ROUTES_NUM
+#define OPENTHREAD_POSIX_CONFIG_MAX_OMR_ROUTES_NUM OPENTHREAD_CONFIG_IP6_SLAAC_NUM_ADDRESSES
+#endif
+
+/**
  * @def OPENTHREAD_POSIX_CONFIG_INSTALL_EXTERNAL_ROUTES_ENABLE
  *
  * Define as 1 to add external routes to POSIX kernel when external routes are changed in netdata.