/* sml3_nw_mbcast.c: Broadcast- und Multicast-Server und -Client */

/* 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 <unistd.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <net/if.h>
#include "config.h"
#ifdef SML3_HAVE_SYS_SELECT_H
# include <sys/select.h>
#endif
#include "sml3_fehler.h"
#include "sml3_util.h"
#include "sml3_nw_support.h"
#include "sml3_nw_udp.h"
#include "sml3_nw_if.h"
#include "sml3_nw_mbcast.h"

#ifdef AF_INET6
# ifndef IPV6_ADD_MEMBERSHIP
#  define IPV6_ADD_MEMBERSHIP IPV6_JOIN_GROUP
# endif
#endif

#ifdef AF_INET6
static const char *mcadd = "ff02::1";
#endif

struct mb_send {
  int ipv;
  const char *daddr;
  const char *iface;
  int sockfd;
  socklen_t salen;
  struct sockaddr *sad;
};

int SML3_nw_mbcast_server(const char *, int, int *, int (*)(void *, const char *, char *, size_t *), void *);
int SML3_nw_mbcast_client(const char *, const char *, int, int, const void *, size_t, struct SML3_nw_mbcast **);
void SML3_nw_mbcast_free(struct SML3_nw_mbcast *);

static int server_bcast(int, const char *, int (*)(void *, const char *, char *, size_t *), void *);
#ifdef AF_INET6
static int server_mcast(int, const char *, int (*)(void *, const char *, char *, size_t *), void *);
#endif


/* SML3_nw_mbcast_server [ohne Auswerte-Funktion: thread-sicher]:
 * Broadcast- bzw. Multicast-Server
 * 1.Arg: Servicename oder Port
 * 2.Arg: IP-Version
 *        4 = IPv4  (Broadcast-Server)
 *        6 = IPv6  (Multicast-Server)
 * 3.Arg: nicht NULL: Pipe erstellen, forken und Pipe-Schreibende zurueckgeben
 *                    (wenn Pipe-Schreibende geschlossen wird, beendet sich der Server)
 *              NULL: nicht forken
 * 4.Arg: Auswerte-Funktion fuer empfangene Broadcast- bzw. Multicast-Anfragen
 *          int funktion(void *vptr, const char *ipnr, char *daten, size_t *len)
 *            (erhaelt in "ipnr" die Remote-IP
 *             erhaelt in "daten" empfangene Daten mit Byteanzahl "len"
 *             setzt zu sendende Daten in "daten" (mit Maximalanzahl SML3_NW_MBCAST_MAXDATA Bytes)
 *               und neue Byteanzahl in "len"
 *             gibt zurueck: 1 = Antwort senden oder 0 = nicht senden
 *            )
 *        oder NULL = keine (sendet Antwort)
 * 5.Arg: 1.Arg der Funktion (siehe 4.Arg) oder NULL
 * Rueckgabe: >=0 = OK  (bei Fork: Server-PID, ansonsten 0)
 *             -1 = Fehler
 * SML3-errno-Wert: EINVAL = Fehler Uebergabeparameter
 *                  EAFNOSUPPORT = IPv6 nicht verfuegbar
 *                  ENOENT = Fehler bei pipe()
 *                  ECHILD = Fehler bei fork()
 *                  ENOMEM = Allokationsfehler
 *                  EIO = Fehler bei select()
 *                  weitere aus SML3_nw_udp_server()
 */
