/*
 * ==========================================================================
 *         _   _      _   ____            __ __  __      _
 *        | \ | | ___| |_|  _ \ ___ _ __ / _|  \/  | ___| |_ ___ _ __
 *        |  \| |/ _ \ __| |_) / _ \ '__| |_| |\/| |/ _ \ __/ _ \ '__|
 *        | |\  |  __/ |_|  __/  __/ |  |  _| |  | |  __/ ||  __/ |
 *        |_| \_|\___|\__|_|   \___|_|  |_| |_|  |_|\___|\__\___|_|
 *
 *                  NetPerfMeter -- Network Performance Meter
 *                 Copyright (C) 2009-2026 by Thomas Dreibholz
 * ==========================================================================
 *
 * This program 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 3 of the License, or
 * (at your option) any later version.

 * This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Contact:  dreibh@simula.no
 * Homepage: https://www.nntb.no/~dreibh/netperfmeter/
 */

#include "assure.h"
#include "flow.h"
#include "control.h"
#include "loglevel.h"
#include "transfer.h"
#include "package-version.h"

#include <cerrno>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <getopt.h>
#include <iostream>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <poll.h>
#include <signal.h>
#include <unistd.h>

#if !defined(HAVE_SCTP)
#warning SCTP is not supported by the API of this system!
#endif
#if !defined(HAVE_MPTCP)
#warning MPTCP is not supported by the API of this system!
#endif
#if !defined(HAVE_DCCP)
#warning DCCP is not supported by the API of this system!
#endif
#if !defined(HAVE_QUIC)
#warning QUIC is not supported by the API of this system!
#endif

#if defined(HAVE_LIBIBERTY)
#include <libiberty.h>
extern "C" {
int getopt_long_only(int argc, char* const* argv, const char* optstring,
                     const struct option* longopts, int* longindex);
}
#endif


#define MAX_LOCAL_ADDRESSES 16
static unsigned int     gLocalDataAddresses = 0;
static sockaddr_union   gLocalDataAddressArray[MAX_LOCAL_ADDRESSES];
static unsigned int     gLocalControlAddresses = 0;
static sockaddr_union   gLocalControlAddressArray[MAX_LOCAL_ADDRESSES];

static const char*      gActiveNodeName        = "Client";
static const char*      gPassiveNodeName       = "Server";
static bool             gBindV6Only            = false;
static int              gSndBufSize            = -1;
static int              gRcvBufSize            = -1;
static int              gFlowCount             = 1;
#if defined(HAVE_SCTP)
static int              gActiveControlProtocol = IPPROTO_SCTP;
static int              gPassiveControlSCTP    = true;
#else
static int              gActiveControlProtocol = IPPROTO_TCP;
#endif
static int              gPassiveControlTCP     = true;
#if defined(HAVE_MPTCP)
static int              gPassiveControlMPTCP   = false;
#endif
static int              gControlSocket         = -1;
static int              gControlSocketTCP      = -1;
#if defined(__OpenBSD__)
static int              gControlSocketTCP6     = -1;
#endif
#if defined(HAVE_SCTP)
static int              gSCTPSocket            = -1;
#endif
static int              gTCPSocket             = -1;
#if defined(__OpenBSD__)
static int              gTCP6Socket            = -1;
#endif
#if defined(HAVE_MPTCP)
static int              gMPTCPSocket           = -1;
#endif
static int              gUDPSocket             = -1;
#if defined(__OpenBSD__)
static int              gUDP6Socket            = -1;
#endif
#if defined(HAVE_DCCP)
static int              gDCCPSocket            = -1;
#endif
#if defined(HAVE_QUIC)
static int              gQUICSocket            = -1;
static const char*      gQUICCA                = nullptr;
static const char*      gQUICCertificate       = nullptr;
static const char*      gQUICKey               = nullptr;
static const char*      gQUICHostname          = nullptr;
#endif
static double           gRuntime               = -1.0;
static bool             gDisplayEnabled        = true;
static bool             gStopTimeReached       = false;
static const char*      gVectorNamePattern     = "";
static OutputFileFormat gVectorFileFormat      = OFF_None;
static const char*      gScalarNamePattern     = "";
static OutputFileFormat gScalarFileFormat      = OFF_None;
static const char*      gConfigName            = "";

struct AssocSpec
{
   int                      Protocol;
   std::vector<const char*> Flows;
};
static std::vector<AssocSpec> gAssocSpecs;

// This is the MessageReader for the Control messages only!
// (The Data messages are handled by the Flow Manager)
static MessageReader gMessageReader;


// ###### Version ###########################################################
[[ noreturn ]] static void version()
{
   std::cerr << "NetPerfMeter" << " " << NETPERFMETER_VERSION << "\n";
   exit(0);
}


// ###### Usage #############################################################
[[ noreturn ]] static void usage(const char* program, const int exitCode)
{
   std::cerr << "Usage:\n"
      << "* Passive (Server) Side:\n  " << program
      << " local_port\n"
         "    [-x|--control-over-sctp|-y|--control-over-tcp|--control-over-mptcp]\n"
         "    [-L address[,address,...]|--local address[,address,...]]\n"
         "    [-l address[,address,...]|--controllocal address[,address,...]]\n"
         "    [-K|--tls-key key_file] [-J|--tls-cert certificate_file] [-I|--tls-ca ca_certificate_file]\n"
         "    [-6|--v6only]\n"
         "    [--display|--nodisplay]\n"
         "    [--loglevel level]\n"
         "    [--logcolor on|off]\n"
         "    [--logfile file]\n"
         "    [--logappend file]\n"
         "    [-q|--quiet]\n"
         "    [-!|--verbose]\n"
      << "* Active (Client) Side:\n  " << program << " remote_endpoint:remote_port\n"
         "    [-x|--control-over-sctp|-X|--no-control-over-sctp]\n"
         "    [-y|--control-over-tcp|-Y|--no-control-over-tcp|--control-over-mptcp|--no-control-over-mptcp]\n"
         "    [-L address[,address,...]|--local address[,address,...]]\n"
         "    [-l address[,address,...]|--controllocal address[,address,...]]\n"
         "    [-6|--v6only]\n"
         "    [--display|--nodisplay]\n"
         "    [-o bytes|--sndbuf bytes]\n"
         "    [-i bytes|--rcvbuf bytes]\n"
         "    [-T seconds|--runtime seconds]\n"
         "    [-C configuration_file_pattern|--config configuration_file_pattern]\n"
         "    [-S scalar_file_pattern|--scalar scalar_file_pattern]\n"
         "    [-V vector_file_pattern|--vector vector_file_pattern]\n"
         "    [-A description|--activenodename description]\n"
         "    [-P description|--passivenodename description]\n"
         "    [-H|--tls-hostname hostname]\n"
         "    [-t|--tcp FLOWSPEC]\n"
         "    [-m|--mptcp FLOWSPEC]\n"
         "    [-u|--udp FLOWSPEC]\n"
         "    [-d|--dccp FLOWSPEC]\n"
         "    [-s|--sctp FLOWSPEC [FLOWSPEC ...]]\n"
         "    [-k|--quic FLOWSPEC [FLOWSPEC ...]]\n"
         "    [--loglevel level]\n"
         "    [--logcolor on|off]\n"
         "    [--logfile file]\n"
         "    [--logappend file]\n"
         "    [-q|--quiet]\n"
         "    [-!|--verbose]\n"
         "  with FLOWSPEC := outgoing_frame_rate:outgoing_frame_size:incoming_frame_rate:incoming_frame_size:[options[:...]\n"
      << "* Version:\n  " << program << " [-v|--version]\n"
      << "* Help:\n  "    << program << " [-h|--help]\n";
   exit(exitCode);
}


