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

> On 27 May 2019, at 19:02, Kevin 'ldir' Darbyshire-Bryant 
> <l...@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 <l...@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 <l...@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 <l...@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 <l...@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 <l...@darbyshire-bryant.me.uk>
> +Date: Sat, 23 Mar 2019 10:21:03 +0000
> +Subject: [PATCH] savedscp
> +
> +Signed-off-by: Kevin Darbyshire-Bryant <l...@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 <l...@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 <l...@darbyshire-bryant.me.uk>
> +
> +net:sched - v3 tweak license
> +
> +Signed-off-by: Kevin Darbyshire-Bryant <l...@darbyshire-bryant.me.uk>
> +
> +drop inline & sort reverse christmas tree
> +
> +Signed-off-by: Kevin Darbyshire-Bryant <l...@darbyshire-bryant.me.uk>
> +
> +v5 stuff
> +
> +Signed-off-by: Kevin Darbyshire-Bryant <l...@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 <l...@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 <l...@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 <l...@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 <l...@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 <l...@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 <l...@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 <l...@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 <l...@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 <l...@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 <l...@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


_______________________________________________
openwrt-devel mailing list
openwrt-devel@lists.openwrt.org
https://lists.openwrt.org/mailman/listinfo/openwrt-devel

Reply via email to