/* sml3_nw_udp.c: UDP-Funktionen */

/* Copyright 2012-2017 Kurt Nienhaus
 *
 * This file is part of libsammel3.
 * libsammel3 is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 * libsammel3 is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with libsammel3.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include "config.h"
#include "sml3_fehler.h"
#include "sml3_util.h"
#include "sml3_nw_support.h"
#include "sml3_nw_udp.h"

int SML3_nw_udp_server(const char *, const char *, int *, socklen_t *, int (*)(int));
int SML3_nw_udp_client(const char *, const char *, int *, struct sockaddr **, socklen_t *, int (*)(int));
int SML3_nw_udp_connect(const char *, const char *, int *, const char *, int (*)(int));
int SML3_nw_udp_peername(int, const struct sockaddr *, socklen_t, char *, size_t, int *);

static int udp_client_connect(const char *, const char *, int *, const char *, struct sockaddr **, socklen_t *, int (*)(int));


/* udp_client_connect: UDP-Client oder UDP-Connect */
static int
udp_client_connect(const char *host, const char *service, int *ipv, const char *bindhost, struct sockaddr **sad, socklen_t *sadlen, int (*optfunk)(int))
{
  struct addrinfo adi0, *adi1, *adi2;
  int sockfd;

  if (host == NULL || *host == '\0' || service == NULL || *service == '\0' || ipv == NULL) {
    SML3_fehlernew(EINVAL, "%s", SML3_strerror(EINVAL));
    return -1;
  }

  if (sadlen == NULL) { sad = NULL; }

  if (sad != NULL && bindhost != NULL && *bindhost != '\0') {
    SML3_fehlernew(EINVAL, "%s", SML3_strerror(EINVAL));
    return -1;
  }

  if (*ipv != 4 && *ipv != 6) { *ipv = 0; }

  memset(&adi0, 0, sizeof(struct addrinfo));
  adi0.ai_socktype = SOCK_DGRAM;
  adi0.ai_family = AF_UNSPEC;

  if (*ipv == 4) {
    adi0.ai_family = AF_INET;
#ifdef AF_INET6
  } else if (*ipv == 6) {
    adi0.ai_family = AF_INET6;
#endif
  }

  if (SML3_getaddrinfo(host, service, &adi0, &adi1) < 0) { SML3_fehleradd(NULL); return -1; }
  sockfd = 0;

  for (adi2 = adi1; adi2 != NULL; adi2 = adi2->ai_next) {
    sockfd = socket(adi2->ai_family, adi2->ai_socktype, adi2->ai_protocol);
    if (sockfd < 0) { continue; }

    if (optfunk != NULL && optfunk(sockfd) < 0) {
      SML3_fehleradd(NULL);
      SML3_freeaddrinfo(adi1);
      close(sockfd);
      return -1;
    }

    if (bindhost != NULL && *bindhost != '\0') {  /* verbindungsorientiert */
      int pt, ai_fam = adi0.ai_family;
      char sv[16];
      struct addrinfo *adi3, *adi4;
      for (pt = 1024; pt < 65536; pt++) {
        snprintf(sv, sizeof(sv), "%d", pt);
        memset(&adi0, 0, sizeof(struct addrinfo));
        adi0.ai_socktype = SOCK_STREAM;
        adi0.ai_family = ai_fam;
        if (SML3_getaddrinfo(bindhost, sv, &adi0, &adi3) == 0) {
          for (adi4 = adi3; adi4 != NULL; adi4 = adi4->ai_next) {
            if (bind(sockfd, adi4->ai_addr, adi4->ai_addrlen) == 0) {
              if (connect(sockfd, adi2->ai_addr, adi2->ai_addrlen) == 0) { break; }
            }
          }
          SML3_freeaddrinfo(adi3);
          if (adi4 != NULL) { break; }  /* connect() erfolgreich */
        }
      }
      if (pt < 65536) { break; }  /* connect() erfolgreich */
    } else if (sad != NULL) {  /* verbindungslos */
      break;
    } else {  /* verbindungsorientiert */
      if (connect(sockfd, adi2->ai_addr, adi2->ai_addrlen) == 0) { break; }
    }
    close(sockfd);
  }

  if (adi2 == NULL) {
    SML3_fehlernew(ENOTCONN, "connect: cannot connect to \"%s\" at port \"%s\"", host, service);
    SML3_freeaddrinfo(adi1);
    return -1;
  }

  *ipv = 0;
  if (adi2->ai_family == AF_INET) { *ipv = 4; }
#ifdef AF_INET6
  if (adi2->ai_family == AF_INET6) { *ipv = 6; }
#endif

  if (sad != NULL) {  /* verbindungslos */
    *sadlen = adi2->ai_addrlen;
    if ((*sad = malloc(*sadlen)) == NULL) {
      SML3_fehlernew(ENOMEM, "malloc: %s", SML3_strerror(errno));
      SML3_freeaddrinfo(adi1);
      close(sockfd);
      return -1;
    }
    memmove(*sad, adi2->ai_addr, *sadlen);
  }

  SML3_freeaddrinfo(adi1);

  fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFL, 0) & ~O_NONBLOCK);
  return sockfd;
} /* Ende udp_client_connect */