// ###### Handle global command-line parameter ##############################
bool handleGlobalParameters(int argc, char** argv)
{
   const static struct option long_options[] = {
      { "control-over-sctp",             no_argument,       0, 'x'    },
      { "no-control-over-sctp",          no_argument,       0, 'X'    },
      { "control-over-tcp",              no_argument,       0, 'y'    },
      { "no-control-over-tcp",           no_argument,       0, 'Y'    },
      { "control-over-mptcp",            no_argument,       0, 'w'    },
      { "no-control-over-mptcp",         no_argument,       0, 'W'    },
      { "local",                         required_argument, 0, 'L'    },
      { "controllocal",                  required_argument, 0, 'l'    },
      { "display",                       no_argument,       0, 0x2001 },
      { "nodisplay",                     no_argument,       0, 0x2002 },
      { "v6only",                        no_argument,       0, '6'    },

      { "runtime",                       required_argument, 0, 'T'    },
      { "sndbuf",                        required_argument, 0, 'o'    },
      { "rcvbuf",                        required_argument, 0, 'i'    },
      { "count",                         required_argument, 0, 'N'    },
      { "config",                        required_argument, 0, 'C'    },
      { "scalar",                        required_argument, 0, 'S'    },
      { "vector",                        required_argument, 0, 'V'    },
      { "activenodename",                required_argument, 0, 'A'    },
      { "passivenodename",               required_argument, 0, 'P'    },
      { "tls-key",                       required_argument, 0, 'K'    },
      { "tls-cert",                      required_argument, 0, 'J'    },
      { "tls-ca",                        required_argument, 0, 'I'    },
      { "tls-hostname",                  required_argument, 0, 'H'    },

      { "tcp",                           required_argument, 0, 't'    },
      { "mptcp",                         required_argument, 0, 'm'    },
      { "udp",                           required_argument, 0, 'u'    },
      { "dccp",                          required_argument, 0, 'd'    },
      { "sctp",                          required_argument, 0, 's'    },
      { "quic",                          required_argument, 0, 'k'    },

      { "loglevel",                      required_argument, 0, 0x3000 },
      { "logcolor",                      required_argument, 0, 0x3001 },
      { "logappend",                     required_argument, 0, 0x3010 },
      { "logfile",                       required_argument, 0, 0x3011 },
      { "quiet",                         no_argument,       0, 'q'    },
      { "verbose",                       no_argument,       0, '!'    },

      { "help",                          no_argument,       0, 'h'    },
      { "version",                       no_argument,       0, 'v'    },
      {  nullptr,                        0,                 0, 0      }
   };

   int      option;
   int      longIndex;
   while( (option = getopt_long_only(argc, argv, "xXyYwWL:l:6T:o:i:N:C:S:V:A:P:K:J:H:t:m:u:d:s:k:q!hv", long_options, &longIndex)) != -1 ) {
      switch(option) {
         case 'x':
#if defined(HAVE_SCTP)
            gActiveControlProtocol = IPPROTO_SCTP;
            gPassiveControlSCTP    = true;
#else
            std::cerr << "ERROR: SCTP support is not compiled in!" << "\n";
            exit(1);
#endif
          break;
         case 'X':
#if defined(HAVE_SCTP)
            gPassiveControlSCTP    = false;
#else
            std::cerr << "ERROR: SCTP support is not compiled in!" << "\n";
            exit(1);
#endif
          break;
         case 'y':
            gActiveControlProtocol = IPPROTO_TCP;
            gPassiveControlTCP     = true;
          break;
         case 'Y':
            gPassiveControlTCP     = false;
#if defined(HAVE_MPTCP)
            gPassiveControlMPTCP   = false;
#endif
          break;
         case 'w':
#if defined(HAVE_MPTCP)
            gActiveControlProtocol = IPPROTO_MPTCP;
            gPassiveControlTCP     = true;
            gPassiveControlMPTCP   = true;
#else
            std::cerr << "ERROR: MPTCP support is not compiled in!" << "\n";
            exit(1);
#endif
          break;
         case 'W':
#if defined(HAVE_MPTCP)
            gPassiveControlMPTCP   = false;
#endif
          break;
         case 'L':
            {
               gLocalDataAddresses = 0;
               char* address = optarg;
               while(gLocalDataAddresses < MAX_LOCAL_ADDRESSES) {
                  char* idx = strchr(address, ',');
                  if(idx) {
                     *idx = 0x00;
                  }
                  if(!string2address(address, &gLocalDataAddressArray[gLocalDataAddresses])) {
                     std::cerr << "ERROR: Bad address " << address
                               << " for local data endpoint! Use format <address:port>.\n";
                     exit(1);
                  }
                  gLocalDataAddresses++;
                  if(!idx) {
                     break;
                  }
                  address = idx;
                  address++;
               }
            }
          break;
         case 'l':
            {
               gLocalControlAddresses = 0;
               char* address = optarg;
               while(gLocalControlAddresses < MAX_LOCAL_ADDRESSES) {
                  char* idx = strchr(address, ',');
                  if(idx) {
                     *idx = 0x00;
                  }
                  if(!string2address(address, &gLocalControlAddressArray[gLocalControlAddresses])) {
                     std::cerr << "ERROR: Bad address " << address
                               << " for local control endpoint! Use format <address:port>.\n";
                     exit(1);
                  }
                  gLocalControlAddresses++;
                  if(!idx) {
                     break;
                  }
                  address = idx;
                  address++;
               }
            }
          break;
         case '6':
            gBindV6Only = true;
          break;
         case 0x2001:
            gDisplayEnabled = true;
          break;
         case 0x2002:
            gDisplayEnabled = false;
          break;
         case 'o':
            gSndBufSize = atol(optarg);
          break;
         case 'i':
            gRcvBufSize = atol(optarg);
          break;
         case 'N':
            gFlowCount = atol(optarg);
            if( (gFlowCount < 1) || (gFlowCount > 256) ) {
               std::cerr << "ERROR: Invalid flow count " << gFlowCount << "!\n";
               exit(1);
            }
          break;
         case 'T':
            gRuntime = atof(optarg);
          break;
         case 'C':
            gConfigName = optarg;
          break;
         case 'S':
            gScalarNamePattern = optarg;
            if(gScalarNamePattern[0] == 0x00)
               gScalarFileFormat = OFF_None;
            else if(hasSuffix(gScalarNamePattern, ".bz2")) {
               gScalarFileFormat = OFF_BZip2;
            }
            else {
               gScalarFileFormat = OFF_Plain;
            }
          break;
         case 'V':
            gVectorNamePattern = optarg;
            if(gVectorNamePattern[0] == 0x00)
               gVectorFileFormat = OFF_None;
            else if(hasSuffix(gVectorNamePattern, ".bz2")) {
               gVectorFileFormat = OFF_BZip2;
            }
            else {
               gVectorFileFormat = OFF_Plain;
            }
          break;
         case 'A':
            gActiveNodeName = optarg;
          break;
         case 'P':
            gPassiveNodeName = optarg;
          break;
         case 't':
         case 'm':
         case 'u':
         case 'd':
         case 's':
         case 'k':
            {
               AssocSpec flow;
               flow.Flows.push_back(optarg);
               switch(option) {
                  case 's':
                     flow.Protocol = IPPROTO_SCTP;
                     while( (optind < argc) && (argv[optind][0] != '-')) {
                        flow.Flows.push_back(argv[optind]);
                        optind++;
                     }
                   break;
                  case 't':
                     flow.Protocol = IPPROTO_TCP;
                   break;
                  case 'u':
                     flow.Protocol = IPPROTO_UDP;
                   break;
                  case 'm':
#if defined(HAVE_MPTCP)
                     flow.Protocol = IPPROTO_MPTCP;
#else
                     std::cerr << "ERROR: MPTCP support is not compiled in!" << "\n";
                     exit(1);
#endif
                   break;
                  case 'd':
#if defined(HAVE_DCCP)
                     flow.Protocol = IPPROTO_DCCP;
#else
                     std::cerr << "ERROR: DCCP support is not compiled in!" << "\n";
                     exit(1);
#endif
                   break;
                  case 'k':
#if defined(HAVE_QUIC)
                     flow.Protocol = IPPROTO_QUIC;
                     while( (optind < argc) && (argv[optind][0] != '-')) {
                        flow.Flows.push_back(argv[optind]);
                        optind++;
                     }
#else
                     std::cerr << "ERROR: QUIC support is not compiled in!" << "\n";
                     exit(1);
#endif
                   break;
                  default:
                     abort();
                   // break;
               }
               gAssocSpecs.push_back(flow);
            }
          break;
         case 'K':
#if defined(HAVE_QUIC)
            gQUICKey = optarg;
#endif
          break;
         case 'J':
#if defined(HAVE_QUIC)
            gQUICCertificate = optarg;
#endif
          break;
         case 'I':
#if defined(HAVE_QUIC)
            gQUICCA = optarg;
#endif
          break;
         case 'H':
#if defined(HAVE_QUIC)
            gQUICHostname = optarg;
#endif
          break;
         case 0x3000:
            gLogLevel = std::min((unsigned int)atoi(optarg),MAX_LOGLEVEL);
          break;
         case 0x3001:
            if(!(strcmp(optarg,"off"))) {
               gColorMode = false;
            }
            else {
               gColorMode = true;
            }
          break;
         case 0x3010:
            if(!initLogFile(gLogLevel,optarg,"a")) {
               std::cerr << format("ERROR: Failed to initialise log file %s!", optarg) << "\n";
               exit(1);
            }
          break;
         case 0x3011:
            if(!initLogFile(gLogLevel,optarg,"w")) {
               std::cerr << format("ERROR: Failed to initialise log file %s!", optarg) << "\n";
               exit(1);
            }
          break;
         case 'q':
            gLogLevel = LOGLEVEL_ERROR;
          break;
         case '!':
            gLogLevel = LOGLEVEL_TRACE;
          break;
         case 'v':
            version();
          break;
         case 'h':
         case '?':
            // Exit with 0 on h/help, exit with 1 on '?' (unknown option):
            usage(argv[0], (option == 'h') ? 0 : 1);
          break;
         case '-':
          break;
         default:
            // This should not happen: wrong getopt parameters, or missing case?
            fprintf(stderr, "INTERNAL ERROR: Unhandled option c=%c code=%x!\n",
                    (isprint(option) ? (char)option : ' '), option);
            return 1;
          break;
      }
   }

   if(
#if defined(HAVE_SCTP)
       (!gPassiveControlSCTP) &&
#endif
       (!gPassiveControlTCP)
     ) {
      std::cerr << "ERROR: At least one control channel protocol must be enabled!" << "\n";
      exit(1);
   }

   return true;
}


// ###### Print global parameter settings ###################################
void printGlobalParameters()
{
   LOG_INFO
   stdlog << "Global Parameters:\n"
          << " - Runtime                   = ";
   if(gRuntime >= 0.0) {
      stdlog << gRuntime << " s\n";
   }
   else {
      stdlog << "until manual stop\n";
   }
   stdlog << " - Minimum Logging Level     = " << gLogLevel << "\n"
          << " - Active Node Name          = " << gActiveNodeName  << "\n"
          << " - Passive Node Name         = " << gPassiveNodeName << "\n"
          << " - Local Data Address(es)    = ";
   if(gLocalDataAddresses > 0) {
      for(unsigned int i = 0; i < gLocalDataAddresses; i++) {
         if(i > 0) {
            stdlog << ", ";
         }
         printAddress(stdlog, &gLocalDataAddressArray[i].sa, false);
      }
   }
   else {
      stdlog << "(any)";
   }
   stdlog << "\n - Local Control Address(es) = ";
   if(gLocalControlAddresses > 0) {
      for(unsigned int i = 0; i < gLocalControlAddresses; i++) {
         if(i > 0) {
            stdlog << ", ";
         }
         printAddress(stdlog, &gLocalControlAddressArray[i].sa, false);
      }
   }
   else {
      stdlog << "(any)";
   }
   stdlog << "\n";
   LOG_END
}


// ###### Read random number parameter ######################################
static const char* parseNextEntry(const char* parameters,
                                  double*     valueArray,
                                  uint8_t*    rng)
{
   int n = 0;
   for(size_t i = 0;i < NETPERFMETER_RNG_INPUT_PARAMETERS;i++) {
      valueArray[i] = 0.0;
   }
   if(sscanf(parameters, "const%lf%n", &valueArray[0], &n) == 1) {
      *rng = RANDOM_CONSTANT;
   }
   else if(sscanf(parameters, "uniform%lf,%lf%n", &valueArray[0], &valueArray[1], &n) == 2) {
      *rng = RANDOM_UNIFORM;
   }
   else if(sscanf(parameters, "exp%lf%n", &valueArray[0], &n) == 1) {
      *rng = RANDOM_EXPONENTIAL;
   }
   else if(sscanf(parameters, "normal%lf,%lf%n", &valueArray[0], &valueArray[1], &n) == 2) {
      *rng = RANDOM_NORMAL;
   }
   else if(sscanf(parameters, "truncnormal%lf,%lf%n", &valueArray[0], &valueArray[1], &n) == 2) {
      *rng = RANDOM_TRUNCATED_NORMAL;
   }
   else if(sscanf(parameters, "pareto%lf,%lf%n", &valueArray[0], &valueArray[1], &n) == 2) {
      *rng = RANDOM_PARETO;
   }
   else if(sscanf(parameters, "%lf%n", &valueArray[0], &n) == 1) {
      // shortcut: number interpreted as constant
      *rng = RANDOM_CONSTANT;
   }
   else {
      std::cerr << "ERROR: Invalid parameters " << parameters << "!\n";
      exit(1);
   }
   if(parameters[n] == 0x00) {
      return nullptr;
   }
   return (const char*)&parameters[n + 1];
}


