[OpenWrt-Devel] [PATCH] restoring dscp on ingress

Kevin 'ldir' Darbyshire-Bryant ldir at darbyshire-bryant.me.uk
Mon May 27 11:07:53 PDT 2019


This was a test - please ignore.  Already deleted from patchwork

> On 27 May 2019, at 19:02, Kevin 'ldir' Darbyshire-Bryant <ldir at darbyshire-bryant.me.uk> wrote:
> 
> A combination of tc filter action act_ctinfo to do the restoration,
> a new savedscp option to iptables xt_connmark to store the DSCP
> 
> Signed-off-by: Kevin Darbyshire-Bryant <ldir at darbyshire-bryant.me.uk>
> ---
> package/kernel/linux/modules/netsupport.mk    |  10 +-
> ...-experimental-support-for-act_ctinfo.patch | 374 ++++++++++
> .../iptables/patches/0001-savedscp.patch      | 213 ++++++
> ...et-sched-Introduce-act_ctinfo-action.patch | 636 +++++++++++++++++
> ...etfilter-connmark-introduce-savedscp.patch | 107 +++
> ...et-sched-Introduce-act_ctinfo-action.patch | 658 ++++++++++++++++++
> ...etfilter-connmark-introduce-savedscp.patch | 142 ++++
> 7 files changed, 2139 insertions(+), 1 deletion(-)
> create mode 100644 package/network/utils/iproute2/patches/0001-initial-experimental-support-for-act_ctinfo.patch
> create mode 100644 package/network/utils/iptables/patches/0001-savedscp.patch
> create mode 100644 target/linux/generic/hack-4.14/0001-net-sched-Introduce-act_ctinfo-action.patch
> create mode 100644 target/linux/generic/hack-4.14/0001-netfilter-connmark-introduce-savedscp.patch
> create mode 100644 target/linux/generic/hack-4.19/0001-net-sched-Introduce-act_ctinfo-action.patch
> create mode 100644 target/linux/generic/hack-4.19/0001-netfilter-connmark-introduce-savedscp.patch
> 
> diff --git a/package/kernel/linux/modules/netsupport.mk b/package/kernel/linux/modules/netsupport.mk
> index d92992e13f..7ab6d3b1b3 100644
> --- a/package/kernel/linux/modules/netsupport.mk
> +++ b/package/kernel/linux/modules/netsupport.mk
> @@ -803,7 +803,6 @@ endef
> 
> $(eval $(call KernelPackage,sched-mqprio))
> 
> -
> define KernelPackage/sched-connmark
>   SUBMENU:=$(NETWORK_SUPPORT_MENU)
>   TITLE:=Traffic shaper conntrack mark support
> @@ -814,6 +813,15 @@ define KernelPackage/sched-connmark
> endef
> $(eval $(call KernelPackage,sched-connmark))
> 
> +define KernelPackage/sched-ctinfo
> +  SUBMENU:=$(NETWORK_SUPPORT_MENU)
> +  TITLE:=Traffic shaper ctinfo support
> +  DEPENDS:=+kmod-sched-core +kmod-ipt-core +kmod-ipt-conntrack-extra
> +  KCONFIG:=CONFIG_NET_ACT_CTINFO
> +  FILES:=$(LINUX_DIR)/net/sched/act_ctinfo.ko
> +  AUTOLOAD:=$(call AutoLoad,71, act_ctinfo)
> +endef
> +$(eval $(call KernelPackage,sched-ctinfo))
> 
> define KernelPackage/sched-ipset
>   SUBMENU:=$(NETWORK_SUPPORT_MENU)
> diff --git a/package/network/utils/iproute2/patches/0001-initial-experimental-support-for-act_ctinfo.patch b/package/network/utils/iproute2/patches/0001-initial-experimental-support-for-act_ctinfo.patch
> new file mode 100644
> index 0000000000..6983e7faed
> --- /dev/null
> +++ b/package/network/utils/iproute2/patches/0001-initial-experimental-support-for-act_ctinfo.patch
> @@ -0,0 +1,374 @@
> +From 5dd8945255b679246041866285faab2e4348362c Mon Sep 17 00:00:00 2001
> +From: Kevin Darbyshire-Bryant <ldir at darbyshire-bryant.me.uk>
> +Date: Fri, 15 Mar 2019 09:35:37 +0000
> +Subject: [PATCH] initial experimental support for act_ctinfo
> +
> +switchtoctinfo
> +
> +Signed-off-by: Kevin Darbyshire-Bryant <ldir at darbyshire-bryant.me.uk>
> +---
> + include/uapi/linux/pkt_cls.h          |   1 +
> + include/uapi/linux/tc_act/tc_ctinfo.h |  52 +++++
> + tc/Makefile                           |   1 +
> + tc/m_ctinfo.c                         | 266 ++++++++++++++++++++++++++
> + 4 files changed, 320 insertions(+)
> + create mode 100644 include/uapi/linux/tc_act/tc_ctinfo.h
> + create mode 100644 tc/m_ctinfo.c
> +
> +diff --git a/include/uapi/linux/pkt_cls.h b/include/uapi/linux/pkt_cls.h
> +index 95d0db2a..7df0e808 100644
> +--- a/include/uapi/linux/pkt_cls.h
> ++++ b/include/uapi/linux/pkt_cls.h
> +@@ -68,6 +68,7 @@ enum {
> + 	TCA_ID_UNSPEC=0,
> + 	TCA_ID_POLICE=1,
> + 	/* other actions go here */
> ++	TCA_ID_CTINFO=27,
> + 	__TCA_ID_MAX=255
> + };
> + 
> +diff --git a/include/uapi/linux/tc_act/tc_ctinfo.h b/include/uapi/linux/tc_act/tc_ctinfo.h
> +new file mode 100644
> +index 00000000..48c40f65
> +--- /dev/null
> ++++ b/include/uapi/linux/tc_act/tc_ctinfo.h
> +@@ -0,0 +1,52 @@
> ++/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
> ++#ifndef __UAPI_TC_CTINFO_H
> ++#define __UAPI_TC_CTINFO_H
> ++
> ++#include <linux/types.h>
> ++#include <linux/pkt_cls.h>
> ++
> ++struct tc_ctinfo {
> ++	tc_gen;
> ++};
> ++
> ++struct tc_ctinfo_dscp {
> ++	__u32 mask;
> ++	__u32 statemask;
> ++};
> ++
> ++struct tc_ctinfo_cpmark {
> ++	__u32 mask;
> ++};
> ++
> ++struct tc_ctinfo_stats_dscp {
> ++	__u64 set;
> ++	__u64 error;
> ++};
> ++
> ++struct tc_ctinfo_stats_cpmark {
> ++	__u64 set;
> ++};
> ++
> ++enum {
> ++	TCA_CTINFO_UNSPEC,
> ++	TCA_CTINFO_PAD,
> ++	TCA_CTINFO_TM,
> ++	TCA_CTINFO_ACT,
> ++	TCA_CTINFO_ZONE,
> ++	TCA_CTINFO_PARMS_DSCP,
> ++	TCA_CTINFO_PARMS_CPMARK,
> ++	TCA_CTINFO_MODE_DSCP,
> ++	TCA_CTINFO_MODE_CPMARK,
> ++	TCA_CTINFO_STATS_DSCP,
> ++	TCA_CTINFO_STATS_CPMARK,
> ++	__TCA_CTINFO_MAX
> ++};
> ++
> ++#define TCA_CTINFO_MAX (__TCA_CTINFO_MAX - 1)
> ++
> ++enum {
> ++	CTINFO_MODE_DSCP	= BIT(0),
> ++	CTINFO_MODE_CPMARK	= BIT(1)
> ++};
> ++
> ++#endif
> +diff --git a/tc/Makefile b/tc/Makefile
> +index 2edaf2c8..ec93a9a1 100644
> +--- a/tc/Makefile
> ++++ b/tc/Makefile
> +@@ -48,6 +48,7 @@ TCMODULES += m_csum.o
> + TCMODULES += m_simple.o
> + TCMODULES += m_vlan.o
> + TCMODULES += m_connmark.o
> ++TCMODULES += m_ctinfo.o
> + TCMODULES += m_bpf.o
> + TCMODULES += m_tunnel_key.o
> + TCMODULES += m_sample.o
> +diff --git a/tc/m_ctinfo.c b/tc/m_ctinfo.c
> +new file mode 100644
> +index 00000000..1ee04452
> +--- /dev/null
> ++++ b/tc/m_ctinfo.c
> +@@ -0,0 +1,266 @@
> ++/*
> ++ * m_ctinfo.c		netfilter ctinfo dscp<->ctinfo mark action
> ++ *
> ++ * Copyright (c) 2019 Kevin Darbyshire-Bryant <ldir at darbyshire-bryant.me.uk>
> ++ *
> ++ * This program is free software; you can redistribute it and/or modify it
> ++ * under the terms and conditions of the GNU General Public License,
> ++ * version 2, as published by the Free Software Foundation.
> ++ *
> ++ * This program is distributed in the hope it will be useful, but WITHOUT
> ++ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> ++ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
> ++ * more details.
> ++ *
> ++ * You should have received a copy of the GNU General Public License along with
> ++ * this program; if not, see <http://www.gnu.org/licenses>.
> ++ */
> ++
> ++#include <stdio.h>
> ++#include <stdlib.h>
> ++#include <unistd.h>
> ++#include <string.h>
> ++#include "utils.h"
> ++#include "tc_util.h"
> ++#include <linux/tc_act/tc_ctinfo.h>
> ++
> ++static void
> ++explain(void)
> ++{
> ++	fprintf(stderr, "Usage: ... ctinfo [dscp mask[/statemask]] [mark [mask]] [zone ZONE] [CONTROL] [index <INDEX>]\n");
> ++	fprintf(stderr, "where :\n"
> ++		"\tMASK is the bitmask to store/restore DSCP\n"
> ++		"\tSTATEMASK is the bitmask to determine conditional storing/restoring\n"
> ++		"\tZONE is the conntrack zone\n"
> ++		"\tCONTROL := reclassify | pipe | drop | continue | ok |\n"
> ++		"\t           goto chain <CHAIN_INDEX>\n");
> ++}
> ++
> ++static void
> ++usage(void)
> ++{
> ++	explain();
> ++	exit(-1);
> ++}
> ++
> ++static int
> ++parse_ctinfo(struct action_util *a, int *argc_p, char ***argv_p, int tca_id,
> ++	      struct nlmsghdr *n)
> ++{
> ++	struct tc_ctinfo_cpmark cpmark = {
> ++		.mask = ~0
> ++	};
> ++	struct tc_ctinfo_dscp dscp = {};
> ++	struct tc_ctinfo sel = {};
> ++	char **argv = *argv_p;
> ++	int argc = *argc_p;
> ++	int ok = 0;
> ++	struct rtattr *tail;
> ++	unsigned short zone = 0;
> ++	int mark = 0;
> ++
> ++	while (argc > 0) {
> ++		if (matches(*argv, "ctinfo") == 0) {
> ++			ok = 1;
> ++			argc--;
> ++			argv++;
> ++		} else if (matches(*argv, "help") == 0) {
> ++			usage();
> ++		} else {
> ++			break;
> ++		}
> ++
> ++	}
> ++
> ++	if (!ok) {
> ++		explain();
> ++		return -1;
> ++	}
> ++
> ++	if (argc) {
> ++		if (matches(*argv, "dscp") == 0) {
> ++			NEXT_ARG();
> ++			char *slash;
> ++			if ((slash = strchr(*argv, '/')))
> ++				*slash = '\0';
> ++			if (get_u32(&dscp.mask, *argv, 0)) {
> ++				fprintf(stderr, "ctinfo: Illegal dscp \"mask\"\n");
> ++				return -1;
> ++			}
> ++			if (slash) {
> ++				if (get_u32(&dscp.statemask, slash + 1, 0)) {
> ++					fprintf(stderr, "ctinfo: Illegal dscp \"statemask\"\n");
> ++					return -1;
> ++				}
> ++			}
> ++			argc--;
> ++			argv++;
> ++		}
> ++	}
> ++
> ++	/* mark has optional mask parameter, so the next arg might not exist, */
> ++	/* or it might be the next option, or it may actually be a 32bit mask */
> ++	if (argc) {
> ++		if (matches(*argv, "mark") == 0) {
> ++			mark++;
> ++			if (NEXT_ARG_OK()) {
> ++				NEXT_ARG_FWD();
> ++				if (!get_u32(&cpmark.mask, *argv, 0)) {
> ++					NEXT_ARG_FWD(); /* was a mask */
> ++				}
> ++			} else {
> ++				NEXT_ARG_FWD();
> ++			}
> ++		}
> ++	}
> ++
> ++	if (argc) {
> ++		if (matches(*argv, "zone") == 0) {
> ++			NEXT_ARG();
> ++			if (get_u16(&zone, *argv, 10)) {
> ++				fprintf(stderr, "ctinfo: Illegal \"zone\"\n");
> ++				return -1;
> ++			}
> ++			argc--;
> ++			argv++;
> ++		}
> ++	}
> ++
> ++	parse_action_control_dflt(&argc, &argv, &sel.action, false, TC_ACT_PIPE);
> ++
> ++	if (argc) {
> ++		if (matches(*argv, "index") == 0) {
> ++			NEXT_ARG();
> ++			if (get_u32(&sel.index, *argv, 10)) {
> ++				fprintf(stderr, "ctinfo: Illegal \"index\"\n");
> ++				return -1;
> ++			}
> ++			argc--;
> ++			argv++;
> ++		}
> ++	}
> ++
> ++	tail = addattr_nest(n, MAX_MSG, tca_id);
> ++	addattr_l(n, MAX_MSG, TCA_CTINFO_ACT, &sel, sizeof(sel));
> ++	if (zone)
> ++		addattr16(n, MAX_MSG, TCA_CTINFO_ZONE, zone);
> ++
> ++	if (dscp.mask) {
> ++		addattr(n, MAX_MSG, TCA_CTINFO_MODE_DSCP);
> ++		addattr_l(n, MAX_MSG, TCA_CTINFO_PARMS_DSCP, &dscp, sizeof(dscp));
> ++	}
> ++
> ++	if (mark) {
> ++		addattr(n, MAX_MSG, TCA_CTINFO_MODE_CPMARK);
> ++		addattr_l(n, MAX_MSG, TCA_CTINFO_PARMS_CPMARK, &cpmark, sizeof(cpmark));
> ++	}
> ++
> ++	addattr_nest_end(n, tail);
> ++
> ++	*argc_p = argc;
> ++	*argv_p = argv;
> ++	return 0;
> ++}
> ++
> ++static int print_ctinfo(struct action_util *au, FILE *f, struct rtattr *arg)
> ++{
> ++	struct tc_ctinfo_dscp *ci_cpmark = NULL;
> ++	struct tc_ctinfo_dscp *ci_dscp = NULL;
> ++	struct rtattr *tb[TCA_CTINFO_MAX + 1];
> ++	unsigned short zone = 0;
> ++	struct tc_ctinfo *ci;
> ++
> ++	if (arg == NULL)
> ++		return -1;
> ++
> ++	parse_rtattr_nested(tb, TCA_CTINFO_MAX, arg);
> ++	if (!tb[TCA_CTINFO_ACT]) {
> ++		print_string(PRINT_FP, NULL, "%s", "[NULL ctinfo parameters]");
> ++		return -1;
> ++	}
> ++
> ++	ci = RTA_DATA(tb[TCA_CTINFO_ACT]);
> ++
> ++	if (tb[TCA_CTINFO_MODE_DSCP]) {
> ++		if (!tb[TCA_CTINFO_PARMS_DSCP]) {
> ++			print_string(PRINT_FP, NULL, "%s", "[NULL dscp parameters]");
> ++			return -1;
> ++		} else {
> ++			if (RTA_PAYLOAD(tb[TCA_CTINFO_PARMS_DSCP]) >= sizeof(ci_dscp)) {
> ++				ci_dscp = RTA_DATA(tb[TCA_CTINFO_PARMS_DSCP]);
> ++			} else {
> ++				print_string(PRINT_FP, NULL, "%s", "[invalid dscp parameters]");
> ++			}
> ++		}
> ++	}
> ++
> ++	if (tb[TCA_CTINFO_MODE_CPMARK]) {
> ++		if (!tb[TCA_CTINFO_PARMS_CPMARK]) {
> ++			print_string(PRINT_FP, NULL, "%s", "[NULL cpmark parameters]");
> ++			return -1;
> ++		} else {
> ++			if (RTA_PAYLOAD(tb[TCA_CTINFO_PARMS_CPMARK]) >= sizeof(ci_cpmark)) {
> ++				ci_cpmark = RTA_DATA(tb[TCA_CTINFO_PARMS_CPMARK]);
> ++			} else {
> ++				print_string(PRINT_FP, NULL, "%s", "[invalid cpmark parameters]");
> ++			}
> ++		}
> ++	}
> ++
> ++	if (tb[TCA_CTINFO_ZONE] &&
> ++	    RTA_PAYLOAD(tb[TCA_CTINFO_ZONE]) >= sizeof(__u16))
> ++			zone = rta_getattr_u16(tb[TCA_CTINFO_ZONE]);
> ++
> ++	print_string(PRINT_ANY, "kind", "%s ", "ctinfo");
> ++	print_hu(PRINT_ANY, "zone", "zone %u", zone);
> ++	print_action_control(f, " ", ci->action, "");
> ++
> ++	print_string(PRINT_FP, NULL, "%s", _SL_);
> ++	print_uint(PRINT_ANY, "index", "\t index %u", ci->index);
> ++	print_int(PRINT_ANY, "ref", " ref %d", ci->refcnt);
> ++	print_int(PRINT_ANY, "bind", " bind %d", ci->bindcnt);
> ++
> ++	if (tb[TCA_CTINFO_MODE_DSCP]) {
> ++		if (ci_dscp) {
> ++			print_0xhex(PRINT_ANY, "dscpmask", " dscp %#llx", ci_dscp->mask);
> ++			print_0xhex(PRINT_ANY, "dscpstatemask", "/%#llx", ci_dscp->statemask);
> ++		}
> ++	}
> ++
> ++	if (tb[TCA_CTINFO_MODE_CPMARK]) {
> ++		if (ci_cpmark) {
> ++			print_0xhex(PRINT_ANY, "mark", " mark %#llx", ci_cpmark->mask);
> ++		}
> ++	}
> ++
> ++	if (show_stats) {
> ++		if (tb[TCA_CTINFO_TM]) {
> ++			struct tcf_t *tm = RTA_DATA(tb[TCA_CTINFO_TM]);
> ++
> ++			print_tm(f, tm);
> ++		}
> ++		if (tb[TCA_CTINFO_STATS_DSCP]) {
> ++			struct tc_ctinfo_stats_dscp *dscpstats = RTA_DATA(tb[TCA_CTINFO_STATS_DSCP]);
> ++
> ++			if (dscpstats) {
> ++				print_lluint(PRINT_ANY, "dscpset", " DSCP set %llu", dscpstats->set);
> ++				print_lluint(PRINT_ANY, "dscperror", " error %llu", dscpstats->error);
> ++			}
> ++		}
> ++		if (tb[TCA_CTINFO_STATS_CPMARK]) {
> ++			struct tc_ctinfo_stats_cpmark *cpmarkstats = RTA_DATA(tb[TCA_CTINFO_STATS_CPMARK]);
> ++
> ++			if (cpmarkstats)
> ++				print_lluint(PRINT_ANY, "markset", " MARK set %llu", cpmarkstats->set);
> ++		}
> ++	}
> ++	print_string(PRINT_FP, NULL, "%s", _SL_);
> ++
> ++	return 0;
> ++}
> ++
> ++struct action_util ctinfo_action_util = {
> ++	.id = "ctinfo",
> ++	.parse_aopt = parse_ctinfo,
> ++	.print_aopt = print_ctinfo,
> ++};
> +-- 
> +2.20.1 (Apple Git-117)
> +
> diff --git a/package/network/utils/iptables/patches/0001-savedscp.patch b/package/network/utils/iptables/patches/0001-savedscp.patch
> new file mode 100644
> index 0000000000..396c8012ec
> --- /dev/null
> +++ b/package/network/utils/iptables/patches/0001-savedscp.patch
> @@ -0,0 +1,213 @@
> +From 69b427fb5e1211c1110ce63be62b70504d04b0ff Mon Sep 17 00:00:00 2001
> +From: Kevin Darbyshire-Bryant <ldir at darbyshire-bryant.me.uk>
> +Date: Sat, 23 Mar 2019 10:21:03 +0000
> +Subject: [PATCH] savedscp
> +
> +Signed-off-by: Kevin Darbyshire-Bryant <ldir at darbyshire-bryant.me.uk>
> +---
> + extensions/libxt_CONNMARK.c           | 68 ++++++++++++++++++++++++++-
> + include/linux/netfilter/xt_connmark.h |  3 +-
> + 2 files changed, 69 insertions(+), 2 deletions(-)
> +
> +diff --git a/extensions/libxt_CONNMARK.c b/extensions/libxt_CONNMARK.c
> +index 21e10913..2dbd7a4a 100644
> +--- a/extensions/libxt_CONNMARK.c
> ++++ b/extensions/libxt_CONNMARK.c
> +@@ -49,6 +49,7 @@ enum {
> + 	O_CTMASK,
> + 	O_NFMASK,
> + 	O_MASK,
> ++	O_SAVEDSCP_MARK,
> + 	F_SET_MARK         = 1 << O_SET_MARK,
> + 	F_SAVE_MARK        = 1 << O_SAVE_MARK,
> + 	F_RESTORE_MARK     = 1 << O_RESTORE_MARK,
> +@@ -61,8 +62,10 @@ enum {
> + 	F_CTMASK           = 1 << O_CTMASK,
> + 	F_NFMASK           = 1 << O_NFMASK,
> + 	F_MASK             = 1 << O_MASK,
> ++	F_SAVEDSCP_MARK	   = 1 << O_SAVEDSCP_MARK,
> + 	F_OP_ANY           = F_SET_MARK | F_SAVE_MARK | F_RESTORE_MARK |
> +-	                     F_AND_MARK | F_OR_MARK | F_XOR_MARK | F_SET_XMARK,
> ++	                     F_AND_MARK | F_OR_MARK | F_XOR_MARK | F_SET_XMARK |
> ++			     F_SAVEDSCP_MARK,
> + };
> + 
> + static const char *const xt_connmark_shift_ops[] = {
> +@@ -75,6 +78,7 @@ static void CONNMARK_help(void)
> + 	printf(
> + "CONNMARK target options:\n"
> + "  --set-mark value[/mask]       Set conntrack mark value\n"
> ++"  --savedscp-mark dscpmask/statemask    Save DSCP to conntrack mark value\n"
> + "  --save-mark [--mask mask]     Save the packet nfmark in the connection\n"
> + "  --restore-mark [--mask mask]  Restore saved nfmark value\n");
> + }
> +@@ -83,6 +87,8 @@ static void CONNMARK_help(void)
> + static const struct xt_option_entry CONNMARK_opts[] = {
> + 	{.name = "set-mark", .id = O_SET_MARK, .type = XTTYPE_MARKMASK32,
> + 	 .excl = F_OP_ANY},
> ++	{.name = "savedscp-mark", .id = O_SAVEDSCP_MARK, .type = XTTYPE_MARKMASK32,
> ++	 .excl = F_OP_ANY},
> + 	{.name = "save-mark", .id = O_SAVE_MARK, .type = XTTYPE_NONE,
> + 	 .excl = F_OP_ANY},
> + 	{.name = "restore-mark", .id = O_RESTORE_MARK, .type = XTTYPE_NONE,
> +@@ -98,6 +104,8 @@ static const struct xt_option_entry connmark_tg_opts[] = {
> + 	 .excl = F_OP_ANY},
> + 	{.name = "set-mark", .id = O_SET_MARK, .type = XTTYPE_MARKMASK32,
> + 	 .excl = F_OP_ANY},
> ++	{.name = "savedscp-mark", .id = O_SAVEDSCP_MARK, .type = XTTYPE_MARKMASK32,
> ++	 .excl = F_OP_ANY},
> + 	{.name = "and-mark", .id = O_AND_MARK, .type = XTTYPE_UINT32,
> + 	 .excl = F_OP_ANY},
> + 	{.name = "or-mark", .id = O_OR_MARK, .type = XTTYPE_UINT32,
> +@@ -124,6 +132,8 @@ static const struct xt_option_entry connmark_tg_opts_v2[] = {
> + 	 .excl = F_OP_ANY},
> + 	{.name = "set-mark", .id = O_SET_MARK, .type = XTTYPE_MARKMASK32,
> + 	 .excl = F_OP_ANY},
> ++	{.name = "savedscp-mark", .id = O_SAVEDSCP_MARK, .type = XTTYPE_MARKMASK32,
> ++	 .excl = F_OP_ANY},
> + 	{.name = "and-mark", .id = O_AND_MARK, .type = XTTYPE_UINT32,
> + 	 .excl = F_OP_ANY},
> + 	{.name = "or-mark", .id = O_OR_MARK, .type = XTTYPE_UINT32,
> +@@ -158,6 +168,7 @@ static void connmark_tg_help(void)
> + "  --restore-mark [--ctmask mask] [--nfmask mask]\n"
> + "                                Copy nfmark to ctmark using masks\n"
> + "  --set-mark value[/mask]       Set conntrack mark value\n"
> ++"  --savedscp-mark value/mask    Save DSCP to conntrack mark value\n"
> + "  --save-mark [--mask mask]     Save the packet nfmark in the connection\n"
> + "  --restore-mark [--mask mask]  Restore saved nfmark value\n"
> + "  --and-mark value              Binary AND the ctmark with bits\n"
> +@@ -210,6 +221,11 @@ static void CONNMARK_parse(struct xt_option_call *cb)
> + 		markinfo->mark = cb->val.mark;
> + 		markinfo->mask = cb->val.mask;
> + 		break;
> ++	case O_SAVEDSCP_MARK:
> ++		markinfo->mode = XT_CONNMARK_SAVEDSCP;
> ++		markinfo->mark = cb->val.mark;
> ++		markinfo->mask = cb->val.mask;
> ++		break;
> + 	case O_SAVE_MARK:
> + 		markinfo->mode = XT_CONNMARK_SAVE;
> + 		break;
> +@@ -238,6 +254,19 @@ static void connmark_tg_parse(struct xt_option_call *cb)
> + 		info->ctmark = cb->val.mark;
> + 		info->ctmask = cb->val.mark | cb->val.mask;
> + 		break;
> ++	case O_SAVEDSCP_MARK:
> ++		info->mode   = XT_CONNMARK_SAVEDSCP;
> ++		info->ctmark = cb->val.mark;
> ++		info->ctmask = cb->val.mask;
> ++		info->nfmask = info->ctmark ? ffs(info->ctmark) - 1 : 0;
> ++		/* need 6 contiguous bits */
> ++		if ((0x3f & (info->ctmark >> info->nfmask)) != 0x3f)
> ++			xtables_error(PARAMETER_PROBLEM,
> ++				"CONNMARK savedscp: insufficient contiguous dscpmask bits");
> ++		if (info->ctmark & info->ctmask)
> ++			xtables_error(PARAMETER_PROBLEM,
> ++				"CONNMARK savedscp: dscpmask/statemask bits overlap");
> ++		break;
> + 	case O_AND_MARK:
> + 		info->mode   = XT_CONNMARK_SET;
> + 		info->ctmark = 0;
> +@@ -283,6 +312,19 @@ static void connmark_tg_parse_v2(struct xt_option_call *cb)
> + 		info->ctmark = cb->val.mark;
> + 		info->ctmask = cb->val.mark | cb->val.mask;
> + 		break;
> ++	case O_SAVEDSCP_MARK:
> ++		info->mode   = XT_CONNMARK_SAVEDSCP;
> ++		info->ctmark = cb->val.mark;
> ++		info->ctmask = cb->val.mask;
> ++		info->shift_bits = info->ctmark ? ffs(info->ctmark) - 1 : 0;
> ++		/* need 6 contiguous bits */
> ++		if ((0x3f & (info->ctmark >> info->shift_bits)) != 0x3f)
> ++			xtables_error(PARAMETER_PROBLEM,
> ++				"CONNMARK savedscp: insufficient contiguous dscpmask bits");
> ++		if (info->ctmark & info->ctmask)
> ++			xtables_error(PARAMETER_PROBLEM,
> ++				"CONNMARK savedscp: dscpmask/statemask bits overlap");
> ++		break;
> + 	case O_AND_MARK:
> + 		info->mode   = XT_CONNMARK_SET;
> + 		info->ctmark = 0;
> +@@ -351,6 +393,11 @@ static void CONNMARK_print(const void *ip,
> + 	    print_mark(markinfo->mark);
> + 	    print_mask("/", markinfo->mask);
> + 	    break;
> ++	case XT_CONNMARK_SAVEDSCP:
> ++	    printf(" CONNMARK savedscp ");
> ++	    print_mark(markinfo->mark);
> ++	    print_mask("/", markinfo->mask);
> ++	    break;
> + 	case XT_CONNMARK_SAVE:
> + 	    printf(" CONNMARK save ");
> + 	    print_mask("mask ", markinfo->mask);
> +@@ -386,6 +433,10 @@ connmark_tg_print(const void *ip, const struct xt_entry_target *target,
> + 			printf(" CONNMARK xset 0x%x/0x%x",
> + 			       info->ctmark, info->ctmask);
> + 		break;
> ++	case XT_CONNMARK_SAVEDSCP:
> ++		printf(" CONNMARK DSCP set 0x%x/0x%x",
> ++		       info->ctmark, info->ctmask);
> ++		break;
> + 	case XT_CONNMARK_SAVE:
> + 		if (info->nfmask == UINT32_MAX && info->ctmask == UINT32_MAX)
> + 			printf(" CONNMARK save");
> +@@ -433,6 +484,10 @@ connmark_tg_print_v2(const void *ip, const struct xt_entry_target *target,
> + 			printf(" CONNMARK xset 0x%x/0x%x",
> + 			       info->ctmark, info->ctmask);
> + 		break;
> ++	case XT_CONNMARK_SAVEDSCP:
> ++		printf(" CONNMARK DSCP xset 0x%x/0x%x",
> ++		       info->ctmark, info->ctmask);
> ++		break;
> + 	case XT_CONNMARK_SAVE:
> + 		if (info->nfmask == UINT32_MAX && info->ctmask == UINT32_MAX)
> + 			printf(" CONNMARK save");
> +@@ -474,6 +529,11 @@ static void CONNMARK_save(const void *ip, const struct xt_entry_target *target)
> + 	    print_mark(markinfo->mark);
> + 	    print_mask("/", markinfo->mask);
> + 	    break;
> ++	case XT_CONNMARK_SAVEDSCP:
> ++	    printf(" --savedscp-mark ");
> ++	    print_mark(markinfo->mark);
> ++	    print_mask("/", markinfo->mask);
> ++	    break;
> + 	case XT_CONNMARK_SAVE:
> + 	    printf(" --save-mark ");
> + 	    print_mask("--mask ", markinfo->mask);
> +@@ -505,6 +565,9 @@ connmark_tg_save(const void *ip, const struct xt_entry_target *target)
> + 	case XT_CONNMARK_SET:
> + 		printf(" --set-xmark 0x%x/0x%x", info->ctmark, info->ctmask);
> + 		break;
> ++	case XT_CONNMARK_SAVEDSCP:
> ++		printf(" --savedscp-mark 0x%x/0x%x", info->ctmark, info->ctmask);
> ++		break;
> + 	case XT_CONNMARK_SAVE:
> + 		printf(" --save-mark --nfmask 0x%x --ctmask 0x%x",
> + 		       info->nfmask, info->ctmask);
> +@@ -529,6 +592,9 @@ connmark_tg_save_v2(const void *ip, const struct xt_entry_target *target)
> + 	case XT_CONNMARK_SET:
> + 		printf(" --set-xmark 0x%x/0x%x", info->ctmark, info->ctmask);
> + 		break;
> ++	case XT_CONNMARK_SAVEDSCP:
> ++		printf(" --savedscp-mark 0x%x/0x%x", info->ctmark, info->ctmask);
> ++		break;
> + 	case XT_CONNMARK_SAVE:
> + 		printf(" --save-mark --nfmask 0x%x --ctmask 0x%x",
> + 		       info->nfmask, info->ctmask);
> +diff --git a/include/linux/netfilter/xt_connmark.h b/include/linux/netfilter/xt_connmark.h
> +index bbf2acc9..cf526101 100644
> +--- a/include/linux/netfilter/xt_connmark.h
> ++++ b/include/linux/netfilter/xt_connmark.h
> +@@ -15,7 +15,8 @@
> + enum {
> + 	XT_CONNMARK_SET = 0,
> + 	XT_CONNMARK_SAVE,
> +-	XT_CONNMARK_RESTORE
> ++	XT_CONNMARK_RESTORE,
> ++	XT_CONNMARK_SAVEDSCP
> + };
> + 
> + struct xt_connmark_tginfo1 {
> +-- 
> +2.20.1 (Apple Git-117)
> +
> diff --git a/target/linux/generic/hack-4.14/0001-net-sched-Introduce-act_ctinfo-action.patch b/target/linux/generic/hack-4.14/0001-net-sched-Introduce-act_ctinfo-action.patch
> new file mode 100644
> index 0000000000..7ff26df7d1
> --- /dev/null
> +++ b/target/linux/generic/hack-4.14/0001-net-sched-Introduce-act_ctinfo-action.patch
> @@ -0,0 +1,636 @@
> +From 82f7c4ec33323a371a97a5d68b4ccfd7bd62e087 Mon Sep 17 00:00:00 2001
> +From: Kevin Darbyshire-Bryant <ldir at darbyshire-bryant.me.uk>
> +Date: Wed, 13 Mar 2019 20:54:49 +0000
> +Subject: [PATCH] net: sched: Introduce act_ctinfo action
> +
> +ctinfo is a new tc filter action module.  It is designed to restore DSCPs
> +stored in conntrack marks
> +
> +The feature is intended for use and has been found useful for restoring
> +ingress classifications based on egress classifications across links
> +that bleach or otherwise change DSCP, typically home ISP Internet links.
> +Restoring DSCP on ingress on the WAN link allows qdiscs such as CAKE to
> +shape inbound packets according to policies that are easier to implement
> +on egress.
> +
> +Ingress classification is traditionally a challenging task since
> +iptables rules haven't yet run and tc filter/eBPF programs are pre-NAT
> +lookups, hence are unable to see internal IPv4 addresses as used on the
> +typical home masquerading gateway.
> +
> +ctinfo understands the following parameters:
> +
> +dscp mask[/statemask]
> +
> +mask - a 32 bit mask of at least 6 contiguous bits where conndscp will
> +place the DSCP in conntrack mark.  The DSCP is left-shifted by the
> +number of unset lower bits of the mask before storing into the mark
> +field.
> +
> +statemask - a 32 bit mask of (usually) 1 bit length, outside the area
> +specified by mask.  This represents a conditional operation flag the
> +DSCP is only restored if the flag is set.  This is useful to implement a
> +'one shot' iptables based classification where the 'complicated'
> +iptables rules are only run once to classify the connection on initial
> +(egress) packet and subsequent packets are all marked/restored with the
> +same DSCP.  A mask of zero disables the conditional behaviour.
> +
> +optional parameters:
> +
> +zone - conntrack zone
> +
> +control - action related control (reclassify | pipe | drop | continue |
> +ok | goto chain <CHAIN_INDEX>
> +
> +Signed-off-by: Kevin Darbyshire-Bryant <ldir at darbyshire-bryant.me.uk>
> +
> +net:sched - v3 tweak license
> +
> +Signed-off-by: Kevin Darbyshire-Bryant <ldir at darbyshire-bryant.me.uk>
> +
> +drop inline & sort reverse christmas tree
> +
> +Signed-off-by: Kevin Darbyshire-Bryant <ldir at darbyshire-bryant.me.uk>
> +
> +v5 stuff
> +
> +Signed-off-by: Kevin Darbyshire-Bryant <ldir at darbyshire-bryant.me.uk>
> +---
> + include/net/tc_act/tc_ctinfo.h        |  28 ++
> + include/uapi/linux/pkt_cls.h          |   3 +-
> + include/uapi/linux/tc_act/tc_ctinfo.h |  52 ++++
> + net/sched/Kconfig                     |  13 +
> + net/sched/Makefile                    |   1 +
> + net/sched/act_ctinfo.c                | 416 ++++++++++++++++++++++++++
> + 6 files changed, 512 insertions(+), 1 deletion(-)
> + create mode 100644 include/net/tc_act/tc_ctinfo.h
> + create mode 100644 include/uapi/linux/tc_act/tc_ctinfo.h
> + create mode 100644 net/sched/act_ctinfo.c
> +
> +diff --git a/include/net/tc_act/tc_ctinfo.h b/include/net/tc_act/tc_ctinfo.h
> +new file mode 100644
> +index 000000000000..d6a688571672
> +--- /dev/null
> ++++ b/include/net/tc_act/tc_ctinfo.h
> +@@ -0,0 +1,28 @@
> ++/* SPDX-License-Identifier: GPL-2.0 */
> ++#ifndef __NET_TC_CTINFO_H
> ++#define __NET_TC_CTINFO_H
> ++
> ++#include <net/act_api.h>
> ++
> ++struct tcf_ctinfo_params {
> ++	struct rcu_head rcu;
> ++	struct net *net;
> ++	u32 dscpmask;
> ++	u32 dscpstatemask;
> ++	u32 cpmarkmask;
> ++	u16 zone;
> ++	u8 mode;
> ++	u8 dscpmaskshift;
> ++};
> ++
> ++struct tcf_ctinfo {
> ++	struct tc_action common;
> ++	struct tcf_ctinfo_params __rcu *params;
> ++	u64 stats_dscp_set;
> ++	u64 stats_dscp_error;
> ++	u64 stats_cpmark_set;
> ++};
> ++
> ++#define to_ctinfo(a) ((struct tcf_ctinfo *)a)
> ++
> ++#endif /* __NET_TC_CTINFO_H */
> +diff --git a/include/uapi/linux/pkt_cls.h b/include/uapi/linux/pkt_cls.h
> +index 46c506615f4a..408b02fbb34a 100644
> +--- a/include/uapi/linux/pkt_cls.h
> ++++ b/include/uapi/linux/pkt_cls.h
> +@@ -66,7 +66,8 @@ enum {
> + 	TCA_ID_UNSPEC=0,
> + 	TCA_ID_POLICE=1,
> + 	/* other actions go here */
> +-	__TCA_ID_MAX=255
> ++	TCA_ID_CTINFO=27,
> ++	__TCA_ID_MAX = 255
> + };
> + 
> + #define TCA_ID_MAX __TCA_ID_MAX
> +diff --git a/include/uapi/linux/tc_act/tc_ctinfo.h b/include/uapi/linux/tc_act/tc_ctinfo.h
> +new file mode 100644
> +index 000000000000..48c40f657575
> +--- /dev/null
> ++++ b/include/uapi/linux/tc_act/tc_ctinfo.h
> +@@ -0,0 +1,52 @@
> ++/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
> ++#ifndef __UAPI_TC_CTINFO_H
> ++#define __UAPI_TC_CTINFO_H
> ++
> ++#include <linux/types.h>
> ++#include <linux/pkt_cls.h>
> ++
> ++struct tc_ctinfo {
> ++	tc_gen;
> ++};
> ++
> ++struct tc_ctinfo_dscp {
> ++	__u32 mask;
> ++	__u32 statemask;
> ++};
> ++
> ++struct tc_ctinfo_cpmark {
> ++	__u32 mask;
> ++};
> ++
> ++struct tc_ctinfo_stats_dscp {
> ++	__u64 set;
> ++	__u64 error;
> ++};
> ++
> ++struct tc_ctinfo_stats_cpmark {
> ++	__u64 set;
> ++};
> ++
> ++enum {
> ++	TCA_CTINFO_UNSPEC,
> ++	TCA_CTINFO_PAD,
> ++	TCA_CTINFO_TM,
> ++	TCA_CTINFO_ACT,
> ++	TCA_CTINFO_ZONE,
> ++	TCA_CTINFO_PARMS_DSCP,
> ++	TCA_CTINFO_PARMS_CPMARK,
> ++	TCA_CTINFO_MODE_DSCP,
> ++	TCA_CTINFO_MODE_CPMARK,
> ++	TCA_CTINFO_STATS_DSCP,
> ++	TCA_CTINFO_STATS_CPMARK,
> ++	__TCA_CTINFO_MAX
> ++};
> ++
> ++#define TCA_CTINFO_MAX (__TCA_CTINFO_MAX - 1)
> ++
> ++enum {
> ++	CTINFO_MODE_DSCP	= BIT(0),
> ++	CTINFO_MODE_CPMARK	= BIT(1)
> ++};
> ++
> ++#endif
> +diff --git a/net/sched/Kconfig b/net/sched/Kconfig
> +index e70ed26485a2..962d90f72f54 100644
> +--- a/net/sched/Kconfig
> ++++ b/net/sched/Kconfig
> +@@ -808,6 +808,19 @@ config NET_ACT_CONNMARK
> + 	  To compile this code as a module, choose M here: the
> + 	  module will be called act_connmark.
> + 
> ++config NET_ACT_CTINFO
> ++        tristate "Netfilter Connmark to DSCP Retriever"
> ++        depends on NET_CLS_ACT && NETFILTER && IP_NF_IPTABLES
> ++        depends on NF_CONNTRACK && NF_CONNTRACK_MARK
> ++        help
> ++	  Say Y here to allow transfer of a connmark stored DSCP into
> ++	  ipv4/v6 diffserv
> ++
> ++	  If unsure, say N.
> ++
> ++	  To compile this code as a module, choose M here: the
> ++	  module will be called act_ctinfo.
> ++
> + config NET_ACT_SKBMOD
> +         tristate "skb data modification action"
> +         depends on NET_CLS_ACT
> +diff --git a/net/sched/Makefile b/net/sched/Makefile
> +index 9e43a4721ef8..44ee5b87b895 100644
> +--- a/net/sched/Makefile
> ++++ b/net/sched/Makefile
> +@@ -21,6 +21,7 @@ obj-$(CONFIG_NET_ACT_CSUM)	+= act_csum.o
> + obj-$(CONFIG_NET_ACT_VLAN)	+= act_vlan.o
> + obj-$(CONFIG_NET_ACT_BPF)	+= act_bpf.o
> + obj-$(CONFIG_NET_ACT_CONNMARK)	+= act_connmark.o
> ++obj-$(CONFIG_NET_ACT_CTINFO)	+= act_ctinfo.o
> + obj-$(CONFIG_NET_ACT_SKBMOD)	+= act_skbmod.o
> + obj-$(CONFIG_NET_ACT_IFE)	+= act_ife.o
> + obj-$(CONFIG_NET_IFE_SKBMARK)	+= act_meta_mark.o
> +diff --git a/net/sched/act_ctinfo.c b/net/sched/act_ctinfo.c
> +new file mode 100644
> +index 000000000000..8150ba2e3867
> +--- /dev/null
> ++++ b/net/sched/act_ctinfo.c
> +@@ -0,0 +1,416 @@
> ++// SPDX-License-Identifier: GPL-2.0+
> ++/* net/sched/act_ctinfo.c  netfilter ctinfo connmark actions
> ++ *
> ++ * Copyright (c) 2019 Kevin Darbyshire-Bryant <ldir at darbyshire-bryant.me.uk>
> ++ */
> ++
> ++#include <linux/module.h>
> ++#include <linux/init.h>
> ++#include <linux/kernel.h>
> ++#include <linux/skbuff.h>
> ++#include <linux/rtnetlink.h>
> ++#include <linux/pkt_cls.h>
> ++#include <linux/ip.h>
> ++#include <linux/ipv6.h>
> ++#include <net/netlink.h>
> ++#include <net/pkt_sched.h>
> ++#include <net/act_api.h>
> ++#include <net/pkt_cls.h>
> ++#include <uapi/linux/tc_act/tc_ctinfo.h>
> ++#include <net/tc_act/tc_ctinfo.h>
> ++
> ++#include <net/netfilter/nf_conntrack.h>
> ++#include <net/netfilter/nf_conntrack_core.h>
> ++#include <net/netfilter/nf_conntrack_ecache.h>
> ++#include <net/netfilter/nf_conntrack_zones.h>
> ++
> ++static struct tc_action_ops act_ctinfo_ops;
> ++static unsigned int ctinfo_net_id;
> ++
> ++static void tcf_ctinfo_dscp_set(struct nf_conn *ct, struct tcf_ctinfo *ca,
> ++				struct tcf_ctinfo_params *cp,
> ++				struct sk_buff *skb, int wlen, int proto)
> ++{
> ++	u8 dscp,newdscp;
> ++
> ++	newdscp = (((ct->mark & cp->dscpmask) >> cp->dscpmaskshift) << 2) &
> ++		     ~INET_ECN_MASK;
> ++
> ++	/* mark contains DSCP so restore DSCP bits from ct->mark into diffserv */
> ++	/* using overlimits stats to count how many DSCP updates	       */
> ++	switch (proto) {
> ++	case NFPROTO_IPV4:
> ++		dscp = ipv4_get_dsfield(ip_hdr(skb)) & ~INET_ECN_MASK;
> ++		if (dscp != newdscp) {
> ++			if (likely(!skb_try_make_writable(skb, wlen))) {
> ++				ipv4_change_dsfield(ip_hdr(skb),
> ++						    INET_ECN_MASK,
> ++						    newdscp);
> ++				ca->stats_dscp_set++;
> ++			} else {
> ++				ca->stats_dscp_error++;
> ++			}
> ++		}
> ++		break;
> ++	case NFPROTO_IPV6:
> ++		dscp = ipv6_get_dsfield(ipv6_hdr(skb)) & ~INET_ECN_MASK;
> ++		if (dscp != newdscp) {
> ++			if (likely(!skb_try_make_writable(skb, wlen))) {
> ++				ipv6_change_dsfield(ipv6_hdr(skb),
> ++						    INET_ECN_MASK,
> ++						    newdscp);
> ++				ca->stats_dscp_set++;
> ++			} else {
> ++				ca->stats_dscp_error++;
> ++			}
> ++		}
> ++		break;
> ++	default:
> ++		break;
> ++	}
> ++}
> ++
> ++static void tcf_ctinfo_cpmark_set(struct nf_conn *ct, struct tcf_ctinfo *ca,
> ++				struct tcf_ctinfo_params *cp,
> ++				struct sk_buff *skb)
> ++{
> ++	ca->stats_cpmark_set++;
> ++	skb->mark = ct->mark & cp->cpmarkmask;
> ++}
> ++
> ++static int tcf_ctinfo_act(struct sk_buff *skb, const struct tc_action *a,
> ++			  struct tcf_result *res)
> ++{
> ++	const struct nf_conntrack_tuple_hash *thash = NULL;
> ++	struct tcf_ctinfo *ca = to_ctinfo(a);
> ++	struct nf_conntrack_tuple tuple;
> ++	struct nf_conntrack_zone zone;
> ++	enum ip_conntrack_info ctinfo;
> ++	struct tcf_ctinfo_params *cp;
> ++	struct nf_conn *ct;
> ++	int proto, wlen;
> ++	int action;
> ++
> ++	cp = rcu_dereference_bh(ca->params);
> ++
> ++	tcf_lastuse_update(&ca->tcf_tm);
> ++	bstats_update(&ca->tcf_bstats, skb);
> ++	action = READ_ONCE(ca->tcf_action);
> ++
> ++	wlen = skb_network_offset(skb);
> ++	if (tc_skb_protocol(skb) == htons(ETH_P_IP)) {
> ++		wlen += sizeof(struct iphdr);
> ++		if (!pskb_may_pull(skb, wlen))
> ++			goto out;
> ++
> ++		proto = NFPROTO_IPV4;
> ++	} else if (tc_skb_protocol(skb) == htons(ETH_P_IPV6)) {
> ++		wlen += sizeof(struct ipv6hdr);
> ++		if (!pskb_may_pull(skb, wlen))
> ++			goto out;
> ++
> ++		proto = NFPROTO_IPV6;
> ++	} else {
> ++		goto out;
> ++	}
> ++
> ++	ct = nf_ct_get(skb, &ctinfo);
> ++	if (!ct) { /* look harder, usually ingress */
> ++		if (!nf_ct_get_tuplepr(skb, skb_network_offset(skb),
> ++				       proto, cp->net, &tuple))
> ++			goto out;
> ++		zone.id = cp->zone;
> ++		zone.dir = NF_CT_DEFAULT_ZONE_DIR;
> ++
> ++		thash = nf_conntrack_find_get(cp->net, &zone, &tuple);
> ++		if (!thash)
> ++			goto out;
> ++
> ++		ct = nf_ct_tuplehash_to_ctrack(thash);
> ++	}
> ++
> ++	if (cp->mode & CTINFO_MODE_DSCP)
> ++		if (!cp->dscpstatemask || (ct->mark & cp->dscpstatemask))
> ++			tcf_ctinfo_dscp_set(ct, ca, cp, skb, wlen, proto);
> ++
> ++	if (cp->mode & CTINFO_MODE_CPMARK)
> ++		tcf_ctinfo_cpmark_set(ct, ca, cp, skb);
> ++
> ++	if (thash)
> ++		nf_ct_put(ct);
> ++out:
> ++	return action;
> ++}
> ++
> ++static const struct nla_policy ctinfo_policy[TCA_CTINFO_MAX + 1] = {
> ++	[TCA_CTINFO_ACT]	  = { .len = sizeof(struct tc_ctinfo) },
> ++	[TCA_CTINFO_ZONE]	  = { .type = NLA_U16 },
> ++	[TCA_CTINFO_PARMS_DSCP]   = { .len = sizeof(struct tc_ctinfo_dscp) },
> ++	[TCA_CTINFO_PARMS_CPMARK] = { .len = sizeof(struct tc_ctinfo_cpmark) },
> ++	[TCA_CTINFO_MODE_DSCP]    = { .type = NLA_FLAG },
> ++	[TCA_CTINFO_MODE_CPMARK]  = { .type = NLA_FLAG },
> ++};
> ++
> ++static int tcf_ctinfo_init(struct net *net, struct nlattr *nla,
> ++			     struct nlattr *est, struct tc_action **a,
> ++			     int ovr, int bind)
> ++{
> ++	struct tc_action_net *tn = net_generic(net, ctinfo_net_id);
> ++	struct tc_ctinfo_cpmark *cpmarkparm = NULL;
> ++	struct nlattr *tb[TCA_CTINFO_MAX + 1];
> ++	struct tcf_ctinfo_params *cp_new;
> ++/*	struct tcf_chain *goto_ch = NULL; */
> ++	struct tc_ctinfo_dscp *dscpparm;
> ++	struct tcf_ctinfo *ci;
> ++	struct tc_ctinfo *actparm;
> ++	int ret = 0, err, i;
> ++
> ++	if (!nla)
> ++		return -EINVAL;
> ++
> ++	err = nla_parse_nested(tb, TCA_CTINFO_MAX, nla, ctinfo_policy, NULL);
> ++	if (err < 0)
> ++		return err;
> ++
> ++	if (!tb[TCA_CTINFO_ACT])
> ++		return -EINVAL;
> ++
> ++	if (tb[TCA_CTINFO_MODE_DSCP] && !tb[TCA_CTINFO_PARMS_DSCP])
> ++		return -EINVAL;
> ++
> ++	if (tb[TCA_CTINFO_PARMS_CPMARK])
> ++		cpmarkparm = nla_data(tb[TCA_CTINFO_PARMS_CPMARK]);
> ++
> ++	dscpparm = nla_data(tb[TCA_CTINFO_PARMS_DSCP]);
> ++	actparm = nla_data(tb[TCA_CTINFO_ACT]);
> ++
> ++	if (dscpparm) {
> ++		/* need contiguous 6 bit mask */
> ++		i = dscpparm->mask ? __ffs(dscpparm->mask) : 0;
> ++		if ((~0 & (dscpparm->mask >> i)) != 0x3f)
> ++			return -EINVAL;
> ++		/* mask & statemask must not overlap */
> ++		if (dscpparm->mask & dscpparm->statemask)
> ++			return -EINVAL;
> ++	}
> ++	/* done the validation:now to the actual action allocation */
> ++	err = tcf_idr_check(tn, actparm->index, a, bind);
> ++	if (!err) {
> ++		ret = tcf_idr_create(tn, actparm->index, est, a,
> ++				     &act_ctinfo_ops, bind, false);
> ++		if (ret) {
> ++		/*	tcf_idr_cleanup(tn, actparm->index); */
> ++			return ret;
> ++		}
> ++		ret = ACT_P_CREATED;
> ++	} else if (err > 0) {
> ++		if (bind) /* don't override defaults */
> ++			return 0;
> ++		if (!ovr) {
> ++			tcf_idr_release(*a, bind);
> ++			return -EEXIST;
> ++		}
> ++	} else {
> ++		return err;
> ++	}
> ++
> ++/*	err = tcf_action_check_ctrlact(actparm->action, tp, &goto_ch, extack);
> ++	if (err < 0)
> ++		goto release_idr;
> ++		*/
> ++
> ++	ci = to_ctinfo(*a);
> ++
> ++	cp_new = kzalloc(sizeof(*cp_new), GFP_KERNEL);
> ++	if (unlikely(!cp_new)) {
> ++		err = -ENOMEM;
> ++		goto put_chain;
> ++	}
> ++
> ++	cp_new->net = net;
> ++	cp_new->zone = tb[TCA_CTINFO_ZONE] ? nla_get_u16(tb[TCA_CTINFO_ZONE]) : 0;
> ++	if (dscpparm) {
> ++		cp_new->dscpmask = dscpparm->mask;
> ++		cp_new->dscpmaskshift = cp_new->dscpmask ? __ffs(cp_new->dscpmask) : 0;
> ++		cp_new->dscpstatemask = dscpparm->statemask;
> ++	}
> ++
> ++	if (cpmarkparm)
> ++		cp_new->cpmarkmask = cpmarkparm->mask;
> ++	else
> ++		cp_new->cpmarkmask = ~0;
> ++
> ++	if (tb[TCA_CTINFO_MODE_DSCP])
> ++		cp_new->mode |= CTINFO_MODE_DSCP;
> ++	else
> ++		cp_new->mode &= ~CTINFO_MODE_DSCP;
> ++
> ++	if (tb[TCA_CTINFO_MODE_CPMARK])
> ++		cp_new->mode |= CTINFO_MODE_CPMARK;
> ++	else
> ++		cp_new->mode &= ~CTINFO_MODE_CPMARK;
> ++
> ++	spin_lock_bh(&ci->tcf_lock);
> ++/*	goto_ch = tcf_action_set_ctrlact(*a, actparm->action, goto_ch); */
> ++	ci->tcf_action = actparm->action;
> ++	rcu_swap_protected(ci->params, cp_new,
> ++				lockdep_is_held(&ci->tcf_lock));
> ++	spin_unlock_bh(&ci->tcf_lock);
> ++
> ++/*	if (goto_ch
> ++		tcf_chain_put_by_act(goto_ch); */
> ++	if (cp_new)
> ++		kfree_rcu(cp_new, rcu);
> ++
> ++	if (ret == ACT_P_CREATED)
> ++		tcf_idr_insert(tn, *a);
> ++
> ++	return ret;
> ++
> ++put_chain:
> ++/*	if (goto_ch)
> ++		tcf_chain_put_by_act(goto_ch); */
> ++/*release_idr:*/
> ++	tcf_idr_release(*a, bind);
> ++	return err;
> ++}
> ++
> ++static int tcf_ctinfo_dump(struct sk_buff *skb, struct tc_action *a,
> ++			   int bind, int ref)
> ++{
> ++	struct tcf_ctinfo *ci = to_ctinfo(a);
> ++	struct tc_ctinfo opt = {
> ++		.index   = ci->tcf_index,
> ++		.refcnt  = ci->tcf_refcnt - ref,
> ++		.bindcnt = ci->tcf_bindcnt - bind,
> ++	};
> ++	struct tc_ctinfo_stats_cpmark cpmarkstats;
> ++	unsigned char *b = skb_tail_pointer(skb);
> ++	struct tc_ctinfo_stats_dscp dscpstats;
> ++	struct tc_ctinfo_cpmark cpmarkparm;
> ++	struct tc_ctinfo_dscp dscpparm;
> ++	struct tcf_ctinfo_params *cp;
> ++	struct tcf_t t;
> ++
> ++	spin_lock_bh(&ci->tcf_lock);
> ++	cp = rcu_dereference_protected(ci->params,
> ++				       lockdep_is_held(&ci->tcf_lock));
> ++	opt.action = ci->tcf_action;
> ++
> ++	if (nla_put(skb, TCA_CTINFO_ACT, sizeof(opt), &opt))
> ++		goto nla_put_failure;
> ++
> ++	if (cp->mode & CTINFO_MODE_DSCP) {
> ++		dscpparm.mask = cp->dscpmask;
> ++		dscpparm.statemask = cp->dscpstatemask;
> ++		if (nla_put(skb, TCA_CTINFO_PARMS_DSCP, sizeof(dscpparm),
> ++			    &dscpparm))
> ++			goto nla_put_failure;
> ++
> ++		if (nla_put_flag(skb, TCA_CTINFO_MODE_DSCP))
> ++			goto nla_put_failure;
> ++
> ++		dscpstats.error = ci->stats_dscp_error;
> ++		dscpstats.set = ci->stats_dscp_set;
> ++		if (nla_put(skb, TCA_CTINFO_STATS_DSCP, sizeof(dscpstats),
> ++			    &dscpstats))
> ++			goto nla_put_failure;
> ++	}
> ++
> ++	if (cp->mode & CTINFO_MODE_CPMARK) {
> ++		cpmarkparm.mask = cp->cpmarkmask;
> ++		if (nla_put(skb, TCA_CTINFO_PARMS_CPMARK, sizeof(cpmarkparm),
> ++			    &cpmarkparm))
> ++			goto nla_put_failure;
> ++
> ++		if (nla_put_flag(skb, TCA_CTINFO_MODE_CPMARK))
> ++			goto nla_put_failure;
> ++
> ++		cpmarkstats.set = ci->stats_cpmark_set;
> ++		if (nla_put_u64_64bit(skb, TCA_CTINFO_STATS_CPMARK,
> ++				      ci->stats_cpmark_set, TCA_CTINFO_PAD))
> ++			goto nla_put_failure;
> ++	}
> ++
> ++	if (cp->zone) {
> ++		if (nla_put_u16(skb, TCA_CTINFO_ZONE, cp->zone))
> ++			goto nla_put_failure;
> ++	}
> ++
> ++	tcf_tm_dump(&t, &ci->tcf_tm);
> ++	if (nla_put_64bit(skb, TCA_CTINFO_TM, sizeof(t), &t, TCA_CTINFO_PAD))
> ++		goto nla_put_failure;
> ++
> ++	spin_unlock_bh(&ci->tcf_lock);
> ++	return skb->len;
> ++
> ++nla_put_failure:
> ++	spin_unlock_bh(&ci->tcf_lock);
> ++	nlmsg_trim(skb, b);
> ++	return -1;
> ++}
> ++
> ++static int tcf_ctinfo_walker(struct net *net, struct sk_buff *skb,
> ++			       struct netlink_callback *cb, int type,
> ++			       const struct tc_action_ops *ops)
> ++{
> ++	struct tc_action_net *tn = net_generic(net, ctinfo_net_id);
> ++
> ++	return tcf_generic_walker(tn, skb, cb, type, ops);
> ++}
> ++
> ++static int tcf_ctinfo_search(struct net *net, struct tc_action **a, u32 index)
> ++{
> ++	struct tc_action_net *tn = net_generic(net, ctinfo_net_id);
> ++
> ++	return tcf_idr_search(tn, a, index);
> ++}
> ++
> ++static struct tc_action_ops act_ctinfo_ops = {
> ++	.kind	= "ctinfo",
> ++	.type	= TCA_ID_CTINFO,
> ++	.owner	= THIS_MODULE,
> ++	.act	= tcf_ctinfo_act,
> ++	.dump	= tcf_ctinfo_dump,
> ++	.init	= tcf_ctinfo_init,
> ++	.walk	= tcf_ctinfo_walker,
> ++	.lookup	= tcf_ctinfo_search,
> ++	.size	= sizeof(struct tcf_ctinfo),
> ++};
> ++
> ++static __net_init int ctinfo_init_net(struct net *net)
> ++{
> ++	struct tc_action_net *tn = net_generic(net, ctinfo_net_id);
> ++
> ++	return tc_action_net_init(tn, &act_ctinfo_ops);
> ++}
> ++
> ++static void __net_exit ctinfo_exit_net(struct net *net)
> ++{
> ++	struct tc_action_net *tn = net_generic(net, ctinfo_net_id);
> ++
> ++	tc_action_net_exit(tn);
> ++}
> ++
> ++static struct pernet_operations ctinfo_net_ops = {
> ++	.init = ctinfo_init_net,
> ++	.exit = ctinfo_exit_net,
> ++	.id   = &ctinfo_net_id,
> ++	.size = sizeof(struct tc_action_net),
> ++};
> ++
> ++static int __init ctinfo_init_module(void)
> ++{
> ++	return tcf_register_action(&act_ctinfo_ops, &ctinfo_net_ops);
> ++}
> ++
> ++static void __exit ctinfo_cleanup_module(void)
> ++{
> ++	tcf_unregister_action(&act_ctinfo_ops, &ctinfo_net_ops);
> ++}
> ++
> ++module_init(ctinfo_init_module);
> ++module_exit(ctinfo_cleanup_module);
> ++MODULE_AUTHOR("Kevin Darbyshire-Bryant <ldir at darbyshire-bryant.me.uk>");
> ++MODULE_DESCRIPTION("Conntrack mark to DSCP restoring");
> ++MODULE_LICENSE("GPL");
> +-- 
> +2.20.1 (Apple Git-117)
> +
> diff --git a/target/linux/generic/hack-4.14/0001-netfilter-connmark-introduce-savedscp.patch b/target/linux/generic/hack-4.14/0001-netfilter-connmark-introduce-savedscp.patch
> new file mode 100644
> index 0000000000..4124793bce
> --- /dev/null
> +++ b/target/linux/generic/hack-4.14/0001-netfilter-connmark-introduce-savedscp.patch
> @@ -0,0 +1,107 @@
> +From 5a4d7714faa28c03e85d696fba82716fbda5c432 Mon Sep 17 00:00:00 2001
> +From: Kevin Darbyshire-Bryant <ldir at darbyshire-bryant.me.uk>
> +Date: Sat, 23 Mar 2019 09:29:49 +0000
> +Subject: [PATCH] netfilter: connmark: introduce savedscp
> +
> +savedscp is a method of storing the DSCP of an ip packet into conntrack
> +mark.  In combination with a suitable tc filter action (conndscp but may
> +end up being integrated into connmark) DSCP values are able to be stored
> +on egress and restored on ingress across links that otherwise alter or
> +bleach DSCP.
> +
> +This is useful for qdiscs such as CAKE which are able to shape according
> +to policies based on DSCP.
> +
> +Ingress classification is traditionally a challenging task since
> +iptables rules haven't yet run and tc filter/eBPF programs are pre-NAT
> +lookups, hence are unable to see internal IPv4 addresses as used on the
> +typical home masquerading gateway.
> +
> +The ingress problem is solved by the tc filter, but the tc people didn't
> +like the idea of tc setting conntrack mark values, though they are ok
> +with reading conntrack values and hence restoring DSCP from conntrack
> +marks.
> +
> +x_tables CONNMARK with the new savedscp action solves the problem of
> +storing the DSCP to the conntrack mark.
> +
> +It accepts 2 parameters.  The mark is a 32bit value with usually one 1
> +bit set.  This bit is set when savedscp saves the DSCP to the mark.
> +This is useful to implement a 'one shot'
> +iptables based classification where the 'complicated' iptables rules are
> +only run once to classify the connection on initial (egress) packet and
> +subsequent packets are all marked/restored with the same DSCP.  A mark
> +of zero disables the setting of a status bit/s.
> +
> +The mask is a 32bit value of at least 6 contiguous bits and represents
> +the area where the DSCP will be stored.
> +
> +e.g.
> +
> +iptables -A QOS_MARK_eth0 -t mangle -j CONNMARK --savedscp-mark 0xfc000000/0x01000000
> +
> +Would store the DSCP in the top 6 bits of the 32bit mark field, and use
> +the LSB of the top byte as the 'DSCP has been stored' marker.
> +
> +Signed-off-by: Kevin Darbyshire-Bryant <ldir at darbyshire-bryant.me.uk>
> +---
> + include/uapi/linux/netfilter/xt_connmark.h |  3 ++-
> + net/netfilter/xt_connmark.c                | 21 ++++++++++++++++++++-
> + 2 files changed, 22 insertions(+), 2 deletions(-)
> +
> +diff --git a/include/uapi/linux/netfilter/xt_connmark.h b/include/uapi/linux/netfilter/xt_connmark.h
> +index 408a9654f05c..e63ad3c89b92 100644
> +--- a/include/uapi/linux/netfilter/xt_connmark.h
> ++++ b/include/uapi/linux/netfilter/xt_connmark.h
> +@@ -16,7 +16,8 @@
> + enum {
> + 	XT_CONNMARK_SET = 0,
> + 	XT_CONNMARK_SAVE,
> +-	XT_CONNMARK_RESTORE
> ++	XT_CONNMARK_RESTORE,
> ++	XT_CONNMARK_SAVEDSCP
> + };
> + 
> + struct xt_connmark_tginfo1 {
> +diff --git a/net/netfilter/xt_connmark.c b/net/netfilter/xt_connmark.c
> +index ec377cc6a369..aa5bcea8f9ba 100644
> +--- a/net/netfilter/xt_connmark.c
> ++++ b/net/netfilter/xt_connmark.c
> +@@ -42,6 +42,7 @@ connmark_tg(struct sk_buff *skb, const struct xt_action_param *par)
> + 	enum ip_conntrack_info ctinfo;
> + 	struct nf_conn *ct;
> + 	u_int32_t newmark;
> ++	u8 dscp, maskshift;
> + 
> + 	ct = nf_ct_get(skb, &ctinfo);
> + 	if (ct == NULL)
> +@@ -57,7 +58,25 @@ connmark_tg(struct sk_buff *skb, const struct xt_action_param *par)
> + 		break;
> + 	case XT_CONNMARK_SAVE:
> + 		newmark = (ct->mark & ~info->ctmask) ^
> +-		          (skb->mark & info->nfmask);
> ++			  (skb->mark & info->nfmask);
> ++		if (ct->mark != newmark) {
> ++			ct->mark = newmark;
> ++			nf_conntrack_event_cache(IPCT_MARK, ct);
> ++		}
> ++		break;
> ++	case XT_CONNMARK_SAVEDSCP:
> ++		if (skb->protocol == htons(ETH_P_IP))
> ++			dscp = ipv4_get_dsfield(ip_hdr(skb)) >> 2;
> ++		else if (skb->protocol == htons(ETH_P_IPV6))
> ++			dscp = ipv6_get_dsfield(ipv6_hdr(skb)) >> 2;
> ++		else	/* protocol doesn't have diffserv */
> ++			break;
> ++
> ++		/* nfmask contains the mask shift value */
> ++		maskshift = info->nfmask & 0x1f;
> ++		newmark = (ct->mark & ~info->ctmark) ^
> ++			  (info->ctmask | (dscp << maskshift));
> ++
> + 		if (ct->mark != newmark) {
> + 			ct->mark = newmark;
> + 			nf_conntrack_event_cache(IPCT_MARK, ct);
> +-- 
> +2.20.1 (Apple Git-117)
> +
> diff --git a/target/linux/generic/hack-4.19/0001-net-sched-Introduce-act_ctinfo-action.patch b/target/linux/generic/hack-4.19/0001-net-sched-Introduce-act_ctinfo-action.patch
> new file mode 100644
> index 0000000000..4b4c75330d
> --- /dev/null
> +++ b/target/linux/generic/hack-4.19/0001-net-sched-Introduce-act_ctinfo-action.patch
> @@ -0,0 +1,658 @@
> +From e2448527ccb52ee173fb7fe3b35dc2f88281f4cc Mon Sep 17 00:00:00 2001
> +From: Kevin Darbyshire-Bryant <ldir at darbyshire-bryant.me.uk>
> +Date: Wed, 13 Mar 2019 20:54:49 +0000
> +Subject: [PATCH] net: sched: Introduce act_ctinfo action
> +
> +ctinfo is a new tc filter action module.  It is designed to restore DSCPs
> +stored in conntrack marks into the ipv4/v6 diffserv field.
> +
> +The feature is intended for use and has been found useful for restoring
> +ingress classifications based on egress classifications across links
> +that bleach or otherwise change DSCP, typically home ISP Internet links.
> +Restoring DSCP on ingress on the WAN link allows qdiscs such as CAKE to
> +shape inbound packets according to policies that are easier to indicate
> +on egress.
> +
> +Ingress classification is traditionally a challenging task since
> +iptables rules haven't yet run and tc filter/eBPF programs are pre-NAT
> +lookups, hence are unable to see internal IPv4 addresses as used on the
> +typical home masquerading gateway.
> +
> +ctinfo understands the following parameters:
> +
> +dscp dscpmask[/statemask]
> +
> +dscpmask - a 32 bit mask of at least 6 contiguous bits and indicates
> +where ctinfo will find the DSCP bits stored in the conntrack mark.
> +
> +statemask - a 32 bit mask of (usually) 1 bit length, outside the area
> +specified by dscpmask.  This represents a conditional operation flag
> +whereby the DSCP is only restored if the flag is set.  This is useful to
> +implement a 'one shot' iptables based classification where the
> +'complicated' iptables rules are only run once to classify the
> +connection on initial (egress) packet and subsequent packets are all
> +marked/restored with the same DSCP.  A mask of zero disables the
> +conditional behaviour ie. the conntrack mark DSCP bits are always
> +restored to the ip diffserv field (assuming the conntrack entry is found
> +& the skb is an ipv4/ipv6 type)
> +
> +optional parameters:
> +
> +zone - conntrack zone
> +
> +control - action related control (reclassify | pipe | drop | continue |
> +ok | goto chain <CHAIN_INDEX>)
> +
> +e.g. dscp 0xfc000000/0x01000000
> +
> +|----0xFC----conntrack mark----000000---|
> +| Bits 31-26 | bit 25 | bit24 |~~~ Bit 0|
> +| DSCP       | unused | flag  |unused   |
> +|-----------------------0x01---000000---|
> +      |                   |
> +      |                   |
> +      ---|             Conditional flag
> +         v             only restore if set
> +|-ip diffserv-|
> +| 6 bits      |
> +|-------------|
> +
> +Signed-off-by: Kevin Darbyshire-Bryant <ldir at darbyshire-bryant.me.uk>
> +---
> + include/net/tc_act/tc_ctinfo.h            |  28 ++
> + include/uapi/linux/pkt_cls.h              |   3 +-
> + include/uapi/linux/tc_act/tc_ctinfo.h     |  52 +++
> + net/sched/Kconfig                         |  17 +
> + net/sched/Makefile                        |   1 +
> + net/sched/act_ctinfo.c                    | 418 ++++++++++++++++++++++
> + tools/testing/selftests/tc-testing/config |   1 +
> + 7 files changed, 519 insertions(+), 1 deletion(-)
> + create mode 100644 include/net/tc_act/tc_ctinfo.h
> + create mode 100644 include/uapi/linux/tc_act/tc_ctinfo.h
> + create mode 100644 net/sched/act_ctinfo.c
> +
> +diff --git a/include/net/tc_act/tc_ctinfo.h b/include/net/tc_act/tc_ctinfo.h
> +new file mode 100644
> +index 000000000000..d6a688571672
> +--- /dev/null
> ++++ b/include/net/tc_act/tc_ctinfo.h
> +@@ -0,0 +1,28 @@
> ++/* SPDX-License-Identifier: GPL-2.0 */
> ++#ifndef __NET_TC_CTINFO_H
> ++#define __NET_TC_CTINFO_H
> ++
> ++#include <net/act_api.h>
> ++
> ++struct tcf_ctinfo_params {
> ++	struct rcu_head rcu;
> ++	struct net *net;
> ++	u32 dscpmask;
> ++	u32 dscpstatemask;
> ++	u32 cpmarkmask;
> ++	u16 zone;
> ++	u8 mode;
> ++	u8 dscpmaskshift;
> ++};
> ++
> ++struct tcf_ctinfo {
> ++	struct tc_action common;
> ++	struct tcf_ctinfo_params __rcu *params;
> ++	u64 stats_dscp_set;
> ++	u64 stats_dscp_error;
> ++	u64 stats_cpmark_set;
> ++};
> ++
> ++#define to_ctinfo(a) ((struct tcf_ctinfo *)a)
> ++
> ++#endif /* __NET_TC_CTINFO_H */
> +diff --git a/include/uapi/linux/pkt_cls.h b/include/uapi/linux/pkt_cls.h
> +index be382fb0592d..71e10c5a96a0 100644
> +--- a/include/uapi/linux/pkt_cls.h
> ++++ b/include/uapi/linux/pkt_cls.h
> +@@ -68,7 +68,8 @@ enum {
> + 	TCA_ID_UNSPEC=0,
> + 	TCA_ID_POLICE=1,
> + 	/* other actions go here */
> +-	__TCA_ID_MAX=255
> ++	TCA_ID_CTINFO,
> ++	__TCA_ID_MAX = 255
> + };
> + 
> + #define TCA_ID_MAX __TCA_ID_MAX
> +diff --git a/include/uapi/linux/tc_act/tc_ctinfo.h b/include/uapi/linux/tc_act/tc_ctinfo.h
> +new file mode 100644
> +index 000000000000..48c40f657575
> +--- /dev/null
> ++++ b/include/uapi/linux/tc_act/tc_ctinfo.h
> +@@ -0,0 +1,52 @@
> ++/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
> ++#ifndef __UAPI_TC_CTINFO_H
> ++#define __UAPI_TC_CTINFO_H
> ++
> ++#include <linux/types.h>
> ++#include <linux/pkt_cls.h>
> ++
> ++struct tc_ctinfo {
> ++	tc_gen;
> ++};
> ++
> ++struct tc_ctinfo_dscp {
> ++	__u32 mask;
> ++	__u32 statemask;
> ++};
> ++
> ++struct tc_ctinfo_cpmark {
> ++	__u32 mask;
> ++};
> ++
> ++struct tc_ctinfo_stats_dscp {
> ++	__u64 set;
> ++	__u64 error;
> ++};
> ++
> ++struct tc_ctinfo_stats_cpmark {
> ++	__u64 set;
> ++};
> ++
> ++enum {
> ++	TCA_CTINFO_UNSPEC,
> ++	TCA_CTINFO_PAD,
> ++	TCA_CTINFO_TM,
> ++	TCA_CTINFO_ACT,
> ++	TCA_CTINFO_ZONE,
> ++	TCA_CTINFO_PARMS_DSCP,
> ++	TCA_CTINFO_PARMS_CPMARK,
> ++	TCA_CTINFO_MODE_DSCP,
> ++	TCA_CTINFO_MODE_CPMARK,
> ++	TCA_CTINFO_STATS_DSCP,
> ++	TCA_CTINFO_STATS_CPMARK,
> ++	__TCA_CTINFO_MAX
> ++};
> ++
> ++#define TCA_CTINFO_MAX (__TCA_CTINFO_MAX - 1)
> ++
> ++enum {
> ++	CTINFO_MODE_DSCP	= BIT(0),
> ++	CTINFO_MODE_CPMARK	= BIT(1)
> ++};
> ++
> ++#endif
> +diff --git a/net/sched/Kconfig b/net/sched/Kconfig
> +index e95741388311..1d79d5dba6e4 100644
> +--- a/net/sched/Kconfig
> ++++ b/net/sched/Kconfig
> +@@ -866,6 +866,23 @@ config NET_ACT_CONNMARK
> + 	  To compile this code as a module, choose M here: the
> + 	  module will be called act_connmark.
> + 
> ++config NET_ACT_CTINFO
> ++        tristate "Netfilter Connection Mark Actions"
> ++        depends on NET_CLS_ACT && NETFILTER && IP_NF_IPTABLES
> ++        depends on NF_CONNTRACK && NF_CONNTRACK_MARK
> ++        help
> ++	  Say Y here to allow transfer of a connmark stored information.
> ++	  Current actions transfer connmark stored DSCP into
> ++	  ipv4/v6 diffserv and/or to transfer connmark to packet
> ++	  mark.  Both are useful for restoring egress based marks
> ++	  back onto ingress connections for qdisc priority mapping
> ++	  purposes.
> ++
> ++	  If unsure, say N.
> ++
> ++	  To compile this code as a module, choose M here: the
> ++	  module will be called act_ctinfo.
> ++
> + config NET_ACT_SKBMOD
> +         tristate "skb data modification action"
> +         depends on NET_CLS_ACT
> +diff --git a/net/sched/Makefile b/net/sched/Makefile
> +index f0403f49edcb..bb3c2bc44af7 100644
> +--- a/net/sched/Makefile
> ++++ b/net/sched/Makefile
> +@@ -21,6 +21,7 @@ obj-$(CONFIG_NET_ACT_CSUM)	+= act_csum.o
> + obj-$(CONFIG_NET_ACT_VLAN)	+= act_vlan.o
> + obj-$(CONFIG_NET_ACT_BPF)	+= act_bpf.o
> + obj-$(CONFIG_NET_ACT_CONNMARK)	+= act_connmark.o
> ++obj-$(CONFIG_NET_ACT_CTINFO)	+= act_ctinfo.o
> + obj-$(CONFIG_NET_ACT_SKBMOD)	+= act_skbmod.o
> + obj-$(CONFIG_NET_ACT_IFE)	+= act_ife.o
> + obj-$(CONFIG_NET_IFE_SKBMARK)	+= act_meta_mark.o
> +diff --git a/net/sched/act_ctinfo.c b/net/sched/act_ctinfo.c
> +new file mode 100644
> +index 000000000000..e0f180887447
> +--- /dev/null
> ++++ b/net/sched/act_ctinfo.c
> +@@ -0,0 +1,418 @@
> ++// SPDX-License-Identifier: GPL-2.0+
> ++/* net/sched/act_ctinfo.c  netfilter ctinfo connmark actions
> ++ *
> ++ * Copyright (c) 2019 Kevin Darbyshire-Bryant <ldir at darbyshire-bryant.me.uk>
> ++ */
> ++
> ++#include <linux/module.h>
> ++#include <linux/init.h>
> ++#include <linux/kernel.h>
> ++#include <linux/skbuff.h>
> ++#include <linux/rtnetlink.h>
> ++#include <linux/pkt_cls.h>
> ++#include <linux/ip.h>
> ++#include <linux/ipv6.h>
> ++#include <net/netlink.h>
> ++#include <net/pkt_sched.h>
> ++#include <net/act_api.h>
> ++#include <net/pkt_cls.h>
> ++#include <uapi/linux/tc_act/tc_ctinfo.h>
> ++#include <net/tc_act/tc_ctinfo.h>
> ++
> ++#include <net/netfilter/nf_conntrack.h>
> ++#include <net/netfilter/nf_conntrack_core.h>
> ++#include <net/netfilter/nf_conntrack_ecache.h>
> ++#include <net/netfilter/nf_conntrack_zones.h>
> ++
> ++static struct tc_action_ops act_ctinfo_ops;
> ++static unsigned int ctinfo_net_id;
> ++
> ++static void tcf_ctinfo_dscp_set(struct nf_conn *ct, struct tcf_ctinfo *ca,
> ++				struct tcf_ctinfo_params *cp,
> ++				struct sk_buff *skb, int wlen, int proto)
> ++{
> ++	u8 dscp, newdscp;
> ++
> ++	newdscp = (((ct->mark & cp->dscpmask) >> cp->dscpmaskshift) << 2) &
> ++		     ~INET_ECN_MASK;
> ++
> ++	switch (proto) {
> ++	case NFPROTO_IPV4:
> ++		dscp = ipv4_get_dsfield(ip_hdr(skb)) & ~INET_ECN_MASK;
> ++		if (dscp != newdscp) {
> ++			if (likely(!skb_try_make_writable(skb, wlen))) {
> ++				ipv4_change_dsfield(ip_hdr(skb),
> ++						    INET_ECN_MASK,
> ++						    newdscp);
> ++				ca->stats_dscp_set++;
> ++			} else {
> ++				ca->stats_dscp_error++;
> ++			}
> ++		}
> ++		break;
> ++	case NFPROTO_IPV6:
> ++		dscp = ipv6_get_dsfield(ipv6_hdr(skb)) & ~INET_ECN_MASK;
> ++		if (dscp != newdscp) {
> ++			if (likely(!skb_try_make_writable(skb, wlen))) {
> ++				ipv6_change_dsfield(ipv6_hdr(skb),
> ++						    INET_ECN_MASK,
> ++						    newdscp);
> ++				ca->stats_dscp_set++;
> ++			} else {
> ++				ca->stats_dscp_error++;
> ++			}
> ++		}
> ++		break;
> ++	default:
> ++		break;
> ++	}
> ++}
> ++
> ++static void tcf_ctinfo_cpmark_set(struct nf_conn *ct, struct tcf_ctinfo *ca,
> ++				  struct tcf_ctinfo_params *cp,
> ++				  struct sk_buff *skb)
> ++{
> ++	ca->stats_cpmark_set++;
> ++	skb->mark = ct->mark & cp->cpmarkmask;
> ++}
> ++
> ++static int tcf_ctinfo_act(struct sk_buff *skb, const struct tc_action *a,
> ++			  struct tcf_result *res)
> ++{
> ++	const struct nf_conntrack_tuple_hash *thash = NULL;
> ++	struct tcf_ctinfo *ca = to_ctinfo(a);
> ++	struct nf_conntrack_tuple tuple;
> ++	struct nf_conntrack_zone zone;
> ++	enum ip_conntrack_info ctinfo;
> ++	struct tcf_ctinfo_params *cp;
> ++	struct nf_conn *ct;
> ++	int proto, wlen;
> ++	int action;
> ++
> ++	cp = rcu_dereference_bh(ca->params);
> ++
> ++	tcf_lastuse_update(&ca->tcf_tm);
> ++	bstats_update(&ca->tcf_bstats, skb);
> ++	action = READ_ONCE(ca->tcf_action);
> ++
> ++	wlen = skb_network_offset(skb);
> ++	if (tc_skb_protocol(skb) == htons(ETH_P_IP)) {
> ++		wlen += sizeof(struct iphdr);
> ++		if (!pskb_may_pull(skb, wlen))
> ++			goto out;
> ++
> ++		proto = NFPROTO_IPV4;
> ++	} else if (tc_skb_protocol(skb) == htons(ETH_P_IPV6)) {
> ++		wlen += sizeof(struct ipv6hdr);
> ++		if (!pskb_may_pull(skb, wlen))
> ++			goto out;
> ++
> ++		proto = NFPROTO_IPV6;
> ++	} else {
> ++		goto out;
> ++	}
> ++
> ++	ct = nf_ct_get(skb, &ctinfo);
> ++	if (!ct) { /* look harder, usually ingress */
> ++		if (!nf_ct_get_tuplepr(skb, skb_network_offset(skb),
> ++				       proto, cp->net, &tuple))
> ++			goto out;
> ++		zone.id = cp->zone;
> ++		zone.dir = NF_CT_DEFAULT_ZONE_DIR;
> ++
> ++		thash = nf_conntrack_find_get(cp->net, &zone, &tuple);
> ++		if (!thash)
> ++			goto out;
> ++
> ++		ct = nf_ct_tuplehash_to_ctrack(thash);
> ++	}
> ++
> ++	if (cp->mode & CTINFO_MODE_DSCP)
> ++		if (!cp->dscpstatemask || (ct->mark & cp->dscpstatemask))
> ++			tcf_ctinfo_dscp_set(ct, ca, cp, skb, wlen, proto);
> ++
> ++	if (cp->mode & CTINFO_MODE_CPMARK)
> ++		tcf_ctinfo_cpmark_set(ct, ca, cp, skb);
> ++
> ++	if (thash)
> ++		nf_ct_put(ct);
> ++out:
> ++	return action;
> ++}
> ++
> ++static const struct nla_policy ctinfo_policy[TCA_CTINFO_MAX + 1] = {
> ++	[TCA_CTINFO_ACT]	  = { .len = sizeof(struct tc_ctinfo) },
> ++	[TCA_CTINFO_ZONE]	  = { .type = NLA_U16 },
> ++	[TCA_CTINFO_PARMS_DSCP]   = { .len = sizeof(struct tc_ctinfo_dscp) },
> ++	[TCA_CTINFO_PARMS_CPMARK] = { .len = sizeof(struct tc_ctinfo_cpmark) },
> ++	[TCA_CTINFO_MODE_DSCP]    = { .type = NLA_FLAG },
> ++	[TCA_CTINFO_MODE_CPMARK]  = { .type = NLA_FLAG },
> ++};
> ++
> ++static int tcf_ctinfo_init(struct net *net, struct nlattr *nla,
> ++			   struct nlattr *est, struct tc_action **a,
> ++			   int ovr, int bind, bool rtnl_held,
> ++			   struct netlink_ext_ack *extack)
> ++{
> ++	struct tc_action_net *tn = net_generic(net, ctinfo_net_id);
> ++	struct tc_ctinfo_cpmark *cpmarkparm = NULL;
> ++	struct nlattr *tb[TCA_CTINFO_MAX + 1];
> ++	struct tcf_ctinfo_params *cp_new;
> ++/*	struct tcf_chain *goto_ch = NULL; */
> ++	struct tc_ctinfo_dscp *dscpparm;
> ++	struct tcf_ctinfo *ci;
> ++	struct tc_ctinfo *actparm;
> ++	int ret = 0, err, i;
> ++
> ++	if (!nla)
> ++		return -EINVAL;
> ++
> ++	err = nla_parse_nested(tb, TCA_CTINFO_MAX, nla, ctinfo_policy, NULL);
> ++	if (err < 0)
> ++		return err;
> ++
> ++	if (!tb[TCA_CTINFO_ACT])
> ++		return -EINVAL;
> ++
> ++	if (tb[TCA_CTINFO_MODE_DSCP] && !tb[TCA_CTINFO_PARMS_DSCP])
> ++		return -EINVAL;
> ++
> ++	if (tb[TCA_CTINFO_PARMS_CPMARK])
> ++		cpmarkparm = nla_data(tb[TCA_CTINFO_PARMS_CPMARK]);
> ++
> ++	dscpparm = nla_data(tb[TCA_CTINFO_PARMS_DSCP]);
> ++	actparm = nla_data(tb[TCA_CTINFO_ACT]);
> ++
> ++	if (dscpparm) {
> ++		/* need contiguous 6 bit mask */
> ++		i = dscpparm->mask ? __ffs(dscpparm->mask) : 0;
> ++		if ((~0 & (dscpparm->mask >> i)) != 0x3f)
> ++			return -EINVAL;
> ++		/* mask & statemask must not overlap */
> ++		if (dscpparm->mask & dscpparm->statemask)
> ++			return -EINVAL;
> ++	}
> ++
> ++	/* done the validation:now to the actual action allocation */
> ++	err = tcf_idr_check_alloc(tn, &actparm->index, a, bind);
> ++	if (!err) {
> ++		ret = tcf_idr_create(tn, actparm->index, est, a,
> ++				     &act_ctinfo_ops, bind, false);
> ++		if (ret) {
> ++			tcf_idr_cleanup(tn, actparm->index);
> ++			return ret;
> ++		}
> ++		ret = ACT_P_CREATED;
> ++	} else if (err > 0) {
> ++		if (bind) /* don't override defaults */
> ++			return 0;
> ++		if (!ovr) {
> ++			tcf_idr_release(*a, bind);
> ++			return -EEXIST;
> ++		}
> ++	} else {
> ++		return err;
> ++	}
> ++
> ++/*	err = tcf_action_check_ctrlact(actparm->action, tp, &goto_ch, extack);
> ++	if (err < 0)
> ++		goto release_idr;
> ++		*/
> ++
> ++	ci = to_ctinfo(*a);
> ++
> ++	cp_new = kzalloc(sizeof(*cp_new), GFP_KERNEL);
> ++	if (unlikely(!cp_new)) {
> ++		err = -ENOMEM;
> ++		goto put_chain;
> ++	}
> ++
> ++	cp_new->net = net;
> ++	cp_new->zone = tb[TCA_CTINFO_ZONE] ?
> ++			nla_get_u16(tb[TCA_CTINFO_ZONE]) : 0;
> ++	if (dscpparm) {
> ++		cp_new->dscpmask = dscpparm->mask;
> ++		cp_new->dscpmaskshift = cp_new->dscpmask ?
> ++				__ffs(cp_new->dscpmask) : 0;
> ++		cp_new->dscpstatemask = dscpparm->statemask;
> ++	}
> ++
> ++	if (cpmarkparm)
> ++		cp_new->cpmarkmask = cpmarkparm->mask;
> ++	else
> ++		cp_new->cpmarkmask = ~0;
> ++
> ++	if (tb[TCA_CTINFO_MODE_DSCP])
> ++		cp_new->mode |= CTINFO_MODE_DSCP;
> ++	else
> ++		cp_new->mode &= ~CTINFO_MODE_DSCP;
> ++
> ++	if (tb[TCA_CTINFO_MODE_CPMARK])
> ++		cp_new->mode |= CTINFO_MODE_CPMARK;
> ++	else
> ++		cp_new->mode &= ~CTINFO_MODE_CPMARK;
> ++
> ++	spin_lock_bh(&ci->tcf_lock);
> ++/*	goto_ch = tcf_action_set_ctrlact(*a, actparm->action, goto_ch); */
> ++	ci->tcf_action = actparm->action;
> ++	rcu_swap_protected(ci->params, cp_new,
> ++			   lockdep_is_held(&ci->tcf_lock));
> ++	spin_unlock_bh(&ci->tcf_lock);
> ++
> ++/*	if (goto_ch)
> ++		tcf_chain_put_by_act(goto_ch); */
> ++	if (cp_new)
> ++		kfree_rcu(cp_new, rcu);
> ++
> ++	if (ret == ACT_P_CREATED)
> ++		tcf_idr_insert(tn, *a);
> ++
> ++	return ret;
> ++
> ++put_chain:
> ++/*	if (goto_ch)
> ++		tcf_chain_put_by_act(goto_ch);
> ++release_idr: */
> ++	tcf_idr_release(*a, bind);
> ++	return err;
> ++}
> ++
> ++static int tcf_ctinfo_dump(struct sk_buff *skb, struct tc_action *a,
> ++			   int bind, int ref)
> ++{
> ++	struct tcf_ctinfo *ci = to_ctinfo(a);
> ++	struct tc_ctinfo opt = {
> ++		.index   = ci->tcf_index,
> ++		.refcnt  = refcount_read(&ci->tcf_refcnt) - ref,
> ++		.bindcnt = atomic_read(&ci->tcf_bindcnt) - bind,
> ++	};
> ++	struct tc_ctinfo_stats_cpmark cpmarkstats;
> ++	unsigned char *b = skb_tail_pointer(skb);
> ++	struct tc_ctinfo_stats_dscp dscpstats;
> ++	struct tc_ctinfo_cpmark cpmarkparm;
> ++	struct tc_ctinfo_dscp dscpparm;
> ++	struct tcf_ctinfo_params *cp;
> ++	struct tcf_t t;
> ++
> ++	spin_lock_bh(&ci->tcf_lock);
> ++	cp = rcu_dereference_protected(ci->params,
> ++				       lockdep_is_held(&ci->tcf_lock));
> ++	opt.action = ci->tcf_action;
> ++
> ++	if (nla_put(skb, TCA_CTINFO_ACT, sizeof(opt), &opt))
> ++		goto nla_put_failure;
> ++
> ++	if (cp->mode & CTINFO_MODE_DSCP) {
> ++		dscpparm.mask = cp->dscpmask;
> ++		dscpparm.statemask = cp->dscpstatemask;
> ++		if (nla_put(skb, TCA_CTINFO_PARMS_DSCP, sizeof(dscpparm),
> ++			    &dscpparm))
> ++			goto nla_put_failure;
> ++
> ++		if (nla_put_flag(skb, TCA_CTINFO_MODE_DSCP))
> ++			goto nla_put_failure;
> ++
> ++		dscpstats.error = ci->stats_dscp_error;
> ++		dscpstats.set = ci->stats_dscp_set;
> ++		if (nla_put(skb, TCA_CTINFO_STATS_DSCP, sizeof(dscpstats),
> ++			    &dscpstats))
> ++			goto nla_put_failure;
> ++	}
> ++
> ++	if (cp->mode & CTINFO_MODE_CPMARK) {
> ++		cpmarkparm.mask = cp->cpmarkmask;
> ++		if (nla_put(skb, TCA_CTINFO_PARMS_CPMARK, sizeof(cpmarkparm),
> ++			    &cpmarkparm))
> ++			goto nla_put_failure;
> ++
> ++		if (nla_put_flag(skb, TCA_CTINFO_MODE_CPMARK))
> ++			goto nla_put_failure;
> ++
> ++		cpmarkstats.set = ci->stats_cpmark_set;
> ++		if (nla_put_u64_64bit(skb, TCA_CTINFO_STATS_CPMARK,
> ++				      ci->stats_cpmark_set, TCA_CTINFO_PAD))
> ++			goto nla_put_failure;
> ++	}
> ++
> ++	if (cp->zone) {
> ++		if (nla_put_u16(skb, TCA_CTINFO_ZONE, cp->zone))
> ++			goto nla_put_failure;
> ++	}
> ++
> ++	tcf_tm_dump(&t, &ci->tcf_tm);
> ++	if (nla_put_64bit(skb, TCA_CTINFO_TM, sizeof(t), &t, TCA_CTINFO_PAD))
> ++		goto nla_put_failure;
> ++
> ++	spin_unlock_bh(&ci->tcf_lock);
> ++	return skb->len;
> ++
> ++nla_put_failure:
> ++	spin_unlock_bh(&ci->tcf_lock);
> ++	nlmsg_trim(skb, b);
> ++	return -1;
> ++}
> ++
> ++static int tcf_ctinfo_walker(struct net *net, struct sk_buff *skb,
> ++			     struct netlink_callback *cb, int type,
> ++			     const struct tc_action_ops *ops,
> ++			     struct netlink_ext_ack *extack)
> ++{
> ++	struct tc_action_net *tn = net_generic(net, ctinfo_net_id);
> ++
> ++	return tcf_generic_walker(tn, skb, cb, type, ops, extack);
> ++}
> ++
> ++static int tcf_ctinfo_search(struct net *net, struct tc_action **a, u32 index,
> ++			     struct netlink_ext_ack *extack)
> ++{
> ++	struct tc_action_net *tn = net_generic(net, ctinfo_net_id);
> ++
> ++	return tcf_idr_search(tn, a, index);
> ++}
> ++
> ++static struct tc_action_ops act_ctinfo_ops = {
> ++	.kind	= "ctinfo",
> ++	.type	= TCA_ID_CTINFO,
> ++	.owner	= THIS_MODULE,
> ++	.act	= tcf_ctinfo_act,
> ++	.dump	= tcf_ctinfo_dump,
> ++	.init	= tcf_ctinfo_init,
> ++	.walk	= tcf_ctinfo_walker,
> ++	.lookup	= tcf_ctinfo_search,
> ++	.size	= sizeof(struct tcf_ctinfo),
> ++};
> ++
> ++static __net_init int ctinfo_init_net(struct net *net)
> ++{
> ++	struct tc_action_net *tn = net_generic(net, ctinfo_net_id);
> ++
> ++	return tc_action_net_init(tn, &act_ctinfo_ops);
> ++}
> ++
> ++static void __net_exit ctinfo_exit_net(struct list_head *net_list)
> ++{
> ++	tc_action_net_exit(net_list, ctinfo_net_id);
> ++}
> ++
> ++static struct pernet_operations ctinfo_net_ops = {
> ++	.init		= ctinfo_init_net,
> ++	.exit_batch	= ctinfo_exit_net,
> ++	.id		= &ctinfo_net_id,
> ++	.size		= sizeof(struct tc_action_net),
> ++};
> ++
> ++static int __init ctinfo_init_module(void)
> ++{
> ++	return tcf_register_action(&act_ctinfo_ops, &ctinfo_net_ops);
> ++}
> ++
> ++static void __exit ctinfo_cleanup_module(void)
> ++{
> ++	tcf_unregister_action(&act_ctinfo_ops, &ctinfo_net_ops);
> ++}
> ++
> ++module_init(ctinfo_init_module);
> ++module_exit(ctinfo_cleanup_module);
> ++MODULE_AUTHOR("Kevin Darbyshire-Bryant <ldir at darbyshire-bryant.me.uk>");
> ++MODULE_DESCRIPTION("Connection tracking mark actions");
> ++MODULE_LICENSE("GPL");
> +diff --git a/tools/testing/selftests/tc-testing/config b/tools/testing/selftests/tc-testing/config
> +index 203302065458..9d1fddcfb887 100644
> +--- a/tools/testing/selftests/tc-testing/config
> ++++ b/tools/testing/selftests/tc-testing/config
> +@@ -37,6 +37,7 @@ CONFIG_NET_ACT_SKBEDIT=m
> + CONFIG_NET_ACT_CSUM=m
> + CONFIG_NET_ACT_VLAN=m
> + CONFIG_NET_ACT_BPF=m
> ++CONFIG_NET_ACT_CONNDSCP=m
> + CONFIG_NET_ACT_CONNMARK=m
> + CONFIG_NET_ACT_SKBMOD=m
> + CONFIG_NET_ACT_IFE=m
> +-- 
> +2.20.1 (Apple Git-117)
> +
> diff --git a/target/linux/generic/hack-4.19/0001-netfilter-connmark-introduce-savedscp.patch b/target/linux/generic/hack-4.19/0001-netfilter-connmark-introduce-savedscp.patch
> new file mode 100644
> index 0000000000..cef2dd80f5
> --- /dev/null
> +++ b/target/linux/generic/hack-4.19/0001-netfilter-connmark-introduce-savedscp.patch
> @@ -0,0 +1,142 @@
> +From f171924dcf1d0b31fb7bd1cff113d7a1f7f05ec2 Mon Sep 17 00:00:00 2001
> +From: Kevin Darbyshire-Bryant <ldir at darbyshire-bryant.me.uk>
> +Date: Sat, 23 Mar 2019 09:29:49 +0000
> +Subject: [PATCH] netfilter: connmark: introduce savedscp
> +
> +savedscp is a method of storing the DSCP of an ip packet into conntrack
> +mark.  In combination with a suitable tc filter action (act_ctinfo) DSCP
> +values are able to be stored in the mark on egress and restored on
> +ingress across links that otherwise alter or bleach DSCP.
> +
> +This is useful for qdiscs such as CAKE which are able to shape according
> +to policies based on DSCP.
> +
> +Ingress classification is traditionally a challenging task since
> +iptables rules haven't yet run and tc filter/eBPF programs are pre-NAT
> +lookups, hence are unable to see internal IPv4 addresses as used on the
> +typical home masquerading gateway.
> +
> +x_tables CONNMARK savedscp action solves the problem of storing the DSCP
> +to the conntrack mark in a way suitable for the new act_ctinfo tc action
> +to restore.
> +
> +The savedsp option accepts 2 parameters, a 32bit 'dscpmask' and a 32bit
> +'statemask'.  The dscp mask must be a minimum of 6 contiguous bits and
> +represents the area where the DSCP will be stored in the connmark.  The
> +state mask is a minimum 1 bit length mask that must not overlap with the
> +dscpmask.  It represents a flag which is set when the DSCP has been
> +stored in the conntrack mark. This is useful to implement a 'one shot'
> +iptables based classification where the 'complicated' iptables rules are
> +only run once to classify the connection on initial (egress) packet and
> +subsequent packets are all marked/restored with the same DSCP.  A state
> +mask of zero disables the setting of a status bit/s.
> +
> +example syntax with a suitably modified iptables user space application:
> +
> +iptables -A QOS_MARK_eth0 -t mangle -j CONNMARK --savedscp-mark 0xfc000000/0x01000000
> +
> +Would store the DSCP in the top 6 bits of the 32bit mark field, and use
> +the LSB of the top byte as the 'DSCP has been stored' marker.
> +
> +|----0xFC----conntrack mark----000000---|
> +| Bits 31-26 | bit 25 | bit24 |~~~ Bit 0|
> +| DSCP       | unused | flag  |unused   |
> +|-----------------------0x01---000000---|
> +      ^                   ^
> +      |                   |
> +      ---|             Conditional flag
> +         |             set this when dscp
> +|-ip diffserv-|        stored in mark
> +| 6 bits      |
> +|-------------|
> +
> +an identically configured tc action to restore looks like:
> +
> +tc filter show dev eth0 ingress
> +filter parent ffff: protocol all pref 10 u32 chain 0
> +filter parent ffff: protocol all pref 10 u32 chain 0 fh 800: ht divisor 1
> +filter parent ffff: protocol all pref 10 u32 chain 0 fh 800::800 order 2048 key ht 800 bkt 0 flowid 1:1 not_in_hw
> +  match 00000000/00000000 at 0
> +	action order 1: ctinfo zone 0 pipe
> +	 index 2 ref 1 bind 1 dscp 0xfc000000/0x1000000
> +
> +	action order 2: mirred (Egress Redirect to device ifb4eth0) stolen
> +	index 1 ref 1 bind 1
> +
> +|----0xFC----conntrack mark----000000---|
> +| Bits 31-26 | bit 25 | bit24 |~~~ Bit 0|
> +| DSCP       | unused | flag  |unused   |
> +|-----------------------0x01---000000---|
> +      |                   |
> +      |                   |
> +      ---|             Conditional flag
> +         v             only restore if set
> +|-ip diffserv-|
> +| 6 bits      |
> +|-------------|
> +
> +Signed-off-by: Kevin Darbyshire-Bryant <ldir at darbyshire-bryant.me.uk>
> +---
> + include/uapi/linux/netfilter/xt_connmark.h |  3 ++-
> + net/netfilter/xt_connmark.c                | 17 +++++++++++++++++
> + 2 files changed, 19 insertions(+), 1 deletion(-)
> +
> +diff --git a/include/uapi/linux/netfilter/xt_connmark.h b/include/uapi/linux/netfilter/xt_connmark.h
> +index 1aa5c955ee1e..24272cac2d37 100644
> +--- a/include/uapi/linux/netfilter/xt_connmark.h
> ++++ b/include/uapi/linux/netfilter/xt_connmark.h
> +@@ -16,7 +16,8 @@
> + enum {
> + 	XT_CONNMARK_SET = 0,
> + 	XT_CONNMARK_SAVE,
> +-	XT_CONNMARK_RESTORE
> ++	XT_CONNMARK_RESTORE,
> ++	XT_CONNMARK_SAVEDSCP
> + };
> + 
> + enum {
> +diff --git a/net/netfilter/xt_connmark.c b/net/netfilter/xt_connmark.c
> +index 29c38aa7f726..5ce2adde5595 100644
> +--- a/net/netfilter/xt_connmark.c
> ++++ b/net/netfilter/xt_connmark.c
> +@@ -42,6 +42,7 @@ connmark_tg_shift(struct sk_buff *skb, const struct xt_connmark_tginfo2 *info)
> + 	u_int32_t new_targetmark;
> + 	struct nf_conn *ct;
> + 	u_int32_t newmark;
> ++	u8 dscp;
> + 
> + 	ct = nf_ct_get(skb, &ctinfo);
> + 	if (ct == NULL)
> +@@ -74,6 +75,21 @@ connmark_tg_shift(struct sk_buff *skb, const struct xt_connmark_tginfo2 *info)
> + 			nf_conntrack_event_cache(IPCT_MARK, ct);
> + 		}
> + 		break;
> ++	case XT_CONNMARK_SAVEDSCP:
> ++		if (skb->protocol == htons(ETH_P_IP))
> ++			dscp = ipv4_get_dsfield(ip_hdr(skb)) >> 2;
> ++		else if (skb->protocol == htons(ETH_P_IPV6))
> ++			dscp = ipv6_get_dsfield(ipv6_hdr(skb)) >> 2;
> ++		else	/* protocol doesn't have diffserv */
> ++			break;
> ++
> ++		newmark = (ct->mark & ~info->ctmark) ^
> ++			  (info->ctmask | (dscp << info->shift_bits));
> ++		if (ct->mark != newmark) {
> ++			ct->mark = newmark;
> ++			nf_conntrack_event_cache(IPCT_MARK, ct);
> ++		}
> ++		break;
> + 	case XT_CONNMARK_RESTORE:
> + 		new_targetmark = (ct->mark & info->ctmask);
> + 		if (info->shift_dir == D_SHIFT_RIGHT)
> +@@ -86,6 +102,7 @@ connmark_tg_shift(struct sk_buff *skb, const struct xt_connmark_tginfo2 *info)
> + 		skb->mark = newmark;
> + 		break;
> + 	}
> ++out:
> + 	return XT_CONTINUE;
> + }
> + 
> +-- 
> +2.20.1 (Apple Git-117)
> +
> -- 
> 2.20.1 (Apple Git-117)
> 


Cheers,

Kevin D-B

gpg: 012C ACB2 28C6 C53E 9775  9123 B3A2 389B 9DE2 334A




More information about the openwrt-devel mailing list