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