dpd.c revision 1.4
1/*	$OpenBSD: dpd.c,v 1.4 2004/08/10 15:59:10 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
30#include "sysdep.h"
31
32#include "conf.h"
33#include "dpd.h"
34#include "exchange.h"
35#include "hash.h"
36#include "ipsec.h"
37#include "isakmp_fld.h"
38#include "log.h"
39#include "message.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
50static const char 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 */
61enum dpd_tstate { DPD_TIMER_NORMAL, DPD_TIMER_CHECK };
62
63static void	 dpd_check_event(void *);
64static void	 dpd_event(void *);
65static u_int32_t dpd_timer_interval(u_int32_t);
66static void	 dpd_timer_reset(struct sa *, u_int32_t, enum dpd_tstate);
67
68/* Add the DPD VENDOR ID payload.  */
69int
70dpd_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 */
96void
97dpd_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 %d != %d", vlen,
113		    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			if (dpd_timer_interval(0) != 0) {
124				LOG_DBG((LOG_EXCHANGE, 10,
125				    "dpd_check_vendor_payload: enabling"));
126				msg->isakmp_sa->flags |= SA_FLAG_DPD;
127				dpd_timer_reset(msg->isakmp_sa, 0,
128				    DPD_TIMER_NORMAL);
129			}
130		}
131		p->flags |= PL_MARK;
132	}
133	return;
134}
135
136/*
137 * All incoming DPD Notify messages enter here. Message has been validated.
138 */
139void
140dpd_handle_notify(struct message *msg, struct payload *p)
141{
142	struct sa	*isakmp_sa = msg->isakmp_sa;
143	u_int16_t	 notify = GET_ISAKMP_NOTIFY_MSG_TYPE(p->p);
144	u_int32_t	 p_seq;
145
146	/* Extract the sequence number.  */
147	memcpy(&p_seq, p->p + ISAKMP_NOTIFY_SPI_OFF + ISAKMP_HDR_COOKIES_LEN,
148	    sizeof p_seq);
149	p_seq = ntohl(p_seq);
150
151	LOG_DBG((LOG_MESSAGE, 40, "dpd_handle_notify: got %s seq %u",
152	    constant_name(isakmp_notify_cst, notify), p_seq));
153
154	switch (notify) {
155	case ISAKMP_NOTIFY_STATUS_DPD_R_U_THERE:
156		/* The other peer wants to know we're alive.  */
157		if (p_seq <= isakmp_sa->dpd_rseq) {
158			log_print("dpd_handle_notify: bad R_U_THERE seqno "
159			    "%u <= %u", p_seq, isakmp_sa->dpd_rseq);
160			return;
161		}
162		isakmp_sa->dpd_rseq = p_seq;
163		message_send_dpd_notify(isakmp_sa,
164		    ISAKMP_NOTIFY_STATUS_DPD_R_U_THERE_ACK, p_seq);
165		break;
166
167	case ISAKMP_NOTIFY_STATUS_DPD_R_U_THERE_ACK:
168		/* This should be a response to a R_U_THERE we've sent.  */
169		if (isakmp_sa->dpd_seq != p_seq) {
170			log_print("dpd_handle_notify: got bad ACK seqno %u, "
171			    "expected %u", p_seq, isakmp_sa->dpd_seq);
172			/* XXX Give up? Retry? */
173			return;
174		}
175		break;
176	default:
177	}
178
179	/* Mark handled.  */
180	p->flags |= PL_MARK;
181
182	/* The other peer is alive, so we can safely wait a while longer.  */
183	if (isakmp_sa->flags & SA_FLAG_DPD)
184		dpd_timer_reset(isakmp_sa, 0, DPD_TIMER_NORMAL);
185}
186
187/* Calculate the time until next DPD exchange.  */
188static u_int32_t
189dpd_timer_interval(u_int32_t offset)
190{
191	int32_t v = 0;
192
193#ifdef notyet
194	v = ...; /* XXX Per-peer specified DPD intervals?  */
195#endif
196	if (!v)
197		v = conf_get_num("General", "DPD-check-interval", 0);
198	if (v < 1)
199		return 0;	/* DPD-Check-Interval < 1 means disable DPD */
200
201	v -= offset;
202	return v < 1 ? 1 : v;
203}
204
205static void
206dpd_timer_reset(struct sa *sa, u_int32_t time_passed, enum dpd_tstate mode)
207{
208	struct timeval	tv;
209
210	if (sa->dpd_event)
211		timer_remove_event(sa->dpd_event);
212
213	gettimeofday(&tv, 0);
214	switch (mode) {
215	case DPD_TIMER_NORMAL:
216		tv.tv_sec += dpd_timer_interval(time_passed);
217		sa->dpd_event = timer_add_event("dpd_event", dpd_event, sa,
218		    &tv);
219		break;
220	case DPD_TIMER_CHECK:
221		tv.tv_sec += DPD_RETRANS_WAIT;
222		sa->dpd_event = timer_add_event("dpd_check_event",
223		    dpd_check_event, sa, &tv);
224		break;
225	default:
226	}
227	if (!sa->dpd_event)
228		log_print("dpd_timer_reset: timer_add_event failed");
229}
230
231/* Helper function for dpd_exchange_finalization().  */
232static int
233dpd_find_sa(struct sa *sa, void *v_sa)
234{
235	struct sa	*isakmp_sa = v_sa;
236
237	return (sa->phase == 2 &&
238	    memcmp(sa->id_i, isakmp_sa->id_i, sa->id_i_len) == 0 &&
239	    memcmp(sa->id_r, isakmp_sa->id_r, sa->id_r_len) == 0);
240}
241
242struct dpd_args {
243	struct sa	*isakmp_sa;
244	u_int32_t	 interval;
245};
246
247/* Helper function for dpd_event().  */
248static int
249dpd_check_time(struct sa *sa, void *v_arg)
250{
251	struct dpd_args *args = v_arg;
252	struct sockaddr *dst;
253	struct proto *proto;
254	struct sa_kinfo *ksa;
255	struct timeval tv;
256
257	if (sa->phase == 1 || (args->isakmp_sa->flags & SA_FLAG_DPD) == 0 ||
258	    dpd_find_sa(sa, args->isakmp_sa) == 0)
259		return 0;
260
261	proto = TAILQ_FIRST(&sa->protos);
262	if (!proto || !proto->data)
263		return 0;
264	sa->transport->vtbl->get_src(sa->transport, &dst);
265
266	gettimeofday(&tv, 0);
267	ksa = sysdep_ipsec_get_kernel_sa(proto->spi[1], proto->spi_sz[1],
268	    proto->proto, dst);
269
270	if (!ksa || !ksa->last_used)
271		return 0;
272
273	LOG_DBG((LOG_MESSAGE, 80, "dpd_check_time: "
274	    "SA %p last use %u second(s) ago", sa,
275	    (u_int32_t)(tv.tv_sec - ksa->last_used)));
276
277	if ((u_int32_t)(tv.tv_sec - ksa->last_used) < args->interval) {
278		args->interval = (u_int32_t)(tv.tv_sec - ksa->last_used);
279		return 1;
280	}
281
282	return 0;
283}
284
285/* Called by the timer.  */
286static void
287dpd_event(void *v_sa)
288{
289	struct sa	*isakmp_sa = v_sa;
290	struct dpd_args args;
291#if defined (USE_DEBUG)
292	struct sockaddr *dst;
293	char *addr;
294#endif
295
296	isakmp_sa->dpd_event = 0;
297
298	/* Check if there's been any incoming SA activity since last time.  */
299	args.isakmp_sa = isakmp_sa;
300	args.interval = dpd_timer_interval(0);
301	if (sa_find(dpd_check_time, &args)) {
302		if (args.interval > dpd_timer_interval(0))
303			args.interval = 0;
304		dpd_timer_reset(isakmp_sa, args.interval, DPD_TIMER_NORMAL);
305		return;
306	}
307
308	/* No activity seen, do a DPD exchange.  */
309	if (isakmp_sa->dpd_seq == 0) {
310		/*
311		 * RFC 3706: first seq# should be random, with MSB zero,
312		 * otherwise we just increment it.
313		 */
314		getrandom((u_int8_t *)&isakmp_sa->dpd_seq,
315		    sizeof isakmp_sa->dpd_seq);
316		isakmp_sa->dpd_seq &= 0x7FFF;
317	} else
318		isakmp_sa->dpd_seq++;
319
320#if defined (USE_DEBUG)
321	isakmp_sa->transport->vtbl->get_dst(isakmp_sa->transport, &dst);
322	if (sockaddr2text(dst, &addr, 0) == -1)
323		addr = 0;
324	LOG_DBG((LOG_MESSAGE, 30, "dpd_event: sending R_U_THERE to %s seq %u",
325	    addr ? addr : "<unknown>", isakmp_sa->dpd_seq));
326	if (addr)
327		free(addr);
328#endif
329	message_send_dpd_notify(isakmp_sa, ISAKMP_NOTIFY_STATUS_DPD_R_U_THERE,
330	    isakmp_sa->dpd_seq);
331
332	/* And set the short timer.  */
333	dpd_timer_reset(isakmp_sa, 0, DPD_TIMER_CHECK);
334}
335
336/*
337 * Called by the timer. If this function is called, it means we did not
338 * recieve any R_U_THERE_ACK confirmation from the other peer.
339 */
340static void
341dpd_check_event(void *v_sa)
342{
343	struct sa	*isakmp_sa = v_sa;
344	struct sa	*sa;
345
346	isakmp_sa->dpd_event = 0;
347
348	if (++isakmp_sa->dpd_failcount < DPD_RETRANS_MAX) {
349		LOG_DBG((LOG_MESSAGE, 10, "dpd_check_event: "
350		    "peer not responding, retry %u of %u",
351		    isakmp_sa->dpd_failcount, DPD_RETRANS_MAX));
352		message_send_dpd_notify(isakmp_sa,
353		    ISAKMP_NOTIFY_STATUS_DPD_R_U_THERE, isakmp_sa->dpd_seq);
354		dpd_timer_reset(isakmp_sa, 0, DPD_TIMER_CHECK);
355		return;
356	}
357
358	/*
359	 * Peer is considered dead. Delete all SAs created under isakmp_sa.
360	 */
361	LOG_DBG((LOG_MESSAGE, 10, "dpd_check_event: peer is dead, "
362	    "deleting all SAs connected to SA %p", isakmp_sa));
363	while ((sa = sa_find(dpd_find_sa, isakmp_sa)) != 0) {
364		LOG_DBG((LOG_MESSAGE, 30, "dpd_check_event: deleting SA %p",
365		    sa));
366		sa_delete(sa, 0);
367	}
368	LOG_DBG((LOG_MESSAGE, 30, "dpd_check_event: deleting ISAKMP SA %p",
369	    isakmp_sa));
370	sa_delete(isakmp_sa, 0);
371}
372