diff mbox

[ovs-dev,v1] Support port level IPFIX

Message ID 1461052510-6066-1-git-send-email-daniely@vmware.com
State Changes Requested
Headers show

Commit Message

Daniel Benli Ye April 19, 2016, 7:55 a.m. UTC
From: Benli Ye <daniely@vmware.com>

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
---
 lib/odp-util.c                |  29 ++-
 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 +++
 vswitchd/bridge.c             | 123 +++++++++++--
 vswitchd/vswitch.ovsschema    |   6 +-
 vswitchd/vswitch.xml          |  34 +++-
 14 files changed, 741 insertions(+), 105 deletions(-)

Comments

Ben Pfaff April 22, 2016, 4:54 p.m. UTC | #1
On Tue, Apr 19, 2016 at 03:55:10PM +0800, Daniel Benli Ye wrote:
> From: Benli Ye <daniely@vmware.com>
> 
> 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

Thanks for working on IPFIX!  We don't have enough IPFIX expertise
around here, so new contributors are always welcome.

The patch lacks a Signed-off-by.  We will need it before it can be
applied.  CONTRIBUTING.md explains the format and the meaning, which is
to agree to the Developer's Certificate of Origin, which is also in
CONTRIBUTING.md; please read it.

Due to the lack of signoff, I did not do a detailed review, but I have
some general comments.  First, this patch follows the coding style
remarkably well, especially for a first patch--well done, thank you!

Second, this is the third form of configuration to be introduced for
IPFIX.  I worry that we'll end up with a fourth, and a fifth, ...  Why
does IPFIX need so many kinds of configuration; that is, why can't the
details of what packets it selects, etc., be controlled from flows in
the flow table, rather than by configuration in the database?  If that
were the case, then we would not need to so many forms of configuration:
the controller could control it through the flows over which it already
has so much control.

Thanks,

Ben.
Daniel Benli Ye April 26, 2016, 3:48 a.m. UTC | #2
Hi Ben, 

Thanks for your review. I have sent a second patch with signed-off tag. Please review it again.
For your comments, I listed the answers of mine below:
1. "I worry that we'll end up with a fourth, and a fifth, ..."
    With flow IPFIX, Bridge IPFIX and Port level IPFIX, we can handle almost all the IPFIX cases. There is no need to add other kinds of IPFIX.

2. " why can't the details of what packets it selects, etc., be controlled from flows in the flow table, rather than by configuration in the database?"
    We can't do this, because flow based IPFIX does not support to export output tunnel information. However, in overlay network, users want to
    know tunnel information.  With Bridge IPFIX, output tunnel information can be retrieved. But, Bridge IPFIX will enable all ports on the bridge to
    collects the IPFIX information and it's not granular. With Port IPFIX, users can just enable IPFIX on the ports, which they are interested in and the
    IPFIX information is preciser. Output tunnel information can be got if you enable port IPFIX on the tunnel port. What's more, IPFIX
    configuration of each port can be different. While Bridge IPFIX can only support one IPFIX configuration per bridge. Users need configure IPFIX
    granularly like port-based IPFIX.

3. If we want to use IPFIX to monitor the packets which ingress and egress the port, it's easier to be configured by port than by flows.

Bests,
Daniel Benli Ye

-----Original Message-----
From: Ben Pfaff [mailto:blp@ovn.org] 
Sent: Saturday, April 23, 2016 12:55 AM
To: Daniel Ye
Cc: Wenyu Zhang; dev@openvswitch.org
Subject: Re: [ovs-dev] [PATCH v1] Support port level IPFIX

On Tue, Apr 19, 2016 at 03:55:10PM +0800, Daniel Benli Ye wrote:
> From: Benli Ye <daniely@vmware.com>
> 
> 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

Thanks for working on IPFIX!  We don't have enough IPFIX expertise around here, so new contributors are always welcome.

The patch lacks a Signed-off-by.  We will need it before it can be applied.  CONTRIBUTING.md explains the format and the meaning, which is to agree to the Developer's Certificate of Origin, which is also in CONTRIBUTING.md; please read it.

Due to the lack of signoff, I did not do a detailed review, but I have some general comments.  First, this patch follows the coding style remarkably well, especially for a first patch--well done, thank you!

Second, this is the third form of configuration to be introduced for IPFIX.  I worry that we'll end up with a fourth, and a fifth, ...  Why does IPFIX need so many kinds of configuration; that is, why can't the details of what packets it selects, etc., be controlled from flows in the flow table, rather than by configuration in the database?  If that were the case, then we would not need to so many forms of configuration:
the controller could control it through the flows over which it already has so much control.

Thanks,

