route/cls: add flower classifier

This patch adds a subset of functions.
Implemented api:

	rtnl_flower_set_proto;
	rtnl_flower_get_proto;
	rtnl_flower_set_vlan_id;
	rtnl_flower_get_vlan_id;
	rtnl_flower_set_vlan_prio;
	rtnl_flower_get_vlan_prio;
	rtnl_flower_set_vlan_ethtype;
	rtnl_flower_set_dst_mac;
	rtnl_flower_get_dst_mac;
	rtnl_flower_set_src_mac;
	rtnl_flower_get_src_mac;
	rtnl_flower_set_ip_dscp;
	rtnl_flower_get_ip_dscp;
	rtnl_flower_set_flags;
	rtnl_flower_append_action;
	rtnl_flower_del_action;
	rtnl_flower_get_action;

[thaller@redhat.com: squashed commit "route:cls:flower: use parentheses in
  macro definitions"]
[thaller@redhat.com: squashed commit "cls:flower: add TCA_FLOWER_FLAGS
  to flower_policy"]
[thaller@redhat.com: squashed commit "cls:flower: vlan priority is
  uint8_t, not uint16_t"]
[thaller@redhat.com: squashed commit "route:cls:flower: substitute nl_data*
  with uint8_t mac[ETH_ALEN]"]
[thaller@redhat.com: drop non-existing TCA_FLOWER_POLICE. That was
  never merged to upstream kernel. While at it, use decimal numbers
  for the bitshift.]
[thaller@redhat.com: fix build by including <linux/if_ether.h> in
  "types.h".]

Signed-off-by: Volodymyr Bendiuga <volodymyr.bendiuga@westermo.se>
diff --git a/Makefile.am b/Makefile.am
index 4fa2147..79f0d2e 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -130,6 +130,7 @@
 	include/netlink/route/cls/basic.h \
 	include/netlink/route/cls/cgroup.h \
 	include/netlink/route/cls/ematch.h \
+	include/netlink/route/cls/flower.h \
 	include/netlink/route/cls/fw.h \
 	include/netlink/route/cls/matchall.h \
 	include/netlink/route/cls/police.h \
@@ -392,6 +393,7 @@
 	lib/route/cls/ematch/meta.c \
 	lib/route/cls/ematch/nbyte.c \
 	lib/route/cls/ematch/text.c \
+	lib/route/cls/flower.c \
 	lib/route/cls/fw.c \
 	lib/route/cls/mall.c \
 	lib/route/cls/police.c \
diff --git a/include/netlink-private/types.h b/include/netlink-private/types.h
index 8e13222..be10d7a 100644
--- a/include/netlink-private/types.h
+++ b/include/netlink-private/types.h
@@ -27,6 +27,7 @@
 #include <linux/tc_act/tc_vlan.h>
 #include <linux/sock_diag.h>
 #include <linux/fib_rules.h>
+#include <linux/if_ether.h>
 
 #define NL_SOCK_PASSCRED	(1<<1)
 #define NL_OWN_PORT		(1<<2)
@@ -610,6 +611,23 @@
 	int              m_mask;
 };
 
+struct rtnl_flower
+{
+        uint16_t                cf_proto;
+        uint16_t                cf_vlan_id;
+        uint8_t                 cf_vlan_prio;
+        uint16_t                cf_vlan_ethtype;
+        uint8_t                 cf_src_mac[ETH_ALEN];
+        uint8_t                 cf_src_mac_mask[ETH_ALEN];
+        uint8_t                 cf_dst_mac[ETH_ALEN];
+        uint8_t                 cf_dst_mac_mask[ETH_ALEN];
+        uint8_t                 cf_ip_dscp;
+        uint8_t                 cf_ip_dscp_mask;
+        uint32_t                cf_flags;
+        struct rtnl_act *       cf_act;
+        int                     cf_mask;
+};
+
 struct rtnl_cgroup
 {
 	struct rtnl_ematch_tree *cg_ematch;
diff --git a/include/netlink/route/cls/flower.h b/include/netlink/route/cls/flower.h
new file mode 100644
index 0000000..f39ce1c
--- /dev/null
+++ b/include/netlink/route/cls/flower.h
@@ -0,0 +1,58 @@
+/*
+ * netlink/route/cls/flower.h	flower classifier
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2018 Volodymyr Bendiuga <volodymyr.bendiuga@westermo.se>
+ */
+
+#ifndef NETLINK_FLOWER_H_
+#define NETLINK_FLOWER_H_
+
+#include <netlink/netlink.h>
+#include <netlink/cache.h>
+#include <netlink/route/classifier.h>
+#include <netlink/route/action.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern int      rtnl_flower_set_proto(struct rtnl_cls *cls, uint16_t);
+extern int      rtnl_flower_get_proto(struct rtnl_cls *cls, uint16_t *);
+
+extern int	rtnl_flower_set_vlan_id(struct rtnl_cls *, uint16_t);
+extern int	rtnl_flower_get_vlan_id(struct rtnl_cls *, uint16_t *);
+
+extern int	rtnl_flower_set_vlan_prio(struct rtnl_cls *, uint8_t);
+extern int	rtnl_flower_get_vlan_prio(struct rtnl_cls *, uint8_t *);
+
+extern int	rtnl_flower_set_vlan_ethtype(struct rtnl_cls *, uint16_t);
+
+extern int	rtnl_flower_set_dst_mac(struct rtnl_cls *, unsigned char *,
+					unsigned char *);
+extern int	rtnl_flower_get_dst_mac(struct rtnl_cls *, unsigned char *,
+					unsigned char *);
+
+extern int	rtnl_flower_set_src_mac(struct rtnl_cls *, unsigned char *,
+					unsigned char *);
+extern int	rtnl_flower_get_src_mac(struct rtnl_cls *, unsigned char *,
+					unsigned char *);
+
+extern int	rtnl_flower_set_ip_dscp(struct rtnl_cls *, uint8_t, uint8_t);
+extern int	rtnl_flower_get_ip_dscp(struct rtnl_cls *, uint8_t *, uint8_t *);
+
+extern int	rtnl_flower_set_flags(struct rtnl_cls *, int);
+
+extern int	rtnl_flower_append_action(struct rtnl_cls *, struct rtnl_act *);
+extern int	rtnl_flower_del_action(struct rtnl_cls *, struct rtnl_act *);
+extern struct rtnl_act* rtnl_flower_get_action(struct rtnl_cls *);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/route/cls/flower.c b/lib/route/cls/flower.c
new file mode 100644
index 0000000..7951376
--- /dev/null
+++ b/lib/route/cls/flower.c
@@ -0,0 +1,731 @@
+/*
+ * lib/route/cls/flower.c		Flow based traffic control filter
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2018 Volodymyr Bendiuga <volodymyr.bendiuga@gmail.com>
+ */
+
+#include <netlink-private/netlink.h>
+#include <netlink-private/tc.h>
+#include <netlink/netlink.h>
+#include <netlink/attr.h>
+#include <netlink/utils.h>
+#include <netlink-private/route/tc-api.h>
+#include <netlink/route/classifier.h>
+#include <netlink/route/action.h>
+#include <netlink/route/cls/flower.h>
+
+
+/** @cond SKIP */
+#define FLOWER_ATTR_FLAGS           (1 << 0)
+#define FLOWER_ATTR_ACTION          (1 << 1)
+#define FLOWER_ATTR_VLAN_ID         (1 << 2)
+#define FLOWER_ATTR_VLAN_PRIO       (1 << 3)
+#define FLOWER_ATTR_VLAN_ETH_TYPE   (1 << 4)
+#define FLOWER_ATTR_DST_MAC         (1 << 5)
+#define FLOWER_ATTR_DST_MAC_MASK    (1 << 6)
+#define FLOWER_ATTR_SRC_MAC         (1 << 7)
+#define FLOWER_ATTR_SRC_MAC_MASK    (1 << 8)
+#define FLOWER_ATTR_IP_DSCP         (1 << 9)
+#define FLOWER_ATTR_IP_DSCP_MASK    (1 << 10)
+#define FLOWER_ATTR_PROTO           (1 << 11)
+/** @endcond */
+
+#define FLOWER_DSCP_MAX             0xe0
+#define FLOWER_DSCP_MASK_MAX        0xe0
+#define FLOWER_VID_MAX              4095
+#define FLOWER_VLAN_PRIO_MAX        7
+
+static struct nla_policy flower_policy[TCA_FLOWER_MAX + 1] = {
+    [TCA_FLOWER_FLAGS]              = { .type = NLA_U32 },
+    [TCA_FLOWER_KEY_ETH_TYPE]	    = { .type = NLA_U16 },
+    [TCA_FLOWER_KEY_ETH_DST]	    = { .maxlen = ETH_ALEN },
+    [TCA_FLOWER_KEY_ETH_DST_MASK]	= { .maxlen = ETH_ALEN },
+    [TCA_FLOWER_KEY_ETH_SRC]	    = { .maxlen = ETH_ALEN },
+    [TCA_FLOWER_KEY_ETH_SRC_MASK]	= { .maxlen = ETH_ALEN },
+    [TCA_FLOWER_KEY_VLAN_ID]	    = { .type = NLA_U16 },
+    [TCA_FLOWER_KEY_VLAN_PRIO]	    = { .type = NLA_U8 },
+    [TCA_FLOWER_KEY_IP_TOS]		    = { .type = NLA_U8 },
+    [TCA_FLOWER_KEY_IP_TOS_MASK]	= { .type = NLA_U8 },
+};
+
+static int flower_msg_parser(struct rtnl_tc *tc, void *data)
+{
+        struct rtnl_flower *f = data;
+	struct nlattr *tb[TCA_FLOWER_MAX + 1];
+	int err;
+
+	err = tca_parse(tb, TCA_FLOWER_MAX, tc, flower_policy);
+	if (err < 0)
+		return err;
+
+	if (tb[TCA_FLOWER_FLAGS]) {
+		f->cf_flags = nla_get_u32(tb[TCA_FLOWER_FLAGS]);
+		f->cf_mask |= FLOWER_ATTR_FLAGS;
+	}
+
+	if (tb[TCA_FLOWER_ACT]) {
+		err = rtnl_act_parse(&f->cf_act, tb[TCA_FLOWER_ACT]);
+		if (err)
+			return err;
+
+		f->cf_mask |= FLOWER_ATTR_ACTION;
+	}
+
+	if (tb[TCA_FLOWER_KEY_ETH_TYPE]) {
+	        f->cf_proto = nla_get_u16(tb[TCA_FLOWER_KEY_ETH_TYPE]);
+		f->cf_mask |= FLOWER_ATTR_PROTO;
+	}
+
+	if (tb[TCA_FLOWER_KEY_VLAN_ID]) {
+	        f->cf_vlan_id = nla_get_u16(tb[TCA_FLOWER_KEY_VLAN_ID]);
+		f->cf_mask |= FLOWER_ATTR_VLAN_ID;
+	}
+
+	if (tb[TCA_FLOWER_KEY_VLAN_PRIO]) {
+	        f->cf_vlan_prio = nla_get_u8(tb[TCA_FLOWER_KEY_VLAN_PRIO]);
+		f->cf_mask |= FLOWER_ATTR_VLAN_PRIO;
+	}
+
+	if (tb[TCA_FLOWER_KEY_VLAN_ETH_TYPE]) {
+	        f->cf_vlan_ethtype = nla_get_u16(tb[TCA_FLOWER_KEY_VLAN_ETH_TYPE]);
+		f->cf_mask |= FLOWER_ATTR_VLAN_ETH_TYPE;
+	}
+
+	if (tb[TCA_FLOWER_KEY_ETH_DST]) {
+		nla_memcpy(f->cf_dst_mac, tb[TCA_FLOWER_KEY_ETH_DST], ETH_ALEN);
+		f->cf_mask |= FLOWER_ATTR_DST_MAC;
+	}
+
+	if (tb[TCA_FLOWER_KEY_ETH_DST_MASK]) {
+		nla_memcpy(f->cf_dst_mac_mask, tb[TCA_FLOWER_KEY_ETH_DST_MASK], ETH_ALEN);
+		f->cf_mask |= FLOWER_ATTR_DST_MAC_MASK;
+	}
+
+	if (tb[TCA_FLOWER_KEY_ETH_SRC]) {
+		nla_memcpy(f->cf_src_mac, tb[TCA_FLOWER_KEY_ETH_SRC], ETH_ALEN);
+		f->cf_mask |= FLOWER_ATTR_SRC_MAC;
+	}
+
+	if (tb[TCA_FLOWER_KEY_ETH_SRC_MASK]) {
+		nla_memcpy(f->cf_src_mac_mask, tb[TCA_FLOWER_KEY_ETH_SRC_MASK], ETH_ALEN);
+		f->cf_mask |= FLOWER_ATTR_SRC_MAC_MASK;
+	}
+
+	if (tb[TCA_FLOWER_KEY_IP_TOS]) {
+	        f->cf_ip_dscp = nla_get_u8(tb[TCA_FLOWER_KEY_IP_TOS]);
+		f->cf_mask |= FLOWER_ATTR_IP_DSCP;
+	}
+
+	if (tb[TCA_FLOWER_KEY_IP_TOS_MASK]) {
+	        f->cf_ip_dscp_mask = nla_get_u8(tb[TCA_FLOWER_KEY_IP_TOS_MASK]);
+		f->cf_mask |= FLOWER_ATTR_IP_DSCP_MASK;
+	}
+
+        return 0;
+}
+
+static int flower_msg_fill(struct rtnl_tc *tc, void *data, struct nl_msg *msg)
+{
+        struct rtnl_flower *f = data;
+	int err;
+
+	if (!f)
+	        return 0;
+
+	if (f->cf_mask & FLOWER_ATTR_FLAGS)
+	        NLA_PUT_U32(msg, TCA_FLOWER_FLAGS, f->cf_mask);
+
+	if (f->cf_mask & FLOWER_ATTR_ACTION) {
+	        err = rtnl_act_fill(msg, TCA_FLOWER_ACT, f->cf_act);
+		if (err)
+		        return err;
+	}
+
+	if (f->cf_mask & FLOWER_ATTR_PROTO)
+	        NLA_PUT_U16(msg, TCA_FLOWER_KEY_ETH_TYPE, f->cf_proto);
+
+	if (f->cf_mask & FLOWER_ATTR_VLAN_ID)
+	        NLA_PUT_U16(msg, TCA_FLOWER_KEY_VLAN_ID, f->cf_vlan_id);
+
+	if (f->cf_mask & FLOWER_ATTR_VLAN_PRIO)
+	        NLA_PUT_U8(msg, TCA_FLOWER_KEY_VLAN_PRIO, f->cf_vlan_prio);
+
+	if (f->cf_mask & FLOWER_ATTR_VLAN_ETH_TYPE)
+	        NLA_PUT_U16(msg, TCA_FLOWER_KEY_VLAN_ETH_TYPE, f->cf_vlan_ethtype);
+
+	if (f->cf_mask & FLOWER_ATTR_DST_MAC)
+	        NLA_PUT(msg, TCA_FLOWER_KEY_ETH_DST, ETH_ALEN, f->cf_dst_mac);
+
+	if (f->cf_mask & FLOWER_ATTR_DST_MAC_MASK)
+	        NLA_PUT(msg, TCA_FLOWER_KEY_ETH_DST_MASK, ETH_ALEN, f->cf_dst_mac_mask);
+
+	if (f->cf_mask & FLOWER_ATTR_SRC_MAC)
+	        NLA_PUT(msg, TCA_FLOWER_KEY_ETH_SRC, ETH_ALEN, f->cf_src_mac);
+
+	if (f->cf_mask & FLOWER_ATTR_SRC_MAC_MASK)
+	        NLA_PUT(msg, TCA_FLOWER_KEY_ETH_SRC_MASK, ETH_ALEN, f->cf_src_mac_mask);
+
+	if (f->cf_mask & FLOWER_ATTR_IP_DSCP)
+	        NLA_PUT_U8(msg, TCA_FLOWER_KEY_IP_TOS, f->cf_ip_dscp);
+
+	if (f->cf_mask & FLOWER_ATTR_IP_DSCP_MASK)
+	        NLA_PUT_U8(msg, TCA_FLOWER_KEY_IP_TOS_MASK, f->cf_ip_dscp_mask);
+
+        return 0;
+
+ nla_put_failure:
+	return -NLE_NOMEM;
+}
+
+static void flower_free_data(struct rtnl_tc *tc, void *data)
+{
+	struct rtnl_flower *f = data;
+
+	if (f->cf_act)
+		rtnl_act_put_all(&f->cf_act);
+}
+
+static int flower_clone(void *_dst, void *_src)
+{
+        struct rtnl_flower *dst = _dst, *src = _src;
+
+	if (src->cf_mask & FLOWER_ATTR_DST_MAC)
+		memcpy(dst->cf_dst_mac, src->cf_dst_mac, ETH_ALEN);
+
+	if (src->cf_mask & FLOWER_ATTR_DST_MAC_MASK)
+		memcpy(dst->cf_dst_mac_mask, src->cf_dst_mac_mask, ETH_ALEN);
+
+	if (src->cf_mask & FLOWER_ATTR_SRC_MAC)
+		memcpy(dst->cf_src_mac, src->cf_src_mac, ETH_ALEN);
+
+	if (src->cf_mask & FLOWER_ATTR_SRC_MAC_MASK)
+		memcpy(dst->cf_src_mac_mask, src->cf_src_mac_mask, ETH_ALEN);
+
+	if (src->cf_act) {
+		if (!(dst->cf_act = rtnl_act_alloc()))
+			return -NLE_NOMEM;
+
+		memcpy(dst->cf_act, src->cf_act, sizeof(struct rtnl_act));
+
+		/* action nl list next and prev pointers must be updated */
+		nl_init_list_head(&dst->cf_act->ce_list);
+
+		if (src->cf_act->c_opts &&
+		    !(dst->cf_act->c_opts = nl_data_clone(src->cf_act->c_opts)))
+			return -NLE_NOMEM;
+
+		if (src->cf_act->c_xstats &&
+		    !(dst->cf_act->c_xstats = nl_data_clone(src->cf_act->c_xstats)))
+			return -NLE_NOMEM;
+
+		if (src->cf_act->c_subdata &&
+		    !(dst->cf_act->c_subdata = nl_data_clone(src->cf_act->c_subdata)))
+			return -NLE_NOMEM;
+
+		if (dst->cf_act->c_link) {
+			nl_object_get(OBJ_CAST(dst->cf_act->c_link));
+		}
+
+		dst->cf_act->a_next = NULL;   /* Only clone first in chain */
+	}
+
+        return 0;
+}
+
+static void flower_dump_details(struct rtnl_tc *tc, void *data,
+				struct nl_dump_params *p)
+{
+	struct rtnl_flower *f = data;
+
+	if (!f)
+	        return;
+
+	if (f->cf_mask & FLOWER_ATTR_FLAGS)
+		nl_dump(p, " flags %u", f->cf_flags);
+
+	if (f->cf_mask & FLOWER_ATTR_PROTO)
+	        nl_dump(p, " protocol %u", f->cf_proto);
+
+	if (f->cf_mask & FLOWER_ATTR_VLAN_ID)
+	        nl_dump(p, " vlan_id %u", f->cf_vlan_id);
+
+	if (f->cf_mask & FLOWER_ATTR_VLAN_PRIO)
+	        nl_dump(p, " vlan_prio %u", f->cf_vlan_prio);
+
+	if (f->cf_mask & FLOWER_ATTR_VLAN_ETH_TYPE)
+	        nl_dump(p, " vlan_ethtype %u", f->cf_vlan_ethtype);
+
+	if (f->cf_mask & FLOWER_ATTR_DST_MAC)
+	        nl_dump(p, " dst_mac %02x:%02x:%02x:%02x:%02x:%02x",
+			f->cf_dst_mac[0], f->cf_dst_mac[1],
+			f->cf_dst_mac[2], f->cf_dst_mac[3],
+			f->cf_dst_mac[4], f->cf_dst_mac[5]);
+
+	if (f->cf_mask & FLOWER_ATTR_DST_MAC_MASK)
+	        nl_dump(p, " dst_mac_mask %02x:%02x:%02x:%02x:%02x:%02x",
+			f->cf_dst_mac_mask[0], f->cf_dst_mac_mask[1],
+			f->cf_dst_mac_mask[2], f->cf_dst_mac_mask[3],
+			f->cf_dst_mac_mask[4], f->cf_dst_mac_mask[5]);
+
+	if (f->cf_mask & FLOWER_ATTR_SRC_MAC)
+	        nl_dump(p, " src_mac %02x:%02x:%02x:%02x:%02x:%02x",
+			f->cf_src_mac[0], f->cf_src_mac[1],
+			f->cf_src_mac[2], f->cf_src_mac[3],
+			f->cf_src_mac[4], f->cf_src_mac[5]);
+
+	if (f->cf_mask & FLOWER_ATTR_SRC_MAC_MASK)
+	        nl_dump(p, " src_mac_mask %02x:%02x:%02x:%02x:%02x:%02x",
+			f->cf_src_mac_mask[0], f->cf_src_mac_mask[1],
+			f->cf_src_mac_mask[2], f->cf_src_mac_mask[3],
+			f->cf_src_mac_mask[4], f->cf_src_mac_mask[5]);
+
+	if (f->cf_mask & FLOWER_ATTR_IP_DSCP)
+	        nl_dump(p, " dscp %u", f->cf_ip_dscp);
+
+	if (f->cf_mask & FLOWER_ATTR_IP_DSCP_MASK)
+	        nl_dump(p, " dscp_mask %u", f->cf_ip_dscp_mask);
+}
+
+/**
+ * @name Attribute Modification
+ * @{
+ */
+
+/**
+ * Set protocol for flower classifier
+ * @arg cls		Flower classifier.
+ * @arg proto		protocol (ETH_P_*)
+ * @return 0 on success or a negative error code.
+ */
+int rtnl_flower_set_proto(struct rtnl_cls *cls, uint16_t proto)
+{
+        struct rtnl_flower *f;
+
+	if (!(f = rtnl_tc_data(TC_CAST(cls))))
+		return -NLE_NOMEM;
+
+	f->cf_proto = htons(proto);
+	f->cf_mask |= FLOWER_ATTR_PROTO;
+
+	return 0;
+}
+
+/**
+ * Get protocol for flower classifier
+ * @arg cls		Flower classifier.
+ * @arg proto		protocol
+ * @return 0 on success or a negative error code.
+*/
+int rtnl_flower_get_proto(struct rtnl_cls *cls, uint16_t *proto)
+{
+        struct rtnl_flower *f;
+
+	if (!(f = rtnl_tc_data_peek(TC_CAST(cls))))
+	        return -NLE_NOMEM;
+
+        if (!(f->cf_mask & FLOWER_ATTR_PROTO))
+	        return -NLE_MISSING_ATTR;
+
+	*proto = ntohs(f->cf_proto);
+
+	return 0;
+}
+
+/**
+ * Set vlan id for flower classifier
+ * @arg cls		Flower classifier.
+ * @arg vid		vlan id
+ * @return 0 on success or a negative error code.
+ */
+int rtnl_flower_set_vlan_id(struct rtnl_cls *cls, uint16_t vid)
+{
+        struct rtnl_flower *f;
+
+	if (!(f = rtnl_tc_data(TC_CAST(cls))))
+		return -NLE_NOMEM;
+
+	if (vid > FLOWER_VID_MAX)
+	        return -NLE_RANGE;
+
+	f->cf_vlan_id = vid;
+	f->cf_mask |= FLOWER_ATTR_VLAN_ID;
+
+	return 0;
+}
+
+/**
+ * Get vlan id for flower classifier
+ * @arg cls		Flower classifier.
+ * @arg vid		vlan id
+ * @return 0 on success or a negative error code.
+*/
+int rtnl_flower_get_vlan_id(struct rtnl_cls *cls, uint16_t *vid)
+{
+        struct rtnl_flower *f;
+
+	if (!(f = rtnl_tc_data_peek(TC_CAST(cls))))
+	        return -NLE_NOMEM;
+
+        if (!(f->cf_mask & FLOWER_ATTR_VLAN_ID))
+	        return -NLE_MISSING_ATTR;
+
+	*vid = f->cf_vlan_id;
+
+	return 0;
+}
+
+/**
+ * Set vlan priority for flower classifier
+ * @arg cls		Flower classifier.
+ * @arg prio		vlan priority
+ * @return 0 on success or a negative error code.
+ */
+int rtnl_flower_set_vlan_prio(struct rtnl_cls *cls, uint8_t prio)
+{
+        struct rtnl_flower *f;
+
+	if (!(f = rtnl_tc_data(TC_CAST(cls))))
+		return -NLE_NOMEM;
+
+	if (prio > FLOWER_VLAN_PRIO_MAX)
+	        return -NLE_RANGE;
+
+	f->cf_vlan_prio = prio;
+	f->cf_mask |= FLOWER_ATTR_VLAN_PRIO;
+
+	return 0;
+}
+
+/**
+ * Get vlan prio for flower classifier
+ * @arg cls		Flower classifier.
+ * @arg prio		vlan priority
+ * @return 0 on success or a negative error code.
+*/
+int rtnl_flower_get_vlan_prio(struct rtnl_cls *cls, uint8_t *prio)
+{
+        struct rtnl_flower *f;
+
+	if (!(f = rtnl_tc_data_peek(TC_CAST(cls))))
+	        return -NLE_NOMEM;
+
+        if (!(f->cf_mask & FLOWER_ATTR_VLAN_PRIO))
+	        return -NLE_MISSING_ATTR;
+
+	*prio = f->cf_vlan_prio;
+
+	return 0;
+}
+
+/**
+ * Set vlan ethertype for flower classifier
+ * @arg cls		Flower classifier.
+ * @arg ethtype		vlan ethertype
+ * @return 0 on success or a negative error code.
+ */
+int rtnl_flower_set_vlan_ethtype(struct rtnl_cls *cls, uint16_t ethtype)
+{
+        struct rtnl_flower *f;
+
+	if (!(f = rtnl_tc_data(TC_CAST(cls))))
+		return -NLE_NOMEM;
+
+	if (!(f->cf_mask & FLOWER_ATTR_PROTO))
+	        return -NLE_MISSING_ATTR;
+
+	if (f->cf_proto != htons(ETH_P_8021Q))
+	        return -NLE_INVAL;
+
+	f->cf_vlan_ethtype = htons(ethtype);
+	f->cf_mask |= FLOWER_ATTR_VLAN_ETH_TYPE;
+
+	return 0;
+}
+
+/**
+ * Set destination mac address for flower classifier
+ * @arg cls		Flower classifier.
+ * @arg mac		destination mac address
+ * @arg mask		mask for mac address
+ * @return 0 on success or a negative error code.
+ */
+int rtnl_flower_set_dst_mac(struct rtnl_cls *cls, unsigned char *mac,
+			    unsigned char *mask)
+{
+        struct rtnl_flower *f;
+
+	if (!(f = rtnl_tc_data(TC_CAST(cls))))
+	        return -NLE_NOMEM;
+
+	if (mac) {
+		memcpy(f->cf_dst_mac, mac, ETH_ALEN);
+		f->cf_mask |= FLOWER_ATTR_DST_MAC;
+
+		if (mask) {
+			memcpy(f->cf_dst_mac_mask, mask, ETH_ALEN);
+			f->cf_mask |= FLOWER_ATTR_DST_MAC_MASK;
+		}
+
+		return 0;
+	}
+
+	return -NLE_FAILURE;
+}
+
+/**
+ * Get destination mac address for flower classifier
+ * @arg cls		Flower classifier.
+ * @arg mac		destination mac address
+ * @arg mask		mask for mac address
+ * @return 0 on success or a negative error code.
+*/
+int rtnl_flower_get_dst_mac(struct rtnl_cls *cls, unsigned char *mac,
+			    unsigned char *mask)
+{
+        struct rtnl_flower *f;
+
+	if (!(f = rtnl_tc_data_peek(TC_CAST(cls))))
+	        return -NLE_NOMEM;
+
+        if (!(f->cf_mask & FLOWER_ATTR_DST_MAC))
+	        return -NLE_MISSING_ATTR;
+
+	memcpy(mac, f->cf_dst_mac, ETH_ALEN);
+
+	if (f->cf_mask & FLOWER_ATTR_DST_MAC_MASK)
+	        memcpy(mask, f->cf_dst_mac_mask, ETH_ALEN);
+
+        return 0;
+}
+
+/**
+ * Set source mac address for flower classifier
+ * @arg cls		Flower classifier.
+ * @arg mac		source mac address
+ * @arg mask		mask for mac address
+ * @return 0 on success or a negative error code.
+ */
+int rtnl_flower_set_src_mac(struct rtnl_cls *cls, unsigned char *mac,
+			    unsigned char *mask)
+{
+        struct rtnl_flower *f;
+
+	if (!(f = rtnl_tc_data(TC_CAST(cls))))
+	        return -NLE_NOMEM;
+
+	if (mac) {
+		memcpy(f->cf_src_mac, mac, ETH_ALEN);
+		f->cf_mask |= FLOWER_ATTR_SRC_MAC;
+
+		if (mask) {
+			memcpy(f->cf_src_mac_mask, mask, ETH_ALEN);
+			f->cf_mask |= FLOWER_ATTR_SRC_MAC_MASK;
+		}
+
+		return 0;
+	}
+
+	return -NLE_FAILURE;
+}
+
+/**
+ * Get source mac address for flower classifier
+ * @arg cls		Flower classifier.
+ * @arg mac		source mac address
+ * @arg mask		mask for mac address
+ * @return 0 on success or a negative error code.
+*/
+int rtnl_flower_get_src_mac(struct rtnl_cls *cls, unsigned char *mac,
+			    unsigned char *mask)
+{
+        struct rtnl_flower *f;
+
+	if (!(f = rtnl_tc_data_peek(TC_CAST(cls))))
+	        return -NLE_NOMEM;
+
+        if (!(f->cf_mask & FLOWER_ATTR_SRC_MAC))
+	        return -NLE_MISSING_ATTR;
+
+	memcpy(mac, f->cf_src_mac, ETH_ALEN);
+
+	if (f->cf_mask & FLOWER_ATTR_SRC_MAC_MASK)
+	        memcpy(mask, f->cf_src_mac_mask, ETH_ALEN);
+
+        return 0;
+}
+
+/**
+ * Set dscp value for flower classifier
+ * @arg cls		Flower classifier.
+ * @arg dscp		dscp value
+ * @arg mask		mask for dscp value
+ * @return 0 on success or a negative error code.
+ */
+int rtnl_flower_set_ip_dscp(struct rtnl_cls *cls, uint8_t dscp, uint8_t mask)
+{
+        struct rtnl_flower *f;
+
+	if (!(f = rtnl_tc_data(TC_CAST(cls))))
+	        return -NLE_NOMEM;
+
+	if (dscp > FLOWER_DSCP_MAX)
+	        return -NLE_RANGE;
+
+	if (mask > FLOWER_DSCP_MASK_MAX)
+	        return -NLE_RANGE;
+
+	f->cf_ip_dscp = dscp;
+	f->cf_mask |= FLOWER_ATTR_IP_DSCP;
+
+	if (mask) {
+	        f->cf_ip_dscp_mask = mask;
+		f->cf_mask |= FLOWER_ATTR_IP_DSCP_MASK;
+	}
+
+	return 0;
+}
+
+/**
+ * Get dscp value for flower classifier
+ * @arg cls		Flower classifier.
+ * @arg dscp		dscp value
+ * @arg mask		mask for dscp value
+ * @return 0 on success or a negative error code.
+*/
+int rtnl_flower_get_ip_dscp(struct rtnl_cls *cls, uint8_t *dscp, uint8_t *mask)
+{
+        struct rtnl_flower *f;
+
+	if (!(f = rtnl_tc_data_peek(TC_CAST(cls))))
+	        return -NLE_NOMEM;
+
+        if (!(f->cf_mask & FLOWER_ATTR_IP_DSCP))
+	        return -NLE_MISSING_ATTR;
+
+	*dscp = f->cf_ip_dscp;
+	*mask = f->cf_ip_dscp_mask;
+
+	return 0;
+}
+
+/**
+ * Append action for flower classifier
+ * @arg cls		Flower classifier.
+ * @arg act		action to append
+ * @return 0 on success or a negative error code.
+ */
+int rtnl_flower_append_action(struct rtnl_cls *cls, struct rtnl_act *act)
+{
+        struct rtnl_flower *f;
+
+	if (!act)
+	        return 0;
+
+	if (!(f = rtnl_tc_data(TC_CAST(cls))))
+		return -NLE_NOMEM;
+
+	f->cf_mask |= FLOWER_ATTR_ACTION;
+
+	rtnl_act_get(act);
+	return rtnl_act_append(&f->cf_act, act);
+}
+
+/**
+ * Delete action from flower classifier
+ * @arg cls		Flower classifier.
+ * @arg act		action to delete
+ * @return 0 on success or a negative error code.
+ */
+int rtnl_flower_del_action(struct rtnl_cls *cls, struct rtnl_act *act)
+{
+        struct rtnl_flower *f;
+	int ret;
+
+	if (!act)
+		return 0;
+
+	if (!(f = rtnl_tc_data(TC_CAST(cls))))
+		return -NLE_NOMEM;
+
+	if (!(f->cf_mask & FLOWER_ATTR_ACTION))
+		return -NLE_INVAL;
+
+	ret = rtnl_act_remove(&f->cf_act, act);
+	if (ret)
+		return ret;
+
+	if (!f->cf_act)
+		f->cf_mask &= ~FLOWER_ATTR_ACTION;
+	rtnl_act_put(act);
+
+	return 0;
+}
+
+/**
+ * Get action from flower classifier
+ * @arg cls		Flower classifier.
+ * @return action on success or NULL on error.
+ */
+struct rtnl_act* rtnl_flower_get_action(struct rtnl_cls *cls)
+{
+        struct rtnl_flower *f;
+
+	if (!(f = rtnl_tc_data_peek(TC_CAST(cls))))
+	        return NULL;
+
+        if (!(f->cf_mask & FLOWER_ATTR_ACTION))
+	        return NULL;
+
+	rtnl_act_get(f->cf_act);
+
+	return f->cf_act;
+}
+
+/**
+ * Set flags for flower classifier
+ * @arg cls		Flower classifier.
+ * @arg flags		(TCA_CLS_FLAGS_SKIP_HW | TCA_CLS_FLAGS_SKIP_SW)
+ * @return 0 on success or a negative error code.
+ */
+int rtnl_flower_set_flags(struct rtnl_cls *cls, int flags)
+{
+	struct rtnl_flower *f;
+
+	if (!(f = rtnl_tc_data(TC_CAST(cls))))
+		return -NLE_NOMEM;
+
+	f->cf_flags = flags;
+	f->cf_mask |= FLOWER_ATTR_FLAGS;
+
+	return 0;
+}
+
+/** @} */
+
+static struct rtnl_tc_ops flower_ops = {
+	.to_kind		= "flower",
+	.to_type		= RTNL_TC_TYPE_CLS,
+	.to_size		= sizeof(struct rtnl_flower),
+	.to_msg_parser		= flower_msg_parser,
+	.to_free_data		= flower_free_data,
+	.to_clone		= flower_clone,
+	.to_msg_fill		= flower_msg_fill,
+	.to_dump = {
+	    [NL_DUMP_DETAILS]	= flower_dump_details,
+	},
+};
+
+static void __init flower_init(void)
+{
+	rtnl_tc_register(&flower_ops);
+}
+
+static void __exit flower_exit(void)
+{
+	rtnl_tc_unregister(&flower_ops);
+}
diff --git a/libnl-route-3.sym b/libnl-route-3.sym
index 6fdd6a9..8561705 100644
--- a/libnl-route-3.sym
+++ b/libnl-route-3.sym
@@ -1155,6 +1155,23 @@
 global:
 	rtnl_cls_find_by_handle;
 	rtnl_cls_find_by_prio;
+	rtnl_flower_append_action;
+	rtnl_flower_del_action;
+	rtnl_flower_get_action;
+	rtnl_flower_get_dst_mac;
+	rtnl_flower_get_ip_dscp;
+	rtnl_flower_get_proto;
+	rtnl_flower_get_src_mac;
+	rtnl_flower_get_vlan_id;
+	rtnl_flower_get_vlan_prio;
+	rtnl_flower_set_dst_mac;
+	rtnl_flower_set_flags;
+	rtnl_flower_set_ip_dscp;
+	rtnl_flower_set_proto;
+	rtnl_flower_set_src_mac;
+	rtnl_flower_set_vlan_ethtype;
+	rtnl_flower_set_vlan_id;
+	rtnl_flower_set_vlan_prio;
 	rtnl_link_can_get_data_bittiming;
 	rtnl_link_can_get_data_bittiming_const;
 	rtnl_link_can_set_data_bittiming;