// ###### Read flow option ##################################################
static const char* parseTrafficSpecOption(const char*      parameters,
                                          FlowTrafficSpec& trafficSpec,
                                          uint32_t&        flowID)
{
   char         description[256];
   int          n        = 0;
   double       dblValue = 0.0;
   unsigned int intValue = 0;

   if(sscanf(parameters, "id=%u%n", &intValue, &n) == 1) {
      flowID = (uint32_t)intValue;
   }
   else if(sscanf(parameters, "maxmsgsize=%u%n", &intValue, &n) == 1) {
      if(intValue > 65535) {
         intValue = 65535;
      }
      else if(intValue < 128) {
         intValue = 128;
      }
      trafficSpec.MaxMsgSize = intValue;
   }
   else if(sscanf(parameters, "defragtimeout=%u%n", &intValue, &n) == 1) {
      trafficSpec.DefragmentTimeout = 1000ULL * (uint32_t)intValue;
   }
   else if(sscanf(parameters, "ordered=%lf%n", &dblValue, &n) == 1) {
      if((dblValue < 0.0) || (dblValue > 1.0)) {
         std::cerr << "ERROR: Bad probability for \"ordered\" option in " << parameters << "!\n";
         exit(1);
      }
      trafficSpec.OrderedMode = dblValue;
   }
   else if(sscanf(parameters, "unordered=%lf%n", &dblValue, &n) == 1) {
      if((dblValue < 0.0) || (dblValue > 1.0)) {
         std::cerr << "ERROR: Bad probability for \"unordered\" option in " << parameters << "!\n";
         exit(1);
      }
      trafficSpec.OrderedMode = 1.0 - dblValue;
   }
   else if(sscanf(parameters, "reliable=%lf%n", &dblValue, &n) == 1) {
      if((dblValue < 0.0) || (dblValue > 1.0)) {
         std::cerr << "ERROR: Bad probability for \"reliable\" option in " << parameters << "!\n";
         exit(1);
      }
      trafficSpec.ReliableMode = dblValue;
   }
   else if(sscanf(parameters, "unreliable=%lf%n", &dblValue, &n) == 1) {
      if((dblValue < 0.0) || (dblValue > 1.0)) {
         std::cerr << "ERROR: Bad probability for \"unreliable\" option in " << parameters << "!\n";
         exit(1);
      }
      trafficSpec.ReliableMode = 1.0 - dblValue;
   }
   else if(sscanf(parameters, "rtx_timeout=%u%n", &intValue, &n) == 1) {
      trafficSpec.RetransmissionTrials     = (uint32_t)intValue;
      trafficSpec.RetransmissionTrialsInMS = true;
   }
   else if(sscanf(parameters, "rtx_trials=%u%n", &intValue, &n) == 1) {
      trafficSpec.RetransmissionTrials     = (uint32_t)intValue;
      trafficSpec.RetransmissionTrialsInMS = false;
   }
   else if(sscanf(parameters, "rcvbuf=%u%n", &intValue, &n) == 1) {
      trafficSpec.RcvBufferSize = (uint32_t)intValue;
   }
   else if(sscanf(parameters, "sndbuf=%u%n", &intValue, &n) == 1) {
      trafficSpec.SndBufferSize = (uint32_t)intValue;
   }
   else if(strncmp(parameters, "cmt=", 4) == 0) {
      unsigned int cmt;
      int          pos;
      if(strncmp((const char*)&parameters[4], "off", 3) == 0) {
         trafficSpec.CMT = NPAF_PRIMARY_PATH;
         n = 4 + 3;
      }
      else if(strncmp((const char*)&parameters[4], "cmtrpv1", 7) == 0) {
         trafficSpec.CMT = NPAF_CMTRPv1;
         n = 4 + 7;
      }
      else if(strncmp((const char*)&parameters[4], "cmtrpv2", 7) == 0) {
         trafficSpec.CMT = NPAF_CMTRPv2;
         n = 4 + 7;
      }
      else if(strncmp((const char*)&parameters[4], "cmt", 3) == 0) {
         trafficSpec.CMT = NPAF_CMT;
         n = 4 + 3;
      }
      else if( (strncmp((const char*)&parameters[4], "like-mptcp", 10) == 0) ||
               (strncmp((const char*)&parameters[4], "mptcp-like", 10) == 0) ) {
         trafficSpec.CMT = NPAF_LikeMPTCP;
         n = 4 + 10;
      }
      else if(strncmp((const char*)&parameters[4], "mptcp", 5) == 0) {
         trafficSpec.CMT = NPAF_LikeMPTCP;
         n = 4 + 5;
      }
      else if(strncmp((const char*)&parameters[4], "normal", 6) == 0) {   // Legacy: use "cmt"!
         trafficSpec.CMT = NPAF_CMT;
         n = 4 + 6;
      }
      else if(strncmp((const char*)&parameters[4], "rp", 2) == 0) {   // Legacy: use "cmtrpv1"!
         trafficSpec.CMT = NPAF_CMTRPv1;
         n = 4 + 2;
      }
      else if(sscanf((const char*)&parameters[4], "%u%n", &cmt, &pos) == 1) {
         trafficSpec.CMT = cmt;
         n = 4 + pos;
      }
      else {
         std::cerr << "ERROR: Invalid \"cmt\" setting: " << (const char*)&parameters[4] << "!\n";
         exit(1);
      }
   }
   else if(strncmp(parameters, "ccid=", 5) == 0) {
      unsigned int ccid;
      int          pos;
      if(sscanf((const char*)&parameters[5], "%u%n", &ccid, &pos) == 1) {
         trafficSpec.CCID = ccid;
         n = 5 + pos;
      }
      else {
         std::cerr << "ERROR: Invalid \"ccid\" setting: " << (const char*)&parameters[5] << "!\n";
         exit(1);
      }
   }
   else if(strncmp(parameters, "error_on_abort=", 15) == 0) {
      if(strncmp((const char*)&parameters[15], "on", 2) == 0) {
         trafficSpec.ErrorOnAbort = true;
         n = 15 + 2;
      }
      else if(strncmp((const char*)&parameters[15], "off", 3) == 0) {
         trafficSpec.ErrorOnAbort = false;
         n = 15 + 3;
      }
      else {
         std::cerr << "ERROR: Invalid \"error_on_abort\" setting: " << (const char*)&parameters[4] << "!\n";
         exit(1);
      }
   }
   else if(strncmp(parameters, "v6only", 6) == 0) {
      trafficSpec.BindV6Only = true;
      n = 6;
   }
   else if(sscanf(parameters, "description=%255[^:]s%n", (char*)&description, &n) == 1) {
      trafficSpec.Description = std::string(description);
      n = 12 + strlen(description);
   }
   else if(strncmp(parameters,"onoff=", 6) == 0) {
      unsigned long m = 5;
      trafficSpec.RepeatOnOff = false;
      while( (parameters[m] != 0x00) && (parameters[m] != ':') ) {
         m++;

         OnOffEvent event;
         event.RandNumGen    = 0x00;
         event.RelativeTime  = false;
         for(size_t i = 0; i < sizeof(event.ValueArray) / sizeof(event.ValueArray[0]); i++) {
            event.ValueArray[i] = 0.0;
         }
         if(parameters[m] == '+') {
            event.RelativeTime = true;
            m++;
         }
         else if(strncmp((const char*)&parameters[m], "repeat", 6) == 0) {
            m += 6;
            trafficSpec.RepeatOnOff = true;
            break;
         }
         const char* p = parseNextEntry((const char*)&parameters[m], (double*)&event.ValueArray, (uint8_t*)&event.RandNumGen);
         if(p != nullptr) {
            m += (unsigned long)p - (unsigned long)&parameters[m + 1];
         }
         else {
            m += strlen((const char*)&parameters[m]);
         }

         trafficSpec.OnOffEvents.push_back(event);
      }
      if(trafficSpec.RepeatOnOff == true) {
         if((trafficSpec.OnOffEvents.size() % 2) != 0) {
            std::cerr << "ERROR: Repeated on/off traffic needs even number of events!\n";
            exit(1);
         }
         for(std::vector<OnOffEvent>::const_iterator iterator = trafficSpec.OnOffEvents.begin();
            iterator != trafficSpec.OnOffEvents.end();iterator++) {
            if((*iterator).RelativeTime == false) {
               std::cerr << "ERROR: Repeated on/off traffic needs relative times!\n";
               exit(1);
            }
         }
      }
      n = m;
   }
   else if(strncmp(parameters, "nodelay=", 8) == 0) {
      if(strncmp((const char*)&parameters[8], "on", 2) == 0) {
         trafficSpec.NoDelay = true;
         n = 8 + 2;
      }
      else if(strncmp((const char*)&parameters[8], "off", 3) == 0) {
         trafficSpec.NoDelay = false;
         n = 8 + 3;
      }
      else {
         std::cerr << "ERROR: Invalid \"nodelay\" setting: " << (const char*)&parameters[8] << "!\n";
         exit(1);
      }
   }
   else if(strncmp(parameters, "debug=", 6) == 0) {
      if(strncmp((const char*)&parameters[6], "on", 2) == 0) {
         trafficSpec.Debug = true;
         // n = 6 + 2;
      }
      else if(strncmp((const char*)&parameters[6], "off", 3) == 0) {
         trafficSpec.Debug = false;
         n = 6 + 3;
      }
      else {
         std::cerr << "ERROR: Invalid \"debug\" setting: " << (const char*)&parameters[6] << "!\n";
         exit(1);
      }
   }
   else if(strncmp(parameters, "cc=", 3) == 0) {
      char   congestionControl[NETPERFMETER_CC_LENGTH + 1];
      size_t i = 0;
      while(i < NETPERFMETER_CC_LENGTH) {
         if( (parameters[3 + i] == ':') ||
             (parameters[3 + i] == 0x00) ) {
            break;
         }
         congestionControl[i] = parameters[3 + i];
         i++;
      }
      congestionControl[i] = 0x00;
      if( (parameters[3 + i] != ':') && (parameters[3 + i] != 0x00) ) {
         std::cerr << "ERROR: Invalid \"cc\" setting: " << (const char*)&parameters[8]
                   << " - name too long!\n";
          exit(1);
      }
      trafficSpec.CongestionControl = std::string((const char*)&congestionControl);
      n = 3 + strlen((const char*)&congestionControl);
   }
   else {
      std::cerr << "ERROR: Invalid option \"" << parameters << "\"!\n";
      exit(1);
   }

   if(parameters[n] == 0x00) {
      return nullptr;
   }
   return (const char*)&parameters[n + 1];
}


#if defined(HAVE_QUIC)
// ###### QUIC server handshake #############################################
static int server_handshake(int          sd,
                            const char*  alpns,
                            const char*  tlsCAFile,
                            const char*  tlsCertFile,
                            const char*  tlsKeyFile,
                            uint8_t*     sessionKey,
                            unsigned int sessionKeyLength)
{
   gnutls_certificate_credentials_t credentials;
   gnutls_session_t                 session;
   gnutls_datum_t                   skey = { sessionKey, sessionKeyLength };
   int                              error;

   error = gnutls_certificate_allocate_credentials(&credentials);
   if(!error) {
      const int loadedCAs =
         gnutls_certificate_set_x509_trust_file(credentials,
                                                tlsCAFile, GNUTLS_X509_FMT_PEM);
      if(loadedCAs <= 0) {
         LOG_ERROR
         stdlog << "Loading CA certificate from "
                << ((tlsCAFile != nullptr) ? tlsCAFile : "(none)") << " failed\n";
         LOG_END
         gnutls_certificate_free_credentials(credentials);
         return -1;
      }

      error = gnutls_certificate_set_x509_key_file(credentials,
                                                   tlsCertFile, tlsKeyFile,
                                                   GNUTLS_X509_FMT_PEM);
      if(!error) {
         error = gnutls_init(&session,
                             GNUTLS_SERVER|
                             GNUTLS_NO_AUTO_SEND_TICKET|
                             GNUTLS_ENABLE_EARLY_DATA|GNUTLS_NO_END_OF_EARLY_DATA);
         if(!error) {
            error = gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, credentials);
            if(!error) {
               error = gnutls_session_ticket_enable_server(session, &skey);
               if(!error) {
                  error = gnutls_record_set_max_early_data_size(session, 0xffffffffu);
                  if(!error) {
                     error = gnutls_priority_set_direct(session, QUIC_PRIORITY, NULL);
                     if( (!error) && (alpns != NULL) ) {
                        error = quic_session_set_alpn(session, alpns, strlen(alpns));
                     }
                     if(!error) {
                        gnutls_transport_set_int(session, sd);
                        error = quic_handshake(session);
                        if( (!error) && (alpns != NULL) ) {
                           size_t alpnLength;
                           char   alpn[64];
                           alpnLength = sizeof(alpn);
                           error = quic_session_get_alpn(session, alpn, &alpnLength);
                        }
                     }
                  }
               }
            }
         }
         gnutls_deinit(session);
      }
      gnutls_certificate_free_credentials(credentials);
   }

   if(error) {
      LOG_ERROR
      stdlog << "TLS setup failed: " << gnutls_strerror(error) << "\n";
      LOG_END
   }
   return error;
}


