/**
    handle proxy connections and relay packets

    Copyright (c) 2020-2022 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 "sssl.h"
#include "socket.h"
#include "network.h"
#include "mainline.h"
#include "contact.h"
#include "param.h"
#include "proto.h"
#include "limit.h"
#include "proxy.h"
#include "proxies.h"
#include "nat.h"
#include "server.h"
#include "client.h"
#include "audio.h"
#include "api.h"

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

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

#define SIM_MODULE "proxy"

#define PROXY_PROTO_CLIENT (-1) /* proxy to client protocol */
#define PROXY_PROTO_PROXY 0     /* client to proxy protocol */
#define PROXY_PROTO_SERVER 1    /* proxy to customer or customer to proxy protocol */

unsigned proxy_localip[3]; /* list of unblacklistable ip addresses */

simcustomer proxy_customer_list;                 /* first customer in linked list */
static simtype proxy_proto_server;               /* proxy server protocol template */
static simtype proxy_proto_handshake;            /* proxy handshake protocol template */
static simtype proxy_proto_udp;                  /* proxy server UDP protocol template */
static simcustomer proxy_proxy_customer = NULL;  /* control connection to proxy */
static struct _customer proxy_stat_customers[2]; /* fake customers for storage of global statistics (indexed by PROXY_STAT_xxx) */
static simnumber proxy_connections[9];           /* number of incoming connections (indexed by PROXY_STATS_THIS or PROXY_STATS_NOW) */
static simnumber proxy_stats[13][4];             /* saved proxy statistics (first index PROXY_STATS_xxx, second index CONTACT_STAT_xxx) */
static simunsigned proxy_duration;               /* number of milliseconds server port had been open, not counting the current session */
static simbool proxy_initializing_flag = false;  /* true if initial proxy connect is taking place (ignore first connection) */
static const char *proxy_address = NULL;         /* proxy_proxy_customer is a preferred proxy if not NULL (contact address) */
static unsigned proxy_realip = 0;                /* ip address of current proxy as reported by it (zero if not reported) */
static int proxy_realport = 0;                   /* port number of current proxy as reported by it (zero if not reported) */
static unsigned proxy_ip = 0;                    /* own ip address as reported by proxy (zero if not reported) */
static simnumber proxy_tick = 0;                 /* system tick when connection to proxy was established */
static simnumber proxy_pong_value = 0;           /* proxy current ping time */
static simnumber proxy_latency = 0;              /* proxy last ping time */

simcustomer proxy_customer_new (int type) {
  simsocket sock = sim_new (sizeof (*sock));
  simcustomer customer = sim_new (sizeof (*customer));
  memset (customer, 0, sizeof (*customer));
  customer->next = proxy_customer_list;
  customer->sock = socket_new (sock);
  customer->count = 1;
  if ((customer->type = type) == PROXY_TYPE_NEW) {
    customer->flags = PROXY_FLAG_INCOMING;
    proxy_connections[PROXY_STATS_NOW]++;
  }
  customer->measured[2] = customer->measured[1] = customer->measured[0] = system_get_tick ();
  return proxy_customer_list = customer;
}

simcustomer proxy_customer_acquire (simcustomer customer) {
  if (customer) {
    if (customer->count > 0) {
      customer->count++;
      return customer;
    }
    LOG_ERROR_ ("acquire%d $%d\n", customer->count, customer->sock->fd);
  }
  return NULL;
}

void proxy_customer_remove (simcustomer customer) {
  if (sim_list_delete (&proxy_customer_list, customer)) {
    LOG_DEBUG_ ("release $%d #%d\n", customer->sock->fd, customer->number);
    sim_free (customer, sizeof (*customer));
  } else
    LOG_ERROR_ ("zombie customer $%d #%d\n", customer->sock->fd, customer->number);
}

static simbool proxy_customer_ignore (simcustomer customer) {
  if (! (customer->flags & PROXY_FLAG_INCOMING))
    return false;
  proxy_connections[PROXY_STATS_NOW]--;
  customer->flags &= ~PROXY_FLAG_INCOMING;
  return true;
}

static void proxy_customer_reset (simcustomer server, simcustomer client) {
  if (server && server->number == client->number) {
    LOG_DEBUG_ ("speed $%d reset $%d #%d\n", server->sock->fd, client->sock->fd, client->number);
    server->number = server->speed = 0;
    server->flags &= ~PROXY_FLAG_SPEED_TALK;
  }
}

void proxy_customer_announce (simcustomer server) {
  if (server) {
    server->announce = system_get_tick ();
    LOG_DEBUG_ ("reannounce '%s'\n", CONTACT_GET_NICK (server->sock->master->from.ptr));
  }
}

static int proxy_customer_check_local (simcustomer customer, unsigned *ip, int *port) {
  simcustomer tmp;
  unsigned ip0 = sim_network_parse_ip_default (param_get_pointer ("proxy.local"), &ip0);
  unsigned ip1 = sim_network_get_default ();
  int err = socket_get_peer (customer->sock, ip, port);
  for (tmp = proxy_customer_list; tmp && err == SIM_OK; tmp = tmp->next)
    if (tmp->type == PROXY_TYPE_LOCAL && tmp->realport == *port && *port)
      if (tmp->ip == *ip || ip0 == *ip || ip1 == *ip) {
        proxy_customer_acquire (customer->sock->local = tmp);
        *ip = tmp->realip;
        customer->sock->sndtimeout = param_get_number ("proxy.send");
        err = SIM_PROXY_LOCAL;
      }
  return err;
}

void proxy_customer_release (simcustomer customer, int error) {
  if (customer) {
    simsocket sock = customer->sock;
    if (! --customer->count) {
      simcontact contact = customer->contact;
      if (customer->type == PROXY_TYPE_SERVER) {
        sock->flags |= SOCKET_FLAG_HANDSHAKE;
        sock->client = SOCKET_NO_SESSION;
        SOCKET_ADD_STAT (&proxy_stat_customers[PROXY_STAT_TOTAL].sock->old, &sock->old);
        SOCKET_ADD_STAT (&proxy_stat_customers[PROXY_STAT_TOTAL].sock->old, sock);
        main_search_denounce (sock->master->from.ptr);
      } else if (customer->type == PROXY_TYPE_CLIENT) {
        simcustomer server = proxy_find_server (sock->master->to.ptr);
        proxy_customer_reset (server, customer);
        if (customer->flags & PROXY_FLAG_INCOMING) {
          if (! server)
            server = &proxy_stat_customers[PROXY_STAT_TOTAL];
          SOCKET_ADD_STAT (&server->sock->old, sock);
          sock->flags |= SOCKET_FLAG_HANDSHAKE;
          sock->client = SOCKET_NO_SESSION;
        } else
          SOCKET_INIT_STAT (sock);
        sock->created = 0;
      } else if (customer->flags & PROXY_FLAG_INCOMING) { /* PROXY_TYPE_NEW or PROXY_TYPE_ANONYMOUS */
        sock->flags |= SOCKET_FLAG_HANDSHAKE;
        sock->client = SOCKET_NO_SESSION;
        SOCKET_ADD_STAT (&proxy_stat_customers[PROXY_STAT_TOTAL].sock->old, sock);
        SOCKET_ADD_STAT (&proxy_stat_customers[PROXY_STAT_HANDSHAKES].sock->old, sock);
        sock->created = 0;
      } else if (customer->type == PROXY_TYPE_PROXY)
        contact = SOCKET_PROXY_SESSION;
      proxy_connections[PROXY_STATS_THIS] += proxy_customer_ignore (customer);
      proxy_customer_remove (customer);
      socket_close (sock, contact);
      sim_free (sock, sizeof (*sock));
    } else if (customer->count < 0) {
      LOG_ERROR_ ("release%d $%d error %d\n", customer->count, sock->fd, error);
    } else if (error != SIM_OK) {
      socket_cancel (sock, error);
      LOG_DEBUG_ ("release%d $%d error %d\n", customer->count, sock->fd, error);
    }
  }
}

int proxy_customer_cancel (simcustomer server, int error) {
  int count = 0;
  simcustomer customer;
  const char *addr = server ? server->sock->master->from.ptr : NULL;
  for (customer = proxy_customer_list; customer; customer = customer->next)
    if (! server || (customer->type == PROXY_TYPE_CLIENT && ! string_check_diff (customer->sock->master->to, addr))) {
      count++;
      socket_cancel (customer->sock, error);
      if (server) {
        SOCKET_ADD_STAT (server->sock, customer->sock);
        SOCKET_INIT_STAT (customer->sock);
      }
    }
  return count;
}

void proxy_customer_set_contact (simcustomer customer, simcontact contact) {
  customer->contact = contact;
}

void proxy_set_stat (simnumber value, unsigned i, unsigned j) {
  proxy_stats[i][j] = value;
}

simnumber proxy_get_stat_contact (simcontact contact, unsigned j) {
  simnumber bytes = 0;
  simcustomer customer;
  for (customer = proxy_customer_list; customer; customer = customer->next)
    if (customer->type == PROXY_TYPE_CLIENT && customer->flags & PROXY_FLAG_INCOMING && customer->contact == contact) {
      bytes += j > CONTACT_STAT_SENT ? 0 : socket_get_stat (customer->sock, j, false);
    } else if (customer->type == PROXY_TYPE_SERVER && customer->contact == contact)
      bytes += socket_get_stat (customer->sock, j, false);
  return bytes;
}

simnumber proxy_get_stat_server (simcustomer server, simnumber *received, simnumber *sent) {
  simnumber rcvbytes = SOCKET_GET_STAT_RECEIVED (server->sock) + SOCKET_GET_STAT_RECEIVED (&server->sock->old);
  simnumber sndbytes = SOCKET_GET_STAT_SENT (server->sock) + SOCKET_GET_STAT_SENT (&server->sock->old);
  if (server->sock->master) {
    simtype addr = server->sock->master->from;
    simcustomer customer;
    for (customer = proxy_customer_list; customer; customer = customer->next)
      if (customer->type == PROXY_TYPE_CLIENT && customer->flags & PROXY_FLAG_INCOMING)
        if (! string_check_diff_len (customer->sock->master->to, addr.str, addr.len)) {
          rcvbytes += SOCKET_GET_STAT_RECEIVED (customer->sock);
          sndbytes += SOCKET_GET_STAT_SENT (customer->sock);
        }
  }
  *received = rcvbytes;
  *sent = sndbytes;
  return server->sock->created;
}

simnumber proxy_get_stat (unsigned i, unsigned j) {
  simnumber rcvd = 0, sent = 0, rcvbytes, sndbytes;
  if (i != PROXY_STATS_THIS && i != PROXY_STATS_NOW)
    return proxy_stats[i][j];
  switch (j) {
    case CONTACT_STAT_RECEIVED:
    case CONTACT_STAT_SENT:
      if (i == PROXY_STATS_NOW) {
        simcustomer customer;
        for (customer = proxy_customer_list; customer; customer = customer->next)
          if (customer->type == PROXY_TYPE_SERVER) {
            proxy_get_stat_server (customer, &rcvbytes, &sndbytes);
            rcvd += rcvbytes;
            sent += sndbytes;
          } else if (customer->type != PROXY_TYPE_CLIENT && customer->flags & PROXY_FLAG_INCOMING) {
            rcvd += SOCKET_GET_STAT_RECEIVED (customer->sock);
            sent += SOCKET_GET_STAT_SENT (customer->sock);
          }
      } else if (proxy_stat_customers[PROXY_STAT_TOTAL].sock)
        proxy_get_stat_server (&proxy_stat_customers[PROXY_STAT_TOTAL], &rcvd, &sent);
      return j == CONTACT_STAT_RECEIVED ? rcvd : sent;
    case CONTACT_STAT_COUNT:
      return proxy_connections[i];
    case CONTACT_STAT_DURATION:
      if (i != PROXY_STATS_NOW)
        return proxy_duration;
      if (proxy_stat_customers[PROXY_STAT_TOTAL].sock && proxy_stat_customers[PROXY_STAT_TOTAL].sock->created)
        return system_get_tick () - proxy_stat_customers[PROXY_STAT_TOTAL].sock->created;
  }
  return 0;
}

