summaryrefslogtreecommitdiff
path: root/net/netfilter/xt_ecn.c
blob: b96e8203ac54982e562ea083f390eff5d213477e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Xtables module for matching the value of the IPv4/IPv6 and TCP ECN bits
 *
 * (C) 2002 by Harald Welte <laforge@gnumonks.org>
 * (C) 2011 Patrick McHardy <kaber@trash.net>
 */
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/in.h>
#include <linux/ip.h>
#include <net/ip.h>
#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/tcp.h>

#include <linux/netfilter/x_tables.h>
#include <linux/netfilter/xt_ecn.h>
#include <linux/netfilter_ipv4/ip_tables.h>
#include <linux/netfilter_ipv6/ip6_tables.h>

MODULE_AUTHOR("Harald Welte <laforge@netfilter.org>");
MODULE_DESCRIPTION("Xtables: Explicit Congestion Notification (ECN) flag match");
MODULE_LICENSE("GPL");
MODULE_ALIAS("ipt_ecn");
MODULE_ALIAS("ip6t_ecn");

static bool match_tcp(const struct sk_buff *skb, struct xt_action_param *par)
{
	const struct xt_ecn_info *einfo = par->matchinfo;
	struct tcphdr _tcph;
	const struct tcphdr *th;

	/* In practice, TCP match does this, so can't fail.  But let's
	 * be good citizens.
	 */
	th = skb_header_pointer(skb, par->thoff, sizeof(_tcph), &_tcph);
	if (th == NULL)
		return false;

	if (einfo->operation & XT_ECN_OP_MATCH_ECE) {
		if (einfo->invert & XT_ECN_OP_MATCH_ECE) {
			if (th->ece == 1)
				return false;
		} else {
			if (th->ece == 0)
				return false;
		}
	}

	if (einfo->operation & XT_ECN_OP_MATCH_CWR) {
		if (einfo->invert & XT_ECN_OP_MATCH_CWR) {
			if (th->cwr == 1)
				return false;
		} else {
			if (th->cwr == 0)
				return false;
		}
	}

	return true;
}

static inline bool match_ip(const struct sk_buff *skb,
			    const struct xt_ecn_info *einfo)
{
	return ((ip_hdr(skb)->tos & XT_ECN_IP_MASK) == einfo->ip_ect) ^
	       !!(einfo->invert & XT_ECN_OP_MATCH_IP);
}

static bool ecn_mt4(const struct sk_buff *skb, struct xt_action_param *par)
{
	const struct xt_ecn_info *info = par->matchinfo;

	if (info->operation & XT_ECN_OP_MATCH_IP && !match_ip(skb, info))
		return false;

	if (info->operation & (XT_ECN_OP_MATCH_ECE | XT_ECN_OP_MATCH_CWR) &&
	    !match_tcp(skb, par))
		return false;

	return true;
}

static int ecn_mt_check4(const struct xt_mtchk_param *par)
{
	const struct xt_ecn_info *info = par->matchinfo;
	const struct ipt_ip *ip = par->entryinfo;

	if (info->operation & XT_ECN_OP_MATCH_MASK)
		return -EINVAL;

	if (info->invert & XT_ECN_OP_MATCH_MASK)
		return -EINVAL;

	if (info->operation & (XT_ECN_OP_MATCH_ECE | XT_ECN_OP_MATCH_CWR) &&
	    (ip->proto != IPPROTO_TCP || ip->invflags & IPT_INV_PROTO)) {
		pr_info_ratelimited("cannot match TCP bits for non-tcp packets\n");
		return -EINVAL;
	}

	return 0;
}

static inline bool match_ipv6(const struct sk_buff *skb,
			      const struct xt_ecn_info *einfo)
{
	return (((ipv6_hdr(skb)->flow_lbl[0] >> 4) & XT_ECN_IP_MASK) ==
	        einfo->ip_ect) ^
	       !!(einfo->invert & XT_ECN_OP_MATCH_IP);
}

static bool ecn_mt6(const struct sk_buff *skb, struct xt_action_param *par)
{
	const struct xt_ecn_info *info = par->matchinfo;

	if (info->operation & XT_ECN_OP_MATCH_IP && !match_ipv6(skb, info))
		return false;

	if (info->operation & (XT_ECN_OP_MATCH_ECE | XT_ECN_OP_MATCH_CWR) &&
	    !match_tcp(skb, par))
		return false;

	return true;
}

static int ecn_mt_check6(const struct xt_mtchk_param *par)
{
	const struct xt_ecn_info *info = par->matchinfo;
	const struct ip6t_ip6 *ip = par->entryinfo;

	if (info->operation & XT_ECN_OP_MATCH_MASK)
		return -EINVAL;

	if (info->invert & XT_ECN_OP_MATCH_MASK)
		return -EINVAL;

	if (info->operation & (XT_ECN_OP_MATCH_ECE | XT_ECN_OP_MATCH_CWR) &&
	    (ip->proto != IPPROTO_TCP || ip->invflags & IP6T_INV_PROTO)) {
		pr_info_ratelimited("cannot match TCP bits for non-tcp packets\n");
		return -EINVAL;
	}

	return 0;
}

static struct xt_match ecn_mt_reg[] __read_mostly = {
	{
		.name		= "ecn",
		.family		= NFPROTO_IPV4,
		.match		= ecn_mt4,
		.matchsize	= sizeof(struct xt_ecn_info),
		.checkentry	= ecn_mt_check4,
		.me		= THIS_MODULE,
	},
	{
		.name		= "ecn",
		.family		= NFPROTO_IPV6,
		.match		= ecn_mt6,
		.matchsize	= sizeof(struct xt_ecn_info),
		.checkentry	= ecn_mt_check6,
		.me		= THIS_MODULE,
	},
};

static int __init ecn_mt_init(void)
{
	return xt_register_matches(ecn_mt_reg, ARRAY_SIZE(ecn_mt_reg));
}

static void __exit ecn_mt_exit(void)
{
	xt_unregister_matches(ecn_mt_reg, ARRAY_SIZE(ecn_mt_reg));
}

module_init(ecn_mt_init);
module_exit(ecn_mt_exit);