/*
 * SPDX-License-Identifier: BSD-3-Clause
 *
 * https://opensource.org/license/bsd-3-clause
 *
 * Copyright (C) 2011, 2012, 2013 Citrix Systems
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the project nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/* recvmmsg() and struct mmsghdr require _GNU_SOURCE on Linux */
#if defined(__linux__) && !defined(_GNU_SOURCE)
#define _GNU_SOURCE
#endif

#include "apputils.h"
#include "mainrelay.h"
#include <errno.h>

#include "dtls_listener.h"
#include "ns_ioalib_impl.h"

#include "ns_turn_openssl.h"
#include "prom_server.h"

#include <pthread.h>
#include <stdint.h>

/* #define REQUEST_CLIENT_CERT */

///////////////////////////////////////////////////
#if defined(WINDOWS)
// TODO: test it!
/* Type to represent a port.  */
typedef uint16_t in_port_t;
#endif

#define FUNCSTART                                                                                                      \
  if (server && eve(server->verbose))                                                                                  \
  TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "%s:%d:start\n", __FUNCTION__, __LINE__)
#define FUNCEND                                                                                                        \
  if (server && eve(server->verbose))                                                                                  \
  TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "%s:%d:end\n", __FUNCTION__, __LINE__)

#define COOKIE_SECRET_LENGTH (32)

#define MAX_SINGLE_UDP_BATCH IOA_UDP_RECVMMSG_MAX_BATCH
#define MAX_RECVMMSG_BATCH MAX_SINGLE_UDP_BATCH

#if defined(__linux__) && defined(CMSG_SPACE)
#if defined(IP_RECVTTL) || defined(IP_TTL)
#define RECVMMSG_IPV4_TTL_CMSG_SZ CMSG_SPACE(sizeof(int))
#else
#define RECVMMSG_IPV4_TTL_CMSG_SZ 0
#endif
#if defined(IP_RECVTOS) || defined(IP_TOS)
#define RECVMMSG_IPV4_TOS_CMSG_SZ CMSG_SPACE(sizeof(int))
#else
#define RECVMMSG_IPV4_TOS_CMSG_SZ 0
#endif
#if defined(IPV6_RECVHOPLIMIT) || defined(IPV6_HOPLIMIT)
#define RECVMMSG_IPV6_TTL_CMSG_SZ CMSG_SPACE(sizeof(int))
#else
#define RECVMMSG_IPV6_TTL_CMSG_SZ 0
#endif
#if defined(IPV6_RECVTCLASS) || defined(IPV6_TCLASS)
#define RECVMMSG_IPV6_TOS_CMSG_SZ CMSG_SPACE(sizeof(int))
#else
#define RECVMMSG_IPV6_TOS_CMSG_SZ 0
#endif
#define RECVMMSG_IPV4_CMSG_SZ (RECVMMSG_IPV4_TTL_CMSG_SZ + RECVMMSG_IPV4_TOS_CMSG_SZ)
#define RECVMMSG_IPV6_CMSG_SZ (RECVMMSG_IPV6_TTL_CMSG_SZ + RECVMMSG_IPV6_TOS_CMSG_SZ)
#define RECVMMSG_CMSG_SZ                                                                                               \
  ((RECVMMSG_IPV4_CMSG_SZ > RECVMMSG_IPV6_CMSG_SZ) ? RECVMMSG_IPV4_CMSG_SZ : RECVMMSG_IPV6_CMSG_SZ)
#define RECVMMSG_CMSG_ALLOC_SZ ((RECVMMSG_CMSG_SZ) > 0 ? (RECVMMSG_CMSG_SZ) : 1)
#endif

#if !defined(WINDOWS)
_Thread_local uint32_t packetcounter = 0;
#else
static uint32_t packetcounter = 0;
#endif

typedef enum {
  UDP_PACKET_CLASS_INVALID = 0,
  UDP_PACKET_CLASS_STUN_OR_CHANNEL,
  UDP_PACKET_CLASS_DTLS_HANDSHAKE,
  UDP_PACKET_CLASS_DTLS_OTHER,
  UDP_PACKET_CLASS_OLD_STUN
} udp_packet_classification_t;

struct dtls_listener_relay_server_info {
  char ifname[1025];
  ioa_addr addr;
  ioa_engine_handle e;
  turn_turnserver *ts;
  int verbose;
  struct event *udp_listen_ev;
  ioa_socket_handle udp_listen_s;
  ur_addr_map *children_ss; /* map of socket children on remote addr */
  struct message_to_relay sm;
  size_t slen0;
  ioa_engine_new_connection_event_handler connect_cb;
#if defined(__linux__)
  struct dtls_listener_recvmmsg_state *recvmmsg_state;
#endif
};

#if defined(__linux__)
struct dtls_listener_recvmmsg_state {
  struct mmsghdr msgs[MAX_RECVMMSG_BATCH];
  struct iovec iovecs[MAX_RECVMMSG_BATCH];
  char cmsgs[MAX_RECVMMSG_BATCH][RECVMMSG_CMSG_ALLOC_SZ];
  ioa_addr src_addrs[MAX_RECVMMSG_BATCH];
  int ttls[MAX_RECVMMSG_BATCH];
  int toss[MAX_RECVMMSG_BATCH];
  udp_packet_classification_t packet_types[MAX_RECVMMSG_BATCH];
  ioa_network_buffer_handle elems[MAX_RECVMMSG_BATCH];
};
#endif

///////////// forward declarations ////////

static int create_server_socket(dtls_listener_relay_server_type *server, int report_creation, int sock_buf_size);
static int clean_server(dtls_listener_relay_server_type *server);
static int reopen_server_socket(dtls_listener_relay_server_type *server, evutil_socket_t fd);
static int create_new_connected_udp_socket(dtls_listener_relay_server_type *server, ioa_socket_handle s);

///////////// dtls message types //////////

int is_dtls_handshake_message(const unsigned char *buf, int len);
int is_dtls_data_message(const unsigned char *buf, int len);
int is_dtls_alert_message(const unsigned char *buf, int len);
int is_dtls_cipher_change_message(const unsigned char *buf, int len);
int get_dtls_version(const unsigned char *buf, int len);

int is_dtls_message(const unsigned char *buf, int len);

