[ovs-dev] [PATCH ovs V1 2/9] dpif-hw-acc: Add tc interface

Paul Blakey paulb at mellanox.com
Tue Nov 1 14:53:23 UTC 2016


Add tc interface in order to send/recv data from/to the HW.
The kerenl side of tc will pass the tc messages to the driver and back.

Signed-off-by: Paul Blakey <paulb at mellanox.com>
Signed-off-by: Shahar Klein <shahark at mellanox.com>
---
 lib/automake.mk |   2 +
 lib/tc.c        | 906 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 lib/tc.h        |  86 ++++++
 3 files changed, 994 insertions(+)
 create mode 100644 lib/tc.c
 create mode 100644 lib/tc.h

diff --git a/lib/automake.mk b/lib/automake.mk
index f00c8ae..be2c8eb 100644
--- a/lib/automake.mk
+++ b/lib/automake.mk
@@ -341,6 +341,8 @@ if LINUX
 lib_libopenvswitch_la_SOURCES += \
 	lib/dpif-netlink.c \
 	lib/dpif-netlink.h \
+	lib/tc.h \
+	lib/tc.c \
 	lib/dpif-hw-acc.c \
 	lib/dpif-hw-acc.h \
 	lib/if-notifier.c \
diff --git a/lib/tc.c b/lib/tc.c
new file mode 100644
index 0000000..2f8acb8
--- /dev/null
+++ b/lib/tc.c
@@ -0,0 +1,906 @@
+
+#include <config.h>
+
+#include <errno.h>
+#include <linux/rtnetlink.h>
+#include <net/if.h>
+#include <linux/tc_act/tc_gact.h>
+#include <linux/tc_act/tc_mirred.h>
+#include <linux/gen_stats.h>
+#include "timeval.h"
+#include "netlink-socket.h"
+#include "netlink.h"
+#include "ofpbuf.h"
+#include "rtnetlink.h"
+#include "openvswitch/vlog.h"
+#include "tc.h"
+#include "util.h"
+
+#ifndef __LINUX_TC_VLAN_H
+#define __LINUX_TC_VLAN_H
+
+#include <linux/pkt_cls.h>
+
+#define TCA_ACT_VLAN 12
+
+#define TCA_VLAN_ACT_POP        1
+#define TCA_VLAN_ACT_PUSH       2
+#define TCA_VLAN_ACT_MODIFY     3
+
+struct tc_vlan {
+        tc_gen;
+        int v_action;
+};
+
+enum {
+        TCA_VLAN_UNSPEC,
+        TCA_VLAN_TM,
+        TCA_VLAN_PARMS,
+        TCA_VLAN_PUSH_VLAN_ID,
+        TCA_VLAN_PUSH_VLAN_PROTOCOL,
+        TCA_VLAN_PAD,
+        TCA_VLAN_PUSH_VLAN_PRIORITY,
+        __TCA_VLAN_MAX,
+};
+#define TCA_VLAN_MAX (__TCA_VLAN_MAX - 1)
+
+#endif
+
+
+bool SKIP_HW = false;
+
+VLOG_DEFINE_THIS_MODULE(tc);
+
+/* Returns tc handle 'major':'minor'. */
+static unsigned int
+tc_make_handle(unsigned int major, unsigned int minor)
+{
+    return TC_H_MAKE(major << 16, minor);
+}
+
+static struct tcmsg *
+hw_tc_make_request(int ifindex, int type, unsigned int flags,
+                   struct ofpbuf *request)
+{
+    struct tcmsg *tcmsg;
+
+    ofpbuf_init(request, 512);
+
+    struct nlmsghdr *nlmsghdr;
+
+    ovs_assert(request->size == 0);
+
+    nl_msg_reserve(request, NLMSG_HDRLEN + sizeof *tcmsg);
+    nlmsghdr = nl_msg_put_uninit(request, NLMSG_HDRLEN);
+    nlmsghdr->nlmsg_len = 0;
+    nlmsghdr->nlmsg_type = type;
+    nlmsghdr->nlmsg_flags = NLM_F_REQUEST | flags;
+    nlmsghdr->nlmsg_seq = 0;
+    nlmsghdr->nlmsg_pid = 0;
+
+    tcmsg = ofpbuf_put_zeros(request, sizeof *tcmsg);
+    tcmsg->tcm_family = AF_UNSPEC;
+    tcmsg->tcm_ifindex = ifindex;
+    /* Caller should fill in tcmsg->tcm_handle. */
+    /* Caller should fill in tcmsg->tcm_parent. */
+
+    return tcmsg;
+}
+
+static int
+tc_transact(struct ofpbuf *request, struct ofpbuf **replyp)
+{
+    int error = nl_transact(NETLINK_ROUTE, request, replyp);
+
+    ofpbuf_uninit(request);
+    return error;
+}
+
+static const struct nl_policy tca_policy[] = {
+	[TCA_KIND] = {.type = NL_A_STRING,.optional = false},
+	[TCA_OPTIONS] = {.type = NL_A_NESTED,.optional = false},
+	[TCA_STATS] = {.type = NL_A_UNSPEC,.min_len =
+		sizeof (struct tc_stats),.optional = true},
+	[TCA_STATS2] = {.type = NL_A_NESTED,.optional = true},
+};
+
+static const struct nl_policy tca_flower_policy[TCA_FLOWER_MAX + 1] = {
+    [TCA_FLOWER_CLASSID] = {.type = NL_A_U32,.optional = true},
+    [TCA_FLOWER_INDEV] = {.type = NL_A_STRING,.max_len =
+                          IFNAMSIZ,.optional = true},
+
+    [TCA_FLOWER_KEY_ETH_DST] = {.type = NL_A_UNSPEC,.min_len =
+                                ETH_ALEN,.optional = true},
+    [TCA_FLOWER_KEY_ETH_DST_MASK] = {.type = NL_A_UNSPEC,.min_len =
+                                     ETH_ALEN,.optional = true},
+    [TCA_FLOWER_KEY_ETH_SRC] = {.type = NL_A_UNSPEC,.min_len =
+                                ETH_ALEN,.optional = true},
+    [TCA_FLOWER_KEY_ETH_SRC_MASK] = {.type = NL_A_UNSPEC,.min_len =
+                                     ETH_ALEN,.optional = true},
+    [TCA_FLOWER_KEY_ETH_TYPE] = {.type = NL_A_U16,.optional = false},
+
+    [TCA_FLOWER_FLAGS] = {.type = NL_A_U32,.optional = false},
+    [TCA_FLOWER_ACT] = {.type = NL_A_NESTED,.optional = false},
+
+    [TCA_FLOWER_KEY_IP_PROTO] = {.type = NL_A_U8,.optional = true},
+
+    [TCA_FLOWER_KEY_IPV4_SRC] = {.type = NL_A_U32,.optional = true},
+    [TCA_FLOWER_KEY_IPV4_SRC_MASK] = {.type = NL_A_U32,.optional = true},
+    [TCA_FLOWER_KEY_IPV4_DST] = {.type = NL_A_U32,.optional = true},
+    [TCA_FLOWER_KEY_IPV4_DST_MASK] = {.type = NL_A_U32,.optional = true},
+
+    [TCA_FLOWER_KEY_IPV6_SRC] = {.type = NL_A_UNSPEC,.min_len =
+                                 sizeof (struct in6_addr),.optional =
+                                 true},
+    [TCA_FLOWER_KEY_IPV6_SRC_MASK] = {.type = NL_A_UNSPEC,.min_len =
+                                      sizeof (struct in6_addr),.optional =
+                                      true},
+    [TCA_FLOWER_KEY_IPV6_DST] = {.type = NL_A_UNSPEC,.min_len =
+                                 sizeof (struct in6_addr),.optional =
+                                 true},
+    [TCA_FLOWER_KEY_IPV6_DST_MASK] = {.type = NL_A_UNSPEC,.min_len =
+                                      sizeof (struct in6_addr),.optional =
+                                      true},
+
+    [TCA_FLOWER_KEY_TCP_SRC] = {.type = NL_A_U16,.optional = true},
+    [TCA_FLOWER_KEY_TCP_DST] = {.type = NL_A_U16,.optional = true},
+    [TCA_FLOWER_KEY_TCP_SRC_MASK] = {.type = NL_A_U16,.optional = true},
+    [TCA_FLOWER_KEY_TCP_DST_MASK] = {.type = NL_A_U16,.optional = true},
+
+    [TCA_FLOWER_KEY_UDP_SRC] = {.type = NL_A_U16,.optional = true},
+    [TCA_FLOWER_KEY_UDP_DST] = {.type = NL_A_U16,.optional = true},
+    [TCA_FLOWER_KEY_UDP_SRC_MASK] = {.type = NL_A_U16,.optional = true},
+    [TCA_FLOWER_KEY_UDP_DST_MASK] = {.type = NL_A_U16,.optional = true},
+
+    [TCA_FLOWER_KEY_VLAN_ID] = {.type = NL_A_U16,.optional = true},
+    [TCA_FLOWER_KEY_VLAN_PRIO] = {.type = NL_A_U8,.optional = true},
+    [TCA_FLOWER_KEY_VLAN_ETH_TYPE] = {.type = NL_A_U16,.optional = true},
+};
+
+int
+parse_tc_flow(struct ofpbuf *reply, struct tc_flow *tc_flow)
+{
+    struct tcmsg *tc;
+    struct ofpbuf mask_d, *mask = &mask_d;
+
+    memset(tc_flow, 0, sizeof (*tc_flow));
+    ofpbuf_init(mask, 512);
+    if (NLMSG_HDRLEN + (sizeof *tc) > reply->size) {
+        return EPROTO;
+    }
+
+    tc = ofpbuf_at_assert(reply, NLMSG_HDRLEN, sizeof *tc);
+    tc_flow->handle = tc->tcm_handle;
+    tc_flow->eth_type = TC_H_MIN(tc->tcm_info);
+    tc_flow->prio = TC_H_MAJ(tc->tcm_info) >> 16;
+    VLOG_DBG("parse_tc_flow: handle: 0x%x, %d, eth_type: 0x%x, prio: %d",
+             tc->tcm_handle, tc->tcm_handle, ntohs(tc_flow->eth_type), tc_flow->prio);
+
+    if (!tc_flow->handle)
+        return EAGAIN;
+
+    struct nlattr *ta[ARRAY_SIZE(tca_policy)];
+
+    if (!nl_policy_parse(reply, NLMSG_HDRLEN + sizeof (struct tcmsg),
+                         tca_policy, ta, ARRAY_SIZE(ta))) {
+        VLOG_ERR("failed to parse tca policy");
+        return EPROTO;
+    }
+
+    const char *kind = nl_attr_get_string(ta[TCA_KIND]);
+
+    if (strcmp(kind, "flower")) {
+        VLOG_ERR("error, TCA_KIND not flower!");
+        return EPROTO;
+    }
+
+    struct nlattr *nl_options = ta[TCA_OPTIONS];
+    struct nlattr *attrs[ARRAY_SIZE(tca_flower_policy)];
+
+    if (!nl_parse_nested(nl_options, tca_flower_policy,
+                         attrs, ARRAY_SIZE(tca_flower_policy))) {
+        VLOG_ERR("failed to parse flower classifier options");
+        return EPROTO;
+    }
+
+    int flags = nl_attr_get_u32(attrs[TCA_FLOWER_FLAGS]);
+
+    VLOG_DBG("flags: 0x%x, skip_sw: %d skip_hw: %d", flags,
+             flags & TCA_CLS_FLAGS_SKIP_SW ? 1 : 0,
+             flags & TCA_CLS_FLAGS_SKIP_HW ? 1 : 0);
+
+    if (tc_flow->eth_type == htons(ETH_P_8021Q)) {
+	tc_flow->encap_eth_type = nl_attr_get_u16(attrs[TCA_FLOWER_KEY_ETH_TYPE]);
+	VLOG_DBG("flower encap eth_type: 0x%x", ntohs(tc_flow->encap_eth_type));
+
+	if (attrs[TCA_FLOWER_KEY_VLAN_ETH_TYPE]) {
+		VLOG_DBG("TCA_FLOWER_KEY_VLAN_ETH_TYPE: 0x%x\n", nl_attr_get_u16(attrs[TCA_FLOWER_KEY_VLAN_ETH_TYPE]));
+	}
+        if (attrs[TCA_FLOWER_KEY_VLAN_ID]) {
+		tc_flow->vlan_id = nl_attr_get_u16(attrs[TCA_FLOWER_KEY_VLAN_ID]);
+		VLOG_DBG("flower vlan id: %d", tc_flow->vlan_id);
+	}
+        if (attrs[TCA_FLOWER_KEY_VLAN_PRIO]) {
+		tc_flow->vlan_prio = nl_attr_get_u8(attrs[TCA_FLOWER_KEY_VLAN_PRIO]);
+		VLOG_DBG("flower vlan prio %d", tc_flow->vlan_prio);
+	}
+    }
+
+
+    const struct eth_addr *eth = 0;
+    char eth_str[32] = "";
+
+    if (attrs[TCA_FLOWER_KEY_ETH_SRC]) {
+        eth = nl_attr_get_unspec(attrs[TCA_FLOWER_KEY_ETH_SRC], ETH_ALEN);
+        sprintf(eth_str, "%02x:%02x:%02x:%02x:%02x:%02x", eth->ea[0],
+                eth->ea[1], eth->ea[2], eth->ea[3], eth->ea[4], eth->ea[5]);
+        VLOG_DBG("eth_src: %s", eth_str);
+        memcpy(&tc_flow->src_mac, eth, sizeof (tc_flow->src_mac));
+
+        eth = nl_attr_get_unspec(attrs[TCA_FLOWER_KEY_ETH_SRC_MASK], ETH_ALEN);
+        sprintf(eth_str, "%02x:%02x:%02x:%02x:%02x:%02x", eth->ea[0],
+                eth->ea[1], eth->ea[2], eth->ea[3], eth->ea[4], eth->ea[5]);
+        VLOG_DBG("eth_src_mask: %s", eth_str);
+        memcpy(&tc_flow->src_mac_mask, eth, sizeof (tc_flow->src_mac_mask));
+    }
+
+    if (attrs[TCA_FLOWER_KEY_ETH_DST]) {
+        eth = nl_attr_get_unspec(attrs[TCA_FLOWER_KEY_ETH_DST], ETH_ALEN);
+        sprintf(eth_str, "%02x:%02x:%02x:%02x:%02x:%02x", eth->ea[0],
+                eth->ea[1], eth->ea[2], eth->ea[3], eth->ea[4], eth->ea[5]);
+        VLOG_DBG("eth_dst: %s", eth_str);
+        memcpy(&tc_flow->dst_mac, eth, sizeof (tc_flow->dst_mac));
+
+        eth = nl_attr_get_unspec(attrs[TCA_FLOWER_KEY_ETH_DST_MASK], ETH_ALEN);
+        sprintf(eth_str, "%02x:%02x:%02x:%02x:%02x:%02x", eth->ea[0],
+                eth->ea[1], eth->ea[2], eth->ea[3], eth->ea[4], eth->ea[5]);
+        VLOG_DBG("eth_dst_mask: %s", eth_str);
+        memcpy(&tc_flow->dst_mac_mask, eth, sizeof (tc_flow->dst_mac_mask));
+    }
+
+    if (attrs[TCA_FLOWER_KEY_IP_PROTO]) {
+        int proto = nl_attr_get_u8(attrs[TCA_FLOWER_KEY_IP_PROTO]);
+
+        tc_flow->ip_proto = proto;
+
+        if (attrs[TCA_FLOWER_KEY_IPV4_SRC])
+            tc_flow->ipv4.ipv4_src =
+                nl_attr_get_be32(attrs[TCA_FLOWER_KEY_IPV4_SRC]);
+        if (attrs[TCA_FLOWER_KEY_IPV4_SRC_MASK])
+            tc_flow->ipv4.ipv4_src_mask =
+                nl_attr_get_be32(attrs[TCA_FLOWER_KEY_IPV4_SRC_MASK]);
+        if (attrs[TCA_FLOWER_KEY_IPV4_DST])
+            tc_flow->ipv4.ipv4_dst =
+                nl_attr_get_be32(attrs[TCA_FLOWER_KEY_IPV4_DST]);
+        if (attrs[TCA_FLOWER_KEY_IPV4_DST_MASK])
+            tc_flow->ipv4.ipv4_dst_mask =
+                nl_attr_get_be32(attrs[TCA_FLOWER_KEY_IPV4_DST_MASK]);
+
+        if (attrs[TCA_FLOWER_KEY_IPV6_SRC])
+            memcpy(tc_flow->ipv6.ipv6_src, nl_attr_get_unspec(attrs[TCA_FLOWER_KEY_IPV6_SRC], sizeof(tc_flow->ipv6.ipv6_src)), sizeof(tc_flow->ipv6.ipv6_src));
+        if (attrs[TCA_FLOWER_KEY_IPV6_SRC_MASK])
+            memcpy(tc_flow->ipv6.ipv6_src_mask, nl_attr_get_unspec(attrs[TCA_FLOWER_KEY_IPV6_SRC_MASK], sizeof(tc_flow->ipv6.ipv6_src_mask)), sizeof(tc_flow->ipv6.ipv6_src_mask));
+        if (attrs[TCA_FLOWER_KEY_IPV6_DST])
+            memcpy(tc_flow->ipv6.ipv6_dst, nl_attr_get_unspec(attrs[TCA_FLOWER_KEY_IPV6_DST], sizeof(tc_flow->ipv6.ipv6_dst)), sizeof(tc_flow->ipv6.ipv6_dst));
+        if (attrs[TCA_FLOWER_KEY_IPV6_DST_MASK])
+            memcpy(tc_flow->ipv6.ipv6_dst_mask, nl_attr_get_unspec(attrs[TCA_FLOWER_KEY_IPV6_DST_MASK], sizeof(tc_flow->ipv6.ipv6_dst_mask)), sizeof(tc_flow->ipv6.ipv6_dst_mask));
+
+        if (proto == IPPROTO_TCP) {
+            if (attrs[TCA_FLOWER_KEY_TCP_SRC])
+                tc_flow->src_port =
+                    nl_attr_get_be16(attrs[TCA_FLOWER_KEY_TCP_SRC]);
+            if (attrs[TCA_FLOWER_KEY_TCP_SRC_MASK])
+                tc_flow->src_port_mask =
+                    nl_attr_get_be16(attrs[TCA_FLOWER_KEY_TCP_SRC_MASK]);
+            else
+                memset(&tc_flow->src_port_mask, 0xFF,
+                       sizeof (tc_flow->src_port_mask));
+            if (attrs[TCA_FLOWER_KEY_TCP_DST])
+                tc_flow->dst_port =
+                    nl_attr_get_be16(attrs[TCA_FLOWER_KEY_TCP_DST]);
+            if (attrs[TCA_FLOWER_KEY_TCP_DST_MASK])
+                tc_flow->dst_port_mask =
+                    nl_attr_get_be16(attrs[TCA_FLOWER_KEY_TCP_DST_MASK]);
+            else
+                memset(&tc_flow->dst_port_mask, 0xFF,
+                       sizeof (tc_flow->dst_port_mask));
+        } else if (proto == IPPROTO_UDP) {
+            if (attrs[TCA_FLOWER_KEY_UDP_SRC]) {
+                tc_flow->src_port =
+                    nl_attr_get_be16(attrs[TCA_FLOWER_KEY_UDP_SRC]);
+            }
+            if (attrs[TCA_FLOWER_KEY_UDP_SRC_MASK]) {
+                tc_flow->src_port_mask =
+                    nl_attr_get_be16(attrs[TCA_FLOWER_KEY_UDP_SRC_MASK]);
+            } else
+                memset(&tc_flow->src_port_mask, 0xFF,
+                       sizeof (tc_flow->src_port_mask));
+            if (attrs[TCA_FLOWER_KEY_UDP_DST]) {
+                tc_flow->dst_port =
+                    nl_attr_get_be16(attrs[TCA_FLOWER_KEY_UDP_DST]);
+            }
+            if (attrs[TCA_FLOWER_KEY_UDP_DST_MASK]) {
+                tc_flow->dst_port_mask =
+                    nl_attr_get_be16(attrs[TCA_FLOWER_KEY_UDP_DST_MASK]);
+            } else
+                memset(&tc_flow->dst_port_mask, 0xFF,
+                       sizeof (tc_flow->dst_port_mask));
+        }
+    }
+
+    if (attrs[TCA_FLOWER_ACT]) {
+        struct nlattr *actions = attrs[TCA_FLOWER_ACT];
+        int i = 0;
+
+        static struct nl_policy actions_orders_policy[32 + 1] = { };
+        struct nlattr *actions_orders[ARRAY_SIZE(actions_orders_policy)];
+
+        for (i = 0; i < 33; i++) {
+            actions_orders_policy[i].type = NL_A_NESTED;
+            actions_orders_policy[i].optional = true;
+        }
+
+        if (!nl_parse_nested
+            (actions, actions_orders_policy, actions_orders,
+             ARRAY_SIZE(actions_orders_policy))) {
+            VLOG_ERR("failed to parse action orders (TCA_FLOWER_ACT)");
+            return EPROTO;
+        }
+
+        for (int i = 0; i < 32; i++) {
+            if (actions_orders[i]) {
+                struct nlattr *action = actions_orders[i];
+
+                static const struct nl_policy act_policy[TCA_ACT_MAX + 1] = {
+                    [TCA_ACT_KIND] = {.type = NL_A_STRING,.optional = false},
+                    [TCA_ACT_OPTIONS] = {.type = NL_A_NESTED,.optional =
+                                         false},
+                    [TCA_ACT_STATS] = {.type = NL_A_NESTED,.optional = true},
+                };
+                struct nlattr *action_attrs[ARRAY_SIZE(act_policy)];
+
+                if (!nl_parse_nested(action, act_policy,
+                                     action_attrs, ARRAY_SIZE(act_policy))) {
+                    VLOG_ERR("failed to parse single action options");
+                    return EPROTO;
+                }
+                const char *act_kind =
+                    nl_attr_get_string(action_attrs[TCA_ACT_KIND]);
+                struct nlattr *act_options = action_attrs[TCA_ACT_OPTIONS];
+
+                if (!strcmp(act_kind, "gact")) {
+                    static const struct nl_policy gact_policy[TCA_GACT_MAX +
+                                                              1] = {
+                        [TCA_GACT_PARMS] = {.type = NL_A_UNSPEC,.min_len =
+                                            sizeof (struct tc_gact),.optional =
+                                            false},
+                        [TCA_GACT_PROB] = {.type = NL_A_UNSPEC,.min_len =
+                                           sizeof (struct tc_gact_p),.optional
+                                           = true},
+                        [TCA_GACT_TM] = {.type = NL_A_UNSPEC,.min_len =
+                                         sizeof (struct tcf_t),.optional =
+                                         false},
+                    };
+
+                    struct nlattr *gact_attrs[ARRAY_SIZE(gact_policy)];
+
+                    if (!nl_parse_nested(act_options, gact_policy,
+                                         gact_attrs,
+                                         ARRAY_SIZE(gact_policy))) {
+                        VLOG_ERR("failed to parse gact action options");
+                        return EPROTO;
+                    }
+
+                    if (gact_attrs[TCA_GACT_PARMS]) {
+                        const struct tc_gact *p =
+                            nl_attr_get_unspec(gact_attrs[TCA_GACT_PARMS],
+                                               sizeof (struct tc_gact));
+
+                        if (p->action == TC_ACT_SHOT) {
+                            VLOG_DBG("kind gact - dropping packet");
+                        } else
+                            VLOG_ERR("unkown actions: %d", p->action);
+                    } else
+                        VLOG_ERR("missing gact params!");
+
+                    if (gact_attrs[TCA_GACT_TM]) {
+                        const struct tcf_t *tm =
+                            nl_attr_get_unspec(gact_attrs[TCA_GACT_TM],
+                                               sizeof (struct tcf_t));
+                        unsigned long long int lastuse = tm->lastuse * 10;
+                        unsigned long long int now = time_msec();
+
+                        tc_flow->lastused = now - lastuse;
+                        VLOG_DBG
+                            ("lastuse: %llu ms, now - lastuse:  %llu",
+                             lastuse, now - lastuse);
+                    } else
+                        VLOG_ERR("missing gact tm!");
+                } else if (!strcmp(act_kind, "mirred")) {
+                    static const struct nl_policy mirred_policy[TCA_GACT_MAX +
+                                                                1] = {
+                        [TCA_MIRRED_PARMS] = {.type = NL_A_UNSPEC,.min_len =
+                                              sizeof (struct
+                                                      tc_mirred),.optional =
+                                              false},
+                        [TCA_MIRRED_TM] = {.type = NL_A_UNSPEC,.min_len =
+                                           sizeof (struct tcf_t),.optional =
+                                           false},
+                    };
+
+                    struct nlattr *mirred_attrs[ARRAY_SIZE(mirred_policy)];
+
+                    if (!nl_parse_nested(act_options, mirred_policy,
+                                         mirred_attrs,
+                                         ARRAY_SIZE(mirred_policy))) {
+                        VLOG_ERR("failed to parse mirred action options");
+                        return EPROTO;
+                    }
+
+                    if (mirred_attrs[TCA_MIRRED_PARMS]) {
+                        const struct tc_mirred *m =
+                            nl_attr_get_unspec(mirred_attrs[TCA_MIRRED_PARMS],
+                                               sizeof (struct tc_mirred));
+
+                        if (m->action == TC_ACT_STOLEN
+                            && m->eaction == TCA_EGRESS_REDIR && m->ifindex) {
+                            VLOG_DBG("mirred - redirect to ifinex: %d",
+                                     m->ifindex);
+                            tc_flow->ifindex_out = m->ifindex;
+                        } else
+                            VLOG_ERR("unkown mirred actions: %d, %d, %d",
+                                     m->action, m->eaction, m->ifindex);
+                    } else
+                        VLOG_ERR("missing mirred params!");
+
+                    if (mirred_attrs[TCA_MIRRED_TM]) {
+                        const struct tcf_t *tm =
+                            nl_attr_get_unspec(mirred_attrs[TCA_MIRRED_TM],
+                                               sizeof (struct tcf_t));
+                        unsigned long long int lastuse = tm->lastuse * 10;
+                        unsigned long long int now = time_msec();
+
+                        VLOG_DBG("lastuse: %llu ms, now - lastuse: %llu",
+                                 lastuse, now - lastuse);
+                        tc_flow->lastused = now - lastuse;
+                    } else
+                        VLOG_ERR("missing mirred tm!");
+                } else if (!strcmp(act_kind, "vlan")) {
+                    static const struct nl_policy vlan_policy[TCA_VLAN_MAX +1] = {
+                        [TCA_VLAN_PARMS] = {.type = NL_A_UNSPEC,
+                                            .min_len = sizeof (struct tc_vlan),
+                                            .optional = false},
+                        [TCA_VLAN_PUSH_VLAN_ID] = {.type = NL_A_U16,.optional = true},
+                        [TCA_VLAN_PUSH_VLAN_PROTOCOL] = {.type = NL_A_U16,.optional = true},
+                        [TCA_VLAN_PUSH_VLAN_PRIORITY] = {.type = NL_A_U8,.optional = true },
+                    };
+
+                    struct nlattr *vlan_attrs[ARRAY_SIZE(vlan_policy)];
+
+                    if (!nl_parse_nested(act_options, vlan_policy,
+                                         vlan_attrs,
+                                         ARRAY_SIZE(vlan_policy))) {
+                        VLOG_ERR("failed to parse vlan action options");
+                        return EPROTO;
+                    }
+
+                    if (vlan_attrs[TCA_VLAN_PARMS]) {
+                        const struct tc_vlan *v =
+                            nl_attr_get_unspec(vlan_attrs[TCA_VLAN_PARMS],
+                                               sizeof (struct tc_vlan));
+
+                        if (v->v_action == TCA_VLAN_ACT_PUSH) {
+				tc_flow->vlan_push_id = nl_attr_get_u16(vlan_attrs[TCA_VLAN_PUSH_VLAN_ID]);
+				tc_flow->vlan_push_prio = nl_attr_get_u8(vlan_attrs[TCA_VLAN_PUSH_VLAN_PRIORITY]);
+			} else if (v->v_action == TCA_VLAN_ACT_POP) {
+				tc_flow->vlan_pop = 1;
+                        } else
+                            VLOG_ERR("unkown vlan actions: %d, %d", v->action, v->v_action);
+                    } else
+                        VLOG_ERR("missing vlan params!");
+		}
+		  else 
+                    VLOG_ERR("unkown TCA_ACT_KIND attribute: %s", act_kind);
+
+                if (action_attrs[TCA_ACT_STATS]) {
+                    struct nlattr *act_stats = action_attrs[TCA_ACT_STATS];
+
+                    static const struct nl_policy stats_policy[TCA_STATS_MAX +
+                                                               1] = {
+                        [TCA_STATS_BASIC] = {.type = NL_A_UNSPEC,.min_len =
+                                             sizeof (struct
+                                                     gnet_stats_basic),.optional
+                                             = true},
+                    };
+
+                    struct nlattr *stats_attrs[ARRAY_SIZE(stats_policy)];
+
+                    if (!nl_parse_nested(act_stats, stats_policy,
+                                         stats_attrs,
+                                         ARRAY_SIZE(stats_policy))) {
+                        VLOG_ERR
+                            ("failed to parse action's TCA_ACT_STATS policy");
+                        return EPROTO;
+                    }
+                    if (stats_attrs[TCA_STATS_BASIC]) {
+                        const struct gnet_stats_basic *bs =
+                            nl_attr_get_unspec(stats_attrs[TCA_STATS_BASIC],
+                                               sizeof (struct
+                                                       gnet_stats_basic));
+                        VLOG_DBG
+                            ("basic stats packets (gnet_stats_basic): %u, %llu",
+                             bs->packets, bs->bytes);
+                        struct ovs_flow_stats *stats = &tc_flow->stats;
+
+                        stats->n_packets.lo = bs->packets;
+                        stats->n_packets.hi = 0;
+
+                        stats->n_bytes.hi = bs->bytes >> 32;
+                        stats->n_bytes.lo = bs->bytes & 0x00000000FFFFFFFF;
+                    } else
+                        VLOG_ERR
+                            ("missing tca action basic stats (TCA_STATS_BASIC)");
+                } else
+                    VLOG_ERR("missing action stats (TCA_ACT_STATS)");
+            }
+        }
+    } else
+        VLOG_ERR("missing flower action (TCA_FLOWER_ACT)");
+
+    return 0;
+}
+
+int
+tc_dump_flower_start(int ifindex, struct nl_dump *dump)
+{
+    struct ofpbuf request;
+    struct tcmsg *tcmsg;
+
+    tcmsg =
+        hw_tc_make_request(ifindex, RTM_GETTFILTER,
+                           0 | (NLM_F_REQUEST | NLM_F_DUMP), &request);
+    tcmsg->tcm_parent = tc_make_handle(0xffff, 0);
+    tcmsg->tcm_info = tc_make_handle(0, 0);
+    tcmsg->tcm_handle = 0;
+
+    nl_dump_start(dump, NETLINK_ROUTE, &request);
+    ofpbuf_uninit(&request);
+
+    return 0;
+}
+
+int
+tc_flush_flower(int ifindex)
+{
+    struct ofpbuf request;
+    int error = 0;
+    struct tcmsg *tcmsg;
+
+    VLOG_DBG("flusing ifindex: %d", ifindex);
+
+    tcmsg = hw_tc_make_request(ifindex, RTM_DELTFILTER, NLM_F_ACK, &request);
+    tcmsg->tcm_parent = tc_make_handle(0xffff, 0);
+    tcmsg->tcm_info = tc_make_handle(0, 0);
+
+    error = tc_transact(&request, 0);
+    if (error) {
+        VLOG_ERR("tc error while requesting a flush: %d", error);
+        return error;
+    }
+    return 0;
+}
+
+int
+tc_del_flower(int ifindex, int handle, int prio)
+{
+    struct ofpbuf request;
+    int error = 0;
+    struct tcmsg *tcmsg;
+    struct ofpbuf *reply;
+
+    tcmsg = hw_tc_make_request(ifindex, RTM_DELTFILTER, NLM_F_ECHO, &request);
+    tcmsg->tcm_parent = tc_make_handle(0xffff, 0);
+    tcmsg->tcm_info = tc_make_handle(prio, 0);
+    tcmsg->tcm_handle = handle;
+
+    error = tc_transact(&request, &reply);
+    if (error) {
+        VLOG_ERR("tc error while deleting a rule: %d", error);
+        return error;
+    }
+    return 0;
+}
+
+int
+tc_get_flower(int ifindex, int handle, int prio, struct tc_flow *tc_flow)
+{
+    struct ofpbuf request;
+    int error = 0;
+    struct tcmsg *tcmsg;
+    struct ofpbuf *reply;
+
+    tcmsg = hw_tc_make_request(ifindex, RTM_GETTFILTER, NLM_F_ECHO, &request);
+    tcmsg->tcm_parent = tc_make_handle(0xffff, 0);
+    tcmsg->tcm_info = tc_make_handle(prio, 0);
+    tcmsg->tcm_handle = handle;
+
+    error = tc_transact(&request, &reply);
+    if (error) {
+        VLOG_ERR("tc error while querying a rule: %d", error);
+        return error;
+    }
+
+    parse_tc_flow(reply, tc_flow);
+    return error;
+}
+
+void
+tc_set_skip_hw(bool set)
+{
+    VLOG_INFO("tc: using skip_hw flag?  %s", set ? "true" : "false");
+    SKIP_HW = set;
+}
+
+
+int
+tc_replace_flower(struct tc_flow *tc_flow, uint16_t prio)
+{
+    struct ofpbuf request;
+    int error = 0;
+    struct tcmsg *tcmsg;
+    struct ofpbuf *reply;
+
+    VLOG_DBG("%s %d %s: eth_type %x ip_proto %d (%x), ifindex fwd: %d -> %d",
+             __FILE__, __LINE__, __func__, ntohs(tc_flow->eth_type),
+             tc_flow->ip_proto, tc_flow->ip_proto, tc_flow->ifindex,
+             tc_flow->ifindex_out);
+
+    tcmsg =
+        hw_tc_make_request(tc_flow->ifindex, RTM_NEWTFILTER,
+                           NLM_F_CREATE | NLM_F_ECHO, &request);
+    tcmsg->tcm_parent = tc_make_handle(0xffff, 0);
+    tcmsg->tcm_info =
+        tc_make_handle((OVS_FORCE uint16_t) prio,
+                       (OVS_FORCE uint16_t) tc_flow->eth_type);
+    if (tc_flow->handle) {
+        VLOG_DBG
+            ("requested handle: %d (%x) (replace?, handle will be replaced if exists, add NLM_F_EXCL to not touch existing)",
+             tc_flow->handle, tc_flow->handle);
+        tcmsg->tcm_handle = tc_flow->handle;
+    }
+
+    nl_msg_put_string(&request, TCA_KIND, "flower");
+    size_t basic_offset = nl_msg_start_nested(&request, TCA_OPTIONS);
+
+    {
+        if (tc_flow->dst_mac.ea[0]) {
+            VLOG_DBG("putting dst_mac/mask");
+            nl_msg_put_unspec(&request, TCA_FLOWER_KEY_ETH_DST,
+                              &tc_flow->dst_mac, ETH_ALEN);
+            nl_msg_put_unspec(&request, TCA_FLOWER_KEY_ETH_DST_MASK,
+                              &tc_flow->dst_mac_mask, ETH_ALEN);
+        }
+        if (tc_flow->src_mac.ea[0]) {
+            VLOG_DBG("putting src_mac/mask");
+            nl_msg_put_unspec(&request, TCA_FLOWER_KEY_ETH_SRC,
+                              &tc_flow->src_mac, ETH_ALEN);
+            nl_msg_put_unspec(&request, TCA_FLOWER_KEY_ETH_SRC_MASK,
+                              &tc_flow->src_mac_mask, ETH_ALEN);
+        }
+
+        if (ntohs(tc_flow->eth_type) == ETH_P_IP
+            || ntohs(tc_flow->eth_type) == ETH_P_IPV6) {
+            VLOG_DBG("flower, protocol is ipv4/v6, proto: %d",
+                     tc_flow->ip_proto);
+
+            if (tc_flow->ip_proto) {
+                VLOG_DBG("adding ip proto");
+                nl_msg_put_u8(&request, TCA_FLOWER_KEY_IP_PROTO,
+                              tc_flow->ip_proto);
+
+                if (tc_flow->ip_proto == IPPROTO_UDP) {
+                    VLOG_DBG("adding udp ports %d/%x, %d/%x",
+                             ntohs(tc_flow->src_port),
+                             ntohs(tc_flow->src_port_mask),
+                             ntohs(tc_flow->dst_port),
+                             ntohs(tc_flow->dst_port_mask));
+                    if (tc_flow->src_port) {
+                        VLOG_DBG("adding udp src port/msk");
+                        nl_msg_put_be16(&request, TCA_FLOWER_KEY_UDP_SRC,
+                                        tc_flow->src_port);
+                        nl_msg_put_be16(&request, TCA_FLOWER_KEY_UDP_SRC_MASK,
+                                        tc_flow->src_port_mask);
+                    }
+                    if (tc_flow->dst_port) {
+                        VLOG_DBG("adding udp dst port/msk");
+                        nl_msg_put_be16(&request, TCA_FLOWER_KEY_UDP_DST,
+                                        tc_flow->dst_port);
+                        nl_msg_put_be16(&request, TCA_FLOWER_KEY_UDP_DST_MASK,
+                                        tc_flow->dst_port_mask);
+                    }
+                } else if (tc_flow->ip_proto == IPPROTO_TCP) {
+                    VLOG_DBG("adding tcp ports %d/%x, %d/%x",
+                             ntohs(tc_flow->src_port), tc_flow->src_port_mask,
+                             ntohs(tc_flow->dst_port), tc_flow->dst_port_mask);
+
+                    if (tc_flow->src_port) {
+                        VLOG_DBG("adding tcp src port/msk");
+                        nl_msg_put_be16(&request, TCA_FLOWER_KEY_TCP_SRC,
+                                        tc_flow->src_port);
+                        nl_msg_put_u16(&request, TCA_FLOWER_KEY_TCP_SRC_MASK,
+                                       tc_flow->src_port_mask);
+                    }
+                    if (tc_flow->dst_port) {
+                        VLOG_DBG("adding tcp dst port/msk");
+                        nl_msg_put_be16(&request, TCA_FLOWER_KEY_TCP_DST,
+                                        tc_flow->dst_port);
+                        nl_msg_put_be16(&request, TCA_FLOWER_KEY_TCP_DST_MASK,
+                                        tc_flow->dst_port_mask);
+                    }
+                } else if (tc_flow->ip_proto == IPPROTO_ICMP) {
+                    VLOG_DBG("proto is icmp");
+                }
+            }
+            if (ntohs(tc_flow->eth_type) == ETH_P_IP) {
+                VLOG_DBG("ip_proto is ip, checking ips");
+                if (tc_flow->ipv4.ipv4_src) {
+                    VLOG_DBG("putting ipv4 src/msk, %d/%d",
+                             tc_flow->ipv4.ipv4_src,
+                             tc_flow->ipv4.ipv4_src_mask);
+                    nl_msg_put_be32(&request, TCA_FLOWER_KEY_IPV4_SRC,
+                                    tc_flow->ipv4.ipv4_src);
+                    nl_msg_put_be32(&request, TCA_FLOWER_KEY_IPV4_SRC_MASK,
+                                    tc_flow->ipv4.ipv4_src_mask);
+                }
+                if (tc_flow->ipv4.ipv4_dst) {
+                    VLOG_DBG("putting ipv4 dst/msk %d/%d",
+                             tc_flow->ipv4.ipv4_dst,
+                             tc_flow->ipv4.ipv4_dst_mask);
+                    nl_msg_put_be32(&request, TCA_FLOWER_KEY_IPV4_DST,
+                                    tc_flow->ipv4.ipv4_dst);
+                    nl_msg_put_be32(&request, TCA_FLOWER_KEY_IPV4_DST_MASK,
+                                    tc_flow->ipv4.ipv4_dst_mask);
+                }
+            } else if (ntohs(tc_flow->eth_type) == ETH_P_IPV6) {
+		if (!is_all_zeros(tc_flow->ipv6.ipv6_src_mask, sizeof(tc_flow->ipv6.ipv6_src_mask))) { 
+                    nl_msg_put_unspec(&request, TCA_FLOWER_KEY_IPV6_SRC,
+                                      tc_flow->ipv6.ipv6_src,
+                                      sizeof (tc_flow->ipv6.ipv6_src));
+                    nl_msg_put_unspec(&request, TCA_FLOWER_KEY_IPV6_SRC_MASK,
+                                      tc_flow->ipv6.ipv6_src_mask,
+                                      sizeof (tc_flow->ipv6.ipv6_src_mask));
+		}
+		if (!is_all_zeros(tc_flow->ipv6.ipv6_dst_mask, sizeof(tc_flow->ipv6.ipv6_dst_mask))) {
+                    nl_msg_put_unspec(&request, TCA_FLOWER_KEY_IPV6_SRC,
+                                      tc_flow->ipv6.ipv6_dst,
+                                      sizeof (tc_flow->ipv6.ipv6_dst));
+                    nl_msg_put_unspec(&request, TCA_FLOWER_KEY_IPV6_SRC_MASK,
+                                      tc_flow->ipv6.ipv6_dst_mask,
+                                      sizeof (tc_flow->ipv6.ipv6_dst_mask));
+		}
+            }
+        }
+
+        VLOG_DBG("putting eth_type: %x (nthos)", ntohs(tc_flow->eth_type));
+        nl_msg_put_be16(&request, TCA_FLOWER_KEY_ETH_TYPE, tc_flow->eth_type);
+
+	if (tc_flow->eth_type == htons(ETH_P_8021Q)) {
+		VLOG_DBG("eth type is VLAN (ETH_P_8021Q)\n");
+		if (tc_flow->vlan_id || tc_flow->vlan_prio) {
+			VLOG_DBG("VLAN id to match: %d", tc_flow->vlan_id);
+			nl_msg_put_u16(&request, TCA_FLOWER_KEY_VLAN_ID, tc_flow->vlan_id);
+			VLOG_DBG("VLAN prio: %d to match", tc_flow->vlan_prio);
+			nl_msg_put_u8(&request, TCA_FLOWER_KEY_VLAN_PRIO, tc_flow->vlan_prio);
+		}
+		if (tc_flow->encap_eth_type) {
+			VLOG_DBG("VLAN encapsulated eth_type: 0x%x", ntohs(tc_flow->encap_eth_type));
+			nl_msg_put_be16(&request, TCA_FLOWER_KEY_VLAN_ETH_TYPE, tc_flow->encap_eth_type);
+		}
+		/* TODO: support for encap ipv4/ipv6 here */
+	}
+
+        if (SKIP_HW) {
+            VLOG_DBG
+                ("putting SKIP_HW to avoid using counters, firmware bugs");
+            nl_msg_put_u32(&request, TCA_FLOWER_FLAGS, TCA_CLS_FLAGS_SKIP_HW);
+        } else
+            nl_msg_put_u32(&request, TCA_FLOWER_FLAGS, TCA_CLS_FLAGS_SKIP_SW);
+
+        size_t offset2 = nl_msg_start_nested(&request, TCA_FLOWER_ACT);
+
+        {
+	    size_t index = 1;
+	    int again = 1;
+	    while (again) {
+		    again = 0;
+		    size_t offset3 = nl_msg_start_nested(&request, index++);
+
+		    {
+			if (tc_flow->vlan_push_id) {
+			    VLOG_DBG("flower action: pusing vlan id: %d, prio: %d", tc_flow->vlan_push_id, tc_flow->vlan_push_prio);
+			    nl_msg_put_string(&request, TCA_ACT_KIND, "vlan");
+			    size_t offset4 =
+				nl_msg_start_nested(&request, TCA_ACT_OPTIONS);
+			    {
+				struct tc_vlan parm = { 0 };
+				parm.action = TC_ACT_PIPE;
+				parm.v_action = TCA_VLAN_ACT_PUSH;
+				nl_msg_put_unspec(&request, TCA_VLAN_PARMS, &parm, sizeof (parm));
+				nl_msg_put_u16(&request, TCA_VLAN_PUSH_VLAN_ID, tc_flow->vlan_push_id);
+				nl_msg_put_u8(&request, TCA_VLAN_PUSH_VLAN_PRIORITY, tc_flow->vlan_push_prio);
+			    }
+			    nl_msg_end_nested(&request, offset4);
+			    tc_flow->vlan_push_id = 0; again = 1;
+			}
+			else if (tc_flow->vlan_pop) {
+			    VLOG_DBG("flower action: poping vlan");
+			    nl_msg_put_string(&request, TCA_ACT_KIND, "vlan");
+			    size_t offset4 =
+				nl_msg_start_nested(&request, TCA_ACT_OPTIONS);
+			    {
+				struct tc_vlan parm = { 0 };
+				parm.action = TC_ACT_PIPE;
+				parm.v_action = TCA_VLAN_ACT_POP;
+				nl_msg_put_unspec(&request, TCA_VLAN_PARMS, &parm, sizeof (parm));
+			    }
+			    nl_msg_end_nested(&request, offset4);
+			    tc_flow->vlan_pop = 0; again = 1;
+			}
+			else if (!tc_flow->ifindex_out) {
+			    VLOG_DBG("flower: dropping");
+			    nl_msg_put_string(&request, TCA_ACT_KIND, "gact");
+			    size_t offset4 =
+				nl_msg_start_nested(&request, TCA_ACT_OPTIONS);
+			    {
+				struct tc_gact p;
+
+				memset(&p, 0, sizeof (p));
+
+				p.action = TC_ACT_SHOT;
+				nl_msg_put_unspec(&request, TCA_GACT_PARMS, &p,
+						  sizeof (p));
+			    }
+			    nl_msg_end_nested(&request, offset4);
+			} else {
+			    VLOG_DBG("flower: reidrecting");
+			    nl_msg_put_string(&request, TCA_ACT_KIND, "mirred");
+			    size_t offset4 =
+				nl_msg_start_nested(&request, TCA_ACT_OPTIONS);
+			    {
+				struct tc_mirred m;
+
+				memset(&m, 0, sizeof (m));
+
+				m.eaction = TCA_EGRESS_REDIR;
+				m.action = TC_ACT_STOLEN;
+				m.ifindex = tc_flow->ifindex_out;
+
+				nl_msg_put_unspec(&request, TCA_MIRRED_PARMS, &m,
+						  sizeof (m));
+			    }
+			    nl_msg_end_nested(&request, offset4);
+			}
+		    }
+		    nl_msg_end_nested(&request, offset3);
+	    }
+        }
+        nl_msg_end_nested(&request, offset2);
+    }
+    nl_msg_end_nested(&request, basic_offset);
+
+    error = tc_transact(&request, &reply);
+    if (error) {
+        VLOG_ERR("%s %d: tc error: %d", __func__, __LINE__, error);
+        return error;
+    } else {
+        VLOG_DBG("REPLY SIZE: %d", reply->size);
+        if (reply->size) {
+            struct tcmsg *tc =
+                ofpbuf_at_assert(reply, NLMSG_HDRLEN, sizeof *tc);
+            tc_flow->prio = TC_H_MAJ(tc->tcm_info) >> 16;
+            tc_flow->handle = tc->tcm_handle;
+            VLOG_DBG("SUCCESS, handle: %x, prio: %d", tc_flow->handle, tc_flow->prio);
+        }
+    }
+    return 0;
+}
diff --git a/lib/tc.h b/lib/tc.h
new file mode 100644
index 0000000..5ef14ac
--- /dev/null
+++ b/lib/tc.h
@@ -0,0 +1,86 @@
+#ifndef TC_H
+#define TC_H 1
+
+#include "odp-netlink.h"
+
+/* tca flags definitions */
+#define TCA_CLS_FLAGS_SKIP_HW	(1 << 0)
+#define TCA_CLS_FLAGS_SKIP_SW	(1 << 1)
+#define TCA_FLOWER_MAX (__TCA_FLOWER_MAX - 1)
+struct netdev;
+
+struct tc_flow {
+    uint32_t handle;
+    uint32_t prio;
+
+    odp_port_t ovs_inport;
+    odp_port_t ovs_outport;
+
+    struct netdev *indev;
+    struct netdev *outdev;
+    int ifindex;
+    int ifindex_out;
+
+    ovs_be16 eth_type;
+    uint8_t ip_proto;
+
+    struct eth_addr dst_mac;
+    struct eth_addr dst_mac_mask;
+    struct eth_addr src_mac;
+    struct eth_addr src_mac_mask;
+
+    uint16_t src_port;
+    uint16_t src_port_mask;
+    uint16_t dst_port;
+    uint16_t dst_port_mask;
+
+    uint8_t vlan_pop;
+    uint16_t vlan_push_id;
+    uint8_t vlan_push_prio;
+    uint16_t vlan_id;
+    uint8_t vlan_prio;
+    ovs_be16 encap_eth_type;
+    uint8_t encap_ip_proto;
+    union {
+        struct {
+            ovs_be32 ipv4_src;
+            ovs_be32 ipv4_src_mask;
+            ovs_be32 ipv4_dst;
+            ovs_be32 ipv4_dst_mask;
+        } encap_ipv4;
+        struct {
+            ovs_be32 ipv6_src[4];
+            ovs_be32 ipv6_src_mask[4];
+            ovs_be32 ipv6_dst[4];
+            ovs_be32 ipv6_dst_mask[4];
+        } encap_ipv6;
+    };
+
+    union {
+        struct {
+            ovs_be32 ipv4_src;
+            ovs_be32 ipv4_src_mask;
+            ovs_be32 ipv4_dst;
+            ovs_be32 ipv4_dst_mask;
+        } ipv4;
+        struct {
+            ovs_be32 ipv6_src[4];
+            ovs_be32 ipv6_src_mask[4];
+            ovs_be32 ipv6_dst[4];
+            ovs_be32 ipv6_dst_mask[4];
+        } ipv6;
+    };
+
+    struct ovs_flow_stats stats;
+    uint64_t lastused;
+};
+
+int tc_replace_flower(struct tc_flow *flow, uint16_t prio);
+int tc_del_flower(int ifindex, int handle, int prio);
+int tc_get_flower(int ifindex, int handle, int prio, struct tc_flow *tc_flow);
+int tc_flush_flower(int ifindex);
+int tc_dump_flower_start(int ifindex, struct nl_dump *dump);
+int parse_tc_flow(struct ofpbuf *reply, struct tc_flow *tc_flow);
+void tc_set_skip_hw(bool set);
+
+#endif /* tc.h */
-- 
1.8.3.1




More information about the dev mailing list