[ovs-dev] [PATCH v2] ipfix: support tunnel information for Flow IPFIX

Ben Pfaff blp at ovn.org
Thu Jun 9 21:07:09 UTC 2016


On Wed, Jun 08, 2016 at 07:13:38PM +0800, Benli Ye wrote:
> Add support to export tunnel information for flow-based IPFIX.
> The original steps to configure flow level IPFIX:
>     1) Create a new record in Flow_Sample_Collector_Set table:
>        'ovs-vsctl -- create Flow_Sample_Collector_Set id=1 bridge="Bridge UUID"'
>     2) Add IPFIX configuration which is referred by corresponding
>        row in Flow_Sample_Collector_Set table:
>        'ovs-vsctl -- set Flow_Sample_Collector_Set
>        "Flow_Sample_Collector_Set UUID" ipfix=@i -- --id=@i create IPFIX
>        targets=\"IP:4739\" obs_domain_id=123 obs_point_id=456
>        cache_active_timeout=60 cache_max_flows=13'
>     3) Add sample action to the flows:
>        'ovs-ofctl add-flow mybridge in_port=1,
>        actions=sample'('probability=65535,collector_set_id=1,
>        obs_domain_id=123,obs_point_id=456')',output:3'

Thanks for posting v2!

I have some suggestions.  Some of them are stylistic.  The others:
  - Use ofp_port_t in struct ofpact_sample for the sampling port.
  - Choose between NXAST_RAW_SAMPLE and NXAST_RAW_SAMPLE2 when encoding
    in a manner closer to that used for other actions.
  - Parse port number using ofputil_port_from_string().
  - Reduce redundancy in formatting.
  - Improve documentation.

I'm appending an incremental diff of the changes I suggest, followed by
a full copy of the revised patch.

However, I'm still a little confused about what's going on in
compose_ipfix_action() and adjust_sample_action().  I think that an
example would go a long way to explaining it, so I looked for a test
that exercises translation of a sample action with sampling_port, but
there is none.  Will you please add one for v3?

Thanks,

Ben.