int is_dtls_handshake_message(const unsigned char *buf, int len) {
  return (buf && len > 3 && buf[0] == 0x16 && buf[1] == 0xfe && ((buf[2] == 0xff) || (buf[2] == 0xfd)));
}

int is_dtls_data_message(const unsigned char *buf, int len) {
  return (buf && len > 3 && buf[0] == 0x17 && buf[1] == 0xfe && ((buf[2] == 0xff) || (buf[2] == 0xfd)));
}

int is_dtls_alert_message(const unsigned char *buf, int len) {
  return (buf && len > 3 && buf[0] == 0x15 && buf[1] == 0xfe && ((buf[2] == 0xff) || (buf[2] == 0xfd)));
}

int is_dtls_cipher_change_message(const unsigned char *buf, int len) {
  return (buf && len > 3 && buf[0] == 0x14 && buf[1] == 0xfe && ((buf[2] == 0xff) || (buf[2] == 0xfd)));
}

int is_dtls_message(const unsigned char *buf, int len) {
  if (buf && (len > 3) && (buf[1]) == 0xfe && ((buf[2] == 0xff) || (buf[2] == 0xfd))) {
    switch (buf[0]) {
    case 0x14:
    case 0x15:
    case 0x16:
    case 0x17:
      return 1;
    default:;
    }
  }
  return 0;
}

/* 0 - 1.0, 1 - 1.2 */
int get_dtls_version(const unsigned char *buf, int len) {
  if (buf && (len > 3) && (buf[2] == 0xfd)) {
    return 1;
  }
  return 0;
}

static size_t print_packet_txt2pcap(uint64_t now, uint8_t *payload, size_t payload_length, uint8_t *txt2pcap,
                                    size_t txt2pcap_length) {
  div_t dv = div(now, 24 * 60 * 60 * 1000);
  dv = div(dv.rem, 60 * 60 * 1000);
  uint32_t hours = dv.quot;
  dv = div(dv.rem, 60 * 1000);
  uint32_t minutes = dv.quot;
  dv = div(dv.rem, 1000);
  uint32_t seconds = dv.quot;
  uint32_t ms = dv.rem;

  size_t index = 0;
  index =
      snprintf((char *)(txt2pcap + index), txt2pcap_length - index, "%02d:%02d:%02d.%03d", hours, minutes, seconds, ms);
  index += snprintf((char *)(txt2pcap + index), txt2pcap_length - index, " 0000");

  for (size_t i = 0; i < payload_length; i++) {
    int n = snprintf((char *)(txt2pcap + index), txt2pcap_length - index, " %02x", payload[i]);
    if (n < 0 || (size_t)n >= txt2pcap_length - index) {
      break;
    }
    index += (size_t)n;
  }
  index += snprintf((char *)(txt2pcap + index), txt2pcap_length - index, " # STUN_PACKET ");
  return index;
}

///////////// utils /////////////////////

#if DTLS_SUPPORTED

static unsigned char dtls_cookie_secret[COOKIE_SECRET_LENGTH];
static pthread_once_t dtls_cookie_secret_once = PTHREAD_ONCE_INIT;

static void init_dtls_cookie_secret(void) {
  if (RAND_bytes(dtls_cookie_secret, sizeof(dtls_cookie_secret)) == 1) {
    return;
  }

  for (size_t i = 0; i < sizeof(dtls_cookie_secret); ++i) {
    dtls_cookie_secret[i] = (unsigned char)turn_random_number();
  }
}

static int generate_cookie(SSL *ssl, unsigned char *cookie, unsigned int *cookie_len) {
  unsigned char buffer[sizeof(struct in6_addr) + sizeof(in_port_t)];
  unsigned char result[EVP_MAX_MD_SIZE];
  unsigned int length = 0;
  unsigned int resultlength;
  ioa_addr peer;

  pthread_once(&dtls_cookie_secret_once, init_dtls_cookie_secret);

  /* Read peer information */
  (void)BIO_dgram_get_peer(SSL_get_wbio(ssl), &peer);

  // TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO,"%s: family=%u(1)\n",__FUNCTION__,(unsigned)peer.ss.sa_family);

  /* Create buffer with peer's address and port */
  length = 0;
  switch (peer.ss.sa_family) {
  case AF_INET:
    length += sizeof(struct in_addr);
    break;
  case AF_INET6:
    length += sizeof(struct in6_addr);
    break;
  default:
    OPENSSL_assert(0);
    break;
  }
  length += sizeof(in_port_t);

  // TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO,"%s: family=%u(2)\n",__FUNCTION__,(unsigned)peer.ss.sa_family);

  switch (peer.ss.sa_family) {
  case AF_INET:
    memcpy(buffer, &peer.s4.sin_port, sizeof(in_port_t));
    memcpy(buffer + sizeof(peer.s4.sin_port), &peer.s4.sin_addr, sizeof(struct in_addr));
    break;
  case AF_INET6:
    memcpy(buffer, &peer.s6.sin6_port, sizeof(in_port_t));
    memcpy(buffer + sizeof(in_port_t), &peer.s6.sin6_addr, sizeof(struct in6_addr));
    break;
  default:
    OPENSSL_assert(0);
    break;
  }

  /* Calculate HMAC of buffer using the secret */
  HMAC(EVP_sha1(), (const void *)dtls_cookie_secret, sizeof(dtls_cookie_secret), (const unsigned char *)buffer, length,
       result, &resultlength);

  memcpy(cookie, result, resultlength);
  *cookie_len = resultlength;

  return 1;
}

static int verify_cookie(SSL *ssl, const unsigned char *cookie, unsigned int cookie_len) {
  unsigned int resultlength = 0;
  unsigned char result[COOKIE_SECRET_LENGTH];

  generate_cookie(ssl, result, &resultlength);

  if (cookie_len == resultlength && memcmp(result, cookie, resultlength) == 0) {
    // TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO,"%s: cookies are OK, length=%u\n",__FUNCTION__,cookie_len);
    return 1;
  } else {
    // TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO,"%s: cookies are OK, length=%u\n",__FUNCTION__,cookie_len);
    return 0;
  }
}

/////////////// io handlers ///////////////////

