/**
    NAT traversal through TCP hole punching and connection reversal

    Copyright (c) 2020-2021 The Creators of Simphone

    See the file COPYING.LESSER.txt for copying permission.
**/

#include "config.h"
#include "spth.h"

#include "logger.h"
#include "table.h"
#include "error.h"
#include "utils.h"
#include "system.h"
#include "crypto.h"
#include "socket.h"
#include "network.h"
#include "contact.h"
#include "param.h"
#include "proto.h"
#include "proxy.h"
#include "proxies.h"
#include "nat.h"
#include "server.h"
#include "client.h"
#include "msg.h"
#include "audio.h"

#include <string.h>
#include <stdlib.h>
#include <errno.h>

#ifdef _WIN32
#undef EINPROGRESS
#define EINPROGRESS WSAEWOULDBLOCK
#undef EALREADY
#define EALREADY WSAEALREADY
#undef EISCONN
#define EISCONN WSAEISCONN
#else
#include <netinet/in.h>
#include <arpa/inet.h>
#endif

#define SIM_MODULE "nat"

#define NAT_MIN_PERIOD 10 /* minimal number of minutes between traversal attempts */
#define NAT_MAX_DELTA 10  /* maximal number of consecutive ports to scan */

#define NAT_TRAVERSE_PHASES 4 /* number of times to auto-repeat a failed traversal attempt */

#define NAT_TRAVERSE_STATE_QUIT (-1) /* traversal thread has quit */
#define NAT_TRAVERSE_STATE_INIT 0    /* not ready for traversal yet (reversal still running) */
#define NAT_TRAVERSE_STATE_SENT 1    /* waiting for NAT_CMD_REPLY (traverse request sent) */
#define NAT_TRAVERSE_STATE_IDLE 2    /* waiting for NAT_CMD_REQUEST, NAT_CMD_START or NAT_CMD_RESTART/NAT_CMD_INVERSE */
#define NAT_TRAVERSE_STATE_WAIT 3    /* waiting for NAT_CMD_REQUEST, NAT_CMD_START or NAT_CMD_RESTART/NAT_CMD_INVERSE */
#define NAT_TRAVERSE_STATE_RECV 4    /* traverse reply sent/received (attempt in progress) */

static const char *nat_traverse_state_names[] = { "QUIT", "INIT", "SENT", "IDLE", "WAIT", "RECV" };
#define NAT_TRAVERSE_LOOKUP_NAME(nat) \
  (! (nat) ? "NONE" : nat_traverse_state_names[(nat)->state - NAT_TRAVERSE_STATE_QUIT])
#define NAT_CONVERT_SEQUENCE(sequence) \
  (0x4000000000000000LL & (sequence) ? (sequence) ^ 0x8000000000000000LL : (simunsigned) (sequence))

#define NAT_FLAG_TRAVERSE 1   /* traversal is allowed */
#define NAT_FLAG_TRAVERSED 2  /* traversal has been attempted initially */
#define NAT_FLAG_TRAVERSING 4 /* traversed socket is about to be set */
#define NAT_FLAG_WAITING 8    /* waiting for traversal attempt to begin */
#define NAT_FLAG_PROXY 16     /* reversal has found a proxy contact */

struct nat {
  pth_msgport_t queue;     /* pth queue of traversal actions */
  pth_event_t event;       /* pth event for the queue */
  int flags;               /* NAT_FLAG_xxx */
  int state;               /* NAT_TRAVERSE_STATE_xxx */
  int timeout;             /* number of seconds to time out */
  int delay;               /* initial delay (milliseconds) */
  int delta;               /* port delta of peer */
  int deltathis[2];        /* port delta of peer to use for current traversal attempt */
  int deltanext[3];        /* my port delta that peer is using (thinks that i have) */
  int port;                /* traversal port number of peer */
  int ownport;             /* own source port number as reported by traversal connection to proxy */
  int reconnect;           /* number of reconnect attempts for establishing traversal connection to proxy */
  int phase;               /* number of times NAT_CMD_RESTART has been repeated already (zero on the initiator) */
  simnumber traverse_time; /* time when NAT traversal has succeeded or zero if not */
  simnumber phasetick;     /* time when NAT_CMD_RESTART must be repeated */
  simnumber statetick;     /* time when NAT_TRAVERSE_STATE_SENT was set or zero if not */
  simnumber starttick;     /* time when NAT_CMD_START was last sent */
  simnumber rcvnonce;      /* received random nonce for traversal or received sequence number for reversal */
  simnumber sndnonce;      /* sent random nonce for traversal or sent sequence number for reversal */
  simcustomer proxy;       /* traverser connection to proxy */
  pth_t tid;               /* thread identifier of traversal thread */
  unsigned status;         /* index into nat_traverse_status_names (NAT_STATUS_xxx) */
  int reversing;           /* NAT_REVERSE_STATE_xxx */
  simsocket reversed;      /* new socket of reversed connection */
  simsocket traversed;     /* new socket of successfully traversed connection */
  struct _socket sock;     /* listening socket bound to traverser customer local port */
};

#define NAT_REVERSE_STATE_READY (-1)  /* reversal not started */
#define NAT_REVERSE_STATE_START 0     /* reversal is starting */
#define NAT_REVERSE_STATE_CONNECT 1   /* outgoing reversal initiated */
#define NAT_REVERSE_STATE_ACCEPTED 2  /* incoming reversal (waiting for execution) */
#define NAT_REVERSE_STATE_CONNECTED 3 /* outgoing reversal waiting for execution */
#define NAT_REVERSE_STATE_DONE 4      /* reversal finished */

static const char *nat_reverse_state_names[] = { "READY", "START", "CONNECT", "ACCEPTED", "CONNECTED", "DONE" };
#define NAT_REVERSE_LOOKUP_NAME(nat) nat_reverse_state_names[(nat)->reversing - NAT_REVERSE_STATE_READY]

#define NAT_STATUS_NONE 0     /* neither traversal nor reversal */
#define NAT_STATUS_LOCAL 1    /* outgoing reversal to local ip address */
#define NAT_STATUS_DIRECT 2   /* outgoing reversal to real ip address */
#define NAT_STATUS_REVERSE 3  /* incoming reversal */
#define NAT_STATUS_INCOMING 4 /* traversal through incoming socket */
#define NAT_STATUS_OUTGOING 5 /* traversal through outgoing socket */

static const char *nat_traverse_status_names[] = { "", "/LOCAL", "/DIRECT", "/REVERSE", "/TRAVERSE",
                                                   "/OUT0", "/OUT1", "/OUT2", "/OUT3", "/OUT4",
                                                   "/OUT5", "/OUT6", "/OUT7", "/OUT8", "/OUT9", "/OUTx" };

struct nat_queue {      /* traverse queue item */
  pth_message_t header; /* message queue item */
  simunsigned tick;     /* time when command was sent */
  int cmd;              /* NAT_CMD_xxx (not NAT_CMD_INVERSED) */
  int req;              /* if cmd is NAT_CMD_REQUEST this is peer's NAT_CMD_xxx.
                           otherwise, it's either NAT_CMD_NONE or NAT_CMD_REQUEST (to disable command logging) */
};

static const char *nat_command_names[] = { "", " stop", " start", " restart", " inverse", " inversed", " request",
                                           " reply", " reverse", " reversing", " reverse-end" };

static int nat_delta = NAT_MAX_DELTA;    /* own port delta set or detected */
                                         /* own port delta successfully traversed or... */
static int nat_delta_ok = NAT_MAX_DELTA; /* NAT_MAX_DELTA if not traversed or -NAT_MAX_DELTA if failed to detect own port delta */

struct nat_reverse_arg {
  simclient newclient; /* temporary client which is only a cancellable socket */
  simclient client;    /* old client which will remain with the new socket */
  unsigned ip;         /* ip address to connect to */
  int port;            /* port number to connect to */
  pth_t tid;           /* thread identifier of reversal thread or zero if not running */
  int err;             /* SIM_NO_ERROR if connecting, SIM_NAT_REVERSE_RETRY if failed or else SIM_OK or error code if failing */
};

#define nat_new_cmd_ack(contact) \
  client_new_cmd (SIM_CMD_ACK, SIM_CMD_ACK_HANDLE, number_new ((contact)->msgs.msgnum), NULL, nil ())
#define nat_new_cmd_nak(abort) \
  client_new_cmd (SIM_CMD_TRAVERSE_REPLY, SIM_CMD_TRAVERSE_TIME, number_new (abort), NULL, nil ())
#define NAT_GET_FD(sock) ((sock) ? (sock)->fd : -2)

static void nat_set_state (simclient client, int state) {
  client->nat->flags &= ~NAT_FLAG_WAITING;
  client->nat->statetick = (client->nat->state = state) == NAT_TRAVERSE_STATE_SENT ? system_get_tick () : 0;
  LOG_DEBUG_ ("traverse $%d state %s\n", client->sock->fd, nat_traverse_state_names[state - NAT_TRAVERSE_STATE_QUIT]);
}

static int nat_put (simclient client, int cmd, int req) {
  int err;
  struct nat_queue *item = sim_new (sizeof (*item));
  item->tick = system_get_tick ();
  item->cmd = cmd;
  item->req = req;
  if ((err = pth_queue_put (client->nat->queue, &item->header)) != SIM_OK)
    sim_free (item, sizeof (*item));
  return err;
}

static void nat_cancel (simclient client, simbool reverse) {
  if (reverse) {
    client_cancel_contact (client->contact, SIM_NAT_REVERSE_CANCELLED);
    client->nat->reversing = NAT_REVERSE_STATE_DONE;
    if (client->nat->reversed)
      socket_cancel (client->nat->reversed, SIM_NAT_REVERSE_CANCELLED);
  }
  nat_set_state (client, NAT_TRAVERSE_STATE_QUIT);
  nat_put (client, NAT_CMD_QUIT, NAT_CMD_NONE);
}

simbool nat_check_locked (simclient client) {
  if (client->nat) {
    if (client->nat->state == NAT_TRAVERSE_STATE_SENT || client->nat->state == NAT_TRAVERSE_STATE_RECV)
      return true;
    if (client->nat->state == NAT_TRAVERSE_STATE_WAIT || client->nat->flags & NAT_FLAG_WAITING)
      return true;
  }
  return false;
}

static simbool nat_check_connecting (int error) {
#ifdef _WIN32 /* not needed with winsock 2.0 ??? */
  if (error == WSAEINPROGRESS || error == WSAEINVAL)
    return true;
#endif
  return error == EINPROGRESS || error == EALREADY;
}

static int nat_socket_open (simsocket sock, int backlog, unsigned ip, int port) {
  int err = socket_open (sock, SOCKET_NO_SESSION);
  if (err == SIM_OK) {
    if ((err = sim_socket_set_reuse (sock->fd)) == SIM_OK)
      err = socket_listen_port (sock->fd, backlog, ip, port, NULL);
    if (err != SIM_OK)
      socket_close (sock, NULL);
  }
  return err;
}

