xref: /linux/net/netfilter/xt_ecn.c (revision 2da68a77)
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * Xtables module for matching the value of the IPv4/IPv6 and TCP ECN bits
4  *
5  * (C) 2002 by Harald Welte <laforge@gnumonks.org>
6  * (C) 2011 Patrick McHardy <kaber@trash.net>
7  */
8 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
9 #include <linux/in.h>
10 #include <linux/ip.h>
11 #include <net/ip.h>
12 #include <linux/module.h>
13 #include <linux/skbuff.h>
14 #include <linux/tcp.h>
15 
16 #include <linux/netfilter/x_tables.h>
17 #include <linux/netfilter/xt_ecn.h>
18 #include <linux/netfilter_ipv4/ip_tables.h>
19 #include <linux/netfilter_ipv6/ip6_tables.h>
20 
21 MODULE_AUTHOR("Harald Welte <laforge@netfilter.org>");
22 MODULE_DESCRIPTION("Xtables: Explicit Congestion Notification (ECN) flag match");
23 MODULE_LICENSE("GPL");
24 MODULE_ALIAS("ipt_ecn");
25 MODULE_ALIAS("ip6t_ecn");
26 
27 static bool match_tcp(const struct sk_buff *skb, struct xt_action_param *par)
28 {
29 	const struct xt_ecn_info *einfo = par->matchinfo;
30 	struct tcphdr _tcph;
31 	const struct tcphdr *th;
32 
33 	/* In practice, TCP match does this, so can't fail.  But let's
34 	 * be good citizens.
35 	 */
36 	th = skb_header_pointer(skb, par->thoff, sizeof(_tcph), &_tcph);
37 	if (th == NULL)
38 		return false;
39 
40 	if (einfo->operation & XT_ECN_OP_MATCH_ECE) {
41 		if (einfo->invert & XT_ECN_OP_MATCH_ECE) {
42 			if (th->ece == 1)
43 				return false;
44 		} else {
45 			if (th->ece == 0)
46 				return false;
47 		}
48 	}
49 
50 	if (einfo->operation & XT_ECN_OP_MATCH_CWR) {
51 		if (einfo->invert & XT_ECN_OP_MATCH_CWR) {
52 			if (th->cwr == 1)
53 				return false;
54 		} else {
55 			if (th->cwr == 0)
56 				return false;
57 		}
58 	}
59 
60 	return true;
61 }
62 
63 static inline bool match_ip(const struct sk_buff *skb,
64 			    const struct xt_ecn_info *einfo)
65 {
66 	return ((ip_hdr(skb)->tos & XT_ECN_IP_MASK) == einfo->ip_ect) ^
67 	       !!(einfo->invert & XT_ECN_OP_MATCH_IP);
68 }
69 
70 static bool ecn_mt4(const struct sk_buff *skb, struct xt_action_param *par)
71 {
72 	const struct xt_ecn_info *info = par->matchinfo;
73 
74 	if (info->operation & XT_ECN_OP_MATCH_IP && !match_ip(skb, info))
75 		return false;
76 
77 	if (info->operation & (XT_ECN_OP_MATCH_ECE | XT_ECN_OP_MATCH_CWR) &&
78 	    !match_tcp(skb, par))
79 		return false;
80 
81 	return true;
82 }
83 
84 static int ecn_mt_check4(const struct xt_mtchk_param *par)
85 {
86 	const struct xt_ecn_info *info = par->matchinfo;
87 	const struct ipt_ip *ip = par->entryinfo;
88 
89 	if (info->operation & XT_ECN_OP_MATCH_MASK)
90 		return -EINVAL;
91 
92 	if (info->invert & XT_ECN_OP_MATCH_MASK)
93 		return -EINVAL;
94 
95 	if (info->operation & (XT_ECN_OP_MATCH_ECE | XT_ECN_OP_MATCH_CWR) &&
96 	    (ip->proto != IPPROTO_TCP || ip->invflags & IPT_INV_PROTO)) {
97 		pr_info_ratelimited("cannot match TCP bits for non-tcp packets\n");
98 		return -EINVAL;
99 	}
100 
101 	return 0;
102 }
103 
104 static inline bool match_ipv6(const struct sk_buff *skb,
105 			      const struct xt_ecn_info *einfo)
106 {
107 	return (((ipv6_hdr(skb)->flow_lbl[0] >> 4) & XT_ECN_IP_MASK) ==
108 	        einfo->ip_ect) ^
109 	       !!(einfo->invert & XT_ECN_OP_MATCH_IP);
110 }
111 
112 static bool ecn_mt6(const struct sk_buff *skb, struct xt_action_param *par)
113 {
114 	const struct xt_ecn_info *info = par->matchinfo;
115 
116 	if (info->operation & XT_ECN_OP_MATCH_IP && !match_ipv6(skb, info))
117 		return false;
118 
119 	if (info->operation & (XT_ECN_OP_MATCH_ECE | XT_ECN_OP_MATCH_CWR) &&
120 	    !match_tcp(skb, par))
121 		return false;
122 
123 	return true;
124 }
125 
126 static int ecn_mt_check6(const struct xt_mtchk_param *par)
127 {
128 	const struct xt_ecn_info *info = par->matchinfo;
129 	const struct ip6t_ip6 *ip = par->entryinfo;
130 
131 	if (info->operation & XT_ECN_OP_MATCH_MASK)
132 		return -EINVAL;
133 
134 	if (info->invert & XT_ECN_OP_MATCH_MASK)
135 		return -EINVAL;
136 
137 	if (info->operation & (XT_ECN_OP_MATCH_ECE | XT_ECN_OP_MATCH_CWR) &&
138 	    (ip->proto != IPPROTO_TCP || ip->invflags & IP6T_INV_PROTO)) {
139 		pr_info_ratelimited("cannot match TCP bits for non-tcp packets\n");
140 		return -EINVAL;
141 	}
142 
143 	return 0;
144 }
145 
146 static struct xt_match ecn_mt_reg[] __read_mostly = {
147 	{
148 		.name		= "ecn",
149 		.family		= NFPROTO_IPV4,
150 		.match		= ecn_mt4,
151 		.matchsize	= sizeof(struct xt_ecn_info),
152 		.checkentry	= ecn_mt_check4,
153 		.me		= THIS_MODULE,
154 	},
155 	{
156 		.name		= "ecn",
157 		.family		= NFPROTO_IPV6,
158 		.match		= ecn_mt6,
159 		.matchsize	= sizeof(struct xt_ecn_info),
160 		.checkentry	= ecn_mt_check6,
161 		.me		= THIS_MODULE,
162 	},
163 };
164 
165 static int __init ecn_mt_init(void)
166 {
167 	return xt_register_matches(ecn_mt_reg, ARRAY_SIZE(ecn_mt_reg));
168 }
169 
170 static void __exit ecn_mt_exit(void)
171 {
172 	xt_unregister_matches(ecn_mt_reg, ARRAY_SIZE(ecn_mt_reg));
173 }
174 
175 module_init(ecn_mt_init);
176 module_exit(ecn_mt_exit);
177