/* SML3_nw_udp_server [ohne Opt-Funktion: thread-sicher]:
 * UDP-Server: Server-Socket anlegen (Verwendung mit sendto() und recvfrom())
 * Hybridmodus bei IPv6 (auch IPv4 als Mapped Adressen (:ffff:<IPv4>) akzeptieren) ist abgeschaltet
 * 1.Arg: Host/IP-Nummer oder NULL = alle IPs des Hosts
 * 2.Arg: Servicename oder Port
 * 3.Arg: IP-Version: Uebergabe gewuenschte und Rueckgabe verwendete
 *        4 = IPv4
 *        6 = IPv6
 *        0 = egal
 * 4.Arg: Rueckgabe Adresslaenge
 * 5.Arg: Funktion zum Setzen von Socketoptionen
 *          (int optfunk(int sockfd) -> Rueckgabe: 0 = OK oder -1 = Fehler)
 *        oder NULL = keine
 * Rueckgabe: blockierender Socketdeskriptor oder -1 = Fehler
 * SML3-errno-Wert: EINVAL = Fehler Uebergabeparameter
 *                  ENOTCONN = Socket nicht erhalten
 *                  weitere aus SML3_getaddrinfo(), Funktion(5.Arg)
 */
int
SML3_nw_udp_server(const char *host, const char *service, int *ipv, socklen_t *addrlen, int (*optfunk)(int))
{
  int on = 1;
  struct addrinfo adi0, *adi1, *adi2;
  int sockfd;

  if (service == NULL || *service == '\0' || ipv == NULL || addrlen == NULL) {
    SML3_fehlernew(EINVAL, "%s", SML3_strerror(EINVAL));
    return -1;
  }

  if (*ipv != 4 && *ipv != 6) { *ipv = 0; }

  memset(&adi0, 0, sizeof(struct addrinfo));
  adi0.ai_flags = AI_PASSIVE;
  adi0.ai_socktype = SOCK_DGRAM;
  adi0.ai_family = AF_UNSPEC;

  if (*ipv == 4) {
    adi0.ai_family = AF_INET;
#ifdef AF_INET6
  } else if (*ipv == 6) {
    adi0.ai_family = AF_INET6;
#endif
  }

  if (SML3_getaddrinfo(host, service, &adi0, &adi1) < 0) { SML3_fehleradd(NULL); return -1; }
  sockfd = 0;

  for (adi2 = adi1; adi2 != NULL; adi2 = adi2->ai_next) {
    sockfd = socket(adi2->ai_family, adi2->ai_socktype, adi2->ai_protocol);
    if (sockfd < 0) { continue; }

    on = 1;
    setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));

#if defined(AF_INET6) && defined(IPV6_V6ONLY)
    /* Hybridmodus (auch IPv4 als Mapped Adressen (:ffff:<IPv4>) akzeptieren) abschalten */
    if (adi2->ai_family == AF_INET6) {
      on = 1;
      setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on));
    }
#endif

    if (optfunk != NULL && optfunk(sockfd) < 0) {
      SML3_fehleradd(NULL);
      SML3_freeaddrinfo(adi1);
      close(sockfd);
      return -1;
    }

    if (bind(sockfd, adi2->ai_addr, adi2->ai_addrlen) == 0) { break; }
    close(sockfd);
  }

  if (adi2 == NULL) {
    SML3_fehlernew(ENOTCONN, "cannot get socket");
    SML3_freeaddrinfo(adi1);
    return -1;
  }

  *addrlen = adi2->ai_addrlen;
  *ipv = 0;
  if (adi2->ai_family == AF_INET) { *ipv = 4; }
#ifdef AF_INET6
  if (adi2->ai_family == AF_INET6) { *ipv = 6; }
#endif
  SML3_freeaddrinfo(adi1);

  fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFL, 0) & ~O_NONBLOCK);
  return sockfd;
} /* Ende SML3_nw_udp_server */


/* SML3_nw_udp_client [ohne Opt-Funktion: thread-sicher]:
 * UDP-Client: verbindungslos (zur Verwendung mit sendto() und recvfrom())
 * 1.Arg: Host/IP-Nummer
 * 2.Arg: Servicename oder Port
 * 3.Arg: IP-Version: Uebergabe gewuenschte und Rueckgabe verwendete
 *        4 = IPv4
 *        6 = IPv6
 *        0 = egal
 * 4.Arg: Adresse fuer Rueckgabe Serverinfo fuer sendto()
 *        (muss mit free() freigegeben werden)
 * 5.Arg: Rueckgabe Adresslaenge 4.Arg fuer sendto()
 * 6.Arg: Funktion zum Setzen von Socketoptionen
 *          (int optfunk(int sockfd) -> Rueckgabe: 0 = OK oder -1 = Fehler)
 *        oder NULL = keine
 * Rueckgabe: blockierender Socketdeskriptor oder -1 = Fehler
 * SML3-errno-Wert: EINVAL = Fehler Uebergabeparameter
 *                  ENOTCONN = Verbindung nicht moeglich
 *                  weitere aus SML3_getaddrinfo(), Funktion(5.Arg)
 *
 * ACHTUNG: Ob die Verbindung tatsaechlich funktioniert, kann nicht ermittelt
 *          werden, da die Schreibvorgaenge ohne Fehler ablaufen.
 */