static ioa_socket_handle dtls_accept_client_connection(dtls_listener_relay_server_type *server, ioa_socket_handle sock,
                                                       SSL *ssl, ioa_addr *remote_addr, ioa_addr *local_addr,
                                                       ioa_network_buffer_handle nbh) {
  FUNCSTART;

  if (!server || !ssl) {
    return NULL;
  }

  const int rc = ssl_read(sock->fd, ssl, nbh, server->verbose);

  if (rc < 0) {
    return NULL;
  }

  addr_debug_print(server->verbose, remote_addr, "Accepted connection from");

  ioa_socket_handle ioas =
      create_ioa_socket_from_ssl(server->e, sock, ssl, DTLS_SOCKET, CLIENT_SOCKET, remote_addr, local_addr);

  if (ioas) {
    set_ioa_socket_buf_size(ioas, server->ts->sock_buf_size);

    addr_cpy(&(server->sm.m.sm.nd.src_addr), remote_addr);
    server->sm.m.sm.nd.recv_ttl = TTL_IGNORE;
    server->sm.m.sm.nd.recv_tos = TOS_IGNORE;
    server->sm.m.sm.s = ioas;
  } else {
    TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Cannot create ioa_socket from SSL\n");
  }

  FUNCEND;

  return ioas;
}

static ioa_socket_handle dtls_server_input_handler(dtls_listener_relay_server_type *server, ioa_socket_handle s,
                                                   ioa_network_buffer_handle nbh) {
  FUNCSTART;

  if (!server || !nbh) {
    return NULL;
  }

  SSL *connecting_ssl = NULL;

  BIO *wbio = NULL;
  struct timeval timeout;

  /* Create BIO */
  wbio = BIO_new_dgram(s->fd, BIO_NOCLOSE);
  (void)BIO_dgram_set_peer(wbio, (struct sockaddr *)&(server->sm.m.sm.nd.src_addr));

  /* Set and activate timeouts */
  timeout.tv_sec = DTLS_MAX_RECV_TIMEOUT;
  timeout.tv_usec = 0;
  BIO_ctrl(wbio, BIO_CTRL_DGRAM_SET_RECV_TIMEOUT, 0, &timeout);

  connecting_ssl = SSL_new(server->e->dtls_ctx);

  SSL_set_accept_state(connecting_ssl);

  SSL_set_bio(connecting_ssl, NULL, wbio);
  SSL_set_options(connecting_ssl, SSL_OP_COOKIE_EXCHANGE
#if defined(SSL_OP_NO_RENEGOTIATION)
                                      | SSL_OP_NO_RENEGOTIATION
#endif
  );
  SSL_set_max_cert_list(connecting_ssl, 655350);

  ioa_socket_handle rc =
      dtls_accept_client_connection(server, s, connecting_ssl, &(server->sm.m.sm.nd.src_addr), &(server->addr), nbh);

  if (!rc) {
    if (!(SSL_get_shutdown(connecting_ssl) & SSL_SENT_SHUTDOWN)) {
      SSL_set_shutdown(connecting_ssl, SSL_RECEIVED_SHUTDOWN);
      SSL_shutdown(connecting_ssl);
    }
    SSL_free(connecting_ssl);
  }

  return rc;
}

#endif

static int handle_udp_packet(dtls_listener_relay_server_type *server, struct message_to_relay *sm,
                             ioa_engine_handle ioa_eng, turn_turnserver *ts, udp_packet_classification_t packet_type) {
  const int verbose = ioa_eng->verbose;
  ioa_socket_handle s = sm->m.sm.s;

  ur_addr_map_value_type mvt = 0;
  if (!(server->children_ss)) {
    server->children_ss = (ur_addr_map *)allocate_super_memory_engine(server->e, sizeof(ur_addr_map));
    ur_addr_map_init(server->children_ss);
  }
  ur_addr_map *amap = server->children_ss;

  ioa_socket_handle chs = NULL;
  if ((ur_addr_map_get(amap, &(sm->m.sm.nd.src_addr), &mvt) > 0) && mvt) {
    chs = (ioa_socket_handle)mvt;
  }

  if (chs && !ioa_socket_tobeclosed(chs) && (chs->sockets_container == amap) && (chs->magic == SOCKET_MAGIC)) {
    s = chs;
    sm->m.sm.s = s;
    if (s->ssl) {
      const int sslret = ssl_read(s->fd, s->ssl, sm->m.sm.nd.nbh, verbose);
      if (sslret < 0) {
        ioa_network_buffer_delete(ioa_eng, sm->m.sm.nd.nbh);
        sm->m.sm.nd.nbh = NULL;
        ts_ur_super_session *ss = (ts_ur_super_session *)s->session;
        if (ss) {
          turn_turnserver *server = (turn_turnserver *)ss->server;
          if (server) {
            shutdown_client_connection(server, ss, 0, "SSL read error");
          }
        } else {
          close_ioa_socket(s);
        }
        ur_addr_map_del(amap, &(sm->m.sm.nd.src_addr), NULL);
        sm->m.sm.s = NULL;
        s = NULL;
        chs = NULL;
      } else if (ioa_network_buffer_get_size(sm->m.sm.nd.nbh) > 0) {
        ;
      } else {
        ioa_network_buffer_delete(ioa_eng, sm->m.sm.nd.nbh);
        sm->m.sm.nd.nbh = NULL;
      }
    }

    if (s && ioa_socket_check_bandwidth(s, sm->m.sm.nd.nbh, 1)) {
      s->e = ioa_eng;
      if (s && s->read_cb && sm->m.sm.nd.nbh) {
        s->read_cb(s, IOA_EV_READ, &(sm->m.sm.nd), s->read_ctx, 1);
        ioa_network_buffer_delete(ioa_eng, sm->m.sm.nd.nbh);
        sm->m.sm.nd.nbh = NULL;

        if (ioa_socket_tobeclosed(s)) {
          ts_ur_super_session *ss = (ts_ur_super_session *)s->session;
          if (ss) {
            turn_turnserver *server = (turn_turnserver *)ss->server;
            if (server) {
              shutdown_client_connection(server, ss, 0, "UDP packet processing error");
            }
          }
        }
      }
    }
  } else {
    if (chs && ioa_socket_tobeclosed(chs)) {
      TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "%s: socket to be closed\n", __FUNCTION__);
      {
        char saddr[MAX_IOA_ADDR_STRING];
        char rsaddr[MAX_IOA_ADDR_STRING];
        addr_to_string(get_local_addr_from_ioa_socket(chs), saddr);
        addr_to_string(get_remote_addr_from_ioa_socket(chs), rsaddr);
        long thrid = 0;
#ifdef WINDOWS
        thrid = GetCurrentThreadId();
#else
        thrid = (long)pthread_self();
#endif
        TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO,
                      "%s: 111.111: thrid=0x%lx: Amap = %p, socket container=%p, local addr %s, remote addr %s, "
                      "s=%p, done=%d, tbc=%d\n",
                      __FUNCTION__, thrid, amap, chs->sockets_container, (char *)saddr, (char *)rsaddr, s,
                      (int)(chs->done), (int)(chs->tobeclosed));
      }
    }

    if (chs && (chs->magic != SOCKET_MAGIC)) {
      TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "%s: wrong socket magic\n", __FUNCTION__);
    }

    if (chs && (chs->sockets_container != amap)) {
      TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "%s: wrong socket container\n", __FUNCTION__);
      {
        char saddr[MAX_IOA_ADDR_STRING];
        char rsaddr[MAX_IOA_ADDR_STRING];
        addr_to_string(get_local_addr_from_ioa_socket(chs), saddr);
        addr_to_string(get_remote_addr_from_ioa_socket(chs), rsaddr);
        long thrid = 0;
#ifdef WINDOWS
        thrid = GetCurrentThreadId();
#else
        thrid = (long)pthread_self();
#endif
        TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO,
                      "%s: 111.222: thrid=0x%lx: Amap = %p, socket container=%p, local addr %s, remote addr %s, "
                      "s=%p, done=%d, tbc=%d, st=%d, sat=%d\n",
                      __FUNCTION__, thrid, amap, chs->sockets_container, (char *)saddr, (char *)rsaddr, (void *)chs,
                      (int)(chs->done), (int)(chs->tobeclosed), (int)(chs->st), (int)(chs->sat));
      }
    }

    chs = NULL;

