The advertised routes would always be specified as blackhole
without any nexthop. Allow the route to be configured with
specific nexthop even from different IP family to allow
routing of IPv4 over IPv6 nexthop.

At the same time also extend the netlink testing with
route-sync so we can test this out independently of
the rest of route advertisement.

Signed-off-by: Ales Musil <[email protected]>
---
 controller/route-exchange-netlink.c | 126 ++++++++++++------
 controller/route-exchange-netlink.h |   9 +-
 controller/route.c                  |  16 ++-
 controller/route.h                  |   5 +-
 tests/automake.mk                   |   2 +
 tests/system-ovn-netlink.at         | 200 ++++++++++++++++++++++++++++
 tests/test-ovn-netlink.c            |  68 ++++++++++
 tests/test-utils.c                  |  18 +++
 tests/test-utils.h                  |   4 +
 9 files changed, 397 insertions(+), 51 deletions(-)

diff --git a/controller/route-exchange-netlink.c 
b/controller/route-exchange-netlink.c
index 60bae4781..55670729f 100644
--- a/controller/route-exchange-netlink.c
+++ b/controller/route-exchange-netlink.c
@@ -38,6 +38,9 @@ VLOG_DEFINE_THIS_MODULE(route_exchange_netlink);
 
 #define NETNL_REQ_BUFFER_SIZE 128
 
+static void re_nl_encode_nexthop(struct ofpbuf *, bool dst_is_ipv4,
+                                 const struct in6_addr *);
+
 int
 re_nl_create_vrf(const char *ifname, uint32_t table_id)
 {
@@ -95,13 +98,32 @@ re_nl_delete_vrf(const char *ifname)
     return err;
 }
 
