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