#if DTLS_SUPPORTED
    if (!turn_params.no_dtls && (packet_type == UDP_PACKET_CLASS_DTLS_HANDSHAKE)) {
      chs = dtls_server_input_handler(server, s, sm->m.sm.nd.nbh);
      ioa_network_buffer_delete(server->e, sm->m.sm.nd.nbh);
      sm->m.sm.nd.nbh = NULL;
    }
#endif

    if (!chs) {
      // Disallow raw UDP if no_udp is enabled
      if (turn_params.no_udp) {
        return -1;
      }
      chs = create_ioa_socket_from_fd(ioa_eng, s->fd, s, UDP_SOCKET, CLIENT_SOCKET, &(sm->m.sm.nd.src_addr),
                                      get_local_addr_from_ioa_socket(s));
    }

    s = chs;
    sm->m.sm.s = s;

    if (s) {
      if (verbose && turn_params.log_binding) {
        char saddr[MAX_IOA_ADDR_STRING];
        char rsaddr[MAX_IOA_ADDR_STRING];
        addr_to_string(get_local_addr_from_ioa_socket(s), saddr);
        addr_to_string(get_remote_addr_from_ioa_socket(s), rsaddr);
        TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "%s: New UDP endpoint: local addr %s, remote addr %s\n", __FUNCTION__,
                      (char *)saddr, (char *)rsaddr);
      }
      s->e = ioa_eng;
      set_ioa_socket_buf_size(s, ts->sock_buf_size);
      add_socket_to_map(s, amap);
      if (open_client_connection_session(ts, &(sm->m.sm)) < 0) {
        return -1;
      }
    }
  }

  return 0;
}

static int process_udp_datagram(dtls_listener_relay_server_type *server, ioa_socket_handle s,
                                ioa_network_buffer_handle elem, const ioa_addr *src_addr, ssize_t bsize, int recv_ttl,
                                int recv_tos, int packet_type, uint32_t *packets_processed, uint32_t *packets_dropped) {
  int rc = 0;

  server->sm.m.sm.s = s;
  server->sm.m.sm.nd.nbh = elem;
  server->sm.m.sm.nd.recv_ttl = recv_ttl;
  server->sm.m.sm.nd.recv_tos = recv_tos;
  server->sm.m.sm.can_resume = 1;
  addr_cpy(&(server->sm.m.sm.nd.src_addr), src_addr);

  ioa_network_buffer_set_size(elem, (size_t)bsize);

  uint8_t *data = ioa_network_buffer_data(elem);
  const bool is_valid_packet = (packet_type != UDP_PACKET_CLASS_INVALID);

  if (turn_params.drop_invalid_packets && !is_valid_packet) {
    packetcounter++;
    if (turn_params.drop_invalid_packets_log && (packetcounter % 1000 == 0)) {
      uint8_t txt2pcap[1000]; // 1000 is enough to print ~300B packet (3 chars per byte) with extras
      print_packet_txt2pcap(packetcounter, data, (size_t)bsize, txt2pcap, sizeof(txt2pcap));
      TURN_LOG_FUNC(TURN_LOG_LEVEL_DEBUG, "TXT2PCAP: %s\n", txt2pcap);
    }
    ++(*packets_dropped);
  } else {
    ++(*packets_processed);

    if (server->connect_cb) {
      rc = create_new_connected_udp_socket(server, s);
      if (rc < 0) {
        TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Cannot handle UDP packet, size %d\n", (int)bsize);
      }
    } else {
      rc = handle_udp_packet(server, &(server->sm), server->e, server->ts, (udp_packet_classification_t)packet_type);
    }

    if (rc < 0 && eve(server->e->verbose)) {
      TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Cannot handle UDP event\n");
    }
  }

  if (server->sm.m.sm.nd.nbh != NULL) {
    /* buffer was not consumed downstream, return ownership to the caller */
    server->sm.m.sm.nd.nbh = NULL;
    return 1;
  }

  return 0;
}