diff --git a/include/openvswitch/ofp-actions.h b/include/openvswitch/ofp-actions.h
index bfd0581..91c7ee5 100644
--- a/include/openvswitch/ofp-actions.h
+++ b/include/openvswitch/ofp-actions.h
@@ -778,11 +778,11 @@ struct ofpact_note {
  * Used for NXAST_SAMPLE and NXAST_SAMPLE2. */
 struct ofpact_sample {
     struct ofpact ofpact;
-    uint16_t probability;  // Always >0.
+    uint16_t probability;  /* Always positive. */
     uint32_t collector_set_id;
     uint32_t obs_domain_id;
     uint32_t obs_point_id;
-    uint16_t sampling_port;
+    ofp_port_t sampling_port;
 };
 
 /* OFPACT_DEC_TTL.
diff --git a/lib/ofp-actions.c b/lib/ofp-actions.c
index 845134b..71c65df 100644
--- a/lib/ofp-actions.c
+++ b/lib/ofp-actions.c
@@ -4730,8 +4730,7 @@ decode_NXAST_RAW_SAMPLE(const struct nx_action_sample *nas,
     sample->collector_set_id = ntohl(nas->collector_set_id);
     sample->obs_domain_id = ntohl(nas->obs_domain_id);
     sample->obs_point_id = ntohl(nas->obs_point_id);
-    /* Default value for sampling port is OFPP_NONE */
-    sample->sampling_port = ofp_to_u16(OFPP_NONE);
+    sample->sampling_port = OFPP_NONE;
 
     if (sample->probability == 0) {
         return OFPERR_OFPBAC_BAD_ARGUMENT;
@@ -4753,7 +4752,7 @@ decode_NXAST_RAW_SAMPLE2(const struct nx_action_sample2 *nas,
     sample->collector_set_id = ntohl(nas->collector_set_id);
     sample->obs_domain_id = ntohl(nas->obs_domain_id);
     sample->obs_point_id = ntohl(nas->obs_point_id);
-    sample->sampling_port = ntohs(nas->sampling_port);
+    sample->sampling_port = u16_to_ofp(ntohs(nas->sampling_port));
 
     if (sample->probability == 0) {
         return OFPERR_OFPBAC_BAD_ARGUMENT;
@@ -4766,19 +4765,16 @@ static void
 encode_SAMPLE(const struct ofpact_sample *sample,
               enum ofp_version ofp_version OVS_UNUSED, struct ofpbuf *out)
 {
-    if (sample->ofpact.raw == NXAST_RAW_SAMPLE2) {
-        struct nx_action_sample2 *nas;
-
-        nas = put_NXAST_SAMPLE2(out);
+    if (sample->ofpact.raw == NXAST_RAW_SAMPLE2
+        || sample->sampling_port != OFPP_NONE) {
+        struct nx_action_sample2 *nas = put_NXAST_SAMPLE2(out);
         nas->probability = htons(sample->probability);
         nas->collector_set_id = htonl(sample->collector_set_id);
         nas->obs_domain_id = htonl(sample->obs_domain_id);
         nas->obs_point_id = htonl(sample->obs_point_id);
-        nas->sampling_port = htons(sample->sampling_port);
+        nas->sampling_port = htons(ofp_to_u16(sample->sampling_port));
     } else {
-        struct nx_action_sample *nas;
-
-        nas = put_NXAST_SAMPLE(out);
+        struct nx_action_sample *nas = put_NXAST_SAMPLE(out);
         nas->probability = htons(sample->probability);
         nas->collector_set_id = htonl(sample->collector_set_id);
         nas->obs_domain_id = htonl(sample->obs_domain_id);
@@ -4796,9 +4792,9 @@ parse_SAMPLE(char *arg, struct ofpbuf *ofpacts,
              enum ofputil_protocol *usable_protocols OVS_UNUSED)
 {
     struct ofpact_sample *os = ofpact_put_SAMPLE(ofpacts);
-    char *key, *value;
-    bool is_existed = false;
+    os->sampling_port = OFPP_NONE;
 
+    char *key, *value;
     while (ofputil_parse_key_value(&arg, &key, &value)) {
         char *error = NULL;
 
@@ -4814,8 +4810,9 @@ parse_SAMPLE(char *arg, struct ofpbuf *ofpacts,
         } else if (!strcmp(key, "obs_point_id")) {
             error = str_to_u32(value, &os->obs_point_id);
         } else if (!strcmp(key, "sampling_port")) {
-            is_existed = true;
-            error = str_to_u16(value, "sampling_port", &os->sampling_port);
+            if (!ofputil_port_from_string(value, &os->sampling_port)) {
+                error = xasprintf("%s: unknown port", value);
+            }
         } else {
             error = xasprintf("invalid key \"%s\" in \"sample\" argument",
                               key);
@@ -4828,45 +4825,26 @@ parse_SAMPLE(char *arg, struct ofpbuf *ofpacts,
         return xstrdup("non-zero \"probability\" must be specified on sample");
     }
 
-    if (!is_existed) {
-        /* default value is OFPP_NONE */
-        os->sampling_port = ofp_to_u16(OFPP_NONE);
-        os->ofpact.raw = NXAST_RAW_SAMPLE;
-    } else {
-        os->ofpact.raw = NXAST_RAW_SAMPLE2;
-    }
-
     return NULL;
 }
 
 static void
 format_SAMPLE(const struct ofpact_sample *a, struct ds *s)
 {
-    if (a->ofpact.raw == NXAST_RAW_SAMPLE) {
-        ds_put_format(s, "%ssample(%s%sprobability=%s%"PRIu16
-                      ",%scollector_set_id=%s%"PRIu32
-                      ",%sobs_domain_id=%s%"PRIu32
-                      ",%sobs_point_id=%s%"PRIu32"%s)%s",
-                      colors.paren, colors.end,
-                      colors.param, colors.end, a->probability,
-                      colors.param, colors.end, a->collector_set_id,
-                      colors.param, colors.end, a->obs_domain_id,
-                      colors.param, colors.end, a->obs_point_id,
-                      colors.paren, colors.end);
-    } else {
-        ds_put_format(s, "%ssample(%s%sprobability=%s%"PRIu16
-                      ",%scollector_set_id=%s%"PRIu32
-                      ",%sobs_domain_id=%s%"PRIu32
-                      ",%sobs_point_id=%s%"PRIu32
-                      ",%ssampling_port=%s%"PRIu16"%s)%s",
-                      colors.paren, colors.end,
-                      colors.param, colors.end, a->probability,
-                      colors.param, colors.end, a->collector_set_id,
-                      colors.param, colors.end, a->obs_domain_id,
-                      colors.param, colors.end, a->obs_point_id,
-                      colors.param, colors.end, a->sampling_port,
-                      colors.paren, colors.end);
+    ds_put_format(s, "%ssample(%s%sprobability=%s%"PRIu16
+                  ",%scollector_set_id=%s%"PRIu32
+                  ",%sobs_domain_id=%s%"PRIu32
+                  ",%sobs_point_id=%s%"PRIu32,
+                  colors.paren, colors.end,
+                  colors.param, colors.end, a->probability,
+                  colors.param, colors.end, a->collector_set_id,
+                  colors.param, colors.end, a->obs_domain_id,
+                  colors.param, colors.end, a->obs_point_id);
+    if (a->sampling_port != OFPP_NONE) {
+        ds_put_format(s, ",%ssampling_port=%s%"PRIu16,
+                      colors.param, colors.end, a->sampling_port);
     }
+    ds_put_format(s, "%s)%s", colors.paren, colors.end);
 }
 
 /* debug_recirc instruction. */
diff --git a/ofproto/ofproto-dpif-ipfix.c b/ofproto/ofproto-dpif-ipfix.c
index e243cf6..31b268d 100644
--- a/ofproto/ofproto-dpif-ipfix.c
+++ b/ofproto/ofproto-dpif-ipfix.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2013, 2014, 2015 Nicira, Inc.
+ * Copyright (c) 2012, 2013, 2014, 2015, 2016 Nicira, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -943,13 +943,10 @@ bool
 dpif_ipfix_flow_exporter_enable(const struct dpif_ipfix *di)
     OVS_EXCLUDED(mutex)
 {
-    bool ret = false;
-
     ovs_mutex_lock(&mutex);
-    if (hmap_count(&di->flow_exporter_map)) {
-        ret = true;
-    }
+    bool ret = hmap_count(&di->flow_exporter_map) > 0;
     ovs_mutex_unlock(&mutex);
+
     return ret;
 }
 
@@ -958,15 +955,14 @@ dpif_ipfix_get_flow_exporter_tunnel_sampling(const struct dpif_ipfix *di,
                                              const uint32_t collector_set_id)
     OVS_EXCLUDED(mutex)
 {
-    bool ret = false;
-    struct dpif_ipfix_flow_exporter_map_node *node;
-
     ovs_mutex_lock(&mutex);
-    node = dpif_ipfix_find_flow_exporter_map_node(di, collector_set_id);
-    if (node && node->exporter.options) {
-        ret = node->exporter.options->enable_tunnel_sampling;
-    }
+    struct dpif_ipfix_flow_exporter_map_node *node
+        = dpif_ipfix_find_flow_exporter_map_node(di, collector_set_id);
+    bool ret = (node
+                && node->exporter.options
+                && node->exporter.options->enable_tunnel_sampling);
     ovs_mutex_unlock(&mutex);
+
     return ret;
 }
 
diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c
index 3fc3167..5d962f3 100644
--- a/ofproto/ofproto-dpif-xlate.c
+++ b/ofproto/ofproto-dpif-xlate.c
@@ -4220,7 +4220,6 @@ xlate_sample_action(struct xlate_ctx *ctx,
 {
     odp_port_t output_odp_port = ODPP_NONE;
     odp_port_t tunnel_out_port = ODPP_NONE;
-    ofp_port_t sampling_port;
     struct dpif_ipfix *ipfix = ctx->xbridge->ipfix;
 
     if (!ipfix || ctx->xin->flow.in_port.ofp_port == OFPP_NONE) {
@@ -4230,7 +4229,6 @@ xlate_sample_action(struct xlate_ctx *ctx,
     /* Scale the probability from 16-bit to 32-bit while representing
      * the same percentage. */
     uint32_t probability = (os->probability << 16) | os->probability;
-    static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
 
     if (!ctx->xbridge->support.variable_length_userdata) {
         static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
@@ -4241,26 +4239,23 @@ xlate_sample_action(struct xlate_ctx *ctx,
         return;
     }
 
-    sampling_port = u16_to_ofp(os->sampling_port);
-    if (sampling_port != OFPP_NONE) {
-        /* If ofp_port in flow sample action is equel to ofp_port,
-         * this sample action is a input port action */
-        if (sampling_port == ctx->xin->flow.in_port.ofp_port) {
-            output_odp_port = ODPP_NONE;
-        } else {
-            output_odp_port = ofp_port_to_odp_port(ctx->xbridge,
-                                                   sampling_port);
-            if (output_odp_port == ODPP_NONE) {
-                VLOG_ERR_RL(&rl, "openflow port: %d in flow sample action "
-                            "is illegal", os->sampling_port);
-                return;
-            }
+    /* If ofp_port in flow sample action is equel to ofp_port,
+     * this sample action is a input port action. */
+    if (os->sampling_port != OFPP_NONE &&
+        os->sampling_port != ctx->xin->flow.in_port.ofp_port) {
+        output_odp_port = ofp_port_to_odp_port(ctx->xbridge,
+                                               os->sampling_port);
+        if (output_odp_port == ODPP_NONE) {
+            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
+            VLOG_WARN_RL(&rl, "can't use unknown port %d in flow sample "
+                         "action", os->sampling_port);
+            return;
+        }
 
-            if (dpif_ipfix_get_flow_exporter_tunnel_sampling(ipfix,
-                    os->collector_set_id) &&
-                    dpif_ipfix_get_tunnel_port(ipfix, output_odp_port)) {
-                tunnel_out_port = output_odp_port;
-            }
+        if (dpif_ipfix_get_flow_exporter_tunnel_sampling(ipfix,
+                                                         os->collector_set_id)
+            && dpif_ipfix_get_tunnel_port(ipfix, output_odp_port)) {
+            tunnel_out_port = output_odp_port;
         }
     }
 
diff --git a/utilities/ovs-ofctl.8.in b/utilities/ovs-ofctl.8.in
index 7a703bb..54ad2b0 100644
--- a/utilities/ovs-ofctl.8.in
+++ b/utilities/ovs-ofctl.8.in
@@ -2302,10 +2302,10 @@ When sending samples to IPFIX collectors, the unsigned 32-bit integer
 Observation Point ID sent in every IPFIX flow record.  Defaults to 0.
 .IP "\fBsampling_port=\fIport\fR"
 Sample packets on the port.  It can be set as input port or output
-port.  When the port number is equal to 65535, IPFIX can neither
-differentiate between ingress packets and egress packets nor export
-egress tunnel information.  The port should be an OpenFlow port number.
-Defaults to 65535.
+port.  When this option is omitted, or specified as \fBNONE\fB, IPFIX
+does not differentiate between ingress packets and egress packets and
+does not export egress tunnel information.  This option was added in
+Open vSwitch 2.5.90.
 .RE
 .IP
 Refer to \fBovs\-vswitchd.conf.db\fR(8) for more details on
diff --git a/vswitchd/vswitch.xml b/vswitchd/vswitch.xml
index cef6cbd..5a65552 100644
--- a/vswitchd/vswitch.xml
+++ b/vswitchd/vswitch.xml
@@ -4733,6 +4733,14 @@
           </p>
         </dd>
       </dl>
+
+      <p>
+        Before Open vSwitch 2.5.90, <ref column="other_config"
+        key="enable-tunnel-sampling"/> was only supported with per-bridge
+        sampling, and ignored otherwise.  Open vSwitch 2.5.90 and later support
+        <ref column="other_config" key="enable-tunnel-sampling"/> for
+        per-bridge and per-flow sampling.
+      </p>
     </column>
 
     <group title="Per-Bridge Sampling">

--8<--------------------------cut here-------------------------->8--

From: Benli Ye <daniely at vmware.com>
Date: Wed, 8 Jun 2016 19:13:38 +0800
Subject: [PATCH] ipfix: support tunnel information for Flow IPFIX

Add support to export tunnel information for flow-based IPFIX.
The original steps to configure flow level IPFIX:
    1) Create a new record in Flow_Sample_Collector_Set table:
       'ovs-vsctl -- create Flow_Sample_Collector_Set id=1 bridge="Bridge UUID"'
    2) Add IPFIX configuration which is referred by corresponding
       row in Flow_Sample_Collector_Set table:
       'ovs-vsctl -- set Flow_Sample_Collector_Set
       "Flow_Sample_Collector_Set UUID" ipfix=@i -- --id=@i create IPFIX
       targets=\"IP:4739\" obs_domain_id=123 obs_point_id=456
       cache_active_timeout=60 cache_max_flows=13'
    3) Add sample action to the flows:
       'ovs-ofctl add-flow mybridge in_port=1,
       actions=sample'('probability=65535,collector_set_id=1,
       obs_domain_id=123,obs_point_id=456')',output:3'
NXAST_SAMPLE action was used in step 3. In order to support exporting tunnel
information, the NXAST_SAMPLE2 action was added. With NXAST_SAMPLE2 action in
this patch, the step 3 should be configured like below:
       'ovs-ofctl add-flow mybridge in_port=1,
       actions=sample'('probability=65535,collector_set_id=1,obs_domain_id=123,
       obs_point_id=456,sampling_port=3')',output:3'
'sampling_port' can be equal to ingress port or one of egress ports. If sampling
port is equal to output port and the output port is a tunnel port,
OVS_USERSPACE_ATTR_EGRESS_TUN_PORT will be set in the datapath flow sample action.
When flow sample action upcall happens, tunnel information will be retrieved from
the datapath and then IPFIX can export egress tunnel port information. If
samping_port=65535 (OFPP_NONE), flow-based IPFIX will keep the same behavior
as before.

This patch mainly do three tasks:
    1) Add a new flow sample action NXAST_SAMPLE2 to support exporting
       tunnel information. NXAST_SAMPLE2 action has a new added field
       'sampling_port'.
    2) Use 'other_configure: enable-tunnel-sampling' to enable or disable
       exporting tunnel information.
    3) Flow sample action for egress tunnel port is moved to the point that
       is just before output action. It makes sure that flow sample action
       for egress tunnel port is always behind corresponding set_tunnel
       action.

How to test flow-based IPFIX:
    1) Setup a test environment with two Linux host with Docker supported
    2) Create a Docker container and a GRE tunnel port on each host
    3) Use ovs-docker to add the container on the bridge
    4) Listen on port 4739 on the collector machine and use wireshark to filter
       'cflow' packets.
    5) Configure flow-based IPFIX:
       - 'ovs-vsctl -- create Flow_Sample_Collector_Set id=1 bridge="Bridge UUID"'
       - 'ovs-vsctl -- set Flow_Sample_Collector_Set
          "Flow_Sample_Collector_Set UUID" ipfix=@i -- --id=@i create IPFIX \
          targets=\"IP:4739\" cache_active_timeout=60 cache_max_flows=13 \
          other_config:enable-tunnel-sampling=true'
       - 'ovs-ofctl add-flow mybridge in_port=1,
          actions=sample'('probability=65535,collector_set_id=1,obs_domain_id=123,
          obs_point_id=456,sampling_port=3')',output:3'
       Note: The in-port is container port. The output port and sampling_port
             are both open flow port and the output port is a GRE tunnel port.
    6) Ping from the container whose host enabled flow-based IPFIX.
    7) Get the IPFIX template pakcets and IPFIX information packets.

Signed-off-by: Benli Ye <daniely at vmware.com>
Signed-off-by: Ben Pfaff <blp at ovn.org>
---
 include/openvswitch/ofp-actions.h |   5 +-
 lib/odp-util.c                    |  13 ++-
 lib/odp-util.h                    |   3 +-
 lib/ofp-actions.c                 |  84 ++++++++++++++++---
 ofproto/ofproto-dpif-ipfix.c      |  63 ++++++++++++--
 ofproto/ofproto-dpif-ipfix.h      |   7 +-
 ofproto/ofproto-dpif-upcall.c     |  13 ++-
 ofproto/ofproto-dpif-xlate.c      | 110 ++++++++++++++++++++++++-
 ofproto/ofproto.h                 |   1 +
 tests/odp.at                      |   4 +-
 tests/ofp-actions.at              |   3 +
 tests/ovs-ofctl.at                |  12 +++
 utilities/ovs-ofctl.8.in          |   6 ++
 vswitchd/bridge.c                 |   3 +
 vswitchd/vswitch.xml              | 168 ++++++++++++++++++++------------------
 15 files changed, 379 insertions(+), 116 deletions(-)

diff --git a/include/openvswitch/ofp-actions.h b/include/openvswitch/ofp-actions.h
index 038ef87..91c7ee5 100644
--- a/include/openvswitch/ofp-actions.h
+++ b/include/openvswitch/ofp-actions.h
@@ -775,13 +775,14 @@ struct ofpact_note {
 
 /* OFPACT_SAMPLE.
  *
- * Used for NXAST_SAMPLE. */
+ * Used for NXAST_SAMPLE and NXAST_SAMPLE2. */
 struct ofpact_sample {
     struct ofpact ofpact;
-    uint16_t probability;  // Always >0.
+    uint16_t probability;  /* Always positive. */
     uint32_t collector_set_id;
     uint32_t obs_domain_id;
     uint32_t obs_point_id;
+    ofp_port_t sampling_port;
 };
 
 /* OFPACT_DEC_TTL.
diff --git a/lib/odp-util.c b/lib/odp-util.c
index d9ace90..49f9a11 100644
--- a/lib/odp-util.c
+++ b/lib/odp-util.c
@@ -311,11 +311,13 @@ format_odp_userspace_action(struct ds *ds, const struct nlattr *attr)
                 ds_put_format(ds, ",flow_sample(probability=%"PRIu16
                               ",collector_set_id=%"PRIu32
                               ",obs_domain_id=%"PRIu32
-                              ",obs_point_id=%"PRIu32")",
+                              ",obs_point_id=%"PRIu32
+                              ",output_port=%"PRIu32")",
                               cookie.flow_sample.probability,
                               cookie.flow_sample.collector_set_id,
                               cookie.flow_sample.obs_domain_id,
-                              cookie.flow_sample.obs_point_id);
+                              cookie.flow_sample.obs_point_id,
+                              cookie.flow_sample.output_odp_port);
             } else if (userdata_len >= sizeof cookie.ipfix
                        && cookie.type == USER_ACTION_COOKIE_IPFIX) {
                 ds_put_format(ds, ",ipfix(output_port=%"PRIu32")",
@@ -951,9 +953,11 @@ parse_odp_userspace_action(const char *s, struct ofpbuf *actions)
         } else if (ovs_scan(&s[n], ",flow_sample(probability=%"SCNi32","
                             "collector_set_id=%"SCNi32","
                             "obs_domain_id=%"SCNi32","
-                            "obs_point_id=%"SCNi32")%n",
+                            "obs_point_id=%"SCNi32","
+                            "output_port=%"SCNi32")%n",
                             &probability, &collector_set_id,
-                            &obs_domain_id, &obs_point_id, &n1)) {
+                            &obs_domain_id, &obs_point_id,
+                            &output, &n1)) {
             n += n1;
 
             cookie.type = USER_ACTION_COOKIE_FLOW_SAMPLE;
@@ -961,6 +965,7 @@ parse_odp_userspace_action(const char *s, struct ofpbuf *actions)
             cookie.flow_sample.collector_set_id = collector_set_id;
             cookie.flow_sample.obs_domain_id = obs_domain_id;
             cookie.flow_sample.obs_point_id = obs_point_id;
+            cookie.flow_sample.output_odp_port = u32_to_odp(output);
             user_data = &cookie;
             user_data_size = sizeof cookie.flow_sample;
         } else if (ovs_scan(&s[n], ",ipfix(output_port=%"SCNi32")%n",
diff --git a/lib/odp-util.h b/lib/odp-util.h
index 51cf5c3..0a8289b 100644
--- a/lib/odp-util.h
+++ b/lib/odp-util.h
@@ -303,6 +303,7 @@ union user_action_cookie {
         uint32_t collector_set_id; /* ID of IPFIX collector set. */
         uint32_t obs_domain_id; /* Observation Domain ID. */
         uint32_t obs_point_id;  /* Observation Point ID. */
+        odp_port_t output_odp_port; /* The output odp port. */
     } flow_sample;
 
     struct {
@@ -310,7 +311,7 @@ union user_action_cookie {
         odp_port_t output_odp_port; /* The output odp port. */
     } ipfix;
 };
-BUILD_ASSERT_DECL(sizeof(union user_action_cookie) == 16);
+BUILD_ASSERT_DECL(sizeof(union user_action_cookie) == 20);
 
 size_t odp_put_userspace_action(uint32_t pid,
                                 const void *userdata, size_t userdata_size,
diff --git a/lib/ofp-actions.c b/lib/ofp-actions.c
index 7ddadb8..71c65df 100644
--- a/lib/ofp-actions.c
+++ b/lib/ofp-actions.c
@@ -289,6 +289,8 @@ enum ofp_raw_action_type {
 
     /* NX1.0+(29): struct nx_action_sample. */
     NXAST_RAW_SAMPLE,
+    /* NX1.0+(38): struct nx_action_sample2. */
+    NXAST_RAW_SAMPLE2,
 
     /* NX1.0+(34): struct nx_action_conjunction. */
     NXAST_RAW_CONJUNCTION,
@@ -4697,6 +4699,24 @@ struct nx_action_sample {
 };
 OFP_ASSERT(sizeof(struct nx_action_sample) == 24);
 
+/* Action structure for NXAST_SAMPLE2.
+ *
+ * This replacement for NXAST_SAMPLE makes it support exporting
+ * egress tunnel information. */
+struct nx_action_sample2 {
+    ovs_be16 type;                  /* OFPAT_VENDOR. */
+    ovs_be16 len;                   /* Length is 32. */
+    ovs_be32 vendor;                /* NX_VENDOR_ID. */
+    ovs_be16 subtype;               /* NXAST_SAMPLE. */
+    ovs_be16 probability;           /* Fraction of packets to sample. */
+    ovs_be32 collector_set_id;      /* ID of collector set in OVSDB. */
+    ovs_be32 obs_domain_id;         /* ID of sampling observation domain. */
+    ovs_be32 obs_point_id;          /* ID of sampling observation point. */
+    ovs_be16 sampling_port;         /* Sampling port. */
+    uint8_t  pad[6];                /* Pad to a multiple of 8 bytes */
+ };
+ OFP_ASSERT(sizeof(struct nx_action_sample2) == 32);
+
 static enum ofperr
 decode_NXAST_RAW_SAMPLE(const struct nx_action_sample *nas,
                         enum ofp_version ofp_version OVS_UNUSED,
@@ -4705,10 +4725,34 @@ decode_NXAST_RAW_SAMPLE(const struct nx_action_sample *nas,
     struct ofpact_sample *sample;
 
     sample = ofpact_put_SAMPLE(out);
+    sample->ofpact.raw = NXAST_RAW_SAMPLE;
     sample->probability = ntohs(nas->probability);
     sample->collector_set_id = ntohl(nas->collector_set_id);
     sample->obs_domain_id = ntohl(nas->obs_domain_id);
     sample->obs_point_id = ntohl(nas->obs_point_id);
+    sample->sampling_port = OFPP_NONE;
+
+    if (sample->probability == 0) {
+        return OFPERR_OFPBAC_BAD_ARGUMENT;
+    }
+
+    return 0;
+}
+
+static enum ofperr
+decode_NXAST_RAW_SAMPLE2(const struct nx_action_sample2 *nas,
+                         enum ofp_version ofp_version OVS_UNUSED,
+                         struct ofpbuf *out)
+{
+    struct ofpact_sample *sample;
+
+    sample = ofpact_put_SAMPLE(out);
+    sample->ofpact.raw = NXAST_RAW_SAMPLE2;
+    sample->probability = ntohs(nas->probability);
+    sample->collector_set_id = ntohl(nas->collector_set_id);
+    sample->obs_domain_id = ntohl(nas->obs_domain_id);
+    sample->obs_point_id = ntohl(nas->obs_point_id);
+    sample->sampling_port = u16_to_ofp(ntohs(nas->sampling_port));
 
     if (sample->probability == 0) {
         return OFPERR_OFPBAC_BAD_ARGUMENT;
@@ -4721,13 +4765,21 @@ static void
 encode_SAMPLE(const struct ofpact_sample *sample,
               enum ofp_version ofp_version OVS_UNUSED, struct ofpbuf *out)
 {
-    struct nx_action_sample *nas;
-
-    nas = put_NXAST_SAMPLE(out);
-    nas->probability = htons(sample->probability);
-    nas->collector_set_id = htonl(sample->collector_set_id);
-    nas->obs_domain_id = htonl(sample->obs_domain_id);
-    nas->obs_point_id = htonl(sample->obs_point_id);
+    if (sample->ofpact.raw == NXAST_RAW_SAMPLE2
+        || sample->sampling_port != OFPP_NONE) {
+        struct nx_action_sample2 *nas = put_NXAST_SAMPLE2(out);
+        nas->probability = htons(sample->probability);
+        nas->collector_set_id = htonl(sample->collector_set_id);
+        nas->obs_domain_id = htonl(sample->obs_domain_id);
+        nas->obs_point_id = htonl(sample->obs_point_id);
+        nas->sampling_port = htons(ofp_to_u16(sample->sampling_port));
+    } else {
+        struct nx_action_sample *nas = put_NXAST_SAMPLE(out);
+        nas->probability = htons(sample->probability);
+        nas->collector_set_id = htonl(sample->collector_set_id);
+        nas->obs_domain_id = htonl(sample->obs_domain_id);
+        nas->obs_point_id = htonl(sample->obs_point_id);
+    }
 }
 
 /* Parses 'arg' as the argument to a "sample" action, and appends such an
@@ -4740,8 +4792,9 @@ parse_SAMPLE(char *arg, struct ofpbuf *ofpacts,
              enum ofputil_protocol *usable_protocols OVS_UNUSED)
 {
     struct ofpact_sample *os = ofpact_put_SAMPLE(ofpacts);
-    char *key, *value;
+    os->sampling_port = OFPP_NONE;
 
+    char *key, *value;
     while (ofputil_parse_key_value(&arg, &key, &value)) {
         char *error = NULL;
 
@@ -4756,6 +4809,10 @@ parse_SAMPLE(char *arg, struct ofpbuf *ofpacts,
             error = str_to_u32(value, &os->obs_domain_id);
         } else if (!strcmp(key, "obs_point_id")) {
             error = str_to_u32(value, &os->obs_point_id);
+        } else if (!strcmp(key, "sampling_port")) {
+            if (!ofputil_port_from_string(value, &os->sampling_port)) {
+                error = xasprintf("%s: unknown port", value);
+            }
         } else {
             error = xasprintf("invalid key \"%s\" in \"sample\" argument",
                               key);
@@ -4767,6 +4824,7 @@ parse_SAMPLE(char *arg, struct ofpbuf *ofpacts,
     if (os->probability == 0) {
         return xstrdup("non-zero \"probability\" must be specified on sample");
     }
+
     return NULL;
 }
 
@@ -4776,13 +4834,17 @@ format_SAMPLE(const struct ofpact_sample *a, struct ds *s)
     ds_put_format(s, "%ssample(%s%sprobability=%s%"PRIu16
                   ",%scollector_set_id=%s%"PRIu32
                   ",%sobs_domain_id=%s%"PRIu32
-                  ",%sobs_point_id=%s%"PRIu32"%s)%s",
+                  ",%sobs_point_id=%s%"PRIu32,
                   colors.paren, colors.end,
                   colors.param, colors.end, a->probability,
                   colors.param, colors.end, a->collector_set_id,
                   colors.param, colors.end, a->obs_domain_id,
-                  colors.param, colors.end, a->obs_point_id,
-                  colors.paren, colors.end);
+                  colors.param, colors.end, a->obs_point_id);
+    if (a->sampling_port != OFPP_NONE) {
+        ds_put_format(s, ",%ssampling_port=%s%"PRIu16,
+                      colors.param, colors.end, a->sampling_port);
+    }
+    ds_put_format(s, "%s)%s", colors.paren, colors.end);
 }
 
 /* debug_recirc instruction. */
diff --git a/ofproto/ofproto-dpif-ipfix.c b/ofproto/ofproto-dpif-ipfix.c
index 79ba234..31b268d 100644
--- a/ofproto/ofproto-dpif-ipfix.c
+++ b/ofproto/ofproto-dpif-ipfix.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2013, 2014, 2015 Nicira, Inc.
+ * Copyright (c) 2012, 2013, 2014, 2015, 2016 Nicira, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -467,6 +467,7 @@ ofproto_ipfix_flow_exporter_options_equal(
     return (a->collector_set_id == b->collector_set_id
             && a->cache_active_timeout == b->cache_active_timeout
             && a->cache_max_flows == b->cache_max_flows
+            && a->enable_tunnel_sampling == b->enable_tunnel_sampling
             && sset_equals(&a->targets, &b->targets));
 }
 
@@ -938,6 +939,33 @@ dpif_ipfix_get_bridge_exporter_tunnel_sampling(const struct dpif_ipfix *di)
     return ret;
 }
 
+bool
+dpif_ipfix_flow_exporter_enable(const struct dpif_ipfix *di)
+    OVS_EXCLUDED(mutex)
+{
+    ovs_mutex_lock(&mutex);
+    bool ret = hmap_count(&di->flow_exporter_map) > 0;
+    ovs_mutex_unlock(&mutex);
+
+    return ret;
+}
+
+bool
+dpif_ipfix_get_flow_exporter_tunnel_sampling(const struct dpif_ipfix *di,
+                                             const uint32_t collector_set_id)
+    OVS_EXCLUDED(mutex)
+{
+    ovs_mutex_lock(&mutex);
+    struct dpif_ipfix_flow_exporter_map_node *node
+        = dpif_ipfix_find_flow_exporter_map_node(di, collector_set_id);
+    bool ret = (node
+                && node->exporter.options
+                && node->exporter.options->enable_tunnel_sampling);
+    ovs_mutex_unlock(&mutex);
+
+    return ret;
+}
+
 static void
 dpif_ipfix_clear(struct dpif_ipfix *di) OVS_REQUIRES(mutex)
 {
@@ -1743,11 +1771,19 @@ dpif_ipfix_bridge_sample(struct dpif_ipfix *di, const struct dp_packet *packet,
 
 void
 dpif_ipfix_flow_sample(struct dpif_ipfix *di, const struct dp_packet *packet,
-                       const struct flow *flow, uint32_t collector_set_id,
-                       uint16_t probability, uint32_t obs_domain_id,
-                       uint32_t obs_point_id) OVS_EXCLUDED(mutex)
+                       const struct flow *flow,
+                       const union user_action_cookie *cookie,
+                       odp_port_t input_odp_port,
+                       const struct flow_tnl *output_tunnel_key)
+    OVS_EXCLUDED(mutex)
 {
     struct dpif_ipfix_flow_exporter_map_node *node;
+    const struct flow_tnl *tunnel_key = NULL;
+    struct dpif_ipfix_port * tunnel_port = NULL;
+    odp_port_t output_odp_port = cookie->flow_sample.output_odp_port;
+    uint32_t collector_set_id = cookie->flow_sample.collector_set_id;
+    uint16_t probability = cookie->flow_sample.probability;
+
     /* Use the sampling probability as an approximation of the number
      * of matched packets. */
     uint64_t packet_delta_count = USHRT_MAX / probability;
@@ -1755,9 +1791,24 @@ dpif_ipfix_flow_sample(struct dpif_ipfix *di, const struct dp_packet *packet,
     ovs_mutex_lock(&mutex);
     node = dpif_ipfix_find_flow_exporter_map_node(di, collector_set_id);
     if (node) {
+        if (node->exporter.options->enable_tunnel_sampling) {
+            if (output_odp_port == ODPP_NONE && flow->tunnel.ip_dst) {
+                /* Input tunnel. */
+                tunnel_key = &flow->tunnel;
+                tunnel_port = dpif_ipfix_find_port(di, input_odp_port);
+            }
+            if (output_odp_port != ODPP_NONE && output_tunnel_key) {
+                /* Output tunnel, output_tunnel_key must be valid. */
+                tunnel_key = output_tunnel_key;
+                tunnel_port = dpif_ipfix_find_port(di, output_odp_port);
+            }
+        }
+
         dpif_ipfix_sample(&node->exporter.exporter, packet, flow,
-                          packet_delta_count, obs_domain_id, obs_point_id,
-                          ODPP_NONE, NULL, NULL);
+                          packet_delta_count,
+                          cookie->flow_sample.obs_domain_id,
+                          cookie->flow_sample.obs_point_id,
+                          output_odp_port, tunnel_port, tunnel_key);
     }
     ovs_mutex_unlock(&mutex);
 }
diff --git a/ofproto/ofproto-dpif-ipfix.h b/ofproto/ofproto-dpif-ipfix.h
index 2bb0e43..915d05c 100644
--- a/ofproto/ofproto-dpif-ipfix.h
+++ b/ofproto/ofproto-dpif-ipfix.h
@@ -40,6 +40,9 @@ uint32_t dpif_ipfix_get_bridge_exporter_probability(const struct dpif_ipfix *);
 bool dpif_ipfix_get_bridge_exporter_tunnel_sampling(const struct dpif_ipfix *);
 bool dpif_ipfix_get_bridge_exporter_input_sampling(const struct dpif_ipfix *);
 bool dpif_ipfix_get_bridge_exporter_output_sampling(const struct dpif_ipfix *);
+bool dpif_ipfix_flow_exporter_enable(const struct dpif_ipfix *);
+bool dpif_ipfix_get_flow_exporter_tunnel_sampling(const struct dpif_ipfix *,
+                                                  const uint32_t);
 bool dpif_ipfix_get_tunnel_port(const struct dpif_ipfix *, odp_port_t);
 void dpif_ipfix_set_options(
     struct dpif_ipfix *,
@@ -50,8 +53,8 @@ void dpif_ipfix_bridge_sample(struct dpif_ipfix *, const struct dp_packet *,
                               const struct flow *,
                               odp_port_t, odp_port_t, const struct flow_tnl *);
 void dpif_ipfix_flow_sample(struct dpif_ipfix *, const struct dp_packet *,
-                            const struct flow *, uint32_t, uint16_t, uint32_t,
-                            uint32_t);
+                            const struct flow *, const union user_action_cookie *,
+                            odp_port_t, const struct flow_tnl *);
 
 void dpif_ipfix_run(struct dpif_ipfix *);
 void dpif_ipfix_wait(struct dpif_ipfix *);
diff --git a/ofproto/ofproto-dpif-upcall.c b/ofproto/ofproto-dpif-upcall.c
index a18fc5a..feed9b1 100644
--- a/ofproto/ofproto-dpif-upcall.c
+++ b/ofproto/ofproto-dpif-upcall.c
@@ -1256,17 +1256,22 @@ process_upcall(struct udpif *udpif, struct upcall *upcall,
     case FLOW_SAMPLE_UPCALL:
         if (upcall->ipfix) {
             union user_action_cookie cookie;
+            struct flow_tnl output_tunnel_key;
 
             memset(&cookie, 0, sizeof cookie);
             memcpy(&cookie, nl_attr_get(userdata), sizeof cookie.flow_sample);
 
+            if (upcall->out_tun_key) {
+                odp_tun_key_from_attr(upcall->out_tun_key, false,
+                                      &output_tunnel_key);
+            }
+
             /* The flow reflects exactly the contents of the packet.
              * Sample the packet using it. */
             dpif_ipfix_flow_sample(upcall->ipfix, packet, flow,
-                                   cookie.flow_sample.collector_set_id,
-                                   cookie.flow_sample.probability,
-                                   cookie.flow_sample.obs_domain_id,
-                                   cookie.flow_sample.obs_point_id);
+                                   &cookie, flow->in_port.odp_port,
+                                   upcall->out_tun_key ?
+                                       &output_tunnel_key : NULL);
         }
         break;
 
diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c
index cca5c5c..5d962f3 100644
--- a/ofproto/ofproto-dpif-xlate.c
+++ b/ofproto/ofproto-dpif-xlate.c
@@ -2655,8 +2655,75 @@ compose_sflow_action(struct xlate_ctx *ctx)
                                  true);
 }
 
-/* If IPFIX is enabled, this appends a "sample" action to implement IPFIX to
- * 'ctx->odp_actions'. */
+static void
+adjust_sample_action(struct xlate_ctx *ctx, odp_port_t output_odp_port)
+{
+    const struct nlattr *nla, *nla_nested_lay1, *nla_nested_lay2;
+    const struct nlattr *tunnel_out_port_attr;
+    size_t step_length = 0;
+    struct ofpbuf *odp_actions;
+
+    if (output_odp_port == ODPP_NONE) {
+        return;
+    }
+
+    odp_actions = ctx->odp_actions;
+    size_t offset = odp_actions->size;
+    /* Set_tunnel action should be in front
+     * of sample action which contains OVS_USERSPACE_ATTR_EGRESS_TUN_PORT
+     * attribute. */
+    while (step_length < offset) {
+        nla = nl_attr_find(odp_actions, step_length, OVS_ACTION_ATTR_SAMPLE);
+
+        if (nla) {
+            nla_nested_lay1 = nl_attr_find_nested(nla,
+                                              OVS_SAMPLE_ATTR_ACTIONS);
+            if (nla_nested_lay1) {
+                nla_nested_lay2 = nl_attr_find_nested(nla_nested_lay1,
+                                                  OVS_ACTION_ATTR_USERSPACE);
+            } else {
+                return;
+            }
+        } else {
+            return;
+        }
+
+        tunnel_out_port_attr = nl_attr_find_nested(nla_nested_lay2,
+                                       OVS_USERSPACE_ATTR_EGRESS_TUN_PORT);
+        if (tunnel_out_port_attr) {
+            size_t len = nla->nla_len;
+            if (output_odp_port !=
+                u32_to_odp(nl_attr_get_u32(tunnel_out_port_attr))) {
+                step_length = (char *) nla - (char *) odp_actions->data +
+                              nla->nla_len;
+                continue;
+            }
+
+            if (offset == (char *) nla - (char *) odp_actions->data +
+                          len) {
+                break;
+            }
+
+            size_t remain_len = (char *) odp_actions->data +
+                                offset - (char *) nla - len;
+            char *tmpBuffer = xmalloc(len);
+            memcpy(tmpBuffer, (char *) nla, len);
+            memmove((char *) nla, (char *) nla + len, remain_len);
+            memcpy((char *) nla + remain_len, tmpBuffer, len);
+            free(tmpBuffer);
+            break;
+        } else {
+            /* Continue to find sample action */
+            step_length = (char *) nla - ((char *) odp_actions->data) +
+                          nla->nla_len;
+        }
+    }
+}
+
+/* If bridge IPFIX is enabled, this appends a "sample" action to
+ * implement IPFIX to 'ctx->odp_actions'. If flow IPFIX is enabled,
+ * make sure IPFIX flow sample action of tunnel port is in front
+ * of corresponding output action. */
 static void
 compose_ipfix_action(struct xlate_ctx *ctx, odp_port_t output_odp_port)
 {
@@ -2667,6 +2734,12 @@ compose_ipfix_action(struct xlate_ctx *ctx, odp_port_t output_odp_port)
         return;
     }
 
+    /* For ipfix flow sample action, output sample action of tunnel port
+     * should be placed in front of output action. */
+    if (dpif_ipfix_flow_exporter_enable(ipfix)) {
+        adjust_sample_action(ctx, output_odp_port);
+    }
+
     /* For input case, output_odp_port is ODPP_NONE, which is an invalid port
      * number. */
     if (output_odp_port == ODPP_NONE &&
@@ -2674,7 +2747,7 @@ compose_ipfix_action(struct xlate_ctx *ctx, odp_port_t output_odp_port)
         return;
     }
 
-    /* For output case, output_odp_port is valid*/
+    /* For output case, output_odp_port is valid. */
     if (output_odp_port != ODPP_NONE) {
         if (!dpif_ipfix_get_bridge_exporter_output_sampling(ipfix)) {
             return;
@@ -4145,6 +4218,14 @@ static void
 xlate_sample_action(struct xlate_ctx *ctx,
                     const struct ofpact_sample *os)
 {
+    odp_port_t output_odp_port = ODPP_NONE;
+    odp_port_t tunnel_out_port = ODPP_NONE;
+    struct dpif_ipfix *ipfix = ctx->xbridge->ipfix;
+
+    if (!ipfix || ctx->xin->flow.in_port.ofp_port == OFPP_NONE) {
+        return;
+    }
+
     /* Scale the probability from 16-bit to 32-bit while representing
      * the same percentage. */
     uint32_t probability = (os->probability << 16) | os->probability;
@@ -4158,6 +4239,26 @@ xlate_sample_action(struct xlate_ctx *ctx,
         return;
     }
 
+    /* If ofp_port in flow sample action is equel to ofp_port,
+     * this sample action is a input port action. */
+    if (os->sampling_port != OFPP_NONE &&
+        os->sampling_port != ctx->xin->flow.in_port.ofp_port) {
+        output_odp_port = ofp_port_to_odp_port(ctx->xbridge,
+                                               os->sampling_port);
+        if (output_odp_port == ODPP_NONE) {
+            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
+            VLOG_WARN_RL(&rl, "can't use unknown port %d in flow sample "
+                         "action", os->sampling_port);
+            return;
+        }
+
+        if (dpif_ipfix_get_flow_exporter_tunnel_sampling(ipfix,
+                                                         os->collector_set_id)
+            && dpif_ipfix_get_tunnel_port(ipfix, output_odp_port)) {
+            tunnel_out_port = output_odp_port;
+        }
+    }
+
     xlate_commit_actions(ctx);
 
     union user_action_cookie cookie = {
@@ -4167,10 +4268,11 @@ xlate_sample_action(struct xlate_ctx *ctx,
             .collector_set_id = os->collector_set_id,
             .obs_domain_id = os->obs_domain_id,
             .obs_point_id = os->obs_point_id,
+            .output_odp_port = output_odp_port,
         }
     };
     compose_sample_action(ctx, probability, &cookie, sizeof cookie.flow_sample,
-                          ODPP_NONE, false);
+                          tunnel_out_port, false);
 }
 
 static bool
diff --git a/ofproto/ofproto.h b/ofproto/ofproto.h
index 8588872..774b5b6 100644
--- a/ofproto/ofproto.h
+++ b/ofproto/ofproto.h
@@ -89,6 +89,7 @@ struct ofproto_ipfix_flow_exporter_options {
     struct sset targets;
     uint32_t cache_active_timeout;
     uint32_t cache_max_flows;
+    bool enable_tunnel_sampling;
 };
 
 struct ofproto_rstp_status {
diff --git a/tests/odp.at b/tests/odp.at
index 808a83b..7b94c92 100644
--- a/tests/odp.at
+++ b/tests/odp.at
@@ -255,8 +255,8 @@ userspace(pid=9765,slow_path(cfm))
 userspace(pid=9765,slow_path(cfm),tunnel_out_port=10)
 userspace(pid=1234567,userdata(0102030405060708090a0b0c0d0e0f),actions)
 userspace(pid=1234567,userdata(0102030405060708090a0b0c0d0e0f),tunnel_out_port=10)
-userspace(pid=6633,flow_sample(probability=123,collector_set_id=1234,obs_domain_id=2345,obs_point_id=3456))
-userspace(pid=6633,flow_sample(probability=123,collector_set_id=1234,obs_domain_id=2345,obs_point_id=3456),tunnel_out_port=10)
+userspace(pid=6633,flow_sample(probability=123,collector_set_id=1234,obs_domain_id=2345,obs_point_id=3456,output_port=10))
+userspace(pid=6633,flow_sample(probability=123,collector_set_id=1234,obs_domain_id=2345,obs_point_id=3456,output_port=10),tunnel_out_port=10)
 userspace(pid=6633,ipfix(output_port=10))
 userspace(pid=6633,ipfix(output_port=10),tunnel_out_port=10)
 set(in_port(2))
diff --git a/tests/ofp-actions.at b/tests/ofp-actions.at
index 83a2301..50f74e9 100644
--- a/tests/ofp-actions.at
+++ b/tests/ofp-actions.at
@@ -127,6 +127,9 @@ ffff 0020 00002320 0015 000500000000 80003039005A02fd 0400000000000000
 # actions=sample(probability=12345,collector_set_id=23456,obs_domain_id=34567,obs_point_id=45678)
 ffff 0018 00002320 001d 3039 00005BA0 00008707 0000B26E
 
+# actions=sample(probability=12345,collector_set_id=23456,obs_domain_id=34567,obs_point_id=45678,sampling_port=56789)
+ffff 0020 00002320 0026 3039 00005BA0 00008707 0000B26E DDD50000 00000000
+
 # bad OpenFlow10 actions: OFPBAC_BAD_LEN
 & ofp_actions|WARN|OpenFlow action OFPAT_OUTPUT length 240 exceeds action buffer length 8
 & ofp_actions|WARN|bad action at offset 0 (OFPBAC_BAD_LEN):
diff --git a/tests/ovs-ofctl.at b/tests/ovs-ofctl.at
index 8287cd2..613d9ce 100644
--- a/tests/ovs-ofctl.at
+++ b/tests/ovs-ofctl.at
@@ -160,6 +160,7 @@ sctp actions=drop
 sctp actions=drop
 in_port=0 actions=resubmit:0
 actions=sample(probability=12345,collector_set_id=23456,obs_domain_id=34567,obs_point_id=45678)
+actions=sample(probability=12345,collector_set_id=23456,obs_domain_id=34567,obs_point_id=45678,sampling_port=56789)
 actions=ct(nat)
 actions=ct(commit,nat(dst))
 actions=ct(commit,nat(src))
@@ -190,6 +191,7 @@ OFPT_FLOW_MOD: ADD sctp actions=drop
 OFPT_FLOW_MOD: ADD sctp actions=drop
 OFPT_FLOW_MOD: ADD in_port=0 actions=resubmit:0
 OFPT_FLOW_MOD: ADD actions=sample(probability=12345,collector_set_id=23456,obs_domain_id=34567,obs_point_id=45678)
+OFPT_FLOW_MOD: ADD actions=sample(probability=12345,collector_set_id=23456,obs_domain_id=34567,obs_point_id=45678,sampling_port=56789)
 OFPT_FLOW_MOD: ADD actions=ct(nat)
 OFPT_FLOW_MOD: ADD actions=ct(commit,nat(dst))
 OFPT_FLOW_MOD: ADD actions=ct(commit,nat(src))
@@ -219,6 +221,7 @@ sctp actions=drop
 sctp actions=drop
 in_port=0 actions=resubmit:0
 actions=sample(probability=12345,collector_set_id=23456,obs_domain_id=34567,obs_point_id=45678)
+actions=sample(probability=12345,collector_set_id=23456,obs_domain_id=34567,obs_point_id=45678,sampling_port=56789)
 ]])
 
 AT_CHECK([ovs-ofctl --protocols OpenFlow11 parse-flows flows.txt
@@ -238,6 +241,7 @@ OFPT_FLOW_MOD (OF1.1): ADD sctp actions=drop
 OFPT_FLOW_MOD (OF1.1): ADD sctp actions=drop
 OFPT_FLOW_MOD (OF1.1): ADD in_port=0 actions=resubmit:0
 OFPT_FLOW_MOD (OF1.1): ADD actions=sample(probability=12345,collector_set_id=23456,obs_domain_id=34567,obs_point_id=45678)
+OFPT_FLOW_MOD (OF1.1): ADD actions=sample(probability=12345,collector_set_id=23456,obs_domain_id=34567,obs_point_id=45678,sampling_port=56789)
 ]])
 AT_CLEANUP
 
@@ -260,6 +264,7 @@ ip actions=mod_nw_src:10.1.1.2,mod_nw_dst:192.168.10.1,mod_nw_ttl:1,mod_nw_tos:1
 in_port=0 actions=mod_dl_src:11:22:33:44:55:66,mod_dl_dst:10:20:30:40:50:60
 in_port=0 actions=resubmit:0
 actions=sample(probability=12345,collector_set_id=23456,obs_domain_id=34567,obs_point_id=45678)
+actions=sample(probability=12345,collector_set_id=23456,obs_domain_id=34567,obs_point_id=45678,sampling_port=56789)
 ]])
 
 AT_CHECK([ovs-ofctl --protocols OpenFlow12 parse-flows flows.txt
@@ -283,6 +288,7 @@ OFPT_FLOW_MOD (OF1.2): ADD ip actions=set_field:10.1.1.2->ip_src,set_field:192.1
 OFPT_FLOW_MOD (OF1.2): ADD in_port=0 actions=set_field:11:22:33:44:55:66->eth_src,set_field:10:20:30:40:50:60->eth_dst
 OFPT_FLOW_MOD (OF1.2): ADD in_port=0 actions=resubmit:0
 OFPT_FLOW_MOD (OF1.2): ADD actions=sample(probability=12345,collector_set_id=23456,obs_domain_id=34567,obs_point_id=45678)
+OFPT_FLOW_MOD (OF1.2): ADD actions=sample(probability=12345,collector_set_id=23456,obs_domain_id=34567,obs_point_id=45678,sampling_port=56789)
 ]])
 AT_CLEANUP
 
@@ -375,6 +381,7 @@ check_overlap,actions=output:1,exit,output:2
 tcp,actions=fin_timeout(idle_timeout=5,hard_timeout=15)
 actions=controller(max_len=123,reason=invalid_ttl,id=555)
 actions=sample(probability=12345,collector_set_id=23456,obs_domain_id=34567,obs_point_id=45678)
+actions=sample(probability=12345,collector_set_id=23456,obs_domain_id=34567,obs_point_id=45678,sampling_port=56789)
 ip,actions=ct(commit,zone=5)
 ip,actions=ct(commit,exec(load(1->NXM_NX_CT_MARK[])))
 ip,actions=ct(commit,exec(load(0x1->NXM_NX_CT_LABEL[])))
@@ -417,6 +424,7 @@ NXT_FLOW_MOD: ADD table:255 check_overlap actions=output:1,exit,output:2
 NXT_FLOW_MOD: ADD table:255 tcp actions=fin_timeout(idle_timeout=5,hard_timeout=15)
 NXT_FLOW_MOD: ADD table:255 actions=controller(reason=invalid_ttl,max_len=123,id=555)
 NXT_FLOW_MOD: ADD table:255 actions=sample(probability=12345,collector_set_id=23456,obs_domain_id=34567,obs_point_id=45678)
+NXT_FLOW_MOD: ADD table:255 actions=sample(probability=12345,collector_set_id=23456,obs_domain_id=34567,obs_point_id=45678,sampling_port=56789)
 NXT_FLOW_MOD: ADD table:255 ip actions=ct(commit,zone=5)
 NXT_FLOW_MOD: ADD table:255 ip actions=ct(commit,exec(load:0x1->NXM_NX_CT_MARK[]))
 NXT_FLOW_MOD: ADD table:255 ip actions=ct(commit,exec(load:0x1->NXM_NX_CT_LABEL[0..63],load:0->NXM_NX_CT_LABEL[64..127]))
@@ -457,6 +465,7 @@ dl_dst=00:00:00:00:00:00/01:00:00:00:00:00,actions=drop
 dl_dst=aa:bb:cc:dd:ee:ff/fe:ff:ff:ff:ff:ff,actions=drop
 dl_dst=aa:bb:cc:dd:ee:ff/00:00:00:00:00:00,actions=drop
 actions=sample(probability=12345,collector_set_id=23456,obs_domain_id=34567,obs_point_id=45678)
+actions=sample(probability=12345,collector_set_id=23456,obs_domain_id=34567,obs_point_id=45678,sampling_port=56789)
 ip,actions=ct(commit,zone=5)
 ip,actions=ct(commit,exec(load(1->NXM_NX_CT_MARK[[]])))
 ip,actions=ct(commit,exec(load(0x1->NXM_NX_CT_LABEL[[]])))
@@ -493,6 +502,7 @@ NXT_FLOW_MOD: ADD dl_dst=00:00:00:00:00:00/01:00:00:00:00:00 actions=drop
 NXT_FLOW_MOD: ADD dl_dst=aa:bb:cc:dd:ee:ff/fe:ff:ff:ff:ff:ff actions=drop
 NXT_FLOW_MOD: ADD actions=drop
 NXT_FLOW_MOD: ADD actions=sample(probability=12345,collector_set_id=23456,obs_domain_id=34567,obs_point_id=45678)
+NXT_FLOW_MOD: ADD actions=sample(probability=12345,collector_set_id=23456,obs_domain_id=34567,obs_point_id=45678,sampling_port=56789)
 NXT_FLOW_MOD: ADD ip actions=ct(commit,zone=5)
 NXT_FLOW_MOD: ADD ip actions=ct(commit,exec(load:0x1->NXM_NX_CT_MARK[[]]))
 NXT_FLOW_MOD: ADD ip actions=ct(commit,exec(load:0x1->NXM_NX_CT_LABEL[[0..63]],load:0->NXM_NX_CT_LABEL[[64..127]]))
@@ -529,6 +539,7 @@ actions=move:OXM_OF_ETH_DST[]->OXM_OF_ETH_SRC[]
 actions=push:NXM_NX_REG0[0..31],pop:NXM_NX_REG0[]
 vlan_tci=0x1123/0x1fff,actions=drop
 actions=sample(probability=12345,collector_set_id=23456,obs_domain_id=34567,obs_point_id=45678)
+actions=sample(probability=12345,collector_set_id=23456,obs_domain_id=34567,obs_point_id=45678,sampling_port=56789)
 ip,actions=ct(commit,zone=5)
 ip,actions=ct(commit,exec(load(1->NXM_NX_CT_MARK[])))
 ip,actions=ct(commit,exec(load(1->NXM_NX_CT_LABEL[])))
@@ -565,6 +576,7 @@ NXT_FLOW_MOD: ADD <any> actions=move:NXM_OF_ETH_DST[]->NXM_OF_ETH_SRC[]
 NXT_FLOW_MOD: ADD <any> actions=push:NXM_NX_REG0[],pop:NXM_NX_REG0[]
 NXT_FLOW_MOD: ADD NXM_OF_VLAN_TCI_W(1123/1fff) actions=drop
 NXT_FLOW_MOD: ADD <any> actions=sample(probability=12345,collector_set_id=23456,obs_domain_id=34567,obs_point_id=45678)
+NXT_FLOW_MOD: ADD <any> actions=sample(probability=12345,collector_set_id=23456,obs_domain_id=34567,obs_point_id=45678,sampling_port=56789)
 NXT_FLOW_MOD: ADD NXM_OF_ETH_TYPE(0800) actions=ct(commit,zone=5)
 NXT_FLOW_MOD: ADD NXM_OF_ETH_TYPE(0800) actions=ct(commit,exec(load:0x1->NXM_NX_CT_MARK[]))
 NXT_FLOW_MOD: ADD NXM_OF_ETH_TYPE(0800) actions=ct(commit,exec(load:0x1->NXM_NX_CT_LABEL[0..63],load:0->NXM_NX_CT_LABEL[64..127]))
diff --git a/utilities/ovs-ofctl.8.in b/utilities/ovs-ofctl.8.in
index e2e26f7..54ad2b0 100644
--- a/utilities/ovs-ofctl.8.in
+++ b/utilities/ovs-ofctl.8.in
@@ -2300,6 +2300,12 @@ Observation Domain ID sent in every IPFIX flow record.  Defaults to 0.
 .IP "\fBobs_point_id=\fIid\fR"
 When sending samples to IPFIX collectors, the unsigned 32-bit integer
 Observation Point ID sent in every IPFIX flow record.  Defaults to 0.
+.IP "\fBsampling_port=\fIport\fR"
+Sample packets on the port.  It can be set as input port or output
+port.  When this option is omitted, or specified as \fBNONE\fB, IPFIX
+does not differentiate between ingress packets and egress packets and
+does not export egress tunnel information.  This option was added in
+Open vSwitch 2.5.90.
 .RE
 .IP
 Refer to \fBovs\-vswitchd.conf.db\fR(8) for more details on
diff --git a/vswitchd/bridge.c b/vswitchd/bridge.c
index 41ec4ba..4273552 100644
--- a/vswitchd/bridge.c
+++ b/vswitchd/bridge.c
@@ -1225,6 +1225,9 @@ bridge_configure_ipfix(struct bridge *br)
                     ? *fe_cfg->ipfix->cache_active_timeout : 0;
                 opts->cache_max_flows = fe_cfg->ipfix->cache_max_flows
                     ? *fe_cfg->ipfix->cache_max_flows : 0;
+                opts->enable_tunnel_sampling = smap_get_bool(
+                                                   &fe_cfg->ipfix->other_config,
+                                                  "enable-tunnel-sampling", true);
                 opts++;
             }
         }