Ben.
Romain Lenglet April 26, 2016, 8:02 a.m. UTC | #3
Hi Daniel,
I had the same reaction as Ben.
I would prefer to see any efforts being made towards improving the existing OpenFlow “sample” action.
Some more comments inline below.

> On Apr 25, 2016, at 8:48 PM, Daniel Ye <daniely@vmware.com> wrote:
> 
> Hi Ben, 
> 
> Thanks for your review. I have sent a second patch with signed-off tag. Please review it again.
> For your comments, I listed the answers of mine below:
> 1. "I worry that we'll end up with a fourth, and a fifth, ..."
>    With flow IPFIX, Bridge IPFIX and Port level IPFIX, we can handle almost all the IPFIX cases. There is no need to add other kinds of IPFIX.
> 
> 2. " why can't the details of what packets it selects, etc., be controlled from flows in the flow table, rather than by configuration in the database?"
>    We can't do this, because flow based IPFIX does not support to export output tunnel information.

I believe it should be straightforward to implement that.
It’s a matter of extending the “sample” OpenFlow action to support a tunnel_out_port field which could be directly converted into the tunnel_out_port field of the corresponding “userspace” datapath action.

> However, in overlay network, users want to
>    know tunnel information.  With Bridge IPFIX, output tunnel information can be retrieved. But, Bridge IPFIX will enable all ports on the bridge to
>    collects the IPFIX information and it's not granular.

You can’t get more granular than per-flow sampling.
If granularity is concern, I would extend the OpenFlow “sample” action to support setting the output port, cf. the design of the datapath action.

> With Port IPFIX, users can just enable IPFIX on the ports, which they are interested in and the
>    IPFIX information is preciser. Output tunnel information can be got if you enable port IPFIX on the tunnel port. What's more, IPFIX
>    configuration of each port can be different. While Bridge IPFIX can only support one IPFIX configuration per bridge. Users need configure IPFIX
>    granularly like port-based IPFIX.
> 
> 3. If we want to use IPFIX to monitor the packets which ingress and egress the port, it's easier to be configured by port than by flows.

There shouldn’t be any significant impact on performance to monitor all ports of a bridge vs. only some of the ports.
Since you didn't mention performance, I can only assume that’s not a concern, so there shouldn’t be any issue for users to enable monitoring of all ports of a bridge even when they are interested in only a subset of the ports.

The per-bridge IPFIX exporter setting was only added to be consistent with the way the sFlow and netflow exporters can be configured in OVS, and that wasn’t meant to be the primary way of using IPFIX.
Adding a per-port setting would break the consistency with sFlow and netflow in OVS, so I believe it’s not appropriate.
The main feature was the OpenFlow “sample” action. Efforts should concentrate on that IMHO.

> 
> Bests,
> Daniel Benli Ye
> 
> -----Original Message-----
> From: Ben Pfaff [mailto:blp@ovn.org] 
> Sent: Saturday, April 23, 2016 12:55 AM
> To: Daniel Ye
> Cc: Wenyu Zhang; dev@openvswitch.org
> Subject: Re: [ovs-dev] [PATCH v1] Support port level IPFIX
> 
> On Tue, Apr 19, 2016 at 03:55:10PM +0800, Daniel Benli Ye wrote:
>> From: Benli Ye <daniely@vmware.com>
>> 
>> 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
> 
> Thanks for working on IPFIX!  We don't have enough IPFIX expertise around here, so new contributors are always welcome.
> 
> The patch lacks a Signed-off-by.  We will need it before it can be applied.  CONTRIBUTING.md explains the format and the meaning, which is to agree to the Developer's Certificate of Origin, which is also in CONTRIBUTING.md; please read it.
> 
> Due to the lack of signoff, I did not do a detailed review, but I have some general comments.  First, this patch follows the coding style remarkably well, especially for a first patch--well done, thank you!
> 
> Second, this is the third form of configuration to be introduced for IPFIX.  I worry that we'll end up with a fourth, and a fifth, ...  Why does IPFIX need so many kinds of configuration; that is, why can't the details of what packets it selects, etc., be controlled from flows in the flow table, rather than by configuration in the database?  If that were the case, then we would not need to so many forms of configuration:
> the controller could control it through the flows over which it already has so much control.
> 
> Thanks,
> 
> Ben.
> _______________________________________________
> dev mailing list
> dev@openvswitch.org
> http://openvswitch.org/mailman/listinfo/dev
Daniel Benli Ye April 30, 2016, 12:50 a.m. UTC | #4
Hi Romain,

Thanks for your review.

First, I would like to you give more context about why we need port level IPFIX.Port level IPFIX is meaningful to network administrator users. Come to a user story:
In virtualization environment, users have deployed critical applications and would like to have flow visibility from the VMs deployed for the application.

