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]>
---
v3: Address comments from Dumitru.
    Adjust for the previous patch changes.
---
 controller/route-exchange-netlink.c | 102 +++++++++-----
 controller/route-exchange-netlink.h |   3 +
 controller/route.c                  |  27 +++-
 controller/route.h                  |   5 +-
 tests/automake.mk                   |   2 +
 tests/system-ovn-netlink.at         | 200 ++++++++++++++++++++++++++++
 tests/test-ovn-netlink.c            |  67 ++++++++++
 tests/test-utils.c                  |  18 +++
 tests/test-utils.h                  |   4 +
 9 files changed, 385 insertions(+), 43 deletions(-)

diff --git a/controller/route-exchange-netlink.c 
b/controller/route-exchange-netlink.c
index d6358343d..4e51bf69c 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,12 +98,31 @@ 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 advertise_route_entry *re)
 {
     uint32_t flags = NLM_F_REQUEST | NLM_F_ACK;
     bool is_ipv4 = IN6_IS_ADDR_V4MAPPED(&re->addr);
+    bool nexthop_unspec = ipv6_is_zero(&re->nexthop);
     struct rtmsg *rt;
     int err;
 
@@ -116,7 +138,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 = re->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, &re->addr);
     }
 
+    if (!nexthop_unspec) {
+        re_nl_encode_nexthop(&request, is_ipv4, &re->nexthop);
+    }
+
     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(&re->addr)) {
-            ds_put_format(&msg, IP_FMT,
-                          IP_ARGS(in6_addr_get_mapped_ipv4(&re->addr)));
-        } else {
-            ipv6_format_addr(&re->addr, &msg);
-        }
-        ds_put_format(&msg, "/%u", re->plen);
-
-        VLOG_DBG("%s", ds_cstr(&msg));
+        re_route_format(&msg, table_id, &re->addr, re->plen, &re->nexthop, 0);
+        VLOG_DBG("%s route %s", type == RTM_DELROUTE ? "Removing" : "Adding",
+                 ds_cstr(&msg));
         ds_destroy(&msg);
     }
 
@@ -248,10 +261,11 @@ handle_route_msg(const struct route_table_msg *msg, void 
*data)
     const struct advertise_route_entry re =
             advertise_route_from_route_data(rd);
     if (handle_data->routes_to_advertise) {
-        uint32_t hash = advertise_route_hash(&re.addr, re.plen);
+        uint32_t hash = advertise_route_hash(&re.addr, &re.nexthop, re.plen);
         HMAP_FOR_EACH_WITH_HASH (ar, node, hash, handle_data->routes) {
             if (ipv6_addr_equals(&ar->addr, &re.addr)
                     && ar->plen == re.plen
+                    && ipv6_addr_equals(&ar->nexthop, &re.nexthop)
                     && ar->priority == re.priority) {
                 hmapx_find_and_delete(handle_data->routes_to_advertise, ar);
                 return;
@@ -267,29 +281,48 @@ handle_route_msg(const struct route_table_msg *msg, void 
*data)
 static int
 re_nl_delete_stale_routes(uint32_t table_id, 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 advertise_route_entry *re;
     VECTOR_FOR_EACH_PTR (stale_routes, re) {
         int err = re_nl_delete_route(table_id, re);
         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", table_id,
-                         ipv6_string_mapped(
-                             addr_s, &re->addr) ? addr_s : "(invalid)",
-                         re->plen,
-                         ovs_strerror(err));
+            re_route_format(&ds, table_id, &re->addr,
+                            re->plen, &re->nexthop, 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,
@@ -319,6 +352,9 @@ re_nl_sync_routes(uint32_t table_id, const struct hmap 
*routes,
 
     int ret = re_nl_delete_stale_routes(table_id, &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;
@@ -326,15 +362,10 @@ re_nl_sync_routes(uint32_t table_id, const struct hmap 
*routes,
         ar = hn->data;
         int err = re_nl_add_route(table_id, ar);
         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;
@@ -344,6 +375,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 ad1f0783b..3ebd4546f 100644
--- a/controller/route-exchange-netlink.h
+++ b/controller/route-exchange-netlink.h
@@ -55,6 +55,9 @@ int re_nl_delete_vrf(const char *ifname);
 int re_nl_add_route(uint32_t table_id, const struct advertise_route_entry *);
 int re_nl_delete_route(uint32_t table_id,
                        const struct advertise_route_entry *);
+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_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 dcf1984ab..c0ed2af48 100644
--- a/controller/route.c
+++ b/controller/route.c
@@ -45,20 +45,31 @@ 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);
 }
 
 struct advertise_route_entry
 advertise_route_from_route_data(const struct route_data *rd)
 {
-    return (struct advertise_route_entry) {
+    struct advertise_route_entry re = (struct advertise_route_entry) {
         .addr = rd->rta_dst,
         .plen = rd->rtm_dst_len,
         .priority = rd->rta_priority,
     };
+
+    if (!ovs_list_is_empty(&rd->nexthops)) {
+        const struct route_data_nexthop *rdnh =
+            CONTAINER_OF(ovs_list_front(&rd->nexthops),
+                         const struct route_data_nexthop, nexthop_node);
+        re.nexthop = rdnh->addr;
+    }
+
+    return re;
 }
 
 const struct sbrec_port_binding*
@@ -330,11 +341,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 a1dd61d53..38564c945 100644
--- a/controller/route.h
+++ b/controller/route.h
@@ -80,6 +80,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;
 };
@@ -89,7 +90,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);
 struct advertise_route_entry
 advertise_route_from_route_data(const struct route_data *);
 void route_run(struct route_ctx_in *, struct route_ctx_out *);
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..6e9b46d04 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,69 @@ 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 +252,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