From patchwork Sat Apr 14 00:07:27 2018
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
X-Patchwork-Submitter: Han Zhou
X-Patchwork-Id: 898123
Return-Path:
X-Original-To: incoming@patchwork.ozlabs.org
Delivered-To: patchwork-incoming@bilbo.ozlabs.org
Authentication-Results: ozlabs.org;
spf=pass (mailfrom) smtp.mailfrom=openvswitch.org
(client-ip=140.211.169.12; helo=mail.linuxfoundation.org;
envelope-from=ovs-dev-bounces@openvswitch.org;
receiver=)
Authentication-Results: ozlabs.org;
dmarc=fail (p=none dis=none) header.from=gmail.com
Authentication-Results: ozlabs.org;
dkim=fail reason="signature verification failed" (2048-bit key;
unprotected) header.d=gmail.com header.i=@gmail.com
header.b="XvvDVNCZ"; dkim-atps=neutral
Received: from mail.linuxfoundation.org (mail.linuxfoundation.org
[140.211.169.12])
(using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256
bits)) (No client certificate requested)
by ozlabs.org (Postfix) with ESMTPS id 40NFMs3VZsz9s0n
for ;
Sat, 14 Apr 2018 10:07:52 +1000 (AEST)
Received: from mail.linux-foundation.org (localhost [127.0.0.1])
by mail.linuxfoundation.org (Postfix) with ESMTP id 1B157D2A;
Sat, 14 Apr 2018 00:07:48 +0000 (UTC)
X-Original-To: dev@openvswitch.org
Delivered-To: ovs-dev@mail.linuxfoundation.org
Received: from smtp1.linuxfoundation.org (smtp1.linux-foundation.org
[172.17.192.35])
by mail.linuxfoundation.org (Postfix) with ESMTPS id 2167BCB3
for ; Sat, 14 Apr 2018 00:07:46 +0000 (UTC)
X-Greylist: whitelisted by SQLgrey-1.7.6
Received: from mail-pl0-f45.google.com (mail-pl0-f45.google.com
[209.85.160.45])
by smtp1.linuxfoundation.org (Postfix) with ESMTPS id 85BD3477
for ; Sat, 14 Apr 2018 00:07:44 +0000 (UTC)
Received: by mail-pl0-f45.google.com with SMTP id 59-v6so6920768plc.13
for ; Fri, 13 Apr 2018 17:07:44 -0700 (PDT)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025;
h=from:to:cc:subject:date:message-id;
bh=cj3u4WBviHDhU+KyuCLpSOf5wTR7KchvdS8+jNOSCrM=;
b=XvvDVNCZAAXzdOBJXmaqTe3oWk6Nw2k0VNvSJOvIfnUkpZVSPu6GWcpVV87DtZ8FrZ
72b93D1dXKhEE47Gxu/rPrO/kRz/AoeJCRvCgoIMdjOxYVVraVRmXL1b2l8rr4Yh4ofd
NgXfV812jCY2v7epf82RrJ4NnXdx8TsgQo7nt+qINg7MFQbojtibMRe8npFrm1By/Gdz
8wzKJitrCoa3DoDbyuIsoYFgsokl66TFyx9rnSX1+N+0HgvcE8DPFDtdnwUCRpP/zLND
pnpQgsqQtEmqUjLy7gGk0VmwdFkOyA2iIMYwnH3bIqGfdIQq/5pL3F3K88o/vwSIex3V
xOsg==
X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
d=1e100.net; s=20161025;
h=x-gm-message-state:from:to:cc:subject:date:message-id;
bh=cj3u4WBviHDhU+KyuCLpSOf5wTR7KchvdS8+jNOSCrM=;
b=lciqg9LqT1Zeteveb7DzT5rW24TicrnUERtk4QC0CxYcB1+xRlyp+GdCGRO6e7RZ7w
jqlkGN0B2DzUwvCPVmtjJ8Pn8/K6O/HKuBiuUy4NG6HvbhJ0HzmMG7UwSt+DU9oBh/Jo
9zz+Z5o30OBuZ2x0EIcuUw5fikLG6Ub2Keik4uZXJF0M2sULa/S6JX16f81hDckVo5Py
1dKxGdCT2jB+rTtCWpgTD/Pa1y49mziJxcSAZ2nLOLn3SkM0pldRFvJ8+eXgJGWDTcD+
0ppS/tRG4WAviWL0fC4LnWjO5jgH/YO4xmCapg1eTE0o/M9vuOzqjVGk4gze4R5gs8Mu
W/rA==
X-Gm-Message-State: ALQs6tCkS5qf4PiGdOxJIB/p1/8IWoxtf/kOg+XvT8/h8reAlwry2pLb
gpjUJJlJzO3rZrt3sBeihKfpWA==
X-Google-Smtp-Source:
AIpwx48v6hWr+fyIDQZCsgp9w2fktguarmj5M4XrbJ5lHRZrAUCOglN8+X6qVkk45T7ycB5DZD5/xQ==
X-Received: by 2002:a17:902:d20b:: with SMTP id
t11-v6mr6919050ply.381.1523664463616;
Fri, 13 Apr 2018 17:07:43 -0700 (PDT)
Received: from localhost.localdomain.localdomain ([216.113.160.71])
by smtp.gmail.com with ESMTPSA id
z1sm1141936pgr.4.2018.04.13.17.07.42
(version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128);
Fri, 13 Apr 2018 17:07:43 -0700 (PDT)
From: Han Zhou
X-Google-Original-From: Han Zhou
To: dev@openvswitch.org
Date: Fri, 13 Apr 2018 17:07:27 -0700
Message-Id: <1523664447-27385-1-git-send-email-hzhou8@ebay.com>
X-Mailer: git-send-email 2.1.0
X-Spam-Status: No, score=-2.0 required=5.0 tests=BAYES_00,DKIM_SIGNED,
DKIM_VALID, DKIM_VALID_AU, FREEMAIL_FROM, LOTS_OF_MONEY,
RCVD_IN_DNSWL_NONE autolearn=ham version=3.3.1
X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on
smtp1.linux-foundation.org
Subject: [ovs-dev] [PATCH v3] ovn: Support address sets generated from port
groups
X-BeenThere: ovs-dev@openvswitch.org
X-Mailman-Version: 2.1.12
Precedence: list
List-Id:
List-Unsubscribe: ,
List-Archive:
List-Post:
List-Help:
List-Subscribe: ,
MIME-Version: 1.0
Sender: ovs-dev-bounces@openvswitch.org
Errors-To: ovs-dev-bounces@openvswitch.org
Address sets are automatically generated from corresponding port
groups, and can be used directly in ACL match conditions.
There are two address sets generated for each port group:
_ip4
_ip6
For example, if port_group1 is created, we can directly use below
match condition in ACL:
"outport == @port_group1 && ip4.src == $port_group1_ip4"
This will simplify OVN client implementation, and avoid some tricky
problems such as race conditions when maintaining address set
memberships as discussed in the link below.
Reported-by: Lucas Alvares Gomes
Reported-at: https://mail.openvswitch.org/pipermail/ovs-discuss/2018-February/046174.html
Reviewed-by: Mark Michelson
Reviewed-by: Daniel Alvarez
Signed-off-by: Han Zhou
---
Notes:
v2->v3:
- fix memory leak
- reduce amount of xreallocs
- xcalloc(1, ...) was kept there as initial allocation for the exponential allocation strategy
NEWS | 3 +-
ovn/northd/ovn-northd.c | 99 ++++++++++++++++++---
ovn/ovn-nb.xml | 18 ++++
ovn/ovn-sb.xml | 23 ++++-
tests/ovn.at | 226 ++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 352 insertions(+), 17 deletions(-)
diff --git a/NEWS b/NEWS
index 58a7b58..77c2b43 100644
--- a/NEWS
+++ b/NEWS
@@ -20,7 +20,8 @@ Post-v2.9.0
* implemented icmp4/icmp6/tcp_reset actions in order to drop the packet
and reply with a RST for TCP or ICMPv4/ICMPv6 unreachable message for
other IPv4/IPv6-based protocols whenever a reject ACL rule is hit.
- * ACL match conditions can now match on Port_Groups.
+ * ACL match conditions can now match on Port_Groups as well as address
+ sets that are automatically generated by Port_Groups.
v2.9.0 - 19 Feb 2018
--------------------
diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c
index 607fc75..b55f5f8 100644
--- a/ovn/northd/ovn-northd.c
+++ b/ovn/northd/ovn-northd.c
@@ -6179,9 +6179,32 @@ build_lflows(struct northd_context *ctx, struct hmap *datapaths,
hmap_destroy(&mcgroups);
}
-/* OVN_Northbound and OVN_Southbound have an identical Address_Set table.
- * We always update OVN_Southbound to match the current data in
- * OVN_Northbound, so that the address sets used in Logical_Flows in
+static void
+sync_address_set(struct northd_context *ctx, const char *name,
+ const char **addrs, size_t n_addrs,
+ struct shash *sb_address_sets)
+{
+ const struct sbrec_address_set *sb_address_set;
+ sb_address_set = shash_find_and_delete(sb_address_sets,
+ name);
+ if (!sb_address_set) {
+ sb_address_set = sbrec_address_set_insert(ctx->ovnsb_txn);
+ sbrec_address_set_set_name(sb_address_set, name);
+ }
+
+ sbrec_address_set_set_addresses(sb_address_set,
+ addrs, n_addrs);
+}
+
+/* OVN_Southbound Address_Set table contains same records as in north
+ * bound, plus the records generated from Port_Group table in north bound.
+ *
+ * There are 2 records generated from each port group, one for IPv4, and
+ * one for IPv6, named in the format: _ip4 and
+ * _ip6 respectively. MAC addresses are ignored.
+ *
+ * We always update OVN_Southbound to match the Address_Set and Port_Group
+ * in OVN_Northbound, so that the address sets used in Logical_Flows in
* OVN_Southbound is checked against the proper set.*/
static void
sync_address_sets(struct northd_context *ctx)
@@ -6193,19 +6216,67 @@ sync_address_sets(struct northd_context *ctx)
shash_add(&sb_address_sets, sb_address_set->name, sb_address_set);
}
- const struct nbrec_address_set *nb_address_set;
- NBREC_ADDRESS_SET_FOR_EACH (nb_address_set, ctx->ovnnb_idl) {
- sb_address_set = shash_find_and_delete(&sb_address_sets,
- nb_address_set->name);
- if (!sb_address_set) {
- sb_address_set = sbrec_address_set_insert(ctx->ovnsb_txn);
- sbrec_address_set_set_name(sb_address_set, nb_address_set->name);
+ /* sync port group generated address sets first */
+ const struct nbrec_port_group *nb_port_group;
+ NBREC_PORT_GROUP_FOR_EACH (nb_port_group, ctx->ovnnb_idl) {
+ char **ipv4_addrs = xcalloc(1, sizeof *ipv4_addrs);
+ size_t n_ipv4_addrs = 0;
+ size_t n_ipv4_addrs_buf = 1;
+ char **ipv6_addrs = xcalloc(1, sizeof *ipv6_addrs);
+ size_t n_ipv6_addrs = 0;
+ size_t n_ipv6_addrs_buf = 1;
+ for (size_t i = 0; i < nb_port_group->n_ports; i++) {
+ for (size_t j = 0; j < nb_port_group->ports[i]->n_addresses; j++) {
+ struct lport_addresses laddrs;
+ extract_lsp_addresses(nb_port_group->ports[i]->addresses[j],
+ &laddrs);
+ while (n_ipv4_addrs_buf < n_ipv4_addrs + laddrs.n_ipv4_addrs) {
+ n_ipv4_addrs_buf *= 2;
+ ipv4_addrs = xrealloc(ipv4_addrs,
+ n_ipv4_addrs_buf * sizeof *ipv4_addrs);
+ }
+ for (size_t k = 0; k < laddrs.n_ipv4_addrs; k++) {
+ ipv4_addrs[n_ipv4_addrs++] =
+ xstrdup(laddrs.ipv4_addrs[k].addr_s);
+ }
+ while (n_ipv6_addrs_buf < n_ipv6_addrs + laddrs.n_ipv6_addrs) {
+ n_ipv6_addrs_buf *= 2;
+ ipv6_addrs = xrealloc(ipv6_addrs,
+ n_ipv6_addrs_buf * sizeof *ipv6_addrs);
+ }
+ for (size_t k = 0; k < laddrs.n_ipv6_addrs; k++) {
+ ipv6_addrs[n_ipv6_addrs++] =
+ xstrdup(laddrs.ipv6_addrs[k].addr_s);
+ }
+ destroy_lport_addresses(&laddrs);
+ }
+ }
+ char *ipv4_addrs_name = xasprintf("%s_ip4", nb_port_group->name);
+ char *ipv6_addrs_name = xasprintf("%s_ip6", nb_port_group->name);
+ sync_address_set(ctx, ipv4_addrs_name, (const char **)ipv4_addrs,
+ n_ipv4_addrs, &sb_address_sets);
+ sync_address_set(ctx, ipv6_addrs_name, (const char **)ipv6_addrs,
+ n_ipv6_addrs, &sb_address_sets);
+ free(ipv4_addrs_name);
+ free(ipv6_addrs_name);
+ for (size_t i = 0; i < n_ipv4_addrs; i++) {
+ free(ipv4_addrs[i]);
+ }
+ free(ipv4_addrs);
+ for (size_t i = 0; i < n_ipv6_addrs; i++) {
+ free(ipv6_addrs[i]);
}
+ free(ipv6_addrs);
+ }
- sbrec_address_set_set_addresses(sb_address_set,
- /* "char **" is not compatible with "const char **" */
- (const char **) nb_address_set->addresses,
- nb_address_set->n_addresses);
+ /* sync user defined address sets, which may overwrite port group
+ * generated address sets if same name is used */
+ const struct nbrec_address_set *nb_address_set;
+ NBREC_ADDRESS_SET_FOR_EACH (nb_address_set, ctx->ovnnb_idl) {
+ sync_address_set(ctx, nb_address_set->name,
+ /* "char **" is not compatible with "const char **" */
+ (const char **)nb_address_set->addresses,
+ nb_address_set->n_addresses, &sb_address_sets);
}
struct shash_node *node, *next;
diff --git a/ovn/ovn-nb.xml b/ovn/ovn-nb.xml
index 291ae1a..62d5a07 100644
--- a/ovn/ovn-nb.xml
+++ b/ovn/ovn-nb.xml
@@ -936,6 +936,24 @@
db="OVN_Southbound"/> database.
+
+ For each port group, there are two address sets generated to the
+ table of the
+ database, containing the IP addresses
+ of the group of ports, one for IPv4, and the other for IPv6, with
+ being
+ the
+ of the followed by
+ a suffix _ip4
for IPv4 and _ip6
for IPv6.
+ The generated address sets can be used in the same way as regular
+ address sets in the column
+ of the table. For syntax information, see the details
+ of the expression language used for the column in the table of the database.
+
+
A name for the port group. Names are ASCII and must match
[a-zA-Z_.][a-zA-Z_.0-9]*
.
diff --git a/ovn/ovn-sb.xml b/ovn/ovn-sb.xml
index 92ec193..5d23774 100644
--- a/ovn/ovn-sb.xml
+++ b/ovn/ovn-sb.xml
@@ -368,9 +368,17 @@
- See the documentation for the table in the database
- for details.
+ and address sets generated from the table in the database.
+
+
+
+ See the documentation for the table and table in the
+ database for details.
@@ -790,6 +798,17 @@
@port_group1
.
+
+ Additionally, you may refer to the set of addresses belonging to a
+ group of logical switch ports stored in the
+ table by its followed by
+ a suffix '_ip4'/'_ip6'. The IPv4 address set of a
+ with a name of port_group1
+ can be referred to as $port_group1_ip4
, and the IPv6
+ address set of the same can be referred to
+ as $port_group1_ip6
+
+
Miscellaneous
diff --git a/tests/ovn.at b/tests/ovn.at
index 7ae6640..d9cb9cf 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -9572,3 +9572,229 @@ done
OVN_CLEANUP([hv1], [hv2], [hv3])
AT_CLEANUP
+
+AT_SETUP([ovn -- Port Groups])
+AT_KEYWORDS([ovnpg])
+AT_SKIP_IF([test $HAVE_PYTHON = no])
+ovn_start
+
+# Logical network:
+#
+# Three logical switches ls1, ls2, ls3.
+# One logical router lr0 connected to ls[123],
+# with nine subnets, three per logical switch:
+#
+# lrp11 on ls1 for subnet 192.168.11.0/24
+# lrp12 on ls1 for subnet 192.168.12.0/24
+# lrp13 on ls1 for subnet 192.168.13.0/24
+# ...
+# lrp33 on ls3 for subnet 192.168.33.0/24
+#
+# 27 VIFs, 9 per LS, 3 per subnet: lp[123][123][123], where the first two
+# digits are the subnet and the last digit distinguishes the VIF.
+#
+# This test will create two port groups and uses them in ACL.
+
+get_lsp_uuid () {
+ ovn-nbctl lsp-list ls${1%??} | grep lp$1 | awk '{ print $1 }'
+}
+
+pg1_ports=
+pg2_ports=
+for i in 1 2 3; do
+ ovn-nbctl ls-add ls$i
+ for j in 1 2 3; do
+ for k in 1 2 3; do
+ ovn-nbctl \
+ -- lsp-add ls$i lp$i$j$k \
+ -- lsp-set-addresses lp$i$j$k "f0:00:00:00:0$i:$j$k \
+ 192.168.$i$j.$k"
+ # logical ports lp[12]?1 belongs to port group pg1
+ if test $i != 3 && test $k == 1; then
+ pg1_ports="$pg1_ports `get_lsp_uuid $i$j$k`"
+ fi
+ # logical ports lp[23]?2 belongs to port group pg2
+ if test $i != 1 && test $k == 2; then
+ pg2_ports="$pg2_ports `get_lsp_uuid $i$j$k`"
+ fi
+ done
+ done
+done
+
+ovn-nbctl lr-add lr0
+for i in 1 2 3; do
+ for j in 1 2 3; do
+ ovn-nbctl lrp-add lr0 lrp$i$j 00:00:00:00:ff:$i$j 192.168.$i$j.254/24
+ ovn-nbctl \
+ -- lsp-add ls$i lrp$i$j-attachment \
+ -- set Logical_Switch_Port lrp$i$j-attachment type=router \
+ options:router-port=lrp$i$j \
+ addresses='"00:00:00:00:ff:'$i$j'"'
+ done
+done
+
+ovn-nbctl create Port_Group name=pg1 ports="$pg1_ports"
+ovn-nbctl create Port_Group name=pg2 ports="$pg2_ports"
+
+# create ACLs on all lswitches to drop traffic from pg2 to pg1
+ovn-nbctl acl-add ls1 to-lport 1001 'outport == @pg1 && ip4.src == $pg2_ip4' drop
+ovn-nbctl acl-add ls2 to-lport 1001 'outport == @pg1 && ip4.src == $pg2_ip4' drop
+ovn-nbctl acl-add ls3 to-lport 1001 'outport == @pg1 && ip4.src == $pg2_ip4' drop
+
+# Physical network:
+#
+# Three hypervisors hv[123].
+# lp?1[123] spread across hv[123]: lp?11 on hv1, lp?12 on hv2, lp?13 on hv3.
+# lp?2[123] spread across hv[23]: lp?21 and lp?22 on hv2, lp?23 on hv3.
+# lp?3[123] all on hv3.
+
+# Given the name of a logical port, prints the name of the hypervisor
+# on which it is located.
+vif_to_hv() {
+ case $1 in dnl (
+ ?11) echo 1 ;; dnl (
+ ?12 | ?21 | ?22) echo 2 ;; dnl (
+ ?13 | ?23 | ?3?) echo 3 ;;
+ esac
+}
+
+# Given the name of a logical port, prints the name of its logical router
+# port, e.g. "vif_to_lrp 123" yields 12.
+vif_to_lrp() {
+ echo ${1%?}
+}
+
+# Given the name of a logical port, prints the name of its logical
+# switch, e.g. "vif_to_ls 123" yields 1.
+vif_to_ls() {
+ echo ${1%??}
+}
+
+net_add n1
+for i in 1 2 3; do
+ sim_add hv$i
+ as hv$i
+ ovs-vsctl add-br br-phys
+ ovn_attach n1 br-phys 192.168.0.$i
+done
+for i in 1 2 3; do
+ for j in 1 2 3; do
+ for k in 1 2 3; do
+ hv=`vif_to_hv $i$j$k`
+ as hv$hv ovs-vsctl \
+ -- add-port br-int vif$i$j$k \
+ -- set Interface vif$i$j$k \
+ external-ids:iface-id=lp$i$j$k \
+ options:tx_pcap=hv$hv/vif$i$j$k-tx.pcap \
+ options:rxq_pcap=hv$hv/vif$i$j$k-rx.pcap \
+ ofport-request=$i$j$k
+ done
+ done
+done
+
+# 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
+
+# test_ip INPORT SRC_MAC DST_MAC SRC_IP DST_IP OUTPORT...
+#
+# This shell function causes a packet to be received on INPORT. The packet's
+# content has Ethernet destination DST and source SRC (each exactly 12 hex
+# digits) and Ethernet type ETHTYPE (4 hex digits). The OUTPORTs (zero or
+# more) list the VIFs on which the packet should be received. INPORT and the
+# OUTPORTs are specified as logical switch port numbers, e.g. 123 for vif123.
+for i in 1 2 3; do
+ for j in 1 2 3; do
+ for k in 1 2 3; do
+ : > $i$j$k.expected
+ done
+ done
+done
+test_ip() {
+ # This packet has bad checksums but logical L3 routing doesn't check.
+ local inport=$1 src_mac=$2 dst_mac=$3 src_ip=$4 dst_ip=$5
+ local packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000
+ shift; shift; shift; shift; shift
+ hv=hv`vif_to_hv $inport`
+ as $hv ovs-appctl netdev-dummy/receive vif$inport $packet
+ #as $hv ovs-appctl ofproto/trace br-int in_port=$inport $packet
+ in_ls=`vif_to_ls $inport`
+ in_lrp=`vif_to_lrp $inport`
+ for outport; do
+ out_ls=`vif_to_ls $outport`
+ if test $in_ls = $out_ls; then
+ # Ports on the same logical switch receive exactly the same packet.
+ echo $packet
+ else
+ # Routing decrements TTL and updates source and dest MAC
+ # (and checksum).
+ out_lrp=`vif_to_lrp $outport`
+ echo f00000000${outport}00000000ff${out_lrp}08004500001c00000000"3f1101"00${src_ip}${dst_ip}0035111100080000
+ fi >> $outport.expected
+ done
+}
+
+as hv1 ovs-vsctl --columns=name,ofport list interface
+as hv1 ovn-sbctl list port_binding
+as hv1 ovn-sbctl list datapath_binding
+as hv1 ovn-sbctl list port_group
+as hv1 ovn-sbctl list address_set
+as hv1 ovn-sbctl dump-flows
+as hv1 ovs-ofctl dump-flows br-int
+
+# Send IP packets between all pairs of source and destination ports,
+# packets matches ACL (pg2 to pg1) should be dropped
+ip_to_hex() {
+ printf "%02x%02x%02x%02x" "$@"
+}
+for is in 1 2 3; do
+ for js in 1 2 3; do
+ for ks in 1 2 3; do
+ bcast=
+ s=$is$js$ks
+ smac=f00000000$s
+ sip=`ip_to_hex 192 168 $is$js $ks`
+ for id in 1 2 3; do
+ for jd in 1 2 3; do
+ for kd in 1 2 3; do
+ d=$id$jd$kd
+ dip=`ip_to_hex 192 168 $id$jd $kd`
+ if test $is = $id; then dmac=f00000000$d; else dmac=00000000ff$is$js; fi
+ if test $d != $s; then unicast=$d; else unicast=; fi
+
+ # packets matches ACL should be dropped
+ if test $id != 3 && test $kd == 1; then
+ if test $is != 1 && test $ks == 2; then
+ unicast=
+ fi
+ fi
+ test_ip $s $smac $dmac $sip $dip $unicast #1
+ done
+ done
+ done
+ done
+ done
+done
+
+# Allow some time for packet forwarding.
+# XXX This can be improved.
+sleep 1
+
+# Now check the packets actually received against the ones expected.
+for i in 1 2 3; do
+ for j in 1 2 3; do
+ for k in 1 2 3; do
+ OVN_CHECK_PACKETS([hv`vif_to_hv $i$j$k`/vif$i$j$k-tx.pcap],
+ [$i$j$k.expected])
+ done
+ done
+done
+
+# Gracefully terminate daemons
+OVN_CLEANUP([hv1], [hv2], [hv3])
+AT_CLEANUP