static int nat_socket_connect (simsocket sock, unsigned ip, int port, const char *name) {
  int err = EINVAL;
  struct sockaddr_in sin;
  if (ip && port > 0) {
    LOG_DEBUG_ ("%s $%d -> %s:%d\n", name, sock->fd, network_convert_ip (ip), port);
    sin.sin_family = AF_INET, sin.sin_addr.s_addr = htonl (ip), sin.sin_port = htons (port);
    if (! connect (sock->fd, (struct sockaddr *) &sin, sizeof (sin))) {
      err = SIM_OK;
    } else if ((err = socket_get_errno ()) == 0)
      err = SIM_SOCKET_TIMEOUT;
  } else
    LOG_WARN_ ("connect $%d invalid %s:%d\n", sock->fd, network_convert_ip (ip), port);
  return err;
}

static void nat_socket_close (simsocket sock, simcontact contact, simbool traverse) {
  if (sock) {
    if (traverse) {
      sock->crypt.decrypt = sock->crypt.encrypt = NULL;
      sock->crypt.ivs = nil ();
    }
    socket_close (sock, contact);
    sim_free (sock, sizeof (*sock));
  }
}

static void *thread_reverse_ (void *arg) {
  struct nat_reverse_arg *args = arg;
  int err = SIM_SOCKET_TIMEOUT, fd = args->client->sock->fd, ver;
  simnumber tick = system_get_tick ();
  LOG_INFO_ ("reverse $%d:$%d connect %s:%d '%s'\n", fd, args->newclient->sock->fd,
             network_convert_ip (args->ip), args->port, args->client->contact->nick.str);
  if (param_get_number ("nat.test") & 2) {
    pth_sleep_ (param_get_number ("socket.connect"));
  } else if ((err = socket_connect_ (args->newclient->sock, &args->ip, args->port, NULL, false)) == SIM_OK) {
    int timeout = param_get_number ("nat.reverse");
    if (timeout > 0)
      args->newclient->sock->sndtimeout = args->newclient->sock->rcvtimeout = timeout;
    if ((err = proxy_handshake_client_ (args->newclient->sock, args->client->contact->addr, args->ip, args->port,
                                        NULL, NULL, &ver)) != SIM_OK)
      LOG_DEBUG_ ("handshake $%d:$%d at %s:%d error: %s\n", fd, args->newclient->sock->fd,
                  network_convert_ip (args->ip), args->port, convert_error (err, false));
  }
  if (err == SIM_OK)
    err = args->newclient->sock->err;
  if ((args->err = err) == SIM_OK) {
    LOG_INFO_ ("reverse $%d:$%d connect (%lld ms) '%s'\n", fd, args->newclient->sock->fd,
               system_get_tick () - tick, args->client->contact->nick.str);
  } else
    LOG_INFO_ ("reverse $%d:$%d connect (error %d) '%s'\n", fd, args->newclient->sock->fd,
               err, args->client->contact->nick.str);
  return pth_thread_exit_ (true);
}

static void event_send_net_traverse (simclient client, const char *chr, int error) {
  simtype event = table_new_name (2, SIM_EVENT_NET), status = string_copy (nat_get_status (client));
  table_add_pointer (event, SIM_EVENT_NET, SIM_EVENT_NET_TRAVERSE);
  table_add_number (event, SIM_EVENT_NET_TRAVERSE, error);
  status.str[0] = *chr;
  if (error == SIM_OK && (*chr == '+' || *chr == '-')) {
    if (client->nat->status >= NAT_STATUS_INCOMING) {
      client->nat->traverse_time = system_get_tick ();
    } else if (client->nat->status != NAT_STATUS_NONE) {
      table_add_pointer (event, SIM_EVENT_NET_TRAVERSE_ENCRYPT, client->sock->crypt.sndcipher);
      table_add_pointer (event, SIM_EVENT_NET_TRAVERSE_DECRYPT, client->sock->crypt.rcvcipher);
    }
  }
  if (! status.len) {
    string_free (status);
    status = pointer_new (chr);
  }
  table_add (event, SIM_EVENT_NET_TRAVERSE_STATUS, status);
  event_send (client->contact, event);
}

int nat_reverse_succeed_ (simclient client, simbool eof) {
  struct nat *nat = client->nat;
  simsocket oldsock;
  int fd = client->sock->fd, err;
  if (nat) {
    int reversing = nat->reversing;
    if (eof && reversing == NAT_REVERSE_STATE_CONNECT) {
      LOG_DEBUG_ ("reverse $%d%s WAIT %s\n", fd, nat_get_status (client), NAT_REVERSE_LOOKUP_NAME (nat));
      while (nat->reversing == reversing && client->sock->err == SIM_OK)
        pth_usleep_ (10000);
      LOG_DEBUG_ ("reverse $%d%s WAIT $%d %s\n", fd,
                  nat_get_status (client), NAT_GET_FD (nat->reversed), NAT_REVERSE_LOOKUP_NAME (nat));
    }
    if ((reversing = eof ? NAT_REVERSE_STATE_CONNECTED : NAT_REVERSE_STATE_ACCEPTED) == nat->reversing) {
      if ((err = socket_lock_ (oldsock = client->sock, -1, fd)) == SIM_OK) {
        if ((eof || (err = client_send_ (client, nat_new_cmd_ack (client->contact))) == SIM_OK) &&
            nat->reversed && nat->reversing == reversing) {
          simsocket sock = nat->reversed;
          nat->reversing = NAT_REVERSE_STATE_DONE;
          nat->reversed = NULL;
          crypt_close_socket (sock, false);
          memcpy (&sock->crypt, &oldsock->crypt, sizeof (sock->crypt));
          if (nat->rcvnonce) {
            sock->crypt.decryptseq = nat->rcvnonce - 2;
            sock->crypt.encryptseq = nat->sndnonce - 2;
          }
          err = client_set_socket_ (sock->client = client, oldsock = sock);
          nat_cancel (client, false);
          nat_socket_close (nat->traversed, client->contact, true), nat->traversed = NULL;
          LOG_ANY_ (err == SIM_OK ? SIM_LOG_INFO : SIM_LOG_WARN, "reverse $%d -> $%d (error %d)\n", fd, sock->fd, err);
          event_send_net_traverse (client, eof ? "-" : "+", err);
          if (err == SIM_OK)
            server_set_ciphers (client);
        } else
          LOG_ANY_ (err == SIM_OK ? SIM_LOG_WARN : SIM_LOG_NOTE, "reverse $%d -> $%d (error %d) %s%s\n",
                    fd, NAT_GET_FD (nat->reversed), err, NAT_REVERSE_LOOKUP_NAME (nat), nat_get_status (client));
        socket_unlock (oldsock, fd);
      }
      return err;
    }
  }
  if (eof)
    return nat_traverse_succeed_ (client, eof);
  LOG_WARN_ ("reverse $%d IGNORED state %s%s\n", fd, nat ? NAT_REVERSE_LOOKUP_NAME (nat) : "", nat_get_status (client));
  return SIM_OK;
}

int nat_reverse_accept_ (simsocket sock, simcontact contact, const simtype request, simclient *client) {
  struct nat *nat = NULL;
  int err = SIM_NAT_REVERSE_DENIED, err2, fd = -1;
  simnumber nonce = 0, tick = system_get_tick ();
  simtype reply = table_new (2);
  if (! socket_check_server (sock)) {
    if (contact->auth >= CONTACT_AUTH_ACCEPTED &&
        (*client = client_acquire (client_find (contact, CLIENT_FLAG_CONNECTED))) != NULL) {
      simbool server = ! socket_check_client ((*client)->sock);
      if ((! server || socket_check_server ((*client)->sock)) && (nat = (*client)->nat) != NULL) {
        if (nat->reversing == NAT_REVERSE_STATE_READY)
          nat->reversing = NAT_REVERSE_STATE_START;
        if (table_get_number (request, SIM_REQUEST_RANDOM) == (*client)->param.nonce[server]) {
          unsigned ip;
          nonce = table_get_number (request, SIM_REQUEST_SEQUENCE);
          nat->rcvnonce = NAT_CONVERT_SEQUENCE (nonce);
          if (socket_get_peer (sock, &ip, NULL) == SIM_OK) {
            LOG_XTRA_ ("country $%d %s '%s'\n", sock->fd, network_convert_ip (ip), contact->nick.str);
            contact->location = ip;
          }
          fd = (*client)->sock->fd;
          nonce = (*client)->param.nonce[! server];
          err = SIM_OK;
        } else
          err = SIM_NAT_REVERSE_NO_PROXY;
      }
    } else
      err = contact->auth >= CONTACT_AUTH_ACCEPTED ? SIM_CLIENT_UNKNOWN : SIM_CONTACT_BLOCKED;
  }
  if (err == SIM_OK) {
    int flags = contact != contact_list.me && proxy_check_provided () ? SIM_REPLY_FLAG_PROXY : 0;
    table_add_number (reply, SIM_REPLY, SIM_PROTO_VERSION_REVERSE);
    table_add_number (reply, SIM_REPLY_RANDOM, nonce);
    nat->sndnonce = (*client)->sock->crypt.encryptseq ^ 1;
    table_add_number (reply, SIM_REPLY_SEQUENCE, NAT_CONVERT_SEQUENCE (nat->sndnonce));
    table_add_number (reply, SIM_REPLY_ACK, contact->msgs.msgnum);
    table_add (reply, SIM_REPLY_FLAGS, sim_convert_flags_to_strings (flags, SIM_ARRAY (server_flag_names)));
  } else {
    LOG_NOTE_ ("handshake $%d error %d\n", sock->fd, err);
    table_add_number (reply, SIM_REPLY, SIM_PROTO_VERSION_MIN);
  }
  sock->crypt.padding = param_get_number ("socket.padding");
  if ((err2 = crypt_handshake_server_ (sock, reply, nil ())) == SIM_OK) {
    sock->flags |= SOCKET_FLAG_HANDSHAKE | SOCKET_FLAG_CLIENT;
    sock->created = 0;
    if (err == SIM_OK) {
      err = nat && (nat = (*client)->nat) == NULL ? SIM_NAT_REVERSE_CANCELLED : SIM_NAT_REVERSE_DENIED;
      if (nat && ! nat->reversed &&
          (nat->reversing == NAT_REVERSE_STATE_READY || nat->reversing == NAT_REVERSE_STATE_START)) {
        LOG_DEBUG_ ("reverse $%d:$%d accept '%s'\n", fd, sock->fd, contact->nick.str);
        nat->status = NAT_STATUS_REVERSE;
        nat->reversing = NAT_REVERSE_STATE_ACCEPTED;
        nat->reversed = sock;
        LOG_INFO_ ("reverse $%d:$%d accepted (%lld ms) '%s'\n", fd, sock->fd,
                   system_get_tick () - tick, contact->nick.str);
        err = SIM_OK;
      } else if (nat)
        LOG_WARN_ ("reverse $%d:$%d accept dropped by $%d %s '%s'\n", fd, sock->fd,
                   NAT_GET_FD (nat->reversed), NAT_REVERSE_LOOKUP_NAME (nat), contact->nick.str);
    }
  } else
    err = err2;
  if (nat && (nat = (*client)->nat) != NULL && nat->reversing == NAT_REVERSE_STATE_START)
    nat->reversing = NAT_REVERSE_STATE_READY;
  if (err != SIM_OK)
    LOG_INFO_ ("reverse $%d:$%d accept (error %d) '%s'\n", fd, sock->fd, err, contact->nick.str);
  table_free (reply);
  return err;
}