// ###### QUIC client handshake #############################################
static int client_handshake(int            sd,
                            const char*    alpns,
                            const char*    host,
                            const char*    tlsCAFile,
                            const uint8_t* ticketIn,
                            size_t         ticketInLength,
                            uint8_t*       ticketOut,
                            size_t*        ticketOutLength)
{
   gnutls_certificate_credentials_t credentials;
   gnutls_session_t                 session;
   int                              error;

   error = gnutls_certificate_allocate_credentials(&credentials);
   if(!error) {
      int loadedCAs = gnutls_certificate_set_x509_trust_file(credentials, tlsCAFile, GNUTLS_X509_FMT_PEM);
      if(loadedCAs <= 0) {
         std::cerr << "ERROR: Loading CA certificate from "
                   << ((tlsCAFile != nullptr) ? tlsCAFile : "(none)") << " failed\n";
         gnutls_certificate_free_credentials(credentials);
         return -1;
      }
      error = gnutls_init(&session, GNUTLS_CLIENT|GNUTLS_ENABLE_EARLY_DATA|GNUTLS_NO_END_OF_EARLY_DATA);
      if(!error) {
         error = gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, credentials);
         if(!error) {
            error = gnutls_priority_set_direct(session, QUIC_PRIORITY, NULL);
         }
         if( (!error) && (alpns != NULL) ) {
            error = quic_session_set_alpn(session, alpns, strlen(alpns));
         }
         if(!error) {
            error = gnutls_server_name_set(session, GNUTLS_NAME_DNS, host, (host != NULL) ? strlen(host) : 0);
         }
         if(!error) {
            gnutls_session_set_verify_cert(session, host, 0);
            gnutls_transport_set_int(session, sd);
            if(ticketIn != NULL) {
               error = quic_session_set_data(session, ticketIn, ticketInLength);
            }
            if(!error) {
               error = quic_handshake(session);
               // if( (!error) && (alpns != NULL) ) {
               //    size_t alpnLength;
               //    char   alpn[64];
               //    alpnLength = sizeof(alpn);
               //    error = quic_session_get_alpn(session, alpn, &alpnLength);
               // }
            }
            if( (!error) && (ticketOut != NULL) ) {
               error =  quic_session_get_data(session, ticketOut, ticketOutLength);
            }
         }
         gnutls_deinit(session);
      }
      gnutls_certificate_free_credentials(credentials);
   }

   if(error) {
      std::cerr << "ERROR: TLS setup failed: " << gnutls_strerror(error) << "\n";
   }
   return error;
}
#endif


// ###### Create Flow for new flow ##########################################
static Flow* createFlow(Flow*                  previousFlow,
                        const char*            parameters,
                        const uint64_t         measurementID,
                        const char*            gVectorNamePattern,
                        const OutputFileFormat gVectorFileFormat,
                        const int              initialProtocol,
                        const sockaddr_union&  remoteAddress)
{
   // ====== Get flow ID and stream ID ======================================
   static uint32_t flowID   = 0; // will be increased with each successfull call
   uint16_t        streamID = (previousFlow != nullptr) ? previousFlow->getStreamID() + 1 : 0;

   // ====== Get FlowTrafficSpec ============================================
   FlowTrafficSpec trafficSpec;
   trafficSpec.Protocol = initialProtocol;
#if defined(HAVE_MPTCP)
   if(trafficSpec.Protocol == IPPROTO_MPTCP) {
      trafficSpec.CMT = NPAF_LikeMPTCP;
   }
#endif

   if(strncmp(parameters, "default", 7) == 0) {
      trafficSpec.OutboundFrameRateRng = RANDOM_CONSTANT;
      trafficSpec.OutboundFrameRate[0] = 0.0;
      switch(trafficSpec.Protocol) {
         case IPPROTO_TCP:
#if defined(HAVE_MPTCP)
         case IPPROTO_MPTCP:
#endif
            trafficSpec.OutboundFrameSize[0] = 1500 - 40 - 20;
          break;
         case IPPROTO_UDP:
            trafficSpec.OutboundFrameSize[0] = 1500 - 40 - 8;
            trafficSpec.OutboundFrameRate[0] = 25;
          break;
#if defined(HAVE_DCCP)
         case IPPROTO_DCCP:
            trafficSpec.OutboundFrameSize[0] = 1500 - 40 - 40;   // 1420B for IPv6 via 1500B MTU!
          break;
#endif
#if defined(HAVE_QUIC)
         case IPPROTO_QUIC:
            trafficSpec.OutboundFrameSize[0] = 1400;   // Some reasonable default
          break;
#endif
         default:
            trafficSpec.OutboundFrameSize[0] = 1500 - 40 - 12 - 16;
          break;
      }
      parameters = (char*)&parameters[7];
      if(parameters[0] != 0x00) {
         parameters++;
         while( (parameters = parseTrafficSpecOption(parameters, trafficSpec, flowID)) ) { }
      }
   }
   else {
      parameters = parseNextEntry(parameters, (double*)&trafficSpec.OutboundFrameRate, &trafficSpec.OutboundFrameRateRng);
      if(parameters) {
         parameters = parseNextEntry(parameters, (double*)&trafficSpec.OutboundFrameSize, &trafficSpec.OutboundFrameSizeRng);
         if(parameters) {
            parameters = parseNextEntry(parameters, (double*)&trafficSpec.InboundFrameRate, &trafficSpec.InboundFrameRateRng);
            if(parameters) {
               parameters = parseNextEntry(parameters, (double*)&trafficSpec.InboundFrameSize, &trafficSpec.InboundFrameSizeRng);
               if(parameters) {
                  while( (parameters = parseTrafficSpecOption(parameters, trafficSpec, flowID)) ) { }
               }
            }
         }
      }
   }
   if(trafficSpec.Description == "") {
      trafficSpec.Description = format("Flow %u", flowID);
   }

   // ------ Use TCP or MPTCP? ----------------------------------------------
#if defined(HAVE_MPTCP)
   if( (trafficSpec.Protocol == IPPROTO_TCP) && (trafficSpec.CMT == NPAF_LikeMPTCP) ) {
      trafficSpec.Protocol = IPPROTO_MPTCP;
   }
   if( (trafficSpec.Protocol == IPPROTO_MPTCP) && (trafficSpec.CMT != NPAF_LikeMPTCP) ) {
      std::cerr << "WARNING: Invalid \"cmt\" setting: " << (const char*)&parameters[4]
                << " for MPTCP! Using default instead!\n";
      exit(1);
   }
#endif
   // -----------------------------------------------------------------------

   // ====== Create new flow ================================================
   if(FlowManager::getFlowManager()->findFlow(measurementID, flowID, streamID) != nullptr) {
      std::cerr << "ERROR: Flow ID " << flowID << " is used twice. Ensure correct id=<ID> parameters!\n";
      exit(1);
   }
   Flow* flow = new Flow(measurementID, flowID, streamID, trafficSpec);
   assure(flow != nullptr);

   // ====== Initialize vector file =========================================
   const std::string vectorName = flow->getNodeOutputName(gVectorNamePattern,
                                                          "active",
                                                          format("-%08x-%04x", flowID, streamID));
   if(!flow->initializeVectorFile(vectorName.c_str(), gVectorFileFormat)) {
      std::cerr << "ERROR: Unable to create vector file <" << vectorName << ">!\n";
      exit(1);
   }

   // ====== Initialise destination address =================================
   sockaddr_union destinationAddress = remoteAddress;
#if defined(HAVE_MPTCP)
   if(trafficSpec.Protocol == IPPROTO_MPTCP) {
      // An MPTCP subflow is just a TCP connection on the wire. To allow for
      // MPTCP and TCP simultaneously, MPTCP needs a different port number:
      setPort(&destinationAddress.sa, getPort(&destinationAddress.sa) - 1);
   }
#endif
#if defined(HAVE_QUIC)
   if(trafficSpec.Protocol == IPPROTO_QUIC) {
      // A QUIC connection is just a UDP "connection" on the wire. To allow for
      // QUIC and UDP simultaneously, QUIC needs a different port number:
      setPort(&destinationAddress.sa, getPort(&destinationAddress.sa) - 1);
   }
#endif

   // ====== Print information ==============================================
   LOG_INFO
   stdlog << format("Flow #%u: connecting %s socket to",
                     flow->getFlowID(), getProtocolName(trafficSpec.Protocol)) << " ";
   printAddress(stdlog, &destinationAddress.sa, true);
   stdlog << " from ";
   if(gLocalDataAddresses > 0) {
      for(unsigned int i = 0;i < gLocalDataAddresses;i++) {
         if(i > 0) {
            stdlog << ", ";
         }
         printAddress(stdlog, &gLocalDataAddressArray[i].sa, false);
      }
   }
   else {
      stdlog << "(any)";
   }
   stdlog << " ...\n";
   LOG_END

   // ====== Set up socket ==================================================
   int  socketDescriptor;
   bool originalSocketDescriptor;

   if(previousFlow) {
      originalSocketDescriptor = false;
      socketDescriptor         = previousFlow->getSocketDescriptor();
      LOG_INFO
      stdlog << format("Flow #%u: connected socket %d",
                       flow->getFlowID(), socketDescriptor) << "\n";
      LOG_END
   }
   else {
      originalSocketDescriptor = true;
      switch(trafficSpec.Protocol) {
         case IPPROTO_SCTP:
            socketDescriptor = createAndBindSocket(remoteAddress.sa.sa_family, SOCK_STREAM, IPPROTO_SCTP, 0,
                                                   gLocalDataAddresses, (const sockaddr_union*)&gLocalDataAddressArray, false,
                                                   trafficSpec.BindV6Only);
           break;
         case IPPROTO_TCP:
            socketDescriptor = createAndBindSocket(remoteAddress.sa.sa_family, SOCK_STREAM, IPPROTO_TCP, 0,
                                                   gLocalDataAddresses, (const sockaddr_union*)&gLocalDataAddressArray, false,
                                                   trafficSpec.BindV6Only);
           break;
         case IPPROTO_UDP:
            socketDescriptor = createAndBindSocket(remoteAddress.sa.sa_family, SOCK_DGRAM, IPPROTO_UDP, 0,
                                                   gLocalDataAddresses, (const sockaddr_union*)&gLocalDataAddressArray, false,
                                                   trafficSpec.BindV6Only);
           break;
#if defined(HAVE_MPTCP)
         case IPPROTO_MPTCP:
            socketDescriptor = createAndBindSocket(remoteAddress.sa.sa_family, SOCK_STREAM, IPPROTO_MPTCP, 0,
                                                   gLocalDataAddresses, (const sockaddr_union*)&gLocalDataAddressArray, false,
                                                   trafficSpec.BindV6Only);
           break;
#endif
#if defined(HAVE_DCCP)
         case IPPROTO_DCCP:
            socketDescriptor = createAndBindSocket(remoteAddress.sa.sa_family, SOCK_DCCP, IPPROTO_DCCP, 0,
                                                   gLocalDataAddresses, (const sockaddr_union*)&gLocalDataAddressArray, false,
                                                   trafficSpec.BindV6Only);
           break;
#endif
#if defined(HAVE_QUIC)
         case IPPROTO_QUIC:
            socketDescriptor = createAndBindSocket(remoteAddress.sa.sa_family, SOCK_DGRAM, IPPROTO_QUIC, 0,
                                                   gLocalDataAddresses, (const sockaddr_union*)&gLocalDataAddressArray, false,
                                                   trafficSpec.BindV6Only);
           break;
#endif
         default:
            abort();

      }
      if(socketDescriptor < 0) {
         std::cerr << "ERROR: Unable to create " << getProtocolName(trafficSpec.Protocol)
                   << " socket - " << strerror(errno) << "!\n";
         exit(1);
      }

      // ====== Establish connection ========================================
#if defined(HAVE_SCTP)
      if(trafficSpec.Protocol == IPPROTO_SCTP) {
         // ------ Set SCTP stream parameters--------------------------------
         sctp_initmsg initmsg;
         memset((char*)&initmsg, 0 ,sizeof(initmsg));
         initmsg.sinit_num_ostreams  = 65535;
         initmsg.sinit_max_instreams = 65535;
         if(ext_setsockopt(socketDescriptor, IPPROTO_SCTP, SCTP_INITMSG,
                           &initmsg, sizeof(initmsg)) < 0) {
            std::cerr << "ERROR: Failed to configure INIT parameters on SCTP socket (SCTP_INITMSG option) - "
                      << strerror(errno) << "!\n";
            exit(1);
         }

         // ------ Enable SCTP events ---------------------------------------
         sctp_event_subscribe events;
         memset((char*)&events, 0 ,sizeof(events));
         events.sctp_data_io_event = 1;
         if(ext_setsockopt(socketDescriptor, IPPROTO_SCTP, SCTP_EVENTS,
                           &events, sizeof(events)) < 0) {
            std::cerr << "ERROR: Failed to configure events on SCTP socket (SCTP_EVENTS option) - "
                      << strerror(errno) << "!\n";
            exit(1);
         }
      }
#endif

      if(flow->configureSocket(socketDescriptor) == false) {
         exit(1);
      }
      if(ext_connect(socketDescriptor, &destinationAddress.sa, getSocklen(&destinationAddress.sa)) < 0) {
         std::cerr << "ERROR: Unable to connect " << getProtocolName(trafficSpec.Protocol)
                   << " socket - " << strerror(errno) << "!\n";
         exit(1);
      }

      LOG_TRACE
      stdlog << "okay <sd=" << socketDescriptor << ">\n";
      LOG_END

      // ====== QUIC handshake ==============================================
#if defined(HAVE_QUIC)
      if(trafficSpec.Protocol == IPPROTO_QUIC) {
         LOG_TRACE
         stdlog << "client handshake <sd=" << socketDescriptor
                << ", CA=" << gQUICCA << " H=" << gQUICHostname << ">\n";
         LOG_END

         if(client_handshake(socketDescriptor,
                             ALPN_NETPERFMETER_DATA, gQUICHostname, gQUICCA,
                             nullptr, 0, nullptr, nullptr) != 0) {
            exit(1);
         }
      }
#endif
   }

   // ====== Update flow with socket descriptor =============================
   flow->setSocketDescriptor(socketDescriptor, originalSocketDescriptor);

   flowID++;
   return flow;
}