int
SML3_nw_mbcast_server(const char *port, int ipv, int *fkpd, int (*cfunk)(void *, const char *, char *, size_t *), void *vptr)
{
  pid_t cpid;
  int retw, pd[2];

  if (ipv != 4 && ipv != 6) { SML3_fehlernew(EINVAL, "%s", SML3_strerror(EINVAL)); return -1; }
  if (port == NULL || *port == '\0') { SML3_fehlernew(EINVAL, "%s", SML3_strerror(EINVAL)); return -1; }
#ifndef AF_INET6
  if (ipv == 6) { SML3_fehlernew(EAFNOSUPPORT, "IPv6: %s", SML3_strerror(EAFNOSUPPORT)); return -1; }
#endif

  if (fkpd != NULL) {
    long imax;
    struct sigaction sac;
    sigset_t sa_mask;

    if (pipe(pd) < 0) { SML3_fehlernew(ENOENT, "pipe: %s", SML3_strerror(errno)); return -1; }

    if ((cpid = fork()) == (pid_t)-1) {
      SML3_fehlernew(ECHILD, "fork: %s", SML3_strerror(errno));
      close(pd[0]); close(pd[1]);
      return -1;
    }
    if (cpid != 0) {  /* parent */
      close(pd[0]);
      *fkpd = pd[1];
      sleep(1);
      return (int)cpid;
    }
    close(pd[1]);

    /* Deskriptoren schliessen */
    imax = sysconf(_SC_OPEN_MAX);
    if (imax > 9999L || imax < 0) { imax = 9999L; }
    while (--imax >= 0) {
      if ((int)imax == STDIN_FILENO) { continue; }
      if ((int)imax == STDOUT_FILENO) { continue; }
      if ((int)imax == STDERR_FILENO) { continue; }
      if ((int)imax == pd[0]) { continue; }
      close((int)imax);
    }

    memset(&sac, 0, sizeof(sac));
    sac.sa_handler = SIG_DFL;
    sigaction(SIGCHLD, &sac, NULL);
    sigemptyset(&sa_mask);
    sigprocmask(SIG_SETMASK, &sa_mask, NULL);
    fcntl(pd[0], F_SETFL, fcntl(pd[0], F_GETFL, 0) | O_NONBLOCK);
  } else {
    pd[0] = -1;
  }

  retw = -1;

#ifdef AF_INET6
  if (ipv == 6) {
    retw = server_mcast(pd[0], port, cfunk, vptr);
    if (retw < 0) { SML3_fehleradd(NULL); }
  }
#endif

  if (ipv == 4) {
    retw = server_bcast(pd[0], port, cfunk, vptr);
    if (retw < 0) { SML3_fehleradd(NULL); }
  }

  if (pd[0] >= 0) { close(pd[0]); _exit(0); }

  return retw;
} /* Ende SML3_nw_mbcast_server */


/* SML3_nw_mbcast_client [ohne Auswerte-Funktion: thread-sicher]:
 * Broadcast- bzw. Multicast-Client
 * versendet Datenpaket via Broadcast- bzw. Multicast und empfaengt Antworten
 * 1.Arg: Name des zu verwendenden Interfaces (ohne Alias-Erweiterung) oder NULL = alle
 * 2.Arg: Servicename oder Port
 * 3.Arg: IP-Version
 *         4 = IPv4  (Broadcast)
 *        44 = IPv4  (falls 1.Arg = NULL: Broadcast zusaetzlich an 255.255.255.255)
 *         6 = IPv6  (Multicast)
 *         0 = egal  (Broadcast + Multicast)
 * 4.Arg: Timeout in Sekunden fuer Warten auf Antworten
 *        oder 0 = nach erster Antwort abbrechen (Timeout ist 5 Sekunden)
 * 5.Arg: zu versendende Daten (beschraenkt auf SML3_NW_MBCAST_MAXDATA Bytes)
 *        oder NULL = egal
 * 6.Arg: Byteanzahl im 5.Arg
 *        oder 0, wenn 5.Arg = NULL
 * 7.Arg: fuer Rueckgabe Antworten (verkettete Liste), ist mit SML3_nw_mbcast_free() freizugeben
 * Rueckgabe: >=0: OK (Anzahl Antworten) oder -1 = Fehler
 * SML3-errno-Wert: EINVAL = Fehler Uebergabeparameter
 *                  EAFNOSUPPORT = IPv6 nicht verfuegbar
 *                  ENOMEM = Allokationsfehler
 *                  EIO = Fehler bei select()
 *                  weitere aus SML3_nw_if_get()
 */