simcustomer proxy_get_proxy (unsigned *ip, int *port, simnumber *latency) {
  if (ip)
    *ip = proxy_proxy_customer ? proxy_proxy_customer->ip : 0;
  if (port)
    *port = proxy_proxy_customer ? proxy_proxy_customer->port : 0;
  if (latency)
    *latency = proxy_proxy_customer ? proxy_pong_value : 0;
  return proxy_proxy_customer;
}

int proxy_get_ip (simsocket sock, unsigned *ip, unsigned *localip) {
  int err = SIM_OK;
  if (! sock) {
    *ip = proxy_ip;
  } else if (proxy_proxy_customer) {
    *ip = proxy_ip;
    err = socket_get_addr (proxy_proxy_customer->sock, localip, NULL);
  } else {
    err = socket_get_addr (sock, localip, NULL);
    *ip = *localip;
  }
  return err;
}

simbool proxy_get_ip_proxy (const char **address, unsigned *ip, int *port) {
  if (address)
    *address = proxy_proxy_customer ? proxy_address : NULL;
  if (! proxy_get_proxy (ip, port, NULL))
    return false;
  if (port && ! address)
    *port = proxy_proxy_customer->proxyport;
  if (proxy_realip && proxy_realport && sim_network_check_local (proxy_proxy_customer->ip)) {
    if (ip)
      *ip = proxy_realip;
    if (port)
      *port = proxy_realport;
  }
  return true;
}

simnumber proxy_get_latency (simclient client, unsigned pingpongidx) {
  simsocket sock = client->sock;
  if (pingpongidx == CLIENT_PONG_PROXY)
    return socket_check_client (sock) ? client->latency[pingpongidx] : socket_check_server (sock) ? proxy_latency : 0;
  return pingpongidx != CLIENT_PING_PROXY || SOCKET_CHECK_PROXY (sock) ? client->latency[pingpongidx] : 0;
}

simcustomer proxy_find_server (const char *address) {
  simcustomer customer;
  for (customer = proxy_customer_list; customer && address; customer = customer->next)
    if (customer->type == PROXY_TYPE_SERVER && customer->sock->err == SIM_OK)
      if (! string_check_diff (customer->sock->master->from, address))
        return customer;
  return NULL;
}

simcustomer proxy_find_client (const char *address, simnumber number, int type) {
  simcustomer customer;
  if (number > 0)
    for (customer = proxy_customer_list; customer; customer = customer->next)
      if (customer->number == number && customer->type == type && customer->sock->err == SIM_OK)
        return address && string_check_diff (customer->sock->master->to, address) ? NULL : customer;
  return NULL;
}

static simbool proxy_find_self (unsigned ip, int port) {
  unsigned localip;
  int localport;
  simcustomer customer;
  for (customer = proxy_customer_list; customer; customer = customer->next)
    if (socket_get_addr (customer->sock, &localip, &localport) == SIM_OK && ip == localip && port == localport) {
      customer->sock->flags |= SOCKET_FLAG_LOCAL;
      return true;
    }
  return false;
}

void proxy_cancel_server (simcustomer server, int error) {
  if (server->sock->err == SIM_OK) {
    int count = proxy_customer_cancel (server, error);
    LOG_DEBUG_ ("cancel $%d error %d (killed %d clients)\n", server->sock->fd, error, count);
  }
  socket_cancel (server->sock, error);
}

int proxy_cancel_proxy (const char *address, int error) {
  int port = 0;
  unsigned ip = 0;
  const char *addr = proxy_address;
  if (address ? addr != address : error == SIM_PROXY_NOT_PREFERRED && addr) {
    if (! address || error != SIM_PROXY_BLOCKED)
      return SIM_NAT_REVERSE_NO_PROXY;
    if ((ip = sim_network_parse_ip_port (address, &port)) == 0 || port <= 0 || port >= 0x10000)
      return SIM_SOCKET_BAD_PORT;
    if (! proxy_proxy_customer || proxy_proxy_customer->ip != ip || proxy_proxy_customer->port != port)
      addr = NULL;
  } else if (! proxy_proxy_customer)
    return SIM_NAT_REVERSE_NO_PROXY;
  if (addr) {
    socket_cancel (proxy_proxy_customer->sock, error);
    ip = proxy_proxy_customer->ip, port = proxy_proxy_customer->port;
  }
  if (error == SIM_PROXY_BLOCKED || error == SIM_PROXY_BLACKLISTED)
    return proxy_blacklist (addr ? addr : "BLOCKED", ip, port);
  return SIM_OK;
}

simbool proxy_check_required (simbool dht) {
  int require = param_get_number ("net.tor.port") > 0 ? true : param_get_number ("proxy.require");
  unsigned flags = dht ? SIM_STATUS_FLAG_DHT_OUT | SIM_STATUS_FLAG_TCP_IN : SIM_STATUS_FLAG_TCP_IN, myflags;
  sim_status_get (&myflags);
  return require < 0 ? false : require || (myflags & flags) != flags;
}

simbool proxy_check_provided (void) {
#ifndef DONOT_DEFINE
  if (! (simself.flags & SIM_STATUS_FLAG_TCP_IN) || ! (simself.flags & SIM_STATUS_FLAG_DHT_OUT))
    return false;
  return param_get_number ("net.tor.port") <= 0 && ! param_get_number ("proxy.require");
#else
  return param_get_number ("net.tor.port") <= 0 && param_get_number ("proxy.require") < 0; /* only for testing */
#endif
}

void proxy_announce (simunsigned tick, simbool now) {
  simcustomer customer, *customers = &proxy_customer_list, first = NULL, *last = &first;
  int ret = MAIN_SEARCH_NEW;
  while ((customer = *customers) != 0) {
    simcustomer *prev = customers;
    customers = &customer->next;
    if (customer->announce) {
      if (now) {
        customer->announce = tick;
      } else if (tick >= customer->announce) {
        if (ret != MAIN_SEARCH_FAILED)
          ret = main_search_start (customer->sock->master->from.ptr, 0, customer->mode);
        if (ret != MAIN_SEARCH_FAILED) {
          *last = customer;
          *prev = *(last = &customer->next);
          *last = NULL;
        }
        if (ret == MAIN_SEARCH_NEW && main_get_status () > MAIN_DHT_DISCONNECT) {
          customer->announce = tick + param_get_number ("proxy.announce") * 1000;
          customer->mode = MAIN_MODE_PASSIVE;
        } else
          customer->announce = tick + param_get_number ("main.research") * 1000;
      }
    }
  }
  if (first) {
    for (customers = &proxy_customer_list; *customers; customers = &(*customers)->next) {}
    *customers = first;
  }
}

static unsigned proxy_add_stat (simsocket sock, unsigned bytes) {
  unsigned len = SOCKET_CALC_OVERHEAD_TCP (bytes);
  sock->sndbytes -= bytes, sock->rcvheaders -= len, sock->sndheaders -= len;
  sock->old.sndbytes += bytes, sock->old.rcvheaders += len, sock->old.sndheaders += len;
  return len;
}

simtype proxy_new_cmd (const char *cmd, const char *key, simtype value, simbool server) {
  simtype table = table_new_long (2);
  table_add_pointer (table, server ? SIM_SERVER_CMD : SIM_CLIENT_CMD, cmd);
  if (key)
    table_add (table, key, value);
  return table;
}

static int proxy_send_cmd_ (simsocket sock, const char *cmd, const char *key, simtype value, int proto) {
  int err;
  unsigned sndbytes = 0;
  simtype table = proxy_new_cmd (cmd, key, value, proto == PROXY_PROTO_SERVER);
  err = socket_send_table_ (sock, table, proto == PROXY_PROTO_PROXY ? SOCKET_SEND_PROXY : SOCKET_SEND_TCP, &sndbytes);
  table_free (table);
  if (proto == PROXY_PROTO_PROXY)
    proxy_add_stat (sock, sndbytes);
  return err;
}

static int proxy_send_ping (simsocket sock, int proto) {
  unsigned sndbytes = 0;
  simbool server = proto == PROXY_PROTO_SERVER;
  const char *cmd = server ? SIM_SERVER_CMD_PING : SIM_CLIENT_CMD_PING;
  const char *key = server ? SIM_SERVER_CMD_PING_PONG : SIM_CLIENT_CMD_PING_PONG;
  simnumber tick = proto == PROXY_PROTO_PROXY ? sock->created : proxy_tick, now = system_get_tick ();
  simtype table = proxy_new_cmd (cmd, key, number_new (tick > 0 && now - tick >= 0 ? now - tick + 1 : 0), server);
  int err = socket_send (sock, table, proto == PROXY_PROTO_PROXY ? SOCKET_SEND_PROXY : SOCKET_SEND_TCP, &sndbytes);
  if (proto == PROXY_PROTO_PROXY)
    proxy_add_stat (sock, sndbytes);
  table_free (table);
  return err;
}

static int proxy_send_end_ (simcustomer customer, simnumber number, int error) {
  simtype table = proxy_new_cmd (SIM_SERVER_CMD_END, SIM_SERVER_DATA_NUMBER, number_new (number), true);
  table_add_number (table, SIM_SERVER_CMD_END_ERROR, error > 0 ? SIM_SOCKET_NO_ERROR : error);
  error = socket_send_table_ (customer->sock, table, SOCKET_SEND_TCP, NULL);
  table_free (table);
  return error;
}

void proxy_send_error (int error) {
  simtype err = number_new (error);
  simcustomer customer;
  for (customer = proxy_customer_list; customer; customer = customer->next)
    if (customer->type == PROXY_TYPE_SERVER && customer->sock->err == SIM_OK) {
      simtype table = proxy_new_cmd (SIM_SERVER_CMD_HANDSHAKE, SIM_SERVER_CMD_HANDSHAKE_ERROR, err, true);
      if (socket_send (customer->sock, table, SOCKET_SEND_TCP, NULL) != SIM_OK)
        customer->error = error;
      table_free (table);
    }
}

int proxy_recv_cmd_ (simsocket sock, const simtype table, simbool proxy) {
  int err = SIM_OK;
  simtype cmd = table_get_string (table, SIM_CLIENT_CMD);
  if (proxy && ! string_check_diff (cmd, SIM_CLIENT_CMD_END)) {
    simnumber error = table_get_number (table, SIM_CLIENT_CMD_END_ERROR);
    LOG_XTRA_ ("EOF $%d (error %lld)\n", sock->fd, error);
#ifndef DONOT_DEFINE
    err = /*error < 0 && error == (int) error ? (int) error :*/ SIM_SOCKET_END;
#endif
  } else if (! string_check_diff (cmd, SIM_CLIENT_CMD_PING) && (proxy || sock->flags & SOCKET_FLAG_PING)) {
    if (proxy || sock->flags & SOCKET_FLAG_PING_PONG) {
      simtype pong = table_get (table, SIM_CLIENT_CMD_PING_PONG);
      const char *pingpong = pong.typ == SIMNUMBER ? SIM_SERVER_CMD_PING_PONG : NULL;
      err = proxy_send_cmd_ (sock, SIM_CLIENT_CMD_PONG, pingpong, pong, proxy ? PROXY_PROTO_PROXY : PROXY_PROTO_CLIENT);
    } else
      sock->flags &= ~(SOCKET_FLAG_PING | SOCKET_FLAG_PING_PONG);
  } else if (! string_check_diff (cmd, SIM_CLIENT_CMD_PONG) && (proxy || ! (sock->flags & SOCKET_FLAG_PONG))) {
    if (sock->client) {
      simnumber pong = table_get_number (table, SIM_CLIENT_CMD_PING_PONG), tick = system_get_tick ();
      if (! pong || tick - sock->created < 0 || (pong = tick - sock->created + 1 - pong) < 0) {
        pong = -1;
      } else
        sock->client->latency[CLIENT_PONG_PROXY] = pong + 1;
      LOG_CODE_ANY_ (SIM_MODULE_TABLE, SIM_LOG_INFO, "pong %lld ms\n", pong + 1);
    }
    sock->flags |= SOCKET_FLAG_PONG;
  } else if (! proxy) {
    err = SIM_LIMIT_BLOCKED;
    LOG_CODE_ANY_ (SIM_MODULE_TABLE, SIM_LOG_WARN, "unknown command $%d: %s\n", sock->fd, cmd.str);
  } else
    LOG_CODE_ANY_ (SIM_MODULE_TABLE, SIM_LOG_NOTE, "unknown command $%d: %s\n", sock->fd, cmd.str);
  return err;
}

