1/*
2 * iptables module for DCCP protocol header matching
3 *
4 * (C) 2005 by Harald Welte <laforge@netfilter.org>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 2 as
8 * published by the Free Software Foundation.
9 */
10
11#include <linux/module.h>
12#include <linux/skbuff.h>
13#include <linux/spinlock.h>
14#include <net/ip.h>
15#include <linux/dccp.h>
16
17#include <linux/netfilter/x_tables.h>
18#include <linux/netfilter/xt_dccp.h>
19
20#include <linux/netfilter_ipv4/ip_tables.h>
21#include <linux/netfilter_ipv6/ip6_tables.h>
22
23MODULE_LICENSE("GPL");
24MODULE_AUTHOR("Harald Welte <laforge@netfilter.org>");
25MODULE_DESCRIPTION("Match for DCCP protocol packets");
26MODULE_ALIAS("ipt_dccp");
27
28#define DCCHECK(cond, option, flag, invflag) (!((flag) & (option)) \
29				  || (!!((invflag) & (option)) ^ (cond)))
30
31static unsigned char *dccp_optbuf;
32static DEFINE_SPINLOCK(dccp_buflock);
33
34static inline int
35dccp_find_option(u_int8_t option,
36		 const struct sk_buff *skb,
37		 unsigned int protoff,
38		 const struct dccp_hdr *dh,
39		 int *hotdrop)
40{
41	/* tcp.doff is only 4 bits, ie. max 15 * 4 bytes */
42	unsigned char *op;
43	unsigned int optoff = __dccp_hdr_len(dh);
44	unsigned int optlen = dh->dccph_doff*4 - __dccp_hdr_len(dh);
45	unsigned int i;
46
47	if (dh->dccph_doff * 4 < __dccp_hdr_len(dh)) {
48		*hotdrop = 1;
49		return 0;
50	}
51
52	if (!optlen)
53		return 0;
54
55	spin_lock_bh(&dccp_buflock);
56	op = skb_header_pointer(skb, protoff + optoff, optlen, dccp_optbuf);
57	if (op == NULL) {
58		/* If we don't have the whole header, drop packet. */
59		spin_unlock_bh(&dccp_buflock);
60		*hotdrop = 1;
61		return 0;
62	}
63
64	for (i = 0; i < optlen; ) {
65		if (op[i] == option) {
66			spin_unlock_bh(&dccp_buflock);
67			return 1;
68		}
69
70		if (op[i] < 2)
71			i++;
72		else
73			i += op[i+1]?:1;
74	}
75
76	spin_unlock_bh(&dccp_buflock);
77	return 0;
78}
79
80
81static inline int
82match_types(const struct dccp_hdr *dh, u_int16_t typemask)
83{
84	return (typemask & (1 << dh->dccph_type));
85}
86
87static inline int
88match_option(u_int8_t option, const struct sk_buff *skb, unsigned int protoff,
89	     const struct dccp_hdr *dh, int *hotdrop)
90{
91	return dccp_find_option(option, skb, protoff, dh, hotdrop);
92}
93
94static int
95match(const struct sk_buff *skb,
96      const struct net_device *in,
97      const struct net_device *out,
98      const struct xt_match *match,
99      const void *matchinfo,
100      int offset,
101      unsigned int protoff,
102      int *hotdrop)
103{
104	const struct xt_dccp_info *info = matchinfo;
105	struct dccp_hdr _dh, *dh;
106
107	if (offset)
108		return 0;
109
110	dh = skb_header_pointer(skb, protoff, sizeof(_dh), &_dh);
111	if (dh == NULL) {
112		*hotdrop = 1;
113		return 0;
114	}
115
116	return  DCCHECK(((ntohs(dh->dccph_sport) >= info->spts[0])
117			&& (ntohs(dh->dccph_sport) <= info->spts[1])),
118			XT_DCCP_SRC_PORTS, info->flags, info->invflags)
119		&& DCCHECK(((ntohs(dh->dccph_dport) >= info->dpts[0])
120			&& (ntohs(dh->dccph_dport) <= info->dpts[1])),
121			XT_DCCP_DEST_PORTS, info->flags, info->invflags)
122		&& DCCHECK(match_types(dh, info->typemask),
123			   XT_DCCP_TYPE, info->flags, info->invflags)
124		&& DCCHECK(match_option(info->option, skb, protoff, dh,
125					hotdrop),
126			   XT_DCCP_OPTION, info->flags, info->invflags);
127}
128
129static int
130checkentry(const char *tablename,
131	   const void *inf,
132	   const struct xt_match *match,
133	   void *matchinfo,
134	   unsigned int hook_mask)
135{
136	const struct xt_dccp_info *info = matchinfo;
137
138	return !(info->flags & ~XT_DCCP_VALID_FLAGS)
139		&& !(info->invflags & ~XT_DCCP_VALID_FLAGS)
140		&& !(info->invflags & ~info->flags);
141}
142
143static struct xt_match xt_dccp_match[] = {
144	{
145		.name 		= "dccp",
146		.family		= AF_INET,
147		.checkentry	= checkentry,
148		.match		= match,
149		.matchsize	= sizeof(struct xt_dccp_info),
150		.proto		= IPPROTO_DCCP,
151		.me 		= THIS_MODULE,
152	},
153	{
154		.name 		= "dccp",
155		.family		= AF_INET6,
156		.checkentry	= checkentry,
157		.match		= match,
158		.matchsize	= sizeof(struct xt_dccp_info),
159		.proto		= IPPROTO_DCCP,
160		.me 		= THIS_MODULE,
161	},
162};
163
164static int __init xt_dccp_init(void)
165{
166	int ret;
167
168	/* doff is 8 bits, so the maximum option size is (4*256).  Don't put
169	 * this in BSS since DaveM is worried about locked TLB's for kernel
170	 * BSS. */
171	dccp_optbuf = kmalloc(256 * 4, GFP_KERNEL);
172	if (!dccp_optbuf)
173		return -ENOMEM;
174	ret = xt_register_matches(xt_dccp_match, ARRAY_SIZE(xt_dccp_match));
175	if (ret)
176		goto out_kfree;
177	return ret;
178
179out_kfree:
180	kfree(dccp_optbuf);
181	return ret;
182}
183
184static void __exit xt_dccp_fini(void)
185{
186	xt_unregister_matches(xt_dccp_match, ARRAY_SIZE(xt_dccp_match));
187	kfree(dccp_optbuf);
188}
189
190module_init(xt_dccp_init);
191module_exit(xt_dccp_fini);
192