int
SML3_nw_mbcast_client(const char *iface, const char *port, int ipv, int timo, const void *daten, size_t datsize, struct SML3_nw_mbcast **iplist)
{
  struct SML3_nw_mbcast **ipptr;
  struct SML3_nw_if *ifi, *ifihead;
  struct mb_send *mbsend;
  int max_mbsend, mbnr, retw, mit255;
  ssize_t rlen;

  if (ipv == 44) { mit255 = 1; ipv = 4; } else { mit255 = 0; }
  if ((ipv != 4 && ipv != 6 && ipv != 0) || iplist == NULL) { SML3_fehlernew(EINVAL, "%s", SML3_strerror(EINVAL)); return -1; }
  if (port == NULL || *port == '\0') { SML3_fehlernew(EINVAL, "%s", SML3_strerror(EINVAL)); return -1; }
#ifndef AF_INET6
  if (ipv == 6) { SML3_fehlernew(EAFNOSUPPORT, "IPv6: %s", SML3_strerror(EAFNOSUPPORT)); return -1; }
  if (ipv == 0) { ipv = 4; }
#endif

  if (daten == NULL || datsize == 0) { daten = (const void *)"\n"; datsize = 1; }
  *iplist = NULL;
  ipptr = iplist;

  ifihead = SML3_nw_if_get(ipv, 1);
  if (ifihead == NULL) {
    if (SML3_fehlererrno() == 0) { return 0; } else { SML3_fehleradd(NULL); return -1; }
  }

  mbsend = NULL;
  max_mbsend = 0;

  /* evtl. allgemeine Broadcast-Adresse hinzufuegen */
  if ((iface == NULL || *iface == '\0') && mit255) {
    struct SML3_nw_if **ifipp;
    for (ifipp = &ifihead; *ifipp != NULL; ifipp = &(*ifipp)->next) { ; }
    if ((*ifipp = calloc(1, sizeof(**ifipp))) == NULL) {
      SML3_fehlernew(ENOMEM, "calloc: %s", SML3_strerror(errno));
      SML3_nw_if_free(ifihead);
      return -1;
    }
    (*ifipp)->next = NULL;
    (*ifipp)->family = 4;
    (*ifipp)->index = -1;
    (*ifipp)->flags = (IFF_UP | IFF_BROADCAST);
    snprintf((*ifipp)->brdaddr, sizeof((*ifipp)->brdaddr), "255.255.255.255");
  }

  /* Schnittstellen durchgehen */
  for (ifi = ifihead; ifi != NULL; ifi = ifi->next) {
    char *pt1;
    if ((pt1 = strchr(ifi->name, ':')) != NULL) { *pt1 = '\0'; }  /* Alias? */
    if (iface != NULL && strcmp(iface, ifi->name) != 0) { continue; }
    if (pt1 != NULL) { *pt1 = ':'; }
    if (!(ifi->flags & IFF_UP)) { continue; }
    if (ifi->flags & IFF_LOOPBACK) { continue; }
    if (ifi->flags & IFF_POINTOPOINT) { continue; }
#ifdef AF_INET6
    if (ifi->family != 4 && ifi->family != 6) { continue; }
    if (ifi->family == 6 && !(ifi->flags & IFF_MULTICAST)) { continue; }
#else
    if (ifi->family != 4) { continue; }
#endif
    if (ifi->family == 4 && !(ifi->flags & IFF_BROADCAST)) { continue; }

    /* an Schnittstelle Broadcast bzw. Multicast absenden */
    if (max_mbsend == 0) {
      mbsend = malloc(sizeof(*mbsend));
    } else {
      mbsend = realloc(mbsend, (max_mbsend + 1) * sizeof(*mbsend));
    }
    if (mbsend == NULL) {
      SML3_fehlernew(ENOMEM, "malloc/realloc: %s", SML3_strerror(errno));
      for (mbnr = 0; mbnr < max_mbsend; mbnr++) {
        if (mbsend[mbnr].sockfd >= 0) { close(mbsend[mbnr].sockfd); free(mbsend[mbnr].sad); }
      }
      SML3_nw_if_free(ifihead);
      return -1;
    }
    max_mbsend++;

    mbsend[max_mbsend - 1].ipv = ifi->family;
    if (mbsend[max_mbsend - 1].ipv == 4) {
      mbsend[max_mbsend - 1].daddr = ifi->brdaddr;
#ifdef AF_INET6
    } else {
      mbsend[max_mbsend - 1].daddr = mcadd;
#endif
    }
    mbsend[max_mbsend - 1].iface = ifi->name;

    mbsend[max_mbsend - 1].sockfd =
      SML3_nw_udp_client(mbsend[max_mbsend - 1].daddr, port,
                        &mbsend[max_mbsend - 1].ipv,
                        &mbsend[max_mbsend - 1].sad,
                        &mbsend[max_mbsend - 1].salen,
                        NULL
                       );
    if (mbsend[max_mbsend - 1].sockfd < 0) { max_mbsend--; continue; }

    if (mbsend[max_mbsend - 1].ipv == 4) {
      int on = 1;
      setsockopt(mbsend[max_mbsend - 1].sockfd, SOL_SOCKET, SO_BROADCAST, &on, (socklen_t)sizeof(on));
#ifdef AF_INET6
    } else {
      int on = ifi->index;
      setsockopt(mbsend[max_mbsend - 1].sockfd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &on, (socklen_t)sizeof(on));
#endif
    }

    rlen = sendto(mbsend[max_mbsend - 1].sockfd, daten, datsize, 0, mbsend[max_mbsend - 1].sad, mbsend[max_mbsend - 1].salen);
    if (rlen != (ssize_t)datsize) {
      close(mbsend[max_mbsend - 1].sockfd); free(mbsend[max_mbsend - 1].sad);
      max_mbsend--;
      continue;
    }
  }

  /* Antworten erhalten */
  { fd_set oset, aset;
    struct timeval tv;
    int maxfd, erg;
    socklen_t salen;
    char vdaten[SML3_NW_MBCAST_MAXDATA];

    FD_ZERO(&oset);
    for (maxfd = -1, mbnr = 0; mbnr < max_mbsend; mbnr++) {
      fcntl(mbsend[mbnr].sockfd, F_SETFL, fcntl(mbsend[mbnr].sockfd, F_GETFL, 0) | O_NONBLOCK);
      FD_SET(mbsend[mbnr].sockfd, &oset);
      if (mbsend[mbnr].sockfd > maxfd) { maxfd = mbsend[mbnr].sockfd; }
    }
    maxfd++;

    retw = 0;
    while (maxfd > 0) {
      aset = oset;
      tv.tv_sec = (timo == 0 ? 5 : timo);
      tv.tv_usec = 0;
      erg = select(maxfd, &aset, NULL, NULL, &tv);
      if (erg < 0) {
        if (errno == EINTR) { continue; }
        SML3_fehlernew(EIO, "select: %s", SML3_strerror(errno));
        retw = -1;
        break;
      }
      if (erg == 0) { break; }  /* Timeout */

      for (mbnr = 0; mbnr < max_mbsend; mbnr++) {
        if (mbsend[mbnr].sockfd >= 0 && FD_ISSET(mbsend[mbnr].sockfd, &aset)) {
          salen = mbsend[mbnr].salen;
          rlen = recvfrom(mbsend[mbnr].sockfd, vdaten, sizeof(vdaten), 0, mbsend[mbnr].sad, &salen);
          if (rlen < 0) {
#ifdef EWOULDBLOCK
            if (errno == EWOULDBLOCK) { errno = EAGAIN; }
#endif
            if (errno == EAGAIN) { continue; }
          }
          if (rlen <= 0) {
            FD_CLR(mbsend[mbnr].sockfd, &oset);
            close(mbsend[mbnr].sockfd); free(mbsend[mbnr].sad);
            mbsend[mbnr].sockfd = -1;
            for (maxfd = -1, erg = 0; erg < max_mbsend; erg++) {
              if (mbsend[erg].sockfd >= 0 && mbsend[erg].sockfd > maxfd) { maxfd = mbsend[erg].sockfd; }
            }
            maxfd++;
            continue;
          }

          *ipptr = malloc(sizeof(**ipptr));
          if (*ipptr == NULL) { SML3_fehlernew(ENOMEM, "malloc: %s", SML3_strerror(errno)); retw = -1; break; }

          (*ipptr)->next = NULL;
          (*ipptr)->ipv = mbsend[mbnr].ipv;
          if (SML3_nw_udp_peername(mbsend[mbnr].sockfd, mbsend[mbnr].sad, salen,
                                  (*ipptr)->ipnr, sizeof((*ipptr)->ipnr), NULL) < 0) {
            snprintf((*ipptr)->ipnr, sizeof((*ipptr)->ipnr), "[?]");
          }
          snprintf((*ipptr)->iface, sizeof((*ipptr)->iface), "%s", mbsend[mbnr].iface);
          if (rlen > (ssize_t)sizeof((*ipptr)->daten)) { rlen = sizeof((*ipptr)->daten); }
          memmove((*ipptr)->daten, vdaten, rlen);
          (*ipptr)->datlen = rlen;

          ipptr = &(*ipptr)->next;
          retw++;

          if (timo == 0) { break; }
        }
      }

      if (mbnr < max_mbsend) { break; }
    }
  }

  for (mbnr = 0; mbnr < max_mbsend; mbnr++) {
    if (mbsend[mbnr].sockfd >= 0) { close(mbsend[mbnr].sockfd); free(mbsend[mbnr].sad); }
  }
  if (max_mbsend > 0) { free(mbsend); }
  SML3_nw_if_free(ifihead);

  return retw;
} /* Ende SML3_nw_mbcast_client */