// ###### Initialize pollFD entry in main loop ##############################
static void addToPollFDs(pollfd* pollFDs, const int fd, nfds_t& n, int* index = nullptr)
{
   if(fd >= 0) {
      pollFDs[n].fd      = fd;
      pollFDs[n].events  = POLLIN;
      pollFDs[n].revents = 0;
      if(index) {
         *index = (int)n;
      }
      n++;
   }
}


// ###### Main loop #########################################################
static bool mainLoop(const bool               isActiveMode,
                     const unsigned long long stopAt)
{
   pollfd             fds[5 + gMessageReader.size()];
   nfds_t             n       = 0;
   int                tcpID   = -1;
#if defined(__OpenBSD__)
   int                tcp6ID  = -1;
#endif
#if defined(HAVE_MPTCP)
   int                mptcpID = -1;
#endif
   int                udpID   = -1;
#if defined(__OpenBSD__)
   int                udp6ID  = -1;
#endif
#if defined(HAVE_SCTP)
   int                sctpID  = -1;
#endif
#if defined(HAVE_DCCP)
   int                dccpID  = -1;
#endif
#if defined(HAVE_QUIC)
   int                quicID  = -1;
#endif
   unsigned long long now     = getMicroTime();
   std::map<int, pollfd*> pollFDIndex;


   // ====== Get parameters for poll() ======================================
   addToPollFDs((pollfd*)&fds, gTCPSocket,     n, &tcpID);
#if defined(__OpenBSD__)
   addToPollFDs((pollfd*)&fds, gTCP6Socket,    n, &tcp6ID);
#endif
#if defined(HAVE_MPTCP)
   addToPollFDs((pollfd*)&fds, gMPTCPSocket,   n, &mptcpID);
#endif
   addToPollFDs((pollfd*)&fds, gUDPSocket,     n, &udpID);
#if defined(__OpenBSD__)
   addToPollFDs((pollfd*)&fds, gUDP6Socket,    n, &udp6ID);
#endif
#if defined(HAVE_SCTP)
   addToPollFDs((pollfd*)&fds, gSCTPSocket,    n, &sctpID);
#endif
#if defined(HAVE_DCCP)
   addToPollFDs((pollfd*)&fds, gDCCPSocket,    n, &dccpID);
#endif
#if defined(HAVE_QUIC)
   addToPollFDs((pollfd*)&fds, gQUICSocket,    n, &quicID);
#endif
   int    controlFDSet[gMessageReader.size()];
   size_t controlFDs = gMessageReader.getAllSDs((int*)&controlFDSet, sizeof(controlFDSet) / sizeof(int));
   const int controlIDMin = (int)n;
   for(size_t i = 0; i < controlFDs; i++) {
      addToPollFDs((pollfd*)&fds, controlFDSet[i], n, nullptr);
   }
   const int controlIDMax = (int)n - 1;


   // ====== Use poll() to wait for events ==================================
   const int timeout = pollTimeout(now, 2,
                                   stopAt,
                                   now + 1000000);

   // printf("timeout=%d\n",timeout);
   const int result = ext_poll_wrapper((pollfd*)&fds, n, timeout);
   // printf("result=%d\n",result);


   // ====== Handle socket events ===========================================
   now = getMicroTime();   // Get current time.
   if(result > 0) {

      // ====== Incoming control message ====================================
      int controlID;
      for(controlID = controlIDMin; controlID <= controlIDMax; controlID++) {
         if(fds[controlID].revents & (POLLIN|POLLERR)) {
            if( (isActiveMode == false) &&
                (fds[controlID].fd == gControlSocket) ) {
               const int newSD = ext_accept(gControlSocket, nullptr, 0);
               if(newSD >= 0) {
                  LOG_TRACE
                  stdlog << format("Accept on SCTP control socket %d -> new control connection %d",
                                   gControlSocket, newSD) << "\n";
                  LOG_END
                  gMessageReader.registerSocket(IPPROTO_SCTP, newSD);
               }
               else {
                  LOG_ERROR
                  stdlog << format("Accept on SCTP control socket %d failed: %s!",
                                   gControlSocket, strerror(errno)) << "\n";
                  LOG_END
               }
            }
            else if( (isActiveMode == false) &&
                     (fds[controlID].fd == gControlSocketTCP) ) {
               const int newSD = ext_accept(gControlSocketTCP, nullptr, 0);
               if(newSD >= 0) {
                  LOG_TRACE
                  stdlog << format("Accept on TCP control socket %d -> new control connection %d",
                                   gControlSocketTCP, newSD) << "\n";
                  LOG_END
                  gMessageReader.registerSocket(IPPROTO_TCP, newSD);
               }
               else {
                  LOG_ERROR
                  stdlog << format("Accept on TCP control socket %d failed: %s!",
                                   gControlSocketTCP, strerror(errno)) << "\n";
                  LOG_END
               }
            }
#if defined(__OpenBSD__)
            else if( (isActiveMode == false) &&
                     (fds[controlID].fd == gControlSocketTCP6) ) {
               const int newSD = ext_accept(gControlSocketTCP6, nullptr, 0);
               if(newSD >= 0) {
                  LOG_TRACE
                  stdlog << format("Accept on TCP6 control socket %d -> new control connection %d",
                                   gControlSocketTCP6, newSD) << "\n";
                  LOG_END
                  gMessageReader.registerSocket(IPPROTO_TCP, newSD);
               }
               else {
                  LOG_ERROR
                  stdlog << format("Accept on TCP6 control socket %d failed: %s!",
                                   gControlSocketTCP6, strerror(errno)) << "\n";
                  LOG_END
               }
            }
#endif
            else {
               const bool controlOkay = handleNetPerfMeterControlMessage(
                                           &gMessageReader, fds[controlID].fd);
               if(!controlOkay) {
                  if(isActiveMode) {
                     return false;
                  }
                  // Make sure to deregister all flows belonging to this
                  // control connection!
                  handleControlAssocShutdown(fds[controlID].fd);
                  gMessageReader.deregisterSocket(fds[controlID].fd);
                  ext_close(fds[controlID].fd);
               }
            }
         }
      }

      // ====== Incoming data message =======================================
      if( (tcpID >= 0) && (fds[tcpID].revents & (POLLIN|POLLERR)) ) {
         const int newSD = ext_accept(gTCPSocket, nullptr, 0);
         if(newSD >= 0) {
            LOG_TRACE
            stdlog << format("Accept on TCP data socket %d -> new TCP data connection %d",
                             gTCPSocket, newSD) << "\n";
            LOG_END
            FlowManager::getFlowManager()->addUnidentifiedSocket(IPPROTO_TCP, newSD);
         }
      }
#if defined(__OpenBSD__)
      if( (tcp6ID >= 0) && (fds[tcp6ID].revents & (POLLIN|POLLERR)) ) {
         const int newSD = ext_accept(gTCP6Socket, nullptr, 0);
         if(newSD >= 0) {
            LOG_TRACE
            stdlog << format("Accept on TCP6 data socket %d -> new TCP6 data connection %d",
                             gTCP6Socket, newSD) << "\n";
            LOG_END
            FlowManager::getFlowManager()->addUnidentifiedSocket(IPPROTO_TCP, newSD);
         }
      }
#endif
#if defined(HAVE_MPTCP)
      if( (mptcpID >= 0) && (fds[mptcpID].revents & (POLLIN|POLLERR)) ) {
         const int newSD = ext_accept(gMPTCPSocket, nullptr, 0);
         if(newSD >= 0) {
            LOG_TRACE
            stdlog << format("Accept on MPTCP data socket %d -> new MPTCP data connection %d",
                             gMPTCPSocket, newSD) << "\n";
            LOG_END
            FlowManager::getFlowManager()->addUnidentifiedSocket(IPPROTO_MPTCP, newSD);
         }
      }
#endif
      if( (udpID >= 0) && (fds[udpID].revents & (POLLIN|POLLERR)) ) {
         FlowManager::getFlowManager()->lock();
         handleNetPerfMeterData(isActiveMode, now, IPPROTO_UDP, gUDPSocket);
         FlowManager::getFlowManager()->unlock();
      }
#if defined(__OpenBSD__)
      if( (udp6ID >= 0) && (fds[udp6ID].revents & (POLLIN|POLLERR)) ) {
         FlowManager::getFlowManager()->lock();
         handleNetPerfMeterData(isActiveMode, now, IPPROTO_UDP, gUDP6Socket);
         FlowManager::getFlowManager()->unlock();
      }
#endif
#if defined(HAVE_SCTP)
      if( (sctpID >= 0) && (fds[sctpID].revents & (POLLIN|POLLERR)) ) {
         const int newSD = ext_accept(gSCTPSocket, nullptr, 0);
         if(newSD >= 0) {
            LOG_TRACE
            stdlog << format("Accept on SCTP data socket %d -> new SCTP data connection %d",
                             gSCTPSocket, newSD) << "\n";
            LOG_END
            FlowManager::getFlowManager()->addUnidentifiedSocket(IPPROTO_SCTP, newSD);
         }
      }
#endif
#if defined(HAVE_DCCP)
      if( (dccpID >= 0) && (fds[dccpID].revents & (POLLIN|POLLERR)) ) {
         const int newSD = ext_accept(gDCCPSocket, nullptr, 0);
         if(newSD >= 0) {
            LOG_TRACE
            stdlog << format("Accept on DCCP data socket %d -> new DCCP data connection %d",
                             gDCCPSocket, newSD) << "\n";
            LOG_END
            FlowManager::getFlowManager()->addUnidentifiedSocket(IPPROTO_DCCP, newSD);
         }
      }
#endif
#if defined(HAVE_QUIC)
      if( (quicID >= 0) && (fds[quicID].revents & (POLLIN|POLLERR)) ) {
         const int newSD = ext_accept(gQUICSocket, nullptr, 0);
         if(newSD >= 0) {
            LOG_TRACE
            stdlog << format("Accept on QUIC data socket %d -> new QUIC data connection %d",
                             gQUICSocket, newSD) << "\n";
            LOG_END
            LOG_TRACE
            stdlog << "server handshake <sd=" << gQUICSocket
                   << ", K="  << ((gQUICKey != nullptr) ? gQUICKey : "(none)")
                   << ", C="  << ((gQUICCertificate != nullptr) ? gQUICCertificate : "(none)")
                   << ", CA=" << ((gQUICCA != nullptr) ? gQUICCA : "(none)")
                   << ">\n";
            LOG_END

            uint8_t      sessionKey[64];
            unsigned int sessionKeyLength = sizeof(sessionKey);
            if(ext_getsockopt(newSD,
                              SOL_QUIC, QUIC_SOCKOPT_SESSION_TICKET,
                              &sessionKey, &sessionKeyLength) == 0) {
               if(server_handshake(newSD, ALPN_NETPERFMETER_DATA,
                                   gQUICCA, gQUICCertificate, gQUICKey,
                                   sessionKey, sessionKeyLength) == 0) {
                  LOG_TRACE
                  stdlog << format("Accept on QUIC data socket %d -> new QUIC data connection %d",
                                   gQUICSocket, newSD) << "\n";
                  LOG_END
                  FlowManager::getFlowManager()->addUnidentifiedSocket(IPPROTO_QUIC, newSD);
               }
               else {
                  ext_close(newSD);
               }
            }
            else {
               LOG_WARNING
               stdlog << "getsockopt(QUIC_SOCKOPT_SESSION_TICKET): "
                      << strerror(errno) << "\n";
               LOG_END
               ext_close(newSD);
            }
         }
      }
#endif

   }

   // ====== Stop-time reached ==============================================
   if(now >= stopAt) {
      gStopTimeReached = true;
   }
   return true;
}



