diff --git a/NEWS b/NEWS index 8657134157d..1dfe7d14d30 100644 --- a/NEWS +++ b/NEWS @@ -4,6 +4,7 @@ Post-v2.6.0 * QoS is now implemented via egress shaping rather than ingress policing. * DSCP marking is now supported, via the new northbound QoS table. * IPAM now supports fixed MAC addresses. + * Support for source IP address based routing. - Fixed regression in table stats maintenance introduced in OVS 2.3.0, wherein the number of OpenFlow table hits and misses was not accurate. diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c index 91affe4c9e9..07c7b2de4c5 100644 --- a/ovn/northd/ovn-northd.c +++ b/ovn/northd/ovn-northd.c @@ -3247,10 +3247,20 @@ find_lrp_member_ip(const struct ovn_port *op, const char *ip_s) static void add_route(struct hmap *lflows, const struct ovn_port *op, const char *lrp_addr_s, const char *network_s, int plen, - const char *gateway) + const char *gateway, const char *policy) { bool is_ipv4 = strchr(network_s, '.') ? true : false; struct ds match = DS_EMPTY_INITIALIZER; + const char *dir; + uint16_t priority; + + if (policy && !strcmp(policy, "src-ip")) { + dir = "src"; + priority = plen * 2; + } else { + dir = "dst"; + priority = (plen * 2) + 1; + } /* IPv6 link-local addresses must be scoped to the local router port. */ if (!is_ipv4) { @@ -3260,7 +3270,7 @@ add_route(struct hmap *lflows, const struct ovn_port *op, ds_put_format(&match, "inport == %s && ", op->json_key); } } - ds_put_format(&match, "ip%s.dst == %s/%d", is_ipv4 ? "4" : "6", + ds_put_format(&match, "ip%s.%s == %s/%d", is_ipv4 ? "4" : "6", dir, network_s, plen); struct ds actions = DS_EMPTY_INITIALIZER; @@ -3284,7 +3294,7 @@ add_route(struct hmap *lflows, const struct ovn_port *op, /* The priority here is calculated to implement longest-prefix-match * routing. */ - ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_ROUTING, plen, + ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_ROUTING, priority, ds_cstr(&match), ds_cstr(&actions)); ds_destroy(&match); ds_destroy(&actions); @@ -3397,7 +3407,9 @@ build_static_route_flow(struct hmap *lflows, struct ovn_datapath *od, goto free_prefix_s; } - add_route(lflows, out_port, lrp_addr_s, prefix_s, plen, route->nexthop); + char *policy = route->policy ? route->policy : "dst-ip"; + add_route(lflows, out_port, lrp_addr_s, prefix_s, plen, route->nexthop, + policy); free_prefix_s: free(prefix_s); @@ -4031,13 +4043,13 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { add_route(lflows, op, op->lrp_networks.ipv4_addrs[i].addr_s, op->lrp_networks.ipv4_addrs[i].network_s, - op->lrp_networks.ipv4_addrs[i].plen, NULL); + op->lrp_networks.ipv4_addrs[i].plen, NULL, NULL); } for (int i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) { add_route(lflows, op, op->lrp_networks.ipv6_addrs[i].addr_s, op->lrp_networks.ipv6_addrs[i].network_s, - op->lrp_networks.ipv6_addrs[i].plen, NULL); + op->lrp_networks.ipv6_addrs[i].plen, NULL, NULL); } } diff --git a/ovn/ovn-nb.ovsschema b/ovn/ovn-nb.ovsschema index 865dd349189..65f2d7cfe5d 100644 --- a/ovn/ovn-nb.ovsschema +++ b/ovn/ovn-nb.ovsschema @@ -1,7 +1,7 @@ { "name": "OVN_Northbound", - "version": "5.4.0", - "cksum": "4176761817 11225", + "version": "5.4.1", + "cksum": "3773248894 11490", "tables": { "NB_Global": { "columns": { @@ -196,6 +196,10 @@ "Logical_Router_Static_Route": { "columns": { "ip_prefix": {"type": "string"}, + "policy": {"type": {"key": {"type": "string", + "enum": ["set", ["src-ip", + "dst-ip"]]}, + "min": 0, "max": 1}}, "nexthop": {"type": "string"}, "output_port": {"type": {"key": "string", "min": 0, "max": 1}}}, "isRoot": false}, diff --git a/ovn/ovn-nb.xml b/ovn/ovn-nb.xml index e1b31361be6..7626551d37b 100644 --- a/ovn/ovn-nb.xml +++ b/ovn/ovn-nb.xml @@ -1083,12 +1083,40 @@ Each record represents a static route.
+
+ When multiple routes match a packet, the longest-prefix match is chosen.
+ For a given prefix length, a dst-ip
route is preferred over
+ a src-ip
route.
+
IP prefix of this route (e.g. 192.168.100.0/24).
+ If it is specified, this setting describes the policy used to make + routing decisions. This setting must be one of the following strings: +
+src-ip
: This policy sends the packet to the
+ when the packet's source IP address matches
+ .
+ dst-ip
: This policy sends the packet to the
+ when the packet's destination IP address
+ matches .
+
+ If not specified, the default is dst-ip
.
+
Nexthop IP address for this route. Nexthop IP address should be the IP diff --git a/ovn/utilities/ovn-nbctl.8.xml b/ovn/utilities/ovn-nbctl.8.xml index 2cbd6e0974a..5b702fcef7a 100644 --- a/ovn/utilities/ovn-nbctl.8.xml +++ b/ovn/utilities/ovn-nbctl.8.xml @@ -380,7 +380,7 @@
--may-exist
] lr-route-add
router prefix nexthop [port]--may-exist
] [--policy
=POLICY] lr-route-add
router prefix nexthop [port]Adds the specified route to router. @@ -395,6 +395,12 @@ on nexthop.
+
+ --policy
describes the policy used to make routing
+ decisions. This should be one of "dst-ip" or "src-ip". If not
+ specified, the default is "dst-ip".
+
It is an error if a route with prefix already exists,
unless --may-exist
is specified.
diff --git a/ovn/utilities/ovn-nbctl.c b/ovn/utilities/ovn-nbctl.c
index df1c40528ed..4df6af82782 100644
--- a/ovn/utilities/ovn-nbctl.c
+++ b/ovn/utilities/ovn-nbctl.c
@@ -377,7 +377,7 @@ Logical router port commands:\n\
('enabled' or 'disabled')\n\
\n\
Route commands:\n\
- lr-route-add ROUTER PREFIX NEXTHOP [PORT]\n\
+ [--policy=POLICY] lr-route-add ROUTER PREFIX NEXTHOP [PORT]\n\
add a route to ROUTER\n\
lr-route-del ROUTER [PREFIX]\n\
remove routes from ROUTER\n\
@@ -2031,6 +2031,11 @@ nbctl_lr_route_add(struct ctl_context *ctx)
lr = lr_by_name_or_uuid(ctx, ctx->argv[1], true);
char *prefix, *next_hop;
+ const char *policy = shash_find_data(&ctx->options, "--policy");
+ if (policy && strcmp(policy, "src-ip") && strcmp(policy, "dst-ip")) {
+ ctl_fatal("bad policy: %s", policy);
+ }
+
prefix = normalize_prefix_str(ctx->argv[2]);
if (!prefix) {
ctl_fatal("bad prefix argument: %s", ctx->argv[2]);
@@ -2091,6 +2096,9 @@ nbctl_lr_route_add(struct ctl_context *ctx)
nbrec_logical_router_static_route_set_output_port(route,
ctx->argv[4]);
}
+ if (policy) {
+ nbrec_logical_router_static_route_set_policy(route, policy);
+ }
free(rt_prefix);
free(next_hop);
free(prefix);
@@ -2104,6 +2112,9 @@ nbctl_lr_route_add(struct ctl_context *ctx)
if (ctx->argc == 5) {
nbrec_logical_router_static_route_set_output_port(route, ctx->argv[4]);
}
+ if (policy) {
+ nbrec_logical_router_static_route_set_policy(route, policy);
+ }
nbrec_logical_router_verify_static_routes(lr);
struct nbrec_logical_router_static_route **new_routes
@@ -2457,7 +2468,7 @@ nbctl_lrp_get_enabled(struct ctl_context *ctx)
}
struct ipv4_route {
- int plen;
+ int priority;
ovs_be32 addr;
const struct nbrec_logical_router_static_route *route;
};
@@ -2468,8 +2479,8 @@ ipv4_route_cmp(const void *route1_, const void *route2_)
const struct ipv4_route *route1p = route1_;
const struct ipv4_route *route2p = route2_;
- if (route1p->plen != route2p->plen) {
- return route1p->plen > route2p->plen ? -1 : 1;
+ if (route1p->priority != route2p->priority) {
+ return route1p->priority > route2p->priority ? -1 : 1;
} else if (route1p->addr != route2p->addr) {
return ntohl(route1p->addr) < ntohl(route2p->addr) ? -1 : 1;
} else {
@@ -2478,7 +2489,7 @@ ipv4_route_cmp(const void *route1_, const void *route2_)
}
struct ipv6_route {
- int plen;
+ int priority;
struct in6_addr addr;
const struct nbrec_logical_router_static_route *route;
};
@@ -2489,8 +2500,8 @@ ipv6_route_cmp(const void *route1_, const void *route2_)
const struct ipv6_route *route1p = route1_;
const struct ipv6_route *route2p = route2_;
- if (route1p->plen != route2p->plen) {
- return route1p->plen > route2p->plen ? -1 : 1;
+ if (route1p->priority != route2p->priority) {
+ return route1p->priority > route2p->priority ? -1 : 1;
}
return memcmp(&route1p->addr, &route2p->addr, sizeof(route1p->addr));
}
@@ -2505,6 +2516,12 @@ print_route(const struct nbrec_logical_router_static_route *route, struct ds *s)
free(prefix);
free(next_hop);
+ if (route->policy) {
+ ds_put_format(s, " %s", route->policy);
+ } else {
+ ds_put_format(s, " %s", "dst-ip");
+ }
+
if (route->output_port) {
ds_put_format(s, " %s", route->output_port);
}
@@ -2530,11 +2547,13 @@ nbctl_lr_route_list(struct ctl_context *ctx)
= lr->static_routes[i];
unsigned int plen;
ovs_be32 ipv4;
+ const char *policy = route->policy ? route->policy : "dst-ip";
char *error;
-
error = ip_parse_cidr(route->ip_prefix, &ipv4, &plen);
if (!error) {
- ipv4_routes[n_ipv4_routes].plen = plen;
+ ipv4_routes[n_ipv4_routes].priority = !strcmp(policy, "dst-ip")
+ ? (2 * plen) + 1
+ : 2 * plen;
ipv4_routes[n_ipv4_routes].addr = ipv4;
ipv4_routes[n_ipv4_routes].route = route;
n_ipv4_routes++;
@@ -2544,7 +2563,9 @@ nbctl_lr_route_list(struct ctl_context *ctx)
struct in6_addr ipv6;
error = ipv6_parse_cidr(route->ip_prefix, &ipv6, &plen);
if (!error) {
- ipv6_routes[n_ipv6_routes].plen = plen;
+ ipv6_routes[n_ipv6_routes].priority = !strcmp(policy, "dst-ip")
+ ? (2 * plen) + 1
+ : 2 * plen;
ipv6_routes[n_ipv6_routes].addr = ipv6;
ipv6_routes[n_ipv6_routes].route = route;
n_ipv6_routes++;
@@ -2947,7 +2968,7 @@ static const struct ctl_command_syntax nbctl_commands[] = {
/* logical router route commands. */
{ "lr-route-add", 3, 4, "ROUTER PREFIX NEXTHOP [PORT]", NULL,
- nbctl_lr_route_add, NULL, "--may-exist", RW },
+ nbctl_lr_route_add, NULL, "--may-exist,--policy=", RW },
{ "lr-route-del", 1, 2, "ROUTER [PREFIX]", NULL, nbctl_lr_route_del,
NULL, "--if-exists", RW },
{ "lr-route-list", 1, 1, "ROUTER", NULL, nbctl_lr_route_list, NULL,
diff --git a/tests/ovn-nbctl.at b/tests/ovn-nbctl.at
index af00dad6d6f..0ea6ab82011 100644
--- a/tests/ovn-nbctl.at
+++ b/tests/ovn-nbctl.at
@@ -657,20 +657,23 @@ AT_CHECK([ovn-nbctl lr-route-add lr0 2001:0db8:1::/64 2001:0db8:0:f103::1/64], [
])
AT_CHECK([ovn-nbctl --may-exist lr-route-add lr0 10.0.0.111/24 11.0.0.1])
+AT_CHECK([ovn-nbctl --policy=src-ip lr-route-add lr0 9.16.1.0/24 11.0.0.1])
AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl
IPv4 Routes
- 10.0.0.0/24 11.0.0.1
- 10.0.1.0/24 11.0.1.1 lp0
- 0.0.0.0/0 192.168.0.1
+ 10.0.0.0/24 11.0.0.1 dst-ip
+ 10.0.1.0/24 11.0.1.1 dst-ip lp0
+ 9.16.1.0/24 11.0.0.1 src-ip
+ 0.0.0.0/0 192.168.0.1 dst-ip
])
AT_CHECK([ovn-nbctl --may-exist lr-route-add lr0 10.0.0.111/24 11.0.0.1 lp1])
AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl
IPv4 Routes
- 10.0.0.0/24 11.0.0.1 lp1
- 10.0.1.0/24 11.0.1.1 lp0
- 0.0.0.0/0 192.168.0.1
+ 10.0.0.0/24 11.0.0.1 dst-ip lp1
+ 10.0.1.0/24 11.0.1.1 dst-ip lp0
+ 9.16.1.0/24 11.0.0.1 src-ip
+ 0.0.0.0/0 192.168.0.1 dst-ip
])
dnl Delete non-existent prefix
@@ -680,11 +683,12 @@ AT_CHECK([ovn-nbctl lr-route-del lr0 10.0.2.1/24], [1], [],
AT_CHECK([ovn-nbctl --if-exists lr-route-del lr0 10.0.2.1/24])
AT_CHECK([ovn-nbctl lr-route-del lr0 10.0.1.1/24])
+AT_CHECK([ovn-nbctl lr-route-del lr0 9.16.1.0/24])
AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl
IPv4 Routes
- 10.0.0.0/24 11.0.0.1 lp1
- 0.0.0.0/0 192.168.0.1
+ 10.0.0.0/24 11.0.0.1 dst-ip lp1
+ 0.0.0.0/0 192.168.0.1 dst-ip
])
AT_CHECK([ovn-nbctl lr-route-del lr0])
@@ -698,17 +702,17 @@ AT_CHECK([ovn-nbctl lr-route-add lr0 2001:0db8:1::/64 2001:0db8:0:f103::1])
AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl
IPv6 Routes
- 2001:db8::/64 2001:db8:0:f102::1 lp0
- 2001:db8:1::/64 2001:db8:0:f103::1
- ::/0 2001:db8:0:f101::1
+ 2001:db8::/64 2001:db8:0:f102::1 dst-ip lp0
+ 2001:db8:1::/64 2001:db8:0:f103::1 dst-ip
+ ::/0 2001:db8:0:f101::1 dst-ip
])
AT_CHECK([ovn-nbctl lr-route-del lr0 2001:0db8:0::/64])
AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl
IPv6 Routes
- 2001:db8:1::/64 2001:db8:0:f103::1
- ::/0 2001:db8:0:f101::1
+ 2001:db8:1::/64 2001:db8:0:f103::1 dst-ip
+ ::/0 2001:db8:0:f101::1 dst-ip
])
AT_CHECK([ovn-nbctl lr-route-del lr0])
@@ -725,14 +729,14 @@ AT_CHECK([ovn-nbctl lr-route-add lr0 2001:0db8:1::/64 2001:0db8:0:f103::1])
AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl
IPv4 Routes
- 10.0.0.0/24 11.0.0.1
- 10.0.1.0/24 11.0.1.1 lp0
- 0.0.0.0/0 192.168.0.1
+ 10.0.0.0/24 11.0.0.1 dst-ip
+ 10.0.1.0/24 11.0.1.1 dst-ip lp0
+ 0.0.0.0/0 192.168.0.1 dst-ip
IPv6 Routes
- 2001:db8::/64 2001:db8:0:f102::1 lp0
- 2001:db8:1::/64 2001:db8:0:f103::1
- ::/0 2001:db8:0:f101::1
+ 2001:db8::/64 2001:db8:0:f102::1 dst-ip lp0
+ 2001:db8:1::/64 2001:db8:0:f103::1 dst-ip
+ ::/0 2001:db8:0:f101::1 dst-ip
])
OVN_NBCTL_TEST_STOP
diff --git a/tests/ovn.at b/tests/ovn.at
index 4b269de1b28..6ae4247999c 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -5748,3 +5748,222 @@ OVN_CHECK_PACKETS([hv1/vm1-tx.pcap], [expected1])
OVN_CLEANUP([hv1],[hv2])
AT_CLEANUP
+
+AT_SETUP([ovn -- 3 HVs, 3 LRs connected via LS, source IP based routes])
+AT_SKIP_IF([test $HAVE_PYTHON = no])
+ovn_start
+
+# Logical network:
+# Three LRs - R1, R2 and R3 that are connected to each other via LS "join"
+# in 20.0.0.0/24 network. R1 has switchess foo (192.168.1.0/24) and bar
+# (192.168.2.0/24) connected to it.
+#
+# R2 and R3 are gateway routers.
+# R2 has alice (172.16.1.0/24) and R3 has bob (172.16.1.0/24)
+# connected to it. Note how both alice and bob have the same subnet behind it.
+# We are trying to simulate external network via those 2 switches. In real
+# world the switch ports of these switches will have addresses set as "unknown"
+# to make them learning switches. Or those switches will be "localnet" ones.
+
+# Create three hypervisors and create OVS ports corresponding to logical ports.
+net_add n1
+
+sim_add hv1
+as hv1
+ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.1
+ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=foo1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+ options:rxq_pcap=hv1/vif1-rx.pcap \
+ ofport-request=1
+
+ovs-vsctl -- add-port br-int hv1-vif2 -- \
+ set interface hv1-vif2 external-ids:iface-id=bar1 \
+ options:tx_pcap=hv1/vif2-tx.pcap \
+ options:rxq_pcap=hv1/vif2-rx.pcap \
+ ofport-request=2
+
+sim_add hv2
+as hv2
+ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.2
+ovs-vsctl -- add-port br-int hv2-vif1 -- \
+ set interface hv2-vif1 external-ids:iface-id=alice1 \
+ options:tx_pcap=hv2/vif1-tx.pcap \
+ options:rxq_pcap=hv2/vif1-rx.pcap \
+ ofport-request=1
+
+sim_add hv3
+as hv3
+ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.3
+ovs-vsctl -- add-port br-int hv3-vif1 -- \
+ set interface hv3-vif1 external-ids:iface-id=bob1 \
+ options:tx_pcap=hv3/vif1-tx.pcap \
+ options:rxq_pcap=hv3/vif1-rx.pcap \
+ ofport-request=1
+
+
+ovn-nbctl create Logical_Router name=R1
+ovn-nbctl create Logical_Router name=R2 options:chassis="hv2"
+ovn-nbctl create Logical_Router name=R3 options:chassis="hv3"
+
+ovn-nbctl ls-add foo
+ovn-nbctl ls-add bar
+ovn-nbctl ls-add alice
+ovn-nbctl ls-add bob
+ovn-nbctl ls-add join
+
+# Connect foo to R1
+ovn-nbctl lrp-add R1 foo 00:00:01:01:02:03 192.168.1.1/24
+ovn-nbctl lsp-add foo rp-foo -- set Logical_Switch_Port rp-foo type=router \
+ options:router-port=foo addresses=\"00:00:01:01:02:03\"
+
+# Connect bar to R1
+ovn-nbctl lrp-add R1 bar 00:00:01:01:02:04 192.168.2.1/24
+ovn-nbctl lsp-add bar rp-bar -- set Logical_Switch_Port rp-bar type=router \
+ options:router-port=bar addresses=\"00:00:01:01:02:04\"
+
+# Connect alice to R2
+ovn-nbctl lrp-add R2 alice 00:00:02:01:02:03 172.16.1.1/24
+ovn-nbctl lsp-add alice rp-alice -- set Logical_Switch_Port rp-alice \
+ type=router options:router-port=alice addresses=\"00:00:02:01:02:03\"
+
+# Connect bob to R3
+ovn-nbctl lrp-add R3 bob 00:00:03:01:02:03 172.16.1.2/24
+ovn-nbctl lsp-add bob rp-bob -- set Logical_Switch_Port rp-bob \
+ type=router options:router-port=bob addresses=\"00:00:03:01:02:03\"
+
+# Connect R1 to join
+ovn-nbctl lrp-add R1 R1_join 00:00:04:01:02:03 20.0.0.1/24
+ovn-nbctl lsp-add join r1-join -- set Logical_Switch_Port r1-join \
+ type=router options:router-port=R1_join addresses='"00:00:04:01:02:03"'
+
+# Connect R2 to join
+ovn-nbctl lrp-add R2 R2_join 00:00:04:01:02:04 20.0.0.2/24
+ovn-nbctl lsp-add join r2-join -- set Logical_Switch_Port r2-join \
+ type=router options:router-port=R2_join addresses='"00:00:04:01:02:04"'
+
+# Connect R3 to join
+ovn-nbctl lrp-add R3 R3_join 00:00:04:01:02:05 20.0.0.3/24
+ovn-nbctl lsp-add join r3-join -- set Logical_Switch_Port r3-join \
+ type=router options:router-port=R3_join addresses='"00:00:04:01:02:05"'
+
+# Install static routes with source ip address as the policy for routing.
+# We want traffic from 'foo' to go via R2 and traffic of 'bar' to go via R3.
+ovn-nbctl --policy="src-ip" lr-route-add R1 192.168.1.0/24 20.0.0.2
+ovn-nbctl --policy="src-ip" lr-route-add R1 192.168.2.0/24 20.0.0.3
+
+# Install static routes with destination ip address as the policy for routing.
+ovn-nbctl lr-route-add R2 192.168.0.0/16 20.0.0.1
+
+ovn-nbctl lr-route-add R3 192.168.0.0/16 20.0.0.1
+
+# Create logical port foo1 in foo
+ovn-nbctl lsp-add foo foo1 \
+-- lsp-set-addresses foo1 "f0:00:00:01:02:03 192.168.1.2"
+
+# Create logical port bar1 in bar
+ovn-nbctl lsp-add bar bar1 \
+-- lsp-set-addresses bar1 "f0:00:00:01:02:04 192.168.2.2"
+
+# Create logical port alice1 in alice
+ovn-nbctl lsp-add alice alice1 \
+-- lsp-set-addresses alice1 "f0:00:00:01:02:05 172.16.1.3"
+
+# Create logical port bob1 in bob
+ovn-nbctl lsp-add bob bob1 \
+-- lsp-set-addresses bob1 "f0:00:00:01:02:06 172.16.1.4"
+
+# Pre-populate the hypervisors' ARP tables so that we don't lose any
+# packets for ARP resolution (native tunneling doesn't queue packets
+# for ARP resolution).
+ovn_populate_arp
+
+# Allow some time for ovn-northd and ovn-controller to catch up.
+# XXX This should be more systematic.
+sleep 1
+
+ip_to_hex() {
+ printf "%02x%02x%02x%02x" "$@"
+}
+trim_zeros() {
+ sed 's/\(00\)\{1,\}$//'
+}
+
+# Send ip packets between foo1 and bar1
+# (East-west traffic should flow normally)
+src_mac="f00000010203"
+dst_mac="000001010203"
+src_ip=`ip_to_hex 192 168 1 2`
+dst_ip=`ip_to_hex 192 168 2 2`
+packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000
+as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet
+
+# Send ip packets between foo1 and alice1
+src_mac="f00000010203"
+dst_mac="000001010203"
+src_ip=`ip_to_hex 192 168 1 2`
+dst_ip=`ip_to_hex 172 16 1 3`
+packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000
+as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet
+#as hv1 ovs-appctl ofproto/trace br-int in_port=1 $packet
+
+# Send ip packets between bar1 and bob1
+src_mac="f00000010204"
+dst_mac="000001010204"
+src_ip=`ip_to_hex 192 168 2 2`
+dst_ip=`ip_to_hex 172 16 1 4`
+packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000
+as hv1 ovs-appctl netdev-dummy/receive hv1-vif2 $packet
+#as hv1 ovs-appctl ofproto/trace br-int in_port=2 $packet
+
+# Packet to expect at bar1
+src_mac="000001010204"
+dst_mac="f00000010204"
+src_ip=`ip_to_hex 192 168 1 2`
+dst_ip=`ip_to_hex 192 168 2 2`
+expected=${dst_mac}${src_mac}08004500001c000000003f110100${src_ip}${dst_ip}0035111100080000
+echo $expected > expected
+OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [expected])
+
+# Packet to Expect at alice1
+src_mac="000002010203"
+dst_mac="f00000010205"
+src_ip=`ip_to_hex 192 168 1 2`
+dst_ip=`ip_to_hex 172 16 1 3`
+expected=${dst_mac}${src_mac}08004500001c000000003e110200${src_ip}${dst_ip}0035111100080000
+echo $expected > expected
+OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected])
+
+# Packet to Expect at bob1
+src_mac="000003010203"
+dst_mac="f00000010206"
+src_ip=`ip_to_hex 192 168 2 2`
+dst_ip=`ip_to_hex 172 16 1 4`
+expected=${dst_mac}${src_mac}08004500001c000000003e110200${src_ip}${dst_ip}0035111100080000
+echo $expected > expected
+OVN_CHECK_PACKETS([hv3/vif1-tx.pcap], [expected])
+
+for sim in hv1 hv2 hv3; do
+ as $sim
+ OVS_APP_EXIT_AND_WAIT([ovn-controller])
+ OVS_APP_EXIT_AND_WAIT([ovs-vswitchd])
+ OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+done
+
+as ovn-sb
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+
+as ovn-nb
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+
+as northd
+OVS_APP_EXIT_AND_WAIT([ovn-northd])
+
+as main
+OVS_APP_EXIT_AND_WAIT([ovs-vswitchd])
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+
+AT_CLEANUP