+void
+re_route_format(struct ds *ds, uint32_t table_id, const struct in6_addr *dst,
+                unsigned int plen, const struct in6_addr *nexthop, int err)
+{
+    ds_put_format(ds, "table_id=%"PRIu32" dst=", table_id);
+    ipv6_format_mapped(dst, ds);
+    ds_put_format(ds, " plen=%u nexthop=", plen);
+    if (ipv6_is_zero(nexthop)) {
+        ds_put_cstr(ds, "(blackhole)");
+    } else {
+        ipv6_format_mapped(nexthop, ds);
+    }
+
+    if (err) {
+        ds_put_format(ds, " failed: %s", ovs_strerror(err));
+    }
+}
+
 static int
 modify_route(uint32_t type, uint32_t flags_arg, uint32_t table_id,
              const struct in6_addr *dst, unsigned int plen,
-             unsigned int priority)
+             const struct in6_addr *nexhhop, unsigned int priority)
 {
     uint32_t flags = NLM_F_REQUEST | NLM_F_ACK;
     bool is_ipv4 = IN6_IS_ADDR_V4MAPPED(dst);
+    bool nexthop_unspec = ipv6_is_zero(nexhhop);
     struct rtmsg *rt;
     int err;
 
@@ -117,7 +139,7 @@ modify_route(uint32_t type, uint32_t flags_arg, uint32_t 
table_id,
     rt->rtm_table = RT_TABLE_UNSPEC; /* RTA_TABLE attribute allows id > 256 */
     /* Manage only OVN routes */
     rt->rtm_protocol = RTPROT_OVN;
-    rt->rtm_type = RTN_BLACKHOLE;
+    rt->rtm_type = nexthop_unspec ? RTN_BLACKHOLE : RTN_UNICAST;
     rt->rtm_scope = RT_SCOPE_UNIVERSE;
     rt->rtm_dst_len = plen;
 
@@ -130,25 +152,16 @@ modify_route(uint32_t type, uint32_t flags_arg, uint32_t 
table_id,
         nl_msg_put_in6_addr(&request, RTA_DST, dst);
     }
 
+    if (!nexthop_unspec) {
+        re_nl_encode_nexthop(&request, is_ipv4, nexhhop);
+    }
+
     if (VLOG_IS_DBG_ENABLED()) {
         struct ds msg = DS_EMPTY_INITIALIZER;
 
-        if (type == RTM_DELROUTE) {
-            ds_put_cstr(&msg, "Removing blackhole route from ");
-        } else {
-            ds_put_cstr(&msg, "Adding blackhole route to ");
-        }
-
-        ds_put_format(&msg, "table %"PRIu32 " for prefix ", table_id);
-        if (IN6_IS_ADDR_V4MAPPED(dst)) {
-            ds_put_format(&msg, IP_FMT,
-                          IP_ARGS(in6_addr_get_mapped_ipv4(dst)));
-        } else {
-            ipv6_format_addr(dst, &msg);
-        }
-        ds_put_format(&msg, "/%u", plen);
-
-        VLOG_DBG("%s", ds_cstr(&msg));
+        re_route_format(&msg, table_id, dst, plen, nexhhop, 0);
+        VLOG_DBG("%s route %s", type == RTM_DELROUTE ? "Removing" : "Adding",
+                 ds_cstr(&msg));
         ds_destroy(&msg);
     }
 
@@ -160,7 +173,8 @@ modify_route(uint32_t type, uint32_t flags_arg, uint32_t 
table_id,
 
 int
 re_nl_add_route(uint32_t table_id, const struct in6_addr *dst,
-                unsigned int plen, unsigned int priority)
+                unsigned int plen, const struct in6_addr *nexthop,
+                unsigned int priority)
 {
     if (!TABLE_ID_VALID(table_id)) {
         static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 20);
@@ -171,12 +185,13 @@ re_nl_add_route(uint32_t table_id, const struct in6_addr 
*dst,
     }
 
     return modify_route(RTM_NEWROUTE, NLM_F_CREATE | NLM_F_EXCL, table_id,
-                        dst, plen, priority);
+                        dst, plen, nexthop, priority);
 }
 
 int
 re_nl_delete_route(uint32_t table_id, const struct in6_addr *dst,
-                   unsigned int plen, unsigned int priority)
+                   unsigned int plen, const struct in6_addr *nexthop,
+                   unsigned int priority)
 {
     if (!TABLE_ID_VALID(table_id)) {
         static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 20);
@@ -186,7 +201,8 @@ re_nl_delete_route(uint32_t table_id, const struct in6_addr 
*dst,
         return EINVAL;
     }
 
-    return modify_route(RTM_DELROUTE, 0, table_id, dst, plen, priority);
+    return modify_route(RTM_DELROUTE, 0, table_id, dst, plen,
+                        nexthop, priority);
 }
 
 struct route_msg_handle_data {
@@ -249,10 +265,14 @@ handle_route_msg(const struct route_table_msg *msg, void 
*data)
     }
 
     if (handle_data->routes_to_advertise) {
-        uint32_t hash = advertise_route_hash(&rd->rta_dst, rd->rtm_dst_len);
+        uint32_t hash = advertise_route_hash(&rd->rta_dst,
+                                             &rd->primary_next_hop__.addr,
+                                             rd->rtm_dst_len);
         HMAP_FOR_EACH_WITH_HASH (ar, node, hash, handle_data->routes) {
             if (ipv6_addr_equals(&ar->addr, &rd->rta_dst)
                     && ar->plen == rd->rtm_dst_len
+                    && ipv6_addr_equals(&ar->nexthop,
+                                        &rd->primary_next_hop__.addr)
                     && ar->priority == rd->rta_priority) {
                 hmapx_find_and_delete(handle_data->routes_to_advertise, ar);
                 return;
@@ -268,30 +288,52 @@ handle_route_msg(const struct route_table_msg *msg, void 
*data)
 static int
 re_nl_delete_stale_routes(const struct vector *stale_routes)
 {
+    static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 20);
+    struct ds ds = DS_EMPTY_INITIALIZER;
     int ret = 0;
 
     const struct route_data *rd;
     VECTOR_FOR_EACH_PTR (stale_routes, rd) {
         int err = re_nl_delete_route(rd->rta_table_id, &rd->rta_dst,
-                                     rd->rtm_dst_len, rd->rta_priority);
+                                     rd->rtm_dst_len,
+                                     &rd->primary_next_hop__.addr,
+                                     rd->rta_priority);
         if (err) {
-            char addr_s[INET6_ADDRSTRLEN + 1];
-            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 20);
-            VLOG_WARN_RL(&rl, "Delete route table_id=%"PRIu32" dst=%s plen=%d "
-                         "failed: %s", rd->rta_table_id,
-                         ipv6_string_mapped(
-                             addr_s, &rd->rta_dst) ? addr_s : "(invalid)",
-                         rd->rtm_dst_len,
-                         ovs_strerror(err));
+            re_route_format(&ds, rd->rta_table_id, &rd->rta_dst,
+                            rd->rtm_dst_len, &rd->primary_next_hop__.addr,
+                            err);
+            VLOG_WARN_RL(&rl, "Delete route %s", ds_cstr(&ds));
+            ds_clear(&ds);
             if (!ret) {
                 ret = err;
             }
         }
     }
 
+    ds_destroy(&ds);
     return ret;
 }
 
