This patch enables port level IPFIX. Before this patch, OVS supported
per bridge IPFIX and per flow IPFX, and exporting packet tunnel headers
is only supported by bridge IPFIX. This patch adds port level IPFIX
for easy configuration and port level IPFIX also supports exporting
packet tunnel headers, just the same with bridge level IPFIX.
Three main things are done in this patch.
  1) Add a column ipfix in Port table to ref IPFIX table
  2) Each interface in the port should use the port IPFiX configuration
  3) A hash map is used to manage the port which is configured IPFIX

CLI to configure Port IPFIX:
  1) Configure
     ovs-vsctl -- set Port port0 ipfix=@i -- --id=@i create IPFIX \
         targets=\"10.24.122.72:4739\" sampling=1 obs_domain_id=123 \
         obs_point_id=456 cache_active_timeout=1 cache_max_flows=128 \
         other_config:enable-tunnel-sampling=true
  2) Clear
     ovs-vsctl clear Port port0 ipfix

Signed-off-by: Benli Ye <dani...@vmware.com>
---
 lib/odp-util.c                |  32 +++-
 lib/odp-util.h                |  19 +-
 ofproto/ofproto-dpif-ipfix.c  | 403 +++++++++++++++++++++++++++++++++++++++---
 ofproto/ofproto-dpif-ipfix.h  |  17 ++
 ofproto/ofproto-dpif-upcall.c |  39 +++-
 ofproto/ofproto-dpif-xlate.c  | 117 ++++++++----
 ofproto/ofproto-dpif-xlate.h  |   3 +-
 ofproto/ofproto-dpif.c        |  19 +-
 ofproto/ofproto-provider.h    |   7 +-
 ofproto/ofproto.c             |   7 +-
 ofproto/ofproto.h             |  23 +++
 tests/odp.at                  |   6 +-
 tests/ofproto-dpif.at         |  43 ++++-
 vswitchd/bridge.c             | 123 +++++++++++--
 vswitchd/vswitch.ovsschema    |   6 +-
 vswitchd/vswitch.xml          |  34 +++-
 16 files changed, 788 insertions(+), 110 deletions(-)

diff --git a/lib/odp-util.c b/lib/odp-util.c
index 10fb6c2..dcf678e 100644
--- a/lib/odp-util.c
+++ b/lib/odp-util.c
@@ -316,10 +316,16 @@ format_odp_userspace_action(struct ds *ds, const struct 
nlattr *attr)
                               cookie.flow_sample.collector_set_id,
                               cookie.flow_sample.obs_domain_id,
                               cookie.flow_sample.obs_point_id);
-            } else if (userdata_len >= sizeof cookie.ipfix
-                       && cookie.type == USER_ACTION_COOKIE_IPFIX) {
-                ds_put_format(ds, ",ipfix(output_port=%"PRIu32")",
-                              cookie.ipfix.output_odp_port);
+            } else if (userdata_len >= sizeof cookie.bridge_ipfix
+                       && cookie.type == USER_ACTION_COOKIE_BRIDGE_IPFIX) {
+                ds_put_format(ds, ",bridge_ipfix(output_port=%"PRIu32")",
+                              cookie.bridge_ipfix.output_odp_port);
+            } else if (userdata_len >= sizeof cookie.port_ipfix
+                       && cookie.type == USER_ACTION_COOKIE_PORT_IPFIX) {
+                ds_put_format(ds, ",port_ipfix(ofp_port=%"PRIu16
+                              ",output_port=%"PRIu32")",
+                              cookie.port_ipfix.ofp_port,
+                              cookie.port_ipfix.output_odp_port);
             } else {
                 userdata_unspec = true;
             }
@@ -909,6 +915,7 @@ parse_odp_userspace_action(const char *s, struct ofpbuf 
*actions)
 
     {
         uint32_t output;
+        uint16_t ofp_port;
         uint32_t probability;
         uint32_t collector_set_id;
         uint32_t obs_domain_id;
@@ -963,13 +970,22 @@ parse_odp_userspace_action(const char *s, struct ofpbuf 
*actions)
             cookie.flow_sample.obs_point_id = obs_point_id;
             user_data = &cookie;
             user_data_size = sizeof cookie.flow_sample;
-        } else if (ovs_scan(&s[n], ",ipfix(output_port=%"SCNi32")%n",
+        } else if (ovs_scan(&s[n], ",bridge_ipfix(output_port=%"SCNi32")%n",
                             &output, &n1) ) {
             n += n1;
-            cookie.type = USER_ACTION_COOKIE_IPFIX;
-            cookie.ipfix.output_odp_port = u32_to_odp(output);
+            cookie.type = USER_ACTION_COOKIE_BRIDGE_IPFIX;
+            cookie.bridge_ipfix.output_odp_port = u32_to_odp(output);
             user_data = &cookie;
-            user_data_size = sizeof cookie.ipfix;
+            user_data_size = sizeof cookie.bridge_ipfix;
+        } else if (ovs_scan(&s[n], ",port_ipfix(ofp_port=%"SCNi16","
+                            "output_port=%"SCNi32")%n",
+                            &ofp_port, &output, &n1) ) {
+            n += n1;
+            cookie.type = USER_ACTION_COOKIE_PORT_IPFIX;
+            cookie.port_ipfix.ofp_port = u16_to_ofp(ofp_port);
+            cookie.port_ipfix.output_odp_port = u32_to_odp(output);
+            user_data = &cookie;
+            user_data_size = sizeof cookie.port_ipfix;
         } else if (ovs_scan(&s[n], ",userdata(%n",
                             &n1)) {
             char *end;
diff --git a/lib/odp-util.h b/lib/odp-util.h
index 51cf5c3..4c9f271 100644
--- a/lib/odp-util.h
+++ b/lib/odp-util.h
@@ -274,10 +274,11 @@ enum slow_path_reason commit_odp_actions(const struct 
flow *,
 
 enum user_action_cookie_type {
     USER_ACTION_COOKIE_UNSPEC,
-    USER_ACTION_COOKIE_SFLOW,        /* Packet for per-bridge sFlow sampling. 
*/
-    USER_ACTION_COOKIE_SLOW_PATH,    /* Userspace must process this flow. */
-    USER_ACTION_COOKIE_FLOW_SAMPLE,  /* Packet for per-flow sampling. */
-    USER_ACTION_COOKIE_IPFIX,        /* Packet for per-bridge IPFIX sampling. 
*/
+    USER_ACTION_COOKIE_SFLOW,         /* Packet for per-bridge sFlow sampling. 
*/
+    USER_ACTION_COOKIE_SLOW_PATH,     /* Userspace must process this flow. */
+    USER_ACTION_COOKIE_FLOW_SAMPLE,   /* Packet for per-flow sampling. */
+    USER_ACTION_COOKIE_BRIDGE_IPFIX,  /* Packet for per-bridge IPFIX sampling. 
*/
+    USER_ACTION_COOKIE_PORT_IPFIX,    /* Packet for per-port IPFIX sampling. */
 };
 
 /* user_action_cookie is passed as argument to OVS_ACTION_ATTR_USERSPACE.
@@ -306,9 +307,15 @@ union user_action_cookie {
     } flow_sample;
 
     struct {
-        uint16_t   type;            /* USER_ACTION_COOKIE_IPFIX. */
+        uint16_t   type;            /* USER_ACTION_COOKIE_BRIDGE_IPFIX. */
         odp_port_t output_odp_port; /* The output odp port. */
-    } ipfix;
+    } bridge_ipfix;
+
+    struct {
+        uint16_t   type;            /* USER_ACTION_COOKIE_PORT_IPFIX. */
+        ofp_port_t ofp_port;        /* ofp_port */
+        odp_port_t output_odp_port; /* The output odp port. */
+    } port_ipfix;
 };
 BUILD_ASSERT_DECL(sizeof(union user_action_cookie) == 16);
 
diff --git a/ofproto/ofproto-dpif-ipfix.c b/ofproto/ofproto-dpif-ipfix.c
index 6d088a6..3e09cca 100644
--- a/ofproto/ofproto-dpif-ipfix.c
+++ b/ofproto/ofproto-dpif-ipfix.c
@@ -93,6 +93,17 @@ struct dpif_ipfix_exporter {
     uint32_t cache_max_flows;
 };
 