// ###### Passive Mode ######################################################
static void passiveMode(const uint16_t localPort)
{
   // ====== Print global parameters ========================================
   printGlobalParameters();

   // ====== Test for problems ==============================================
   sockaddr_union testAddress;
   assure(string2address("172.17.0.1:0", &testAddress));
   int testSD = createAndBindSocket(AF_INET, SOCK_STREAM, IPPROTO_SCTP, 0, 1,
                                    &testAddress, true, false);
   if(testSD >= 0) {
      LOG_WARNING
      stdlog << "WARNING: This machine seems to have an interface with address 172.17.0.1! This is typically used by Docker setups. If you connect from another machine having the same configuration, in an environment with only private addresses SCTP may try to use this address -> OOTB ABORT." << "\n";
      LOG_END
      ext_close(testSD);
   }

   // ====== Initialize SCTP control socket =================================
#if defined(HAVE_SCTP)
   if(gPassiveControlSCTP) {
      gControlSocket = createAndBindSocket(AF_UNSPEC, SOCK_STREAM, IPPROTO_SCTP,
                                           localPort + 1,
                                           gLocalControlAddresses,
                                           (const sockaddr_union*)&gLocalControlAddressArray,
                                           true, gBindV6Only);
      if(gControlSocket >= 0) {
         // ------ Set default send parameters ---------------------------------
         sctp_sndrcvinfo sinfo;
         memset(&sinfo, 0, sizeof(sinfo));
         sinfo.sinfo_ppid = htobe32(PPID_NETPERFMETER_CONTROL);
         if(ext_setsockopt(gControlSocket, IPPROTO_SCTP, SCTP_DEFAULT_SEND_PARAM,
                           &sinfo, sizeof(sinfo)) < 0) {
            LOG_FATAL
            stdlog << format("Failed to configure default send parameters (SCTP_DEFAULT_SEND_PARAM option) on SCTP control socket %d: %s!",
                             gControlSocket, strerror(errno)) << "\n";
            LOG_END_FATAL
         }

         const int noDelayOption = 1;
         if(ext_setsockopt(gControlSocket, IPPROTO_SCTP, SCTP_NODELAY,
                           (const char*)&noDelayOption, sizeof(noDelayOption)) < 0) {
            LOG_ERROR
            stdlog << format("Failed to set SCTP_NODELAY on SCTP control socket: %s!",
                              strerror(errno)) << "\n";
            LOG_END_FATAL
         }

         // ------ Enable SCTP events ------------------------------------------
         sctp_event_subscribe events;
         memset((char*)&events, 0 ,sizeof(events));
         events.sctp_data_io_event     = 1;
         events.sctp_association_event = 1;
         if(ext_setsockopt(gControlSocket, IPPROTO_SCTP, SCTP_EVENTS, &events, sizeof(events)) < 0) {
            LOG_FATAL
            stdlog << format("Failed to configure events (SCTP_EVENTS option) on SCTP socket %d: %s!",
                             gControlSocket, strerror(errno)) << "\n";
            LOG_END_FATAL
         }

         gMessageReader.registerSocket(IPPROTO_SCTP, gControlSocket);
      }
      else {
         if( (errno != EPROTONOSUPPORT) ||
             (gPassiveControlTCP == false) ) {
            LOG_FATAL
            stdlog << format("Failed to create and bind SCTP control socket on port %d: %s!",
                             localPort + 1, strerror(errno)) << "\n";
            if(errno == EPROTONOSUPPORT) {
               stdlog << "SCTP is not available => Try -y|--control-over-tcp for control over TCP!\n";
            }
            LOG_END_FATAL
         }
         LOG_WARNING
         stdlog << "WARNING: SCTP is not available => no control connections via SCTP possible!\n"
                   "SCTP can be disabled by -X|--no-control-over-sctp.\n";
         LOG_END
      }
   }
#endif

   // ====== Initialize TCP/MPTCP control socket ============================
   if(gPassiveControlTCP) {
      gControlSocketTCP = createAndBindSocket(
#if defined(__OpenBSD__)
                                              (gLocalControlAddresses > 0) ? gLocalControlAddressArray[0].sa.sa_family : AF_INET,
#else
                                              AF_UNSPEC,
#endif
                                              SOCK_STREAM,
#if defined(HAVE_MPTCP)
                                              (gPassiveControlMPTCP) ? IPPROTO_MPTCP : IPPROTO_TCP,
#else
                                              IPPROTO_TCP,
#endif
                                              localPort + 1,
                                              gLocalControlAddresses,
                                              (const sockaddr_union*)&gLocalControlAddressArray,
                                              true, gBindV6Only);
      if(gControlSocketTCP < 0) {
         LOG_FATAL
         stdlog << format("Failed to create and bind TCP control socket on port %d: %s!",
                        localPort + 1, strerror(errno)) << "\n";
         LOG_END_FATAL
      }
      const int noDelayOption = 1;
      if(ext_setsockopt(gControlSocketTCP, IPPROTO_TCP, TCP_NODELAY,
                        (const char*)&noDelayOption, sizeof(noDelayOption)) < 0) {
         LOG_ERROR
         stdlog << format("Failed to set TCP_NODELAY on TCP control socket: %s!",
                           strerror(errno)) << "\n";
         LOG_END_FATAL
      }
      gMessageReader.registerSocket(IPPROTO_TCP, gControlSocketTCP);

#if defined(__OpenBSD__)
      if(gLocalControlAddresses == 0) {
         gControlSocketTCP6 = createAndBindSocket(AF_INET6,
                                                  SOCK_STREAM,
#if defined(HAVE_MPTCP)
                                                  (gPassiveControlMPTCP) ? IPPROTO_MPTCP : IPPROTO_TCP,
#else
                                                  IPPROTO_TCP,
#endif
                                                  localPort + 1,
                                                  gLocalControlAddresses,
                                                  (const sockaddr_union*)&gLocalControlAddressArray,
                                                  true, gBindV6Only);
         if(gControlSocketTCP6 < 0) {
            LOG_FATAL
            stdlog << format("Failed to create and bind TCP6 control socket on port %d: %s!",
                             localPort + 1, strerror(errno)) << "\n";
            LOG_END_FATAL
         }
         const int noDelayOption = 1;
         if(ext_setsockopt(gControlSocketTCP6, IPPROTO_TCP, TCP_NODELAY,
                           (const char*)&noDelayOption, sizeof(noDelayOption)) < 0) {
            LOG_ERROR
            stdlog << format("Failed to set TCP6_NODELAY on TCP6 control socket: %s!",
                             strerror(errno)) << "\n";
            LOG_END_FATAL
         }
         gMessageReader.registerSocket(IPPROTO_TCP, gControlSocketTCP6);
      }
#endif
   }

   // ====== Initialize data socket for each protocol =======================

   // ------ TCP ------------------------------------------------------------
   gTCPSocket = createAndBindSocket(
#if defined(__OpenBSD__)
                                    (gLocalDataAddresses > 0) ? gLocalDataAddressArray[0].sa.sa_family : AF_INET,
#else
                                    AF_UNSPEC,
#endif
                                    SOCK_STREAM, IPPROTO_TCP, localPort,
                                    gLocalDataAddresses,
                                    (const sockaddr_union*)&gLocalDataAddressArray,
                                    true, gBindV6Only);
   if(gTCPSocket < 0) {
      LOG_FATAL
      stdlog << format("Failed to create and bind TCP socket on port %d: %s!",
                       localPort, strerror(errno)) << "\n";
      LOG_END_FATAL
   }
   if(setBufferSizes(gTCPSocket, gSndBufSize, gRcvBufSize) == false) {
      LOG_FATAL
      stdlog << format("Failed to configure buffer sizes on TCP socket %d!",
                       gTCPSocket) << "\n";
      LOG_END_FATAL
   }
#if defined(__OpenBSD__)
   if(gLocalDataAddresses == 0) {
      gTCP6Socket = createAndBindSocket(AF_INET6, SOCK_STREAM, IPPROTO_TCP, localPort,
                                        gLocalDataAddresses,
                                        (const sockaddr_union*)&gLocalDataAddressArray,
                                        true, gBindV6Only);
      if(gTCP6Socket < 0) {
         LOG_FATAL
         stdlog << format("Failed to create and bind TCP6 socket on port %d: %s!",
                          localPort, strerror(errno)) << "\n";
         LOG_END_FATAL
      }
      if(setBufferSizes(gTCP6Socket, gSndBufSize, gRcvBufSize) == false) {
         LOG_FATAL
         stdlog << format("Failed to configure buffer sizes on TCP6 socket %d!",
                          gTCP6Socket) << "\n";
         LOG_END_FATAL
      }
   }
#endif

   // ------ MPTCP ----------------------------------------------------------
#if defined(HAVE_MPTCP)
   gMPTCPSocket = createAndBindSocket(AF_UNSPEC, SOCK_STREAM, IPPROTO_MPTCP, localPort - 1,
                                      gLocalDataAddresses,
                                      (const sockaddr_union*)&gLocalDataAddressArray,
                                      true, gBindV6Only);
   if(gMPTCPSocket < 0) {
      LOG_DEBUG
      stdlog << format("NOTE: Failed to create and bind MPTCP socket on port %d: %s!",
                        localPort - 1, strerror(errno)) << "\n";
      LOG_END
   }
   else {
      if(setBufferSizes(gMPTCPSocket, gSndBufSize, gRcvBufSize) == false) {
         LOG_FATAL
         stdlog << format("Failed to configure buffer sizes on MPTCP socket %d!",
                          gMPTCPSocket) << "\n";
         LOG_END_FATAL
      }
   }
#endif

   // ------ UDP ------------------------------------------------------------
   gUDPSocket = createAndBindSocket(
#if defined(__OpenBSD__)
                                    (gLocalDataAddresses > 0) ? gLocalDataAddressArray[0].sa.sa_family : AF_INET,
#else
                                    AF_UNSPEC,
#endif
                                    SOCK_DGRAM, IPPROTO_UDP, localPort,
                                    gLocalDataAddresses,
                                    (const sockaddr_union*)&gLocalDataAddressArray,
                                    true, gBindV6Only);
   if(gUDPSocket < 0) {
      LOG_FATAL
      stdlog << "ERROR: Failed to create and bind UDP socket on port " << localPort << " - "
             << strerror(errno) << "!\n";
      LOG_END_FATAL
   }
   // NOTE: For connection-less UDP, the FlowManager takes care of the socket!
   FlowManager::getFlowManager()->addUnidentifiedSocket(IPPROTO_UDP, gUDPSocket);
#if defined(__OpenBSD__)
   if(gLocalDataAddresses == 0) {
      gUDP6Socket = createAndBindSocket(AF_INET6,
                                        SOCK_DGRAM, IPPROTO_UDP, localPort,
                                        gLocalDataAddresses,
                                        (const sockaddr_union*)&gLocalDataAddressArray,
                                        true, gBindV6Only);
      if(gUDP6Socket < 0) {
         LOG_FATAL
         stdlog << "ERROR: Failed to create and bind UDP6 socket on port " << localPort << " - "
                << strerror(errno) << "!\n";
         LOG_END_FATAL
      }
      // NOTE: For connection-less UDP, the FlowManager takes care of the socket!
      FlowManager::getFlowManager()->addUnidentifiedSocket(IPPROTO_UDP, gUDP6Socket);
   }
#endif
   // ------ DCCP -----------------------------------------------------------
#if defined(HAVE_DCCP)
   gDCCPSocket = createAndBindSocket(AF_UNSPEC, SOCK_DCCP, IPPROTO_DCCP, localPort,
                                     gLocalDataAddresses,
                                     (const sockaddr_union*)&gLocalDataAddressArray,
                                     true, gBindV6Only);
   if(gDCCPSocket < 0) {
      LOG_INFO
      stdlog << format("NOTE: Failed to create and bind DCCP socket on port %d: %s!",
                        localPort, strerror(errno)) << "\n";
      LOG_END
   }
   else {
      const uint32_t service[1] = { htobe32(SC_NETPERFMETER_DATA) };
      if(ext_setsockopt(gDCCPSocket, SOL_DCCP, DCCP_SOCKOPT_SERVICE, &service, sizeof(service)) < 0) {
         LOG_FATAL
         stdlog << format("Failed to configure DCCP service code %u (DCCP_SOCKOPT_SERVICE option) on DCCP socket: %s!",
                           (unsigned int)be32toh(service[0]), gDCCPSocket, strerror(errno)) << "\n";
         LOG_END_FATAL
      }
      if(setBufferSizes(gDCCPSocket, gSndBufSize, gRcvBufSize) == false) {
         LOG_FATAL
         stdlog << format("Failed to configure buffer sizes on DCCP socket %d!",
                          gDCCPSocket) << "\n";
         LOG_END_FATAL
      }
   }
#endif

   // ------ QUIC -----------------------------------------------------------
#if defined(HAVE_QUIC)
   gQUICSocket = createAndBindSocket(AF_UNSPEC, SOCK_DGRAM, IPPROTO_QUIC, localPort - 1,
                                     gLocalDataAddresses, (const sockaddr_union*)&gLocalDataAddressArray, false, gBindV6Only);
   // NOTE: It is necessary to set the ALPN before enabling the listening mode!
   if(gQUICSocket < 0) {
      LOG_INFO
      stdlog << format("NOTE: Failed to create and bind QUIC socket on port %d: %s!",
                        localPort, strerror(errno)) << "\n";
      LOG_END
   }
   else {
      if(ext_setsockopt(gQUICSocket, SOL_QUIC, QUIC_SOCKOPT_ALPN,
                        ALPN_NETPERFMETER_DATA, strlen(ALPN_NETPERFMETER_DATA)) < 0) {
         LOG_FATAL
         stdlog << format("Failed to configure QUIC ALPN (QUIC_SOCKOPT_ALPN option) on QUIC socket: %s!",
                          gQUICSocket, strerror(errno)) << "\n";
         LOG_END_FATAL
      }
      if(setBufferSizes(gQUICSocket, gSndBufSize, gRcvBufSize) == false) {
         LOG_FATAL
         stdlog << format("Failed to configure buffer sizes on QUIC socket %d!",
                          gQUICSocket) << "\n";
         LOG_END_FATAL
      }
      if(ext_listen(gQUICSocket, 100) < 0) {
         LOG_FATAL
         stdlog << format("Failed to enable listening on QUIC socket %d!",
                          gQUICSocket) << "\n";
         LOG_END_FATAL
      }
   }
#endif

   // ------ SCTP -----------------------------------------------------------
#if defined(HAVE_SCTP)
   gSCTPSocket = createAndBindSocket(AF_UNSPEC, SOCK_STREAM, IPPROTO_SCTP, localPort,
                                     gLocalDataAddresses,
                                     (const sockaddr_union*)&gLocalDataAddressArray,
                                     true, gBindV6Only);
   if(gSCTPSocket >= 0) {

      // ------ Set SCTP stream parameters-----------------------------------
      sctp_initmsg initmsg;
      memset((char*)&initmsg, 0 ,sizeof(initmsg));
      initmsg.sinit_num_ostreams  = 65535;
      initmsg.sinit_max_instreams = 65535;
      if(ext_setsockopt(gSCTPSocket, IPPROTO_SCTP, SCTP_INITMSG,
                        &initmsg, sizeof(initmsg)) < 0) {
         LOG_FATAL
         stdlog << format("Failed to configure INIT parameters (SCTP_INITMSG option) on SCTP socket %d: %s!",
                          gSCTPSocket, strerror(errno)) << "\n";
         LOG_END_FATAL
      }

      // ------ Enable SCTP events ------------------------------------------
      sctp_event_subscribe events;
      memset((char*)&events, 0 ,sizeof(events));
      events.sctp_data_io_event = 1;
      if(ext_setsockopt(gSCTPSocket, IPPROTO_SCTP, SCTP_EVENTS, &events, sizeof(events)) < 0) {
         LOG_FATAL
         stdlog << format("Failed to configure events (SCTP_EVENTS option) on SCTP socket %d: %s!",
                          gSCTPSocket, strerror(errno)) << "\n";
         LOG_END_FATAL
      }

      // ------ Set SCTP buffer sizes ---------------------------------------
      if(setBufferSizes(gSCTPSocket, gSndBufSize, gRcvBufSize) == false) {
         LOG_FATAL
         stdlog << format("Failed to configure buffer sizes on SCTP socket %d!",
                          gSCTPSocket) << "\n";
         LOG_END_FATAL
      }

   }
   else {
      if( (errno != EPROTONOSUPPORT) || (gPassiveControlTCP == false) ) {
         LOG_FATAL
         stdlog << format("Failed to create and bind SCTP socket on port %d: %s!",
                          localPort, strerror(errno)) << "\n";
         LOG_END_FATAL
      }
   }
#endif

   // ====== Print status ===================================================
   LOG_INFO
   std::string controlProtocols;
#if defined(HAVE_SCTP)
   if(gControlSocket >= 0) {
      controlProtocols += "SCTP";
   }
#endif
   if(gControlSocketTCP >= 0) {
      if(controlProtocols.size() > 0) {
         controlProtocols += "/";
      }
      controlProtocols += "TCP";
#if defined(HAVE_MPTCP)
      if(gPassiveControlMPTCP) {
         controlProtocols += "/MPTCP";
      }
#endif
   }
#if defined(HAVE_SCTP)
   std::string dataProtocols1("SCTP/TCP/UDP");
#else
   std::string dataProtocols1("TCP/UDP");
#endif
   std::string dataProtocols2;
#if defined(HAVE_DCCP)
   if(gDCCPSocket >= 0) {
      dataProtocols1 += "/DCCP";
   }
#endif
#if defined(HAVE_MPTCP)
   if(gMPTCPSocket > 0) {
      dataProtocols2 += "MPTCP";
   }
#endif
#if defined(HAVE_QUIC)
   if(gQUICSocket > 0) {
      if(dataProtocols2.size() > 0) {
         dataProtocols2 += "/";
      }
      dataProtocols2 += "QUIC";
   }
#endif
   if(dataProtocols2.size() > 0) {
      dataProtocols2 = format(", and %s on port %u",
                              dataProtocols2.c_str(), localPort - 1);
   }
   stdlog << format("Passive Mode: Waiting for incoming connections:\n"
                    " - Control Channel = %s on port %u\n"
                    " - Data Channel    = %s on port %u%s\n",
                    controlProtocols.c_str(), localPort + 1,
                    dataProtocols1.c_str(), localPort,
                    dataProtocols2.c_str());
   LOG_END
   LOG_TRACE
   stdlog << "Sockets:\n"
          << "- SCTP Control = " << gControlSocket     << "\n"
          << "- TCP Control  = " << gControlSocketTCP  << "\n"
#if defined(__OpenBSD__)
          << "- TCP6 Control = " << gControlSocketTCP6 << "\n"
#endif
          << "- TCP Data     = " << gTCPSocket         << "\n"
#if defined(__OpenBSD__)
          << "- TCP6 Data    = " << gTCP6Socket        << "\n"
#endif
#if defined(HAVE_MPTCP)
          << "- MPTCP Data   = " << gMPTCPSocket       << "\n"
#endif
          << "- UDP Data     = " << gUDPSocket         << "\n"
#if defined(__OpenBSD__)
          << "- UDP6 Data    = " << gUDP6Socket        << "\n"
#endif
#if defined(HAVE_SCTP)
          << "- SCTP Data    = " << gSCTPSocket        << "\n"
#endif
#if defined(HAVE_DCCP)
          << "- DCCP Data    = " << gDCCPSocket        << "\n"
#endif
#if defined(HAVE_QUIC)
          << "- QUIC Data    = " << gQUICSocket        << "\n"
#endif
          ;
   LOG_END


   // ====== Main loop ======================================================
   signal(SIGPIPE, SIG_IGN);
   installBreakDetector();

   FlowManager::getFlowManager()->configureDisplay(gDisplayEnabled);

   const unsigned long long stopAt = (gRuntime > 0) ?
      (getMicroTime() + (unsigned long long)rint(gRuntime * 1000000.0)) : ~0ULL;
   while( (!breakDetected()) && (!gStopTimeReached) ) {
      mainLoop(false, stopAt);
   }

   FlowManager::getFlowManager()->configureDisplay(false);


   // ====== Clean up =======================================================
#if defined(__OpenBSD__)
   if(gControlSocketTCP6 >= 0) {
      gMessageReader.deregisterSocket(gControlSocketTCP6);
      ext_close(gControlSocketTCP6);
   }
#endif
   if(gControlSocketTCP >= 0) {
      gMessageReader.deregisterSocket(gControlSocketTCP);
      ext_close(gControlSocketTCP);
   }
   if(gControlSocket >= 0) {
      gMessageReader.deregisterSocket(gControlSocket);
      ext_close(gControlSocket);
   }
#if defined(__OpenBSD__)
   if(gTCP6Socket >= 0) {
      ext_close(gTCP6Socket);
   }
#endif
   ext_close(gTCPSocket);
#if defined(HAVE_MPTCP)
   if(gMPTCPSocket >= 0) {
      ext_close(gMPTCPSocket);
   }
#endif
#if defined(__OpenBSD__)
   if(gUDP6Socket >= 0) {
      FlowManager::getFlowManager()->removeUnidentifiedSocket(gUDP6Socket, false);
      ext_close(gUDP6Socket);
   }
#endif
   FlowManager::getFlowManager()->removeUnidentifiedSocket(gUDPSocket, false);
   ext_close(gUDPSocket);
#if defined(HAVE_SCTP)
   if(gSCTPSocket >= 0) {
      ext_close(gSCTPSocket);
   }
#endif
#if defined(HAVE_DCCP)
   if(gDCCPSocket >= 0) {
      ext_close(gDCCPSocket);
   }
#endif
#if defined(HAVE_QUIC)
   if(gQUICSocket >= 0) {
      ext_close(gQUICSocket);
   }
#endif
}