+static void
+re_nl_encode_nexthop(struct ofpbuf *request, bool dst_is_ipv4,
+                     const struct in6_addr *nexthop)
+{
+    bool nh_is_ipv4 = IN6_IS_ADDR_V4MAPPED(nexthop);
+    size_t len = nh_is_ipv4 ? sizeof(ovs_be32) : sizeof(struct in6_addr);
+
+    ovs_be32 nexthop4 = in6_addr_get_mapped_ipv4(nexthop);
+    void *nl_attr_dst = nh_is_ipv4 ? (void *) &nexthop4 : (void *) nexthop;
+
+    if (dst_is_ipv4 != nh_is_ipv4) {
+        struct rtvia *via = nl_msg_put_unspec_uninit(request, RTA_VIA,
+                                                     sizeof *via + len);
+        via->rtvia_family = nh_is_ipv4 ? AF_INET : AF_INET6;
+        memcpy(via->rtvia_addr, nl_attr_dst, len);
+    } else {
+        nl_msg_put_unspec(request, RTA_GATEWAY, nl_attr_dst, len);
+    }
+}
+
 int
 re_nl_sync_routes(uint32_t table_id, const struct hmap *routes,
                   struct vector *learned_routes,
@@ -320,22 +362,21 @@ re_nl_sync_routes(uint32_t table_id, const struct hmap 
*routes,
 
     int ret = re_nl_delete_stale_routes(&stale_routes);
 
+    static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 20);
+    struct ds ds = DS_EMPTY_INITIALIZER;
+
     /* Add any remaining routes in the routes_to_advertise hmapx to the
      * system routing table. */
     struct hmapx_node *hn;
     HMAPX_FOR_EACH (hn, &routes_to_advertise) {
         ar = hn->data;
-        int err = re_nl_add_route(table_id, &ar->addr, ar->plen, ar->priority);
+        int err = re_nl_add_route(table_id, &ar->addr, ar->plen,
+                                  &ar->nexthop, ar->priority);
         if (err) {
-            char addr_s[INET6_ADDRSTRLEN + 1];
-            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 20);
-            VLOG_WARN_RL(&rl, "Add route table_id=%"PRIu32" dst=%s "
-                         "plen=%d: %s",
-                         table_id,
-                         ipv6_string_mapped(
-                             addr_s, &ar->addr) ? addr_s : "(invalid)",
-                         ar->plen,
-                         ovs_strerror(err));
+            re_route_format(&ds, table_id, &ar->addr, ar->plen,
+                            &ar->nexthop, err);
+            VLOG_WARN_RL(&rl, "Add route %s", ds_cstr(&ds));
+            ds_clear(&ds);
             if (!ret) {
                 /* Report the first error value to the caller. */
                 ret = err;
@@ -345,6 +386,7 @@ re_nl_sync_routes(uint32_t table_id, const struct hmap 
*routes,
 
     hmapx_destroy(&routes_to_advertise);
     vector_destroy(&stale_routes);
+    ds_destroy(&ds);
 
     return ret;
 }
diff --git a/controller/route-exchange-netlink.h 
b/controller/route-exchange-netlink.h
index 1741f761d..4ee513d40 100644
--- a/controller/route-exchange-netlink.h
+++ b/controller/route-exchange-netlink.h
@@ -52,10 +52,15 @@ struct re_nl_received_route_node {
 int re_nl_create_vrf(const char *ifname, uint32_t table_id);
 int re_nl_delete_vrf(const char *ifname);
 
+void re_route_format(struct ds *, uint32_t table_id,
+                     const struct in6_addr *dst, unsigned int plen,
+                     const struct in6_addr *nexthop, int err);
 int re_nl_add_route(uint32_t table_id, const struct in6_addr *dst,
-                    unsigned int plen, unsigned int priority);
+                    unsigned int plen, const struct in6_addr *nexthop,
+                    unsigned int priority);
 int re_nl_delete_route(uint32_t table_id, const struct in6_addr *dst,
-                       unsigned int plen, unsigned int priority);
+                       unsigned int plen, const struct in6_addr *nexthop,
+                       unsigned int priority);
 
 int re_nl_sync_routes(uint32_t table_id, const struct hmap *routes,
                       struct vector *learned_routes,
diff --git a/controller/route.c b/controller/route.c
index 093306c5b..082a4630d 100644
--- a/controller/route.c
+++ b/controller/route.c
@@ -43,9 +43,11 @@ route_exchange_relevant_port(const struct sbrec_port_binding 
*pb)
 }
 
 uint32_t
-advertise_route_hash(const struct in6_addr *dst, unsigned int plen)
+advertise_route_hash(const struct in6_addr *dst,
+                     const struct in6_addr *nexthop, unsigned int plen)
 {
-    uint32_t hash = hash_bytes(dst->s6_addr, 16, 0);
+    uint32_t hash = hash_add_in6_addr(0, dst);
+    hash = hash_add_in6_addr(hash, nexthop);
     return hash_int(plen, hash);
 }
 
@@ -318,11 +320,13 @@ route_run(struct route_ctx_in *r_ctx_in,
         }
 
         struct advertise_route_entry *ar = xmalloc(sizeof(*ar));
-        ar->addr = prefix;
-        ar->plen = plen;
-        ar->priority = priority;
+        *ar = (struct advertise_route_entry) {
+            .addr = prefix,
+            .plen = plen,
+            .priority = priority,
+        };
         hmap_insert(&ad->routes, &ar->node,
-                    advertise_route_hash(&prefix, plen));
+                    advertise_route_hash(&ar->addr, &ar->nexthop, plen));
     }
 
     smap_destroy(&port_mapping);
