Add two new commands `ovs-appctl ovs/router/rule/add` and
`ovs-appctl ovs/router/rule/del` for adding and deleting user routing
rules in OVS:

  ovs/route/rule/add      [not] from=<all|IP/PLEN> [prio=NUM] 
table=<local|main|default|ID>
  ovs/route/rule/del      [not] from=<all|IP/PLEN> [prio=NUM] 
table=<local|main|default|ID>

Examples for adding user rules:

  ovs-appctl ovs/route/rule/add from=all table=10
  ovs-appctl ovs/route/rule/add from=all prio=10 table=10
  ovs-appctl ovs/route/rule/add not from=10.0.0.0/24 table=10

Examples for deleting user rules:

  ovs-appctl ovs/route/rule/del from=all prio=10 table=10
  ovs-appctl ovs/route/rule/del from=all table=10
  ovs-appctl ovs/route/rule/del not from=10.0.0.0/24 table=10

When adding a new rule, if priority is not specified, the lowest unused
priority will be selected automatically. Multiple rules with the same
priority may exist. User added rules and kernel cached rules co-exist
together.

Only user added rules can be deleted with 'ovs/route/rule/del' command.
For kernel cached rules they must be deleted using `ip rule del` system
command. If priority is not specified when deleting a rule, the first
matching rule will be selected for deletion.

Signed-off-by: Dima Chumak <dchu...@nvidia.com>
---
 Documentation/howto/userspace-tunneling.rst |   5 +
 NEWS                                        |   2 +
 lib/ovs-router.c                            | 212 +++++++++++++++++++-
 lib/ovs-router.h                            |   9 +-
 lib/route-table.c                           |   6 +-
 ofproto/ofproto-tnl-unixctl.man             |  14 ++
 tests/ovs-router.at                         |  72 ++++++-
 tests/tunnel-push-pop.at                    | 119 +++++++++++
 8 files changed, 422 insertions(+), 17 deletions(-)

diff --git a/Documentation/howto/userspace-tunneling.rst 
b/Documentation/howto/userspace-tunneling.rst
index 5bebbce69e4a..62f87c426b16 100644
--- a/Documentation/howto/userspace-tunneling.rst
+++ b/Documentation/howto/userspace-tunneling.rst
@@ -207,6 +207,11 @@ To see all routes configured::
 
     $ ovs-appctl ovs/route/show [table=ID|all]
 
+To add/delete router rule::
+
+    $ ovs-appctl ovs/route/rule/add [not] from=<all|IP/PLEN> [prio=NUM] 
table=<local|main|default|ID>
+    $ ovs-appctl ovs/route/rule/del [not] from=<all|IP/PLEN> [prio=NUM] 
table=<local|main|default|ID>
+
 To see all router rules configured::
 
     $ ovs-appctl ovs/router/rule/show
diff --git a/NEWS b/NEWS
index a58baa0a8365..440c4cfc5d49 100644
--- a/NEWS
+++ b/NEWS
@@ -31,6 +31,8 @@ v3.6.0 - xx xxx xxxx
        add/delete a route from a specific OVS table.
      * 'ovs/route/lookup': added new option, src=IP, to perform lookup with
        a specific source IP address.
+     * Added new sub-commands, ovs/router/rule/{add,del}, to add and remove
+       user routing rules in OVS.
    - ovs-vsctl:
      * Now exits with error code 160 (ERROR_BAD_ARGUMENTS) on Windows and
        65 (EX_DATAERR) on other platforms if it fails while waiting for