static udp_packet_classification_t classify_udp_packet(const uint8_t *data, size_t blen) {
  size_t candidate_len = blen;
  uint16_t chnum = 0;
  uint32_t old_stun_cookie = 0;

  if (stun_is_channel_message_str(data, &candidate_len, &chnum, false) ||
      stun_is_command_message_str(data, candidate_len)) {
    return UDP_PACKET_CLASS_STUN_OR_CHANNEL;
  }
#if DTLS_SUPPORTED
  if (!turn_params.no_dtls && is_dtls_handshake_message(data, (int)blen)) {
    return UDP_PACKET_CLASS_DTLS_HANDSHAKE;
  }
  if (!turn_params.no_dtls && is_dtls_message(data, (int)blen)) {
    return UDP_PACKET_CLASS_DTLS_OTHER;
  }
#endif
  if (turn_params.stun_backward_compatibility && old_stun_is_command_message_str(data, blen, &old_stun_cookie)) {
    return UDP_PACKET_CLASS_OLD_STUN;
  }

  return UDP_PACKET_CLASS_INVALID;
}
#if defined(__linux__)
static int ensure_recvmmsg_state(dtls_listener_relay_server_type *server) {
  if (!server) {
    return -1;
  }

  if (server->recvmmsg_state) {
    return 0;
  }

  server->recvmmsg_state =
      (struct dtls_listener_recvmmsg_state *)calloc(1, sizeof(struct dtls_listener_recvmmsg_state));
  if (!server->recvmmsg_state) {
    TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "%s: Cannot allocate recvmmsg scratch state\n", __FUNCTION__);
    return -1;
  }

  for (unsigned int i = 0; i < MAX_RECVMMSG_BATCH; ++i) {
    ioa_init_recvmmsg_hdr(&(server->recvmmsg_state->msgs[i]), &(server->recvmmsg_state->iovecs[i]),
                          &(server->recvmmsg_state->src_addrs[i]), server->recvmmsg_state->cmsgs[i], RECVMMSG_CMSG_SZ,
                          (socklen_t)server->slen0, NULL, 0);
    server->recvmmsg_state->elems[i] = ioa_network_buffer_allocate(server->e);
    if (!server->recvmmsg_state->elems[i]) {
      TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "%s: Cannot allocate recvmmsg batch buffer\n", __FUNCTION__);
      return -1;
    }
  }

  return 0;
}

static int receive_udp_batch_recvmmsg(dtls_listener_relay_server_type *server, evutil_socket_t fd) {
  unsigned int i = 0;

  if (ensure_recvmmsg_state(server) < 0) {
    return -1;
  }

  struct dtls_listener_recvmmsg_state *state = server->recvmmsg_state;

  for (i = 0; i < MAX_RECVMMSG_BATCH; ++i) {
    if (!state->elems[i]) {
      state->elems[i] = ioa_network_buffer_allocate(server->e);
      if (!state->elems[i]) {
        ioa_engine_record_udp_recvmmsg_no_buffer(server->e);
        break;
      }
    }

    ioa_network_buffer_reset(state->elems[i]);
    addr_set_any(&(state->src_addrs[i]));
    state->ttls[i] = TTL_IGNORE;
    state->toss[i] = TOS_IGNORE;
    state->packet_types[i] = UDP_PACKET_CLASS_INVALID;
    ioa_init_recvmmsg_hdr(&(state->msgs[i]), &(state->iovecs[i]), &(state->src_addrs[i]), state->cmsgs[i],
                          RECVMMSG_CMSG_SZ, (socklen_t)server->slen0, ioa_network_buffer_data(state->elems[i]),
                          ioa_network_buffer_get_capacity_udp());
  }

  if (i == 0) {
    return -1;
  }

  const int rc = recvmmsg(fd, state->msgs, i, MSG_DONTWAIT, NULL);
  if (rc <= 0) {
    if (rc == 0 || would_block()) {
      ioa_engine_record_udp_recvmmsg_wouldblock(server->e);
    }
    return rc;
  }

  ioa_engine_record_udp_recvmmsg_batch(server->e, rc);

  for (int j = 0; j < rc; ++j) {
    ioa_network_buffer_set_size(state->elems[j], state->msgs[j].msg_len);
    ioa_parse_udp_recvmsg_cmsg(&(state->msgs[j].msg_hdr), &(state->ttls[j]), &(state->toss[j]), NULL);
    state->packet_types[j] =
        classify_udp_packet(ioa_network_buffer_data(state->elems[j]), ioa_network_buffer_get_size(state->elems[j]));
  }

  return rc;
}
#endif

static int create_new_connected_udp_socket(dtls_listener_relay_server_type *server, ioa_socket_handle s) {

  evutil_socket_t udp_fd = socket(s->local_addr.ss.sa_family, CLIENT_DGRAM_SOCKET_TYPE, CLIENT_DGRAM_SOCKET_PROTOCOL);
  if (udp_fd < 0) {
    TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "%s: Cannot allocate new socket. Error: %s\n", __FUNCTION__, strerror(errno));
    return -1;
  }

  if (sock_bind_to_device(udp_fd, (unsigned char *)(s->e->relay_ifname)) < 0) {
    TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Cannot bind udp server socket to device %s\n", (char *)(s->e->relay_ifname));
  }

  ioa_socket_handle ret = (ioa_socket *)calloc(1, sizeof(ioa_socket));
  if (!ret) {
    TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "%s: Cannot allocate new socket structure\n", __FUNCTION__);
    socket_closesocket(udp_fd);
    return -1;
  }

  ret->magic = SOCKET_MAGIC;

  ret->fd = udp_fd;

  ret->family = s->family;
  ret->st = s->st;
  ret->sat = CLIENT_SOCKET;
  ret->local_addr_known = 1;
  addr_cpy(&(ret->local_addr), &(s->local_addr));

  if (addr_bind(udp_fd, &(s->local_addr), 1, 1, UDP_SOCKET) < 0) {
    TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Cannot bind new detached udp server socket to local addr\n");
    IOA_CLOSE_SOCKET(ret);
    return -1;
  }
  ret->bound = 1;

  {
    int connect_err = 0;
    if (addr_connect(udp_fd, &(server->sm.m.sm.nd.src_addr), &connect_err) < 0) {
      char sl[MAX_IOA_ADDR_STRING];
      char sr[MAX_IOA_ADDR_STRING];
      addr_to_string(&(ret->local_addr), sl);
      addr_to_string(&(server->sm.m.sm.nd.src_addr), sr);
      TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR,
                    "Cannot connect new detached udp client socket from local addr %s to remote addr %s\n", sl, sr);
      IOA_CLOSE_SOCKET(ret);
      return -1;
    }
  }
  ret->connected = 1;
  addr_cpy(&(ret->remote_addr), &(server->sm.m.sm.nd.src_addr));

  set_socket_options(ret);

  ret->current_ttl = s->current_ttl;
  ret->default_ttl = s->default_ttl;

  ret->current_tos = s->current_tos;
  ret->default_tos = s->default_tos;