In order to use IPFIX to investigate the flows of VMs,  both Bridge IPFIX and Flow IPFIX can be used. However, Bridge IPFIX only supports global IPFIX
configuration per bridge. All the ports connected to VMs on a bridge will use the same "sampling rate" or "Time out" parameter. If there are two VMs on
a bridge, one is used to run traffic-intensive applications and the other one is only accessed by few terminals, it's not reasonable to set the same IPFIX
configuration (including sampling rate, time out and so on) to the two VM ports by Bridge IPFIX. What's more, if user only wants to monitor one
of the VMs and Bridge IPFIX is not suitable for this user case.

Second, Flow level IPFIX is not proper for the case mentioned here and not for all use cases in virtual network. In order to investigate the flows egressing
and ingressing the VM, Network admins should find all the flows related to the VM ports and add a sample action to each flow. It's obvious to add flow
IPFIX sample action on the flow which can be recognized from the input or output key world. What about the flows which don't have these key words?
Moreover, users also need to keep in mind about the relationship between port and "collector_set_id". Flow IPFIX is suitable to use in the case
that users want to investigate specific flow, but not suitable in the case that users want to investigate all the flows passing through the VM port.

Thirdly, output tunnel information is not supported by Flow IPFIX is one of the reasons that we don't use flow based IPFIX for this use case.
According to your comments, I investigated the code about how to support output tunnel information in Flow IPFIX. Yes, we can extend
the “sample” openFlow action to support a tunnel_out_port field. And Tunnel support in flow IPFIX is useful, but not for the user case I
mentioned before. We may implement tunnel supported Flow IPFIX later.

In short, port IPFIX is suitable for a virtual environment and port level configuration enriches IPFIX user cases, especially in virtual environment.

Bests,
Daniel Benli Ye

On Apr 26, 2016, at 4:02 PM, Romain Lenglet <romain.lenglet@oracle.com<mailto:romain.lenglet@oracle.com>> wrote:

Hi Daniel,
I had the same reaction as Ben.
I would prefer to see any efforts being made towards improving the existing OpenFlow “sample” action.
Some more comments inline below.

On Apr 25, 2016, at 8:48 PM, Daniel Ye <daniely@vmware.com<mailto:daniely@vmware.com>> wrote:

Hi Ben,

Thanks for your review. I have sent a second patch with signed-off tag. Please review it again.
For your comments, I listed the answers of mine below:
1. "I worry that we'll end up with a fourth, and a fifth, ..."
  With flow IPFIX, Bridge IPFIX and Port level IPFIX, we can handle almost all the IPFIX cases. There is no need to add other kinds of IPFIX.

2. " why can't the details of what packets it selects, etc., be controlled from flows in the flow table, rather than by configuration in the database?"
  We can't do this, because flow based IPFIX does not support to export output tunnel information.

I believe it should be straightforward to implement that.
It’s a matter of extending the “sample” OpenFlow action to support a tunnel_out_port field which could be directly converted into the tunnel_out_port field of the corresponding “userspace” datapath action.

However, in overlay network, users want to
  know tunnel information.  With Bridge IPFIX, output tunnel information can be retrieved. But, Bridge IPFIX will enable all ports on the bridge to
  collects the IPFIX information and it's not granular.

You can’t get more granular than per-flow sampling.
If granularity is concern, I would extend the OpenFlow “sample” action to support setting the output port, cf. the design of the datapath action.

With Port IPFIX, users can just enable IPFIX on the ports, which they are interested in and the
  IPFIX information is preciser. Output tunnel information can be got if you enable port IPFIX on the tunnel port. What's more, IPFIX
  configuration of each port can be different. While Bridge IPFIX can only support one IPFIX configuration per bridge. Users need configure IPFIX
  granularly like port-based IPFIX.

3. If we want to use IPFIX to monitor the packets which ingress and egress the port, it's easier to be configured by port than by flows.

There shouldn’t be any significant impact on performance to monitor all ports of a bridge vs. only some of the ports.
Since you didn't mention performance, I can only assume that’s not a concern, so there shouldn’t be any issue for users to enable monitoring of all ports of a bridge even when they are interested in only a subset of the ports.

The per-bridge IPFIX exporter setting was only added to be consistent with the way the sFlow and netflow exporters can be configured in OVS, and that wasn’t meant to be the primary way of using IPFIX.
Adding a per-port setting would break the consistency with sFlow and netflow in OVS, so I believe it’s not appropriate.
The main feature was the OpenFlow “sample” action. Efforts should concentrate on that IMHO.


Bests,
Daniel Benli Ye

-----Original Message-----
From: Ben Pfaff [mailto:blp@ovn.org]

