dpd.c revision 1.2
1/*	$OpenBSD: dpd.c,v 1.2 2004/06/20 17:17:34 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 "dpd.h"
33#include "exchange.h"
34#include "ipsec.h"
35#include "isakmp_fld.h"
36#include "log.h"
37#include "message.h"
38#include "sa.h"
39#include "timer.h"
40#include "util.h"
41
42/* From RFC 3706.  */
43#define DPD_MAJOR		0x01
44#define DPD_MINOR		0x00
45#define DPD_SEQNO_SZ		4
46
47static const char dpd_vendor_id[] = {
48	0xAF, 0xCA, 0xD7, 0x13, 0x68, 0xA1, 0xF1,	/* RFC 3706 */
49	0xC9, 0x6B, 0x86, 0x96, 0xFC, 0x77, 0x57,
50	DPD_MAJOR,
51	DPD_MINOR
52};
53
54int16_t script_dpd[] = {
55	ISAKMP_PAYLOAD_NOTIFY,	/* Initiator -> responder.  */
56	ISAKMP_PAYLOAD_HASH,
57	EXCHANGE_SCRIPT_SWITCH,
58	ISAKMP_PAYLOAD_NOTIFY,	/* Responder -> initiator.  */
59	ISAKMP_PAYLOAD_HASH,
60	EXCHANGE_SCRIPT_END
61};
62
63static int	dpd_initiator_send_notify(struct message *);
64static int	dpd_initiator_recv_ack(struct message *);
65static int	dpd_responder_recv_notify(struct message *);
66static int	dpd_responder_send_ack(struct message *);
67static void	dpd_event(void *);
68
69int (*isakmp_dpd_initiator[])(struct message *) = {
70	dpd_initiator_send_notify,
71	dpd_initiator_recv_ack
72};
73
74int (*isakmp_dpd_responder[])(struct message *) = {
75	dpd_responder_recv_notify,
76	dpd_responder_send_ack
77};
78
79/* Add the DPD VENDOR ID payload.  */
80int
81dpd_add_vendor_payload(struct message *msg)
82{
83	u_int8_t *buf;
84	size_t buflen = sizeof dpd_vendor_id + ISAKMP_GEN_SZ;
85
86	buf = malloc(buflen);
87	if (!buf) {
88		log_error("dpd_add_vendor_payload: malloc(%lu) failed",
89		    (unsigned long)buflen);
90		return -1;
91	}
92
93	SET_ISAKMP_GEN_LENGTH(buf, buflen);
94	memcpy(buf + ISAKMP_VENDOR_ID_OFF, dpd_vendor_id,
95	    sizeof dpd_vendor_id);
96	if (message_add_payload(msg, ISAKMP_PAYLOAD_VENDOR, buf, buflen, 1)) {
97		free(buf);
98		return -1;
99	}
100
101	return 0;
102}
103
104/*
105 * Check an incoming message for DPD capability markers.
106 */
107void
108dpd_check_vendor_payload(struct message *msg, struct payload *p)
109{
110	u_int8_t *pbuf = p->p;
111	size_t vlen;
112
113	/* Already checked? */
114	if (msg->exchange->flags & EXCHANGE_FLAG_DPD_CAP_PEER) {
115		/* Just mark it as handled and return.  */
116		p->flags |= PL_MARK;
117		return;
118	}
119
120	vlen = GET_ISAKMP_GEN_LENGTH(pbuf) - ISAKMP_GEN_SZ;
121	if (vlen != sizeof dpd_vendor_id) {
122		LOG_DBG((LOG_EXCHANGE, 90,
123		    "dpd_check_vendor_payload: bad size %d != %d", vlen,
124		    sizeof dpd_vendor_id));
125		return;
126	}
127
128	if (memcmp(dpd_vendor_id, pbuf + ISAKMP_GEN_SZ, vlen) == 0) {
129		/* This peer is DPD capable.  */
130		msg->exchange->flags |= EXCHANGE_FLAG_DPD_CAP_PEER;
131		LOG_DBG((LOG_EXCHANGE, 10, "dpd_check_vendor_payload: "
132		    "DPD capable peer detected"));
133		p->flags |= PL_MARK;
134		return;
135	}
136
137	return;
138}
139
140static int
141dpd_add_notify(struct message *msg, u_int16_t type, u_int32_t seqno)
142{
143	struct sa *isakmp_sa = msg->isakmp_sa;
144	char *buf;
145	u_int32_t buflen;
146
147	if (!isakmp_sa)	{
148		log_print("dpd_add_notify: no isakmp_sa");
149		return -1;
150	}
151
152	buflen = ISAKMP_NOTIFY_SZ + ISAKMP_HDR_COOKIES_LEN + DPD_SEQNO_SZ;
153	buf = malloc(buflen);
154	if (!buf) {
155		log_error("dpd_add_notify: malloc(%d) failed",
156		    ISAKMP_NOTIFY_SZ + DPD_SEQNO_SZ);
157		return -1;
158	}
159
160	SET_ISAKMP_NOTIFY_DOI(buf, IPSEC_DOI_IPSEC);
161	SET_ISAKMP_NOTIFY_PROTO(buf, ISAKMP_PROTO_ISAKMP);
162	SET_ISAKMP_NOTIFY_SPI_SZ(buf, ISAKMP_HDR_COOKIES_LEN);
163	SET_ISAKMP_NOTIFY_MSG_TYPE(buf, type);
164	memcpy(buf + ISAKMP_NOTIFY_SPI_OFF, isakmp_sa->cookies,
165	    ISAKMP_HDR_COOKIES_LEN);
166
167	memcpy(buf + ISAKMP_NOTIFY_SPI_OFF + ISAKMP_HDR_COOKIES_LEN, &seqno,
168	    sizeof (u_int32_t));
169
170	if (message_add_payload(msg, ISAKMP_PAYLOAD_NOTIFY, buf, buflen, 1)) {
171		free(buf);
172		return -1;
173	}
174
175	return 0;
176}
177
178static int
179dpd_initiator_send_notify(struct message *msg)
180{
181	if (!msg->isakmp_sa) {
182		log_print("dpd_initiator_send_notify: no isakmp_sa");
183		return -1;
184	}
185
186	if (msg->isakmp_sa->dpd_seq == 0) {
187		/* RFC 3706: first seq# should be random, with MSB zero. */
188		getrandom((u_int8_t *)&msg->isakmp_sa->seq,
189		    sizeof msg->isakmp_sa->seq);
190		msg->isakmp_sa->dpd_seq &= 0x7FFF;
191	} else
192		msg->isakmp_sa->dpd_seq++;
193
194	return dpd_add_notify(msg, ISAKMP_NOTIFY_STATUS_DPD_R_U_THERE,
195	    msg->isakmp_sa->dpd_seq);
196}
197
198static int
199dpd_initiator_recv_ack(struct message *msg)
200{
201	struct payload	*p = payload_first(msg, ISAKMP_PAYLOAD_NOTIFY);
202	struct sa	*isakmp_sa = msg->isakmp_sa;
203	struct timeval	 tv;
204	u_int32_t	 rseq;
205
206	if (msg->exchange->phase != 2) {
207		message_drop(msg, ISAKMP_NOTIFY_INVALID_EXCHANGE_TYPE, 0, 1,
208		    0);
209		return -1;
210	}
211
212	if (GET_ISAKMP_NOTIFY_MSG_TYPE(p->p)
213	    != ISAKMP_NOTIFY_STATUS_DPD_R_U_THERE_ACK) {
214		message_drop(msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED, 0, 1, 0);
215		return -1;
216	}
217
218	/* Presumably, we've been through message_validate_notify().  */
219
220	/* Validate the SPI. Perhaps move to message_validate_notify().  */
221	if (memcmp(p->p + ISAKMP_NOTIFY_SPI_OFF, isakmp_sa->cookies,
222	    ISAKMP_HDR_COOKIES_LEN) != 0) {
223		log_print("dpd_initiator_recv_ack: bad cookies");
224		message_drop(msg, ISAKMP_NOTIFY_INVALID_SPI, 0, 1, 0);
225		return -1;
226	}
227
228	/* Check the seqno.  */
229	memcpy(p->p + ISAKMP_NOTIFY_SPI_OFF + ISAKMP_HDR_COOKIES_LEN, &rseq,
230	    sizeof rseq);
231	rseq = ntohl(rseq);
232
233	if (isakmp_sa->seq != rseq) {
234		log_print("dpd_initiator_recv_ack: bad seqno %u, expected %u",
235		    rseq, isakmp_sa->seq);
236		message_drop(msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED, 0, 1, 0);
237		return -1;
238	}
239
240	/* Peer is alive. Reset timer.  */
241	gettimeofday(&tv, 0);
242	tv.tv_sec += DPD_DEFAULT_WORRY_METRIC; /* XXX Configurable */
243
244	isakmp_sa->dpd_nextev = timer_add_event("dpd_event", dpd_event,
245	    isakmp_sa, &tv);
246	if (!isakmp_sa->dpd_nextev)
247		log_print("dpd_initiator_recv_ack: timer_add_event "
248		    "failed");
249	else
250		sa_reference(isakmp_sa);
251
252	/* Mark handled.  */
253	p->flags |= PL_MARK;
254
255	return 0;
256}
257
258static int
259dpd_responder_recv_notify(struct message *msg)
260{
261	struct payload	*p = payload_first(msg, ISAKMP_PAYLOAD_NOTIFY);
262	struct sa	*isakmp_sa = msg->isakmp_sa;
263	struct timeval	 tv;
264	u_int32_t	 rseq;
265
266	if (msg->exchange->phase != 2) {
267		message_drop(msg, ISAKMP_NOTIFY_INVALID_EXCHANGE_TYPE, 0, 1,
268		    0);
269		return -1;
270	}
271
272	if (GET_ISAKMP_NOTIFY_MSG_TYPE(p->p) !=
273	    ISAKMP_NOTIFY_STATUS_DPD_R_U_THERE)	{
274		message_drop(msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED, 0, 1, 0);
275		return -1;
276	}
277
278	/* Presumably, we've gone through message_validate_notify().  */
279	/* XXX */
280
281	/* Validate the SPI. Perhaps move to message_validate_notify().  */
282	if (memcmp(p->p + ISAKMP_NOTIFY_SPI_OFF, isakmp_sa->cookies,
283	    ISAKMP_HDR_COOKIES_LEN) != 0) {
284		log_print("dpd_initiator_recv_notify: bad cookies");
285		message_drop(msg, ISAKMP_NOTIFY_INVALID_SPI, 0, 1, 0);
286		return -1;
287	}
288
289	/* Get the seqno.  */
290	memcpy(p->p + ISAKMP_NOTIFY_SPI_OFF + ISAKMP_HDR_COOKIES_LEN, &rseq,
291	    sizeof rseq);
292	rseq = ntohl(rseq);
293
294	/* Check increasing seqno.  */
295	if (rseq <= isakmp_sa->dpd_rseq) {
296		log_print("dpd_initiator_recv_notify: bad seqno (%u <= %u)",
297		    rseq, isakmp_sa->dpd_rseq);
298		message_drop(msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED, 0, 1, 0);
299		return -1;
300	}
301	isakmp_sa->dpd_rseq = rseq;
302
303	/*
304	 * Ok, now we know the peer is alive, in case we're wondering.
305	 * If so, reset timers, etc... here.
306	 */
307	if (isakmp_sa->dpd_nextev) {
308		timer_remove_event(isakmp_sa->dpd_nextev);
309		sa_release(isakmp_sa);
310
311		gettimeofday(&tv, 0);
312		tv.tv_sec += DPD_DEFAULT_WORRY_METRIC; /* XXX Configurable */
313
314		isakmp_sa->dpd_nextev = timer_add_event("dpd_event", dpd_event,
315		    isakmp_sa, &tv);
316		if (!isakmp_sa->dpd_nextev)
317			log_print("dpd_responder_recv_notify: timer_add_event "
318			    "failed");
319		else
320			sa_reference(isakmp_sa);
321	}
322
323	/* Mark handled.  */
324	p->flags |= PL_MARK;
325
326	return 0;
327}
328
329static int
330dpd_responder_send_ack(struct message *msg)
331{
332	if (!msg->isakmp_sa)
333		return -1;
334
335	return dpd_add_notify(msg, ISAKMP_NOTIFY_STATUS_DPD_R_U_THERE_ACK,
336	    msg->isakmp_sa->dpd_rseq);
337}
338
339static void
340dpd_event(void *v_sa)
341{
342	struct sa	*sa = v_sa;
343
344	sa->dpd_nextev = 0;
345	sa_release(sa);
346
347	if ((sa->flags & SA_FLAG_DPD) == 0)
348		return;
349
350	/* Create a new DPD exchange.  XXX */
351}
352
353