diff --git a/lib/ovs-router.c b/lib/ovs-router.c
index a3df5c6fc2cc..11d1e7b2d4e1 100644
--- a/lib/ovs-router.c
+++ b/lib/ovs-router.c
@@ -60,6 +60,7 @@ struct clsmap_node {
 struct router_rule {
     uint32_t prio;
     bool invert;
+    bool user;
     uint8_t src_len;
     struct in6_addr from_addr;
     uint32_t lookup_table;
@@ -893,6 +894,7 @@ ovs_router_rules_show_json(struct json *rule_entries)
         struct json *entry = json_object_create();
 
         json_object_put(entry, "priority", json_integer_create(rule->prio));
+        json_object_put(entry, "user", json_integer_create(rule->user));
         json_object_put(entry, "invert", json_boolean_create(rule->invert));
         json_object_put(entry, "src_len", json_integer_create(rule->src_len));
         json_object_put(entry, "lookup",
@@ -932,6 +934,11 @@ ovs_router_rules_show_text(struct ds *ds)
     struct router_rule *rule;
 
     PVECTOR_FOR_EACH (rule, &rules) {
+        if (rule->user) {
+            ds_put_format(ds, "User: ");
+        } else {
+            ds_put_format(ds, "Cached: ");
+        }
         ds_put_format(ds, "%u: ", rule->prio);
         if (rule->invert) {
             ds_put_format(ds, "not ");
@@ -974,6 +981,156 @@ ovs_router_rules_show(struct unixctl_conn *conn, int argc 
OVS_UNUSED,
     }
 }
 
+static void
+ovs_router_rule_add_cmd(struct unixctl_conn *conn, int argc OVS_UNUSED,
+                        const char *argv[] OVS_UNUSED, void *aux OVS_UNUSED)
+{
+    unsigned int src_len = 0;
+    struct in6_addr from;
+    bool invert = false;
+    uint32_t prio = 0;
+    uint32_t table;
+    ovs_be32 ip;
+    int i = 1;
+
+    if (ovs_scan(argv[i], "not")) {
+        invert = true;
+        i++;
+    }
+
+    if (ovs_scan(argv[i], "from=all")) {
+        from = in6addr_any;
+    } else if (ovs_scan(argv[i], "from=")) {
+        const char *arg = &argv[i][strlen("from=")];
+
+        if (scan_ipv4_route(arg, &ip, &src_len)) {
+            in6_addr_set_mapped_ipv4(&from, ip);
+            src_len += 96;
+        } else if (!scan_ipv6_route(arg, &from, &src_len)) {
+            unixctl_command_reply_error(conn, "Invalid from=ip/plen");
+            return;
+        }
+    } else {
+        unixctl_command_reply_error(conn, "Invalid 'from' parameter");
+        return;
+    }
+    if (argc <= ++i) {
+        unixctl_command_reply_error(conn, "Not enough arguments");
+        return;
+    }
+
+    if (ovs_scan(argv[i], "prio=%"SCNi32, &prio)) {
+        if (argc <= ++i) {
+            unixctl_command_reply_error(conn, "Not enough arguments");
+            return;
+        }
+    }
+
+    if (ovs_scan(argv[i], "table=local")) {
+        table = CLS_LOCAL;
+    } else if (ovs_scan(argv[i], "table=main")) {
+        table = CLS_MAIN;
+    } else if (ovs_scan(argv[i], "table=default")) {
+        table = CLS_DEFAULT;
+    } else if (!ovs_scan(argv[i], "table=%"SCNi32, &table)) {
+        unixctl_command_reply_error(conn, "Invalid 'table' format");
+        return;
+    }
+
+    if (!prio) {
+        struct router_rule *rule;
+        uint32_t prev_prio = 0;
+
+        PVECTOR_FOR_EACH (rule, &rules) {
+            if ((!prio && rule->prio) ||
+                (rule->prio - prev_prio > 1)) {
+                prio = rule->prio - 1;
+            }
+            prev_prio = rule->prio;
+        }
+    }
+
+    ovs_mutex_lock(&mutex);
+    ovs_router_rule_add(prio, invert, true, src_len, &from, table);
+    ovs_mutex_unlock(&mutex);
+    unixctl_command_reply(conn, "OK");
+}
+
+static void
+ovs_router_rule_del_cmd(struct unixctl_conn *conn, int argc OVS_UNUSED,
+                        const char *argv[] OVS_UNUSED, void *aux OVS_UNUSED)
+{
+    unsigned int src_len = 0;
+    struct in6_addr from;
+    bool invert = false;
+    uint32_t prio = 0;
+    uint32_t table;
+    ovs_be32 ip;
+    int i = 1;
+    int err;
+
+    if (ovs_scan(argv[i], "not")) {
+        invert = true;
+        i++;
+    }
+
+    if (ovs_scan(argv[i], "from=all")) {
+        from = in6addr_any;
+    } else if (ovs_scan(argv[i], "from=")) {
+        const char *arg = &argv[i][strlen("from=")];
+
+        if (scan_ipv4_route(arg, &ip, &src_len)) {
+            in6_addr_set_mapped_ipv4(&from, ip);
+            src_len += 96;
+        } else if (!scan_ipv6_route(arg, &from, &src_len)) {
+            unixctl_command_reply_error(conn, "Invalid from=ip/plen");
+            return;
+        }
+    } else {
+        unixctl_command_reply_error(conn, "Invalid 'from' parameter");
+        return;
+    }
+    if (argc <= ++i) {
+        unixctl_command_reply_error(conn, "Not enough arguments");
+        return;
+    }
+
+    if (ovs_scan(argv[i], "prio=%"SCNi32, &prio)) {
+        if (argc <= ++i) {
+            unixctl_command_reply_error(conn, "Not enough arguments");
+            return;
+        }
+    }
+
+    if (ovs_scan(argv[i], "table=local")) {
+        table = CLS_LOCAL;
+    } else if (ovs_scan(argv[i], "table=main")) {
+        table = CLS_MAIN;
+    } else if (ovs_scan(argv[i], "table=default")) {
+        table = CLS_DEFAULT;
+    } else if (!ovs_scan(argv[i], "table=%"SCNi32, &table)) {
+        unixctl_command_reply_error(conn, "Invalid 'table' format");
+        return;
+    }
+
+    ovs_mutex_lock(&mutex);
+    err = ovs_router_rule_del(prio, invert, src_len, &from, table);
+    ovs_mutex_unlock(&mutex);
+
+    if (err) {
+        struct ds ds = DS_EMPTY_INITIALIZER;
+
+        ds_put_format(&ds, "Failed to delete router rule: %d", err);
+        if (err == -ENOENT) {
+            ds_put_format(&ds, " (No such entry)");
+        }
+        unixctl_command_reply_error(conn, ds_cstr_ro(&ds));
+        ds_destroy(&ds);
+    } else {
+        unixctl_command_reply(conn, "OK");
+    }
+}
+
 static void
 ovs_router_lookup_cmd(struct unixctl_conn *conn, int argc,
                       const char *argv[], void *aux OVS_UNUSED)
@@ -1063,9 +1220,9 @@ static void
 init_standard_rules(void)
 {
     /* Add default rules using same priorities as Linux kernel does. */
-    ovs_router_rule_add(0, false, 0, &in6addr_any, CLS_LOCAL);
-    ovs_router_rule_add(0x7FFE, false, 0, &in6addr_any, CLS_MAIN);
-    ovs_router_rule_add(0x7FFF, false, 0, &in6addr_any, CLS_DEFAULT);
+    ovs_router_rule_add(0, false, false, 0, &in6addr_any, CLS_LOCAL);
+    ovs_router_rule_add(0x7FFE, false, false, 0, &in6addr_any, CLS_MAIN);
+    ovs_router_rule_add(0x7FFF, false, false, 0, &in6addr_any, CLS_DEFAULT);
 }
 
 static void
@@ -1075,22 +1232,26 @@ rule_destroy_cb(struct router_rule *rule)
 }
 
 void
-ovs_router_rules_flush(void)
+ovs_router_rules_flush(bool flush_all)
 {
     struct router_rule *rule;
 
+    ovs_mutex_lock(&mutex);
     PVECTOR_FOR_EACH (rule, &rules) {
-        pvector_remove(&rules, rule);
-        ovsrcu_postpone(rule_destroy_cb, rule);
+        if (flush_all || !rule->user) {
+            pvector_remove(&rules, rule);
+            ovsrcu_postpone(rule_destroy_cb, rule);
+        }
     }
     pvector_publish(&rules);
     init_standard_rules();
+    ovs_mutex_unlock(&mutex);
 }
 
 static void
 ovs_router_flush_handler(void *aux OVS_UNUSED)
 {
-    ovs_router_rules_flush();
+    ovs_router_rules_flush(true);
     ovs_router_flush(true);
 
     ovs_mutex_lock(&mutex);
@@ -1137,13 +1298,14 @@ rule_pvec_prio(uint32_t prio)
 }
 
 void
-ovs_router_rule_add(uint32_t prio, bool invert, uint8_t src_len,
+ovs_router_rule_add(uint32_t prio, bool invert, bool user, uint8_t src_len,
                     const struct in6_addr *from, uint32_t lookup_table)
 {
     struct router_rule *rule = xzalloc(sizeof *rule);
 
     rule->prio = prio;
     rule->invert = invert;
+    rule->user = user;
     rule->src_len = src_len;
     rule->from_addr = *from;
     rule->lookup_table = lookup_table;
@@ -1152,6 +1314,32 @@ ovs_router_rule_add(uint32_t prio, bool invert, uint8_t 
src_len,
     pvector_publish(&rules);
 }
 
+int
+ovs_router_rule_del(uint32_t prio, bool invert, uint8_t src_len,
+                    const struct in6_addr *from, uint32_t lookup_table)
+{
+    struct router_rule *rule;
+
+    PVECTOR_FOR_EACH (rule, &rules) {
+        if (prio && rule->prio > prio) {
+            break;
+        }
+        if (rule->user &&
+            rule->invert == invert &&
+            (!prio || rule->prio == prio) &&
+            rule->src_len == src_len &&
+            ipv6_addr_equals(&rule->from_addr, from) &&
+            rule->lookup_table == lookup_table) {
+            pvector_remove(&rules, rule);
+            ovsrcu_postpone(rule_destroy_cb, rule);
+            pvector_publish(&rules);
+            return 0;
+        }
+    }
+
+    return -ENOENT;
+}
+
 void
 ovs_router_init(void)
 {
@@ -1181,6 +1369,14 @@ ovs_router_init(void)
                                  ovs_router_lookup_cmd, NULL);
         unixctl_command_register("ovs/route/rule/show", "", 0, 0,
                                  ovs_router_rules_show, NULL);
+        unixctl_command_register("ovs/route/rule/add",
+                                 "[not] from=<all|IP/PLEN> [prio=NUM] "
+                                 "table=<local|main|default|ID>",
+                                 2, 5, ovs_router_rule_add_cmd, NULL);
+        unixctl_command_register("ovs/route/rule/del",
+                                 "[not] from=<all|IP/PLEN> [prio=NUM] "
+                                 "table=<local|main|default|ID>",
+                                 2, 5, ovs_router_rule_del_cmd, NULL);
         ovsthread_once_done(&once);
     }
 }
diff --git a/lib/ovs-router.h b/lib/ovs-router.h
index a6e100e2f79d..c7d55f30554e 100644
--- a/lib/ovs-router.h
+++ b/lib/ovs-router.h
@@ -50,10 +50,13 @@ void ovs_router_force_insert(uint32_t table, uint32_t mark,
                              const char output_netdev[],
                              const struct in6_addr *gw,
                              const struct in6_addr *prefsrc);
-void ovs_router_rule_add(uint32_t prio, bool invert, uint8_t src_len,
-                         const struct in6_addr *from, uint32_t lookup_table);
+void ovs_router_rule_add(uint32_t prio, bool invert, bool user,
+                         uint8_t src_len, const struct in6_addr *from,
+                         uint32_t lookup_table);
+int ovs_router_rule_del(uint32_t prio, bool invert, uint8_t src_len,
+                        const struct in6_addr *from, uint32_t lookup_table);
 void ovs_router_flush(bool flush_all);
-void ovs_router_rules_flush(void);
+void ovs_router_rules_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 85ded4716d7c..688229a72f6c 100644
--- a/lib/route-table.c
+++ b/lib/route-table.c
@@ -288,8 +288,8 @@ rule_handle_msg(const struct route_table_msg *change)
             route_table_dump_one_table(rd->lookup_table,
                                        route_table_handle_msg, NULL);
         }
-        ovs_router_rule_add(rd->prio, rd->invert, rd->src_len, &rd->from_addr,
-                            rd->lookup_table);
+        ovs_router_rule_add(rd->prio, rd->invert, false, rd->src_len,
+                            &rd->from_addr, rd->lookup_table);
     }
 }
 