/* SML3_nw_mbcast_free: [thread-sicher]
 * gibt mit SML3_nw_mbcast_client() erstellte Antwort-Liste frei
 * 1.Arg: Antwort-Liste
 */
void
SML3_nw_mbcast_free(struct SML3_nw_mbcast *iplist)
{
  struct SML3_nw_mbcast *ipnext;
  for (; iplist != NULL; iplist = ipnext) {
    ipnext = iplist->next;
    free(iplist);
  }
} /* Ende SML3_nw_mbcast_free */


/* Broadcast-Server */
static int
server_bcast(int parentfd, const char *port, int (*cfunk)(void *, const char *, char *, size_t *), void *vptr)
{
  int sockfd, ipv, maxfd, retw, fcval;
  struct sockaddr *sad;
  socklen_t salen, satmp;
  fd_set oset, aset;
  char buf[SML3_NW_MBCAST_MAXDATA], ipnr[64];
  ssize_t rlen;
  size_t slen;

  /* Server-Socket oeffnen */
  ipv = 4;
  sockfd = SML3_nw_udp_server(NULL, port, &ipv, &salen, NULL);
  if (sockfd < 0) { SML3_fehleradd(NULL); return -1; }

  if ((sad = malloc(salen)) == NULL) { 
    SML3_fehlernew(ENOMEM, "malloc: %s", SML3_strerror(errno));
    close(sockfd);
    return -1;
  }

  fcval = fcntl(sockfd, F_GETFL, 0);
  fcntl(sockfd, F_SETFL, fcval | O_NONBLOCK);

  /* Anfragen beantworten */
  FD_ZERO(&oset);
  FD_SET(sockfd, &oset); maxfd = sockfd;
  if (parentfd >= 0) {
    FD_SET(parentfd, &oset);
    maxfd = (parentfd > maxfd ? parentfd : maxfd);
  }
  maxfd++;

  retw = 0;
  for (;;) {
    aset = oset;
    if (select(maxfd, &aset, NULL, NULL, NULL) < 0) {
      if (errno == EINTR) { continue; }
      SML3_fehlernew(EIO, "select: %s", SML3_strerror(errno));
      retw = -1;
      break;
    }

    if (parentfd >= 0 && FD_ISSET(parentfd, &aset)) {
      ssize_t sr = read(parentfd, buf, sizeof(buf));
      if (sr == 0) { break; }  /* Parent beendet */
      if (sr < 0) {
#ifdef EWOULDBLOCK
        if (errno == EWOULDBLOCK) { errno = EAGAIN; }
#endif
        if (errno != EAGAIN) { break; }
      }
    }

    while (FD_ISSET(sockfd, &aset)) {  /* Anfrage(n) beantworten */
      satmp = salen;
      rlen = recvfrom(sockfd, buf, sizeof(buf), 0, sad, &satmp);
      if (rlen < 0) {
#ifdef EWOULDBLOCK
        if (errno == EWOULDBLOCK) { errno = EAGAIN; }
#endif
        if (errno != EAGAIN) { fprintf(stderr, "SML3_nw_mbcast_server (server_bcast): recvfrom: %s\n", SML3_strerror(errno)); }
        break;
      }
      if (rlen == 0) {  /* shutdown? */
        fprintf(stderr, "SML3_nw_mbcast_server (server_bcast): shutdown\n");
        break;
      }

      /* Antwort */
      if (SML3_nw_udp_peername(sockfd, sad, satmp, ipnr, sizeof(ipnr), NULL) < 0) {
        snprintf(ipnr, sizeof(ipnr), "[?]");
      }
      slen = rlen;
      if (cfunk != NULL) {  /* Auswertefunktion */
        if (cfunk(vptr, ipnr, buf, &slen) > 0) {  /* Antwort senden */
          if (slen == 0) { snprintf(buf, sizeof(buf), "\n"); slen = 1; }
          if (slen > sizeof(buf)) { slen = sizeof(buf); }
          fcntl(sockfd, F_SETFL, fcval & ~O_NONBLOCK);
          rlen = sendto(sockfd, buf, slen, 0, sad, satmp);
          fcntl(sockfd, F_SETFL, fcval | O_NONBLOCK);
        } else {
          slen = rlen;
        }
      } else {
        snprintf(buf, sizeof(buf), "\n"); slen = 1;
        fcntl(sockfd, F_SETFL, fcval & ~O_NONBLOCK);
        rlen = sendto(sockfd, buf, slen, 0, sad, satmp);
        fcntl(sockfd, F_SETFL, fcval | O_NONBLOCK);
      }
      if (rlen != (ssize_t)slen) {
        fprintf(stderr, "SML3_nw_mbcast_server (server_bcast): sendto \"%s\": short write (%d bytes from %d)\n",
                ipnr, (int)rlen, (int)slen);
      }
    }
  }

  close(sockfd);
  free(sad);

  return retw;
} /* Ende server_bcast */


