diff --git a/Documentation/faq/releases.rst b/Documentation/faq/releases.rst
index 9fbee90edc..7c32d7acfc 100644
--- a/Documentation/faq/releases.rst
+++ b/Documentation/faq/releases.rst
@@ -32,7 +32,7 @@ Q: What does it mean for an Open vSwitch release to be LTS (long-term support)?
If a significant bug is identified in an LTS release, we will provide an
updated release that includes the fix. Releases that are not LTS may not
be fixed and may just be supplanted by the next major release. The current
- LTS release is 2.17.x.
+ LTS release is 3.3.x.
For more information on the Open vSwitch release process, refer to
:doc:`/internals/release-process`.
diff --git a/NEWS b/NEWS
index 5290696a86..5a1010d931 100644
--- a/NEWS
+++ b/NEWS
@@ -1,4 +1,7 @@
-v3.4.0 - xx xxx xxxx
+v3.4.1 - xx xxx xxxx
+--------------------
+
+v3.4.0 - 15 Aug 2024
--------------------
- Option '--mlockall' now only locks memory pages on fault, if possible.
This also makes it compatible with vHost Post-copy Live Migration.
diff --git a/configure.ac b/configure.ac
index 3e39120af8..f5d9648f0d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -13,7 +13,7 @@
# limitations under the License.
AC_PREREQ(2.63)
-AC_INIT(openvswitch, 3.4.0, bugs@openvswitch.org)
+AC_INIT(openvswitch, 3.4.1, bugs@openvswitch.org)
AC_CONFIG_SRCDIR([vswitchd/ovs-vswitchd.c])
AC_CONFIG_MACRO_DIR([m4])
AC_CONFIG_AUX_DIR([build-aux])
diff --git a/debian/changelog b/debian/changelog
index 929b8ecf43..c5396cc09c 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,8 +1,14 @@
+openvswitch (3.4.1-1) unstable; urgency=low
+ [ Open vSwitch team ]
+ * New upstream version
+
+ -- Open vSwitch team <dev@openvswitch.org> Thu, 15 Aug 2024 14:59:36 +0200
+
openvswitch (3.4.0-1) unstable; urgency=low
* New upstream version
- -- Open vSwitch team <dev@openvswitch.org> Mon, 15 Jul 2024 13:00:00 +0100
+ -- Open vSwitch team <dev@openvswitch.org> Thu, 15 Aug 2024 14:59:36 +0200
openvswitch (3.3.0-1) unstable; urgency=low
diff --git a/lib/dp-packet.h b/lib/dp-packet.h
index a75b1c5cdb..e816b9f20b 100644
--- a/lib/dp-packet.h
+++ b/lib/dp-packet.h
@@ -529,6 +529,16 @@ dp_packet_inner_l3(const struct dp_packet *b)
: NULL;
}
+static inline size_t
+dp_packet_inner_l3_size(const struct dp_packet *b)
+{
+ return OVS_LIKELY(b->inner_l3_ofs != UINT16_MAX)
+ ? (const char *) dp_packet_tail(b)
+ - (const char *) dp_packet_inner_l3(b)
+ - dp_packet_l2_pad_size(b)
+ : 0;
+}
+
static inline void *
dp_packet_inner_l4(const struct dp_packet *b)
{
@@ -1390,11 +1400,26 @@ dp_packet_hwol_l3_ipv4(const struct dp_packet *b)
static inline void
dp_packet_ip_set_header_csum(struct dp_packet *p, bool inner)
{
- struct ip_header *ip = (inner) ? dp_packet_inner_l3(p) : dp_packet_l3(p);
+ struct ip_header *ip;
+ size_t l3_size;
+ size_t ip_len;
+
+ if (inner) {
+ ip = dp_packet_inner_l3(p);
+ l3_size = dp_packet_inner_l3_size(p);
+ } else {
+ ip = dp_packet_l3(p);
+ l3_size = dp_packet_l3_size(p);
+ }
ovs_assert(ip);
- ip->ip_csum = 0;
- ip->ip_csum = csum(ip, sizeof *ip);
+
+ ip_len = IP_IHL(ip->ip_ihl_ver) * 4;
+
+ if (OVS_LIKELY(ip_len >= IP_HEADER_LEN && ip_len < l3_size)) {
+ ip->ip_csum = 0;
+ ip->ip_csum = csum(ip, ip_len);
+ }
}
/* Returns 'true' if the packet 'p' has good integrity and the
diff --git a/lib/netdev-dpdk.c b/lib/netdev-dpdk.c
index 02cef6e451..7cced0f226 100644
--- a/lib/netdev-dpdk.c
+++ b/lib/netdev-dpdk.c
@@ -4637,10 +4637,11 @@ netdev_dpdk_get_mempool_info(struct unixctl_conn *conn,
int argc, const char *argv[],
void *aux OVS_UNUSED)
{
- size_t size;
- FILE *stream;
- char *response = NULL;
struct netdev *netdev = NULL;
+ const char *error = NULL;
+ char *response = NULL;
+ FILE *stream;
+ size_t size;
if (argc == 2) {
netdev = netdev_from_name(argv[1]);
@@ -4664,10 +4665,14 @@ netdev_dpdk_get_mempool_info(struct unixctl_conn *conn,
ovs_mutex_lock(&dev->mutex);
ovs_mutex_lock(&dpdk_mp_mutex);
- rte_mempool_dump(stream, dev->dpdk_mp->mp);
- fprintf(stream, " count: avail (%u), in use (%u)\n",
- rte_mempool_avail_count(dev->dpdk_mp->mp),
- rte_mempool_in_use_count(dev->dpdk_mp->mp));
+ if (dev->dpdk_mp) {
+ rte_mempool_dump(stream, dev->dpdk_mp->mp);
+ fprintf(stream, " count: avail (%u), in use (%u)\n",
+ rte_mempool_avail_count(dev->dpdk_mp->mp),
+ rte_mempool_in_use_count(dev->dpdk_mp->mp));
+ } else {
+ error = "Not allocated";
+ }
ovs_mutex_unlock(&dpdk_mp_mutex);
ovs_mutex_unlock(&dev->mutex);
@@ -4679,7 +4684,11 @@ netdev_dpdk_get_mempool_info(struct unixctl_conn *conn,
fclose(stream);
- unixctl_command_reply(conn, response);
+ if (error) {
+ unixctl_command_reply_error(conn, error);
+ } else {
+ unixctl_command_reply(conn, response);
+ }
out:
free(response);
netdev_close(netdev);
diff --git a/lib/netdev-linux.c b/lib/netdev-linux.c
index c316238cd5..0cd0850a31 100644
--- a/lib/netdev-linux.c
+++ b/lib/netdev-linux.c
@@ -1061,8 +1061,7 @@ netdev_linux_construct_tap(struct netdev *netdev_)
if (tap_supports_vnet_hdr
&& ioctl(netdev->tap_fd, TUNSETOFFLOAD, oflags) == 0) {
- netdev_->ol_flags |= (NETDEV_TX_OFFLOAD_IPV4_CKSUM
- | NETDEV_TX_OFFLOAD_TCP_CKSUM
+ netdev_->ol_flags |= (NETDEV_TX_OFFLOAD_TCP_CKSUM
| NETDEV_TX_OFFLOAD_UDP_CKSUM);
if (userspace_tso_enabled()) {
@@ -2510,13 +2509,11 @@ netdev_linux_set_ol(struct netdev *netdev_)
char *string;
uint32_t value;
} t_list[] = {
- {"tx-checksum-ipv4", NETDEV_TX_OFFLOAD_IPV4_CKSUM |
- NETDEV_TX_OFFLOAD_TCP_CKSUM |
+ {"tx-checksum-ipv4", NETDEV_TX_OFFLOAD_TCP_CKSUM |
NETDEV_TX_OFFLOAD_UDP_CKSUM},
{"tx-checksum-ipv6", NETDEV_TX_OFFLOAD_TCP_CKSUM |
NETDEV_TX_OFFLOAD_UDP_CKSUM},
- {"tx-checksum-ip-generic", NETDEV_TX_OFFLOAD_IPV4_CKSUM |
- NETDEV_TX_OFFLOAD_TCP_CKSUM |
+ {"tx-checksum-ip-generic", NETDEV_TX_OFFLOAD_TCP_CKSUM |
NETDEV_TX_OFFLOAD_UDP_CKSUM},
{"tx-checksum-sctp", NETDEV_TX_OFFLOAD_SCTP_CKSUM},
{"tx-tcp-segmentation", NETDEV_TX_OFFLOAD_TCP_TSO},
@@ -6738,7 +6735,8 @@ get_stats_via_netlink(const struct netdev *netdev_, struct netdev_stats *stats)
struct rtnl_link_stats64 aligned_lstats;
if (!IS_PTR_ALIGNED(lstats)) {
- memcpy(&aligned_lstats, lstats, sizeof aligned_lstats);
+ memcpy(&aligned_lstats, (void *) lstats,
+ sizeof aligned_lstats);
lstats = &aligned_lstats;
}
netdev_stats_from_rtnl_link_stats64(stats, lstats);
@@ -7203,13 +7201,6 @@ netdev_linux_prepend_vnet_hdr(struct dp_packet *b, int mtu)
/* The packet has good L4 checksum. No need to validate again. */
vnet->csum_start = vnet->csum_offset = (OVS_FORCE __virtio16) 0;
vnet->flags = VIRTIO_NET_HDR_F_DATA_VALID;
-
- /* It is possible that L4 is good but the IPv4 checksum isn't
- * complete. For example in the case of UDP encapsulation of an ARP
- * packet where the UDP checksum is 0. */
- if (dp_packet_hwol_l3_csum_ipv4_ol(b)) {
- dp_packet_ip_set_header_csum(b, false);
- }
} else if (dp_packet_hwol_tx_l4_checksum(b)) {
/* The csum calculation is offloaded. */
if (dp_packet_hwol_l4_is_tcp(b)) {
diff --git a/lib/route-table.c b/lib/route-table.c
index f1fe32714e..c6cb21394a 100644
--- a/lib/route-table.c
+++ b/lib/route-table.c
@@ -85,7 +85,7 @@ static bool route_table_valid = false;
static void route_table_reset(void);
static void route_table_handle_msg(const struct route_table_msg *);
-static int route_table_parse(struct ofpbuf *, struct route_table_msg *);
+static int route_table_parse(struct ofpbuf *, void *change);
static void route_table_change(const struct route_table_msg *, void *);
static void route_map_clear(void);
@@ -110,8 +110,7 @@ route_table_init(void)
ovs_assert(!route6_notifier);
ovs_router_init();
- nln = nln_create(NETLINK_ROUTE, (nln_parse_func *) route_table_parse,
- &rtmsg);
+ nln = nln_create(NETLINK_ROUTE, route_table_parse, &rtmsg);
route_notifier =
nln_notifier_create(nln, RTNLGRP_IPV4_ROUTE,
@@ -223,8 +222,9 @@ route_table_reset(void)
/* Return RTNLGRP_IPV4_ROUTE or RTNLGRP_IPV6_ROUTE on success, 0 on parse
* error. */
static int
-route_table_parse(struct ofpbuf *buf, struct route_table_msg *change)
+route_table_parse(struct ofpbuf *buf, void *change_)
{
+ struct route_table_msg *change = change_;
bool parsed, ipv4 = false;
static const struct nl_policy policy[] = {
diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c
index 850597b3a4..7506ab5371 100644
--- a/ofproto/ofproto-dpif-xlate.c
+++ b/ofproto/ofproto-dpif-xlate.c
@@ -679,6 +679,7 @@ static size_t count_skb_priorities(const struct xport *);
static bool dscp_from_skb_priority(const struct xport *, uint32_t skb_priority,
uint8_t *dscp);
+static bool xlate_resubmit_resource_check(struct xlate_ctx *);
static void xlate_xbridge_init(struct xlate_cfg *, struct xbridge *);
static void xlate_xbundle_init(struct xlate_cfg *, struct xbundle *);
static void xlate_xport_init(struct xlate_cfg *, struct xport *);
@@ -3723,6 +3724,10 @@ compose_table_xlate(struct xlate_ctx *ctx, const struct xport *out_dev,
struct ofpact_output output;
struct flow flow;
+ if (!xlate_resubmit_resource_check(ctx)) {
+ return 0;
+ }
+
ofpact_init(&output.ofpact, OFPACT_OUTPUT, sizeof output);
flow_extract(packet, &flow);
flow.in_port.ofp_port = out_dev->ofp_port;
@@ -3731,7 +3736,8 @@ compose_table_xlate(struct xlate_ctx *ctx, const struct xport *out_dev,
return ofproto_dpif_execute_actions__(xbridge->ofproto, version, &flow,
NULL, &output.ofpact, sizeof output,
- ctx->depth, ctx->resubmits, packet);
+ ctx->depth + 1, ctx->resubmits,
+ packet);
}
static void
diff --git a/ovsdb/transaction.c b/ovsdb/transaction.c
index 65eca64783..98fff1a744 100644
--- a/ovsdb/transaction.c
+++ b/ovsdb/transaction.c
@@ -1090,7 +1090,6 @@ ovsdb_txn_precommit(struct ovsdb_txn *txn)
* was really a no-op. */
error = for_each_txn_row(txn, determine_changes);
if (error) {
- ovsdb_txn_abort(txn);
return OVSDB_WRAP_BUG("can't happen", error);
}
if (ovs_list_is_empty(&txn->txn_tables)) {
diff --git a/tests/dpif-netdev.at b/tests/dpif-netdev.at
index bdc24cc307..c16bdd0326 100644
--- a/tests/dpif-netdev.at
+++ b/tests/dpif-netdev.at
@@ -807,6 +807,41 @@ AT_CHECK([ovs-appctl netdev-dummy/receive p1 ${bad_frame}])
AT_CHECK([ovs-pcap p2.pcap > p2.pcap.txt 2>&1])
AT_CHECK_UNQUOTED([tail -n 1 p2.pcap.txt], [0], [${good_expected}
])
+
+dnl Test with IP optional fields in a valid packet. Note that neither this
+dnl packet nor the following one contain a correct checksum. OVS is
+dnl expected to replace this dummy checksum with a valid one if possible.
+m4_define([OPT_PKT], m4_join([],
+dnl eth(dst=aa:aa:aa:aa:aa:aa,src=bb:bb:bb:bb:bb:bb,type=0x0800)
+[aaaaaaaaaaaabbbbbbbbbbbb0800],
+dnl ipv4(dst=10.0.0.2,src=10.0.0.1,proto=1,len=60,tot_len=68,csum=0xeeee)
+[4f000044abab00004001eeee0a0000010a000002],
+dnl IPv4 Opt: type 7 (Record Route) len 39 + type 0 (EOL).
+[07270c010203040a000003000000000000000000],
+[0000000000000000000000000000000000000000],
+dnl icmp(type=8,code=0), csum 0x3e2f incorrect, should be 0x412f.
+[08003e2fb6d00000]))
+
+dnl IP header indicates optional fields but doesn't contain any.
+m4_define([MICROGRAM], m4_join([],
+dnl eth(dst=aa:aa:aa:aa:aa:aa,src=bb:bb:bb:bb:bb:bb,type=0x0800)
+[aaaaaaaaaaaabbbbbbbbbbbb0800],
+dnl ipv4(dst=10.0.0.2,src=10.0.0.1,proto=1,len=60,tot_len=68,csum=0xeeee)
+[4f000044abab00004001eeee0a0000010a000002]))
+
+AT_CHECK([ovs-vsctl set Interface p1 options:ol_ip_csum=true])
+AT_CHECK([ovs-vsctl set Interface p1 options:ol_ip_csum_set_good=true])
+AT_CHECK([ovs-appctl netdev-dummy/receive p1 OPT_PKT])
+AT_CHECK([ovs-appctl netdev-dummy/receive p1 MICROGRAM])
+AT_CHECK([ovs-pcap p2.pcap > p2.pcap.txt 2>&1])
+
+dnl Build the expected modified packets. The first packet has a valid IPv4
+dnl checksum and modified destination IP address. The second packet isn't
+dnl expected to change.
+AT_CHECK([echo "OPT_PKT" | sed -e "s/0a000002/c0a80101/" -e "s/eeee/dd2e/" > expout])
+AT_CHECK([echo "MICROGRAM" >> expout])
+AT_CHECK([tail -n 2 p2.pcap.txt], [0], [expout])
+
OVS_VSWITCHD_STOP
AT_CLEANUP
diff --git a/tests/system-dpdk.at b/tests/system-dpdk.at
index 1c97bf7772..e79c755657 100644
--- a/tests/system-dpdk.at
+++ b/tests/system-dpdk.at
@@ -88,6 +88,12 @@ ADD_VHOST_USER_CLIENT_PORT([br10], [dpdkvhostuserclient0], [$OVS_RUNDIR/dpdkvhos
AT_CHECK([ovs-vsctl show], [], [stdout])
sleep 2
+dnl Check that no mempool was allocated.
+AT_CHECK([ovs-appctl netdev-dpdk/get-mempool-info dpdkvhostuserclient0], [2], [], [dnl
+Not allocated
+ovs-appctl: ovs-vswitchd: server returned an error
+])
+
dnl Clean up
AT_CHECK([ovs-vsctl del-port br10 dpdkvhostuserclient0], [], [stdout], [stderr])
OVS_DPDK_STOP_VSWITCHD(["dnl
diff --git a/tests/tunnel-push-pop-ipv6.at b/tests/tunnel-push-pop-ipv6.at
index 3edec5fbca..824a226b21 100644
--- a/tests/tunnel-push-pop-ipv6.at
+++ b/tests/tunnel-push-pop-ipv6.at
@@ -825,3 +825,86 @@ hash(l4(0)),recirc(0x2)
OVS_VSWITCHD_STOP
AT_CLEANUP
+
+AT_SETUP([tunnel_push_pop_ipv6 - Mirror over tunnels])
+OVS_VSWITCHD_START([dnl
+ add-br br-ext -- set bridge br-ext datapath_type=dummy \
+ other-config:hwaddr=aa:55:aa:55:00:00 \
+ -- add-port br0 t1 -- set Interface t1 type=geneve \
+ options:remote_ip=2001:cafe::91 \
+ -- add-port br0 t2 -- set Interface t2 type=erspan \
+ options:remote_ip=2001:cafe::92 options:key=flow \
+ options:erspan_ver=1 options:erspan_idx=flow \
+ -- add-port br0 p0 -- set Interface p0 type=dummy \
+ -- add-port br0 p1 -- set Interface p1 type=dummy \
+ -- add-port br-ext p-ext -- set Interface p-ext type=dummy \
+ options:pcap=ext.pcap])
+
+dnl Configure mirroring over the UDP and ERSPAN tunnels.
+AT_CHECK([dnl
+ ovs-vsctl \
+ set Bridge br0 mirrors=@m1,@m2 -- \
+ --id=@t1 get Port t1 -- \
+ --id=@t2 get Port t2 -- \
+ --id=@m1 create Mirror name=vxlan select_all=true output_port=@t1 -- \
+ --id=@m2 create Mirror name=erspan select_all=true output_port=@t2],
+ [0], [stdout])
+
+AT_CHECK([ovs-ofctl add-flow br-ext actions=normal])
+AT_CHECK([ovs-ofctl add-flow br0 actions=normal])
+
+dnl Make sure ephemeral ports stay static across tests.
+AT_CHECK([ovs-appctl tnl/egress_port_range 35190 35190], [0], [OK
+])
+
+dnl Setup an IP address.
+AT_CHECK([ovs-appctl netdev-dummy/ip6addr br-ext 2001:cafe::90/64], [0], [OK
+])
+
+dnl Send two ND packets to set the tunnel's port and mac address.
+AT_CHECK([ovs-appctl netdev-dummy/receive p-ext dnl
+ 'eth(src=f8:bc:12:44:34:b3,dst=aa:55:aa:55:00:00),eth_type(0x86dd),dnl
+ ipv6(src=2001:cafe::91,dst=2001:cafe::90,label=0,proto=58,tclass=0,hlimit=255,frag=no),dnl
+ icmpv6(type=136,code=0),dnl
+ nd(target=2001:cafe::91,sll=00:00:00:00:00:00,tll=f8:bc:12:44:34:b3)'
+])
+AT_CHECK([ovs-appctl netdev-dummy/receive p-ext dnl
+ 'eth(src=f8:bc:12:44:34:b6,dst=aa:55:aa:55:00:00),eth_type(0x86dd),dnl
+ ipv6(src=2001:cafe::92,dst=2001:cafe::90,label=0,proto=58,tclass=0,hlimit=255,frag=no),dnl
+ icmpv6(type=136,code=0),dnl
+ nd(target=2001:cafe::92,sll=00:00:00:00:00:00,tll=f8:bc:12:44:34:b6)'
+])
+
+m4_define([FLOW], [m4_join([,],
+ [in_port(p1)],
+ [eth(src=50:54:00:00:00:05,dst=50:54:00:00:00:07),eth_type(0x0800)],
+ [ipv4(src=192.168.0.1,dst=192.168.0.2,proto=1,tos=0,ttl=128,frag=no)],
+ [icmp(type=8,code=0)])])
+
+m4_define([ERSPAN_ACT], [m4_join([,],
+ [clone(tnl_push(tnl_port(erspan_sys)],
+ [header(size=70,type=108],
+ [eth(dst=f8:bc:12:44:34:b6,src=aa:55:aa:55:00:00,dl_type=0x86dd)],
+ [ipv6(src=2001:cafe::90,dst=2001:cafe::92,label=0,proto=47,tclass=0x0,hlimit=64)],
+ [erspan(ver=1,sid=0x0,idx=0x0))],
+ [out_port(br-ext))],
+ [p-ext)])])
+
+m4_define([GENEVE_ACT], [m4_join([,],
+ [clone(tnl_push(tnl_port(genev_sys_6081)],
+ [header(size=70,type=5],
+ [eth(dst=f8:bc:12:44:34:b3,src=aa:55:aa:55:00:00,dl_type=0x86dd)],
+ [ipv6(src=2001:cafe::90,dst=2001:cafe::91,label=0,proto=17,tclass=0x0,hlimit=64)],
+ [udp(src=0,dst=6081,csum=0xffff)],
+ [geneve(vni=0x0))],
+ [out_port(br-ext))],
+ [p-ext)])])
+
+dnl Verify packet is mirrored to both tunnels. Tunnel actions may happen
+dnl in any order.
+AT_CHECK([ovs-appctl ofproto/trace --names ovs-dummy "FLOW"], [0], [stdout])
+AT_CHECK([grep -q "ERSPAN_ACT" stdout])
+AT_CHECK([grep -q "GENEVE_ACT" stdout])
+
+OVS_VSWITCHD_STOP
+AT_CLEANUP
diff --git a/tests/tunnel-push-pop.at b/tests/tunnel-push-pop.at
index 7ec4c31ab2..99b1b02bf9 100644
--- a/tests/tunnel-push-pop.at
+++ b/tests/tunnel-push-pop.at
@@ -1259,3 +1259,82 @@ hash(l4(0)),recirc(0x2)
OVS_VSWITCHD_STOP
AT_CLEANUP
+
+AT_SETUP([tunnel_push_pop - Mirror over tunnels])
+OVS_VSWITCHD_START([dnl
+ add-br br-ext -- set bridge br-ext datapath_type=dummy \
+ other-config:hwaddr=aa:55:aa:55:00:00 \
+ -- add-port br0 t1 -- set Interface t1 type=geneve \
+ options:remote_ip=1.1.1.1 \
+ -- add-port br0 t2 -- set Interface t2 type=erspan \
+ options:remote_ip=1.1.1.2 options:key=flow options:erspan_ver=1 \
+ options:erspan_idx=flow \
+ -- add-port br0 p0 -- set Interface p0 type=dummy \
+ -- add-port br0 p1 -- set Interface p1 type=dummy \
+ -- add-port br-ext p-ext -- set Interface p-ext type=dummy \
+ options:pcap=ext.pcap])
+
+dnl Configure mirroring over the UDP and ERSPAN tunnels.
+AT_CHECK([dnl
+ ovs-vsctl \
+ set Bridge br0 mirrors=@m1,@m2 -- \
+ --id=@t1 get Port t1 -- \
+ --id=@t2 get Port t2 -- \
+ --id=@m1 create Mirror name=vxlan select_all=true output_port=@t1 -- \
+ --id=@m2 create Mirror name=erspan select_all=true output_port=@t2],
+ [0], [stdout])
+
+AT_CHECK([ovs-ofctl add-flow br-ext actions=normal])
+AT_CHECK([ovs-ofctl add-flow br0 actions=normal])
+
+dnl Make sure ephemeral ports stay static across tests.
+AT_CHECK([ovs-appctl tnl/egress_port_range 35190 35190], [0], [OK
+])
+
+dnl Setup an IP address for the local side of the tunnel.
+AT_CHECK([ovs-appctl netdev-dummy/ip4addr br-ext 1.1.1.3/24], [0], [OK
+])
+
+dnl Send two arp replies to populate arp table with tunnel remote endpoints.
+AT_CHECK([ovs-appctl netdev-dummy/receive p-ext dnl
+ 'eth(src=f8:bc:12:44:34:b6,dst=ff:ff:ff:ff:ff:ff),eth_type(0x0806),dnl
+ arp(sip=1.1.1.1,tip=1.1.1.3,op=2,sha=f8:bc:12:44:34:b6,tha=00:00:00:00:00:00)'
+])
+AT_CHECK([ovs-appctl netdev-dummy/receive p-ext dnl
+ 'eth(src=f8:bc:12:44:34:b3,dst=ff:ff:ff:ff:ff:ff),eth_type(0x0806),dnl
+ arp(sip=1.1.1.2,tip=1.1.1.3,op=2,sha=f8:bc:12:44:34:b3,tha=00:00:00:00:00:00)'
+])
+
+m4_define([FLOW], [m4_join([,],
+ [in_port(p1)],
+ [eth(src=50:54:00:00:00:05,dst=50:54:00:00:00:07),eth_type(0x0800)],
+ [ipv4(src=192.168.0.1,dst=192.168.0.2,proto=1,tos=0,ttl=128,frag=no)],
+ [icmp(type=8,code=0)])])
+
+m4_define([ERSPAN_ACT], [m4_join([,],
+ [clone(tnl_push(tnl_port(erspan_sys)],
+ [header(size=50,type=107],
+ [eth(dst=f8:bc:12:44:34:b3,src=aa:55:aa:55:00:00,dl_type=0x0800)],
+ [ipv4(src=1.1.1.3,dst=1.1.1.2,proto=47,tos=0,ttl=64,frag=0x4000)],
+ [erspan(ver=1,sid=0x0,idx=0x0))],
+ [out_port(br-ext))],
+ [p-ext)])])
+
+m4_define([GENEVE_ACT], [m4_join([,],
+ [clone(tnl_push(tnl_port(genev_sys_6081)],
+ [header(size=50,type=5],
+ [eth(dst=f8:bc:12:44:34:b6,src=aa:55:aa:55:00:00,dl_type=0x0800)],
+ [ipv4(src=1.1.1.3,dst=1.1.1.1,proto=17,tos=0,ttl=64,frag=0x4000)],
+ [udp(src=0,dst=6081,csum=0x0)],
+ [geneve(vni=0x0))],
+ [out_port(br-ext))],
+ [p-ext)])])
+
+dnl Verify packet is mirrored to both tunnels. Tunnel actions may happen
+dnl in any order.
+AT_CHECK([ovs-appctl ofproto/trace --names ovs-dummy "FLOW"], [0], [stdout])
+AT_CHECK([grep -q "ERSPAN_ACT" stdout])
+AT_CHECK([grep -q "GENEVE_ACT" stdout])
+
+OVS_VSWITCHD_STOP
+AT_CLEANUP