+struct dpif_ipfix_port_exporter {
+    struct dpif_ipfix_exporter exporter;
+    struct ofproto_ipfix_port_exporter_options *options;
+    uint32_t probability;
+};
+
+struct dpif_ipfix_port_exporter_map_node {
+    struct hmap_node node;
+    struct dpif_ipfix_port_exporter exporter;
+};
+
 struct dpif_ipfix_bridge_exporter {
     struct dpif_ipfix_exporter exporter;
     struct ofproto_ipfix_bridge_exporter_options *options;
@@ -111,6 +122,7 @@ struct dpif_ipfix_flow_exporter_map_node {
 
 struct dpif_ipfix {
     struct dpif_ipfix_bridge_exporter bridge_exporter;
+    struct hmap port_exporter_map;  /* dpif_ipfix_port_exporter_map_node. */
     struct hmap flow_exporter_map;  /* dpif_ipfix_flow_exporter_map_node. */
     struct hmap tunnel_ports;       /* Contains "struct dpif_ipfix_port"s.
                                      * It makes tunnel port lookups faster in
@@ -424,6 +436,42 @@ static void get_export_time_now(uint64_t *, uint32_t *);
 static void dpif_ipfix_cache_expire_now(struct dpif_ipfix_exporter *, bool);
 
 static bool
+ofproto_ipfix_port_exporter_options_equal(
+    const struct ofproto_ipfix_port_exporter_options *a,
+    const struct ofproto_ipfix_port_exporter_options *b)
+{
+    return (a->obs_domain_id == b->obs_domain_id
+            && a->obs_point_id == b->obs_point_id
+            && a->sampling_rate == b->sampling_rate
+            && a->cache_active_timeout == b->cache_active_timeout
+            && a->cache_max_flows == b->cache_max_flows
+            && a->enable_tunnel_sampling == b->enable_tunnel_sampling
+            && a->enable_input_sampling == b->enable_input_sampling
+            && a->enable_output_sampling == b->enable_output_sampling
+            && sset_equals(&a->targets, &b->targets));
+}
+
+static struct ofproto_ipfix_port_exporter_options *
+ofproto_ipfix_port_exporter_options_clone(
+    const struct ofproto_ipfix_port_exporter_options *old)
+{
+    struct ofproto_ipfix_port_exporter_options *new =
+        xmemdup(old, sizeof *old);
+    sset_clone(&new->targets, &old->targets);
+    return new;
+}
+
+static void
+ofproto_ipfix_port_exporter_options_destroy(
+    struct ofproto_ipfix_port_exporter_options *options)
+{
+    if (options) {
+        sset_destroy(&options->targets);
+        free(options);
+    }
+}
+
+static bool
 ofproto_ipfix_bridge_exporter_options_equal(
     const struct ofproto_ipfix_bridge_exporter_options *a,
     const struct ofproto_ipfix_bridge_exporter_options *b)
@@ -643,6 +691,102 @@ dpif_ipfix_get_tunnel_port(const struct dpif_ipfix *di, 
odp_port_t odp_port)
     return dip != NULL;
 }
 
+static struct dpif_ipfix_port_exporter_map_node*
+dpif_ipfix_find_port_exporter_map_node(
+    const struct dpif_ipfix *di, ofp_port_t ofp_port)
+    OVS_REQUIRES(mutex)
+{
+    struct dpif_ipfix_port_exporter_map_node *exporter_node;
+
+    if (!di) {
+        return NULL;
+    }
+
+    HMAP_FOR_EACH_WITH_HASH (exporter_node, node,
+                             hash_ofp_port(ofp_port),
+                             &di->port_exporter_map) {
+        if (exporter_node->exporter.options != NULL &&
+            exporter_node->exporter.options->ofp_port ==
+            ofp_port) {
+            return exporter_node;
+        }
+    }
+
+    return NULL;
+}
+
+static void
+dpif_ipfix_port_exporter_init(struct dpif_ipfix_port_exporter *exporter)
+{
+    dpif_ipfix_exporter_init(&exporter->exporter);
+    exporter->options = NULL;
+    exporter->probability = 0;
+}
+
+static void
+dpif_ipfix_port_exporter_clear(struct dpif_ipfix_port_exporter *exporter)
+{
+    dpif_ipfix_exporter_clear(&exporter->exporter);
+    ofproto_ipfix_port_exporter_options_destroy(exporter->options);
+    exporter->options = NULL;
+    exporter->probability = 0;
+}
+
+static void
+dpif_ipfix_port_exporter_destroy(struct dpif_ipfix_port_exporter *exporter)
+{
+    dpif_ipfix_port_exporter_clear(exporter);
+    dpif_ipfix_exporter_destroy(&exporter->exporter);
+}
+
+static void
+dpif_ipfix_port_exporter_set_options(
+    struct dpif_ipfix_port_exporter *exporter,
+    const struct ofproto_ipfix_port_exporter_options *options)
+{
+    bool options_changed;
+
+    if (!options || sset_is_empty(&options->targets)) {
+        /* No point in doing any work if there are no targets. */
+        dpif_ipfix_port_exporter_clear(exporter);
+        return;
+    }
+
+    options_changed = (
+        !exporter->options
+        || !ofproto_ipfix_port_exporter_options_equal(
+            options, exporter->options));
+
+    /* Configure collectors if options have changed or if we're
+     * shortchanged in collectors (which indicates that opening one or
+     * more of the configured collectors failed, so that we should
+     * retry). */
+    if (options_changed
+        || collectors_count(exporter->exporter.collectors)
+            < sset_count(&options->targets)) {
+        if (!dpif_ipfix_exporter_set_options(
+                &exporter->exporter, &options->targets,
+                options->cache_active_timeout, options->cache_max_flows)) {
+            return;
+        }
+    }
+
+    /* Avoid reconfiguring if options didn't change. */
+    if (!options_changed) {
+        return;
+    }
+
+    ofproto_ipfix_port_exporter_options_destroy(exporter->options);
+    exporter->options = ofproto_ipfix_port_exporter_options_clone(options);
+    exporter->probability =
+        MAX(1, UINT32_MAX / exporter->options->sampling_rate);
+
+    /* Run over the cache as some entries might have expired after
+     * changing the timeouts. */
+    dpif_ipfix_cache_expire_now(&exporter->exporter, false);
+}
+
+
 static void
 dpif_ipfix_bridge_exporter_init(struct dpif_ipfix_bridge_exporter *exporter)
 {
@@ -805,32 +949,64 @@ dpif_ipfix_flow_exporter_set_options(
 void
 dpif_ipfix_set_options(
     struct dpif_ipfix *di,
+    const struct ofproto_ipfix_port_exporter_options *port_exporter_options,
+    const ofp_port_t ofp_port,
     const struct ofproto_ipfix_bridge_exporter_options 
*bridge_exporter_options,
     const struct ofproto_ipfix_flow_exporter_options *flow_exporters_options,
     size_t n_flow_exporters_options) OVS_EXCLUDED(mutex)
 {
     int i;
     struct ofproto_ipfix_flow_exporter_options *options;
-    struct dpif_ipfix_flow_exporter_map_node *node, *next;
+    struct dpif_ipfix_port_exporter_map_node *port_node, *port_next;
+    struct dpif_ipfix_flow_exporter_map_node *flow_node, *flow_next;
     size_t n_broken_flow_exporters_options = 0;
 
     ovs_mutex_lock(&mutex);
     dpif_ipfix_bridge_exporter_set_options(&di->bridge_exporter,
                                            bridge_exporter_options);
 
+    /* Add/update/remove current port exporters */
+    if (ofp_port != OFPP_NONE) {
+        port_node = dpif_ipfix_find_port_exporter_map_node(di,
+                        ofp_port);
+        if (port_exporter_options) {
+            if (!port_node) {
+                port_node = xzalloc(sizeof *port_node);
+                dpif_ipfix_port_exporter_init(&port_node->exporter);
+                hmap_insert(&di->port_exporter_map, &port_node->node,
+                    hash_ofp_port(port_exporter_options->ofp_port));
+            }
+            dpif_ipfix_port_exporter_set_options(&port_node->exporter,
+                port_exporter_options);
+        } else {
+            if (port_node) {
+                /* Remove unused IPFIX port in hash map */
+                HMAP_FOR_EACH_SAFE (port_node, port_next, node,
+                                    &di->port_exporter_map) {
+                    hmap_remove(&di->port_exporter_map, &port_node->node);
+                    dpif_ipfix_port_exporter_destroy(&port_node->exporter);
+                    free(port_node);
+                }
+            }
+        }
+
+        ovs_mutex_unlock(&mutex);
+        return;
+    }
+
     /* Add new flow exporters and update current flow exporters. */
     options = (struct ofproto_ipfix_flow_exporter_options *)
         flow_exporters_options;
     for (i = 0; i < n_flow_exporters_options; i++) {
-        node = dpif_ipfix_find_flow_exporter_map_node(
+        flow_node = dpif_ipfix_find_flow_exporter_map_node(
             di, options->collector_set_id);
-        if (!node) {
-            node = xzalloc(sizeof *node);
-            dpif_ipfix_flow_exporter_init(&node->exporter);
-            hmap_insert(&di->flow_exporter_map, &node->node,
+        if (!flow_node) {
+            flow_node = xzalloc(sizeof *flow_node);
+            dpif_ipfix_flow_exporter_init(&flow_node->exporter);
+            hmap_insert(&di->flow_exporter_map, &flow_node->node,
                         hash_int(options->collector_set_id, 0));
         }
-        if (!dpif_ipfix_flow_exporter_set_options(&node->exporter, options)) {
+        if (!dpif_ipfix_flow_exporter_set_options(&flow_node->exporter, 
options)) {
             n_broken_flow_exporters_options++;
         }
         options++;
@@ -841,22 +1017,22 @@ dpif_ipfix_set_options(
 
     /* Remove dropped flow exporters, if any needs to be removed. */
     if (hmap_count(&di->flow_exporter_map) > n_flow_exporters_options) {
-        HMAP_FOR_EACH_SAFE (node, next, node, &di->flow_exporter_map) {
+        HMAP_FOR_EACH_SAFE (flow_node, flow_next, node, 
&di->flow_exporter_map) {
             /* This is slow but doesn't take any extra memory, and
              * this table is not supposed to contain many rows anyway. */
             options = (struct ofproto_ipfix_flow_exporter_options *)
                 flow_exporters_options;
             for (i = 0; i < n_flow_exporters_options; i++) {
-              if (node->exporter.options->collector_set_id
+              if (flow_node->exporter.options->collector_set_id
                   == options->collector_set_id) {
                   break;
               }
               options++;
             }
             if (i == n_flow_exporters_options) {  // Not found.
-                hmap_remove(&di->flow_exporter_map, &node->node);
-                dpif_ipfix_flow_exporter_destroy(&node->exporter);
-                free(node);
+                hmap_remove(&di->flow_exporter_map, &flow_node->node);
+                dpif_ipfix_flow_exporter_destroy(&flow_node->exporter);
+                free(flow_node);
             }
         }
     }
@@ -872,6 +1048,7 @@ dpif_ipfix_create(void)
     struct dpif_ipfix *di;
     di = xzalloc(sizeof *di);
     dpif_ipfix_bridge_exporter_init(&di->bridge_exporter);
+    hmap_init(&di->port_exporter_map);
     hmap_init(&di->flow_exporter_map);
     hmap_init(&di->tunnel_ports);
     ovs_refcount_init(&di->ref_cnt);
@@ -888,6 +1065,94 @@ dpif_ipfix_ref(const struct dpif_ipfix *di_)
     return di;
 }
 
+bool
+dpif_ipfix_port_ipfix_enable(const struct dpif_ipfix *di,
+                             ofp_port_t ofp_port)
+    OVS_EXCLUDED(mutex)
+{
+    bool ret = false;
+    if (!di) {
+        return ret;
+    }
+
+    ovs_mutex_lock(&mutex);
+    ret = (NULL != dpif_ipfix_find_port_exporter_map_node(di, ofp_port));
+    ovs_mutex_unlock(&mutex);
+    return ret;
+}
+
+uint32_t
+dpif_ipfix_get_port_exporter_probability(const struct dpif_ipfix *di,
+                                         ofp_port_t ofp_port)
+    OVS_EXCLUDED(mutex)
+{
+    uint32_t ret = 0;
+    struct dpif_ipfix_port_exporter_map_node* exporter = NULL;
+
+    ovs_mutex_lock(&mutex);
+    exporter = dpif_ipfix_find_port_exporter_map_node(di, ofp_port);
+    if (exporter) {
+        ret = exporter->exporter.probability;
+    }
+    ovs_mutex_unlock(&mutex);
+
+    return ret;
+}
+
+bool
+dpif_ipfix_get_port_exporter_input_sampling(const struct dpif_ipfix *di,
+                                            ofp_port_t ofp_port)
+    OVS_EXCLUDED(mutex)
+{
+    bool ret = false;
+    struct dpif_ipfix_port_exporter_map_node* exporter = NULL; 
+
+    ovs_mutex_lock(&mutex);
+    exporter = dpif_ipfix_find_port_exporter_map_node(di, ofp_port);
+    if (exporter) {
+        ret = exporter->exporter.options->enable_input_sampling;
+    }
+    ovs_mutex_unlock(&mutex);
+
+    return ret;
+}
+
+bool
+dpif_ipfix_get_port_exporter_output_sampling(const struct dpif_ipfix *di,
+                                             ofp_port_t ofp_port)
+    OVS_EXCLUDED(mutex)
+{
+    bool ret = false;
+    struct dpif_ipfix_port_exporter_map_node* exporter = NULL;
+
+    ovs_mutex_lock(&mutex);
+    exporter = dpif_ipfix_find_port_exporter_map_node(di, ofp_port);
+    if (exporter) {
+        ret = exporter->exporter.options->enable_output_sampling;
+    }
+    ovs_mutex_unlock(&mutex);
+
+    return ret;
+}
+
+bool
+dpif_ipfix_get_port_exporter_tunnel_sampling(const struct dpif_ipfix *di,
+                                             ofp_port_t ofp_port)
+    OVS_EXCLUDED(mutex)
+{
+    bool ret = false;
+    struct dpif_ipfix_port_exporter_map_node* exporter = NULL;
+
+    ovs_mutex_lock(&mutex);
+    exporter = dpif_ipfix_find_port_exporter_map_node(di, ofp_port);
+    if (exporter) {
+        ret = exporter->exporter.options->enable_tunnel_sampling;
+    }
+    ovs_mutex_unlock(&mutex);
+
+    return ret;
+}
+
 uint32_t
 dpif_ipfix_get_bridge_exporter_probability(const struct dpif_ipfix *di)
     OVS_EXCLUDED(mutex)
@@ -903,7 +1168,7 @@ bool
 dpif_ipfix_get_bridge_exporter_input_sampling(const struct dpif_ipfix *di)
     OVS_EXCLUDED(mutex)
 {
-    bool ret = true;
+    bool ret = false;
     ovs_mutex_lock(&mutex);
     if (di->bridge_exporter.options) {
         ret = di->bridge_exporter.options->enable_input_sampling;
@@ -916,7 +1181,7 @@ bool
 dpif_ipfix_get_bridge_exporter_output_sampling(const struct dpif_ipfix *di)
     OVS_EXCLUDED(mutex)
 {
-    bool ret = true;
+    bool ret = false;
     ovs_mutex_lock(&mutex);
     if (di->bridge_exporter.options) {
         ret = di->bridge_exporter.options->enable_output_sampling;
@@ -941,15 +1206,24 @@ dpif_ipfix_get_bridge_exporter_tunnel_sampling(const 
struct dpif_ipfix *di)
 static void
 dpif_ipfix_clear(struct dpif_ipfix *di) OVS_REQUIRES(mutex)
 {
-    struct dpif_ipfix_flow_exporter_map_node *exp_node, *exp_next;
+    struct dpif_ipfix_flow_exporter_map_node *flow_exp_node, *flow_exp_next;
+    struct dpif_ipfix_port_exporter_map_node *port_exp_node, *port_exp_next;
     struct dpif_ipfix_port *dip, *next;
 
     dpif_ipfix_bridge_exporter_clear(&di->bridge_exporter);
 
-    HMAP_FOR_EACH_SAFE (exp_node, exp_next, node, &di->flow_exporter_map) {
-        hmap_remove(&di->flow_exporter_map, &exp_node->node);
-        dpif_ipfix_flow_exporter_destroy(&exp_node->exporter);
-        free(exp_node);
+    HMAP_FOR_EACH_SAFE (port_exp_node, port_exp_next,
+                        node, &di->port_exporter_map) {
+        hmap_remove(&di->port_exporter_map, &port_exp_node->node);
+        dpif_ipfix_port_exporter_destroy(&port_exp_node->exporter);
+        free(port_exp_node);
+    }
+
+    HMAP_FOR_EACH_SAFE (flow_exp_node, flow_exp_next, node,
+                        &di->flow_exporter_map) {
+        hmap_remove(&di->flow_exporter_map, &flow_exp_node->node);
+        dpif_ipfix_flow_exporter_destroy(&flow_exp_node->exporter);
+        free(flow_exp_node);
     }
 
     HMAP_FOR_EACH_SAFE (dip, next, hmap_node, &di->tunnel_ports) {
@@ -964,6 +1238,7 @@ dpif_ipfix_unref(struct dpif_ipfix *di) OVS_EXCLUDED(mutex)
         ovs_mutex_lock(&mutex);
         dpif_ipfix_clear(di);
         dpif_ipfix_bridge_exporter_destroy(&di->bridge_exporter);
+        hmap_destroy(&di->port_exporter_map);
         hmap_destroy(&di->flow_exporter_map);
         hmap_destroy(&di->tunnel_ports);
         free(di);
@@ -1680,6 +1955,72 @@ dpif_ipfix_sample(struct dpif_ipfix_exporter *exporter,
 }
 
 static bool
+port_exporter_enabled(struct dpif_ipfix_port_exporter exporter)
+{
+    return exporter.probability > 0;
+}
+
+void
+dpif_ipfix_port_sample(struct dpif_ipfix *di, const struct dp_packet *packet,
+                       const struct flow *flow, odp_port_t input_odp_port,
+                       ofp_port_t ofp_port, odp_port_t output_odp_port,
+                       const struct flow_tnl *output_tunnel_key)
+    OVS_EXCLUDED(mutex)
+{
+    uint64_t packet_delta_count;
+    const struct flow_tnl *tunnel_key = NULL;
+    struct dpif_ipfix_port * tunnel_port = NULL;
+    struct dpif_ipfix_port_exporter_map_node *node = NULL;
+
+    ovs_mutex_lock(&mutex);
+    node = dpif_ipfix_find_port_exporter_map_node(di, ofp_port);
+ 
+    if (!node || !port_exporter_enabled(node->exporter)) {
+        ovs_mutex_unlock(&mutex);
+        return;
+    }
+
+    /* Skip BFD packets:
+     * Bidirectional Forwarding Detection(BFD) packets are for monitoring
+     * the tunnel link status and consumed by ovs itself. No need to
+     * smaple them.
+     * CF  IETF RFC 5881, BFD control packet is the UDP packet with
+     * destination port 3784, and BFD echo packet is the UDP packet with
+     * destination port 3785.
+     */
+    if (is_ip_any(flow) &&
+        flow->nw_proto == IPPROTO_UDP &&
+        (flow->tp_dst == htons(BFD_CONTROL_DEST_PORT) ||
+         flow->tp_dst == htons(BFD_ECHO_DEST_PORT))) {
+        ovs_mutex_unlock(&mutex);
+        return;
+    }
+
+    /* Use the sampling probability as an approximation of the number
+     * of matched packets. */
+    packet_delta_count = UINT32_MAX / node->exporter.probability;
+    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,
+                      node->exporter.options->obs_domain_id,
+                      node->exporter.options->obs_point_id,
+                      output_odp_port, tunnel_port, tunnel_key);
+    ovs_mutex_unlock(&mutex);
+}
+
+static bool
 bridge_exporter_enabled(struct dpif_ipfix *di)
 {
     return di->bridge_exporter.probability > 0;
@@ -1852,13 +2193,21 @@ dpif_ipfix_run(struct dpif_ipfix *di) 
OVS_EXCLUDED(mutex)
     uint64_t export_time_usec;
     uint32_t export_time_sec;
     struct dpif_ipfix_flow_exporter_map_node *flow_exporter_node;
+    struct dpif_ipfix_port_exporter_map_node *port_exporter_node;
 
     ovs_mutex_lock(&mutex);
     get_export_time_now(&export_time_usec, &export_time_sec);
+    HMAP_FOR_EACH (port_exporter_node, node, &di->port_exporter_map) {
+        if (port_exporter_enabled(port_exporter_node->exporter)) {
+            dpif_ipfix_cache_expire(
+                &port_exporter_node->exporter.exporter, false,
+                export_time_usec, export_time_sec);
+        }
+    }
     if (bridge_exporter_enabled(di)) {
-      dpif_ipfix_cache_expire(
-          &di->bridge_exporter.exporter, false, export_time_usec,
-          export_time_sec);
+        dpif_ipfix_cache_expire(
+            &di->bridge_exporter.exporter, false, export_time_usec,
+            export_time_sec);
     }
     HMAP_FOR_EACH (flow_exporter_node, node, &di->flow_exporter_map) {
         dpif_ipfix_cache_expire(
@@ -1873,8 +2222,18 @@ dpif_ipfix_wait(struct dpif_ipfix *di) 
OVS_EXCLUDED(mutex)
 {
     long long int next_timeout_msec = LLONG_MAX;
     struct dpif_ipfix_flow_exporter_map_node *flow_exporter_node;
+    struct dpif_ipfix_port_exporter_map_node *port_exporter_node;
 
     ovs_mutex_lock(&mutex);
+    HMAP_FOR_EACH (port_exporter_node, node, &di->port_exporter_map) {
+        if (port_exporter_enabled(port_exporter_node->exporter)) {
+            if (ipfix_cache_next_timeout_msec(
+                    &port_exporter_node->exporter.exporter,
+                    &next_timeout_msec)) {
+                poll_timer_wait_until(next_timeout_msec);
+            }      
+        }
+    }
     if (bridge_exporter_enabled(di)) {
         if (ipfix_cache_next_timeout_msec(
                 &di->bridge_exporter.exporter, &next_timeout_msec)) {
diff --git a/ofproto/ofproto-dpif-ipfix.h b/ofproto/ofproto-dpif-ipfix.h
index 2bb0e43..54d4e74 100644
--- a/ofproto/ofproto-dpif-ipfix.h
+++ b/ofproto/ofproto-dpif-ipfix.h
@@ -24,6 +24,7 @@
 
 struct flow;
 struct dp_packet;
+struct ofproto_ipfix_port_exporter_options;
 struct ofproto_ipfix_bridge_exporter_options;
 struct ofproto_ipfix_flow_exporter_options;
 struct flow_tnl;
@@ -36,6 +37,17 @@ void dpif_ipfix_unref(struct dpif_ipfix *);
 void dpif_ipfix_add_tunnel_port(struct dpif_ipfix *, struct ofport *, 
odp_port_t);
 void dpif_ipfix_del_tunnel_port(struct dpif_ipfix *, odp_port_t);
 
+bool dpif_ipfix_port_ipfix_enable(const struct dpif_ipfix *di,
+                                  ofp_port_t ofp_port);
+uint32_t dpif_ipfix_get_port_exporter_probability(const struct dpif_ipfix *,
+                                                  ofp_port_t ofp_port);
+bool dpif_ipfix_get_port_exporter_tunnel_sampling(const struct dpif_ipfix *,
+                                                  ofp_port_t ofp_port);
+bool dpif_ipfix_get_port_exporter_input_sampling(const struct dpif_ipfix *,
+                                                 ofp_port_t ofp_port);
+bool dpif_ipfix_get_port_exporter_output_sampling(const struct dpif_ipfix *,
+                                                  ofp_port_t ofp_port);
+
 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 *);
@@ -43,9 +55,14 @@ bool dpif_ipfix_get_bridge_exporter_output_sampling(const 
struct dpif_ipfix *);
 bool dpif_ipfix_get_tunnel_port(const struct dpif_ipfix *, odp_port_t);
 void dpif_ipfix_set_options(
     struct dpif_ipfix *,
+    const struct ofproto_ipfix_port_exporter_options *,
+    const ofp_port_t,
     const struct ofproto_ipfix_bridge_exporter_options *,
     const struct ofproto_ipfix_flow_exporter_options *, size_t);
 
+void dpif_ipfix_port_sample(struct dpif_ipfix *, const struct dp_packet *,
+                            const struct flow *, odp_port_t,
+                            ofp_port_t, odp_port_t, const struct flow_tnl *);
 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 *);
diff --git a/ofproto/ofproto-dpif-upcall.c b/ofproto/ofproto-dpif-upcall.c
index 2612b7d..aa69904 100644
--- a/ofproto/ofproto-dpif-upcall.c
+++ b/ofproto/ofproto-dpif-upcall.c
@@ -183,7 +183,8 @@ enum upcall_type {
     MISS_UPCALL,                /* A flow miss.  */
     SFLOW_UPCALL,               /* sFlow sample. */
     FLOW_SAMPLE_UPCALL,         /* Per-flow sampling. */
-    IPFIX_UPCALL                /* Per-bridge sampling. */
+    BRIDGE_IPFIX_UPCALL,        /* Per-bridge sampling. */
+    PORT_IPFIX_UPCALL,          /* Per-port sampling. */
 };
 
 enum reval_result {
@@ -972,9 +973,12 @@ classify_upcall(enum dpif_upcall_type type, const struct 
nlattr *userdata)
     } else if (userdata_len == MAX(8, sizeof cookie.flow_sample)
                && cookie.type == USER_ACTION_COOKIE_FLOW_SAMPLE) {
         return FLOW_SAMPLE_UPCALL;
-    } else if (userdata_len == MAX(8, sizeof cookie.ipfix)
-               && cookie.type == USER_ACTION_COOKIE_IPFIX) {
-        return IPFIX_UPCALL;
+    } else if (userdata_len == MAX(8, sizeof cookie.bridge_ipfix)
+               && cookie.type == USER_ACTION_COOKIE_BRIDGE_IPFIX) {
+        return BRIDGE_IPFIX_UPCALL;
+    } else if (userdata_len == MAX(8, sizeof cookie.port_ipfix)
+               && cookie.type == USER_ACTION_COOKIE_PORT_IPFIX){
+        return PORT_IPFIX_UPCALL;
     } else {
         VLOG_WARN_RL(&rl, "invalid user cookie of type %"PRIu16
                      " and size %"PRIuSIZE, cookie.type, userdata_len);
@@ -1238,13 +1242,34 @@ process_upcall(struct udpif *udpif, struct upcall 
*upcall,
         }
         break;
 
-    case IPFIX_UPCALL:
+    case PORT_IPFIX_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.ipfix);
+            memcpy(&cookie, nl_attr_get(userdata), sizeof cookie.port_ipfix);
+
+            if (upcall->out_tun_key) {
+                odp_tun_key_from_attr(upcall->out_tun_key, false,
+                                      &output_tunnel_key);
+            }
+            dpif_ipfix_port_sample(upcall->ipfix, packet, flow,
+                                   flow->in_port.odp_port,
+                                   cookie.port_ipfix.ofp_port,
+                                   cookie.port_ipfix.output_odp_port,
+                                   upcall->out_tun_key ?
+                                       &output_tunnel_key : NULL);
+        }
+        break;
+
+    case BRIDGE_IPFIX_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.bridge_ipfix);
 
             if (upcall->out_tun_key) {
                 odp_tun_key_from_attr(upcall->out_tun_key, false,
@@ -1252,7 +1277,7 @@ process_upcall(struct udpif *udpif, struct upcall *upcall,
             }
             dpif_ipfix_bridge_sample(upcall->ipfix, packet, flow,
                                      flow->in_port.odp_port,
-                                     cookie.ipfix.output_odp_port,
+                                     cookie.bridge_ipfix.output_odp_port,
                                      upcall->out_tun_key ?
                                          &output_tunnel_key : NULL);
         }
diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c
index 7a201bd..a09f191 100644
--- a/ofproto/ofproto-dpif-xlate.c
+++ b/ofproto/ofproto-dpif-xlate.c
@@ -152,6 +152,7 @@ struct xport {
     struct cfm *cfm;                 /* CFM handle or null. */
     struct bfd *bfd;                 /* BFD handle or null. */
     struct lldp *lldp;               /* LLDP handle or null. */
+    struct dpif_ipfix *ipfix;        /* Ipfix handle, or null. */
 };
 
 struct xlate_ctx {
@@ -563,7 +564,8 @@ static void xlate_xbundle_set(struct xbundle *xbundle,
 static void xlate_xport_set(struct xport *xport, odp_port_t odp_port,
                             const struct netdev *netdev, const struct cfm *cfm,
                             const struct bfd *bfd, const struct lldp *lldp,
-                            int stp_port_no, const struct rstp_port *rstp_port,
+                            const struct dpif_ipfix *ipfix, int stp_port_no,
+                            const struct rstp_port *rstp_port,
                             enum ofputil_port_config config,
                             enum ofputil_port_state state, bool is_tunnel,
                             bool may_enable);
@@ -726,7 +728,8 @@ xlate_xbundle_set(struct xbundle *xbundle,
 static void
 xlate_xport_set(struct xport *xport, odp_port_t odp_port,
                 const struct netdev *netdev, const struct cfm *cfm,
-                const struct bfd *bfd, const struct lldp *lldp, int 
stp_port_no,
+                const struct bfd *bfd, const struct lldp *lldp,
+                const struct dpif_ipfix *ipfix, int stp_port_no,
                 const struct rstp_port* rstp_port,
                 enum ofputil_port_config config, enum ofputil_port_state state,
                 bool is_tunnel, bool may_enable)
@@ -758,6 +761,11 @@ xlate_xport_set(struct xport *xport, odp_port_t odp_port,
         xport->lldp = lldp_ref(lldp);
     }
 
+    if (xport->ipfix != ipfix) {
+        dpif_ipfix_unref(xport->ipfix);
+        xport->ipfix = dpif_ipfix_ref(ipfix);
+    }
+
     if (xport->netdev != netdev) {
         netdev_close(xport->netdev);
         xport->netdev = netdev_ref(netdev);
@@ -823,9 +831,9 @@ xlate_xport_copy(struct xbridge *xbridge, struct xbundle 
*xbundle,
     xlate_xport_init(new_xcfg, new_xport);
 
     xlate_xport_set(new_xport, xport->odp_port, xport->netdev, xport->cfm,
-                    xport->bfd, xport->lldp, xport->stp_port_no,
-                    xport->rstp_port, xport->config, xport->state,
-                    xport->is_tunnel, xport->may_enable);
+                    xport->bfd, xport->lldp, xport->ipfix,
+                    xport->stp_port_no, xport->rstp_port, xport->config,
+                    xport->state, xport->is_tunnel, xport->may_enable);
 
     if (xport->peer) {
         struct xport *peer = xport_lookup(new_xcfg, xport->peer->ofport);
@@ -1058,8 +1066,9 @@ xlate_ofport_set(struct ofproto_dpif *ofproto, struct 
ofbundle *ofbundle,
                  struct ofport_dpif *ofport, ofp_port_t ofp_port,
                  odp_port_t odp_port, const struct netdev *netdev,
                  const struct cfm *cfm, const struct bfd *bfd,
-                 const struct lldp *lldp, struct ofport_dpif *peer,
-                 int stp_port_no, const struct rstp_port *rstp_port,
+                 const struct lldp *lldp, const struct dpif_ipfix *ipfix,
+                 struct ofport_dpif *peer, int stp_port_no,
+                 const struct rstp_port *rstp_port,
                  const struct ofproto_port_queue *qdscp_list, size_t n_qdscp,
                  enum ofputil_port_config config,
                  enum ofputil_port_state state, bool is_tunnel,
@@ -1067,6 +1076,7 @@ xlate_ofport_set(struct ofproto_dpif *ofproto, struct 
ofbundle *ofbundle,
 {
     size_t i;
     struct xport *xport;
+    bool port_ipfix = false;
 
     ovs_assert(new_xcfg);
 
@@ -1082,7 +1092,13 @@ xlate_ofport_set(struct ofproto_dpif *ofproto, struct 
ofbundle *ofbundle,
 
     ovs_assert(xport->ofp_port == ofp_port);
 
+    if (ipfix) {
+        port_ipfix = dpif_ipfix_port_ipfix_enable(ipfix,
+            ofp_port);
+    }
+
     xlate_xport_set(xport, odp_port, netdev, cfm, bfd, lldp,
+                    port_ipfix ? ipfix : NULL,
                     stp_port_no, rstp_port, config, state, is_tunnel,
                     may_enable);
 
@@ -1147,6 +1163,7 @@ xlate_xport_remove(struct xlate_cfg *xcfg, struct xport 
*xport)
     cfm_unref(xport->cfm);
     bfd_unref(xport->bfd);
     lldp_unref(xport->lldp);
+    dpif_ipfix_unref(xport->ipfix);
     free(xport);
 }
 
@@ -1221,7 +1238,11 @@ xlate_lookup(const struct dpif_backer *backer, const 
struct flow *flow,
     }
 
     if (ipfix) {
-        *ipfix = xport ? xport->xbridge->ipfix : NULL;
+        if (xport) {
+            *ipfix = xport->ipfix ? xport->ipfix : xport->xbridge->ipfix;
+        } else {
+            *ipfix = NULL;
+        }
     }
 
     if (sflow) {
@@ -2609,48 +2630,77 @@ compose_sflow_action(struct xlate_ctx *ctx)
 }
 
 /* If IPFIX is enabled, this appends a "sample" action to implement IPFIX to
- * 'ctx->odp_actions'. */
+ * 'ctx->odp_actions'. If both port and bridge are configured IPFIX, use the
+ * configuration on port */
 static void
-compose_ipfix_action(struct xlate_ctx *ctx, odp_port_t output_odp_port)
+compose_ipfix_action(struct xlate_ctx *ctx, ofp_port_t ofp_port,
+                     odp_port_t output_odp_port)
 {
     struct dpif_ipfix *ipfix = ctx->xbridge->ipfix;
+    bool port_ipfix = false;
     odp_port_t tunnel_out_port = ODPP_NONE;
 
     if (!ipfix || ctx->xin->flow.in_port.ofp_port == OFPP_NONE) {
         return;
     }
 
-    /* For input case, output_odp_port is ODPP_NONE, which is an invalid port
-     * number. */
-    if (output_odp_port == ODPP_NONE &&
-        !dpif_ipfix_get_bridge_exporter_input_sampling(ipfix)) {
-        return;
+    /* Chech whether port IPFIX is enabled on the port */
+    if (ofp_port != OFPP_NONE) {
+        port_ipfix = dpif_ipfix_port_ipfix_enable(ipfix, ofp_port);
     }
 
-    /* For output case, output_odp_port is valid*/
-    if (output_odp_port != ODPP_NONE) {
-        if (!dpif_ipfix_get_bridge_exporter_output_sampling(ipfix)) {
+    /* For input case, output_odp_port is ODPP_NONE, which is an invalid port
+     * number. */
+    if (output_odp_port == ODPP_NONE) {
+        if (port_ipfix ?
+            !dpif_ipfix_get_port_exporter_input_sampling(ipfix, ofp_port) :
+            !dpif_ipfix_get_bridge_exporter_input_sampling(ipfix)) {
+            return;
+        }
+    } else {
+        /* For output case, output_odp_port is valid*/
+        if (port_ipfix ?
+            !dpif_ipfix_get_port_exporter_output_sampling(ipfix, ofp_port) :
+            !dpif_ipfix_get_bridge_exporter_output_sampling(ipfix)) {
             return;
         }
         /* If tunnel sampling is enabled, put an additional option attribute:
          * OVS_USERSPACE_ATTR_TUNNEL_OUT_PORT
          */
-        if (dpif_ipfix_get_bridge_exporter_tunnel_sampling(ipfix) &&
-            dpif_ipfix_get_tunnel_port(ipfix, output_odp_port) ) {
-           tunnel_out_port = output_odp_port;
+        if (port_ipfix ?
+            dpif_ipfix_get_port_exporter_tunnel_sampling(ipfix, ofp_port) :
+            dpif_ipfix_get_bridge_exporter_tunnel_sampling(ipfix)) {
+            if (dpif_ipfix_get_tunnel_port(ipfix, output_odp_port)) {
+                tunnel_out_port = output_odp_port;
+            }
         }
     }
 
-    union user_action_cookie cookie = {
-        .ipfix = {
-            .type = USER_ACTION_COOKIE_IPFIX,
-            .output_odp_port = output_odp_port,
-        }
-    };
-    compose_sample_action(ctx,
-                          dpif_ipfix_get_bridge_exporter_probability(ipfix),
-                          &cookie, sizeof cookie.ipfix, tunnel_out_port,
-                          false);
+    if (port_ipfix) {
+        union user_action_cookie port_cookie = {
+            .port_ipfix = {
+                .type = USER_ACTION_COOKIE_PORT_IPFIX,
+                .ofp_port = ofp_port,
+                .output_odp_port = output_odp_port,
+            }
+        };
+        compose_sample_action(ctx,
+                              dpif_ipfix_get_port_exporter_probability(ipfix,
+                                  ofp_port),
+                              &port_cookie, sizeof port_cookie.port_ipfix,
+                              tunnel_out_port, false);        
+    } else {
+        union user_action_cookie bridge_cookie = {
+            .bridge_ipfix = {
+                .type = USER_ACTION_COOKIE_BRIDGE_IPFIX,
+                .output_odp_port = output_odp_port,
+            }
+        };
+        compose_sample_action(ctx,
+                              
dpif_ipfix_get_bridge_exporter_probability(ipfix),
+                              &bridge_cookie, sizeof 
bridge_cookie.bridge_ipfix,
+                              tunnel_out_port, false);
+    }
 }
 
 /* Fix "sample" action according to data collected while composing ODP actions,
@@ -3177,7 +3227,7 @@ compose_output_action__(struct xlate_ctx *ctx, ofp_port_t 
ofp_port,
                 } else {
                     /* Tunnel push-pop action is not compatible with
                      * IPFIX action. */
-                    compose_ipfix_action(ctx, out_port);
+                    compose_ipfix_action(ctx, ofp_port, out_port);
                     nl_msg_put_odp_port(ctx->odp_actions,
                                         OVS_ACTION_ATTR_OUTPUT,
                                         out_port);
@@ -5294,7 +5344,8 @@ xlate_actions(struct xlate_in *xin, struct xlate_out 
*xout)
         unsigned int user_cookie_offset = 0;
         if (!xin->frozen_state) {
             user_cookie_offset = compose_sflow_action(&ctx);
-            compose_ipfix_action(&ctx, ODPP_NONE);
+            compose_ipfix_action(&ctx, ctx.xin->flow.in_port.ofp_port,
+                                 ODPP_NONE);
         }
         size_t sample_actions_len = ctx.odp_actions->size;
 
diff --git a/ofproto/ofproto-dpif-xlate.h b/ofproto/ofproto-dpif-xlate.h
index c4c23d5..300d243 100644
--- a/ofproto/ofproto-dpif-xlate.h
+++ b/ofproto/ofproto-dpif-xlate.h
@@ -163,7 +163,8 @@ void xlate_bundle_remove(struct ofbundle *);
 void xlate_ofport_set(struct ofproto_dpif *, struct ofbundle *,
                       struct ofport_dpif *, ofp_port_t, odp_port_t,
                       const struct netdev *, const struct cfm *, const struct 
bfd *,
-                      const struct lldp *, struct ofport_dpif *peer,
+                      const struct lldp *, const struct dpif_ipfix*,
+                      struct ofport_dpif *peer,
                       int stp_port_no, const struct rstp_port *rstp_port,
                       const struct ofproto_port_queue *qdscp,
                       size_t n_qdscp, enum ofputil_port_config,
diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
index 285e377..b38e394 100644
--- a/ofproto/ofproto-dpif.c
+++ b/ofproto/ofproto-dpif.c
@@ -668,7 +668,8 @@ type_run(const char *type)
                 xlate_ofport_set(ofproto, ofport->bundle, ofport,
                                  ofport->up.ofp_port, ofport->odp_port,
                                  ofport->up.netdev, ofport->cfm, ofport->bfd,
-                                 ofport->lldp, ofport->peer, stp_port,
+                                 ofport->lldp, ofproto->ipfix,
+                                 ofport->peer, stp_port,
                                  ofport->rstp_port, ofport->qdscp,
                                  ofport->n_qdscp, ofport->up.pp.config,
                                  ofport->up.pp.state, ofport->is_tunnel,
@@ -1953,15 +1954,25 @@ set_sflow(struct ofproto *ofproto_,
 static int
 set_ipfix(
     struct ofproto *ofproto_,
+    const struct ofproto_ipfix_port_exporter_options *port_exporter_options,
+    ofp_port_t ofp_port,
     const struct ofproto_ipfix_bridge_exporter_options 
*bridge_exporter_options,
     const struct ofproto_ipfix_flow_exporter_options *flow_exporters_options,
     size_t n_flow_exporters_options)
 {
     struct ofproto_dpif *ofproto = ofproto_dpif_cast(ofproto_);
     struct dpif_ipfix *di = ofproto->ipfix;
-    bool has_options = bridge_exporter_options || flow_exporters_options;
+    bool has_options = port_exporter_options || bridge_exporter_options
+                       || flow_exporters_options;
     bool new_di = false;
 
+    /* To remove unused IPFIX port in port_exporter_map */
+    if (!port_exporter_options && ofp_port != OFPP_NONE && di) {
+        dpif_ipfix_set_options(di, NULL, ofp_port, NULL, NULL, 0);
+
+        return 0;
+    }
+
     if (has_options && !di) {
         di = ofproto->ipfix = dpif_ipfix_create();
         new_di = true;
@@ -1971,7 +1982,9 @@ set_ipfix(
         /* Call set_options in any case to cleanly flush the flow
          * caches in the last exporters that are to be destroyed. */
         dpif_ipfix_set_options(
-            di, bridge_exporter_options, flow_exporters_options,
+            di, port_exporter_options, ofp_port,
+            bridge_exporter_options,
+            flow_exporters_options,
             n_flow_exporters_options);
 
         /* Add tunnel ports only when a new ipfix created */
diff --git a/ofproto/ofproto-provider.h b/ofproto/ofproto-provider.h
index daa0077..aba2a1c 100644
--- a/ofproto/ofproto-provider.h
+++ b/ofproto/ofproto-provider.h
@@ -1356,14 +1356,17 @@ struct ofproto_class {
                      const struct ofproto_sflow_options *sflow_options);
 
     /* Configures IPFIX on 'ofproto' according to the options in
-     * 'bridge_exporter_options' and the 'flow_exporters_options'
-     * array, or turns off IPFIX if 'bridge_exporter_options' and
+     * 'port_exporter_options', 'bridge_exporter_options' and
+     * the 'flow_exporters_options' array, or turns off IPFIX if
+     * 'port_exporter_options', 'bridge_exporter_options' and
      * 'flow_exporters_options' is NULL.
      *
      * EOPNOTSUPP as a return value indicates that 'ofproto' does not support
      * IPFIX, as does a null pointer. */
     int (*set_ipfix)(
         struct ofproto *ofproto,
+        const struct ofproto_ipfix_port_exporter_options
+            *port_exporter_options, const ofp_port_t ofp_port,
         const struct ofproto_ipfix_bridge_exporter_options
             *bridge_exporter_options,
         const struct ofproto_ipfix_flow_exporter_options
diff --git a/ofproto/ofproto.c b/ofproto/ofproto.c
index ff6affd..83cc28f 100644
--- a/ofproto/ofproto.c
+++ b/ofproto/ofproto.c
@@ -852,14 +852,17 @@ ofproto_set_sflow(struct ofproto *ofproto,
 
 int
 ofproto_set_ipfix(struct ofproto *ofproto,
+                  const struct ofproto_ipfix_port_exporter_options *po,
+                  const ofp_port_t ofp_port,
                   const struct ofproto_ipfix_bridge_exporter_options *bo,
                   const struct ofproto_ipfix_flow_exporter_options *fo,
                   size_t n_fo)
 {
     if (ofproto->ofproto_class->set_ipfix) {
-        return ofproto->ofproto_class->set_ipfix(ofproto, bo, fo, n_fo);
+        return (ofproto->ofproto_class->set_ipfix(ofproto, po,
+                   ofp_port, bo, fo, n_fo));
     } else {
-        return (bo || fo) ? EOPNOTSUPP : 0;
+        return (po || bo || fo) ? EOPNOTSUPP : 0;
     }
 }
 
diff --git a/ofproto/ofproto.h b/ofproto/ofproto.h
index 2d241c9..6bc11a2 100644
--- a/ofproto/ofproto.h
+++ b/ofproto/ofproto.h
@@ -72,6 +72,14 @@ struct ofproto_sflow_options {
     char *control_ip;
 };
 
+typedef enum ofproto_ipfix_type {
+    NONE_IPFIX   = 0x0000,   /* none ipfix type */
+    PORT_IPFIX   = 0x0001,   /* port ipfix type */
+    BRIDGE_IPFIX = 0x0002,   /* bridge ipfix type */
+    FLOW_IPFIX   = 0x0004,   /* flow ipfix type */
+    IPFIX_VALID  = 0x0007    /* valid ipfix type */
+} ofproto_ipfix_type;
+
 struct ofproto_ipfix_bridge_exporter_options {
     struct sset targets;
     uint32_t sampling_rate;
@@ -84,6 +92,19 @@ struct ofproto_ipfix_bridge_exporter_options {
     bool enable_output_sampling;
 };
 
+struct ofproto_ipfix_port_exporter_options {
+    ofp_port_t ofp_port;  /* hmap key */
+    struct sset targets;
+    uint32_t sampling_rate;  
+    uint32_t obs_domain_id;  /* Observation Domain ID. */ 
+    uint32_t obs_point_id;  /* Observation Point ID. */
+    uint32_t cache_active_timeout;
+    uint32_t cache_max_flows;
+    bool enable_tunnel_sampling;
+    bool enable_input_sampling;
+    bool enable_output_sampling;
+};
+
 struct ofproto_ipfix_flow_exporter_options {
     uint32_t collector_set_id;
     struct sset targets;
@@ -323,6 +344,8 @@ int ofproto_set_netflow(struct ofproto *,
                         const struct netflow_options *nf_options);
 int ofproto_set_sflow(struct ofproto *, const struct ofproto_sflow_options *);
 int ofproto_set_ipfix(struct ofproto *,
+                      const struct ofproto_ipfix_port_exporter_options *,
+                      const ofp_port_t ofp_port,
                       const struct ofproto_ipfix_bridge_exporter_options *,
                       const struct ofproto_ipfix_flow_exporter_options *,
                       size_t);
diff --git a/tests/odp.at b/tests/odp.at
index 808a83b..f3c5203 100644
--- a/tests/odp.at
+++ b/tests/odp.at
@@ -257,8 +257,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,ipfix(output_port=10))
-userspace(pid=6633,ipfix(output_port=10),tunnel_out_port=10)
+userspace(pid=6633,bridge_ipfix(output_port=10))
+userspace(pid=6633,bridge_ipfix(output_port=10),tunnel_out_port=10)
+userspace(pid=6633,port_ipfix(ofp_port=0,output_port=10))
+userspace(pid=6633,port_ipfix(ofp_port=0,output_port=10),tunnel_out_port=10)
 set(in_port(2))
 set(eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15))
 set(eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15/ff:ff:ff:00:00:00))
diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at
index 9ac2e2a..8b50887 100644
--- a/tests/ofproto-dpif.at
+++ b/tests/ofproto-dpif.at
@@ -5780,9 +5780,9 @@ CHECK_NETFLOW_ACTIVE_EXPIRATION([[[::1]]])
 AT_CLEANUP
 
 dnl In the absence of an IPFIX collector to verify protocol correctness, simply
-dnl configure IPFIX and ensure that sample action generation works at the
+dnl configure bridge IPFIX and ensure that sample action generation works at 
the
 dnl datapath level.
-AT_SETUP([ofproto-dpif - Basic IPFIX sanity check])
+AT_SETUP([ofproto-dpif - Basic Bridge IPFIX sanity check])
 OVS_VSWITCHD_START
 add_of_ports br0 1 2
 
@@ -5797,7 +5797,7 @@ for i in `seq 1 3`; do
 done
 AT_CHECK([ovs-appctl dpctl/dump-flows | sed 's/.*\(packets:\)/\1/' | sed 
's/used:[[0-9]].[[0-9]]*s/used:0.001s/'], [0], [dnl
 flow-dump from non-dpdk interfaces:
-packets:2, bytes:120, used:0.001s, 
actions:sample(sample=100.0%,actions(userspace(pid=0,ipfix(output_port=4294967295))))
+packets:2, bytes:120, used:0.001s, 
actions:sample(sample=100.0%,actions(userspace(pid=0,bridge_ipfix(output_port=4294967295))))
 ])
 
 dnl Remove the IPFIX configuration
@@ -5816,6 +5816,43 @@ packets:2, bytes:120, used:0.001s, actions:drop
 OVS_VSWITCHD_STOP(["/sending to collector failed/d"])
 AT_CLEANUP
 
+dnl Configure port IPFIX and ensure that sample action generation works at
+dnl datapatch level
+AT_SETUP([ofproto-dpif - Basic Port IPFIX sanity check])
+OVS_VSWITCHD_START
+add_of_ports br0 1 2
+
+dnl Sample every packet using port-based sampling
+AT_CHECK([ovs-vsctl -- set port p1 ipfix=@fix -- \
+                    --id=@fix create ipfix targets=\"127.0.0.1:4739\" \
+                              sampling=1], [0], [ignore])
+
+dnl Send some packets that should be sampled
+for i in `seq 1 3`; do
+    AT_CHECK([ovs-appctl netdev-dummy/receive p1 
'in_port(1),eth(src=50:54:00:00:00:09,dst=50:54:00:00:00:0a),eth_type(0x0800)'])
+done
+AT_CHECK([ovs-appctl dpctl/dump-flows | sed 's/.*\(packets:\)/\1/' | sed 
's/used:[[0-9]].[[0-9]]*s/used:0.001s/'], [0], [dnl
+flow-dump from non-dpdk interfaces:
+packets:2, bytes:120, used:0.001s, 
actions:sample(sample=100.0%,actions(userspace(pid=0,port_ipfix(ofp_port=1,output_port=4294967295))))
+])
+
+dnl Remove the IPFIX configuration
+AT_CHECK([ovs-vsctl clear port p1 ipfix])
+AT_CHECK([ovs-appctl revalidator/purge])
+
+dnl Send some more packets, to ensure that these are not sampled.
+for i in `seq 1 3`; do
+    AT_CHECK([ovs-appctl netdev-dummy/receive p1 
'in_port(1),eth(src=50:54:00:00:00:09,dst=50:54:00:00:00:0a),eth_type(0x0800)'])
+done
+AT_CHECK([ovs-appctl dpctl/dump-flows | sed 's/.*\(packets:\)/\1/' | sed 
's/used:[[0-9]].[[0-9]]*s/used:0.001s/'], [0], [dnl
+flow-dump from non-dpdk interfaces:
+packets:2, bytes:120, used:0.001s, actions:drop
+])
+
+OVS_VSWITCHD_STOP(["/sending to collector failed/d"])
+AT_CLEANUP
+
+
 AT_SETUP([ofproto-dpif - flow stats])
 OVS_VSWITCHD_START
 AT_CHECK([ovs-ofctl add-flow br0 "ip,actions=NORMAL"])
diff --git a/vswitchd/bridge.c b/vswitchd/bridge.c
index 700f65c..299fabc 100644
--- a/vswitchd/bridge.c
+++ b/vswitchd/bridge.c
@@ -249,7 +249,7 @@ static void bridge_configure_forward_bpdu(struct bridge *);
 static void bridge_configure_mac_table(struct bridge *);
 static void bridge_configure_mcast_snooping(struct bridge *);
 static void bridge_configure_sflow(struct bridge *, int *sflow_bridge_number);
-static void bridge_configure_ipfix(struct bridge *);
+static void bridge_configure_ipfix(struct bridge *, enum ofproto_ipfix_type);
 static void bridge_configure_spanning_tree(struct bridge *);
 static void bridge_configure_tables(struct bridge *);
 static void bridge_configure_dp_desc(struct bridge *);
@@ -282,6 +282,8 @@ static struct lacp_settings *port_configure_lacp(struct 
port *,
 static void port_configure_bond(struct port *, struct bond_settings *);
 static bool port_is_synthetic(const struct port *);
 
+static void iface_configure_ipfix(struct iface *, const bool);
+
 static void reconfigure_system_stats(const struct ovsrec_open_vswitch *);
 static void run_system_stats(void);
 
@@ -319,6 +321,11 @@ static ofp_port_t iface_get_requested_ofp_port(
     const struct ovsrec_interface *);
 static ofp_port_t iface_pick_ofport(const struct ovsrec_interface *);
 
+static bool ovsrec_ipfix_is_valid(const struct ovsrec_ipfix *ipfix);
+static bool ovsrec_fscs_is_valid(
+    const struct ovsrec_flow_sample_collector_set *fscs,
+    const struct bridge *br);
+
 
 /* Linux VLAN device support (e.g. "eth0.10" for VLAN 10.)
  *
@@ -569,6 +576,30 @@ collect_in_band_managers(const struct ovsrec_open_vswitch 
*ovs_cfg,
     *n_managersp = n_managers;
 }
 
+static ofproto_ipfix_type
+bridge_get_ipfix_type(struct bridge *br)
+{
+    bool flow_ipfix_exsited = false;
+    enum ofproto_ipfix_type ipfix_type = NONE_IPFIX;
+    const struct ovsrec_flow_sample_collector_set *fe_cfg;
+
+    if (ovsrec_ipfix_is_valid(br->cfg->ipfix)) {
+        ipfix_type |= BRIDGE_IPFIX;
+    }
+
+    OVSREC_FLOW_SAMPLE_COLLECTOR_SET_FOR_EACH(fe_cfg, idl) {
+        if (ovsrec_fscs_is_valid(fe_cfg, br)) {
+            flow_ipfix_exsited = true;
+            break;
+        }
+    }
+    if (flow_ipfix_exsited) {
+        ipfix_type |= FLOW_IPFIX;
+    }
+
+   return ipfix_type;
+}
+
 static void
 bridge_reconfigure(const struct ovsrec_open_vswitch *ovs_cfg)
 {
@@ -655,16 +686,25 @@ bridge_reconfigure(const struct ovsrec_open_vswitch 
*ovs_cfg)
     collect_in_band_managers(ovs_cfg, &managers, &n_managers);
     HMAP_FOR_EACH (br, node, &all_bridges) {
         struct port *port;
+        bool port_ipfix = false;
+        enum ofproto_ipfix_type ipfix_type;
 
         /* We need the datapath ID early to allow LACP ports to use it as the
          * default system ID. */
         bridge_configure_datapath_id(br);
+        ipfix_type = bridge_get_ipfix_type(br);
 
         HMAP_FOR_EACH (port, hmap_node, &br->ports) {
             struct iface *iface;
 
             port_configure(port);
 
+            port_ipfix = ovsrec_ipfix_is_valid(port->cfg->ipfix);
+            if (port_ipfix) {
+                /* Port on the bridge was configured port level IPFIX */
+                ipfix_type |= PORT_IPFIX;
+            }
+
             LIST_FOR_EACH (iface, port_elem, &port->ifaces) {
                 iface_set_ofport(iface->cfg, iface->ofp_port);
                 /* Clear eventual previous errors */
@@ -676,6 +716,7 @@ bridge_reconfigure(const struct ovsrec_open_vswitch 
*ovs_cfg)
                                      &iface->cfg->bfd);
                 ofproto_port_set_lldp(br->ofproto, iface->ofp_port,
                                       &iface->cfg->lldp);
+                iface_configure_ipfix(iface, port_ipfix);
             }
         }
         bridge_configure_mirrors(br);
@@ -685,7 +726,7 @@ bridge_reconfigure(const struct ovsrec_open_vswitch 
*ovs_cfg)
         bridge_configure_remotes(br, managers, n_managers);
         bridge_configure_netflow(br);
         bridge_configure_sflow(br, &sflow_bridge_number);
-        bridge_configure_ipfix(br);
+        bridge_configure_ipfix(br, ipfix_type);
         bridge_configure_spanning_tree(br);
         bridge_configure_tables(br);
         bridge_configure_dp_desc(br);
@@ -1182,28 +1223,87 @@ ovsrec_fscs_is_valid(const struct 
ovsrec_flow_sample_collector_set *fscs,
     return ovsrec_ipfix_is_valid(fscs->ipfix) && fscs->bridge == br->cfg;
 }
 
+/* Set IPIFX configuration on iface per port. */
+static void
+iface_configure_ipfix(struct iface *iface, const bool ipfix_valid)
+{
+    const struct ovsrec_ipfix *pe_cfg = iface->port->cfg->ipfix;
+    struct ofproto_ipfix_port_exporter_options pe_opts;
+
+    memset(&pe_opts, 0, sizeof pe_opts);
+
+    if (!ipfix_valid) {
+        /* Remove IPFIX on the port */
+        ofproto_set_ipfix(iface->port->bridge->ofproto, NULL,
+                          iface->ofp_port, NULL, NULL, 0);
+        return;
+    }
+
+    sset_init(&pe_opts.targets);
+    sset_add_array(&pe_opts.targets, pe_cfg->targets, pe_cfg->n_targets);
+
+    if (pe_cfg->sampling) {
+        pe_opts.sampling_rate = *pe_cfg->sampling;
+    } else {
+        pe_opts.sampling_rate = SFL_DEFAULT_SAMPLING_RATE;
+    }
+    if (pe_cfg->obs_domain_id) {
+        pe_opts.obs_domain_id = *pe_cfg->obs_domain_id;
+    }
+    if (pe_cfg->obs_point_id) {
+        pe_opts.obs_point_id = *pe_cfg->obs_point_id;
+    }
+    if (pe_cfg->cache_active_timeout) {
+        pe_opts.cache_active_timeout = *pe_cfg->cache_active_timeout;
+    }
+    if (pe_cfg->cache_max_flows) {
+        pe_opts.cache_max_flows = *pe_cfg->cache_max_flows;
+    }
+
+    pe_opts.enable_tunnel_sampling = smap_get_bool(&pe_cfg->other_config,
+                                         "enable-tunnel-sampling", true);
+
+    pe_opts.enable_input_sampling = !smap_get_bool(&pe_cfg->other_config,
+                                          "enable-input-sampling", false);
+
+    pe_opts.enable_output_sampling = !smap_get_bool(&pe_cfg->other_config,
+                                          "enable-output-sampling", false);
+
+    pe_opts.ofp_port = iface->ofp_port;
+    ofproto_set_ipfix(iface->port->bridge->ofproto, &pe_opts,
+                      pe_opts.ofp_port, NULL, NULL, 0);
+
+    sset_destroy(&pe_opts.targets);
+}
+
 /* Set IPFIX configuration on 'br'. */
 static void
-bridge_configure_ipfix(struct bridge *br)
+bridge_configure_ipfix(struct bridge *br,
+                       enum ofproto_ipfix_type ipfix_type)
 {
     const struct ovsrec_ipfix *be_cfg = br->cfg->ipfix;
-    bool valid_be_cfg = ovsrec_ipfix_is_valid(be_cfg);
+    bool valid_be_cfg = ipfix_type & BRIDGE_IPFIX;
     const struct ovsrec_flow_sample_collector_set *fe_cfg;
     struct ofproto_ipfix_bridge_exporter_options be_opts;
     struct ofproto_ipfix_flow_exporter_options *fe_opts = NULL;
     size_t n_fe_opts = 0;
 
+    if (ipfix_type == NONE_IPFIX) {
+        ofproto_set_ipfix(br->ofproto,
+                          NULL, OFPP_NONE, NULL, NULL, 0);
+        return;
+    }
+    if (!(ipfix_type & (BRIDGE_IPFIX | FLOW_IPFIX))) {
+        /* If only port level IPFIX is set, just return */
+        return;
+    }
+
     OVSREC_FLOW_SAMPLE_COLLECTOR_SET_FOR_EACH(fe_cfg, idl) {
         if (ovsrec_fscs_is_valid(fe_cfg, br)) {
             n_fe_opts++;
         }
     }
 
-    if (!valid_be_cfg && n_fe_opts == 0) {
-        ofproto_set_ipfix(br->ofproto, NULL, NULL, 0);
-        return;
-    }
-
     if (valid_be_cfg) {
         memset(&be_opts, 0, sizeof be_opts);
 
@@ -1257,8 +1357,9 @@ bridge_configure_ipfix(struct bridge *br)
         }
     }
 
-    ofproto_set_ipfix(br->ofproto, valid_be_cfg ? &be_opts : NULL, fe_opts,
-                      n_fe_opts);
+    ofproto_set_ipfix(br->ofproto, NULL, OFPP_NONE,
+                      valid_be_cfg ? &be_opts : NULL,
+                      fe_opts, n_fe_opts);
 
     if (valid_be_cfg) {
         sset_destroy(&be_opts.targets);
diff --git a/vswitchd/vswitch.ovsschema b/vswitchd/vswitch.ovsschema
index e0937f4..703bef0 100644
--- a/vswitchd/vswitch.ovsschema
+++ b/vswitchd/vswitch.ovsschema
@@ -1,6 +1,6 @@
 {"name": "Open_vSwitch",
  "version": "7.13.0",
- "cksum": "2202834738 22577",
+ "cksum": "59911373 22725",
  "tables": {
    "Open_vSwitch": {
      "columns": {
@@ -192,6 +192,10 @@
        "statistics": {
          "type": {"key": "string", "value": "integer", "min": 0, "max": 
"unlimited"},
          "ephemeral": true},
+       "ipfix": {
+         "type": {"key": {"type": "uuid",
+                          "refTable": "IPFIX"},
+                  "min": 0, "max": 1}},
        "other_config": {
          "type": {"key": "string", "value": "string", "min": 0, "max": 
"unlimited"}},
        "external_ids": {
diff --git a/vswitchd/vswitch.xml b/vswitchd/vswitch.xml
index 166f264..5bb7956 100644
--- a/vswitchd/vswitch.xml
+++ b/vswitchd/vswitch.xml
@@ -1661,6 +1661,10 @@
         Bridge?  See ovs-vsctl(8) for more information.
       </column>
 
+      <column name="ipfix">
+        IPFIX configuration.
+      </column>
+
       <column name="external_ids" key="fake-bridge-id-*">
         External IDs for a fake bridge (see the <ref column="fake_bridge"/>
         column) are defined by prefixing a <ref table="Bridge"/> <ref
@@ -4527,7 +4531,7 @@
     </p>
 
     <p>
-      IPFIX in Open vSwitch can be configured two different ways:
+      IPFIX in Open vSwitch can be configured three different ways:
     </p>
 
     <ul>
@@ -4536,11 +4540,22 @@
         automatically on all packets that pass through a bridge.  To configure
         per-bridge sampling, create an <ref table="IPFIX"/> record and point a
         <ref table="Bridge"/> table's <ref table="Bridge" column="ipfix"/>
-        column to it.  The <ref table="Flow_Sample_Collector_Set"/> table is
+        column to it. The <ref table="Flow_Sample_Collector_Set"/> table and
+        <ref table="Port" column="ipfix"/> in the <ref table="Port"/> are
         not used for per-bridge sampling.
       </li>
 
       <li>
+        With <em>per-port sampling</em>, Open vSwitch performs IPFIX sampling
+        automatically on all packets that pass through the port.  To configure
+        per-bridge sampling, create an <ref table="IPFIX"/> record and point a
+        <ref table="Port"/> table's <ref table="Port" column="ipfix"/> column
+        to it. The <ref table="Flow_Sample_Collector_Set"/> table and
+        <ref table="Bridge" column="ipfix"/> in the <ref table="Bridge"/> are
+        not used for per-port sampling.
+      </li>
+
+      <li>
         <p>
           With <em>flow-based sampling</em>, <code>sample</code> actions in the
           OpenFlow flow table drive IPFIX sampling.  See
@@ -4555,7 +4570,8 @@
           the <ref table="Bridge"/> whose flow table holds the
           <code>sample</code> actions and to <ref table="IPFIX"/> record.  The
           <ref table="Bridge" column="ipfix"/> in the <ref table="Bridge"/>
-          table is not used for flow-based sampling.
+          and <ref table="Port" column="ipfix"/> in the <ref table="Port"/>
+          are not used for flow-based sampling.
         </p>
       </li>
     </ul>
@@ -4577,11 +4593,11 @@
       disabled.
     </column>
 
-    <group title="Per-Bridge Sampling">
+    <group title="Per-Bridge and Per-Port Sampling">
       <p>
-        These values affect only per-bridge sampling.  See above for a
-        description of the differences between per-bridge and flow-based
-        sampling.
+        These values affect only per-bridge and per-port sampling.  See
+        above for a description of the differences between per-bridge,
+        per-port sampling and flow-based sampling.
       </p>
 
       <column name="sampling">
@@ -4707,8 +4723,8 @@
     <p>
       A set of IPFIX collectors of packet samples generated by OpenFlow
       <code>sample</code> actions.  This table is used only for IPFIX
-      flow-based sampling, not for per-bridge sampling (see the <ref
-      table="IPFIX"/> table for a description of the two forms).
+      flow-based sampling, not for per-bridge or per-port sampling (see
+      the <ref table="IPFIX"/> table for a description of the three forms).
     </p>
 
     <column name="id">
-- 
1.9.1

_______________________________________________
dev mailing list
dev@openvswitch.org
http://openvswitch.org/mailman/listinfo/dev

Reply via email to