#if DTLS_SUPPORTED
  if (!turn_params.no_dtls && is_dtls_handshake_message(ioa_network_buffer_data(server->sm.m.sm.nd.nbh),
                                                        (int)ioa_network_buffer_get_size(server->sm.m.sm.nd.nbh))) {

    SSL *connecting_ssl = NULL;

    BIO *wbio = NULL;
    struct timeval timeout;

    /* Create BIO */
    wbio = BIO_new_dgram(ret->fd, BIO_NOCLOSE);
    (void)BIO_dgram_set_peer(wbio, (struct sockaddr *)&(server->sm.m.sm.nd.src_addr));

    BIO_ctrl(wbio, BIO_CTRL_DGRAM_SET_CONNECTED, 0, &(server->sm.m.sm.nd.src_addr));

    /* Set and activate timeouts */
    timeout.tv_sec = DTLS_MAX_RECV_TIMEOUT;
    timeout.tv_usec = 0;
    BIO_ctrl(wbio, BIO_CTRL_DGRAM_SET_RECV_TIMEOUT, 0, &timeout);

    connecting_ssl = SSL_new(server->e->dtls_ctx);

    SSL_set_accept_state(connecting_ssl);

    SSL_set_bio(connecting_ssl, NULL, wbio);

    SSL_set_options(connecting_ssl, SSL_OP_COOKIE_EXCHANGE
#if defined(SSL_OP_NO_RENEGOTIATION)
                                        | SSL_OP_NO_RENEGOTIATION
#endif
    );

    SSL_set_max_cert_list(connecting_ssl, 655350);
    const int rc = ssl_read(ret->fd, connecting_ssl, server->sm.m.sm.nd.nbh, server->verbose);

    if (rc < 0) {
      if (!(SSL_get_shutdown(connecting_ssl) & SSL_SENT_SHUTDOWN)) {
        SSL_set_shutdown(connecting_ssl, SSL_RECEIVED_SHUTDOWN);
        SSL_shutdown(connecting_ssl);
      }
      SSL_free(connecting_ssl);
      IOA_CLOSE_SOCKET(ret);
      return -1;
    }

    addr_debug_print(server->verbose, &(server->sm.m.sm.nd.src_addr), "Accepted DTLS connection from");

    ret->ssl = connecting_ssl;

    ioa_network_buffer_delete(server->e, server->sm.m.sm.nd.nbh);
    server->sm.m.sm.nd.nbh = NULL;

    ret->st = DTLS_SOCKET;
  }
#endif

  server->sm.m.sm.s = ret;
  return server->connect_cb(server->e, &(server->sm));
}