#define PROXY_CHECK_UDP(type) ((type) == PROXY_TYPE_SERVER || (type) == PROXY_TYPE_CLIENT || (type) == PROXY_TYPE_PROXY)

int proxy_recv_udp (simclient client, const simtype input, unsigned ip, int port) {
  const int bits = SOCKET_RECV_UDP | SOCKET_RECV_PROXY;
  int err = SIM_SOCKET_BAD_PACKET;
  simcustomer customer = NULL;
  simtype table = nil ();
  if (! client ||
      (err = socket_recv_table (client->sock, proxy_proto_udp, nil (), -1, input, bits, &table)) != SIM_OK) {
    event_test_error_crypto (client ? client->contact : NULL, client ? client->contact->addr : NULL, err);
    for (customer = proxy_customer_list; customer; customer = customer->next)
      if (customer->ip == ip && PROXY_CHECK_UDP (customer->type)) {
        const char *addr = proxy_address;
        char ipstr[100];
        if (customer->type != PROXY_TYPE_PROXY || param_get_number ("nat.udp")) {
          simnumber seq = customer->type == PROXY_TYPE_PROXY ? -1 : ((simnumber) 1 << 62) - 1;
          if ((err = limit_test_udp (customer, input.len)) == SIM_OK) {
            err = socket_recv_table (customer->sock, proxy_proto_udp, nil (), seq, input, SOCKET_RECV_UDP, &table);
          } else if (customer->type == PROXY_TYPE_CLIENT)
            break;
        }
        if (err == SIM_OK || err == SIM_LIMIT_NO_SPEED) {
          if (! (customer->sock->flags & SOCKET_FLAG_LOCAL))
            customer->sock->rcvbytes += input.len, customer->sock->rcvheaders += SOCKET_CALC_OVERHEAD_UDP (input.len);
          break;
        }
        if (customer->type == PROXY_TYPE_SERVER) {
          addr = customer->sock->master->from.ptr;
        } else if (customer->type != PROXY_TYPE_PROXY)
          addr = strcpy (ipstr, network_convert_ip (ip));
        event_test_error_crypto (NULL, addr, err);
        err = SIM_SOCKET_NO_ERROR;
      }
  }
  if (err == SIM_OK) {
    simsocket sock = customer ? customer->sock : client->sock;
    if (! customer && ! (sock->flags & SOCKET_FLAG_LOCAL))
      proxy_stats[PROXY_STATS_OUTPUT][CONTACT_STAT_RECEIVED] += input.len + SOCKET_CALC_OVERHEAD_UDP (input.len);
    LOG_DEBUG_SIMTYPE_ (table, 0, "udp $%d recv (%u bytes) from %s:%d ", sock->fd,
                        input.len, network_convert_ip (ip), port);
    if (sock->err == SIM_OK && ! string_check_diff (table_get_string (table, SIM_CMD_UDP), SIM_CMD_UDP_REQUEST)) {
      if (customer && customer->type != PROXY_TYPE_PROXY) {
        unsigned sndbytes = 0;
        if (! sim_network_check_local (ip) && ! client_check_local (ip, true)) {
          simself.flags |= SIM_STATUS_FLAG_UDP_IN;
          LOG_CODE_DEBUG_ (network_log_flags (SIM_MODULE, SIM_LOG_DEBUG));
        }
        table_set_number (table, SIM_CMD_UDP_TIME, ++customer->sndtick);
        table_set (table, SIM_CMD_UDP_SRC_IP, string_copy (network_convert_ip (ip)));
        table_set_number (table, SIM_CMD_UDP_SRC_PORT, port);
        if (socket_send_udp (customer->sock, table, -1, ip, port, SOCKET_SEND_UDP, &sndbytes) == SIM_OK)
          LOG_DEBUG_SIMTYPE_ (table, 0, "udp $%d send (%u bytes) to %s:%d ", customer->sock->fd,
                              socket_size_table (customer->sock, table, SOCKET_SEND_UDP) - SOCKET_OVERHEAD_SSL,
                              network_convert_ip (ip), port);
        limit_test_udp (customer, -(int) sndbytes);
      } else if (audio_status.client && audio_status.client != AUDIO_CLIENT_TEST)
        if (! audio_status.udport && ! audio_status.client->sock->udport) {
          simnumber srcport = table_get_number (table, SIM_CMD_UDP_SRC_PORT);
          if (srcport > 0 && srcport < 0x10000 && param_get_number ("client.udp")) {
            audio_status.udport = (int) srcport;
            client_send_cmd (audio_status.client, SIM_CMD_UDP, SIM_CMD_UDP_SRC_PORT,
                             number_new (srcport), SIM_CMD_UDP_SRC_IP, string_copy (network_convert_ip (ip)));
            if (audio_status.client->call.udport && ! nat_get_connected (audio_status.client))
              audio_start_udp (false);
          }
        }
    }
    table_free (table);
  } else if (err == SIM_SOCKET_NO_ERROR)
    for (customer = proxy_customer_list; customer; customer = customer->next)
      if (customer->ip == ip && ! (customer->sock->flags & SOCKET_FLAG_LOCAL) && PROXY_CHECK_UDP (customer->type))
        customer->sock->rcvbytes += input.len, customer->sock->rcvheaders += SOCKET_CALC_OVERHEAD_UDP (input.len);
  return err;
}

int proxy_ping (simclient client) {
  int err = SIM_OK;
  if (client && socket_check_client (client->sock)) {
    if (client->sock->flags & SOCKET_FLAG_PONG)
      if ((err = proxy_send_ping (client->sock, PROXY_PROTO_PROXY)) == SIM_OK)
        client->sock->flags &= ~SOCKET_FLAG_PONG;
    if (client->sock->err != SIM_OK)
      err = client->sock->err;
  } else if (proxy_proxy_customer && (! client || socket_check_server (client->sock))) {
    if (! client)
      proxy_pong_value = 0;
    proxy_send_ping (proxy_proxy_customer->sock, PROXY_PROTO_SERVER);
    if (proxy_proxy_customer->sock->err != SIM_OK)
      err = proxy_proxy_customer->sock->err;
  }
  return err;
}

int proxy_handshake_client_ (simsocket sock, const char *address, unsigned proxyip, int proxyport,
                             unsigned *ip, int *port, int *protover) {
  unsigned ownip = 0;
  simnumber ownport = 0, tick = system_get_tick (), identifier = 0, cipher = 0;
  int err = ssl_handshake_client_ (sock, address, SSL_HANDSHAKE_NOKEY);
  simtype version = table_new (2);
  table_add_number (version, SIM_PROXY_REQUEST, SIM_PROTO_PROXY_VERSION_MAX);
  table_add (version, SIM_PROXY_REQUEST_IP, string_copy (network_convert_ip (proxyip)));
  table_add_number (version, SIM_PROXY_REQUEST_PORT, proxyport);
  sim_crypt_cipher_get (3, NULL, &identifier);
  table_add_number (version, SIM_PROXY_REQUEST_CIPHER, identifier);
  if (err == SIM_OK)
    err = crypt_handshake_client_ (sock, &version, proxy_proto_handshake, NULL);
  if (err == SIM_OK) {
    simnumber ver = table_get_number (version, SIM_PROXY_REPLY);
    if ((cipher = table_get_number (version, SIM_PROXY_REPLY_CIPHER)) != 0)
      if (strcmp (address, PROXY_ADDRESS_CONTROL) && strcmp (address, PROXY_ADDRESS_TRAVERSER)) {
        SOCKET_ADD_STAT (&sock->old, sock);
        SOCKET_INIT_STAT (sock);
        socket_set_buffer (sock, param_get_number ("socket.tcp.client"), false);
      }
    if (ver < SIM_PROTO_PROXY_VERSION_MIN || ver > SIM_PROTO_PROXY_VERSION_MAX) {
      LOG_NOTE_ ("handshake $%d failed (version %lld)%s\n", sock->fd, ver, cipher ? " proxy" : "");
      err = SIM_CLIENT_BAD_VERSION;
    } else if (! cipher || cipher == identifier) {
      if (protover)
        *protover = (int) (port || ! cipher ? ver : table_get_number (version, SIM_PROXY_REPLY_VERSION));
      LOG_DEBUG_ ("handshake $%d succeeded (version %lld)%s\n", sock->fd, ver, cipher ? " proxy" : "");
    } else
      err = SIM_CRYPT_BAD_CIPHER;
  }
  if (event_test_error_crypto (NULL, address, err) == SIM_OK) {
    if (! strcmp (address, PROXY_ADDRESS_CONTROL)) {
      err = ssl_handshake_client_ (sock, PROXY_ADDRESS_CONTROL, SSL_HANDSHAKE_PROXY);
    } else if (strcmp (address, PROXY_ADDRESS_TRAVERSER)) {
      if (protover && (err = ssl_handshake_client_ (sock, address, SSL_HANDSHAKE_CLIENT)) == SIM_OK) {
        simcontact contact = contact_list_find_address (address);
        err = contact_set_keys (contact, sock->master->pub[SSL_KEY_EC], sock->master->pub[SSL_KEY_RSA]);
      }
    }
    if (err == SIM_OK) {
      simtype ipstr = table_get_string (version, SIM_PROXY_REPLY_IP);
      ownport = table_get_number (version, SIM_PROXY_REPLY_PORT);
      if (ownport <= 0 || ownport >= 0x10000) {
        LOG_WARN_ ("handshake $%d invalid port %lld\n", sock->fd, ownport);
        ownport = 0;
      }
      if ((ownip = sim_network_parse_ip (ipstr.ptr)) == 0)
        LOG_WARN_ ("handshake $%d invalid ip %s\n", sock->fd, ipstr.str);
      if (cipher && strcmp (address, PROXY_ADDRESS_CONTROL) && strcmp (address, PROXY_ADDRESS_TRAVERSER)) {
        sock->sndtimeout = param_get_number ("proxy.send");
      } else
        crypt_close_socket (sock, true);
      LOG_DEBUG_SIMTYPE_ (table_get_array_number (version, SIM_PROXY_REPLY_SPEED), 0,
                          "handshake $%d (%lld ms) own ip %s:%lld ", sock->fd,
                          system_get_tick () - tick, network_convert_ip (ownip), ownport);
    }
  }
  table_free (version);
  if (ip)
    *ip = ownip;
  if (port)
    *port = (int) ownport;
  return err;
}

int proxy_connect_ (simcustomer proxy, simsocket lock, unsigned ip, int port, unsigned *ownip, int *ownport) {
  int err;
  const char *addr = proxy->type == PROXY_TYPE_PROXY ? PROXY_ADDRESS_CONTROL : PROXY_ADDRESS_TRAVERSER;
  proxy->ip = ip, proxy->port = port;
  socket_set_buffer (proxy->sock, param_get_number ("socket.tcp.server"), false);
  if ((err = socket_connect_lock_ (proxy->sock, lock, &ip, port, NULL)) == SIM_OK) {
    if (proxy->type == PROXY_TYPE_PROXY)
      LOG_DEBUG_ ("connected $%d control connection to proxy %s:%d\n", proxy->sock->fd, network_convert_ip (ip), port);
    if ((err = proxy_handshake_client_ (proxy->sock, addr, ip, port, ownip, ownport, &proxy->version)) != SIM_OK) {
      LOG_INFO_ ("handshake $%d at %s:%d error: %s\n", proxy->sock->fd,
                 network_convert_ip (ip), port, convert_error (err, false));
    } else if (proxy->type == PROXY_TYPE_PROXY)
      proxy->sock->sndtimeout = param_get_number ("proxy.send");
    if (err == SIM_CRYPT_BAD_TABLE)
      proxy_blacklist (NULL, ip, port);
  }
  return err;
}