static int nat_reverse_connect_ (const struct nat_reverse_arg *arg, simclient client, simbool local) {
  struct nat *nat = client->nat;
  int fd = client->sock->fd, done;
  int err = arg->newclient->sock->err != SIM_OK ? arg->newclient->sock->err : SIM_NAT_REVERSE_PROXY;
  simbool server = ! socket_check_client (client->sock);
  simnumber tick = system_get_tick ();
  LOG_INFO_ ("reverse $%d:$%d connected %s:%d '%s'\n", fd, arg->newclient->sock->fd,
             network_convert_ip (arg->ip), arg->port, client->contact->nick.str);
  if (! socket_check_client (arg->newclient->sock)) {
    simtype version = table_new (2);
    table_add_number (version, SIM_REQUEST, SIM_PROTO_VERSION_REVERSE);
    table_add_number (version, SIM_REQUEST_RANDOM, client->param.nonce[! server]);
    nat->sndnonce = client->sock->crypt.encryptseq ^ 1;
    table_add_number (version, SIM_REQUEST_SEQUENCE, NAT_CONVERT_SEQUENCE (nat->sndnonce));
    table_add_number (version, SIM_REQUEST_ACK, client->contact->msgs.msgnum);
    err = crypt_handshake_client_ (arg->newclient->sock, &version, server_proto_handshake, SIM_HANDSHAKE_TYPE_REVERSE);
    if (err == SIM_OK) {
      arg->newclient->sock->flags |= SOCKET_FLAG_HANDSHAKE | SOCKET_FLAG_CLIENT;
      arg->newclient->sock->created = 0;
      if (table_get_number (version, SIM_REPLY) != SIM_PROTO_VERSION_REVERSE) {
        err = SIM_NAT_REVERSE_DENIED;
      } else if (table_get_number (version, SIM_REPLY_RANDOM) == client->param.nonce[server]) {
        simtype array = table_get (version, SIM_REPLY_FLAGS);
        simnumber nonce = table_get_number (version, SIM_REPLY_SEQUENCE);
        nat->rcvnonce = NAT_CONVERT_SEQUENCE (nonce);
        msg_recv_ack (client->contact, table_get_number (version, SIM_REPLY_ACK));
        LOG_DEBUG_ ("ip $%d = %s:%d '%s'\n", arg->newclient->sock->fd, network_convert_ip (arg->ip), arg->port,
                    client->contact->nick.str);
        client->contact->location = arg->ip;
        if (param_get_number ("proxy.require") <= 1 && ! proxy_find_server (client->contact->addr))
          contact_set_ip (client->contact, arg->ip, arg->port, CONTACT_IP_ADD);
        if (array.typ == SIMARRAY_STRING)
          if (sim_convert_strings_to_flags (array, SIM_ARRAY (server_flag_names)) & SIM_REPLY_FLAG_PROXY)
            nat->flags |= NAT_FLAG_PROXY;
      } else
        err = SIM_NAT_REVERSE_NO_PROXY;
    }
    table_free (version);
    event_test_error_crypto (client->contact, client->contact->addr, err);
  }
  if ((done = err == SIM_OK) != 0) {
    err = SIM_NAT_REVERSE_DENIED;
    if ((nat->reversing == NAT_REVERSE_STATE_READY || nat->reversing == NAT_REVERSE_STATE_START) && ! nat->reversed) {
      nat->reversing = NAT_REVERSE_STATE_CONNECT;
      if ((err = client_send_cmd_ (client, SIM_CMD_REVERSED, NULL, nil (), NULL, nil ())) != SIM_OK ||
          arg->newclient->sock->err != SIM_OK || nat->reversing != NAT_REVERSE_STATE_CONNECT || nat->reversed) {
        if (nat->reversing == NAT_REVERSE_STATE_CONNECT)
          nat->reversing = NAT_REVERSE_STATE_DONE;
        if (err == SIM_OK)
          err = arg->newclient->sock->err != SIM_OK ? arg->newclient->sock->err : SIM_NAT_REVERSE_ABANDONED;
      } else {
        arg->newclient->sock->rcvtimeout = client->sock->rcvtimeout;
        arg->newclient->sock->sndtimeout = client->sock->sndtimeout;
        nat->status = local ? NAT_STATUS_LOCAL : NAT_STATUS_DIRECT;
        nat->reversing = NAT_REVERSE_STATE_CONNECTED;
        nat->reversed = arg->newclient->sock;
        arg->newclient->sock->client = NULL;
        socket_new (arg->newclient->sock = sim_new (sizeof (*arg->newclient->sock)));
        LOG_INFO_ ("reverse $%d:$%d connected (%lld ms) '%s'\n", fd, nat->reversed->fd,
                   system_get_tick () - tick, client->contact->nick.str);
      }
    } else
      LOG_WARN_ ("reverse $%d:$%d connect dropped by $%d %s '%s'\n", fd, arg->newclient->sock->fd,
                 NAT_GET_FD (nat->reversed), NAT_REVERSE_LOOKUP_NAME (nat), client->contact->nick.str);
  }
  if (err != SIM_OK)
    LOG_INFO_ ("reverse $%d:$%d connected (error %d) '%s'\n", fd, NAT_GET_FD (arg->newclient->sock),
               err, client->contact->nick.str);
  return arg->newclient->sock->err == SIM_OK ? done : -1;
}

static void nat_reverse_stop_ (struct nat_reverse_arg *arg) {
  pth_thread_join_ (&arg->tid, NULL, thread_reverse_, arg->client->sock->fd);
  client_release (arg->newclient), arg->newclient = NULL;
}

static int nat_reverse_start (struct nat_reverse_arg *arg, simclient client, unsigned ip, int port) {
  simsocket sock = sim_new (sizeof (*sock));
  int err = socket_open (sock, arg->newclient = client_new (sock, client->contact, CLIENT_FLAG_REVERSE));
  sock->flags |= SOCKET_FLAG_NO_SESSION;
  arg->client = client, arg->ip = ip, arg->port = port, arg->tid = NULL, arg->err = SIM_NO_ERROR;
  if (err == SIM_OK)
    err = pth_thread_spawn (thread_reverse_, arg, &arg->tid, client->sock->fd);
  if (err != SIM_OK)
    client_release (arg->newclient), arg->newclient = NULL;
  return err;
}

static simbool nat_reverse_add (struct nat_reverse_arg *args, int count, unsigned ip, int port) {
  int i;
  if (! ip || ! port)
    return false;
  for (i = 0; i < count; i++)
    if (args[i].ip == ip && args[i].port == port)
      return false;
  args[count].ip = ip, args[count].port = port;
  return true;
}

static int nat_reverse_init (struct nat_reverse_arg *args, simclient client, int *local) {
  int i, count = 0, ips = param_get_number ("nat.ips");
  int port = local ? SIM_PROTO_PORT : client->call.udport ? client->call.udport : client->param.port[1];
  for (i = 0; (unsigned) i < SIM_ARRAY_SIZE (client->param.localip); i++) {
    count += nat_reverse_add (args, count, client->param.localip[i], client->param.localport[i]);
    count += nat_reverse_add (args, count, client->param.localip[i], port);
  }
  if (local)
    *local = count;
  for (i = 1; i < ips; i++)
    count += nat_reverse_add (args, count, client->param.ip[0], client->param.port[i]);
  for (i = 0; i < ips; i++) {
    count += nat_reverse_add (args, count, client->param.ip[i], client->param.port[i]);
    count += nat_reverse_add (args, count, client->param.ip[i], client->param.localport[0]);
    count += nat_reverse_add (args, count, client->param.ip[i], port);
  }
  return count;
}

static int nat_reverse_loop_ (simclient client) {
  simbool logging = true;
  struct nat_reverse_arg args[4 * SIM_ARRAY_SIZE (client->param.ip) - 1 + 2 * SIM_ARRAY_SIZE (client->param.localip)];
  int i, winner, local, fd = client->sock->fd, unfinished, done = 0, count = nat_reverse_init (args, client, &local);
#ifndef _WIN32
  if (client_count (NULL, NULL, NULL) + count >= socket_max_limit[SOCKET_MAX_CLIENTS]) {
    LOG_NOTE_ ("sockets %d + %d > %d\n", client_count (NULL, NULL, NULL), count, socket_max_limit[SOCKET_MAX_CLIENTS]);
    return 0;
  }
#endif
  for (i = 0; i < count; i++)
    nat_reverse_start (&args[i], client, args[i].ip, args[i].port);
  if (client->nat->reversing == NAT_REVERSE_STATE_READY)
    client->nat->reversing = NAT_REVERSE_STATE_START;
  do {
    if (logging) {
      if (LOG_PROTECT_ (SIM_MODULE, SIM_LOG_XTRA)) {
        LOG_XTRA_ ("select $%d", fd);
        for (i = 0; i < count; i++)
          if (args[i].tid && args[i].newclient)
            LOG_XTRA_ (" $%d", args[i].newclient->sock->fd);
        LOG_XTRA_ ("\n");
        LOG_UNPROTECT_ ();
      }
      logging = false;
    }
    pth_sleep_ (1);
    for (unfinished = winner = 0; winner < count; winner++)
      if (args[winner].tid && args[winner].newclient) {
        if (args[winner].err == SIM_OK)
          break;
        if (args[winner].err == SIM_NO_ERROR)
          unfinished++;
      }
    if (winner < count || client->nat->reversed) {
      logging = true;
      for (i = 0; i < count; i++)
        if (args[i].tid && args[i].newclient && i != winner) {
          if (args[i].err == SIM_OK)
            args[i].err = SIM_NO_ERROR;
          socket_cancel (args[i].newclient->sock, SIM_NAT_REVERSE_RETRY);
        }
      if (! client->nat->reversed) {
        unfinished = (done = nat_reverse_connect_ (&args[winner], client, winner < local)) == false;
      } else
        unfinished = 0;
      for (i = 0; i < count; i++)
        nat_reverse_stop_ (&args[i]);
      if (unfinished) {
        args[winner].err = SIM_NAT_REVERSE_RETRY;
        for (i = 0; i < count; i++)
          if (args[i].err == SIM_NO_ERROR)
            nat_reverse_start (&args[i], client, args[i].ip, args[i].port);
      }
    }
  } while (unfinished);
  for (i = 0; i < count; i++)
    nat_reverse_stop_ (&args[i]);
  if (client->nat->reversing == NAT_REVERSE_STATE_START)
    client->nat->reversing = NAT_REVERSE_STATE_READY;
  return done;
}