diff --git a/vswitchd/vswitch.xml b/vswitchd/vswitch.xml
index 5cf1ee1..5a65552 100644
--- a/vswitchd/vswitch.xml
+++ b/vswitchd/vswitch.xml
@@ -4655,6 +4655,94 @@
       disabled.
     </column>
 
+    <column name="other_config" key="enable-tunnel-sampling"
+            type='{"type": "boolean"}'>
+      <p>
+        Set to <code>true</code> to enable sampling and reporting tunnel
+        header 7-tuples in IPFIX flow records.  Tunnel sampling is disabled
+        by default.
+      </p>
+
+      <p>
+        The following enterprise entities report the sampled tunnel info:
+      </p>
+
+      <dl>
+        <dt>tunnelType:</dt>
+        <dd>
+          <p>ID: 891, and enterprise ID 6876 (VMware).</p>
+          <p>type: unsigned 8-bit integer.</p>
+          <p>data type semantics: identifier.</p>
+          <p>description: Identifier of the layer 2 network overlay network
+          encapsulation type: 0x01 VxLAN, 0x02 GRE, 0x03 LISP, 0x05 IPsec+GRE,
+          0x07 GENEVE.</p>
+        </dd>
+        <dt>tunnelKey:</dt>
+        <dd>
+          <p>ID: 892, and enterprise ID 6876 (VMware).</p>
+          <p>type: variable-length octetarray.</p>
+          <p>data type semantics: identifier.</p>
+          <p>description: Key which is used for identifying an individual
+          traffic flow within a VxLAN (24-bit VNI), GENEVE (24-bit VNI),
+          GRE (32-bit key), or LISP (24-bit instance ID) tunnel. The
+          key is encoded in this octetarray as a 3-, 4-, or 8-byte integer
+          ID in network byte order.</p>
+        </dd>
+        <dt>tunnelSourceIPv4Address:</dt>
+        <dd>
+          <p>ID: 893, and enterprise ID 6876 (VMware).</p>
+          <p>type: unsigned 32-bit integer.</p>
+          <p>data type semantics: identifier.</p>
+          <p>description: The IPv4 source address in the tunnel IP packet
+          header.</p>
+        </dd>
+        <dt>tunnelDestinationIPv4Address:</dt>
+        <dd>
+          <p>ID: 894, and enterprise ID 6876 (VMware).</p>
+          <p>type: unsigned 32-bit integer.</p>
+          <p>data type semantics: identifier.</p>
+          <p>description: The IPv4 destination address in the tunnel IP
+          packet header.</p>
+        </dd>
+        <dt>tunnelProtocolIdentifier:</dt>
+        <dd>
+          <p>ID: 895, and enterprise ID 6876 (VMware).</p>
+          <p>type: unsigned 8-bit integer.</p>
+          <p>data type semantics: identifier.</p>
+          <p>description: The value of the protocol number in the tunnel
+          IP packet header. The protocol number identifies the tunnel IP
+          packet payload type.</p>
+        </dd>
+        <dt>tunnelSourceTransportPort:</dt>
+        <dd>
+          <p>ID: 896, and enterprise ID 6876 (VMware).</p>
+          <p>type: unsigned 16-bit integer.</p>
+          <p>data type semantics: identifier.</p>
+          <p>description: The source port identifier in the tunnel transport
+          header. For the transport protocols UDP, TCP, and SCTP, this is
+          the source port number given in the respective header.</p>
+        </dd>
+        <dt>tunnelDestinationTransportPort:</dt>
+        <dd>
+          <p>ID: 897, and enterprise ID 6876 (VMware).</p>
+          <p>type: unsigned 16-bit integer.</p>
+          <p>data type semantics: identifier.</p>
+          <p>description: The destination port identifier in the tunnel
+          transport header. For the transport protocols UDP, TCP, and SCTP,
+          this is the destination port number given in the respective header.
+          </p>
+        </dd>
+      </dl>
+
+      <p>
+        Before Open vSwitch 2.5.90, <ref column="other_config"
+        key="enable-tunnel-sampling"/> was only supported with per-bridge
+        sampling, and ignored otherwise.  Open vSwitch 2.5.90 and later support
+        <ref column="other_config" key="enable-tunnel-sampling"/> for
+        per-bridge and per-flow sampling.
+      </p>
+    </column>
+
     <group title="Per-Bridge Sampling">
       <p>
         These values affect only per-bridge sampling.  See above for a