@@ -777,7 +777,7 @@ rules_change(const struct route_table_msg *change 
OVS_UNUSED,
 static void
 route_map_clear(void)
 {
-    ovs_router_rules_flush();
+    ovs_router_rules_flush(false);
     ovs_router_flush(false);
 }
 
diff --git a/ofproto/ofproto-tnl-unixctl.man b/ofproto/ofproto-tnl-unixctl.man
index 8e48c0e809ac..0ebbe33db2cf 100644
--- a/ofproto/ofproto-tnl-unixctl.man
+++ b/ofproto/ofproto-tnl-unixctl.man
@@ -30,6 +30,20 @@ additional parameters are used: \fIpkt_mark\fR and \fIsrc\fR.
 Print routing rules in OVS. This includes routing rules cached from the system
 routing policy database and user configured routing rules.
 .
+.IP "\fBovs/route/rule/add [not] from=<\fIall|IP/PLEN\fB> [prio=\fINUM\fB] \
+table=<\fIlocal|main|default|ID\fB>\fR"
+Add user-configured routing rule to vswitchd. It can be useful to reference
+non-standard routing tables for configuring advanced routing policies, for
+example matching on the source IP address. If priority is not specified, the
+lowest unused priority will be selected automatically. Multiple rules with the
+same priority may exist. User-configured rules and system cached rules co-exist
+together.
+.
+.IP "\fBovs/route/rule/del [not] from=<\fIall|IP/PLEN\fB> [prio=\fINUM\fB] \
+table=<\fIlocal|main|default|ID\fB>\fR"
+Delete user-configured routing rule from vswitchd. If priority is not specified
+the first matching rule will be selected for deletion.
+.
 .IP "\fBtnl/neigh/show\fR"
 .IP "\fBtnl/arp/show\fR"
 OVS builds ARP cache by snooping are messages. This command shows
diff --git a/tests/ovs-router.at b/tests/ovs-router.at
index 450eebb28fb0..aa2f5f1ffc53 100644
--- a/tests/ovs-router.at
+++ b/tests/ovs-router.at
@@ -238,9 +238,75 @@ AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 
192.0.2.1/24], [0], [OK
 
 dnl Check standard rules exist.
 AT_CHECK([ovs-appctl ovs/route/rule/show], [0], [dnl
-0: from all lookup local
-32766: from all lookup main
-32767: from all lookup default
+Cached: 0: from all lookup local
+Cached: 32766: from all lookup main
+Cached: 32767: from all lookup default
+])
+
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([appctl - route/rule/{add,del}])
+AT_KEYWORDS([ovs_router])
+OVS_VSWITCHD_START([add-port br0 p1 -- set Interface p1 type=dummy])
+AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 192.0.2.1/24], [0], [OK
+])
+
+AT_CHECK([ovs-appctl ovs/route/add 10.1.1.0/24 br0 192.0.2.2 table=11], [0], 
[OK
+])
+AT_CHECK([ovs-appctl ovs/route/add 10.2.2.0/24 br0 192.0.2.2 table=12], [0], 
[OK
+])
+AT_CHECK([ovs-appctl ovs/route/show table=all | sort], [0], [dnl
+Cached: 192.0.2.0/24 dev br0 SRC 192.0.2.1 local
+User: 10.1.1.0/24 dev br0 GW 192.0.2.2 SRC 192.0.2.1 table 11
+User: 10.2.2.0/24 dev br0 GW 192.0.2.2 SRC 192.0.2.1 table 12
+])
+
+AT_CHECK([ovs-appctl ovs/route/rule/add from=192.0.2.10/32 table=11], [0], [OK
+])
+AT_CHECK([ovs-appctl ovs/route/rule/add from=192.0.2.20/32 table=12], [0], [OK
+])
+AT_CHECK([ovs-appctl ovs/route/rule/show], [0], [dnl
+Cached: 0: from all lookup local
+User: 32764: from 192.0.2.20 lookup 12
+User: 32765: from 192.0.2.10 lookup 11
+Cached: 32766: from all lookup main
+Cached: 32767: from all lookup default
+])
+
+AT_CHECK([ovs-appctl ovs/route/rule/add from=192.0.2.10/32 table=11], [0], [OK
+])
+AT_CHECK([ovs-appctl ovs/route/rule/add from=192.0.2.10/32 table=11], [0], [OK
+])
+AT_CHECK([ovs-appctl ovs/route/rule/show], [0], [dnl
+Cached: 0: from all lookup local
+User: 32762: from 192.0.2.10 lookup 11
+User: 32763: from 192.0.2.10 lookup 11
+User: 32764: from 192.0.2.20 lookup 12
+User: 32765: from 192.0.2.10 lookup 11
+Cached: 32766: from all lookup main
+Cached: 32767: from all lookup default
+])
+
+AT_CHECK([ovs-appctl ovs/route/rule/del from=192.0.2.10/32 table=11], [0], [OK
+])
+AT_CHECK([ovs-appctl ovs/route/rule/show], [0], [dnl
+Cached: 0: from all lookup local
+User: 32763: from 192.0.2.10 lookup 11
+User: 32764: from 192.0.2.20 lookup 12
+User: 32765: from 192.0.2.10 lookup 11
+Cached: 32766: from all lookup main
+Cached: 32767: from all lookup default
+])
+
+AT_CHECK([ovs-appctl ovs/route/rule/del from=192.0.2.10/32 prio=32765 
table=11], [0], [OK
+])
+AT_CHECK([ovs-appctl ovs/route/rule/show], [0], [dnl
+Cached: 0: from all lookup local
+User: 32763: from 192.0.2.10 lookup 11
+User: 32764: from 192.0.2.20 lookup 12
+Cached: 32766: from all lookup main
+Cached: 32767: from all lookup default
 ])
 
 OVS_VSWITCHD_STOP