diff --git a/controller/route.h b/controller/route.h
index 5c800f4ab..cf84a3079 100644
--- a/controller/route.h
+++ b/controller/route.h
@@ -79,6 +79,7 @@ struct advertise_datapath_entry {
 struct advertise_route_entry {
     struct hmap_node node;
     struct in6_addr addr;
+    struct in6_addr nexthop;
     unsigned int plen;
     unsigned int priority;
 };
@@ -88,7 +89,9 @@ const struct sbrec_port_binding *route_exchange_find_port(
     const struct sbrec_chassis *chassis,
     const struct sbrec_port_binding *pb,
     const char **dynamic_routing_port_name);
-uint32_t advertise_route_hash(const struct in6_addr *dst, unsigned int plen);
+uint32_t advertise_route_hash(const struct in6_addr *dst,
+                              const struct in6_addr *nexthop,
+                              unsigned int plen);
 void route_run(struct route_ctx_in *, struct route_ctx_out *);
 void route_cleanup(struct hmap *announce_routes);
 uint32_t route_get_table_id(const struct sbrec_datapath_binding *);
diff --git a/tests/automake.mk b/tests/automake.mk
index 18472d9cc..b037a3634 100644
--- a/tests/automake.mk
+++ b/tests/automake.mk
@@ -306,6 +306,8 @@ tests_ovstest_SOURCES += \
        controller/neighbor-table-notify.h \
        controller/neighbor.c \
        controller/neighbor.h \
+       controller/route-exchange-netlink.c \
+       controller/route-exchange-netlink.h \
        tests/test-ovn-netlink.c
 endif
 
diff --git a/tests/system-ovn-netlink.at b/tests/system-ovn-netlink.at
index 392973144..79988534f 100644
--- a/tests/system-ovn-netlink.at
+++ b/tests/system-ovn-netlink.at
@@ -299,3 +299,203 @@ AT_CHECK([ovstest test-ovn-netlink host-if-monitor 
lo-test \
 0
 ])
 AT_CLEANUP
+
+AT_SETUP([sync netlink routes - Blackhole])
+AT_KEYWORDS([netlink-routes])
+
+table_id=100
+
+check ip link add vrf-$table_id type vrf table $table_id
+on_exit 'ip link del vrf-$table_id'
+check ip link set dev vrf-$table_id up
+
+check ip link add lo-test type dummy
+on_exit 'ip link del lo-test'
+check ip link set lo-test master vrf-$table_id
+check ip link set lo-test address 00:00:00:00:00:10
+check ip addr add 20.0.0.10/24 dev lo-test
+check ip addr add fd20::10/64 dev lo-test
+check ip link set up lo-test
+
+OVS_WAIT_FOR_OUTPUT_UNQUOTED([ovstest test-ovn-netlink route-sync \
+    $table_id | sort], [0], [dnl
+])
+
+check ip route add 10.10.10.1 via 20.0.0.1 vrf vrf-$table_id proto zebra
+check ip route add fd20:100::10 via fd20::1 vrf vrf-$table_id proto zebra
+check ip route add 10.10.20.0/24 via 20.0.0.1 vrf vrf-$table_id
+
+AS_BOX([No advertisement])
+OVS_WAIT_FOR_OUTPUT_UNQUOTED([ovstest test-ovn-netlink route-sync \
+    $table_id | sort], [0], [dnl
+Route table_id=$table_id dst=10.10.10.1 plen=32 nexthop=20.0.0.1
+Route table_id=$table_id dst=fd20:100::10 plen=128 nexthop=fd20::1
+])
+
+OVN_ROUTE_EQUAL([vrf-$table_id], [dnl
+10.10.10.1 via 20.0.0.1 dev lo-test proto zebra
+10.10.20.0/24 via 20.0.0.1 dev lo-test
+20.0.0.0/24 dev lo-test proto kernel scope link src 20.0.0.10])
+
+OVN_ROUTE_V6_EQUAL([vrf-$table_id], [dnl
+fd20::/64 dev lo-test proto kernel metric 256 pref medium
+fd20:100::10 via fd20::1 dev lo-test proto zebra metric 1024 pref medium
+fe80::/64 dev lo-test proto kernel metric 256 pref medium
+multicast ff00::/8 dev lo-test proto kernel metric 256 pref medium])
+
+AS_BOX([Advertise just IPv4])
+OVS_WAIT_FOR_OUTPUT_UNQUOTED([ovstest test-ovn-netlink route-sync \
+    $table_id 192.168.100.0/24 | sort], [0], [dnl
+Route table_id=$table_id dst=10.10.10.1 plen=32 nexthop=20.0.0.1
+Route table_id=$table_id dst=fd20:100::10 plen=128 nexthop=fd20::1
+])
+
+OVN_ROUTE_EQUAL([vrf-$table_id], [dnl
+10.10.10.1 via 20.0.0.1 dev lo-test proto zebra
+10.10.20.0/24 via 20.0.0.1 dev lo-test
+20.0.0.0/24 dev lo-test proto kernel scope link src 20.0.0.10
+blackhole 192.168.100.0/24 proto ovn])
+
+OVN_ROUTE_V6_EQUAL([vrf-$table_id], [dnl
+fd20::/64 dev lo-test proto kernel metric 256 pref medium
+fd20:100::10 via fd20::1 dev lo-test proto zebra metric 1024 pref medium
+fe80::/64 dev lo-test proto kernel metric 256 pref medium
+multicast ff00::/8 dev lo-test proto kernel metric 256 pref medium])
+
+AS_BOX([Advertise both IPv4 and IPv6])
+OVS_WAIT_FOR_OUTPUT_UNQUOTED([ovstest test-ovn-netlink route-sync \
+    $table_id 192.168.100.0/24 fd20:100::/64 | sort], [0], [dnl
+Route table_id=$table_id dst=10.10.10.1 plen=32 nexthop=20.0.0.1
+Route table_id=$table_id dst=fd20:100::10 plen=128 nexthop=fd20::1
+])
+
+OVN_ROUTE_EQUAL([vrf-$table_id], [dnl
+10.10.10.1 via 20.0.0.1 dev lo-test proto zebra
+10.10.20.0/24 via 20.0.0.1 dev lo-test
+20.0.0.0/24 dev lo-test proto kernel scope link src 20.0.0.10
+blackhole 192.168.100.0/24 proto ovn])
+
+OVN_ROUTE_V6_EQUAL([vrf-$table_id], [dnl
+fd20::/64 dev lo-test proto kernel metric 256 pref medium
+fd20:100::10 via fd20::1 dev lo-test proto zebra metric 1024 pref medium
+blackhole fd20:100::/64 dev lo proto ovn metric 1024 pref medium
+fe80::/64 dev lo-test proto kernel metric 256 pref medium
+multicast ff00::/8 dev lo-test proto kernel metric 256 pref medium])
+
+AS_BOX([Advertise just IPv4, should remove the IPv6])
+OVS_WAIT_FOR_OUTPUT_UNQUOTED([ovstest test-ovn-netlink route-sync \
+    $table_id 192.168.100.0/24 | sort], [0], [dnl
+Route table_id=$table_id dst=10.10.10.1 plen=32 nexthop=20.0.0.1
+Route table_id=$table_id dst=fd20:100::10 plen=128 nexthop=fd20::1
+])
+
+OVN_ROUTE_EQUAL([vrf-$table_id], [dnl
+10.10.10.1 via 20.0.0.1 dev lo-test proto zebra
+10.10.20.0/24 via 20.0.0.1 dev lo-test
+20.0.0.0/24 dev lo-test proto kernel scope link src 20.0.0.10
+blackhole 192.168.100.0/24 proto ovn])
+
+OVN_ROUTE_V6_EQUAL([vrf-$table_id], [dnl
+fd20::/64 dev lo-test proto kernel metric 256 pref medium
+fd20:100::10 via fd20::1 dev lo-test proto zebra metric 1024 pref medium
+fe80::/64 dev lo-test proto kernel metric 256 pref medium
+multicast ff00::/8 dev lo-test proto kernel metric 256 pref medium])
+
+AS_BOX([No advertisement should remove IPv4])
+OVS_WAIT_FOR_OUTPUT_UNQUOTED([ovstest test-ovn-netlink route-sync \
+    $table_id | sort], [0], [dnl
+Route table_id=$table_id dst=10.10.10.1 plen=32 nexthop=20.0.0.1
+Route table_id=$table_id dst=fd20:100::10 plen=128 nexthop=fd20::1
+])
+
+OVN_ROUTE_EQUAL([vrf-$table_id], [dnl
+10.10.10.1 via 20.0.0.1 dev lo-test proto zebra
+10.10.20.0/24 via 20.0.0.1 dev lo-test
+20.0.0.0/24 dev lo-test proto kernel scope link src 20.0.0.10])
+
+OVN_ROUTE_V6_EQUAL([vrf-$table_id], [dnl
+fd20::/64 dev lo-test proto kernel metric 256 pref medium
+fd20:100::10 via fd20::1 dev lo-test proto zebra metric 1024 pref medium
+fe80::/64 dev lo-test proto kernel metric 256 pref medium
+multicast ff00::/8 dev lo-test proto kernel metric 256 pref medium])
+
+AT_CLEANUP
+
+AT_SETUP([sync netlink routes - Nexthop])
+AT_KEYWORDS([netlink-routes])
+
+table_id=100
+
+check ip link add vrf-$table_id type vrf table $table_id
+on_exit 'ip link del vrf-$table_id'
+check ip link set dev vrf-$table_id up
+
+check ip link add lo-test type dummy
+on_exit 'ip link del lo-test'
+check ip link set lo-test master vrf-$table_id
+check ip link set lo-test address 00:00:00:00:00:10
+check ip addr add 20.0.0.10/24 dev lo-test
+check ip addr add fd20::10/64 dev lo-test
+check ip link set up lo-test
+
+check ovstest test-ovn-netlink route-sync $table_id
+
+AS_BOX([Advertise IPv4 via IPv4])
+check ovstest test-ovn-netlink route-sync $table_id \
+    192.168.100.0/24 via 20.0.0.1
+
+OVN_ROUTE_EQUAL([vrf-$table_id], [dnl
+20.0.0.0/24 dev lo-test proto kernel scope link src 20.0.0.10
+192.168.100.0/24 via 20.0.0.1 dev lo-test proto ovn])
+
+OVN_ROUTE_V6_EQUAL([vrf-$table_id], [dnl
+fd20::/64 dev lo-test proto kernel metric 256 pref medium
+fe80::/64 dev lo-test proto kernel metric 256 pref medium
+multicast ff00::/8 dev lo-test proto kernel metric 256 pref medium])
+
+AS_BOX([Advertise IPv4 via IPv4 and IPv6 via IPv6])
+check ovstest test-ovn-netlink route-sync $table_id \
+    192.168.100.0/24 via 20.0.0.1 \
+    fd20:100::/64 via fd20::1
+
+OVN_ROUTE_EQUAL([vrf-$table_id], [dnl
+20.0.0.0/24 dev lo-test proto kernel scope link src 20.0.0.10
+192.168.100.0/24 via 20.0.0.1 dev lo-test proto ovn])
+
+OVN_ROUTE_V6_EQUAL([vrf-$table_id], [dnl
+fd20::/64 dev lo-test proto kernel metric 256 pref medium
+fd20:100::/64 via fd20::1 dev lo-test proto ovn metric 1024 pref medium
+fe80::/64 dev lo-test proto kernel metric 256 pref medium
+multicast ff00::/8 dev lo-test proto kernel metric 256 pref medium])
+
+AS_BOX([Advertise IPv4 via IPv6 and IPv6 via IPv6])
+check ovstest test-ovn-netlink route-sync $table_id \
+    192.168.100.0/24 via fd20::1 \
+    fd20:100::/64 via fd20::1
+
+OVN_ROUTE_EQUAL([vrf-$table_id], [dnl
+20.0.0.0/24 dev lo-test proto kernel scope link src 20.0.0.10
+192.168.100.0/24 via inet6 fd20::1 dev lo-test proto ovn])
+
+OVN_ROUTE_V6_EQUAL([vrf-$table_id], [dnl
+fd20::/64 dev lo-test proto kernel metric 256 pref medium
+fd20:100::/64 via fd20::1 dev lo-test proto ovn metric 1024 pref medium
+fe80::/64 dev lo-test proto kernel metric 256 pref medium
+multicast ff00::/8 dev lo-test proto kernel metric 256 pref medium])
+
+AS_BOX([Replace both with blackhole])
+check ovstest test-ovn-netlink route-sync $table_id \
+    192.168.100.0/24 \
+    fd20:100::/64
+
+OVN_ROUTE_EQUAL([vrf-$table_id], [dnl
+20.0.0.0/24 dev lo-test proto kernel scope link src 20.0.0.10
+blackhole 192.168.100.0/24 proto ovn])
+
+OVN_ROUTE_V6_EQUAL([vrf-$table_id], [dnl
+fd20::/64 dev lo-test proto kernel metric 256 pref medium
+blackhole fd20:100::/64 dev lo proto ovn metric 1024 pref medium
+fe80::/64 dev lo-test proto kernel metric 256 pref medium
+multicast ff00::/8 dev lo-test proto kernel metric 256 pref medium])
+
+AT_CLEANUP
diff --git a/tests/test-ovn-netlink.c b/tests/test-ovn-netlink.c
index 84e24d0dd..701692fa6 100644
--- a/tests/test-ovn-netlink.c
+++ b/tests/test-ovn-netlink.c
@@ -16,6 +16,7 @@
 #include <config.h>
 
 #include "openvswitch/hmap.h"
+#include "route-table.h"
 #include "packets.h"
 #include "tests/ovstest.h"
 #include "tests/test-utils.h"
@@ -24,6 +25,8 @@
 #include "controller/neighbor-exchange-netlink.h"
 #include "controller/neighbor-table-notify.h"
 #include "controller/neighbor.h"
+#include "controller/route.h"
+#include "controller/route-exchange-netlink.h"
 
 static void
 test_neighbor_sync(struct ovs_cmdl_context *ctx)
@@ -177,6 +180,70 @@ test_host_if_monitor(struct ovs_cmdl_context *ctx)
     sset_destroy(&if_names);
 }
 
