/* * Copyright (C) 2010 Tobias Brunner * Copyright (C) 2009 Andreas Steffen * Hochschule fuer Technik Rapperswil * 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 . * * 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. */ #include #include #include #include #include #include #include #include #include /* used only if MSG_NOSIGNAL not defined */ #include #include #include #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 "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 *oakley_hasher, char *hash, u_int8_t *icookie, u_int8_t *rcookie, const ip_address *ip, u_int16_t port) { 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 */ { chunk_t icookie_chunk = { icookie, COOKIE_SIZE }; chunk_t rcookie_chunk = { rcookie, COOKIE_SIZE }; chunk_t port_chunk = chunk_from_thing(port); chunk_t addr_chunk; hash_algorithm_t hash_alg; hasher_t *hasher; size_t hash_size; hash_alg = oakley_to_hash_algorithm(oakley_hasher->algo_id); hasher = lib->crypto->create_hasher(lib->crypto, hash_alg); hasher->get_hash(hasher, icookie_chunk, NULL); hasher->get_hash(hasher, rcookie_chunk, NULL); switch (addrtypeof(ip)) { case AF_INET: addr_chunk = chunk_from_thing(ip->u.v4.sin_addr.s_addr); break; case AF_INET6: addr_chunk = chunk_from_thing(ip->u.v6.sin6_addr.s6_addr); break; default: addr_chunk = chunk_empty; /* should never occur */ } hasher->get_hash(hasher, addr_chunk, NULL); hasher->get_hash(hasher, port_chunk, hash); hash_size = hasher->get_hash_size(hasher); hasher->destroy(hasher); #ifdef NAT_D_DEBUG DBG(DBG_NATT, DBG_dump_chunk("_natd_hash: icookie=", icookie_chunk); DBG_dump_chunk("_natd_hash: rcookie=", rcookie_chunk); DBG_dump_chunk("_natd_hash: ip=", addr_chunk); DBG_log("_natd_hash: port=%d", port); DBG_dump("_natd_hash: hash=", hash, hash_size); ) #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 negotiation", 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 && memeq(p->pbs.cur, hash, st->st_oakley.hasher->hash_digest_len))) { #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 && memeq(p->pbs.cur, hash, st->st_oakley.hasher->hash_digest_len)) { 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 */ sav = st->st_tpacket; /* send keep alive */ st->st_tpacket = chunk_create(&ka_payload, 1); send_packet(st, "NAT-T Keep Alive"); /* restore state chunk */ st->st_tpacket = sav; } /** * 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 connection_t *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) { connection_t *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) { connection_t *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_kernel_mapp_nfo { u_int32_t reqid; u_int32_t spi; ip_address *addr; }; static void nat_t_new_kernel_mapp (struct state *st, void *data) { connection_t *c = st->st_connection; struct _new_kernel_mapp_nfo *nfo = (struct _new_kernel_mapp_nfo *)data; if (c != NULL && st->st_esp.present && nfo->spi == st->st_esp.our_spi && nfo->reqid == c->spd.reqid) { u_int16_t port = ntohs(portof(nfo->addr)); DBG(DBG_NATT, { char text_said[SATOT_BUF]; char olda[ADDRTOT_BUF]; char newa[ADDRTOT_BUF]; ip_said said; initsaid(&c->spd.that.host_addr, nfo->spi, SA_ESP, &said); satot(&said, 0, text_said, SATOT_BUF); addrtot(&c->spd.that.host_addr, 0, olda, ADDRTOT_BUF); addrtot(nfo->addr, 0, newa, ADDRTOT_BUF); DBG_log("new kernel mapping %s %s:%d %s:%d", text_said, olda, c->spd.that.host_port, newa, port); }) nat_traversal_new_mapping(&c->spd.that.host_addr, c->spd.that.host_port, nfo->addr, port); } } void process_nat_t_new_mapping(u_int32_t reqid, u_int32_t spi, ip_address *new_end) { struct _new_kernel_mapp_nfo nfo = { .reqid = reqid, .spi = spi, .addr = new_end, }; for_each_state((void *)nat_t_new_kernel_mapp, &nfo); }