summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/openvswitch/flow.h4
-rw-r--r--include/openvswitch/match.h12
-rw-r--r--include/openvswitch/meta-flow.h56
-rw-r--r--include/openvswitch/packets.h6
-rw-r--r--lib/dpif-netlink.c4
-rw-r--r--lib/flow.c32
-rw-r--r--lib/flow.h2
-rw-r--r--lib/match.c69
-rw-r--r--lib/meta-flow.c77
-rw-r--r--lib/meta-flow.xml89
-rw-r--r--lib/netdev-native-tnl.c166
-rw-r--r--lib/netdev-native-tnl.h12
-rw-r--r--lib/netdev-vport.c54
-rw-r--r--lib/netdev.h4
-rw-r--r--lib/nx-match.c13
-rw-r--r--lib/odp-util.c80
-rw-r--r--lib/odp-util.h2
-rw-r--r--lib/ofp-match.c2
-rw-r--r--lib/packets.h126
-rw-r--r--lib/tnl-ports.c3
-rw-r--r--ofproto/ofproto-dpif-rid.h2
-rw-r--r--ofproto/ofproto-dpif-xlate.c2
-rw-r--r--ofproto/tunnel.c13
-rw-r--r--tests/odp.at4
-rw-r--r--tests/ofproto.at6
-rw-r--r--tests/ovn-controller.at3
-rw-r--r--tests/tunnel-push-pop-ipv6.at129
-rw-r--r--tests/tunnel-push-pop.at192
-rw-r--r--tests/tunnel.at12
-rw-r--r--vswitchd/vswitch.xml34
30 files changed, 1183 insertions, 27 deletions
diff --git a/include/openvswitch/flow.h b/include/openvswitch/flow.h
index cd61ffff0..5d2cf0968 100644
--- a/include/openvswitch/flow.h
+++ b/include/openvswitch/flow.h
@@ -27,7 +27,7 @@ extern "C" {
/* This sequence number should be incremented whenever anything involving flows
* or the wildcarding of flows changes. This will cause build assertion
* failures in places which likely need to be updated. */
-#define FLOW_WC_SEQ 40
+#define FLOW_WC_SEQ 41
/* Number of Open vSwitch extension 32-bit registers. */
#define FLOW_N_REGS 16
@@ -166,7 +166,7 @@ BUILD_ASSERT_DECL(sizeof(struct ovs_key_nsh) % sizeof(uint64_t) == 0);
/* Remember to update FLOW_WC_SEQ when changing 'struct flow'. */
BUILD_ASSERT_DECL(offsetof(struct flow, igmp_group_ip4) + sizeof(uint32_t)
== sizeof(struct flow_tnl) + sizeof(struct ovs_key_nsh) + 300
- && FLOW_WC_SEQ == 40);
+ && FLOW_WC_SEQ == 41);
/* Incremental points at which flow classification may be performed in
* segments.
diff --git a/include/openvswitch/match.h b/include/openvswitch/match.h
index 49333ae8f..49e463a22 100644
--- a/include/openvswitch/match.h
+++ b/include/openvswitch/match.h
@@ -109,6 +109,18 @@ void match_set_tun_gbp_id_masked(struct match *match, ovs_be16 gbp_id, ovs_be16
void match_set_tun_gbp_id(struct match *match, ovs_be16 gbp_id);
void match_set_tun_gbp_flags_masked(struct match *match, uint8_t flags, uint8_t mask);
void match_set_tun_gbp_flags(struct match *match, uint8_t flags);
+void match_set_tun_erspan_ver(struct match *match, uint8_t ver);
+void match_set_tun_erspan_ver_masked(struct match *match, uint8_t ver,
+ uint8_t mask);
+void match_set_tun_erspan_idx(struct match *match, uint32_t idx);
+void match_set_tun_erspan_idx_masked(struct match *match, uint32_t idx,
+ uint32_t mask);
+void match_set_tun_erspan_dir(struct match *match, uint8_t dir);
+void match_set_tun_erspan_dir_masked(struct match *match, uint8_t dir,
+ uint8_t mask);
+void match_set_tun_erspan_hwid(struct match *match, uint8_t hwid);
+void match_set_tun_erspan_hwid_masked(struct match *match, uint8_t hwid,
+ uint8_t mask);
void match_set_in_port(struct match *, ofp_port_t ofp_port);
void match_set_pkt_mark(struct match *, uint32_t pkt_mark);
void match_set_pkt_mark_masked(struct match *, uint32_t pkt_mark, uint32_t mask);
diff --git a/include/openvswitch/meta-flow.h b/include/openvswitch/meta-flow.h
index 98c9e1cb5..64470b524 100644
--- a/include/openvswitch/meta-flow.h
+++ b/include/openvswitch/meta-flow.h
@@ -450,6 +450,62 @@ enum OVS_PACKED_ENUM mf_field_id {
*/
MFF_TUN_GBP_FLAGS,
+ /* "tun_erspan_idx".
+ *
+ * ERSPAN index (direction/port number)
+ *
+ * Type: be32 (low 20 bits).
+ * Maskable: bitwise.
+ * Formatting: hexadecimal.
+ * Prerequisites: none.
+ * Access: read/write.
+ * NXM: none.
+ * OXM: NXOXM_ET_ERSPAN_IDX(11) since OF1.5 and v2.10.
+ */
+ MFF_TUN_ERSPAN_IDX,
+
+ /* "tun_erspan_ver".
+ *
+ * ERSPAN version (v1 / v2)
+ *
+ * Type: u8 (low 4 bits).
+ * Maskable: bitwise.
+ * Formatting: decimal.
+ * Prerequisites: none.
+ * Access: read/write.
+ * NXM: none.
+ * OXM: NXOXM_ET_ERSPAN_VER(12) since OF1.5 and v2.10.
+ */
+ MFF_TUN_ERSPAN_VER,
+
+ /* "tun_erspan_dir".
+ *
+ * ERSPAN mirrored traffic's direction
+ *
+ * Type: u8 (low 1 bits).
+ * Maskable: bitwise.
+ * Formatting: decimal.
+ * Prerequisites: none.
+ * Access: read/write.
+ * NXM: none.
+ * OXM: NXOXM_ET_ERSPAN_DIR(13) since OF1.5 and v2.10.
+ */
+ MFF_TUN_ERSPAN_DIR,
+
+ /* "tun_erspan_hwid".
+ *
+ * ERSPAN Hardware ID
+ *
+ * Type: u8 (low 6 bits).
+ * Maskable: bitwise.
+ * Formatting: hexadecimal.
+ * Prerequisites: none.
+ * Access: read/write.
+ * NXM: none.
+ * OXM: NXOXM_ET_ERSPAN_HWID(14) since OF1.5 and v2.10.
+ */
+ MFF_TUN_ERSPAN_HWID,
+
#if TUN_METADATA_NUM_OPTS == 64
/* "tun_metadata<N>".
*
diff --git a/include/openvswitch/packets.h b/include/openvswitch/packets.h
index fef756b72..925844eda 100644
--- a/include/openvswitch/packets.h
+++ b/include/openvswitch/packets.h
@@ -39,7 +39,11 @@ struct flow_tnl {
ovs_be16 tp_dst;
ovs_be16 gbp_id;
uint8_t gbp_flags;
- uint8_t pad1[5]; /* Pad to 64 bits. */
+ uint8_t erspan_ver;
+ uint32_t erspan_idx;
+ uint8_t erspan_dir;
+ uint8_t erspan_hwid;
+ uint8_t pad1[6]; /* Pad to 64 bits. */
struct tun_metadata metadata;
};
diff --git a/lib/dpif-netlink.c b/lib/dpif-netlink.c
index e8ffd6f37..a8b3ec623 100644
--- a/lib/dpif-netlink.c
+++ b/lib/dpif-netlink.c
@@ -819,6 +819,10 @@ netdev_to_ovs_vport_type(const char *type)
return OVS_VPORT_TYPE_VXLAN;
} else if (!strcmp(type, "lisp")) {
return OVS_VPORT_TYPE_LISP;
+ } else if (!strcmp(type, "erspan")) {
+ return OVS_VPORT_TYPE_ERSPAN;
+ } else if (!strcmp(type, "ip6erspan")) {
+ return OVS_VPORT_TYPE_IP6ERSPAN;
} else {
return OVS_VPORT_TYPE_UNSPEC;
}
diff --git a/lib/flow.c b/lib/flow.c
index 09b66b818..136f06074 100644
--- a/lib/flow.c
+++ b/lib/flow.c
@@ -126,7 +126,7 @@ struct mf_ctx {
* away. Some GCC versions gave warnings on ALWAYS_INLINE, so these are
* defined as macros. */
-#if (FLOW_WC_SEQ != 40)
+#if (FLOW_WC_SEQ != 41)
#define MINIFLOW_ASSERT(X) ovs_assert(X)
BUILD_MESSAGE("FLOW_WC_SEQ changed: miniflow_extract() will have runtime "
"assertions enabled. Consider updating FLOW_WC_SEQ after "
@@ -1014,7 +1014,7 @@ flow_get_metadata(const struct flow *flow, struct match *flow_metadata)
{
int i;
- BUILD_ASSERT_DECL(FLOW_WC_SEQ == 40);
+ BUILD_ASSERT_DECL(FLOW_WC_SEQ == 41);
match_init_catchall(flow_metadata);
if (flow->tunnel.tun_id != htonll(0)) {
@@ -1042,6 +1042,18 @@ flow_get_metadata(const struct flow *flow, struct match *flow_metadata)
if (flow->tunnel.gbp_flags) {
match_set_tun_gbp_flags(flow_metadata, flow->tunnel.gbp_flags);
}
+ if (flow->tunnel.erspan_ver) {
+ match_set_tun_erspan_ver(flow_metadata, flow->tunnel.erspan_ver);
+ }
+ if (flow->tunnel.erspan_idx) {
+ match_set_tun_erspan_idx(flow_metadata, flow->tunnel.erspan_idx);
+ }
+ if (flow->tunnel.erspan_dir) {
+ match_set_tun_erspan_dir(flow_metadata, flow->tunnel.erspan_dir);
+ }
+ if (flow->tunnel.erspan_hwid) {
+ match_set_tun_erspan_hwid(flow_metadata, flow->tunnel.erspan_hwid);
+ }
tun_metadata_get_fmd(&flow->tunnel, flow_metadata);
if (flow->metadata != htonll(0)) {
match_set_metadata(flow_metadata, flow->metadata);
@@ -1581,7 +1593,7 @@ flow_wildcards_init_for_packet(struct flow_wildcards *wc,
memset(&wc->masks, 0x0, sizeof wc->masks);
/* Update this function whenever struct flow changes. */
- BUILD_ASSERT_DECL(FLOW_WC_SEQ == 40);
+ BUILD_ASSERT_DECL(FLOW_WC_SEQ == 41);
if (flow_tnl_dst_is_set(&flow->tunnel)) {
if (flow->tunnel.flags & FLOW_TNL_F_KEY) {
@@ -1598,6 +1610,10 @@ flow_wildcards_init_for_packet(struct flow_wildcards *wc,
WC_MASK_FIELD(wc, tunnel.tp_dst);
WC_MASK_FIELD(wc, tunnel.gbp_id);
WC_MASK_FIELD(wc, tunnel.gbp_flags);
+ WC_MASK_FIELD(wc, tunnel.erspan_ver);
+ WC_MASK_FIELD(wc, tunnel.erspan_idx);
+ WC_MASK_FIELD(wc, tunnel.erspan_dir);
+ WC_MASK_FIELD(wc, tunnel.erspan_hwid);
if (!(flow->tunnel.flags & FLOW_TNL_F_UDPIF)) {
if (flow->tunnel.metadata.present.map) {
@@ -1728,7 +1744,7 @@ void
flow_wc_map(const struct flow *flow, struct flowmap *map)
{
/* Update this function whenever struct flow changes. */
- BUILD_ASSERT_DECL(FLOW_WC_SEQ == 40);
+ BUILD_ASSERT_DECL(FLOW_WC_SEQ == 41);
flowmap_init(map);
@@ -1829,7 +1845,7 @@ void
flow_wildcards_clear_non_packet_fields(struct flow_wildcards *wc)
{
/* Update this function whenever struct flow changes. */
- BUILD_ASSERT_DECL(FLOW_WC_SEQ == 40);
+ BUILD_ASSERT_DECL(FLOW_WC_SEQ == 41);
memset(&wc->masks.metadata, 0, sizeof wc->masks.metadata);
memset(&wc->masks.regs, 0, sizeof wc->masks.regs);
@@ -1973,7 +1989,7 @@ flow_wildcards_set_xxreg_mask(struct flow_wildcards *wc, int idx,
uint32_t
miniflow_hash_5tuple(const struct miniflow *flow, uint32_t basis)
{
- BUILD_ASSERT_DECL(FLOW_WC_SEQ == 40);
+ BUILD_ASSERT_DECL(FLOW_WC_SEQ == 41);
uint32_t hash = basis;
if (flow) {
@@ -2020,7 +2036,7 @@ ASSERT_SEQUENTIAL(ipv6_src, ipv6_dst);
uint32_t
flow_hash_5tuple(const struct flow *flow, uint32_t basis)
{
- BUILD_ASSERT_DECL(FLOW_WC_SEQ == 40);
+ BUILD_ASSERT_DECL(FLOW_WC_SEQ == 41);
uint32_t hash = basis;
if (flow) {
@@ -2609,7 +2625,7 @@ flow_push_mpls(struct flow *flow, int n, ovs_be16 mpls_eth_type,
if (clear_flow_L3) {
/* Clear all L3 and L4 fields and dp_hash. */
- BUILD_ASSERT(FLOW_WC_SEQ == 40);
+ BUILD_ASSERT(FLOW_WC_SEQ == 41);
memset((char *) flow + FLOW_SEGMENT_2_ENDS_AT, 0,
sizeof(struct flow) - FLOW_SEGMENT_2_ENDS_AT);
flow->dp_hash = 0;
diff --git a/lib/flow.h b/lib/flow.h
index af829319e..7a9e7d0bf 100644
--- a/lib/flow.h
+++ b/lib/flow.h
@@ -959,7 +959,7 @@ static inline void
pkt_metadata_from_flow(struct pkt_metadata *md, const struct flow *flow)
{
/* Update this function whenever struct flow changes. */
- BUILD_ASSERT_DECL(FLOW_WC_SEQ == 40);
+ BUILD_ASSERT_DECL(FLOW_WC_SEQ == 41);
md->recirc_id = flow->recirc_id;
md->dp_hash = flow->dp_hash;
diff --git a/lib/match.c b/lib/match.c
index 2e9a80329..bf7e63614 100644
--- a/lib/match.c
+++ b/lib/match.c
@@ -320,6 +320,61 @@ match_set_tun_gbp_flags(struct match *match, uint8_t flags)
}
void
+match_set_tun_erspan_ver_masked(struct match *match, uint8_t ver, uint8_t mask)
+{
+ match->wc.masks.tunnel.erspan_ver = ver;
+ match->flow.tunnel.erspan_ver = ver & mask;
+}
+
+void
+match_set_tun_erspan_ver(struct match *match, uint8_t ver)
+{
+ match_set_tun_erspan_ver_masked(match, ver, UINT8_MAX);
+}
+
+void
+match_set_tun_erspan_idx_masked(struct match *match, uint32_t erspan_idx,
+ uint32_t mask)
+{
+ match->wc.masks.tunnel.erspan_idx = mask;
+ match->flow.tunnel.erspan_idx = erspan_idx & mask;
+}
+
+void
+match_set_tun_erspan_idx(struct match *match, uint32_t erspan_idx)
+{
+ match_set_tun_erspan_idx_masked(match, erspan_idx, UINT32_MAX);
+}
+
+void
+match_set_tun_erspan_dir_masked(struct match *match, uint8_t dir,
+ uint8_t mask)
+{
+ match->wc.masks.tunnel.erspan_dir = dir;
+ match->flow.tunnel.erspan_dir = dir & mask;
+}
+
+void
+match_set_tun_erspan_dir(struct match *match, uint8_t dir)
+{
+ match_set_tun_erspan_dir_masked(match, dir, UINT8_MAX);
+}
+
+void
+match_set_tun_erspan_hwid_masked(struct match *match, uint8_t hwid,
+ uint8_t mask)
+{
+ match->wc.masks.tunnel.erspan_hwid = hwid;
+ match->flow.tunnel.erspan_hwid = hwid & mask;
+}
+
+void
+match_set_tun_erspan_hwid(struct match *match, uint8_t hwid)
+{
+ match_set_tun_erspan_hwid_masked(match, hwid, UINT8_MAX);
+}
+
+void
match_set_in_port(struct match *match, ofp_port_t ofp_port)
{
match->wc.masks.in_port.ofp_port = u16_to_ofp(UINT16_MAX);
@@ -1232,6 +1287,18 @@ format_flow_tunnel(struct ds *s, const struct match *match)
if (wc->masks.tunnel.ip_ttl) {
ds_put_format(s, "tun_ttl=%"PRIu8",", tnl->ip_ttl);
}
+ if (wc->masks.tunnel.erspan_ver) {
+ ds_put_format(s, "tun_erspan_ver=%"PRIu8",", tnl->erspan_ver);
+ }
+ if (wc->masks.tunnel.erspan_idx && tnl->erspan_ver == 1) {
+ ds_put_format(s, "tun_erspan_idx=%#"PRIx32",", tnl->erspan_idx);
+ }
+ if (wc->masks.tunnel.erspan_dir && tnl->erspan_ver == 2) {
+ ds_put_format(s, "tun_erspan_dir=%"PRIu8",", tnl->erspan_dir);
+ }
+ if (wc->masks.tunnel.erspan_hwid && tnl->erspan_ver == 2) {
+ ds_put_format(s, "tun_erspan_hwid=%#"PRIx8",", tnl->erspan_hwid);
+ }
if (wc->masks.tunnel.flags & FLOW_TNL_F_MASK) {
format_flags_masked(s, "tun_flags", flow_tun_flag_to_string,
tnl->flags & FLOW_TNL_F_MASK,
@@ -1303,7 +1370,7 @@ match_format(const struct match *match,
bool is_megaflow = false;
int i;
- BUILD_ASSERT_DECL(FLOW_WC_SEQ == 40);
+ BUILD_ASSERT_DECL(FLOW_WC_SEQ == 41);
if (priority != OFP_DEFAULT_PRIORITY) {
ds_put_format(s, "%spriority=%s%d,",
diff --git a/lib/meta-flow.c b/lib/meta-flow.c
index aa2ec012d..8b8d174c1 100644
--- a/lib/meta-flow.c
+++ b/lib/meta-flow.c
@@ -231,6 +231,14 @@ mf_is_all_wild(const struct mf_field *mf, const struct flow_wildcards *wc)
return !wc->masks.tunnel.gbp_id;
case MFF_TUN_GBP_FLAGS:
return !wc->masks.tunnel.gbp_flags;
+ case MFF_TUN_ERSPAN_VER:
+ return !wc->masks.tunnel.erspan_ver;
+ case MFF_TUN_ERSPAN_IDX:
+ return !wc->masks.tunnel.erspan_idx;
+ case MFF_TUN_ERSPAN_DIR:
+ return !wc->masks.tunnel.erspan_dir;
+ case MFF_TUN_ERSPAN_HWID:
+ return !wc->masks.tunnel.erspan_hwid;
CASE_MFF_TUN_METADATA:
return !ULLONG_GET(wc->masks.tunnel.metadata.present.map,
mf->id - MFF_TUN_METADATA0);
@@ -513,6 +521,10 @@ mf_is_value_valid(const struct mf_field *mf, const union mf_value *value)
case MFF_TUN_TTL:
case MFF_TUN_GBP_ID:
case MFF_TUN_GBP_FLAGS:
+ case MFF_TUN_ERSPAN_IDX:
+ case MFF_TUN_ERSPAN_VER:
+ case MFF_TUN_ERSPAN_DIR:
+ case MFF_TUN_ERSPAN_HWID:
CASE_MFF_TUN_METADATA:
case MFF_METADATA:
case MFF_IN_PORT:
@@ -680,6 +692,18 @@ mf_get_value(const struct mf_field *mf, const struct flow *flow,
case MFF_TUN_TOS:
value->u8 = flow->tunnel.ip_tos;
break;
+ case MFF_TUN_ERSPAN_VER:
+ value->u8 = flow->tunnel.erspan_ver;
+ break;
+ case MFF_TUN_ERSPAN_IDX:
+ value->be32 = htonl(flow->tunnel.erspan_idx);
+ break;
+ case MFF_TUN_ERSPAN_DIR:
+ value->u8 = flow->tunnel.erspan_dir;
+ break;
+ case MFF_TUN_ERSPAN_HWID:
+ value->u8 = flow->tunnel.erspan_hwid;
+ break;
CASE_MFF_TUN_METADATA:
tun_metadata_read(&flow->tunnel, mf, value);
break;
@@ -994,6 +1018,18 @@ mf_set_value(const struct mf_field *mf,
case MFF_TUN_TTL:
match_set_tun_ttl(match, value->u8);
break;
+ case MFF_TUN_ERSPAN_VER:
+ match_set_tun_erspan_ver(match, value->u8);
+ break;
+ case MFF_TUN_ERSPAN_IDX:
+ match_set_tun_erspan_idx(match, ntohl(value->be32));
+ break;
+ case MFF_TUN_ERSPAN_DIR:
+ match_set_tun_erspan_dir(match, value->u8);
+ break;
+ case MFF_TUN_ERSPAN_HWID:
+ match_set_tun_erspan_hwid(match, value->u8);
+ break;
CASE_MFF_TUN_METADATA:
tun_metadata_set_match(mf, value, NULL, match, err_str);
break;
@@ -1391,6 +1427,18 @@ mf_set_flow_value(const struct mf_field *mf,
case MFF_TUN_TTL:
flow->tunnel.ip_ttl = value->u8;
break;
+ case MFF_TUN_ERSPAN_VER:
+ flow->tunnel.erspan_ver = value->u8;
+ break;
+ case MFF_TUN_ERSPAN_IDX:
+ flow->tunnel.erspan_idx = ntohl(value->be32);
+ break;
+ case MFF_TUN_ERSPAN_DIR:
+ flow->tunnel.erspan_dir = value->u8;
+ break;
+ case MFF_TUN_ERSPAN_HWID:
+ flow->tunnel.erspan_hwid = value->u8;
+ break;
CASE_MFF_TUN_METADATA:
tun_metadata_write(&flow->tunnel, mf, value);
break;
@@ -1700,6 +1748,10 @@ mf_is_pipeline_field(const struct mf_field *mf)
case MFF_TUN_FLAGS:
case MFF_TUN_GBP_ID:
case MFF_TUN_GBP_FLAGS:
+ case MFF_TUN_ERSPAN_VER:
+ case MFF_TUN_ERSPAN_IDX:
+ case MFF_TUN_ERSPAN_DIR:
+ case MFF_TUN_ERSPAN_HWID:
CASE_MFF_TUN_METADATA:
case MFF_METADATA:
case MFF_IN_PORT:
@@ -1876,6 +1928,18 @@ mf_set_wild(const struct mf_field *mf, struct match *match, char **err_str)
case MFF_TUN_TTL:
match_set_tun_ttl_masked(match, 0, 0);
break;
+ case MFF_TUN_ERSPAN_VER:
+ match_set_tun_erspan_ver_masked(match, 0, 0);
+ break;
+ case MFF_TUN_ERSPAN_IDX:
+ match_set_tun_erspan_idx_masked(match, 0, 0);
+ break;
+ case MFF_TUN_ERSPAN_DIR:
+ match_set_tun_erspan_dir_masked(match, 0, 0);
+ break;
+ case MFF_TUN_ERSPAN_HWID:
+ match_set_tun_erspan_hwid_masked(match, 0, 0);
+ break;
CASE_MFF_TUN_METADATA:
tun_metadata_set_match(mf, NULL, NULL, match, err_str);
break;
@@ -2256,6 +2320,19 @@ mf_set(const struct mf_field *mf,
case MFF_TUN_TOS:
match_set_tun_tos_masked(match, value->u8, mask->u8);
break;
+ case MFF_TUN_ERSPAN_VER:
+ match_set_tun_erspan_ver_masked(match, value->u8, mask->u8);
+ break;
+ case MFF_TUN_ERSPAN_IDX:
+ match_set_tun_erspan_idx_masked(match, ntohl(value->be32),
+ ntohl(mask->be32));
+ break;
+ case MFF_TUN_ERSPAN_DIR:
+ match_set_tun_erspan_dir_masked(match, value->u8, mask->u8);
+ break;
+ case MFF_TUN_ERSPAN_HWID:
+ match_set_tun_erspan_hwid_masked(match, value->u8, mask->u8);
+ break;
CASE_MFF_TUN_METADATA:
tun_metadata_set_match(mf, value, mask, match, err_str);
break;
diff --git a/lib/meta-flow.xml b/lib/meta-flow.xml
index 933d4b86b..144657c34 100644
--- a/lib/meta-flow.xml
+++ b/lib/meta-flow.xml
@@ -1456,6 +1456,7 @@ ovs-ofctl add-flow br-int 'in_port=3,tun_src=192.168.1.1,tun_id=5001 actions=1'
<li>LISP has a 24-bit instance ID.</li>
<li>GRE has an optional 32-bit key.</li>
<li>STT has a 64-bit key.</li>
+ <li>ERSPAN has a 10-bit key (Session ID).</li>
</ul>
<p>
@@ -1715,6 +1716,87 @@ ovs-ofctl add-flow br-int 'in_port=3,tun_src=192.168.1.1,tun_id=5001 actions=1'
</dl>
</field>
+ <h2>ERSPAN Metadata Fields</h2>
+ <p>
+ These fields provide access to features in the ERSPAN tunneling protocol
+ [ERSPAN], which has two major versions: version 1 (aka type II) and
+ version 2 (aka type III).
+ </p>
+
+ <p>
+ Regardless of version, ERSPAN is encapsulated within a fixed 8-byte GRE
+ header that consists of a 4-byte GRE base header and a 4-byte sequence
+ number. The ERSPAN version 1 header format is:
+ </p>
+
+ <diagram>
+ <header name="GRE">
+ <bits name="..." above="16" width="0.4"/>
+ <bits name="type" above="16" below="0x88be" width="0.4"/>
+ <bits name="seq" above="32" width=".4"/>
+ </header>
+ <header name="ERSPAN v1">
+ <bits name="ver" above="4" below="1" width="0.4"/>
+ <bits name="..." above="18" width="0.4"/>
+ <bits name="session" above="10" below="tun_id" width="0.5"/>
+ <bits name="..." above="12" width="0.4"/>
+ <bits name="idx" above="20" width="0.6"/>
+ </header>
+ <header name="Ethernet">
+ <bits name="dst" above="48" width="0.4"/>
+ <bits name="src" above="48" width="0.4"/>
+ <bits name="type" above="16" width="0.4"/>
+ </header>
+ <dots/>
+ </diagram>
+
+ <p>
+ The ERSPAN version 2 header format is:
+ </p>
+
+ <diagram>
+ <header name="GRE">
+ <bits name="..." above="16" width="0.4"/>
+ <bits name="type" above="16" below="0x22eb" width="0.4"/>
+ <bits name="seq" above="32" width=".4"/>
+ </header>
+ <header name="ERSPAN v2">
+ <bits name="ver" above="4" below="2" width="0.4"/>
+ <bits name="..." above="18" width="0.4"/>
+ <bits name="session" above="10" below="tun_id" width="0.5"/>
+ <bits name="timestamp" above="32" width=".7"/>
+ <bits name="..." above="22" width="0.4"/>
+ <bits name="hwid" above="6" width="0.4"/>
+ <bits name="dir" above="1" below="0/1" width="0.4"/>
+ <bits name="..." above="3" width="0.4"/>
+ </header>
+ <header name="Ethernet">
+ <bits name="dst" above="48" width="0.4"/>
+ <bits name="src" above="48" width="0.4"/>
+ <bits name="type" above="16" width="0.4"/>
+ </header>
+ <dots/>
+ </diagram>
+
+ <field id="MFF_TUN_ERSPAN_VER" title="ERSPAN Version">
+ ERSPAN version number: 1 for version 1, or 2 for version 2.
+ </field>
+
+ <field id="MFF_TUN_ERSPAN_IDX" title="ERSPAN Index">
+ This field is a 20-bit index/port number associated with the ERSPAN
+ traffic's source port and direction (ingress/egress). This field is
+ platform dependent.
+ </field>
+
+ <field id="MFF_TUN_ERSPAN_DIR" title="ERSPAN Direction">
+ For ERSPAN v2, the mirrored traffic's direction: 0 for ingress traffic, 1
+ for egress traffic.
+ </field>
+
+ <field id="MFF_TUN_ERSPAN_HWID" title="ERSPAN Hardware ID">
+ A 6-bit unique identifier of an ERSPAN v2 engine within a system.
+ </field>
+
<h2>Geneve Fields</h2>
<p>
@@ -4520,6 +4602,13 @@ r r c c c.
Computer Communications Review, October 2007.
</dd>
+ <dt>ERSPAN</dt>
+ <dd>
+ M. Foschiano, K. Ghosh, M. Mehta, ``Cisco Systems' Encapsulated Remote
+ Switch Port Analyzer (ERSPAN),'' <url
+ href="https://tools.ietf.org/html/draft-foschiano-erspan-03"/>.
+ </dd>
+
<dt>EXT-56</dt>
<dd>
J. Tonsing, ``Permit one of a set of prerequisites to apply, e.g. don't
diff --git a/lib/netdev-native-tnl.c b/lib/netdev-native-tnl.c
index 876718a6b..66eb18ef9 100644
--- a/lib/netdev-native-tnl.c
+++ b/lib/netdev-native-tnl.c
@@ -518,6 +518,172 @@ netdev_gre_build_header(const struct netdev *netdev,
}
struct dp_packet *
+netdev_erspan_pop_header(struct dp_packet *packet)
+{
+ const struct gre_base_hdr *greh;
+ const struct erspan_base_hdr *ersh;
+ struct pkt_metadata *md = &packet->md;
+ struct flow_tnl *tnl = &md->tunnel;
+ int hlen = sizeof(struct eth_header);
+ unsigned int ulen;
+ uint16_t greh_protocol;
+
+ hlen += netdev_tnl_is_header_ipv6(dp_packet_data(packet)) ?
+ IPV6_HEADER_LEN : IP_HEADER_LEN;
+
+ pkt_metadata_init_tnl(md);
+ if (hlen > dp_packet_size(packet)) {
+ goto err;
+ }
+
+ greh = netdev_tnl_ip_extract_tnl_md(packet, tnl, &ulen);
+ if (!greh) {
+ goto err;
+ }
+
+ greh_protocol = ntohs(greh->protocol);
+ if (greh_protocol != ETH_TYPE_ERSPAN1 &&
+ greh_protocol != ETH_TYPE_ERSPAN2) {
+ goto err;
+ }
+
+ if (greh->flags & ~htons(GRE_SEQ)) {
+ goto err;
+ }
+
+ ersh = ERSPAN_HDR(greh);
+ tnl->tun_id = be32_to_be64(be16_to_be32(htons(get_sid(ersh))));
+ tnl->erspan_ver = ersh->ver;
+
+ if (ersh->ver == 1) {
+ ovs_16aligned_be32 *index = ALIGNED_CAST(ovs_16aligned_be32 *,
+ ersh + 1);
+ tnl->erspan_idx = ntohl(get_16aligned_be32(index));
+ tnl->flags |= FLOW_TNL_F_KEY;
+ hlen = ulen + ERSPAN_GREHDR_LEN + sizeof *ersh + ERSPAN_V1_MDSIZE;
+ } else if (ersh->ver == 2) {
+ struct erspan_md2 *md2 = ALIGNED_CAST(struct erspan_md2 *, ersh + 1);
+ tnl->erspan_dir = md2->dir;
+ tnl->erspan_hwid = get_hwid(md2);
+ tnl->flags |= FLOW_TNL_F_KEY;
+ hlen = ulen + ERSPAN_GREHDR_LEN + sizeof *ersh + ERSPAN_V2_MDSIZE;
+ } else {
+ VLOG_WARN_RL(&err_rl, "ERSPAN version error %d", ersh->ver);
+ goto err;
+ }
+
+ if (hlen > dp_packet_size(packet)) {
+ goto err;
+ }
+
+ dp_packet_reset_packet(packet, hlen);
+
+ return packet;
+err:
+ dp_packet_delete(packet);
+ return NULL;
+}
+
+void
+netdev_erspan_push_header(const struct netdev *netdev,
+ struct dp_packet *packet,
+ const struct ovs_action_push_tnl *data)
+{
+ struct netdev_vport *dev = netdev_vport_cast(netdev);
+ struct netdev_tunnel_config *tnl_cfg;
+ struct erspan_base_hdr *ersh;
+ struct gre_base_hdr *greh;
+ struct erspan_md2 *md2;
+ int ip_tot_size;
+
+ greh = netdev_tnl_push_ip_header(packet, data->header,
+ data->header_len, &ip_tot_size);
+
+ /* update GRE seqno */
+ tnl_cfg = &dev->tnl_cfg;
+ ovs_16aligned_be32 *seqno = (ovs_16aligned_be32 *) (greh + 1);
+ put_16aligned_be32(seqno, htonl(tnl_cfg->seqno++));
+
+ /* update v2 timestamp */
+ if (greh->protocol == htons(ETH_TYPE_ERSPAN2)) {
+ ersh = ERSPAN_HDR(greh);
+ md2 = ALIGNED_CAST(struct erspan_md2 *, ersh + 1);
+ put_16aligned_be32(&md2->timestamp, get_erspan_ts(ERSPAN_100US));
+ }
+ return;
+}
+
+int
+netdev_erspan_build_header(const struct netdev *netdev,
+ struct ovs_action_push_tnl *data,
+ const struct netdev_tnl_build_header_params *params)
+{
+ struct netdev_vport *dev = netdev_vport_cast(netdev);
+ struct netdev_tunnel_config *tnl_cfg;
+ struct gre_base_hdr *greh;
+ struct erspan_base_hdr *ersh;
+ unsigned int hlen;
+ uint32_t tun_id;
+ uint16_t sid;
+
+ /* XXX: RCUfy tnl_cfg. */
+ ovs_mutex_lock(&dev->mutex);
+ tnl_cfg = &dev->tnl_cfg;
+ greh = netdev_tnl_ip_build_header(data, params, IPPROTO_GRE);
+ ersh = ERSPAN_HDR(greh);
+
+ tun_id = ntohl(be64_to_be32(params->flow->tunnel.tun_id));
+ /* ERSPAN only has 10-bit session ID */
+ if (tun_id & ~ERSPAN_SID_MASK) {
+ ovs_mutex_unlock(&dev->mutex);
+ return 1;
+ } else {
+ sid = (uint16_t) tun_id;
+ }
+
+ if (tnl_cfg->erspan_ver == 1) {
+ greh->protocol = htons(ETH_TYPE_ERSPAN1);
+ greh->flags = htons(GRE_SEQ);
+ ersh->ver = 1;
+ set_sid(ersh, sid);
+
+ put_16aligned_be32(ALIGNED_CAST(ovs_16aligned_be32 *, ersh + 1),
+ htonl(tnl_cfg->erspan_idx));
+
+ hlen = ERSPAN_GREHDR_LEN + sizeof *ersh + ERSPAN_V1_MDSIZE;
+ } else if (tnl_cfg->erspan_ver == 2) {
+ greh->protocol = htons(ETH_TYPE_ERSPAN2);
+ greh->flags = htons(GRE_SEQ);
+ ersh->ver = 2;
+ set_sid(ersh, sid);
+
+ struct erspan_md2 *md2 = ALIGNED_CAST(struct erspan_md2 *, ersh + 1);
+ md2->sgt = 0; /* security group tag */
+ md2->gra = 0;
+ put_16aligned_be32(&md2->timestamp, 0);
+ set_hwid(md2, tnl_cfg->erspan_hwid);
+ md2->dir = tnl_cfg->erspan_dir;
+
+ hlen = ERSPAN_GREHDR_LEN + sizeof *ersh + ERSPAN_V2_MDSIZE;
+ } else {
+ VLOG_WARN_RL(&err_rl, "ERSPAN version error %d", tnl_cfg->erspan_ver);
+ ovs_mutex_unlock(&dev->mutex);
+ return 1;
+ }
+
+ ovs_mutex_unlock(&dev->mutex);
+
+ data->header_len += hlen;
+
+ if (params->is_ipv6) {
+ data->tnl_type = OVS_VPORT_TYPE_IP6ERSPAN;
+ } else {
+ data->tnl_type = OVS_VPORT_TYPE_ERSPAN;
+ }
+ return 0;
+}
+
+struct dp_packet *
netdev_vxlan_pop_header(struct dp_packet *packet)
{
struct pkt_metadata *md = &packet->md;
diff --git a/lib/netdev-native-tnl.h b/lib/netdev-native-tnl.h
index 5012b2dba..5dc00122d 100644
--- a/lib/netdev-native-tnl.h
+++ b/lib/netdev-native-tnl.h
@@ -40,6 +40,18 @@ netdev_gre_push_header(const struct netdev *netdev,
struct dp_packet *
netdev_gre_pop_header(struct dp_packet *packet);
+int
+netdev_erspan_build_header(const struct netdev *netdev,
+ struct ovs_action_push_tnl *data,
+ const struct netdev_tnl_build_header_params *p);
+
+void
+netdev_erspan_push_header(const struct netdev *netdev,
+ struct dp_packet *packet,
+ const struct ovs_action_push_tnl *data);
+struct dp_packet *
+netdev_erspan_pop_header(struct dp_packet *packet);
+
void
netdev_tnl_push_udp_header(const struct netdev *netdev,
struct dp_packet *packet,
diff --git a/lib/netdev-vport.c b/lib/netdev-vport.c
index cc50aa5ef..4311d2b88 100644
--- a/lib/netdev-vport.c
+++ b/lib/netdev-vport.c
@@ -544,6 +544,38 @@ set_tunnel_config(struct netdev *dev_, const struct smap *args, char **errp)
} else if (!strcmp(node->key, "egress_pkt_mark")) {
tnl_cfg.egress_pkt_mark = strtoul(node->value, NULL, 10);
tnl_cfg.set_egress_pkt_mark = true;
+ } else if (!strcmp(node->key, "erspan_idx")) {
+ tnl_cfg.erspan_idx = strtol(node->value, NULL, 16);
+ if (tnl_cfg.erspan_idx & ~ERSPAN_IDX_MASK) {
+ ds_put_format(&errors, "%s: invalid erspan index: %s\n",
+ name, node->value);
+ err = EINVAL;
+ goto out;
+ }
+ } else if (!strcmp(node->key, "erspan_ver")) {
+ tnl_cfg.erspan_ver = atoi(node->value);
+ if (tnl_cfg.erspan_ver != 1 && tnl_cfg.erspan_ver != 2) {
+ ds_put_format(&errors, "%s: invalid erspan version: %s\n",
+ name, node->value);
+ err = EINVAL;
+ goto out;
+ }
+ } else if (!strcmp(node->key, "erspan_dir")) {
+ tnl_cfg.erspan_dir = atoi(node->value);
+ if (tnl_cfg.erspan_dir != 0 && tnl_cfg.erspan_dir != 1) {
+ ds_put_format(&errors, "%s: invalid erspan direction: %s\n",
+ name, node->value);
+ err = EINVAL;
+ goto out;
+ }
+ } else if (!strcmp(node->key, "erspan_hwid")) {
+ tnl_cfg.erspan_hwid = strtol(node->value, NULL, 16);
+ if (tnl_cfg.erspan_hwid & ~(ERSPAN_HWID_MASK >> 4)) {
+ ds_put_format(&errors, "%s: invalid erspan hardware ID: %s\n",
+ name, node->value);
+ err = EINVAL;
+ goto out;
+ }
} else {
ds_put_format(&errors, "%s: unknown %s argument '%s'\n", name,
type, node->key);
@@ -734,6 +766,20 @@ get_tunnel_config(const struct netdev *dev, struct smap *args)
smap_add_format(args, "egress_pkt_mark",
"%"PRIu32, tnl_cfg.egress_pkt_mark);
}
+
+ if (tnl_cfg.erspan_idx) {
+ smap_add_format(args, "erspan_idx", "0x%x", tnl_cfg.erspan_idx);
+ }
+ if (tnl_cfg.erspan_ver) {
+ smap_add_format(args, "erspan_ver", "%d", tnl_cfg.erspan_ver);
+ }
+ if (tnl_cfg.erspan_dir) {
+ smap_add_format(args, "erspan_dir", "%d", tnl_cfg.erspan_dir);
+ }
+ if (tnl_cfg.erspan_hwid) {
+ smap_add_format(args, "erspan_hwid", "0x%x", tnl_cfg.erspan_hwid);
+ }
+
return 0;
}
@@ -988,6 +1034,14 @@ netdev_vport_tunnel_register(void)
NETDEV_VPORT_GET_IFINDEX),
TUNNEL_CLASS("lisp", "lisp_sys", NULL, NULL, NULL, NULL),
TUNNEL_CLASS("stt", "stt_sys", NULL, NULL, NULL, NULL),
+ TUNNEL_CLASS("erspan", "erspan_sys", netdev_erspan_build_header,
+ netdev_erspan_push_header,
+ netdev_erspan_pop_header,
+ NULL),
+ TUNNEL_CLASS("ip6erspan", "ip6erspan_sys", netdev_erspan_build_header,
+ netdev_erspan_push_header,
+ netdev_erspan_pop_header,
+ NULL),
};
static struct ovsthread_once once = OVSTHREAD_ONCE_INITIALIZER;
diff --git a/lib/netdev.h b/lib/netdev.h
index e331cacb1..0c74dfebd 100644
--- a/lib/netdev.h
+++ b/lib/netdev.h
@@ -130,6 +130,10 @@ struct netdev_tunnel_config {
bool set_seq;
uint32_t seqno;
+ uint32_t erspan_idx;
+ uint8_t erspan_ver;
+ uint8_t erspan_dir;
+ uint8_t erspan_hwid;
};
void netdev_run(void);
diff --git a/lib/nx-match.c b/lib/nx-match.c
index a3c22ee88..6b1b563ea 100644
--- a/lib/nx-match.c
+++ b/lib/nx-match.c
@@ -1026,7 +1026,7 @@ nx_put_raw(struct ofpbuf *b, enum ofp_version oxm, const struct match *match,
ovs_be32 spi_mask;
int match_len;
- BUILD_ASSERT_DECL(FLOW_WC_SEQ == 40);
+ BUILD_ASSERT_DECL(FLOW_WC_SEQ == 41);
struct nxm_put_ctx ctx = { .output = b, .implied_ethernet = false };
@@ -1155,6 +1155,17 @@ nx_put_raw(struct ofpbuf *b, enum ofp_version oxm, const struct match *match,
flow->tunnel.gbp_flags, match->wc.masks.tunnel.gbp_flags);
tun_metadata_to_nx_match(b, oxm, match);
+ /* ERSPAN */
+ nxm_put_32m(&ctx, MFF_TUN_ERSPAN_IDX, oxm,
+ htonl(flow->tunnel.erspan_idx),
+ htonl(match->wc.masks.tunnel.erspan_idx));
+ nxm_put_8m(&ctx, MFF_TUN_ERSPAN_VER, oxm,
+ flow->tunnel.erspan_ver, match->wc.masks.tunnel.erspan_ver);
+ nxm_put_8m(&ctx, MFF_TUN_ERSPAN_DIR, oxm,
+ flow->tunnel.erspan_dir, match->wc.masks.tunnel.erspan_dir);
+ nxm_put_8m(&ctx, MFF_TUN_ERSPAN_HWID, oxm,
+ flow->tunnel.erspan_hwid, match->wc.masks.tunnel.erspan_hwid);
+
/* Network Service Header */
nxm_put_8m(&ctx, MFF_NSH_FLAGS, oxm, flow->nsh.flags,
match->wc.masks.nsh.flags);
diff --git a/lib/odp-util.c b/lib/odp-util.c
index 752836ae5..fc842ee55 100644
--- a/lib/odp-util.c
+++ b/lib/odp-util.c
@@ -712,6 +712,28 @@ format_odp_tnl_push_header(struct ds *ds, struct ovs_action_push_tnl *data)
options++;
}
ds_put_format(ds, ")");
+ } else if (data->tnl_type == OVS_VPORT_TYPE_ERSPAN ||
+ data->tnl_type == OVS_VPORT_TYPE_IP6ERSPAN) {
+ const struct gre_base_hdr *greh;
+ const struct erspan_base_hdr *ersh;
+
+ greh = (const struct gre_base_hdr *) l4;
+ ersh = ERSPAN_HDR(greh);
+
+ if (ersh->ver == 1) {
+ ovs_16aligned_be32 *index = ALIGNED_CAST(ovs_16aligned_be32 *,
+ ersh + 1);
+ ds_put_format(ds, "erspan(ver=1,sid=0x%"PRIx16",idx=0x%"PRIx32")",
+ get_sid(ersh), ntohl(get_16aligned_be32(index)));
+ } else if (ersh->ver == 2) {
+ struct erspan_md2 *md2 = ALIGNED_CAST(struct erspan_md2 *,
+ ersh + 1);
+ ds_put_format(ds, "erspan(ver=2,sid=0x%"PRIx16
+ ",dir=%"PRIu8",hwid=0x%"PRIx8")",
+ get_sid(ersh), md2->dir, get_hwid(md2));
+ } else {
+ VLOG_WARN("%s Invalid ERSPAN version %d\n", __func__, ersh->ver);
+ }
}
ds_put_format(ds, ")");
}
@@ -1399,11 +1421,14 @@ ovs_parse_tnl_push(const char *s, struct ovs_action_push_tnl *data)
struct ovs_16aligned_ip6_hdr *ip6;
struct udp_header *udp;
struct gre_base_hdr *greh;
- uint16_t gre_proto, gre_flags, dl_type, udp_src, udp_dst, csum;
+ struct erspan_base_hdr *ersh;
+ struct erspan_md2 *md2;
+ uint16_t gre_proto, gre_flags, dl_type, udp_src, udp_dst, csum, sid;
ovs_be32 sip, dip;
- uint32_t tnl_type = 0, header_len = 0, ip_len = 0;
+ uint32_t tnl_type = 0, header_len = 0, ip_len = 0, erspan_idx = 0;
void *l3, *l4;
int n = 0;
+ uint8_t hwid, dir;
if (!ovs_scan_len(s, &n, "tnl_push(tnl_port(%"SCNi32"),", &data->tnl_port)) {
return -EINVAL;
@@ -1574,6 +1599,57 @@ ovs_parse_tnl_push(const char *s, struct ovs_action_push_tnl *data)
header_len = sizeof *eth + ip_len +
((uint8_t *) options - (uint8_t *) greh);
+ } else if (ovs_scan_len(s, &n, "erspan(ver=1,sid="SCNx16",idx=0x"SCNx32")",
+ &sid, &erspan_idx)) {
+ ersh = ERSPAN_HDR(greh);
+ ovs_16aligned_be32 *index = ALIGNED_CAST(ovs_16aligned_be32 *,
+ ersh + 1);
+
+ if (eth->eth_type == htons(ETH_TYPE_IP)) {
+ tnl_type = OVS_VPORT_TYPE_ERSPAN;
+ } else {
+ tnl_type = OVS_VPORT_TYPE_IP6ERSPAN;
+ }
+
+ greh->flags = htons(GRE_SEQ);
+ greh->protocol = htons(ETH_TYPE_ERSPAN1);
+
+ ersh->ver = 1;
+ set_sid(ersh, sid);
+ put_16aligned_be32(index, htonl(erspan_idx));
+
+ if (!ovs_scan_len(s, &n, ")")) {
+ return -EINVAL;
+ }
+ header_len = sizeof *eth + ip_len + ERSPAN_GREHDR_LEN +
+ sizeof *ersh + ERSPAN_V1_MDSIZE;
+
+ } else if (ovs_scan_len(s, &n, "erspan(ver=2,sid="SCNx16"dir="SCNu8
+ ",hwid=0x"SCNx8")", &sid, &dir, &hwid)) {
+
+ ersh = ERSPAN_HDR(greh);
+ md2 = ALIGNED_CAST(struct erspan_md2 *, ersh + 1);
+
+ if (eth->eth_type == htons(ETH_TYPE_IP)) {
+ tnl_type = OVS_VPORT_TYPE_ERSPAN;
+ } else {
+ tnl_type = OVS_VPORT_TYPE_IP6ERSPAN;
+ }
+
+ greh->flags = htons(GRE_SEQ);
+ greh->protocol = htons(ETH_TYPE_ERSPAN2);
+
+ ersh->ver = 2;
+ set_sid(ersh, sid);
+ set_hwid(md2, hwid);
+ md2->dir = dir;
+
+ if (!ovs_scan_len(s, &n, ")")) {
+ return -EINVAL;
+ }
+
+ header_len = sizeof *eth + ip_len + ERSPAN_GREHDR_LEN +
+ sizeof *ersh + ERSPAN_V2_MDSIZE;
} else {
return -EINVAL;
}
diff --git a/lib/odp-util.h b/lib/odp-util.h
index 49467c933..6e684f8e6 100644
--- a/lib/odp-util.h
+++ b/lib/odp-util.h
@@ -147,7 +147,7 @@ void odp_portno_name_format(const struct hmap *portno_names,
* add another field and forget to adjust this value.
*/
#define ODPUTIL_FLOW_KEY_BYTES 640
-BUILD_ASSERT_DECL(FLOW_WC_SEQ == 40);
+BUILD_ASSERT_DECL(FLOW_WC_SEQ == 41);
/* A buffer with sufficient size and alignment to hold an nlattr-formatted flow
* key. An array of "struct nlattr" might not, in theory, be sufficiently
diff --git a/lib/ofp-match.c b/lib/ofp-match.c
index acdf0b776..3a46b3978 100644
--- a/lib/ofp-match.c
+++ b/lib/ofp-match.c
@@ -65,7 +65,7 @@ ofputil_netmask_to_wcbits(ovs_be32 netmask)
void
ofputil_wildcard_from_ofpfw10(uint32_t ofpfw, struct flow_wildcards *wc)
{
- BUILD_ASSERT_DECL(FLOW_WC_SEQ == 40);
+ BUILD_ASSERT_DECL(FLOW_WC_SEQ == 41);
/* Initialize most of wc. */
flow_wildcards_init_catchall(wc);
diff --git a/lib/packets.h b/lib/packets.h
index b2bf70697..7645a9de0 100644
--- a/lib/packets.h
+++ b/lib/packets.h
@@ -32,6 +32,7 @@
#include "tun-metadata.h"
#include "unaligned.h"
#include "util.h"
+#include "timeval.h"
struct dp_packet;
struct ds;
@@ -404,6 +405,8 @@ ovs_be32 set_mpls_lse_values(uint8_t ttl, uint8_t tc, uint8_t bos,
#define ETH_TYPE_MPLS 0x8847
#define ETH_TYPE_MPLS_MCAST 0x8848
#define ETH_TYPE_NSH 0x894f
+#define ETH_TYPE_ERSPAN1 0x88be /* version 1 type II */
+#define ETH_TYPE_ERSPAN2 0x22eb /* version 2 type III */
static inline bool eth_type_mpls(ovs_be16 eth_type)
{
@@ -1246,6 +1249,129 @@ struct gre_base_hdr {
#define GRE_FLAGS 0x00F8
#define GRE_VERSION 0x0007
+/*
+ * ERSPAN protocol header and metadata
+ *
+ * Version 1 (Type II) header (8 octets [42:49])
+ * 0 1 2 3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Ver | VLAN | COS | En|T| Session ID |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Reserved | Index |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *
+ *
+ * ERSPAN Version 2 (Type III) header (12 octets [42:49])
+ * 0 1 2 3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Ver | VLAN | COS |BSO|T| Session ID |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Timestamp |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | SGT |P| FT | Hw ID |D|Gra|O|
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *
+ */
+
+/* ERSPAN has fixed 8-byte GRE header */
+#define ERSPAN_GREHDR_LEN 8
+#define ERSPAN_HDR(gre_base_hdr) \
+ ((struct erspan_base_hdr *)((char *)gre_base_hdr + ERSPAN_GREHDR_LEN))
+
+#define ERSPAN_V1_MDSIZE 4
+#define ERSPAN_V2_MDSIZE 8
+
+#define ERSPAN_SID_MASK 0x03ff /* 10-bit Session ID. */
+#define ERSPAN_IDX_MASK 0xfffff /* v1 Index */
+#define ERSPAN_HWID_MASK 0x03f0
+#define ERSPAN_DIR_MASK 0x0008
+
+struct erspan_base_hdr {
+ uint8_t vlan_upper:4,
+ ver:4;
+ uint8_t vlan:8;
+ uint8_t session_id_upper:2,
+ t:1,
+ en:2,
+ cos:3;
+ uint8_t session_id:8;
+};
+
+struct erspan_md2 {
+ ovs_16aligned_be32 timestamp;
+ ovs_be16 sgt;
+ uint8_t hwid_upper:2,
+ ft:5,
+ p:1;
+ uint8_t o:1,
+ gra:2,
+ dir:1,
+ hwid:4;
+};
+
+struct erspan_metadata {
+ int version;
+ union {
+ ovs_be32 index; /* Version 1 (type II)*/
+ struct erspan_md2 md2; /* Version 2 (type III) */
+ } u;
+};
+
+static inline uint16_t get_sid(const struct erspan_base_hdr *ershdr)
+{
+ return (ershdr->session_id_upper << 8) + ershdr->session_id;
+}
+
+static inline void set_sid(struct erspan_base_hdr *ershdr, uint16_t id)
+{
+ ershdr->session_id = id & 0xff;
+ ershdr->session_id_upper = (id >> 8) &0x3;
+}
+
+static inline uint8_t get_hwid(const struct erspan_md2 *md2)
+{
+ return (md2->hwid_upper << 4) + md2->hwid;
+}
+
+static inline void set_hwid(struct erspan_md2 *md2, uint8_t hwid)
+{
+ md2->hwid = hwid & 0xf;
+ md2->hwid_upper = (hwid >> 4) & 0x3;
+}
+
+/* ERSPAN timestamp granularity
+ * 00b --> granularity = 100 microseconds
+ * 01b --> granularity = 100 nanoseconds
+ * 10b --> granularity = IEEE 1588
+ * Here we only support 100 microseconds.
+ */
+enum erspan_ts_gra {
+ ERSPAN_100US,
+ ERSPAN_100NS,
+ ERSPAN_IEEE1588,
+};
+
+static inline ovs_be32 get_erspan_ts(enum erspan_ts_gra gra)
+{
+ ovs_be32 ts = 0;
+
+ switch (gra) {
+ case ERSPAN_100US:
+ ts = htonl((uint32_t)(time_wall_usec() / 100));
+ break;
+ case ERSPAN_100NS:
+ /* fall back */
+ case ERSPAN_IEEE1588:
+ /* fall back */
+ default:
+ OVS_NOT_REACHED();
+ break;
+ }
+ return ts;
+}
+
/* VXLAN protocol header */
struct vxlanhdr {
union {
diff --git a/lib/tnl-ports.c b/lib/tnl-ports.c
index b814f7a0a..57531794c 100644
--- a/lib/tnl-ports.c
+++ b/lib/tnl-ports.c
@@ -171,7 +171,8 @@ tnl_type_to_nw_proto(const char type[])
if (!strcmp(type, "stt")) {
return IPPROTO_TCP;
}
- if (!strcmp(type, "gre")) {
+ if (!strcmp(type, "gre") || !strcmp(type, "erspan") ||
+ !strcmp(type, "ip6erspan")) {
return IPPROTO_GRE;
}
if (!strcmp(type, "vxlan")) {
diff --git a/ofproto/ofproto-dpif-rid.h b/ofproto/ofproto-dpif-rid.h
index 441584af8..ab5b87a05 100644
--- a/ofproto/ofproto-dpif-rid.h
+++ b/ofproto/ofproto-dpif-rid.h
@@ -99,7 +99,7 @@ struct rule;
/* Metadata for restoring pipeline context after recirculation. Helpers
* are inlined below to keep them together with the definition for easier
* updates. */
-BUILD_ASSERT_DECL(FLOW_WC_SEQ == 40);
+BUILD_ASSERT_DECL(FLOW_WC_SEQ == 41);
struct frozen_metadata {
/* Metadata in struct flow. */
diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c
index ddb834926..c7c9df501 100644
--- a/ofproto/ofproto-dpif-xlate.c
+++ b/ofproto/ofproto-dpif-xlate.c
@@ -3917,7 +3917,7 @@ compose_output_action__(struct xlate_ctx *ctx, ofp_port_t ofp_port,
/* If 'struct flow' gets additional metadata, we'll need to zero it out
* before traversing a patch port. */
- BUILD_ASSERT_DECL(FLOW_WC_SEQ == 40);
+ BUILD_ASSERT_DECL(FLOW_WC_SEQ == 41);
memset(&flow_tnl, 0, sizeof flow_tnl);
if (!check_output_prerequisites(ctx, xport, flow, check_stp)) {
diff --git a/ofproto/tunnel.c b/ofproto/tunnel.c
index a040b547e..2fd6ffbd7 100644
--- a/ofproto/tunnel.c
+++ b/ofproto/tunnel.c
@@ -474,6 +474,19 @@ tnl_port_send(const struct ofport_dpif *ofport, struct flow *flow,
wc->masks.pkt_mark = UINT32_MAX;
}
+ if (cfg->erspan_ver) {
+ flow->tunnel.erspan_ver = cfg->erspan_ver;
+ }
+ if (cfg->erspan_idx) {
+ flow->tunnel.erspan_idx = cfg->erspan_idx;
+ }
+ if (cfg->erspan_dir) {
+ flow->tunnel.erspan_dir = cfg->erspan_dir;
+ }
+ if (cfg->erspan_hwid) {
+ flow->tunnel.erspan_hwid = cfg->erspan_hwid;
+ }
+
if (pre_flow_str) {
char *post_flow_str = flow_to_string(flow, NULL);
char *tnl_str = tnl_port_to_string(tnl_port);
diff --git a/tests/odp.at b/tests/odp.at
index cdf3d6645..64012c8d7 100644
--- a/tests/odp.at
+++ b/tests/odp.at
@@ -314,8 +314,8 @@ tnl_push(tnl_port(6),header(size=50,type=4,eth(dst=f8:bc:12:44:34:b6,src=f8:bc:1
tnl_push(tnl_port(6),header(size=50,type=5,eth(dst=f8:bc:12:44:34:b6,src=f8:bc:12:46:58:e0,dl_type=0x0800),ipv4(src=1.1.2.88,dst=1.1.2.92,proto=17,tos=0,ttl=64,frag=0x4000),udp(src=0,dst=6081,csum=0x0),geneve(oam,vni=0x1c7)),out_port(1))
tnl_push(tnl_port(6),header(size=58,type=5,eth(dst=f8:bc:12:44:34:b6,src=f8:bc:12:46:58:e0,dl_type=0x0800),ipv4(src=1.1.2.88,dst=1.1.2.92,proto=17,tos=0,ttl=64,frag=0x4000),udp(src=0,dst=6081,csum=0x0),geneve(crit,vni=0x1c7,options({class=0xffff,type=0x80,len=4,0xa}))),out_port(1))
tnl_push(tnl_port(6),header(size=50,type=5,eth(dst=f8:bc:12:44:34:b6,src=f8:bc:12:46:58:e0,dl_type=0x0800),ipv4(src=1.1.2.88,dst=1.1.2.92,proto=17,tos=0,ttl=64,frag=0x4000),udp(src=0,dst=6081,csum=0xffff),geneve(vni=0x1c7)),out_port(1))
-tnl_push(tnl_port(4),header(size=62,type=3,eth(dst=f8:bc:12:44:34:b6,src=f8:bc:12:46:58:e0,dl_type=0x86dd),ipv6(src=2001:cafe::88,dst=2001:cafe::92,label=0,proto=47,tclass=0x0,hlimit=64),gre((flags=0x2000,proto=0x6558),key=0x1e241)),out_port(1))
-tnl_push(tnl_port(4),header(size=66,type=3,eth(dst=f8:bc:12:44:34:b6,src=f8:bc:12:46:58:e0,dl_type=0x86dd),ipv6(src=2001:cafe::88,dst=2001:cafe::92,label=0,proto=47,tclass=0x0,hlimit=64),gre((flags=0xa000,proto=0x6558),csum=0x0,key=0x1e241)),out_port(1))
+tnl_push(tnl_port(4),header(size=62,type=109,eth(dst=f8:bc:12:44:34:b6,src=f8:bc:12:46:58:e0,dl_type=0x86dd),ipv6(src=2001:cafe::88,dst=2001:cafe::92,label=0,proto=47,tclass=0x0,hlimit=64),gre((flags=0x2000,proto=0x6558),key=0x1e241)),out_port(1))
+tnl_push(tnl_port(4),header(size=66,type=109,eth(dst=f8:bc:12:44:34:b6,src=f8:bc:12:46:58:e0,dl_type=0x86dd),ipv6(src=2001:cafe::88,dst=2001:cafe::92,label=0,proto=47,tclass=0x0,hlimit=64),gre((flags=0xa000,proto=0x6558),csum=0x0,key=0x1e241)),out_port(1))
tnl_push(tnl_port(6),header(size=70,type=4,eth(dst=f8:bc:12:44:34:b6,src=f8:bc:12:46:58:e0,dl_type=0x86dd),ipv6(src=2001:cafe::88,dst=2001:cafe::92,label=0,proto=17,tclass=0x0,hlimit=64),udp(src=0,dst=4789,csum=0x0),vxlan(flags=0x8000000,vni=0x1c7)),out_port(1))
tnl_push(tnl_port(6),header(size=70,type=5,eth(dst=f8:bc:12:44:34:b6,src=f8:bc:12:46:58:e0,dl_type=0x86dd),ipv6(src=2001:cafe::88,dst=2001:cafe::92,label=0,proto=17,tclass=0x0,hlimit=64),udp(src=0,dst=6081,csum=0x0),geneve(oam,vni=0x1c7)),out_port(1))
tnl_push(tnl_port(6),header(size=78,type=5,eth(dst=f8:bc:12:44:34:b6,src=f8:bc:12:46:58:e0,dl_type=0x86dd),ipv6(src=2001:cafe::88,dst=2001:cafe::92,label=0,proto=17,tclass=0x0,hlimit=64),udp(src=0,dst=6081,csum=0x0),geneve(crit,vni=0x1c7,options({class=0xffff,type=0x80,len=4,0xa}))),out_port(1))
diff --git a/tests/ofproto.at b/tests/ofproto.at
index fff06eb0d..26a6ff698 100644
--- a/tests/ofproto.at
+++ b/tests/ofproto.at
@@ -2454,7 +2454,7 @@ head_table () {
instructions: meter,apply_actions,clear_actions,write_actions,write_metadata,goto_table
Write-Actions and Apply-Actions features:
actions: output group set_field strip_vlan push_vlan mod_nw_ttl dec_ttl set_mpls_ttl dec_mpls_ttl push_mpls pop_mpls set_queue
- supported on Set-Field: tun_id tun_src tun_dst tun_ipv6_src tun_ipv6_dst tun_flags tun_gbp_id tun_gbp_flags tun_metadata0 dnl
+ supported on Set-Field: tun_id tun_src tun_dst tun_ipv6_src tun_ipv6_dst tun_flags tun_gbp_id tun_gbp_flags tun_erspan_idx tun_erspan_ver tun_erspan_dir tun_erspan_hwid tun_metadata0 dnl
tun_metadata1 tun_metadata2 tun_metadata3 tun_metadata4 tun_metadata5 tun_metadata6 tun_metadata7 tun_metadata8 tun_metadata9 tun_metadata10 tun_metadata11 tun_metadata12 tun_metadata13 tun_metadata14 tun_metadata15 tun_metadata16 tun_metadata17 tun_metadata18 tun_metadata19 tun_metadata20 tun_metadata21 tun_metadata22 tun_metadata23 tun_metadata24 tun_metadata25 tun_metadata26 tun_metadata27 tun_metadata28 tun_metadata29 tun_metadata30 tun_metadata31 tun_metadata32 tun_metadata33 tun_metadata34 tun_metadata35 tun_metadata36 tun_metadata37 tun_metadata38 tun_metadata39 tun_metadata40 tun_metadata41 tun_metadata42 tun_metadata43 tun_metadata44 tun_metadata45 tun_metadata46 tun_metadata47 tun_metadata48 tun_metadata49 tun_metadata50 tun_metadata51 tun_metadata52 tun_metadata53 tun_metadata54 tun_metadata55 tun_metadata56 tun_metadata57 tun_metadata58 tun_metadata59 tun_metadata60 tun_metadata61 tun_metadata62 tun_metadata63 dnl
metadata in_port in_port_oxm pkt_mark ct_mark ct_label reg0 reg1 reg2 reg3 reg4 reg5 reg6 reg7 reg8 reg9 reg10 reg11 reg12 reg13 reg14 reg15 xreg0 xreg1 xreg2 xreg3 xreg4 xreg5 xreg6 xreg7 xxreg0 xxreg1 xxreg2 xxreg3 eth_src eth_dst vlan_tci vlan_vid vlan_pcp mpls_label mpls_tc mpls_ttl ip_src ip_dst ipv6_src ipv6_dst ipv6_label nw_tos ip_dscp nw_ecn nw_ttl arp_op arp_spa arp_tpa arp_sha arp_tha tcp_src tcp_dst udp_src udp_dst sctp_src sctp_dst icmp_type icmp_code icmpv6_type icmpv6_code nd_target nd_sll nd_tll nsh_flags nsh_spi nsh_si nsh_c1 nsh_c2 nsh_c3 nsh_c4 nsh_ttl
matching:
@@ -2470,6 +2470,10 @@ metadata in_port in_port_oxm pkt_mark ct_mark ct_label reg0 reg1 reg2 reg3 reg4
tun_flags: arbitrary mask
tun_gbp_id: arbitrary mask
tun_gbp_flags: arbitrary mask
+ tun_erspan_idx: arbitrary mask
+ tun_erspan_ver: arbitrary mask
+ tun_erspan_dir: arbitrary mask
+ tun_erspan_hwid: arbitrary mask
tun_metadata0: arbitrary mask
tun_metadata1: arbitrary mask
tun_metadata2: arbitrary mask
diff --git a/tests/ovn-controller.at b/tests/ovn-controller.at
index 00900d43d..ecc6a50da 100644
--- a/tests/ovn-controller.at
+++ b/tests/ovn-controller.at
@@ -158,7 +158,8 @@ OVS_WAIT_UNTIL([check_datapath_type bar])
ovs-vsctl set Bridge br-int datapath-type=\"\"
OVS_WAIT_UNTIL([check_datapath_type ""])
-expected_iface_types=$(ovs-vsctl get Open_vSwitch . iface_types|sed 's/[[]][[ ]]//g')
+expected_iface_types=$(ovs-vsctl get Open_vSwitch . iface_types | tr -d '[[]] ""')
+echo "expected_iface_types = ${expected_iface_types}"
chassis_iface_types=$(ovn-sbctl get Chassis ${sysid} external_ids:iface-types | sed -e 's/\"//g')
echo "chassis_iface_types = ${chassis_iface_types}"
AT_CHECK([test "${expected_iface_types}" = "${chassis_iface_types}"])
diff --git a/tests/tunnel-push-pop-ipv6.at b/tests/tunnel-push-pop-ipv6.at
index 772f62c14..aff5fa74c 100644
--- a/tests/tunnel-push-pop-ipv6.at
+++ b/tests/tunnel-push-pop-ipv6.at
@@ -1,5 +1,124 @@
AT_BANNER([tunnel_push_pop_ipv6])
+AT_SETUP([tunnel_push_pop_ipv6 - ip6erspan])
+
+OVS_VSWITCHD_START([add-port br0 p0 -- set Interface p0 type=dummy ofport_request=1 other-config:hwaddr=aa:55:aa:55:00:00])
+AT_CHECK([ovs-vsctl add-br int-br -- set bridge int-br datapath_type=dummy], [0])
+AT_CHECK([ovs-vsctl add-port int-br t2 -- set Interface t2 type=ip6erspan \
+ options:remote_ip=2001:cafe::92 options:key=123 \
+ options:erspan_ver=1 options:erspan_idx=3 ofport_request=2\
+ -- add-port int-br t3 -- set Interface t3 type=ip6erspan \
+ options:remote_ip=2001:cafe::93 options:key=567 \
+ options:erspan_ver=2 options:erspan_dir=1 options:erspan_hwid=0x7 \
+ ofport_request=3\
+ ], [0])
+
+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)
+ int-br:
+ int-br 65534/2: (dummy-internal)
+ t2 2/6: (ip6erspan: erspan_idx=0x3, erspan_ver=1, key=123, remote_ip=2001:cafe::92)
+ t3 3/6: (ip6erspan: erspan_dir=1, erspan_hwid=0x7, erspan_ver=2, key=567, remote_ip=2001:cafe::93)
+])
+
+dnl First setup dummy interface IP address, then add the route
+dnl so that tnl-port table can get valid IP address for the device.
+AT_CHECK([ovs-appctl netdev-dummy/ip6addr br0 2001:cafe::88/24], [0], [OK
+])
+AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 1.1.2.88/24], [0], [OK
+])
+AT_CHECK([ovs-appctl ovs/route/add 2001:cafe::92/24 br0], [0], [OK
+])
+
+AT_CHECK([ovs-ofctl add-flow br0 action=normal])
+
+dnl Check Neighbour discovery.
+AT_CHECK([ovs-vsctl -- set Interface p0 options:pcap=p0.pcap])
+
+AT_CHECK([ovs-appctl netdev-dummy/receive int-br 'in_port(2),eth(src=aa:55:aa:55:00:00,dst=f8:bc:12:ff:ff:ff),eth_type(0x0800),ipv4(src=1.1.3.92,dst=1.1.3.88,proto=1,tos=0,ttl=64,frag=no),icmp(type=0,code=0)'])
+AT_CHECK([ovs-pcap p0.pcap > p0.pcap.txt 2>&1])
+
+AT_CHECK([cat p0.pcap.txt | grep 92aa55aa55000086dd6000000000203aff2001cafe | uniq], [0], [dnl
+3333ff000092aa55aa55000086dd6000000000203aff2001cafe000000000000000000000088ff0200000000000000000001ff00009287004d48000000002001cafe0000000000000000000000920101aa55aa550000
+])
+AT_CHECK([cat p0.pcap.txt | grep 93aa55aa55000086dd6000000000203aff2001cafe | uniq], [0], [dnl
+3333ff000093aa55aa55000086dd6000000000203aff2001cafe000000000000000000000088ff0200000000000000000001ff00009387004d46000000002001cafe0000000000000000000000930101aa55aa550000
+])
+
+
+dnl
+AT_CHECK([ovs-appctl netdev-dummy/receive p0 'in_port(1),eth(src=f8:bc:12:44:34:b6,dst=aa:55:aa:55:00:00),eth_type(0x86dd),ipv6(src=2001:cafe::92,dst=2001:cafe::88,label=0,proto=58,tclass=0,hlimit=255,frag=no),icmpv6(type=136,code=0),nd(target=2001:cafe::92,sll=00:00:00:00:00:00,tll=f8:bc:12:44:34:b6)'])
+dnl
+AT_CHECK([ovs-appctl netdev-dummy/receive p0 'in_port(1),eth(src=f8:bc:12:44:34:b7,dst=aa:55:aa:55:00:00),eth_type(0x86dd),ipv6(src=2001:cafe::93,dst=2001:cafe::88,label=0,proto=58,tclass=0,hlimit=255,frag=no),icmpv6(type=136,code=0),nd(target=2001:cafe::93,sll=00:00:00:00:00:00,tll=f8:bc:12:44:34:b7)'])
+
+dnl Check ARP Snoop
+dnl AT_CHECK([ovs-appctl netdev-dummy/receive p0 'in_port(1),eth(src=f8:bc:12:44:34:b6,dst=aa:55:aa:55:00:00),eth_type(0x86dd),ipv6(src=2001:cafe::92,dst=2001:cafe::94,label=0,proto=58,tclass=0,hlimit=255,frag=no),icmpv6(type=136,code=0),nd(target=2001:cafe::92,sll=00:00:00:00:00:00,tll=f8:bc:12:44:34:b6)'])
+
+dnl AT_CHECK([ovs-appctl netdev-dummy/receive p0 'in_port(1),eth(src=f8:bc:12:44:34:b7,dst=aa:55:aa:55:00:00),eth_type(0x86dd),ipv6(src=2001:cafe::93,dst=2001:cafe::94,label=0,proto=58,tclass=0,hlimit=255,frag=no),icmpv6(type=136,code=0),nd(target=2001:cafe::93,sll=00:00:00:00:00:00,tll=f8:bc:12:44:34:b7)'])
+
+ovs-appctl time/warp 1000
+ovs-appctl time/warp 1000
+
+AT_CHECK([ovs-appctl tnl/arp/show | tail -n+3 | sort], [0], [dnl
+2001:cafe::92 f8:bc:12:44:34:b6 br0
+2001:cafe::93 f8:bc:12:44:34:b7 br0
+])
+
+AT_CHECK([ovs-appctl tnl/ports/show |sort], [0], [dnl
+Listening ports:
+ip6erspan_sys (6) ref_cnt=2
+])
+
+dnl Check ERSPAN tunnel pop
+AT_CHECK([ovs-appctl ofproto/trace ovs-dummy 'in_port(1),eth(src=f8:bc:12:44:34:b6,dst=aa:55:aa:55:00:00),eth_type(0x86dd),ipv6(src=2001:cafe::92,dst=2001:cafe::88,label=0,proto=47,tclass=0x0,hlimit=64)'], [0], [stdout])
+AT_CHECK([tail -1 stdout], [0],
+ [Datapath actions: tnl_pop(6)
+])
+
+dnl Check ERSPAN v1 tunnel push
+AT_CHECK([ovs-ofctl add-flow int-br action=2])
+AT_CHECK([ovs-appctl ofproto/trace ovs-dummy 'in_port(2),eth(src=f8:bc:12:44:34:b6,dst=aa:55:aa:55:00:01),eth_type(0x0800),ipv4(src=1.1.3.88,dst=1.1.3.112,proto=47,tos=0,ttl=64,frag=no)'], [0], [stdout])
+AT_CHECK([tail -1 stdout], [0],
+ [Datapath actions: clone(tnl_push(tnl_port(6),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::88,dst=2001:cafe::92,label=0,proto=47,tclass=0x0,hlimit=64),erspan(ver=1,sid=0x7b,idx=0x3)),out_port(100)),1)
+])
+
+dnl Check ERSPAN v2 tunnel push
+AT_CHECK([ovs-ofctl mod-flows int-br action=3])
+AT_CHECK([ovs-appctl ofproto/trace ovs-dummy 'in_port(2),eth(src=f8:bc:12:44:34:b6,dst=aa:55:aa:55:00:01),eth_type(0x0800),ipv4(src=1.1.3.88,dst=1.1.3.112,proto=47,tos=0,ttl=64,frag=no)'], [0], [stdout])
+AT_CHECK([tail -1 stdout], [0],
+ [Datapath actions: clone(tnl_push(tnl_port(6),header(size=74,type=108,eth(dst=f8:bc:12:44:34:b7,src=aa:55:aa:55:00:00,dl_type=0x86dd),ipv6(src=2001:cafe::88,dst=2001:cafe::93,label=0,proto=47,tclass=0x0,hlimit=64),erspan(ver=2,sid=0x237,dir=1,hwid=0x7)),out_port(100)),1)
+])
+
+ovs-appctl vlog/set dbg
+dnl Check decapsulation of ERSPAN v1
+dnl Hex dump: GRE:(100088be)
+dnl ERSPAN: v1, session id = 0x7b (1000007b), index=3 (00000003)
+AT_CHECK([ovs-appctl netdev-dummy/receive p0 'aa55aa550000001b213cab6486dd60000000006a2f402001cafe0000000000000000000000922001cafe000000000000000000000088100088be000000011000007b00000003fe71d883724fbeb6f4e1494a080045000054ba200000400184861e0000011e00000200004227e75400030af3195500000000f265010000000000101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031323334353637'])
+AT_CHECK([ovs-appctl netdev-dummy/receive p0 'aa55aa550000001b213cab6486dd60000000006a2f402001cafe0000000000000000000000922001cafe000000000000000000000088100088be000000011000007b00000003fe71d883724fbeb6f4e1494a080045000054ba200000400184861e0000011e00000200004227e75400030af3195500000000f265010000000000101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031323334353637'])
+
+ovs-appctl time/warp 1000
+
+AT_CHECK([ovs-ofctl dump-ports int-br | grep 'port 2'], [0], [dnl
+ port 2: rx pkts=2, bytes=196, drop=?, errs=?, frame=?, over=?, crc=?
+])
+
+dnl Check decapsulation ERSPAN v2
+dnl Hex dump: GRE:(100022eb)
+dnl ERSPAN: v2, session id = 0x237 (20000237), hwid = 8,dir = 1 (00000078)
+AT_CHECK([ovs-appctl netdev-dummy/receive p0 'aa55aa550000001b213cab6486dd60000000006a2f402001cafe0000000000000000000000932001cafe000000000000000000000088100022eb000000012000023710abcd0100000078fe71d883724fbeb6f4e1494a080045000054ba200000400184861e0000011e00000200004227e75400030af3195500000000f265010000000000101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031323334353637'])
+
+ovs-appctl time/warp 1000
+
+AT_CHECK([ovs-ofctl dump-ports int-br | grep 'port 3'], [0], [dnl
+ port 3: rx pkts=1, bytes=98, drop=?, errs=?, frame=?, over=?, crc=?
+])
+
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
AT_SETUP([tunnel_push_pop_ipv6 - action])
OVS_VSWITCHD_START([add-port br0 p0 -- set Interface p0 type=dummy ofport_request=1 other-config:hwaddr=aa:55:aa:55:00:00])
@@ -30,6 +149,14 @@ dummy@ovs-dummy: hit:0 missed:0
t5 6/3: (gre: key=455, packet_type=legacy_l3, remote_ip=2001:cafe::92)
])
+AT_CHECK([ovs-appctl tnl/ports/show |sort], [0], [dnl
+Listening ports:
+genev_sys_6081 (6081) ref_cnt=1
+gre_sys (3) ref_cnt=2
+vxlan_sys_4789 (4789) ref_cnt=2
+])
+
+
dnl First setup dummy interface IP address, then add the route
dnl so that tnl-port table can get valid IP address for the device.
AT_CHECK([ovs-appctl netdev-dummy/ip6addr br0 2001:cafe::88/24], [0], [OK
@@ -176,7 +303,7 @@ dnl Check GRE tunnel push
AT_CHECK([ovs-ofctl add-flow int-br action=3])
AT_CHECK([ovs-appctl ofproto/trace ovs-dummy 'in_port(2),eth(src=f8:bc:12:44:34:b6,dst=aa:55:aa:55:00:01),eth_type(0x0800),ipv4(src=1.1.3.88,dst=1.1.3.112,proto=47,tos=0,ttl=64,frag=no)'], [0], [stdout])
AT_CHECK([tail -1 stdout], [0],
- [Datapath actions: clone(tnl_push(tnl_port(3),header(size=62,type=3,eth(dst=f8:bc:12:44:34:b6,src=aa:55:aa:55:00:00,dl_type=0x86dd),ipv6(src=2001:cafe::88,dst=2001:cafe::92,label=0,proto=47,tclass=0x0,hlimit=64),gre((flags=0x2000,proto=0x6558),key=0x1c8)),out_port(100)),1)
+ [Datapath actions: clone(tnl_push(tnl_port(3),header(size=62,type=109,eth(dst=f8:bc:12:44:34:b6,src=aa:55:aa:55:00:00,dl_type=0x86dd),ipv6(src=2001:cafe::88,dst=2001:cafe::92,label=0,proto=47,tclass=0x0,hlimit=64),gre((flags=0x2000,proto=0x6558),key=0x1c8)),out_port(100)),1)
])
dnl Check Geneve tunnel push
diff --git a/tests/tunnel-push-pop.at b/tests/tunnel-push-pop.at
index 4bd412e85..a3b284a6c 100644
--- a/tests/tunnel-push-pop.at
+++ b/tests/tunnel-push-pop.at
@@ -1,5 +1,195 @@
AT_BANNER([tunnel_push_pop])
+AT_SETUP([tunnel_push_pop - erspan])
+OVS_VSWITCHD_START([add-port br0 p0 -- set Interface p0 type=dummy ofport_request=1 other-config:hwaddr=aa:55:aa:55:00:00])
+AT_CHECK([ovs-vsctl add-br int-br -- set bridge int-br datapath_type=dummy], [0])
+AT_CHECK([ovs-vsctl add-port int-br t1 -- set Interface t1 type=erspan \
+ options:remote_ip=1.1.2.92 options:key=123 options:erspan_ver=1 \
+ options:erspan_idx=3 ofport_request=2 \
+ -- add-port int-br t2 -- set Interface t2 type=erspan \
+ options:remote_ip=1.1.2.92 options:key=567 options:erspan_ver=2 \
+ options:erspan_dir=1 options:erspan_hwid=0x7 ofport_request=3\
+ ], [0])
+
+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)
+ int-br:
+ int-br 65534/2: (dummy-internal)
+ t1 2/3: (erspan: erspan_idx=0x3, erspan_ver=1, key=123, remote_ip=1.1.2.92)
+ t2 3/3: (erspan: erspan_dir=1, erspan_hwid=0x7, erspan_ver=2, key=567, remote_ip=1.1.2.92)
+])
+
+dnl First setup dummy interface IP address, then add the route
+dnl so that tnl-port table can get valid IP address for the device.
+AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 1.1.2.88/24], [0], [OK
+])
+AT_CHECK([ovs-appctl netdev-dummy/ip6addr br0 2001:cafe::88/24], [0], [OK
+])
+
+AT_CHECK([ovs-appctl ovs/route/add 1.1.2.92/24 br0], [0], [OK
+])
+
+AT_CHECK([ovs-appctl ovs/route/add 1.1.2.92/24 br0 pkt_mark=1234], [0], [OK
+])
+
+AT_CHECK([ovs-ofctl add-flow br0 action=normal])
+
+dnl Check ARP request
+AT_CHECK([ovs-vsctl -- set Interface p0 options:pcap=p0.pcap])
+
+AT_CHECK([ovs-appctl netdev-dummy/receive int-br 'in_port(2),eth(src=aa:55:aa:55:00:00,dst=f8:bc:12:ff:ff:ff),eth_type(0x0800),ipv4(src=1.1.3.92,dst=1.1.3.88,proto=1,tos=0,ttl=64,frag=no),icmp(type=0,code=0)'])
+
+dnl Wait for the two ARP requests to be sent. Sometimes the system
+dnl can be slow (e.g. under valgrind)
+OVS_WAIT_UNTIL([test `ovs-pcap p0.pcap | sort | uniq | wc -l` -ge 1])
+
+AT_CHECK([ovs-pcap p0.pcap > p0.pcap.txt 2>&1])
+
+AT_CHECK([cat p0.pcap.txt | grep 101025 | uniq], [0], [dnl
+ffffffffffffaa55aa55000008060001080006040001aa55aa550000010102580000000000000101025c
+])
+
+
+dnl Check ARP Snoop
+AT_CHECK([ovs-appctl netdev-dummy/receive p0 'recirc_id(0),in_port(100),eth(src=f8:bc:12:44:34:b6,dst=ff:ff:ff:ff:ff:ff),eth_type(0x0806),arp(sip=1.1.2.92,tip=1.1.2.88,op=2,sha=f8:bc:12:44:34:b6,tha=00:00:00:00:00:00)'])
+AT_CHECK([ovs-appctl netdev-dummy/receive p0 'recirc_id(0),in_port(100),eth(src=f8:bc:12:44:34:b7,dst=ff:ff:ff:ff:ff:ff),eth_type(0x0806),arp(sip=1.1.2.93,tip=1.1.2.88,op=2,sha=f8:bc:12:44:34:b7,tha=00:00:00:00:00:00)'])
+AT_CHECK([ovs-appctl netdev-dummy/receive p0 'recirc_id(0),in_port(100),eth(src=f8:bc:12:44:34:b8,dst=ff:ff:ff:ff:ff:ff),eth_type(0x0806),arp(sip=1.1.2.94,tip=1.1.2.88,op=2,sha=f8:bc:12:44:34:b8,tha=00:00:00:00:00:00)'])
+
+ovs-appctl time/warp 1000
+ovs-appctl time/warp 1000
+
+AT_CHECK([ovs-appctl tnl/neigh/show | tail -n+3 | sort], [0], [dnl
+1.1.2.92 f8:bc:12:44:34:b6 br0
+1.1.2.93 f8:bc:12:44:34:b7 br0
+1.1.2.94 f8:bc:12:44:34:b8 br0
+])
+
+AT_CHECK([ovs-appctl tnl/ports/show |sort], [0], [dnl
+Listening ports:
+erspan_sys (3) ref_cnt=2
+])
+
+dnl Check ERSPAN v1 tunnel push
+AT_CHECK([ovs-vsctl -- set Interface br0 options:pcap=br0.pcap])
+AT_CHECK([ovs-ofctl add-flow int-br action=2])
+AT_CHECK([ovs-appctl ofproto/trace ovs-dummy 'in_port(2),eth(src=f8:bc:12:44:34:b6,dst=aa:55:aa:55:00:00),eth_type(0x0800),ipv4(src=1.1.3.88,dst=1.1.3.112,proto=47,tos=0,ttl=64,frag=no)'], [0], [stdout])
+AT_CHECK([tail -1 stdout], [0],
+ [Datapath actions: clone(tnl_push(tnl_port(3),header(size=50,type=107,eth(dst=f8:bc:12:44:34:b6,src=aa:55:aa:55:00:00,dl_type=0x0800),ipv4(src=1.1.2.88,dst=1.1.2.92,proto=47,tos=0,ttl=64,frag=0x4000),erspan(ver=1,sid=0x7b,idx=0x3)),out_port(100)),1)
+])
+
+dnl Check ERSPAN v2 tunnel push
+AT_CHECK([ovs-ofctl mod-flows int-br action=3])
+AT_CHECK([ovs-appctl revalidator/wait])
+AT_CHECK([ovs-appctl ofproto/trace ovs-dummy 'in_port(2),eth(src=f8:bc:12:44:34:b6,dst=aa:55:aa:55:00:00),eth_type(0x0800),ipv4(src=1.1.3.88,dst=1.1.3.112,proto=47,tos=0,ttl=64,frag=no)'], [0], [stdout])
+AT_CHECK([tail -1 stdout], [0],
+ [Datapath actions: clone(tnl_push(tnl_port(3),header(size=54,type=107,eth(dst=f8:bc:12:44:34:b6,src=aa:55:aa:55:00:00,dl_type=0x0800),ipv4(src=1.1.2.88,dst=1.1.2.92,proto=47,tos=0,ttl=64,frag=0x4000),erspan(ver=2,sid=0x237,dir=1,hwid=0x7)),out_port(100)),1)
+])
+
+dnl Check ERSPAN v2 flow-based tunnel push
+AT_CHECK([ovs-ofctl mod-flows int-br "action=set_field:1.1.2.94->tun_dst,set_field:1->tun_erspan_dir,set_field:0x1->tun_erspan_hwid,4"])
+AT_CHECK([ovs-appctl revalidator/wait])
+AT_CHECK([ovs-appctl ofproto/trace ovs-dummy 'in_port(2),eth(src=f8:bc:12:44:34:b6,dst=aa:55:aa:55:00:00),eth_type(0x0800),ipv4(src=1.1.3.88,dst=1.1.3.112,proto=47,tos=0,ttl=64,frag=no)'], [0], [stdout])
+AT_CHECK([tail -1 stdout], [0],
+ [Datapath actions: clone(tnl_push(tnl_port(3),header(size=54,type=107,eth(dst=f8:bc:12:44:34:b8,src=aa:55:aa:55:00:00,dl_type=0x0800),ipv4(src=1.1.2.88,dst=1.1.2.94,proto=47,tos=0,ttl=64,frag=0x4000),erspan(ver=2,sid=0x1c8,dir=1,hwid=0x1)),out_port(100)),1)
+])
+
+dnl Check ERSPAN v2 flow-based tunnel push, erspan_ver=flow
+dnl Dynamically set erspan v2
+AT_CHECK([ovs-ofctl mod-flows int-br "action=set_field:1.1.2.94->tun_dst,set_field:2->tun_erspan_ver,set_field:1->tun_erspan_dir,set_field:0x1->tun_erspan_hwid,5"])
+AT_CHECK([ovs-appctl revalidator/wait])
+AT_CHECK([ovs-appctl ofproto/trace ovs-dummy 'in_port(2),eth(src=f8:bc:12:44:34:b6,dst=aa:55:aa:55:00:00),eth_type(0x0800),ipv4(src=1.1.3.88,dst=1.1.3.112,proto=47,tos=0,ttl=64,frag=no)'], [0], [stdout])
+AT_CHECK([tail -1 stdout], [0],
+ [Datapath actions: clone(tnl_push(tnl_port(3),header(size=54,type=107,eth(dst=f8:bc:12:44:34:b8,src=aa:55:aa:55:00:00,dl_type=0x0800),ipv4(src=1.1.2.88,dst=1.1.2.94,proto=47,tos=0,ttl=64,frag=0x4000),erspan(ver=2,sid=0x38,dir=1,hwid=0x1)),out_port(100)),1)
+])
+
+dnl Dynamically set erspan v1
+AT_CHECK([ovs-ofctl mod-flows int-br "action=set_field:1.1.2.94->tun_dst,set_field:1->tun_erspan_ver,set_field:1->tun_erspan_idx,5"])
+AT_CHECK([ovs-appctl revalidator/wait])
+AT_CHECK([ovs-appctl ofproto/trace ovs-dummy 'in_port(2),eth(src=f8:bc:12:44:34:b6,dst=aa:55:aa:55:00:00),eth_type(0x0800),ipv4(src=1.1.3.88,dst=1.1.3.112,proto=47,tos=0,ttl=64,frag=no)'], [0], [stdout])
+AT_CHECK([tail -1 stdout], [0],
+ [Datapath actions: clone(tnl_push(tnl_port(3),header(size=50,type=107,eth(dst=f8:bc:12:44:34:b8,src=aa:55:aa:55:00:00,dl_type=0x0800),ipv4(src=1.1.2.88,dst=1.1.2.94,proto=47,tos=0,ttl=64,frag=0x4000),erspan(ver=1,sid=0x38,idx=0x1)),out_port(100)),1)
+])
+
+dnl Check ERSPAN v2 flow-based tunnel push
+AT_CHECK([ovs-ofctl mod-flows int-br "action=set_field:1.1.2.94->tun_dst,set_field:1->tun_erspan_dir,set_field:0x1->tun_erspan_hwid,4"])
+AT_CHECK([ovs-appctl revalidator/wait])
+AT_CHECK([ovs-appctl ofproto/trace ovs-dummy 'in_port(2),eth(src=f8:bc:12:44:34:b6,dst=aa:55:aa:55:00:00),eth_type(0x0800),ipv4(src=1.1.3.88,dst=1.1.3.112,proto=47,tos=0,ttl=64,frag=no)'], [0], [stdout])
+AT_CHECK([tail -1 stdout], [0],
+ [Datapath actions: clone(tnl_push(tnl_port(3),header(size=54,type=107,eth(dst=f8:bc:12:44:34:b8,src=aa:55:aa:55:00:00,dl_type=0x0800),ipv4(src=1.1.2.88,dst=1.1.2.94,proto=47,tos=0,ttl=64,frag=0x4000),erspan(ver=2,sid=0x1c8,dir=1,hwid=0x1)),out_port(100)),1)
+])
+
+dnl Check ERSPAN v2 flow-based tunnel push, erspan_ver=flow
+dnl Dynamically set erspan v2
+AT_CHECK([ovs-ofctl mod-flows int-br "action=set_field:1.1.2.94->tun_dst,set_field:2->tun_erspan_ver,set_field:1->tun_erspan_dir,set_field:0x1->tun_erspan_hwid,5"])
+AT_CHECK([ovs-appctl revalidator/wait])
+AT_CHECK([ovs-appctl ofproto/trace ovs-dummy 'in_port(2),eth(src=f8:bc:12:44:34:b6,dst=aa:55:aa:55:00:00),eth_type(0x0800),ipv4(src=1.1.3.88,dst=1.1.3.112,proto=47,tos=0,ttl=64,frag=no)'], [0], [stdout])
+AT_CHECK([tail -1 stdout], [0],
+ [Datapath actions: clone(tnl_push(tnl_port(3),header(size=54,type=107,eth(dst=f8:bc:12:44:34:b8,src=aa:55:aa:55:00:00,dl_type=0x0800),ipv4(src=1.1.2.88,dst=1.1.2.94,proto=47,tos=0,ttl=64,frag=0x4000),erspan(ver=2,sid=0x38,dir=1,hwid=0x1)),out_port(100)),1)
+])
+
+dnl Dynamically set erspan v1
+AT_CHECK([ovs-ofctl mod-flows int-br "action=set_field:1.1.2.94->tun_dst,set_field:1->tun_erspan_ver,set_field:1->tun_erspan_idx,5"])
+AT_CHECK([ovs-appctl revalidator/wait])
+AT_CHECK([ovs-appctl ofproto/trace ovs-dummy 'in_port(2),eth(src=f8:bc:12:44:34:b6,dst=aa:55:aa:55:00:00),eth_type(0x0800),ipv4(src=1.1.3.88,dst=1.1.3.112,proto=47,tos=0,ttl=64,frag=no)'], [0], [stdout])
+AT_CHECK([tail -1 stdout], [0],
+ [Datapath actions: clone(tnl_push(tnl_port(3),header(size=50,type=107,eth(dst=f8:bc:12:44:34:b8,src=aa:55:aa:55:00:00,dl_type=0x0800),ipv4(src=1.1.2.88,dst=1.1.2.94,proto=47,tos=0,ttl=64,frag=0x4000),erspan(ver=1,sid=0x38,idx=0x1)),out_port(100)),1)
+])
+
+dnl Check ERSPAN tunnel pop
+AT_CHECK([ovs-appctl ofproto/trace ovs-dummy 'in_port(1),eth(src=f8:bc:12:44:34:b6,dst=aa:55:aa:55:00:00),eth_type(0x0800),ipv4(src=1.1.11.93,dst=1.1.2.88,proto=47,tos=0,ttl=64,frag=no)'], [0], [stdout])
+AT_CHECK([tail -1 stdout], [0],
+ [Datapath actions: tnl_pop(3)
+])
+
+AT_CHECK([ovs-ofctl del-flows int-br])
+
+dnl Check decapsulation of ERSPAN v1
+dnl Hex dump: GRE:(100088be)
+dnl ERSPAN: v1, session id = 0x7b (1000007b), index=3 (00000003)
+AT_CHECK([ovs-appctl netdev-dummy/receive p0 'aa55aa550000001b213cab6408004500007e79464000402fba550101025c01010258100088be000000011000007b00000003fe71d883724fbeb6f4e1494a080045000054ba200000400184861e0000011e00000200004227e75400030af3195500000000f265010000000000101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031323334353637'])
+
+AT_CHECK([ovs-appctl revalidator/wait])
+AT_CHECK([ovs-ofctl dump-ports int-br | grep 'port 2'], [0], [dnl
+ port 2: rx pkts=1, bytes=98, drop=?, errs=?, frame=?, over=?, crc=?
+])
+
+dnl Check decapsulation ERSPAN v2
+dnl Hex dump: GRE:(100022eb)
+dnl ERSPAN: v2, session id = 0x237 (20000237), hwid =7, dir = 1 (00000078)
+AT_CHECK([ovs-appctl netdev-dummy/receive p0 'aa55aa550000001b213cab6408004500007e79464000402fba550101025c01010258100022eb000000012000023710abcd0100000078fe71d883724fbeb6f4e1494a080045000054ba200000400184861e0000011e00000200004227e75400030af3195500000000f265010000000000101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031323334353637'])
+
+AT_CHECK([ovs-appctl revalidator/wait])
+AT_CHECK([ovs-ofctl dump-ports int-br | grep 'port 3'], [0], [dnl
+ port 3: rx pkts=1, bytes=98, drop=?, errs=?, frame=?, over=?, crc=?
+])
+
+dnl Check ERSPAN encap in pcap file
+dnl This ARP reply from p0 has two effects:
+dnl 1. The ARP cache will learn that 1.1.2.92 is at f8:bc:12:44:34:b6.
+dnl 2. The br0 mac learning will learn that f8:bc:12:44:34:b6 is on p0.
+AT_CHECK([ovs-appctl netdev-dummy/receive p0 'recirc_id(0),in_port(2),eth(src=f8:bc:12:44:34:b6,dst=ff:ff:ff:ff:ff:ff),eth_type(0x0806),arp(sip=1.1.2.92,tip=1.1.2.88,op=2,sha=f8:bc:12:44:34:b6,tha=00:00:00:00:00:00)'])
+
+AT_CHECK([ovs-vsctl -- set Interface p0 options:tx_pcap=p0.pcap])
+
+dnl Output to tunnel from a int-br internal port
+AT_CHECK([ovs-ofctl add-flow int-br "in_port=LOCAL,actions=output:2"])
+AT_CHECK([ovs-appctl revalidator/wait])
+AT_CHECK([ovs-appctl netdev-dummy/receive int-br '50540000000a5054000000091234'])
+dnl 100088be: GRE with ERSPANv1, 00000001: Seqno, 1000007b: v1 with 0x7b session ID (key)
+OVS_WAIT_UNTIL([test `ovs-pcap p0.pcap | grep 100088be000000011000007b | wc -l` -ge 1])
+
+AT_CHECK([ovs-ofctl mod-flows int-br "in_port=LOCAL,actions=output:3"])
+AT_CHECK([ovs-appctl revalidator/wait])
+AT_CHECK([ovs-appctl netdev-dummy/receive int-br '50540000000a5054000000091235'])
+dnl 100022eb: GRE with ERSPANv2, 00000001: Seqno, 20000237: v2 with 0x237 session ID (key)
+OVS_WAIT_UNTIL([test `ovs-pcap p0.pcap | grep 100022eb0000000120000237 | wc -l` -ge 1])
+
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
AT_SETUP([tunnel_push_pop - action])
OVS_VSWITCHD_START([add-port br0 p0 -- set Interface p0 type=dummy ofport_request=1 other-config:hwaddr=aa:55:aa:55:00:00])
@@ -390,7 +580,7 @@ AT_CHECK([ovs-ofctl add-flow int-br action=3])
AT_CHECK([ovs-appctl ofproto/trace ovs-dummy 'in_port(2),eth(src=f8:bc:12:44:34:b6,dst=aa:55:aa:55:00:00),eth_type(0x0800),ipv4(src=1.1.3.88,dst=1.1.3.112,proto=17,tos=0,ttl=64,frag=no),udp(src=51283,dst=4789)'], [0], [stdout])
AT_CHECK([tail -1 stdout], [0],
- [Datapath actions: tnl_push(tnl_port(3),header(size=46,type=3,eth(dst=f8:bc:12:44:34:b6,src=aa:55:aa:55:00:00,dl_type=0x0800),ipv4(src=1.1.2.88,dst=1.1.2.92,proto=47,tos=0,ttl=64,frag=0x4000),gre((flags=0x3000,proto=0x6558),key=0x1c8,seq=0x0)),out_port(100))
+ [Datapath actions: clone(tnl_push(tnl_port(3),header(size=46,type=3,eth(dst=f8:bc:12:44:34:b6,src=aa:55:aa:55:00:00,dl_type=0x0800),ipv4(src=1.1.2.88,dst=1.1.2.92,proto=47,tos=0,ttl=64,frag=0x4000),gre((flags=0x3000,proto=0x6558),key=0x1c8,seq=0x0)),out_port(100)),1)
])
dnl Verify outer L2 and L3 header flow fields can be matched in the underlay bridge
diff --git a/tests/tunnel.at b/tests/tunnel.at
index eeadb2f93..620f4c829 100644
--- a/tests/tunnel.at
+++ b/tests/tunnel.at
@@ -406,6 +406,18 @@ AT_CHECK([ovs-appctl dpif/show | tail -n +3], [0], [dnl
OVS_VSWITCHD_STOP
AT_CLEANUP
+AT_SETUP([tunnel - ERSPAN])
+OVS_VSWITCHD_START([add-port br0 p1 -- set Interface p1 type=erspan \
+ options:remote_ip=1.1.1.1 ofport_request=1])
+
+AT_CHECK([ovs-appctl dpif/show | tail -n +3], [0], [dnl
+ br0 65534/100: (dummy-internal)
+ p1 1/1: (erspan: remote_ip=1.1.1.1)
+])
+
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
AT_SETUP([tunnel - different VXLAN UDP port])
OVS_VSWITCHD_START([add-port br0 p1 -- set Interface p1 type=vxlan \
options:remote_ip=1.1.1.1 ofport_request=1 options:dst_port=4341])
diff --git a/vswitchd/vswitch.xml b/vswitchd/vswitch.xml
index acc1b0314..3940b8de6 100644
--- a/vswitchd/vswitch.xml
+++ b/vswitchd/vswitch.xml
@@ -2633,6 +2633,40 @@
</group>
</group>
+ <group title="Tunnel Options: erspan only">
+ <p>
+ Only <code>erspan</code> interfaces support these options.
+ </p>
+ <column name="options" key="erspan_idx">
+ <p>
+ 20 bit index/port number associated with the ERSPAN traffic's
+ source port and direction (ingress/egress). This field is
+ platform dependent.
+ </p>
+ </column>
+
+ <column name="options" key="erspan_ver">
+ <p>
+ ERSPAN version: 1 for version 1 (type II)
+ or 2 for version 2 (type III).
+ </p>
+ </column>
+
+ <column name="options" key="erspan_dir">
+ <p>
+ Specifies the ERSPAN v2 mirrored traffic's direction.
+ 1 for egress traffic, and 0 for ingress traffic.
+ </p>
+ </column>
+
+ <column name="options" key="erspan_hwid">
+ <p>
+ ERSPAN hardware ID is a 6-bit unique identifier of an
+ ERSPAN v2 engine within a system.
+ </p>
+ </column>
+ </group>
+
<group title="Patch Options">
<p>
These options apply only to <dfn>patch ports</dfn>, that is, interfaces