Sent: Saturday, April 23, 2016 12:55 AM
To: Daniel Ye
Cc: Wenyu Zhang; dev@openvswitch.org<mailto:dev@openvswitch.org>
Subject: Re: [ovs-dev] [PATCH v1] Support port level IPFIX

On Tue, Apr 19, 2016 at 03:55:10PM +0800, Daniel Benli Ye wrote:
From: Benli Ye <daniely@vmware.com<mailto:daniely@vmware.com>>


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

Thanks for working on IPFIX!  We don't have enough IPFIX expertise around here, so new contributors are always welcome.

The patch lacks a Signed-off-by.  We will need it before it can be applied.  CONTRIBUTING.md explains the format and the meaning, which is to agree to the Developer's Certificate of Origin, which is also in CONTRIBUTING.md; please read it.

Due to the lack of signoff, I did not do a detailed review, but I have some general comments.  First, this patch follows the coding style remarkably well, especially for a first patch--well done, thank you!

Second, this is the third form of configuration to be introduced for IPFIX.  I worry that we'll end up with a fourth, and a fifth, ...  Why does IPFIX need so many kinds of configuration; that is, why can't the details of what packets it selects, etc., be controlled from flows in the flow table, rather than by configuration in the database?  If that were the case, then we would not need to so many forms of configuration:
the controller could control it through the flows over which it already has so much control.

Thanks,

Ben.
_______________________________________________
dev mailing list
dev@openvswitch.org<mailto:dev@openvswitch.org>
https://urldefense.proofpoint.com/v2/url?u=http-3A__openvswitch.org_mailman_listinfo_dev&d=BQIFaQ&c=Sqcl0Ez6M0X8aeM67LKIiDJAXVeAw-YihVMNtXt-uEs&r=AMeQBzObb3Yn4XemNxgato0M1gEhd6eNH0myARLK2io&m=p6RVGLlKKgItBp9hXJb3BHK2hQG72W6p6ZSZ7VQn0_Q&s=gof8sGycUuQkSRCtfpuiiIUunWwHQvfScoInIJVAVoY&e=
diff mbox

Patch

diff --git a/lib/odp-util.c b/lib/odp-util.c
index b4689cc..453ae4f 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;
             }
@@ -963,13 +969,20 @@  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(output_port=%"SCNi32")%n",
+                            &output, &n1) ) {
+            n += n1;
+            cookie.type = USER_ACTION_COOKIE_PORT_IPFIX;
+            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 46d0bb1..080ee27 100644
--- a/ofproto/ofproto-dpif-ipfix.c
+++ b/ofproto/ofproto-dpif-ipfix.c
@@ -92,6 +92,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;
@@ -110,6 +121,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
@@ -423,6 +435,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)
@@ -642,6 +690,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)
 {
@@ -804,32 +948,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++;
@@ -840,22 +1016,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);
             }
         }
     }
@@ -871,6 +1047,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);
@@ -887,6 +1064,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)
@@ -902,7 +1167,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;
@@ -915,7 +1180,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;
@@ -940,15 +1205,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) {
@@ -963,6 +1237,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);
@@ -1679,6 +1954,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;
@@ -1851,13 +2192,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(
@@ -1872,8 +2221,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 a02dc24..66ece06 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);
@@ -5290,7 +5340,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 cf9932c..ca2567d 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 530c49a..40e9f68 100644
--- a/ofproto/ofproto-dpif.c
+++ b/ofproto/ofproto-dpif.c
@@ -670,7 +670,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,
@@ -1955,15 +1956,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;
@@ -1973,7 +1984,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 9373a2c..15c3da5 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 6e74e5e..602a3ac 100644
--- a/ofproto/ofproto.c
+++ b/ofproto/ofproto.c
@@ -851,14 +851,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 2d5a481..a82a56d 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/vswitchd/bridge.c b/vswitchd/bridge.c
index cfe313b..b8c997f0 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 35f145f..a4693eb 100644
--- a/vswitchd/vswitch.ovsschema
+++ b/vswitchd/vswitch.ovsschema
@@ -1,6 +1,6 @@ 
 {"name": "Open_vSwitch",
  "version": "7.12.1",
- "cksum": "2211824403 22535",
+ "cksum": "2540968244 22683",
  "tables": {
    "Open_vSwitch": {
      "columns": {
@@ -191,6 +191,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 7d6976f..818c099 100644
--- a/vswitchd/vswitch.xml
+++ b/vswitchd/vswitch.xml
@@ -1668,6 +1668,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
@@ -4534,7 +4538,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>
@@ -4543,11 +4547,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
@@ -4562,7 +4577,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>
@@ -4584,11 +4600,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">
@@ -4714,8 +4730,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">