[][MAC80211][hnat][Add Netfilter Netlink Ftnl package]

[Description]
Add Netfilter Netlink Ftnl package.
    - Delete specific PPE entry: ftnl -D --sip=192.168.1.3 --dip=10.10.10.3
      --proto=tcp --sport=1234 --dport=5678
    - Flush all PPE entries: ftnl -F

If without this patch, user cannot delete or fluse PPE entries manually.

[Release-log]
N/A


Change-Id: I5d50840e5565b100421ded580ed0f68fe7d08edc
Reviewed-on: https://gerrit.mediatek.inc/c/openwrt/feeds/mtk_openwrt_feeds/+/7190260
diff --git a/autobuild_mac80211_release/0005-add-netfilter-netlink-ftnl-package.patch b/autobuild_mac80211_release/0005-add-netfilter-netlink-ftnl-package.patch
new file mode 100644
index 0000000..4789e02
--- /dev/null
+++ b/autobuild_mac80211_release/0005-add-netfilter-netlink-ftnl-package.patch
@@ -0,0 +1,393 @@
+diff --git a/package/libs/libnfnetlink/patches/101-flowtable-support.patch b/package/libs/libnfnetlink/patches/101-flowtable-support.patch
+new file mode 100644
+index 00000000..2ba78022
+--- /dev/null
++++ b/package/libs/libnfnetlink/patches/101-flowtable-support.patch
+@@ -0,0 +1,12 @@
++Index: libnfnetlink-1.0.1/include/libnfnetlink/linux_nfnetlink.h
++===================================================================
++--- libnfnetlink-1.0.1.orig/include/libnfnetlink/linux_nfnetlink.h	2008-06-18 20:36:57.000000000 +0800
+++++ libnfnetlink-1.0.1/include/libnfnetlink/linux_nfnetlink.h	2019-08-30 18:04:36.026372012 +0800
++@@ -47,6 +47,7 @@
++ #define NFNL_SUBSYS_QUEUE		3
++ #define NFNL_SUBSYS_ULOG		4
++ #define NFNL_SUBSYS_COUNT		5
+++#define NFNL_SUBSYS_FLOWTABLE		12
++ 
++ #ifdef __KERNEL__
++ 
+diff --git a/package/mtk/applications/flowtable/Makefile b/package/mtk/applications/flowtable/Makefile
+new file mode 100755
+index 00000000..150a1a3b
+--- /dev/null
++++ b/package/mtk/applications/flowtable/Makefile
+@@ -0,0 +1,51 @@
++#
++# Copyright (C) 2009-2013 OpenWrt.org
++#
++# This is free software, licensed under the GNU General Public License v2.
++# See /LICENSE for more information.
++#
++
++include $(TOPDIR)/rules.mk
++
++PKG_NAME:=netfilter_flowtable
++PKG_VERSION:=1.0
++PKG_RELEASE:=1
++
++PKG_LICENSE:=GPL-2.0+
++#PKG_INSTALL:=1
++
++include $(INCLUDE_DIR)/package.mk
++
++define Package/netfilter-flowtable
++  SECTION:=MTK Properties
++  CATEGORY:=MTK Properties
++  DEPENDS:=+libnfnetlink +libmnl +kmod-nf-flow-netlink
++  TITLE:=API to the in-kernel flow offload table
++  SUBMENU:=Applications
++endef
++
++define Package/netfilter-flowtable/description
++  API to the in-kernel flow offload table
++endef
++
++TARGET_CFLAGS += $(FPIC)
++
++TARGET_CPPFLAGS := \
++	-D_GNU_SOURCE \
++	-I$(LINUX_DIR)/user_headers/include \
++	-I$(PKG_BUILD_DIR) \
++	$(TARGET_CPPFLAGS) \
++
++define Build/Compile
++	CFLAGS="$(TARGET_CPPFLAGS) $(TARGET_CFLAGS)" \
++	$(MAKE) -C $(PKG_BUILD_DIR) \
++		$(TARGET_CONFIGURE_OPTS) \
++		LIBS="$(TARGET_LDFLAGS) -lnfnetlink  -lm"
++endef
++
++define Package/netfilter-flowtable/install
++	$(INSTALL_DIR) $(1)/usr/bin
++	$(CP) $(PKG_BUILD_DIR)/ftnl $(1)/usr/bin/
++endef
++
++$(eval $(call BuildPackage,netfilter-flowtable))
+\ No newline at end of file
+diff --git a/package/mtk/applications/flowtable/src/Makefile b/package/mtk/applications/flowtable/src/Makefile
+new file mode 100644
+index 00000000..3f450ad2
+--- /dev/null
++++ b/package/mtk/applications/flowtable/src/Makefile
+@@ -0,0 +1,9 @@
++EXEC = ftnl
++SRC = api.c ftnl.c
++
++all:$(EXEC)
++$(EXEC):$(SRC)
++	$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(SRC) $(LDLIBS) $(LIBS)
++
++clean:
++	-rm -f $(EXEC) *.elf *.gdb *.o
+diff --git a/package/mtk/applications/flowtable/src/api.c b/package/mtk/applications/flowtable/src/api.c
+new file mode 100644
+index 00000000..17b88b43
+--- /dev/null
++++ b/package/mtk/applications/flowtable/src/api.c
+@@ -0,0 +1,106 @@
++#include <stdio.h>
++#include <stdlib.h>
++#include <string.h>
++#include "netfilter_flowtable.h"
++
++static void attr_dump(struct nfattr *attr)
++{
++	char *data = nla_data(attr);
++	int i = 0;
++
++	while (i < nal_len(attr)) {
++		printf("%x ", *(data + i));
++		i++;
++		if (i % 16 == 0)
++			printf("\n");
++	}
++	printf("\n");
++}
++
++struct ftnl_handle* ftnl_open(void)
++{
++	struct ftnl_handle *h = NULL;
++
++	h = malloc(sizeof(struct ftnl_handle));
++	if (!h)
++		return NULL;
++
++	h->nfnlh = nfnl_open();
++	if (!h->nfnlh) {
++		printf("nfnl open fail\n");
++		free(h);
++		return NULL;
++	}
++
++	h->ftnlssh = nfnl_subsys_open(h->nfnlh, NFNL_SUBSYS_FLOWTABLE, 1, 0);
++	if (!h->ftnlssh) {
++		nfnl_close(h->nfnlh);
++		printf("subsys open fail\n");
++		free(h);
++		return NULL;
++	}
++
++	return h;
++}
++
++void ftnl_close(struct ftnl_handle *h)
++{
++	nfnl_subsys_close(h->ftnlssh);
++	nfnl_close(h->nfnlh);
++	free(h);
++}
++
++static void build_tuple(struct nlmsghdr *nlh,size_t size, struct flow_tuple *tuple)
++{
++	struct nfattr *nest_tuple, *nest_ip, *nest_proto;
++
++	nest_tuple = nfnl_nest(nlh, size, FTA_TUPLE);
++
++	nest_ip = nfnl_nest(nlh, size, FTA_TUPLE_IP);
++	nfnl_addattr_l(nlh, size, FTA_IP_V4_SRC, &tuple->sip4, sizeof(uint32_t));
++	nfnl_addattr_l(nlh, size, FTA_IP_V4_DST, &tuple->dip4, sizeof(uint32_t));
++	nfnl_nest_end(nlh, nest_ip);
++
++	nest_proto = nfnl_nest(nlh, size, FTA_TUPLE_PROTO);
++	nfnl_addattr_l(nlh, size, FTA_PROTO_NUM, &tuple->proto, sizeof(uint8_t));
++	nfnl_addattr_l(nlh, size, FTA_PROTO_SPORT, &tuple->sport, sizeof(uint16_t));
++	nfnl_addattr_l(nlh, size, FTA_PROTO_DPORT, &tuple->dport, sizeof(uint16_t));
++	nfnl_nest_end(nlh, nest_proto);
++
++	nfnl_nest_end(nlh,nest_tuple);
++//	attr_dump(nest_tuple);
++}
++
++int ftnl_flush_table(struct ftnl_handle *h)
++{
++	struct nlmsghdr nlh;
++	int ret;
++
++	/* construct msg */
++	nfnl_fill_hdr(h->ftnlssh, &nlh, 0, AF_INET, 0,
++		      FT_MSG_FLUSH, NLM_F_REQUEST | NLM_F_ACK);
++
++	/* send msg */
++	ret = nfnl_send(h->nfnlh, &nlh);
++	return ret;
++}
++
++int ftnl_del_flow(struct ftnl_handle *h, struct flow_tuple *tuple)
++{
++	const int size = 256;
++	union {
++		char buffer[size];
++		struct nlmsghdr nlh;
++	} u;
++	int ret;
++
++	/* construct msg */
++	nfnl_fill_hdr(h->ftnlssh, &u.nlh, 0, AF_INET, 0,
++		      FT_MSG_DEL, NLM_F_REQUEST|NLM_F_ACK);
++	build_tuple(&u.nlh, size, tuple);
++
++	/* send msg */
++	ret = nfnl_send(h->nfnlh, &u.nlh);
++
++	return ret;
++}
+diff --git a/package/mtk/applications/flowtable/src/ftnl.c b/package/mtk/applications/flowtable/src/ftnl.c
+new file mode 100644
+index 00000000..30352b29
+--- /dev/null
++++ b/package/mtk/applications/flowtable/src/ftnl.c
+@@ -0,0 +1,92 @@
++#include <stdio.h>
++#include <stdlib.h>
++#include <string.h>
++#include <errno.h>
++#include <unistd.h>
++#include <getopt.h>
++
++#include "netfilter_flowtable.h"
++
++void usage(void)
++{
++	printf("#########flush flow table\n");
++	printf("ftnl -F\n");
++	printf("#########del flow from offload table\n");
++	printf("ftnl -D [sip] [dip] [proto] [sport] [dport]\n");
++}
++
++int main (int argc, char *argv[])
++{
++	struct ftnl_handle *h;
++	struct flow_tuple tuple = {0};
++	int msg = -1;
++	int c;
++	int ret = -1;
++	const char* optstring = "FD";
++	struct option opts[] = {
++		{"sip", required_argument, NULL, 's'},
++		{"dip", required_argument, NULL, 'd'},
++		{"proto", required_argument, NULL, 'p'},
++		{"sport", required_argument, NULL, 'm'},
++		{"dport", required_argument, NULL, 'n'}
++	};
++
++	/* open netlink socket */
++	h = ftnl_open();
++	if (!h)
++		return ret;
++
++	/* parse arg */
++	while ((c = getopt_long(argc, argv, optstring, opts, NULL)) != -1) {
++		switch (c) {
++			case 'F':
++				msg = FT_MSG_FLUSH;
++				break;
++			case 'D':
++				msg = FT_MSG_DEL;
++				break;
++			case 's':
++				inet_aton(optarg, &tuple.sip4);
++				break;
++			case 'd':
++				inet_aton(optarg, &tuple.dip4);
++				break;
++			case 'p':
++				if (!strcmp(optarg, "tcp"))
++					tuple.proto = IPPROTO_TCP;
++				else if (!strcmp(optarg, "udp"))
++					tuple.proto = IPPROTO_UDP;
++				else {
++					printf("proto bad value "
++					       "pls set proto to udp or tcp "
++					       "arg : %s\n", optarg);
++					goto out;
++				}
++				break;
++			case 'm':
++				tuple.sport = htons(atoi(optarg));
++				break;
++			case 'n':
++				tuple.dport = htons(atoi(optarg));
++				break;
++			default:
++				usage();
++				goto out;
++		}
++	}
++
++	switch (msg) {
++		case FT_MSG_FLUSH:
++			ftnl_flush_table(h);
++			break;
++		case FT_MSG_DEL:
++			ftnl_del_flow(h, &tuple);
++			break;
++		default:
++			break;
++	}
++
++out:
++	ftnl_close(h);
++	return ret;
++}
+diff --git a/package/mtk/applications/flowtable/src/netfilter_flowtable.h b/package/mtk/applications/flowtable/src/netfilter_flowtable.h
+new file mode 100644
+index 00000000..3ea8916e
+--- /dev/null
++++ b/package/mtk/applications/flowtable/src/netfilter_flowtable.h
+@@ -0,0 +1,63 @@
++#include <netinet/in.h>
++#include <arpa/inet.h>
++#include <libnfnetlink/libnfnetlink.h>
++
++struct ftnl_handle {
++	struct nfnl_handle		*nfnlh;
++	struct nfnl_subsys_handle	*ftnlssh;
++};
++
++struct flow_tuple {
++	struct in_addr sip4;
++	struct in_addr dip4;
++	unsigned char proto;
++	unsigned short int sport;
++	unsigned short int dport;
++};
++
++enum ft_msg_types{
++	FT_MSG_DEL,
++	FT_MSG_ADD,	//not support now
++	FT_MSG_FLUSH,
++	FT_MSG_MAX
++};
++
++enum ftattr_type {
++	FTA_UNSPEC,
++	FTA_TUPLE,
++	__FTA_MAX
++};
++#define FTA_MAX (__FTA_MAX - 1)
++
++enum ftattr_tuple {
++	FTA_TUPLE_UNSPEC,
++	FTA_TUPLE_IP,
++	FTA_TUPLE_PROTO,
++	FTA_TUPLE_ZONE,
++	__FTA_TUPLE_MAX
++};
++#define FTA_TUPLE_MAX (__FTA_TUPLE_MAX - 1)
++
++enum ftattr_ip {
++	FTA_IP_UNSPEC,
++	FTA_IP_V4_SRC,
++	FTA_IP_V4_DST,
++	FTA_IP_V6_SRC,
++	FTA_IP_V6_DST,
++	__FTA_IP_MAX
++};
++#define FTA_IP_MAX (__FTA_IP_MAX - 1)
++
++enum ftattr_l4proto {
++	FTA_PROTO_UNSPEC,
++	FTA_PROTO_NUM,
++	FTA_PROTO_SPORT,
++	FTA_PROTO_DPORT,
++	__FTA_PROTO_MAX
++};
++#define FTA_PROTO_MAX (__FTA_PROTO_MAX - 1)
++
++struct ftnl_handle* ftnl_open(void);
++void ftnl_close(struct ftnl_handle *h);
++int ftnl_flush_table(struct ftnl_handle *h);
++int ftnl_del_flow(struct ftnl_handle *h, struct flow_tuple *tuple);
+diff --git a/package/kernel/linux/modules/netfilter.mk b/package/kernel/linux/modules/netfilter.mk
+index 97153e5c..e30484b7 100644
+--- a/package/kernel/linux/modules/netfilter.mk
++++ b/package/kernel/linux/modules/netfilter.mk
+@@ -161,6 +161,18 @@ endef
+ 
+ $(eval $(call KernelPackage,nf-flow))
+ 
++define KernelPackage/nf-flow-netlink
++  SUBMENU:=$(NF_MENU)
++  TITLE:=Netfilter flowtable netlink support
++  KCONFIG:= \
++	CONFIG_NF_FLOW_TABLE_NETLINK
++  DEPENDS:=+kmod-nf-flow +kmod-nfnetlink
++  FILES:= \
++	$(LINUX_DIR)/net/netfilter/nf_flow_table_netlink.ko
++  AUTOLOAD:=$(call AutoProbe,nf_flow_table_netlink)
++endef
++
++$(eval $(call KernelPackage,nf-flow-netlink))
+ 
+ define AddDepends/ipt
+   SUBMENU:=$(NF_MENU)
diff --git a/autobuild_mac80211_release/target/linux/mediatek/patches-5.4/9999-9-flow-offload-add-mtkhnat-netlink.patch b/autobuild_mac80211_release/target/linux/mediatek/patches-5.4/9999-9-flow-offload-add-mtkhnat-netlink.patch
new file mode 100644
index 0000000..3b9e863
--- /dev/null
+++ b/autobuild_mac80211_release/target/linux/mediatek/patches-5.4/9999-9-flow-offload-add-mtkhnat-netlink.patch
@@ -0,0 +1,337 @@
+diff --git a/include/net/netfilter/nf_flow_table.h b/include/net/netfilter/nf_flow_table.h
+index 8a84de3..1a23c03 100644
+--- a/include/net/netfilter/nf_flow_table.h
++++ b/include/net/netfilter/nf_flow_table.h
+@@ -276,6 +276,7 @@ int nf_flow_table_init(struct nf_flowtable *flow_table);
+ void nf_flow_table_free(struct nf_flowtable *flow_table);
+ 
+ void flow_offload_teardown(struct flow_offload *flow);
++void flow_offload_teardown_by_tuple(struct flow_offload_tuple *tuple);
+ 
+ int nf_flow_table_iterate(struct nf_flowtable *flow_table,
+ 			  void (*iter)(struct flow_offload *flow, void *data),
+diff --git a/include/uapi/linux/netfilter/nfnetlink.h b/include/uapi/linux/netfilter/nfnetlink.h
+index 5bc960f..603d9c0 100644
+--- a/include/uapi/linux/netfilter/nfnetlink.h
++++ b/include/uapi/linux/netfilter/nfnetlink.h
+@@ -60,7 +60,8 @@ struct nfgenmsg {
+ #define NFNL_SUBSYS_CTHELPER		9
+ #define NFNL_SUBSYS_NFTABLES		10
+ #define NFNL_SUBSYS_NFT_COMPAT		11
+-#define NFNL_SUBSYS_COUNT		12
++#define NFNL_SUBSYS_FLOWTABLE		12
++#define NFNL_SUBSYS_COUNT		13
+ 
+ /* Reserved control nfnetlink messages */
+ #define NFNL_MSG_BATCH_BEGIN		NLMSG_MIN_TYPE
+diff --git a/net/netfilter/Kconfig b/net/netfilter/Kconfig
+index 5d690ab..8ec87aa 100644
+--- a/net/netfilter/Kconfig
++++ b/net/netfilter/Kconfig
+@@ -708,6 +708,15 @@ config NF_FLOW_TABLE
+ 
+ 	  To compile it as a module, choose M here.
+ 
++config NF_FLOW_TABLE_NETLINK
++	tristate "Netfilter flow table netlink module"
++	depends on NETFILTER_INGRESS
++	depends on NF_CONNTRACK
++	help
++	  This option adds the flow table core infrastructure.
++
++	  To compile it as a module, choose M here.
++
+ config NETFILTER_XTABLES
+ 	tristate "Netfilter Xtables support (required for ip_tables)"
+ 	default m if NETFILTER_ADVANCED=n
+diff --git a/net/netfilter/Makefile b/net/netfilter/Makefile
+index d93a121..fa6ffb1 100644
+--- a/net/netfilter/Makefile
++++ b/net/netfilter/Makefile
+@@ -124,6 +124,7 @@ nf_flow_table-objs		:= nf_flow_table_core.o nf_flow_table_ip.o \
+ 				   nf_flow_table_offload.o
+ 
+ obj-$(CONFIG_NF_FLOW_TABLE_INET) += nf_flow_table_inet.o
++obj-$(CONFIG_NF_FLOW_TABLE_NETLINK)	+= nf_flow_table_netlink.o
+ 
+ # generic X tables
+ obj-$(CONFIG_NETFILTER_XTABLES) += x_tables.o xt_tcpudp.o
+diff --git a/net/netfilter/nf_flow_table_core.c b/net/netfilter/nf_flow_table_core.c
+index 1036558..a0f52f6 100644
+--- a/net/netfilter/nf_flow_table_core.c
++++ b/net/netfilter/nf_flow_table_core.c
+@@ -373,6 +373,29 @@ void flow_offload_teardown(struct flow_offload *flow)
+ }
+ EXPORT_SYMBOL_GPL(flow_offload_teardown);
+ 
++void flow_offload_teardown_by_tuple(struct flow_offload_tuple *tuple)
++{
++	struct net_device *netdev;
++	struct nf_flowtable *flowtable;
++	struct flow_offload_tuple_rhash *tuplehash;
++	struct flow_offload *flow;
++	int dir;
++
++	list_for_each_entry(flowtable, &flowtables, list) {
++		for_each_netdev(&init_net, netdev) {
++			tuple->iifidx = netdev->ifindex;
++			tuplehash = flow_offload_lookup(flowtable, tuple);
++			if (!tuplehash)
++				continue;
++
++			dir = tuplehash->tuple.dir;
++			flow = container_of(tuplehash, struct flow_offload, tuplehash[dir]);
++			flow_offload_teardown(flow);
++		}
++	};
++}
++EXPORT_SYMBOL_GPL(flow_offload_teardown_by_tuple);
++
+ struct flow_offload_tuple_rhash *
+ flow_offload_lookup(struct nf_flowtable *flow_table,
+ 		    struct flow_offload_tuple *tuple)
+diff --git a/net/netfilter/nf_flow_table_netlink.c b/net/netfilter/nf_flow_table_netlink.c
+new file mode 100644
+index 0000000..f05f29e
+--- /dev/null
++++ b/net/netfilter/nf_flow_table_netlink.c
+@@ -0,0 +1,239 @@
++#include <linux/types.h>
++#include <linux/kernel.h>
++#include <linux/init.h>
++#include <linux/module.h>
++#include <linux/netfilter.h>
++#include <linux/netlink.h>
++#include <net/netlink.h>
++#include <net/ip.h>
++#include <linux/netfilter/nfnetlink.h>
++#include <net/netfilter/nf_flow_table.h>
++
++enum ft_netlink_msg_types {
++	FT_MSG_DEL,
++	FT_MSG_ADD,
++	FT_MSG_FLUSH,
++	FT_MSG_MAX
++};
++
++enum ftattr_type {
++	FTA_UNSPEC,
++	FTA_TUPLE,
++	__FTA_MAX
++};
++#define FTA_MAX (__FTA_MAX - 1)
++
++enum ftattr_tuple {
++	FTA_TUPLE_UNSPEC,
++	FTA_TUPLE_IP,
++	FTA_TUPLE_PROTO,
++	FTA_TUPLE_ZONE,
++	__FTA_TUPLE_MAX
++};
++#define FTA_TUPLE_MAX (__FTA_TUPLE_MAX - 1)
++
++enum ftattr_ip {
++	FTA_IP_UNSPEC,
++	FTA_IP_V4_SRC,
++	FTA_IP_V4_DST,
++	__FTA_IP_MAX
++};
++#define FTA_IP_MAX (__FTA_IP_MAX - 1)
++
++enum ftattr_l4proto {
++	FTA_PROTO_UNSPEC,
++	FTA_PROTO_NUM,
++	FTA_PROTO_SPORT,
++	FTA_PROTO_DPORT,
++	__FTA_PROTO_MAX
++};
++#define FTA_PROTO_MAX (__FTA_PROTO_MAX - 1)
++
++static const struct nla_policy tuple_nla_policy[FTA_TUPLE_MAX + 1] = {
++	[FTA_TUPLE_IP]		= { .type = NLA_NESTED },
++	[FTA_TUPLE_PROTO]	= { .type = NLA_NESTED },
++	[FTA_TUPLE_ZONE]	= { .type = NLA_U16 },
++};
++
++static const struct nla_policy ip_nla_policy[FTA_IP_MAX + 1] = {
++	[FTA_IP_V4_SRC]		= { .type = NLA_U32 },
++	[FTA_IP_V4_DST]		= { .type = NLA_U32 },
++};
++
++static const struct nla_policy l4proto_nla_policy[FTA_PROTO_MAX + 1] = {
++	[FTA_PROTO_NUM]		= { .type = NLA_U8 },
++	[FTA_PROTO_SPORT]	= {.type = NLA_U16},
++	[FTA_PROTO_DPORT]	= {.type = NLA_U16},
++};
++
++static inline int ftnetlink_parse_tuple_ip(struct nlattr *attr,
++					   struct flow_offload_tuple *tuple)
++{
++	struct nlattr *tb[FTA_IP_MAX+1];
++	int err;
++
++	err = nla_parse_nested_deprecated(tb, FTA_IP_MAX, attr, ip_nla_policy, NULL);
++
++	if (err < 0)
++		return err;
++
++	switch (tuple->l3proto) {
++	case NFPROTO_IPV4:
++		if (!tb[FTA_IP_V4_SRC] || !tb[FTA_IP_V4_DST])
++			return -EINVAL;
++
++		tuple->src_v4.s_addr = nla_get_in_addr(tb[FTA_IP_V4_SRC]);
++		tuple->dst_v4.s_addr = nla_get_in_addr(tb[FTA_IP_V4_DST]);
++	}
++
++	return err;
++}
++
++static inline int ftnetlink_parse_tuple_proto(struct nlattr *attr,
++					      struct flow_offload_tuple *tuple)
++{
++	struct nlattr *tb[FTA_PROTO_MAX+1];
++	int err;
++
++	err = nla_parse_nested_deprecated(tb, FTA_PROTO_MAX, attr, l4proto_nla_policy, NULL);
++
++	if(err < 0)
++		return err;
++
++	if (!tb[FTA_PROTO_NUM] || !tb[FTA_PROTO_SPORT] || !tb[FTA_PROTO_DPORT])
++		return -EINVAL;
++
++	tuple->l4proto = nla_get_u8(tb[FTA_PROTO_NUM]);
++	tuple->src_port = nla_get_u16(tb[FTA_PROTO_SPORT]);
++	tuple->dst_port = nla_get_u16(tb[FTA_PROTO_DPORT]);
++
++	return err;
++}
++
++static int ftnetlink_parse_tuple(const struct nlattr * const cda[],
++				 struct flow_offload_tuple *tuple,
++				 int attrtype, int l3proto)
++{
++	struct nlattr *tb[FTA_TUPLE_MAX+1];
++	int err;
++
++	memset(tuple, 0, sizeof(*tuple));
++
++	err = nla_parse_nested_deprecated(tb, FTA_TUPLE_MAX, cda[attrtype], tuple_nla_policy, NULL);
++	if (err < 0)
++		return err;
++
++	if (!tb[FTA_TUPLE_IP])
++		return -EINVAL;
++
++	/* parse IP */
++	tuple->l3proto = l3proto;
++	err = ftnetlink_parse_tuple_ip(tb[FTA_TUPLE_IP], tuple);
++	if (err < 0)
++		return err;
++
++	/* parse proto */
++	if (!tb[FTA_TUPLE_PROTO])
++		return -EINVAL;
++	err = ftnetlink_parse_tuple_proto(tb[FTA_TUPLE_PROTO], tuple);
++
++	if (err >= 0)
++		printk("tuple info:sip=%pI4,dip=%pI4 proto=%d "
++		       "sport=%d dport=%d\n",
++		       &tuple->src_v4, &tuple->dst_v4, tuple->l4proto,
++		       ntohs(tuple->src_port), ntohs(tuple->dst_port));
++
++	return err;
++}
++
++static int ftnetlink_del_nf_flow(struct net *net, struct sock *ftnl, struct sk_buff *skb,
++				 const struct nlmsghdr *nlh,
++				 const struct nlattr * const cda[],
++				 struct netlink_ext_ack *extack)
++{
++	struct net_device *dev = skb->dev;
++	struct flow_offload_tuple tuple;
++	int err = -1;
++	struct nfgenmsg *nfmsg = nlmsg_data(nlh);
++	u_int8_t u3 = nfmsg->nfgen_family;
++
++	/* parse tuple */
++	if(!cda[FTA_TUPLE])
++		return -EINVAL;
++
++	err = ftnetlink_parse_tuple(cda, &tuple, FTA_TUPLE, u3);
++	if (err < 0)
++		return err;
++
++	/* teardown the flow */
++	flow_offload_teardown_by_tuple(&tuple);
++
++	return 0;
++}
++
++static int ftnetlink_add_nf_flow(struct net *net, struct sock *ftnl, struct sk_buff *skb,
++				 const struct nlmsghdr *nlh,
++				 const struct nlattr * const cda[],
++				 struct netlink_ext_ack *extack)
++{
++	return 0;
++}
++
++static int ftnetlink_flush_table(struct net *net, struct sock *ftnl, struct sk_buff *skb,
++				 const struct nlmsghdr *nlh,
++				 const struct nlattr * const cda[],
++				 struct netlink_ext_ack *extack)
++{
++	struct net_device *dev = skb->dev;
++
++	nf_flow_table_cleanup(dev);
++
++	return 0;
++}
++
++static const struct nla_policy ft_nla_policy[FTA_MAX + 1] = {
++	[FTA_TUPLE] = { .type = NLA_NESTED },
++};
++
++static const struct nfnl_callback flow_table_cb[FT_MSG_MAX] = {
++	[FT_MSG_DEL] = {
++		.call = ftnetlink_del_nf_flow,
++		.attr_count = FTA_MAX,
++		.policy = ft_nla_policy
++	},
++	[FT_MSG_ADD] = {
++		.call = ftnetlink_add_nf_flow,
++		.attr_count = FTA_MAX,
++		.policy = ft_nla_policy
++	},
++	[FT_MSG_FLUSH] = {
++		.call = ftnetlink_flush_table,
++		.attr_count = FTA_MAX,
++		.policy = ft_nla_policy
++	},
++};
++
++static const struct nfnetlink_subsystem ftnl_subsys = {
++	.name = "flowtable",
++	.subsys_id = NFNL_SUBSYS_FLOWTABLE,
++	.cb_count = FT_MSG_MAX,
++	.cb = flow_table_cb,
++};
++
++static int __init ftnetlink_init(void)
++{
++	int ret;
++
++	ret = nfnetlink_subsys_register(&ftnl_subsys);
++
++	return ret;
++}
++
++static void ftnetlink_exit(void)
++{
++	nfnetlink_subsys_unregister(&ftnl_subsys);
++}
++
++MODULE_LICENSE("GPL");
++module_init(ftnetlink_init);
++module_exit(ftnetlink_exit);