diff --git a/tests/tunnel-push-pop.at b/tests/tunnel-push-pop.at
index 64c41460bbb2..065a1fcacba6 100644
--- a/tests/tunnel-push-pop.at
+++ b/tests/tunnel-push-pop.at
@@ -1440,3 +1440,122 @@ AT_CHECK([tail -1 stdout], [0],
 
 OVS_VSWITCHD_STOP
 AT_CLEANUP
+
+AT_SETUP([tunnel_push_pop - use two VTEPs in same subnet with rules])
+
+dnl Create bridge that has a MAC address.
+OVS_VSWITCHD_START([set bridge br0 datapath_type=dummy dnl
+                    -- set Interface br0 
other-config:hwaddr=aa:55:aa:55:00:00])
+
+AT_CHECK([ovs-vsctl add-port br0 p0 \
+          -- set Interface p0 type=dummy ofport_request=1])
+
+AT_CHECK([ovs-vsctl add-br br1 \
+          -- set bridge br1 datapath_type=dummy \
+          -- set Interface br1 other-config:hwaddr=aa:66:aa:66:00:00])
+AT_CHECK([ovs-vsctl add-port br1 p1 \
+          -- set interface p1 type=dummy ofport_request=2])
+
+AT_CHECK([ovs-vsctl \
+          -- add-br int-br \
+          -- set bridge int-br datapath_type=dummy \
+          -- set Interface int-br other-config:hwaddr=aa:77:aa:77:00:00])
+AT_CHECK([ovs-vsctl \
+          -- add-port int-br t0 \
+          -- set Interface t0 type=gre ofport_request=3 \
+                              options:local_ip=1.1.2.80 \
+                              options:remote_ip=1.1.2.90
+])
+AT_CHECK([ovs-vsctl \
+          -- add-port int-br t1 \
+          -- set Interface t1 type=gre ofport_request=4 \
+                              options:local_ip=1.1.2.81 \
+                              options:remote_ip=1.1.2.91
+])
+
+AT_CHECK([ovs-appctl dpif/show], [0], [dnl
+dummy@ovs-dummy: hit:0 missed:0
+  br0:
+    br0 65534/100: (dummy-internal)
+    p0 1/1: (dummy)
+  br1:
+    br1 65534/101: (dummy-internal)
+    p1 2/2: (dummy)
+  int-br:
+    int-br 65534/3: (dummy-internal)
+    t0 3/4: (gre: local_ip=1.1.2.80, remote_ip=1.1.2.90)
+    t1 4/4: (gre: local_ip=1.1.2.81, remote_ip=1.1.2.91)
+])
+
+AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 1.1.2.80/24], [0], [OK
+])
+AT_CHECK([ovs-appctl netdev-dummy/ip4addr br1 1.1.2.81/24], [0], [OK
+])
+dnl Checking that a local route for added IP was successfully installed
+dnl on p1 only.
+AT_CHECK([ovs-appctl ovs/route/show | grep Cached], [0], [dnl
+Cached: 1.1.2.0/24 dev br1 SRC 1.1.2.81 local
+])
+
+AT_CHECK([ovs-ofctl add-flow br0 action=normal])
+AT_CHECK([ovs-ofctl add-flow br1 action=normal])
+AT_CHECK([ovs-ofctl add-flow int-br action=normal])
+
+dnl Use arp reply to achieve tunnel next hop mac binding
+AT_CHECK([ovs-appctl netdev-dummy/receive p0 dnl
+ 'recirc_id(0),in_port(1),dnl
+  eth(src=f8:bc:12:44:34:b0,dst=aa:55:aa:55:00:00),eth_type(0x0806),dnl
+  
arp(sip=1.1.2.90,tip=1.1.2.80,op=2,sha=f8:bc:12:44:34:b0,tha=00:00:00:00:00:00)'
+])
+AT_CHECK([ovs-appctl netdev-dummy/receive p1 dnl
+ 'recirc_id(0),in_port(2),dnl
+  eth(src=f8:bc:12:44:34:b1,dst=aa:66:aa:66:00:00),eth_type(0x0806),dnl
+  
arp(sip=1.1.2.91,tip=1.1.2.81,op=2,sha=f8:bc:12:44:34:b1,tha=00:00:00:00:00:00)'
+])
+
+AT_CHECK([ovs-appctl tnl/neigh/show | tail -n+3 | sort], [0], [dnl
+1.1.2.90                                      f8:bc:12:44:34:b0   br0
+1.1.2.91                                      f8:bc:12:44:34:b1   br1
+])
+
+AT_CHECK([ovs-appctl ovs/route/add 1.1.2.0/24 br0 src=1.1.2.80 table=10], [0], 
[OK
+])
+AT_CHECK([ovs-appctl ovs/route/add 1.1.2.0/24 br1 src=1.1.2.81 table=11], [0], 
[OK
+])
+
+AT_CHECK([ovs-appctl ovs/route/show table=all | sort], [0], [dnl
+Cached: 1.1.2.0/24 dev br1 SRC 1.1.2.81 local
+User: 1.1.2.0/24 dev br0 SRC 1.1.2.80 table 10
+User: 1.1.2.0/24 dev br1 SRC 1.1.2.81 table 11
+])
+
+AT_CHECK([ovs-appctl ofproto/trace ovs-dummy 'in_port(int-br),dnl
+          eth(src=00:00:00:00:00:00,dst=f8:bc:12:44:34:b0),eth_type(0x0800),dnl
+          ipv4(src=1.1.3.100,dst=1.1.3.200,proto=1,tos=0,ttl=64,frag=no),dnl
+          icmp(type=8,code=0)' | grep 'tunneling to.*via'], [0], [dnl
+     -> tunneling to 1.1.2.90 via br1
+     -> tunneling to 1.1.2.91 via br1
+])
+
+AT_CHECK([ovs-appctl ovs/route/rule/add from=1.1.2.80/32 table=10], [0], [OK
+])
+AT_CHECK([ovs-appctl ovs/route/rule/add from=1.1.2.81/32 table=11], [0], [OK
+])
+AT_CHECK([ovs-appctl ovs/route/rule/show], [0], [dnl
+Cached: 0: from all lookup local
+User: 32764: from 1.1.2.81 lookup 11
+User: 32765: from 1.1.2.80 lookup 10
+Cached: 32766: from all lookup main
+Cached: 32767: from all lookup default
+])
+
+AT_CHECK([ovs-appctl ofproto/trace ovs-dummy 'in_port(int-br),dnl
+          eth(src=00:00:00:00:00:00,dst=f8:bc:12:44:34:b0),eth_type(0x0800),dnl
+          ipv4(src=1.1.3.100,dst=1.1.3.200,proto=1,tos=0,ttl=64,frag=no),dnl
+          icmp(type=8,code=0)' | grep 'tunneling to.*via'], [0], [dnl
+     -> tunneling to 1.1.2.90 via br0
+     -> tunneling to 1.1.2.91 via br1
+])
+
+OVS_VSWITCHD_STOP
+AT_CLEANUP
-- 
2.50.1

_______________________________________________
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to