From d7ad70b5ef5ab8dedaa403e0e5c711ca1aa8cb14 Mon Sep 17 00:00:00 2001 From: Zahari Doychev Date: Thu, 8 Jun 2023 12:56:46 +0200 Subject: net: flow_dissector: add support for cfm packets Add support for dissecting cfm packets. The cfm packet header fields maintenance domain level and opcode can be dissected. Signed-off-by: Zahari Doychev Reviewed-by: Simon Horman Reviewed-by: Ido Schimmel Signed-off-by: Jakub Kicinski --- include/net/flow_dissector.h | 21 +++++++++++++++++++++ net/core/flow_dissector.c | 30 ++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/include/net/flow_dissector.h b/include/net/flow_dissector.h index 8b41668c77fc..8664ed4fbbdf 100644 --- a/include/net/flow_dissector.h +++ b/include/net/flow_dissector.h @@ -301,6 +301,26 @@ struct flow_dissector_key_l2tpv3 { __be32 session_id; }; +/** + * struct flow_dissector_key_cfm + * @mdl_ver: maintenance domain level (mdl) and cfm protocol version + * @opcode: code specifying a type of cfm protocol packet + * + * See 802.1ag, ITU-T G.8013/Y.1731 + * 1 2 + * |7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0| + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | mdl | version | opcode | + * +-----+---------+-+-+-+-+-+-+-+-+ + */ +struct flow_dissector_key_cfm { + u8 mdl_ver; + u8 opcode; +}; + +#define FLOW_DIS_CFM_MDL_MASK GENMASK(7, 5) +#define FLOW_DIS_CFM_MDL_MAX 7 + enum flow_dissector_key_id { FLOW_DISSECTOR_KEY_CONTROL, /* struct flow_dissector_key_control */ FLOW_DISSECTOR_KEY_BASIC, /* struct flow_dissector_key_basic */ @@ -333,6 +353,7 @@ enum flow_dissector_key_id { FLOW_DISSECTOR_KEY_NUM_OF_VLANS, /* struct flow_dissector_key_num_of_vlans */ FLOW_DISSECTOR_KEY_PPPOE, /* struct flow_dissector_key_pppoe */ FLOW_DISSECTOR_KEY_L2TPV3, /* struct flow_dissector_key_l2tpv3 */ + FLOW_DISSECTOR_KEY_CFM, /* struct flow_dissector_key_cfm */ FLOW_DISSECTOR_KEY_MAX, }; diff --git a/net/core/flow_dissector.c b/net/core/flow_dissector.c index 481ca4080cbd..85a2d0d9bd39 100644 --- a/net/core/flow_dissector.c +++ b/net/core/flow_dissector.c @@ -557,6 +557,30 @@ __skb_flow_dissect_arp(const struct sk_buff *skb, return FLOW_DISSECT_RET_OUT_GOOD; } +static enum flow_dissect_ret +__skb_flow_dissect_cfm(const struct sk_buff *skb, + struct flow_dissector *flow_dissector, + void *target_container, const void *data, + int nhoff, int hlen) +{ + struct flow_dissector_key_cfm *key, *hdr, _hdr; + + if (!dissector_uses_key(flow_dissector, FLOW_DISSECTOR_KEY_CFM)) + return FLOW_DISSECT_RET_OUT_GOOD; + + hdr = __skb_header_pointer(skb, nhoff, sizeof(*key), data, hlen, &_hdr); + if (!hdr) + return FLOW_DISSECT_RET_OUT_BAD; + + key = skb_flow_dissector_target(flow_dissector, FLOW_DISSECTOR_KEY_CFM, + target_container); + + key->mdl_ver = hdr->mdl_ver; + key->opcode = hdr->opcode; + + return FLOW_DISSECT_RET_OUT_GOOD; +} + static enum flow_dissect_ret __skb_flow_dissect_gre(const struct sk_buff *skb, struct flow_dissector_key_control *key_control, @@ -1400,6 +1424,12 @@ proto_again: break; } + case htons(ETH_P_CFM): + fdret = __skb_flow_dissect_cfm(skb, flow_dissector, + target_container, data, + nhoff, hlen); + break; + default: fdret = FLOW_DISSECT_RET_OUT_BAD; break; -- cgit v1.2.3 From 7cfffd5fed3e385010583840402f0bf66c4ed147 Mon Sep 17 00:00:00 2001 From: Zahari Doychev Date: Thu, 8 Jun 2023 12:56:47 +0200 Subject: net: flower: add support for matching cfm fields Add support to the tc flower classifier to match based on fields in CFM information elements like level and opcode. tc filter add dev ens6 ingress protocol 802.1q \ flower vlan_id 698 vlan_ethtype 0x8902 cfm mdl 5 op 46 \ action drop Signed-off-by: Zahari Doychev Reviewed-by: Simon Horman Reviewed-by: Ido Schimmel Signed-off-by: Jakub Kicinski --- include/uapi/linux/pkt_cls.h | 9 ++++ net/sched/cls_flower.c | 102 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+) diff --git a/include/uapi/linux/pkt_cls.h b/include/uapi/linux/pkt_cls.h index 00933dda7b10..7865f5a9885b 100644 --- a/include/uapi/linux/pkt_cls.h +++ b/include/uapi/linux/pkt_cls.h @@ -596,6 +596,8 @@ enum { TCA_FLOWER_L2_MISS, /* u8 */ + TCA_FLOWER_KEY_CFM, /* nested */ + __TCA_FLOWER_MAX, }; @@ -704,6 +706,13 @@ enum { TCA_FLOWER_KEY_FLAGS_FRAG_IS_FIRST = (1 << 1), }; +enum { + TCA_FLOWER_KEY_CFM_OPT_UNSPEC, + TCA_FLOWER_KEY_CFM_MD_LEVEL, + TCA_FLOWER_KEY_CFM_OPCODE, + TCA_FLOWER_KEY_CFM_OPT_MAX, +}; + #define TCA_FLOWER_MASK_FLAGS_RANGE (1 << 0) /* Range-based match */ /* Match-all classifier */ diff --git a/net/sched/cls_flower.c b/net/sched/cls_flower.c index e02ecabbb75c..56065cc5a661 100644 --- a/net/sched/cls_flower.c +++ b/net/sched/cls_flower.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -71,6 +72,7 @@ struct fl_flow_key { struct flow_dissector_key_num_of_vlans num_of_vlans; struct flow_dissector_key_pppoe pppoe; struct flow_dissector_key_l2tpv3 l2tpv3; + struct flow_dissector_key_cfm cfm; } __aligned(BITS_PER_LONG / 8); /* Ensure that we can do comparisons as longs. */ struct fl_flow_mask_range { @@ -725,6 +727,7 @@ static const struct nla_policy fl_policy[TCA_FLOWER_MAX + 1] = { [TCA_FLOWER_KEY_PPP_PROTO] = { .type = NLA_U16 }, [TCA_FLOWER_KEY_L2TPV3_SID] = { .type = NLA_U32 }, [TCA_FLOWER_L2_MISS] = NLA_POLICY_MAX(NLA_U8, 1), + [TCA_FLOWER_KEY_CFM] = { .type = NLA_NESTED }, }; static const struct nla_policy @@ -773,6 +776,12 @@ mpls_stack_entry_policy[TCA_FLOWER_KEY_MPLS_OPT_LSE_MAX + 1] = { [TCA_FLOWER_KEY_MPLS_OPT_LSE_LABEL] = { .type = NLA_U32 }, }; +static const struct nla_policy cfm_opt_policy[TCA_FLOWER_KEY_CFM_OPT_MAX] = { + [TCA_FLOWER_KEY_CFM_MD_LEVEL] = NLA_POLICY_MAX(NLA_U8, + FLOW_DIS_CFM_MDL_MAX), + [TCA_FLOWER_KEY_CFM_OPCODE] = { .type = NLA_U8 }, +}; + static void fl_set_key_val(struct nlattr **tb, void *val, int val_type, void *mask, int mask_type, int len) @@ -1660,6 +1669,53 @@ static bool is_vlan_key(struct nlattr *tb, __be16 *ethertype, return false; } +static void fl_set_key_cfm_md_level(struct nlattr **tb, + struct fl_flow_key *key, + struct fl_flow_key *mask, + struct netlink_ext_ack *extack) +{ + u8 level; + + if (!tb[TCA_FLOWER_KEY_CFM_MD_LEVEL]) + return; + + level = nla_get_u8(tb[TCA_FLOWER_KEY_CFM_MD_LEVEL]); + key->cfm.mdl_ver = FIELD_PREP(FLOW_DIS_CFM_MDL_MASK, level); + mask->cfm.mdl_ver = FLOW_DIS_CFM_MDL_MASK; +} + +static void fl_set_key_cfm_opcode(struct nlattr **tb, + struct fl_flow_key *key, + struct fl_flow_key *mask, + struct netlink_ext_ack *extack) +{ + fl_set_key_val(tb, &key->cfm.opcode, TCA_FLOWER_KEY_CFM_OPCODE, + &mask->cfm.opcode, TCA_FLOWER_UNSPEC, + sizeof(key->cfm.opcode)); +} + +static int fl_set_key_cfm(struct nlattr **tb, + struct fl_flow_key *key, + struct fl_flow_key *mask, + struct netlink_ext_ack *extack) +{ + struct nlattr *nla_cfm_opt[TCA_FLOWER_KEY_CFM_OPT_MAX]; + int err; + + if (!tb[TCA_FLOWER_KEY_CFM]) + return 0; + + err = nla_parse_nested(nla_cfm_opt, TCA_FLOWER_KEY_CFM_OPT_MAX, + tb[TCA_FLOWER_KEY_CFM], cfm_opt_policy, extack); + if (err < 0) + return err; + + fl_set_key_cfm_opcode(nla_cfm_opt, key, mask, extack); + fl_set_key_cfm_md_level(nla_cfm_opt, key, mask, extack); + + return 0; +} + static int fl_set_key(struct net *net, struct nlattr **tb, struct fl_flow_key *key, struct fl_flow_key *mask, struct netlink_ext_ack *extack) @@ -1814,6 +1870,10 @@ static int fl_set_key(struct net *net, struct nlattr **tb, TCA_FLOWER_KEY_L2TPV3_SID, &mask->l2tpv3.session_id, TCA_FLOWER_UNSPEC, sizeof(key->l2tpv3.session_id)); + } else if (key->basic.n_proto == htons(ETH_P_CFM)) { + ret = fl_set_key_cfm(tb, key, mask, extack); + if (ret) + return ret; } if (key->basic.ip_proto == IPPROTO_TCP || @@ -1996,6 +2056,8 @@ static void fl_init_dissector(struct flow_dissector *dissector, FLOW_DISSECTOR_KEY_PPPOE, pppoe); FL_KEY_SET_IF_MASKED(mask, keys, cnt, FLOW_DISSECTOR_KEY_L2TPV3, l2tpv3); + FL_KEY_SET_IF_MASKED(mask, keys, cnt, + FLOW_DISSECTOR_KEY_CFM, cfm); skb_flow_dissector_init(dissector, keys, cnt); } @@ -3029,6 +3091,43 @@ nla_put_failure: return -EMSGSIZE; } +static int fl_dump_key_cfm(struct sk_buff *skb, + struct flow_dissector_key_cfm *key, + struct flow_dissector_key_cfm *mask) +{ + struct nlattr *opts; + int err; + u8 mdl; + + if (!memchr_inv(mask, 0, sizeof(*mask))) + return 0; + + opts = nla_nest_start(skb, TCA_FLOWER_KEY_CFM); + if (!opts) + return -EMSGSIZE; + + if (FIELD_GET(FLOW_DIS_CFM_MDL_MASK, mask->mdl_ver)) { + mdl = FIELD_GET(FLOW_DIS_CFM_MDL_MASK, key->mdl_ver); + err = nla_put_u8(skb, TCA_FLOWER_KEY_CFM_MD_LEVEL, mdl); + if (err) + goto err_cfm_opts; + } + + if (mask->opcode) { + err = nla_put_u8(skb, TCA_FLOWER_KEY_CFM_OPCODE, key->opcode); + if (err) + goto err_cfm_opts; + } + + nla_nest_end(skb, opts); + + return 0; + +err_cfm_opts: + nla_nest_cancel(skb, opts); + return err; +} + static int fl_dump_key_options(struct sk_buff *skb, int enc_opt_type, struct flow_dissector_key_enc_opts *enc_opts) { @@ -3316,6 +3415,9 @@ static int fl_dump_key(struct sk_buff *skb, struct net *net, sizeof(key->hash.hash))) goto nla_put_failure; + if (fl_dump_key_cfm(skb, &key->cfm, &mask->cfm)) + goto nla_put_failure; + return 0; nla_put_failure: -- cgit v1.2.3 From 1668a55a73f5a3ddde1019695223eed8e23b9436 Mon Sep 17 00:00:00 2001 From: Zahari Doychev Date: Thu, 8 Jun 2023 12:56:48 +0200 Subject: selftests: net: add tc flower cfm test New cfm flower test case is added to the net forwarding selfttests. Example output: # ./tc_flower_cfm.sh p1 p2 TEST: CFM opcode match test [ OK ] TEST: CFM level match test [ OK ] TEST: CFM opcode and level match test [ OK ] Signed-off-by: Zahari Doychev Reviewed-by: Ido Schimmel Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/forwarding/Makefile | 1 + .../selftests/net/forwarding/tc_flower_cfm.sh | 206 +++++++++++++++++++++ 2 files changed, 207 insertions(+) create mode 100755 tools/testing/selftests/net/forwarding/tc_flower_cfm.sh diff --git a/tools/testing/selftests/net/forwarding/Makefile b/tools/testing/selftests/net/forwarding/Makefile index 9d0062b542e5..770efbe24f0d 100644 --- a/tools/testing/selftests/net/forwarding/Makefile +++ b/tools/testing/selftests/net/forwarding/Makefile @@ -84,6 +84,7 @@ TEST_PROGS = bridge_igmp.sh \ tc_flower_router.sh \ tc_flower.sh \ tc_flower_l2_miss.sh \ + tc_flower_cfm.sh \ tc_mpls_l2vpn.sh \ tc_police.sh \ tc_shblocks.sh \ diff --git a/tools/testing/selftests/net/forwarding/tc_flower_cfm.sh b/tools/testing/selftests/net/forwarding/tc_flower_cfm.sh new file mode 100755 index 000000000000..3ca20df952eb --- /dev/null +++ b/tools/testing/selftests/net/forwarding/tc_flower_cfm.sh @@ -0,0 +1,206 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 + +ALL_TESTS="match_cfm_opcode match_cfm_level match_cfm_level_and_opcode" +NUM_NETIFS=2 +source tc_common.sh +source lib.sh + +h1_create() +{ + simple_if_init $h1 +} + +h1_destroy() +{ + simple_if_fini $h1 +} + +h2_create() +{ + simple_if_init $h2 + tc qdisc add dev $h2 clsact +} + +h2_destroy() +{ + tc qdisc del dev $h2 clsact + simple_if_fini $h2 +} + +u8_to_hex() +{ + local u8=$1; shift + + printf "%02x" $u8 +} + +generate_cfm_hdr() +{ + local mdl=$1; shift + local op=$1; shift + local flags=$1; shift + local tlv_offset=$1; shift + + local cfm_hdr=$(: + )"$(u8_to_hex $((mdl << 5))):"$( : MD level and Version + )"$(u8_to_hex $op):"$( : OpCode + )"$(u8_to_hex $flags):"$( : Flags + )"$(u8_to_hex $tlv_offset)"$( : TLV offset + ) + + echo $cfm_hdr +} + +match_cfm_opcode() +{ + local ethtype="89 02"; readonly ethtype + RET=0 + + tc filter add dev $h2 ingress protocol cfm pref 1 handle 101 \ + flower cfm op 47 action drop + tc filter add dev $h2 ingress protocol cfm pref 1 handle 102 \ + flower cfm op 43 action drop + + pkt="$ethtype $(generate_cfm_hdr 7 47 0 32)" + $MZ $h1 -c 1 -p 64 -a $h1mac -b $h2mac "$pkt" -q + pkt="$ethtype $(generate_cfm_hdr 6 5 0 4)" + $MZ $h1 -c 1 -p 64 -a $h1mac -b $h2mac "$pkt" -q + + tc_check_packets "dev $h2 ingress" 101 1 + check_err $? "Did not match on correct opcode" + + tc_check_packets "dev $h2 ingress" 102 0 + check_err $? "Matched on the wrong opcode" + + pkt="$ethtype $(generate_cfm_hdr 0 43 0 12)" + $MZ $h1 -c 1 -p 64 -a $h1mac -b $h2mac "$pkt" -q + + tc_check_packets "dev $h2 ingress" 101 1 + check_err $? "Matched on the wrong opcode" + + tc_check_packets "dev $h2 ingress" 102 1 + check_err $? "Did not match on correct opcode" + + tc filter del dev $h2 ingress protocol cfm pref 1 handle 101 flower + tc filter del dev $h2 ingress protocol cfm pref 1 handle 102 flower + + log_test "CFM opcode match test" +} + +match_cfm_level() +{ + local ethtype="89 02"; readonly ethtype + RET=0 + + tc filter add dev $h2 ingress protocol cfm pref 1 handle 101 \ + flower cfm mdl 5 action drop + tc filter add dev $h2 ingress protocol cfm pref 1 handle 102 \ + flower cfm mdl 3 action drop + tc filter add dev $h2 ingress protocol cfm pref 1 handle 103 \ + flower cfm mdl 0 action drop + + pkt="$ethtype $(generate_cfm_hdr 5 42 0 12)" + $MZ $h1 -c 1 -p 64 -a $h1mac -b $h2mac "$pkt" -q + pkt="$ethtype $(generate_cfm_hdr 6 1 0 70)" + $MZ $h1 -c 1 -p 64 -a $h1mac -b $h2mac "$pkt" -q + pkt="$ethtype $(generate_cfm_hdr 0 1 0 70)" + $MZ $h1 -c 1 -p 64 -a $h1mac -b $h2mac "$pkt" -q + + tc_check_packets "dev $h2 ingress" 101 1 + check_err $? "Did not match on correct level" + + tc_check_packets "dev $h2 ingress" 102 0 + check_err $? "Matched on the wrong level" + + tc_check_packets "dev $h2 ingress" 103 1 + check_err $? "Did not match on correct level" + + pkt="$ethtype $(generate_cfm_hdr 3 0 0 4)" + $MZ $h1 -c 1 -p 64 -a $h1mac -b $h2mac "$pkt" -q + + tc_check_packets "dev $h2 ingress" 101 1 + check_err $? "Matched on the wrong level" + + tc_check_packets "dev $h2 ingress" 102 1 + check_err $? "Did not match on correct level" + + tc_check_packets "dev $h2 ingress" 103 1 + check_err $? "Matched on the wrong level" + + tc filter del dev $h2 ingress protocol cfm pref 1 handle 101 flower + tc filter del dev $h2 ingress protocol cfm pref 1 handle 102 flower + tc filter del dev $h2 ingress protocol cfm pref 1 handle 103 flower + + log_test "CFM level match test" +} + +match_cfm_level_and_opcode() +{ + local ethtype="89 02"; readonly ethtype + RET=0 + + tc filter add dev $h2 ingress protocol cfm pref 1 handle 101 \ + flower cfm mdl 5 op 41 action drop + tc filter add dev $h2 ingress protocol cfm pref 1 handle 102 \ + flower cfm mdl 7 op 42 action drop + + pkt="$ethtype $(generate_cfm_hdr 5 41 0 4)" + $MZ $h1 -c 1 -p 64 -a $h1mac -b $h2mac "$pkt" -q + pkt="$ethtype $(generate_cfm_hdr 7 3 0 4)" + $MZ $h1 -c 1 -p 64 -a $h1mac -b $h2mac "$pkt" -q + pkt="$ethtype $(generate_cfm_hdr 3 42 0 12)" + $MZ $h1 -c 1 -p 64 -a $h1mac -b $h2mac "$pkt" -q + + tc_check_packets "dev $h2 ingress" 101 1 + check_err $? "Did not match on correct level and opcode" + + tc_check_packets "dev $h2 ingress" 102 0 + check_err $? "Matched on the wrong level and opcode" + + pkt="$ethtype $(generate_cfm_hdr 7 42 0 12)" + $MZ $h1 -c 1 -p 64 -a $h1mac -b $h2mac "$pkt" -q + + tc_check_packets "dev $h2 ingress" 101 1 + check_err $? "Matched on the wrong level and opcode" + + tc_check_packets "dev $h2 ingress" 102 1 + check_err $? "Did not match on correct level and opcode" + + tc filter del dev $h2 ingress protocol cfm pref 1 handle 101 flower + tc filter del dev $h2 ingress protocol cfm pref 1 handle 102 flower + + log_test "CFM opcode and level match test" +} + +setup_prepare() +{ + h1=${NETIFS[p1]} + h2=${NETIFS[p2]} + h1mac=$(mac_get $h1) + h2mac=$(mac_get $h2) + + vrf_prepare + + h1_create + h2_create +} + +cleanup() +{ + pre_cleanup + + h2_destroy + h1_destroy + + vrf_cleanup +} + +trap cleanup EXIT + +setup_prepare +setup_wait + +tests_run + +exit $EXIT_STATUS -- cgit v1.2.3