int nat_reverse_exec (simclient client, int cmd) {
  int err = SIM_NAT_TRAVERSE_ATTEMPT;
  if (client->nat)
    if (client->nat->state == NAT_TRAVERSE_STATE_INIT ||
        (client->nat->state == NAT_TRAVERSE_STATE_IDLE && (cmd == NAT_CMD_REVERSE || cmd == NAT_CMD_REVERSING)))
      return nat_put (client, cmd, NAT_CMD_NONE);
  LOG_WARN_ ("%s $%d ignored state %s%s\n", nat_command_names[cmd] + 1, client->sock->fd,
             NAT_TRAVERSE_LOOKUP_NAME (client->nat), nat_get_status (client));
  return err;
}

int nat_reverse_attempt (simclient client) {
  if (! client->nat || client->nat->reversing == NAT_REVERSE_STATE_READY)
    return nat_reverse_exec (client, NAT_CMD_REVERSE);
  LOG_WARN_ ("%s $%d ignored state %s%s\n", nat_command_names[NAT_CMD_REVERSE] + 1, client->sock->fd,
             NAT_REVERSE_LOOKUP_NAME (client->nat), nat_get_status (client));
  return SIM_NAT_REVERSE_RETRY;
}

static void nat_reverse_close (simclient client) {
  nat_socket_close (client->nat->reversed, client->contact, false), client->nat->reversed = NULL;
  nat_socket_close (client->nat->traversed, client->contact, true), client->nat->traversed = NULL;
  client->nat->status = NAT_STATUS_NONE;
  client->nat->reversing = NAT_REVERSE_STATE_READY;
}

void nat_reverse_send_udp (simclient client) {
  struct nat_reverse_arg args[4 * SIM_ARRAY_SIZE (client->param.ip) + 2 * SIM_ARRAY_SIZE (client->param.localip)];
  unsigned ip;
  int port, flags = nat_get_connected (client), count = nat_reverse_init (args, client, NULL), i;
  if (flags & (CLIENT_FLAG_ACCEPTED | CLIENT_FLAG_CONNECTED))
    if (socket_get_peer (client->sock, &ip, &port) == SIM_OK) {
      if (flags & CLIENT_FLAG_ACCEPTED)
        port = client->param.port[1];
      count += nat_reverse_add (args, count, ip, port);
    }
  for (i = 0; i < count; i++)
    audio_send_udp (client, table_new (2), SIM_CMD_UDP_REQUEST, args[i].ip, args[i].port);
}

int nat_traverse_succeed_ (simclient client, simbool eof) {
  struct nat *nat = client->nat;
  simsocket oldsock = client->sock;
  int fd = oldsock->fd, err = SIM_OK;
  if (nat && nat->traversed && (eof || ! socket_check_client (oldsock))) {
    if ((err = socket_lock_ (oldsock, -1, fd)) == SIM_OK) {
      if (eof || (err = client_send_ (client, nat_new_cmd_ack (client->contact)), nat->traversed)) {
        simsocket sock = nat->traversed;
        nat->traversed = NULL;
        client->rcvtimeout = param_get_number ("nat.recv") - (client->flags & CLIENT_FLAG_ACCEPTED ? 2 : 0);
        if (oldsock->rcvtimeout > client->rcvtimeout)
          oldsock->rcvtimeout = client->rcvtimeout;
        if (err != SIM_OK)
          LOG_NOTE_ ("traverse $%d -> $%d (error %d)\n", fd, sock->fd, err);
        err = client_set_socket_ (sock->client = client, oldsock = sock);
        nat_cancel (client, true);
        LOG_ANY_ (err == SIM_OK ? SIM_LOG_INFO : SIM_LOG_WARN, "traverse $%d -> $%d (error %d)\n", fd, sock->fd, err);
        event_send_net_traverse (client, eof ? "-" : "+", err);
      } else
        LOG_WARN_ ("traverse $%d IGNORED (error %d)\n", fd, err);
      socket_unlock (oldsock, fd);
    }
  } else if (! eof) {
    LOG_WARN_ ("traverse $%d IGNORED state %s%s\n", fd, NAT_TRAVERSE_LOOKUP_NAME (nat), nat_get_status (client));
  } else
    err = SIM_NAT_TRAVERSE_CANCELLED;
  return err;
}

void nat_traverse_reset (simclient client, int error) {
  if (client) {
    int blacklist = param_get_number_min ("nat.blacklist", param_get_default_number ("nat.blacklist", 0));
    if (blacklist && client->nat && client->nat->traverse_time) {
      simunsigned elapsed = system_get_tick () - client->nat->traverse_time, timeout = client->sock->rcvtimeout * 2;
      if (elapsed < (unsigned) (blacklist + (int) timeout) * 1000) {
        LOG_WARN_ ("traverse $%d blacklisted (dead in %d seconds) error %d\n",
                   client->sock->fd, (int) (elapsed / 1000 - timeout), error);
        client->contact->msgs.flags |= CONTACT_MSG_NO_TRAVERSE;
      }
    }
  } else
    nat_delta_ok = NAT_MAX_DELTA;
}

static void nat_traverse_set_delta (simclient client, const char *name) {
  if ((nat_delta = param_get_number ("nat.delta")) >= NAT_MAX_DELTA)
    nat_delta = nat_delta_ok <= -NAT_MAX_DELTA ? NAT_MAX_DELTA : nat_delta_ok;
  LOG_INFO_ ("%s $%d delta %d\n", name, client->sock->fd, nat_delta);
}

static void nat_traverse_set_delta_ok (simclient client) {
  if (nat_delta_ok == NAT_MAX_DELTA) {
    nat_delta_ok = nat_delta;
    LOG_INFO_ ("traverse $%d delta = %d\n", client->sock->fd, nat_delta);
  }
}

static int nat_traverse_get_delta (simclient client, simbool peer) {
  static const int nat_phases[2][5] = { { 2, 0, 1, 1, 2 }, { 2, 2, 0, 1, 2 } };
  int phase = nat_phases[peer][client->nat->phase];
  int delta = ! phase ? param_get_number ("nat.zero") : phase == 1 ? client->nat->delta : nat_delta;
  LOG_XTRA_ ("%s $%d delta%d = %d\n", peer ? "peer" : "my", client->sock->fd, client->nat->phase, delta);
  return delta;
}

static int nat_traverse_connect_ (simclient client, int *error, simbool lock) {
  struct nat *nat = client->nat;
  simcustomer proxy = proxy_customer_acquire (nat->proxy = proxy_customer_new (PROXY_TYPE_TRAVERSER)), traverser = NULL;
  simsocket sock = proxy->sock;
  unsigned ip, localip = 0, ownip = 0, ownip2 = 0;
  int err, fd = client->sock->fd, port, ownport = 0, localport = 0, retry = 1000;
#ifndef _WIN32
  if (client_count (NULL, NULL, NULL) > socket_max_limit[SOCKET_MAX_CLIENTS]) {
    proxy_customer_release (nat->proxy, SIM_OK), nat->proxy = NULL;
    proxy_customer_release (proxy, *error = SIM_LIMIT_NO_CLIENTS);
    return SIM_OK;
  }
#endif
  proxy_get_proxy (&ip, &port, NULL);
  if ((err = socket_open (sock, SOCKET_NO_SESSION)) == SIM_OK)
    err = sim_socket_set_reuse (sock->fd);
  if (err == SIM_OK && socket_check_client (client->sock))
    err = socket_get_peer (client->sock, &ip, &port);
  if (nat_delta >= NAT_MAX_DELTA) {
    for (;;) {
      if (err == SIM_OK)
        err = socket_open (sock = ((traverser = proxy_customer_new (PROXY_TYPE_TRAVERSER))->sock), SOCKET_NO_SESSION);
      if (err == SIM_OK)
        *error = proxy_connect_ (traverser, sock, ip, port, &ownip, &ownport);
      if (err == SIM_OK && *error == SIM_OK)
        err = socket_get_addr (sock, &localip, &localport);
      if (err == SIM_OK && *error == SIM_OK && nat->proxy) {
        char ipstr[100];
        int tmp = nat->proxy->sock->fd;
        LOG_CODE_DEBUG_ (strcpy (ipstr, network_convert_ip (localip)));
        LOG_DEBUG_ ("own ip $%d:$%d = %s:%d -> %s:%d\n", fd, tmp,
                    ipstr, localport, network_convert_ip (ownip), ownport);
        if (++localport >= 0x10000 || (err = socket_listen_port (tmp, -1, 0, localport, NULL)) != SIM_OK) {
          proxy_customer_release (nat->proxy, SIM_OK);
          proxy_customer_release (nat->proxy, err == SIM_OK ? SIM_SERVER_PORT : err);
          nat->proxy = proxy_customer_acquire (proxy = proxy_customer_new (PROXY_TYPE_TRAVERSER));
          if ((err = socket_open (proxy->sock, SOCKET_NO_SESSION)) == SIM_OK)
            err = sim_socket_set_reuse (proxy->sock->fd);
          if (retry--) {
            proxy_customer_release (traverser, err == SIM_OK ? SIM_SERVER_PORT : err);
            continue;
          }
          LOG_ERROR_ ("port %d unavailable\n", localport);
          localport = 0;
        }
      }
      break;
    }
  } else if (! lock)
    sock = NULL;
  if (err == SIM_OK && *error == SIM_OK && nat->proxy)
    *error = proxy_connect_ (nat->proxy, sock, ip, port, &ownip2, &nat->ownport);
  if (err == SIM_OK && *error == SIM_OK && nat->proxy && traverser) {
    if (localport) {
      sock = nat->proxy->sock;
      if (lock)
        socket_get_lock (sock);
      LOG_INFO_ ("detected $%d:$%d delta %d\n", fd, sock->fd, nat->ownport - ownport);
      if (abs (nat_delta = nat->ownport - ownport) >= NAT_MAX_DELTA)
        nat_delta = 1;
      if (localip == ownip || localip == ownip2) {
        LOG_NOTE_ ("detected $%d:$%d delta invalid ip %s\n", fd, sock->fd, network_convert_ip (ownip2));
        nat_delta_ok = -NAT_MAX_DELTA;
      }
    } else {
      nat_delta = 1;
      nat_delta_ok = -NAT_MAX_DELTA;
    }
  }
  proxy_customer_release (traverser, SIM_PROXY_NAT_CLOSE);
  proxy_customer_release (proxy, SIM_OK);
  return err;
}