static void udp_server_input_handler(evutil_socket_t fd, short what, void *arg) {

  if (!arg) {
    return;
  }

  int cycle = 0;

  dtls_listener_relay_server_type *server = (dtls_listener_relay_server_type *)arg;
  ioa_socket_handle s = server->udp_listen_s;

  FUNCSTART;

  if (!(what & EV_READ)) {
    return;
  }

  // printf_server_socket(server, fd);

  ioa_network_buffer_handle elem = NULL;
  uint32_t packets_processed = 0;
  uint32_t packets_dropped = 0;

#if defined(__linux__)
  if (turn_params.udp_recvmmsg) {
    const int batch_rc = receive_udp_batch_recvmmsg(server, fd);

    if (batch_rc > 0) {
      struct dtls_listener_recvmmsg_state *state = server->recvmmsg_state;
      udp_sendmmsg_batch_begin();
      for (int i = 0; i < batch_rc; ++i) {
        if (!state->elems[i]) {
          continue;
        }
        const int keep_elem = process_udp_datagram(
            server, s, state->elems[i], &(state->src_addrs[i]), (ssize_t)ioa_network_buffer_get_size(state->elems[i]),
            state->ttls[i], state->toss[i], state->packet_types[i], &packets_processed, &packets_dropped);
        if (keep_elem) {
          ioa_network_buffer_reset(state->elems[i]);
        } else {
          state->elems[i] = NULL;
        }
      }
      udp_sendmmsg_batch_end();

      prom_inc_packet_dropped(packets_dropped);
      prom_inc_packet_processed(packets_processed);
      FUNCEND;
      return;
    }

    if (batch_rc < 0) {
      if (errno == ENOSYS || errno == EINVAL || errno == EOPNOTSUPP) {
        TURN_LOG_FUNC(TURN_LOG_LEVEL_WARNING,
                      "%s: recvmmsg() is unavailable on this system, disabling udp-recvmmsg fast path\n", __FUNCTION__);
        ioa_engine_record_udp_recvmmsg_unavailable(server->e);
        turn_params.udp_recvmmsg = false;
      } else if (would_block()) {
        prom_inc_packet_dropped(packets_dropped);
        prom_inc_packet_processed(packets_processed);
        FUNCEND;
        return;
      } else if (is_connreset()) {
        reopen_server_socket(server, fd);
        prom_inc_packet_dropped(packets_dropped);
        prom_inc_packet_processed(packets_processed);
        FUNCEND;
        return;
      }
    }
  }
#endif

start_udp_cycle:

  if (!elem) {
    elem = ioa_network_buffer_allocate(server->e);
  }

  server->sm.m.sm.nd.nbh = elem;
  server->sm.m.sm.nd.recv_ttl = TTL_IGNORE;
  server->sm.m.sm.nd.recv_tos = TOS_IGNORE;
  server->sm.m.sm.can_resume = 1;

  addr_set_any(&(server->sm.m.sm.nd.src_addr));

  ssize_t bsize = 0;
#if defined(WINDOWS)
  int flags = 0;
  u_long iMode = 1;
  ioctlsocket(fd, FIONBIO, &iMode);
#else
  const int flags = MSG_DONTWAIT;
#endif
  bsize = udp_recvfrom(fd, &(server->sm.m.sm.nd.src_addr), &(server->addr), (char *)ioa_network_buffer_data(elem),
                       (int)ioa_network_buffer_get_capacity_udp(), &(server->sm.m.sm.nd.recv_ttl),
                       &(server->sm.m.sm.nd.recv_tos), server->e->cmsg, flags, NULL);

  int conn_reset = is_connreset();
  int to_block = would_block();

#if defined(WINDOWS)
  iMode = 0;
  ioctlsocket(fd, FIONBIO, &iMode);
#endif

  if (bsize < 0) {

    if (to_block) {
      ioa_network_buffer_delete(server->e, server->sm.m.sm.nd.nbh);
      server->sm.m.sm.nd.nbh = NULL;
      FUNCEND;
      return;
    }

#if defined(MSG_ERRQUEUE)

#if defined(WINDOWS)
    int eflags = MSG_ERRQUEUE;
    iMode = 1;
    ioctlsocket(fd, FIONBIO, &iMode);
#else
    // Linux
    const int eflags = MSG_ERRQUEUE | MSG_DONTWAIT;
#endif
    static char buffer[65535];
    uint32_t errcode = 0;
    ioa_addr orig_addr = {0};
    int ttl = 0;
    int tos = 0;
    socklen_t slen = server->slen0;
    udp_recvfrom(fd, &orig_addr, &(server->addr), buffer, (int)sizeof(buffer), &ttl, &tos, server->e->cmsg, eflags,
                 &errcode);
    // try again...
    do {
      bsize = recvfrom(fd, ioa_network_buffer_data(elem), ioa_network_buffer_get_capacity_udp(), flags,
                       (struct sockaddr *)&(server->sm.m.sm.nd.src_addr), &slen);
    } while (bsize < 0 && socket_eintr());

    conn_reset = is_connreset();
    to_block = would_block();

#if defined(WINDOWS)
    iMode = 0;
    ioctlsocket(fd, FIONBIO, &iMode);
#endif

#endif

    if (conn_reset) {
      ioa_network_buffer_delete(server->e, server->sm.m.sm.nd.nbh);
      server->sm.m.sm.nd.nbh = NULL;
      reopen_server_socket(server, fd);
      FUNCEND;
      return;
    }
  }

  if (bsize < 0) {
    if (!to_block && !conn_reset) {
      TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "%s: recvfrom error %d\n", __FUNCTION__, socket_errno());
    }
    ioa_network_buffer_delete(server->e, server->sm.m.sm.nd.nbh);
    server->sm.m.sm.nd.nbh = NULL;
    FUNCEND;
    return;
  }

  int keep_elem = 1;
  if (bsize > 0) {
    udp_sendmmsg_batch_begin();
    keep_elem = process_udp_datagram(server, s, elem, &(server->sm.m.sm.nd.src_addr), bsize,
                                     server->sm.m.sm.nd.recv_ttl, server->sm.m.sm.nd.recv_tos,
                                     (int)classify_udp_packet(ioa_network_buffer_data(elem), (size_t)bsize),
                                     &packets_processed, &packets_dropped);
    udp_sendmmsg_batch_end();
  }

  if (keep_elem) {
    /* buffer was not consumed downstream, reuse it on the next iteration */
    server->sm.m.sm.nd.nbh = NULL;
  } else {
    /* buffer was consumed (and freed) downstream, need a fresh one next time */
    elem = NULL;
  }

  if ((bsize > 0) && (cycle++ < MAX_SINGLE_UDP_BATCH)) {
    goto start_udp_cycle;
  }

  ioa_network_buffer_delete(server->e, elem);
  elem = NULL;

  prom_inc_packet_dropped(packets_dropped);
  prom_inc_packet_processed(packets_processed);

  FUNCEND;
}

///////////////////// operations //////////////////////////

static int create_server_socket(dtls_listener_relay_server_type *server, int report_creation, int sock_buf_size) {

  FUNCSTART;

  if (!server) {
    return -1;
  }

  clean_server(server);

  {
    ioa_socket_raw udp_listen_fd = -1;

    udp_listen_fd = socket(server->addr.ss.sa_family, CLIENT_DGRAM_SOCKET_TYPE, CLIENT_DGRAM_SOCKET_PROTOCOL);
    if (udp_listen_fd < 0) {
      TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "%s: socket error: %s\n", __FUNCTION__, strerror(errno));
      return -1;
    }

    server->udp_listen_s =
        create_ioa_socket_from_fd(server->e, udp_listen_fd, NULL, UDP_SOCKET, LISTENER_SOCKET, NULL, &(server->addr));

    set_ioa_socket_buf_size(server->udp_listen_s, sock_buf_size);

    if (sock_bind_to_device(udp_listen_fd, (unsigned char *)server->ifname) < 0) {
      TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "Cannot bind listener socket to device %s\n", server->ifname);
    }

    set_raw_socket_ttl_options(udp_listen_fd, server->addr.ss.sa_family);
    set_raw_socket_tos_options(udp_listen_fd, server->addr.ss.sa_family);

    {
      const int max_binding_time = 60;
      int addr_bind_cycle = 0;
    retry_addr_bind:

      if (addr_bind(udp_listen_fd, &server->addr, 1, 1, UDP_SOCKET) < 0) {
        char saddr[MAX_IOA_ADDR_STRING];
        addr_to_string(&server->addr, saddr);
        TURN_LOG_FUNC(TURN_LOG_LEVEL_WARNING, "Cannot bind DTLS/UDP listener socket to addr %s. Error: %s\n", saddr,
                      strerror(errno));
        if (addr_bind_cycle++ < max_binding_time) {
          TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "Trying to bind DTLS/UDP listener socket to addr %s, again...\n", saddr);
          sleep(1);
          goto retry_addr_bind;
        }
        TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR,
                      "Fatal final failure: cannot bind DTLS/UDP listener socket to addr %s. Error: %s\n", saddr,
                      strerror(errno));
        exit(-1);
      }
    }

    server->udp_listen_ev =
        event_new(server->e->event_base, udp_listen_fd, EV_READ | EV_PERSIST, udp_server_input_handler, server);

    event_add(server->udp_listen_ev, NULL);
  }

  if (report_creation) {
    if (!turn_params.no_udp && !turn_params.no_dtls) {
      addr_debug_print(server->verbose, &server->addr, "DTLS/UDP listener opened on");
    } else if (!turn_params.no_dtls) {
      addr_debug_print(server->verbose, &server->addr, "DTLS listener opened on");
    } else if (!turn_params.no_udp) {
      addr_debug_print(server->verbose, &server->addr, "UDP listener opened on");
    }
  }

  FUNCEND;

  return 0;
}