@@ -4678,86 +4766,6 @@
         specified, defaults to 0.
       </column>
 
-      <column name="other_config" key="enable-tunnel-sampling"
-              type='{"type": "boolean"}'>
-        <p>
-          Set to <code>true</code> to enable sampling and reporting tunnel
-          header 7-tuples in IPFIX flow records.  Tunnel sampling is disabled
-          by default.
-        </p>
-
-        <p>
-          The following enterprise entities report the sampled tunnel info:
-        </p>
-
-        <dl>
-          <dt>tunnelType:</dt>
-          <dd>
-            <p>ID: 891, and enterprise ID 6876 (VMware).</p>
-            <p>type: unsigned 8-bit integer.</p>
-            <p>data type semantics: identifier.</p>
-            <p>description: Identifier of the layer 2 network overlay network
-            encapsulation type: 0x01 VxLAN, 0x02 GRE, 0x03 LISP, 0x05 IPsec+GRE,
-            0x07 GENEVE.</p>
-          </dd>
-          <dt>tunnelKey:</dt>
-          <dd>
-            <p>ID: 892, and enterprise ID 6876 (VMware).</p>
-            <p>type: variable-length octetarray.</p>
-            <p>data type semantics: identifier.</p>
-            <p>description: Key which is used for identifying an individual
-            traffic flow within a VxLAN (24-bit VNI), GENEVE (24-bit VNI),
-            GRE (32-bit key), or LISP (24-bit instance ID) tunnel. The
-            key is encoded in this octetarray as a 3-, 4-, or 8-byte integer
-            ID in network byte order.</p>
-          </dd>
-          <dt>tunnelSourceIPv4Address:</dt>
-          <dd>
-            <p>ID: 893, and enterprise ID 6876 (VMware).</p>
-            <p>type: unsigned 32-bit integer.</p>
-            <p>data type semantics: identifier.</p>
-            <p>description: The IPv4 source address in the tunnel IP packet
-            header.</p>
-          </dd>
-          <dt>tunnelDestinationIPv4Address:</dt>
-          <dd>
-            <p>ID: 894, and enterprise ID 6876 (VMware).</p>
-            <p>type: unsigned 32-bit integer.</p>
-            <p>data type semantics: identifier.</p>
-            <p>description: The IPv4 destination address in the tunnel IP
-            packet header.</p>
-          </dd>
-          <dt>tunnelProtocolIdentifier:</dt>
-          <dd>
-            <p>ID: 895, and enterprise ID 6876 (VMware).</p>
-            <p>type: unsigned 8-bit integer.</p>
-            <p>data type semantics: identifier.</p>
-            <p>description: The value of the protocol number in the tunnel
-            IP packet header. The protocol number identifies the tunnel IP
-            packet payload type.</p>
-          </dd>
-          <dt>tunnelSourceTransportPort:</dt>
-          <dd>
-            <p>ID: 896, and enterprise ID 6876 (VMware).</p>
-            <p>type: unsigned 16-bit integer.</p>
-            <p>data type semantics: identifier.</p>
-            <p>description: The source port identifier in the tunnel transport
-            header. For the transport protocols UDP, TCP, and SCTP, this is
-            the source port number given in the respective header.</p>
-          </dd>
-          <dt>tunnelDestinationTransportPort:</dt>
-          <dd>
-            <p>ID: 897, and enterprise ID 6876 (VMware).</p>
-            <p>type: unsigned 16-bit integer.</p>
-            <p>data type semantics: identifier.</p>
-            <p>description: The destination port identifier in the tunnel
-            transport header. For the transport protocols UDP, TCP, and SCTP,
-            this is the destination port number given in the respective header.
-            </p>
-          </dd>
-        </dl>
-      </column>
-
       <column name="other_config" key="enable-input-sampling"
               type='{"type": "boolean"}'>
         By default, Open vSwitch samples and reports flows at bridge port input
-- 
2.1.3




More information about the dev mailing list