162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * net/sched/em_cmp.c	Simple packet data comparison ematch
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Authors:	Thomas Graf <tgraf@suug.ch>
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <linux/module.h>
962306a36Sopenharmony_ci#include <linux/types.h>
1062306a36Sopenharmony_ci#include <linux/kernel.h>
1162306a36Sopenharmony_ci#include <linux/skbuff.h>
1262306a36Sopenharmony_ci#include <linux/tc_ematch/tc_em_cmp.h>
1362306a36Sopenharmony_ci#include <asm/unaligned.h>
1462306a36Sopenharmony_ci#include <net/pkt_cls.h>
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_cistatic inline int cmp_needs_transformation(struct tcf_em_cmp *cmp)
1762306a36Sopenharmony_ci{
1862306a36Sopenharmony_ci	return unlikely(cmp->flags & TCF_EM_CMP_TRANS);
1962306a36Sopenharmony_ci}
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_cistatic int em_cmp_match(struct sk_buff *skb, struct tcf_ematch *em,
2262306a36Sopenharmony_ci			struct tcf_pkt_info *info)
2362306a36Sopenharmony_ci{
2462306a36Sopenharmony_ci	struct tcf_em_cmp *cmp = (struct tcf_em_cmp *) em->data;
2562306a36Sopenharmony_ci	unsigned char *ptr = tcf_get_base_ptr(skb, cmp->layer) + cmp->off;
2662306a36Sopenharmony_ci	u32 val = 0;
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_ci	if (!tcf_valid_offset(skb, ptr, cmp->align))
2962306a36Sopenharmony_ci		return 0;
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_ci	switch (cmp->align) {
3262306a36Sopenharmony_ci	case TCF_EM_ALIGN_U8:
3362306a36Sopenharmony_ci		val = *ptr;
3462306a36Sopenharmony_ci		break;
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ci	case TCF_EM_ALIGN_U16:
3762306a36Sopenharmony_ci		val = get_unaligned_be16(ptr);
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ci		if (cmp_needs_transformation(cmp))
4062306a36Sopenharmony_ci			val = be16_to_cpu(val);
4162306a36Sopenharmony_ci		break;
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_ci	case TCF_EM_ALIGN_U32:
4462306a36Sopenharmony_ci		/* Worth checking boundaries? The branching seems
4562306a36Sopenharmony_ci		 * to get worse. Visit again.
4662306a36Sopenharmony_ci		 */
4762306a36Sopenharmony_ci		val = get_unaligned_be32(ptr);
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci		if (cmp_needs_transformation(cmp))
5062306a36Sopenharmony_ci			val = be32_to_cpu(val);
5162306a36Sopenharmony_ci		break;
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_ci	default:
5462306a36Sopenharmony_ci		return 0;
5562306a36Sopenharmony_ci	}
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_ci	if (cmp->mask)
5862306a36Sopenharmony_ci		val &= cmp->mask;
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_ci	switch (cmp->opnd) {
6162306a36Sopenharmony_ci	case TCF_EM_OPND_EQ:
6262306a36Sopenharmony_ci		return val == cmp->val;
6362306a36Sopenharmony_ci	case TCF_EM_OPND_LT:
6462306a36Sopenharmony_ci		return val < cmp->val;
6562306a36Sopenharmony_ci	case TCF_EM_OPND_GT:
6662306a36Sopenharmony_ci		return val > cmp->val;
6762306a36Sopenharmony_ci	}
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ci	return 0;
7062306a36Sopenharmony_ci}
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_cistatic struct tcf_ematch_ops em_cmp_ops = {
7362306a36Sopenharmony_ci	.kind	  = TCF_EM_CMP,
7462306a36Sopenharmony_ci	.datalen  = sizeof(struct tcf_em_cmp),
7562306a36Sopenharmony_ci	.match	  = em_cmp_match,
7662306a36Sopenharmony_ci	.owner	  = THIS_MODULE,
7762306a36Sopenharmony_ci	.link	  = LIST_HEAD_INIT(em_cmp_ops.link)
7862306a36Sopenharmony_ci};
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_cistatic int __init init_em_cmp(void)
8162306a36Sopenharmony_ci{
8262306a36Sopenharmony_ci	return tcf_em_register(&em_cmp_ops);
8362306a36Sopenharmony_ci}
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_cistatic void __exit exit_em_cmp(void)
8662306a36Sopenharmony_ci{
8762306a36Sopenharmony_ci	tcf_em_unregister(&em_cmp_ops);
8862306a36Sopenharmony_ci}
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ciMODULE_LICENSE("GPL");
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_cimodule_init(init_em_cmp);
9362306a36Sopenharmony_cimodule_exit(exit_em_cmp);
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ciMODULE_ALIAS_TCF_EMATCH(TCF_EM_CMP);
96