1 | /* $NetBSD: ip_ipsec_pxy.c,v 1.3 2012/07/22 14:27:51 darrenr Exp $ */ |
2 | |
3 | /* |
4 | * Copyright (C) 2012 by Darren Reed. |
5 | * |
6 | * See the IPFILTER.LICENCE file for details on licencing. |
7 | * |
8 | * Simple ISAKMP transparent proxy for in-kernel use. For use with the NAT |
9 | * code. |
10 | * |
11 | * Id: ip_ipsec_pxy.c,v 1.1.1.2 2012/07/22 13:45:19 darrenr Exp |
12 | * |
13 | */ |
14 | |
15 | #include <sys/cdefs.h> |
16 | __KERNEL_RCSID(1, "$NetBSD: ip_ipsec_pxy.c,v 1.3 2012/07/22 14:27:51 darrenr Exp $" ); |
17 | |
18 | #define IPF_IPSEC_PROXY |
19 | |
20 | |
21 | /* |
22 | * IPSec proxy |
23 | */ |
24 | typedef struct ipf_ipsec_softc_s { |
25 | frentry_t ipsec_fr; |
26 | int ipsec_proxy_init; |
27 | int ipsec_proxy_ttl; |
28 | ipftq_t *ipsec_nat_tqe; |
29 | ipftq_t *ipsec_state_tqe; |
30 | char ipsec_buffer[1500]; |
31 | } ipf_ipsec_softc_t; |
32 | |
33 | |
34 | void *ipf_p_ipsec_soft_create(ipf_main_softc_t *); |
35 | void ipf_p_ipsec_soft_destroy(ipf_main_softc_t *, void *); |
36 | int ipf_p_ipsec_soft_init(ipf_main_softc_t *, void *); |
37 | void ipf_p_ipsec_soft_fini(ipf_main_softc_t *, void *); |
38 | int ipf_p_ipsec_init(void); |
39 | void ipf_p_ipsec_fini(void); |
40 | int ipf_p_ipsec_new(void *, fr_info_t *, ap_session_t *, nat_t *); |
41 | void ipf_p_ipsec_del(ipf_main_softc_t *, ap_session_t *); |
42 | int ipf_p_ipsec_inout(void *, fr_info_t *, ap_session_t *, nat_t *); |
43 | int ipf_p_ipsec_match(fr_info_t *, ap_session_t *, nat_t *); |
44 | |
45 | |
46 | /* |
47 | * IPSec application proxy initialization. |
48 | */ |
49 | void * |
50 | ipf_p_ipsec_soft_create(ipf_main_softc_t *softc) |
51 | { |
52 | ipf_ipsec_softc_t *softi; |
53 | |
54 | KMALLOC(softi, ipf_ipsec_softc_t *); |
55 | if (softi == NULL) |
56 | return NULL; |
57 | |
58 | bzero((char *)softi, sizeof(*softi)); |
59 | softi->ipsec_fr.fr_ref = 1; |
60 | softi->ipsec_fr.fr_flags = FR_OUTQUE|FR_PASS|FR_QUICK|FR_KEEPSTATE; |
61 | MUTEX_INIT(&softi->ipsec_fr.fr_lock, "IPsec proxy rule lock" ); |
62 | softi->ipsec_proxy_init = 1; |
63 | softi->ipsec_proxy_ttl = 60; |
64 | |
65 | return softi; |
66 | } |
67 | |
68 | |
69 | int |
70 | ipf_p_ipsec_soft_init(ipf_main_softc_t *softc, void *arg) |
71 | { |
72 | ipf_ipsec_softc_t *softi = arg; |
73 | |
74 | softi->ipsec_nat_tqe = ipf_state_add_tq(softc, softi->ipsec_proxy_ttl); |
75 | if (softi->ipsec_nat_tqe == NULL) |
76 | return -1; |
77 | softi->ipsec_state_tqe = ipf_nat_add_tq(softc, softi->ipsec_proxy_ttl); |
78 | if (softi->ipsec_state_tqe == NULL) { |
79 | if (ipf_deletetimeoutqueue(softi->ipsec_nat_tqe) == 0) |
80 | ipf_freetimeoutqueue(softc, softi->ipsec_nat_tqe); |
81 | softi->ipsec_nat_tqe = NULL; |
82 | return -1; |
83 | } |
84 | |
85 | softi->ipsec_nat_tqe->ifq_flags |= IFQF_PROXY; |
86 | softi->ipsec_state_tqe->ifq_flags |= IFQF_PROXY; |
87 | softi->ipsec_fr.fr_age[0] = softi->ipsec_proxy_ttl; |
88 | softi->ipsec_fr.fr_age[1] = softi->ipsec_proxy_ttl; |
89 | return 0; |
90 | } |
91 | |
92 | |
93 | void |
94 | ipf_p_ipsec_soft_fini(ipf_main_softc_t *softc, void *arg) |
95 | { |
96 | ipf_ipsec_softc_t *softi = arg; |
97 | |
98 | if (arg == NULL) |
99 | return; |
100 | |
101 | if (softi->ipsec_nat_tqe != NULL) { |
102 | if (ipf_deletetimeoutqueue(softi->ipsec_nat_tqe) == 0) |
103 | ipf_freetimeoutqueue(softc, softi->ipsec_nat_tqe); |
104 | } |
105 | softi->ipsec_nat_tqe = NULL; |
106 | if (softi->ipsec_state_tqe != NULL) { |
107 | if (ipf_deletetimeoutqueue(softi->ipsec_state_tqe) == 0) |
108 | ipf_freetimeoutqueue(softc, softi->ipsec_state_tqe); |
109 | } |
110 | softi->ipsec_state_tqe = NULL; |
111 | } |
112 | |
113 | |
114 | void |
115 | ipf_p_ipsec_soft_destroy(ipf_main_softc_t *softc, void *arg) |
116 | { |
117 | ipf_ipsec_softc_t *softi = arg; |
118 | |
119 | if (softi->ipsec_proxy_init == 1) { |
120 | MUTEX_DESTROY(&softi->ipsec_fr.fr_lock); |
121 | softi->ipsec_proxy_init = 0; |
122 | } |
123 | |
124 | KFREE(softi); |
125 | } |
126 | |
127 | |
128 | /* |
129 | * Setup for a new IPSEC proxy. |
130 | */ |
131 | int |
132 | ipf_p_ipsec_new(void *arg, fr_info_t *fin, ap_session_t *aps, nat_t *nat) |
133 | { |
134 | ipf_ipsec_softc_t *softi = arg; |
135 | ipf_main_softc_t *softc = fin->fin_main_soft; |
136 | #ifdef USE_MUTEXES |
137 | ipf_nat_softc_t *softn = softc->ipf_nat_soft; |
138 | #endif |
139 | int p, off, dlen, ttl; |
140 | ipsec_pxy_t *ipsec; |
141 | ipnat_t *ipn, *np; |
142 | fr_info_t fi; |
143 | char *ptr; |
144 | int size; |
145 | ip_t *ip; |
146 | mb_t *m; |
147 | |
148 | if (fin->fin_v != 4) |
149 | return -1; |
150 | |
151 | off = fin->fin_plen - fin->fin_dlen + fin->fin_ipoff; |
152 | bzero(softi->ipsec_buffer, sizeof(softi->ipsec_buffer)); |
153 | ip = fin->fin_ip; |
154 | m = fin->fin_m; |
155 | |
156 | dlen = M_LEN(m) - off; |
157 | if (dlen < 16) |
158 | return -1; |
159 | COPYDATA(m, off, MIN(sizeof(softi->ipsec_buffer), dlen), |
160 | softi->ipsec_buffer); |
161 | |
162 | if (ipf_nat_outlookup(fin, 0, IPPROTO_ESP, nat->nat_nsrcip, |
163 | ip->ip_dst) != NULL) |
164 | return -1; |
165 | |
166 | np = nat->nat_ptr; |
167 | size = np->in_size; |
168 | KMALLOC(ipsec, ipsec_pxy_t *); |
169 | if (ipsec == NULL) |
170 | return -1; |
171 | |
172 | KMALLOCS(ipn, ipnat_t *, size); |
173 | if (ipn == NULL) { |
174 | KFREE(ipsec); |
175 | return -1; |
176 | } |
177 | |
178 | aps->aps_data = ipsec; |
179 | aps->aps_psiz = sizeof(*ipsec); |
180 | bzero((char *)ipsec, sizeof(*ipsec)); |
181 | bzero((char *)ipn, size); |
182 | ipsec->ipsc_rule = ipn; |
183 | |
184 | /* |
185 | * Create NAT rule against which the tunnel/transport mapping is |
186 | * created. This is required because the current NAT rule does not |
187 | * describe ESP but UDP instead. |
188 | */ |
189 | ipn->in_size = size; |
190 | ttl = IPF_TTLVAL(softi->ipsec_nat_tqe->ifq_ttl); |
191 | ipn->in_tqehead[0] = ipf_nat_add_tq(softc, ttl); |
192 | ipn->in_tqehead[1] = ipf_nat_add_tq(softc, ttl); |
193 | ipn->in_ifps[0] = fin->fin_ifp; |
194 | ipn->in_apr = NULL; |
195 | ipn->in_use = 1; |
196 | ipn->in_hits = 1; |
197 | ipn->in_snip = ntohl(nat->nat_nsrcaddr); |
198 | ipn->in_ippip = 1; |
199 | ipn->in_osrcip = nat->nat_osrcip; |
200 | ipn->in_osrcmsk = 0xffffffff; |
201 | ipn->in_nsrcip = nat->nat_nsrcip; |
202 | ipn->in_nsrcmsk = 0xffffffff; |
203 | ipn->in_odstip = nat->nat_odstip; |
204 | ipn->in_odstmsk = 0xffffffff; |
205 | ipn->in_ndstip = nat->nat_ndstip; |
206 | ipn->in_ndstmsk = 0xffffffff; |
207 | ipn->in_redir = NAT_MAP; |
208 | ipn->in_pr[0] = IPPROTO_ESP; |
209 | ipn->in_pr[1] = IPPROTO_ESP; |
210 | ipn->in_flags = (np->in_flags | IPN_PROXYRULE); |
211 | MUTEX_INIT(&ipn->in_lock, "IPSec proxy NAT rule" ); |
212 | |
213 | ipn->in_namelen = np->in_namelen; |
214 | bcopy(np->in_names, ipn->in_ifnames, ipn->in_namelen); |
215 | ipn->in_ifnames[0] = np->in_ifnames[0]; |
216 | ipn->in_ifnames[1] = np->in_ifnames[1]; |
217 | |
218 | bcopy((char *)fin, (char *)&fi, sizeof(fi)); |
219 | fi.fin_fi.fi_p = IPPROTO_ESP; |
220 | fi.fin_fr = &softi->ipsec_fr; |
221 | fi.fin_data[0] = 0; |
222 | fi.fin_data[1] = 0; |
223 | p = ip->ip_p; |
224 | ip->ip_p = IPPROTO_ESP; |
225 | fi.fin_flx &= ~(FI_TCPUDP|FI_STATE|FI_FRAG); |
226 | fi.fin_flx |= FI_IGNORE; |
227 | |
228 | ptr = softi->ipsec_buffer; |
229 | bcopy(ptr, (char *)ipsec->ipsc_icookie, sizeof(ipsec_cookie_t)); |
230 | ptr += sizeof(ipsec_cookie_t); |
231 | bcopy(ptr, (char *)ipsec->ipsc_rcookie, sizeof(ipsec_cookie_t)); |
232 | /* |
233 | * The responder cookie should only be non-zero if the initiator |
234 | * cookie is non-zero. Therefore, it is safe to assume(!) that the |
235 | * cookies are both set after copying if the responder is non-zero. |
236 | */ |
237 | if ((ipsec->ipsc_rcookie[0]|ipsec->ipsc_rcookie[1]) != 0) |
238 | ipsec->ipsc_rckset = 1; |
239 | |
240 | MUTEX_ENTER(&softn->ipf_nat_new); |
241 | ipsec->ipsc_nat = ipf_nat_add(&fi, ipn, &ipsec->ipsc_nat, |
242 | NAT_SLAVE|SI_WILDP, NAT_OUTBOUND); |
243 | MUTEX_EXIT(&softn->ipf_nat_new); |
244 | if (ipsec->ipsc_nat != NULL) { |
245 | (void) ipf_nat_proto(&fi, ipsec->ipsc_nat, 0); |
246 | MUTEX_ENTER(&ipsec->ipsc_nat->nat_lock); |
247 | ipf_nat_update(&fi, ipsec->ipsc_nat); |
248 | MUTEX_EXIT(&ipsec->ipsc_nat->nat_lock); |
249 | |
250 | fi.fin_data[0] = 0; |
251 | fi.fin_data[1] = 0; |
252 | (void) ipf_state_add(softc, &fi, &ipsec->ipsc_state, SI_WILDP); |
253 | } |
254 | ip->ip_p = p & 0xff; |
255 | return 0; |
256 | } |
257 | |
258 | |
259 | /* |
260 | * For outgoing IKE packets. refresh timeouts for NAT & state entries, if |
261 | * we can. If they have disappeared, recreate them. |
262 | */ |
263 | int |
264 | ipf_p_ipsec_inout(void *arg, fr_info_t *fin, ap_session_t *aps, nat_t *nat) |
265 | { |
266 | ipf_ipsec_softc_t *softi = arg; |
267 | ipf_main_softc_t *softc = fin->fin_main_soft; |
268 | ipsec_pxy_t *ipsec; |
269 | fr_info_t fi; |
270 | ip_t *ip; |
271 | int p; |
272 | |
273 | if ((fin->fin_out == 1) && (nat->nat_dir == NAT_INBOUND)) |
274 | return 0; |
275 | |
276 | if ((fin->fin_out == 0) && (nat->nat_dir == NAT_OUTBOUND)) |
277 | return 0; |
278 | |
279 | ipsec = aps->aps_data; |
280 | |
281 | if (ipsec != NULL) { |
282 | ip = fin->fin_ip; |
283 | p = ip->ip_p; |
284 | |
285 | if ((ipsec->ipsc_nat == NULL) || (ipsec->ipsc_state == NULL)) { |
286 | bcopy((char *)fin, (char *)&fi, sizeof(fi)); |
287 | fi.fin_fi.fi_p = IPPROTO_ESP; |
288 | fi.fin_fr = &softi->ipsec_fr; |
289 | fi.fin_data[0] = 0; |
290 | fi.fin_data[1] = 0; |
291 | ip->ip_p = IPPROTO_ESP; |
292 | fi.fin_flx &= ~(FI_TCPUDP|FI_STATE|FI_FRAG); |
293 | fi.fin_flx |= FI_IGNORE; |
294 | } |
295 | |
296 | /* |
297 | * Update NAT timeout/create NAT if missing. |
298 | */ |
299 | if (ipsec->ipsc_nat != NULL) |
300 | ipf_queueback(softc->ipf_ticks, |
301 | &ipsec->ipsc_nat->nat_tqe); |
302 | else { |
303 | #ifdef USE_MUTEXES |
304 | ipf_nat_softc_t *softn = softc->ipf_nat_soft; |
305 | #endif |
306 | |
307 | MUTEX_ENTER(&softn->ipf_nat_new); |
308 | ipsec->ipsc_nat = ipf_nat_add(&fi, ipsec->ipsc_rule, |
309 | &ipsec->ipsc_nat, |
310 | NAT_SLAVE|SI_WILDP, |
311 | nat->nat_dir); |
312 | MUTEX_EXIT(&softn->ipf_nat_new); |
313 | if (ipsec->ipsc_nat != NULL) { |
314 | (void) ipf_nat_proto(&fi, ipsec->ipsc_nat, 0); |
315 | MUTEX_ENTER(&ipsec->ipsc_nat->nat_lock); |
316 | ipf_nat_update(&fi, ipsec->ipsc_nat); |
317 | MUTEX_EXIT(&ipsec->ipsc_nat->nat_lock); |
318 | } |
319 | } |
320 | |
321 | /* |
322 | * Update state timeout/create state if missing. |
323 | */ |
324 | READ_ENTER(&softc->ipf_state); |
325 | if (ipsec->ipsc_state != NULL) { |
326 | ipf_queueback(softc->ipf_ticks, |
327 | &ipsec->ipsc_state->is_sti); |
328 | ipsec->ipsc_state->is_die = nat->nat_age; |
329 | RWLOCK_EXIT(&softc->ipf_state); |
330 | } else { |
331 | RWLOCK_EXIT(&softc->ipf_state); |
332 | fi.fin_data[0] = 0; |
333 | fi.fin_data[1] = 0; |
334 | (void) ipf_state_add(softc, &fi, &ipsec->ipsc_state, |
335 | SI_WILDP); |
336 | } |
337 | ip->ip_p = p; |
338 | } |
339 | return 0; |
340 | } |
341 | |
342 | |
343 | /* |
344 | * This extends the NAT matching to be based on the cookies associated with |
345 | * a session and found at the front of IKE packets. The cookies are always |
346 | * in the same order (not reversed depending on packet flow direction as with |
347 | * UDP/TCP port numbers). |
348 | */ |
349 | int |
350 | ipf_p_ipsec_match(fr_info_t *fin, ap_session_t *aps, nat_t *nat) |
351 | { |
352 | ipsec_pxy_t *ipsec; |
353 | u_32_t cookies[4]; |
354 | mb_t *m; |
355 | int off; |
356 | |
357 | nat = nat; /* LINT */ |
358 | |
359 | if ((fin->fin_dlen < sizeof(cookies)) || (fin->fin_flx & FI_FRAG)) |
360 | return -1; |
361 | |
362 | off = fin->fin_plen - fin->fin_dlen + fin->fin_ipoff; |
363 | ipsec = aps->aps_data; |
364 | m = fin->fin_m; |
365 | COPYDATA(m, off, sizeof(cookies), (char *)cookies); |
366 | |
367 | if ((cookies[0] != ipsec->ipsc_icookie[0]) || |
368 | (cookies[1] != ipsec->ipsc_icookie[1])) |
369 | return -1; |
370 | |
371 | if (ipsec->ipsc_rckset == 0) { |
372 | if ((cookies[2]|cookies[3]) == 0) { |
373 | return 0; |
374 | } |
375 | ipsec->ipsc_rckset = 1; |
376 | ipsec->ipsc_rcookie[0] = cookies[2]; |
377 | ipsec->ipsc_rcookie[1] = cookies[3]; |
378 | return 0; |
379 | } |
380 | |
381 | if ((cookies[2] != ipsec->ipsc_rcookie[0]) || |
382 | (cookies[3] != ipsec->ipsc_rcookie[1])) |
383 | return -1; |
384 | return 0; |
385 | } |
386 | |
387 | |
388 | /* |
389 | * clean up after ourselves. |
390 | */ |
391 | void |
392 | ipf_p_ipsec_del(ipf_main_softc_t *softc, ap_session_t *aps) |
393 | { |
394 | ipsec_pxy_t *ipsec; |
395 | |
396 | ipsec = aps->aps_data; |
397 | |
398 | if (ipsec != NULL) { |
399 | /* |
400 | * Don't bother changing any of the NAT structure details, |
401 | * *_del() is on a callback from aps_free(), from nat_delete() |
402 | */ |
403 | |
404 | READ_ENTER(&softc->ipf_state); |
405 | if (ipsec->ipsc_state != NULL) { |
406 | ipsec->ipsc_state->is_die = softc->ipf_ticks + 1; |
407 | ipsec->ipsc_state->is_me = NULL; |
408 | ipf_queuefront(&ipsec->ipsc_state->is_sti); |
409 | } |
410 | RWLOCK_EXIT(&softc->ipf_state); |
411 | |
412 | ipsec->ipsc_state = NULL; |
413 | ipsec->ipsc_nat = NULL; |
414 | ipsec->ipsc_rule->in_flags |= IPN_DELETE; |
415 | ipf_nat_rule_deref(softc, &ipsec->ipsc_rule); |
416 | } |
417 | } |
418 | |