1/*
2 * Kernel module to match cone target.
3 *
4 * Copyright (C) 2010, Broadcom Corporation. All Rights Reserved.
5 *
6 * Permission to use, copy, modify, and/or distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
13 * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
15 * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
16 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 *
18 * $Id: ipt_cone.c,v 1.2 2010/07/12 23:55:51 Exp $
19 */
20#include <linux/module.h>
21#include <linux/types.h>
22#include <linux/timer.h>
23#include <linux/skbuff.h>
24#include <linux/vmalloc.h>
25#include <net/ip.h>
26#include <linux/udp.h>
27#include <net/netfilter/nf_nat_rule.h>
28#include <linux/netfilter_ipv4.h>
29#include <linux/netfilter/x_tables.h>
30
31#define DEBUGP(format, args...)
32
33static DEFINE_RWLOCK(cone_lock);
34
35/* Calculated at init based on memory size */
36static unsigned int ipt_cone_htable_size;
37static struct list_head *bycone;
38
39static inline size_t
40hash_by_cone(__be32 ip, __be16 port, u_int8_t protonum)
41{
42	/* Original src, to ensure we map it consistently if poss. */
43	return (ip + port + protonum) % ipt_cone_htable_size;
44}
45
46static inline int
47cone_cmp(const struct nf_conn *ct,
48	const struct nf_conntrack_tuple *tuple)
49{
50	return (ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.protonum
51		== tuple->dst.protonum &&
52		ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.u3.ip
53		== tuple->dst.u3.ip &&
54		ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.u.udp.port
55		== tuple->dst.u.udp.port);
56}
57
58/* Only called for SRC manip */
59static struct nf_conn *
60find_appropriate_cone(const struct nf_conntrack_tuple *tuple)
61{
62	unsigned int h = hash_by_cone(tuple->dst.u3.ip,
63		tuple->dst.u.udp.port, tuple->dst.protonum);
64
65	struct nf_conn_nat *nat;
66	struct nf_conn *ct;
67
68	read_lock_bh(&cone_lock);
69	list_for_each_entry(nat, &bycone[h], info.bycone) {
70		ct = (struct nf_conn *)((char *)nat - offsetof(struct nf_conn, data));
71		if (cone_cmp(ct, tuple)) {
72			read_unlock_bh(&cone_lock);
73			return ct;
74		}
75	}
76	read_unlock_bh(&cone_lock);
77	return 0;
78}
79
80void
81ipt_cone_place_in_hashes(struct nf_conn *ct)
82{
83	struct nf_conn_nat *nat = nfct_nat(ct);
84	struct nf_conntrack_tuple *tuple = &ct->tuplehash[IP_CT_DIR_REPLY].tuple;
85
86	/* Make sure it's IPV4 */
87	if (tuple->src.l3num != PF_INET)
88		return;
89
90	if ((nat->info.nat_type & NFC_IP_CONE_NAT)) {
91		if (!find_appropriate_cone(tuple)) {
92			unsigned int conehash = hash_by_cone(
93				tuple->dst.u3.ip, tuple->dst.u.udp.port, tuple->dst.protonum);
94
95			write_lock_bh(&cone_lock);
96			list_add(&nat->info.bycone, &bycone[conehash]);
97			write_unlock_bh(&cone_lock);
98		}
99	}
100}
101
102void
103ipt_cone_cleanup_conntrack(struct nf_conn_nat *nat)
104{
105	if (nat->info.bycone.next) {
106		write_lock_bh(&cone_lock);
107		list_del(&nat->info.bycone);
108		write_unlock_bh(&cone_lock);
109	}
110}
111
112unsigned int
113ipt_cone_target(struct sk_buff **pskb,
114	unsigned int hooknum,
115	const struct net_device *in,
116	const struct net_device *out,
117	const struct xt_target *target,
118	const void *targinfo)
119{
120	struct nf_conn *ct;
121	struct nf_conn_nat *nat;
122	enum ip_conntrack_info ctinfo;
123	struct nf_nat_range newrange;
124	const struct nf_nat_multi_range_compat *mr = targinfo;
125	__be32 newdst;
126	__be16 newport;
127	struct nf_conntrack_tuple *tuple;
128	struct nf_conn *cone;
129
130
131	/* Care about only new created one */
132	ct = nf_ct_get(*pskb, &ctinfo);
133	if (ct == 0 || (ctinfo != IP_CT_NEW && ctinfo != IP_CT_RELATED))
134		return XT_CONTINUE;
135
136	nat = nfct_nat(ct);
137
138	/* As a matter of fact, CONE NAT should only apply on IPPROTO_UDP */
139	tuple = &ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple;
140	if (tuple->src.l3num != PF_INET || tuple->dst.protonum != IPPROTO_UDP)
141		return XT_CONTINUE;
142
143	/* Handle forwarding from WAN to LAN */
144	if (hooknum == NF_IP_FORWARD) {
145		if (nat->info.nat_type & NFC_IP_CONE_NAT_ALTERED)
146			return NF_ACCEPT;
147		else
148			return XT_CONTINUE;
149	}
150
151	/* Make sure it is pre routing */
152	if (hooknum != NF_IP_PRE_ROUTING)
153		return XT_CONTINUE;
154
155	if (nat->info.nat_type & NFC_IP_CONE_NAT_ALTERED)
156		return NF_ACCEPT;
157
158	/* Get cone dst */
159	cone = find_appropriate_cone(tuple);
160	if (cone == NULL)
161		return XT_CONTINUE;
162
163	/* Mark it's a CONE_NAT_TYPE, so NF_IP_FORWARD can accept it */
164	nat->info.nat_type |= NFC_IP_CONE_NAT_ALTERED;
165
166	/* Setup new dst ip and port */
167	newdst = cone->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.u3.ip;
168	newport = cone->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.u.udp.port;
169
170	/* Transfer from original range. */
171	newrange = ((struct nf_nat_range)
172		{ mr->range[0].flags | IP_NAT_RANGE_MAP_IPS,
173		newdst, newdst,
174		{newport}, {newport} });
175
176	/* Hand modified range to generic setup. */
177	return nf_nat_setup_info(ct, &newrange, hooknum);
178}
179
180/* Init/cleanup */
181static int __init ipt_cone_init(void)
182{
183	size_t i;
184
185	/* Leave them the same for the moment. */
186	ipt_cone_htable_size = nf_conntrack_htable_size;
187
188	/* vmalloc for hash table */
189	bycone = vmalloc(sizeof(struct list_head) * ipt_cone_htable_size);
190	if (!bycone)
191		return -ENOMEM;
192
193	for (i = 0; i < ipt_cone_htable_size; i++) {
194		INIT_LIST_HEAD(&bycone[i]);
195	}
196
197	return 0;
198}
199
200static void __exit ipt_cone_cleanup(void)
201{
202	vfree(bycone);
203}
204
205module_init(ipt_cone_init);
206module_exit(ipt_cone_cleanup);
207MODULE_LICENSE("Proprietary");
208