static void nat_traverse_close (simclient client, int state) {
  nat_set_state (client, state);
  socket_close (&client->nat->sock, NULL);
  proxy_customer_release (client->nat->proxy, SIM_PROXY_NAT_CLOSE), client->nat->proxy = NULL;
  client->nat->ownport = 0;
}

static simbool nat_traverse_test_end (simclient client, const char *chr, int error) {
  nat_set_state (client, NAT_TRAVERSE_STATE_IDLE);
  event_send_net_traverse (client, chr, error);
  if (client->nat->phase)
    return false;
  nat_traverse_close (client, NAT_TRAVERSE_STATE_INIT);
  nat_reverse_close (client);
  return true;
}

int nat_traverse_attempt (simclient client) {
  if (client->nat && client->nat->flags & NAT_FLAG_TRAVERSE) {
    client->nat->starttick = system_get_tick ();
    if (client->nat->state == NAT_TRAVERSE_STATE_IDLE || client->nat->state == NAT_TRAVERSE_STATE_INIT) {
      nat_traverse_set_delta (client, "attempt");
      return nat_put (client, NAT_CMD_START, NAT_CMD_NONE);
    }
    if (client->nat->state != NAT_TRAVERSE_STATE_QUIT) {
      LOG_NOTE_ ("traverse $%d IGNORED state %s%s\n", client->sock->fd,
                 NAT_TRAVERSE_LOOKUP_NAME (client->nat), nat_get_status (client));
      return SIM_NAT_TRAVERSE_ATTEMPT;
    }
  }
  return SIM_NAT_TRAVERSE_QUIT;
}

int nat_traverse_exec (simclient client, const simtype table, int cmd) {
  int err = SIM_OK, type = cmd, req = cmd == NAT_CMD_NONE ? (type = NAT_CMD_REPLY) : NAT_CMD_REQUEST;
  struct nat *nat = client->nat;
  int mytimeout = nat && nat->flags & NAT_FLAG_TRAVERSE ? param_get_number ("nat.traverse") : 0;
  simnumber timeout = table_get_number (table, SIM_CMD_TRAVERSE_TIME);
  if (! nat) {
    LOG_WARN_ ("command $%d:%s ignored (timeout = %lld)\n", client->sock->fd, nat_command_names[type], timeout);
  } else if (nat->state == NAT_TRAVERSE_STATE_RECV) {
    LOG_NOTE_ ("command $%d:%s ignored (timeout = %lld)\n", client->sock->fd, nat_command_names[type], timeout);
  } else if (nat->state != NAT_TRAVERSE_STATE_QUIT && mytimeout && timeout > SIM_CMD_TRAVERSE_TIME_REJECT) {
    simnumber delay = table_get_number (table, SIM_CMD_TRAVERSE_DELAY);
    simnumber port = table_get_number (table, SIM_CMD_TRAVERSE_PORT);
    if (port > 0 && port < 0x10000 && nat->flags & NAT_FLAG_TRAVERSE) {
      simtype nextdelta = table_get (table, SIM_CMD_TRAVERSE_DELTA);
      if (timeout < mytimeout)
        timeout = mytimeout;
      mytimeout = param_get_max ("nat.traverse", mytimeout);
      if ((nat->timeout = timeout > mytimeout ? mytimeout : (int) timeout) != timeout)
        LOG_NOTE_ ("adjust $%d timeout from %lld to %d\n", client->sock->fd, timeout, nat->timeout);
      nat->port = (int) port;
      mytimeout = param_get_max ("nat.delay", 0);
      if ((nat->delay = delay < 0 ? 0 : delay > mytimeout ? mytimeout : (int) delay) != delay)
        LOG_NOTE_ ("adjust $%d delay from %lld to %d\n", client->sock->fd, delay, nat->delay);
      if (nextdelta.typ != SIMNIL) {
        int delta = 1 - NAT_MAX_DELTA;
        if (nextdelta.num > -NAT_MAX_DELTA)
          delta = nextdelta.num >= NAT_MAX_DELTA ? NAT_MAX_DELTA - 1 : (int) nextdelta.num;
        if (delta != nextdelta.num)
          LOG_NOTE_ ("adjust $%d delta from %lld to %d\n", client->sock->fd, nextdelta.num, delta);
        if (cmd != NAT_CMD_INVERSE) {
          if ((nat->deltathis[1] >= NAT_MAX_DELTA && req == NAT_CMD_REPLY) || cmd == NAT_CMD_START)
            nat->delta = delta;
          nat->deltathis[1] = delta;
        } else
          nat->deltanext[1] = delta;
      }
      if (req != NAT_CMD_REPLY)
        nat->rcvnonce = table_get_number (table, SIM_CMD_TRAVERSE_RANDOM);
      if (cmd == NAT_CMD_START)
        nat_traverse_set_delta (client, "command");
      nat_put (client, req, cmd);
    } else
      LOG_WARN_ ("command $%d:%s bad port %lld\n", client->sock->fd, nat_command_names[type], port);
  } else {
    LOG_NOTE_ ("command $%d:%s nak (timeout = %lld state %s)\n", client->sock->fd,
               nat_command_names[type], timeout, NAT_TRAVERSE_LOOKUP_NAME (nat));
    if (nat->state == NAT_TRAVERSE_STATE_QUIT || ! mytimeout || timeout <= SIM_CMD_TRAVERSE_TIME_ABORT) {
      nat->flags &= ~NAT_FLAG_TRAVERSE;
    } else if (req != NAT_CMD_REPLY || nat->state != NAT_TRAVERSE_STATE_SENT) {
      LOG_WARN_ ("command $%d:%s nak IGNORED (state = %s)\n", client->sock->fd,
                 nat_command_names[type], NAT_TRAVERSE_LOOKUP_NAME (nat));
    } else if (timeout <= SIM_CMD_TRAVERSE_TIME_REJECT) {
      int retry = param_get_number ("nat.retry") * 1000;
      nat_traverse_close (client, NAT_TRAVERSE_STATE_WAIT);
      nat->phasetick = system_get_tick () + retry;
      LOG_DEBUG_ ("command $%d PHASE %d:%d (in %d ms)\n", client->sock->fd, nat->phase, nat->reconnect, retry);
    } else
      LOG_WARN_ ("command $%d:%s nak IGNORED (timeout = %lld)\n", client->sock->fd, nat_command_names[type], timeout);
  }
  if (! nat || ! (nat->flags & NAT_FLAG_TRAVERSE))
    if (req == NAT_CMD_REPLY ? nat && (nat->state == NAT_TRAVERSE_STATE_SENT || nat->state == NAT_TRAVERSE_STATE_RECV) :
                               (err = client_send (client, nat_new_cmd_nak (SIM_CMD_TRAVERSE_TIME_ABORT))) == SIM_OK)
      if (nat && nat_traverse_test_end (client, req == NAT_CMD_REPLY ? "-" : "+", SIM_NAT_TRAVERSE_QUIT))
        err = client_send_cmd (client, SIM_CMD_REVERSE, NULL, nil (), NULL, nil ());
  return err;
}

static simbool nat_traverse_handshake_ (simclient client, simsocket sock, simnumber nonce) {
  struct nat *nat = client->nat;
  simsocket newsock = sim_new (sizeof (*newsock)), oldsock = client->sock;
  simbool server = ! socket_check_client (oldsock);
  int err, i, fd = oldsock->fd, ack = param_get_number ("nat.ack");
  simnumber encryptseq = 0, decryptseq = 0;
  simtype table = nil (), proto;
  if (param_get_number ("nat.test") & 1) {
    sim_free (newsock, sizeof (*newsock));
    return false;
  }
  if ((err = socket_create (socket_new (newsock), sock->fd)) == SIM_OK) {
    newsock->rcvtimeout = 1;
    memcpy (&newsock->crypt, &oldsock->crypt, sizeof (newsock->crypt));
    newsock->crypt.decryptseq = (newsock->crypt.decryptseq & 0x4000000000000000LL) - 1;
    newsock->crypt.encryptseq = (newsock->crypt.encryptseq & 0x4000000000000000LL) - 1;
    table = client_new_cmd (SIM_CMD_TRAVERSED, SIM_CMD_TRAVERSED_RANDOM, number_new (nonce),
                            SIM_CMD_TRAVERSED_ACK, number_new (ack));
    encryptseq = oldsock->crypt.encryptseq + 1;
    table_add_number (table, SIM_CMD_TRAVERSED_SEQUENCE, encryptseq & 0x3FFFFFFFFFFFFFFFLL);
    err = socket_send_table_ (newsock, table, SOCKET_SEND_TCP, NULL);
    table_free (table), table = nil ();
  }
  proto = table_new_const (4, SIM_NEW_STRING (SIM_CMD), SIM_NEW_NUMBER (SIM_CMD_TRAVERSED_RANDOM),
                           SIM_NEW_NUMBER (SIM_CMD_TRAVERSED_SEQUENCE), SIM_NEW_NUMBER (SIM_CMD_TRAVERSED_ACK), NULL);
  if (err == SIM_OK)
    err = socket_recv_table_ (newsock, proto, nil (), &table, NULL);
  if (err == SIM_OK) {
    newsock->flags |= SOCKET_FLAG_HANDSHAKE | SOCKET_FLAG_CLIENT;
    if (string_check_diff (table_get_string (table, SIM_CMD), SIM_CMD_TRAVERSED)) {
      err = SIM_CRYPT_BAD_TABLE;
    } else if (table_get_number (table, SIM_CMD_TRAVERSED_RANDOM) != nonce) {
      LOG_DEBUG_ ("traverse $%d:$%d #%lld (wanted #%lld)\n",
                  fd, sock->fd, table_get_number (table, SIM_CMD_TRAVERSED_RANDOM), nonce);
      err = SIM_CRYPT_BAD_PACKET;
    } else {
      if (server) {
        simnumber max = table_get_number (table, SIM_CMD_TRAVERSED_ACK);
        ack = param_get_max ("nat.ack", 1);
        if (max <= 0) {
          ack = 1;
        } else if (max < ack)
          ack = (int) max;
      }
      decryptseq = table_get_number (table, SIM_CMD_TRAVERSED_SEQUENCE) | (server ? 0x4000000000000000LL : 0);
    }
  }
  table_free (table);
  for (i = param_get_number ("nat.ping"); i && err == SIM_OK; i--) {
    int syn = param_get_number ("nat.syn");
    err = client_send_ (client, nat_new_cmd_ack (client->contact));
    if (syn > 0)
      pth_usleep_ (syn * 1000);
  }
  if (err == SIM_OK) {
    if (nat->proxy)
      socket_destroy (nat->proxy->sock, NULL, SOCKET_NO_UNLOCK);
    newsock->crypt.decryptseq = decryptseq - 2;
    newsock->crypt.encryptseq = encryptseq - 2;
    if ((err = socket_lock_ (oldsock = client->sock, -1, fd)) == SIM_OK) {
      for (i = 0; i < ack + server && err == SIM_OK; i++) {
        if (! server || i) {
          table_add_pointer (table = table_new (1), SIM_CMD, SIM_CMD_TRAVERSED);
          err = socket_send_table_ (newsock, table, SOCKET_SEND_TCP, NULL);
          table_free (table);
        }
        if (err == SIM_OK && i < ack) {
          if ((err = socket_recv_table_ (newsock, proto, nil (), &table, NULL)) == SIM_OK)
            if (string_check_diff (table_get_string (table, SIM_CMD), SIM_CMD_TRAVERSED))
              err = SIM_CRYPT_BAD_TABLE;
          table_free (table);
        }
      }
      LOG_ANY_ (err == SIM_OK ? SIM_LOG_DEBUG : SIM_LOG_WARN,
                "traverse $%d:$%d switch (count %d error %d)\n", fd, sock->fd, ack, err);
      if (err == SIM_OK) {
        nat_socket_close (nat->traversed, client->contact, true);
        nat->traversed = newsock;
        SOCKET_ADD_STAT (newsock, sock);
        sock->fd = INVALID_SOCKET;
        if (! server)
          client_send_cmd_ (client, SIM_CMD_TRAVERSED, NULL, nil (), NULL, nil ());
      }
      socket_unlock (oldsock, fd);
    }
  } else
    LOG_WARN_ ("traverse $%d:$%d error %d\n", fd, sock->fd, err);
  table_free (proto);
  if (event_test_error_crypto (client->contact, client->contact->addr, err) == SIM_OK)
    return true;
  SOCKET_ADD_STAT (sock, newsock);
  newsock->fd = INVALID_SOCKET;
  nat_socket_close (newsock, NULL, true);
  return false;
}

