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