diff options
Diffstat (limited to 'src/pluto/nat_traversal.c')
-rw-r--r-- | src/pluto/nat_traversal.c | 866 |
1 files changed, 866 insertions, 0 deletions
diff --git a/src/pluto/nat_traversal.c b/src/pluto/nat_traversal.c new file mode 100644 index 000000000..4a52cc107 --- /dev/null +++ b/src/pluto/nat_traversal.c @@ -0,0 +1,866 @@ +/* FreeS/WAN NAT-Traversal + * Copyright (C) 2002-2005 Mathieu Lafon - Arkoon Network Security + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: nat_traversal.c,v 1.8 2005/01/06 22:36:58 as Exp $ + */ + +#include <stdio.h> +#include <stdlib.h> +#include <ctype.h> +#include <stdarg.h> +#include <syslog.h> +#include <errno.h> +#include <string.h> +#include <unistd.h> +#include <signal.h> /* used only if MSG_NOSIGNAL not defined */ +#include <sys/queue.h> + +#include <freeswan.h> +#include <ipsec_policy.h> +#include <pfkeyv2.h> +#include <pfkey.h> + +#include "constants.h" +#include "defs.h" +#include "log.h" +#include "server.h" +#include "state.h" +#include "connections.h" +#include "packet.h" +#include "demux.h" +#include "kernel.h" +#include "whack.h" +#include "timer.h" +#include "cookie.h" +#include "sha1.h" +#include "md5.h" +#include "crypto.h" +#include "vendor.h" +#include "ike_alg.h" +#include "nat_traversal.h" + +/* #define FORCE_NAT_TRAVERSAL */ +#define NAT_D_DEBUG +#define NAT_T_SUPPORT_LAST_DRAFTS + +#ifndef SOL_UDP +#define SOL_UDP 17 +#endif + +#ifndef UDP_ESPINUDP +#define UDP_ESPINUDP 100 +#endif + +#define DEFAULT_KEEP_ALIVE_PERIOD 20 + +#ifdef _IKE_ALG_H +/* Alg patch: hash_digest_len -> hash_digest_size */ +#define hash_digest_len hash_digest_size +#endif + +bool nat_traversal_enabled = FALSE; +bool nat_traversal_support_non_ike = FALSE; +bool nat_traversal_support_port_floating = FALSE; + +static unsigned int _kap = 0; +static unsigned int _ka_evt = 0; +static bool _force_ka = 0; + +static const char *natt_version = "0.6c"; + +void init_nat_traversal (bool activate, unsigned int keep_alive_period, + bool fka, bool spf) +{ + nat_traversal_enabled = activate; + nat_traversal_support_non_ike = activate; +#ifdef NAT_T_SUPPORT_LAST_DRAFTS + nat_traversal_support_port_floating = activate ? spf : FALSE; +#endif + _force_ka = fka; + _kap = keep_alive_period ? keep_alive_period : DEFAULT_KEEP_ALIVE_PERIOD; + plog(" including NAT-Traversal patch (Version %s)%s%s%s" + , natt_version, activate ? "" : " [disabled]" + , activate & fka ? " [Force KeepAlive]" : "" + , activate & !spf ? " [Port Floating disabled]" : ""); +} + +static void disable_nat_traversal (int type) +{ + if (type == ESPINUDP_WITH_NON_IKE) + nat_traversal_support_non_ike = FALSE; + else + nat_traversal_support_port_floating = FALSE; + + if (!nat_traversal_support_non_ike && + !nat_traversal_support_port_floating) + nat_traversal_enabled = FALSE; +} + +static void _natd_hash(const struct hash_desc *hasher, char *hash, + u_int8_t *icookie, u_int8_t *rcookie, + const ip_address *ip, u_int16_t port) +{ + union hash_ctx ctx; + + if (is_zero_cookie(icookie)) + DBG_log("_natd_hash: Warning, icookie is zero !!"); + if (is_zero_cookie(rcookie)) + DBG_log("_natd_hash: Warning, rcookie is zero !!"); + + /** + * draft-ietf-ipsec-nat-t-ike-01.txt + * + * HASH = HASH(CKY-I | CKY-R | IP | Port) + * + * All values in network order + */ + hasher->hash_init(&ctx); + hasher->hash_update(&ctx, icookie, COOKIE_SIZE); + hasher->hash_update(&ctx, rcookie, COOKIE_SIZE); + switch (addrtypeof(ip)) { + case AF_INET: + hasher->hash_update(&ctx, (const u_char *)&ip->u.v4.sin_addr.s_addr + , sizeof(ip->u.v4.sin_addr.s_addr)); + break; + case AF_INET6: + hasher->hash_update(&ctx, (const u_char *)&ip->u.v6.sin6_addr.s6_addr + , sizeof(ip->u.v6.sin6_addr.s6_addr)); + break; + } + hasher->hash_update(&ctx, (const u_char *)&port, sizeof(u_int16_t)); + hasher->hash_final(hash, &ctx); +#ifdef NAT_D_DEBUG + DBG(DBG_NATT, + DBG_log("_natd_hash: hasher=%p(%d)", hasher, (int)hasher->hash_digest_len); + DBG_dump("_natd_hash: icookie=", icookie, COOKIE_SIZE); + DBG_dump("_natd_hash: rcookie=", rcookie, COOKIE_SIZE); + switch (addrtypeof(ip)) { + case AF_INET: + DBG_dump("_natd_hash: ip=", &ip->u.v4.sin_addr.s_addr + , sizeof(ip->u.v4.sin_addr.s_addr)); + break; + } + DBG_log("_natd_hash: port=%d", port); + DBG_dump("_natd_hash: hash=", hash, hasher->hash_digest_len); + ); +#endif +} + +/* Add NAT-Traversal VIDs (supported ones) + * used when we are Initiator + */ +bool nat_traversal_add_vid(u_int8_t np, pb_stream *outs) +{ + bool r = TRUE; + + if (nat_traversal_support_port_floating) + { + u_int8_t last_np = nat_traversal_support_non_ike ? + ISAKMP_NEXT_VID : np; + + if (r) + r = out_vendorid(ISAKMP_NEXT_VID, outs, VID_NATT_RFC); + if (r) + r = out_vendorid(ISAKMP_NEXT_VID, outs, VID_NATT_IETF_03); + if (r) + r = out_vendorid(ISAKMP_NEXT_VID, outs, VID_NATT_IETF_02); + if (r) + r = out_vendorid(last_np, outs, VID_NATT_IETF_02_N); + } + if (nat_traversal_support_non_ike) + { + if (r) + r = out_vendorid(np, outs, VID_NATT_IETF_00); + } + return r; +} + +u_int32_t nat_traversal_vid_to_method(unsigned short nat_t_vid) +{ + switch (nat_t_vid) + { + case VID_NATT_IETF_00: + return LELEM(NAT_TRAVERSAL_IETF_00_01); + case VID_NATT_IETF_02: + case VID_NATT_IETF_02_N: + case VID_NATT_IETF_03: + return LELEM(NAT_TRAVERSAL_IETF_02_03); + case VID_NATT_RFC: + return LELEM(NAT_TRAVERSAL_RFC); + } + return 0; +} + +void nat_traversal_natd_lookup(struct msg_digest *md) +{ + char hash[MAX_DIGEST_LEN]; + struct payload_digest *p; + struct state *st = md->st; + int i; + + if (!st || !md->iface || !st->st_oakley.hasher) + { + loglog(RC_LOG_SERIOUS, "NAT-Traversal: assert failed %s:%d" + , __FILE__, __LINE__); + return; + } + + /** Count NAT-D **/ + for (p = md->chain[ISAKMP_NEXT_NATD_RFC], i=0; p != NULL; p = p->next, i++); + + /* + * We need at least 2 NAT-D (1 for us, many for peer) + */ + if (i < 2) + { + loglog(RC_LOG_SERIOUS, + "NAT-Traversal: Only %d NAT-D - Aborting NAT-Traversal negociation", i); + st->nat_traversal = 0; + return; + } + + /* + * First one with my IP & port + */ + p = md->chain[ISAKMP_NEXT_NATD_RFC]; + _natd_hash(st->st_oakley.hasher, hash, st->st_icookie, st->st_rcookie, + &(md->iface->addr), ntohs(st->st_connection->spd.this.host_port)); + + if (!(pbs_left(&p->pbs) == st->st_oakley.hasher->hash_digest_len && + memcmp(p->pbs.cur, hash, st->st_oakley.hasher->hash_digest_len) == 0)) + { +#ifdef NAT_D_DEBUG + DBG(DBG_NATT, + DBG_log("NAT_TRAVERSAL_NAT_BHND_ME"); + DBG_dump("expected NAT-D:", hash + , st->st_oakley.hasher->hash_digest_len); + DBG_dump("received NAT-D:", p->pbs.cur, pbs_left(&p->pbs)); + ) +#endif + st->nat_traversal |= LELEM(NAT_TRAVERSAL_NAT_BHND_ME); + } + + /* + * The others with sender IP & port + */ + _natd_hash(st->st_oakley.hasher, hash, st->st_icookie, st->st_rcookie, + &(md->sender), ntohs(md->sender_port)); + for (p = p->next, i=0 ; p != NULL; p = p->next) + { + if (pbs_left(&p->pbs) == st->st_oakley.hasher->hash_digest_len && + memcmp(p->pbs.cur, hash, st->st_oakley.hasher->hash_digest_len) == 0) + { + i++; + } + } + if (!i) + { +#ifdef NAT_D_DEBUG + DBG(DBG_NATT, + DBG_log("NAT_TRAVERSAL_NAT_BHND_PEER"); + DBG_dump("expected NAT-D:", hash + , st->st_oakley.hasher->hash_digest_len); + p = md->chain[ISAKMP_NEXT_NATD_RFC]; + for (p = p->next, i=0 ; p != NULL; p = p->next) + { + DBG_dump("received NAT-D:", p->pbs.cur, pbs_left(&p->pbs)); + } + ) +#endif + st->nat_traversal |= LELEM(NAT_TRAVERSAL_NAT_BHND_PEER); + } +#ifdef FORCE_NAT_TRAVERSAL + st->nat_traversal |= LELEM(NAT_TRAVERSAL_NAT_BHND_PEER); + st->nat_traversal |= LELEM(NAT_TRAVERSAL_NAT_BHND_ME); +#endif +} + +bool nat_traversal_add_natd(u_int8_t np, pb_stream *outs, + struct msg_digest *md) +{ + char hash[MAX_DIGEST_LEN]; + struct state *st = md->st; + + if (!st || !st->st_oakley.hasher) + { + loglog(RC_LOG_SERIOUS, "NAT-Traversal: assert failed %s:%d" + , __FILE__, __LINE__); + return FALSE; + } + + DBG(DBG_EMITTING, + DBG_log("sending NATD payloads") + ) + + /* + * First one with sender IP & port + */ + _natd_hash(st->st_oakley.hasher, hash, st->st_icookie, + is_zero_cookie(st->st_rcookie) ? md->hdr.isa_rcookie : st->st_rcookie, + &(md->sender), +#ifdef FORCE_NAT_TRAVERSAL + 0 +#else + ntohs(md->sender_port) +#endif + ); + if (!out_generic_raw((st->nat_traversal & NAT_T_WITH_RFC_VALUES + ? ISAKMP_NEXT_NATD_RFC : ISAKMP_NEXT_NATD_DRAFTS), &isakmp_nat_d, outs, + hash, st->st_oakley.hasher->hash_digest_len, "NAT-D")) + { + return FALSE; + } + + /* + * Second one with my IP & port + */ + _natd_hash(st->st_oakley.hasher, hash, st->st_icookie, + is_zero_cookie(st->st_rcookie) ? md->hdr.isa_rcookie : st->st_rcookie, + &(md->iface->addr), +#ifdef FORCE_NAT_TRAVERSAL + 0 +#else + ntohs(st->st_connection->spd.this.host_port) +#endif + ); + return (out_generic_raw(np, &isakmp_nat_d, outs, + hash, st->st_oakley.hasher->hash_digest_len, "NAT-D")); +} + +/* + * nat_traversal_natoa_lookup() + * + * Look for NAT-OA in message + */ +void nat_traversal_natoa_lookup(struct msg_digest *md) +{ + struct payload_digest *p; + struct state *st = md->st; + int i; + ip_address ip; + + if (!st || !md->iface) + { + loglog(RC_LOG_SERIOUS, "NAT-Traversal: assert failed %s:%d" + , __FILE__, __LINE__); + return; + } + + /* Initialize NAT-OA */ + anyaddr(AF_INET, &st->nat_oa); + + /* Count NAT-OA **/ + for (p = md->chain[ISAKMP_NEXT_NATOA_RFC], i=0; p != NULL; p = p->next, i++); + + DBG(DBG_NATT, + DBG_log("NAT-Traversal: received %d NAT-OA.", i) + ) + + if (i == 0) + return; + + if (!(st->nat_traversal & LELEM(NAT_TRAVERSAL_NAT_BHND_PEER))) + { + loglog(RC_LOG_SERIOUS, "NAT-Traversal: received %d NAT-OA. " + "ignored because peer is not NATed", i); + return; + } + + if (i > 1) + { + loglog(RC_LOG_SERIOUS, "NAT-Traversal: received %d NAT-OA. " + "using first, ignoring others", i); + } + + /* Take first */ + p = md->chain[ISAKMP_NEXT_NATOA_RFC]; + + DBG(DBG_PARSING, + DBG_dump("NAT-OA:", p->pbs.start, pbs_room(&p->pbs)); + ); + + switch (p->payload.nat_oa.isanoa_idtype) + { + case ID_IPV4_ADDR: + if (pbs_left(&p->pbs) == sizeof(struct in_addr)) + { + initaddr(p->pbs.cur, pbs_left(&p->pbs), AF_INET, &ip); + } + else + { + loglog(RC_LOG_SERIOUS, "NAT-Traversal: received IPv4 NAT-OA " + "with invalid IP size (%d)", (int)pbs_left(&p->pbs)); + return; + } + break; + case ID_IPV6_ADDR: + if (pbs_left(&p->pbs) == sizeof(struct in6_addr)) + { + initaddr(p->pbs.cur, pbs_left(&p->pbs), AF_INET6, &ip); + } + else + { + loglog(RC_LOG_SERIOUS, "NAT-Traversal: received IPv6 NAT-OA " + "with invalid IP size (%d)", (int)pbs_left(&p->pbs)); + return; + } + break; + default: + loglog(RC_LOG_SERIOUS, "NAT-Traversal: " + "invalid ID Type (%d) in NAT-OA - ignored", + p->payload.nat_oa.isanoa_idtype); + return; + } + + DBG(DBG_NATT, + { + char ip_t[ADDRTOT_BUF]; + addrtot(&ip, 0, ip_t, sizeof(ip_t)); + + DBG_log("received NAT-OA: %s", ip_t); + } + ) + + if (isanyaddr(&ip)) + loglog(RC_LOG_SERIOUS, "NAT-Traversal: received %%any NAT-OA..."); + else + st->nat_oa = ip; +} + +bool nat_traversal_add_natoa(u_int8_t np, pb_stream *outs, + struct state *st) +{ + struct isakmp_nat_oa natoa; + pb_stream pbs; + unsigned char ip_val[sizeof(struct in6_addr)]; + size_t ip_len = 0; + ip_address *ip; + + if ((!st) || (!st->st_connection)) + { + loglog(RC_LOG_SERIOUS, "NAT-Traversal: assert failed %s:%d" + , __FILE__, __LINE__); + return FALSE; + } + ip = &(st->st_connection->spd.this.host_addr); + + memset(&natoa, 0, sizeof(natoa)); + natoa.isanoa_np = np; + + switch (addrtypeof(ip)) + { + case AF_INET: + ip_len = sizeof(ip->u.v4.sin_addr.s_addr); + memcpy(ip_val, &ip->u.v4.sin_addr.s_addr, ip_len); + natoa.isanoa_idtype = ID_IPV4_ADDR; + break; + case AF_INET6: + ip_len = sizeof(ip->u.v6.sin6_addr.s6_addr); + memcpy(ip_val, &ip->u.v6.sin6_addr.s6_addr, ip_len); + natoa.isanoa_idtype = ID_IPV6_ADDR; + break; + default: + loglog(RC_LOG_SERIOUS, "NAT-Traversal: " + "invalid addrtypeof()=%d", addrtypeof(ip)); + return FALSE; + } + + if (!out_struct(&natoa, &isakmp_nat_oa, outs, &pbs)) + return FALSE; + + if (!out_raw(ip_val, ip_len, &pbs, "NAT-OA")) + return FALSE; + + DBG(DBG_NATT, + DBG_dump("NAT-OA (S):", ip_val, ip_len) + ) + + close_output_pbs(&pbs); + return TRUE; +} + +void nat_traversal_show_result (u_int32_t nt, u_int16_t sport) +{ + const char *mth = NULL, *rslt = NULL; + + switch (nt & NAT_TRAVERSAL_METHOD) + { + case LELEM(NAT_TRAVERSAL_IETF_00_01): + mth = natt_type_bitnames[0]; + break; + case LELEM(NAT_TRAVERSAL_IETF_02_03): + mth = natt_type_bitnames[1]; + break; + case LELEM(NAT_TRAVERSAL_RFC): + mth = natt_type_bitnames[2]; + break; + } + + switch (nt & NAT_T_DETECTED) + { + case 0: + rslt = "no NAT detected"; + break; + case LELEM(NAT_TRAVERSAL_NAT_BHND_ME): + rslt = "i am NATed"; + break; + case LELEM(NAT_TRAVERSAL_NAT_BHND_PEER): + rslt = "peer is NATed"; + break; + case LELEM(NAT_TRAVERSAL_NAT_BHND_ME) | LELEM(NAT_TRAVERSAL_NAT_BHND_PEER): + rslt = "both are NATed"; + break; + } + + loglog(RC_LOG_SERIOUS, + "NAT-Traversal: Result using %s: %s", + mth ? mth : "unknown method", + rslt ? rslt : "unknown result" + ); + + if ((nt & LELEM(NAT_TRAVERSAL_NAT_BHND_PEER)) + && (sport == IKE_UDP_PORT) + && ((nt & NAT_T_WITH_PORT_FLOATING)==0)) + { + loglog(RC_LOG_SERIOUS, + "Warning: peer is NATed but source port is still udp/%d. " + "Ipsec-passthrough NAT device suspected -- NAT-T may not work.", + IKE_UDP_PORT + ); + } +} + +int nat_traversal_espinudp_socket (int sk, u_int32_t type) +{ + int r = setsockopt(sk, SOL_UDP, UDP_ESPINUDP, &type, sizeof(type)); + + if (r < 0 && errno == ENOPROTOOPT) + { + loglog(RC_LOG_SERIOUS, + "NAT-Traversal: ESPINUDP(%d) not supported by kernel -- " + "NAT-T disabled", type); + disable_nat_traversal(type); + } + return r; +} + +void nat_traversal_new_ka_event (void) +{ + if (_ka_evt) + return; /* event already scheduled */ + + event_schedule(EVENT_NAT_T_KEEPALIVE, _kap, NULL); + _ka_evt = 1; +} + +static void nat_traversal_send_ka (struct state *st) +{ + static unsigned char ka_payload = 0xff; + chunk_t sav; + + DBG(DBG_NATT, + DBG_log("ka_event: send NAT-KA to %s:%d", + ip_str(&st->st_connection->spd.that.host_addr), + st->st_connection->spd.that.host_port); + ) + + /* save state chunk */ + setchunk(sav, st->st_tpacket.ptr, st->st_tpacket.len); + + /* send keep alive */ + setchunk(st->st_tpacket, &ka_payload, 1); + send_packet(st, "NAT-T Keep Alive"); + + /* restore state chunk */ + setchunk(st->st_tpacket, sav.ptr, sav.len); +} + +/** + * Find ISAKMP States with NAT-T and send keep-alive + */ +static void nat_traversal_ka_event_state (struct state *st, void *data) +{ + unsigned int *_kap_st = (unsigned int *)data; + const struct connection *c = st->st_connection; + + if (!c) + return; + + if ((st->st_state == STATE_MAIN_R3 || st->st_state == STATE_MAIN_I4) + && (st->nat_traversal & NAT_T_DETECTED) + && ((st->nat_traversal & LELEM(NAT_TRAVERSAL_NAT_BHND_ME)) || _force_ka)) + { + /* + * - ISAKMP established + * - NAT-Traversal detected + * - NAT-KeepAlive needed (we are NATed) + */ + if (c->newest_isakmp_sa != st->st_serialno) + { + /* + * if newest is also valid, ignore this one, we will only use + * newest. + */ + struct state *st_newest; + + st_newest = state_with_serialno(c->newest_isakmp_sa); + if (st_newest + && (st_newest->st_state == STATE_MAIN_R3 || st_newest->st_state == STATE_MAIN_I4) + && (st_newest->nat_traversal & NAT_T_DETECTED) + && ((st_newest->nat_traversal & LELEM(NAT_TRAVERSAL_NAT_BHND_ME)) || _force_ka)) + { + return; + } + } + set_cur_state(st); + nat_traversal_send_ka(st); + reset_cur_state(); + (*_kap_st)++; + } +} + +void nat_traversal_ka_event (void) +{ + unsigned int _kap_st = 0; + + _ka_evt = 0; /* ready to be reschedule */ + + for_each_state((void *)nat_traversal_ka_event_state, &_kap_st); + + /* if there are still states who needs Keep-Alive, schedule new event */ + if (_kap_st) + nat_traversal_new_ka_event(); +} + +struct _new_mapp_nfo { + ip_address addr; + u_int16_t sport, dport; +}; + +static void nat_traversal_find_new_mapp_state (struct state *st, void *data) +{ + struct connection *c = st->st_connection; + struct _new_mapp_nfo *nfo = (struct _new_mapp_nfo *)data; + + if (c != NULL + && sameaddr(&c->spd.that.host_addr, &(nfo->addr)) + && c->spd.that.host_port == nfo->sport) + { + + /* change host port */ + c->spd.that.host_port = nfo->dport; + + if (IS_IPSEC_SA_ESTABLISHED(st->st_state) + || IS_ONLY_INBOUND_IPSEC_SA_ESTABLISHED(st->st_state)) + { + if (!update_ipsec_sa(st)) + { + /* + * If ipsec update failed, restore old port or we'll + * not be able to update anymore. + */ + c->spd.that.host_port = nfo->sport; + } + } + } +} + +static int nat_traversal_new_mapping(const ip_address *src, u_int16_t sport, + const ip_address *dst, u_int16_t dport) +{ + char srca[ADDRTOT_BUF], dsta[ADDRTOT_BUF]; + struct _new_mapp_nfo nfo; + + addrtot(src, 0, srca, ADDRTOT_BUF); + addrtot(dst, 0, dsta, ADDRTOT_BUF); + + if (!sameaddr(src, dst)) + { + loglog(RC_LOG_SERIOUS, "nat_traversal_new_mapping: " + "address change currently not supported [%s:%d,%s:%d]", + srca, sport, dsta, dport); + return -1; + } + + if (sport == dport) + { + /* no change */ + return 0; + } + + DBG_log("NAT-T: new mapping %s:%d/%d)", srca, sport, dport); + + nfo.addr = *src; + nfo.sport = sport; + nfo.dport = dport; + + for_each_state((void *)nat_traversal_find_new_mapp_state, &nfo); + + return 0; +} + +void nat_traversal_change_port_lookup(struct msg_digest *md, struct state *st) +{ + struct connection *c = st ? st->st_connection : NULL; + struct iface *i = NULL; + + if ((st == NULL) || (c == NULL)) + return; + + if (md) + { + /* + * If source port has changed, update (including other states and + * established kernel SA) + */ + if (c->spd.that.host_port != md->sender_port) + { + nat_traversal_new_mapping(&c->spd.that.host_addr, c->spd.that.host_port, + &c->spd.that.host_addr, md->sender_port); + } + + /* + * If interface type has changed, update local port (500/4500) + */ + if ((c->spd.this.host_port == NAT_T_IKE_FLOAT_PORT && !md->iface->ike_float) + || (c->spd.this.host_port != NAT_T_IKE_FLOAT_PORT && md->iface->ike_float)) + { + c->spd.this.host_port = (md->iface->ike_float) + ? NAT_T_IKE_FLOAT_PORT : pluto_port; + + DBG(DBG_NATT, + DBG_log("NAT-T: updating local port to %d", c->spd.this.host_port); + ); + } + } + + /* + * If we're initiator and NAT-T (with port floating) is detected, we + * need to change port (MAIN_I3 or QUICK_I1) + */ + if ((st->st_state == STATE_MAIN_I3 || st->st_state == STATE_QUICK_I1) + && (st->nat_traversal & NAT_T_WITH_PORT_FLOATING) + && (st->nat_traversal & NAT_T_DETECTED) + && (c->spd.this.host_port != NAT_T_IKE_FLOAT_PORT)) + { + DBG(DBG_NATT, + DBG_log("NAT-T: floating to port %d", NAT_T_IKE_FLOAT_PORT); + ) + c->spd.this.host_port = NAT_T_IKE_FLOAT_PORT; + c->spd.that.host_port = NAT_T_IKE_FLOAT_PORT; + /* + * Also update pending connections or they will be deleted if uniqueids + * option is set. + */ + update_pending(st, st); + } + + /* + * Find valid interface according to local port (500/4500) + */ + if ((c->spd.this.host_port == NAT_T_IKE_FLOAT_PORT && !c->interface->ike_float) + || (c->spd.this.host_port != NAT_T_IKE_FLOAT_PORT && c->interface->ike_float)) + { + for (i = interfaces; i != NULL; i = i->next) + { + if (sameaddr(&c->interface->addr, &i->addr) + && i->ike_float != c->interface->ike_float) + { + DBG(DBG_NATT, + DBG_log("NAT-T: using interface %s:%d", i->rname, + i->ike_float ? NAT_T_IKE_FLOAT_PORT : pluto_port); + ) + c->interface = i; + break; + } + } + } +} + +struct _new_klips_mapp_nfo { + struct sadb_sa *sa; + ip_address src, dst; + u_int16_t sport, dport; +}; + +static void nat_t_new_klips_mapp (struct state *st, void *data) +{ + struct connection *c = st->st_connection; + struct _new_klips_mapp_nfo *nfo = (struct _new_klips_mapp_nfo *)data; + + if (c != NULL && st->st_esp.present + && sameaddr(&c->spd.that.host_addr, &(nfo->src)) + && st->st_esp.our_spi == nfo->sa->sadb_sa_spi) + { + nat_traversal_new_mapping(&c->spd.that.host_addr, c->spd.that.host_port, + &(nfo->dst), nfo->dport); + } +} + +void process_pfkey_nat_t_new_mapping( + struct sadb_msg *msg __attribute__ ((unused)), + struct sadb_ext *extensions[SADB_EXT_MAX + 1]) +{ + struct _new_klips_mapp_nfo nfo; + struct sadb_address *srcx = (void *) extensions[SADB_EXT_ADDRESS_SRC]; + struct sadb_address *dstx = (void *) extensions[SADB_EXT_ADDRESS_DST]; + struct sockaddr *srca, *dsta; + err_t ugh = NULL; + + nfo.sa = (void *) extensions[SADB_EXT_SA]; + + if (!nfo.sa || !srcx || !dstx) + { + plog("SADB_X_NAT_T_NEW_MAPPING message from KLIPS malformed: " + "got NULL params"); + return; + } + + srca = ((struct sockaddr *)(void *)&srcx[1]); + dsta = ((struct sockaddr *)(void *)&dstx[1]); + + if (srca->sa_family != AF_INET || dsta->sa_family != AF_INET) + { + ugh = "only AF_INET supported"; + } + else + { + char text_said[SATOT_BUF]; + char _srca[ADDRTOT_BUF], _dsta[ADDRTOT_BUF]; + ip_said said; + + initaddr((const void *) &((const struct sockaddr_in *)srca)->sin_addr, + sizeof(((const struct sockaddr_in *)srca)->sin_addr), + srca->sa_family, &(nfo.src)); + nfo.sport = ntohs(((const struct sockaddr_in *)srca)->sin_port); + initaddr((const void *) &((const struct sockaddr_in *)dsta)->sin_addr, + sizeof(((const struct sockaddr_in *)dsta)->sin_addr), + dsta->sa_family, &(nfo.dst)); + nfo.dport = ntohs(((const struct sockaddr_in *)dsta)->sin_port); + + DBG(DBG_NATT, + initsaid(&nfo.src, nfo.sa->sadb_sa_spi, SA_ESP, &said); + satot(&said, 0, text_said, SATOT_BUF); + addrtot(&nfo.src, 0, _srca, ADDRTOT_BUF); + addrtot(&nfo.dst, 0, _dsta, ADDRTOT_BUF); + DBG_log("new klips mapping %s %s:%d %s:%d", + text_said, _srca, nfo.sport, _dsta, nfo.dport); + ) + + for_each_state((void *)nat_t_new_klips_mapp, &nfo); + } + + if (ugh != NULL) + plog("SADB_X_NAT_T_NEW_MAPPING message from KLIPS malformed: %s", ugh); +} + |