static simbool nat_traverse_loop_ (simclient client, simnumber nonce) {
  struct nat *nat = client->nat;
  unsigned localip, ip, remoteip = client->param.ip[0];
  simbool done = false, logging;
  int localport, port, probe = nat->delay, retries = 0, ret, delta = nat->deltathis[! nat->phase];
  simunsigned tick = system_get_tick (), syn = tick + probe, t;
  simsocket sock = nat->proxy->sock;
  int err = socket_get_addr (sock, &localip, &localport), count = abs (delta) + 1, fd = client->sock->fd;
  struct _socket in, *out = sim_new (sizeof (*out) * count);
  nat->flags |= NAT_FLAG_TRAVERSING;
  nat_set_state (client, NAT_TRAVERSE_STATE_RECV);
  LOG_NOTE_ ("traverse $%d:$%d (timeout=%d delay=%d delta=%d) #%lld\n", fd, sock->fd,
             nat->timeout, probe, delta, nonce);
  for (; count--; socket_new (&out[count])) {}
  if (err == SIM_OK && ! probe)
    goto start;
  while (err == SIM_OK) {
    simnumber elapsed = system_get_tick () - tick;
#if ! HAVE_LIBPTH && ! defined(_WIN32)
    struct pollfd fds[NAT_MAX_DELTA + 2];
    fds[0].fd = nat->sock.fd;
    fds[0].events = POLLIN;
#else
    int maxsock = nat->sock.fd;
    struct timeval tv;
    fd_set rset, wset;
    tv.tv_sec = probe / 1000, tv.tv_usec = (probe % 1000) * 1000;
    FD_ZERO (&rset);
    FD_ZERO (&wset);
    FD_SET (maxsock, &rset);
#endif
    err = SIM_NAT_TRAVERSE_TIMEOUT;
    if (elapsed >= nat->timeout * 1000)
      break;
    LOG_DEBUG_ ("traverse $%d attempt %d (%d/%d ms)\n", fd, retries, probe, nat->timeout * 1000 - (int) elapsed);
    err = SIM_SOCKET_CANCELLED;
    if (client->sock->err != SIM_OK || (nat->proxy && sock->err != SIM_OK))
      break;
    if (nat->state != NAT_TRAVERSE_STATE_RECV) {
      err = SIM_NAT_TRAVERSE_CANCELLED;
      break;
    }
    logging = LOG_PROTECT_ (SIM_MODULE, SIM_LOG_XTRA);
    LOG_XTRA_ ("select $%d:$%d", fd, nat->sock.fd);
    for (count = abs (delta) + 1; count--;) {
#if ! HAVE_LIBPTH && ! defined(_WIN32)
      fds[count + 1].fd = out[count].udport ? INVALID_SOCKET : out[count].fd;
      fds[count + 1].events = POLLOUT;
      LOG_XTRA_ (" $%d", fds[count + 1].fd);
#else
      if (out[count].fd != INVALID_SOCKET && ! out[count].udport) {
        if (out[count].fd > maxsock)
          maxsock = out[count].fd;
        FD_SET (out[count].fd, &wset);
        LOG_XTRA_ (" $%d", out[count].fd);
      }
#endif
    }
    LOG_XTRA_ (" (%d ms)\n", probe);
    if (logging)
      LOG_UNPROTECT_ ();
#if ! HAVE_LIBPTH && ! defined(_WIN32)
    ret = pth_poll_ (fds, abs (delta) + 2, probe);
#else
    ret = pth_select_ (maxsock + 1, &rset, &wset, NULL, &tv);
#endif
    if (client->sock->err != SIM_OK || (nat->proxy && sock->err != SIM_OK))
      break;
    err = SIM_NAT_TRAVERSE_CANCELLED;
    if (nat->state != NAT_TRAVERSE_STATE_RECV)
      break;
    if (ret > 0) {
#if ! HAVE_LIBPTH && ! defined(_WIN32)
      if (fds[0].revents & (POLLIN | POLLHUP | POLLERR)) {
#else
      if (FD_ISSET (nat->sock.fd, &rset)) {
#endif
        err = socket_accept (&nat->sock, &in, &ip, &port);
        SOCKET_INIT_STAT (&in);
        in.created = 0;
        if (err == SIM_OK) {
          for (count = SIM_ARRAY_SIZE (client->param.ip); count--;)
            if (ip == client->param.ip[count] && ip)
              break;
          if (count < 0 && (ip != client->param.localip[0] || ! ip)) {
            char ipstr[100];
            LOG_CODE_WARN_ (strcpy (ipstr, network_convert_ip (ip)));
            LOG_WARN_ ("accept $%d:$%d from %s:%d (wanted %s:%d)\n", fd, nat->sock.fd,
                       ipstr, port, network_convert_ip (remoteip), nat->port);
          } else if ((done = nat_traverse_handshake_ (client, &in, nonce)) != false)
            nat->status = NAT_STATUS_INCOMING;
        } else if (err != SIM_SOCKET_ACCEPT)
          pth_sleep_ (1);
        socket_destroy (&in, NULL, SOCKET_NO_LINGER);
        if (done)
          break;
      }
      port = nat->port + delta;
      for (count = abs (delta) + 1; count--; port += delta < 0 ? 1 : -1)
#if ! HAVE_LIBPTH && ! defined(_WIN32)
        if (fds[count + 1].revents & (POLLOUT | POLLERR)) {
#else
        if (out[count].fd != INVALID_SOCKET && ! out[count].udport && FD_ISSET (out[count].fd, &wset)) {
#endif
          err = nat_socket_connect (&out[count], remoteip, port, "reconnect");
          if (err == SIM_OK || ! nat_check_connecting (err)) {
            if (err == SIM_OK || err == EISCONN) {
              if ((done = nat_traverse_handshake_ (client, &out[count], nonce)) != false) {
                nat->status = count + NAT_STATUS_OUTGOING;
                if (nat->status >= SIM_ARRAY_SIZE (nat_traverse_status_names))
                  nat->status = SIM_ARRAY_SIZE (nat_traverse_status_names) - 1;
                break;
              }
              out[count].udport = -1;
            } else {
              LOG_WARN_ ("reconnect $%d:$%d error %d\n", fd, out[count].fd, err);
              socket_destroy (&out[count], NULL, SOCKET_NO_LINGER);
            }
          }
        }
      err = SIM_OK;
      if (done)
        break;
    } else if (ret < 0) {
      err = socket_get_errno ();
#ifndef _WIN32
      if (err != EINTR)
#endif
      {
        LOG_ERROR_ ("select $%d:$%d error %d\n", fd, nat->sock.fd, err);
        break;
      }
      err = SIM_OK;
    } else {
      unsigned resyn = param_get_number ("nat.resyn");
      if (resyn && (t = system_get_tick ()) - syn >= resyn) {
        syn = t;
        for (count = abs (delta) + 1; count--; socket_destroy (&out[count], NULL, SOCKET_NO_LINGER)) {}
      }
      err = SIM_OK;
    start:
      port = nat->port + delta;
      for (count = abs (delta) + 1; count-- && err == SIM_OK; port += delta < 0 ? 1 : -1)
        if (out[count].fd == INVALID_SOCKET && port > 0 && port < 0x10000) {
          if ((ret = nat_socket_open (&out[count], -1, localip, localport)) != SIM_OK) {
            socket_close (&nat->sock, NULL); /* EADDRINUSE on linux without SO_REUSEPORT? try to reopen */
            ret = nat_socket_open (&out[count], -1, localip, localport);
            err = nat_socket_open (&nat->sock, param_get_number ("socket.listen"), 0, localport);
          }
          if (ret == SIM_OK) {
            if (port != nat->port + delta && (ret = param_get_number ("nat.syn")) > 0)
              pth_usleep_ (ret * 1000);
            err = nat_socket_connect (&out[count], remoteip, port, "connect");
            if (err != SIM_OK && ! nat_check_connecting (err)) {
              LOG_WARN_ ("connect $%d:$%d error %d\n", fd, out[count].fd, err);
              socket_destroy (&out[count], NULL, SOCKET_NO_LINGER);
            }
            err = SIM_OK;
          }
        } else if (out[count].fd == INVALID_SOCKET)
          LOG_WARN_ ("traverse $%d ignored port %d\n", fd, port);
      retries++;
    }
    if (probe) {
      probe = param_get_number_min ("nat.probe", param_get_default_number ("nat.probe", 0));
    } else
      probe = nat->timeout * 1000;
  }
  if (err == SIM_OK)
    LOG_DEBUG_ ("traversed $%d -> $%d in %d steps\n", fd, NAT_GET_FD (nat->traversed), retries);
  for (count = abs (delta) + 1; count--; socket_close (&out[count], NULL)) {}
  LOG_NOTE_ ("traverse $%d error %d (%lld ms)\n", fd, err, system_get_tick () - tick);
  nat_traverse_close (client, nat->state == NAT_TRAVERSE_STATE_RECV ? NAT_TRAVERSE_STATE_WAIT : nat->state);
  sim_free (out, sizeof (*out) * (abs (delta) + 1));
  nat->flags &= ~NAT_FLAG_TRAVERSING;
  return done;
}

static void *thread_traverse_ (void *arg) {
  simclient client = arg;
  int fd = client->sock->fd, done = 0;
  struct nat *nat = client->nat;
  LOG_INFO_ ("traverse $%d init\n", fd);
  while (! done && pth_queue_wait_ (nat->event, fd) > 0) {
    struct nat_queue *item = (struct nat_queue *) pth_queue_get (nat->queue);
    simunsigned tick = item->tick, t;
    int err = SIM_OK, err2 = SIM_OK, cmd = item->cmd, req = item->req, port;
    int timeout = nat->flags & NAT_FLAG_TRAVERSE ? param_get_number ("nat.traverse") : 0;
    sim_free (item, sizeof (*item));
    if (req != NAT_CMD_REQUEST)
      LOG_INFO_ ("traverse $%d%s%s '%s'\n", fd, nat_command_names[req], nat_command_names[cmd],
                 client->contact->nick.str);
    if (nat->state == NAT_TRAVERSE_STATE_QUIT) {
      cmd = NAT_CMD_QUIT;
    } else if ((cmd == NAT_CMD_START && req == NAT_CMD_NONE) || (cmd == NAT_CMD_REQUEST && req == NAT_CMD_START)) {
      event_send_net_traverse (client, cmd == NAT_CMD_START ? "+" : "-", SIM_NAT_TRAVERSE_ATTEMPT);
    } else if (cmd == NAT_CMD_RESTART && ! nat->phase)
      nat->deltanext[0] = nat->deltanext[1];
    switch (cmd) {
      case NAT_CMD_REVERSE:
        nat_traverse_close (client, NAT_TRAVERSE_STATE_INIT);
        nat_reverse_close (client);
      case NAT_CMD_REVERSING:
        nat->flags &= ~NAT_FLAG_PROXY;
        done = param_get_number ("nat.reverse") ? nat_reverse_loop_ (client) : 0;
        LOG_DEBUG_ ("%s $%d %s%s done = %d (%lld ms)\n", nat_command_names[cmd] + 1, fd,
                    NAT_REVERSE_LOOKUP_NAME (nat), nat_get_status (client), done, system_get_tick () - tick);
        if (done >= 0) {
          unsigned ip;
          if (nat->status != NAT_STATUS_NONE ||
              (nat->reversing == NAT_REVERSE_STATE_CONNECTED || nat->reversing == NAT_REVERSE_STATE_ACCEPTED ||
               nat->reversing == NAT_REVERSE_STATE_CONNECT)) {
            event_send_net_traverse (client, "+", SIM_NAT_REVERSE_FINISHED);
            nat_set_state (client, NAT_TRAVERSE_STATE_IDLE);
            err = client_send_cmd_ (client, SIM_CMD_REVERSEND, NULL, nil (), NULL, nil ());
            if (nat->flags & NAT_FLAG_PROXY && nat->reversed && socket_get_peer (nat->reversed, &ip, &port) == SIM_OK)
              proxy_probe (client->contact->addr, ip, port, PROXY_PROBE_CONTACT);
          } else if (cmd == NAT_CMD_REVERSE) {
            err = client_send_cmd_ (client, SIM_CMD_REVERSING, NULL, nil (), NULL, nil ());
          } else if ((err = client_send_cmd_ (client, SIM_CMD_REVERSEND, NULL, nil (), NULL, nil ())) == SIM_OK) {
            event_send_net_traverse (client, "-", SIM_NAT_REVERSE_FINISHED);
            nat_set_state (client, NAT_TRAVERSE_STATE_IDLE);
            if (! (nat->flags & NAT_FLAG_TRAVERSED)) {
              nat->flags |= NAT_FLAG_TRAVERSED;
              if (! timeout) {
                if ((err = client_send_ (client, nat_new_cmd_nak (SIM_CMD_TRAVERSE_TIME_ABORT))) == SIM_OK)
                  event_send_net_traverse (client, "", SIM_NAT_TRAVERSE_QUIT);
              } else if ((done = param_get_number ("nat.init")) != 0) {
                if (done > 0) {
                  nat_traverse_attempt (client);
                } else
                  err = client_send_cmd_ (client, SIM_CMD_TRAVERSE_INIT, NULL, nil (), NULL, nil ());
              }
            }
          }
          if ((done = -(err != SIM_OK)) < 0)
            LOG_WARN_ ("reverse $%d error %d\n", fd, err);
        }
        nat->flags &= ~NAT_FLAG_PROXY;
        continue;
      case NAT_CMD_REVERSEND:
        event_send_net_traverse (client, "", SIM_NAT_REVERSE_FINISHED);
        nat_set_state (client, NAT_TRAVERSE_STATE_IDLE);
        continue;
      case NAT_CMD_START:
        nat->deltathis[1] = NAT_MAX_DELTA;
        nat->reconnect = nat->deltanext[0] = 0; /* peer's nat.zero */
      case NAT_CMD_RESTART:
      case NAT_CMD_INVERSE:
        if (nat->state != NAT_TRAVERSE_STATE_WAIT && nat->state != NAT_TRAVERSE_STATE_IDLE)
          break; /* ignore attempt if already traversing */
        if (cmd != NAT_CMD_INVERSE)
          nat->phase = 0;
      case NAT_CMD_REQUEST:
        if (nat->state == NAT_TRAVERSE_STATE_SENT) {
          if (! socket_check_client (client->sock))
            break; /* ignore request only on server if already traversing */
        } else if (nat->state != NAT_TRAVERSE_STATE_WAIT && nat->state != NAT_TRAVERSE_STATE_IDLE)
          break;
        if (cmd == NAT_CMD_REQUEST) {
          if (req == NAT_CMD_START) {
            nat->reconnect = 0;
            nat->phase = 1;
          } else if (req != NAT_CMD_RESTART)
            nat->phase = 0;
        }
        if (timeout) {
          int localport, natlock = param_get_number ("nat.lock"), delta = nat->deltanext[nat->phase ? 2 : 0];
          if ((req == NAT_CMD_START || req == NAT_CMD_RESTART) && nat->phase)
            delta = nat_traverse_get_delta (client, true);
          if (! nat->proxy) {
            simbool lock = natlock > 0 || (natlock && delta);
            nat->flags |= NAT_FLAG_WAITING;
            if ((lock || (natlock && nat_delta >= NAT_MAX_DELTA)) && socket_get_lock (NULL) != SOCKET_NOT_LOCKED) {
              if (cmd != NAT_CMD_REQUEST) {
                pth_usleep_ (100000);
                err = nat_put (client, cmd, NAT_CMD_REQUEST);
              } else
                err = client_send_ (client, nat_new_cmd_nak (SIM_CMD_TRAVERSE_TIME_REJECT));
              if (err == SIM_OK)
                continue;
            }
            if (natlock < 0)
              LOG_NOTE_ ("lock $%d delta %d\n", fd, delta);
            if (err == SIM_OK)
              err = nat_traverse_connect_ (client, &err2, lock);
            t = system_get_tick ();
            if (err == SIM_OK)
              if ((err2 != SIM_OK || (cmd == NAT_CMD_REQUEST && (tick = t - tick) >= (unsigned) nat->timeout * 1000))) {
                LOG_WARN_ ("traverse $%d%s%s PHASE %d:%d (%s %d)\n", fd, nat_command_names[req], nat_command_names[cmd],
                           nat->phase, nat->reconnect + 1,
                           err2 != SIM_OK ? "error" : "TIMEOUT", err2 != SIM_OK ? err2 : (int) tick);
                if (nat->reconnect++ < param_get_number ("nat.reconnect")) {
                  if (cmd == NAT_CMD_REQUEST) {
                    err = client_send_ (client, nat_new_cmd_nak (SIM_CMD_TRAVERSE_TIME_REJECT));
                  } else
                    nat->phasetick = t + param_get_number ("nat.retry") * 1000;
                }
                if (err == SIM_OK && (nat->state == NAT_TRAVERSE_STATE_WAIT || nat->state == NAT_TRAVERSE_STATE_IDLE)) {
                  nat_traverse_close (client, nat->state);
                  if (nat->reconnect <= param_get_number ("nat.reconnect")) {
                    nat->flags |= NAT_FLAG_WAITING;
                  } else if (nat_traverse_test_end (client, "-", SIM_NAT_TRAVERSE_FAILED))
                    err = client_send_cmd_ (client, SIM_CMD_REVERSE, NULL, nil (), NULL, nil ());
                } /* else NAT_TRAVERSE_STATE_QUIT */
                if (err == SIM_OK)
                  continue;
              }
            if (nat->state == NAT_TRAVERSE_STATE_QUIT)
              break;
            if (err == SIM_OK && nat->proxy && (err = socket_get_addr (nat->proxy->sock, NULL, &localport)) == SIM_OK)
              err = nat_socket_open (&nat->sock, param_get_number ("socket.listen"), 0, localport);
          }
          if (err == SIM_OK && nat->proxy) {
            simnumber nonce = nat->rcvnonce;
            simtype table;
            if (cmd == NAT_CMD_REQUEST) {
              table = client_new_cmd (SIM_CMD_TRAVERSE_REPLY, NULL, nil (), NULL, nil ());
              if ((req == NAT_CMD_START || req == NAT_CMD_RESTART) && nat->phase) {
                nat->deltanext[2] = nat_traverse_get_delta (client, true);
                table_add_number (table, SIM_CMD_TRAVERSE_DELTA, nat->deltanext[2]);
              }
            } else {
              const char *cmdstr;
              nat->sndnonce = nonce = random_get_number (random_public, 0x3FFFFFFFFFFFFFFFLL);
              if (cmd == NAT_CMD_RESTART) {
                cmdstr = SIM_CMD_TRAVERSE_RESTART;
              } else if (cmd == NAT_CMD_START) {
                cmdstr = SIM_CMD_TRAVERSE_START;
              } else
                cmdstr = nat->phase <= NAT_TRAVERSE_PHASES ? SIM_CMD_TRAVERSE_INVERSE : SIM_CMD_TRAVERSE_INVERSED;
              table = client_new_cmd (cmdstr, SIM_CMD_TRAVERSE_DELAY, number_new (param_get_number ("nat.delay")),
                                      SIM_CMD_TRAVERSE_RANDOM, number_new (nonce));
              if (cmd == NAT_CMD_START && ! nat->phase) {
                table_add_number (table, SIM_CMD_TRAVERSE_DELTA, nat_traverse_get_delta (client, true));
              } else if (cmd == NAT_CMD_INVERSE && nat->phase && nat->phase <= NAT_TRAVERSE_PHASES)
                table_add_number (table, SIM_CMD_TRAVERSE_DELTA, nat_traverse_get_delta (client, false));
            }
            table_add_number (table, SIM_CMD_TRAVERSE_TIME, timeout);
            table_add_number (table, SIM_CMD_TRAVERSE_PORT, nat->ownport);
            nat_set_state (client, NAT_TRAVERSE_STATE_SENT);
            err = nat->ownport ? client_send_ (client, table) : (table_free (table), SIM_SOCKET_BAD_PORT);
            if (err == SIM_OK) {
              tick = system_get_tick ();
              if (cmd == NAT_CMD_REQUEST && nat->state == NAT_TRAVERSE_STATE_SENT) {
                if (nat->phase)
                  nat->deltathis[0] = nat_traverse_get_delta (client, false);
                if (nat_traverse_loop_ (client, nonce)) {
                  nat_traverse_set_delta_ok (client);
                } else if (req != NAT_CMD_INVERSED) {
                  nat->phasetick = tick + (param_get_max ("nat.traverse", 0) + 3) * 1000; /* + 2 seems enough but be safer */
                  LOG_DEBUG_ ("traverse $%d phase %d:%d (in %lld ms)\n", fd,
                              nat->phase, nat->reconnect, nat->phasetick - system_get_tick ());
                  if (nat->phase) {
                    int zero = param_get_number ("nat.zero");
                    nat->phase += nat->phase >= 2 && nat->delta == zero;
                    nat->phase += (nat->phase == 3 && (nat_delta == nat->delta || nat_delta == zero)) + 1;
                  }
                } else {
                  LOG_DEBUG_ ("traverse $%d phase %d:%d STOP\n", fd, nat->phase, nat->reconnect);
                  if (nat_traverse_test_end (client, "", SIM_NAT_TRAVERSE_FAILED))
                    err = client_send_cmd_ (client, SIM_CMD_REVERSE, NULL, nil (), NULL, nil ());
                }
              }
            }
          }
        } else if ((err = client_send_ (client, nat_new_cmd_nak (SIM_CMD_TRAVERSE_TIME_ABORT))) == SIM_OK)
          if (nat_traverse_test_end (client, "", SIM_NAT_TRAVERSE_QUIT))
            err = client_send_cmd_ (client, SIM_CMD_REVERSE, NULL, nil (), NULL, nil ());
        if (err != SIM_OK) {
          LOG_WARN_ ("traverse $%d error %d\n", fd, err);
          case NAT_CMD_QUIT:
            done = 1;
        }
        continue;
      case NAT_CMD_REPLY: /* ignore unless already traversing */
        if (timeout && nat->state == NAT_TRAVERSE_STATE_SENT && nat->proxy) {
          if (nat_traverse_loop_ (client, nat->sndnonce)) {
            nat_traverse_set_delta_ok (client);
          } else if (nat->phase > NAT_TRAVERSE_PHASES) {
            event_send_net_traverse (client, "", SIM_NAT_TRAVERSE_FAILED);
            nat_set_state (client, NAT_TRAVERSE_STATE_IDLE);
          }
          continue;
        }
    }
    LOG_WARN_ ("traverse $%d%s%s IGNORED state %s%s\n", fd,
               nat_command_names[req], nat_command_names[cmd], NAT_TRAVERSE_LOOKUP_NAME (nat), nat_get_status (client));
  }
  nat_traverse_close (client, NAT_TRAVERSE_STATE_QUIT);
  LOG_INFO_ ("traverse $%d exit\n", fd);
  if (nat->status == NAT_STATUS_NONE) {
    event_send_net_traverse (client, done < 0 ? "+" : "-", SIM_NAT_REVERSE_QUIT);
    client_send_ (client, nat_new_cmd_nak (SIM_CMD_TRAVERSE_TIME_ABORT));
  }
  return pth_thread_exit_ (true);
}

void nat_periodic (simclient client, simnumber tick) {
  if (client->nat) {
    simnumber period = param_get_number_min ("nat.periodic", NAT_MIN_PERIOD);
    if (client->nat->statetick && tick - client->nat->statetick >= param_get_max ("nat.traverse", 0) * 1000) {
      LOG_WARN_ ("traverse $%d timeout (%lld ms)\n", client->sock->fd, tick - client->nat->statetick);
      nat_traverse_close (client, NAT_TRAVERSE_STATE_IDLE);
      if (nat_traverse_test_end (client, "+", SIM_NAT_TRAVERSE_FAILED))
        client_send_cmd (client, SIM_CMD_REVERSE, NULL, nil (), NULL, nil ());
    }
    if (period && tick - client->nat->starttick >= period * 60000)
      nat_traverse_attempt (client);
    if (client->nat->phasetick && tick >= client->nat->phasetick)
      if (client->nat->state == NAT_TRAVERSE_STATE_WAIT || client->nat->state == NAT_TRAVERSE_STATE_IDLE) {
        client->nat->phasetick = 0;
        nat_put (client, client->nat->phase ? NAT_CMD_INVERSE : NAT_CMD_RESTART, NAT_CMD_NONE);
      }
  }
}

const char *nat_get_status (simclient client) {
  return nat_traverse_status_names[client->nat ? client->nat->status : NAT_STATUS_NONE];
}

int nat_get_connected (simclient client) {
  if (! SOCKET_CHECK_PROXY (client->sock)) {
    if (! client->nat || client->nat->status == NAT_STATUS_NONE)
      return client->flags & (CLIENT_FLAG_ACCEPTED | CLIENT_FLAG_CONNECTED);
    if (client->nat->status == NAT_STATUS_REVERSE)
      return CLIENT_FLAG_ACCEPTED;
    if (client->nat->status == NAT_STATUS_LOCAL || client->nat->status == NAT_STATUS_DIRECT)
      return CLIENT_FLAG_CONNECTED;
  }
  return 0; /* either relayed or traversed */
}

int nat_get_sockets (simclient client) {
#ifndef _WIN32
  struct nat *nat = client->nat;
  if (nat)
    return (nat->reversed != NULL || nat->reversing == NAT_REVERSE_STATE_START) + (nat->traversed != NULL) +
           ((nat->proxy || (nat->flags & NAT_FLAG_TRAVERSING)) ? NAT_MAX_DELTA + 2 : 0);
#endif
  return 0;
}

int nat_init_ (simclient client) {
  int err, fd = client->sock->fd;
  unsigned period = param_get_number_min ("nat.periodic", NAT_MIN_PERIOD) * 60;
  struct nat *nat = client->nat = sim_new (sizeof (*nat));
  memset (nat, 0, sizeof (*nat));
  if (client->param.ip[0] && ! (client->contact->msgs.flags & CONTACT_MSG_NO_TRAVERSE))
    if (param_get_number ("net.tor.port") <= 0 && param_get_number ("nat.traverse"))
      nat->flags = NAT_FLAG_TRAVERSE;
  nat_set_state (client, NAT_TRAVERSE_STATE_INIT);
  nat->delta = NAT_MAX_DELTA;
  nat->phase = NAT_TRAVERSE_PHASES + 1;
  nat->starttick = system_get_tick ();
  nat->status = NAT_STATUS_NONE;
  if (! socket_check_client (client->sock) || param_get_number ("nat.init")) {
    if (period >= 30 * 60) {
      nat->starttick -= (simunsigned) (sim_get_random (period * 2 / 3) + period / 6) * 1000;
    } else
      nat->starttick -= period * 500;
  }
  socket_new (&nat->sock);
  nat->reversing = NAT_REVERSE_STATE_READY;
  if ((err = pth_queue_new (&nat->queue, &nat->event, fd)) == SIM_OK)
    if ((err = pth_thread_spawn (thread_traverse_, client, &nat->tid, fd)) == SIM_OK)
      if (socket_check_client (client->sock))
        /*err =*/client_send_cmd_ (client, SIM_CMD_REVERSE, NULL, nil (), NULL, nil ());
  if (err != SIM_OK) {
    pth_queue_free (&nat->queue, &nat->event, sizeof (struct nat_queue));
    sim_free (nat, sizeof (*nat)), client->nat = NULL;
  }
  return err;
}

void nat_uninit_ (simclient client) {
  if (client->nat) {
    nat_cancel (client, true);
    pth_thread_join_ (&client->nat->tid, NULL, thread_traverse_, client->sock->fd);
    pth_queue_free (&client->nat->queue, &client->nat->event, sizeof (struct nat_queue));
    nat_socket_close (client->nat->reversed, client->contact, false);
    nat_socket_close (client->nat->traversed, client->contact, true);
    sim_free (client->nat, sizeof (*client->nat)), client->nat = NULL;
  }
}

void nat_log_traverser (const char *module, int level, simclient client) {
  if (! client) {
    log_any_ (module, level, "port delta = %d/%d\n", nat_delta, nat_delta_ok);
  } else if (client->nat) {
    struct nat *nat = client->nat;
    simnumber tick = system_get_tick ();
    log_any_ (module, level, "%c%d:$%d %s:%s%s", client->sock->err == SIM_OK ? '$' : '!',
              client->sock->fd, nat->proxy ? nat->proxy->sock->fd : 0,
              NAT_REVERSE_LOOKUP_NAME (nat), NAT_TRAVERSE_LOOKUP_NAME (nat), nat_get_status (client));
    if (nat->statetick)
      log_any_ (module, level, " (%lld ms)", tick - nat->statetick);
    log_any_ (module, level, " %s:%d+%d phase %d:%d", network_convert_ip (client->param.ip[0]),
              nat->port, nat->delta, nat->phase, nat->reconnect);
    if (nat->phasetick)
      log_any_ (module, level, " (in %lld ms)", nat->phasetick - tick);
    log_any_ (module, level, " %s", client->contact->nick.str);
    if (nat->state != NAT_TRAVERSE_STATE_QUIT)
      log_any_ (module, level, " (in %lld s)",
                (nat->starttick - tick) / 1000 + param_get_number_min ("nat.periodic", NAT_MIN_PERIOD) * 60);
    if (nat->flags & NAT_FLAG_WAITING)
      log_any_ (module, level, " WAITING");
    if (! (nat->flags & NAT_FLAG_TRAVERSE))
      log_any_ (module, level, " ABORT");
    if (! (nat->flags & NAT_FLAG_TRAVERSED))
      log_any_ (module, level, " INIT");
    if (nat->flags & NAT_FLAG_PROXY)
      log_any_ (module, level, " PROXY");
    if (nat->reversed)
      log_any_ (module, level, " REVERSED");
    if (nat->traversed)
      log_any_ (module, level, " TRAVERSED");
    log_any_ (module, level, "\n");
  }
}
