/* * Copyright (c) 2020, Broadband Forum * Copyright (c) 2020, AT&T Communications * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * 3. Neither the name of the copyright holder nor the names of its * contributors may be used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * * UDP Speed Test - udpst_control.c * * This file handles the control message processing needed to setup and * activate test sessions. This includes allocating connections and managing * the associated sockets. * * Author Date Comments * -------------------- ---------- ---------------------------------- * Len Ciavattone 01/16/2019 Created * Len Ciavattone 10/18/2019 Add param for load sample period * Len Ciavattone 11/04/2019 Add minimum delays to summary * Len Ciavattone 06/16/2020 Add dual-stack (IPv6) support * Len Ciavattone 07/02/2020 Added (HMAC-SHA256) authentication * Len Ciavattone 08/04/2020 Rearranged source files * Len Ciavattone 09/03/2020 Added __linux__ conditionals * Len Ciavattone 11/10/2020 Add option to ignore OoO/Dup * Len Ciavattone 10/13/2021 Refresh with clang-format * Add TR-181 fields in JSON * Add interface traffic rate support * Len Ciavattone 11/18/2021 Add backward compat. protocol version * Add bandwidth management support * Len Ciavattone 12/08/2021 Add starting sending rate * Len Ciavattone 12/17/2021 Add payload randomization * Len Ciavattone 02/02/2022 Add rate adj. algo. selection * */ #define UDPST_CONTROL #if __linux__ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #ifdef AUTH_KEY_ENABLE #include #include #endif #else #include "../udpst_control_alt1.h" #endif // #include "cJSON.h" #include "udpst_common.h" #include "udpst_protocol.h" #include "udpst.h" #include "udpst_control.h" #include "udpst_data.h" #ifndef __linux__ #include "../udpst_control_alt2.h" #endif //---------------------------------------------------------------------------- // // Internal function prototypes // int timeout_testinit(int); int service_actreq(int); int service_actresp(int); int sock_connect(int); int connected(int); //---------------------------------------------------------------------------- // // External data // extern int errConn, monConn; extern char scratch[STRING_SIZE]; extern struct configuration conf; extern struct repository repo; extern struct connection *conn; extern char *boolText[]; extern char *rateAdjAlgo[]; // extern cJSON *json_top, *json_output; //---------------------------------------------------------------------------- // // Global data // #define SRAUTO_TEXT "" #define OWD_TEXT "OWD" #define RTT_TEXT "RTT" #define ZERO_TEXT "zeroes" #define RAND_TEXT "random" #define TESTHDR_LINE1 \ "%s%s Test Int(sec): %d, DelayVar Thresh(ms): %d-%d [%s], Trial Int(ms): %d, Ignore OoO/Dup: %s, Payload: %s,\n" #define TESTHDR_LINE2 " SendRate Index: %s, Cong. Thresh: %d, High-Speed Delta: %d, SeqError Thresh: %d, Algo: %s, " static char *testHdrV4 = TESTHDR_LINE1 TESTHDR_LINE2 "IPv4 ToS: %d%s\n"; static char *testHdrV6 = TESTHDR_LINE1 TESTHDR_LINE2 "IPv6 TClass: %d%s\n"; //---------------------------------------------------------------------------- // Function definitions //---------------------------------------------------------------------------- // // Initialize a connection structure // void init_conn(int connindex, BOOL cleanup) { register struct connection *c = &conn[connindex]; int i; // // Cleanup prior to clear and init // if (cleanup) { if (connindex == repo.maxConnIndex) { for (i = connindex - 1; i >= 0; i--) { if (conn[i].fd == -1) continue; repo.maxConnIndex = i; break; } } if (c->fd >= 0) { #ifdef __linux__ // Event needed to be non-null before kernel version 2.6.9 epoll_ctl(repo.epollFD, EPOLL_CTL_DEL, c->fd, NULL); #endif close(c->fd); } if (c->intfFD >= 0) close(c->intfFD); } // // Clear structure // memset(&conn[connindex], 0, sizeof(struct connection)); // // Initialize non-zero values // c->fd = -1; c->priAction = &null_action; c->secAction = &null_action; c->timer1Action = &null_action; c->timer2Action = &null_action; c->timer3Action = &null_action; c->intfFD = -1; return; } //---------------------------------------------------------------------------- // // Null action routine // int null_action(int connindex) { (void) (connindex); return 0; } //---------------------------------------------------------------------------- // // Client function to send setup request to server's control port // // A setup response is expected back from the server // int send_setupreq(int connindex) { register struct connection *c = &conn[connindex]; int var; struct timespec tspecvar; char addrstr[INET6_ADDR_STRLEN], portstr[8], intfpath[IFNAMSIZ + 64]; struct controlHdrSR *cHdrSR = (struct controlHdrSR *) repo.defBuffer; #ifdef AUTH_KEY_ENABLE unsigned int uvar; #endif // // Open local sysfs interface statistics // if (*conf.intfName) { var = sprintf(intfpath, "/sys/class/net/%s/statistics/", conf.intfName); if (conf.usTesting) strcat(&intfpath[var], "tx_bytes"); else strcat(&intfpath[var], "rx_bytes"); if ((c->intfFD = open(intfpath, O_RDONLY)) < 0) { var = sprintf(scratch, "OPEN ERROR: %s (%s)\n", strerror(errno), intfpath); send_proc(errConn, scratch, var); return -1; } } // // Build setup request PDU // memset(cHdrSR, 0, CHSR_SIZE_CVER); cHdrSR->controlId = htons(CHSR_ID); c->protocolVer = PROTOCOL_VER; // Client always uses current version cHdrSR->protocolVer = htons((uint16_t) c->protocolVer); cHdrSR->cmdRequest = CHSR_CREQ_SETUPREQ; cHdrSR->cmdResponse = CHSR_CRSP_NONE; if (conf.maxBandwidth > 0) { c->maxBandwidth = conf.maxBandwidth; var = c->maxBandwidth; if (conf.usTesting) var |= CHSR_USDIR_BIT; // Set upstream bit of max bandwidth being transmitted cHdrSR->maxBandwidth = htons((uint16_t) var); } if (conf.jumboStatus) { cHdrSR->modifierBitmap |= CHSR_JUMBO_STATUS; } if (conf.traditionalMTU) { cHdrSR->modifierBitmap |= CHSR_TRADITIONAL_MTU; } if (*conf.authKey == '\0') { cHdrSR->authMode = AUTHMODE_NONE; cHdrSR->authUnixTime = 0; #ifdef AUTH_KEY_ENABLE } else { cHdrSR->authMode = AUTHMODE_SHA256; cHdrSR->authUnixTime = htonl((uint32_t) repo.systemClock.tv_sec); HMAC(EVP_sha256(), conf.authKey, strlen(conf.authKey), (const unsigned char *) cHdrSR, CHSR_SIZE_CVER, cHdrSR->authDigest, &uvar); #endif } // // Update global address info for subsequent send // if ((var = sock_mgmt(connindex, repo.serverIp, conf.controlPort, NULL, SMA_UPDATE)) != 0) { send_proc(errConn, scratch, var); return -1; } // // Send setup request PDU (socket not yet connected) // var = CHSR_SIZE_CVER; if (send_proc(connindex, (char *) cHdrSR, var) != var) return -1; if (monConn >= 0) { getnameinfo((struct sockaddr *) &repo.remSas, repo.remSasLen, addrstr, INET6_ADDR_STRLEN, portstr, sizeof(portstr), NI_NUMERICHOST | NI_NUMERICSERV); var = sprintf(scratch, "[%d]Setup request sent from %s:%d to %s:%s\n", connindex, c->locAddr, c->locPort, addrstr, portstr); send_proc(monConn, scratch, var); } // // Set timeout timer awaiting test initiation // tspecvar.tv_sec = TIMEOUT_NOTRAFFIC; tspecvar.tv_nsec = 0; tspecplus(&repo.systemClock, &tspecvar, &c->timer3Thresh); c->timer3Action = &timeout_testinit; return 0; } //---------------------------------------------------------------------------- // // Client function to process timeout awaiting test initiation // int timeout_testinit(int connindex) { register struct connection *c = &conn[connindex]; int var; // // Clear timeout timer // tspecclear(&c->timer3Thresh); c->timer3Action = &null_action; // // Notify user and set immediate end time // var = sprintf(scratch, "Timeout awaiting server response, exiting!\n"); send_proc(errConn, scratch, var); tspeccpy(&c->endTime, &repo.systemClock); return 0; } //---------------------------------------------------------------------------- // // Server function to service client setup request received on control port // // A new test connection is allocated and a setup response is sent back // int service_setupreq(int connindex) { register struct connection *c = &conn[connindex]; int i, var, pver, mbw = 0, currbw = repo.dsBandwidth; BOOL usbw = FALSE; struct timespec tspecvar; char addrstr[INET6_ADDR_STRLEN], portstr[8]; struct controlHdrSR *cHdrSR = (struct controlHdrSR *) repo.defBuffer; #ifdef AUTH_KEY_ENABLE unsigned int uvar; unsigned char digest1[AUTH_DIGEST_LENGTH], digest2[AUTH_DIGEST_LENGTH]; #endif // // Verify PDU // if (repo.rcvDataSize < (int) CHSR_SIZE_MVER || repo.rcvDataSize > (int) CHSR_SIZE_CVER || ntohs(cHdrSR->controlId) != CHSR_ID) { return 0; // Ignore bad PDU } if (cHdrSR->cmdRequest != CHSR_CREQ_SETUPREQ) { return 0; } if (cHdrSR->cmdResponse != CHSR_CRSP_NONE) { return 0; } // // Check specifics of setup request from client // var = 0; if (monConn >= 0) { getnameinfo((struct sockaddr *) &repo.remSas, repo.remSasLen, addrstr, INET6_ADDR_STRLEN, portstr, sizeof(portstr), NI_NUMERICHOST | NI_NUMERICSERV); } pver = (int) ntohs(cHdrSR->protocolVer); if (pver >= BWMGMT_PVER) { mbw = (int) (ntohs(cHdrSR->maxBandwidth) & ~CHSR_USDIR_BIT); // Obtain max bandwidth while ignoring upstream bit if (ntohs(cHdrSR->maxBandwidth) & CHSR_USDIR_BIT) { usbw = TRUE; // Max bandwidth is for upstream currbw = repo.usBandwidth; } } if (pver < PROTOCOL_MIN || pver > PROTOCOL_VER) { if (monConn >= 0) { var = sprintf(scratch, "[%d]Invalid version (%d) in setup request from %s:%s\n", connindex, pver, addrstr, portstr); } cHdrSR->protocolVer = htons(PROTOCOL_VER); // Send back expected version cHdrSR->cmdResponse = CHSR_CRSP_BADVER; } else if (((cHdrSR->modifierBitmap & CHSR_JUMBO_STATUS) && !conf.jumboStatus) || !((cHdrSR->modifierBitmap & CHSR_JUMBO_STATUS) && conf.jumboStatus)) { if (monConn >= 0) { var = sprintf(scratch, "[%d]Invalid jumbo datagram option in setup request from %s:%s\n", connindex, addrstr, portstr); } cHdrSR->cmdResponse = CHSR_CRSP_BADJS; } else if (((cHdrSR->modifierBitmap & CHSR_TRADITIONAL_MTU) && !conf.traditionalMTU) || (!(cHdrSR->modifierBitmap & CHSR_TRADITIONAL_MTU) && conf.traditionalMTU)) { if (monConn >= 0) { var = sprintf(scratch, "[%d]Invalid traditional MTU option in setup request from %s:%s\n", connindex, addrstr, portstr); } cHdrSR->cmdResponse = CHSR_CRSP_BADTMTU; } else if (pver >= BWMGMT_PVER && conf.maxBandwidth > 0 && mbw == 0) { if (monConn >= 0) { var = sprintf(scratch, "[%d]Required bandwidth not specified in setup request from %s:%s\n", connindex, addrstr, portstr); } cHdrSR->cmdResponse = CHSR_CRSP_NOMAXBW; } else if (pver >= BWMGMT_PVER && conf.maxBandwidth > 0 && currbw + mbw > conf.maxBandwidth) { if (monConn >= 0) { var = sprintf(scratch, "[%d]Capacity exceeded by required bandwidth (%d) in setup request from %s:%s\n", connindex, mbw, addrstr, portstr); } cHdrSR->cmdResponse = CHSR_CRSP_CAPEXC; } else if (cHdrSR->authMode != AUTHMODE_NONE && *conf.authKey == '\0') { if (monConn >= 0) { var = sprintf(scratch, "[%d]Unexpected authentication in setup request from %s:%s\n", connindex, addrstr, portstr); } cHdrSR->cmdResponse = CHSR_CRSP_AUTHNC; #ifdef AUTH_KEY_ENABLE } else if (cHdrSR->authMode == AUTHMODE_NONE && *conf.authKey != '\0') { if (monConn >= 0) { var = sprintf(scratch, "[%d]Authentication missing in setup request from %s:%s\n", connindex, addrstr, portstr); } cHdrSR->cmdResponse = CHSR_CRSP_AUTHREQ; } else if (cHdrSR->authMode != AUTHMODE_SHA256 && *conf.authKey != '\0') { if (monConn >= 0) { var = sprintf(scratch, "[%d]Invalid authentication method in setup request from %s:%s\n", connindex, addrstr, portstr); } cHdrSR->cmdResponse = CHSR_CRSP_AUTHINV; } else if (cHdrSR->authMode == AUTHMODE_SHA256 && *conf.authKey != '\0') { // // Validate authentication digest (leave zeroed for response) and check time window if enforced // memcpy(digest1, cHdrSR->authDigest, AUTH_DIGEST_LENGTH); memset(cHdrSR->authDigest, 0, AUTH_DIGEST_LENGTH); HMAC(EVP_sha256(), conf.authKey, strlen(conf.authKey), (const unsigned char *) cHdrSR, CHSR_SIZE_CVER, digest2, &uvar); if (memcmp(digest1, digest2, AUTH_DIGEST_LENGTH)) { if (monConn >= 0) { var = sprintf(scratch, "[%d]Authentication failure of setup request from %s:%s\n", connindex, addrstr, portstr); } cHdrSR->cmdResponse = CHSR_CRSP_AUTHFAIL; } else if (AUTH_ENFORCE_TIME) { tspecvar.tv_sec = (time_t) ntohl(cHdrSR->authUnixTime); if (tspecvar.tv_sec < repo.systemClock.tv_sec - AUTH_TIME_WINDOW || tspecvar.tv_sec > repo.systemClock.tv_sec + AUTH_TIME_WINDOW) { if (monConn >= 0) { var = sprintf(scratch, "[%d]Authentication time invalid in setup request from %s:%s\n", connindex, addrstr, portstr); } cHdrSR->cmdResponse = CHSR_CRSP_AUTHTIME; } } #endif } cHdrSR->cmdRequest = CHSR_CREQ_SETUPRSP; // Convert to setup response if (cHdrSR->cmdResponse != CHSR_CRSP_NONE) { // // If error, send back appropriate setup response and exit // if (monConn >= 0) send_proc(monConn, scratch, var); var = CHSR_SIZE_CVER; send_proc(connindex, (char *) cHdrSR, var); return 0; } if (monConn >= 0) { var = sprintf(scratch, "[%d]Setup request (Ver: %d, MaxBW: %d) received from %s:%s\n", connindex, pver, mbw, addrstr, portstr); send_proc(monConn, scratch, var); } // // Obtain new test connection for this client // if ((i = new_conn(-1, repo.serverIp, 0, T_UDP, &recv_proc, &service_actreq)) < 0) return 0; conn[i].protocolVer = pver; if (conf.maxBandwidth > 0) { conn[i].maxBandwidth = mbw; // Save bandwidth for adjustment at end of test if (usbw) repo.usBandwidth += mbw; // Update current upstream bandwidth else repo.dsBandwidth += mbw; // Update current downstream bandwidth } if (monConn >= 0) { var = sprintf(scratch, "[%d]Connection %d allocated and assigned %s:%d (New USBW: %d, DSBW: %d)\n", connindex, i, conn[i].locAddr, conn[i].locPort, repo.usBandwidth, repo.dsBandwidth); send_proc(monConn, scratch, var); } // // Set end time (used as watchdog) in case client goes quiet // tspecvar.tv_sec = TIMEOUT_NOTRAFFIC; tspecvar.tv_nsec = 0; tspecplus(&repo.systemClock, &tspecvar, &conn[i].endTime); // // Send setup response to client with port number of new test connection // cHdrSR->cmdResponse = CHSR_CRSP_ACKOK; cHdrSR->testPort = htons((uint16_t) conn[i].locPort); var = CHSR_SIZE_CVER; if (send_proc(connindex, (char *) cHdrSR, var) != var) return 0; if (monConn >= 0) { getnameinfo((struct sockaddr *) &repo.remSas, repo.remSasLen, addrstr, INET6_ADDR_STRLEN, portstr, sizeof(portstr), NI_NUMERICHOST | NI_NUMERICSERV); var = sprintf(scratch, "[%d]Setup response sent from %s:%d to %s:%s\n", connindex, c->locAddr, c->locPort, addrstr, portstr); send_proc(monConn, scratch, var); } return 0; } //---------------------------------------------------------------------------- // // Client function to service setup response received from server // // Send test activation request to server for the new test connection // int service_setupresp(int connindex) { register struct connection *c = &conn[connindex]; int var; char addrstr[INET6_ADDR_STRLEN], portstr[8]; struct controlHdrSR *cHdrSR = (struct controlHdrSR *) repo.defBuffer; struct controlHdrTA *cHdrTA = (struct controlHdrTA *) repo.defBuffer; // // Verify PDU // if (repo.rcvDataSize < (int) CHSR_SIZE_CVER || ntohs(cHdrSR->controlId) != CHSR_ID) { return 0; // Ignore bad PDU } if (cHdrSR->cmdRequest != CHSR_CREQ_SETUPRSP) { return 0; } // // Process any setup response errors // if (cHdrSR->cmdResponse != CHSR_CRSP_ACKOK) { if (cHdrSR->cmdResponse == CHSR_CRSP_BADVER) { var = sprintf(scratch, "ERROR: Client version (%i) does not match server (%u)\n", PROTOCOL_VER, ntohs(cHdrSR->protocolVer)); } else if (cHdrSR->cmdResponse == CHSR_CRSP_BADJS) { var = sprintf(scratch, "ERROR: Client jumbo datagram size option does not match server\n"); } else if (cHdrSR->cmdResponse == CHSR_CRSP_BADTMTU) { var = sprintf(scratch, "ERROR: Client traditional MTU option does not match server\n"); } else if (cHdrSR->cmdResponse == CHSR_CRSP_AUTHNC) { var = sprintf(scratch, "ERROR: Authentication not configured on server\n"); } else if (cHdrSR->cmdResponse == CHSR_CRSP_AUTHREQ) { var = sprintf(scratch, "ERROR: Authentication required by server\n"); } else if (cHdrSR->cmdResponse == CHSR_CRSP_AUTHINV) { var = sprintf(scratch, "ERROR: Authentication method does not match server\n"); } else if (cHdrSR->cmdResponse == CHSR_CRSP_AUTHFAIL) { var = sprintf(scratch, "ERROR: Authentication verification failed at server\n"); } else if (cHdrSR->cmdResponse == CHSR_CRSP_AUTHTIME) { var = sprintf(scratch, "ERROR: Authentication time outside server time window\n"); } else if (cHdrSR->cmdResponse == CHSR_CRSP_NOMAXBW) { var = sprintf(scratch, "ERROR: Max bandwidth option required by server\n"); } else if (cHdrSR->cmdResponse == CHSR_CRSP_CAPEXC) { var = sprintf(scratch, "ERROR: Required max bandwidth exceeds available server capacity\n"); } else { var = sprintf(scratch, "ERROR: Unexpected CRSP (%u) in setup response from server\n", cHdrSR->cmdResponse); } send_proc(errConn, scratch, var); tspeccpy(&c->endTime, &repo.systemClock); // Set for immediate close/exit return 0; } // // Obtain IP address and port number of sender // getnameinfo((struct sockaddr *) &repo.remSas, repo.remSasLen, addrstr, INET6_ADDR_STRLEN, portstr, sizeof(portstr), NI_NUMERICHOST | NI_NUMERICSERV); if (monConn >= 0) { var = sprintf(scratch, "[%d]Setup response received from %s:%s\n", connindex, addrstr, portstr); send_proc(monConn, scratch, var); } // // Update global address info with new server specified address/port number and connect socket // var = (int) ntohs(cHdrSR->testPort); if ((var = sock_mgmt(connindex, addrstr, var, NULL, SMA_UPDATE)) != 0) { send_proc(errConn, scratch, var); return 0; } if (sock_connect(connindex) < 0) return 0; // // Build test activation PDU // memset(cHdrTA, 0, CHTA_SIZE_CVER); cHdrTA->controlId = htons(CHTA_ID); cHdrTA->protocolVer = htons((uint16_t) c->protocolVer); if (conf.usTesting) { c->testType = TEST_TYPE_US; cHdrTA->cmdRequest = CHTA_CREQ_TESTACTUS; } else { c->testType = TEST_TYPE_DS; cHdrTA->cmdRequest = CHTA_CREQ_TESTACTDS; } cHdrTA->cmdResponse = CHTA_CRSP_NONE; // // Save configured parameters in connection and copy to test activation request // c->lowThresh = conf.lowThresh; cHdrTA->lowThresh = htons((uint16_t) c->lowThresh); c->upperThresh = conf.upperThresh; cHdrTA->upperThresh = htons((uint16_t) c->upperThresh); c->trialInt = conf.trialInt; cHdrTA->trialInt = htons((uint16_t) c->trialInt); c->testIntTime = conf.testIntTime; cHdrTA->testIntTime = htons((uint16_t) c->testIntTime); c->subIntPeriod = conf.subIntPeriod; cHdrTA->subIntPeriod = (uint8_t) c->subIntPeriod; c->ipTosByte = conf.ipTosByte; cHdrTA->ipTosByte = (uint8_t) c->ipTosByte; c->srIndexConf = conf.srIndexConf; cHdrTA->srIndexConf = htons((uint16_t) c->srIndexConf); c->useOwDelVar = (BOOL) conf.useOwDelVar; cHdrTA->useOwDelVar = (uint8_t) c->useOwDelVar; c->highSpeedDelta = conf.highSpeedDelta; cHdrTA->highSpeedDelta = (uint8_t) c->highSpeedDelta; c->slowAdjThresh = conf.slowAdjThresh; cHdrTA->slowAdjThresh = htons((uint16_t) c->slowAdjThresh); c->seqErrThresh = conf.seqErrThresh; cHdrTA->seqErrThresh = htons((uint16_t) c->seqErrThresh); c->ignoreOooDup = (BOOL) conf.ignoreOooDup; cHdrTA->ignoreOooDup = (uint8_t) c->ignoreOooDup; if (conf.srIndexIsStart) { c->srIndexIsStart = TRUE; // Designate configured value as starting point cHdrTA->modifierBitmap |= CHTA_SRIDX_ISSTART; } if (conf.randPayload) { c->randPayload = TRUE; cHdrTA->modifierBitmap |= CHTA_RAND_PAYLOAD; } c->rateAdjAlgo = conf.rateAdjAlgo; cHdrTA->rateAdjAlgo = (uint8_t) c->rateAdjAlgo; // // Send test activation request // c->secAction = &service_actresp; // Set service handler for response var = CHTA_SIZE_CVER; if (send_proc(connindex, (char *) cHdrTA, var) != var) return 0; if (monConn >= 0) { var = sprintf(scratch, "[%d]Test activation request sent from %s:%d to %s:%d\n", connindex, c->locAddr, c->locPort, c->remAddr, c->remPort); send_proc(monConn, scratch, var); } return 0; } //---------------------------------------------------------------------------- // // Server function to service test activation request received on new test connection // // Send test activation response back to client, connection is ready for testing // int service_actreq(int connindex) { register struct connection *c = &conn[connindex]; int var; char *testhdr, *testtype, connid[16], delusage[8], sritext[16], payload[8]; char addrstr[INET6_ADDR_STRLEN], portstr[8], intflabel[IFNAMSIZ + 8]; struct sendingRate *sr = repo.sendingRates; // Set to first row of table struct timespec tspecvar; struct controlHdrTA *cHdrTA = (struct controlHdrTA *) repo.defBuffer; // // Verify PDU // var = (int) ntohs(cHdrTA->protocolVer); if (repo.rcvDataSize < (int) CHTA_SIZE_MVER || repo.rcvDataSize > (int) CHTA_SIZE_CVER || ntohs(cHdrTA->controlId) != CHTA_ID || var < PROTOCOL_MIN || var > PROTOCOL_VER) { return 0; // Ignore bad PDU } if ((cHdrTA->cmdRequest != CHTA_CREQ_TESTACTUS) && (cHdrTA->cmdRequest != CHTA_CREQ_TESTACTDS)) { return 0; } if (cHdrTA->cmdResponse != CHTA_CRSP_NONE) { return 0; } // // Obtain IP address and port number of sender // getnameinfo((struct sockaddr *) &repo.remSas, repo.remSasLen, addrstr, INET6_ADDR_STRLEN, portstr, sizeof(portstr), NI_NUMERICHOST | NI_NUMERICSERV); if (monConn >= 0) { var = sprintf(scratch, "[%d]Test activation request received from %s:%s\n", connindex, addrstr, portstr); send_proc(monConn, scratch, var); } // // Update global address info with client address/port number and connect socket // var = atoi(portstr); if ((var = sock_mgmt(connindex, addrstr, var, NULL, SMA_UPDATE)) != 0) { send_proc(errConn, scratch, var); return 0; } if (sock_connect(connindex) < 0) return 0; //==================================================================================== // Accept (but police) most test parameters as is and enforce server configured // maximums where applicable. Update modified values for communication back to client. // If the request needs to be rejected use command response value CHTA_CRSP_BADPARAM. // cHdrTA->cmdResponse = CHTA_CRSP_ACKOK; // Initialize to request accepted // // Low and upper delay variation thresholds // c->lowThresh = (int) ntohs(cHdrTA->lowThresh); if (c->lowThresh < MIN_LOW_THRESH || c->lowThresh > MAX_LOW_THRESH) { c->lowThresh = DEF_LOW_THRESH; cHdrTA->lowThresh = htons((uint16_t) c->lowThresh); } c->upperThresh = (int) ntohs(cHdrTA->upperThresh); if (c->upperThresh < MIN_UPPER_THRESH || c->upperThresh > MAX_UPPER_THRESH) { c->upperThresh = DEF_UPPER_THRESH; cHdrTA->upperThresh = htons((uint16_t) c->upperThresh); } if (c->lowThresh > c->upperThresh) { // Check for invalid relationship c->lowThresh = DEF_LOW_THRESH; cHdrTA->lowThresh = htons((uint16_t) c->lowThresh); c->upperThresh = DEF_UPPER_THRESH; cHdrTA->upperThresh = htons((uint16_t) c->upperThresh); } // // Trial interval // c->trialInt = (int) ntohs(cHdrTA->trialInt); if (c->trialInt < MIN_TRIAL_INT || c->trialInt > MAX_TRIAL_INT) { c->trialInt = DEF_TRIAL_INT; cHdrTA->trialInt = htons((uint16_t) c->trialInt); } // // Test interval time and sub-interval period // c->testIntTime = (int) ntohs(cHdrTA->testIntTime); if (c->testIntTime < MIN_TESTINT_TIME || c->testIntTime > MAX_TESTINT_TIME) { c->testIntTime = DEF_TESTINT_TIME; cHdrTA->testIntTime = htons((uint16_t) c->testIntTime); } else if (c->testIntTime > conf.testIntTime) { // Enforce server maximum c->testIntTime = conf.testIntTime; cHdrTA->testIntTime = htons((uint16_t) c->testIntTime); } c->subIntPeriod = (int) cHdrTA->subIntPeriod; if (c->subIntPeriod < MIN_SUBINT_PERIOD || c->subIntPeriod > MAX_SUBINT_PERIOD) { c->subIntPeriod = DEF_SUBINT_PERIOD; cHdrTA->subIntPeriod = (uint8_t) c->subIntPeriod; } if (c->subIntPeriod > c->testIntTime) { // Check for invalid relationship c->testIntTime = DEF_TESTINT_TIME; cHdrTA->testIntTime = htons((uint16_t) c->testIntTime); c->subIntPeriod = DEF_SUBINT_PERIOD; cHdrTA->subIntPeriod = (uint8_t) c->subIntPeriod; } // // IP ToS/TClass byte (also set socket option) // c->ipTosByte = (int) cHdrTA->ipTosByte; if (c->ipTosByte < MIN_IPTOS_BYTE || c->ipTosByte > MAX_IPTOS_BYTE) { c->ipTosByte = DEF_IPTOS_BYTE; cHdrTA->ipTosByte = (uint8_t) c->ipTosByte; } else if (c->ipTosByte > conf.ipTosByte) { // Enforce server maximum c->ipTosByte = conf.ipTosByte; cHdrTA->ipTosByte = (uint8_t) c->ipTosByte; } if (c->ipTosByte != 0) { if (c->ipProtocol == IPPROTO_IPV6) var = IPV6_TCLASS; else var = IP_TOS; if (setsockopt(c->fd, c->ipProtocol, var, (const void *) &c->ipTosByte, sizeof(c->ipTosByte)) < 0) { c->ipTosByte = 0; cHdrTA->ipTosByte = (uint8_t) c->ipTosByte; } } // // Static or starting sending rate index (special case , which is the default but greater than max) // c->srIndexConf = (int) ntohs(cHdrTA->srIndexConf); if (c->srIndexConf != DEF_SRINDEX_CONF) { if (c->srIndexConf < MIN_SRINDEX_CONF || c->srIndexConf > MAX_SRINDEX_CONF) { c->srIndexConf = DEF_SRINDEX_CONF; cHdrTA->srIndexConf = htons((uint16_t) c->srIndexConf); } else if (c->srIndexConf > conf.srIndexConf) { // Enforce server maximum c->srIndexConf = conf.srIndexConf; cHdrTA->srIndexConf = htons((uint16_t) c->srIndexConf); } if (cHdrTA->modifierBitmap & CHTA_SRIDX_ISSTART) { c->srIndexIsStart = TRUE; // Designate configured value as starting point c->srIndex = c->srIndexConf; // Set starting point from configured value sr = &repo.sendingRates[c->srIndexConf]; // Select starting SR table row } } // // Use one-way delay flag // c->useOwDelVar = (BOOL) cHdrTA->useOwDelVar; if (c->useOwDelVar != TRUE && c->useOwDelVar != FALSE) { // Enforce C boolean c->useOwDelVar = DEF_USE_OWDELVAR; cHdrTA->useOwDelVar = (uint8_t) c->useOwDelVar; } // // High-speed delta // c->highSpeedDelta = (int) cHdrTA->highSpeedDelta; if (c->highSpeedDelta < MIN_HS_DELTA || c->highSpeedDelta > MAX_HS_DELTA) { c->highSpeedDelta = DEF_HS_DELTA; cHdrTA->highSpeedDelta = (uint8_t) c->highSpeedDelta; } // // Slow rate adjustment threshold // c->slowAdjThresh = (int) ntohs(cHdrTA->slowAdjThresh); if (c->slowAdjThresh < MIN_SLOW_ADJ_TH || c->slowAdjThresh > MAX_SLOW_ADJ_TH) { c->slowAdjThresh = DEF_SLOW_ADJ_TH; cHdrTA->slowAdjThresh = htons((uint16_t) c->slowAdjThresh); } // // Sequence error threshold // c->seqErrThresh = (int) ntohs(cHdrTA->seqErrThresh); if (c->seqErrThresh < MIN_SEQ_ERR_TH || c->seqErrThresh > MAX_SEQ_ERR_TH) { c->seqErrThresh = DEF_SEQ_ERR_TH; cHdrTA->seqErrThresh = htons((uint16_t) c->seqErrThresh); } // // Ignore Out-of-Order/Duplicate flag // c->ignoreOooDup = (BOOL) cHdrTA->ignoreOooDup; if (c->ignoreOooDup != TRUE && c->ignoreOooDup != FALSE) { // Enforce C boolean c->ignoreOooDup = DEF_IGNORE_OOODUP; cHdrTA->ignoreOooDup = (uint8_t) c->ignoreOooDup; } // // Payload randomization (only allow if also configured on server) // if (cHdrTA->modifierBitmap & CHTA_RAND_PAYLOAD) { if (conf.randPayload) { c->randPayload = TRUE; } else { cHdrTA->modifierBitmap &= ~CHTA_RAND_PAYLOAD; // Reset bit for return } } // // Rate adjustment algorithm // c->rateAdjAlgo = (int) cHdrTA->rateAdjAlgo; if (c->rateAdjAlgo < CHTA_RA_ALGO_MIN || c->rateAdjAlgo > CHTA_RA_ALGO_MAX) { c->rateAdjAlgo = DEF_RA_ALGO; cHdrTA->rateAdjAlgo = (uint8_t) c->rateAdjAlgo; } // // If upstream test, send back sending rate parameters from first row of table // if (cHdrTA->cmdRequest == CHTA_CREQ_TESTACTUS) { sr_copy(sr, &cHdrTA->srStruct, TRUE); } else { memset(&cHdrTA->srStruct, 0, sizeof(struct sendingRate)); } //==================================================================================== // // Continue updating connection if test activation is NOT being rejected // testtype = NULL; if (cHdrTA->cmdResponse == CHTA_CRSP_ACKOK) { // // Set connection test action as testing and initialize PDU received time // c->testAction = TEST_ACT_TEST; tspeccpy(&c->pduRxTime, &repo.systemClock); // // Finalize connection for testing based on test type // if (cHdrTA->cmdRequest == CHTA_CREQ_TESTACTUS) { // // Upstream // Setup to receive load PDUs and send status PDUs // c->testType = TEST_TYPE_US; testtype = USTEST_TEXT; c->rttMinimum = INITIAL_MIN_DELAY; c->rttSample = INITIAL_MIN_DELAY; c->secAction = &service_loadpdu; // c->delayVarMin = INITIAL_MIN_DELAY; tspeccpy(&c->trialIntClock, &repo.systemClock); tspecvar.tv_sec = 0; tspecvar.tv_nsec = (long) (c->trialInt * NSECINMSEC); tspecplus(&repo.systemClock, &tspecvar, &c->timer1Thresh); c->timer1Action = &send_statuspdu; } else { // // Downstream // Setup to receive status PDUs and send load PDUs // c->testType = TEST_TYPE_DS; testtype = DSTEST_TEXT; c->secAction = &service_statuspdu; // if (sr->txInterval1 > 0) { tspecvar.tv_sec = 0; tspecvar.tv_nsec = (long) ((sr->txInterval1 - SEND_TIMER_ADJ) * NSECINUSEC); tspecplus(&repo.systemClock, &tspecvar, &c->timer1Thresh); } c->timer1Action = &send1_loadpdu; if (sr->txInterval2 > 0) { tspecvar.tv_sec = 0; tspecvar.tv_nsec = (long) ((sr->txInterval2 - SEND_TIMER_ADJ) * NSECINUSEC); tspecplus(&repo.systemClock, &tspecvar, &c->timer2Thresh); } c->timer2Action = &send2_loadpdu; } } // // Send test activation response to client // var = CHTA_SIZE_CVER; if (send_proc(connindex, (char *) cHdrTA, var) != var) return 0; if (monConn >= 0) { var = sprintf(scratch, "[%d]Test activation response sent from %s:%d to %s:%d\n", connindex, c->locAddr, c->locPort, c->remAddr, c->remPort); send_proc(monConn, scratch, var); } // // Do not continue if test activation request is being rejected // if (cHdrTA->cmdResponse != CHTA_CRSP_ACKOK) { tspeccpy(&c->endTime, &repo.systemClock); // Set for immediate close/exit return 0; } // // Display test settings and general info if needed // *connid = '\0'; if (!conf.jsonOutput) { if (conf.verbose) sprintf(connid, "[%d]", connindex); if (!repo.isServer || conf.verbose) { if (c->ipProtocol == IPPROTO_IPV6) testhdr = testHdrV6; else testhdr = testHdrV4; if (c->useOwDelVar) strcpy(delusage, OWD_TEXT); else strcpy(delusage, RTT_TEXT); if (c->randPayload) strcpy(payload, RAND_TEXT); else strcpy(payload, ZERO_TEXT); if (c->srIndexConf == DEF_SRINDEX_CONF) { strcpy(sritext, SRAUTO_TEXT); } else if (c->srIndexIsStart) { sprintf(sritext, "%c%d", SRIDX_ISSTART_PREFIX, c->srIndexConf); } else { sprintf(sritext, "%d", c->srIndexConf); } *intflabel = '\0'; if (c->intfFD >= 0) { // Append interface label snprintf(intflabel, sizeof(intflabel), ", [%s]", conf.intfName); } var = sprintf(scratch, testhdr, connid, testtype, c->testIntTime, c->lowThresh, c->upperThresh, delusage, c->trialInt, boolText[c->ignoreOooDup], payload, sritext, c->slowAdjThresh, c->highSpeedDelta, c->seqErrThresh, rateAdjAlgo[c->rateAdjAlgo], c->ipTosByte, intflabel); send_proc(errConn, scratch, var); } } // // Update end time (used as watchdog) in case client goes quiet // tspecvar.tv_sec = TIMEOUT_NOTRAFFIC; tspecvar.tv_nsec = 0; tspecplus(&repo.systemClock, &tspecvar, &c->endTime); // // Set timer to stop test after desired test interval time // tspecvar.tv_sec = (time_t) c->testIntTime; tspecvar.tv_nsec = NSECINSEC / 2; tspecplus(&repo.systemClock, &tspecvar, &c->timer3Thresh); c->timer3Action = &stop_test; return 0; } //---------------------------------------------------------------------------- // // Client function to service test activation response from server // // Connection is ready for testing // int service_actresp(int connindex) { register struct connection *c = &conn[connindex]; int var, ipv6add; char *testhdr, *testtype, connid[16], delusage[8], sritext[16], payload[8]; char intflabel[IFNAMSIZ + 8]; struct sendingRate *sr = &c->srStruct; // Set to connection structure struct timespec tspecvar; struct controlHdrTA *cHdrTA = (struct controlHdrTA *) repo.defBuffer; // // Verify PDU // if (repo.rcvDataSize < (int) CHTA_SIZE_CVER || ntohs(cHdrTA->controlId) != CHTA_ID) { return 0; // Ignore bad PDU } if ((cHdrTA->cmdRequest != CHTA_CREQ_TESTACTUS) && (cHdrTA->cmdRequest != CHTA_CREQ_TESTACTDS)) { return 0; } // // Process any test activation response errors // if (cHdrTA->cmdResponse != CHTA_CRSP_ACKOK) { if (cHdrTA->cmdResponse == CHTA_CRSP_BADPARAM) { var = sprintf(scratch, "ERROR: Requested test parameter(s) rejected by server\n"); } else { var = sprintf(scratch, "ERROR: Unexpected CRSP (%u) in test activation response from server\n", cHdrTA->cmdResponse); } send_proc(errConn, scratch, var); tspeccpy(&c->endTime, &repo.systemClock); // Set for immediate close/exit return 0; } if (monConn >= 0) { var = sprintf(scratch, "[%d]Test activation response received from %s:%d\n", connindex, c->remAddr, c->remPort); send_proc(monConn, scratch, var); } // // Update test parameters (and set socket option) that may have been modified by server // c->lowThresh = (int) ntohs(cHdrTA->lowThresh); c->upperThresh = (int) ntohs(cHdrTA->upperThresh); c->trialInt = (int) ntohs(cHdrTA->trialInt); c->testIntTime = (int) ntohs(cHdrTA->testIntTime); c->subIntPeriod = (int) cHdrTA->subIntPeriod; c->ipTosByte = (int) cHdrTA->ipTosByte; if (c->ipTosByte != 0) { if (c->ipProtocol == IPPROTO_IPV6) var = IPV6_TCLASS; else var = IP_TOS; if (setsockopt(c->fd, c->ipProtocol, var, (const void *) &c->ipTosByte, sizeof(c->ipTosByte)) < 0) { var = sprintf(scratch, "ERROR: Failure setting IP ToS/TClass (%d) %s\n", c->ipTosByte, strerror(errno)); send_proc(errConn, scratch, var); tspeccpy(&c->endTime, &repo.systemClock); // Set for immediate close/exit return 0; } } c->srIndexConf = (int) ntohs(cHdrTA->srIndexConf); c->useOwDelVar = (BOOL) cHdrTA->useOwDelVar; c->highSpeedDelta = (int) cHdrTA->highSpeedDelta; c->slowAdjThresh = (int) ntohs(cHdrTA->slowAdjThresh); c->seqErrThresh = (int) ntohs(cHdrTA->seqErrThresh); c->ignoreOooDup = (BOOL) cHdrTA->ignoreOooDup; if (cHdrTA->cmdRequest == CHTA_CREQ_TESTACTUS) { // If upstream test, save sending rate parameters sent by server sr_copy(sr, &cHdrTA->srStruct, FALSE); } if (!(cHdrTA->modifierBitmap & CHTA_RAND_PAYLOAD)) { c->randPayload = FALSE; // Payload randomization rejected by server } c->rateAdjAlgo = (int) cHdrTA->rateAdjAlgo; // // Set connection test action as testing and initialize PDU received time // c->testAction = TEST_ACT_TEST; tspeccpy(&c->pduRxTime, &repo.systemClock); // // Finalize connection for testing based on test type // if (cHdrTA->cmdRequest == CHTA_CREQ_TESTACTUS) { // // Upstream // Setup to receive status PDUs and send load PDUs // testtype = USTEST_TEXT; c->secAction = &service_statuspdu; // if (sr->txInterval1 > 0) { tspecvar.tv_sec = 0; tspecvar.tv_nsec = (long) ((sr->txInterval1 - SEND_TIMER_ADJ) * NSECINUSEC); tspecplus(&repo.systemClock, &tspecvar, &c->timer1Thresh); } c->timer1Action = &send1_loadpdu; if (sr->txInterval2 > 0) { tspecvar.tv_sec = 0; tspecvar.tv_nsec = (long) ((sr->txInterval2 - SEND_TIMER_ADJ) * NSECINUSEC); tspecplus(&repo.systemClock, &tspecvar, &c->timer2Thresh); } c->timer2Action = &send2_loadpdu; } else { // // Downstream // Setup to receive load PDUs and send status PDUs // testtype = DSTEST_TEXT; c->rttMinimum = INITIAL_MIN_DELAY; c->rttSample = INITIAL_MIN_DELAY; c->secAction = &service_loadpdu; // c->delayVarMin = INITIAL_MIN_DELAY; tspeccpy(&c->trialIntClock, &repo.systemClock); tspecvar.tv_sec = 0; tspecvar.tv_nsec = (long) (c->trialInt * NSECINMSEC); tspecplus(&repo.systemClock, &tspecvar, &c->timer1Thresh); c->timer1Action = &send_statuspdu; } // // Display test settings and general info if needed // *connid = '\0'; if (conf.verbose) sprintf(connid, "[%d]", connindex); if (!repo.isServer || conf.verbose) { if (c->ipProtocol == IPPROTO_IPV6) testhdr = testHdrV6; else testhdr = testHdrV4; if (c->useOwDelVar) strcpy(delusage, OWD_TEXT); else strcpy(delusage, RTT_TEXT); if (c->randPayload) strcpy(payload, RAND_TEXT); else strcpy(payload, ZERO_TEXT); if (c->srIndexConf == DEF_SRINDEX_CONF) { strcpy(sritext, SRAUTO_TEXT); } else if (c->srIndexIsStart) { sprintf(sritext, "%c%d", SRIDX_ISSTART_PREFIX, c->srIndexConf); } else { sprintf(sritext, "%d", c->srIndexConf); } *intflabel = '\0'; if (c->intfFD >= 0) { // Append interface label snprintf(intflabel, sizeof(intflabel), ", [%s]", conf.intfName); } if (!conf.jsonOutput) { var = sprintf(scratch, testhdr, connid, testtype, c->testIntTime, c->lowThresh, c->upperThresh, delusage, c->trialInt, boolText[c->ignoreOooDup], payload, sritext, c->slowAdjThresh, c->highSpeedDelta, c->seqErrThresh, rateAdjAlgo[c->rateAdjAlgo], c->ipTosByte, intflabel); send_proc(errConn, scratch, var); } else { if (!conf.jsonBrief) { // // Create JSON input object // cJSON *json_input = cJSON_CreateObject(); // // Add items to input object // cJSON_AddStringToObject(json_input, "Interface", conf.intfName); if (cHdrTA->cmdRequest == CHTA_CREQ_TESTACTUS) { cJSON_AddStringToObject(json_input, "Role", "Sender"); } else { cJSON_AddStringToObject(json_input, "Role", "Receiver"); } cJSON_AddStringToObject(json_input, "Host", repo.serverName); cJSON_AddNumberToObject(json_input, "Port", c->remPort); cJSON_AddStringToObject(json_input, "HostIPAddress", c->remAddr); cJSON_AddStringToObject(json_input, "ClientIPAddress", c->locAddr); cJSON_AddNumberToObject(json_input, "ClientPort", c->locPort); cJSON_AddNumberToObject(json_input, "JumboFramesPermitted", conf.jumboStatus); cJSON_AddNumberToObject(json_input, "NumberOfConnections", 1); cJSON_AddNumberToObject(json_input, "DSCP", c->ipTosByte >> 2); if (conf.ipv4Only) { cJSON_AddStringToObject(json_input, "ProtocolVersion", "IPv4"); } else if (conf.ipv6Only) { cJSON_AddStringToObject(json_input, "ProtocolVersion", "IPv6"); } else { cJSON_AddStringToObject(json_input, "ProtocolVersion", "Any"); } ipv6add = 0; if (c->ipProtocol == IPPROTO_IPV6) ipv6add = IPV6_ADDSIZE; cJSON_AddNumberToObject(json_input, "UDPPayloadMin", MIN_PAYLOAD_SIZE - ipv6add); if (conf.jumboStatus) var = MAX_JPAYLOAD_SIZE; else if (conf.traditionalMTU) var = MAX_TPAYLOAD_SIZE; else var = MAX_PAYLOAD_SIZE; cJSON_AddNumberToObject(json_input, "UDPPayloadMax", var - ipv6add); if (conf.traditionalMTU) var = MAX_TPAYLOAD_SIZE; else var = MAX_PAYLOAD_SIZE; cJSON_AddNumberToObject(json_input, "UDPPayloadDefault", var - ipv6add); if (c->randPayload) { cJSON_AddStringToObject(json_input, "UDPPayloadContent", RAND_TEXT); } else { cJSON_AddStringToObject(json_input, "UDPPayloadContent", ZERO_TEXT); } if (c->srIndexConf == DEF_SRINDEX_CONF || c->srIndexIsStart) { cJSON_AddStringToObject(json_input, "TestType", "Search"); } else { cJSON_AddStringToObject(json_input, "TestType", "Fixed"); } cJSON_AddNumberToObject(json_input, "IPDVEnable", c->useOwDelVar); cJSON_AddNumberToObject(json_input, "IPRREnable", 1); cJSON_AddNumberToObject(json_input, "RIPREnable", 1); cJSON_AddNumberToObject(json_input, "PreambleDuration", 0); // Using "[Start]SendingRateIndex" instead of "StartSendingRate" for this implementation if (c->srIndexConf == DEF_SRINDEX_CONF || c->srIndexIsStart) { var = 0; if (c->srIndexIsStart) var = c->srIndexConf; cJSON_AddNumberToObject(json_input, "StartSendingRateIndex", var); cJSON_AddNumberToObject(json_input, "SendingRateIndex", -1); } else { cJSON_AddNumberToObject(json_input, "StartSendingRateIndex", c->srIndexConf); cJSON_AddNumberToObject(json_input, "SendingRateIndex", c->srIndexConf); } cJSON_AddNumberToObject(json_input, "NumberTestSubIntervals", c->testIntTime / c->subIntPeriod); cJSON_AddNumberToObject(json_input, "NumberFirstModeTestSubIntervals", conf.bimodalCount); cJSON_AddNumberToObject(json_input, "TestSubInterval", c->subIntPeriod * MSECINSEC); cJSON_AddNumberToObject(json_input, "StatusFeedbackInterval", c->trialInt); cJSON_AddNumberToObject(json_input, "TimeoutNoTestTraffic", WARNING_NOTRAFFIC * MSECINSEC); cJSON_AddNumberToObject(json_input, "TimeoutNoStatusMessage", WARNING_NOTRAFFIC * MSECINSEC); cJSON_AddNumberToObject(json_input, "Tmax", WARNING_NOTRAFFIC * MSECINSEC); cJSON_AddNumberToObject(json_input, "TmaxRTT", TIMEOUT_NOTRAFFIC * MSECINSEC); cJSON_AddNumberToObject(json_input, "TimestampResolution", 1); cJSON_AddNumberToObject(json_input, "SeqErrThresh", c->seqErrThresh); cJSON_AddNumberToObject(json_input, "ReordDupIgnoreEnable", c->ignoreOooDup); cJSON_AddNumberToObject(json_input, "LowerThresh", c->lowThresh); cJSON_AddNumberToObject(json_input, "UpperThresh", c->upperThresh); cJSON_AddNumberToObject(json_input, "HighSpeedDelta", c->highSpeedDelta); cJSON_AddNumberToObject(json_input, "SlowAdjThresh", c->slowAdjThresh); cJSON_AddNumberToObject(json_input, "HSpeedThresh", repo.hSpeedThresh * 1000000); cJSON_AddStringToObject(json_input, "RateAdjAlgorithm", rateAdjAlgo[c->rateAdjAlgo]); // // Add input object to top-level object // cJSON_AddItemToObject(json_top, "Input", json_input); } // // Create output object and add initial items // if (json_output == NULL) { json_output = cJSON_CreateObject(); } create_timestamp(&repo.systemClock); cJSON_AddStringToObject(json_output, "BOMTime", scratch); // cJSON_AddNumberToObject(json_output, "TmaxUsed", WARNING_NOTRAFFIC * MSECINSEC); cJSON_AddNumberToObject(json_output, "TestInterval", c->testIntTime); cJSON_AddNumberToObject(json_output, "TmaxRTTUsed", TIMEOUT_NOTRAFFIC * MSECINSEC); cJSON_AddNumberToObject(json_output, "TimestampResolutionUsed", 1); } } // // Clear timeout timer // tspecclear(&c->timer3Thresh); c->timer3Action = &null_action; // // Set end time (used as watchdog) in case server goes quiet // tspecvar.tv_sec = TIMEOUT_NOTRAFFIC; tspecvar.tv_nsec = 0; tspecplus(&repo.systemClock, &tspecvar, &c->endTime); return 0; } //---------------------------------------------------------------------------- // // Socket mgmt function for socket-based connections // // Populate scratch buffer and return length on error // int sock_mgmt(int connindex, char *host, int port, char *ip, int action) { register struct connection *c = &conn[connindex]; int i, var, fd; BOOL hostisaddr = FALSE; char addrstr[INET6_ADDR_STRLEN], portstr[8]; struct addrinfo hints, *res = NULL, *ai; struct sockaddr_storage sas; // // Process/resolve address parameter // memset(&hints, 0, sizeof(hints)); hints.ai_flags = AI_NUMERICSERV; hints.ai_family = conf.addrFamily; hints.ai_socktype = SOCK_DGRAM; if (host == NULL) { hints.ai_flags |= AI_PASSIVE; } else if (*host == '\0') { host = NULL; hints.ai_flags |= AI_PASSIVE; } else { // // Use inet_pton() to prevent possibly unnecessary name lookup by getaddrinfo() // if (inet_pton(AF_INET, host, &sas) == 1) { hostisaddr = TRUE; hints.ai_family = AF_INET; hints.ai_flags |= AI_NUMERICHOST; } else if (inet_pton(AF_INET6, host, &sas) == 1) { // IPv6 link-local addresses may require a Zone/Scope ID suffix ('%') hostisaddr = TRUE; hints.ai_family = AF_INET6; hints.ai_flags |= AI_NUMERICHOST; } } snprintf(portstr, sizeof(portstr), "%d", port); // // Obtain address info/resolve name if needed // if ((i = getaddrinfo(host, portstr, &hints, &res)) != 0) { var = sprintf(scratch, "GETADDRINFO ERROR: %s (%s)\n", strerror(errno), (const char *) gai_strerror(i)); if (res) freeaddrinfo(res); return var; } // // Check specified address against address family (if also specified), else output name resolution details // if (action == SMA_LOOKUP && host != NULL) { if (hostisaddr) { if (conf.addrFamily != AF_UNSPEC && conf.addrFamily != hints.ai_family) { var = sprintf(scratch, "ERROR: Specified IP address does not match address family\n"); if (res) freeaddrinfo(res); return var; } } else if (monConn >= 0) { var = sprintf(scratch, "%s =", host); for (ai = res; ai != NULL; ai = ai->ai_next) { getnameinfo(ai->ai_addr, ai->ai_addrlen, addrstr, INET6_ADDR_STRLEN, NULL, 0, NI_NUMERICHOST); var += sprintf(&scratch[var], " %s", addrstr); } var += sprintf(&scratch[var], "\n"); send_proc(monConn, scratch, var); } } // // Process address info based on action (prefer returned order of addresses) // if (host == NULL) host = ""; var = sprintf(scratch, "ERROR: Socket mgmt, action %d failure for %s:%d\n", action, host, port); for (ai = res; ai != NULL; ai = ai->ai_next) { if (action == SMA_LOOKUP) { // // If address family not specified, set it to match for subsequent calls // if (conf.addrFamily == AF_UNSPEC) conf.addrFamily = ai->ai_family; // // Save IP address to designated location // getnameinfo((struct sockaddr *) ai->ai_addr, ai->ai_addrlen, ip, INET6_ADDR_STRLEN, NULL, 0, NI_NUMERICHOST); var = 0; break; } else if (action == SMA_BIND) { // // Special case for server when no bind address (or address family) is specified. Continue // to next ai if it is INET6 but this one isn't, so server supports both by default. // if (repo.isServer && repo.serverName == NULL && ai->ai_next != NULL) { if (ai->ai_family != AF_INET6 && ai->ai_next->ai_family == AF_INET6) continue; } // // Obtain socket, restrict to INET6 if needed, and bind // if ((fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol)) == -1) { var = sprintf(scratch, "SOCKET ERROR: %s (%s:%d)\n", strerror(errno), host, port); continue; } if (conf.ipv6Only) { i = 1; if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, (const void *) &i, sizeof(i)) == -1) { var = sprintf(scratch, "IPV6_V6ONLY ERROR: %s\n", strerror(errno)); close(fd); continue; } } if (bind(fd, ai->ai_addr, ai->ai_addrlen) == -1) { var = sprintf(scratch, "BIND ERROR: %s (%s:%d)\n", strerror(errno), host, port); if (errno == EINVAL && ai->ai_family == AF_INET6) { var += sprintf(&scratch[var], "%s\n", "HINT: Address may require a Zone/Scope ID suffix (e.g., '%eth1')"); } close(fd); continue; } c->fd = fd; c->subType = SOCK_DGRAM; c->state = S_BOUND; var = 0; break; } else if (action == SMA_UPDATE) { // // Update address info for subsequent operations // memcpy(&repo.remSas, ai->ai_addr, ai->ai_addrlen); repo.remSasLen = ai->ai_addrlen; var = 0; break; } } if (res != NULL) freeaddrinfo(res); return var; } //---------------------------------------------------------------------------- // // Obtain and initialize a new connection structure // int new_conn(int activefd, char *host, int port, int type, int (*priaction)(int), int (*secaction)(int)) { int i, var, fd, sndbuf, rcvbuf; struct sockaddr_storage sas; char portstr[8]; #ifdef __linux__ struct epoll_event epevent; #endif // // Find available connection within connection array // fd = activefd; for (i = 0; i < MAX_CONNECTIONS; i++) { if (conn[i].fd == -1) { conn[i].fd = fd; // Save initial descriptor conn[i].type = type; // Set connection type conn[i].state = S_CREATED; // Set connection state conn[i].priAction = priaction; // Set primary action routine conn[i].secAction = secaction; // Set secondary action routine break; } } if (i == MAX_CONNECTIONS) { var = sprintf(scratch, "ERROR: Max connections exceeded\n"); send_proc(errConn, scratch, var); return -1; } if (i > repo.maxConnIndex) repo.maxConnIndex = i; // // Perform socket creation and bind // if (type == T_UDP) { if ((var = sock_mgmt(i, host, port, NULL, SMA_BIND)) != 0) { send_proc(errConn, scratch, var); init_conn(i, TRUE); return -1; } fd = conn[i].fd; // Update local descriptor } // // Set FD as non-blocking // Console FD (i.e., stdin) gets setup in main() // if (type != T_CONSOLE) { var = fcntl(fd, F_GETFL, 0); if (fcntl(fd, F_SETFL, var | O_NONBLOCK) != 0) { var = sprintf(scratch, "[%d]F_SETFL ERROR: %s\n", i, strerror(errno)); send_proc(errConn, scratch, var); init_conn(i, TRUE); return -1; } } #ifdef __linux__ // // Add fd for epoll read operations (exclude console when command line not supported) // if ((type != T_LOG) && (type != T_NULL) && (type != T_CONSOLE)) { epevent.events = EPOLLIN; epevent.data.u32 = (uint32_t) i; if (epoll_ctl(repo.epollFD, EPOLL_CTL_ADD, fd, &epevent) != 0) { var = sprintf(scratch, "[%d]EPOLL_CTL ERROR: %s\n", i, strerror(errno)); send_proc(errConn, scratch, var); init_conn(i, TRUE); return -1; } } #endif // // Return if FD already existed // if (activefd != -1) return i; // // Set address reuse // if (type == T_UDP) { var = 1; if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (const void *) &var, sizeof(var)) < 0) { var = sprintf(scratch, "[%d]SET SO_REUSEADDR ERROR: %s\n", i, strerror(errno)); send_proc(errConn, scratch, var); init_conn(i, TRUE); return -1; } } // // Change buffering if specified // if (type == T_UDP) { // // Set socket buffers // if (conf.sockSndBuf != 0 && conf.sockRcvBuf != 0) { if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, (const void *) &conf.sockSndBuf, sizeof(conf.sockSndBuf)) < 0) { var = sprintf(scratch, "[%d]SET SO_SNDBUF ERROR: %s\n", i, strerror(errno)); send_proc(errConn, scratch, var); init_conn(i, TRUE); return -1; } if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, (const void *) &conf.sockRcvBuf, sizeof(conf.sockRcvBuf)) < 0) { var = sprintf(scratch, "[%d]SET SO_RCVBUF ERROR: %s\n", i, strerror(errno)); send_proc(errConn, scratch, var); init_conn(i, TRUE); return -1; } } // // Get buffer values // if (monConn >= 0) { sndbuf = 0; var = sizeof(sndbuf); if (getsockopt(fd, SOL_SOCKET, SO_SNDBUF, (void *) &sndbuf, (socklen_t *) &var) < 0) { var = sprintf(scratch, "[%d]GET SO_SNDBUF ERROR: %s\n", i, strerror(errno)); send_proc(errConn, scratch, var); init_conn(i, TRUE); return -1; } rcvbuf = 0; var = sizeof(rcvbuf); if (getsockopt(fd, SOL_SOCKET, SO_RCVBUF, (void *) &rcvbuf, (socklen_t *) &var) < 0) { var = sprintf(scratch, "[%d]GET SO_RCVBUF ERROR: %s\n", i, strerror(errno)); send_proc(errConn, scratch, var); init_conn(i, TRUE); return -1; } var = sprintf(scratch, "[%d]Socket created with SO_SNDBUF/SO_RCVBUF of %d/%d\n", i, sndbuf, rcvbuf); send_proc(monConn, scratch, var); } } // // Obtain local IP address and port number // var = sizeof(sas); if (getsockname(fd, (struct sockaddr *) &sas, (socklen_t *) &var) < 0) { var = sprintf(scratch, "[%d]GETSOCKNAME ERROR: %s\n", i, strerror(errno)); send_proc(errConn, scratch, var); init_conn(i, TRUE); return -1; } getnameinfo((struct sockaddr *) &sas, var, conn[i].locAddr, INET6_ADDR_STRLEN, portstr, sizeof(portstr), NI_NUMERICHOST | NI_NUMERICSERV); conn[i].locPort = atoi(portstr); // // Finish processing by setting to data state // conn[i].state = S_DATA; return i; } //---------------------------------------------------------------------------- // // Initiate a socket connect // int sock_connect(int connindex) { register struct connection *c = &conn[connindex]; int var; // // Issue connect // if (connect(c->fd, (struct sockaddr *) &repo.remSas, repo.remSasLen) == -1) { // // Connect error (immediate completion expected with SOCK_DGRAM) // var = sprintf(scratch, "[%d]CONNECT ERROR: %s\n", connindex, strerror(errno)); send_proc(errConn, scratch, var); return -1; } c->state = S_DATA; c->connected = TRUE; // // Call connect completion handler directly // return connected(connindex); } //---------------------------------------------------------------------------- // // Socket connect completion handler // int connected(int connindex) { register struct connection *c = &conn[connindex]; char *p; int var; char portstr[8]; struct sockaddr_storage sas; // // Initialize post-connect action routines // c->priAction = &recv_proc; c->secAction = &null_action; // // Update local IP address and port number // var = sizeof(sas); if (getsockname(c->fd, (struct sockaddr *) &sas, (socklen_t *) &var) < 0) { var = sprintf(scratch, "[%d]GETSOCKNAME ERROR: %s\n", connindex, strerror(errno)); send_proc(errConn, scratch, var); return -1; } getnameinfo((struct sockaddr *) &sas, var, c->locAddr, INET6_ADDR_STRLEN, portstr, sizeof(portstr), NI_NUMERICHOST | NI_NUMERICSERV); c->locPort = atoi(portstr); // // Obtain remote IP address and port number // var = sizeof(sas); if (getpeername(c->fd, (struct sockaddr *) &sas, (socklen_t *) &var) < 0) { var = sprintf(scratch, "[%d]GETPEERNAME ERROR: %s\n", connindex, strerror(errno)); send_proc(errConn, scratch, var); return -1; } getnameinfo((struct sockaddr *) &sas, var, c->remAddr, INET6_ADDR_STRLEN, portstr, sizeof(portstr), NI_NUMERICHOST | NI_NUMERICSERV); c->remPort = atoi(portstr); // // Check if peer is IPv6 (i.e., not an IPv4 [x.x.x.x] or IPv4-mapped address [::ffff:x.x.x.x]) // var = 0; for (p = c->remAddr; *p; p++) { if (*p == '.') var++; } if (var != 3) { c->ipProtocol = IPPROTO_IPV6; } else { c->ipProtocol = IPPROTO_IP; } return 0; } //----------------------------------------------------------------------------