/* * XXTCP * * Copyright 2019 VMS Software, Inc. * * Runs in a initiator(client)/target(server) configuration. The initiator * xxtcp connects to the target xxtcp identified by port number and IP * address. The initiator xxtcp then sends TCP messages to the target which * receives them and sends them back (if requested). The initiator and * target xxtcp also do a data compare of received data if requested. * * Multiple initiators can be directed at a single target instance (up to * MAX_TARGET). * * To get help: * * xxtcp -h * * Message data format: * +---------------------------------+ * | Port number | 0 * +---------------------------------+ * | 0 | 4 * +---------------------------------+ * | Port number | 8 * +---------------------------------+ * | 1 | 12 * +---------------------------------+ * | Port number | 16 * +---------------------------------+ * | 2 | 20 * +---------------------------------+ * ... * * To build: * cc xxtcp+xx_support+sys$library:sys$lib_c.tlb/lib * link xxtcp */ #define _SOCKADDR_LEN 1 #include #include #include #include #include #include "socket.h" #include "xx_support.h" #define socklen_t uint /* Message and transmit defaults */ #define MSG_MIN 1 #define MSG_MAX (64*1024) #define MAX_TARGET 4 /* Statistics */ uint64_t stat_xmsg = 0, stat_xfull = 0, stat_xby = 0, stat_rmsg = 0, stat_rby = 0, stat_cmp_err = 0, stat_out = 0; uint64_t last_stat_xmsg = 0, last_stat_xfull = 0, last_stat_xby = 0, last_stat_rmsg = 0, last_stat_rby = 0, last_stat_cmp_err = 0; uint64_t interval_stat_xmsg, interval_stat_xfull, interval_stat_xby, interval_stat_rmsg, interval_stat_rby, interval_stat_cmp_err; double bwx, bwr, bwpx, bwpr, bwxt, bwrt, bwpxt, bwprt; /* Send and receive context */ char xmt_buffer[MSG_MAX * 2]; char rcv_buffer[MSG_MAX * 2]; char cmp_buffer[MSG_MAX * 2]; int pipeline = 0; int maxxmt = 0; uint64_t req_transmits = 0; int msg_len = 60000, msg_max, msg_mod; int alt_recv_flag = FALSE; int share_flag = FALSE; char dest_ip[40]; /* Socket information */ int sockfd, socktarget[MAX_TARGET]; struct sockaddr_in sock_addr[MAX_TARGET]; uint64_t sock_rby[MAX_TARGET]; struct sockaddr_in sockaddr; int sockport = 1234; int sobuflen_send = -1; int sobuflen_recv = -1; /* Process send errors */ int check_send_error (int err) { if (err < 0) { int tmp; unsigned int tmplen; if ((errno == EAGAIN) || (errno == ENOBUFS) || (errno == EWOULDBLOCK)) { stat_xfull++; // tmplen = sizeof(tmp); // getsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &tmp, &tmplen); // sprintf(pline, "? xfull, sndbuf %d", tmp); // print_line(pline, pline); } else { sprintf(pline, "? Send error %d (%s)", errno, strerror(errno)); print_line(pline, pline); return SS$_FAIL; } } return SS$_NORMAL; } /* Process receive errors */ int check_recv_error (int err) { if ((err < 0) && (errno != EAGAIN) && (errno != EDEADLK) && (errno != EWOULDBLOCK) && (errno != ESHUTDOWN)) { sprintf(pline, "? Recv error %d (%s)", errno, strerror(errno)); print_line(pline, pline); return SS$_FAIL; /* If remote side closed connection */ } else if (err == 0) return SS$_DISCONNECT; /* Else informational error */ return SS$_NORMAL; } /* socket_setup_initiator (client): * * - Create the socket * - Connect to requested IP address and port number */ int socket_setup_initiator () { int option = 1; memset(&sockaddr, 0, sizeof(sockaddr)); sockaddr.sin_family = AF_INET; sockaddr.sin_port = htons(sockport); sockaddr.sin_addr.s_addr = inet_addr(dest_ip); /* Create the socket */ if ((sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) { sprintf(pline, "? Failed to create socket %d (%s)", errno, strerror(errno)); print_line(pline, pline); return SS$_FAIL; } if ((sobuflen_send > 0) && setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &sobuflen_send, sizeof(sobuflen_send))) { sprintf(pline, "? Failed to set socket sndbuf size, error %d (%s)", errno, strerror(errno)); print_line(pline, pline); sobuflen_send = -1; } if ((sobuflen_recv > 0) && setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &sobuflen_recv, sizeof(sobuflen_recv))) { sprintf(pline, "? Failed to set socket rcvbuf size, error %d (%s)", errno, strerror(errno)); print_line(pline, pline); sobuflen_recv = -1; } if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option))) { sprintf(pline, "? Failed to set socket reuseaddr, error %d (%s)", errno, strerror(errno)); print_line(pline, pline); } /* Connect the socket */ if (connect(sockfd, (struct sockaddr *)&sockaddr, sizeof(struct sockaddr)) < 0) { sprintf(pline, "? Failed to connect socket %d (%s)", errno, strerror(errno)); print_line(pline, pline); return SS$_FAIL; } return SS$_NORMAL; } /* socket_setup_target (server): * * - Create the socket * - Listen to any IP address and requested port number */ int socket_setup_target () { int option = 1; memset(&sockaddr, 0, sizeof(sockaddr)); sockaddr.sin_family = AF_INET; sockaddr.sin_port = htons(sockport); sockaddr.sin_addr.s_addr = htonl(INADDR_ANY); /* Create the socket */ if ((sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) { sprintf(pline, "? Failed to create socket %d (%s)", errno, strerror(errno)); print_line(pline, pline); return SS$_FAIL; } if ((sobuflen_send > 0) && setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &sobuflen_send, sizeof(sobuflen_send))) { sprintf(pline, "? Failed to set socket sndbuf size, error %d (%s)", errno, strerror(errno)); print_line(pline, pline); sobuflen_send = -1; } if ((sobuflen_recv > 0) && setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &sobuflen_recv, sizeof(sobuflen_recv))) { sprintf(pline, "? Failed to set socket rcvbuf size, error %d (%s)", errno, strerror(errno)); print_line(pline, pline); sobuflen_recv = -1; } if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option))) { sprintf(pline, "? Failed to set socket reuseaddr, error %d (%s)", errno, strerror(errno)); print_line(pline, pline); } option = (share_flag) ? 1 : 0; if (setsockopt(sockfd, SOL_SOCKET, SO_SHARE, &option, sizeof(option))) { sprintf(pline, "? Failed to set socket share, error %d (%s)", errno, strerror(errno)); print_line(pline, pline); } /* Listen to any host and requested port number */ if (bind(sockfd, (struct sockaddr *)&sockaddr, sizeof(sockaddr)) < 0) { sprintf(pline, "? Failed to bind socket %d (%s)", errno, strerror(errno)); print_line(pline, pline); return SS$_FAIL; } listen(sockfd, MAX_TARGET); /* Clear list of connections */ memset(socktarget, 0, sizeof(socktarget)); return SS$_NORMAL; } /* msg_echo - Echo message data for the length specified */ void msg_echo (char *what, char *buf, int msg_len) { int echo_len = (msg_len > echo_size) ? echo_size : msg_len; uint32_t *pk = (uint32_t *)buf; struct timeval echo_time; uint8_t *ipaddr = (uint8_t *)&sockaddr.sin_addr.s_addr; echo_num--; /* Print packet context */ gettimeofday(&echo_time, NULL); sprintf(pline, "%lld.%06lld %s %d.%d.%d.%d, len %d", echo_time.tv_sec, echo_time.tv_usec, what, ipaddr[0], ipaddr[1], ipaddr[2], ipaddr[3], msg_len); print_line(pline, pline); /* Print packet contents if specified */ pk_dump_packet(pk, echo_len); } /* pk_comp - Do data compare */ int pk_comp (char *what, char *buf, int msg_len, int offset, int snum) { int save_echo_size; #if DEBUG_CERR /* Check for debug forcing a compare error */ if (force_cerr_flag && (stat_rmsg == force_cerr_flag)) { buf[33] ^= 1; force_cerr_flag = 0; } #endif /* Do the data compare */ if (memcmp(&cmp_buffer[offset], buf, msg_len)) { int err_offset; for (err_offset=0; err_offset 0) { if (alt_recv_flag) { socklen_t fromlen = sizeof(addr); rx_len = recvfrom(socktarget[snum], (void *)&rcv_buffer, msg_len, MSG_DONTWAIT, &addr, &fromlen); } else rx_len = recv(socktarget[snum], (void *)&rcv_buffer, msg_len, MSG_DONTWAIT); /* If data received */ if (rx_len > 0) { if (echo_num) msg_echo("Rcv from", rcv_buffer, rx_len); if (compare_flag && (pk_comp("Rcv", rcv_buffer, rx_len, sock_rby[snum] & 0xFFFF, snum) == SS$_FAIL)) stat_cmp_err++; stat_rmsg++; stat_rby += rx_len; sock_rby[snum] += rx_len; /* Loop back the packet if enabled */ if (!discard_flag) { int offset = 0; while (rx_len) { tx_len = send(socktarget[snum], (void *)&rcv_buffer[offset], rx_len, 0); /* Successful send */ if (tx_len > 0) { stat_xmsg++; stat_xby += tx_len; if (echo_num) msg_echo("Xmt to", rcv_buffer, tx_len); rx_len -= tx_len; offset += tx_len; /* Socket full or other error */ } else if (check_send_error(tx_len) != SS$_NORMAL) break; } } /* Error or other side closed the connection (rx_len was zero) */ } else if ((status = check_recv_error(rx_len)) != SS$_NORMAL) { if (status == SS$_DISCONNECT) { int i, j; uint8_t *ipaddr = (uint8_t *)&sock_addr[snum].sin_addr.s_addr; sprintf(pline, "Connection closed from %d.%d.%d.%d", ipaddr[0], ipaddr[1], ipaddr[2], ipaddr[3]); print_line(pline, pline); socktarget[snum] = 0; } continue; } } /* Print periodic stats */ cur_time = time(0); if ((req_transmits <= stat_xby) || (end_time <= cur_time)) goto done; if ((stat_time <= cur_time) || override_interval_flag) { override_interval_flag = 0; print_target_stats(); stat_time = cur_time + stats_interval; } } } done: /* Do final stats */ print_target_stats(); print_final_target_stats(); for (int snum=0; snum 0) close(socktarget[snum]); } /* do_initiator_test: * * while (1) * - Send to initiator socket * - While receive data available: * - Receive on initiator socket * - Data compare if requested * - Print stats periodically */ void do_initiator_test () { int rx_len, tx_len, burst, offset, status; struct sockaddr addr; /* Set up initiator socket */ if (socket_setup_initiator() == SS$_FAIL) { sprintf(pline, "? Failed to set up initiator socket"); print_line(pline, pline); exit(SS$_FAIL); } /* Display test parameters */ print_banner("Initiator Test"); /* Loop till no more packets to send */ while (req_transmits > stat_xby) { if ((maxxmt == 0) || oktoxmt()) { GEN_BURST; for (burst=0; burst 0) { if (echo_num) msg_echo("Xmt to", &xmt_buffer[offset], tx_len); stat_xmsg++; stat_xby += tx_len; /* Else socket full or other error */ } else if (check_send_error(tx_len) != SS$_NORMAL) goto done; } } /* Receive until outstanding less than pipeline */ if (discard_flag == FALSE) { while (1) { /* Receive at last point in receive buffer */ offset = stat_rby & 0xFFFF; if (alt_recv_flag) { socklen_t fromlen = sizeof(addr); rx_len = recvfrom(sockfd, (void *)&rcv_buffer[offset], msg_len, MSG_DONTWAIT, &addr, &fromlen); } else rx_len = recv(sockfd, (void *)&rcv_buffer[offset], msg_len, MSG_DONTWAIT); /* If data received */ if (rx_len > 0) { if (echo_num) msg_echo("Rcv from", &rcv_buffer[offset], rx_len); if (compare_flag && (pk_comp("Rcv", &rcv_buffer[offset], rx_len, offset, 0) == SS$_FAIL)) stat_cmp_err++; stat_rmsg++; stat_rby += rx_len; /* Error or other side closed the connection (rx_len was zero) */ } else if ((status = check_recv_error(rx_len)) != SS$_NORMAL) { if (status == SS$_DISCONNECT) { sprintf(pline, "Connection closed, exiting"); print_line(pline, pline); exit(SS$_NORMAL); } break; /* Nothing received, but check pipeline if need to keep checking */ } else { /* If pipeline unlimited, just exit this receive loop and do next tranmsit */ if (pipeline == 0) break; /* If pipeline not full, exit to keep transmitting */ if (stat_xby - stat_rby < pipeline) break; /* * Otherwise, keep looking for receives until the end of the second (or when * outstanding drops below pipeline, checked at beginning of while loop) */ tmp_time = time(0); if (cur_time != tmp_time) { cur_time = tmp_time; break; } } } } /* Print periodic stats */ cur_time = time(0); if ((req_transmits <= stat_xby) || (end_time <= cur_time)) goto done; if ((stat_time <= cur_time) || override_interval_flag) { override_interval_flag = 0; print_initiator_stats(); stat_time = cur_time + stats_interval; } } done: print_final_initiator_stats(); for (int i=0; i<10; i++) { if (shutdown(sockfd, SHUT_RDWR) != 0) { sprintf(pline, "? Failed to shutdown socket %d (%s)", errno, strerror(errno)); print_vline(pline); } else break; } close(sockfd); } /* print_help */ void print_help () { printf("Arguments are:\n"); printf( " -p port number Port number to use (default 1234)\n" " -t d1.d2.d3.d4 IP address of the target system (running the target(server))\n" " -l msglen Message length to transmit (default 60000)\n" " (message data only, doesn't include header+CRC)\n" " -r Random message size from %d bytes to msglen\n" " -n numbytes Number of bytes to transmit (unsigned 32-bits)\n" " (can be followed by k,m,g,t)(default 0)\n" " (0 is 0x7FFFFFFFFFFFFFFF (default))\n" " -o numpkts Pipeline, maximum outstanding bytes (default 0, no limit)\n" " -m msgpersec Maximum messages per second (0 if no limit)\n" " (only where target IP address is specified)\n" " -c Do data compare on receive\n" " -d Discard messages (don't loop back)\n" " -e echosize Echo messages, displaying specified amount\n" " (0 displays message context only)\n" " -s Display message data left-to-right\n" " -f nummsgs Number of messages to echo (default all)\n" " -i seconds Interval between stats display (default 10 seconds)\n" " -g Enable SO_SHARE on the socket\n" " -w seconds When to stop, after w seconds (default 0, never)\n" " -b nummsgs Number of messages in a burst (default 1)\n" " -j sobuflen_send Socket buffer send size (override default)\n" " -k sobuflen_recv Socket buffer receive size (override default)\n" " -v Verbose mode (default if any options other than -p, -t specified)\n" " -a Alternate receive mode (recvfrom() instead of recv())\n" " -u filespec Log to filespec (default SYS$LOGIN:XXTCP_nodename.LOG)\n" " -h Display help\n\n", MSG_MIN); printf( "To run, define xxtcp as a foreign command (xxtcp :== $disk:[dir]xxtcp.exe)\n\n"); printf( "Example, initiator, target using default port number 1234:\n" " On the target system: xxtcp\n" " On the initiator system: xxtcp -t 10.10.41.122\n\n"); printf( "Example, initiator, target using default port number 1234, verbose mode:\n" " On the target system: xxtcp -v\n" " On the initiator system: xxtcp -v -t 10.10.41.122\n" " or with non-standard parameters, for example:\n" " xxtcp -i 1 -t 10.10.41.122\n\n"); printf( "Example, initiator, target using port number 12345:\n" " On the target system: xxtcp -p 12345\n" " On the initiator system: xxtcp -p 12345 -t 10.10.41.122\n\n"); printf( "Example, initiator, target using default port number 1234, but target running\n" "as a detached process so you can start it up and run the initiator over and over:\n" " On the target system: run/detached xxtcp\n" " On the initiator system: xxtcp -t 10.10.41.122\n\n"); printf( "Example, default settings, but running for just 1gbyte or 10 seconds max\n" " On the target system: xxtcp\n" " On the initiator system: xxtcp -t 10.10.41.122 -n 1000000000 -w 10\n\n"); printf("Control-T for immediate stats\n\n"); printf( "To increase socket buffer size:\n" " $ TCPIP sysconfig -r socket sb_max=10000000\n" " $ TCPIP sysconfig -Q socket\n\n"); exit(SS$_NORMAL); } /* main: * * - Get arguments * - Open the socket * - Initiate initiator or target test */ int main (int argc, char *argv[]) { int i; char c; extern char *optarg; extern int optind, optopt, opterr; int getopt (int argc, char *const argv[], const char *optstring); uint64_t *buf64, j; #define OTHER_ARGS "agp:t:l:j:k:" /* Initialize preliminary log file context */ log_flag = FALSE; strcpy(log_prefix, "XXTCP"); log_name[0] = 0; /* Get common arguments */ xx$common_args(argc, argv, OTHER_ARGS); /* Get arguments */ opterr = 1; optind = 1; while ((c = getopt(argc, argv, COMMON_ARGS OTHER_ARGS)) != -1) { switch (c) { case 'a': alt_recv_flag = TRUE; break; case 'g': share_flag = TRUE; break; case 'p': sockport = atoi(optarg); break; case 't': { sscanf(optarg, "%s", &dest_ip); is_initiator = TRUE; is_target = FALSE; break; } case 'l': verbose_flag = TRUE; msg_len = atoi(optarg); if (msg_len > MSG_MAX) msg_len = MSG_MAX; if (msg_len < MSG_MIN) msg_len = MSG_MIN; break; case 'j': verbose_flag = TRUE; sobuflen_send = atoi(optarg); break; case 'k': verbose_flag = TRUE; sobuflen_recv = atoi(optarg); break; } } /* Set maximum packet size for random setting */ msg_max = msg_len; msg_mod = 1; while (msg_mod < (msg_max - MSG_MIN)) msg_mod <<= 1; msg_mod--; /* Set maximum burst number for random setting */ burst_max = burst_num; burst_mod = 1; while (burst_mod < burst_max) burst_mod <<= 1; burst_mod--; /* Check if we should run forever */ if (req_transmits == 0) req_transmits = 0x7FFFFFFFFFFFFFFF; /* Set up the buffer for transmit */ memset((void *)&xmt_buffer, 0, sizeof(xmt_buffer)); memset((void *)&rcv_buffer, 0, sizeof(rcv_buffer)); memset((void *)&cmp_buffer, 0, sizeof(cmp_buffer)); buf64 = (uint64_t *)xmt_buffer; for (i=0; i