static int reopen_server_socket(dtls_listener_relay_server_type *server, evutil_socket_t fd) {
  UNUSED_ARG(fd);

  if (!server) {
    return 0;
  }

  FUNCSTART;

  {
    EVENT_DEL(server->udp_listen_ev);

    if (server->udp_listen_s->fd >= 0) {
      socket_closesocket(server->udp_listen_s->fd);
      server->udp_listen_s->fd = -1;
    }

    if (!(server->udp_listen_s)) {
      return create_server_socket(server, 1, turn_params.sock_buf_size);
    }

    const ioa_socket_raw udp_listen_fd =
        socket(server->addr.ss.sa_family, CLIENT_DGRAM_SOCKET_TYPE, CLIENT_DGRAM_SOCKET_PROTOCOL);
    if (udp_listen_fd < 0) {
      TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "%s: socket error: %s\n", __FUNCTION__, strerror(errno));
      FUNCEND;
      return -1;
    }

    server->udp_listen_s->fd = udp_listen_fd;

    /* some UDP sessions may fail due to the race condition here */

    set_socket_options(server->udp_listen_s);
    set_ioa_socket_buf_size(server->udp_listen_s, server->ts->sock_buf_size);

    if (sock_bind_to_device(udp_listen_fd, (unsigned char *)server->ifname) < 0) {
      TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Cannot bind listener socket to device %s. Error: %s\n", server->ifname,
                    strerror(errno));
    }

    if (addr_bind(udp_listen_fd, &server->addr, 1, 1, UDP_SOCKET) < 0) {
      char saddr[MAX_IOA_ADDR_STRING];
      addr_to_string(&server->addr, saddr);
      TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Cannot bind local socket to addr %s. Error: %s\n", saddr, strerror(errno));
      return -1;
    }

    server->udp_listen_ev =
        event_new(server->e->event_base, udp_listen_fd, EV_READ | EV_PERSIST, udp_server_input_handler, server);

    event_add(server->udp_listen_ev, NULL);
  }

  if (!turn_params.no_udp && !turn_params.no_dtls) {
    addr_debug_print(server->verbose, &server->addr, "DTLS/UDP listener opened on ");
  } else if (!turn_params.no_dtls) {
    addr_debug_print(server->verbose, &server->addr, "DTLS listener opened on ");
  } else if (!turn_params.no_udp) {
    addr_debug_print(server->verbose, &server->addr, "UDP listener opened on ");
  }

  FUNCEND;

  return 0;
}

#if defined(REQUEST_CLIENT_CERT)

static int dtls_verify_callback(int ok, X509_STORE_CTX *ctx) {
  /* This function should ask the user
   * if he trusts the received certificate.
   * Here we always trust.
   */
  if (ok && ctx) {
    return 1;
  }
  return -1;
}

#endif

static int init_server(dtls_listener_relay_server_type *server, const char *ifname, const char *local_address,
                       uint16_t port, int sock_buf_size, int verbose, ioa_engine_handle e, turn_turnserver *ts,
                       int report_creation, ioa_engine_new_connection_event_handler send_socket) {

  if (!server) {
    return -1;
  }

  server->ts = ts;
  server->connect_cb = send_socket;

  if (ifname) {
    STRCPY(server->ifname, ifname);
  }

  if (make_ioa_addr((const uint8_t *)local_address, port, &server->addr) < 0) {
    TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Cannot create a DTLS/UDP listener for address: %s\n", local_address);
    return -1;
  }

  server->slen0 = get_ioa_addr_len(&(server->addr));

  server->verbose = verbose;

  server->e = e;

  return create_server_socket(server, report_creation, sock_buf_size);
}

static int clean_server(dtls_listener_relay_server_type *server) {
  if (server) {
    EVENT_DEL(server->udp_listen_ev);
    close_ioa_socket(server->udp_listen_s);
    server->udp_listen_s = NULL;
#if defined(__linux__)
    if (server->recvmmsg_state) {
      for (unsigned int i = 0; i < MAX_RECVMMSG_BATCH; ++i) {
        ioa_network_buffer_delete(server->e, server->recvmmsg_state->elems[i]);
        server->recvmmsg_state->elems[i] = NULL;
      }
      free(server->recvmmsg_state);
      server->recvmmsg_state = NULL;
    }
#endif
  }
  return 0;
}

///////////////////////////////////////////////////////////

#if DTLS_SUPPORTED
void setup_dtls_callbacks(SSL_CTX *ctx) {
  if (!ctx) {
    return;
  }

#if defined(REQUEST_CLIENT_CERT)
  /* If client has to authenticate, then  */
  SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE, dtls_verify_callback);
#endif

  SSL_CTX_set_cookie_generate_cb(ctx, generate_cookie);
  SSL_CTX_set_cookie_verify_cb(ctx, verify_cookie);
}
#endif

dtls_listener_relay_server_type *create_dtls_listener_server(const char *ifname, const char *local_address,
                                                             uint16_t port, int sock_buf_size, int verbose,
                                                             ioa_engine_handle e, turn_turnserver *ts,
                                                             int report_creation,
                                                             ioa_engine_new_connection_event_handler send_socket) {

  dtls_listener_relay_server_type *server =
      (dtls_listener_relay_server_type *)allocate_super_memory_engine(e, sizeof(dtls_listener_relay_server_type));

  if (init_server(server, ifname, local_address, port, sock_buf_size, verbose, e, ts, report_creation, send_socket) <
      0) {
    return NULL;
  } else {
    return server;
  }
}

ioa_engine_handle get_engine(dtls_listener_relay_server_type *server) {
  if (server) {
    return server->e;
  }
  return NULL;
}

//////////// UDP send ////////////////

void udp_send_message(dtls_listener_relay_server_type *server, ioa_network_buffer_handle nbh, ioa_addr *dest) {
  if (server && dest && nbh && (server->udp_listen_s)) {
    udp_send(server->udp_listen_s, dest, (char *)ioa_network_buffer_data(nbh), (int)ioa_network_buffer_get_size(nbh));
  }
}

//////////////////////////////////////////////////////////////////