int
SML3_nw_udp_client(const char *host, const char *service, int *ipv, struct sockaddr **sad, socklen_t *sadlen, int (*optfunk)(int))
{
  int erg = udp_client_connect(host, service, ipv, NULL, sad, sadlen, optfunk);

  if (erg < 0) { SML3_fehleradd(NULL); return -1; }

  return erg;
} /* Ende SML3_nw_udp_client */


/* SML3_nw_udp_connect [ohne Opt-Funktion: thread-sicher]:
 * UDP-Client: verbindungsorientiert (zur Verwendung mit SML3_writen() und read())
 * 1.Arg: Host/IP-Nummer
 * 2.Arg: Servicename oder Port
 * 3.Arg: IP-Version: Uebergabe gewuenschte und Rueckgabe verwendete
 *        4 = IPv4
 *        6 = IPv6
 *        0 = egal
 * 4.Arg: Host/IP-Nummer des Absenders fuer bind() oder NULL = automatisch
 * 5.Arg: Funktion zum Setzen von Socketoptionen
 *          (int optfunk(int sockfd) -> Rueckgabe: 0 = OK oder -1 = Fehler)
 *        oder NULL = keine
 * Rueckgabe: blockierender Socketdeskriptor oder -1 = Fehler
 * SML3-errno-Wert: EINVAL = Fehler Uebergabeparameter
 *                  ENOTCONN = Verbindung nicht moeglich
 *                  ENOMEM = Allokationsfehler
 *                  weitere aus SML3_getaddrinfo(), Funktion(5.Arg)
 *
 * ACHTUNG: Ob die Verbindung tatsaechlich da ist, erweist sich erst beim
 *          ersten Schreibvorgang zum Server (i.a. errno = ECONNREFUSED).
 *          Ausserdem sollte ein Datagram mit EINEM read()-Vorgang gelesen
 *          werden, da restliche Daten sonst verloren gehen koennten.
 */
int
SML3_nw_udp_connect(const char *host, const char *service, int *ipv, const char *bindhost, int (*optfunk)(int))
{
  int erg = udp_client_connect(host, service, ipv, bindhost, NULL, NULL, optfunk);

  if (erg < 0) { SML3_fehleradd(NULL); return -1; }

  return erg;
} /* Ende SML3_nw_udp_connect */


/* SML3_nw_udp_peername [thread-sicher]:
 * IP und Port des UDP-Remote-Hosts erhalten
 * 1.Arg: Socketdeskriptor
 * 2.+3.Arg: Serverinfo + Adresslaenge von recvfrom()
 *           (bei SML3_nw_udp_connect(): NULL, 0)
 * 4.Arg: fuer Rueckgabe IP
 * 5.Arg: sizeof(4.Arg)
 * 6.Arg: Rueckgabe Port oder NULL
 * Rueckgabe: 0 = OK oder -1 = Fehler
 * SML3-errno-Wert: ENOMEM = Allokationsfehler
 *                  ENXIO = Fehler getpeername()
 *                  weitere aus SML3_getnameinfo()
 */
int
SML3_nw_udp_peername(int sockfd, const struct sockaddr *rad, socklen_t radlen, char *host, size_t hostlen, int *port)
{
  char serv[NI_MAXSERV];
  struct sockaddr *sad;
#ifdef SML3_HAVE_SOCKADDR_STORAGE
  socklen_t salen = sizeof(struct sockaddr_storage);
#else
  socklen_t salen = 128;
#endif

  if ((int)hostlen > NI_MAXHOST) { hostlen = (size_t)NI_MAXHOST; }
  if (host == NULL) { hostlen = 0; }

  if (rad == NULL || radlen <= 0) {
    if ((sad = calloc(1, salen)) == NULL) { SML3_fehlernew(ENOMEM, "calloc: %s", SML3_strerror(errno));; return -1; }

    if (getpeername(sockfd, sad, &salen) < 0) {
      SML3_fehlernew(ENXIO, "getpeername: %s", SML3_strerror(errno));
      free(sad);
      return -1;
    }

    if (SML3_getnameinfo(sad, salen, host, hostlen, serv, NI_MAXSERV, NI_NUMERICHOST | NI_NUMERICSERV) < 0) {
      SML3_fehleradd(NULL);
      free(sad);
      return -1;
    }
    free(sad);

  } else {
    if (SML3_getnameinfo(rad, radlen, host, hostlen, serv, NI_MAXSERV, NI_NUMERICHOST | NI_NUMERICSERV) < 0) {
      SML3_fehleradd(NULL);
      return -1;
    }
  }

  if (port != NULL) { *port = atoi(serv); }

  return 0;
} /* Ende SML3_nw_udp_peername */