+static void
+test_route_sync(struct ovs_cmdl_context *ctx)
+{
+    struct advertise_route_entry *e;
+    unsigned int shift = 1;
+
+    unsigned int table_id;
+    if (!test_read_uint_value(ctx, shift++, "table id", &table_id)) {
+        return;
+    }
+
+    struct hmap routes_to_advertise = HMAP_INITIALIZER(&routes_to_advertise);
+    struct vector received_routes =
+        VECTOR_EMPTY_INITIALIZER(struct re_nl_received_route_node);
+
+    while (shift < ctx->argc) {
+        struct advertise_route_entry *ar = xzalloc(sizeof *ar);
+        if (!test_read_ipv6_cidr_mapped_value(ctx, shift++, "IP address",
+                                              &ar->addr, &ar->plen)) {
+            free(ar);
+            goto done;
+        }
+
+        /* Check if we are adding only blackhole route. */
+        if (shift + 1 < ctx->argc) {
+            const char *via = test_read_value(ctx, shift++, "via");
+            if (strcmp(via, "via")) {
+                shift--;
+                continue;
+            }
+
+            if (!test_read_ipv6_mapped_value(ctx, shift++, "IP address",
+                                             &ar->nexthop)) {
+                free(ar);
+                goto done;
+            }
+        }
+        hmap_insert(&routes_to_advertise, &ar->node,
+                    advertise_route_hash(&ar->addr, &ar->nexthop, ar->plen));
+    }
+
+    ovs_assert(re_nl_sync_routes(table_id, &routes_to_advertise,
+                                 &received_routes, NULL) == 0);
+
+
+    struct ds msg = DS_EMPTY_INITIALIZER;
+
+    struct re_nl_received_route_node *rr;
+    VECTOR_FOR_EACH_PTR (&received_routes, rr) {
+        re_route_format(&msg, table_id, &rr->prefix,
+                        rr->plen, &rr->nexthop, 0);
+        printf("Route %s\n", ds_cstr(&msg));
+        ds_clear(&msg);
+    }
+
+done:
+    HMAP_FOR_EACH_POP (e, node, &routes_to_advertise) {
+        free(e);
+    }
+    hmap_destroy(&routes_to_advertise);
+    vector_destroy(&received_routes);
+    ds_destroy(&msg);
+}
+
 static void
 test_ovn_netlink(int argc, char *argv[])
 {
@@ -186,6 +253,7 @@ test_ovn_netlink(int argc, char *argv[])
         {"neighbor-table-notify", NULL, 3, 4,
          test_neighbor_table_notify, OVS_RO},
         {"host-if-monitor", NULL, 2, 3, test_host_if_monitor, OVS_RO},
+        {"route-sync", NULL, 1, INT_MAX, test_route_sync, OVS_RO},
         {NULL, NULL, 0, 0, NULL, OVS_RO},
     };
     struct ovs_cmdl_context ctx;
