diff options
author | Patrick McHardy <kaber@trash.net> | 2006-12-03 01:09:41 -0500 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2006-12-03 01:09:41 -0500 |
commit | f09943fefe6b702e40893d35b4f10fd1064037fe (patch) | |
tree | b170d046ecf0642471bb3c55d8e1f316fe9e5ddc /net | |
parent | 92703eee4ccde3c55ee067a89c373e8a51a8adf9 (diff) |
[NETFILTER]: nf_conntrack/nf_nat: add PPTP helper port
Add nf_conntrack port of the PPtP conntrack/NAT helper. Since there seems
to be no IPv6-capable PPtP implementation the helper only support IPv4.
Signed-off-by: Patrick McHardy <kaber@trash.net>
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'net')
-rw-r--r-- | net/ipv4/netfilter/Kconfig | 10 | ||||
-rw-r--r-- | net/ipv4/netfilter/Makefile | 4 | ||||
-rw-r--r-- | net/ipv4/netfilter/nf_nat_pptp.c | 315 | ||||
-rw-r--r-- | net/ipv4/netfilter/nf_nat_proto_gre.c | 179 | ||||
-rw-r--r-- | net/netfilter/Kconfig | 23 | ||||
-rw-r--r-- | net/netfilter/Makefile | 2 | ||||
-rw-r--r-- | net/netfilter/nf_conntrack_core.c | 4 | ||||
-rw-r--r-- | net/netfilter/nf_conntrack_pptp.c | 607 | ||||
-rw-r--r-- | net/netfilter/nf_conntrack_proto_gre.c | 305 |
9 files changed, 1449 insertions, 0 deletions
diff --git a/net/ipv4/netfilter/Kconfig b/net/ipv4/netfilter/Kconfig index 4555f721dfc1..c3327ac024de 100644 --- a/net/ipv4/netfilter/Kconfig +++ b/net/ipv4/netfilter/Kconfig | |||
@@ -484,6 +484,10 @@ config IP_NF_NAT_SNMP_BASIC | |||
484 | # <expr> '&&' <expr> (6) | 484 | # <expr> '&&' <expr> (6) |
485 | # | 485 | # |
486 | # (6) Returns the result of min(/expr/, /expr/). | 486 | # (6) Returns the result of min(/expr/, /expr/). |
487 | config NF_NAT_PROTO_GRE | ||
488 | tristate | ||
489 | depends on NF_NAT && NF_CT_PROTO_GRE | ||
490 | |||
487 | config IP_NF_NAT_FTP | 491 | config IP_NF_NAT_FTP |
488 | tristate | 492 | tristate |
489 | depends on IP_NF_IPTABLES && IP_NF_CONNTRACK && IP_NF_NAT | 493 | depends on IP_NF_IPTABLES && IP_NF_CONNTRACK && IP_NF_NAT |
@@ -528,6 +532,12 @@ config IP_NF_NAT_PPTP | |||
528 | default IP_NF_NAT if IP_NF_PPTP=y | 532 | default IP_NF_NAT if IP_NF_PPTP=y |
529 | default m if IP_NF_PPTP=m | 533 | default m if IP_NF_PPTP=m |
530 | 534 | ||
535 | config NF_NAT_PPTP | ||
536 | tristate | ||
537 | depends on IP_NF_IPTABLES && NF_CONNTRACK && NF_NAT | ||
538 | default NF_NAT && NF_CONNTRACK_PPTP | ||
539 | select NF_NAT_PROTO_GRE | ||
540 | |||
531 | config IP_NF_NAT_H323 | 541 | config IP_NF_NAT_H323 |
532 | tristate | 542 | tristate |
533 | depends on IP_NF_IPTABLES!=n && IP_NF_CONNTRACK!=n && IP_NF_NAT!=n | 543 | depends on IP_NF_IPTABLES!=n && IP_NF_CONNTRACK!=n && IP_NF_NAT!=n |
diff --git a/net/ipv4/netfilter/Makefile b/net/ipv4/netfilter/Makefile index 56733c370327..ef33ff2cdda9 100644 --- a/net/ipv4/netfilter/Makefile +++ b/net/ipv4/netfilter/Makefile | |||
@@ -54,6 +54,10 @@ obj-$(CONFIG_NF_NAT_AMANDA) += nf_nat_amanda.o | |||
54 | obj-$(CONFIG_NF_NAT_FTP) += nf_nat_ftp.o | 54 | obj-$(CONFIG_NF_NAT_FTP) += nf_nat_ftp.o |
55 | obj-$(CONFIG_NF_NAT_H323) += nf_nat_h323.o | 55 | obj-$(CONFIG_NF_NAT_H323) += nf_nat_h323.o |
56 | obj-$(CONFIG_NF_NAT_IRC) += nf_nat_irc.o | 56 | obj-$(CONFIG_NF_NAT_IRC) += nf_nat_irc.o |
57 | obj-$(CONFIG_NF_NAT_PPTP) += nf_nat_pptp.o | ||
58 | |||
59 | # NAT protocols (nf_nat) | ||
60 | obj-$(CONFIG_NF_NAT_PROTO_GRE) += nf_nat_proto_gre.o | ||
57 | 61 | ||
58 | # generic IP tables | 62 | # generic IP tables |
59 | obj-$(CONFIG_IP_NF_IPTABLES) += ip_tables.o | 63 | obj-$(CONFIG_IP_NF_IPTABLES) += ip_tables.o |
diff --git a/net/ipv4/netfilter/nf_nat_pptp.c b/net/ipv4/netfilter/nf_nat_pptp.c new file mode 100644 index 000000000000..0ae45b79a4eb --- /dev/null +++ b/net/ipv4/netfilter/nf_nat_pptp.c | |||
@@ -0,0 +1,315 @@ | |||
1 | /* | ||
2 | * nf_nat_pptp.c | ||
3 | * | ||
4 | * NAT support for PPTP (Point to Point Tunneling Protocol). | ||
5 | * PPTP is a a protocol for creating virtual private networks. | ||
6 | * It is a specification defined by Microsoft and some vendors | ||
7 | * working with Microsoft. PPTP is built on top of a modified | ||
8 | * version of the Internet Generic Routing Encapsulation Protocol. | ||
9 | * GRE is defined in RFC 1701 and RFC 1702. Documentation of | ||
10 | * PPTP can be found in RFC 2637 | ||
11 | * | ||
12 | * (C) 2000-2005 by Harald Welte <laforge@gnumonks.org> | ||
13 | * | ||
14 | * Development of this code funded by Astaro AG (http://www.astaro.com/) | ||
15 | * | ||
16 | * TODO: - NAT to a unique tuple, not to TCP source port | ||
17 | * (needs netfilter tuple reservation) | ||
18 | */ | ||
19 | |||
20 | #include <linux/module.h> | ||
21 | #include <linux/tcp.h> | ||
22 | |||
23 | #include <net/netfilter/nf_nat.h> | ||
24 | #include <net/netfilter/nf_nat_helper.h> | ||
25 | #include <net/netfilter/nf_nat_rule.h> | ||
26 | #include <net/netfilter/nf_conntrack_helper.h> | ||
27 | #include <net/netfilter/nf_conntrack_expect.h> | ||
28 | #include <linux/netfilter/nf_conntrack_proto_gre.h> | ||
29 | #include <linux/netfilter/nf_conntrack_pptp.h> | ||
30 | |||
31 | #define NF_NAT_PPTP_VERSION "3.0" | ||
32 | |||
33 | #define REQ_CID(req, off) (*(__be16 *)((char *)(req) + (off))) | ||
34 | |||
35 | MODULE_LICENSE("GPL"); | ||
36 | MODULE_AUTHOR("Harald Welte <laforge@gnumonks.org>"); | ||
37 | MODULE_DESCRIPTION("Netfilter NAT helper module for PPTP"); | ||
38 | MODULE_ALIAS("ip_nat_pptp"); | ||
39 | |||
40 | #if 0 | ||
41 | extern const char *pptp_msg_name[]; | ||
42 | #define DEBUGP(format, args...) printk(KERN_DEBUG "%s:%s: " format, __FILE__, \ | ||
43 | __FUNCTION__, ## args) | ||
44 | #else | ||
45 | #define DEBUGP(format, args...) | ||
46 | #endif | ||
47 | |||
48 | static void pptp_nat_expected(struct nf_conn *ct, | ||
49 | struct nf_conntrack_expect *exp) | ||
50 | { | ||
51 | struct nf_conn *master = ct->master; | ||
52 | struct nf_conntrack_expect *other_exp; | ||
53 | struct nf_conntrack_tuple t; | ||
54 | struct nf_ct_pptp_master *ct_pptp_info; | ||
55 | struct nf_nat_pptp *nat_pptp_info; | ||
56 | struct ip_nat_range range; | ||
57 | |||
58 | ct_pptp_info = &nfct_help(master)->help.ct_pptp_info; | ||
59 | nat_pptp_info = &nfct_nat(master)->help.nat_pptp_info; | ||
60 | |||
61 | /* And here goes the grand finale of corrosion... */ | ||
62 | if (exp->dir == IP_CT_DIR_ORIGINAL) { | ||
63 | DEBUGP("we are PNS->PAC\n"); | ||
64 | /* therefore, build tuple for PAC->PNS */ | ||
65 | t.src.l3num = AF_INET; | ||
66 | t.src.u3.ip = master->tuplehash[!exp->dir].tuple.src.u3.ip; | ||
67 | t.src.u.gre.key = ct_pptp_info->pac_call_id; | ||
68 | t.dst.u3.ip = master->tuplehash[!exp->dir].tuple.dst.u3.ip; | ||
69 | t.dst.u.gre.key = ct_pptp_info->pns_call_id; | ||
70 | t.dst.protonum = IPPROTO_GRE; | ||
71 | } else { | ||
72 | DEBUGP("we are PAC->PNS\n"); | ||
73 | /* build tuple for PNS->PAC */ | ||
74 | t.src.l3num = AF_INET; | ||
75 | t.src.u3.ip = master->tuplehash[exp->dir].tuple.src.u3.ip; | ||
76 | t.src.u.gre.key = nat_pptp_info->pns_call_id; | ||
77 | t.dst.u3.ip = master->tuplehash[exp->dir].tuple.dst.u3.ip; | ||
78 | t.dst.u.gre.key = nat_pptp_info->pac_call_id; | ||
79 | t.dst.protonum = IPPROTO_GRE; | ||
80 | } | ||
81 | |||
82 | DEBUGP("trying to unexpect other dir: "); | ||
83 | NF_CT_DUMP_TUPLE(&t); | ||
84 | other_exp = nf_conntrack_expect_find_get(&t); | ||
85 | if (other_exp) { | ||
86 | nf_conntrack_unexpect_related(other_exp); | ||
87 | nf_conntrack_expect_put(other_exp); | ||
88 | DEBUGP("success\n"); | ||
89 | } else { | ||
90 | DEBUGP("not found!\n"); | ||
91 | } | ||
92 | |||
93 | /* This must be a fresh one. */ | ||
94 | BUG_ON(ct->status & IPS_NAT_DONE_MASK); | ||
95 | |||
96 | /* Change src to where master sends to */ | ||
97 | range.flags = IP_NAT_RANGE_MAP_IPS; | ||
98 | range.min_ip = range.max_ip | ||
99 | = ct->master->tuplehash[!exp->dir].tuple.dst.u3.ip; | ||
100 | if (exp->dir == IP_CT_DIR_ORIGINAL) { | ||
101 | range.flags |= IP_NAT_RANGE_PROTO_SPECIFIED; | ||
102 | range.min = range.max = exp->saved_proto; | ||
103 | } | ||
104 | /* hook doesn't matter, but it has to do source manip */ | ||
105 | nf_nat_setup_info(ct, &range, NF_IP_POST_ROUTING); | ||
106 | |||
107 | /* For DST manip, map port here to where it's expected. */ | ||
108 | range.flags = IP_NAT_RANGE_MAP_IPS; | ||
109 | range.min_ip = range.max_ip | ||
110 | = ct->master->tuplehash[!exp->dir].tuple.src.u3.ip; | ||
111 | if (exp->dir == IP_CT_DIR_REPLY) { | ||
112 | range.flags |= IP_NAT_RANGE_PROTO_SPECIFIED; | ||
113 | range.min = range.max = exp->saved_proto; | ||
114 | } | ||
115 | /* hook doesn't matter, but it has to do destination manip */ | ||
116 | nf_nat_setup_info(ct, &range, NF_IP_PRE_ROUTING); | ||
117 | } | ||
118 | |||
119 | /* outbound packets == from PNS to PAC */ | ||
120 | static int | ||
121 | pptp_outbound_pkt(struct sk_buff **pskb, | ||
122 | struct nf_conn *ct, | ||
123 | enum ip_conntrack_info ctinfo, | ||
124 | struct PptpControlHeader *ctlh, | ||
125 | union pptp_ctrl_union *pptpReq) | ||
126 | |||
127 | { | ||
128 | struct nf_ct_pptp_master *ct_pptp_info; | ||
129 | struct nf_nat_pptp *nat_pptp_info; | ||
130 | u_int16_t msg; | ||
131 | __be16 new_callid; | ||
132 | unsigned int cid_off; | ||
133 | |||
134 | ct_pptp_info = &nfct_help(ct)->help.ct_pptp_info; | ||
135 | nat_pptp_info = &nfct_nat(ct)->help.nat_pptp_info; | ||
136 | |||
137 | new_callid = ct_pptp_info->pns_call_id; | ||
138 | |||
139 | switch (msg = ntohs(ctlh->messageType)) { | ||
140 | case PPTP_OUT_CALL_REQUEST: | ||
141 | cid_off = offsetof(union pptp_ctrl_union, ocreq.callID); | ||
142 | /* FIXME: ideally we would want to reserve a call ID | ||
143 | * here. current netfilter NAT core is not able to do | ||
144 | * this :( For now we use TCP source port. This breaks | ||
145 | * multiple calls within one control session */ | ||
146 | |||
147 | /* save original call ID in nat_info */ | ||
148 | nat_pptp_info->pns_call_id = ct_pptp_info->pns_call_id; | ||
149 | |||
150 | /* don't use tcph->source since we are at a DSTmanip | ||
151 | * hook (e.g. PREROUTING) and pkt is not mangled yet */ | ||
152 | new_callid = ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.u.tcp.port; | ||
153 | |||
154 | /* save new call ID in ct info */ | ||
155 | ct_pptp_info->pns_call_id = new_callid; | ||
156 | break; | ||
157 | case PPTP_IN_CALL_REPLY: | ||
158 | cid_off = offsetof(union pptp_ctrl_union, icack.callID); | ||
159 | break; | ||
160 | case PPTP_CALL_CLEAR_REQUEST: | ||
161 | cid_off = offsetof(union pptp_ctrl_union, clrreq.callID); | ||
162 | break; | ||
163 | default: | ||
164 | DEBUGP("unknown outbound packet 0x%04x:%s\n", msg, | ||
165 | (msg <= PPTP_MSG_MAX)? | ||
166 | pptp_msg_name[msg]:pptp_msg_name[0]); | ||
167 | /* fall through */ | ||
168 | case PPTP_SET_LINK_INFO: | ||
169 | /* only need to NAT in case PAC is behind NAT box */ | ||
170 | case PPTP_START_SESSION_REQUEST: | ||
171 | case PPTP_START_SESSION_REPLY: | ||
172 | case PPTP_STOP_SESSION_REQUEST: | ||
173 | case PPTP_STOP_SESSION_REPLY: | ||
174 | case PPTP_ECHO_REQUEST: | ||
175 | case PPTP_ECHO_REPLY: | ||
176 | /* no need to alter packet */ | ||
177 | return NF_ACCEPT; | ||
178 | } | ||
179 | |||
180 | /* only OUT_CALL_REQUEST, IN_CALL_REPLY, CALL_CLEAR_REQUEST pass | ||
181 | * down to here */ | ||
182 | DEBUGP("altering call id from 0x%04x to 0x%04x\n", | ||
183 | ntohs(REQ_CID(pptpReq, cid_off)), ntohs(new_callid)); | ||
184 | |||
185 | /* mangle packet */ | ||
186 | if (nf_nat_mangle_tcp_packet(pskb, ct, ctinfo, | ||
187 | cid_off + sizeof(struct pptp_pkt_hdr) + | ||
188 | sizeof(struct PptpControlHeader), | ||
189 | sizeof(new_callid), (char *)&new_callid, | ||
190 | sizeof(new_callid)) == 0) | ||
191 | return NF_DROP; | ||
192 | return NF_ACCEPT; | ||
193 | } | ||
194 | |||
195 | static void | ||
196 | pptp_exp_gre(struct nf_conntrack_expect *expect_orig, | ||
197 | struct nf_conntrack_expect *expect_reply) | ||
198 | { | ||
199 | struct nf_conn *ct = expect_orig->master; | ||
200 | struct nf_ct_pptp_master *ct_pptp_info; | ||
201 | struct nf_nat_pptp *nat_pptp_info; | ||
202 | |||
203 | ct_pptp_info = &nfct_help(ct)->help.ct_pptp_info; | ||
204 | nat_pptp_info = &nfct_nat(ct)->help.nat_pptp_info; | ||
205 | |||
206 | /* save original PAC call ID in nat_info */ | ||
207 | nat_pptp_info->pac_call_id = ct_pptp_info->pac_call_id; | ||
208 | |||
209 | /* alter expectation for PNS->PAC direction */ | ||
210 | expect_orig->saved_proto.gre.key = ct_pptp_info->pns_call_id; | ||
211 | expect_orig->tuple.src.u.gre.key = nat_pptp_info->pns_call_id; | ||
212 | expect_orig->tuple.dst.u.gre.key = ct_pptp_info->pac_call_id; | ||
213 | expect_orig->dir = IP_CT_DIR_ORIGINAL; | ||
214 | |||
215 | /* alter expectation for PAC->PNS direction */ | ||
216 | expect_reply->saved_proto.gre.key = nat_pptp_info->pns_call_id; | ||
217 | expect_reply->tuple.src.u.gre.key = nat_pptp_info->pac_call_id; | ||
218 | expect_reply->tuple.dst.u.gre.key = ct_pptp_info->pns_call_id; | ||
219 | expect_reply->dir = IP_CT_DIR_REPLY; | ||
220 | } | ||
221 | |||
222 | /* inbound packets == from PAC to PNS */ | ||
223 | static int | ||
224 | pptp_inbound_pkt(struct sk_buff **pskb, | ||
225 | struct nf_conn *ct, | ||
226 | enum ip_conntrack_info ctinfo, | ||
227 | struct PptpControlHeader *ctlh, | ||
228 | union pptp_ctrl_union *pptpReq) | ||
229 | { | ||
230 | struct nf_nat_pptp *nat_pptp_info; | ||
231 | u_int16_t msg; | ||
232 | __be16 new_pcid; | ||
233 | unsigned int pcid_off; | ||
234 | |||
235 | nat_pptp_info = &nfct_nat(ct)->help.nat_pptp_info; | ||
236 | new_pcid = nat_pptp_info->pns_call_id; | ||
237 | |||
238 | switch (msg = ntohs(ctlh->messageType)) { | ||
239 | case PPTP_OUT_CALL_REPLY: | ||
240 | pcid_off = offsetof(union pptp_ctrl_union, ocack.peersCallID); | ||
241 | break; | ||
242 | case PPTP_IN_CALL_CONNECT: | ||
243 | pcid_off = offsetof(union pptp_ctrl_union, iccon.peersCallID); | ||
244 | break; | ||
245 | case PPTP_IN_CALL_REQUEST: | ||
246 | /* only need to nat in case PAC is behind NAT box */ | ||
247 | return NF_ACCEPT; | ||
248 | case PPTP_WAN_ERROR_NOTIFY: | ||
249 | pcid_off = offsetof(union pptp_ctrl_union, wanerr.peersCallID); | ||
250 | break; | ||
251 | case PPTP_CALL_DISCONNECT_NOTIFY: | ||
252 | pcid_off = offsetof(union pptp_ctrl_union, disc.callID); | ||
253 | break; | ||
254 | case PPTP_SET_LINK_INFO: | ||
255 | pcid_off = offsetof(union pptp_ctrl_union, setlink.peersCallID); | ||
256 | break; | ||
257 | default: | ||
258 | DEBUGP("unknown inbound packet %s\n", (msg <= PPTP_MSG_MAX)? | ||
259 | pptp_msg_name[msg]:pptp_msg_name[0]); | ||
260 | /* fall through */ | ||
261 | case PPTP_START_SESSION_REQUEST: | ||
262 | case PPTP_START_SESSION_REPLY: | ||
263 | case PPTP_STOP_SESSION_REQUEST: | ||
264 | case PPTP_STOP_SESSION_REPLY: | ||
265 | case PPTP_ECHO_REQUEST: | ||
266 | case PPTP_ECHO_REPLY: | ||
267 | /* no need to alter packet */ | ||
268 | return NF_ACCEPT; | ||
269 | } | ||
270 | |||
271 | /* only OUT_CALL_REPLY, IN_CALL_CONNECT, IN_CALL_REQUEST, | ||
272 | * WAN_ERROR_NOTIFY, CALL_DISCONNECT_NOTIFY pass down here */ | ||
273 | |||
274 | /* mangle packet */ | ||
275 | DEBUGP("altering peer call id from 0x%04x to 0x%04x\n", | ||
276 | ntohs(REQ_CID(pptpReq, pcid_off)), ntohs(new_pcid)); | ||
277 | |||
278 | if (nf_nat_mangle_tcp_packet(pskb, ct, ctinfo, | ||
279 | pcid_off + sizeof(struct pptp_pkt_hdr) + | ||
280 | sizeof(struct PptpControlHeader), | ||
281 | sizeof(new_pcid), (char *)&new_pcid, | ||
282 | sizeof(new_pcid)) == 0) | ||
283 | return NF_DROP; | ||
284 | return NF_ACCEPT; | ||
285 | } | ||
286 | |||
287 | static int __init nf_nat_helper_pptp_init(void) | ||
288 | { | ||
289 | nf_nat_need_gre(); | ||
290 | |||
291 | BUG_ON(rcu_dereference(nf_nat_pptp_hook_outbound)); | ||
292 | rcu_assign_pointer(nf_nat_pptp_hook_outbound, pptp_outbound_pkt); | ||
293 | |||
294 | BUG_ON(rcu_dereference(nf_nat_pptp_hook_inbound)); | ||
295 | rcu_assign_pointer(nf_nat_pptp_hook_inbound, pptp_inbound_pkt); | ||
296 | |||
297 | BUG_ON(rcu_dereference(nf_nat_pptp_hook_exp_gre)); | ||
298 | rcu_assign_pointer(nf_nat_pptp_hook_exp_gre, pptp_exp_gre); | ||
299 | |||
300 | BUG_ON(rcu_dereference(nf_nat_pptp_hook_expectfn)); | ||
301 | rcu_assign_pointer(nf_nat_pptp_hook_expectfn, pptp_nat_expected); | ||
302 | return 0; | ||
303 | } | ||
304 | |||
305 | static void __exit nf_nat_helper_pptp_fini(void) | ||
306 | { | ||
307 | rcu_assign_pointer(nf_nat_pptp_hook_expectfn, NULL); | ||
308 | rcu_assign_pointer(nf_nat_pptp_hook_exp_gre, NULL); | ||
309 | rcu_assign_pointer(nf_nat_pptp_hook_inbound, NULL); | ||
310 | rcu_assign_pointer(nf_nat_pptp_hook_outbound, NULL); | ||
311 | synchronize_rcu(); | ||
312 | } | ||
313 | |||
314 | module_init(nf_nat_helper_pptp_init); | ||
315 | module_exit(nf_nat_helper_pptp_fini); | ||
diff --git a/net/ipv4/netfilter/nf_nat_proto_gre.c b/net/ipv4/netfilter/nf_nat_proto_gre.c new file mode 100644 index 000000000000..d3de579e09d2 --- /dev/null +++ b/net/ipv4/netfilter/nf_nat_proto_gre.c | |||
@@ -0,0 +1,179 @@ | |||
1 | /* | ||
2 | * nf_nat_proto_gre.c | ||
3 | * | ||
4 | * NAT protocol helper module for GRE. | ||
5 | * | ||
6 | * GRE is a generic encapsulation protocol, which is generally not very | ||
7 | * suited for NAT, as it has no protocol-specific part as port numbers. | ||
8 | * | ||
9 | * It has an optional key field, which may help us distinguishing two | ||
10 | * connections between the same two hosts. | ||
11 | * | ||
12 | * GRE is defined in RFC 1701 and RFC 1702, as well as RFC 2784 | ||
13 | * | ||
14 | * PPTP is built on top of a modified version of GRE, and has a mandatory | ||
15 | * field called "CallID", which serves us for the same purpose as the key | ||
16 | * field in plain GRE. | ||
17 | * | ||
18 | * Documentation about PPTP can be found in RFC 2637 | ||
19 | * | ||
20 | * (C) 2000-2005 by Harald Welte <laforge@gnumonks.org> | ||
21 | * | ||
22 | * Development of this code funded by Astaro AG (http://www.astaro.com/) | ||
23 | * | ||
24 | */ | ||
25 | |||
26 | #include <linux/module.h> | ||
27 | #include <linux/skbuff.h> | ||
28 | #include <linux/ip.h> | ||
29 | |||
30 | #include <net/netfilter/nf_nat.h> | ||
31 | #include <net/netfilter/nf_nat_rule.h> | ||
32 | #include <net/netfilter/nf_nat_protocol.h> | ||
33 | #include <linux/netfilter/nf_conntrack_proto_gre.h> | ||
34 | |||
35 | MODULE_LICENSE("GPL"); | ||
36 | MODULE_AUTHOR("Harald Welte <laforge@gnumonks.org>"); | ||
37 | MODULE_DESCRIPTION("Netfilter NAT protocol helper module for GRE"); | ||
38 | |||
39 | #if 0 | ||
40 | #define DEBUGP(format, args...) printk(KERN_DEBUG "%s:%s: " format, __FILE__, \ | ||
41 | __FUNCTION__, ## args) | ||
42 | #else | ||
43 | #define DEBUGP(x, args...) | ||
44 | #endif | ||
45 | |||
46 | /* is key in given range between min and max */ | ||
47 | static int | ||
48 | gre_in_range(const struct nf_conntrack_tuple *tuple, | ||
49 | enum nf_nat_manip_type maniptype, | ||
50 | const union nf_conntrack_man_proto *min, | ||
51 | const union nf_conntrack_man_proto *max) | ||
52 | { | ||
53 | __be16 key; | ||
54 | |||
55 | if (maniptype == IP_NAT_MANIP_SRC) | ||
56 | key = tuple->src.u.gre.key; | ||
57 | else | ||
58 | key = tuple->dst.u.gre.key; | ||
59 | |||
60 | return ntohs(key) >= ntohs(min->gre.key) && | ||
61 | ntohs(key) <= ntohs(max->gre.key); | ||
62 | } | ||
63 | |||
64 | /* generate unique tuple ... */ | ||
65 | static int | ||
66 | gre_unique_tuple(struct nf_conntrack_tuple *tuple, | ||
67 | const struct nf_nat_range *range, | ||
68 | enum nf_nat_manip_type maniptype, | ||
69 | const struct nf_conn *conntrack) | ||
70 | { | ||
71 | static u_int16_t key; | ||
72 | __be16 *keyptr; | ||
73 | unsigned int min, i, range_size; | ||
74 | |||
75 | if (maniptype == IP_NAT_MANIP_SRC) | ||
76 | keyptr = &tuple->src.u.gre.key; | ||
77 | else | ||
78 | keyptr = &tuple->dst.u.gre.key; | ||
79 | |||
80 | if (!(range->flags & IP_NAT_RANGE_PROTO_SPECIFIED)) { | ||
81 | DEBUGP("%p: NATing GRE PPTP\n", conntrack); | ||
82 | min = 1; | ||
83 | range_size = 0xffff; | ||
84 | } else { | ||
85 | min = ntohs(range->min.gre.key); | ||
86 | range_size = ntohs(range->max.gre.key) - min + 1; | ||
87 | } | ||
88 | |||
89 | DEBUGP("min = %u, range_size = %u\n", min, range_size); | ||
90 | |||
91 | for (i = 0; i < range_size; i++, key++) { | ||
92 | *keyptr = htons(min + key % range_size); | ||
93 | if (!nf_nat_used_tuple(tuple, conntrack)) | ||
94 | return 1; | ||
95 | } | ||
96 | |||
97 | DEBUGP("%p: no NAT mapping\n", conntrack); | ||
98 | return 0; | ||
99 | } | ||
100 | |||
101 | /* manipulate a GRE packet according to maniptype */ | ||
102 | static int | ||
103 | gre_manip_pkt(struct sk_buff **pskb, unsigned int iphdroff, | ||
104 | const struct nf_conntrack_tuple *tuple, | ||
105 | enum nf_nat_manip_type maniptype) | ||
106 | { | ||
107 | struct gre_hdr *greh; | ||
108 | struct gre_hdr_pptp *pgreh; | ||
109 | struct iphdr *iph = (struct iphdr *)((*pskb)->data + iphdroff); | ||
110 | unsigned int hdroff = iphdroff + iph->ihl * 4; | ||
111 | |||
112 | /* pgreh includes two optional 32bit fields which are not required | ||
113 | * to be there. That's where the magic '8' comes from */ | ||
114 | if (!skb_make_writable(pskb, hdroff + sizeof(*pgreh) - 8)) | ||
115 | return 0; | ||
116 | |||
117 | greh = (void *)(*pskb)->data + hdroff; | ||
118 | pgreh = (struct gre_hdr_pptp *)greh; | ||
119 | |||
120 | /* we only have destination manip of a packet, since 'source key' | ||
121 | * is not present in the packet itself */ | ||
122 | if (maniptype != IP_NAT_MANIP_DST) | ||
123 | return 1; | ||
124 | switch (greh->version) { | ||
125 | case 0: | ||
126 | if (!greh->key) { | ||
127 | DEBUGP("can't nat GRE w/o key\n"); | ||
128 | break; | ||
129 | } | ||
130 | if (greh->csum) { | ||
131 | /* FIXME: Never tested this code... */ | ||
132 | nf_proto_csum_replace4(gre_csum(greh), *pskb, | ||
133 | *(gre_key(greh)), | ||
134 | tuple->dst.u.gre.key, 0); | ||
135 | } | ||
136 | *(gre_key(greh)) = tuple->dst.u.gre.key; | ||
137 | break; | ||
138 | case GRE_VERSION_PPTP: | ||
139 | DEBUGP("call_id -> 0x%04x\n", ntohs(tuple->dst.u.gre.key)); | ||
140 | pgreh->call_id = tuple->dst.u.gre.key; | ||
141 | break; | ||
142 | default: | ||
143 | DEBUGP("can't nat unknown GRE version\n"); | ||
144 | return 0; | ||
145 | } | ||
146 | return 1; | ||
147 | } | ||
148 | |||
149 | static struct nf_nat_protocol gre __read_mostly = { | ||
150 | .name = "GRE", | ||
151 | .protonum = IPPROTO_GRE, | ||
152 | .manip_pkt = gre_manip_pkt, | ||
153 | .in_range = gre_in_range, | ||
154 | .unique_tuple = gre_unique_tuple, | ||
155 | #if defined(CONFIG_IP_NF_CONNTRACK_NETLINK) || \ | ||
156 | defined(CONFIG_IP_NF_CONNTRACK_NETLINK_MODULE) | ||
157 | .range_to_nfattr = nf_nat_port_range_to_nfattr, | ||
158 | .nfattr_to_range = nf_nat_port_nfattr_to_range, | ||
159 | #endif | ||
160 | }; | ||
161 | |||
162 | int __init nf_nat_proto_gre_init(void) | ||
163 | { | ||
164 | return nf_nat_protocol_register(&gre); | ||
165 | } | ||
166 | |||
167 | void __exit nf_nat_proto_gre_fini(void) | ||
168 | { | ||
169 | nf_nat_protocol_unregister(&gre); | ||
170 | } | ||
171 | |||
172 | module_init(nf_nat_proto_gre_init); | ||
173 | module_exit(nf_nat_proto_gre_fini); | ||
174 | |||
175 | void nf_nat_need_gre(void) | ||
176 | { | ||
177 | return; | ||
178 | } | ||
179 | EXPORT_SYMBOL_GPL(nf_nat_need_gre); | ||
diff --git a/net/netfilter/Kconfig b/net/netfilter/Kconfig index d1a365d83c53..6b2eb26ae03f 100644 --- a/net/netfilter/Kconfig +++ b/net/netfilter/Kconfig | |||
@@ -120,6 +120,10 @@ config NF_CONNTRACK_EVENTS | |||
120 | 120 | ||
121 | If unsure, say `N'. | 121 | If unsure, say `N'. |
122 | 122 | ||
123 | config NF_CT_PROTO_GRE | ||
124 | tristate | ||
125 | depends on EXPERIMENTAL && NF_CONNTRACK | ||
126 | |||
123 | config NF_CT_PROTO_SCTP | 127 | config NF_CT_PROTO_SCTP |
124 | tristate 'SCTP protocol on new connection tracking support (EXPERIMENTAL)' | 128 | tristate 'SCTP protocol on new connection tracking support (EXPERIMENTAL)' |
125 | depends on EXPERIMENTAL && NF_CONNTRACK | 129 | depends on EXPERIMENTAL && NF_CONNTRACK |
@@ -213,6 +217,25 @@ config NF_CONNTRACK_NETBIOS_NS | |||
213 | 217 | ||
214 | To compile it as a module, choose M here. If unsure, say N. | 218 | To compile it as a module, choose M here. If unsure, say N. |
215 | 219 | ||
220 | config NF_CONNTRACK_PPTP | ||
221 | tristate "PPtP protocol support (EXPERIMENTAL)" | ||
222 | depends on EXPERIMENTAL && NF_CONNTRACK | ||
223 | select NF_CT_PROTO_GRE | ||
224 | help | ||
225 | This module adds support for PPTP (Point to Point Tunnelling | ||
226 | Protocol, RFC2637) connection tracking and NAT. | ||
227 | |||
228 | If you are running PPTP sessions over a stateful firewall or NAT | ||
229 | box, you may want to enable this feature. | ||
230 | |||
231 | Please note that not all PPTP modes of operation are supported yet. | ||
232 | Specifically these limitations exist: | ||
233 | - Blindy assumes that control connections are always established | ||
234 | in PNS->PAC direction. This is a violation of RFC2637. | ||
235 | - Only supports a single call within each session | ||
236 | |||
237 | To compile it as a module, choose M here. If unsure, say N. | ||
238 | |||
216 | config NF_CT_NETLINK | 239 | config NF_CT_NETLINK |
217 | tristate 'Connection tracking netlink interface (EXPERIMENTAL)' | 240 | tristate 'Connection tracking netlink interface (EXPERIMENTAL)' |
218 | depends on EXPERIMENTAL && NF_CONNTRACK && NETFILTER_NETLINK | 241 | depends on EXPERIMENTAL && NF_CONNTRACK && NETFILTER_NETLINK |
diff --git a/net/netfilter/Makefile b/net/netfilter/Makefile index 67144b2af647..897bed4cbd79 100644 --- a/net/netfilter/Makefile +++ b/net/netfilter/Makefile | |||
@@ -14,6 +14,7 @@ obj-$(CONFIG_NETFILTER_NETLINK_LOG) += nfnetlink_log.o | |||
14 | obj-$(CONFIG_NF_CONNTRACK) += nf_conntrack.o | 14 | obj-$(CONFIG_NF_CONNTRACK) += nf_conntrack.o |
15 | 15 | ||
16 | # SCTP protocol connection tracking | 16 | # SCTP protocol connection tracking |
17 | obj-$(CONFIG_NF_CT_PROTO_GRE) += nf_conntrack_proto_gre.o | ||
17 | obj-$(CONFIG_NF_CT_PROTO_SCTP) += nf_conntrack_proto_sctp.o | 18 | obj-$(CONFIG_NF_CT_PROTO_SCTP) += nf_conntrack_proto_sctp.o |
18 | 19 | ||
19 | # netlink interface for nf_conntrack | 20 | # netlink interface for nf_conntrack |
@@ -27,6 +28,7 @@ obj-$(CONFIG_NF_CONNTRACK_FTP) += nf_conntrack_ftp.o | |||
27 | obj-$(CONFIG_NF_CONNTRACK_H323) += nf_conntrack_h323.o | 28 | obj-$(CONFIG_NF_CONNTRACK_H323) += nf_conntrack_h323.o |
28 | obj-$(CONFIG_NF_CONNTRACK_IRC) += nf_conntrack_irc.o | 29 | obj-$(CONFIG_NF_CONNTRACK_IRC) += nf_conntrack_irc.o |
29 | obj-$(CONFIG_NF_CONNTRACK_NETBIOS_NS) += nf_conntrack_netbios_ns.o | 30 | obj-$(CONFIG_NF_CONNTRACK_NETBIOS_NS) += nf_conntrack_netbios_ns.o |
31 | obj-$(CONFIG_NF_CONNTRACK_PPTP) += nf_conntrack_pptp.o | ||
30 | 32 | ||
31 | # generic X tables | 33 | # generic X tables |
32 | obj-$(CONFIG_NETFILTER_XTABLES) += x_tables.o xt_tcpudp.o | 34 | obj-$(CONFIG_NETFILTER_XTABLES) += x_tables.o xt_tcpudp.o |
diff --git a/net/netfilter/nf_conntrack_core.c b/net/netfilter/nf_conntrack_core.c index aa8beabfeebb..ed756c928bc4 100644 --- a/net/netfilter/nf_conntrack_core.c +++ b/net/netfilter/nf_conntrack_core.c | |||
@@ -300,6 +300,7 @@ static void | |||
300 | destroy_conntrack(struct nf_conntrack *nfct) | 300 | destroy_conntrack(struct nf_conntrack *nfct) |
301 | { | 301 | { |
302 | struct nf_conn *ct = (struct nf_conn *)nfct; | 302 | struct nf_conn *ct = (struct nf_conn *)nfct; |
303 | struct nf_conn_help *help = nfct_help(ct); | ||
303 | struct nf_conntrack_l3proto *l3proto; | 304 | struct nf_conntrack_l3proto *l3proto; |
304 | struct nf_conntrack_l4proto *l4proto; | 305 | struct nf_conntrack_l4proto *l4proto; |
305 | 306 | ||
@@ -310,6 +311,9 @@ destroy_conntrack(struct nf_conntrack *nfct) | |||
310 | nf_conntrack_event(IPCT_DESTROY, ct); | 311 | nf_conntrack_event(IPCT_DESTROY, ct); |
311 | set_bit(IPS_DYING_BIT, &ct->status); | 312 | set_bit(IPS_DYING_BIT, &ct->status); |
312 | 313 | ||
314 | if (help && help->helper && help->helper->destroy) | ||
315 | help->helper->destroy(ct); | ||
316 | |||
313 | /* To make sure we don't get any weird locking issues here: | 317 | /* To make sure we don't get any weird locking issues here: |
314 | * destroy_conntrack() MUST NOT be called with a write lock | 318 | * destroy_conntrack() MUST NOT be called with a write lock |
315 | * to nf_conntrack_lock!!! -HW */ | 319 | * to nf_conntrack_lock!!! -HW */ |
diff --git a/net/netfilter/nf_conntrack_pptp.c b/net/netfilter/nf_conntrack_pptp.c new file mode 100644 index 000000000000..f0ff00e0d052 --- /dev/null +++ b/net/netfilter/nf_conntrack_pptp.c | |||
@@ -0,0 +1,607 @@ | |||
1 | /* | ||
2 | * Connection tracking support for PPTP (Point to Point Tunneling Protocol). | ||
3 | * PPTP is a a protocol for creating virtual private networks. | ||
4 | * It is a specification defined by Microsoft and some vendors | ||
5 | * working with Microsoft. PPTP is built on top of a modified | ||
6 | * version of the Internet Generic Routing Encapsulation Protocol. | ||
7 | * GRE is defined in RFC 1701 and RFC 1702. Documentation of | ||
8 | * PPTP can be found in RFC 2637 | ||
9 | * | ||
10 | * (C) 2000-2005 by Harald Welte <laforge@gnumonks.org> | ||
11 | * | ||
12 | * Development of this code funded by Astaro AG (http://www.astaro.com/) | ||
13 | * | ||
14 | * Limitations: | ||
15 | * - We blindly assume that control connections are always | ||
16 | * established in PNS->PAC direction. This is a violation | ||
17 | * of RFFC2673 | ||
18 | * - We can only support one single call within each session | ||
19 | * TODO: | ||
20 | * - testing of incoming PPTP calls | ||
21 | */ | ||
22 | |||
23 | #include <linux/module.h> | ||
24 | #include <linux/skbuff.h> | ||
25 | #include <linux/in.h> | ||
26 | #include <linux/tcp.h> | ||
27 | |||
28 | #include <net/netfilter/nf_conntrack.h> | ||
29 | #include <net/netfilter/nf_conntrack_core.h> | ||
30 | #include <net/netfilter/nf_conntrack_helper.h> | ||
31 | #include <linux/netfilter/nf_conntrack_proto_gre.h> | ||
32 | #include <linux/netfilter/nf_conntrack_pptp.h> | ||
33 | |||
34 | #define NF_CT_PPTP_VERSION "3.1" | ||
35 | |||
36 | MODULE_LICENSE("GPL"); | ||
37 | MODULE_AUTHOR("Harald Welte <laforge@gnumonks.org>"); | ||
38 | MODULE_DESCRIPTION("Netfilter connection tracking helper module for PPTP"); | ||
39 | MODULE_ALIAS("ip_conntrack_pptp"); | ||
40 | |||
41 | static DEFINE_SPINLOCK(nf_pptp_lock); | ||
42 | |||
43 | int | ||
44 | (*nf_nat_pptp_hook_outbound)(struct sk_buff **pskb, | ||
45 | struct nf_conn *ct, enum ip_conntrack_info ctinfo, | ||
46 | struct PptpControlHeader *ctlh, | ||
47 | union pptp_ctrl_union *pptpReq) __read_mostly; | ||
48 | EXPORT_SYMBOL_GPL(nf_nat_pptp_hook_outbound); | ||
49 | |||
50 | int | ||
51 | (*nf_nat_pptp_hook_inbound)(struct sk_buff **pskb, | ||
52 | struct nf_conn *ct, enum ip_conntrack_info ctinfo, | ||
53 | struct PptpControlHeader *ctlh, | ||
54 | union pptp_ctrl_union *pptpReq) __read_mostly; | ||
55 | EXPORT_SYMBOL_GPL(nf_nat_pptp_hook_inbound); | ||
56 | |||
57 | void | ||
58 | (*nf_nat_pptp_hook_exp_gre)(struct nf_conntrack_expect *expect_orig, | ||
59 | struct nf_conntrack_expect *expect_reply) | ||
60 | __read_mostly; | ||
61 | EXPORT_SYMBOL_GPL(nf_nat_pptp_hook_exp_gre); | ||
62 | |||
63 | void | ||
64 | (*nf_nat_pptp_hook_expectfn)(struct nf_conn *ct, | ||
65 | struct nf_conntrack_expect *exp) __read_mostly; | ||
66 | EXPORT_SYMBOL_GPL(nf_nat_pptp_hook_expectfn); | ||
67 | |||
68 | #if 0 | ||
69 | /* PptpControlMessageType names */ | ||
70 | const char *pptp_msg_name[] = { | ||
71 | "UNKNOWN_MESSAGE", | ||
72 | "START_SESSION_REQUEST", | ||
73 | "START_SESSION_REPLY", | ||
74 | "STOP_SESSION_REQUEST", | ||
75 | "STOP_SESSION_REPLY", | ||
76 | "ECHO_REQUEST", | ||
77 | "ECHO_REPLY", | ||
78 | "OUT_CALL_REQUEST", | ||
79 | "OUT_CALL_REPLY", | ||
80 | "IN_CALL_REQUEST", | ||
81 | "IN_CALL_REPLY", | ||
82 | "IN_CALL_CONNECT", | ||
83 | "CALL_CLEAR_REQUEST", | ||
84 | "CALL_DISCONNECT_NOTIFY", | ||
85 | "WAN_ERROR_NOTIFY", | ||
86 | "SET_LINK_INFO" | ||
87 | }; | ||
88 | EXPORT_SYMBOL(pptp_msg_name); | ||
89 | #define DEBUGP(format, args...) printk(KERN_DEBUG "%s:%s: " format, __FILE__, __FUNCTION__, ## args) | ||
90 | #else | ||
91 | #define DEBUGP(format, args...) | ||
92 | #endif | ||
93 | |||
94 | #define SECS *HZ | ||
95 | #define MINS * 60 SECS | ||
96 | #define HOURS * 60 MINS | ||
97 | |||
98 | #define PPTP_GRE_TIMEOUT (10 MINS) | ||
99 | #define PPTP_GRE_STREAM_TIMEOUT (5 HOURS) | ||
100 | |||
101 | static void pptp_expectfn(struct nf_conn *ct, | ||
102 | struct nf_conntrack_expect *exp) | ||
103 | { | ||
104 | typeof(nf_nat_pptp_hook_expectfn) nf_nat_pptp_expectfn; | ||
105 | DEBUGP("increasing timeouts\n"); | ||
106 | |||
107 | /* increase timeout of GRE data channel conntrack entry */ | ||
108 | ct->proto.gre.timeout = PPTP_GRE_TIMEOUT; | ||
109 | ct->proto.gre.stream_timeout = PPTP_GRE_STREAM_TIMEOUT; | ||
110 | |||
111 | /* Can you see how rusty this code is, compared with the pre-2.6.11 | ||
112 | * one? That's what happened to my shiny newnat of 2002 ;( -HW */ | ||
113 | |||
114 | rcu_read_lock(); | ||
115 | nf_nat_pptp_expectfn = rcu_dereference(nf_nat_pptp_hook_expectfn); | ||
116 | if (nf_nat_pptp_expectfn && ct->status & IPS_NAT_MASK) | ||
117 | nf_nat_pptp_expectfn(ct, exp); | ||
118 | else { | ||
119 | struct nf_conntrack_tuple inv_t; | ||
120 | struct nf_conntrack_expect *exp_other; | ||
121 | |||
122 | /* obviously this tuple inversion only works until you do NAT */ | ||
123 | nf_ct_invert_tuplepr(&inv_t, &exp->tuple); | ||
124 | DEBUGP("trying to unexpect other dir: "); | ||
125 | NF_CT_DUMP_TUPLE(&inv_t); | ||
126 | |||
127 | exp_other = nf_conntrack_expect_find_get(&inv_t); | ||
128 | if (exp_other) { | ||
129 | /* delete other expectation. */ | ||
130 | DEBUGP("found\n"); | ||
131 | nf_conntrack_unexpect_related(exp_other); | ||
132 | nf_conntrack_expect_put(exp_other); | ||
133 | } else { | ||
134 | DEBUGP("not found\n"); | ||
135 | } | ||
136 | } | ||
137 | rcu_read_unlock(); | ||
138 | } | ||
139 | |||
140 | static int destroy_sibling_or_exp(const struct nf_conntrack_tuple *t) | ||
141 | { | ||
142 | struct nf_conntrack_tuple_hash *h; | ||
143 | struct nf_conntrack_expect *exp; | ||
144 | struct nf_conn *sibling; | ||
145 | |||
146 | DEBUGP("trying to timeout ct or exp for tuple "); | ||
147 | NF_CT_DUMP_TUPLE(t); | ||
148 | |||
149 | h = nf_conntrack_find_get(t, NULL); | ||
150 | if (h) { | ||
151 | sibling = nf_ct_tuplehash_to_ctrack(h); | ||
152 | DEBUGP("setting timeout of conntrack %p to 0\n", sibling); | ||
153 | sibling->proto.gre.timeout = 0; | ||
154 | sibling->proto.gre.stream_timeout = 0; | ||
155 | if (del_timer(&sibling->timeout)) | ||
156 | sibling->timeout.function((unsigned long)sibling); | ||
157 | nf_ct_put(sibling); | ||
158 | return 1; | ||
159 | } else { | ||
160 | exp = nf_conntrack_expect_find_get(t); | ||
161 | if (exp) { | ||
162 | DEBUGP("unexpect_related of expect %p\n", exp); | ||
163 | nf_conntrack_unexpect_related(exp); | ||
164 | nf_conntrack_expect_put(exp); | ||
165 | return 1; | ||
166 | } | ||
167 | } | ||
168 | return 0; | ||
169 | } | ||
170 | |||
171 | /* timeout GRE data connections */ | ||
172 | static void pptp_destroy_siblings(struct nf_conn *ct) | ||
173 | { | ||
174 | struct nf_conn_help *help = nfct_help(ct); | ||
175 | struct nf_conntrack_tuple t; | ||
176 | |||
177 | nf_ct_gre_keymap_destroy(ct); | ||
178 | |||
179 | /* try original (pns->pac) tuple */ | ||
180 | memcpy(&t, &ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple, sizeof(t)); | ||
181 | t.dst.protonum = IPPROTO_GRE; | ||
182 | t.src.u.gre.key = help->help.ct_pptp_info.pns_call_id; | ||
183 | t.dst.u.gre.key = help->help.ct_pptp_info.pac_call_id; | ||
184 | if (!destroy_sibling_or_exp(&t)) | ||
185 | DEBUGP("failed to timeout original pns->pac ct/exp\n"); | ||
186 | |||
187 | /* try reply (pac->pns) tuple */ | ||
188 | memcpy(&t, &ct->tuplehash[IP_CT_DIR_REPLY].tuple, sizeof(t)); | ||
189 | t.dst.protonum = IPPROTO_GRE; | ||
190 | t.src.u.gre.key = help->help.ct_pptp_info.pac_call_id; | ||
191 | t.dst.u.gre.key = help->help.ct_pptp_info.pns_call_id; | ||
192 | if (!destroy_sibling_or_exp(&t)) | ||
193 | DEBUGP("failed to timeout reply pac->pns ct/exp\n"); | ||
194 | } | ||
195 | |||
196 | /* expect GRE connections (PNS->PAC and PAC->PNS direction) */ | ||
197 | static int exp_gre(struct nf_conn *ct, __be16 callid, __be16 peer_callid) | ||
198 | { | ||
199 | struct nf_conntrack_expect *exp_orig, *exp_reply; | ||
200 | enum ip_conntrack_dir dir; | ||
201 | int ret = 1; | ||
202 | typeof(nf_nat_pptp_hook_exp_gre) nf_nat_pptp_exp_gre; | ||
203 | |||
204 | exp_orig = nf_conntrack_expect_alloc(ct); | ||
205 | if (exp_orig == NULL) | ||
206 | goto out; | ||
207 | |||
208 | exp_reply = nf_conntrack_expect_alloc(ct); | ||
209 | if (exp_reply == NULL) | ||
210 | goto out_put_orig; | ||
211 | |||
212 | /* original direction, PNS->PAC */ | ||
213 | dir = IP_CT_DIR_ORIGINAL; | ||
214 | nf_conntrack_expect_init(exp_orig, ct->tuplehash[dir].tuple.src.l3num, | ||
215 | &ct->tuplehash[dir].tuple.src.u3, | ||
216 | &ct->tuplehash[dir].tuple.dst.u3, | ||
217 | IPPROTO_GRE, &peer_callid, &callid); | ||
218 | exp_orig->expectfn = pptp_expectfn; | ||
219 | |||
220 | /* reply direction, PAC->PNS */ | ||
221 | dir = IP_CT_DIR_REPLY; | ||
222 | nf_conntrack_expect_init(exp_reply, ct->tuplehash[dir].tuple.src.l3num, | ||
223 | &ct->tuplehash[dir].tuple.src.u3, | ||
224 | &ct->tuplehash[dir].tuple.dst.u3, | ||
225 | IPPROTO_GRE, &callid, &peer_callid); | ||
226 | exp_reply->expectfn = pptp_expectfn; | ||
227 | |||
228 | nf_nat_pptp_exp_gre = rcu_dereference(nf_nat_pptp_hook_exp_gre); | ||
229 | if (nf_nat_pptp_exp_gre && ct->status & IPS_NAT_MASK) | ||
230 | nf_nat_pptp_exp_gre(exp_orig, exp_reply); | ||
231 | if (nf_conntrack_expect_related(exp_orig) != 0) | ||
232 | goto out_put_both; | ||
233 | if (nf_conntrack_expect_related(exp_reply) != 0) | ||
234 | goto out_unexpect_orig; | ||
235 | |||
236 | /* Add GRE keymap entries */ | ||
237 | if (nf_ct_gre_keymap_add(ct, IP_CT_DIR_ORIGINAL, &exp_orig->tuple) != 0) | ||
238 | goto out_unexpect_both; | ||
239 | if (nf_ct_gre_keymap_add(ct, IP_CT_DIR_REPLY, &exp_reply->tuple) != 0) { | ||
240 | nf_ct_gre_keymap_destroy(ct); | ||
241 | goto out_unexpect_both; | ||
242 | } | ||
243 | ret = 0; | ||
244 | |||
245 | out_put_both: | ||
246 | nf_conntrack_expect_put(exp_reply); | ||
247 | out_put_orig: | ||
248 | nf_conntrack_expect_put(exp_orig); | ||
249 | out: | ||
250 | return ret; | ||
251 | |||
252 | out_unexpect_both: | ||
253 | nf_conntrack_unexpect_related(exp_reply); | ||
254 | out_unexpect_orig: | ||
255 | nf_conntrack_unexpect_related(exp_orig); | ||
256 | goto out_put_both; | ||
257 | } | ||
258 | |||
259 | static inline int | ||
260 | pptp_inbound_pkt(struct sk_buff **pskb, | ||
261 | struct PptpControlHeader *ctlh, | ||
262 | union pptp_ctrl_union *pptpReq, | ||
263 | unsigned int reqlen, | ||
264 | struct nf_conn *ct, | ||
265 | enum ip_conntrack_info ctinfo) | ||
266 | { | ||
267 | struct nf_ct_pptp_master *info = &nfct_help(ct)->help.ct_pptp_info; | ||
268 | u_int16_t msg; | ||
269 | __be16 cid = 0, pcid = 0; | ||
270 | typeof(nf_nat_pptp_hook_inbound) nf_nat_pptp_inbound; | ||
271 | |||
272 | msg = ntohs(ctlh->messageType); | ||
273 | DEBUGP("inbound control message %s\n", pptp_msg_name[msg]); | ||
274 | |||
275 | switch (msg) { | ||
276 | case PPTP_START_SESSION_REPLY: | ||
277 | /* server confirms new control session */ | ||
278 | if (info->sstate < PPTP_SESSION_REQUESTED) | ||
279 | goto invalid; | ||
280 | if (pptpReq->srep.resultCode == PPTP_START_OK) | ||
281 | info->sstate = PPTP_SESSION_CONFIRMED; | ||
282 | else | ||
283 | info->sstate = PPTP_SESSION_ERROR; | ||
284 | break; | ||
285 | |||
286 | case PPTP_STOP_SESSION_REPLY: | ||
287 | /* server confirms end of control session */ | ||
288 | if (info->sstate > PPTP_SESSION_STOPREQ) | ||
289 | goto invalid; | ||
290 | if (pptpReq->strep.resultCode == PPTP_STOP_OK) | ||
291 | info->sstate = PPTP_SESSION_NONE; | ||
292 | else | ||
293 | info->sstate = PPTP_SESSION_ERROR; | ||
294 | break; | ||
295 | |||
296 | case PPTP_OUT_CALL_REPLY: | ||
297 | /* server accepted call, we now expect GRE frames */ | ||
298 | if (info->sstate != PPTP_SESSION_CONFIRMED) | ||
299 | goto invalid; | ||
300 | if (info->cstate != PPTP_CALL_OUT_REQ && | ||
301 | info->cstate != PPTP_CALL_OUT_CONF) | ||
302 | goto invalid; | ||
303 | |||
304 | cid = pptpReq->ocack.callID; | ||
305 | pcid = pptpReq->ocack.peersCallID; | ||
306 | if (info->pns_call_id != pcid) | ||
307 | goto invalid; | ||
308 | DEBUGP("%s, CID=%X, PCID=%X\n", pptp_msg_name[msg], | ||
309 | ntohs(cid), ntohs(pcid)); | ||
310 | |||
311 | if (pptpReq->ocack.resultCode == PPTP_OUTCALL_CONNECT) { | ||
312 | info->cstate = PPTP_CALL_OUT_CONF; | ||
313 | info->pac_call_id = cid; | ||
314 | exp_gre(ct, cid, pcid); | ||
315 | } else | ||
316 | info->cstate = PPTP_CALL_NONE; | ||
317 | break; | ||
318 | |||
319 | case PPTP_IN_CALL_REQUEST: | ||
320 | /* server tells us about incoming call request */ | ||
321 | if (info->sstate != PPTP_SESSION_CONFIRMED) | ||
322 | goto invalid; | ||
323 | |||
324 | cid = pptpReq->icreq.callID; | ||
325 | DEBUGP("%s, CID=%X\n", pptp_msg_name[msg], ntohs(cid)); | ||
326 | info->cstate = PPTP_CALL_IN_REQ; | ||
327 | info->pac_call_id = cid; | ||
328 | break; | ||
329 | |||
330 | case PPTP_IN_CALL_CONNECT: | ||
331 | /* server tells us about incoming call established */ | ||
332 | if (info->sstate != PPTP_SESSION_CONFIRMED) | ||
333 | goto invalid; | ||
334 | if (info->cstate != PPTP_CALL_IN_REP && | ||
335 | info->cstate != PPTP_CALL_IN_CONF) | ||
336 | goto invalid; | ||
337 | |||
338 | pcid = pptpReq->iccon.peersCallID; | ||
339 | cid = info->pac_call_id; | ||
340 | |||
341 | if (info->pns_call_id != pcid) | ||
342 | goto invalid; | ||
343 | |||
344 | DEBUGP("%s, PCID=%X\n", pptp_msg_name[msg], ntohs(pcid)); | ||
345 | info->cstate = PPTP_CALL_IN_CONF; | ||
346 | |||
347 | /* we expect a GRE connection from PAC to PNS */ | ||
348 | exp_gre(ct, cid, pcid); | ||
349 | break; | ||
350 | |||
351 | case PPTP_CALL_DISCONNECT_NOTIFY: | ||
352 | /* server confirms disconnect */ | ||
353 | cid = pptpReq->disc.callID; | ||
354 | DEBUGP("%s, CID=%X\n", pptp_msg_name[msg], ntohs(cid)); | ||
355 | info->cstate = PPTP_CALL_NONE; | ||
356 | |||
357 | /* untrack this call id, unexpect GRE packets */ | ||
358 | pptp_destroy_siblings(ct); | ||
359 | break; | ||
360 | |||
361 | case PPTP_WAN_ERROR_NOTIFY: | ||
362 | case PPTP_ECHO_REQUEST: | ||
363 | case PPTP_ECHO_REPLY: | ||
364 | /* I don't have to explain these ;) */ | ||
365 | break; | ||
366 | |||
367 | default: | ||
368 | goto invalid; | ||
369 | } | ||
370 | |||
371 | nf_nat_pptp_inbound = rcu_dereference(nf_nat_pptp_hook_inbound); | ||
372 | if (nf_nat_pptp_inbound && ct->status & IPS_NAT_MASK) | ||
373 | return nf_nat_pptp_inbound(pskb, ct, ctinfo, ctlh, pptpReq); | ||
374 | return NF_ACCEPT; | ||
375 | |||
376 | invalid: | ||
377 | DEBUGP("invalid %s: type=%d cid=%u pcid=%u " | ||
378 | "cstate=%d sstate=%d pns_cid=%u pac_cid=%u\n", | ||
379 | msg <= PPTP_MSG_MAX ? pptp_msg_name[msg] : pptp_msg_name[0], | ||
380 | msg, ntohs(cid), ntohs(pcid), info->cstate, info->sstate, | ||
381 | ntohs(info->pns_call_id), ntohs(info->pac_call_id)); | ||
382 | return NF_ACCEPT; | ||
383 | } | ||
384 | |||
385 | static inline int | ||
386 | pptp_outbound_pkt(struct sk_buff **pskb, | ||
387 | struct PptpControlHeader *ctlh, | ||
388 | union pptp_ctrl_union *pptpReq, | ||
389 | unsigned int reqlen, | ||
390 | struct nf_conn *ct, | ||
391 | enum ip_conntrack_info ctinfo) | ||
392 | { | ||
393 | struct nf_ct_pptp_master *info = &nfct_help(ct)->help.ct_pptp_info; | ||
394 | u_int16_t msg; | ||
395 | __be16 cid = 0, pcid = 0; | ||
396 | typeof(nf_nat_pptp_hook_outbound) nf_nat_pptp_outbound; | ||
397 | |||
398 | msg = ntohs(ctlh->messageType); | ||
399 | DEBUGP("outbound control message %s\n", pptp_msg_name[msg]); | ||
400 | |||
401 | switch (msg) { | ||
402 | case PPTP_START_SESSION_REQUEST: | ||
403 | /* client requests for new control session */ | ||
404 | if (info->sstate != PPTP_SESSION_NONE) | ||
405 | goto invalid; | ||
406 | info->sstate = PPTP_SESSION_REQUESTED; | ||
407 | break; | ||
408 | |||
409 | case PPTP_STOP_SESSION_REQUEST: | ||
410 | /* client requests end of control session */ | ||
411 | info->sstate = PPTP_SESSION_STOPREQ; | ||
412 | break; | ||
413 | |||
414 | case PPTP_OUT_CALL_REQUEST: | ||
415 | /* client initiating connection to server */ | ||
416 | if (info->sstate != PPTP_SESSION_CONFIRMED) | ||
417 | goto invalid; | ||
418 | info->cstate = PPTP_CALL_OUT_REQ; | ||
419 | /* track PNS call id */ | ||
420 | cid = pptpReq->ocreq.callID; | ||
421 | DEBUGP("%s, CID=%X\n", pptp_msg_name[msg], ntohs(cid)); | ||
422 | info->pns_call_id = cid; | ||
423 | break; | ||
424 | |||
425 | case PPTP_IN_CALL_REPLY: | ||
426 | /* client answers incoming call */ | ||
427 | if (info->cstate != PPTP_CALL_IN_REQ && | ||
428 | info->cstate != PPTP_CALL_IN_REP) | ||
429 | goto invalid; | ||
430 | |||
431 | cid = pptpReq->icack.callID; | ||
432 | pcid = pptpReq->icack.peersCallID; | ||
433 | if (info->pac_call_id != pcid) | ||
434 | goto invalid; | ||
435 | DEBUGP("%s, CID=%X PCID=%X\n", pptp_msg_name[msg], | ||
436 | ntohs(cid), ntohs(pcid)); | ||
437 | |||
438 | if (pptpReq->icack.resultCode == PPTP_INCALL_ACCEPT) { | ||
439 | /* part two of the three-way handshake */ | ||
440 | info->cstate = PPTP_CALL_IN_REP; | ||
441 | info->pns_call_id = cid; | ||
442 | } else | ||
443 | info->cstate = PPTP_CALL_NONE; | ||
444 | break; | ||
445 | |||
446 | case PPTP_CALL_CLEAR_REQUEST: | ||
447 | /* client requests hangup of call */ | ||
448 | if (info->sstate != PPTP_SESSION_CONFIRMED) | ||
449 | goto invalid; | ||
450 | /* FUTURE: iterate over all calls and check if | ||
451 | * call ID is valid. We don't do this without newnat, | ||
452 | * because we only know about last call */ | ||
453 | info->cstate = PPTP_CALL_CLEAR_REQ; | ||
454 | break; | ||
455 | |||
456 | case PPTP_SET_LINK_INFO: | ||
457 | case PPTP_ECHO_REQUEST: | ||
458 | case PPTP_ECHO_REPLY: | ||
459 | /* I don't have to explain these ;) */ | ||
460 | break; | ||
461 | |||
462 | default: | ||
463 | goto invalid; | ||
464 | } | ||
465 | |||
466 | nf_nat_pptp_outbound = rcu_dereference(nf_nat_pptp_hook_outbound); | ||
467 | if (nf_nat_pptp_outbound && ct->status & IPS_NAT_MASK) | ||
468 | return nf_nat_pptp_outbound(pskb, ct, ctinfo, ctlh, pptpReq); | ||
469 | return NF_ACCEPT; | ||
470 | |||
471 | invalid: | ||
472 | DEBUGP("invalid %s: type=%d cid=%u pcid=%u " | ||
473 | "cstate=%d sstate=%d pns_cid=%u pac_cid=%u\n", | ||
474 | msg <= PPTP_MSG_MAX ? pptp_msg_name[msg] : pptp_msg_name[0], | ||
475 | msg, ntohs(cid), ntohs(pcid), info->cstate, info->sstate, | ||
476 | ntohs(info->pns_call_id), ntohs(info->pac_call_id)); | ||
477 | return NF_ACCEPT; | ||
478 | } | ||
479 | |||
480 | static const unsigned int pptp_msg_size[] = { | ||
481 | [PPTP_START_SESSION_REQUEST] = sizeof(struct PptpStartSessionRequest), | ||
482 | [PPTP_START_SESSION_REPLY] = sizeof(struct PptpStartSessionReply), | ||
483 | [PPTP_STOP_SESSION_REQUEST] = sizeof(struct PptpStopSessionRequest), | ||
484 | [PPTP_STOP_SESSION_REPLY] = sizeof(struct PptpStopSessionReply), | ||
485 | [PPTP_OUT_CALL_REQUEST] = sizeof(struct PptpOutCallRequest), | ||
486 | [PPTP_OUT_CALL_REPLY] = sizeof(struct PptpOutCallReply), | ||
487 | [PPTP_IN_CALL_REQUEST] = sizeof(struct PptpInCallRequest), | ||
488 | [PPTP_IN_CALL_REPLY] = sizeof(struct PptpInCallReply), | ||
489 | [PPTP_IN_CALL_CONNECT] = sizeof(struct PptpInCallConnected), | ||
490 | [PPTP_CALL_CLEAR_REQUEST] = sizeof(struct PptpClearCallRequest), | ||
491 | [PPTP_CALL_DISCONNECT_NOTIFY] = sizeof(struct PptpCallDisconnectNotify), | ||
492 | [PPTP_WAN_ERROR_NOTIFY] = sizeof(struct PptpWanErrorNotify), | ||
493 | [PPTP_SET_LINK_INFO] = sizeof(struct PptpSetLinkInfo), | ||
494 | }; | ||
495 | |||
496 | /* track caller id inside control connection, call expect_related */ | ||
497 | static int | ||
498 | conntrack_pptp_help(struct sk_buff **pskb, unsigned int protoff, | ||
499 | struct nf_conn *ct, enum ip_conntrack_info ctinfo) | ||
500 | |||
501 | { | ||
502 | int dir = CTINFO2DIR(ctinfo); | ||
503 | struct nf_ct_pptp_master *info = &nfct_help(ct)->help.ct_pptp_info; | ||
504 | struct tcphdr _tcph, *tcph; | ||
505 | struct pptp_pkt_hdr _pptph, *pptph; | ||
506 | struct PptpControlHeader _ctlh, *ctlh; | ||
507 | union pptp_ctrl_union _pptpReq, *pptpReq; | ||
508 | unsigned int tcplen = (*pskb)->len - protoff; | ||
509 | unsigned int datalen, reqlen, nexthdr_off; | ||
510 | int oldsstate, oldcstate; | ||
511 | int ret; | ||
512 | u_int16_t msg; | ||
513 | |||
514 | /* don't do any tracking before tcp handshake complete */ | ||
515 | if (ctinfo != IP_CT_ESTABLISHED && | ||
516 | ctinfo != IP_CT_ESTABLISHED + IP_CT_IS_REPLY) | ||
517 | return NF_ACCEPT; | ||
518 | |||
519 | nexthdr_off = protoff; | ||
520 | tcph = skb_header_pointer(*pskb, nexthdr_off, sizeof(_tcph), &_tcph); | ||
521 | BUG_ON(!tcph); | ||
522 | nexthdr_off += tcph->doff * 4; | ||
523 | datalen = tcplen - tcph->doff * 4; | ||
524 | |||
525 | pptph = skb_header_pointer(*pskb, nexthdr_off, sizeof(_pptph), &_pptph); | ||
526 | if (!pptph) { | ||
527 | DEBUGP("no full PPTP header, can't track\n"); | ||
528 | return NF_ACCEPT; | ||
529 | } | ||
530 | nexthdr_off += sizeof(_pptph); | ||
531 | datalen -= sizeof(_pptph); | ||
532 | |||
533 | /* if it's not a control message we can't do anything with it */ | ||
534 | if (ntohs(pptph->packetType) != PPTP_PACKET_CONTROL || | ||
535 | ntohl(pptph->magicCookie) != PPTP_MAGIC_COOKIE) { | ||
536 | DEBUGP("not a control packet\n"); | ||
537 | return NF_ACCEPT; | ||
538 | } | ||
539 | |||
540 | ctlh = skb_header_pointer(*pskb, nexthdr_off, sizeof(_ctlh), &_ctlh); | ||
541 | if (!ctlh) | ||
542 | return NF_ACCEPT; | ||
543 | nexthdr_off += sizeof(_ctlh); | ||
544 | datalen -= sizeof(_ctlh); | ||
545 | |||
546 | reqlen = datalen; | ||
547 | msg = ntohs(ctlh->messageType); | ||
548 | if (msg > 0 && msg <= PPTP_MSG_MAX && reqlen < pptp_msg_size[msg]) | ||
549 | return NF_ACCEPT; | ||
550 | if (reqlen > sizeof(*pptpReq)) | ||
551 | reqlen = sizeof(*pptpReq); | ||
552 | |||
553 | pptpReq = skb_header_pointer(*pskb, nexthdr_off, reqlen, &_pptpReq); | ||
554 | if (!pptpReq) | ||
555 | return NF_ACCEPT; | ||
556 | |||
557 | oldsstate = info->sstate; | ||
558 | oldcstate = info->cstate; | ||
559 | |||
560 | spin_lock_bh(&nf_pptp_lock); | ||
561 | |||
562 | /* FIXME: We just blindly assume that the control connection is always | ||
563 | * established from PNS->PAC. However, RFC makes no guarantee */ | ||
564 | if (dir == IP_CT_DIR_ORIGINAL) | ||
565 | /* client -> server (PNS -> PAC) */ | ||
566 | ret = pptp_outbound_pkt(pskb, ctlh, pptpReq, reqlen, ct, | ||
567 | ctinfo); | ||
568 | else | ||
569 | /* server -> client (PAC -> PNS) */ | ||
570 | ret = pptp_inbound_pkt(pskb, ctlh, pptpReq, reqlen, ct, | ||
571 | ctinfo); | ||
572 | DEBUGP("sstate: %d->%d, cstate: %d->%d\n", | ||
573 | oldsstate, info->sstate, oldcstate, info->cstate); | ||
574 | spin_unlock_bh(&nf_pptp_lock); | ||
575 | |||
576 | return ret; | ||
577 | } | ||
578 | |||
579 | /* control protocol helper */ | ||
580 | static struct nf_conntrack_helper pptp __read_mostly = { | ||
581 | .name = "pptp", | ||
582 | .me = THIS_MODULE, | ||
583 | .max_expected = 2, | ||
584 | .timeout = 5 * 60, | ||
585 | .tuple.src.l3num = AF_INET, | ||
586 | .tuple.src.u.tcp.port = __constant_htons(PPTP_CONTROL_PORT), | ||
587 | .tuple.dst.protonum = IPPROTO_TCP, | ||
588 | .mask.src.l3num = 0xffff, | ||
589 | .mask.src.u.tcp.port = __constant_htons(0xffff), | ||
590 | .mask.dst.protonum = 0xff, | ||
591 | .help = conntrack_pptp_help, | ||
592 | .destroy = pptp_destroy_siblings, | ||
593 | }; | ||
594 | |||
595 | static int __init nf_conntrack_pptp_init(void) | ||
596 | { | ||
597 | return nf_conntrack_helper_register(&pptp); | ||
598 | } | ||
599 | |||
600 | static void __exit nf_conntrack_pptp_fini(void) | ||
601 | { | ||
602 | nf_conntrack_helper_unregister(&pptp); | ||
603 | nf_ct_gre_keymap_flush(); | ||
604 | } | ||
605 | |||
606 | module_init(nf_conntrack_pptp_init); | ||
607 | module_exit(nf_conntrack_pptp_fini); | ||
diff --git a/net/netfilter/nf_conntrack_proto_gre.c b/net/netfilter/nf_conntrack_proto_gre.c new file mode 100644 index 000000000000..ac193ce70249 --- /dev/null +++ b/net/netfilter/nf_conntrack_proto_gre.c | |||
@@ -0,0 +1,305 @@ | |||
1 | /* | ||
2 | * ip_conntrack_proto_gre.c - Version 3.0 | ||
3 | * | ||
4 | * Connection tracking protocol helper module for GRE. | ||
5 | * | ||
6 | * GRE is a generic encapsulation protocol, which is generally not very | ||
7 | * suited for NAT, as it has no protocol-specific part as port numbers. | ||
8 | * | ||
9 | * It has an optional key field, which may help us distinguishing two | ||
10 | * connections between the same two hosts. | ||
11 | * | ||
12 | * GRE is defined in RFC 1701 and RFC 1702, as well as RFC 2784 | ||
13 | * | ||
14 | * PPTP is built on top of a modified version of GRE, and has a mandatory | ||
15 | * field called "CallID", which serves us for the same purpose as the key | ||
16 | * field in plain GRE. | ||
17 | * | ||
18 | * Documentation about PPTP can be found in RFC 2637 | ||
19 | * | ||
20 | * (C) 2000-2005 by Harald Welte <laforge@gnumonks.org> | ||
21 | * | ||
22 | * Development of this code funded by Astaro AG (http://www.astaro.com/) | ||
23 | * | ||
24 | */ | ||
25 | |||
26 | #include <linux/module.h> | ||
27 | #include <linux/types.h> | ||
28 | #include <linux/timer.h> | ||
29 | #include <linux/list.h> | ||
30 | #include <linux/seq_file.h> | ||
31 | #include <linux/in.h> | ||
32 | #include <linux/skbuff.h> | ||
33 | |||
34 | #include <net/netfilter/nf_conntrack_l4proto.h> | ||
35 | #include <net/netfilter/nf_conntrack_helper.h> | ||
36 | #include <net/netfilter/nf_conntrack_core.h> | ||
37 | #include <linux/netfilter/nf_conntrack_proto_gre.h> | ||
38 | #include <linux/netfilter/nf_conntrack_pptp.h> | ||
39 | |||
40 | #define GRE_TIMEOUT (30 * HZ) | ||
41 | #define GRE_STREAM_TIMEOUT (180 * HZ) | ||
42 | |||
43 | #if 0 | ||
44 | #define DEBUGP(format, args...) printk(KERN_DEBUG "%s:%s: " format, __FILE__, __FUNCTION__, ## args) | ||
45 | #else | ||
46 | #define DEBUGP(x, args...) | ||
47 | #endif | ||
48 | |||
49 | static DEFINE_RWLOCK(nf_ct_gre_lock); | ||
50 | static LIST_HEAD(gre_keymap_list); | ||
51 | |||
52 | void nf_ct_gre_keymap_flush(void) | ||
53 | { | ||
54 | struct list_head *pos, *n; | ||
55 | |||
56 | write_lock_bh(&nf_ct_gre_lock); | ||
57 | list_for_each_safe(pos, n, &gre_keymap_list) { | ||
58 | list_del(pos); | ||
59 | kfree(pos); | ||
60 | } | ||
61 | write_unlock_bh(&nf_ct_gre_lock); | ||
62 | } | ||
63 | EXPORT_SYMBOL(nf_ct_gre_keymap_flush); | ||
64 | |||
65 | static inline int gre_key_cmpfn(const struct nf_ct_gre_keymap *km, | ||
66 | const struct nf_conntrack_tuple *t) | ||
67 | { | ||
68 | return km->tuple.src.l3num == t->src.l3num && | ||
69 | !memcmp(&km->tuple.src.u3, &t->src.u3, sizeof(t->src.u3)) && | ||
70 | !memcmp(&km->tuple.dst.u3, &t->dst.u3, sizeof(t->dst.u3)) && | ||
71 | km->tuple.dst.protonum == t->dst.protonum && | ||
72 | km->tuple.dst.u.all == t->dst.u.all; | ||
73 | } | ||
74 | |||
75 | /* look up the source key for a given tuple */ | ||
76 | static __be16 gre_keymap_lookup(struct nf_conntrack_tuple *t) | ||
77 | { | ||
78 | struct nf_ct_gre_keymap *km; | ||
79 | __be16 key = 0; | ||
80 | |||
81 | read_lock_bh(&nf_ct_gre_lock); | ||
82 | list_for_each_entry(km, &gre_keymap_list, list) { | ||
83 | if (gre_key_cmpfn(km, t)) { | ||
84 | key = km->tuple.src.u.gre.key; | ||
85 | break; | ||
86 | } | ||
87 | } | ||
88 | read_unlock_bh(&nf_ct_gre_lock); | ||
89 | |||
90 | DEBUGP("lookup src key 0x%x for ", key); | ||
91 | NF_CT_DUMP_TUPLE(t); | ||
92 | |||
93 | return key; | ||
94 | } | ||
95 | |||
96 | /* add a single keymap entry, associate with specified master ct */ | ||
97 | int nf_ct_gre_keymap_add(struct nf_conn *ct, enum ip_conntrack_dir dir, | ||
98 | struct nf_conntrack_tuple *t) | ||
99 | { | ||
100 | struct nf_conn_help *help = nfct_help(ct); | ||
101 | struct nf_ct_gre_keymap **kmp, *km; | ||
102 | |||
103 | BUG_ON(strcmp(help->helper->name, "pptp")); | ||
104 | kmp = &help->help.ct_pptp_info.keymap[dir]; | ||
105 | if (*kmp) { | ||
106 | /* check whether it's a retransmission */ | ||
107 | list_for_each_entry(km, &gre_keymap_list, list) { | ||
108 | if (gre_key_cmpfn(km, t) && km == *kmp) | ||
109 | return 0; | ||
110 | } | ||
111 | DEBUGP("trying to override keymap_%s for ct %p\n", | ||
112 | dir == IP_CT_DIR_REPLY ? "reply" : "orig", ct); | ||
113 | return -EEXIST; | ||
114 | } | ||
115 | |||
116 | km = kmalloc(sizeof(*km), GFP_ATOMIC); | ||
117 | if (!km) | ||
118 | return -ENOMEM; | ||
119 | memcpy(&km->tuple, t, sizeof(*t)); | ||
120 | *kmp = km; | ||
121 | |||
122 | DEBUGP("adding new entry %p: ", km); | ||
123 | NF_CT_DUMP_TUPLE(&km->tuple); | ||
124 | |||
125 | write_lock_bh(&nf_ct_gre_lock); | ||
126 | list_add_tail(&km->list, &gre_keymap_list); | ||
127 | write_unlock_bh(&nf_ct_gre_lock); | ||
128 | |||
129 | return 0; | ||
130 | } | ||
131 | EXPORT_SYMBOL_GPL(nf_ct_gre_keymap_add); | ||
132 | |||
133 | /* destroy the keymap entries associated with specified master ct */ | ||
134 | void nf_ct_gre_keymap_destroy(struct nf_conn *ct) | ||
135 | { | ||
136 | struct nf_conn_help *help = nfct_help(ct); | ||
137 | enum ip_conntrack_dir dir; | ||
138 | |||
139 | DEBUGP("entering for ct %p\n", ct); | ||
140 | BUG_ON(strcmp(help->helper->name, "pptp")); | ||
141 | |||
142 | write_lock_bh(&nf_ct_gre_lock); | ||
143 | for (dir = IP_CT_DIR_ORIGINAL; dir < IP_CT_DIR_MAX; dir++) { | ||
144 | if (help->help.ct_pptp_info.keymap[dir]) { | ||
145 | DEBUGP("removing %p from list\n", | ||
146 | help->help.ct_pptp_info.keymap[dir]); | ||
147 | list_del(&help->help.ct_pptp_info.keymap[dir]->list); | ||
148 | kfree(help->help.ct_pptp_info.keymap[dir]); | ||
149 | help->help.ct_pptp_info.keymap[dir] = NULL; | ||
150 | } | ||
151 | } | ||
152 | write_unlock_bh(&nf_ct_gre_lock); | ||
153 | } | ||
154 | EXPORT_SYMBOL_GPL(nf_ct_gre_keymap_destroy); | ||
155 | |||
156 | /* PUBLIC CONNTRACK PROTO HELPER FUNCTIONS */ | ||
157 | |||
158 | /* invert gre part of tuple */ | ||
159 | static int gre_invert_tuple(struct nf_conntrack_tuple *tuple, | ||
160 | const struct nf_conntrack_tuple *orig) | ||
161 | { | ||
162 | tuple->dst.u.gre.key = orig->src.u.gre.key; | ||
163 | tuple->src.u.gre.key = orig->dst.u.gre.key; | ||
164 | return 1; | ||
165 | } | ||
166 | |||
167 | /* gre hdr info to tuple */ | ||
168 | static int gre_pkt_to_tuple(const struct sk_buff *skb, | ||
169 | unsigned int dataoff, | ||
170 | struct nf_conntrack_tuple *tuple) | ||
171 | { | ||
172 | struct gre_hdr_pptp _pgrehdr, *pgrehdr; | ||
173 | __be16 srckey; | ||
174 | struct gre_hdr _grehdr, *grehdr; | ||
175 | |||
176 | /* first only delinearize old RFC1701 GRE header */ | ||
177 | grehdr = skb_header_pointer(skb, dataoff, sizeof(_grehdr), &_grehdr); | ||
178 | if (!grehdr || grehdr->version != GRE_VERSION_PPTP) { | ||
179 | /* try to behave like "nf_conntrack_proto_generic" */ | ||
180 | tuple->src.u.all = 0; | ||
181 | tuple->dst.u.all = 0; | ||
182 | return 1; | ||
183 | } | ||
184 | |||
185 | /* PPTP header is variable length, only need up to the call_id field */ | ||
186 | pgrehdr = skb_header_pointer(skb, dataoff, 8, &_pgrehdr); | ||
187 | if (!pgrehdr) | ||
188 | return 1; | ||
189 | |||
190 | if (ntohs(grehdr->protocol) != GRE_PROTOCOL_PPTP) { | ||
191 | DEBUGP("GRE_VERSION_PPTP but unknown proto\n"); | ||
192 | return 0; | ||
193 | } | ||
194 | |||
195 | tuple->dst.u.gre.key = pgrehdr->call_id; | ||
196 | srckey = gre_keymap_lookup(tuple); | ||
197 | tuple->src.u.gre.key = srckey; | ||
198 | |||
199 | return 1; | ||
200 | } | ||
201 | |||
202 | /* print gre part of tuple */ | ||
203 | static int gre_print_tuple(struct seq_file *s, | ||
204 | const struct nf_conntrack_tuple *tuple) | ||
205 | { | ||
206 | return seq_printf(s, "srckey=0x%x dstkey=0x%x ", | ||
207 | ntohs(tuple->src.u.gre.key), | ||
208 | ntohs(tuple->dst.u.gre.key)); | ||
209 | } | ||
210 | |||
211 | /* print private data for conntrack */ | ||
212 | static int gre_print_conntrack(struct seq_file *s, | ||
213 | const struct nf_conn *ct) | ||
214 | { | ||
215 | return seq_printf(s, "timeout=%u, stream_timeout=%u ", | ||
216 | (ct->proto.gre.timeout / HZ), | ||
217 | (ct->proto.gre.stream_timeout / HZ)); | ||
218 | } | ||
219 | |||
220 | /* Returns verdict for packet, and may modify conntrack */ | ||
221 | static int gre_packet(struct nf_conn *ct, | ||
222 | const struct sk_buff *skb, | ||
223 | unsigned int dataoff, | ||
224 | enum ip_conntrack_info ctinfo, | ||
225 | int pf, | ||
226 | unsigned int hooknum) | ||
227 | { | ||
228 | /* If we've seen traffic both ways, this is a GRE connection. | ||
229 | * Extend timeout. */ | ||
230 | if (ct->status & IPS_SEEN_REPLY) { | ||
231 | nf_ct_refresh_acct(ct, ctinfo, skb, | ||
232 | ct->proto.gre.stream_timeout); | ||
233 | /* Also, more likely to be important, and not a probe. */ | ||
234 | set_bit(IPS_ASSURED_BIT, &ct->status); | ||
235 | nf_conntrack_event_cache(IPCT_STATUS, skb); | ||
236 | } else | ||
237 | nf_ct_refresh_acct(ct, ctinfo, skb, | ||
238 | ct->proto.gre.timeout); | ||
239 | |||
240 | return NF_ACCEPT; | ||
241 | } | ||
242 | |||
243 | /* Called when a new connection for this protocol found. */ | ||
244 | static int gre_new(struct nf_conn *ct, const struct sk_buff *skb, | ||
245 | unsigned int dataoff) | ||
246 | { | ||
247 | DEBUGP(": "); | ||
248 | NF_CT_DUMP_TUPLE(&ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple); | ||
249 | |||
250 | /* initialize to sane value. Ideally a conntrack helper | ||
251 | * (e.g. in case of pptp) is increasing them */ | ||
252 | ct->proto.gre.stream_timeout = GRE_STREAM_TIMEOUT; | ||
253 | ct->proto.gre.timeout = GRE_TIMEOUT; | ||
254 | |||
255 | return 1; | ||
256 | } | ||
257 | |||
258 | /* Called when a conntrack entry has already been removed from the hashes | ||
259 | * and is about to be deleted from memory */ | ||
260 | static void gre_destroy(struct nf_conn *ct) | ||
261 | { | ||
262 | struct nf_conn *master = ct->master; | ||
263 | DEBUGP(" entering\n"); | ||
264 | |||
265 | if (!master) | ||
266 | DEBUGP("no master !?!\n"); | ||
267 | else | ||
268 | nf_ct_gre_keymap_destroy(master); | ||
269 | } | ||
270 | |||
271 | /* protocol helper struct */ | ||
272 | static struct nf_conntrack_l4proto nf_conntrack_l4proto_gre4 = { | ||
273 | .l3proto = AF_INET, | ||
274 | .l4proto = IPPROTO_GRE, | ||
275 | .name = "gre", | ||
276 | .pkt_to_tuple = gre_pkt_to_tuple, | ||
277 | .invert_tuple = gre_invert_tuple, | ||
278 | .print_tuple = gre_print_tuple, | ||
279 | .print_conntrack = gre_print_conntrack, | ||
280 | .packet = gre_packet, | ||
281 | .new = gre_new, | ||
282 | .destroy = gre_destroy, | ||
283 | .me = THIS_MODULE, | ||
284 | #if defined(CONFIG_NF_CONNTRACK_NETLINK) || \ | ||
285 | defined(CONFIG_NF_CONNTRACK_NETLINK_MODULE) | ||
286 | .tuple_to_nfattr = nf_ct_port_tuple_to_nfattr, | ||
287 | .nfattr_to_tuple = nf_ct_port_nfattr_to_tuple, | ||
288 | #endif | ||
289 | }; | ||
290 | |||
291 | static int __init nf_ct_proto_gre_init(void) | ||
292 | { | ||
293 | return nf_conntrack_l4proto_register(&nf_conntrack_l4proto_gre4); | ||
294 | } | ||
295 | |||
296 | static void nf_ct_proto_gre_fini(void) | ||
297 | { | ||
298 | nf_conntrack_l4proto_unregister(&nf_conntrack_l4proto_gre4); | ||
299 | nf_ct_gre_keymap_flush(); | ||
300 | } | ||
301 | |||
302 | module_init(nf_ct_proto_gre_init); | ||
303 | module_exit(nf_ct_proto_gre_fini); | ||
304 | |||
305 | MODULE_LICENSE("GPL"); | ||