// ###### Active Mode #######################################################
static void activeMode(const char* remoteEndpoint)
{
   // ====== Initialize remote and control addresses ========================
   sockaddr_union remoteAddress;
   if(string2address(remoteEndpoint, &remoteAddress) == false) {
      LOG_FATAL
      std::cerr << format("ERROR: Invalid remote address %s", remoteEndpoint) << "!\n";
      LOG_END_FATAL
   }
   if(getPort(&remoteAddress.sa) < 2) {
      setPort(&remoteAddress.sa, 9000);
   }
   else if(getPort(&remoteAddress.sa) > 65534) {
      setPort(&remoteAddress.sa, 65534);
   }
   sockaddr_union controlAddress = remoteAddress;
   setPort(&controlAddress.sa, getPort(&remoteAddress.sa) + 1);


   // ====== Initialize IDs and print status ================================
   uint64_t measurementID = random64();

   std::string controlProtocol;
   switch(gActiveControlProtocol) {
      case IPPROTO_SCTP:
         controlProtocol = "SCTP";
       break;
      case IPPROTO_TCP:
         controlProtocol = "TCP";
       break;
#if defined(HAVE_MPTCP)
      case IPPROTO_MPTCP:
         controlProtocol = "MPTCP";
       break;
#endif
   }

   LOG_INFO
   stdlog << "Active Mode:\n"
          << "- Measurement ID  = " << format("$%llx", measurementID) << "\n"
          << "- Remote Address  = ";
   printAddress(stdlog, &remoteAddress.sa, true);
   stdlog << "\n"
          << "- Control Address = ";
   printAddress(stdlog, &controlAddress.sa, true);
   stdlog << " - connecting via " << controlProtocol << " ...\n";
   LOG_END

   // ====== Initialize control socket ======================================
   gControlSocket = createAndBindSocket(controlAddress.sa.sa_family, SOCK_STREAM, gActiveControlProtocol,
                                        0, gLocalControlAddresses, (const sockaddr_union*)&gLocalControlAddressArray,
                                        false, gBindV6Only);
   if(gControlSocket < 0) {
      LOG_FATAL
      stdlog << format("Failed to create and bind control socket: %s!",
                       strerror(errno)) << "\n";
      if( (gActiveControlProtocol == IPPROTO_SCTP) && (errno == EPROTONOSUPPORT) ) {
         stdlog << "SCTP is not available => Try -y|--control-over-tcp for control over TCP!\n";
      }
      LOG_END_FATAL
   }
#if defined(HAVE_SCTP)
   if(gActiveControlProtocol == IPPROTO_SCTP) {
      sctp_sndrcvinfo sinfo;
      memset(&sinfo, 0, sizeof(sinfo));
      sinfo.sinfo_ppid = htobe32(PPID_NETPERFMETER_CONTROL);
      if(ext_setsockopt(gControlSocket, IPPROTO_SCTP, SCTP_DEFAULT_SEND_PARAM, &sinfo, sizeof(sinfo)) < 0) {
         LOG_FATAL
         stdlog << format("Failed to configure default send parameters (SCTP_DEFAULT_SEND_PARAM) on SCTP control socket: %s!",
                          strerror(errno)) << "\n";
         LOG_END_FATAL
      }
   }
#endif
   if(ext_connect(gControlSocket, &controlAddress.sa, getSocklen(&controlAddress.sa)) < 0) {
      LOG_FATAL
      stdlog << format("Unable to establish control association: %s!",
                       strerror(errno)) << "\n";
      if(gActiveControlProtocol == IPPROTO_SCTP) {
         stdlog << "Note: Try -y|--control-over-tcp for control over TCP in case of NAT traversal or restrictive firewalls!\n";
      }
      LOG_END_FATAL
   }
#if defined(HAVE_SCTP)
   if(gActiveControlProtocol == IPPROTO_SCTP) {
      sctp_paddrparams paddr;
      memset(&paddr, 0, sizeof(paddr));
      memcpy(&paddr.spp_address, &controlAddress.sa, getSocklen(&controlAddress.sa));
      paddr.spp_flags      = SPP_HB_ENABLE;
      paddr.spp_hbinterval = 30000;
      if(ext_setsockopt(gControlSocket, IPPROTO_SCTP, SCTP_PEER_ADDR_PARAMS, &paddr, sizeof(paddr)) < 0) {
         LOG_WARNING
         stdlog << format("Unable to enable heartbeats on control association: %s!",
                          strerror(errno)) << "\n";
         LOG_END
      }
   }
#endif
   LOG_TRACE
   stdlog << "<okay; sd=" << gControlSocket << ">\n";
   LOG_END
   gMessageReader.registerSocket(gActiveControlProtocol, gControlSocket);


   // ====== Handle command-line parameters =================================

   // ------ Handle other parameters ----------------------------------------
   for(int f = 0; f <gFlowCount; f++) {
      for(std::vector<AssocSpec>::const_iterator assocSpecIterator = gAssocSpecs.begin();
         assocSpecIterator != gAssocSpecs.end(); assocSpecIterator++) {
         const AssocSpec& assocSpec = *assocSpecIterator;
         Flow* lastFlow = nullptr;
         for(std::vector<const char*>::const_iterator flowIterator = assocSpec.Flows.begin();
            flowIterator != assocSpec.Flows.end(); flowIterator++) {
            const char* flowSpec = *flowIterator;
            lastFlow = createFlow(lastFlow, flowSpec, measurementID,
                                  gVectorNamePattern, gVectorFileFormat,
                                  assocSpec.Protocol, remoteAddress);
            if(!performNetPerfMeterAddFlow(&gMessageReader, gControlSocket, lastFlow)) {
               LOG_FATAL
               stdlog << "ERROR: Failed to add flow to remote node!\n";
               LOG_END_FATAL
            }
            LOG_TRACE
            stdlog << "<okay; sd=" << gControlSocket << ">\n";
            LOG_END
         }
         lastFlow = nullptr;
      }
   }

   // ====== Print global parameters ========================================
   printGlobalParameters();

   // ====== Start measurement ==============================================
   if(!performNetPerfMeterStart(&gMessageReader, gControlSocket, measurementID,
                                gActiveNodeName, gPassiveNodeName,
                                gConfigName,
                                gVectorNamePattern, gVectorFileFormat,
                                gScalarNamePattern, gScalarFileFormat)) {
      LOG_FATAL
      stdlog << "ERROR: Failed to start measurement!\n";
      LOG_END_FATAL
   }


   // ====== Main loop ======================================================
   const unsigned long long stopAt = (gRuntime > 0) ?
      (getMicroTime() + (unsigned long long)rint(gRuntime * 1000000.0)) : ~0ULL;
   signal(SIGPIPE, SIG_IGN);
   installBreakDetector();

   FlowManager::getFlowManager()->configureDisplay(gDisplayEnabled);
   while( (!breakDetected()) && (!gStopTimeReached) ) {
      if(!mainLoop(true, stopAt)) {
         std::cout << "\n" << "*** Aborted ***\n";
         break;
      }
   }
   FlowManager::getFlowManager()->configureDisplay(false);

   if(gStopTimeReached) {
      std::cout << "\n";
   }


   // ====== Stop measurement ===============================================
   LOG_INFO
   stdlog << "Shutdown" << "\n";
   LOG_END
   if(!performNetPerfMeterStop(&gMessageReader, gControlSocket, measurementID)) {
      LOG_FATAL
      stdlog << "Failed to stop measurement and download the results!\n";
      LOG_END_FATAL
   }
}



// ###### Main program ######################################################
int main(int argc, char** argv)
{
   // ====== Handle parameters ==============================================
   handleGlobalParameters(argc, argv);
   if(argc - optind != 1) {
      usage(argv[0], 1);
   }
   const unsigned int localPort     = (strchr(argv[optind], ':') == nullptr) ?
                                         atol(argv[optind]) : 0;
   const bool         inPassiveMode = (localPort >= 1) && (localPort < 65535);
   if( (!inPassiveMode) && (gAssocSpecs.size() < 1) ) {
      std::cerr << "ERROR: No flows specified!\n";
      return 1;
   }

   // ====== Start logging ==================================================
   beginLogging();
   LOG_INFO
   stdlog << "Network Performance Meter " << NETPERFMETER_VERSION
          << " in "
          << ((inPassiveMode == true) ? "Passive Mode" : "Active Mode")
          << "\n";
   LOG_END

   // ====== Run active or passive instance =================================
   if(inPassiveMode) {
      passiveMode(localPort);
   }
   else {
      activeMode(argv[optind]);
   }

   // ====== Finish logging =================================================
   finishLogging();

   return 0;
}