diff --git a/tests/test-utils.c b/tests/test-utils.c
index e55557066..8edd521d0 100644
--- a/tests/test-utils.c
+++ b/tests/test-utils.c
@@ -114,3 +114,21 @@ test_read_ipv6_mapped_value(struct ovs_cmdl_context *ctx, 
unsigned int index,
     }
     return true;
 }
+
+bool
+test_read_ipv6_cidr_mapped_value(struct ovs_cmdl_context *ctx,
+                                 unsigned int index, const char *descr,
+                                 struct in6_addr *result, unsigned int *plen)
+{
+    if (index >= ctx->argc) {
+        fprintf(stderr, "Missing %s argument\n", descr);
+        return false;
+    }
+
+    const char *arg = ctx->argv[index];
+    if (!ip46_parse_cidr(arg, result, plen)) {
+        fprintf(stderr, "Invalid %s: %s\n", descr, arg);
+        return false;
+    }
+    return true;
+}
diff --git a/tests/test-utils.h b/tests/test-utils.h
index fef67e799..e8176ce44 100644
--- a/tests/test-utils.h
+++ b/tests/test-utils.h
@@ -35,4 +35,8 @@ bool test_read_eth_addr_value(struct ovs_cmdl_context *ctx, 
unsigned int index,
 bool test_read_ipv6_mapped_value(struct ovs_cmdl_context *ctx,
                                  unsigned int index, const char *descr,
                                  struct in6_addr *result);
+bool test_read_ipv6_cidr_mapped_value(struct ovs_cmdl_context *ctx,
+                                      unsigned int index, const char *descr,
+                                      struct in6_addr *result,
+                                      unsigned int *plen);
 #endif /* tests/test-utils.h */
-- 
2.51.1

_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to