#ifdef AF_INET6
/* Multicast-Server */
static int
server_mcast(int parentfd, const char *port, int (*cfunk)(void *, const char *, char *, size_t *), void *vptr)
{
  int sockfd, ipv, maxfd, retw, fcval;
  struct sockaddr *sad;
  socklen_t salen, satmp;
  fd_set oset, aset;
  char buf[SML3_NW_MBCAST_MAXDATA], ipnr[64];
  ssize_t rlen;
  size_t slen;
# ifdef SML3_HAVE_NAMEINDEX
  struct ipv6_mreq mreq6;
  struct if_nameindex *ifn1, *ifn2;
  char bif[512];
# endif

  /* Server-Socket oeffnen */
  ipv = 6;
  sockfd = SML3_nw_udp_server(NULL, port, &ipv, &salen, NULL);
  if (sockfd < 0) { SML3_fehleradd(NULL); return -1; }

  if ((sad = malloc(salen)) == NULL) {
    SML3_fehlernew(ENOMEM, "malloc: %s", SML3_strerror(errno));
    close(sockfd);
    return -1;
  }

# ifdef SML3_HAVE_NAMEINDEX
  SML3_inet_pton(AF_INET6, mcadd, &mreq6.ipv6mr_multiaddr);
  mreq6.ipv6mr_interface = 0;
  ifn1 = if_nameindex();
  slen = 0;
  retw = snprintf(bif + slen, sizeof(bif) - slen, "SML3_nw_mbcast_server (server_mcast): adding membership for \"");
  if (retw > 0 && retw < (int)(sizeof(bif) - slen)) { slen += retw; }
  if (ifn1 != NULL) {
    int leer = 0;
    ifn2 = ifn1;
    while (ifn2->if_index > 0) {
      mreq6.ipv6mr_interface = ifn2->if_index;
      if (setsockopt(sockfd, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &mreq6, (socklen_t)sizeof(mreq6)) == 0) {
        retw = snprintf(bif + slen, sizeof(bif) - slen, "%s%s", leer ? " " : "", ifn2->if_name);
        if (retw > 0 && retw < (int)(sizeof(bif) - slen)) { slen += retw; }
        leer = 1;
      }
      ifn2++;
    }
    if_freenameindex(ifn1);
  }
  retw = snprintf(bif + slen, sizeof(bif) - slen, "\"");
  if (retw > 0 && retw < (int)(sizeof(bif) - slen)) { slen += retw; }
  fprintf(stderr, "%s\n", bif);
# endif

  fcval = fcntl(sockfd, F_GETFL, 0);
  fcntl(sockfd, F_SETFL, fcval | O_NONBLOCK);

  /* Anfragen beantworten */
  FD_ZERO(&oset);
  FD_SET(sockfd, &oset); maxfd = sockfd;
  if (parentfd >= 0) {
    FD_SET(parentfd, &oset);
    maxfd = (parentfd > maxfd ? parentfd : maxfd);
  }
  maxfd++;

  retw = 0;
  for (;;) {
    aset = oset;
    if (select(maxfd, &aset, NULL, NULL, NULL) < 0) {
      if (errno == EINTR) { continue; }
      SML3_fehlernew(EIO, "select: %s", SML3_strerror(errno));
      retw = -1;
      break;
    }

    if (parentfd >= 0 && FD_ISSET(parentfd, &aset)) {
      ssize_t sr = read(parentfd, buf, sizeof(buf));
      if (sr == 0) { break; }  /* Parent beendet */
      if (sr < 0) {
#ifdef EWOULDBLOCK
        if (errno == EWOULDBLOCK) { errno = EAGAIN; }
#endif
        if (errno != EAGAIN) { break; }
      }
    }

    while (FD_ISSET(sockfd, &aset)) {  /* Anfrage(n) beantworten */
      satmp = salen;
      rlen = recvfrom(sockfd, buf, sizeof(buf), 0, sad, &satmp);
      if (rlen < 0) {
# ifdef EWOULDBLOCK
        if (errno == EWOULDBLOCK) { errno = EAGAIN; }
# endif
        if (errno != EAGAIN) { fprintf(stderr, "SML3_nw_mbcast_server (server_mcast): recvfrom: %s\n", SML3_strerror(errno)); }
        break;
      }
      if (rlen == 0) {  /* shutdown? */
        fprintf(stderr, "SML3_nw_mbcast_server (server_mcast): shutdown\n");
        break;
      }

      /* Antwort */
      if (SML3_nw_udp_peername(sockfd, sad, satmp, ipnr, sizeof(ipnr), NULL) < 0) {
        snprintf(ipnr, sizeof(ipnr), "[?]");
      }
      slen = rlen;
      if (cfunk != NULL) {  /* Auswertefunktion */
        if (cfunk(vptr, ipnr, buf, &slen) > 0) {  /* Antwort senden */
          if (slen == 0) { snprintf(buf, sizeof(buf), "\n"); slen = 1; }
          if (slen > sizeof(buf)) { slen = sizeof(buf); }
          fcntl(sockfd, F_SETFL, fcval & ~O_NONBLOCK);
          rlen = sendto(sockfd, buf, slen, 0, sad, satmp);
          fcntl(sockfd, F_SETFL, fcval | O_NONBLOCK);
        } else {
          slen = rlen;
        }
      } else {
        snprintf(buf, sizeof(buf), "\n"); slen = 1;
        fcntl(sockfd, F_SETFL, fcval & ~O_NONBLOCK);
        rlen = sendto(sockfd, buf, slen, 0, sad, satmp);
        fcntl(sockfd, F_SETFL, fcval | O_NONBLOCK);
      }
      if (rlen != (ssize_t)slen) {
        fprintf(stderr, "SML3_nw_mbcast_server (server_mcast): sendto \"%s\": short write (%d bytes from %d)\n",
                ipnr, (int)rlen, (int)slen);
      }
    }
  }

  close(sockfd);
  free(sad);

  return retw;
} /* Ende server_mcast */
#endif
