1 |
|
|
/* $OpenBSD: dpd.c,v 1.19 2015/12/10 17:27:00 mmcc Exp $ */ |
2 |
|
|
|
3 |
|
|
/* |
4 |
|
|
* Copyright (c) 2004 Håkan Olsson. All rights reserved. |
5 |
|
|
* |
6 |
|
|
* Redistribution and use in source and binary forms, with or without |
7 |
|
|
* modification, are permitted provided that the following conditions |
8 |
|
|
* are met: |
9 |
|
|
* 1. Redistributions of source code must retain the above copyright |
10 |
|
|
* notice, this list of conditions and the following disclaimer. |
11 |
|
|
* 2. Redistributions in binary form must reproduce the above copyright |
12 |
|
|
* notice, this list of conditions and the following disclaimer in the |
13 |
|
|
* documentation and/or other materials provided with the distribution. |
14 |
|
|
* |
15 |
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR |
16 |
|
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
17 |
|
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. |
18 |
|
|
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, |
19 |
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
20 |
|
|
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
21 |
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
22 |
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
23 |
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
24 |
|
|
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
25 |
|
|
*/ |
26 |
|
|
|
27 |
|
|
#include <sys/types.h> |
28 |
|
|
#include <stdlib.h> |
29 |
|
|
#include <string.h> |
30 |
|
|
|
31 |
|
|
#include "conf.h" |
32 |
|
|
#include "dpd.h" |
33 |
|
|
#include "exchange.h" |
34 |
|
|
#include "hash.h" |
35 |
|
|
#include "ipsec.h" |
36 |
|
|
#include "isakmp_fld.h" |
37 |
|
|
#include "log.h" |
38 |
|
|
#include "message.h" |
39 |
|
|
#include "pf_key_v2.h" |
40 |
|
|
#include "sa.h" |
41 |
|
|
#include "timer.h" |
42 |
|
|
#include "transport.h" |
43 |
|
|
#include "util.h" |
44 |
|
|
|
45 |
|
|
/* From RFC 3706. */ |
46 |
|
|
#define DPD_MAJOR 0x01 |
47 |
|
|
#define DPD_MINOR 0x00 |
48 |
|
|
#define DPD_SEQNO_SZ 4 |
49 |
|
|
|
50 |
|
|
static const u_int8_t dpd_vendor_id[] = { |
51 |
|
|
0xAF, 0xCA, 0xD7, 0x13, 0x68, 0xA1, 0xF1, /* RFC 3706 */ |
52 |
|
|
0xC9, 0x6B, 0x86, 0x96, 0xFC, 0x77, 0x57, |
53 |
|
|
DPD_MAJOR, |
54 |
|
|
DPD_MINOR |
55 |
|
|
}; |
56 |
|
|
|
57 |
|
|
#define DPD_RETRANS_MAX 5 /* max number of retries. */ |
58 |
|
|
#define DPD_RETRANS_WAIT 5 /* seconds between retries. */ |
59 |
|
|
|
60 |
|
|
/* DPD Timer State */ |
61 |
|
|
enum dpd_tstate { DPD_TIMER_NORMAL, DPD_TIMER_CHECK }; |
62 |
|
|
|
63 |
|
|
static void dpd_check_event(void *); |
64 |
|
|
static void dpd_event(void *); |
65 |
|
|
static u_int32_t dpd_timer_interval(u_int32_t); |
66 |
|
|
static void dpd_timer_reset(struct sa *, u_int32_t, enum dpd_tstate); |
67 |
|
|
|
68 |
|
|
/* Add the DPD VENDOR ID payload. */ |
69 |
|
|
int |
70 |
|
|
dpd_add_vendor_payload(struct message *msg) |
71 |
|
|
{ |
72 |
|
|
u_int8_t *buf; |
73 |
|
|
size_t buflen = sizeof dpd_vendor_id + ISAKMP_GEN_SZ; |
74 |
|
|
|
75 |
|
|
buf = malloc(buflen); |
76 |
|
|
if (!buf) { |
77 |
|
|
log_error("dpd_add_vendor_payload: malloc(%lu) failed", |
78 |
|
|
(unsigned long)buflen); |
79 |
|
|
return -1; |
80 |
|
|
} |
81 |
|
|
|
82 |
|
|
SET_ISAKMP_GEN_LENGTH(buf, buflen); |
83 |
|
|
memcpy(buf + ISAKMP_VENDOR_ID_OFF, dpd_vendor_id, |
84 |
|
|
sizeof dpd_vendor_id); |
85 |
|
|
if (message_add_payload(msg, ISAKMP_PAYLOAD_VENDOR, buf, buflen, 1)) { |
86 |
|
|
free(buf); |
87 |
|
|
return -1; |
88 |
|
|
} |
89 |
|
|
|
90 |
|
|
return 0; |
91 |
|
|
} |
92 |
|
|
|
93 |
|
|
/* |
94 |
|
|
* Check an incoming message for DPD capability markers. |
95 |
|
|
*/ |
96 |
|
|
void |
97 |
|
|
dpd_check_vendor_payload(struct message *msg, struct payload *p) |
98 |
|
|
{ |
99 |
|
|
u_int8_t *pbuf = p->p; |
100 |
|
|
size_t vlen; |
101 |
|
|
|
102 |
|
|
/* Already checked? */ |
103 |
|
|
if (msg->exchange->flags & EXCHANGE_FLAG_DPD_CAP_PEER) { |
104 |
|
|
/* Just mark it as handled and return. */ |
105 |
|
|
p->flags |= PL_MARK; |
106 |
|
|
return; |
107 |
|
|
} |
108 |
|
|
|
109 |
|
|
vlen = GET_ISAKMP_GEN_LENGTH(pbuf) - ISAKMP_GEN_SZ; |
110 |
|
|
if (vlen != sizeof dpd_vendor_id) { |
111 |
|
|
LOG_DBG((LOG_EXCHANGE, 90, |
112 |
|
|
"dpd_check_vendor_payload: bad size %lu != %lu", |
113 |
|
|
(unsigned long)vlen, (unsigned long)sizeof dpd_vendor_id)); |
114 |
|
|
return; |
115 |
|
|
} |
116 |
|
|
|
117 |
|
|
if (memcmp(dpd_vendor_id, pbuf + ISAKMP_GEN_SZ, vlen) == 0) { |
118 |
|
|
/* This peer is DPD capable. */ |
119 |
|
|
if (msg->isakmp_sa) { |
120 |
|
|
msg->exchange->flags |= EXCHANGE_FLAG_DPD_CAP_PEER; |
121 |
|
|
LOG_DBG((LOG_EXCHANGE, 10, "dpd_check_vendor_payload: " |
122 |
|
|
"DPD capable peer detected")); |
123 |
|
|
} |
124 |
|
|
p->flags |= PL_MARK; |
125 |
|
|
} |
126 |
|
|
} |
127 |
|
|
|
128 |
|
|
/* |
129 |
|
|
* Arm the DPD timer |
130 |
|
|
*/ |
131 |
|
|
void |
132 |
|
|
dpd_start(struct sa *isakmp_sa) |
133 |
|
|
{ |
134 |
|
|
if (dpd_timer_interval(0) != 0) { |
135 |
|
|
LOG_DBG((LOG_EXCHANGE, 10, "dpd_enable: enabling")); |
136 |
|
|
isakmp_sa->flags |= SA_FLAG_DPD; |
137 |
|
|
dpd_timer_reset(isakmp_sa, 0, DPD_TIMER_NORMAL); |
138 |
|
|
} |
139 |
|
|
} |
140 |
|
|
|
141 |
|
|
/* |
142 |
|
|
* All incoming DPD Notify messages enter here. Message has been validated. |
143 |
|
|
*/ |
144 |
|
|
void |
145 |
|
|
dpd_handle_notify(struct message *msg, struct payload *p) |
146 |
|
|
{ |
147 |
|
|
struct sa *isakmp_sa = msg->isakmp_sa; |
148 |
|
|
u_int16_t notify = GET_ISAKMP_NOTIFY_MSG_TYPE(p->p); |
149 |
|
|
u_int32_t p_seq; |
150 |
|
|
|
151 |
|
|
/* Extract the sequence number. */ |
152 |
|
|
memcpy(&p_seq, p->p + ISAKMP_NOTIFY_SPI_OFF + ISAKMP_HDR_COOKIES_LEN, |
153 |
|
|
sizeof p_seq); |
154 |
|
|
p_seq = ntohl(p_seq); |
155 |
|
|
|
156 |
|
|
LOG_DBG((LOG_MESSAGE, 40, "dpd_handle_notify: got %s seq %u", |
157 |
|
|
constant_name(isakmp_notify_cst, notify), p_seq)); |
158 |
|
|
|
159 |
|
|
switch (notify) { |
160 |
|
|
case ISAKMP_NOTIFY_STATUS_DPD_R_U_THERE: |
161 |
|
|
/* The other peer wants to know we're alive. */ |
162 |
|
|
if (p_seq < isakmp_sa->dpd_rseq || |
163 |
|
|
(p_seq == isakmp_sa->dpd_rseq && |
164 |
|
|
++isakmp_sa->dpd_rdupcount >= DPD_RETRANS_MAX)) { |
165 |
|
|
log_print("dpd_handle_notify: bad R_U_THERE seqno " |
166 |
|
|
"%u <= %u", p_seq, isakmp_sa->dpd_rseq); |
167 |
|
|
return; |
168 |
|
|
} |
169 |
|
|
if (isakmp_sa->dpd_rseq != p_seq) { |
170 |
|
|
isakmp_sa->dpd_rdupcount = 0; |
171 |
|
|
isakmp_sa->dpd_rseq = p_seq; |
172 |
|
|
} |
173 |
|
|
message_send_dpd_notify(isakmp_sa, |
174 |
|
|
ISAKMP_NOTIFY_STATUS_DPD_R_U_THERE_ACK, p_seq); |
175 |
|
|
break; |
176 |
|
|
|
177 |
|
|
case ISAKMP_NOTIFY_STATUS_DPD_R_U_THERE_ACK: |
178 |
|
|
/* This should be a response to a R_U_THERE we've sent. */ |
179 |
|
|
if (isakmp_sa->dpd_seq != p_seq) { |
180 |
|
|
log_print("dpd_handle_notify: got bad ACK seqno %u, " |
181 |
|
|
"expected %u", p_seq, isakmp_sa->dpd_seq); |
182 |
|
|
/* XXX Give up? Retry? */ |
183 |
|
|
return; |
184 |
|
|
} |
185 |
|
|
break; |
186 |
|
|
default: |
187 |
|
|
break; |
188 |
|
|
} |
189 |
|
|
|
190 |
|
|
/* Mark handled. */ |
191 |
|
|
p->flags |= PL_MARK; |
192 |
|
|
|
193 |
|
|
/* The other peer is alive, so we can safely wait a while longer. */ |
194 |
|
|
if (isakmp_sa->flags & SA_FLAG_DPD) |
195 |
|
|
dpd_timer_reset(isakmp_sa, 0, DPD_TIMER_NORMAL); |
196 |
|
|
} |
197 |
|
|
|
198 |
|
|
/* Calculate the time until next DPD exchange. */ |
199 |
|
|
static u_int32_t |
200 |
|
|
dpd_timer_interval(u_int32_t offset) |
201 |
|
|
{ |
202 |
|
|
int32_t v = 0; |
203 |
|
|
|
204 |
|
|
#ifdef notyet |
205 |
|
|
v = ...; /* XXX Per-peer specified DPD intervals? */ |
206 |
|
|
#endif |
207 |
|
|
if (!v) |
208 |
|
|
v = conf_get_num("General", "DPD-check-interval", 0); |
209 |
|
|
if (v < 1) |
210 |
|
|
return 0; /* DPD-Check-Interval < 1 means disable DPD */ |
211 |
|
|
|
212 |
|
|
v -= offset; |
213 |
|
|
return v < 1 ? 1 : v; |
214 |
|
|
} |
215 |
|
|
|
216 |
|
|
static void |
217 |
|
|
dpd_timer_reset(struct sa *sa, u_int32_t time_passed, enum dpd_tstate mode) |
218 |
|
|
{ |
219 |
|
|
struct timeval tv; |
220 |
|
|
|
221 |
|
|
if (sa->dpd_event) |
222 |
|
|
timer_remove_event(sa->dpd_event); |
223 |
|
|
|
224 |
|
|
gettimeofday(&tv, 0); |
225 |
|
|
switch (mode) { |
226 |
|
|
case DPD_TIMER_NORMAL: |
227 |
|
|
sa->dpd_failcount = 0; |
228 |
|
|
tv.tv_sec += dpd_timer_interval(time_passed); |
229 |
|
|
sa->dpd_event = timer_add_event("dpd_event", dpd_event, sa, |
230 |
|
|
&tv); |
231 |
|
|
break; |
232 |
|
|
case DPD_TIMER_CHECK: |
233 |
|
|
tv.tv_sec += DPD_RETRANS_WAIT; |
234 |
|
|
sa->dpd_event = timer_add_event("dpd_check_event", |
235 |
|
|
dpd_check_event, sa, &tv); |
236 |
|
|
break; |
237 |
|
|
default: |
238 |
|
|
break; |
239 |
|
|
} |
240 |
|
|
if (!sa->dpd_event) |
241 |
|
|
log_print("dpd_timer_reset: timer_add_event failed"); |
242 |
|
|
} |
243 |
|
|
|
244 |
|
|
/* Helper function for dpd_exchange_finalization(). */ |
245 |
|
|
static int |
246 |
|
|
dpd_find_sa(struct sa *sa, void *v_sa) |
247 |
|
|
{ |
248 |
|
|
struct sa *isakmp_sa = v_sa; |
249 |
|
|
|
250 |
|
|
if (!isakmp_sa->id_i || !isakmp_sa->id_r) |
251 |
|
|
return 0; |
252 |
|
|
return (sa->phase == 2 && (sa->flags & SA_FLAG_READY) && |
253 |
|
|
memcmp(sa->id_i, isakmp_sa->id_i, sa->id_i_len) == 0 && |
254 |
|
|
memcmp(sa->id_r, isakmp_sa->id_r, sa->id_r_len) == 0); |
255 |
|
|
} |
256 |
|
|
|
257 |
|
|
struct dpd_args { |
258 |
|
|
struct sa *isakmp_sa; |
259 |
|
|
u_int32_t interval; |
260 |
|
|
}; |
261 |
|
|
|
262 |
|
|
/* Helper function for dpd_event(). */ |
263 |
|
|
static int |
264 |
|
|
dpd_check_time(struct sa *sa, void *v_arg) |
265 |
|
|
{ |
266 |
|
|
struct dpd_args *args = v_arg; |
267 |
|
|
struct sockaddr *dst; |
268 |
|
|
struct proto *proto; |
269 |
|
|
struct sa_kinfo *ksa; |
270 |
|
|
struct timeval tv; |
271 |
|
|
|
272 |
|
|
if (sa->phase == 1 || (args->isakmp_sa->flags & SA_FLAG_DPD) == 0 || |
273 |
|
|
dpd_find_sa(sa, args->isakmp_sa) == 0) |
274 |
|
|
return 0; |
275 |
|
|
|
276 |
|
|
proto = TAILQ_FIRST(&sa->protos); |
277 |
|
|
if (!proto || !proto->data) |
278 |
|
|
return 0; |
279 |
|
|
sa->transport->vtbl->get_src(sa->transport, &dst); |
280 |
|
|
|
281 |
|
|
gettimeofday(&tv, 0); |
282 |
|
|
ksa = pf_key_v2_get_kernel_sa(proto->spi[1], proto->spi_sz[1], |
283 |
|
|
proto->proto, dst); |
284 |
|
|
|
285 |
|
|
if (!ksa || !ksa->last_used) |
286 |
|
|
return 0; |
287 |
|
|
|
288 |
|
|
LOG_DBG((LOG_MESSAGE, 80, "dpd_check_time: " |
289 |
|
|
"SA %p last use %u second(s) ago", sa, |
290 |
|
|
(u_int32_t)(tv.tv_sec - ksa->last_used))); |
291 |
|
|
|
292 |
|
|
if ((u_int32_t)(tv.tv_sec - ksa->last_used) < args->interval) { |
293 |
|
|
args->interval = (u_int32_t)(tv.tv_sec - ksa->last_used); |
294 |
|
|
return 1; |
295 |
|
|
} |
296 |
|
|
return 0; |
297 |
|
|
} |
298 |
|
|
|
299 |
|
|
/* Called by the timer. */ |
300 |
|
|
static void |
301 |
|
|
dpd_event(void *v_sa) |
302 |
|
|
{ |
303 |
|
|
struct sa *isakmp_sa = v_sa; |
304 |
|
|
struct dpd_args args; |
305 |
|
|
struct sockaddr *dst; |
306 |
|
|
char *addr; |
307 |
|
|
|
308 |
|
|
isakmp_sa->dpd_event = 0; |
309 |
|
|
|
310 |
|
|
/* Check if there's been any incoming SA activity since last time. */ |
311 |
|
|
args.isakmp_sa = isakmp_sa; |
312 |
|
|
args.interval = dpd_timer_interval(0); |
313 |
|
|
if (sa_find(dpd_check_time, &args)) { |
314 |
|
|
if (args.interval > dpd_timer_interval(0)) |
315 |
|
|
args.interval = 0; |
316 |
|
|
dpd_timer_reset(isakmp_sa, args.interval, DPD_TIMER_NORMAL); |
317 |
|
|
return; |
318 |
|
|
} |
319 |
|
|
|
320 |
|
|
/* No activity seen, do a DPD exchange. */ |
321 |
|
|
if (isakmp_sa->dpd_seq == 0) { |
322 |
|
|
/* |
323 |
|
|
* RFC 3706: first seq# should be random, with MSB zero, |
324 |
|
|
* otherwise we just increment it. |
325 |
|
|
*/ |
326 |
|
|
arc4random_buf((u_int8_t *)&isakmp_sa->dpd_seq, |
327 |
|
|
sizeof isakmp_sa->dpd_seq); |
328 |
|
|
isakmp_sa->dpd_seq &= 0x7FFF; |
329 |
|
|
} else |
330 |
|
|
isakmp_sa->dpd_seq++; |
331 |
|
|
|
332 |
|
|
isakmp_sa->transport->vtbl->get_dst(isakmp_sa->transport, &dst); |
333 |
|
|
if (sockaddr2text(dst, &addr, 0) == -1) |
334 |
|
|
addr = 0; |
335 |
|
|
LOG_DBG((LOG_MESSAGE, 30, "dpd_event: sending R_U_THERE to %s seq %u", |
336 |
|
|
addr ? addr : "<unknown>", isakmp_sa->dpd_seq)); |
337 |
|
|
free(addr); |
338 |
|
|
message_send_dpd_notify(isakmp_sa, ISAKMP_NOTIFY_STATUS_DPD_R_U_THERE, |
339 |
|
|
isakmp_sa->dpd_seq); |
340 |
|
|
|
341 |
|
|
/* And set the short timer. */ |
342 |
|
|
dpd_timer_reset(isakmp_sa, 0, DPD_TIMER_CHECK); |
343 |
|
|
} |
344 |
|
|
|
345 |
|
|
/* |
346 |
|
|
* Called by the timer. If this function is called, it means we did not |
347 |
|
|
* received any R_U_THERE_ACK confirmation from the other peer. |
348 |
|
|
*/ |
349 |
|
|
static void |
350 |
|
|
dpd_check_event(void *v_sa) |
351 |
|
|
{ |
352 |
|
|
struct sa *isakmp_sa = v_sa; |
353 |
|
|
struct sa *sa; |
354 |
|
|
|
355 |
|
|
isakmp_sa->dpd_event = 0; |
356 |
|
|
|
357 |
|
|
if (++isakmp_sa->dpd_failcount < DPD_RETRANS_MAX) { |
358 |
|
|
LOG_DBG((LOG_MESSAGE, 10, "dpd_check_event: " |
359 |
|
|
"peer not responding, retry %u of %u", |
360 |
|
|
isakmp_sa->dpd_failcount, DPD_RETRANS_MAX)); |
361 |
|
|
message_send_dpd_notify(isakmp_sa, |
362 |
|
|
ISAKMP_NOTIFY_STATUS_DPD_R_U_THERE, isakmp_sa->dpd_seq); |
363 |
|
|
dpd_timer_reset(isakmp_sa, 0, DPD_TIMER_CHECK); |
364 |
|
|
return; |
365 |
|
|
} |
366 |
|
|
|
367 |
|
|
/* |
368 |
|
|
* Peer is considered dead. Delete all SAs created under isakmp_sa. |
369 |
|
|
*/ |
370 |
|
|
LOG_DBG((LOG_MESSAGE, 10, "dpd_check_event: peer is dead, " |
371 |
|
|
"deleting all SAs connected to SA %p", isakmp_sa)); |
372 |
|
|
while ((sa = sa_find(dpd_find_sa, isakmp_sa)) != 0) { |
373 |
|
|
LOG_DBG((LOG_MESSAGE, 30, "dpd_check_event: deleting SA %p", |
374 |
|
|
sa)); |
375 |
|
|
sa_delete(sa, 0); |
376 |
|
|
} |
377 |
|
|
LOG_DBG((LOG_MESSAGE, 30, "dpd_check_event: deleting ISAKMP SA %p", |
378 |
|
|
isakmp_sa)); |
379 |
|
|
sa_delete(isakmp_sa, 0); |
380 |
|
|
} |