diff options
Diffstat (limited to 'keyexchange/isakmpd-20041012/nat_traversal.c')
-rw-r--r-- | keyexchange/isakmpd-20041012/nat_traversal.c | 439 |
1 files changed, 439 insertions, 0 deletions
diff --git a/keyexchange/isakmpd-20041012/nat_traversal.c b/keyexchange/isakmpd-20041012/nat_traversal.c new file mode 100644 index 0000000..86d2d57 --- /dev/null +++ b/keyexchange/isakmpd-20041012/nat_traversal.c @@ -0,0 +1,439 @@ +/* $OpenBSD: nat_traversal.c,v 1.17 2006/06/14 14:03:33 hshoexer Exp $ */ + +/* + * Copyright (c) 2004 Håkan Olsson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <sys/types.h> +#include <stdlib.h> +#include <string.h> + +#include "sysdep.h" + +#include "conf.h" +#include "exchange.h" +#include "hash.h" +#include "ipsec.h" +#include "isakmp_fld.h" +#include "isakmp_num.h" +#include "ipsec_num.h" +#include "hash.h" +#include "log.h" +#include "message.h" +#include "nat_traversal.h" +#include "prf.h" +#include "sa.h" +#include "timer.h" +#include "transport.h" +#include "util.h" +#include "virtual.h" + +int disable_nat_t = 0; + +/* + * NAT-T capability of the other peer is determined by a particular vendor + * ID sent in the first message. This vendor ID string is supposed to be a + * MD5 hash of "RFC 3947". + * + * These seem to be the "well" known variants of this string in use by + * products today. + */ + +static struct nat_t_cap isakmp_nat_t_cap[] = { + { VID_DRAFT_V2_N, EXCHANGE_FLAG_NAT_T_DRAFT, + "draft-ietf-ipsec-nat-t-ike-02\n", NULL, 0 }, + { VID_DRAFT_V3, EXCHANGE_FLAG_NAT_T_DRAFT, + "draft-ietf-ipsec-nat-t-ike-03", NULL, 0 }, + { VID_RFC3947, EXCHANGE_FLAG_NAT_T_RFC, + "RFC 3947", NULL, 0 }, +}; + +#define NUMNATTCAP (sizeof isakmp_nat_t_cap / sizeof isakmp_nat_t_cap[0]) + +/* In seconds. Recommended in draft-ietf-ipsec-udp-encaps-09. */ +#define NAT_T_KEEPALIVE_INTERVAL 20 + +static int nat_t_setup_hashes(void); +static int nat_t_add_vendor_payload(struct message *, struct nat_t_cap *); +static int nat_t_add_nat_d(struct message *, struct sockaddr *); +static int nat_t_match_nat_d_payload(struct message *, struct sockaddr *); + +void +nat_t_init(void) +{ + nat_t_setup_hashes(); +} + +/* Generate the NAT-T capability marker hashes. Executed only once. */ +static int +nat_t_setup_hashes(void) +{ + struct hash *hash; + int n = NUMNATTCAP; + int i; + + /* The draft says to use MD5. */ + hash = hash_get(HASH_MD5); + if (!hash) { + /* Should never happen. */ + log_print("nat_t_setup_hashes: " + "could not find MD5 hash structure!"); + return -1; + } + + /* Populate isakmp_nat_t_cap with hashes. */ + for (i = 0; i < n; i++) { + isakmp_nat_t_cap[i].hashsize = hash->hashsize; + isakmp_nat_t_cap[i].hash = (char *)malloc(hash->hashsize); + if (!isakmp_nat_t_cap[i].hash) { + log_error("nat_t_setup_hashes: malloc (%lu) failed", + (unsigned long)hash->hashsize); + goto errout; + } + + hash->Init(hash->ctx); + hash->Update(hash->ctx, + (unsigned char *)isakmp_nat_t_cap[i].text, + strlen(isakmp_nat_t_cap[i].text)); + hash->Final(isakmp_nat_t_cap[i].hash, hash->ctx); + + LOG_DBG((LOG_EXCHANGE, 50, "nat_t_setup_hashes: " + "MD5(\"%s\") (%lu bytes)", isakmp_nat_t_cap[i].text, + (unsigned long)hash->hashsize)); + LOG_DBG_BUF((LOG_EXCHANGE, 50, "nat_t_setup_hashes", + isakmp_nat_t_cap[i].hash, hash->hashsize)); + } + + return 0; + +errout: + for (i = 0; i < n; i++) + if (isakmp_nat_t_cap[i].hash) + free(isakmp_nat_t_cap[i].hash); + return -1; +} + +/* Add one NAT-T VENDOR payload. */ +static int +nat_t_add_vendor_payload(struct message *msg, struct nat_t_cap *cap) +{ + size_t buflen = cap->hashsize + ISAKMP_GEN_SZ; + u_int8_t *buf; + + if (disable_nat_t) + return 0; + + buf = malloc(buflen); + if (!buf) { + log_error("nat_t_add_vendor_payload: malloc (%lu) failed", + (unsigned long)buflen); + return -1; + } + + SET_ISAKMP_GEN_LENGTH(buf, buflen); + memcpy(buf + ISAKMP_VENDOR_ID_OFF, cap->hash, cap->hashsize); + if (message_add_payload(msg, ISAKMP_PAYLOAD_VENDOR, buf, buflen, 1)) { + free(buf); + return -1; + } + return 0; +} + +/* Add the NAT-T capability markers (VENDOR payloads). */ +int +nat_t_add_vendor_payloads(struct message *msg) +{ + int i; + + if (disable_nat_t) + return 0; + + for (i = 0; i < NUMNATTCAP; i++) + if (nat_t_add_vendor_payload(msg, &isakmp_nat_t_cap[i])) + return -1; + return 0; +} + +/* + * Check an incoming message for NAT-T capability markers. + */ +void +nat_t_check_vendor_payload(struct message *msg, struct payload *p) +{ + u_int8_t *pbuf = p->p; + size_t vlen; + int i; + + if (disable_nat_t) + return; + + vlen = GET_ISAKMP_GEN_LENGTH(pbuf) - ISAKMP_GEN_SZ; + + for (i = 0; i < NUMNATTCAP; i++) { + if (vlen != isakmp_nat_t_cap[i].hashsize) { + LOG_DBG((LOG_EXCHANGE, 50, "nat_t_check_vendor_payload: " + "bad size %lu != %lu", (unsigned long)vlen, + (unsigned long)isakmp_nat_t_cap[i].hashsize)); + continue; + } + if (memcmp(isakmp_nat_t_cap[i].hash, pbuf + ISAKMP_GEN_SZ, + vlen) == 0) { + /* This peer is NAT-T capable. */ + msg->exchange->flags |= EXCHANGE_FLAG_NAT_T_CAP_PEER; + msg->exchange->flags |= isakmp_nat_t_cap[i].flags; + LOG_DBG((LOG_EXCHANGE, 10, + "nat_t_check_vendor_payload: " + "NAT-T capable peer detected")); + p->flags |= PL_MARK; + } + } + + return; +} + +/* Generate the NAT-D payload hash : HASH(CKY-I | CKY-R | IP | Port). */ +static u_int8_t * +nat_t_generate_nat_d_hash(struct message *msg, struct sockaddr *sa, + size_t *hashlen) +{ + struct ipsec_exch *ie = (struct ipsec_exch *)msg->exchange->data; + struct hash *hash; + u_int8_t *res; + in_port_t port; + + hash = hash_get(ie->hash->type); + if (hash == NULL) { + log_print ("nat_t_generate_nat_d_hash: no hash"); + return NULL; + } + + *hashlen = hash->hashsize; + + res = (u_int8_t *)malloc((unsigned long)*hashlen); + if (!res) { + log_print("nat_t_generate_nat_d_hash: malloc (%lu) failed", + (unsigned long)*hashlen); + *hashlen = 0; + return NULL; + } + + port = sockaddr_port(sa); + bzero(res, *hashlen); + + hash->Init(hash->ctx); + hash->Update(hash->ctx, msg->exchange->cookies, + sizeof msg->exchange->cookies); + hash->Update(hash->ctx, sockaddr_addrdata(sa), sockaddr_addrlen(sa)); + hash->Update(hash->ctx, (unsigned char *)&port, sizeof port); + hash->Final(res, hash->ctx); + return res; +} + +/* Add a NAT-D payload to our message. */ +static int +nat_t_add_nat_d(struct message *msg, struct sockaddr *sa) +{ + int ret; + u_int8_t *hbuf, *buf; + size_t hbuflen, buflen; + + hbuf = nat_t_generate_nat_d_hash(msg, sa, &hbuflen); + if (!hbuf) { + log_print("nat_t_add_nat_d: NAT-D hash gen failed"); + return -1; + } + + buflen = ISAKMP_NAT_D_DATA_OFF + hbuflen; + buf = malloc(buflen); + if (!buf) { + log_error("nat_t_add_nat_d: malloc (%lu) failed", + (unsigned long)buflen); + free(hbuf); + return -1; + } + + SET_ISAKMP_GEN_LENGTH(buf, buflen); + memcpy(buf + ISAKMP_NAT_D_DATA_OFF, hbuf, hbuflen); + free(hbuf); + + if (msg->exchange->flags & EXCHANGE_FLAG_NAT_T_RFC) + ret = message_add_payload(msg, ISAKMP_PAYLOAD_NAT_D, buf, + buflen, 1); + else if (msg->exchange->flags & EXCHANGE_FLAG_NAT_T_DRAFT) + ret = message_add_payload(msg, ISAKMP_PAYLOAD_NAT_D_DRAFT, + buf, buflen, 1); + else + ret = -1; + + if (ret) { + free(buf); + return -1; + } + return 0; +} + +/* We add two NAT-D payloads, one each for src and dst. */ +int +nat_t_exchange_add_nat_d(struct message *msg) +{ + struct sockaddr *sa; + + /* Remote address first. */ + msg->transport->vtbl->get_dst(msg->transport, &sa); + if (nat_t_add_nat_d(msg, sa)) + return -1; + + msg->transport->vtbl->get_src(msg->transport, &sa); + if (nat_t_add_nat_d(msg, sa)) + return -1; + return 0; +} + +/* Generate and match a NAT-D hash against the NAT-D payload (pl.) data. */ +static int +nat_t_match_nat_d_payload(struct message *msg, struct sockaddr *sa) +{ + struct payload *p; + u_int8_t *hbuf; + size_t hbuflen; + int found = 0; + + /* + * If there are no NAT-D payloads in the message, return "found" + * as this will avoid NAT-T (see nat_t_exchange_check_nat_d()). + */ + if ((p = payload_first(msg, ISAKMP_PAYLOAD_NAT_D_DRAFT)) == NULL && + (p = payload_first(msg, ISAKMP_PAYLOAD_NAT_D)) == NULL) + return 1; + + hbuf = nat_t_generate_nat_d_hash(msg, sa, &hbuflen); + if (!hbuf) + return 0; + + while (p) { + if (GET_ISAKMP_GEN_LENGTH (p->p) != + hbuflen + ISAKMP_NAT_D_DATA_OFF) + continue; + + if (memcmp(p->p + ISAKMP_NAT_D_DATA_OFF, hbuf, hbuflen) == 0) { + found++; + break; + } + p = TAILQ_NEXT(p, link); + } + free(hbuf); + return found; +} + +/* + * Check if we need to activate NAT-T, and if we need to send keepalive + * messages to the other side, i.e if we are a nat:ed peer. + */ +int +nat_t_exchange_check_nat_d(struct message *msg) +{ + struct sockaddr *sa; + int outgoing_path_is_clear, incoming_path_is_clear; + + /* Assume trouble, i.e NAT-boxes in our path. */ + outgoing_path_is_clear = incoming_path_is_clear = 0; + + msg->transport->vtbl->get_src(msg->transport, &sa); + if (nat_t_match_nat_d_payload(msg, sa)) + outgoing_path_is_clear = 1; + + msg->transport->vtbl->get_dst(msg->transport, &sa); + if (nat_t_match_nat_d_payload(msg, sa)) + incoming_path_is_clear = 1; + + if (outgoing_path_is_clear && incoming_path_is_clear) { + LOG_DBG((LOG_EXCHANGE, 40, "nat_t_exchange_check_nat_d: " + "no NAT")); + return 0; /* No NAT-T required. */ + } + + /* NAT-T handling required. */ + msg->exchange->flags |= EXCHANGE_FLAG_NAT_T_ENABLE; + + if (!outgoing_path_is_clear) { + msg->exchange->flags |= EXCHANGE_FLAG_NAT_T_KEEPALIVE; + LOG_DBG((LOG_EXCHANGE, 10, "nat_t_exchange_check_nat_d: " + "NAT detected, we're behind it")); + } else + LOG_DBG ((LOG_EXCHANGE, 10, + "nat_t_exchange_check_nat_d: NAT detected")); + return 1; +} + +static void +nat_t_send_keepalive(void *v_arg) +{ + struct sa *sa = (struct sa *)v_arg; + struct transport *t; + struct timeval now; + int interval; + + /* Send the keepalive message. */ + t = ((struct virtual_transport *)sa->transport)->encap; + t->vtbl->send_message(NULL, t); + + /* Set new timer. */ + interval = conf_get_num("General", "NAT-T-Keepalive", 0); + if (interval < 1) + interval = NAT_T_KEEPALIVE_INTERVAL; + gettimeofday(&now, 0); + now.tv_sec += interval; + + sa->nat_t_keepalive = timer_add_event("nat_t_send_keepalive", + nat_t_send_keepalive, v_arg, &now); + if (!sa->nat_t_keepalive) + log_print("nat_t_send_keepalive: " + "timer_add_event() failed, will send no more keepalives"); +} + +void +nat_t_setup_keepalive(struct sa *sa) +{ + struct sockaddr *src; + struct timeval now; + + if (sa->initiator) + sa->transport->vtbl->get_src(sa->transport, &src); + else + sa->transport->vtbl->get_dst(sa->transport, &src); + + if (!virtual_listen_lookup(src)) + return; + + gettimeofday(&now, 0); + now.tv_sec += NAT_T_KEEPALIVE_INTERVAL; + + sa->nat_t_keepalive = timer_add_event("nat_t_send_keepalive", + nat_t_send_keepalive, sa, &now); + if (!sa->nat_t_keepalive) + log_print("nat_t_setup_keepalive: " + "timer_add_event() failed, will not send keepalives"); + + LOG_DBG((LOG_TRANSPORT, 50, "nat_t_setup_keepalive: " + "added event for phase 1 SA %p", sa)); +} |