static int proxy_connect_proxy_ (simcustomer proxy, unsigned ip, int port, int *proxyport, unsigned *ownip) {
  int err = SIM_PROXY_BLACKLISTED, ownport;
  if (proxy->type != PROXY_TYPE_PROXY || ! proxy_check_blacklisted (ip, port))
    if ((err = proxy_connect_ (proxy, NULL, ip, proxy->proxyport = port, ownip, &ownport)) != SIM_OK)
      if ((proxy->type != PROXY_TYPE_PROXY || ! proxy_check_blacklisted (ip, SIM_PROTO_PORT)) && port != SIM_PROTO_PORT)
        if ((err = socket_reopen (proxy->sock, NULL, NULL, err)) == SIM_OK)
          err = proxy_connect_ (proxy, NULL, ip, SIM_PROTO_PORT, ownip, &ownport);
  if (err == SIM_OK)
    *proxyport = proxy->port;
  return err;
}

static int proxy_connect_local_ip_port (simsocket sock, unsigned ip, int port) {
  int err = sock->err != SIM_OK ? sock->err : sock->fd == INVALID_SOCKET ? EBADF : SIM_OK;
  struct sockaddr_in sin;
  sin.sin_family = AF_INET, sin.sin_addr.s_addr = htonl (ip), sin.sin_port = htons (port);
  LOG_XTRA_ ("connect $%d -> %s:%d\n", sock->fd, inet_ntoa (sin.sin_addr), port);
  if (err == SIM_OK && connect (sock->fd, (struct sockaddr *) &sin, sizeof (sin))) {
    if ((err = socket_get_errno ()) == EINPROGRESS) {
      for (;;) {
#ifdef _WIN32
        fd_set ok, nok;
        struct timeval tv;
        FD_ZERO (&ok);
        FD_SET (sock->fd, &ok);
        FD_ZERO (&nok);
        FD_SET (sock->fd, &nok);
        tv.tv_sec = 1, tv.tv_usec = 0;
        if ((err = select (sock->fd + 1, NULL, &ok, &nok, &tv)) > 0) {
          err = FD_ISSET (sock->fd, &nok) ? WSAECONNREFUSED : SIM_OK;
#else
        struct pollfd fds;
        fds.fd = sock->fd;
        fds.events = POLLOUT;
        if ((err = poll (&fds, 1, 1000)) > 0) {
          socklen_t len = sizeof (err);
          if (getsockopt (sock->fd, SOL_SOCKET, SO_ERROR, &err, &len) && (err = socket_get_errno ()) == 0)
            err = EINVAL;
#endif
        } else if (err) {
          if ((err = socket_get_errno ()) == 0)
            err = EINVAL;
#ifndef _WIN32
          if (err == EINTR && sock->err == SIM_OK)
            continue;
#endif
        } else
          err = SIM_SOCKET_TIMEOUT;
        break;
      }
    } else if (! err)
      err = SIM_SOCKET_TIMEOUT;
  }
  if (sock->err != SIM_OK)
    err = sock->err;
  if (err == SIM_OK) {
    LOG_XTRA_ ("connected $%d -> %s:%d\n", sock->fd, network_convert_ip (ip), port);
  } else
    LOG_WARN_ ("connect $%d error %d\n", sock->fd, err);
  return err;
}

static int proxy_connect_local (simsocket sock) {
  int err, port = abs (param_get_number ("main.port"));
  unsigned ip = sim_network_parse_ip_default (param_get_pointer ("proxy.local"), &ip);
  simtype ipstr = param_get_default ("proxy.local", nil ());
  proxy_localip[0] = sim_network_parse_ip (ipstr.ptr);
  if ((err = proxy_connect_local_ip_port (sock, proxy_localip[1] = ip, port)) != SIM_OK) {
    socket_close (sock, NULL);
    if ((err = socket_open (sock, SOCKET_NO_SESSION)) == SIM_OK)
      if ((err = proxy_connect_local_ip_port (sock, proxy_localip[0], port)) != SIM_OK) {
        socket_close (sock, NULL); /* localhost interface doesn't exist? */
        if ((err = socket_open (sock, SOCKET_NO_SESSION)) == SIM_OK)
          err = proxy_connect_local_ip_port (sock, sim_network_get_default (), port);
      }
  }
  return err;
}

static void *thread_backwards_ (void *arg) {
  simcustomer server = arg, reverse = NULL;
  unsigned ip = server->ip;
  int port = server->realport, fd = server->sock->fd;
  server->realport = 0;
  LOG_API_DEBUG_ ("$%d %s:%d\n", fd, network_convert_ip (ip), port);
  if (limit_test_socket_ (server) == SIM_OK) {
    reverse = proxy_customer_new (PROXY_TYPE_REVERSE);
    if (socket_open (reverse->sock, NULL) == SIM_OK && proxy_connect_proxy_ (reverse, ip, port, &port, NULL) == SIM_OK)
      LOG_INFO_ ("reverse connection $%d:$%d to customer %s:%d '%s'\n", fd, reverse->sock->fd,
                 network_convert_ip (ip), port, CONTACT_GET_NICK (server->sock->master->from.ptr));
    SOCKET_ADD_STAT (server->sock, reverse->sock);
    SOCKET_INIT_STAT (reverse->sock);
    reverse->sock->created = 0;
  }
  if (! server->reversed)
    server->reversed = system_get_tick ();
  proxy_customer_release (server, SIM_OK);
  proxy_customer_release (reverse, SIM_PROXY_REVERSE);
  LOG_API_DEBUG_ ("$%d\n", fd);
  return pth_thread_exit_ (true);
}

static void *thread_local_ (void *arg) { /* read from local write to proxy */
  int err;
  simcustomer local = arg, proxy = proxy_customer_acquire (proxy_proxy_customer);
  simtype buf = nil (), table;
  LOG_API_DEBUG_ ("$%d\n", local->sock->fd);
  if ((err = proxy_connect_local (local->sock)) == SIM_OK)
    err = socket_get_addr (local->sock, &local->ip, &local->realport);
  if (err == SIM_OK) {
    LOG_API_DEBUG_ ("$%d connected from %s:%d\n", local->sock->fd, network_convert_ip (local->ip), local->realport);
    if (proxy) {
      buf = string_buffer_new (socket_size_max (proxy->sock, SOCKET_SEND_PROXY));
    } else
      err = SIM_PROXY_NO_CUSTOMER;
  } else if (proxy) {
    LOG_API_DEBUG_ ("$%d disconnected #%d\n", local->sock->fd, local->number);
  fail:
    proxy_send_end_ (proxy, local->number, err);
  }
  while (err == SIM_OK) {
    unsigned len;
    if ((table = limit_new_cmd (LIMIT_CMD_REQUEST)).typ != SIMNIL) {
      err = socket_send_table_ (proxy->sock, table, SOCKET_SEND_TCP, NULL);
    } else if ((table = limit_new_cmd (LIMIT_CMD_START)).typ == SIMNIL) {
      do {
        err = socket_recv_some_ (local->sock, SOCKET_RECV_TCP, pointer_new_len (buf.str + 2, buf.len - 2), &len);
      } while (err == SIM_SOCKET_RECV_TIMEOUT);
      if (err == SIM_OK) {
        local->sock->rcvbytes -= len;
        if (len) {
          int bits = local->sock->flags & SOCKET_FLAG_CPU;
          unsigned sndbytes = 0;
          table_add_number (table = table_new_short (2), SIM_SERVER_DATA_NUMBER, local->number);
          if (local->flags & PROXY_FLAG_COMPLETED) {
            table_add_pointer_len (table, SIM_SERVER_DATA, buf.str + 2, len);
          } else if (len < 0x8000) {
            buf.str[0] = (simbyte) (len >> 8) | 0x80;
            buf.str[1] = (simbyte) len;
            table_add_pointer_len (table, SIM_SERVER_DATA, buf.str, len + 2);
            bits |= SOCKET_SEND_PAD;
          } else
            socket_cancel (proxy->sock, err = SIM_SOCKET_BAD_LENGTH);
          if (err == SIM_OK)
            err = socket_send_table_ (proxy->sock, table, bits, &sndbytes);
          len = proxy_add_stat (proxy->sock, sndbytes);
          local->sock->rcvheaders += len, local->sock->sndbytes += sndbytes, local->sock->sndheaders += len;
        } else if ((err = proxy_send_end_ (proxy, local->number, local->sock->err)) == SIM_OK)
          err = SIM_PROXY_END_LOCAL;
      } else if (local->sock->err == SIM_OK && local->number)
        goto fail;
    } else
      err = socket_send_table_ (proxy->sock, table, SOCKET_SEND_TCP, NULL);
    table_free (table);
  }
  LOG_API_DEBUG_ ("$%d %s %d\n", local->sock->fd, local->flags & PROXY_FLAG_ACCEPTED ? "error" : "ERROR", err);
  local->flags |= PROXY_FLAG_ACCEPTED;
  socket_close_lock_ (local->sock, local->contact);
  proxy_customer_release (local, err);
  proxy_customer_release (proxy, SIM_OK);
  string_buffer_free (buf);
  return pth_thread_exit_ (false);
}

int proxy_loop_proxy_ (const char *address, unsigned ip, int port, unsigned senderip) { /* read from proxy write to local */
  simbool ping = true, okerr[2] = { false, false }, ok;
  unsigned ownip = 0;
  simunsigned cpu = senderip ? sim_system_cpu_get (SYSTEM_CPU_TIME_CYCLES, NULL) : 0, tick = system_get_tick ();
  int err, err2, fd, msec = 0, upnp = -1, proxyport = port;
  simcustomer proxy = proxy_customer_new (PROXY_TYPE_PROXY), local, customer;
  simtype table;
  proxy_address = address;
  LOG_API_DEBUG_ ("%s:%d '%s'\n", network_convert_ip (ip), port, CONTACT_GET_NICK (address));
  if ((err = socket_open (proxy->sock, NULL)) == SIM_OK)
    if ((err = proxy_connect_proxy_ (proxy, ip, port, &port, &ownip)) == SIM_OK)
      if ((err = sim_crypt_handshake (proxy->sock->master)) == SIM_OK) {
        int strangers = param_get_number ("proxy.strangers");
        crypt_open_socket (proxy->sock, 3, 3, 0, 0, false, true);
        nat_traverse_reset (NULL, SIM_OK);
        proxy_address = NULL;
        if (string_check_diff (proxy->sock->master->from, PROXY_ADDRESS_CONTROL))
          proxy_address = proxy->sock->master->from.ptr;
        if (strangers <= 0 && ((strangers < 0) ^ ! proxy_address)) {
          err = SIM_PROXY_NOT_PREFERRED;
        } else
          main_search_denounce (PROXY_ADDRESS_PROXY);
      }
  proxy_tick = system_get_tick ();
  if (senderip)
    msec = main_cputime_test (cpu, senderip, proxy_tick, proxy->sock->flags & SOCKET_FLAG_CONNECT ? 512 : 4);
  proxy->sock->rcvtimeout = param_get_number ("proxy.recv");
  if (! proxy_address && sim_network_check_local (ip))
    proxy_address = "";
  if ((err2 = err) == SIM_OK)
    LOG_INFO_ ("connected $%d to %s:%d (%lld ms) '%s'\n", proxy->sock->fd,
               network_convert_ip (ip), port, proxy_tick - tick, CONTACT_GET_NICK (proxy_address));
  proxy_proxy_customer = proxy;
  proxy_realport = proxy_realip = 0;
  limit_send_speed (NULL, 0);
  proxy_set_score (NULL, SIM_OK, PROXY_SCORE_INIT);
  main_set_tick (! sim_network_check_local (ip));
  while (err == SIM_OK) {
    unsigned rcvbytes = 0, l;
    proxy_set_score (NULL, SIM_OK, PROXY_SCORE_NOK);
    err = _socket_recv_table_ (proxy->sock, proxy_proto_server, nil (), &table, &rcvbytes, &l, __FUNCTION__, __LINE__);
    if (err == SIM_OK) {
      simtype cmd, data = table_get_string (table, SIM_SERVER_DATA);
      ping = okerr[0];
      if (data.typ != SIMNIL ||
          ! string_check_diff (cmd = table_get_string (table, SIM_SERVER_CMD), SIM_SERVER_CMD_END)) {
        simnumber num = table_get_number (table, SIM_SERVER_DATA_NUMBER);
        if ((local = proxy_customer_acquire (proxy_find_client (NULL, num, PROXY_TYPE_LOCAL))) != NULL) {
          if (data.len) {
            if (local->sock->flags & SOCKET_FLAG_CPU) {
              cpu = sim_system_cpu_get (SYSTEM_CPU_TIME_CYCLES, NULL);
              limit_set_cpu (table_get_number (table, SIM_SERVER_DATA_CYCLES), cpu);
            }
            local->sock->rcvbytes += rcvbytes, local->sock->rcvheaders += l, local->sock->sndheaders += l;
            proxy->sock->rcvbytes -= rcvbytes, proxy->sock->rcvheaders -= l, proxy->sock->sndheaders -= l;
            proxy->sock->old.rcvbytes += rcvbytes, proxy->sock->old.rcvheaders += l, proxy->sock->old.sndheaders += l;
          }
          if (! (local->flags & PROXY_FLAG_CONNECTED) && data.len >= 2 && data.str[0] >= 0x80) {
            if ((l = (data.str[0] << 8) + data.str[1] - 0x8000) + 2 > data.len) {
              l = 0;
              err = SIM_SOCKET_BAD_PACKET;
            } else
              data.str += 2;
            data.len = l;
          } else
            local->flags |= PROXY_FLAG_CONNECTED;
          local->sock->sndbytes -= data.len;
          if (! data.len || (err = socket_send_all_ (local->sock, data.ptr, data.len)) != SIM_OK) {
            if (! data.len)
              local->number = 0;
            fd = local->sock->fd;
            proxy_customer_release (local, err == SIM_OK ? SIM_PROXY_END_CLIENT : err);
            LOG_XTRA_ ("EOF #%lld ($%d error %lld:%d)\n", num, fd,
                       table_get_number (table, SIM_SERVER_CMD_END_ERROR), err);
#ifndef DONOT_DEFINE
            for (customer = proxy_customer_list; customer; customer = customer->next)
              if (socket_check_server (customer->sock) == local)
                break;
            err = EBADF;
            if (fd == INVALID_SOCKET || (err = client_cancel_local (local, customer ? customer->sock : NULL)) != SIM_OK)
              LOG_XTRA_ ("EOF #%lld ($%d error %d)\n", num, fd, err);
#endif
            err = SIM_OK;
          } else
            proxy_customer_release (local, SIM_OK);
        } else
          LOG_WARN_ ("received $%d from unknown client #%lld (%u bytes)\n", proxy->sock->fd, num, data.len);
      } else if (! string_check_diff (cmd, SIM_SERVER_CMD_PING)) {
        simtype pong = proxy_new_cmd (SIM_SERVER_CMD_PONG, NULL, nil (), true);
        simtype pad = table_get (table, SIM_SERVER_CMD_PING_PONG);
        if (pad.typ == SIMNUMBER)
          table_add (pong, SIM_SERVER_CMD_PING_PONG, pad);
        if ((pad = table_get (table, SIM_SERVER_CMD_PONG_PAD)).typ == SIMSTRING)
          table_add_pointer_len (pong, SIM_SERVER_CMD_PONG_PAD, pad.str, pad.len);
        if (! (contact_list.test & 0x4000))
          err = socket_send_table_ (proxy->sock, pong, SOCKET_SEND_TCP, NULL);
        table_free (pong);
      } else if (! string_check_diff (cmd, SIM_SERVER_CMD_CONNECT)) {
        simnumber num = table_get_number (table, SIM_SERVER_CMD_CONNECT_NUMBER);
        local = proxy_find_client (NULL, num, PROXY_TYPE_LOCAL);
        if (local || num < PROXY_NUMBER_MIN || num > PROXY_NUMBER_MAX) {
          LOG_WARN_ ("connect $%d from old client #%lld\n", local ? local->sock->fd : -2, num);
          local = NULL;
        } else if (local = proxy_customer_new (PROXY_TYPE_LOCAL),
                   (err = socket_open (local->sock, SOCKET_NO_SESSION)) == SIM_OK) {
          simnumber ver = table_get_number (table, SIM_SERVER_CMD_CONNECT_VERSION);
          local->contact = SOCKET_NO_SESSION;
          local->number = (unsigned) num;
          local->version = ver == (int) ver ? (int) ver : 0;
          local->realip = sim_network_parse_ip (table_get_pointer (table, SIM_SERVER_CMD_CONNECT_IP));
          if (! local->realip && table_get (table, SIM_SERVER_CMD_CONNECT_IP).typ == SIMNIL)
            local->realip = proxy->ip;
          LOG_DEBUG_ ("connect $%d client #%lld from %s\n", local->sock->fd, num, network_convert_ip (local->realip));
          if ((err = limit_check_blacklisted (local, local->realip)) != SIM_OK) {
            err = proxy_send_end_ (proxy, local->number, err);
          } else if ((err = pth_thread_spawn (thread_local_, local, NULL, num)) == SIM_OK) {
            simnumber size = table_get_number (table, SIM_SERVER_CMD_CONNECT_BUFSIZE);
            proxy_customer_acquire (local);
            socket_set_buffer (local->sock, size == (int) size ? (int) size : 0, true);
            do {
              pth_usleep_ (1000);
            } while (! (local->flags & PROXY_FLAG_ACCEPTED));
          }
        }
        proxy_customer_release (local, err);
        err = SIM_OK;
      } else if (! string_check_diff (cmd, SIM_SERVER_CMD_HANDSHAKE)) {
        if ((err = (int) table_get_number (table, SIM_SERVER_CMD_HANDSHAKE_ERROR)) == SIM_OK) {
          if (! okerr[0] && ! okerr[1]) {
            simtype ports = table_get (table, SIM_SERVER_CMD_HANDSHAKE_PROXY_PORT);
            if (ports.typ == SIMNUMBER && ports.num > 0 && ports.num < 0x10000) {
              proxy_realip = sim_network_parse_ip (table_get_pointer (table, SIM_SERVER_CMD_HANDSHAKE_PROXY_IP));
              proxy_realport = (int) ports.num;
            } else
              LOG_WARN_ ("invalid $%d proxy port %lld\n", proxy->sock->fd, ports.num);
            if (proxy_realport && ! sim_network_check_local (proxy_realip))
              main_set_tick (true);
            if (! (contact_list.test & 0x8000))
              client_send_proxy ();
          }
        } else if (! okerr[1]) {
          LOG_INFO_ ("proxy $%d %s error %d\n", proxy->sock->fd, okerr[0] ? "disconnect" : "connect", err);
          if (err == SIM_PROXY_RECONNECT && port > 0 && port < 0x10000) {
            LOG_DEBUG_ ("proxy $%d duplicate %s:%d\n", proxy->sock->fd, network_convert_ip (ip), port);
            contact_probe (contact_list.me, ip, port, NULL, CONTACT_PROBE_RECONNECT);
          }
          client_cancel_proxy (err < 0 ? err : SIM_SOCKET_NO_ERROR);
        }
        ok = ! okerr[0] && ! okerr[1] && ! (contact_list.test & 0x4000);
        okerr[err != SIM_OK] = true;
        err = ok ? proxy_send_cmd_ (proxy->sock, SIM_SERVER_CMD_PONG, NULL, nil (), PROXY_PROTO_SERVER) : SIM_OK;
      } else if (! string_check_diff (cmd, SIM_SERVER_CMD_PONG)) {
        simnumber pong = table_get_number (table, SIM_SERVER_CMD_PING_PONG);
        if (pong) {
          if ((simnumber) (tick = system_get_tick ()) - proxy_tick < 0 || (pong = tick - proxy_tick + 1 - pong) < 0) {
            pong = -1;
          } else
            proxy_latency = pong + 1;
          if (! proxy_pong_value)
            proxy_pong_value = pong + 1;
          LOG_INFO_ ("pong %lld ms\n", pong + 1);
        }
      } else if (! string_check_diff (cmd, SIM_SERVER_CMD_SHAKEHAND_REPLY)) {
        err = limit_exec_reply (proxy, table);
      } else
        LOG_NOTE_ ("unknown command $%d: %s\n", proxy->sock->fd, cmd.str);
      proxy_ip = ownip;
      if (ping && err == SIM_OK) {
        if (upnp < 0 || (! upnp && simself.flags & SIM_STATUS_FLAG_UPNP)) {
          table_free (table);
          table = proxy_new_cmd (SIM_SERVER_CMD_SHAKEHAND, NULL, nil (), true);
          if (param_get_number ("net.tor.port") <= 0) {
            simtype ports = array_new_types (2, number_new (abs (param_get_number ("main.port"))));
            if ((ports.arr[2] = number_new (network_get_port (NULL))).num == 0)
              ports.len = 1;
            LOG_INFO_ ("reverse $%d to port %lld:%lld\n", proxy->sock->fd, ports.arr[1].num, ports.arr[2].num);
            table_add (table, SIM_SERVER_CMD_SHAKEHAND_PORT, ports);
          }
          if (upnp < 0)
            table_add_number (table, SIM_SERVER_CMD_SHAKEHAND_SPEED, PROXY_SPEED_AUDIO);
          if (table_count (table) > 1)
            err = socket_send_table_ (proxy->sock, table, SOCKET_SEND_TCP, NULL);
          upnp = true;
        } else
          upnp = (simself.flags & SIM_STATUS_FLAG_UPNP) != 0;
      }
      ping = true;
    } else if (err == SIM_SOCKET_RECV_TIMEOUT) {
      if (ping)
        err = proxy_send_cmd_ (proxy->sock, SIM_SERVER_CMD_PING, NULL, nil (), PROXY_PROTO_SERVER);
      ping = false;
    }
    table_free (table);
  }
  main_set_tick (false);
  fd = proxy->sock->fd;
  LOG_DEBUG_ ("proxy loop $%d error %d (%lld seconds)\n", fd, err, (system_get_tick () - proxy_tick) / 1000);
  if (! proxy_check_required (true))
    proxy_ip = 0;
  proxy_latency = proxy_pong_value = 0;
  for (customer = proxy_customer_list; customer; customer = customer->next)
    if (customer->type == PROXY_TYPE_LOCAL)
      socket_cancel (customer->sock, err);
  proxy_proxy_customer = NULL;
  proxy_address = NULL;
  if (event_test_error_crypto (NULL, address, err) == SIM_CRYPT_BAD_TABLE)
    proxy_blacklist (NULL, ip, proxyport);
  /*pth_sleep_ (1);*/
  proxy_customer_release (proxy, (err = proxy->sock->err) == SIM_OK ? SIM_PROXY_END_PROXY : err);
  if (err == SIM_PROXY_DROPPED) {
    err = SIM_NO_ERROR;
  } else if (err == SIM_OK || err == SIM_PROXY_RECONNECT) {
    err = SIM_OK;
    if (err2 != SIM_OK || (simnumber) system_get_tick () - proxy_tick < param_get_number ("proxy.reconnect") * 1000)
      err = SIM_NO_ERROR; /* could not connect to proxy */
  } else if (err == SIM_PROXY_NOT_NEEDED && ! (contact_list.test & 0x8000))
    client_send_proxy ();
  nat_traverse_reset (NULL, err);
  LOG_API_DEBUG_ ("$%d error %d\n", fd, err);
  if (msec && err == SIM_NO_ERROR)
    proxy_cancel_probe_ (senderip, (int) ((msec - ((simnumber) system_get_tick () - proxy_tick)) / 1000) + 1);
  proxy_tick = 0;
  return err;
}

static int proxy_loop_server_ (simcustomer server, unsigned ownip) { /* read from server write to client */
  int err = SIM_PROXY_NOT_SERVING;
  int remeasure = param_get_number ("proxy.measure") * 1000, require = param_get_number ("proxy.require");
  int measure = server->flags & PROXY_FLAG_LOCAL || server->ip == ownip ? 0 : limit_get_param (LIMIT_PARAM_MEASURE);
  simunsigned tick = 0, rcvd = 0, sent = 0;
  simbool ping = false;
  simtype table = nil ();
  LOG_API_DEBUG_ ("$%d (measure = %d)\n", server->sock->fd, measure);
  if (simself.flags & SIM_STATUS_FLAG_DHT_OUT || (require < -1 && sim_network_check_local (server->ip)))
    if (! proxy_check_required (false)) {
      if ((err = ssl_handshake_server_ (server->sock, SSL_HANDSHAKE_SERVER)) != SIM_OK) {
        LOG_DEBUG_ ("handshake $%d error: %s\n", server->sock->fd, convert_error (err, false));
      } else if ((server->contact = contact_list_find_address (server->sock->master->from.ptr)) == contact_list.me) {
        err = SIM_CLIENT_SELF;
      } else if ((err = limit_test_server (server)) == SIM_OK)
        if ((err = sim_crypt_handshake (server->sock->master)) == SIM_OK)
          crypt_open_socket (server->sock, 3, 3, 0, 0, true, true);
    }
  if (err == SIM_OK) {
    simcustomer dead = proxy_find_server (server->sock->master->from.ptr);
    crypt_close_socket (server->sock, true);
    if (proxy_customer_acquire (dead)) {
      LOG_DEBUG_ ("dropping $%d duplicate customer %s\n", dead->sock->fd, server->sock->master->from.str);
      if ((err = proxy_send_cmd_ (dead->sock, SIM_SERVER_CMD_HANDSHAKE, SIM_SERVER_CMD_HANDSHAKE_ERROR,
                                  number_new (SIM_PROXY_RECONNECT), PROXY_PROTO_SERVER)) == SIM_OK)
        err = limit_test_server (server);
      proxy_cancel_server (dead, err != SIM_OK ? err : SIM_PROXY_RECONNECT);
      proxy_customer_release (dead, SIM_OK);
    } else
      err = limit_test_server_ip (server);
  }
  if (err == SIM_OK) {
    LOG_INFO_ ("accepted $%d connection from %s:%d customer '%s'\n", server->sock->fd,
               network_convert_ip (server->ip), server->port, CONTACT_GET_NICK (server->sock->master->from.ptr));
    server->sock->master->to = pointer_new (PROXY_ADDRESS_CONTROL);
    server->sock->rcvtimeout = param_get_number ("proxy.recv") + 2;
    server->type = PROXY_TYPE_SERVER;
    server->reversed = server->announce = tick = system_get_tick ();
    server->mode = MAIN_MODE_ACTIVE;
    if (! server->contact || server->contact->auth < CONTACT_AUTH_ACCEPTED)
      server->mode = MAIN_MODE_PASSIVE;
    table = proxy_new_cmd (SIM_SERVER_CMD_HANDSHAKE, SIM_SERVER_CMD_HANDSHAKE_ERROR, number_new (SIM_OK), true);
    server_add_ip (table, NULL, false, true);
    rcvd = SOCKET_GET_STAT_RECEIVED (server->sock), sent = SOCKET_GET_STAT_SENT (server->sock);
    err = socket_send_table_ (server->sock, table, SOCKET_SEND_TCP, NULL);
    table_free (table);
  }
  while (err == SIM_OK) {
    unsigned rcvbytes = 0, len = 0;
    if ((err = server->error) != SIM_OK) {
      server->error = SIM_OK;
      err = proxy_send_cmd_ (server->sock, SIM_SERVER_CMD_HANDSHAKE, SIM_SERVER_CMD_HANDSHAKE_ERROR,
                             number_new (err), PROXY_PROTO_SERVER);
    }
    if (err == SIM_OK && measure && ! server->number)
      if (! server->sock->pinged || (remeasure && (simnumber) tick - server->sock->pinged >= remeasure)) {
        if (ping) {
          rcvd = SOCKET_GET_STAT_RECEIVED (server->sock), sent = SOCKET_GET_STAT_SENT (server->sock);
          tick = system_get_tick ();
        }
        if (limit_start_ping (server->sock, server->contact, tick, rcvd, sent, measure * 1000) && ping) {
          err = proxy_send_cmd_ (server->sock, SIM_SERVER_CMD_PING, NULL, nil (), PROXY_PROTO_SERVER);
          ping = false;
        }
      }
    if (err == SIM_OK)
      err = _socket_recv_table_ (server->sock, proxy_proto_server, nil (), &table, &rcvbytes, &len,
                                 __FUNCTION__, __LINE__);
    tick = system_get_tick ();
    if (err == SIM_OK) {
      simtype cmd, data = table_get_string (table, SIM_SERVER_DATA);
      if (data.typ != SIMNIL ||
          ! string_check_diff (cmd = table_get_string (table, SIM_SERVER_CMD), SIM_SERVER_CMD_END)) {
        simnumber num = table_get_number (table, SIM_SERVER_DATA_NUMBER);
        simcustomer client = proxy_find_client (server->sock->master->from.ptr, num, PROXY_TYPE_CLIENT);
        if ((client = proxy_customer_acquire (client)) != NULL && ! (client->flags & PROXY_FLAG_INCOMING) && data.len)
          server->sock->rcvbytes -= rcvbytes, server->sock->rcvheaders -= len, server->sock->sndheaders -= len;
        if (! client) {
          LOG_WARN_ ("received $%d to unknown client #%lld (%u bytes)\n", server->sock->fd, num, data.len);
        } else if (! data.len || (err = socket_send_proxy_ (client->sock, data, SOCKET_SEND_SSL)) != SIM_OK) {
          if (! data.len) {
            simtype e = number_new (table_get_number (table, SIM_SERVER_CMD_END_ERROR));
            proxy_customer_reset (server, client);
            client->number = 0;
            err = proxy_send_cmd_ (client->sock, SIM_CLIENT_CMD_END, SIM_CLIENT_CMD_END_ERROR, e, PROXY_PROTO_CLIENT);
          }
          LOG_XTRA_ ("EOF $%d #%lld ($%d error %lld:%d)\n", server->sock->fd, num,
                     client->sock->fd, table_get_number (table, SIM_SERVER_CMD_END_ERROR), err);
          proxy_customer_release (client, err == SIM_OK ? SIM_PROXY_END_SERVER : err);
          err = SIM_OK;
        } else
          proxy_customer_release (client, SIM_OK);
      } else if (! string_check_diff (cmd, SIM_SERVER_CMD_PING)) {
        simtype pong = table_get (table, SIM_SERVER_CMD_PING_PONG);
        const char *pingpong = pong.typ == SIMNUMBER ? SIM_SERVER_CMD_PING_PONG : NULL;
        err = proxy_send_cmd_ (server->sock, SIM_SERVER_CMD_PONG, pingpong, pong, PROXY_PROTO_SERVER);
      } else if (! string_check_diff (cmd, SIM_SERVER_CMD_SHAKEHAND)) {
        int port[2];
        simtype limits = table, ports = table_get_array_number (table, SIM_SERVER_CMD_SHAKEHAND_PORT);
        simcustomer client = limit_exec_request (server, &limits);
        if (sim_network_parse_ips_ports (nil (), ports, SIM_ARRAY_SIZE (port), NULL, port) < 0) {
          LOG_WARN_SIMTYPE_ (ports, 0, "reverse $%d invalid ports ", server->sock->fd);
        } else if ((port[0] || port[1]) && limit_get_param (LIMIT_PARAM_REVERSE))
          if (! sim_network_check_local (server->ip) && ! client_check_local (server->ip, true))
            server->realport = port[1] ? port[1] : port[0];
        if (client)
          proxy_send_cmd_ (client->sock, SIM_CLIENT_CMD_PONG, NULL, number_new (0), PROXY_PROTO_CLIENT);
        if (limits.typ != SIMNIL)
          err = socket_send_table_ (server->sock, limits, SOCKET_SEND_TCP, NULL);
        table_free (limits);
      } else if (! string_check_diff (cmd, SIM_SERVER_CMD_PONG)) {
        if ((err = limit_ping_ (server->sock, server->contact, tick, rcvbytes)) == SIM_LIMIT_NO_ERROR) {
          err = SIM_OK;
          table_free (table);
          continue;
        }
        if (err == SIM_NO_ERROR) {
          server->sock->pinged = tick;
          err = SIM_OK;
        }
      } else
        LOG_INFO_ ("unknown command $%d: %s\n", server->sock->fd, cmd.str);
      ping = true;
    } else if (err == SIM_SOCKET_RECV_TIMEOUT) {
      rcvd = SOCKET_GET_STAT_RECEIVED (server->sock), sent = SOCKET_GET_STAT_SENT (server->sock);
      if (ping)
        err = proxy_send_cmd_ (server->sock, SIM_SERVER_CMD_PING, NULL, nil (), PROXY_PROTO_SERVER);
      ping = false;
    }
    if (err == SIM_OK) {
      if (server->realport) {
        if (server->reversed > 1 && tick - server->reversed >= limit_get_param (LIMIT_PARAM_REVERSE)) {
          if (pth_thread_spawn (thread_backwards_, proxy_customer_acquire (server), NULL, server->sock->fd) != SIM_OK) {
            server->realport = 0;
            proxy_customer_release (server, SIM_OK);
          } else
            server->reversed = 0;
        } else
          LOG_XTRA_ ("reverse $%d skip (%lld ms)\n", server->sock->fd,
                     (server->reversed <= 1 ? 0 : limit_get_param (LIMIT_PARAM_REVERSE) - tick) + server->reversed);
      }
      err = limit_wait_server_ (server, NULL);
    }
    table_free (table);
  }
  event_test_error_crypto (NULL, server->sock->master->from.ptr, err);
  limit_stop_ping (server->sock);
  if (server->type == PROXY_TYPE_SERVER) {
    proxy_cancel_server (server, err);
    limit_wait_server_ (server, NULL);
  }
  LOG_API_DEBUG_ ("$%d error %d\n", server->sock->fd, err);
  return err;
}

static int proxy_loop_client_ (simcustomer client) { /* read from client write to server */
  int err;
  simbool ping = true;
  unsigned len = 0, num;
  simtype table, buf;
  simcustomer server = proxy_customer_acquire (proxy_find_server (client->sock->master->to.ptr));
  char ipstr[100];
  if (! server) {
    LOG_WARN_ ("server $%d no customer '%s'\n", client->sock->fd, CONTACT_GET_NICK (client->sock->master->to.ptr));
    return SIM_PROXY_NO_CUSTOMER;
  }
  if ((err = limit_test_handshake (client, server, proxy_stat_customers)) == SIM_OK)
    err = limit_test_client_ip (client, server);
  if (err == SIM_OK) {
    do {
      if (++len > PROXY_NUMBER_MAX - 1536) {
        LOG_WARN_ ("server $%d no client '%s'\n", client->sock->fd, CONTACT_GET_NICK (client->sock->master->to.ptr));
        return SIM_PROXY_NO_CLIENTS;
      }
    } while (proxy_find_client (NULL, num = (unsigned) proxy_get_random (), PROXY_TYPE_CLIENT));
    client->contact = server->contact;
    client->type = PROXY_TYPE_CLIENT;
    client->number = num;
    client->realport = -1;
    LOG_API_DEBUG_ ("$%d #%d %s:%d -> $%d '%s'\n", client->sock->fd, client->number, network_convert_ip (client->ip),
                    client->port, server->sock->fd, CONTACT_GET_NICK (server->sock->master->from.ptr));
    table = proxy_new_cmd (SIM_SERVER_CMD_CONNECT, SIM_SERVER_CMD_CONNECT_NUMBER, number_new (num), true);
    table_add_number (table, SIM_SERVER_CMD_CONNECT_VERSION, client->version);
    if (client->flags & PROXY_FLAG_INCOMING)
      table_add (table, SIM_SERVER_CMD_CONNECT_IP, string_copy (network_convert_ip (client->ip)));
    table_add_number (table, SIM_SERVER_CMD_CONNECT_BUFSIZE, param_get_number ("socket.tcp.proxy"));
    err = socket_send_table_ (server->sock, table, SOCKET_SEND_TCP, NULL);
    table_free (table);
    client->sock->flags |= SOCKET_FLAG_PONG;
    client->sock->rcvtimeout = param_get_number ("proxy.recv");
    while (err == SIM_OK && (err = limit_wait_client_ (client, server)) == SIM_OK)
      if ((err = socket_recv_proxy_ (client->sock, SOCKET_RECV_TCP, &buf)) == SIM_OK) {
        ping = true;
        if (buf.typ != SIMNIL) {
          unsigned sndbytes = 0;
          table_add_number (table = table_new_short (2), SIM_SERVER_DATA_NUMBER, num);
          table_add (table, SIM_SERVER_DATA, buf);
          err = socket_send_table_ (server->sock, table, SOCKET_SEND_SSL, &sndbytes);
          table_free (table);
          if (! (client->flags & PROXY_FLAG_INCOMING)) {
            len = SOCKET_CALC_OVERHEAD_TCP (sndbytes);
            server->sock->rcvheaders -= len, server->sock->sndbytes -= sndbytes, server->sock->sndheaders -= len;
          }
        }
      } else if (err == SIM_SOCKET_RECV_TIMEOUT && ping) {
        ping = false;
        client->sock->flags &= ~SOCKET_FLAG_PONG;
        err = proxy_send_cmd_ (client->sock, SIM_CLIENT_CMD_PING, NULL, nil (), PROXY_PROTO_CLIENT);
      }
    if (client->number)
      proxy_send_end_ (server, num, err);
  }
  LOG_API_DEBUG_ ("$%d error %d\n", client->sock->fd, err);
  proxy_customer_release (server, SIM_OK);
  return event_test_error_crypto (NULL, strcpy (ipstr, network_convert_ip (client->ip)), err);
}

static int proxy_loop_traverser_ (simcustomer traverser) {
  int fin = param_get_number ("proxy.fin"), minfin = param_get_min ("proxy.fin", 0), err = SIM_SOCKET_RECV_TIMEOUT;
  if (fin > minfin) {
    traverser->type = PROXY_TYPE_ANONYMOUS;
    LOG_API_DEBUG_ ("$%d\n", traverser->sock->fd);
    while (fin-- && err == SIM_SOCKET_RECV_TIMEOUT) {
#ifndef _WIN32
      do {
#endif
        simsocket sock = traverser->sock;
        err = socket_select_readable_ (sock->fd, 1, 0);
        err = err < 0 ? socket_get_errno () : sock->err ? sock->err : err ? SIM_OK : SIM_SOCKET_RECV_TIMEOUT;
#ifndef _WIN32
      } while (err == EINTR);
#endif
    }
    LOG_API_DEBUG_ ("$%d error %d\n", traverser->sock->fd, err);
    if (fin < 0)
      LOG_WARN_ ("traverser $%d timeout (error %d)\n", traverser->sock->fd, err);
  }
  return err;
}

static int _proxy_handshake_socket (simsocket sock, const simtype input, const char *address, simtype *table,
                                    const char *file) {
  int err;
  sock->master->to = pointer_new (address);
  crypt_open_socket (sock, 3, 3, 0, 0, true, true);
  if ((err = _socket_recv_table (sock, proxy_proto_handshake, nil (), sock->crypt.decryptseq += 2,
                                 input, SOCKET_RECV_TCP, table, file, 0)) != SIM_OK) {
    crypt_close_socket (sock, false);
    sock->master->to = nil ();
  }
  return err;
}

#define proxy_handshake_socket(sock, input, address, table) \
  _proxy_handshake_socket (sock, input, address, table, __FUNCTION__)

static int proxy_handshake_customer_ (simcustomer customer, unsigned *ip) {
  simbool crypt = true;
  unsigned i;
  simcustomer tmp;
  simtype version = nil (), reply = table_new (2), buf;
  const char *addr = NULL;
  int err = socket_recv_ssl_ (customer->sock, SOCKET_RECV_TCP, &buf), vers = 0;
  if (err == SIM_OK) {
    err = proxy_handshake_socket (customer->sock, buf, addr = PROXY_ADDRESS_TRAVERSER, &version);
    if (err == SIM_CRYPT_BAD_PACKET)
      if ((err = proxy_handshake_socket (customer->sock, buf, addr = contact_list.me->addr, &version)) == SIM_OK)
        crypt = false;
    if (err == SIM_CRYPT_BAD_PACKET)
      err = proxy_handshake_socket (customer->sock, buf, addr = PROXY_ADDRESS_CONTROL, &version);
    for (tmp = proxy_customer_list; tmp && err == SIM_CRYPT_BAD_PACKET; tmp = tmp->next)
      if (tmp->type == PROXY_TYPE_SERVER && tmp->sock->err == SIM_OK) {
        err = proxy_handshake_socket (customer->sock, buf, addr = tmp->sock->master->from.ptr, &version);
        if (err == SIM_OK) {
          vers = tmp->version;
          customer->sock->master->to = nil ();
          err = limit_test_handshake (customer, tmp, NULL);
        }
      }
    if (err != SIM_OK) {
      if (err == SIM_CRYPT_BAD_PACKET)
        err = SIM_PROXY_NO_CUSTOMER;
      LOG_NOTE_ ("server $%d error %d\n", customer->sock->fd, err);
      addr = NULL;
    } else {
      if (customer->sock->master->to.typ == SIMNIL)
        customer->sock->master->to = string_copy (addr);
      LOG_INFO_ ("server $%d to customer '%s'\n", customer->sock->fd, CONTACT_GET_NICK (addr));
    }
  }
  string_free (buf);
  if (err == SIM_OK) {
    simnumber ver = table_get_number (version, SIM_PROXY_REQUEST), identifier = 0;
    if (ver >= SIM_PROTO_PROXY_VERSION_MIN) {
      *ip = sim_network_parse_ip (table_get_pointer (version, SIM_PROXY_REQUEST_IP));
      LOG_DEBUG_ ("handshake $%d succeeded (version %lld) own ip %s:%lld\n", customer->sock->fd,
                  ver, network_convert_ip (*ip), table_get_number (version, SIM_PROXY_REQUEST_PORT));
      table_add (reply, SIM_PROXY_REPLY_IP, string_copy (network_convert_ip (customer->ip)));
      table_add_number (reply, SIM_PROXY_REPLY_PORT, customer->port);
      table_add_number (reply, SIM_PROXY_REPLY, ver > SIM_PROTO_PROXY_VERSION_MAX ? SIM_PROTO_PROXY_VERSION_MAX : ver);
      if (crypt) {
        simtype ciphers = table_get_array_number (version, SIM_PROXY_REQUEST_CIPHER);
        sim_crypt_cipher_get (3, NULL, &identifier);
        for (i = 1; i <= ciphers.len; i++)
          if (ciphers.arr[i].num == identifier)
            break;
        if (i > ciphers.len) {
          err = SIM_CRYPT_BAD_CIPHER;
        } else if (strcmp (addr, PROXY_ADDRESS_TRAVERSER)) {
          if (strcmp (addr, PROXY_ADDRESS_CONTROL))
            table_add (reply, SIM_PROXY_REPLY_SPEED, limit_new_cmd (LIMIT_CMD_REPLY));
        } else if (! param_get_number ("proxy.anonymous")) {
          for (tmp = proxy_customer_list; tmp; tmp = tmp->next)
            if (tmp->ip == customer->ip && (tmp->type == PROXY_TYPE_SERVER || tmp->type == PROXY_TYPE_CLIENT))
              break;
          if (! tmp)
            err = SIM_PROXY_NO_CUSTOMER;
        }
        if (vers)
          table_add_number (reply, SIM_PROXY_REPLY_VERSION, vers);
      }
      table_add_number (reply, SIM_PROXY_REPLY_CIPHER, identifier);
      if (err == SIM_OK && (err = crypt_handshake_server_ (customer->sock, reply, nil ())) == SIM_OK)
        customer->version = (int) ver;
    } else {
      LOG_NOTE_ ("handshake $%d failed (version %lld)\n", customer->sock->fd, ver);
      table_add_number (reply, SIM_PROXY_REPLY, SIM_PROTO_PROXY_VERSION_MIN);
      if ((err = socket_send_table_ (customer->sock, reply, SOCKET_SEND_TCP, NULL)) == SIM_OK)
        err = SIM_CLIENT_BAD_VERSION;
    }
  }
  table_free (version);
  table_free (reply);
  return event_test_error_crypto (NULL, addr, err);
}

int proxy_handshake_server_ (simcustomer customer, unsigned *ip) {
  unsigned ownip = 0;
  int port, err = proxy_customer_check_local (customer, ip, &port);
  limit_test_customer_ip (customer, *ip);
  if (err == SIM_OK) {
    customer->ip = *ip, customer->port = port;
    if (sim_network_check_local (*ip) || client_check_local (*ip, false))
      customer->flags |= PROXY_FLAG_LOCAL;
  }
  limit_wait_handshake_ (customer, proxy_stat_customers);
  if (err == SIM_OK) {
    if (client_find_local (*ip, port) || proxy_find_self (*ip, port) || proxy_initializing_flag) {
      customer->sock->flags |= SOCKET_FLAG_LOCAL;
      proxy_customer_ignore (customer);
      proxy_initializing_flag = false;
    }
    if (param_get_number ("net.tor.port") > 0) { /* also prevents local probes from succeeding */
      LOG_WARN_ ("server $%d leaked ip to %s\n", customer->sock->fd, network_convert_ip (err == SIM_OK ? *ip : 0));
      return SIM_PROXY_LOCAL;
    }
    if ((err = ssl_handshake_server_ (customer->sock, SSL_HANDSHAKE_ANONYMOUS)) == SIM_OK)
      err = proxy_handshake_customer_ (customer, &ownip);
    if (err == SIM_OK) {
      if (string_check_diff (customer->sock->master->to, contact_list.me->addr)) {
        if (! string_check_diff (customer->sock->master->to, PROXY_ADDRESS_TRAVERSER)) {
          err = proxy_loop_traverser_ (customer);
        } else if (string_check_diff (customer->sock->master->to, PROXY_ADDRESS_CONTROL)) {
          err = proxy_loop_client_ (customer);
        } else
          err = proxy_loop_server_ (customer, ownip);
        return err == SIM_OK ? SIM_PROXY_LOCAL : err; /* make sure its not SIM_OK */
      }
      if (simself.status == SIM_STATUS_OFF) {
        err = SIM_CLIENT_CANCELLED;
      } else if ((err = ssl_handshake_server_ (customer->sock, SSL_HANDSHAKE_CLIENT)) == SIM_OK) {
        crypt_close_socket (customer->sock, true);
        proxy_customer_ignore (customer);
      }
    }
  } else if (err == SIM_PROXY_LOCAL) { /* skip proxy handshake because it has already been done by remote proxy */
    socket_check_server (customer->sock)->flags |= PROXY_FLAG_ACCEPTED;
    proxy_customer_ignore (customer);
    socket_set_buffer (customer->sock, param_get_number ("socket.tcp.local"), false);
    if ((err = ssl_handshake_server_ (customer->sock, SSL_HANDSHAKE_LOCAL)) == SIM_OK)
      customer->sock->master->to = pointer_new (contact_list.me->addr);
    socket_check_server (customer->sock)->flags |= PROXY_FLAG_COMPLETED;
  }
  if (err != SIM_OK)
    LOG_DEBUG_ ("handshake $%d error: %s\n", customer->sock->fd, convert_error (err, false));
  if (err == SIM_SSL_ERROR && customer->sock->master && customer->sock->master->from.typ != SIMNIL)
    if (! string_check_diff (customer->sock->master->to, contact_list.me->addr))
      err = SIM_SERVER_CONTACT;
  return err;
}

static void proxy_log_stat (const char *module, int level) {
  static const char *proxy_stat_names[CONTACT_STAT_COUNT + 1] = { "sent", "rcvd", "uptime", "connections" };
  simnumber duration = PROXY_GET_STAT (CONTACT_STAT_DURATION);
  unsigned i;
  for (i = 0; i <= CONTACT_STAT_COUNT; i++) {
    simnumber n = proxy_get_stat (PROXY_STATS_NOW, i), m = proxy_get_stat (PROXY_STATS_THIS, i);
    if (m || n) {
      log_any_ (module, level, "%s: %lld", proxy_stat_names[i], i == CONTACT_STAT_DURATION ? m / 1000 : m);
      log_any_ (module, level, " + %lld", i == CONTACT_STAT_DURATION ? n / 1000 : n);
      if (i == CONTACT_STAT_RECEIVED || i == CONTACT_STAT_SENT) {
        simnumber speed = duration ? (m + n) * 15625 / (16 * duration) : 0;
        log_any_ (module, level, " (%lld.%03lld KB/sec)", speed / 1000, speed % 1000);
      } else if (i == CONTACT_STAT_DURATION && proxy_tick)
        log_any_ (module, level, " (connected = %lld)", (system_get_tick () - proxy_tick) / 1000);
      log_any_ (module, level, "\n");
    }
  }
}

void proxy_log_customers (const char *module, int level) {
  static const char *proxy_type_names[] = {
    "new", "client", "server", "traverser-in", "traverser-out", "reverse", "local", "proxy"
  };
  int type = SIM_ARRAY_SIZE (proxy_type_names), limit, handshakes, port;
  unsigned ip;
  while (type--) {
    simbool first = true;
    simcustomer customer;
    for (customer = proxy_customer_list; customer; customer = customer->next)
      if (customer->type == type) {
        int flags = ! (customer->flags & 7) && customer->sock->flags & SOCKET_FLAG_LOCAL ? 8 : customer->flags & 7;
        if (type == PROXY_TYPE_CLIENT) {
          simcustomer server = proxy_find_server (customer->sock->master->to.ptr);
          if (server && customer->number != server->number)
            flags = server->flags & PROXY_FLAG_SPEED_TALK ? (flags & 7) | PROXY_FLAG_SPEED_STOP : flags;
        }
        if (first) {
          log_any_ (module, level, "[%s]\n", proxy_type_names[type]);
          first = false;
        }
        log_any_ (module, level, "%c", customer->sock->err != SIM_OK ? '!' : "$+=-****%"[flags]);
        log_any_ (module, level, "%d 0x%02X #%d", customer->sock->fd, customer->flags, customer->number);
        log_any_ (module, level, " %s", network_convert_ip (customer->realip ? customer->realip : customer->ip));
        log_any_ (module, level, ":%d", customer->port);
        if (customer->sock->master) {
          if (customer->sock->master->from.ptr && strcmp (customer->sock->master->from.ptr, PROXY_ADDRESS_CONTROL))
            if (strcmp (customer->sock->master->from.ptr, PROXY_ADDRESS_TRAVERSER))
              log_any_ (module, level, " %s", module ? "" : CONTACT_GET_NICK (customer->sock->master->from.ptr));
          if (string_check_diff (customer->sock->master->to, PROXY_ADDRESS_CONTROL))
            if (string_check_diff (customer->sock->master->to, PROXY_ADDRESS_TRAVERSER))
              log_any_ (module, level, " -> %s", module ? "" : CONTACT_GET_NICK (customer->sock->master->to.ptr));
        }
        if (type != PROXY_TYPE_LOCAL) {
          int i;
          struct _socket_stat tmp;
          SOCKET_INIT_STAT (&tmp);
          SOCKET_ADD_STAT (&tmp, customer->sock);
          for (i = 0; i <= (type == PROXY_TYPE_PROXY || type == PROXY_TYPE_SERVER); i++) {
            log_any_ (module, level, "%s (%lld.%03d+%lld.%03d : %lld.%03d+%lld.%03d)", i ? " ->" : "",
                      tmp.rcvbytes / 1000, (int) (tmp.rcvbytes % 1000),
                      tmp.rcvheaders / 1000, (int) (tmp.rcvheaders % 1000),
                      tmp.sndbytes / 1000, (int) (tmp.sndbytes % 1000),
                      tmp.sndheaders / 1000, (int) (tmp.sndheaders % 1000));
            SOCKET_ADD_STAT (&tmp, &customer->sock->old);
          }
        }
        if (type == PROXY_TYPE_SERVER) {
          simnumber rcvd, sent, created = proxy_get_stat_server (customer, &rcvd, &sent);
          simnumber rate = created ? (rcvd + sent) * 15625 / (16 * (system_get_tick () - created + 1)) : 0;
          limit = limit_get_speed_server (customer);
          log_any_ (module, level, " %lld.%03lld / %d.%03d KB/sec",
                    rate / 1000, rate % 1000, limit / 1024, limit % 1024 * 1000 / 1024);
        }
        log_any_ (module, level, "\n");
      }
  }
  proxy_log_stat (module, level);
  if (proxy_ip)
    log_any_ (module, level, "IP = %s", network_convert_ip (proxy_ip));
  if (proxy_get_ip_proxy (NULL, &ip, &port)) {
    log_any_ (module, level, " -> %s:%d", network_convert_ip (ip), port);
    if (proxy_address)
      log_any_ (module, level, ":%s", CONTACT_GET_NICK (proxy_address));
  }
  if (proxy_ip || proxy_proxy_customer)
    log_any_ (module, level, "\n");
  if (contact_list.me) {
    limit = limit_get_speed_limit (param_get_number ("limit.server") << 10);
    if ((handshakes = param_get_number ("limit.handshakes")) != 0 && proxy_stat_customers[PROXY_STAT_TOTAL].sock) {
      simnumber rcvd, sent, created = proxy_get_stat_server (&proxy_stat_customers[PROXY_STAT_TOTAL], &rcvd, &sent);
      proxy_get_stat_server (&proxy_stat_customers[PROXY_STAT_HANDSHAKES], &rcvd, &sent);
      if (created)
        log_any_ (module, level, "handshakes = %lld/%d bytes/sec\n",
                  (rcvd + sent) * 1000 / (system_get_tick () - created + 1), limit * handshakes / 100);
    }
    if ((int) limit_get_param (LIMIT_PARAM_RECV) >= 0)
      log_any_ (module, level, "audio speed = %d/%d bytes/sec\n",
                audio_get_param (AUDIO_PARAM_SPEED), limit_get_param (LIMIT_PARAM_RECV));
    if (param_get_number ("limit.measure"))
      log_any_ (module, level, "max speed = %d/%d bytes/sec\n", limit, limit_get_param (LIMIT_PARAM_MAX));
    if (proxy_proxy_customer && limit_get_param (LIMIT_PARAM_SEND))
      log_any_ (module, level, "proxy speed = %d bytes/sec\n", limit_get_param (LIMIT_PARAM_SEND));
  }
}

void proxy_init_stat (int init) {
  simcustomer customer = &proxy_stat_customers[PROXY_STAT_TOTAL];
  if (init) {
    limit_reinit (init > 0);
    if (init > 0) {
      if (! proxy_stat_customers[PROXY_STAT_TOTAL].sock) {
        customer->sock = socket_new (sim_new (sizeof (*customer->sock)));
        proxy_stat_customers[PROXY_STAT_HANDSHAKES].sock = socket_new (sim_new (sizeof (*customer->sock)));
      } else
        SOCKET_INIT_STAT (&proxy_stat_customers[PROXY_STAT_HANDSHAKES].sock->old);
      proxy_stat_customers[PROXY_STAT_HANDSHAKES].flags = 0;
      *proxy_stat_customers[PROXY_STAT_HANDSHAKES].bytes = *customer->bytes = 0;
      proxy_stat_customers[PROXY_STAT_HANDSHAKES].sock->created = customer->sock->created = system_get_tick ();
    }
    proxy_localip[2] = sim_network_get_default ();
  } else if (customer->sock && customer->sock->created) {
    proxy_duration += system_get_tick () - customer->sock->created;
    customer->sock->created = 0;
  }
}

int proxy_init (simbool init) {
  int err = SIM_OK;
  if (! init || simself.status != SIM_STATUS_OFF) {
    struct _socket sock;
    if ((err = socket_open (&sock, SOCKET_NO_SESSION)) == SIM_OK)
      if ((err = proxy_connect_local (&sock)) == SIM_OK)
        proxy_initializing_flag = true;
    socket_close (&sock, NULL);
    if (err != SIM_OK)
      LOG_WARN_ ("failed to connect to localhost (error %d)\n", err);
  }
  if (init) {
    SOCKET_INIT_STAT (&proxy_stat_customers[PROXY_STAT_TOTAL].sock->old);
    proxy_duration = proxy_connections[PROXY_STATS_NOW] = proxy_connections[PROXY_STATS_THIS] = 0;
    proxy_latency = proxy_pong_value = proxy_tick = proxy_ip = 0;
  }
  if (! proxy_proto_server.ptr) {
    proxy_proto_server =
      table_new_long_const (37, SIM_NEW_STRING (SIM_SERVER_CMD), SIM_NEW_NUMBER (SIM_SERVER_CMD_HANDSHAKE_ERROR),
                            SIM_NEW_STRING (SIM_SERVER_CMD_HANDSHAKE_PROXY_IP),
                            SIM_NEW_NUMBER (SIM_SERVER_CMD_HANDSHAKE_PROXY_PORT),
                            SIM_NEW_NUMBERS (SIM_SERVER_CMD_SHAKEHAND_PORT, 2),
                            SIM_NEW_NUMBERS (SIM_SERVER_CMD_SHAKEHAND_SPEED, 2),
                            SIM_NEW_NUMBER (SIM_SERVER_CMD_SHAKEHAND_NUMBER),
                            SIM_NEW_NUMBER (SIM_SERVER_CMD_SHAKEHAND_SOCKETS),
                            SIM_NEW_NUMBER (SIM_SERVER_CMD_SHAKEHAND_TIMES),
                            SIM_NEW_NUMBER (SIM_SERVER_CMD_PING_PONG), SIM_NEW_STRING (SIM_SERVER_CMD_PING_PAD),
                            SIM_NEW_STRING (SIM_SERVER_CMD_PONG_PAD), SIM_NEW_NUMBER (SIM_SERVER_CMD_CONNECT_VERSION),
                            SIM_NEW_STRING (SIM_SERVER_CMD_CONNECT_IP), SIM_NEW_NUMBER (SIM_SERVER_CMD_CONNECT_NUMBER),
                            SIM_NEW_NUMBER (SIM_SERVER_CMD_CONNECT_BUFSIZE), SIM_NEW_NUMBER (SIM_SERVER_DATA_NUMBER),
                            SIM_NEW_NUMBER (SIM_SERVER_CMD_END_ERROR), NULL);
    proxy_proto_handshake =
      table_new_const (29, SIM_NEW_NUMBER (SIM_PROXY_REPLY), SIM_NEW_NUMBER (SIM_PROXY_REPLY_VERSION),
                       SIM_NEW_STRING (SIM_PROXY_REPLY_IP), SIM_NEW_NUMBER (SIM_PROXY_REPLY_PORT),
                       SIM_NEW_NUMBER (SIM_PROXY_REPLY_CIPHER), SIM_NEW_NUMBERS (SIM_PROXY_REPLY_SPEED, 3),
                       SIM_NEW_NUMBER (SIM_PROXY_REQUEST), SIM_NEW_STRING (SIM_PROXY_REQUEST_IP),
                       SIM_NEW_NUMBER (SIM_PROXY_REQUEST_PORT), SIM_NEW_NUMBERS (SIM_PROXY_REQUEST_CIPHER, 4), NULL);
    proxy_proto_udp = table_new_const (3, SIM_NEW_STRING (SIM_CMD_UDP), SIM_NEW_NUMBER (SIM_CMD_UDP_SRC_PORT), NULL);
  }
  return err;
}

void proxy_uninit (void) {
  simcustomer customers = proxy_stat_customers;
  if (proxy_proto_server.ptr) {
    LOG_CODE_DEBUG_ (proxy_log_stat (SIM_MODULE, SIM_LOG_DEBUG));
    table_free (proxy_proto_server);
    table_free (proxy_proto_handshake);
    table_free (proxy_proto_udp);
    proxy_proto_udp = proxy_proto_handshake = proxy_proto_server = number_new (0);
  }
  sim_free (customers[PROXY_STAT_TOTAL].sock, sizeof (*customers[PROXY_STAT_TOTAL].sock));
  sim_free (customers[PROXY_STAT_HANDSHAKES].sock, sizeof (*customers[PROXY_STAT_HANDSHAKES].sock));
  customers[PROXY_STAT_HANDSHAKES].sock = customers[PROXY_STAT_TOTAL].sock = NULL;
}
