diff mbox series

[ovs-dev,v4,01/11] ovs-router: Add infrastructure for multi-table routing.

Message ID 20251027124259.3395209-2-dchumak@nvidia.com
State Changes Requested
Delegated to: Ilya Maximets
Headers show
Series ovs-router: Multi-table routing infrastructure. | expand

Checks

Context Check Description
ovsrobot/apply-robot success apply and check: success
ovsrobot/github-robot-_Build_and_Test success github build: passed
ovsrobot/github-robot-_Build_and_Test success github build: passed

Commit Message

Dima Chumak Oct. 27, 2025, 12:42 p.m. UTC
For route lookup based on the destination address a single routing table
is sufficient. For a more advanced routing when a lookup needs to take
into consideration other parameters, such as a source address, a
multi-table lookup is needed.

This change introduces infrastructure for using multiple routing
tables that can be added dynamically, as a pre-step towards importing
non-default routing tables from kernel.

Signed-off-by: Dima Chumak <dchumak@nvidia.com>
---
 lib/netdev-dummy.c            |  14 ++-
 lib/ovs-router.c              | 166 +++++++++++++++++++++++++++-------
 lib/ovs-router.h              |  13 ++-
 lib/route-table.c             |   6 +-
 tests/nsh.at                  |   9 +-
 tests/ofproto-dpif.at         |   9 +-
 tests/ovs-router.at           |  36 +++++---
 tests/packet-type-aware.at    |  15 ++-
 tests/tunnel-push-pop-ipv6.at |  30 ++++--
 tests/tunnel-push-pop.at      |  42 ++++++---
 tests/tunnel.at               |   6 +-
 11 files changed, 253 insertions(+), 93 deletions(-)

Comments

Ilya Maximets Dec. 9, 2025, 9:19 p.m. UTC | #1
On 10/27/25 1:42 PM, Dima Chumak via dev wrote:
> For route lookup based on the destination address a single routing table
> is sufficient. For a more advanced routing when a lookup needs to take
> into consideration other parameters, such as a source address, a
> multi-table lookup is needed.
> 
> This change introduces infrastructure for using multiple routing
> tables that can be added dynamically, as a pre-step towards importing
> non-default routing tables from kernel.
> 
> Signed-off-by: Dima Chumak <dchumak@nvidia.com>
> ---
>  lib/netdev-dummy.c            |  14 ++-
>  lib/ovs-router.c              | 166 +++++++++++++++++++++++++++-------
>  lib/ovs-router.h              |  13 ++-
>  lib/route-table.c             |   6 +-
>  tests/nsh.at                  |   9 +-
>  tests/ofproto-dpif.at         |   9 +-
>  tests/ovs-router.at           |  36 +++++---
>  tests/packet-type-aware.at    |  15 ++-
>  tests/tunnel-push-pop-ipv6.at |  30 ++++--
>  tests/tunnel-push-pop.at      |  42 ++++++---
>  tests/tunnel.at               |   6 +-
>  11 files changed, 253 insertions(+), 93 deletions(-)

Mostly LGTM.  Just a couple nits below.

