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