> 
> diff --git a/lib/netdev-dummy.c b/lib/netdev-dummy.c
> index b72820fcc506..bad86d3c4c76 100644
> --- a/lib/netdev-dummy.c
> +++ b/lib/netdev-dummy.c
> @@ -2221,10 +2221,13 @@ netdev_dummy_ip4addr(struct unixctl_conn *conn, int argc OVS_UNUSED,
>              mask.s_addr = be32_prefix_mask(plen);
>              netdev_dummy_add_in4(netdev, ip, mask);
>  
> -            /* Insert local route entry for the new address. */
>              in6_addr_set_mapped_ipv4(&ip6, ip.s_addr);
> -            ovs_router_force_insert(0, &ip6, plen + 96, true, argv[1],
> -                                    &in6addr_any, &ip6);
> +            /* Insert local route entry for the new address. */
> +            ovs_router_force_insert(CLS_MAIN, 0, &ip6, 32 + 96, true,
> +                                    argv[1], &in6addr_any, &ip6);
> +            /* Insert network route entry for the new address. */
> +            ovs_router_force_insert(CLS_MAIN, 0, &ip6, plen + 96, false,
> +                                    argv[1], &in6addr_any, &ip6);
>  
>              unixctl_command_reply(conn, "OK");
>          } else {
> @@ -2257,7 +2260,10 @@ netdev_dummy_ip6addr(struct unixctl_conn *conn, int argc OVS_UNUSED,
>              netdev_dummy_add_in6(netdev, &ip6, &mask);
>  
>              /* Insert local route entry for the new address. */
> -            ovs_router_force_insert(0, &ip6, plen, true, argv[1],
> +            ovs_router_force_insert(CLS_MAIN, 0, &ip6, 128, true, argv[1],
> +                                    &in6addr_any, &ip6);
> +            /* Insert network route entry for the new address. */
> +            ovs_router_force_insert(CLS_MAIN, 0, &ip6, plen, false, argv[1],
>                                      &in6addr_any, &ip6);
>  
>              unixctl_command_reply(conn, "OK");
> diff --git a/lib/ovs-router.c b/lib/ovs-router.c
> index 2827a6e43574..70644f7bd4f2 100644
> --- a/lib/ovs-router.c
> +++ b/lib/ovs-router.c
> @@ -33,6 +33,7 @@
>  #include "classifier.h"
>  #include "command-line.h"
>  #include "compiler.h"
> +#include "cmap.h"
>  #include "dpif.h"
>  #include "fatal-signal.h"
>  #include "openvswitch/dynamic-string.h"
> @@ -50,10 +51,16 @@
>  
>  VLOG_DEFINE_THIS_MODULE(ovs_router);
>  
> +struct clsmap_node {
> +    struct cmap_node cmap_node;
> +    uint32_t table;
> +    struct classifier cls;
> +};
> +
>  static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
>  
>  static struct ovs_mutex mutex = OVS_MUTEX_INITIALIZER;
> -static struct classifier cls;
> +static struct cmap clsmap = CMAP_INITIALIZER;
>  
>  /* By default, use the system routing table.  For system-independent testing,
>   * the unit tests disable using the system routing table. */
> @@ -71,6 +78,50 @@ struct ovs_router_entry {
>      uint32_t mark;
>  };
>  
> +static void rt_entry_delete__(const struct cls_rule *, struct classifier *);
> +
> +static struct classifier *
> +cls_find(uint32_t table)
> +{
> +    struct clsmap_node *node;
> +
> +    CMAP_FOR_EACH_WITH_HASH (node, cmap_node, hash_int(table, 0), &clsmap) {
> +        if (node->table == table) {
> +            return &node->cls;
> +        }
> +    }
> +
> +    return NULL;
> +}
> +
> +static struct classifier *
> +cls_create(uint32_t table) OVS_REQUIRES(mutex)
> +{
> +    struct clsmap_node *node;
> +
> +    node = xmalloc(sizeof *node);
> +    classifier_init(&node->cls, NULL);
> +    node->table = table;
> +    cmap_insert(&clsmap, &node->cmap_node, hash_int(table, 0));
> +
> +    return &node->cls;
> +}
> +
> +static void
> +cls_flush(struct classifier *cls, bool flush_all)
> +    OVS_REQUIRES(mutex)
> +{
> +    struct ovs_router_entry *rt;
> +
> +    classifier_defer(cls);
> +    CLS_FOR_EACH (rt, cr, cls) {
> +        if (flush_all || rt->priority == rt->plen || rt->local) {
> +            rt_entry_delete__(&rt->cr, cls);
> +        }
> +    }
> +    classifier_publish(cls);
> +}
> +
>  static struct ovs_router_entry *
>  ovs_router_entry_cast(const struct cls_rule *cr)
>  {
> @@ -110,15 +161,20 @@ ovs_router_lookup(uint32_t mark, const struct in6_addr *ip6_dst,
>                    char output_netdev[],
>                    struct in6_addr *src, struct in6_addr *gw)
>  {
> -    const struct cls_rule *cr;
>      struct flow flow = {.ipv6_dst = *ip6_dst, .pkt_mark = mark};
> +    struct classifier *cls_main = cls_find(CLS_MAIN);
> +    const struct cls_rule *cr;
> +
> +    if (!cls_main) {
> +        return false;
> +    }
>  
>      if (src && ipv6_addr_is_set(src)) {
>          const struct cls_rule *cr_src;
>          struct flow flow_src = {.ipv6_dst = *src, .pkt_mark = mark};
>  
> -        cr_src = classifier_lookup(&cls, OVS_VERSION_MAX, &flow_src, NULL,
> -                                   NULL);
> +        cr_src = classifier_lookup(cls_main, OVS_VERSION_MAX, &flow_src,
> +                                   NULL, NULL);
>          if (cr_src) {
>              struct ovs_router_entry *p_src = ovs_router_entry_cast(cr_src);
>              if (!p_src->local) {
> @@ -129,7 +185,7 @@ ovs_router_lookup(uint32_t mark, const struct in6_addr *ip6_dst,
>          }
>      }
>  
> -    cr = classifier_lookup(&cls, OVS_VERSION_MAX, &flow, NULL, NULL);
> +    cr = classifier_lookup(cls_main, OVS_VERSION_MAX, &flow, NULL, NULL);
>      if (cr) {
>          struct ovs_router_entry *p = ovs_router_entry_cast(cr);
>  
> @@ -257,8 +313,8 @@ out:
>  }
>  
>  static int
> -ovs_router_insert__(uint32_t mark, uint8_t priority, bool local,
> -                    const struct in6_addr *ip6_dst,
> +ovs_router_insert__(uint32_t table, uint32_t mark, uint8_t priority,
> +                    bool local, const struct in6_addr *ip6_dst,
>                      uint8_t plen, const char output_netdev[],
>                      const struct in6_addr *gw,
>                      const struct in6_addr *ip6_src)
> @@ -268,6 +324,7 @@ ovs_router_insert__(uint32_t mark, uint8_t priority, bool local,
>                          struct in6_addr *prefsrc);
>      const struct cls_rule *cr;
>      struct ovs_router_entry *p;
> +    struct classifier *cls;
>      struct match match;
>      int err;
>  
> @@ -308,7 +365,11 @@ ovs_router_insert__(uint32_t mark, uint8_t priority, bool local,
>      cls_rule_init(&p->cr, &match, priority);
>  
>      ovs_mutex_lock(&mutex);
> -    cr = classifier_replace(&cls, &p->cr, OVS_VERSION_MIN, NULL, 0);
> +    cls = cls_find(table);
> +    if (!cls) {
> +        cls = cls_create(table);
> +    }
> +    cr = classifier_replace(cls, &p->cr, OVS_VERSION_MIN, NULL, 0);
>      ovs_mutex_unlock(&mutex);
>  
>      if (cr) {
> @@ -321,13 +382,13 @@ ovs_router_insert__(uint32_t mark, uint8_t priority, bool local,
>  }
>  
>  void
> -ovs_router_insert(uint32_t mark, const struct in6_addr *ip_dst, uint8_t plen,
> -                  bool local, const char output_netdev[],
> +ovs_router_insert(uint32_t table, uint32_t mark, const struct in6_addr *ip_dst,
> +                  uint8_t plen, bool local, const char output_netdev[],
>                    const struct in6_addr *gw, const struct in6_addr *prefsrc)
>  {
>      if (use_system_routing_table) {
>          uint8_t priority = local ? plen + 64 : plen;
> -        ovs_router_insert__(mark, priority, local, ip_dst, plen,
> +        ovs_router_insert__(table, mark, priority, local, ip_dst, plen,
>                              output_netdev, gw, prefsrc);
>      }
>  }
> @@ -335,24 +396,25 @@ ovs_router_insert(uint32_t mark, const struct in6_addr *ip_dst, uint8_t plen,
>  /* The same as 'ovs_router_insert', but it adds the route even if updates
>   * from the system routing table are disabled.  Used for unit tests. */
>  void
> -ovs_router_force_insert(uint32_t mark, const struct in6_addr *ip_dst,
> +ovs_router_force_insert(uint32_t table, uint32_t mark,
> +                        const struct in6_addr *ip_dst,
>                          uint8_t plen, bool local, const char output_netdev[],
>                          const struct in6_addr *gw,
>                          const struct in6_addr *prefsrc)
>  {
>      uint8_t priority = local ? plen + 64 : plen;
>  
> -    ovs_router_insert__(mark, priority, local, ip_dst, plen,
> +    ovs_router_insert__(table, mark, priority, local, ip_dst, plen,
>                          output_netdev, gw, prefsrc);
>  }
>  
>  static void
> -rt_entry_delete__(const struct cls_rule *cr)
> +rt_entry_delete__(const struct cls_rule *cr, struct classifier *cls)
>  {
>      struct ovs_router_entry *p = ovs_router_entry_cast(cr);
>  
>      tnl_port_map_delete_ipdev(p->output_netdev);
> -    classifier_remove_assert(&cls, cr);
> +    classifier_remove_assert(cls, cr);
>      ovsrcu_postpone(rt_entry_free, ovs_router_entry_cast(cr));
>  }
>  
> @@ -360,20 +422,25 @@ static bool
>  rt_entry_delete(uint32_t mark, uint8_t priority,
>                  const struct in6_addr *ip6_dst, uint8_t plen)
>  {
> +    struct classifier *cls_main = cls_find(CLS_MAIN);
>      const struct cls_rule *cr;
>      struct cls_rule rule;
>      struct match match;
>      bool res = false;
>  
> +    if (!cls_main) {
> +        return false;
> +    }
> +
>      rt_init_match(&match, mark, ip6_dst, plen);
>  
>      cls_rule_init(&rule, &match, priority);
>  
>      /* Find the exact rule. */
> -    cr = classifier_find_rule_exactly(&cls, &rule, OVS_VERSION_MAX);
> +    cr = classifier_find_rule_exactly(cls_main, &rule, OVS_VERSION_MAX);
>      if (cr) {
>          ovs_mutex_lock(&mutex);
> -        rt_entry_delete__(cr);
> +        rt_entry_delete__(cr, cls_main);
>          ovs_mutex_unlock(&mutex);
>  
>          res = true;
> @@ -469,8 +536,8 @@ ovs_router_add(struct unixctl_conn *conn, int argc,
>          in6_addr_set_mapped_ipv4(&src6, src);
>      }
>  
> -    err = ovs_router_insert__(mark, plen + 32, false, &ip6, plen, argv[2],
> -                              &gw6, &src6);
> +    err = ovs_router_insert__(CLS_MAIN, mark, plen + 32, false, &ip6, plen,
> +                              argv[2], &gw6, &src6);
>      if (err) {
>          unixctl_command_reply_error(conn, "Error while inserting route.");
>      } else {
> @@ -512,12 +579,19 @@ ovs_router_del(struct unixctl_conn *conn, int argc OVS_UNUSED,
>  static void
>  ovs_router_show_json(struct json **routes)
>  {
> -    int n_rules = classifier_count(&cls);
>      struct json **json_entries = NULL;
>      struct ovs_router_entry *rt;
> +    struct classifier *cls_main;
>      struct ds ds;
> +    int n_rules;
>      int i = 0;
>  
> +    cls_main = cls_find(CLS_MAIN);
> +    if (!cls_main) {
> +        goto out;
> +    }
> +
> +    n_rules = classifier_count(cls_main);
>      if (!n_rules) {
>          goto out;
>      }
> @@ -525,7 +599,7 @@ ovs_router_show_json(struct json **routes)
>      json_entries = xmalloc(n_rules * sizeof *json_entries);
>      ds_init(&ds);
>  
> -    CLS_FOR_EACH (rt, cr, &cls) {
> +    CLS_FOR_EACH (rt, cr, cls_main) {
>          bool user = rt->priority != rt->plen && !rt->local;
>          uint8_t plen = rt->plen;
>          struct json *json, *nh;
> @@ -579,9 +653,15 @@ static void
>  ovs_router_show_text(struct ds *ds)
>  {
>      struct ovs_router_entry *rt;
> +    struct classifier *cls_main;
> +
> +    cls_main = cls_find(CLS_MAIN);
> +    if (!cls_main) {
> +        return;
> +    }
>  
>      ds_put_format(ds, "Route Table:\n");
> -    CLS_FOR_EACH (rt, cr, &cls) {
> +    CLS_FOR_EACH (rt, cr, cls_main) {
>          uint8_t plen;
>          if (rt->priority == rt->plen || rt->local) {
>              ds_put_format(ds, "Cached: ");
> @@ -668,27 +748,46 @@ ovs_router_lookup_cmd(struct unixctl_conn *conn, int argc,
>      }
>  }
>  
> -void
> -ovs_router_flush(void)
> +static void
> +clsmap_node_destroy_cb(struct clsmap_node *node)
>  {
> -    struct ovs_router_entry *rt;
> +    classifier_destroy(&node->cls);
> +    ovsrcu_postpone(free, node);
> +}
>  
> -    ovs_mutex_lock(&mutex);
> -    classifier_defer(&cls);
> -    CLS_FOR_EACH(rt, cr, &cls) {
> -        if (rt->priority == rt->plen || rt->local) {
> -            rt_entry_delete__(&rt->cr);
> +static void
> +ovs_router_flush_protected(bool flush_all)
> +    OVS_REQUIRES(mutex)
> +{
> +    struct clsmap_node *node;
> +
> +    CMAP_FOR_EACH (node, cmap_node, &clsmap) {
> +        cls_flush(&node->cls, flush_all);
> +        if (!node->cls.n_rules) {
> +            cmap_remove(&clsmap, &node->cmap_node, hash_int(node->table, 0));
> +            ovsrcu_postpone(clsmap_node_destroy_cb, node);
>          }
>      }
> -    classifier_publish(&cls);
> -    ovs_mutex_unlock(&mutex);
>      seq_change(tnl_conf_seq);
>  }
>  
> +void
> +ovs_router_flush(bool flush_all)
> +{
> +    ovs_mutex_lock(&mutex);
> +    ovs_router_flush_protected(flush_all);
> +    ovs_mutex_unlock(&mutex);
> +}
> +
>  static void
>  ovs_router_flush_handler(void *aux OVS_UNUSED)
>  {
> -    ovs_router_flush();
> +    ovs_mutex_lock(&mutex);
> +    ovs_router_flush_protected(true);
> +    ovs_assert(cmap_is_empty(&clsmap));
> +    cmap_destroy(&clsmap);
> +    cmap_init(&clsmap);
> +    ovs_mutex_unlock(&mutex);
>  }
>  
>  void
> @@ -698,7 +797,6 @@ ovs_router_init(void)
>  
>      if (ovsthread_once_start(&once)) {
>          fatal_signal_add_hook(ovs_router_flush_handler, NULL, NULL, true);
> -        classifier_init(&cls, NULL);
>          unixctl_command_register("ovs/route/add",
>                                   "ip/plen dev [gw] "
>                                   "[pkt_mark=mark] [src=src_ip]",
> diff --git a/lib/ovs-router.h b/lib/ovs-router.h
> index b61712707b1e..f4e6487d3dc6 100644
> --- a/lib/ovs-router.h
> +++ b/lib/ovs-router.h
> @@ -26,20 +26,27 @@
>  extern "C" {
>  #endif
>  
> +enum {
> +    CLS_MAIN = 254,
> +    CLS_ALL = UINT32_MAX,
> +};
> +
>  bool ovs_router_lookup(uint32_t mark, const struct in6_addr *ip_dst,
>                         char output_netdev[],
>                         struct in6_addr *src, struct in6_addr *gw);
>  void ovs_router_init(void);
> -void ovs_router_insert(uint32_t mark, const struct in6_addr *ip_dst,
> +void ovs_router_insert(uint32_t table, uint32_t mark,
> +                       const struct in6_addr *ip_dst,
>                         uint8_t plen, bool local,
>                         const char output_netdev[], const struct in6_addr *gw,
>                         const struct in6_addr *prefsrc);
> -void ovs_router_force_insert(uint32_t mark, const struct in6_addr *ip_dst,
> +void ovs_router_force_insert(uint32_t table, uint32_t mark,
> +                             const struct in6_addr *ip_dst,
>                               uint8_t plen, bool local,
>                               const char output_netdev[],
>                               const struct in6_addr *gw,
>                               const struct in6_addr *prefsrc);
> -void ovs_router_flush(void);
> +void ovs_router_flush(bool flush_all);
>  
>  void ovs_router_disable_system_routing_table(void);
>  
> diff --git a/lib/route-table.c b/lib/route-table.c
> index ca87ff7dbba4..0e77a9c9325e 100644
> --- a/lib/route-table.c
> +++ b/lib/route-table.c
> @@ -57,6 +57,8 @@ VLOG_DEFINE_THIS_MODULE(route_table);
>  
>  COVERAGE_DEFINE(route_table_dump);
>  
> +BUILD_ASSERT_DECL((enum rt_class_t) CLS_MAIN == RT_TABLE_MAIN);
> +
>  static struct ovs_mutex route_table_mutex = OVS_MUTEX_INITIALIZER;
>  static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 20);
>  
> @@ -565,7 +567,7 @@ route_table_handle_msg(const struct route_table_msg *change,
>          rdnh = CONTAINER_OF(ovs_list_front(&change->rd.nexthops),
>                              const struct route_data_nexthop, nexthop_node);
>  
> -        ovs_router_insert(rd->rta_mark, &rd->rta_dst,
> +        ovs_router_insert(CLS_MAIN, rd->rta_mark, &rd->rta_dst,
>                            IN6_IS_ADDR_V4MAPPED(&rd->rta_dst)
>                            ? rd->rtm_dst_len + 96 : rd->rtm_dst_len,
>                            rd->rtn_local, rdnh->ifname, &rdnh->addr,
> @@ -576,7 +578,7 @@ route_table_handle_msg(const struct route_table_msg *change,
>  static void
>  route_map_clear(void)
>  {
> -    ovs_router_flush();
> +    ovs_router_flush(false);
>  }
>  
>  bool
> diff --git a/tests/nsh.at b/tests/nsh.at
> index 022540dd68a4..3a2f2ed06774 100644
> --- a/tests/nsh.at
> +++ b/tests/nsh.at
> @@ -557,9 +557,12 @@ AT_CHECK([
>  AT_CHECK([
>      ovs-appctl ovs/route/show | grep Cached: | sort
>  ], [0], [dnl
> -Cached: 10.0.0.0/24 dev br-p1 SRC 10.0.0.1 local
> -Cached: 20.0.0.0/24 dev br-p2 SRC 20.0.0.2 local
> -Cached: 30.0.0.0/24 dev br-p3 SRC 30.0.0.3 local
> +Cached: 10.0.0.0/24 dev br-p1 SRC 10.0.0.1
> +Cached: 10.0.0.1/32 dev br-p1 SRC 10.0.0.1 local
> +Cached: 20.0.0.0/24 dev br-p2 SRC 20.0.0.2
> +Cached: 20.0.0.2/32 dev br-p2 SRC 20.0.0.2 local
> +Cached: 30.0.0.0/24 dev br-p3 SRC 30.0.0.3
> +Cached: 30.0.0.3/32 dev br-p3 SRC 30.0.0.3 local
>  ])
>  
>  AT_CHECK([
> diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at
> index 7ebbee56d9e6..319640aee5cb 100644
> --- a/tests/ofproto-dpif.at
> +++ b/tests/ofproto-dpif.at
> @@ -8510,7 +8510,8 @@ AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 1.1.2.88/24], [0], [OK
>  AT_CHECK([ovs-ofctl add-flow br0 action=normal])
>  dnl Checking that a local route for added IP was successfully installed.
>  AT_CHECK([ovs-appctl ovs/route/show | grep Cached], [0], [dnl

Need to add sort to the end, as these are still stored in a single
classifier and the order is not defined.  This will be less important
with the next patches, but seems less brittle nevertheless.

> -Cached: 1.1.2.0/24 dev br0 SRC 1.1.2.88 local
> +Cached: 1.1.2.88/32 dev br0 SRC 1.1.2.88 local
> +Cached: 1.1.2.0/24 dev br0 SRC 1.1.2.88
>  ])
>  
>  dnl Prime ARP Cache for 1.1.2.92
> @@ -8527,8 +8528,10 @@ AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 192.168.1.1/16], [0], [OK
>  ])
>  dnl Checking that a local route for added IP was successfully installed.
>  AT_CHECK([ovs-appctl ovs/route/show | grep Cached | sort], [0], [dnl
> -Cached: 1.1.2.0/24 dev br0 SRC 1.1.2.88 local
> -Cached: 192.168.0.0/16 dev br0 SRC 192.168.1.1 local
> +Cached: 1.1.2.0/24 dev br0 SRC 1.1.2.88
> +Cached: 1.1.2.88/32 dev br0 SRC 1.1.2.88 local
> +Cached: 192.168.0.0/16 dev br0 SRC 192.168.1.1
> +Cached: 192.168.1.1/32 dev br0 SRC 192.168.1.1 local
>  ])
>  
>  dnl add rule for int-br to force packet onto tunnel. There is no ifindex
> diff --git a/tests/ovs-router.at b/tests/ovs-router.at
> index 641b780a582a..d5f56da786d9 100644
> --- a/tests/ovs-router.at
> +++ b/tests/ovs-router.at
> @@ -31,14 +31,35 @@ User: 2.2.2.3/32 MARK 1 dev br0 SRC 2.2.2.2
>  AT_CHECK([ovs-appctl --format=json --pretty ovs/route/show], [0], [dnl
>  [[
>    {
> -    "dst": "2.2.2.0",
> +    "dst": "2.2.2.2",
>      "local": true,
> +    "nexthops": [
> +      {
> +        "dev": "br0"}],
> +    "prefix": 32,
> +    "prefsrc": "2.2.2.2",
> +    "priority": 192,
> +    "user": false},
> +  {
> +    "dst": "2.2.2.3",
> +    "local": false,
> +    "mark": 1,
> +    "nexthops": [
> +      {
> +        "dev": "br0"}],
> +    "prefix": 32,
> +    "prefsrc": "2.2.2.2",
> +    "priority": 160,
> +    "user": true},
> +  {
> +    "dst": "2.2.2.0",
> +    "local": false,
>      "nexthops": [
>        {
>          "dev": "br0"}],
>      "prefix": 24,
>      "prefsrc": "2.2.2.2",
> -    "priority": 184,
> +    "priority": 120,
>      "user": false},
>    {
>      "dst": "1.1.1.0",
> @@ -62,17 +83,6 @@ AT_CHECK([ovs-appctl --format=json --pretty ovs/route/show], [0], [dnl
>      "prefix": 24,
>      "prefsrc": "2.2.2.2",
>      "priority": 152,
> -    "user": true},
> -  {
> -    "dst": "2.2.2.3",
> -    "local": false,
> -    "mark": 1,
> -    "nexthops": [
> -      {
> -        "dev": "br0"}],
> -    "prefix": 32,
> -    "prefsrc": "2.2.2.2",
> -    "priority": 160,
>      "user": true}]]
>  ])
>  OVS_VSWITCHD_STOP
> diff --git a/tests/packet-type-aware.at b/tests/packet-type-aware.at
> index cebd8e6eeb46..300b17209d83 100644
> --- a/tests/packet-type-aware.at
> +++ b/tests/packet-type-aware.at
> @@ -160,9 +160,12 @@ AT_CHECK([
>  AT_CHECK([
>      ovs-appctl ovs/route/show | grep Cached: | sort
>  ], [0], [dnl
> -Cached: 10.0.0.0/24 dev br-p1 SRC 10.0.0.1 local
> -Cached: 20.0.0.0/24 dev br-p2 SRC 20.0.0.2 local
> -Cached: 30.0.0.0/24 dev br-p3 SRC 30.0.0.3 local
> +Cached: 10.0.0.0/24 dev br-p1 SRC 10.0.0.1
> +Cached: 10.0.0.1/32 dev br-p1 SRC 10.0.0.1 local
> +Cached: 20.0.0.0/24 dev br-p2 SRC 20.0.0.2
> +Cached: 20.0.0.2/32 dev br-p2 SRC 20.0.0.2 local
> +Cached: 30.0.0.0/24 dev br-p3 SRC 30.0.0.3
> +Cached: 30.0.0.3/32 dev br-p3 SRC 30.0.0.3 local
>  ])
>  
>  AT_CHECK([
> @@ -684,7 +687,8 @@ AT_CHECK([
>  AT_CHECK([
>      ovs-appctl ovs/route/show | grep Cached:

sort

>  ], [0], [dnl
> -Cached: 10.0.0.0/24 dev br2 SRC 10.0.0.1 local
> +Cached: 10.0.0.1/32 dev br2 SRC 10.0.0.1 local
> +Cached: 10.0.0.0/24 dev br2 SRC 10.0.0.1
>  ])
>  
>  
> @@ -960,7 +964,8 @@ ovs-appctl time/warp 1000
>  AT_CHECK([
>      ovs-appctl ovs/route/show | grep Cached:

sort

>  ],[0], [dnl
> -Cached: 20.0.0.0/24 dev br0 SRC 20.0.0.1 local
> +Cached: 20.0.0.1/32 dev br0 SRC 20.0.0.1 local
> +Cached: 20.0.0.0/24 dev br0 SRC 20.0.0.1
>  ])
>  
>  AT_CHECK([
> diff --git a/tests/tunnel-push-pop-ipv6.at b/tests/tunnel-push-pop-ipv6.at
> index 39fbd2d35f55..2c8c9d9fb881 100644
> --- a/tests/tunnel-push-pop-ipv6.at
> +++ b/tests/tunnel-push-pop-ipv6.at
> @@ -24,7 +24,8 @@ AT_CHECK([ovs-appctl netdev-dummy/ip6addr br0 2001:cafe::88/24], [0], [OK
>  ])
>  dnl Checking that a local routes for added IPs were successfully installed.
>  AT_CHECK([ovs-appctl ovs/route/show | grep Cached], [0], [dnl

sort

> -Cached: 2001:ca00::/24 dev br0 SRC 2001:cafe::88 local
> +Cached: 2001:cafe::88/128 dev br0 SRC 2001:cafe::88 local
> +Cached: 2001:ca00::/24 dev br0 SRC 2001:cafe::88
>  ])
>  AT_CHECK([ovs-appctl tnl/neigh/set br0 2001:cafe::91 aa:55:aa:55:00:01], [0], [OK
>  ])
> @@ -113,8 +114,10 @@ AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 1.1.2.88/24], [0], [OK
>  ])
>  dnl Checking that a local routes for added IPs were successfully installed.
>  AT_CHECK([ovs-appctl ovs/route/show | grep Cached | sort], [0], [dnl
> -Cached: 1.1.2.0/24 dev br0 SRC 1.1.2.88 local
> -Cached: 2001:ca00::/24 dev br0 SRC 2001:cafe::88 local
> +Cached: 1.1.2.0/24 dev br0 SRC 1.1.2.88
> +Cached: 1.1.2.88/32 dev br0 SRC 1.1.2.88 local
> +Cached: 2001:ca00::/24 dev br0 SRC 2001:cafe::88
> +Cached: 2001:cafe::88/128 dev br0 SRC 2001:cafe::88 local
>  ])
>  
>  AT_CHECK([ovs-ofctl add-flow br0 action=normal])
> @@ -189,8 +192,10 @@ AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 1.1.2.88/24], [0], [OK
>  ])
>  dnl Checking that a local routes for added IPs were successfully installed.
>  AT_CHECK([ovs-appctl ovs/route/show | grep Cached | sort], [0], [dnl
> -Cached: 1.1.2.0/24 dev br0 SRC 1.1.2.88 local
> -Cached: 2001:ca00::/24 dev br0 SRC 2001:cafe::88 local
> +Cached: 1.1.2.0/24 dev br0 SRC 1.1.2.88
> +Cached: 1.1.2.88/32 dev br0 SRC 1.1.2.88 local
> +Cached: 2001:ca00::/24 dev br0 SRC 2001:cafe::88
> +Cached: 2001:cafe::88/128 dev br0 SRC 2001:cafe::88 local
>  ])
>  
>  AT_CHECK([ovs-ofctl add-flow br0 action=normal])
> @@ -328,8 +333,10 @@ AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 1.1.2.88/24], [0], [OK
>  ])
>  dnl Checking that a local routes for added IPs were successfully installed.
>  AT_CHECK([ovs-appctl ovs/route/show | grep Cached | sort], [0], [dnl
> -Cached: 1.1.2.0/24 dev br0 SRC 1.1.2.88 local
> -Cached: 2001:ca00::/24 dev br0 SRC 2001:cafe::88 local
> +Cached: 1.1.2.0/24 dev br0 SRC 1.1.2.88
> +Cached: 1.1.2.88/32 dev br0 SRC 1.1.2.88 local
> +Cached: 2001:ca00::/24 dev br0 SRC 2001:cafe::88
> +Cached: 2001:cafe::88/128 dev br0 SRC 2001:cafe::88 local
>  ])
>  
>  AT_CHECK([ovs-ofctl add-flow br0 action=normal])
> @@ -701,8 +708,10 @@ AT_CHECK([ovs-appctl netdev-dummy/ip6addr br0 2001:beef::88/64], [0], [OK
>  ])
>  dnl Checking that a local route for added IP was successfully installed.
>  AT_CHECK([ovs-appctl ovs/route/show | grep Cached | sort], [0], [dnl
> -Cached: 2001:beef::/64 dev br0 SRC 2001:beef::88 local
> -Cached: 2001:cafe::/64 dev br0 SRC 2001:cafe::88 local
> +Cached: 2001:beef::/64 dev br0 SRC 2001:beef::88
> +Cached: 2001:beef::88/128 dev br0 SRC 2001:beef::88 local
> +Cached: 2001:cafe::/64 dev br0 SRC 2001:cafe::88
> +Cached: 2001:cafe::88/128 dev br0 SRC 2001:cafe::88 local
>  ])
>  AT_CHECK([ovs-ofctl add-flow br0 action=normal])
>  AT_CHECK([ovs-ofctl add-flow int-br action=normal])
> @@ -784,7 +793,8 @@ AT_CHECK([ovs-appctl netdev-dummy/ip6addr br0 2001:cafe::88/64], [0], [OK
>  ])
>  dnl Checking that a local route for added IP was successfully installed.
>  AT_CHECK([ovs-appctl ovs/route/show | grep Cached | sort], [0], [dnl
> -Cached: 2001:cafe::/64 dev br0 SRC 2001:cafe::88 local
> +Cached: 2001:cafe::/64 dev br0 SRC 2001:cafe::88
> +Cached: 2001:cafe::88/128 dev br0 SRC 2001:cafe::88 local
>  ])
>  
>  dnl Add a dp-hash selection group.
> diff --git a/tests/tunnel-push-pop.at b/tests/tunnel-push-pop.at
> index 795817d62246..4108cc4a7dac 100644
> --- a/tests/tunnel-push-pop.at
> +++ b/tests/tunnel-push-pop.at
> @@ -37,8 +37,10 @@ AT_CHECK([ovs-appctl netdev-dummy/ip6addr br0 2001:cafe::88/24], [0], [OK
>  ])
>  dnl Checking that a local routes for added IPs were successfully installed.
>  AT_CHECK([ovs-appctl ovs/route/show | grep Cached | sort], [0], [dnl
> -Cached: 1.1.2.0/24 dev br0 SRC 1.1.2.88 local
> -Cached: 2001:ca00::/24 dev br0 SRC 2001:cafe::88 local
> +Cached: 1.1.2.0/24 dev br0 SRC 1.1.2.88
> +Cached: 1.1.2.88/32 dev br0 SRC 1.1.2.88 local
> +Cached: 2001:ca00::/24 dev br0 SRC 2001:cafe::88
> +Cached: 2001:cafe::88/128 dev br0 SRC 2001:cafe::88 local
>  ])
>  
>  AT_CHECK([ovs-ofctl add-flow br0 action=normal])
> @@ -241,8 +243,10 @@ AT_CHECK([ovs-appctl ovs/route/add 1.1.2.92/24 br0 pkt_mark=1234], [0], [OK
>  dnl Checking that local routes for added IPs and the static route with a mark
>  dnl were successfully installed.
>  AT_CHECK([ovs-appctl ovs/route/show | grep br0 | sort], [0], [dnl
> -Cached: 1.1.2.0/24 dev br0 SRC 1.1.2.88 local
> -Cached: 2001:ca00::/24 dev br0 SRC 2001:cafe::88 local
> +Cached: 1.1.2.0/24 dev br0 SRC 1.1.2.88
> +Cached: 1.1.2.88/32 dev br0 SRC 1.1.2.88 local
> +Cached: 2001:ca00::/24 dev br0 SRC 2001:cafe::88
> +Cached: 2001:cafe::88/128 dev br0 SRC 2001:cafe::88 local
>  User: 1.1.2.0/24 MARK 1234 dev br0 SRC 1.1.2.88
>  ])
>  
> @@ -778,7 +782,8 @@ AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 1.1.2.88/24], [0], [OK
>  ])
>  dnl Checking that a local route for added IP was successfully installed.
>  AT_CHECK([ovs-appctl ovs/route/show | grep Cached], [0], [dnl

sort

> -Cached: 1.1.2.0/24 dev br0 SRC 1.1.2.88 local
> +Cached: 1.1.2.88/32 dev br0 SRC 1.1.2.88 local
> +Cached: 1.1.2.0/24 dev br0 SRC 1.1.2.88
>  ])
>  
>  AT_CHECK([ovs-ofctl add-flow br0 action=normal])
> @@ -821,7 +826,8 @@ AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 1.1.2.88/24], [0], [OK
>  ])
>  dnl Checking that a local route for added IP was successfully installed.
>  AT_CHECK([ovs-appctl ovs/route/show | grep Cached], [0], [dnl

sort

> -Cached: 1.1.2.0/24 dev br0 SRC 1.1.2.88 local
> +Cached: 1.1.2.88/32 dev br0 SRC 1.1.2.88 local
> +Cached: 1.1.2.0/24 dev br0 SRC 1.1.2.88
>  ])
>  AT_CHECK([ovs-ofctl add-flow br0 action=normal])
>  AT_CHECK([ovs-appctl revalidator/wait])
> @@ -896,8 +902,10 @@ AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 2.2.2.88/24], [0], [OK
>  ])
>  dnl Checking that a local route for added IP was successfully installed.
>  AT_CHECK([ovs-appctl ovs/route/show | grep Cached | sort], [0], [dnl
> -Cached: 1.1.2.0/24 dev br0 SRC 1.1.2.88 local
> -Cached: 2.2.2.0/24 dev br0 SRC 2.2.2.88 local
> +Cached: 1.1.2.0/24 dev br0 SRC 1.1.2.88
> +Cached: 1.1.2.88/32 dev br0 SRC 1.1.2.88 local
> +Cached: 2.2.2.0/24 dev br0 SRC 2.2.2.88
> +Cached: 2.2.2.88/32 dev br0 SRC 2.2.2.88 local
>  ])
>  AT_CHECK([ovs-ofctl add-flow br0 action=normal])
>  AT_CHECK([ovs-ofctl add-flow int-br action=normal])
> @@ -979,7 +987,8 @@ AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 1.1.2.88/24], [0], [OK
>  ])
>  dnl Checking that a local route for added IP was successfully installed.
>  AT_CHECK([ovs-appctl ovs/route/show | grep Cached], [0], [dnl

sort

> -Cached: 1.1.2.0/24 dev br0 SRC 1.1.2.88 local
> +Cached: 1.1.2.88/32 dev br0 SRC 1.1.2.88 local
> +Cached: 1.1.2.0/24 dev br0 SRC 1.1.2.88
>  ])
>  
>  AT_CHECK([ovs-ofctl add-flow br0 'arp,priority=1,action=normal'])
> @@ -1031,7 +1040,8 @@ AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 1.1.2.88/24], [0], [OK
>  ])
>  dnl Checking that a local route for added IP was successfully installed.
>  AT_CHECK([ovs-appctl ovs/route/show | grep Cached], [0], [dnl

sort

> -Cached: 1.1.2.0/24 dev br0 SRC 1.1.2.88 local
> +Cached: 1.1.2.88/32 dev br0 SRC 1.1.2.88 local
> +Cached: 1.1.2.0/24 dev br0 SRC 1.1.2.88
>  ])
>  AT_CHECK([ovs-ofctl add-flow br0 action=normal])
>  AT_CHECK([ovs-appctl revalidator/wait])
> @@ -1101,7 +1111,8 @@ AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 10.0.0.2/24], [0], [OK
>  ])
>  dnl Checking that a local route for added IP was successfully installed.
>  AT_CHECK([ovs-appctl ovs/route/show | grep Cached], [0], [dnl

sort

> -Cached: 10.0.0.0/24 dev br0 SRC 10.0.0.2 local
> +Cached: 10.0.0.2/32 dev br0 SRC 10.0.0.2 local
> +Cached: 10.0.0.0/24 dev br0 SRC 10.0.0.2
>  ])
>  
>  dnl Send an ARP reply to port b8 on br0, so that packets will be forwarded
> @@ -1149,7 +1160,8 @@ AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 10.0.0.2/24], [0], [OK
>  ])
>  dnl Checking that a local route for added IP was successfully installed.
>  AT_CHECK([ovs-appctl ovs/route/show | grep Cached], [0], [dnl

sort

> -Cached: 10.0.0.0/24 dev br0 SRC 10.0.0.2 local
> +Cached: 10.0.0.2/32 dev br0 SRC 10.0.0.2 local
> +Cached: 10.0.0.0/24 dev br0 SRC 10.0.0.2
>  ])
>  
>  dnl Send an ARP reply to port b8 on br0, so that packets will be forwarded
> @@ -1223,7 +1235,8 @@ AT_CHECK([ovs-appctl netdev-dummy/ip4addr vtep0 1.1.2.88/24], [0], [OK
>  ])
>  dnl Checking that a local route for added IP was successfully installed.
>  AT_CHECK([ovs-appctl ovs/route/show | grep Cached], [0], [dnl

sort

> -Cached: 1.1.2.0/24 dev vtep0 SRC 1.1.2.88 local
> +Cached: 1.1.2.88/32 dev vtep0 SRC 1.1.2.88 local
> +Cached: 1.1.2.0/24 dev vtep0 SRC 1.1.2.88
>  ])
>  
>  AT_CHECK([ovs-ofctl add-flow br0 action=normal])
> @@ -1288,7 +1301,8 @@ AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 1.1.2.88/24], [0], [OK
>  ])
>  dnl Checking that a local route for added IP was successfully installed.
>  AT_CHECK([ovs-appctl ovs/route/show | grep Cached | sort], [0], [dnl
> -Cached: 1.1.2.0/24 dev br0 SRC 1.1.2.88 local
> +Cached: 1.1.2.0/24 dev br0 SRC 1.1.2.88
> +Cached: 1.1.2.88/32 dev br0 SRC 1.1.2.88 local
>  ])
>  
>  dnl Add a dp-hash selection group.
> diff --git a/tests/tunnel.at b/tests/tunnel.at
> index e1a16138fa29..be022f3a087c 100644
> --- a/tests/tunnel.at
> +++ b/tests/tunnel.at
> @@ -529,7 +529,8 @@ AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 172.31.1.1/24], [0], [OK
>  ])
>  dnl Checking that a local route for added IP was successfully installed.
>  AT_CHECK([ovs-appctl ovs/route/show | grep Cached], [0], [dnl

sort

> -Cached: 172.31.1.0/24 dev br0 SRC 172.31.1.1 local
> +Cached: 172.31.1.1/32 dev br0 SRC 172.31.1.1 local
> +Cached: 172.31.1.0/24 dev br0 SRC 172.31.1.1
>  ])
>  
>  dnl change the flow table to bump the internal table version
> @@ -1286,7 +1287,8 @@ AT_CHECK([ovs-appctl netdev-dummy/ip6addr br0 fc00::1/64], [0], [OK
>  ])
>  dnl Checking that a local route for added IP was successfully installed.
>  AT_CHECK([ovs-appctl ovs/route/show | grep Cached], [0], [dnl

sort

> -Cached: fc00::/64 dev br0 SRC fc00::1 local
> +Cached: fc00::1/128 dev br0 SRC fc00::1 local
> +Cached: fc00::/64 dev br0 SRC fc00::1
>  ])
>  
>  AT_DATA([flows.txt], [dnl
diff mbox series

Patch

diff --git a/lib/netdev-dummy.c b/lib/netdev-dummy.c
index b72820fcc506..bad86d3c4c76 100644
--- a/lib/netdev-dummy.c
+++ b/lib/netdev-dummy.c
@@ -2221,10 +2221,13 @@  netdev_dummy_ip4addr(struct unixctl_conn *conn, int argc OVS_UNUSED,
             mask.s_addr = be32_prefix_mask(plen);
             netdev_dummy_add_in4(netdev, ip, mask);
 
-            /* Insert local route entry for the new address. */
             in6_addr_set_mapped_ipv4(&ip6, ip.s_addr);
-            ovs_router_force_insert(0, &ip6, plen + 96, true, argv[1],
-                                    &in6addr_any, &ip6);
+            /* Insert local route entry for the new address. */
+            ovs_router_force_insert(CLS_MAIN, 0, &ip6, 32 + 96, true,
+                                    argv[1], &in6addr_any, &ip6);
+            /* Insert network route entry for the new address. */
+            ovs_router_force_insert(CLS_MAIN, 0, &ip6, plen + 96, false,
+                                    argv[1], &in6addr_any, &ip6);
 
             unixctl_command_reply(conn, "OK");
         } else {
@@ -2257,7 +2260,10 @@  netdev_dummy_ip6addr(struct unixctl_conn *conn, int argc OVS_UNUSED,
             netdev_dummy_add_in6(netdev, &ip6, &mask);
 
             /* Insert local route entry for the new address. */
-            ovs_router_force_insert(0, &ip6, plen, true, argv[1],
+            ovs_router_force_insert(CLS_MAIN, 0, &ip6, 128, true, argv[1],
+                                    &in6addr_any, &ip6);
+            /* Insert network route entry for the new address. */
+            ovs_router_force_insert(CLS_MAIN, 0, &ip6, plen, false, argv[1],
                                     &in6addr_any, &ip6);
 
             unixctl_command_reply(conn, "OK");
diff --git a/lib/ovs-router.c b/lib/ovs-router.c
index 2827a6e43574..70644f7bd4f2 100644
--- a/lib/ovs-router.c
+++ b/lib/ovs-router.c
@@ -33,6 +33,7 @@ 
 #include "classifier.h"
 #include "command-line.h"
 #include "compiler.h"
+#include "cmap.h"
 #include "dpif.h"
 #include "fatal-signal.h"
 #include "openvswitch/dynamic-string.h"
@@ -50,10 +51,16 @@ 
 
 VLOG_DEFINE_THIS_MODULE(ovs_router);
 
+struct clsmap_node {
+    struct cmap_node cmap_node;
+    uint32_t table;
+    struct classifier cls;
+};
+
 static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
 
 static struct ovs_mutex mutex = OVS_MUTEX_INITIALIZER;
-static struct classifier cls;
+static struct cmap clsmap = CMAP_INITIALIZER;
 
 /* By default, use the system routing table.  For system-independent testing,
  * the unit tests disable using the system routing table. */
@@ -71,6 +78,50 @@  struct ovs_router_entry {
     uint32_t mark;
 };
 
+static void rt_entry_delete__(const struct cls_rule *, struct classifier *);
+
+static struct classifier *
+cls_find(uint32_t table)
+{
+    struct clsmap_node *node;
+
+    CMAP_FOR_EACH_WITH_HASH (node, cmap_node, hash_int(table, 0), &clsmap) {
+        if (node->table == table) {
+            return &node->cls;
+        }
+    }
+
+    return NULL;
+}
+
+static struct classifier *
+cls_create(uint32_t table) OVS_REQUIRES(mutex)
+{
+    struct clsmap_node *node;
+
+    node = xmalloc(sizeof *node);
+    classifier_init(&node->cls, NULL);
+    node->table = table;
+    cmap_insert(&clsmap, &node->cmap_node, hash_int(table, 0));
+
+    return &node->cls;
+}
+
+static void
+cls_flush(struct classifier *cls, bool flush_all)
+    OVS_REQUIRES(mutex)
+{
+    struct ovs_router_entry *rt;
+
+    classifier_defer(cls);
+    CLS_FOR_EACH (rt, cr, cls) {
+        if (flush_all || rt->priority == rt->plen || rt->local) {
+            rt_entry_delete__(&rt->cr, cls);
+        }
+    }
+    classifier_publish(cls);
+}
+
 static struct ovs_router_entry *
 ovs_router_entry_cast(const struct cls_rule *cr)
 {
@@ -110,15 +161,20 @@  ovs_router_lookup(uint32_t mark, const struct in6_addr *ip6_dst,
                   char output_netdev[],
                   struct in6_addr *src, struct in6_addr *gw)
 {
-    const struct cls_rule *cr;
     struct flow flow = {.ipv6_dst = *ip6_dst, .pkt_mark = mark};
+    struct classifier *cls_main = cls_find(CLS_MAIN);
+    const struct cls_rule *cr;
+
+    if (!cls_main) {
+        return false;
+    }
 
     if (src && ipv6_addr_is_set(src)) {
         const struct cls_rule *cr_src;
         struct flow flow_src = {.ipv6_dst = *src, .pkt_mark = mark};
 
-        cr_src = classifier_lookup(&cls, OVS_VERSION_MAX, &flow_src, NULL,
-                                   NULL);
+        cr_src = classifier_lookup(cls_main, OVS_VERSION_MAX, &flow_src,
+                                   NULL, NULL);
         if (cr_src) {
             struct ovs_router_entry *p_src = ovs_router_entry_cast(cr_src);
             if (!p_src->local) {
@@ -129,7 +185,7 @@  ovs_router_lookup(uint32_t mark, const struct in6_addr *ip6_dst,
         }
     }
 
-    cr = classifier_lookup(&cls, OVS_VERSION_MAX, &flow, NULL, NULL);
+    cr = classifier_lookup(cls_main, OVS_VERSION_MAX, &flow, NULL, NULL);
     if (cr) {
         struct ovs_router_entry *p = ovs_router_entry_cast(cr);
 
@@ -257,8 +313,8 @@  out:
 }
 
 static int
-ovs_router_insert__(uint32_t mark, uint8_t priority, bool local,
-                    const struct in6_addr *ip6_dst,
+ovs_router_insert__(uint32_t table, uint32_t mark, uint8_t priority,
+                    bool local, const struct in6_addr *ip6_dst,
                     uint8_t plen, const char output_netdev[],
                     const struct in6_addr *gw,
                     const struct in6_addr *ip6_src)
@@ -268,6 +324,7 @@  ovs_router_insert__(uint32_t mark, uint8_t priority, bool local,
                         struct in6_addr *prefsrc);
     const struct cls_rule *cr;
     struct ovs_router_entry *p;
+    struct classifier *cls;
     struct match match;
     int err;
 
@@ -308,7 +365,11 @@  ovs_router_insert__(uint32_t mark, uint8_t priority, bool local,
     cls_rule_init(&p->cr, &match, priority);
 
     ovs_mutex_lock(&mutex);
-    cr = classifier_replace(&cls, &p->cr, OVS_VERSION_MIN, NULL, 0);
+    cls = cls_find(table);
+    if (!cls) {
+        cls = cls_create(table);
+    }
+    cr = classifier_replace(cls, &p->cr, OVS_VERSION_MIN, NULL, 0);
     ovs_mutex_unlock(&mutex);
 
     if (cr) {
@@ -321,13 +382,13 @@  ovs_router_insert__(uint32_t mark, uint8_t priority, bool local,
 }
 
 void
-ovs_router_insert(uint32_t mark, const struct in6_addr *ip_dst, uint8_t plen,
-                  bool local, const char output_netdev[],
+ovs_router_insert(uint32_t table, uint32_t mark, const struct in6_addr *ip_dst,
+                  uint8_t plen, bool local, const char output_netdev[],
                   const struct in6_addr *gw, const struct in6_addr *prefsrc)
 {
     if (use_system_routing_table) {
         uint8_t priority = local ? plen + 64 : plen;
-        ovs_router_insert__(mark, priority, local, ip_dst, plen,
+        ovs_router_insert__(table, mark, priority, local, ip_dst, plen,
                             output_netdev, gw, prefsrc);
     }
 }
@@ -335,24 +396,25 @@  ovs_router_insert(uint32_t mark, const struct in6_addr *ip_dst, uint8_t plen,
 /* The same as 'ovs_router_insert', but it adds the route even if updates
  * from the system routing table are disabled.  Used for unit tests. */
 void
-ovs_router_force_insert(uint32_t mark, const struct in6_addr *ip_dst,
+ovs_router_force_insert(uint32_t table, uint32_t mark,
+                        const struct in6_addr *ip_dst,
                         uint8_t plen, bool local, const char output_netdev[],
                         const struct in6_addr *gw,
                         const struct in6_addr *prefsrc)
 {
     uint8_t priority = local ? plen + 64 : plen;
 
-    ovs_router_insert__(mark, priority, local, ip_dst, plen,
+    ovs_router_insert__(table, mark, priority, local, ip_dst, plen,
                         output_netdev, gw, prefsrc);
 }
 
 static void
-rt_entry_delete__(const struct cls_rule *cr)
+rt_entry_delete__(const struct cls_rule *cr, struct classifier *cls)
 {
     struct ovs_router_entry *p = ovs_router_entry_cast(cr);
 
     tnl_port_map_delete_ipdev(p->output_netdev);
-    classifier_remove_assert(&cls, cr);
+    classifier_remove_assert(cls, cr);
     ovsrcu_postpone(rt_entry_free, ovs_router_entry_cast(cr));
 }
 
@@ -360,20 +422,25 @@  static bool
 rt_entry_delete(uint32_t mark, uint8_t priority,
                 const struct in6_addr *ip6_dst, uint8_t plen)
 {
+    struct classifier *cls_main = cls_find(CLS_MAIN);
     const struct cls_rule *cr;
     struct cls_rule rule;
     struct match match;
     bool res = false;
 
+    if (!cls_main) {
+        return false;
+    }
+
     rt_init_match(&match, mark, ip6_dst, plen);
 
     cls_rule_init(&rule, &match, priority);
 
     /* Find the exact rule. */
-    cr = classifier_find_rule_exactly(&cls, &rule, OVS_VERSION_MAX);
+    cr = classifier_find_rule_exactly(cls_main, &rule, OVS_VERSION_MAX);
     if (cr) {
         ovs_mutex_lock(&mutex);
-        rt_entry_delete__(cr);
+        rt_entry_delete__(cr, cls_main);
         ovs_mutex_unlock(&mutex);
 
         res = true;
@@ -469,8 +536,8 @@  ovs_router_add(struct unixctl_conn *conn, int argc,
         in6_addr_set_mapped_ipv4(&src6, src);
     }
 
-    err = ovs_router_insert__(mark, plen + 32, false, &ip6, plen, argv[2],
-                              &gw6, &src6);
+    err = ovs_router_insert__(CLS_MAIN, mark, plen + 32, false, &ip6, plen,
+                              argv[2], &gw6, &src6);
     if (err) {
         unixctl_command_reply_error(conn, "Error while inserting route.");
     } else {
@@ -512,12 +579,19 @@  ovs_router_del(struct unixctl_conn *conn, int argc OVS_UNUSED,
 static void
 ovs_router_show_json(struct json **routes)
 {
-    int n_rules = classifier_count(&cls);
     struct json **json_entries = NULL;
     struct ovs_router_entry *rt;
+    struct classifier *cls_main;
     struct ds ds;
+    int n_rules;
     int i = 0;
 
+    cls_main = cls_find(CLS_MAIN);
+    if (!cls_main) {
+        goto out;
+    }
+
+    n_rules = classifier_count(cls_main);
     if (!n_rules) {
         goto out;
     }
@@ -525,7 +599,7 @@  ovs_router_show_json(struct json **routes)
     json_entries = xmalloc(n_rules * sizeof *json_entries);
     ds_init(&ds);
 
-    CLS_FOR_EACH (rt, cr, &cls) {
+    CLS_FOR_EACH (rt, cr, cls_main) {
         bool user = rt->priority != rt->plen && !rt->local;
         uint8_t plen = rt->plen;
         struct json *json, *nh;
@@ -579,9 +653,15 @@  static void
 ovs_router_show_text(struct ds *ds)
 {
     struct ovs_router_entry *rt;
+    struct classifier *cls_main;
+
+    cls_main = cls_find(CLS_MAIN);
+    if (!cls_main) {
+        return;
+    }
 
     ds_put_format(ds, "Route Table:\n");
-    CLS_FOR_EACH (rt, cr, &cls) {
+    CLS_FOR_EACH (rt, cr, cls_main) {
         uint8_t plen;
         if (rt->priority == rt->plen || rt->local) {
             ds_put_format(ds, "Cached: ");
@@ -668,27 +748,46 @@  ovs_router_lookup_cmd(struct unixctl_conn *conn, int argc,
     }
 }
 
-void
-ovs_router_flush(void)
+static void
+clsmap_node_destroy_cb(struct clsmap_node *node)
 {
-    struct ovs_router_entry *rt;
+    classifier_destroy(&node->cls);
+    ovsrcu_postpone(free, node);
+}
 
-    ovs_mutex_lock(&mutex);
-    classifier_defer(&cls);
-    CLS_FOR_EACH(rt, cr, &cls) {
-        if (rt->priority == rt->plen || rt->local) {
-            rt_entry_delete__(&rt->cr);
+static void
+ovs_router_flush_protected(bool flush_all)
+    OVS_REQUIRES(mutex)
+{
+    struct clsmap_node *node;
+
+    CMAP_FOR_EACH (node, cmap_node, &clsmap) {
+        cls_flush(&node->cls, flush_all);
+        if (!node->cls.n_rules) {
+            cmap_remove(&clsmap, &node->cmap_node, hash_int(node->table, 0));
+            ovsrcu_postpone(clsmap_node_destroy_cb, node);
         }
     }
-    classifier_publish(&cls);
-    ovs_mutex_unlock(&mutex);
     seq_change(tnl_conf_seq);
 }
 
+void
+ovs_router_flush(bool flush_all)
+{
+    ovs_mutex_lock(&mutex);
+    ovs_router_flush_protected(flush_all);
+    ovs_mutex_unlock(&mutex);
+}
+
 static void
 ovs_router_flush_handler(void *aux OVS_UNUSED)
 {
-    ovs_router_flush();
+    ovs_mutex_lock(&mutex);
+    ovs_router_flush_protected(true);
+    ovs_assert(cmap_is_empty(&clsmap));
+    cmap_destroy(&clsmap);
+    cmap_init(&clsmap);
+    ovs_mutex_unlock(&mutex);
 }
 
 void
@@ -698,7 +797,6 @@  ovs_router_init(void)
 
     if (ovsthread_once_start(&once)) {
         fatal_signal_add_hook(ovs_router_flush_handler, NULL, NULL, true);
-        classifier_init(&cls, NULL);
         unixctl_command_register("ovs/route/add",
                                  "ip/plen dev [gw] "
                                  "[pkt_mark=mark] [src=src_ip]",
diff --git a/lib/ovs-router.h b/lib/ovs-router.h
index b61712707b1e..f4e6487d3dc6 100644
--- a/lib/ovs-router.h
+++ b/lib/ovs-router.h
@@ -26,20 +26,27 @@ 
 extern "C" {
 #endif
 
+enum {
+    CLS_MAIN = 254,
+    CLS_ALL = UINT32_MAX,
+};
+
 bool ovs_router_lookup(uint32_t mark, const struct in6_addr *ip_dst,
                        char output_netdev[],
                        struct in6_addr *src, struct in6_addr *gw);
 void ovs_router_init(void);
-void ovs_router_insert(uint32_t mark, const struct in6_addr *ip_dst,
+void ovs_router_insert(uint32_t table, uint32_t mark,
+                       const struct in6_addr *ip_dst,
                        uint8_t plen, bool local,
                        const char output_netdev[], const struct in6_addr *gw,
                        const struct in6_addr *prefsrc);
-void ovs_router_force_insert(uint32_t mark, const struct in6_addr *ip_dst,
+void ovs_router_force_insert(uint32_t table, uint32_t mark,
+                             const struct in6_addr *ip_dst,
                              uint8_t plen, bool local,
                              const char output_netdev[],
                              const struct in6_addr *gw,
                              const struct in6_addr *prefsrc);
-void ovs_router_flush(void);
+void ovs_router_flush(bool flush_all);
 
 void ovs_router_disable_system_routing_table(void);
 
diff --git a/lib/route-table.c b/lib/route-table.c
index ca87ff7dbba4..0e77a9c9325e 100644
--- a/lib/route-table.c
+++ b/lib/route-table.c
@@ -57,6 +57,8 @@  VLOG_DEFINE_THIS_MODULE(route_table);
 
 COVERAGE_DEFINE(route_table_dump);
 
+BUILD_ASSERT_DECL((enum rt_class_t) CLS_MAIN == RT_TABLE_MAIN);
+
 static struct ovs_mutex route_table_mutex = OVS_MUTEX_INITIALIZER;
 static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 20);
 
@@ -565,7 +567,7 @@  route_table_handle_msg(const struct route_table_msg *change,
         rdnh = CONTAINER_OF(ovs_list_front(&change->rd.nexthops),
                             const struct route_data_nexthop, nexthop_node);
 
-        ovs_router_insert(rd->rta_mark, &rd->rta_dst,
+        ovs_router_insert(CLS_MAIN, rd->rta_mark, &rd->rta_dst,
                           IN6_IS_ADDR_V4MAPPED(&rd->rta_dst)
                           ? rd->rtm_dst_len + 96 : rd->rtm_dst_len,
                           rd->rtn_local, rdnh->ifname, &rdnh->addr,
@@ -576,7 +578,7 @@  route_table_handle_msg(const struct route_table_msg *change,
 static void
 route_map_clear(void)
 {
-    ovs_router_flush();
+    ovs_router_flush(false);
 }
 
 bool
diff --git a/tests/nsh.at b/tests/nsh.at
index 022540dd68a4..3a2f2ed06774 100644
--- a/tests/nsh.at
+++ b/tests/nsh.at
@@ -557,9 +557,12 @@  AT_CHECK([
 AT_CHECK([
     ovs-appctl ovs/route/show | grep Cached: | sort
 ], [0], [dnl
-Cached: 10.0.0.0/24 dev br-p1 SRC 10.0.0.1 local
-Cached: 20.0.0.0/24 dev br-p2 SRC 20.0.0.2 local
-Cached: 30.0.0.0/24 dev br-p3 SRC 30.0.0.3 local
+Cached: 10.0.0.0/24 dev br-p1 SRC 10.0.0.1
+Cached: 10.0.0.1/32 dev br-p1 SRC 10.0.0.1 local
+Cached: 20.0.0.0/24 dev br-p2 SRC 20.0.0.2
+Cached: 20.0.0.2/32 dev br-p2 SRC 20.0.0.2 local
+Cached: 30.0.0.0/24 dev br-p3 SRC 30.0.0.3
+Cached: 30.0.0.3/32 dev br-p3 SRC 30.0.0.3 local
 ])
 
 AT_CHECK([
diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at
index 7ebbee56d9e6..319640aee5cb 100644
--- a/tests/ofproto-dpif.at
+++ b/tests/ofproto-dpif.at
@@ -8510,7 +8510,8 @@  AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 1.1.2.88/24], [0], [OK
 AT_CHECK([ovs-ofctl add-flow br0 action=normal])
 dnl Checking that a local route for added IP was successfully installed.
 AT_CHECK([ovs-appctl ovs/route/show | grep Cached], [0], [dnl
-Cached: 1.1.2.0/24 dev br0 SRC 1.1.2.88 local
+Cached: 1.1.2.88/32 dev br0 SRC 1.1.2.88 local
+Cached: 1.1.2.0/24 dev br0 SRC 1.1.2.88
 ])
 
 dnl Prime ARP Cache for 1.1.2.92
@@ -8527,8 +8528,10 @@  AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 192.168.1.1/16], [0], [OK
 ])
 dnl Checking that a local route for added IP was successfully installed.
 AT_CHECK([ovs-appctl ovs/route/show | grep Cached | sort], [0], [dnl
-Cached: 1.1.2.0/24 dev br0 SRC 1.1.2.88 local
-Cached: 192.168.0.0/16 dev br0 SRC 192.168.1.1 local
+Cached: 1.1.2.0/24 dev br0 SRC 1.1.2.88
+Cached: 1.1.2.88/32 dev br0 SRC 1.1.2.88 local
+Cached: 192.168.0.0/16 dev br0 SRC 192.168.1.1
+Cached: 192.168.1.1/32 dev br0 SRC 192.168.1.1 local
 ])
 
 dnl add rule for int-br to force packet onto tunnel. There is no ifindex
diff --git a/tests/ovs-router.at b/tests/ovs-router.at
index 641b780a582a..d5f56da786d9 100644
--- a/tests/ovs-router.at
+++ b/tests/ovs-router.at
@@ -31,14 +31,35 @@  User: 2.2.2.3/32 MARK 1 dev br0 SRC 2.2.2.2
 AT_CHECK([ovs-appctl --format=json --pretty ovs/route/show], [0], [dnl
 [[
   {
-    "dst": "2.2.2.0",
+    "dst": "2.2.2.2",
     "local": true,
+    "nexthops": [
+      {
+        "dev": "br0"}],
+    "prefix": 32,
+    "prefsrc": "2.2.2.2",
+    "priority": 192,
+    "user": false},
+  {
+    "dst": "2.2.2.3",
+    "local": false,
+    "mark": 1,
+    "nexthops": [
+      {
+        "dev": "br0"}],
+    "prefix": 32,
+    "prefsrc": "2.2.2.2",
+    "priority": 160,
+    "user": true},
+  {
+    "dst": "2.2.2.0",
+    "local": false,
     "nexthops": [
       {
         "dev": "br0"}],
     "prefix": 24,
     "prefsrc": "2.2.2.2",
-    "priority": 184,
+    "priority": 120,
     "user": false},
   {
     "dst": "1.1.1.0",
@@ -62,17 +83,6 @@  AT_CHECK([ovs-appctl --format=json --pretty ovs/route/show], [0], [dnl
     "prefix": 24,
     "prefsrc": "2.2.2.2",
     "priority": 152,
-    "user": true},
-  {
-    "dst": "2.2.2.3",
-    "local": false,
-    "mark": 1,
-    "nexthops": [
-      {
-        "dev": "br0"}],
-    "prefix": 32,
-    "prefsrc": "2.2.2.2",
-    "priority": 160,
     "user": true}]]
 ])
 OVS_VSWITCHD_STOP
diff --git a/tests/packet-type-aware.at b/tests/packet-type-aware.at
index cebd8e6eeb46..300b17209d83 100644
--- a/tests/packet-type-aware.at
+++ b/tests/packet-type-aware.at
@@ -160,9 +160,12 @@  AT_CHECK([
 AT_CHECK([
     ovs-appctl ovs/route/show | grep Cached: | sort
 ], [0], [dnl
-Cached: 10.0.0.0/24 dev br-p1 SRC 10.0.0.1 local
-Cached: 20.0.0.0/24 dev br-p2 SRC 20.0.0.2 local
-Cached: 30.0.0.0/24 dev br-p3 SRC 30.0.0.3 local
+Cached: 10.0.0.0/24 dev br-p1 SRC 10.0.0.1
+Cached: 10.0.0.1/32 dev br-p1 SRC 10.0.0.1 local
+Cached: 20.0.0.0/24 dev br-p2 SRC 20.0.0.2
+Cached: 20.0.0.2/32 dev br-p2 SRC 20.0.0.2 local
+Cached: 30.0.0.0/24 dev br-p3 SRC 30.0.0.3
+Cached: 30.0.0.3/32 dev br-p3 SRC 30.0.0.3 local
 ])
 
 AT_CHECK([
@@ -684,7 +687,8 @@  AT_CHECK([
 AT_CHECK([
     ovs-appctl ovs/route/show | grep Cached:
 ], [0], [dnl
-Cached: 10.0.0.0/24 dev br2 SRC 10.0.0.1 local
+Cached: 10.0.0.1/32 dev br2 SRC 10.0.0.1 local
+Cached: 10.0.0.0/24 dev br2 SRC 10.0.0.1
 ])
 
 
@@ -960,7 +964,8 @@  ovs-appctl time/warp 1000
 AT_CHECK([
     ovs-appctl ovs/route/show | grep Cached:
 ],[0], [dnl
-Cached: 20.0.0.0/24 dev br0 SRC 20.0.0.1 local
+Cached: 20.0.0.1/32 dev br0 SRC 20.0.0.1 local
+Cached: 20.0.0.0/24 dev br0 SRC 20.0.0.1
 ])
 
 AT_CHECK([
diff --git a/tests/tunnel-push-pop-ipv6.at b/tests/tunnel-push-pop-ipv6.at
index 39fbd2d35f55..2c8c9d9fb881 100644
--- a/tests/tunnel-push-pop-ipv6.at
+++ b/tests/tunnel-push-pop-ipv6.at
@@ -24,7 +24,8 @@  AT_CHECK([ovs-appctl netdev-dummy/ip6addr br0 2001:cafe::88/24], [0], [OK
 ])
 dnl Checking that a local routes for added IPs were successfully installed.
 AT_CHECK([ovs-appctl ovs/route/show | grep Cached], [0], [dnl
-Cached: 2001:ca00::/24 dev br0 SRC 2001:cafe::88 local
+Cached: 2001:cafe::88/128 dev br0 SRC 2001:cafe::88 local
+Cached: 2001:ca00::/24 dev br0 SRC 2001:cafe::88
 ])
 AT_CHECK([ovs-appctl tnl/neigh/set br0 2001:cafe::91 aa:55:aa:55:00:01], [0], [OK
 ])
@@ -113,8 +114,10 @@  AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 1.1.2.88/24], [0], [OK
 ])
 dnl Checking that a local routes for added IPs were successfully installed.
 AT_CHECK([ovs-appctl ovs/route/show | grep Cached | sort], [0], [dnl
-Cached: 1.1.2.0/24 dev br0 SRC 1.1.2.88 local
-Cached: 2001:ca00::/24 dev br0 SRC 2001:cafe::88 local
+Cached: 1.1.2.0/24 dev br0 SRC 1.1.2.88
+Cached: 1.1.2.88/32 dev br0 SRC 1.1.2.88 local
+Cached: 2001:ca00::/24 dev br0 SRC 2001:cafe::88
+Cached: 2001:cafe::88/128 dev br0 SRC 2001:cafe::88 local
 ])
 
 AT_CHECK([ovs-ofctl add-flow br0 action=normal])
@@ -189,8 +192,10 @@  AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 1.1.2.88/24], [0], [OK
 ])
 dnl Checking that a local routes for added IPs were successfully installed.
 AT_CHECK([ovs-appctl ovs/route/show | grep Cached | sort], [0], [dnl
-Cached: 1.1.2.0/24 dev br0 SRC 1.1.2.88 local
-Cached: 2001:ca00::/24 dev br0 SRC 2001:cafe::88 local
+Cached: 1.1.2.0/24 dev br0 SRC 1.1.2.88
+Cached: 1.1.2.88/32 dev br0 SRC 1.1.2.88 local
+Cached: 2001:ca00::/24 dev br0 SRC 2001:cafe::88
+Cached: 2001:cafe::88/128 dev br0 SRC 2001:cafe::88 local
 ])
 
 AT_CHECK([ovs-ofctl add-flow br0 action=normal])
@@ -328,8 +333,10 @@  AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 1.1.2.88/24], [0], [OK
 ])
 dnl Checking that a local routes for added IPs were successfully installed.
 AT_CHECK([ovs-appctl ovs/route/show | grep Cached | sort], [0], [dnl
-Cached: 1.1.2.0/24 dev br0 SRC 1.1.2.88 local
-Cached: 2001:ca00::/24 dev br0 SRC 2001:cafe::88 local
+Cached: 1.1.2.0/24 dev br0 SRC 1.1.2.88
+Cached: 1.1.2.88/32 dev br0 SRC 1.1.2.88 local
+Cached: 2001:ca00::/24 dev br0 SRC 2001:cafe::88
+Cached: 2001:cafe::88/128 dev br0 SRC 2001:cafe::88 local
 ])
 
 AT_CHECK([ovs-ofctl add-flow br0 action=normal])
@@ -701,8 +708,10 @@  AT_CHECK([ovs-appctl netdev-dummy/ip6addr br0 2001:beef::88/64], [0], [OK
 ])
 dnl Checking that a local route for added IP was successfully installed.
 AT_CHECK([ovs-appctl ovs/route/show | grep Cached | sort], [0], [dnl
-Cached: 2001:beef::/64 dev br0 SRC 2001:beef::88 local
-Cached: 2001:cafe::/64 dev br0 SRC 2001:cafe::88 local
+Cached: 2001:beef::/64 dev br0 SRC 2001:beef::88
+Cached: 2001:beef::88/128 dev br0 SRC 2001:beef::88 local
+Cached: 2001:cafe::/64 dev br0 SRC 2001:cafe::88
+Cached: 2001:cafe::88/128 dev br0 SRC 2001:cafe::88 local
 ])
 AT_CHECK([ovs-ofctl add-flow br0 action=normal])
 AT_CHECK([ovs-ofctl add-flow int-br action=normal])
@@ -784,7 +793,8 @@  AT_CHECK([ovs-appctl netdev-dummy/ip6addr br0 2001:cafe::88/64], [0], [OK
 ])
 dnl Checking that a local route for added IP was successfully installed.
 AT_CHECK([ovs-appctl ovs/route/show | grep Cached | sort], [0], [dnl
-Cached: 2001:cafe::/64 dev br0 SRC 2001:cafe::88 local
+Cached: 2001:cafe::/64 dev br0 SRC 2001:cafe::88
+Cached: 2001:cafe::88/128 dev br0 SRC 2001:cafe::88 local
 ])
 
 dnl Add a dp-hash selection group.
diff --git a/tests/tunnel-push-pop.at b/tests/tunnel-push-pop.at
index 795817d62246..4108cc4a7dac 100644
--- a/tests/tunnel-push-pop.at
+++ b/tests/tunnel-push-pop.at
@@ -37,8 +37,10 @@  AT_CHECK([ovs-appctl netdev-dummy/ip6addr br0 2001:cafe::88/24], [0], [OK
 ])
 dnl Checking that a local routes for added IPs were successfully installed.
 AT_CHECK([ovs-appctl ovs/route/show | grep Cached | sort], [0], [dnl
-Cached: 1.1.2.0/24 dev br0 SRC 1.1.2.88 local
-Cached: 2001:ca00::/24 dev br0 SRC 2001:cafe::88 local
+Cached: 1.1.2.0/24 dev br0 SRC 1.1.2.88
+Cached: 1.1.2.88/32 dev br0 SRC 1.1.2.88 local
+Cached: 2001:ca00::/24 dev br0 SRC 2001:cafe::88
+Cached: 2001:cafe::88/128 dev br0 SRC 2001:cafe::88 local
 ])
 
 AT_CHECK([ovs-ofctl add-flow br0 action=normal])
@@ -241,8 +243,10 @@  AT_CHECK([ovs-appctl ovs/route/add 1.1.2.92/24 br0 pkt_mark=1234], [0], [OK
 dnl Checking that local routes for added IPs and the static route with a mark
 dnl were successfully installed.
 AT_CHECK([ovs-appctl ovs/route/show | grep br0 | sort], [0], [dnl
-Cached: 1.1.2.0/24 dev br0 SRC 1.1.2.88 local
-Cached: 2001:ca00::/24 dev br0 SRC 2001:cafe::88 local
+Cached: 1.1.2.0/24 dev br0 SRC 1.1.2.88
+Cached: 1.1.2.88/32 dev br0 SRC 1.1.2.88 local
+Cached: 2001:ca00::/24 dev br0 SRC 2001:cafe::88
+Cached: 2001:cafe::88/128 dev br0 SRC 2001:cafe::88 local
 User: 1.1.2.0/24 MARK 1234 dev br0 SRC 1.1.2.88
 ])
 
@@ -778,7 +782,8 @@  AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 1.1.2.88/24], [0], [OK
 ])
 dnl Checking that a local route for added IP was successfully installed.
 AT_CHECK([ovs-appctl ovs/route/show | grep Cached], [0], [dnl
-Cached: 1.1.2.0/24 dev br0 SRC 1.1.2.88 local
+Cached: 1.1.2.88/32 dev br0 SRC 1.1.2.88 local
+Cached: 1.1.2.0/24 dev br0 SRC 1.1.2.88
 ])
 
 AT_CHECK([ovs-ofctl add-flow br0 action=normal])
@@ -821,7 +826,8 @@  AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 1.1.2.88/24], [0], [OK
 ])
 dnl Checking that a local route for added IP was successfully installed.
 AT_CHECK([ovs-appctl ovs/route/show | grep Cached], [0], [dnl
-Cached: 1.1.2.0/24 dev br0 SRC 1.1.2.88 local
+Cached: 1.1.2.88/32 dev br0 SRC 1.1.2.88 local
+Cached: 1.1.2.0/24 dev br0 SRC 1.1.2.88
 ])
 AT_CHECK([ovs-ofctl add-flow br0 action=normal])
 AT_CHECK([ovs-appctl revalidator/wait])
@@ -896,8 +902,10 @@  AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 2.2.2.88/24], [0], [OK
 ])
 dnl Checking that a local route for added IP was successfully installed.
 AT_CHECK([ovs-appctl ovs/route/show | grep Cached | sort], [0], [dnl
-Cached: 1.1.2.0/24 dev br0 SRC 1.1.2.88 local
-Cached: 2.2.2.0/24 dev br0 SRC 2.2.2.88 local
+Cached: 1.1.2.0/24 dev br0 SRC 1.1.2.88
+Cached: 1.1.2.88/32 dev br0 SRC 1.1.2.88 local
+Cached: 2.2.2.0/24 dev br0 SRC 2.2.2.88
+Cached: 2.2.2.88/32 dev br0 SRC 2.2.2.88 local
 ])
 AT_CHECK([ovs-ofctl add-flow br0 action=normal])
 AT_CHECK([ovs-ofctl add-flow int-br action=normal])
@@ -979,7 +987,8 @@  AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 1.1.2.88/24], [0], [OK
 ])
 dnl Checking that a local route for added IP was successfully installed.
 AT_CHECK([ovs-appctl ovs/route/show | grep Cached], [0], [dnl
-Cached: 1.1.2.0/24 dev br0 SRC 1.1.2.88 local
+Cached: 1.1.2.88/32 dev br0 SRC 1.1.2.88 local
+Cached: 1.1.2.0/24 dev br0 SRC 1.1.2.88
 ])
 
 AT_CHECK([ovs-ofctl add-flow br0 'arp,priority=1,action=normal'])
@@ -1031,7 +1040,8 @@  AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 1.1.2.88/24], [0], [OK
 ])
 dnl Checking that a local route for added IP was successfully installed.
 AT_CHECK([ovs-appctl ovs/route/show | grep Cached], [0], [dnl
-Cached: 1.1.2.0/24 dev br0 SRC 1.1.2.88 local
+Cached: 1.1.2.88/32 dev br0 SRC 1.1.2.88 local
+Cached: 1.1.2.0/24 dev br0 SRC 1.1.2.88
 ])
 AT_CHECK([ovs-ofctl add-flow br0 action=normal])
 AT_CHECK([ovs-appctl revalidator/wait])
@@ -1101,7 +1111,8 @@  AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 10.0.0.2/24], [0], [OK
 ])
 dnl Checking that a local route for added IP was successfully installed.
 AT_CHECK([ovs-appctl ovs/route/show | grep Cached], [0], [dnl
-Cached: 10.0.0.0/24 dev br0 SRC 10.0.0.2 local
+Cached: 10.0.0.2/32 dev br0 SRC 10.0.0.2 local
+Cached: 10.0.0.0/24 dev br0 SRC 10.0.0.2
 ])
 
 dnl Send an ARP reply to port b8 on br0, so that packets will be forwarded
@@ -1149,7 +1160,8 @@  AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 10.0.0.2/24], [0], [OK
 ])
 dnl Checking that a local route for added IP was successfully installed.
 AT_CHECK([ovs-appctl ovs/route/show | grep Cached], [0], [dnl
-Cached: 10.0.0.0/24 dev br0 SRC 10.0.0.2 local
+Cached: 10.0.0.2/32 dev br0 SRC 10.0.0.2 local
+Cached: 10.0.0.0/24 dev br0 SRC 10.0.0.2
 ])
 
 dnl Send an ARP reply to port b8 on br0, so that packets will be forwarded
@@ -1223,7 +1235,8 @@  AT_CHECK([ovs-appctl netdev-dummy/ip4addr vtep0 1.1.2.88/24], [0], [OK
 ])
 dnl Checking that a local route for added IP was successfully installed.
 AT_CHECK([ovs-appctl ovs/route/show | grep Cached], [0], [dnl
-Cached: 1.1.2.0/24 dev vtep0 SRC 1.1.2.88 local
+Cached: 1.1.2.88/32 dev vtep0 SRC 1.1.2.88 local
+Cached: 1.1.2.0/24 dev vtep0 SRC 1.1.2.88
 ])
 
 AT_CHECK([ovs-ofctl add-flow br0 action=normal])
@@ -1288,7 +1301,8 @@  AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 1.1.2.88/24], [0], [OK
 ])
 dnl Checking that a local route for added IP was successfully installed.
 AT_CHECK([ovs-appctl ovs/route/show | grep Cached | sort], [0], [dnl
-Cached: 1.1.2.0/24 dev br0 SRC 1.1.2.88 local
+Cached: 1.1.2.0/24 dev br0 SRC 1.1.2.88
+Cached: 1.1.2.88/32 dev br0 SRC 1.1.2.88 local
 ])
 
 dnl Add a dp-hash selection group.
diff --git a/tests/tunnel.at b/tests/tunnel.at
index e1a16138fa29..be022f3a087c 100644
--- a/tests/tunnel.at
+++ b/tests/tunnel.at
@@ -529,7 +529,8 @@  AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 172.31.1.1/24], [0], [OK
 ])
 dnl Checking that a local route for added IP was successfully installed.
 AT_CHECK([ovs-appctl ovs/route/show | grep Cached], [0], [dnl
-Cached: 172.31.1.0/24 dev br0 SRC 172.31.1.1 local
+Cached: 172.31.1.1/32 dev br0 SRC 172.31.1.1 local
+Cached: 172.31.1.0/24 dev br0 SRC 172.31.1.1
 ])
 
 dnl change the flow table to bump the internal table version
@@ -1286,7 +1287,8 @@  AT_CHECK([ovs-appctl netdev-dummy/ip6addr br0 fc00::1/64], [0], [OK
 ])
 dnl Checking that a local route for added IP was successfully installed.
 AT_CHECK([ovs-appctl ovs/route/show | grep Cached], [0], [dnl
-Cached: fc00::/64 dev br0 SRC fc00::1 local
+Cached: fc00::1/128 dev br0 SRC fc00::1 local
+Cached: fc00::/64 dev br0 SRC fc00::1
 ])
 
 AT_DATA([flows.txt], [dnl